2.20

View in English

2.20 エラー処理とレジリエンスのパターン

概要と動機

書いたプログラムはすべて、いずれ失敗します。ディスクが満杯になり、ネットワークが切れ、サービスがタイムアウトし、呼び出し元がゴミを渡し、依存先がドキュメントに書かれていなかったものを返します。問いは、失敗が起きるかどうかでは決してありません。コードがその失敗に計画で応じるか、驚きで応じるかです。エラー処理とは、世界が協力しないとき、コードが何をするかを、行ごと、関数ごとに決める技芸です。それは構築の中で最も地味な部分であり、どんな機能よりも、人々がシステムを信頼するかどうかを決める部分です。

本章は、コードとコンポーネントのレベルのレジリエンス、つまり関数、モジュール、APIの内部での選択についてです。システムレベルのレジリエンス(負荷分散、レプリケーション、サービス間のフェイルオーバー)を扱う3.5章を補完します。3.5章は、リージョンが暗転してもプラットフォーム全体を立たせ続け、本章は、単一のリクエストがデータを壊したり、跡を残さずに消えたりしないようにします。両者は互いを強化します。アーキテクチャのサーキットブレーカーは、その背後のコードが例外を握りつぶしているなら、ほとんど意味がなく、周囲のシステムに冗長性がなければ、防御的な関数はあなたを救えません。本章はまた、エラーの処理が多くの規律の一つだった2.9章(ソフトウェア構築)の上に築かれており、ここではそれが主題の全体になります。

大きなチームにとって、一貫性が賞品です。何百人ものエンジニアが何百通りもの方法でエラーを処理するとき、すべてのサービスはパズルになり、すべてのインシデントは発掘になります。企業の設定では、その不一致が、あらゆる監査とあらゆる統合のコストを上げます。政府やその他の賭け金の高いシステムでは、賭け金はもっと鋭くなります。正しさ、安全な失敗、明確な監査証跡は、後から加える機能ではなく、最初のコミットからシステムが持たなければならない性質です。黙って誤計算する給付システムや、失敗を記録せずに失う記録システムは、単にバグがあるのではありません。その背後にある制度を蝕む形で、信頼できないのです。

主要原則

  • エラー、フォールト、失敗を区別し、それぞれを適切な層で扱います。
  • フェイルファストかフェイルセーフかを、偶然ではなく、文脈ごとに意図して選びます。
  • すべての関数とAPIのエラー処理の契約を、明示的で誠実なものにします。
  • 境界で検証し、その内側では信頼し、被害妄想なしに防御します。
  • エラーを黙って握りつぶしてはいけません。表面化させるか、包むか、意図して処理します。
  • 冪等性、タイムアウト、バックオフ、ジッターで、リトライを安全にします。
  • エラーの経路に、正常系と同じ設計上の注意を払います。

推奨事項

エラー、フォールト、失敗を区別する

雑な語彙は雑な処理を生むので、明確な用語から始めます。フォールトはシステムの欠陥です。バグ、悪い設定、ダウンしている依存先。エラーは、フォールトが生む誤った内部状態です。値があるべき所のnull、もう照合しない残高。失敗は外部の観察者が見るものです。リクエストが間違った答え、あるいは答えなしを返す。一つのフォールトが多くのエラーを引き起こしえて、多くのエラーが、どれかが目に見える失敗になる前に捉えられうるのです。エラー処理の要点全体は、その連鎖を断ち切ること、つまりエラーが、ユーザーや監査人が経験する失敗になる前に捉えることです。

この語彙は、どこで行動すべきかも教えます。フォールトは、レビュー、テスト、設定で対処されます。エラーは、本章のパターンによって実行時に対処されます。失敗は、オブザーバビリティ(9.2章)と3.5章のシステムレベルのレジリエンスによって対処されます。チームがこれらの言葉を共有すると、インシデントのレビューが鋭くなります。「バグ」が何だったかを議論する代わりに、連鎖がどこで断たれるべきで断たれなかったかを、正確に言えるからです。

文脈ごとにフェイルファストかフェイルセーフかを選ぶ

