メインコンテンツまでスキップ

ポストモーテム

復旧が完了し、サービスは正常に戻りました。しかしここで終わりではありません。

同じ障害が来月また起きたら?今度は深夜に起きたら?別の人が対応したら同じ速さで復旧できる?同じ障害を二度と起こさないための振り返りがポストモーテムです。


ポストモーテムとは

ポストモーテム ≠ 犯人探し

❌ 「誰がバグを入れたのか」を追及する場
✅ 「なぜシステムがこの障害を防げなかったか」を分析する場

目的:
├── 根本原因を特定する
├── 再発防止策を決める
└── 組織の学習として共有する

いつポストモーテムを書くか

条件ポストモーテム
Sev 1(全テナント影響)必須
Sev 2(一部テナント影響)必須
Sev 3(性能劣化)推奨
Sev 4(軽度)任意
障害にならなかったが危なかった(ニアミス)推奨

ニアミスのポストモーテムが最も価値がある。 実害がなかったからこそ冷静に分析でき、同じパターンの本当の障害を防げる。


ポストモーテムのテンプレート

基本情報

# ポストモーテム: {タイトル}

## 基本情報
- **日時**: YYYY-MM-DD HH:MM 〜 HH:MM JST
- **影響時間**: X時間Y分
- **重大度**: Sev X
- **影響範囲**: {全テナント / 特定テナント / 特定機能}
- **対応者**: {名前}
- **ステータス**: {対応完了 / 恒久対応中}

タイムライン

## タイムライン

| 時刻 | イベント |
|------|---------|
| 09:00 | デプロイ開始(v2.1.0) |
| 09:15 | デプロイ完了 |
| 09:25 | CloudWatch Alarm: 5xx 率 > 1% |
| 09:27 | IC が Sev 2 を宣言 |
| 09:30 | DB コネクション枯渇を確認 |
| 09:35 | ロールバック開始 |
| 09:38 | ロールバック完了、5xx 率が 0.1% に低下 |
| 09:45 | 復旧宣言 |

影響

## 影響

- エラーを受けたリクエスト数: 約 1,200 件
- 影響を受けたユニークユーザー数: 約 300 人
- 影響時間: 13分(09:25 〜 09:38)
- SLA 影響: 月間 99.9% 目標のエラーバジェット(約43分)のうち約13分を消費

根本原因

## 根本原因

v2.1.0 でコネクションプールの minimum-idle を 10 → 30 に変更。
4 インスタンス × 30 接続 = 120 で DB の max_connections (150) に近づき、
他のサービスのコネクション確保が困難になった。

直接の原因: 設定変更のレビューで接続数の総量計算が漏れた
根底の原因: コネクションプール設定の変更がコードレビューのチェック項目に含まれていなかった

対応内容

## 対応内容

### 暫定対応(実施済み)
- v2.1.0 → v2.0.0 にロールバック

### 恒久対応(予定)
- [ ] minimum-idle を 10 に戻し、max-pool-size のみ 20 → 30 に変更
- [ ] コネクション総量計算をリリースチェックリストに追加
- [ ] DB の接続数 / max_connections 比率のアラートを追加(80% で Sev 3)
- [ ] 期限: 2026-04-05

再発防止

## 再発防止

### 検知の改善
- DB の接続数 / max_connections 比率のアラートを追加

### プロセスの改善
- コネクションプール設定変更時は「インスタンス数 × pool-size ≤ max_connections の 70%」を確認

### 設計の改善
- コネクションプーリングプロキシの導入検討(PgBouncer / RDS Proxy 等)

学んだこと

## 学んだこと

- コネクションプールの minimum-idle は「1インスタンスあたり」だが、
max_connections は「DB 全体」。掛け算を忘れると枯渇する
- ロールバック(Blue-Green)が3分で完了した。投資の価値があった
- アラートが 5xx > 1% で鳴ったが、5xx > 0.5% に下げてもよかった

5 Whys(なぜなぜ分析)

根本原因を深掘りするテクニック。表面的な原因で止まらず、5回「なぜ?」を繰り返す。

実事例(#1442 ロック競合):

なぜ statistics_events の INSERT が 1.94秒かかった?
→ 同一行へのロック待ちが発生していたから

なぜロック待ちが発生した?
→ 統計 UPDATE(ロック取得)の後にフック実行(450ms I/O)が走り、
ロック保持時間が 500ms+ になっていたから

なぜロック保持中にI/Oが走る設計だった?
→ SecurityEventHandler.handle() が全処理を1トランザクションで
順番に実行する設計だったから

なぜ1トランザクションで全処理を実行する設計だった?
→ 当初はフック実行がなく、統計更新だけだった。
フック機能の追加時に全体の設計を見直さなかったから

なぜフック追加時に設計を見直さなかった?
→ トランザクション内で外部I/Oを行うリスクが認識されていなかったから

→ 根本原因: トランザクション内I/Oのアンチパターンに関する知識不足
→ 対策: 学習コンテンツの整備(PostgreSQL ロック実践 + ケーススタディ)

ポストモーテムの共有

書いて終わりではなく、チームで共有する:

① ポストモーテムレビュー会議(30分)
・タイムラインの確認
・根本原因の議論
・再発防止策の合意
・アクションアイテムの担当者と期限

② ドキュメントとして保存
・Wiki / Notion / GitHub Issues
・検索可能な場所に保存
・類似障害が発生したときに参照できるように

③ ランブックの更新
・今回の障害パターンと対応手順をランブックに追加
・アラートの閾値調整

まとめ

ポストモーテムの価値:

・犯人探しではなく、システムの弱点を見つける場
・暫定対応 + 恒久対応 + 再発防止のアクションを決める
・5 Whys で表面的な原因の奥にある根本原因を掘る
・書いて、共有して、ランブックを更新して、初めて完了

次のステップ