自動処理の失敗時設計 — 黙って再実行させるか人を呼ぶか

自動処理が失敗したとき、システムが黙って再実行を繰り返すか、すぐ担当者に通知するかは、設計段階で明確に判断しなければならない問題です。その線引きを曖昧にしたまま運用に入ると、エラーが蓄積して翌朝にデータ欠損として発覚したり、逆にアラートが鳴り過ぎて担当者が通知を無視するようになります。失敗の種類を「一時的か構造的か」「冪等か非冪等か」という軸で分類し、リトライ・回収・即時停止の三択を設計段階で決めておくことが、安定した自動化運用の前提になります。

失敗しても誰も気づかず翌朝データが欠ける構造

サイレントフェイルの発生メカニズム

バッチ処理やスクリプト自動実行において最も危険なのは、エラーが発生しているにもかかわらずプロセスが「成功扱い」で終了するパターンです。シェルスクリプトでは、パイプラインの途中でコマンドが失敗しても、デフォルト設定では最後のコマンドの終了コードだけが評価されます。100台規模のサーバーで夜間バッチを動かしている環境では、一部のノードだけスクリプトが途中で抜けても、ジョブ管理ツール側は「終了コード0」として受け取るケースがあります。データ収集や同期処理では、このずれが翌朝まで気づかれずに積み重なり、欠損として顕在化します。

エラーが記録されない設計の問題

エラーが出力されていても、標準エラー出力をどこにも転送していなければ記録は残りません。cronで動かすスクリプトはデフォルトではメール送信に頼りますが、メール設定がされていない環境では出力が単純に捨てられます。本番環境でcronを使っている現場では、標準エラーのリダイレクト設定を見直すだけで、見落とされていた失敗が毎晩5〜10件は存在したという話をよく聞きます。記録されないエラーは「発生しなかったこと」と同義になり、問題の発見が大幅に遅れる原因になります。

失敗が見えない設計が生む技術的負債

エラーを可視化しないまま放置すると、処理が成功しているという誤った前提で次の設計が積み上がります。データパイプラインで言えば、上流の欠損を補正する下流処理が追加され、さらにその補正が誤検知を起こすという多重対応になりがちです。3段階以上の補正ロジックが積み上がった環境では、元の失敗箇所の特定に数日を要することもあります。設計の入口で「失敗を可視化する」仕組みを据えることが、後工程の複雑化を防ぐ基本になります。

自動で再試行してよい失敗と即座に止める失敗の判断

一時的な障害と構造的な障害の区別

リトライが有効なのは、原因が「一時的な外部要因」である場合に限られます。ネットワークの瞬断、APIの一時的なレート制限、外部ストレージへの書き込み競合など、時間を置けば自然解消する失敗はリトライが合理的です。一方、接続先のURIが変わった、認証情報が失効した、スキーマが変更されたといった構造的な原因は、いくら再試行しても成功しません。構造的な失敗に対してリトライを設定しても、ただ時間と資源を消費するだけで問題の発見が遅れます。失敗の分類を設計段階で整理しておくことが、無駄なリトライを防ぐ前提です。

冪等性を確認しない再試行の危険

リトライを設計するうえで見落とされやすいのが冪等性の確認です。同一処理を複数回実行しても結果が変わらない冪等な処理であれば、リトライは安全に実行できます。しかしDB書き込みや外部APIへのPOST送信を伴う処理は、リトライによって二重書き込みや重複登録が起きるリスクがあります。構築を任されたある担当者が、冪等性の確認をしないままリトライを実装した結果、外部連携先に同一データが2件送信されてしまったという事例も報告されています。処理の冪等性を確認する前にリトライ数を増やすのはアンチパターンです。

即時停止すべき失敗のシグナル

ディスクの空き容量がゼロになった、マウントが外れた、データソースの更新が48時間以上止まっているなど、処理環境そのものが壊れているケースは即座に停止してアラートを出すべきです。これらの状態でリトライを続けると、エラーログが大量に積み重なって後の調査を困難にし、場合によっては壊れたデータが正常データとして扱われるリスクも生まれます。失敗の種類を事前にリスト化し、「この種別は即停止」という判断基準を設計段階で明文化しておくことが重要です。

