システムデザイン完全ガイド ⑤|壊れないシステムを作る

システムデザイン

SYSTEM DESIGN GUIDE — PART 5 / 7

  1. 壊れないシステムを作る
  2. CHAPTER 28可用性・SLI / SLO / エラーバジェット
    1. 28.1可用性の数字
    2. 28.2直列と並列の可用性
    3. 28.3SLI / SLO / SLA
    4. 28.4エラーバジェット(Google SRE の中核概念)
    5. 28.5その他の指標
  3. CHAPTER 29冗長化とフェイルオーバー
    1. 29.1単一障害点 (SPOF) を洗い出す
    2. 29.2冗長化の 3 パターン
    3. 29.3障害ドメインとブラストレディウス
      1. ① セルベースアーキテクチャ(セル分割)
      2. ② シャッフルシャーディング
      3. ③ 分離(Bulkhead / 隔壁)
    4. 29.4バックアップとリストア
    5. 29.5カオスエンジニアリング
  4. CHAPTER 30タイムアウト・リトライ・サーキットブレーカ
    1. 30.1タイムアウト
    2. 30.2リトライ
    3. 30.3サーキットブレーカ
    4. 30.4バルクヘッド(隔壁)
    5. 30.5負荷制限(Load Shedding)と優先度
    6. 30.6まとめ:レジリエンスパターン一覧
  5. CHAPTER 31カスケード障害とメタスタブル障害
    1. 31.1カスケード障害の典型的な流れ
    2. 31.2メタスタブル障害(Metastable Failure)
    3. 31.3サンダリングハード(Thundering Herd)
    4. 31.4事例に学ぶ(有名な障害パターン)
  6. CHAPTER 32オブザーバビリティ(監視・ログ・トレース)
    1. 32.13 本の柱(+2)
    2. 32.2メトリクスの設計
    3. 32.3分散トレーシング
    4. 32.4ログ
    5. 32.5アラート設計
  7. CHAPTER 33安全なデプロイとスキーマ変更
    1. 33.1デプロイ戦略
    2. 33.2データベースのスキーマ変更(面接頻出の実務問題)
    3. 33.3API の後方互換性
    4. 33.4デプロイの安全装置
    5. 33.5テストと検証の層
  8. CHAPTER 34キャパシティプランニングとオートスケーリング
    1. 34.1キャパシティプランニングの手順
    2. 34.2負荷試験
    3. 34.3オートスケーリング
    4. 34.4コスト効率
  9. CHAPTER 35インシデント対応とポストモーテム
    1. 35.1インシデント対応の流れ
    2. 35.2役割分担(Incident Command System)
    3. 35.3ポストモーテム(振り返り)
    4. 35.4運用の成熟度チェックリスト

壊れないシステムを作る

必ず壊れます。ネットワークは切れ、ディスクは飛び、デプロイは失敗します。目指すのは「壊れないシステム」ではなく「壊れても被害が小さく、早く直せるシステム」です。

収録:第 28 章 〜 第 35 章

ここからは SRE(Site Reliability Engineering)の領域です。Google の面接では「障害時にどうなるか」を必ず聞かれます。ここを語れると一段上の評価になります。

CHAPTER 28可用性・SLI / SLO / エラーバジェット

🎯 GOAL

「9 の数」を計算でき、SLO を要件から定義できる。

28.1可用性の数字

📐 暗記推奨

下の表は面接でそのまま使えます。

可用性 年間ダウンタイム 月間 週間 呼び方
99% 3.65 日 7.3 時間 1.7 時間 2 nines
99.9% 8.77 時間 43.8 分 10.1 分 3 nines
99.95% 4.38 時間 21.9 分 5 分
99.99% 52.6 分 4.38 分 1 分 4 nines
99.999% 5.26 分 26 秒 6 秒 5 nines
💡 直感

99.99% は「月に約 4 分」です。検知・切り替え・復旧を人手だけで達成するのは難しいため、自動化、冗長化、変更管理、復旧訓練を組み合わせます。自動フェイルオーバーだけで SLO が達成されるわけではありません。

28.2直列と並列の可用性

SERIES — 掛け算になる
[LB 99.99%] → [API 99.9%] → [DB 99.95%]
可用性 = 0.9999 × 0.999 × 0.9995 = 99.84%   ← 一番弱い所より悪くなる
⚠️ 落とし穴

依存が増えるほど可用性は必ず下がります。「マイクロサービスにしたら可用性が下がった」の正体はこれです。

PARALLEL — 全部落ちる確率を引く
可用性 99% のサーバー 3 台(どれか 1 台生きていれば OK)
可用性 = 1 - (0.01)^3 = 99.9999%
📐 設計の帰結

  1. 依存の数を減らす(そのサービス呼び出しは本当に必要か)
  2. 依存を「必須」と「任意」に分ける(推薦機能が落ちても商品ページは表示する = graceful degradation)
  3. 冗長化する(ただし相関障害=同じ AZ・同じバグ・同じ設定ミスには無力)
⚠️ 相関障害

「3 台冗長だから 99.9999%」は故障が独立している場合のみ成立します。実際には同一 AZ の電源断、同じデプロイのバグ、共通の設定ミス、証明書の同時失効など、相関する障害が現実の停止原因の大半です。

28.3SLI / SLO / SLA

用語 意味
SLI (Indicator) 測定する指標 「成功したリクエストの割合」「p99 レイテンシ」
SLO (Objective) 社内で目指す目標 「30 日間で成功率 99.9%、p99 < 300 ms」
SLA (Agreement) 顧客との契約(違反時は返金) 「99.5% を下回ったら 10% 返金」
📐 数字・公式

SLA < SLO にするのが鉄則です(SLA を破る前に社内で気づけるように)。

良い SLI の選び方
✅ ユーザー体験に直結するもの
   可用性 : 成功レスポンス数 / 全リクエスト数
   レイテンシ: p99 が閾値以内のリクエストの割合
   品質    : 検索結果の適合率、動画の再生開始時間
   鮮度    : データの遅延が N 秒以内である割合

❌ 内部指標だけ(CPU 使用率、メモリ使用量)
   → これらはアラートの原因調査には使うが、SLO にはしない

