影響範囲を先に描く
一言でいうと
どんな変更でも、する前に、誰が・何が影響を受けるかを紙に描けなければなりません。描けないなら、まだその変更をする準備ができていません。そしてその図は、推測ではなく、ログ、ソケット、設定、スケジュールから掘り出したものでなければなりません。
なぜ必要なのか
注文サーバーの応答が遅いので、コネクションプールを10から30に増やしたとします。設定1行で、元に戻すのも簡単そうに見えます。ところがこのサーバーは4台動いていて、4台とも同じデータベースを使っています。プールの合計が40から120になります。PostgreSQLのmax_connectionsの既定値は通常100で、この値はサーバーを再起動しないと変わりません。新しい接続はsorry, too many clients alreadyで拒否され始め、最初に倒れるのは注文サーバーではなく、同じDBを使っていた精算バッチです。構成図には、精算バッチが描かれていませんでした。
「設定1行を変えるだけです」から始まって、サービスが止まることが繰り返される理由は、その1行が何に及ぶかを誰も確認しないからです。見知らぬシステムでは特にそうです。私たちにとっては新しいシステムでも、顧客にとっては10年もののシステムで、その間に誰も記録していない依存が積み重なっています。
影響範囲を描く4つの質問
1. この構成要素を誰が呼び出しているか(上流) 設定ファイル1つを直すときも、そのプロセスにリクエストを送る側が誰かを知っている必要があります。アクセスログの送信元IPの分布が過去の呼び出し元を、いま接続しているソケットが現在の呼び出し元を示します。
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head
ss -tn state established '( sport = :8080 )'
最初のコマンドは、IPごとのリクエスト数を多い順に表示します。件数の少ないIPを無視しないでください。1日1回動くバッチや、月末にだけ来る外部機関かもしれません。2つ目のコマンドでは、ローカルポートがサービスポートである接続の相手アドレスが上流です。
2. これが何を呼び出しているか(下流) 変更した結果、下流に負荷が集中することがあります。タイムアウトを延ばす変更が代表的です。私たちは余裕を与えたと思いますが、下流ではコネクションが長く保持されることになります。プールサイズ、リトライ回数、並行度も、同じ方向に下流を圧迫します。
grep -nE '[a-z0-9.-]+:[0-9]{2,5}' /etc/app/*.yml
ss -tnp state established '( dport = :5432 )'
設定からhost:portの形をすべて抜き出すと下流の候補が出てきて、いまそのポートに出ている接続が何本かを数えれば、変更後の接続数を見積もれます。インスタンスが複数台なら、1台の値に台数を掛ける必要があることを忘れないでください。
3. 状態を共有しているか 同じDB、同じファイルシステム、同じキャッシュを使っている別のシステムがあるかどうかです。共有状態は構成図にあまり描かれませんが、事故はここで起きます。別々のサービスの設定ファイルで同じ値が出てくるかを比べるのが、早い方法です。
grep -hE 'db|dsn|path|dir|cache' service-a.yml service-b.yml | sort | uniq -d
uniq -dは、2回以上出てきた行だけを表示します。ここに出たDBのアドレスやディレクトリが、2つのサービスが一緒に使っているリソースです。
4. いつが安全か バッチが動く時間、締めがかかる日、精算日。技術的に安全でも、タイミングが間違っていれば事故です。スケジュールは1か所にだけあるわけではありません。
crontab -l; ls /etc/cron.d /etc/cron.daily
systemctl list-timers --all
crontabの最初の5つのフィールドは、分、時、日、月、曜日の順です。30 23 * * 5は「毎週金曜日の23時30分」です。systemdタイマーは、次の実行時刻(NEXT)と前回の実行時刻(LAST)を一緒に表示します。ただし、バッチが何時に始まるかはここで見えても、何時に終わるかは見えません。それはログか、顧客に聞かないとわかりません。
ロールバックを先に書く
変更計画書の最初の行は、変更内容ではなく、元に戻す方法でなければなりません。
변경: /etc/app/config.yml 의 pool_size 10 → 30
백업: cp -p config.yml config.yml.2026-08-20
되돌림: cp -p config.yml.2026-08-20 config.yml && systemctl reload app
확인: curl -s localhost:8080/healthz 가 200, 에러율 5분간 관찰
소요: 되돌림 2분
このコードブロックの韓国語の見出しは、順に、変更・バックアップ・ロールバック・確認・所要時間という意味です。
「ロールバック: 2分」と書けるなら、その変更はしてもかまいません。書けなければ、まだです。書くときに、3つを確認します。
cp -pでバックアップします。-pなしでコピーすると、バックアップはコピーしたアカウントの所有になり、権限もumaskに従って新しく決まります。そのバックアップをmvで元の場所に戻すと、設定ファイルの所有者と権限が変わり、「ロールバックしたのにサービスが設定を読めない」という2つ目の事故が起きます。-pは、権限、所有者(権限があるとき)、更新時刻を一緒に保存します。reloadが本当にその値を再読み込みするかを確認します。systemctl reloadは、サービス自身に設定を再読み込みするよう要求するもので、サービスがreloadに対応していなければ失敗します。対応していても、一部の値は起動時にしか読みません。そのような場合は再起動が必要で、再起動は開いている接続を切るので、所要時間と影響が変わります。- ロールバックを事前に1回実行してみます。コピーした設定を変更してからロールバックスクリプトを実行し、結果が元と同じかを
diffやsha256sumで比較します。一度も実行していないロールバックは、計画ではなく希望です。
観察ウィンドウを決める
変更後、いつまで見守るかを事前に決めます。5分見て席を離れると、10分後に起きた問題が原因の候補から外れます。逆に、無限に張り付いてもいられないので、「30分観察、指標は3つ(エラー率・レイテンシ・キューの長さ)」のように明示します。
そして変更前の値を先に書いておきます。エラー率が0.4%という数字は、普段が0.1%なのか0.5%なのかがわかって初めて判定できます。観察ウィンドウの中にバッチが動く時刻が含まれているなら、その時刻の変化が変更によるものかバッチによるものか区別できないので、ウィンドウを移動します。
現場での姿
- タイムアウトを延ばしたら下流のDBコネクションが枯渇した → 下流を見ていませんでした。症状は自分たちのサービスではなく、同じDBを使っている別のサービスの接続拒否として、先に現れます。
- 夜間にデプロイしたら、その時刻に精算バッチが動いていた → タイミングを聞いていませんでした。crontabには開始時刻しかなく、バッチが何時間かかるかは書かれていませんでした。
- ロールバックしようとしたら、元の設定を誰も知らない → バックアップを取らずに直しました。
- バックアップで元に戻したのに、サービスが設定を読めない →
-pなしで取ったバックアップをmvで戻したため、所有者と権限が変わりました。 - 1日に1回だけ来る呼び出し元を見落とした → アクセスログを1時間分しか見ていませんでした。上流を探すときは、ログの保存期間が許す限り広く見ます。
続くラボですること
構成図もWikiもない顧客先のシステムを受け取ります。設定ファイル2つ、アクセスログ、ローテーション設定、crontab。これがすべてです。
ここで上流・下流・共有状態・バッチウィンドウを、この節のコマンドで自分で掘り出し、ロールバックを先に書いた変更計画を作ります。採点ツールが設定をわざと変えてから、書いたロールバックスクリプトを実行して、元に戻るかを確認するので、提出する前に、上で述べたとおり、コピー上で1回実行してみてください。