フェイルファストとは、何かがおかしくなった瞬間に止まり、悪い状態で進むことを拒んで、問題が大きな音を立てて、原因の近くで表面化するようにすることです。フェイルセーフとは、既知の無害な状態に縮退し、安全に提供できるものを提供し続けることです。どちらも普遍的に正しいわけではなく、スキルは文脈ごとに選ぶことです。開発中と内部の境界では、フェイルファストが味方です。不変条件の違反で停止するプログラムは、長い謎ではなく短いスタックトレースを渡してくれます。本番で、ユーザー向けのシステムの縁では、フェイルセーフがしばしば勝ちます。何も返さない推薦パネルは、読み込めないチェックアウトページよりましです。

これを境界ごとに意図して決め、その決定を書き留めてください。飛行制御や医療機器のコンポーネントは、壊れたデータで続けると誰かを傷つけうるので、定義された状態にフェイルセーフします。元帳の仕訳は、誤った記入を計上するのは、何も計上しないよりも悪いので、フェイルファストします。誤った組み合わせは、どちらの方向でも危険です。フェイルファストが必要な所でのフェイルセーフは破損を隠し、フェイルセーフが必要な所でのフェイルファストは、見た目の問題を障害に変えます。

エラー通知の仕組みを選び、一貫して使う

言語は、何かがおかしくなったことを知らせる、大きく二つの方法を提供します。例外処理は、何らかのハンドラが捕まえるまで、オブジェクトをコールスタックの上へ投げ、エラーの経路を主要なロジックから分けます。代替は明示的なエラー値です。関数が結果とエラーの両方を返し、呼び出し元が両方を検査しなければなりません。多くの現代の言語は、後者をResult型で形式化しており、しばしばResultやEitherと呼ばれ、値を使う前に呼び出し元に成功か失敗を取り出させます。それぞれのアプローチにコストがあります。例外は正常系をきれいに保ちますが、制御フローを隠し、開発者を、情報を消すcatch-allのブロックに誘いえます。明示的な結果は、あらゆる失敗を型シグネチャに見えるようにしますが、儀式を加え、言語がチェックを強制しなければ無視されえます。

正しい答えは、どの仕組みかよりも、一貫性と誠実さにあります。言語とエコシステムが好むイディオムを選び、サービス全体で一様に適用して、失敗がどう伝わるかを読み手が常に知れるようにします。例外は、本当に例外的な条件のために取っておき、「ユーザーが見つからない」のような通常の制御フローには使いません。それは通常の結果としてモデル化したほうがよいものです。何を選ぶにせよ、失敗を見えなくしてはいけません。チェックされないエラー値は、空のcatchブロックと同じくらい危険です。大きなコードベースでは、書面の規約と、無視されたエラーを指摘するリンターが、どんな個人の好みにも勝ります。

エラー処理の契約を明示的にする

すべての関数とすべてのAPIは、誰かが書き留めたかどうかにかかわらず、エラー処理の契約を持っています。それは次に答えます。ここで何がおかしくなりうるか、どうやってそれを知るか、そして起きたときに状態について何が保証されるか。その契約を明示的にします。関数がどのエラーを返したり投げたりしうるかを文書化し、回復可能なエラー(呼び出し元が妥当にリトライしたりフォールバックしたりできる)と回復不能なもの(呼び出し元がこれを直せず、伝播するか中止すべき)を区別し、失敗時に関数が状態を変えないままにするかを述べます。この最後の性質は、ときに強い例外保証と呼ばれ、失敗した呼び出しが起きなかったかのようであることを意味し、それこそが呼び出し元が安全にリトライできるようにするものです。

公開された、あるいはチーム間のAPIにとって、この契約はインターフェースの一部で、パラメータの型と同じくらい本物です。小さく安定したエラーの分類を設計します。検証エラー、見つからない、衝突、未承認、依存先利用不可、内部エラーのようなカテゴリの限られた集合です。呼び出し元は、文字列を解析せずにカテゴリで分岐できます。明確な分類は、エラー処理を多くのサービスにわたって合成可能にし、失敗を監査可能にします。すべての失敗が、既知の名前付きの種類に対応するからです。

境界で検証し、被害妄想なしに防御する

