さくらコードSakura Code
小説/第11話

Novel

第11話 深夜の障害対応

第11話 深夜の障害対応のイメージイラスト

Script

Cold Open

シーン1 二十三時のSlack

 十月十三日。二十三時。

 秋雨の合間の、湿った夜だった。窓を、ほんの少しだけ開けると、外の空気が、夏のころより、ずっと、密度を持って、入ってくる。桜子は、入浴を終えて、パジャマの上に薄手のカーディガンを羽織り、ベッドの脇でスマホの充電ケーブルを差そうとしていた。

 画面の上部に、通知が、立て続けに鳴った。

[ALERT] sakura-support api: 5xx surge (12 errors / 60s)
[ALERT] sakura-support api: 5xx surge continues (47 errors / 5min)
[ALERT] severity: P2

 胃のあたりが、ぎゅっと冷たくなった。

 数秒後、鈴木からのDM。

@suzuki-kenichi: 桜子さん、もし起きてたら、参加してみない?
今夜は P2 だから、現場に立ち会うだけでいい。
オンコールは僕と山下さん。中村くんも入る予定
無理しないで、断ってくれていい

 桜子は、しばらく画面を見つめてから、返信を打った。

@mochizuki-sakurako: 起きてます。
いまから入ります。
よろしくお願いします

 送信ボタンを押すと、心臓が、自分が思っていたより速く打っていることに気づいた。


Part A

シーン2 ビデオ通話の中の三人

深夜のビデオ通話。四分割の画面に鈴木、遥、翔太、桜子が映り、桜子はタイムラインを記録している

 Google Meet のリンクを開く。

 画面の左上に、鈴木。背景は、彼の自宅のリビングだろう、薄暗く、本棚の影が見える。中央に、遥。落ち着いた色のTシャツ姿で、すでにエディタとログ画面を並べている。右上に、翔太。Tシャツ姿、すでに目が冴えている。

 桜子のカメラが映ると、鈴木が、いつもの穏やかな声色のまま、言った。

「桜子さん、夜分にありがとう。よろしくね」

「お、おはようございます。じゃない、ええと、こんばんは」

 Meet の画面で、遥が、ほんの少しだけ笑った。

「いまから、状況、整理する。桜子さんは、記録役、お願いできる?」

「記録、ですか」

「うん。社内wikiの障害対応ページに、新しいタイムラインを切ってほしい。あとは、ここで誰が何を提案して、何を試して、何が分かったかを、五分おきくらいに事実だけ書いていく。それが、桜子さんの今夜の仕事」

「……はい、やります」

 桜子は、wiki の incidents/ の下に、新しいページを作った。

# Incident: 2022-10-13 23:10 sakura-support api 5xx surge
- severity: P2
- status: investigating
- on-call: 鈴木健一 (lead) / 山下遥 / 中村翔太
- recorder: 望月桜子

## Timeline (JST)

 手が、少し震えた。


シーン3 5xxの正体を追う

 遥が、Meet の画面共有で、ログ集約ツールを開いた。

「いま見てるのは、サクラサポートの api ロール、過去30分。/api/v1/inquiries/{id} で 500、平均で毎秒0.8件。10分前から、徐々に増えて、いまピーク」

 翔太が、すかさず仮説を並べる。

「直近のデプロイは、僕の #118 と、桜子さんの #103。#118 は、JOINに変えただけだから、5xxを引く理由は、たぶん、ない。#103 は、Redisキャッシュ。Redisが落ちてたら、説明はつく」

 鈴木が、

「Redis、生きてる?」

「生きてます。レイテンシも普通」

「桜子さんのフラグは、いま、どう?」

 桜子は、自分のwiki画面と、featureflags.yaml の現在状態を、別タブで開いた。

「unreadcacheenabled: true、有効です」

「OK。すぐに切れる選択肢がある、ってことだけ、頭に置いとこう」

 遥が、ログをさらに絞った。

「5xxの中身、トレースバックを見ると、外部に投げてる『顧客企業マスタ取得API』からのレスポンスがタイムアウトしてる」

「それ、うちのじゃない外部API?」

