無人ジョブを1件の画像エラーで止めない — 非同期attachの失敗隔離

  • #自動化
  • #画像処理
  • #障害隔離
  • #非同期処理
無人ジョブを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再検索の設計を整理。

訂正履歴

  • なし。