信頼の境界を越えるデータ(ネットワークのリクエスト、ファイル、ユーザー入力、別のサービスからのメッセージ)は、検証されるまで敵対的なものとして扱い、境界で、一度、徹底的に検証します。これは判断をもって適用された防御的プログラミングです。すでに入力を検証したモジュールの内側では、すべての行の冗長なチェックが、ロジックを隠し、見たいはずの失敗そのものを抑え込みます。規律は、縁で強く守り、その内側では信頼することです。データが入る所で構造、範囲、不変条件を検証し、それを不正な状態を表現不可能にする型に変換し、内部のコードは、きれいなデータで作業していると想定させます。

被害妄想には本物のコストがあります。nullチェックと防御的な分岐に覆われたコードは読みにくく、さらに悪いことに、警報を鳴らすべき所でデフォルトを返すことで、明確な失敗を黙った肩すくめに変えることがよくあります。バグを覆い隠す防御は安全ではなく、先送りです。

リトライを安全に、限りのある、礼儀正しいものにする

多くのフォールトは一時的です。一瞬のネットワークの瞬断、再起動するサービス、短いロックの競合。あらゆる分散システム(3.3章)では、これらの部分的な失敗は例外ではなく通常のケースです。リトライは自然な対応ですが、素朴なリトライのループは装填された銃です。まず、リトライする操作を冪等にします。二度実行しても一度実行したのと同じ効果を持つということです。冪等性がなければ、タイムアウト後のリトライは、カードに二重に課金したり二つのレコードを作ったりしえます。最初の試みが失敗したのか、単にその確認応答が失われたのか、判別できないからです。受信側が繰り返しを認識して重複排除できるよう、書き込みには冪等性キーを使います。

次に、ハングした依存先があなたをハングさせないよう、すべてのリモート呼び出しにタイムアウトを設けます。三つ目に、リトライの間隔を指数バックオフで空け、各試みの後に待ち時間を倍にし、ジッター(小さなランダムな遅延)を加えて、同時に回復する千のクライアントが同期して、回復中のサービスを再び倒す殺到にならないようにします。四つ目に、リトライの回数と合計時間に上限を設け、それから穏やかに諦めます。上限、バックオフ、ジッター、冪等性のないリトライは、小さな不具合が自ら招いた障害になる最も一般的な方法の一つです。

コードにサーキットブレーカー、バルクヘッド、穏やかな縮退を加える

依存先が本当にダウンしているとき、リトライは労力を無駄にして穴を深くするだけです。サーキットブレーカーは、依存先への呼び出しの失敗率を監視し、失敗が閾値を超えたら「開き」、運命づけられた呼び出しを待つ代わりに、クールダウンの期間、即座に失敗します。クールダウンの後に試行の呼び出しを通し、依存先が回復していれば再び閉じます。これは、呼び出し元(積み重なったタイムアウトの代わりに、速く予測可能な失敗)と、苦しんでいる依存先(回復するための息継ぎの余地)の両方を守ります。船の水密区画にちなんで名付けられたバルクヘッドパターンは、リソースを隔離し、一つの飽和した依存先がすべてのスレッドや接続を消費してプロセス全体を沈めないようにします。各依存先に、独自の上限付きのプールを与えます。

これらのパターンは、コードレベルでの穏やかな縮退と組み合わされます。必須でない依存先が利用できないとき、エラーではなく、縮小されても有用な結果を返す。古さの注記付きでキャッシュしたデータを表示する、パーソナライズのパネルを隠す、書き込みを後のためにキューに入れる。これは3.5章のシステムレベルのレジリエンスの局所的な補完です。アーキテクチャはマシンをまたぐ冗長性を提供し、コードは部品が欠けたときの正気な振る舞いを提供します。

エラーを文脈で包み、決して握りつぶさない

起きた場所から十層上で「connection refused」と読めるエラーは、ほとんど役に立ちません。エラーが伝播するとき、何をしようとしていたか、どのエンティティやリクエストか、どの依存先かという文脈でそれを包み、根本が失われないよう元の原因を保持します。良い言語とライブラリは、このエラーの連鎖を直接サポートします。目標は、一行のログが、何が失敗したか、どの操作中か、どの入力についてかを、オンコールのエンジニアに伝えることです。これは9.2章のオブザーバビリティと2.15章のデバッグの原材料です。

