
本番でしか起きない不具合を追う|ログとデータ差分の調べ方
「本番で、また例のエラーが出ました」
連絡を受けて、検証環境で同じ操作をしてみる。何度やっても、ちゃんと動く。ローカルでも動く。コードも同じはずなのに、本番だけで、しかも「ときどき」起きる。再現しない不具合ほど、手の打ちようがなくて落ち着かないものです。
これは、あなたの調べ方が足りないわけではありません。本番と検証は、コードが同じでも「まったく同じ環境」ではないからです。データの中身、設定、アクセスの量やタイミング、つながっている外部のサービス。本番だけで起きる不具合は、たいていこの「違い」のどこかに隠れています。
この記事では、本番でしか起きない不具合を、ログとデータの差分から追っていく順番を、小さく分けて一緒に整理していきます。
結論:本番だけの不具合は、次の4つの順で追います。①起きた瞬間をログでつかむ(いつ・誰が・どのデータで)、②「違い」をデータ・設定・量とタイミング・外部の4つに分けて当たりをつける、③失敗した行と成功した行を並べて、差分を見つける、④見つけた差分を検証環境に持ち込んで再現してから直す。本番では「見るだけ」に徹し、データを書き換えたり、試しに設定を変えたりしないのが基本です。
なお、コマンドや設定の名前・確認方法は、OS・ミドルウェア・バージョンで変わります。ここで持ち帰ってほしいのは順番と観点です。手元の環境の公式ドキュメントで確かめ、変更は必ず検証環境で試してから本番に反映してください。
何が起きているか:本番と検証には「4つの違い」がある
本番だけで起きる不具合の原因は、ほとんどが次の4つのどれかです。
- データの違い:本番には、何年も使われてきたデータがあります。空欄(NULL)、極端に長い名前、絵文字や機種依存文字、昔の仕様で入った値、削除済みのはずの関連データ。検証のきれいなテストデータには、こうした「くせのある行」がほとんどありません。
- 設定の違い:PHPのメモリ上限やアップロードサイズ、DBの
sql_modeや文字コード、タイムアウト時間、環境変数。同じコードでも、設定が違えば動きが変わります(環境差で事故らない設定管理)。 - 量とタイミングの違い:本番は件数が多く、同時に何人も操作し、夜中にはバッチも動きます。検証では1人で順番に操作するので、処理の重なりや件数が増えたときの遅さが出てきません。
- 外部とのつながりの違い:決済・メール・外部API・ファイル置き場。検証ではテスト用の接続先やダミーを使っていることが多く、本番の相手が返す応答(遅延・エラー・仕様変更)には触れていません。
「再現しない」は、言いかえると「検証には、本番のどれかの違いが足りていない」ということです。足りない違いを1つずつ探していけば、再現にちゃんと近づけます。
本番だけの不具合を追う4つの手順
① 起きた瞬間をログでつかむ
最初にやるのは、検証で再現させることではなく、本番で起きたときの事実を集めることです。エラーログ・アクセスログ・アプリのログから、次の3つを書き出します。
- いつ:発生した日時。毎回同じ時間帯か、バッチの時間と重なっていないか。
- 誰が・どの画面で:特定のユーザー・権限・ブラウザだけか、全員か(特定のユーザーだけエラーになるときの切り分け)。
- どのデータで:エラーになったときの注文番号・会員ID・ファイル名など、対象のデータを特定できる値。
# エラーが出た時刻の前後だけを見る(パスやログの形式は環境に合わせて)
grep "2026-10-07 14:3" /var/log/app/error.log | less
# 同じエラーが何回、どの時間帯に出ているかを数える
grep "Undefined index" /var/log/app/error.log | cut -c1-13 | sort | uniq -c
大量のログから絞り込むコツはgrep・tail・lessでエラーを絞り込むにまとめています。ログに対象のIDが出ていなくて追えないときは、次に起きたときのために、処理の入口でIDを書き出すログを1行足しておきます(改修時にログを仕込んで原因を追う)。パスワードや個人情報はログに出さないよう、気をつけてください。
② 「違い」を4つに分けて当たりをつける
集めた事実を、さっきの4つの違いに当てはめます。症状から、疑う場所がかなり絞れます。
| 症状のくせ | 疑う違い | まず見るところ |
|---|---|---|
| 特定の注文・会員・ファイルでだけ起きる | データの違い | エラーになった行そのものの中身 |
| どの操作でも起きる、または特定の機能だけ必ず起きる | 設定の違い | PHP・DB・Webサーバの設定値、環境変数 |
| 夕方や月末、バッチの時間帯に起きる。たまにしか起きない | 量とタイミングの違い | その時間の件数・同時アクセス・スロークエリ |
| 決済・メール・連携の前後でだけ起きる | 外部とのつながりの違い | 外部への通信ログ、相手から返ってきた応答 |
いちばん多いのは、データの違いです。「たまに起きる」と感じる不具合も、実は「特定の行に当たったときだけ起きる」ことがよくあります。迷ったら、まずデータから疑ってみてください。
③ 失敗した行と成功した行を並べて、差分を見つける