28.4エラーバジェット(Google SRE の中核概念)

ERROR BUDGET
SLO = 99.9% → エラーバジェット = 0.1%
月間 10 億リクエストなら → 100 万リクエストまで失敗してよい

【使い方】
バジェットが残っている → 新機能を積極的にリリースしてよい(リスクを取る)
バジェットを使い切った → 機能開発を止め、信頼性の改善に専念する
💡 本質

エラーバジェットは「信頼性 100% を目指さない」という宣言です。

  • 100% を目指すとコストが無限大になり、リリース速度がゼロになる
  • ユーザーのネットワーク、端末、DNS、外部依存もエンドツーエンドの体験に影響するため、サービス単体の SLO だけで体感を断定しない
  • 開発チームと SRE の対立を「共通のバジェット」という数字で解消する仕組み
🎤 面接

SCRIPT
「この機能の SLO は可用性 99.9%、p99 レイテンシ 300 ms とします。
 決済のように失敗が致命的な部分は 99.99% にしますが、
 推薦機能は 99% で十分で、落ちたらデフォルトの人気順を返す形で degrade させます。
 全機能を同じ可用性にするのはコストの無駄なので、機能ごとに SLO を分けます。」

28.5その他の指標

指標 意味
MTBF 平均故障間隔(どれだけ壊れないか)
MTTR 平均復旧時間(どれだけ早く直せるか)
MTTD 平均検知時間
RPO (Recovery Point Objective) どれだけのデータ損失を許すか(例:5 分)
RTO (Recovery Time Objective) どれだけの復旧時間を許すか(例:1 時間)
📐 数字・公式

可用性 ≒ MTBF / (MTBF + MTTR)。つまり「壊れにくくする」より「早く直す」方が費用対効果が高いことが多い。これが SRE の「MTTR を短縮せよ」という思想の根拠です。

第 28 章 一問一答

99.99% の月間ダウンタイムは。
約 4.4 分。
直列に並ぶ依存の可用性はどうなるか。
掛け算になり、必ず最弱の依存より悪くなる。
エラーバジェットの目的は。
信頼性とリリース速度のトレードオフを数値で管理し、判断基準を共有すること。
RPO と RTO の違いは。
RPO は許容データ損失量(時間)、RTO は許容復旧時間。

CHAPTER 29冗長化とフェイルオーバー

🎯 GOAL

単一障害点を見つけ、除去する設計ができる。

29.1単一障害点 (SPOF) を洗い出す

設計図を描いたら、すべての箱と線に指を置いて「これが死んだらどうなるか」を問う

SPOF CHECKLIST
□ ロードバランサ自体      → 複数 AZ、Anycast、DNS フェイルオーバー
□ DB のプライマリ         → 自動フェイルオーバー(Raft or マネージド)
□ キャッシュ              → 落ちても DB で耐えられるか(レート制限で保護)
□ メッセージキュー        → レプリケーション、min.insync.replicas
□ 認証サービス            → キャッシュされたトークンで一定時間は動くか
□ 設定サーバー / 秘密管理  → 起動時のみ依存 or ローカルにフォールバック
□ DNS                     → 複数プロバイダ
□ 証明書                  → 自動更新の失敗が全断を招く(監視必須)
□ デプロイパイプライン    → 壊れると「直せない」状態になる
□ 人間(1 人しか知らない) → ドキュメントと訓練
⚠️ 見落とされがちな SPOF

「共通ライブラリ」「共通の設定ファイル」「共通の Feature Flag サービス」。これらは論理的な単一障害点で、同時に全サービスを壊します

29.2冗長化の 3 パターン

パターン 説明 切替時間 コスト
Active-Active 全台が稼働し負荷を分担 即時に近い場合がある 高い。障害時の余力、競合、運用コストも必要
Active-Passive (Hot Standby) 待機系が同期状態で待つ 数秒〜数十秒 待機分が無駄
Warm / Cold Standby 起動していない、データだけ同期 数分〜数時間 安い
📐 N+1 / N+2 冗長

必要台数 N に対し、1 台 or 2 台の余裕を持つ。

⚠️ 重要

「1 台落ちても大丈夫」な構成は、残りの台数で全負荷を捌けなければ意味がありません。2 台で 50% ずつ使っている構成は、1 台落ちると残り 1 台が 100% になり、第 4 章の待ち行列によりレイテンシが爆発して結局全滅します。→ 各台 50% 以下、または N+2 で設計するのが定石です。

29.3障害ドメインとブラストレディウス

FAILURE DOMAIN
障害ドメイン(同時に壊れる単位)
  プロセス ⊂ ホスト ⊂ ラック ⊂ AZ(データセンタ)⊂ リージョン ⊂ 地球

① セルベースアーキテクチャ(セル分割)

全ユーザーを 1 つのシステムで捌く → 障害は全ユーザーに影響
       ↓
ユーザーを 10 個のセルに分け、各セルが独立したフルスタックを持つ
       ↓
1 セルの障害 = 10% のユーザーだけが影響を受ける
🔬 深掘り

AWS や Slack が採用。デプロイもセル単位で段階的に行うことで、バグの影響も 10% に限定できます。

② シャッフルシャーディング

8 台のワーカーから 2 台をランダムに選んで各顧客に割り当てる
  → 組み合わせは 8C2 = 28 通り
  → 悪意ある顧客 A が 2 台を殺しても、完全に重なる顧客は 1/28 だけ
  → 他の顧客は少なくとも 1 台が生き残る
🔬 深掘り

AWS Route53 / Shuffle Sharding の考え方。極めて費用対効果が高い手法です。

③ 分離(Bulkhead / 隔壁)

スレッドプール・コネクションプールを依存先ごとに分ける
  → 遅い依存先 A がプールを食い潰しても、B への呼び出しは影響を受けない

29.4バックアップとリストア

⚠️ 最重要

「バックアップを取っている」は無意味です。「リストアを検証している」だけが意味を持ちます。

