デプロイ直後だけエラーが出る
数分で自然に収まるエラーの正体を特定できる。ウォームアップ・新旧混在・接続の張り替え・マイグレーション順序という4つの原因を切り分け、ロールバックすべきか待つべきかを根拠を持って判断できる。
- 自然に収まるエラーは「収まったから解決」ではない。デプロイのたびに一定数のユーザーが確実に踏んでおり、原因はほぼ4つのどれかに特定できる。
- 原因はウォームアップ(JIT・キャッシュ空・接続確立)、新旧バージョンの混在、接続の張り替え、そしてスキーマ変更とコード配布の順序ずれ。
- 切り分けの決め手は継続時間と分布。数十秒で収まりインスタンス起動と相関するならウォームアップ、ロールアウト完了まで続き旧バージョンだけで出るなら混在、DB絡みならマイグレーション順序。
デプロイした直後の数分間だけエラー率が跳ね上がり、放っておくと自然に収まる。グラフが元に戻るので「様子見」で流されがちですが、これは毎回のデプロイで確実に一定数のユーザーが失敗を踏んでいるという状態です。1日に何度もデプロイする運用なら、その回数だけ積み上がります。
幸いこの症状は原因の候補が少なく、継続時間と、どのインスタンスで出ているかの分布を見るだけでほぼ絞り込めます。
4つの原因と、それぞれの見え方
横にスクロール
| 原因 | 継続時間 | どこで出るか | 収まり方 |
|---|---|---|---|
| ウォームアップ | 起動から数十秒〜数分 | 新しく起動したインスタンスのみ | そのインスタンスが温まると収まる |
| 新旧バージョンの混在 | ロールアウト完了まで | 旧バージョン側、または特定の組み合わせ | 全台が新版になった瞬間に収まる |
| 接続の張り替え | 切り替え直後の数秒〜数十秒 | 全体に薄く、瞬間的に集中 | コネクションが再確立されると収まる |
| マイグレーション順序 | 適用からコード配布完了まで | スキーマとコードが食い違う側 | 順序が揃うまで収まらない |
原因1: ウォームアップ
新しいプロセスは、起動した瞬間が最も遅く、最も脆い状態です。
- JIT がまだ効いていない。 最初の数百〜数千リクエストはインタプリタ実行に近く、数倍遅いことがあります。
- キャッシュが空。 アプリ内キャッシュも、その先のデータベースのバッファプールも空なので、すべてがミスします。
- 接続プールが未確立。 最初のリクエストが接続確立を待たされます。
- 遅延初期化。 初回アクセス時に構築されるものが、最初の1件に全部乗ります。
ヘルスチェックが通った直後に全開の負荷を流すと、この最も弱い瞬間に本番トラフィックが当たります。エラーではなくタイムアウトとして観測されることが多いのが特徴です。
対処は3点セットです。readiness と liveness を分ける(起動途中は readiness を通さない)、ウォームアップ処理を起動時に走らせる(主要な経路を自分で数回叩いてから readiness を通す)、そしてスロースタート(ロードバランサ側で新規インスタンスへの重みを徐々に上げる)。
両者を同じエンドポイントにすると、ウォームアップ中に liveness が失敗して起動途中のプロセスが再起動され続けるという最悪の状態に陥ります。liveness は「プロセスが壊れていないか」、readiness は「トラフィックを受けられるか」で、聞いている問いがまったく違います。起動が遅いアプリでは、さらに startup probe を分けるのが安全です。
原因2: 新旧バージョンの混在
ローリングアップデート中は、新旧のバージョンが同時に本番で動いています。これは一時的な状態ではなく、正しく設計すべき状態です。混在で壊れるのは主に次の3つです。
共有状態の形式変更。 セッションやキャッシュに保存するデータの形を変えると、旧版が書いたものを新版が読めない(またはその逆)が起きます。
旧版が書いたキャッシュ {"name": "..."}
新版が期待する形式 {"profile": {"name": "..."}}
→ 新版が旧版のキャッシュを読んだ瞬間に例外
APIの契約変更。 内部サービス間で、呼び出し側だけ先に新しくなると、まだ古い受け手が新しいフィールドを理解できません。
永続化されたジョブ・メッセージ。 キューに旧版が積んだメッセージを新版が処理する(またはその逆)。デプロイをまたいで残るデータはすべて混在の対象です。
対処は変更を2段階に分けることです。まず「新旧どちらの形式も読めるが、書くのは旧形式」を配布し、全台が入れ替わってから「新形式で書く」を配布します。読める→書く の順で、1回のデプロイに1段階だけ。これは列の追加と削除でも同じで、「使わなくなった列を消す」のはコードが全台入れ替わった後にしか行えません。
原因3: 接続の張り替え
デプロイでインスタンスが入れ替わると、確立済みの接続が切れます。ここで問題になるのは切り方です。
- グレースフルシャットダウンが無い。 終了シグナルを受けた瞬間に処理中のリクエストごと落ちる。処理中だったユーザーはエラーになります。
- ロードバランサからの切り離しが遅い。 プロセスが終了してもLBがまだ振り分けており、その間の振り分けは接続拒否になります。先に振り分けを止め、猶予を置いてから終了する順序が要ります。
- 接続がキャッシュされている。 クライアント側がkeep-aliveで掴んでいる接続が、サーバー消滅で切れます。クライアントに再試行が無いと、そのまま失敗として返ります。
- DNSのTTLが長い。 切り替えをDNSで行っている場合、キャッシュが切れるまで古い宛先に行き続けます。
対処はシャットダウンの順序を明示することです。(1) readiness を落とす → (2) LBが気づく時間を待つ → (3) 新規受付を止める → (4) 処理中のリクエストの完了を待つ → (5) 終了。この (2) の待ちを省略するのが、この症状の最頻の原因です。クライアント側の再試行設計は指数バックオフとジッタ可視化も参考になります。
多数のクライアントが同時に接続を張り直すと、その瞬間に接続確立とTLSハンドシェイクが集中します。これは復旧直後のリトライストームと同じ構造で、キャッシュが空であることと重なって二重に効きます。デプロイを段階的にする(一度に入れ替える台数を絞る)だけで、この山は大きく下がります。
原因4: マイグレーションとコード配布の順序
スキーマ変更を伴うデプロイでは、DBの状態とコードの状態が一時的に食い違います。どちらが先に進んでも壊れるパターンがあります。
マイグレーションが先、コードが後
→ 旧コードが「消された列」や「変わった型」に触れて落ちる
コードが先、マイグレーションが後
→ 新コードが「まだ無い列」を読もうとして落ちる
安全な順序は決まっていて、追加は先、削除は後です。
- 列やテーブルの追加(既存コードに影響しない形で)をマイグレーション
- 新旧どちらでも動くコードを配布
- データの移行
- 全台の入れ替え完了を確認
- 使わなくなった列やテーブルの削除をマイグレーション
NOT NULL 制約の追加や型変更は、この流れの中で「デフォルト付きで追加 → 書き込み開始 → バックフィル → 制約付与」と分解します。1回のデプロイで完結させようとすることが、そもそもの原因です。
切り分けの手順
1. エラーの継続時間を測る
数十秒で収まる → ウォームアップ(原因1)
ロールアウト完了まで続く → 混在(原因2)または順序(原因4)
2. どのインスタンスで出ているかを分ける
新しいインスタンスだけ → ウォームアップ
古いインスタンスだけ → 混在。旧版が新しいデータ/スキーマを踏んでいる
全体に薄く瞬間的 → 接続の張り替え(原因3)
3. エラーの内容がDBやスキーマに触れているか
触れている → 順序(原因4)。ロールバックの可否をまず確認する
原因4のとき、コードだけロールバックしても DB は前に進んだままです。旧コードが新スキーマで動くかどうかを確認せずにロールバックすると、被害が拡大します。「追加は先、削除は後」を守っていれば旧コードは新スキーマでも動くので、この規律はロールバック可能性そのものを担保する仕組みでもあります。
まとめ
- 自然に収まっても毎回のデプロイでユーザーが確実に踏んでいる。原因は4つに絞れる。
- 継続時間と分布で切り分ける。数十秒+新インスタンスのみならウォームアップ、ロールアウト完了までなら混在、瞬間的で全体に薄いなら接続の張り替え。
- ウォームアップ対策は readiness と liveness の分離・起動時の自己ウォームアップ・スロースタート。両プローブを同じにすると起動中のプロセスが再起動され続ける。
- 混在は避けられない状態として設計する。読める→書く の順で、1回のデプロイに1段階だけ。
- シャットダウンは readiness を落とす→LBが気づくのを待つ→新規停止→処理中の完了待ち→終了。この「待ち」の省略が最頻の原因。
- スキーマ変更は追加は先、削除は後。この規律がロールバック可能性そのものを担保する。
トラブルシュート実戦の記事ガイド
デプロイ直後だけエラーが出るを実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
トラブルシュート
比較で見る軸
難易度: advanced / カテゴリ: トラブルシュート実戦 / タグ数: 5
導入後に効く点
原因はウォームアップ(JIT・キャッシュ空・接続確立)、新旧バージョンの混在、接続の張り替え、そしてスキーマ変更とコード配布の順序ずれ。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- トラブルシュート実戦
- タグ数
- 5
判断チェックリスト
- 自社の用途が「トラブルシュート / デプロイ」に近いか確認する。
- 強みである「自然に収まるエラーは「収まったから解決」ではない。デプロイのたびに一定数のユーザーが確実に踏んでおり、原因はほぼ4つのどれかに特定できる。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。