ここがいちばん効く手順です。①で特定したエラーになったデータと、同じ操作で問題なく動いたデータを1件ずつ選び、横に並べてくらべます。
-- エラーになった注文と、正常に処理された注文を並べて見る(閲覧だけ。テーブル名・列名は環境に合わせて)
SELECT * FROM orders WHERE id IN (10234, 10235)\G
見るポイントは、片方にだけある「くせ」です。
- 空欄(NULL)や空文字:住所2が空、電話番号が未登録、関連する行がない。
- 長さ・文字:名前が極端に長い、絵文字や半角カナ・機種依存文字が入っている。
- 古い値:昔の仕様で入った区分コード、今はない商品やステータス。
- 件数:明細が100行ある、添付ファイルが大きい。
設定の違いを疑うときも、同じように本番と検証の値を並べて差分を出します。目で見くらべるより、ファイルに書き出して diff にかけるほうが確実です。
# PHPの設定を本番・検証それぞれで書き出し、手元で差分を見る
php -i > php_honban.txt # 本番で実行
php -i > php_kensho.txt # 検証で実行
diff php_kensho.txt php_honban.txt
-- DBの設定値を書き出す(MySQLの例。PostgreSQLなら SHOW ALL;)
SHOW VARIABLES WHERE Variable_name IN ('sql_mode', 'character_set_server', 'max_allowed_packet', 'time_zone');
ライブラリのバージョンも、composer show や pip freeze などで両方の一覧を書き出せば、同じように diff でくらべられます。差分が出た行が、そのまま調べる候補のリストになります。
④ 見つけた差分を検証環境に持ち込んで再現する
差分が見つかったら、それを検証環境に持ち込んで、同じエラーが起きるかを確かめます。
- データの違いなら:くせのある行と同じ形のデータを、検証環境に作ります。本番データをそのままコピーするときは、名前・メールアドレス・電話番号などの個人情報を置き換えてから使うのが基本です。社内のルールがあれば、そちらに従ってください。
- 設定の違いなら:検証環境の設定を、本番と同じ値にそろえて試します。
- 量とタイミングなら:件数を本番並みに増やす、2つのブラウザで同時に同じ操作をする、バッチを動かしながら操作する(同時実行で起きるバグと排他制御)。
- 外部とのつながりなら:外部サービスの障害情報や仕様変更のお知らせを確かめ、相手がエラーや遅い応答を返したときの動きを試します(クラウド・SaaSの障害を確認して説明する)。
検証で同じエラーが出せたら、原因はほぼ確定です。そこから先は、ふだんのバグ調査と同じ流れで直していけます(バグ報告から再現・原因特定までの最短ルート)。
具体例:「ときどき注文確定でエラーになる」を4つの手順で追う
社内のECサイトを一人で保守している担当者の、よくある流れです。
- 以前:「注文確定でエラーになった」と問い合わせが来るたびに、検証で同じ商品を注文してみるが、何度やっても通る。月に数件、原因が分からないまま、お客様には「お手数ですがもう一度お試しください」と返していた。
- 4つの手順で追ったあと:①エラーログから発生時刻を拾い、アプリのログで注文番号を特定した。②特定の注文でだけ起きているので、データの違いと当たりをつけた。③エラーになった注文と正常な注文を並べると、エラーの注文はどれも「お届け先の建物名」に絵文字が入っていた。本番と検証のDB設定を書き出して
diffにかけると、本番のテーブルだけ古い文字コード(utf8mb3)のままで、4バイトの絵文字を保存できずにエラーになっていた。④検証環境のテーブルを本番と同じ文字コードにそろえ、建物名に絵文字を入れたら、同じエラーが出た。そこで、本番のテーブルをutf8mb4に変える手順と戻し方を用意し、作業時間を決めて反映した。
変わったのは、調べる腕ではありません。「検証で再現させよう」とする前に、本番で起きた行と、うまくいった行を並べたことです。「もう一度お試しください」と返す日も、それきりなくなりました。
影響:本番だけの不具合を追えるようになると、何が変わるか
- 「再現しません」で止まらずに済む:再現しないときに何を見ればいいかが分かるので、調査が止まりません。
- 依頼者に途中経過を説明しやすくなる:「エラーになった注文には共通点がありました。いま検証で確かめています」と、具体的に伝えられます(障害の中間報告の書き方)。
- 本番を触らずに原因に近づける:見るだけで差分を集めるので、調査そのもので新しい事故を起こしません。
- 検証環境が少しずつ本番に近づく:見つけた差分をそろえていくたびに、次のリリースで「本番だけ」が起きにくくなります。
明日やること
- 最近の「再現しない」不具合を1つ思い出す。②の表の4つの違いのどれに近いか、当てはめてみます。
- その不具合のログに、対象データのIDが出ているか確かめる。出ていなければ、IDを書き出すログを1行足す候補にします。
- 本番と検証の設定を1つだけ書き出してくらべる。
php -iでも、DBのSHOW VARIABLESでも、どちらか1つで大丈夫です。 - 検証データに「くせのある行」を1件足す。空欄・長い名前・絵文字が入った会員や注文を1件作っておきます。
今日やるのは、3の「設定を1つ書き出してくらべる」だけでも十分です。差分が出なければ一安心、出たら、次に「本番だけ」が起きたときの有力な手がかりが1つ見つかったということです。
本番だけの不具合を追うチェックリスト
- 検証で試す前に、本番で起きた日時・ユーザー・対象データをログから書き出したか
- 症状を「データ/設定/量とタイミング/外部」のどの違いかに当てはめたか
- エラーになった行と、正常に動いた行を1件ずつ並べてくらべたか
- 空欄(NULL)・長さ・絵文字や機種依存文字・古い値・件数のくせを確かめたか
- PHP・DB・Webサーバの設定値を本番と検証で書き出し、diffで差分を見たか
- ライブラリやミドルウェアのバージョンを、本番と検証でくらべたか
- 発生時間帯と、バッチ・アクセスの多い時間帯が重なっていないか確かめたか
- 外部サービスの障害情報や仕様変更のお知らせを確かめたか
- 本番では「見るだけ」に徹し、データや設定を試しに変えていないか
- 本番データを検証で使うとき、個人情報を置き換えたか
- 見つけた差分を検証に持ち込み、同じエラーが出ることを確かめてから直したか
- 直したあと、本番で同じデータの処理が通ることを確かめたか
全部を一度にそろえなくても大丈夫です。まずは「起きた瞬間をログでつかむ」と「失敗した行と成功した行を並べる」の2つだけで、つかめなかった不具合がぐっと近くに見えてきます。
よければ、こちらも
- バグ報告から再現・原因特定までの最短ルート:再現できたあと、原因を挟み撃ちにして直すまでの流れを。
- 環境差で事故らない設定管理:見つけた設定の違いを、次から生まないための整理に。
- 特定のユーザーだけエラーになるときの切り分け:「誰で起きるか」がはっきりしているときに。
- 同時実行で起きるバグと排他制御:たまにしか起きない、タイミングの不具合を疑うときに。
- 直したつもりが直っていない、を防ぐ:本番で本当に直ったかを確かめるときに。

「本番でしか起きない」と悩んだ時間は、無駄ではありません。それは、検証で動いたことに安心しきらずに、本番で困っている人のほうを向き続けたしるしです。本番だけの不具合にも、必ずどこかに「違い」があります。違いを1つずつ並べていけば、その場所に少しずつ近づけます。
まずは明日、本番と検証の設定を、1つだけ書き出してくらべてみてください。
ほかの実務ヒントは記事一覧からどうぞ。保守運用の小さな備えを、メールでも少しずつお届けしています。