比較: 即時リトライ/次回実行で回収/停止して通知

戦略 適した失敗の種類 前提条件 リスク・注意点
即時リトライ ネットワーク瞬断・APIレート制限・一時的なタイムアウト 処理が冪等である、リトライ上限と待機間隔を設定済み 構造的障害に適用すると資源を浪費し、発見が遅れる
次回実行で回収 スケジュール起動のズレ・他ジョブとの一時的な競合・リソース不足 次回実行タイミングが業務影響の許容範囲内である 回収されたかどうかの確認設計がないと欠損が見えなくなる
停止して通知 認証失効・スキーマ変更・ディスク枯渇・データ破損疑い アラート送信先と対応フローが整備済みである 通知設計が粗いとアラート疲れで無視されるようになる

途中まで進んだ処理をどこから再開させるか

チェックポイントの設計思想

大量データを処理するバッチ処理で途中失敗が起きた場合、先頭から再実行すると処理済み分が二重に動き、時間と資源を無駄にします。チェックポイントとは、処理が正常完了した最後の単位を記録しておき、再実行時にそこから再開できるようにする仕組みです。1回の実行で1,000万件を処理するETL処理であれば、10万件単位でチェックポイントを打つことで、失敗時の再処理量を全体の1%程度に抑えられます。単位の粒度はトランザクションの重さと再実行コストのバランスで決めます。

状態管理の実装方式の選択

チェックポイントの実装方法として、状態ファイルに最終処理IDを書き込む方法とDBテーブルに記録する方法があります。ファイル方式はシンプルですが、並列実行時に競合が起きやすい欠点があります。DB方式は原子的な更新が保証できますが、DBへの依存が増えます。10台以上の並列ワーカーが動く環境では、ファイルロックの競合でチェックポイントそのものが書き込まれないケースが出るため、DBかメッセージキューを使った進捗管理が実運用には適しています。方式の選択は並列度と処理の重さを基準に判断します。

再開ポイントと冪等処理の組み合わせ

チェックポイントから再開する際に重要なのは、再開直前の単位を「すでに処理済み」とみなすか「もう一度実行する」かの一貫した定義です。チェックポイントを「処理完了後に書き込む」設計にすると、書き込み前にプロセスが落ちた場合に同じ単位が再実行されます。そのため、再開単位の処理そのものが冪等であることを前提とした設計が安全です。チェックポイントと冪等性をセットで設計することが、中断・再開に強いバッチの基本構成になります。

終了コードと実行記録をどう残して追えるようにするか

終了コードの体系と振り分け設計

自動処理が終了する際、終了コード0(正常)以外を返す設計は最低限必要です。ただし終了コードを0と非0の二値だけで管理すると、「接続タイムアウト」と「データ検証エラー」を区別できなくなります。POSIX標準の範囲(0〜255)を活用して種別ごとに終了コードを定義し、ジョブ管理側でそれを受け取れるようにすることで、アラートの優先度付けが自動化できます。コード1は即時通知、コード2はリトライ対象、コード3は次回実行で回収、という体系は、運用フロー全体の自動振り分けを可能にします。

ログの構造化と検索性の確保

テキストログは量が増えると検索が困難になります。JSON形式で出力し、ジョブID・開始時刻・処理件数・終了ステータスを固定フィールドとして持たせることで、ログ収集基盤への取り込みと集計が容易になります。1日に5,000件以上のバッチタスクが動く環境では、構造化ログなしに失敗を横断検索するのは現実的ではありません。ログの形式はスクリプトの初期設計段階で固定しておかないと、後から追加するときに影響範囲が広がります。手を動かして実装する段階での詳細は各現場の実装フェーズで詰める必要がありますが、フィールド定義だけは設計段階で決めておくことが重要です。

実行記録の保持期間と監査要件の整合

実行記録を「どこに・何日間」保持するかも設計段階で決める必要があります。規制要件がある環境では、90日以上の保持が求められるケースもあります。一方でストレージコストを抑えたい環境では、生ログを7日で圧縮・30日でアーカイブ・90日で削除する段階保持を設計します。保持ポリシーを決めずに運用を始めると、ディスクが溢れて処理が止まるという本末転倒な事態になるため、初期構築時に方針を明文化しておきます。

