サインアップ ダッシュボード
2026年9月21日

「スロップ」を本番環境に到達させない方法

Explore with AI

おそらく、ソフトウェアファクトリーを自社で構築しているか、プロバイダーからリースしているかのどちらかでしょう。プロンプトからプルリクエストまでを担うシステムがあり、テスト、リンティング、フォーマット、自動コードレビューを処理します。

しかし、最も重要な問い、この変更は本番環境に出しても大丈夫か、にはまだ答えがありません。

graph TB
    W["Agent writes the code"] --> T["Types and tests"]
    T --> L["Linter and formatter"]
    L --> R["Code review"]
    R --> Q(["Is this okay for prod?"])
    Q --> D["Deploy"]
    style Q fill:#fee2e2,stroke:#fca5a5,color:#7f1d1d

既存のチェックはすべて差分を見ていますが、ファクトリー内のどこにも、その差分がこれから適用される本番システムについて把握しているものはありません。スロップが本番環境に到達するのを実際に防ぐものは何もないのです。

私たちはこの機能をPolylaneの中に構築しました。この記事では、その実装方法の技術的な詳細について説明します。

仕組み

このシステムが答えるべき唯一の問いは、次のとおりです。

この変更は、マージされてデプロイされた場合、本番環境に悪影響を及ぼすでしょうか。

コードレビューエージェントが通常チェックするような、スタイル、命名、テストカバレッジなどには、私たちはあまり関心がありません。そして、この問いにはプルリクエストの段階で、既存のすべてのテストと並行して答えることにしました。

最終的に、Polylaneは調査の証拠とともに、シンプルな「go」または「no-go」のメッセージをプルリクエストにコメントします。

流れは至ってシンプルです。

  • このプルリクエストは、本番環境に影響を与える可能性のあるファイルに触れているか
  • どのクラウドリソースが影響を受ける可能性があるか
  • それらのリソースについて、本番環境の現在の状態に関するコンテキストを収集する
  • この変更が引き起こしうる複数の潜在的な障害モードを評価する
  • これらの変更がデプロイされた場合に本番環境がどう変化しうるかを予測する
  • 起こりうる障害モードについて開発者に警告する
polylane bot commented 2 minutes ago ···
Caution

Merging this pull request may degrade production (high impact).

Merging this blocks every write to orders while the index builds. migrations/0114_order_search_trgm.sql:3 adds CREATE INDEX … USING gin (search_text gin_trgm_ops) without CONCURRENTLY, and a plain CREATE INDEX takes a full write lock on orders for the whole build. Checkout sustains ~38 writes/s on that table; each one queues behind the lock until the build finishes.

To make this safe: build the index with CREATE INDEX CONCURRENTLY outside the transactional migration.

orders-db · writes per second · last 48h
0 20 40
-48h -24h now
every one of these writes blocks while the index builds
no-goの判定: 1行目にメカニズム、その下に証拠、そして変更を安全にする条件が示されます。

これはすべて、様々なクラウドアカウント内のあらゆるクラウドリソースを結びつけながら継続的に構築しているContext graphに依存しています。

graph TB
    P["Open pull request"] --> Q(["Meaningful code change?"])
    Q -->|"docs, tests, comments"| C["Conclude the check"]
    Q -->|"yes"| R["Find affected resources<br/>in the context graph"]
    R -->|"none"| C
    R -->|"yes"| E["Gather context"]
    E --> X["Build failure trajectories"]
    X --> F["Forecast the affected series"]
    F --> V["Verdict"]
    V --> C
    style X fill:#fef3c7,stroke:#fcd34d,color:#78350f
    style F fill:#fef3c7,stroke:#fcd34d,color:#78350f
    style V fill:#d1fae5,stroke:#6ee7b7,color:#065f46

コンテキストの構築

私たちのContext graphがこれを可能にする鍵であり、あらゆるクラウドリソース、リポジトリ、チームなどのレジストリを構築します。例えば、Lambda Functionのようなコンピュートノードは、それが読み取るデータベースや、それをトリガーするキューと結びついています。リポジトリもグラフに追加します。これは、リポジトリ内にある典型的なマニフェストファイル、例えばTerraformファイル、CloudFormationファイル、Wranglerファイルなどを調べることで行われます。これにより、リポジトリとクラウドリソースの間の結びつきが可能になります。

graph LR
    Q["SQS queue<br/>orders-events"] -->|"triggers"| A["Lambda function<br/>checkout-api"]
    R["Repository<br/>checkout-edge"] -->|"deploys_to"| A
    A -->|"connects_to"| D["RDS instance<br/>orders-db"]
    style R fill:#d1fae5,stroke:#6ee7b7,color:#065f46

プルリクエストがリポジトリに送信されると、Context graph上のパスをたどり、その変更によって影響を受ける可能性のあるすべてのクラウドリソースを収集します。同じリポジトリからデプロイされるリソースが大量にある場合があるため、小規模なモデルを使ってリソースを絞り込みます。このコンテキストを、差分、PRの説明、PR内のコミットとあわせてエージェントに渡します。