大罪はエラーを握りつぶすことです。空のcatchブロック、無視された戻り値、デバッグレベルでログを取って何事もなかったかのように続けるcatch。握りつぶされたエラーは消えず、後で壊れたデータや説明のつかない欠陥として、今や原因から切り離されて再び現れます。すべてのエラーは三つの運命のどれかに出会わなければなりません。処理する(回復または縮退する)、包んで伝播する、あるいはスタックの最上位で、完全な文脈とともにログに残して失敗する。エラーを捕まえてこのどれもしないなら、将来のインシデントを将来の自分から隠すことを選んだのです。

トレードオフ: 長所と短所

アプローチ長所短所
例外正常系がきれい。チェックされなければ無視しにくい隠れた制御フロー。catch-allによる消去に誘う
明示的なエラー値 / Result型失敗がシグネチャに見える。処理を強制する儀式が多い。強制がなければ無視されうる
フェイルファストバグを大きな音で、原因の近くで表面化する縁で使うとユーザー体験が悪い
フェイルセーフ提供し続ける。ユーザーとデータを守るフェイルファストが必要な所で使うと破損を隠しうる
バックオフ付きのリトライ一時的なフォールトを自動的に乗り切る冪等性がなければ負荷を増幅し二重書き込みを起こす
サーキットブレーカー速い失敗。依存先が回復できる状態と調整が加わる。持続的な問題を覆い隠しうる
境界での防御的な検証悪いデータを早期に一度、大きな音で捉えるやりすぎるとロジックを散らかし本物の失敗を隠す

中心的な緊張は、可視性と雑音の間にあります。エラーをあまりに静かに処理すれば、高くつくまで問題を隠し、あまりに大きくあらゆる所で処理すれば、儀式の中にシグナルを溺れさせ、重要な失敗を覆い隠します。場所と意図で解決します。悪いデータと依存先の失敗が入る境界では、大きく厳格に。入力がすでにきれいな内部では、静かに信頼して。境界ごとにフェイルファストとフェイルセーフを決めて書き留めます。目指すのは、すべての失敗にちょうど一つの明確な所有者と一つの明確な運命があり、何も黙って隙間に落ちないコードです。