「うん。外部の、子会社が運用してる方。先月から契約変えたところ」

 Meet の画面で、四人とも、一瞬、口を閉じた。

 四人分の、息遣いだけが、マイク越しに、低く、つながっていた。鈴木のキーボードを叩く音。遥のマウスのスクロール音。翔太の、小さく舌打ちしたあとの、慌てた咳払い。それぞれの自宅の、それぞれの夜の音が、薄く、Meetの画面の中で、混ざっていた。

 沈黙は、たぶん、十秒もなかった。けれど、その十秒は、桜子の身体の中では、もっと、ずっと長かった。

 (誰も、何も、言わない時間って、こんなに、こわいんだ)

 桜子は、手のひらが、また、わずかに、湿ったのを、感じた。沈黙の中で、桜子の指は、知らず知らず、wikiの編集画面のテキストエリアの上で、待機の姿勢を、続けていた。書くことが、思いつかない時間でも、書く準備の手だけは、机の上に、置いておく。それだけが、いま、桜子に出来ることだった。

 桜子は、聞こえてくる事実だけを、wiki に書き続けた。

- 23:10 5xx surge detected, severity P2 raised
- 23:14 on-call assembled (鈴木/山下/中村/望月)
- 23:18 5xx の主体は /api/v1/inquiries/{id}
- 23:22 直近デプロイ: #118 (JOIN), #103 (Redis cache)
- 23:24 Redis health OK, latency normal
- 23:25 unread_cache_enabled: true (有効、切替可能)
- 23:27 5xx のトレース: 外部「顧客企業マスタ取得API」のタイムアウト

Part B

シーン4 タイムラインを書く

「先月、契約変えてから、向こうの安定度の話、ちゃんと出てなかったよね」

 遥が、画面を見たまま、言った。

「うん。SLA、確認しよう」

 鈴木が、別のタブで、契約資料のページを開く。SLAは99.5%の表記。月単位で換算すると、月に何時間かは落ちうる、という前提だ。

 翔太が、

「いま、向こうの公開ステータスページ、見てます。あ、十五分前に、向こう側で、incident を立ててます。タイムアウト多発、調査中、って」

 桜子は、書く。

- 23:32 契約資料: 外部APIのSLAは99.5% (月単位換算で残時間あり)
- 23:34 外部側ステータスページに incident あり
        (23:17 開始, "increased latency, investigating")

 Meet の中で、鈴木が、しばらく考えてから、言った。

「ここで、二つ、選択肢がある」

「一つ。直近デプロイの何かが、結果として、外部APIのタイムアウトを誘発してる、と仮定して、#118 か #103 を戻す」

「もう一つ。原因は外部側、と仮定して、向こうの復旧を待つ。我々側は、5xxを返さないように、フォールバック処理(顧客企業情報なしで詳細表示)を一時的に出す」

 遥が、

「#118 は、JOIN追加だけ。外部APIを叩く頻度は、変えてない。だから、戻しても、たぶん、5xxは止まらない」

「#103 は、Redisキャッシュ。これを切ると、countunread が全件ロードに戻って、別の負荷が出るだけ。5xxの直接の原因とは、つながらない」

「結論として、戻すべき直近デプロイは、ないと思う」

 鈴木が、

「同意。じゃあ、フォールバックの一時挿入と、向こうの復旧を待つ、で行こう。フォールバックの実装、翔太くん、できる?」

「やります。フィーチャーフラグで、/api/v1/inquiries/{id} で外部API失敗時に、顧客企業欄をnullで返すモード。十分でPR出します」


シーン5 戻すべきか、踏み込むべきか

 その間、桜子は、ログ件数を時系列でグラフにして、Meet にペタッと貼り付けた。

時間帯        5xx 件数
23:10-23:15   12
23:15-23:20   28
23:20-23:25   47
23:25-23:30   52
23:30-23:35   55 (ほぼ横ばい)

「五分前から、ほぼ、横ばいです」

 遥が、

「そう。たぶん、外部API側がもう悪化してない。いま、こっちが踏み込んで戻すより、待つほうが、合ってる」

 鈴木が、

「桜子さん、その横ばいの数字、すごく助かる」

「あ、はい」

 桜子は、自分の頬が、急に熱くなったのを、画面の暗さの中で、どうにか、隠した。

 ほんの、ひと拍だけ、桜子は、自分の手元の wiki ページを、見下ろした。

 切り抜き、コピー、貼り付け。たったそれだけの動作で、五分おきに増えていった、二十数行の事実の連なり。判断はしていない。仮説も立てていない。コードも書いていない。書いたのは、ただの、時刻と、誰の発言と、何が分かったか、の、ログ。

 (書いた、だけだ)

 でも、その「ただの、ログ」が、いま、いま、こっちが踏み込まないほうが、合ってる という、四人の判断の、台座になっていた。

 桜子は、四月の初出社の朝、駅までの坂道で、みんな、もう仕事してる と思って、自分は何もしていない、と感じたあの感覚を、ふっと、思い出した。

 (記録は、何もしていない、ことじゃ、なかった)

 その小さな書き換えが、桜子の中で、起きた。

 翔太のフォールバックPR #125 が、その十分後に届いた。レビューは遥が即時、Approved。鈴木が Merged。デプロイは、十一分で完了。新しいリクエストから、5xxは止まり、フォールバックの200が返る。

 二十三時五十分過ぎ。Meetの画面の中で、四人がほぼ同時に、息を吐いた。