種類 説明
フルバックアップ 全体のコピー。復旧は速いが容量とコストが大きい
増分 / 差分 変更分のみ。容量が小さいが復旧が遅い
PITR(Point-in-Time Recovery) フル + WAL の適用で任意時点に戻せる。論理削除事故に有効な選択肢の一つ
論理バックアップ(dump) SQL 文。移植性が高いが遅い
スナップショット ストレージレベル。高速だが同一インフラに依存
📐 3-2-1 ルール

3 つのコピー、2 種類の媒体、1 つはオフサイト(別リージョン)。さらに現代は1 つは改変不能 (immutable / WORM) を加えます(ランサムウェア対策)。

📐 必ずやること

  • 定期的なリストア訓練(四半期に 1 回など)。リストア時間を実測する(= RTO の検証)
  • バックアップ自体の監視(「3 日間バックアップが失敗していた」が最悪のパターン)
  • 本番の認証情報と分離する(同じ権限で消せるならバックアップの意味がない)

29.5カオスエンジニアリング

本番環境を含む環境に、管理された範囲で障害を注入し、耐障害性を検証する。

CHAOS EXPERIMENT
① 定常状態を定義する(成功率 99.9%、注文数 1000/分)
② 仮説を立てる(「1 台落としても定常状態は維持される」)
③ 実験する(ランダムに 1 台停止、レイテンシ注入、ネットワーク分断)
④ 差分を検証し、修正する

本番で行う場合は、まずステージングで検証し、対象・時間・ブラストレディウス・停止条件・kill switch・バックアップ・オンコール・承認・PII の扱いを明記します。無制限にプライマリを落とすことを定期作業にしません。

🔬 深掘り

Netflix の Chaos Monkey が有名。Google では DiRT (Disaster Recovery Testing) という全社的な障害訓練を定期実施しています。

🎤 面接

SCRIPT
「フェイルオーバーの設計は書くだけでなく、実際に動くことを継続的に検証しないと
 いざという時に動きません。定期的にプライマリを意図的に落とすゲームデーを実施し、
 フェイルオーバー時間を実測して RTO の目標と照合します。」

第 29 章 一問一答

2 台構成で各 50% 使用時、1 台落ちるとどうなるか。
残り 1 台が 100% になり、待ち行列でレイテンシが爆発する。N+2 か使用率をさらに下げる必要がある。
シャッフルシャーディングの効果は。
顧客ごとに異なるノードの組合せを割り当て、単一顧客起因の障害の影響範囲を大幅に限定する。
バックアップで最も重要な作業は。
定期的なリストアの検証。
論理削除事故にどう備えるか。
PITR、スナップショット、論理バックアップ、遅延レプリカ、soft delete などを RPO/RTO と誤操作モデルに応じて組み合わせる。

CHAPTER 30タイムアウト・リトライ・サーキットブレーカ

🎯 GOAL

障害の伝播を止める 6 つの道具を使いこなす。

30.1タイムアウト

⚠️ 最も致命的な設計ミス

タイムアウトを設定しないのは、最も多く、最も致命的な設計ミスです。

タイムアウトなし → 下流が遅い → スレッドが全部待機で埋まる
                → 自分も応答しなくなる → その上流も止まる → 全体が死ぬ
📐 タイムアウトの決め方

1. 下流のレイテンシ分布、接続・TLS・読み取り・アイドル時間、
   ネットワーク、再試行を測る
2. タイムアウトは固定倍率ではなく、ユーザーの deadline とサービスの SLO から
   予算を分配する(例の数値はワークロード依存)
3. 上流のタイムアウト > 自分のタイムアウト + リトライ分 になるよう階層で整合させる
⚠️ 階層の逆転

❌ ユーザー: 1 秒でタイムアウト
   API:     3 秒でタイムアウト
   DB:      10 秒でタイムアウト
→ ユーザーはとっくに諦めているのに、サーバーは 10 秒間リソースを握り続ける
📐 デッドライン伝播(Google の標準)

タイムアウトではなく「絶対時刻の締切」を下流へ渡す

リクエストヘッダ: deadline = 2026-08-22T10:00:03.500Z
→ 各サービスは「あと何 ms 残っているか」を知り、残り時間が足りなければ即座に諦める
→ 無駄な処理をしない、タイムアウトの二重計上が起きない
🎤 面接

gRPC の context.WithDeadline はまさにこれです。面接で言うと非常に強いトピックです。

30.2リトライ

⚠️ 素朴なリトライは障害を悪化させる

❌ 即時リトライ 3 回
   → 下流が過負荷 → 全クライアントが 3 倍の負荷をかける
   → 完全に死ぬ(リトライストーム)
✅ 正しいリトライ
① 指数バックオフ: 100ms → 200ms → 400ms → 800ms
② ジッタ(ランダム化): sleep(random(0, min(cap, base * 2^attempt)))
   ← これがないと全クライアントが同じタイミングで再送し、波が同期する
③ リトライ回数の上限(3 回程度)
④ 冪等な操作のみリトライ(第 18 章)
⑤ リトライ予算(Retry Budget): 全リクエストの 10% までしかリトライに使わない
⑥ リトライ可能なエラーだけリトライ
   (冪等性、429/5xx、接続失敗、Retry-After を総合判断)
📐 Full Jitter(AWS 推奨)

PYTHON
sleep = random.uniform(0, min(cap, base * 2 ** attempt))
⚠️ 多層リトライの罠(超重要)

各層で 3 回リトライすると、4 階層で 3^4 = 81 倍の負荷になり得る
→ リトライ所有者を一つに決めるか、
  各層で共有する deadline・予算を協調させる

30.3サーキットブレーカ

CIRCUIT BREAKER
        失敗率が閾値超え
 [Closed] ──────────────► [Open]
 通常通り                  即座に失敗を返す(下流を呼ばない)
    ▲                         │ 一定時間後
    │  成功                    ▼
    └──────────────────── [Half-Open]
                          少数のリクエストだけ通して様子を見る
💡 たとえ話

電気のブレーカーです。ショートしたら回路を切り、家全体の火災を防ぎます。

効果

  1. 下流に回復の時間を与える(過負荷の下流を叩き続けない)
  2. 自分のスレッドを守る(無駄な待機をしない)
  3. フェイルファスト(ユーザーを 30 秒待たせずに即座に代替を返す)
