障害が起きた日のログはすでに消えていた
目標
ローテーションされたログの束から実際の保管区間を測り、事故の区間がその範囲外であることを数字で明らかにし、圧縮率を実測して保存日数と容量を計算し、ローテーションのポリシーとして書きます。最後に、レート制限にかかって、そもそも記録されない行を数えます。
なぜ重要なのか
調査の初日に最もよくぶつかる壁は、難しい質問ではなく、空のディレクトリです。そのとき必要なのは、嘆きではなく、3つの数字です。何日足りなかったか、あと何日分残すべきか、その値がディスクのバジェットに合うか。3つの数字は、すべて今あるファイルから測れます。そして、「ログがない」には2つの意味があります。ローテーションで消えたか、レート制限にかかって、そもそも記録されなかったか。対策がまったく違うので、2つを見分ける必要があります。
ステップ
/root/keep/gen_keep.pyを作成して実行し、/root/keep/var/log/の下に、保管状態を再現してください。/root/keep/order.jsonに、ローテーション済みファイルを古いものから並べた一覧と、そのルールを書いてください。/root/keep/coverage.jsonに、ファイルごとの行数と、最初の行・最後の行の時刻を書いてください。/root/keep/window.jsonに、事故の区間が保管範囲の中か、どれだけ足りなかったかを書いてください。/root/keep/budget.jsonに、圧縮率の実測値と、保存日数・容量の計算を書いてください。/root/keep/logrotate.confに、ローテーションのポリシーを書いてください。rotateの値は、ステップ5の計算と同じである必要があります。/root/keep/ratelimit.jsonに、レート制限にかかって捨てられる行数を数えて書いてください。/root/keep/keep_report.mdに、4つの節で報告書を残してください。
参考
- このラボが与える仮定です。事故の区間は
2026-04-05T02:10:00Zから2026-04-05T02:40:00Zまでです。本番サーバーは、このサンプルの1200倍を記録します。/var/logのディスクバジェットは4 GiB(4294967296バイト)、要求される保存期間は30日、journaldの設定はRateLimitIntervalSec=30・RateLimitBurst=200で、空き容量による倍数は1と見なします。 - ステップ5の計算ルールです。
sample_raw_bytes_per_dayはpayments.log.1のサイズ、sample_gz_bytes_per_dayはpayments.log.2.gzのサイズです。compression_ratioは2つの比を小数第4位まで四捨五入します。delaycompressのために、直近の2日は圧縮されていないものと見なします。required_bytes_for_30_daysは原本2日分 + 圧縮28日分、max_days_in_budgetは、バジェットから原本2日分を引いたあと、圧縮1日分で割った商に2を足した値です。rotate_valueは、30日分を残すときにlogrotateに書く数字です。 zcatとzgrepで、圧縮ファイルを展開せずに読め、Pythonではgzip.open(path, "rt")です。- よくあるミスは、
rotateを保存日数と同じ値で書くこと、番号の名前と日付の名前を同じ方向でソートすること、圧縮率を目分量で入れること、直近の2日分を圧縮ファイルのサイズで計算することの4つです。 - 成果物はすべて
/root/keep/の下に集めます。セッションが終わると消えます。
顧客企業の保管状態を再現する
/root/keep/gen_keep.pyを作成して実行し、/root/keep/var/logディレクトリの下に、payments.log(720行)・payments.log.1(2000行)・payments.log.2.gz・payments.log.3.gz・old/payments.log-20260407.gz・old/payments.log-20260408.gz・burst.ndjson(360行)を作ってください。
ローテーションの名前が2種類混ざっている状態を作ります。番号が付いたものと日付が付いたもの、圧縮されたものとされていないものが、一緒にあって初めて、このラボが成立します。圧縮ファイルは、Pythonのgzip.GzipFileでmtime=0を与えて書けば、作り直しても同じファイルになります。
ローテーション済みファイルを古いものから並べる
/root/keep/order.jsonに、order(ファイルのパスを古いものから入れた配列で、/root/keep/var/log/を除いた相対パス)とrule(そのように並べたルールを1文で)を書いてください。
番号が付いた名前と日付が付いた名前は、ソートの方向が互いに逆です。一方は数字が大きいほど過去で、もう一方は名前が小さいほど過去です。今書いているファイルが最も新しいものです。ファイルを開かずに名前だけで並べてみて、次のステップで内容で確認します。
ファイルごとに実際に含まれる区間を測る
/root/keep/coverage.jsonに、ファイルごとにfile・lines・first_ts・last_tsを入れたオブジェクトを、古いものから配列で書いてください。時刻は、ログの行の最初の欄をそのまま使います。
ファイル名はうそをつくことがあります。ローテーションが失敗したり、人が手で移したファイルが混ざったりすると、名前の順序と内容の順序がずれます。そのため、内容でもう一度測ります。圧縮ファイルは、zcatで読むか、Pythonでgzip.open(path, 'rt')で開きます。
事故の区間が範囲外であることを数字で明らかにする
/root/keep/window.jsonに、oldest_retained・newest_retained・incident_start・incident_end・incident_covered(trueかfalse)・short_by_seconds(整数)・extra_rotations_needed(整数)を書いてください。extra_rotations_neededは、足りない秒を1日で割ったあと、切り上げた値です。
事故の区間は、指示文の参考の節に書かれています。足りない秒は、保管されている最も古い時刻から、事故の開始時刻を引いた値です。1日単位でローテーションしているので、何日分多く残すべきだったかは、切り上げで求めます。
圧縮率を実測して保存日数を計算する
/root/keep/budget.jsonに、sample_raw_bytes_per_day・sample_gz_bytes_per_day・compression_ratio・prod_raw_bytes_per_day・prod_gz_bytes_per_day・required_bytes_for_30_days・fits_in_budget・max_days_in_budget・rotate_valueを、指示文の計算ルールのとおりに書いてください。
圧縮率は目分量で使わず、この顧客のファイルで測ります。アクセスログとスタックトレースでは、縮む度合いが違います。delaycompressのために、直近の2日分は原本のサイズで見積もる必要があり、logrotateのrotateが現在のファイルを除いた個数であることが、この計算の落とし穴です。
計算した値でローテーションのポリシーを書く
/root/keep/logrotate.confに、/root/keep/var/log/payments.logのブロックを書いてください。daily・rotate <5단계의 rotate_value>(プレースホルダーはステップ5のrotate_valueです)・compress・delaycompress・missingok・notifempty・dateext・create 0640 root admが必要で、copytruncateは入れません。
rotateの値は、ステップ5で計算した、その数字です。保存日数と同じ値ではありません。copytruncateを外す理由は、マニュアルに書かれています。コピーと空にする処理の間の短い隙間に書かれた行が消えるからで、それは調査で最も惜しい瞬間に消えます。
そもそも記録されない行を数える
/root/keep/ratelimit.jsonに、interval_sec・burst・windows_over_limit・dropped_total・dropped_by_service(サービス名をキーにしたオブジェクト)・worst_window(window_start・service・dropped)を書いてください。1つの区間で捨てられる行はcount - burstで、負なら0です。
journaldのレート制限は、サービスごとに別々に適用され、1つの区間で上限を超えると、その区間の残りがすべて捨てられます。保管期間をどれだけ延ばしても、この損失は残ります。worst_windowは、捨てられた行が最も多い区間1つです。
何を変えるかを文章に残す
/root/keep/keep_report.mdに、## 무엇이 없었나、## 지금 보관 정책은 무엇인가、## 얼마를 남겨야 하는가、## 무엇을 바꾸기로 했나(韓国語の見出しは、順に「何がなかったか」「今の保管ポリシーは何か」「いくら残すべきか」「何を変えることにしたか」という意味です)の4つの節で書いてください。足りなかった秒・rotateの値・バジェットの中で可能な日数・レート制限で捨てられる行数を、数字で含める必要があります。
この報告書を読む人は、ディスクのバジェットを握っている人です。「ログをもっと残してください」ではなく、「1日Nバイト、30日でMバイト、バジェットに収まります」と言って初めて、決定が下されます。レート制限による損失は、保存とは別の対策が必要だという点も、一緒に書いてください。