392件のインシデントを分析した結果、最も多い原因は「データパイプラインの変更」でした。全体の30%を占めるこのカテゴリは、2位の「テスト不足」(20%)を大きく引き離しています。
AIに「このカラム名をリネームしてください」と依頼します。AIは指示通りリネームします。しかし、そのカラムを参照している5つの下流プログラムが壊れます。本記事では、データパイプライン変更時に必須となるIMPACT_ANALYSIS(影響範囲分析)チェックリストの設計と運用を記録します。
392件のインシデント分類
カテゴリ別の内訳
| 原因分類 | 割合 | 件数 | 代表的な事象 |
|---|---|---|---|
| データパイプライン変更 | 30% | 118件 | カラムリネームで下流5プログラム停止 |
| テスト不足 | 20% | 78件 | NULL値の未考慮、境界値未テスト |
| デプロイミス | 20% | 78件 | 環境変数未設定、plistパス誤り |
| モジュールインポート失敗 | 20% | 78件 | 相対インポートがlaunchd環境で解決不能 |
| launchd設定問題 | 10% | 40件 | plist構文エラー、スケジュール設定ミス |
データパイプライン変更が最大カテゴリになった理由は明確です。パイプラインの変更は「変更箇所」と「影響箇所」が異なるファイルに存在します。AIは依頼されたファイルを変更しますが、影響を受ける別のファイルを自動的には調査しません。
データパイプライン変更で実際に起きた事故
事故1:カラムリネームで5プログラム停止
1# 変更依頼: priority_scoreをpriority_valueにリネーム2# analyzer.py(変更箇所)3df["priority_value"] = calculate_priority(df) # priority_score → priority_value4
5# しかし以下の5ファイルがpriority_scoreを参照していた6# 1. trigger_generator.py7# 2. report_generator.py8# 3. slack_notifier.py9# 4. db_inserter.py(DDLも変更が必要)10# 5. regression_test_runner.pyAIはanalyzer.pyのリネームは完璧に行いました。しかし、下流の5ファイルは依頼の範囲外だったため、触れませんでした。結果、本番パイプラインが全停止しました。
事故2:Parquetファイルの命名規則変更
1# 変更前2output_path = f"data/{source}_analysis_{date}.parquet"3# 変更後(AIがリファクタリングで変更)4output_path = f"data/analysis_{source}_{date}.parquet"5# 例: data/analysis_server01_20260315.parquetファイル名のプレフィックスが変わりました。監視スクリプトが{source}_analysis_のパターンでファイル存在チェックをしていたため、「ファイル未生成」のアラートが大量発生しました。
事故3:型変更(int→float)で比較失敗
1# 変更前: resource_countはint2resource_count: int = get_resources()3if resource_count == 3: # intの比較はOK4 stop_processing()5
6# 変更後: AIがfloatに変更(計算の都合で)7resource_count: float = get_resources()8if resource_count == 3: # floatとintの比較は予期しない結果になりうる9 stop_processing() # 3.0000000001 != 3 で条件不成立浮動小数点の比較問題は古典的なバグですが、AIがリファクタリング中に型を変更したことで混入しました。
IMPACT_ANALYSIS_CHECKLIST
全体構成(6段階)
392件の分析から、データパイプライン変更時に必須となる6段階のチェックリストを策定しました。
1# IMPACT_ANALYSIS_CHECKLIST2
3## Stage 1: 変更内容の明確化4- [ ] 変更対象のファイル名を列挙5- [ ] 変更するカラム名/型/フォーマットを明記6- [ ] 変更の目的と期待する効果を記述7
8## Stage 2: 影響範囲の特定9- [ ] 変更するカラム名でプロジェクト全体をgrep10- [ ] 変更するファイル名のimport元をgrep11- [ ] Parquet/CSV/DBスキーマへの影響確認12- [ ] plistファイルへの影響確認13
14## Stage 3: launchdジョブへの影響確認15- [ ] 変更ファイルを使用するplistの特定15 collapsed lines
16- [ ] 実行順序(依存関係)の確認17- [ ] スケジュール時刻への影響確認18
19## Stage 4: 型チェック20- [ ] mypy --strict の実行21- [ ] 型変更がある場合、下流の型整合性を確認22
23## Stage 5: 実施順序の策定24- [ ] DDL変更(DB側)→ Reader変更 → Writer変更 → Test → Deploy25- [ ] ロールバック手順の準備26
27## Stage 6: デプロイ後確認28- [ ] 5分後: エラーログの確認29- [ ] 1時間後: データ出力の正常性確認30- [ ] 24時間後: 全パイプラインの通し確認Stage 2の具体的な実行方法
影響範囲の特定は、grepで機械的に行います。
1# カラム名の変更時: 旧カラム名でプロジェクト全体を検索2grep -rn "priority_score" --include="*.py" /path/to/project/3
4# 出力例:5# analyzer.py:45: df["priority_score"] = calculate_priority(df)6# trigger_generator.py:23: score = row["priority_score"]7# report_generator.py:67: print(f"Priority: {data['priority_score']}")8# slack_notifier.py:34: msg = f"Score: {result.priority_score}"9# db_inserter.py:12: INSERT INTO results (priority_score, ...)10# regression_test_runner.py:89: if item.priority_score > threshold:1# Parquetスキーマの変更時: ファイル名パターンで検索2grep -rn "analysis_.*parquet\|_analysis_" --include="*.py" /path/to/project/3
4# plist内の参照確認5grep -rn "priority_score\|analyzer.py" --include="*.plist" /path/to/launchd/1# 影響範囲を自動検出するスクリプト2import subprocess3from pathlib import Path4
5
6def find_impact(7 search_term: str,8 project_root: str,9 file_extensions: list[str] | None = None,10) -> list[dict[str, str]]:11 """変更対象の文字列を参照しているファイルを特定する。"""12 extensions = file_extensions or [".py", ".plist", ".sql", ".md"]13 results: list[dict[str, str]] = []14
15 for ext in extensions:25 collapsed lines
16 cmd = [17 "grep", "-rn", search_term,18 "--include", f"*{ext}",19 project_root,20 ]21 proc = subprocess.run(cmd, capture_output=True, text=True)22 for line in proc.stdout.strip().split("\n"):23 if not line:24 continue25 parts = line.split(":", 2)26 if len(parts) >= 3:27 results.append({28 "file": parts[0],29 "line": parts[1],30 "content": parts[2].strip(),31 })32
33 return results34
35
36# 使用例37impacts = find_impact("priority_score", "/path/to/project")38print(f"影響範囲: {len(impacts)}箇所")39for impact in impacts:40 print(f" {impact['file']}:{impact['line']} - {impact['content']}")Stage 5: 実施順序の重要性
データパイプライン変更の順序を間違えると、一時的にデータが不整合になります。
1❌ 間違った順序:21. Writerを先に変更 → 新カラム名で書き込み32. Readerはまだ旧カラム名で読もうとする → KeyError43. DB DDLは未変更 → INSERT失敗5
6✅ 正しい順序:71. DDL変更(ALTER TABLE ADD COLUMN / RENAME COLUMN)82. Reader変更(新旧両方のカラム名に対応する互換コード)93. Writer変更(新カラム名で書き込み)104. テスト実行(全パイプラインの通し)115. 互換コードの除去(旧カラム名対応を削除)126. デプロイ1# Stage 2の互換コード例:新旧カラム名の両方に対応2def read_priority_value(df):3 """priority_score → priority_value のマイグレーション中の互換関数。"""4 if "priority_value" in df.columns:5 return df["priority_value"]6 if "priority_score" in df.columns:7 # 旧カラム名でも動作(移行期間中)8 return df["priority_score"]9 raise KeyError("priority_value も priority_score も見つかりません")Stage 6: 3段階のデプロイ後確認
デプロイ直後に問題が見つかるとは限りません。時間差で顕在化する問題があります。
1# デプロイ後確認の3段階2POST_DEPLOY_CHECKS = {3 "5分後": {4 "確認項目": [5 "stderrにエラーが出ていないか",6 "プロセスが正常に起動しているか",7 "直近のログにWARNING/ERRORがないか",8 ],9 "見つかる問題": "構文エラー、インポートエラー、設定ミス",10 },11 "1時間後": {12 "確認項目": [13 "出力ファイルが正常に生成されているか",14 "DBへのINSERTが成功しているか",15 "Slack通知が届いているか",12 collapsed lines
16 ],17 "見つかる問題": "データ形式不整合、スキーマ不一致、型変換エラー",18 },19 "24時間後": {20 "確認項目": [21 "全launchdジョブが正常に実行されたか",22 "パイプライン全体のデータフローが正常か",23 "日次レポートの数値に異常がないか",24 ],25 "見つかる問題": "日次バッチの依存関係エラー、データ欠損、集計値の不整合",26 },27}なぜ24時間後まで確認が必要か
1実例: カラム型変更(int→float)のインシデント2
35分後: ✅ エラーなし(INSERTは成功)41時間後: ✅ データ出力OK(数値は入っている)524時間後: ❌ 日次集計で異常発見6 → 前日のint値と当日のfloat値の比較で7 「変化率: +0.0000001%」が大量発生8 → 監視アラートが鳴りっぱなし5分後と1時間後のチェックを通過しても、24時間後に問題が見つかることがあります。日次バッチや日次レポートは、24時間経たないと検証できません。
CLAUDE.mdとの統合
ルールの追加
1## Critical Rules2
3### データパイプライン変更時の必須手順(BLOCKING)4以下のファイル/データへの変更時は、IMPACT_ANALYSIS必須:5- `.parquet` ファイルのスキーマ変更6- `.csv` ファイルのカラム変更7- DB DDL変更(CREATE/ALTER TABLE)8- API レスポンス形式の変更9- `.plist` ファイルの変更10- データフォーマットの変更(型変更を含む)11
12手順:131. 変更内容を明文化142. grep で影響範囲を特定(変更するカラム名/ファイル名/型名で検索)153. launchdジョブへの影響を確認3 collapsed lines
164. mypy --strict を実行175. 実施順序を策定(DDL → Reader → Writer → Test → Deploy)186. デプロイ後確認(5分/1時間/24時間)AIの振る舞いの変化
ルール追加前後のAIの対応の違いです。
1【ルール追加前】2人間: 「priority_scoreカラムをpriority_valueにリネームしてください」3AI: 「analyzer.pyのカラム名を変更しました。完了です」4→ 下流5ファイルが壊れる5
6【ルール追加後】7人間: 「priority_scoreカラムをpriority_valueにリネームしてください」8AI: 「データパイプライン変更のため、IMPACT_ANALYSISを作成します。9
10 影響範囲の調査結果:11 - trigger_generator.py:23行目 — priority_scoreを参照12 - report_generator.py:67行目 — priority_scoreを参照13 - slack_notifier.py:34行目 — priority_scoreを参照14 - db_inserter.py:12行目 — DDLにpriority_scoreカラムあり15 - regression_test_runner.py:89行目 — priority_scoreを参照4 collapsed lines
16
17 合計5箇所の影響があります。18 実施順序: DDL変更 → 互換コード追加 → 全ファイル変更 → テスト → 互換コード除去19 進めてよろしいですか?」IMPACT_ANALYSISのテンプレート生成
チェックリストの手動作成は面倒なので、テンプレートを自動生成する仕組みも用意しました。
1from datetime import datetime2
3
4def generate_impact_analysis(5 change_description: str,6 changed_files: list[str],7 search_terms: list[str],8 project_root: str,9) -> str:10 """IMPACT_ANALYSISのテンプレートを生成する。"""11 timestamp = datetime.now().strftime("%Y-%m-%d %H:%M")12
13 impacts: list[str] = []14 for term in search_terms:15 results = find_impact(term, project_root)42 collapsed lines
16 for r in results:17 impacts.append(f" - {r['file']}:{r['line']} — {r['content']}")18
19 template = f"""# IMPACT_ANALYSIS20生成日時: {timestamp}21
22## 変更内容23{change_description}24
25## 変更対象ファイル26{chr(10).join(f'- {f}' for f in changed_files)}27
28## 影響範囲(自動検出)29{chr(10).join(impacts) if impacts else ' 検出なし'}30
31## チェックリスト32- [ ] Stage 1: 変更内容の明確化 — 完了33- [ ] Stage 2: 影響範囲の特定 — 上記参照34- [ ] Stage 3: launchdジョブへの影響確認35- [ ] Stage 4: mypy --strict 実行36- [ ] Stage 5: 実施順序の策定37- [ ] Stage 6: デプロイ後確認(5分/1時間/24時間)38
39## 実施順序401. DDL変更412. Reader変更(互換コード追加)423. Writer変更434. テスト実行445. 互換コード除去456. デプロイ46"""47 return template48
49
50# 使用例51analysis = generate_impact_analysis(52 change_description="priority_score → priority_value カラムリネーム",53 changed_files=["analyzer.py"],54 search_terms=["priority_score"],55 project_root="/path/to/project",56)57print(analysis)392件の分析から得た統計的な知見
カテゴリ別の平均復旧時間
1データパイプライン変更: 平均2.3時間(下流プログラムの特定に時間がかかる)2テスト不足: 平均1.1時間(原因特定は比較的容易)3デプロイミス: 平均0.8時間(設定ファイルの修正で解決)4インポート失敗: 平均0.5時間(エラーメッセージが明確)5launchd設定: 平均0.3時間(plutil -lint で特定可能)データパイプライン変更のインシデントは、復旧時間も最長です。「どのファイルが影響を受けているか」の特定に時間がかかるためです。事前にgrepで影響範囲を把握しておけば、この2.3時間は大幅に短縮できます。
インシデント発生のタイミング
1デプロイ直後(5分以内): 40%21時間以内: 25%324時間以内: 20%424時間超: 15%(日次バッチで初めて顕在化)60%のインシデントは1時間以内に発見されますが、残りの35%は24時間以内、15%は翌日以降に発見されます。3段階のデプロイ後確認は、この分布に基づいて設計しました。
学んだこと
1. データパイプライン変更は最もリスクが高い
392件の30%を占める最大カテゴリです。変更箇所と影響箇所が別ファイルに分散するため、AIが自発的に全影響範囲を把握することは期待できません。人間が「grepで検索しろ」と指示するか、CLAUDE.mdに「IMPACT_ANALYSIS必須」と書くことで、網羅的な影響調査を強制します。
2. grep検索で影響範囲を機械的に特定できる
カラム名、ファイル名、型名でプロジェクト全体をgrepすれば、影響を受けるファイルと行番号が機械的に特定できます。手動で「このカラムを使っているファイルはどれだっけ」と記憶に頼る必要はありません。
3. デプロイ後確認は3段階(5分/1時間/24時間)が必要
インシデントの40%はデプロイ直後に発見されますが、35%は1時間後〜24時間後に顕在化します。「デプロイ直後にエラーがなければOK」という判断は危険です。特に日次バッチの依存関係は、24時間経たないと検証できません。
まとめ
データパイプライン変更のIMPACT_ANALYSISで重要なのは以下の3点です。
- grepで影響範囲を網羅: カラム名・ファイル名・型名でプロジェクト全体を検索し、影響を受ける全ファイルを事前に特定。AIに「変更して」と依頼する前に実施
- 実施順序を守る: DDL → Reader(互換コード) → Writer → Test → Deploy。順序を間違えると一時的なデータ不整合が発生し、復旧が困難になる
- 3段階のデプロイ後確認: 5分後(構文エラー)→ 1時間後(データ形式)→ 24時間後(日次バッチ)。インシデントの35%は1時間後以降に顕在化する