📐 パラメータ

直近 N 件中の失敗率(例:50%)、Open の継続時間(例:30 秒)、Half-Open で通す件数。最低リクエスト数の閾値も必要(10 件中 5 件失敗で開くのは早計)。

30.4バルクヘッド(隔壁)

BULKHEAD
❌ 共有スレッドプール 100 本
   サービス A が遅くなる → 100 本全部が A の待機に使われる → B も C も死ぬ

✅ 分離
   A 用 30 本 / B 用 30 本 / C 用 40 本
   → A が死んでも B, C は動き続ける

30.5負荷制限(Load Shedding)と優先度

⚠️ 落とし穴

過負荷時に全リクエストを受け付けると、全部が遅くなって全部が失敗します。一部を早めに捨てて、残りを確実に処理する方が総効用が高い。

LOAD SHEDDING
【実装】
① キューの長さ or レイテンシを監視
② 閾値を超えたら、低優先度のリクエストから 503 で即座に拒否
③ ヘルスチェックと重要な管理系は必ず通す

【優先度の例】
  最優先: 決済完了、ヘルスチェック
  高    : ログイン、商品購入フロー
  中    : 商品閲覧
  低    : 推薦、アナリティクス、プリフェッチ、バッチ
🔬 深掘り

CoDel(Controlled Delay)方式:キューの滞留時間が閾値を超えたリクエストを捨てる。「キューの長さ」より「待ち時間」で判断する方が適応的で優れています。

⚠️ 上流でのキャンセル

ユーザーが既に諦めた(切断した)リクエストを処理し続けるのは純粋な無駄です。デッドライン超過のリクエストは処理前に捨てる

30.6まとめ:レジリエンスパターン一覧

パターン 目的
タイムアウト 無限待機の防止
デッドライン伝播 全体の締切を守り、無駄な処理を排除
リトライ + バックオフ + ジッタ 一時的失敗からの回復(ただし増幅に注意)
サーキットブレーカ 障害中の下流を叩かない、フェイルファスト
バルクヘッド 障害の隔離
負荷制限 過負荷時に一部を捨てて全滅を防ぐ
フォールバック 代替の応答(キャッシュ、デフォルト値、機能停止)
ヘッジドリクエスト テールレイテンシの削減
アドミッションコントロール 入口で受付量を制御
🎤 面接

SCRIPT
「推薦サービスへの呼び出しには 100 ms のタイムアウトとサーキットブレーカを
 設定します。推薦が落ちた場合は人気順のフォールバックを返し、
 商品ページ自体は表示します。
 リトライはユーザー向けの同期パスでは 1 回のみ、ジッタ付きバックオフで行い、
 リトライ予算を全体の 10% に制限してリトライストームを防ぎます。」

第 30 章 一問一答

リトライにジッタが必要な理由は。
クライアントの再送タイミングが同期し、周期的な負荷の波を作るのを防ぐため。
多層リトライの危険性は。
各層の回数が乗算され、下流への負荷が指数的に増える。
サーキットブレーカの 3 状態は。
Closed / Open / Half-Open。
デッドライン伝播の利点は。
全体の締切を共有し、既に間に合わない処理を早期に打ち切れる。

CHAPTER 31カスケード障害とメタスタブル障害

🎯 GOAL

「一部の障害が全体に広がる」メカニズムを説明でき、防げる。

31.1カスケード障害の典型的な流れ

CASCADE
① サーバー 10 台のうち 2 台が高負荷でダウン
② 残り 8 台に負荷が集中(1.25 倍)
③ 8 台のレイテンシが悪化 → タイムアウト増加
④ クライアントがリトライ → さらに負荷が 2〜3 倍に
⑤ 8 台のうち 3 台がヘルスチェックに失敗して LB から外れる
⑥ 残り 5 台に全負荷 → 即死
⑦ 全滅。しかもここで再起動しても、待っていた全リクエストが殺到して再び即死
⚠️ 最も恐ろしいのは ⑦

原因を取り除いても復旧しない状態をメタスタブル障害と呼びます。

31.2メタスタブル障害(Metastable Failure)

「引き金となった原因が消えても、システムが自力で正常状態に戻れない状態」

METASTABLE
【正常】負荷 = 処理能力の 60%
   │ 一時的なスパイクやデプロイなどのトリガー
   ▼
【メタスタブル】リトライで負荷が増幅 → キャッシュミス増加 → さらに遅くなる
   → 原因を取り除いても、増幅ループが自己維持する
   → 抜け出すには回復能力を下回るまで負荷を絞り、
     キュー・リトライ・キャッシュを段階的に回復させる
自己維持する増幅ループ 説明
リトライ増幅 遅い → リトライ → もっと遅い → もっとリトライ
キャッシュ空振り キャッシュが空 → DB 過負荷 → キャッシュ充填が失敗 → 空のまま
GC スパイラル メモリ逼迫 → GC 頻発 → 遅くなる → キューが伸びる → さらにメモリ逼迫
接続の枯渇 タイムアウト増 → 接続滞留 → 新規接続不可 → 全部タイムアウト
ヘルスチェック連鎖 遅い → ヘルスチェック失敗 → 台数減 → もっと遅い
📐 対策

  • 入口での負荷制限(受け付ける量を能力以下に保つ = アドミッションコントロール)
  • リトライ予算(増幅の上限を作る)
  • ヘルスチェックを負荷に依存させない
  • 復旧時は段階的にトラフィックを戻す(0% → 1% → 10% → 100%)
  • キャッシュのウォームアップを復旧手順に入れる
🎤 面接(復旧手順は?)

SCRIPT
「単に再起動すると、待機していた全リクエストが殺到して再び倒れます
 (メタスタブル状態)。
 なので、まず入口でトラフィックを絞り、キャッシュを温めてから
 1% → 10% → 50% → 100% と段階的に戻します。
 この『スロースタート』の仕組みをロードバランサ側に組み込んでおきます。」

31.3サンダリングハード(Thundering Herd)

同じ瞬間に大量のクライアントが同じ行動をする現象。

