はじめに
β リリース直前のメンテナンスモード設計を詰めている最中、「エッジ層 (Cloudflare Worker による停止) は β 中はオーバーキルではないか」と自問しました。SLA を規約で適用しないと宣言済みなのに、なぜそこまで作り込むのか。
たどり着いた結論は「β 前にしかできない drill があるからこそ、リリース前に完成させる」でした。本記事はその思考プロセスの記録です。
状況設定
私たちのメンテナンスモード設計は エッジ層 + アプリ層の 2 段防御 で組みました:
- アプリ層: NestJS の Global Guard で feature flag テーブルを read し、READ-ONLY / kill-switch / FULL_STOP を判定
- エッジ層: Cloudflare Worker + KV でエッジレベルの即停止。DB / Cloud Run が落ちていても 503 を返せる
アプリ層だけで対応できないシナリオは限定的です:
- リージョン全停止: クラウドのリージョンが丸ごと停止 (極低確率)
- Central DB 停止: 中央データベースの停止 (低確率)
- セキュリティインシデント: 低確率、しかし発生時は致命的
β 期間 (1 年) で発生する期待値だけ見ると、アプリ層のみで十分そうに見えます。エッジ層を作らない選択肢は十分に合理的でした。
「SLA なし」と「責任放棄」は違う
利用規約で 「β 期間中は SLA を適用しません」 と定めました。これは法務的には「停止しても規約上の責任なし」を意味しますが、運用思想としては別問題です。
たとえばセキュリティインシデントを考えます:
- 情報漏洩中、エッジレベルで全停止できないと 証跡記録だけが流れ続ける 状態になります
- 規約で「責任なし」と定めていても、個人情報保護法 / GDPR の通知義務 は別途存在します
- 「ベータだから止められません」では法的にも商業的にも擁護できません
ここで気づくのは、「規約で SLA なし宣言」=「停止経路を作らなくていい」ではない ということです。両者は別レイヤーの話で、規約が緩いほど技術的な責任は逆に重くなるとすら言えます。
β 前にしか実施できないドリル
もう一つの観点が、ドリル実施タイミングの非可逆性 です。
エッジ層が想定するシナリオ — DB 全停止のドリルを正しく検証するには、
# STG の Cloud SQL を意図的に止める
gcloud sql instances patch your-stg-central --activation-policy=NEVER
を実行して、本番に近い障害を発生させる必要があります。これを β リリース後にやると、次のような事態になります:
- 在席している顧客のデータが失われる (RTO/RPO の検証中はバックアップ復旧経路を試している)
- 顧客から問い合わせが殺到する
- 復旧時間中の損失が発生する
β 前に同じ drill をやれば、次のように進められます:
- 顧客は誰もいない (= 損失ゼロ)
- 復旧ミスをしても誰にも迷惑がかからない
- 「何分で復旧できるか」「手順書のどこが詰まったか」を時計を見ながら計測できる
- 運用手順書に結果を追記する余裕がある
「失敗しても安全な環境で失敗してみる」のが drill の本質 であり、それができるのは β 前だけです。
DR drill の 4 種類
設計ドキュメントには 4 種の drill を運用手順書化しました:
| # | drill | 想定シナリオ | 所要時間 |
|---|---|---|---|
| 1 | アプリ層 READ-ONLY | 計画 migration | 30 分 |
| 2 | アプリ層 kill-switch | endpoint 暴走 | 20 分 |
| 3 | エッジ層 Cloudflare Worker | DB 全停止 | 45 分 |
| 4 | 完全 dark scenario | セキュリティ事案 | 60 分 |
特に #3 (エッジ層の drill) は 本番でやれば即インシデント なので、STG で意図的に DB を停止する drill を入れています。本番でしか出ない挙動 (CF Worker の KV キャッシュ伝播 30-60 秒、エッジ別の挙動差) を 想定通り 503 が出るか で確認できるのは STG / 本番に近い構成だけです。
「コスト < 期待ダメージ」では判断しない
製品開発で機能採否を判断するとき、しばしば 「実装コスト < 発生確率 × ダメージ」 で評価します。エッジ層の場合、この計算式だと次のようになります:
- 実装コスト: Cloudflare Worker 1-2 日
- 発生確率: DB 全停止 (年 < 1 回) + セキュリティインシデント (年 < 1 回)
- ダメージ (セキュリティインシデント): 法令対応 + 顧客信頼喪失 (定量化困難)
確率ベースでセキュリティインシデントは「年 1 回未満」だとしても、発生時のダメージが法的影響を伴うため期待値計算では弾けません。「ダメージが法的責任 / 顧客信頼に及ぶケースは確率にかかわらず作る」 という判断軸が必要です。
これは保険の発想に近いものです。火災の発生確率は年 < 1% でも、火災保険には多くの人が入ります。発生時のダメージが定量化可能なリスク許容範囲を超えるからです。
β リリース前完成スコープに含めた理由
最終的に β リリース前完成必須として以下を入れました:
- ✅ エッジ層 (CF Worker + KV + Access)
- ✅ アプリ層 (NestJS Guard + feature flag テーブル)
- ✅ 管理コンソール UI (フラグ切替 + kill-switch 管理)
- ✅ 3 アプリ メンテバナー (ローカルファースト準拠)
- ✅ 監査ログ + 運用通知メール
- 🟡 STG / PRD デプロイ + DR drill 4 種実施 ← これが本記事の焦点
drill そのものは数時間で終わりますが、その経験が運用手順書の精度を決めます。書いたきりの手順書は障害時に役に立ちません。実際に時計を見ながら手順を踏んで、「ここで詰まった」「コマンドが違った」を手順書に書き戻すまでが drill です。
まとめ
| 観点 | 結論 |
|---|---|
| 規約で SLA なし宣言済 | それでもエッジ層の停止経路は作ります (法令 / 信頼 / 商業性の論理) |
| ドリル実施タイミング | β 前が唯一です (顧客在席後は drill 自体がリスクになります) |
| 実装コスト判断軸 | 期待値計算で弾けない法的リスクは確率に関わらず作ります |
| 運用手順書の精度 | drill 実施後に「詰まった所」を書き戻すまでが完成です |
「β なので最低限でいいよね」という判断は、規約の話と運用の話を混同しがちです。β は機能要件を絞る期間であって、運用堅牢性を犠牲にする期間ではありません。むしろ顧客が少ない今が、運用堅牢性を仕込む最後のチャンスです。
私たちは来週から STG drill を始めます。所要時間と詰まった所を運用手順書に書き戻したら、また続編を書こうと思います。