画像オフロードの403を再試行し続けない — 失敗を状態として残す

  • #画像処理
  • #Cloudflare Worker
  • #リトライ
  • #テレメトリ
画像オフロードの403を再試行し続けない — 失敗を状態として残す

今回やったこと

画像を外部から取得してアーカイブする処理では、HTTP 403 を一時的な拒否と見るか、恒久的に取得できない状態と見るかで挙動が変わる。以前の実装は Worker の 403 を一時障害と解釈し、再試行を続けていた。削除済み画像でも毎時取りに行くため、処理は失敗しているのにジョブ全体は動き続ける状態になった。

対処は、403を一律に成功扱いすることでも、すべてを即時打ち切ることでもない。Worker 経由の試行回数をマニフェストに残し、回復可能な範囲だけ再試行し、上限を超えたらフォールバックまたは終了へ進めるようにした。

403と404を同じ失敗にしない

scripts/lib/image_archive.py の取得処理では、404 と 410 を「本当に消えた」expired として扱う。一方、403、HTML 応答、タイムアウトなどは error:* として扱い、すぐに恒久削除とは断定しない。理由は、403 が経路や送信元によって変わる場合があるからだ。

Worker 側の取り込みでも、okexpired は再試行しない。WORKER_NO_RETRY_STATUSES に含まれる拒否は待ち時間を使わず、M1 側のフォールバックへ進む。それ以外の一時的な失敗は指数バックオフで再試行する。

for attempt in range(retry + 1):
    status = _ingest_once(url, r2_key)
    if status in ("ok", "expired"):
        return status
    if status in WORKER_NO_RETRY_STATUSES:
        return status
    if attempt < retry:
        time.sleep(backoff * (2 ** attempt))
return status

ここで重要なのは、再試行の有無と、最終的な画像取得の成否を分けることだ。Worker が取得できなかったからといって、直ちに元画像が存在しないとは限らない。逆に、削除済み画像を無期限に再試行しても復旧はしない。

マニフェストに上限を持たせる

MAX_IMAGE_ATTEMPTS = 3 を共通上限とし、should_retry_image() がマニフェストの attempts を見て再取得可否を決める。未取得の新しい URL は試行でき、成功済みは再取得しない。失敗済みは上限未満の間だけ再試行する。

def should_retry_image(entry, max_attempts=MAX_IMAGE_ATTEMPTS):
    if entry is None:
        return True
    if entry.get("status") == "ok":
        return False
    return int(entry.get("attempts") or 0) < max_attempts

この値をマニフェストへ残すことで、毎時のジョブが「前回失敗したことを知らない」状態を避けられる。処理が再起動しても、同じ URL を無限に初回扱いしない。

失敗をテレメトリで見えるようにする

失敗を止めるだけでは、オフロードが効いていないことに気づけない。scripts/lib/offload_stats.py では、Worker 経由の処理、M1 のフォールバック、フォールバック失敗、Worker が拒否した回数を日次で分けて記録する。M1 が実バイトをダウンロードした量も m1_bytes として保存する。

正常時は Worker が画像を取得するため、M1 の転送量はほぼゼロになる想定である。今日の m1_bytes が 500KB を超えたときは degraded として表示する。この閾値は障害を断定するためではなく、オフロード劣化を調査する入口として使う。

watch_threads.py はマニフェストを読み、画像の状態を更新し、必要に応じて再構築したインデックスへ反映する。スレッドの監視継続と画像取得の失敗を同じ例外にしないことで、1枚の画像が取れないだけで無人ジョブ全体が停止する事態を避ける。

やってみてわかったこと

再試行は「多いほど親切」ではない。恒久的な失敗に対する無制限の再試行は、帯域と実行時間を消費し、失敗を正常運転のログへ埋め込む。HTTP ステータス、取得経路、試行回数、フォールバックの実バイト量を別々に保存すると、回復可能なエラーと打ち切るべきエラーを後から説明できる。

画像処理の本体を壊さないことも重要である。テレメトリの書き込みが失敗しても画像取得を止めないよう、統計記録は例外を本筋へ返さない設計にしている。観測は必要だが、観測のために本体を止めてはいけない。

更新履歴

  • 2026-08-25: 初稿。画像アーカイブ、オフロード統計、監視スクリプトの実装をもとに整理。