発生源
cron の同時実行 全サーバーが 0 分ちょうどに集計を開始
キャッシュの同時失効 全キーの TTL が同時に切れる
再接続の同期 サーバー再起動で全クライアントが同時に再接続
クライアントのポーリング周期 アプリが全端末で 60 秒ごとに同期
セール開始・チケット発売 人間側の同期(これは避けられない → 待機列で制御)
📐 万能の対策は「ジッタ」

PYTHON
# ❌ 全員が同時に動く
schedule_at("0 * * * *")
sleep(60)

# ✅ ばらける
schedule_at(f"{random.randint(0,59)} * * * *")
sleep(60 + random.uniform(-10, 10))
ttl = 300 + random.randint(0, 60)
⚠️ 仮想待合室

チケット販売のように人間側が同期する場合、入場を制御する待機列(キュー)を前段に置く有力な方法です。座席の条件付き更新、Bot 対策、レート制限、容量増強、販売時間の設計も必要です。

31.4事例に学ぶ(有名な障害パターン)

パターン 実例的なシナリオ
設定変更の全体展開 誤った設定を全リージョンに一斉配信 → 全断。設定もコードと同じく段階展開すべき
証明書の期限切れ 全サービスが同時に通信不能。期限監視は必須
依存の逆転 復旧に必要なツール自体が、落ちているシステムに依存していた
DNS 障害 名前解決できず全サービスが停止
リージョン間の相互依存 「マルチリージョンだから安全」なはずが、両方が同じメタデータサービスに依存していた
キャパシティの過小評価 オートスケールが間に合わない(起動に 5 分かかる)
🎤 面接での加点

「この設計の中で、変更を全体に一斉適用する箇所はどこか」を意識して「設定配信も段階的にする」と言えると、実運用経験のあるシグナルになります。

第 31 章 一問一答

メタスタブル障害とは。
トリガーが消えても増幅ループにより自力で正常状態に戻れない障害。
復旧時に一気にトラフィックを戻してはいけない理由は。
滞留リクエストとコールドキャッシュで再び過負荷になるから。
サンダリングハードの汎用対策は。
ジッタによるタイミングの分散。

CHAPTER 32オブザーバビリティ(監視・ログ・トレース)

🎯 GOAL

「動いているか」ではなく「なぜ遅いか」を答えられる仕組みを設計する。

32.13 本の柱(+2)

メトリクス ログ トレース
何が分かる 集計された数値の時系列 個別の出来事の詳細 1 リクエストの全経路と時間配分
コスト 安い(集約済み) 高い(量が多い) 中(サンプリングする)
用途 アラート、ダッシュボード 原因の特定、監査 ボトルネックの特定、依存の可視化
Prometheus, Monarch Loki, ELK, Cloud Logging Jaeger, Dapper, OpenTelemetry

+2プロファイル(CPU/メモリの継続プロファイリング)とイベント(デプロイ・設定変更の記録)。

📐 数字・公式

変更直後の障害を切り分けるため、デプロイ・設定・スキーマ・フラグの変更イベントを記録します。「障害の何割が変更起因か」という数字は組織・期間で異なるため、出典と対象を明記します。

32.2メトリクスの設計

4 つの型
Counter    : 単調増加(総リクエスト数)→ rate() で秒あたりに変換して使う
Gauge      : 上下する値(メモリ使用量、キューの長さ)
Histogram  : 分布(レイテンシ)→ パーセンタイルが計算できる
Summary    : クライアント側で計算済みのパーセンタイル(集約できないので注意)
⚠️ 落とし穴

平均レイテンシだけをユーザー SLO に使ってはいけません。平均、合計、件数は容量分析に有用なので保持し、ヒストグラムで p50/p90/p99 も併記します。

パーセンタイルは平均できません。「3 台の p99 の平均」は p99 ではありません。→ ヒストグラム(バケット)を集約してから計算する必要があります。

📐 RED メソッド(サービス向け)

Rate      : 秒あたりのリクエスト数
Errors    : エラー率
Duration  : レイテンシの分布
📐 USE メソッド(リソース向け)

Utilization : 使用率
Saturation  : 飽和度(キューの長さ、待ち時間)
Errors      : エラー数
📐 Google の 4 大シグナル

THE FOUR GOLDEN SIGNALS
① レイテンシ(成功と失敗を分けて測る! 失敗が速いと平均が良く見える罠)
② トラフィック
③ エラー
④ 飽和度
⚠️ カーディナリティ爆発

メトリクスのラベルに user_idrequest_id のような高カーディナリティの値を入れると、時系列の数が爆発して監視システムが死にます

❌ http_requests{user_id="12345", url="/users/12345"}   → 数億の時系列
✅ http_requests{endpoint="/users/:id", status="200"}   → 数十の時系列

高カーディナリティの情報はログかトレースに入れます。

32.3分散トレーシング

TRACE
Trace ID: abc123(1 リクエスト全体で共通)
├─ Span: API Gateway        [0ms ────────────────── 250ms]
   ├─ Span: Auth Service       [5ms ── 15ms]
   ├─ Span: User Service       [20ms ───── 60ms]
   │   └─ Span: PostgreSQL       [25ms ── 55ms]  ← ここが遅い!
   └─ Span: Recommend Service  [65ms ─────────── 240ms]
       └─ Span: Redis            [70ms ─ 75ms]

仕組み:リクエストの入口で Trace ID を生成し、すべての下流呼び出しにヘッダで伝播する(W3C Trace Context: traceparent)。

⚠️ 落とし穴

伝播が 1 箇所でも切れると、そこから先が見えなくなります。非同期(キュー経由)でも、メッセージのヘッダに Trace ID を入れて繋ぐ必要があります。

📐 サンプリング

全リクエストを記録するとコストが膨大。

  • ヘッドベース:入口で 1% だけ記録すると決める(単純だが、レアなエラーを取り逃す)
  • テールベース:全部一旦バッファし、遅い / エラーのものだけ保存(賢いが実装が重い)
🔬 深掘り

Google の Dapper 論文がこの分野の原点です。

32.4ログ

📐 構造化ログ(JSON)にする