- 23:48 フォールバックPR #125 デプロイ完了
- 23:49 5xx 停止、フォールバック200のみ
- 23:54 外部APIのステータスページ: latency restored
- 23:58 フォールバックOFFのPR #126 マージ予定 (明朝)
- 00:02 状況: stable

 桜子は、wiki のステータスを、investigating から mitigated に書き換えた。


Part C

シーン6 復旧と、フラグの記憶

 深夜零時を回ったところで、鈴木が、

「お疲れさま。今夜は、ここで一旦、解散しよう。明日の朝、postmortem ミーティング、十時から。桜子さん、タイムライン、そのままドラフトに使います」

「はい、ありがとうございます」

「無理しないで。明日の朝は、ミーティングに間に合えば、それでいいから」

 全員が「お疲れさまです」と返して、Meet が静かに切れた。

 画面の中で、桜子だけが、まだ少し、ぼーっとしていた。

 ふと、Slackで、遥からDMが来ていた。

@yamashita-haruka: 今日、桜子さんのフラグは、結局、触らなかったね。
でも、「すぐ切れる選択肢がある」って状態は、判断を軽くした。
使わなかったけど、あって、よかった

 桜子は、しばらくその文字を眺めてから、

@mochizuki-sakurako: ありがとうございます。
記録、ぜんぶ、ちゃんと、残せてたか、不安です

 と返した。

 遥の返事は、すぐ来た。

@yamashita-haruka: 大丈夫。読みやすかったよ。
横ばいの数字、あれ、PMが翌朝、お客さんに説明するとき、
ほぼそのまま使えるかたちになってる

 桜子は、メモ帳を開きかけて、いま、深夜だと気づいた。

 明日、書こう。

 ベッドの上で、ノートPCを閉じた。エアコンのかすかな音と、外を走るタクシーの音が、秋の夜の中で、混ざっていた。


Ending

シーン7 翌朝のpostmortem下書き

 翌朝。

 桜子は、起きるのに、いつもの倍くらい、時間がかかった。アラームを止めてから、ベッドの上で、しばらく、天井を、見ていた。身体の重さは、寝不足というより、昨夜の三時間で、自分の中の何かを、ひと回り、使い切ったあとの、独特の、重さだった。

 駅までの道は、いつもと同じ景色のはずだった。けれど、街路樹の下を歩く自分の足音が、いつもより、ほんの少しだけ、遅く、聞こえた。改札を抜けるときの、Suicaのピッ、という音さえ、頭の奥のほうで、半拍、遅れて、響いた。

 オフィスに着いた桜子は、自分の机に、鈴木が置いてくれたペットボトルのお茶を、見つけた。冷蔵庫から出して時間が経っているらしく、ボトルの肩のあたりに、結露の粒が、ゆっくり、流れた跡を残している。封のされていない、ただの「ありがとう」のしるしだった。

 桜子は、お茶のキャップを開けて、ひとくち、飲んだ。お茶の温度は、もう、室温に近かった。けれど、その薄い温度が、いまの桜子の喉には、ちょうど、合っていた。

 会議室「桜」のドアを開けると、鈴木と遥と翔太が、もう、いた。三人とも、顔色が、桜子と同じくらい、薄かった。翔太は、コーヒーを、両手で、抱えるように持っていた。遥のキーボードの横には、紙コップが、二杯、並んでいた。

「お疲れさま」

 鈴木が、いつもより、半トーンだけ、低い声で、言った。

「お疲れさまです」

 桜子も、半トーンだけ、低く、返した。

 誰も、まだ、本題には、入らなかった。三人とも、ほんの少しだけ、椅子に深く腰を下ろし直して、自分の身体の、置きどころを、整えていた。桜子は、自分の身体の重さが、自分だけのものではないことを、その三人の身体の重さの中に、置きどころを、見つけた。

 十時から始まったpostmortem ミーティングは、淡々と進んだ。鈴木が「桜子さんの夜のタイムライン、そのままベースで書いていきます」と言って、見慣れた障害報告テンプレに、夜の事実が流し込まれていく。

