無人ジョブを1件の画像エラーで止めない — 非同期attachの失敗隔離
今回やったこと / この記事について
記事本文の作成と画像処理を別のジョブで進める構成では、「記事はできているが画像がまだ無い」「画像はできたが投稿がまだ無い」という中間状態が必ず発生する。本稿では、画像を投稿へattachする後段で、投稿IDが未採番のケースや1件のAPIエラーを処理できず、バッチ全体が止まっていた問題を整理する。対象は、同じバッチで複数記事を扱う自動化を運用している人である。
実装ステータスは実装済み。参照した記録では、画像生成と投稿作成は別プロセス・別マシンで非同期に動き、画像キュー作成時点では投稿IDが null になり得る。この前提を、後段のコードが取り込めていなかった。
失敗した前提は「投稿が常に先にある」だった
旧来の処理は、画像側の状態ファイルに投稿IDが入っていることを暗黙の前提にしていた。ところが、記事作成と画像生成を分離すると、次の順序がどちらも起こる。
| 順序 | 先に完了する処理 | 後から行う処理 | 起こる状態 |
|---|---|---|---|
| A | 記事の投稿作成 | 画像生成とattach | 投稿はあるが画像が無い |
| B | 画像キューの作成 | 投稿作成とattach | 投稿IDが未採番 |
Bの状態で画像attach処理が投稿IDを直接参照すると、KeyError: 'wp_post_id' のような例外が発生し、後続記事まで処理されない。画像が未生成であること自体は異常ではない。それを「投稿に必要な情報が欠けている」と解釈して、全体停止へつなげたことが問題だった。
解決策はslugによる再検索と記事単位の境界
投稿IDを機械間で書き戻す方式は、同じ状態ファイルを複数マシンが編集するため競合しやすい。そこでattach側は、投稿IDが空でもslugを使って投稿APIを検索する。検索対象の状態は、公開済みだけに限定せず、下書きや予約を含める。該当する投稿がまだ無ければ、その記事だけを次回へ残す。
処理の境界は、バッチ全体ではなく記事単位に置く。
for article in targets:
try:
post_id = article.get("wp_post_id")
if post_id is None:
post_id = find_post_id_by_slug(article["slug"])
if post_id is None:
continue # 次回に残す
attach_images(post_id, article)
except Exception as exc:
log_article_failure(article["slug"], exc)
continue
この形では、投稿がまだ存在しない記事は待機し、別の記事のattachは続く。画像がまだ無い場合も、投稿作成を止める理由にはしない。画像処理は後から再実行できる工程であり、本文の在庫を失わないことを優先する。
「待機」と「失敗」を分ける
運用上は、少なくとも次の3状態をログに残すと判断しやすい。
- 投稿未作成: slug検索で見つからず、次回へ待機
- 画像未生成: 投稿はあるが、画像ファイルを待機
- attach失敗: APIエラーや入力形式の不整合を記事単位で記録
この分類は、成功件数だけを通知するより有用である。バッチが終了コード0でも、何も処理していない可能性があるためだ。一方、1件の失敗で終了コードを全体エラーにすると、他の記事が処理済みかどうか分からなくなる。記事単位の結果と、次回に残した件数を合わせて出すと、再実行の判断ができる。
やってみてわかったこと
非同期化の難しさは、処理を分けることではなく、順序が逆転しても壊れない状態遷移を定義することにある。投稿IDを必須入力にする設計は、単一プロセスでは自然でも、画像生成と投稿作成を分けた瞬間に脆い契約になる。
今回の設計では、slugを再検索の安定した識別子にし、記事単位で例外を隔離し、画像欠落を投稿停止の条件から外した。これにより、画像が遅れても本文の在庫は積み上がり、1件の入力不備が無人ジョブ全体を巻き込まない。非同期パイプラインでは「順番を守らせる」より、「どの順番でも安全に待てる」ことを先に決める方が保守しやすい。
更新履歴
- 2026-08-19: 初稿。画像attachの失敗隔離とslug再検索の設計を整理。
訂正履歴
- なし。