チームで議論すべき問い

  1. サービス間で共有されたエラーの分類とエラー処理の規約が一つありますか。それとも、すべてのチームが即興していますか。 大きなチームでは、これが、合成する失敗と混乱させる失敗の違いです。あるサービスが検証の問題にHTTP 500を返し、別のサービスが型付きの例外を投げ、三つ目がnullを返すとき、あらゆる統合は交渉になり、あらゆるインシデントは翻訳の演習になります。「レコードが見つからない」のような同じ論理的な失敗が、三つのサービスでどう現れるかの例を持ち込み、どれだけ異なる形で通知されるかを見てください。答えは書面の標準になるべきです。限られたエラーカテゴリの集合、それらを通知する一貫した方法、それを徹底するリンターやレビューのチェックリスト。ここでの一貫性は、将来のあらゆる統合、監査、オンコールの当番に見返りをもたらします。

  2. 各重要な境界について、フェイルファストかフェイルセーフかを意図して選びましたか。そしてコードはその選択に合っていますか。 ほとんどのチームは、この決定を明示的に行ったことがなく、つまり、最初にコードを書いた人によって、一貫せずに代わりに決められてきました。相反する考慮は本物です。フェイルセーフはユーザーに提供し続けますが、破損を広げさせえ、フェイルファストはデータを守りますが、小さな依存先の障害を目に見える失敗に変ええます。インシデントの履歴を持ち込み、最悪のいくつかについて、コードが、事前に尋ねられたなら選んだであろう形で失敗したかを問ってください。求める証拠は、各境界に意図したラベルを付けた境界のマップです。特にお金、安全、市民の記録が関わる所では。ラベルとコードが食い違う所が、次の修正です。

  3. 最後に意図してエラーの経路を動かしたのはいつで、設計どおりに振る舞いましたか。 エラーの経路は通常、所有するコードの中で最もテストされていない部分ですが、信頼が勝ち取られたり失われたりする所であり、「安全に失敗する」は、それが起きるのを一度も見たことがなければ、裏付けられない主張です。冪等性のないリトライのループ、閾値が間違ったサーキットブレーカー、まれにしか通らない分岐で握りつぶされた例外。これらは、本物のインシデントがあなたに代わって見つけるまで隠れています。現実的な環境に、失敗を意図して注入した結果(殺された依存先、引き起こされたタイムアウト、不正なペイロード)を持ち込んでください。その後の行動は、失敗の注入を日常にして、回復、縮退、安全な失敗の振る舞いが、願うのではなく継続的に検証されるようにすることです。一度も引き起こしたことのないエラーの経路は、テストしていない約束です。

  4. 私たちの書き込み操作のうち、どれが冪等で、確認応答が失われた後のリトライが、決済やレコードのような現実世界の効果を重複させるのはどこですか。 リトライは最も一般的なレジリエンスの反射であり、不注意に行えば、一時的な瞬断が重複したお金やデータに変わる最も一般的な方法です。大きなチームでは、リトライのロジックは、共有のクライアント、ミドルウェア、個々のサービスのすべてに同時に存在することが多く、一つの書き込みが複数の層でリトライされえて、誰も全体の振る舞いを所有していません。相反する引力は、冪等性キー、重複排除、保存されたリクエストの結果が、ストレージとコードを加えることで、デリバリーの圧力のもとのチームは、誤って安全だと想定する書き込みでそれを飛ばすことです。外部から見える書き込みの一覧を持ち込み、それぞれについて、冪等性キーを持つか、受信側が繰り返しをどう認識し重複排除するかを印付けしてください。企業や政府の設定では、お金を動かしたり市民の記録を変更したりするものを最初に挙げてください。二重の支払いや重複した給付は、単なる欠陥ではなく、監査の指摘であり、ときに法的リスクだからです。

  5. タイムアウト、サーキットブレーカー、バルクヘッドは、共有のテスト済みのライブラリから来ていますか。それとも、各チームが手作りしていますか。 これらのパターンは説明するのは簡単で、微妙に間違えるのも簡単です。欠けたタイムアウト、決して作動しないブレーカーの閾値、一つの遅い依存先がプロセス全体を飢えさせるようなサイズのコネクションプール。すべてのチームが再実装すると、少しずつ壊れた多くのコピーと、欠陥を見つけたときに一度で直せる場所がない状態が積み上がります。相反する考慮は、共有のライブラリが共通のインターフェースとアップグレードの周期を課し、変わったランタイムやレイテンシのニーズを持つチームが窮屈に感じたり迂回したりしうることです。本番で実際に動いているリトライとブレーカーの実装がいくつ異なるか、そしてどのサービスが外向きの呼び出しにまだタイムアウトをまったく持たないかの調査を持ち込んでください。大きな企業や機関では、審査された共有ライブラリは、セキュリティレビュアーと監査人に、数十ではなく認証すべき一つのコンポーネントを与え、あらゆるレビューのコストを下げます。

  6. 昨夜インシデントがあったとして、オンコールのエンジニアは誰でも一行のログからそれをたどれ、監査人は後で、システムが記録したすべての失敗を見られますか。 包まれ、分類され、よくログに残されたエラーは、10分の診断と真夜中の発掘の違いであり、握りつぶされたものは、自分から隠した将来のインシデントです。大きなチームでは、失敗は多くのサービスの跳躍をまたぐので、価値は、どのチームの勤勉さからでもなく、その跳躍を生き延びる一貫した文脈と相関識別子から来ます。相反する緊張はコストと雑音です。すべてをログに残せばシグナルを溺れさせ、保存のコストを払い、少なすぎれば何が起きたかを再構成できません。最近の本物の失敗を持ち込み、その跡を端から端までたどって、文脈が落とされたり、エラーが捕まえられて捨てられたりしたすべての跳躍を記録してください。規制された、あるいは政府のシステムでは、これをコンプライアンスの性質として扱ってください。監査できない失敗や、何年も後に説明できない決定は、単なる運用上のギャップではなく、法的リスクだからです。