graph LR
    R["Repository"] -->|"deploys_to"| C["Candidate resources"]
    C --> F(["Small model filters"])
    F --> A["Agent"]
    D["Diff, description, commits"] --> A
    style A fill:#d1fae5,stroke:#6ee7b7,color:#065f46

また、差分を一連の決定的なヒューリスティックにも通し、通常本番環境に悪影響を及ぼしやすい事柄へエージェントの注意を素早く向けさせます。

  • 手動で適用する必要がある、あるいはデプロイに対して特定の順序で適用する必要があるマイグレーション
  • CONCURRENTLYを伴わないCREATE INDEXや、デフォルト値のないADD COLUMN ... NOT NULL。どちらもその間テーブルにロックをかけます
  • 現在デプロイされているバージョンがまだ読み取っているエンドポイントが削除されること
  • 差分の中で何もプロビジョニングしていない環境変数、シークレット、またはバインディングを読み取り始めるコード

これらはあくまで推奨事項であり、エージェントを起こりうるデプロイのリスクへと導くものです。

障害のトラジェクトリー

上記のコンテキストを踏まえ、モデルは新しい差分が本番環境にもたらしうる様々な障害モードを考え出し、それぞれを調査します。

その出力は、障害のトラジェクトリーの台帳です。トラジェクトリーとは、トリガーから変更されたコードを経て、特定のメトリクスにおける観測可能な劣化に至る、1本の因果の連鎖です。その中のすべてのリンクには、ファイルと行、件数付きのログテンプレート、メトリクスの読み取り値、設定キー、グラフのエッジといった引用が付いています。

エージェントは判定を下す前に、それぞれのトラジェクトリーを検証し、また反証しようと試みます。各トラジェクトリーは3つの状態のいずれかで終わります。

  • トラジェクトリーが本番環境に対して確認された場合のconfirmed
  • 連鎖は具体的だが、1つまたは複数のリンクを確認するテレメトリーデータがなく、「推測」するしかなかった場合のplausible
  • 本番環境のテレメトリーデータが、このトラジェクトリーが本番環境で発生する可能性が低いことを示す十分な証拠を提供した場合のrefuted

私たちは保守的なアプローチを取っており、confirmedのトラジェクトリーが1つでもあれば、評価は失敗となります。

export function deriveTrajectoryVerdict(trajectories: Trajectory[]): "pass" | "fail" {
  return trajectories.some((entry) => entry.status === "confirmed") ? "fail" : "pass";
}

graph LR
    D["The diff"] --> T1["Dropped index still<br/>used by checkout"]
    D --> T2["Retry change amplifies<br/>load on orders-db"]
    D --> T3["Removed binding<br/>breaks the worker"]
    T1 --> C1["confirmed"]
    T2 --> C2["plausible"]
    T3 --> C3["refuted"]
    C1 --> V["No-go"]
    C2 --> V
    C3 --> V
    style C1 fill:#fee2e2,stroke:#fca5a5,color:#7f1d1d
    style C2 fill:#dbeafe,stroke:#93c5fd,color:#1e3a8a
    style C3 fill:#d1fae5,stroke:#6ee7b7,color:#065f46
    style V fill:#fee2e2,stroke:#fca5a5,color:#7f1d1d

影響の予測

エージェントには、潜在的な外部要因を注入しながら、過去のデータに基づいて時系列を予測するツールを与えています。

例えば、あるトラジェクトリーが、ある変更によってリトライするパスのタイムアウトが半減すると主張しているとします。それが重要かどうかはトラフィック次第であり、トラフィックは単一の数値ではなく、形を持っています。キューの深さが60%で安定している分には問題ありません。同じキューが60%でも、毎週上昇し続けているなら話は別です。直近1時間のメトリクスを見ただけでは、どちらの状況なのかは分かりません。

そのため、エージェントはトラジェクトリーの判定案を作成する前に、影響を受けるリソースの過去の系列を取得し、まとめて予測します。現在はToto-2.0-22mモデルを自前でホストしています。

graph LR
    A["Agent"] -->|"telemetry query"| P["Provider"]
    P -->|"aligned series"| A
    A -->|"up to 16 series"| M["Forecasting model"]
    M -->|"p10, p50, p90"| A
    style M fill:#fef3c7,stroke:#fcd34d,color:#78350f

別のプロンプトではなく専用の多変量モデルを使う理由は、系列が独立していないためです。1つのリソースにおけるリクエストレート、エラーレート、レイテンシー、キューの深さは連動して動くため、それぞれを個別に予測すると、予測を意味あるものにしている相関を捨ててしまうことになります。1回の呼び出しに含まれるすべての系列は、1つのアテンショングループを共有します。

これは最近追加した中で最も実験的な機能であり、その効果はまだ計測中です。しかし、すでに「vibes-eval」には合格しています。