JSON
{"ts":"2026-08-22T10:00:00Z","level":"error","service":"payment",
 "trace_id":"abc123","user_id":"u42","event":"charge_failed",
 "amount":1000,"error":"card_declined","duration_ms":230}

→ grep ではなくクエリできるようになる。trace_id でトレースと結合できる。

ログレベルの使い分け
ERROR: 人間の対応が必要。アラートの対象になりうる
WARN : 異常だが自動回復した(リトライ成功など)
INFO : 業務上の重要イベント(注文作成、ログイン)
DEBUG: 開発時のみ。本番では通常オフ(サンプリングで一部だけ有効化も可)
⚠️ 落とし穴

パスワード、秘密鍵、完全なアクセストークン、カード番号はログに出しません。個人情報は最小化、マスキング、アクセス制御、保持期間、監査を適用し、正当な監査用途では限定的に扱います。

32.5アラート設計

⚠️ アラート疲れが最大の敵

オオカミ少年になったアラートは、本当の障害時に無視されます。

📐 良いアラートの条件

  1. 症状ベース(ユーザー影響)でアラートする。原因ベースではない
  2. アクション可能である(受け取った人がやることが明確)
  3. 緊急度で分ける(ページング=叩き起こす / チケット=翌営業日)
❌ 「CPU が 90% を超えた」(だから何? ユーザーは困っていないかもしれない)
✅ 「エラー率が SLO を超えた」「p99 レイテンシが 1 秒を超えた」
📐 バーンレートアラート(Google SRE の標準)

エラーバジェットの消費速度で警告する
  1 時間で月間バジェットの 2% を消費 → 即座にページング(14.4 倍速)
  6 時間で 5% を消費 → 高優先度
  3 日で 10% を消費 → チケット
→ 短期の瞬間的なスパイクでは鳴らず、本当に危険な傾向でだけ鳴る
🎤 面接

SCRIPT
「監視は 4 大シグナル(レイテンシ・トラフィック・エラー・飽和度)で行い、
 アラートは原因ではなく SLO のバーンレートに対して設定します。
 CPU 使用率のような原因指標はダッシュボードとデバッグに使い、
 深夜に人を起こす条件にはしません。」

第 32 章 一問一答

4 大シグナルとは。
レイテンシ、トラフィック、エラー、飽和度。
メトリクスに user_id を入れてはいけない理由は。
カーディナリティ爆発により時系列数が膨大になり、監視基盤が破綻する。
分散トレースの前提は。
Trace ID がすべてのサービス間(非同期含む)で伝播すること。
アラートを症状ベースにする理由は。
ユーザー影響に直結し、誤報が減り、原因が未知の障害も検知できるから。

CHAPTER 33安全なデプロイとスキーマ変更

🎯 GOAL

「デプロイで壊さない」仕組みを設計できる。

33.1デプロイ戦略

戦略 仕組み 利点 欠点
ローリング 少しずつ入れ替える 追加リソース不要 新旧が混在する(互換性が必要)、切り戻しが遅い
Blue/Green 旧環境と新環境を両方用意し、一気に切替 アプリ層は戻しやすい リソースが 2 倍必要。DB、スキーマ、キュー、外部副作用は即時ロールバックできない場合がある
カナリア 1% → 5% → 25% → 100% と段階的に 影響を最小化しつつ実データで検証 時間がかかる、指標の自動判定が必要
フィーチャーフラグ コードは配布済み、フラグで機能を ON/OFF デプロイと機能公開を分離、即座に無効化できる フラグの管理が煩雑になる、フラグ自体が SPOF
シャドー(ダークローンチ) 本番トラフィックを複製して新系にも流す(結果は捨てる) 実負荷で安全に検証 副作用のある処理には使えない
📐 実務の定番の組合せ

コードは常にデプロイ(フィーチャーフラグで OFF)
 → カナリアで 1 台に展開 → メトリクス自動判定(エラー率・レイテンシ)
 → 問題なければ段階的に拡大
 → 機能はフラグで 1% のユーザーに公開 → 段階的に 100% へ
 → 問題が出たらフラグを OFF(デプロイのロールバック不要 = 数秒で復旧)
⚠️ 最重要

ロールバックできることが最重要。「前進あるのみ」の設計は事故を長引かせます。

33.2データベースのスキーマ変更(面接頻出の実務問題)

⚠️ 前提

アプリのコードとスキーマは同時に切り替わりません。デプロイ中は必ず「新旧のコードが同時に動く」期間があります。

📐 Expand-Contract パターン

よく使われる安全な方法の一つです。オンライン DDL、互換性のある読み取り、バックフィル、CDC、段階移行など別の方法も、失敗時の復旧と併せて評価します。

例:name カラムを first_name / last_name に分割する

EXPAND-CONTRACT — 5 PHASES
【Phase 1: Expand(拡張)】新カラムを追加する。旧カラムはそのまま
   ALTER TABLE users ADD COLUMN first_name TEXT, ADD COLUMN last_name TEXT;
   → 旧コードは影響を受けない(NULL 許容で追加する)

【Phase 2: Dual Write(両書き)】新コードをデプロイ。両方に書く
   INSERT/UPDATE で name も first_name/last_name も更新する
   読み取りはまだ name を使う

【Phase 3: Backfill(埋め戻し)】既存データを変換する
   バッチで少しずつ(1000 行ずつ、スロットリングしながら)変換
   → 一括 UPDATE はロックとレプリケーションラグを引き起こすので厳禁

【Phase 4: 読み取り切替】新カラムから読むようにデプロイ。検証期間を置く

【Phase 5: Contract(収縮)】両書きをやめる → 十分な期間を置いて旧カラムを削除
   ALTER TABLE users DROP COLUMN name;
📐 原則

  • 1 回のデプロイで「追加」と「削除」を同時にやらない
  • 各フェーズの間にロールバック可能な状態を保つ
  • カラム削除は「もう誰も使っていない」ことを監視で確認してから
⚠️ オンライン DDL の注意

  • MySQL の ALTER TABLE は(種類により)テーブル全体をロックする → gh-ost / pt-online-schema-change でシャドーテーブルを作って切替
  • PostgreSQL でも ALTER TABLE ... ADD COLUMN NOT NULL DEFAULT はバージョンによりテーブル全体を書き換える(PG11 以降は高速化されている)
  • インデックス作成は CREATE INDEX CONCURRENTLY(PostgreSQL)で書き込みを止めない

