施策を1つずつ見ていると問題は見えません。2つの施策が同時に本番にデプロイされたとき、初めて競合が顕在化します。
本番システムで複数の施策を並行開発・同時デプロイした際に、施策間の競合が原因で障害が発生しました。AIに「全施策のコードを読み比べて競合を検出する」ことを依頼する手法を確立した結果、本番デプロイ前に競合を検出できるようになりました。本記事では、AIを使った施策間競合分析の実践を記録します。
問題:同時デプロイで起きた競合
実際のインシデント
RNG-B3(バッチ分割処理)とRNG-B4(Z-score閾値判定)を同日にデプロイしようとしました。
1RNG-B3: バッチ分割処理2 → 一定間隔でリソースを複数保持する3 → リソースは個別にタイムアウト/リトライで管理4
5RNG-B4: Z-score閾値判定6 → 統計的Z-scoreが閾値を超えたらリソースを解放7 → 対象: 安定期間中に保持中の全リソース個別に見ると、どちらも正しく動作します。しかし同時にデプロイすると問題が起きます。
1競合シナリオ:21. RNG-B3がsource_alphaで5段階のグリッド処理リソースを確保32. RNG-B4が「Z-scoreが閾値超過」を検知43. RNG-B4が安定期間中の全リソースを解放54. → RNG-B3のグリッド処理リソースが意図せず解放される65. → グリッド処理が破綻(中間リソースだけ残る等の不整合状態)sc:analyze(Claude Codeの分析コマンド)がこの競合を検出しました。
1sc:analyze の出力:2⚠️ CONFLICT DETECTED:3 RNG-B4 could unintentionally release RNG-B3 grid resources4 after API production migration.5
6 Root cause: RNG-B4の _fetch_active_resources() が7 processing_method_filter を使用していないため、8 RNG-B3のリソースも取得対象に含まれる。9
10 Recommendation: processing_method_filter に "grid" を11 除外条件として追加するか、RNG-B4のスコープを明示的に制限する。本番デプロイ前に検出できたことで、潜在的な障害を回避できました。
施策間競合の6カテゴリ
インシデント分析から、競合は6つのカテゴリに分類できます。
A. リソース競合
複数の施策が同一のリソースを操作しようとします。
1# 競合の例: 2つの施策が同じリソースを解放しようとする2# RNG-B3のコード3def release_grid_resource(source: str, level: int) -> None:4 resources = fetch_active_resources(source=source)5 target = find_grid_level(resources, level)6 release_resource(target.resource_id)7
8# RNG-B4のコード(競合あり)9def zscore_exit_check(source: str) -> None:10 resources = fetch_active_resources(source=source) # ← RNG-B3のリソースも含む11 for res in resources:12 if calculate_zscore(res) > threshold:13 release_resource(res.resource_id) # ← RNG-B3のグリッド処理を破壊B. トリガーファイル競合
複数のプロセスが同じトリガーファイルを読み込み・消費します。
1# 施策Aがトリガーファイルを書き込み2trigger_path = Path("triggers/entry_signal.json")3trigger_path.write_text(json.dumps(signal_data))4
5# 施策Bが同じファイルを読んで「消費済み」にする6signal = json.loads(trigger_path.read_text())7trigger_path.unlink() # ← 施策Aが読む前に削除される可能性8
9# 施策Aが読もうとしたときにはファイルが存在しないC. launchdスケジュール競合
同時刻に起動するジョブがAPIレート制限に抵触します。
1<!-- 施策A: 毎時0分にAPI呼び出し -->2<key>StartCalendarInterval</key>3<dict>4 <key>Minute</key>5 <integer>0</integer>6</dict>7
8<!-- 施策B: 毎時0分にAPI呼び出し(競合) -->9<key>StartCalendarInterval</key>10<dict>11 <key>Minute</key>12 <integer>0</integer>13</dict>14
15<!-- 同時起動 → APIレート制限(例: 1req/sec)に抵触 -->D. Parquet参照競合
施策が同じParquetファイルに異なるカラムを期待します。
1# 施策Aが新カラムを追加2df["new_indicator"] = calculate_indicator(df)3df.to_parquet("pipeline_output.parquet")4
5# 施策Bが古いスキーマを前提に読み込み6df = pd.read_parquet("pipeline_output.parquet")7result = df["old_column_name"] # ← リネームされていたらKeyErrorE. リソース割当・リスク管理競合
施策を個別に見るとリスク許容内ですが、合算すると超過します。
1# 施策A: 最大リソース割当量 = 全体キャパシティの3%2max_allocation_a = total_capacity * 0.033
4# 施策B: 最大リソース割当量 = 全体キャパシティの3%5max_allocation_b = total_capacity * 0.036
7# 合算: 6% → DAILY_ERROR_LIMIT(5%)を超過する可能性8combined_allocation = max_allocation_a + max_allocation_b # 0.06 > 0.05F. Go/No-Go依存
施策Aの結果に基づいて施策Bの有効/無効が決まる関係です。
1# 施策A(状態判定)がNo-Goを出した場合2state_result = evaluate_system_state() # → "NO-GO"3
4# 施策B(安定期処理ロジック)は施策AのGo判定に依存5if state_result != "GO":6 # 施策Bは動作すべきでないが、7 # 依存関係が未設定だと施策Bが独立に動いてしまう8 passAIに競合分析を依頼する方法
基本プロンプト
1プロンプト:2@.wbs/CURRENT.md 現在デプロイ済み・Dry-Run中の施策同士の3矛盾点がないか調査してください。4特に以下を確認:51. 同一データソースのリソースを操作する施策の組み合わせ62. 同じトリガーファイルを参照する施策73. 同時刻に実行されるlaunchdジョブAIはWBSから施策一覧を取得し、各施策のコードを読み比べて競合を検出します。50以上の施策の組み合わせチェックは人間には不可能ですが、AIには得意な作業です。
特定施策のデプロイ前チェック
1プロンプト:2RNG-B3とRNG-B4を同日にデプロイする予定です。3以下の観点で競合分析をしてください:41. リソース操作のスコープが重複していないか52. processing_method_filter の設定は正しいか63. launchdの実行タイミングが衝突しないか74. 合算したリソース割当量がリスク上限を超えないか8
9@engine_a/.docs/378_*.md(RNG-B3のドキュメント)10@engine_a/.docs/379_*.md(RNG-B4のドキュメント)sc:analyzeの活用
1プロンプト:2/sc:analyze3対象: RNG-B3, RNG-B4の同時デプロイ4観点: 施策間競合(リソース・トリガー・スケジュール・リスク)sc:analyzeはコード全体を分析し、潜在的な競合を検出します。前述のRNG-B3/B4の競合もこのコマンドで発見されました。
施策間競合チェックリスト
デプロイ前に必ず確認するチェックリストです。
1## 施策間競合チェックリスト2
3### A. リソース競合チェック4- [ ] 同一データソースのリソースを複数施策が操作しないか5- [ ] _fetch_active_resources のスコープに他施策のリソースが含まれないか6- [ ] processing_method_filter が正しく設定されているか7- [ ] リソース解放時に他施策への影響を確認したか8
9### B. トリガーファイル競合チェック10- [ ] 「消費済みシグナル」の二重処理は起きないか11- [ ] processing_method_filter の設定は正しいか12- [ ] 同一トリガーファイルを参照する施策が他にないか13- [ ] トリガーファイルの読み書きに排他制御があるか14
15### C. launchdスケジュール競合チェック16 collapsed lines
16- [ ] 同一時刻に起動するジョブがAPIレート制限に抵触しないか17- [ ] ジョブ間の実行順序に依存関係がある場合、順序保証があるか18- [ ] 1分以内に複数ジョブがAPIを叩かないか19
20### D. Parquet参照競合チェック21- [ ] 新施策が既存Parquetのスキーマを変更していないか22- [ ] カラム追加・リネームが他施策に影響しないか23
24### E. リソース割当・リスク管理競合チェック25- [ ] 施策の合算リソース割当量がDAILY_ERROR_LIMITを超えないか26- [ ] 同一方向のリソースが集中していないか27- [ ] CircuitBreakerが全施策をカバーしているか28
29### F. Go/No-Go依存チェック30- [ ] 施策間の依存関係がWBSに明記されているか31- [ ] 依存先がNo-Goの場合に依存元が正しく停止するか競合の記録方法
検出された競合は、両方の施策のドキュメントに記録します。
1# 施策A(.docs/378_*.md)に追加:2## 競合情報3- **競合施策**: RNG-B4(Z-score閾値判定)4- **競合カテゴリ**: A. リソース競合5- **内容**: RNG-B4の_fetch_active_resourcesがRNG-B3のグリッド処理リソースを6 取得対象に含む。RNG-B4がZ-score超過で解放すると、7 グリッド処理の一部リソースが意図せず解放される。8- **対策**: processing_method_filterに"grid"を除外条件として追加9- **ステータス**: 対処済み(2026-03-15)10
11# 施策B(.docs/379_*.md)に追加:12## 競合情報13- **競合施策**: RNG-B3(バッチ分割処理)14- **競合カテゴリ**: A. リソース競合15- **内容**: RNG-B3との競合あり。本番移行前に対処必須。2 collapsed lines
16- **対策**: processing_method_filterに"grid"を除外条件として追加17- **ステータス**: 対処済み(2026-03-15)クロスリファレンスにすることで、どちらの施策を見ても競合の存在がわかります。
競合分析のタイミング
チェックを実行すべき3つのタイミングがあります。
1┌────────────────────────────────────────────────────┐2│ タイミング1: 施策設計完了直後(実装開始前) │3│ → 設計段階で検出すれば、実装方針の変更が容易 │4│ → WBSの依存関係を追加するだけで済むことが多い │5├────────────────────────────────────────────────────┤6│ タイミング2: 本番デプロイ前 │7│ → 実コードを対象にsc:analyzeで検出 │8│ → processing_method_filterの設定漏れなど具体的な問題│9├────────────────────────────────────────────────────┤10│ タイミング3: 複数施策を同日デプロイする場合 │11│ → 同日デプロイは競合リスクが最大 │12│ → 全組み合わせのチェックが必要 │13│ → RNG-B3/B4のインシデントはこのタイミングで検出 │14└────────────────────────────────────────────────────┘施策ワークフローへの組み込み
CLAUDE.mdの施策ワークフローに競合チェックを必須化しました。
1## 施策開始(AUTO実行)2- A-1: git-new-feature.shでブランチ作成3- A-2: CLAUDE.md・WBS・.docs/から施策内容把握4- **A-3: 競合チェック(リクエスト系・フィルター系・パイプライン)→ ユーザー確認待ち**A-3の競合チェックがない状態で実装に入ることはできません。
AIに競合分析を任せる理由
人間には不可能なスケール
153のアクティブ施策の組み合わせ:2 53 × 52 / 2 = 1,378 ペア3
4各ペアで6カテゴリをチェック:5 1,378 × 6 = 8,268 チェックポイント6
7人間が全てを確認するのに必要な時間:8 8,268 × 5分/チェック = 688時間 ≈ 86営業日9
10AIの場合:11 コード読み比べ + WBS参照 = 数分〜数十分AIは「全施策のコードを読み比べる」ことが人間より圧倒的に得意です。特に、_fetch_active_resourcesのような関数が複数の施策で使われている場合に、スコープの違いを正確に比較できます。
ただし完全ではない
AIが検出できないタイプの競合もあります。
1AIが得意:2 - コードレベルの競合(関数呼び出し・変数参照の重複)3 - スケジュールの衝突(plistの時刻比較)4 - データスキーマの不整合(Parquetカラムの差異)5
6AIが苦手:7 - ビジネスロジックレベルの競合(運用環境に依存する競合)8 - タイミング依存の競合(特定の負荷状況でのみ発生)9 - 暗黙の前提に基づく競合(ドキュメント化されていない仕様)AIの分析結果は「スクリーニング」として使い、最終判断は人間が行います。
学んだこと
1. 1つの施策を見ているだけでは競合は見つからない
WBS全体を俯瞰して初めて検出できます。RNG-B3のコードだけを見ても、RNG-B4との競合は見えません。@.wbs/CURRENT.mdを起点に全施策の関係を分析させるプロンプトが効果的でした。
2. AIは「全施策のコードを読み比べる」ことが人間より得意
50以上の施策の組み合わせチェックは人間には不可能です。1,378ペア × 6カテゴリ = 8,268チェックポイントを人間が確認するには86営業日かかります。AIなら数分から数十分で完了します。
3. 競合検出は「やらないと事故が起きてから気づく」タイプの作業
競合チェックを怠っても、99%の場合は何も起きません。しかし1%の確率で、本番でリソース競合が発生して障害が起きます。チェックリスト化して施策ワークフローに組み込み、必須化するしかありません。
まとめ
AIを使った施策間競合分析で重要なのは以下の3点です。
- 6カテゴリで網羅的にチェック: リソース・トリガーファイル・スケジュール・Parquet・リスク・Go/No-Go依存の6観点。チェックリストを使って漏れを防止します
- 3つのタイミングで実行: 設計完了後・デプロイ前・同日デプロイ時。特に同日デプロイは競合リスクが最大であり、全組み合わせチェックが必須です
- AIを「スクリーニング」として活用: 1,378ペアの組み合わせチェックは人間には不可能です。AIに網羅的に検出させ、最終判断は人間が行います