## 障害報告: 2022-10-13 sakura-support api 5xx surge (P2)

- 発生日時: 2022-10-13 23:10 JST 検知
- 検知日時: 2022-10-13 23:10 JST (アラート自動発火)
- 影響範囲: お問い合わせ詳細API /api/v1/inquiries/{id} の一部に 5xx
  実利用への影響: お問い合わせ詳細を開けないユーザー、約120件 (推定)
  業務影響: サポート担当の対応が、約45分間、ブロック
- 事象: 詳細API の 5xx が、最大で毎秒0.8件、約45分継続
- 一次原因: 外部「顧客企業マスタ取得API」のタイムアウト多発 (外部側 incident)
- 暫定対応: 外部API失敗時のフォールバック (#125) を導入し 200 で返す
- 恒久対応:
  - 外部APIの呼び出しに、明示的なタイムアウトとリトライ上限を入れる (#127 起票)
  - 外部API失敗時のフォールバックを、フラグでデフォルトON運用に変更検討 (#128 起票)
- 再発防止:
  - 外部依存の状態確認手順を Runbook に追加
    (公開ステータスページのURL、SLA、過去のincident頻度)
  - 月次で、外部依存の incident 履歴をレビューする会を設定 (鈴木)
- 学び:
  - 直近デプロイがあったとき、つい戻したくなるが、
    原因が外部側にあるかどうかを先に切り分けると、判断が速くなる
  - 「使わなかった停止手段」も、判断の幅として価値がある

 桜子は、再発防止の欄に、自分の名前で、一行だけ、提案を書いた。

- (望月) 外部依存ごとに、Runbook に
  "公開ステータスページURL / SLA / 過去incident頻度" の欄を必ず置く

 鈴木が、それを読んで、軽くうなずいた。

「これ、いいね。やろう」

 ミーティング後、桜子は、自分のメモ帳を開いた。

2022/10/14 新人日記

- 初・本番障害(P2)
- 自分の役割は「記録」
- 五分おきに事実を書く。仮説と判断は、書く人ではなく、
  チームで分担する
- 「すぐ切れる選択肢がある」だけで、判断は軽くなる
- 直近デプロイより、外部依存の状態を、まず見ること
- 戻すか、待つか、踏み込むか。三択を並べてから決める

 ペンを置いて、桜子は、窓の外を見た。

 秋分を過ぎた空に、雲の切れ間から、細い光が差し込んでいた。

 たぶん、何ヶ月かしたら、また、夜のアラートが鳴る。次は、自分が、もう少しだけ、踏み込んだ役割で出るかもしれない。記録から、調査へ。調査から、判断へ。

 (運用は、夜にも、ある)

 その当たり前を、桜子は、初めて、自分の身体の側から、知った。

 お問い合わせ詳細を開けなかった、推定百二十人の顔は、桜子には見えなかった。けれど、その百二十回分の「あれ、開かない」を、深夜の四人が、引き受けた。

 明日からの仕事は、その百二十人のうちの誰かが、また、安心してお問い合わせを開けるための続きだ。

 ミーティングのあと、桜子は、トイレで、洗面台の鏡の前に、しばらく、立っていた。

 目の下に、薄い、青い影が、ふたつ、ある。普段の自分の顔とは、別の人の顔のようで、桜子は、しばらく、その顔と、自分との距離を、測れずにいた。蛇口をひねって、冷たい水を、両手のひらに、ためた。それを、目元の下に、軽く、当てる。

 冷たかった。

 いつもの「冷たい」とは、また、別の冷たさだった。睡眠不足の皮膚に、秋の水道水の温度は、いっそう、深く、染みてくる。

 顔を上げて、もう一度、鏡を、見る。

 目の下の影は、消えなかった。けれど、目そのものは、たぶん、昨日の朝よりも、ほんの少しだけ、奥の方が、しっかりしているように、見えた。

 桜子は、ハンドペーパーで、顔を、軽く、押さえた。

 席に戻る通路で、翔太と、すれ違った。翔太も、目の下に、桜子と同じくらい、青い影を、ふたつ、持っていた。お互い、目だけで、軽く、うなずきあった。言葉は、いらなかった。同じ夜を、同じMeetの画面の中で、起きていた人どうしの、無言の挨拶だった。

 席に戻ると、桜子は、ペットボトルのお茶の、残りを、もうひと口、飲んだ。室温の、薄い温度。今度は、喉の奥のほうまで、ゆっくり、降りていった。