セクター別の視点

スタートアップ。 少数のエンジニアで余裕のあるランウェイもないので、エラー処理の予算を、失敗が顧客やデータを犠牲にする所に使ってください。すべての外向きの呼び出しにタイムアウトを設け、お金を動かす書き込みを冪等にし、無視されたエラーに対するリンタールールを加える。凝ったフレームワークは省き、中核の関数のResult型と、必須でない依存先の穏やかな縮退が、数日の作業で安全のほとんどを買います。バグが大きな音で表面化するよう、開発ではフェイルファストにし、それが正当化される依存先が実際にできる前に、サーキットブレーカーを手作りするのは控えます。

小規模事業者。 レジリエンスの専門家はおらず予算も厳しいので、パターンをゼロから作るのではなく、言語、フレームワーク、クラウドプロバイダーがすでに与えるものに頼ってください。マネージドキュー、プロバイダー側のリトライ、ライブラリのタイムアウトは、ほとんどのチームが想定するより多くをカバーします。決定を買うか作るかとして枠づけ、成熟した依存先がリトライ、バックオフ、冪等性を扱ってくれる所では買います。乏しい注意を、誤ったり失われたりしたトランザクションが本当に痛い一つか二つの境界に集中し、それらが安全に失敗して跡を残すようにします。

大企業。 多くのチームにわたって、賞品は一貫性です。共有のエラー分類一つ、タイムアウト、リトライ、サーキットブレーカー、バルクヘッドのための共通のライブラリ、そしてそれらをパイプラインで徹底するリンターとレビューのチェックリスト。すべてのエラーを相関識別子とともに統一されたオブザーバビリティのプラットフォームに流し、失敗がサービスの跳躍をまたいでたどれるようにし、境界ごとのフェイルファストとフェイルセーフの決定を標準化して、監査が局所的な習慣の寄せ集めではなく、文書化された擁護できるパターンを見つけられるようにします。共有ライブラリを本物の製品として統治してください。そこで一度直された欠陥は、あらゆる所で直された欠陥だからです。

政府。 正しさ、安全な失敗、持続的な監査証跡は、好みではなく義務です。お金や適格性に触れる違反された不変条件にはフェイルファストし、市民向けのすべての入力を境界で検証し、決定が何年も後に説明されレビューされるよう、各失敗を十分な文脈とともに不変のログに書き込みます。調達と長いシステムの寿命は、エラーの契約が文書化されて、元の作者がいなくなってからずっと後に公務員がコードを保守できるようにしなければならず、ベンダーのコンポーネントは、失敗の振る舞いを不透明なインターフェースの背後に隠すのではなく、公開しなければならないことを意味します。

事例

スタートアップ。 4人のスタートアップが、サードパーティの決済プロバイダーとメールサービスを呼び出すアプリを出荷します。早い段階で素朴なリトライのループを加え、タイムアウトが成功した課金を覆い隠したときに、すぐに顧客に二重課金してしまいます。その修正が教訓を教えます。すべての書き込みに冪等性キーを加え、すべての外向きの呼び出しにタイムアウトを設け、ジッター付きの指数バックオフに切り替えます。中核のサービス関数にはResult型を採用して、失敗がシグネチャに現れるようにし、リンタールールが無視されたエラーを指摘します。メール送信が失敗したとき、チェックアウトは販売をブロックせず、メッセージをキューに入れることで穏やかに縮退します。その規律は数日かかり、返金と信頼ではるかに高くついたはずの一種のインシデントを省いてくれます。

大企業。 グローバルな物流会社が何百ものサービスを運用し、そのすべてにわたってエラー処理を標準化しています。すべてのサービスは失敗を共有の分類(検証、見つからない、衝突、依存先利用不可、内部)に対応させるので、呼び出し元はメッセージを解析せずにカテゴリで分岐します。共通のライブラリがサーキットブレーカー、バックオフとジッターを伴う限りのあるリトライ、バルクヘッド化されたコネクションプールを提供するので、誰もこれらのパターンを間違って手作りしません。すべてのエラーは、9.2章のオブザーバビリティのプラットフォームに流れる相関の文脈とともにログに残され、オンコールのエンジニアが一行から、サービスの跳躍をまたいで失敗をたどれます。標準が一様でパイプラインで徹底されるので、エンジニアは見慣れないサービスの間を自信を持って移動でき、監査人は、すべての失敗が記録され、分類され、追跡可能であることを確認できます。