checkout-api request forecast
Hourly observations from the latest 64 complete buckets, followed by a 24-hour probabilistic forecast.
observed p10–p90 forecast
20,000 25,000 30,000 35,000 Forecast starts Sep 18 00:00 Sep 18 20:00 Sep 19 16:00 Sep 20 12:00 Sep 21 08:00 Time (UTC) Requests per hour
図2
予測の仕組みの例
影響を受ける1つのリソースについて、1時間ごとの観測値64件を入力すると、24時間分の中央値とp10からp90が返ってきます。予測期間が長くなるほど帯は広がり、エージェントは中央値だけでなくその帯を読み取ります。トラジェクトリーが依存するしきい値を上回ったままのp10は、それを下回るp10とは異なる答えになります。

レイテンシーの制約

プルリクエストのフロー内で本番環境への影響評価を実行するということは、それが高速でなければならないということです。CIパイプラインに15分も追加するステップを望む人はいません。例えば、私たち自身のリポジトリでは、この評価は必須のCIステップとなっており、遅ければSDLC全体が滞ってしまいます。

正確な影響評価を出すための予算は、実質2分から3分です。それを超えるものはCI/CDパイプラインでは許容できません。

最初のプロトタイプは、この制約を完全に満たせていませんでした。中央値は7分近くあり、実行に20分もかかることも珍しくありませんでした。

0 s 60 s 120 s 180 s 240 s 300 s 前処理 Webhook、レコード、差分 7.4 s サンドボックス headをクローン 16.5 s レビューターン 242.4 s 配信 チェック実行、コメント 3 s 計上外 38.6 s
図3
本番環境への影響評価の各ステップの中央値レイテンシー
本番環境の中央値、2026年9月15日、325回の実行に基づきます。各フェーズはそれぞれ独自の中央値であるため、末尾の計上外ブロックは、フェーズ間のキューイングと中央値を加算する際の演算によるものです。

目標は、フロー全体を3分以内に収めるために、モデルのステップ数をいかに減らすかを見極めることになりました。通常は30から40の逐次的なモデルステップを実行しており、最悪のケースではほぼ600ステップに達することもありました。各ステップは予算のうち17秒を消費し、その出力の大部分は推論トークンでした。

過去3週間でかなりの数の変更を加え、パフォーマンスへの影響も様々でした。ここでは特に大きなものを紹介します。

ステップ予算を設定し、エージェントに伝える

エージェントのターンごとに30ステップを上限とするステップ予算を導入し、モデルには各ステップで自身が消費したステップ数を明確に伝えるようにしました。プロンプトにその数を含めることで、モデルはそれを見越して計画を立てられるようになり、これは数値そのものよりも重要だと分かりました。

graph TB
    A["Turn starts, 30 steps"] --> B["Model step"]
    B --> T["Tool call"]
    T --> C(["Steps left?"])
    C -->|"1"| E["Record the verdict now"]
    C -->|"more"| R["Next step, carrying<br/>the count in the prompt"]
    R --> B
    style R fill:#fef3c7,stroke:#fcd34d,color:#78350f
    style E fill:#d1fae5,stroke:#6ee7b7,color:#065f46

既存のレビューに畳み込むのではなく、新しいレビューを開始する

典型的なプルリクエストでは、開いた後も新しいコミットが追加され続けます。最初のプロトタイプでは、新しい差分全体を、ステアリング用のユーザーメッセージとして同じレビュースレッドに畳み込んでいました。このステアリングはモデルを大きく混乱させ、すでに調べたファイルを再度読み込んだり、すでに完了したテレメトリークエリを再実行したりする原因になっていました。

現在では、新しいコミットが来ると前回の評価実行をキャンセルし、まったく新しいスレッドを開始します。直感に反するように思えますが、最終的にはレビュー全体のレイテンシーを引き下げる結果になりました。

以前の作業を再利用する

レビューがすでに完了した後にプルリクエストへ新しいコミットが加わると、以前は単純にゼロから新しい影響評価を開始していました。

以前の評価を再利用し、連続する2つのコミット間のより小さな差分に対して新しい評価をトリガーする機能を導入しました。

これらの変更を9月を通してデプロイし、レイテンシーを徐々に改善してきました。現在ではレイテンシーは予算内に余裕を持って収まっています。

0 s 150 s 300 s 450 s 600 s 27 Aug 31 Aug 4 Sep 8 Sep 12 Sep 16 Sep 94
図4
影響評価ターンの中央値レイテンシー

これは本当に効果があるのか

意味のある結果が得られないのなら、これほど大量のトークンを消費する意味がどこにあるでしょうか。私たちの成功指標は、各顧客について週あたりに防いだインシデントの数です。防がれたインシデントとは、次のようなプルリクエストです。

  • 本番環境への潜在的なリスクにフラグを立てる
  • エンジニアが1つまたは複数のコミットをプッシュする
  • 新しい評価により、その潜在的なリスクが軽減されたと結論づけられる
  • プルリクエストがマージされる
2.3
顧客ごと、週あたりに防がれた本番環境のインシデント数

これはすでにかなり大きな数字であり、今後も伸び続けると見込んでいます。私たちは継続的に反復し、評価を実行して、エージェントのパフォーマンスと品質の向上に取り組んでいます。

誰もオンコールをするべきではありません。 Polylaneはインフラを見守り、調査し、壊れたものを直します。

サインアップ

続きを読む