33.3API の後方互換性

COMPATIBILITY
✅ 安全な変更:
   - 任意フィールドの追加
   - 新しいエンドポイントの追加
   - enum の値を追加(クライアントが未知の値を無視できる設計なら)

❌ 破壊的変更:
   - フィールドの削除・改名
   - 型の変更、必須化
   - デフォルト値の変更、意味の変更
   - エラーコードの変更
📐 数字・公式

破壊的変更が必要なときは、新バージョンを併存させて移行期間を設ける(/v1/v2)。Protobuf ではフィールド番号を reserved にして絶対に再利用しません。

33.4デプロイの安全装置

SAFETY CHECKLIST
□ 自動ロールバック(カナリア中のメトリクス悪化を検知)
□ デプロイの段階展開(1 ゾーン → 1 リージョン → 全体)。
  リージョン間に待ち時間を置く
□ フリーズ期間(大型セール前、年末年始はデプロイしない)
□ 設定変更もコードと同じレビュー・段階展開を通す
□ 「壊れた状態でデプロイパイプラインが動かない」状況を作らない(緊急パスの用意)
□ デプロイイベントをメトリクスに重ねて表示(因果の特定が一瞬でできる)
🎤 面接での加点

SCRIPT
「スキーマ変更は Expand-Contract で 5 段階に分け、
 各段階でロールバック可能な状態を保ちます。
 バックフィルはレプリケーションラグを監視しながらバッチサイズを調整して実行します。
 アプリのデプロイ中は新旧コードが同時に動くため、
 『旧コードが新スキーマで動く』ことと『新コードが旧スキーマで動く』ことの
 両方を満たす期間を必ず設けます。」

33.5テストと検証の層

「テスト済み」は、単体テストが通ったという意味だけではありません。変更の種類と失敗モードに対応する検証を、安いものから段階的に重ねます。

何を守るか
単体・境界値 純粋なロジックと入力制約 空、最大値、タイムゾーン、丸め、再試行回数
プロパティ・不変条件 常に成り立つべき性質 残高が負にならない、重複イベントで結果が増えない
統合・契約 DB、キュー、外部 API との接続 スキーマ互換、タイムアウト、エラーコード、認証
負荷・容量 SLO と限界点 ピーク、スパイク、バックプレッシャー、GC、キュー滞留
障害・復旧 部分障害と再開 ノード停止、遅延、重複、半端な書き込み、バックアップ復元
セキュリティ・プライバシー 越権・漏洩・削除 テナント越境、プロンプトインジェクション、削除伝播、監査

本番に近いデータ量・依存関係で、リプレイ可能なテストデータと合成個人データを使います。カナリアやシャドーでは、成功率だけでなく p99、コスト、リソース使用量、データ差分を比較し、自動停止条件と担当者を決めておきます。テストが見つけられない失敗(外部障害、未知の入力、運用手順の欠落)もあるため、実験後の監視・ロールバック・ポストモーテムまでを検証範囲に含めます。

第 33 章 一問一答

Expand-Contract パターンの目的は。
新旧コードが混在する期間でも安全にスキーマを変更するため。
カナリアリリースの利点は。
実トラフィックで検証しつつ、問題の影響範囲を限定できる。
フィーチャーフラグの利点は。
デプロイと機能公開を分離し、問題時に再デプロイなしで即座に無効化できる。

CHAPTER 34キャパシティプランニングとオートスケーリング

🎯 GOAL

必要な台数を根拠を持って決められる。

34.1キャパシティプランニングの手順

6 STEPS
① 現在の実測値を取る(ピーク QPS、1 台あたりの処理能力、リソース使用率)
② 成長率を予測する(前年比、プロモーション、新機能)
③ ピーク倍率を掛ける(平均の 3 倍、セール時は 10 倍)
④ 冗長性を加える(N+2、AZ 障害時に残り 2 AZ で全負荷を捌けるか)
⑤ 余裕率を掛ける(使用率 50〜60% を目標に)
⑥ リードタイムを考慮(GPU や専用ハードは数ヶ月かかることもある)
📐 計算例

現在: ピーク 20,000 QPS、1 台 2,000 QPS(使用率 70% 時)
安全な 1 台あたり = 2,000 × (50/70) ≒ 1,400 QPS
来年の予測 = 20,000 × 1.5(成長)= 30,000 QPS
必要台数 = 30,000 / 1,400 = 22 台
3 AZ 構成で 1 AZ 障害に耐える → 22 × 3/2 = 33 台

34.2負荷試験

種類 目的
ロードテスト 想定負荷で性能要件を満たすか
ストレステスト どこで壊れるか(限界点の把握)
スパイクテスト 急激な増加への耐性
ソークテスト(耐久) 長時間でメモリリーク・リソースリークが出ないか
カオステスト 障害注入時の挙動
⚠️ 落とし穴

本番と同じ条件でないと意味がありません:データ量、キャッシュの状態、ネットワーク構成が違うと結果が変わります。

🔬 深掘り

本番トラフィックのシャドーイングやロードシフトは有効ですが、PII の除去、外部副作用の無効化、容量上限、認可、停止条件、オンコール、費用を先に設計します。本番と同じ条件であることだけを理由に無制限に流してはいけません。

34.3オートスケーリング

指標の選び方
CPU 使用率     → 汎用的。ただし I/O バウンドなワークロードでは反応しない
QPS / 同時接続 → 直接的で良い
キューの長さ   → 非同期ワーカーの候補指標。
                 古いメッセージの年齢、到着率、処理率、可視性タイムアウトも併用する