政府。 国の給付機関が、誤った答えが誰かの家賃を拒否したり、公的資金から過払いしたりしうる、適格性と支払いのシステムを構築します。正しさと安全な失敗は交渉の余地がないので、コードはあらゆる違反された財務の不変条件でフェイルファストします。照合できない計算は、間違った数字を計上するのではなく、計上を拒否します。市民向けのすべての入力は境界で検証され、不正な状態はドメインの型で表現不可能にされます。各失敗は、完全な文脈とともに不変の監査ログに書き込まれ、決定が何年も後に説明されレビューされなければならないという法的要件を満たします。文書のプレビューのような必須でない依存先がダウンしているとき、システムは穏やかに縮退し、担当者が引き続き申請を処理できます。新しい公務員は、エラーの契約が文書化されたコードを引き継ぐので、元の作者が去ってからずっと後も、安全にそれを保守できます。

ビジネスケース: 動機、ROI、TCO

規律あるエラー処理の見返りは、より少なく、より短く、より安いインシデントとして現れます。ほとんどの本番障害は風変わりなものではありません。握りつぶされた例外、欠けたタイムアウト、リトライの嵐、検証すべきデータを信頼した境界にたどれます。それぞれが、ここのパターンで防げ、防がれた各インシデントは、ダウンタイムの直接のコストだけでなく、緊急対応、顧客の離反、調査という複利で増えるコストも省きます。包まれてよくログに残されたエラーは、数時間ではなく数分で診断できるので、平均復旧時間は下がり、エンジニアがエラーの経路を恐れなくなるにつれて、変更失敗率も下がります。

採用のコストはささやかで、大半が一度きりです。エラーの分類を書き留め、チームが下手に再発明しないようリトライとサーキットブレーカーの共有ライブラリを提供し、無視されたエラーに対するリンタールールを加え、失敗の注入の習慣を築く。放置のコストは静かに複利で増えます。握りつぶされたエラーは、解きほぐすのが高価な壊れたデータに積み重なり、一貫しない処理はあらゆる統合とあらゆる監査のコストを増やします。規制された、あるいは政府の設定では、監査できない失敗は、単なるエンジニアリングの問題ではなく、コンプライアンス上と法的リスクです。リーダーシップに論拠を示すには、エラー処理の規律を、彼らがすでに注視している指標に結びつけてください。インシデントの頻度、平均復旧時間、変更失敗率、監査の指摘。

アンチパターンと落とし穴

  • 黙った握りつぶし: 失敗を、遅れて切り離された謎に変える、空のcatchブロックと無視された戻り値。
  • catch-allによる消去: 汎用的なメッセージをログに残し、元のエラーとその文脈を捨てる、広いcatch。
  • 冪等性のないリトライ: タイムアウト後に冪等でない書き込みを再実行し、二重に課金したりレコードを複製したりすること。
  • リトライの嵐: バックオフもジッターも上限もなく、クライアントが同期して、回復中の依存先を叩き倒すこと。
  • タイムアウトがない: 制限のないリモート呼び出しが、ハングした依存先一つにスレッドを使い果たさせ、プロセス全体を固まらせること。
  • 制御フローとしての例外: 「見つからない」のような通常の結果に対して投げて捕まえ、ロジックを隠しコードを遅くすること。
  • 防御的な被害妄想: ロジックを埋もれさせ、本物の失敗を黙ったデフォルトに変える、すべての行のチェック。
  • 文字列型のエラー: 分岐する安定した分類されたエラーの体系がないため、呼び出し元がエラーメッセージのテキストを解析すること。
  • フェイルファストが必要な所でのフェイルセーフ: 間違った答えが答えなしより悪いシステムで、壊れた状態で続けること。