リトライを厚くして故障を隠してしまう境界

リトライが成功体験を偽造するケース

リトライを設定すると「最終的に成功した」記録しか残らないケースがあります。3回リトライして3回目に成功した場合、ジョブ管理ツールには「成功」とだけ表示され、2回の失敗があったことが記録されない設定になっていることが多いです。この状況が続くと、実は毎晩2回失敗しているネットワーク経路の問題や、外部APIの安定性の低下が見落とされます。リトライは「回収手段」であり、「問題隠蔽のバッファ」ではありません。失敗回数のカウントは成功で終わった実行にも記録する設計が必要です。

アラート閾値とリトライ数を切り離して設計する

通知が多過ぎると担当者はアラートを無視するようになります。この問題を解決しようとしてリトライを増やす判断をすると、アラート数は減っても実際の障害が隠れるという別の問題が生じます。適切なのは、アラートの閾値と条件を見直すことであり、リトライ数を増やすことではありません。「3回失敗したら通知」という閾値設計は合理的ですが、「10回失敗したら通知」まで緩めると、障害が深刻になってから気づくことになります。閾値設計はリトライ設計とは独立した判断として行います。

外部依存サービスの劣化をリトライで飲み込む問題

外部APIやクラウドサービスが劣化状態に入ると、タイムアウトが増えてリトライが頻発します。この状態でリトライを多く設定していると、外部サービスへのリクエストが増加し、さらに劣化を促進するフィードバックループが生まれます。サーキットブレーカーパターンは、一定回数の失敗を検出した後は外部呼び出しを一時的に停止し、定期的に回復確認だけを行う設計です。リトライの上限に加えてこの考え方を組み合わせることで、外部依存の劣化を自らが拡大させる問題を防げます。

失敗時設計チェックフレーム

実際の構築フェーズで確認すべき判断項目を以下に整理します。設計レビューの際に各項目を照合することで、失敗時の動作が意図通りに設計されているかを確認できます。

  1. 失敗の分類定義:想定される失敗をリストアップし、「一時的」「構造的」「環境破損」の3種に分類しているか。
  2. 冪等性の確認:リトライを設定する処理が冪等かどうか確認済みか。非冪等な処理にリトライを設定していないか。
  3. リトライ上限の設定:リトライ回数の上限と待機間隔(エクスポネンシャルバックオフ等)が定義されているか。
  4. 失敗カウントの記録:最終的に成功した場合も、途中の失敗回数と内容が記録されるようになっているか。
  5. 即時停止条件の明文化:環境破損系の失敗条件(ディスク枯渇・認証失効・データ破損疑い)が即停止・通知フローに直結しているか。
  6. チェックポイントの設計:長時間処理で途中から再開できるチェックポイント機構があるか。再開単位は冪等性を担保しているか。
  7. 終了コードの体系化:失敗種別ごとに異なる終了コードを返し、ジョブ管理側で受け取れる設計になっているか。
  8. ログの構造化:実行ログにジョブID・処理件数・終了ステータスが含まれ、検索・集計できる形式で出力されているか。
  9. アラート設計の分離:アラート閾値とリトライ設定を独立した判断で設計しているか。リトライ追加でアラート数を減らす設計になっていないか。
  10. サーキットブレーカーの検討:外部依存がある処理でサーキットブレーカーパターンの適用を検討したか。
  11. ログ保持期間の決定:実行記録の保持期間と保持場所が明文化されているか。監査要件がある場合は規制要件との整合が取れているか。

▶ 手を動かして本番まで到達する

脱属人化の第一歩は「読める・書けるスクリプト」から始まります

ここまでの設計判断は、読めば腹に落ちます。ですが「自分の手で本番まで通しきる自信」は、それとは別の話になります。

自動化の設計を、手を動かして身につける2日間です。手順書に閉じ込めた構築を、他人が読めて再現できる形へ変えていきます。

自動処理の失敗時設計をスクリプトで型化するシェルスクリプト実践コースへ →

シェルスクリプトマスターセミナー|自動化の設計を手で身につける2日間