レイテンシ     → 遅行指標なので単独では危険(もう手遅れ)
⚠️ 5 つの落とし穴

  1. 起動が遅い:仮想マシン起動 + アプリ起動 + JIT ウォームアップ + キャッシュ充填 = 5〜10 分。スパイクには間に合わない → 予測スケーリング or 事前プロビジョニング
  2. フラッピング:増やす → 負荷が下がる → 減らす → 負荷が上がる、の振動 → クールダウン期間と、スケールアウトは速く・スケールインは遅く(非対称)
  3. 下流を殺す:アプリを 10 倍にすると DB のコネクションが 10 倍要求される → DB のコネクション上限が真のボトルネック。プロキシ(PgBouncer 等)で接続オーバーヘッドを減らせるが、DB のクエリ処理能力そのものは増えない
  4. コールドキャッシュ:新しいノードはキャッシュが空でレイテンシが悪い → スロースタート(LB が徐々に流量を上げる)
  5. スケールアウトできない部分:DB のプライマリ、ライセンス制限、外部 API のクォータ
📐 スケジュールドスケーリング

日次・週次の周期があるサービスでは、予測に基づく事前スケールが最も確実です(毎朝 8 時に増やす)。

34.4コスト効率

手法 効果
オートスケール 夜間・週末の削減
スポット / プリエンプティブル VM 60〜90% 安い。中断される前提のワークロード(バッチ、ステートレス)に
リザーブド / Committed Use 1〜3 年契約で 30〜70% 安い。ベースライン分に適用
右サイジング 使っていない大きなインスタンスを縮小
ストレージ階層 アクセスの少ないデータを安いクラスへ自動移行
データ転送の削減 クラウドの外向き転送は非常に高い。CDN・同一 AZ 内通信・圧縮
📐 実務の定番

ベースライン = リザーブド、変動分 = オンデマンド、バッチ = スポット。

第 34 章 一問一答

非同期ワーカーのオートスケール指標として最適なものは。
キューの長さ(または滞留時間)。
オートスケールがスパイクに間に合わない理由は。
インスタンス起動・ウォームアップに数分かかるため。予測スケールや事前確保が必要。
アプリを 10 倍にスケールする際の下流のリスクは。
DB コネクション数の枯渇。プーラーで集約する必要がある。

CHAPTER 35インシデント対応とポストモーテム

🎯 GOAL

障害対応の流れを説明でき、文化的な側面を理解する。

35.1インシデント対応の流れ

INCIDENT FLOW
① 検知    : アラート、ユーザー報告、監視ダッシュボード
② 招集    : インシデントコマンダー (IC) を立てる
③ 影響緩和: 原因究明より先に、まず止血する
④ 原因究明: 変更履歴を最初に見る(デプロイ・設定変更・フラグ)
⑤ 復旧    : 段階的にトラフィックを戻す
⑥ 記録    : タイムラインを残す
⑦ ポストモーテム: 再発防止
📐 最重要原則

「Mitigate first, diagnose later(まず止血、診断は後)」。ただし不可逆なスキーマ変更やデータ副作用がある場合、ロールバックが最も安全とは限りません。影響、可逆性、変更範囲を短時間で確認し、最低リスクの緩和策を選びます。

  • 直前のデプロイをロールバック(可逆で安全な場合)
  • トラフィックを別リージョンへ
  • フィーチャーフラグを OFF
  • 負荷制限を強める
⚠️ 落とし穴

原因が分かるまで待つのは間違いです。ユーザーは今困っています。

35.2役割分担(Incident Command System)

役割 責任
Incident Commander (IC) 全体の意思決定。自分では作業しない。次の一手を決める
Operations Lead 実際に手を動かす(1 人だけが変更を行う)
Communications Lead 社内外への状況報告、ステータスページ更新
Scribe タイムラインの記録
⚠️ 最悪のパターン

全員が同時に本番を触るのが最悪のパターンです。誰が何を変えたか分からなくなり、「直そうとして壊す」が起きます。

35.3ポストモーテム(振り返り)

Blameless(非難しない)文化が絶対の前提:「人間はミスをする。ミスを許すシステムを作れていなかったことが問題である。」

個人を責めると、次から人は情報を隠します。隠された情報は次の障害を大きくします

ポストモーテムに書くこと
1. サマリ(何が起きたか、1 段落)
2. 影響(何人のユーザーが、何分間、何ができなかったか。金額換算できれば尚良い)
3. タイムライン(分刻み。検知・エスカレーション・緩和・復旧の各時刻)
4. 根本原因(技術的な連鎖。「なぜ」を 5 回)
5. うまくいったこと / いかなかったこと / 運が良かったこと
6. アクションアイテム(担当者と期限つき。優先度も明記)
⚠️ アクションではないもの

「気をつける」「レビューを強化する」はアクションではありません。

❌ 「今後は設定変更時に注意する」
✅ 「設定変更を段階展開する仕組みを 9/30 までに実装する(担当: 山田)」
✅ 「同種の設定ミスを CI で検出する lint を追加する(担当: 佐藤、9/15)」
📐 根本原因は 1 つではない

現代の障害は複数の要因の組み合わせで起きます(スイスチーズモデル:複数の防御層の穴がたまたま一直線に並んだとき事故になる)。「1 つの根本原因」を探すより、「どの防御層を足せば止められたか」を複数挙げる方が有効です。

35.4運用の成熟度チェックリスト

MATURITY CHECKLIST
□ すべてのアラートに Runbook(対応手順書)がある
□ オンコール担当が明確で、ローテーションが持続可能(燃え尽きない)
□ 過去のポストモーテムが検索でき、読まれている
□ アクションアイテムの完了率が追跡されている
□ 障害訓練(ゲームデー)を定期実施している
□ 新メンバーが 1 週間以内にオンコールに入れる(ドキュメントの質)
□ 「切り戻し」が誰でも 5 分でできる
🎤 面接

SCRIPT
「障害時はまず影響の緩和を優先し、
 ロールバックとフィーチャーフラグの無効化で止血します。
 原因究明はその後です。ポストモーテムは非難なしで行い、
 『人が気をつける』ではなく『仕組みで防ぐ』アクションに落とし込みます。」

第 35 章 一問一答

インシデント対応の最優先事項は。
原因究明ではなく影響の緩和(止血)。
Blameless ポストモーテムの理由は。
個人を責めると情報が隠され、次の障害が悪化するから。
良いアクションアイテムの条件は。
具体的な仕組みの変更で、担当者と期限がある。