成熟度モデル

  • レベル1、開始: エラー処理は場当たり的で反応的で、開発者ごとに決められます。空のcatchブロックと無視された戻りが一般的で、リトライは素朴で、タイムアウトは欠けており、失敗は壊れたデータや、一貫したログのない謎の欠陥として表面化します。
  • レベル2、発展: チームは基本的な実践を採用しますが、一貫していません。エラーはある程度の文脈とともにログに残され、明らかな握りつぶしはレビューで戒められ、タイムアウトと単純なリトライは存在しますが、規約はサービス間で異なり、冪等性はまばらで、エラーの経路はめったにテストされません。
  • レベル3、標準化: 共有のエラー分類と処理の規約が文書化され、組織全体で徹底されています。境界の検証、バックオフとジッターを伴う冪等なリトライ、サーキットブレーカー、バルクヘッド、エラーの包み込みが標準で、共通のライブラリが提供し、すべてのエラーが統一されたオブザーバビリティのパイプラインに流れます。
  • レベル4、管理: エラー処理の振る舞いが、ベースラインに対して測定され、データで制御されます。リトライ率、サーキットブレーカーの作動、タイムアウト数、静的解析による握りつぶされたエラーの指摘、平均復旧時間、変更失敗率がサービスごとに追跡され、サーキットブレーカーの閾値とタイムアウトは推測ではなく観察されたレイテンシと失敗のデータから調整され、失敗の注入がスケジュールで実行され、チームはこれらの指標をレビューして、回帰を捉え、各フェイルファストまたはフェイルセーフの選択を証拠に照らして保ちます。
  • レベル5、オーケストレーション: レジリエンスは、デリバリーとリスクの計画と統合され、継続的に改善されます。分類、共有ライブラリ、標準はあらゆるインシデントから進化し、カオスと失敗の注入の実験は日常的で、組織はトラフィック、依存先、リスクの状況の変化に応じて、タイムアウト、ブレーカーの閾値、縮退の戦略、境界の決定を適応させます。

議論のためのアイデア

  1. コードベースのどこで、今エラーが握りつぶされていて、握りつぶされていないというあなたが間違っているなら、どうやってそれを知りますか。
  2. 書き込み操作のうち、どれが冪等で、確認応答が失われた後にリトライが発火すると、どれが二重に実行されますか。
  3. 「ユーザーが見つからない」は、例外、エラー値、通常の結果のどれであるべきで、チームはそれに一貫して答えていますか。
  4. 検証がどこで行われるかについての実際のルールは何で、信頼すべきでないデータを信頼している境界を指し示せますか。
  5. サーキットブレーカーの閾値とクールダウンをどう決め、現在の設定が間違っていることをどうやって知りますか。
  6. 監査人が先月システムが経験したすべての失敗を見せてほしいと言ったら、分類し文脈とともに提示できますか。

要点

  • フォールト、エラー、失敗を区別し、内部のエラーが目に見える失敗になる前に連鎖を断ちます。
  • 境界ごとにフェイルファストかフェイルセーフかを意図して選び、すべての関数のエラー処理の契約を明示的にします。
  • 信頼の境界で強く検証し、その内側では信頼します。失敗を覆い隠す防御は、安全ではなく先送りです。
  • 冪等性、タイムアウト、指数バックオフ、ジッターでリトライを安全にし、コードにサーキットブレーカーと穏やかな縮退を加えます。
  • エラーを文脈で包み、オブザーバビリティに流し、決して握りつぶしません。すべてのエラーは、処理されるか、伝播されるか、ログに残されて表面化されなければなりません。

参考文献とさらなる読み物

  • Michael T. Nygard, Release It! Design and Deploy Production-Ready Software
  • Andrew Hunt and David Thomas, The Pragmatic Programmer
  • Steve McConnell, Code Complete: A Practical Handbook of Software Construction
  • Betsy Beyer, Chris Jones, Jennifer Petoff, and Niall Richard Murphy (eds.), Site Reliability Engineering: How Google Runs Production Systems
  • Marc Brooker, “Timeouts, Retries, and Backoff with Jitter,” Amazon Builders’ Library
  • Martin Fowler, “CircuitBreaker,” martinfowler.com
  • Nassim Nicholas Taleb, Antifragile: Things That Gain from Disorder