ANTIGRAVITY LABEN
記事一覧/アプリ開発
アプリ開発/2026-04-26上級

Antigravity のマルチエージェントで SQL クエリ最適化を分業する実践ワークフロー

EXPLAIN プランの解析、インデックス設計、マイグレーション生成、ベンチマーク検証を Antigravity の専門エージェントに分業させ、SQL チューニングをチーム作業のように回す実践設計を解説します。

antigravity436sqlpostgresql5multi-agent21performance8database4

データベースが遅くなり始めるのは、たいてい「実装を一段落させた直後」です。テーブル件数がまだ少ない開発初期はどんな書き方でも秒未満で返ってくるので、何が悪いのか分からないまま本番に移ります。そして数ヶ月経って、ある朝ダッシュボードを開いた瞬間にスピナーが回り続けるという体験は、個人開発をしていると何度か必ず味わうものではないでしょうか。

私自身、運営している壁紙アプリの管理画面でこの問題に何度かぶつかってきました。原因は毎回違うのですが、やることはだいたい同じです。EXPLAIN ANALYZE を眺めて、インデックス候補を考え、マイグレーションを書き、本番前に手元で検証します。この一連の流れを「自分一人 + 単発のチャット AI」で回していると、どうしても判断の偏りが出ます。

そこで最近運用しているのが、Antigravity のマルチエージェントを「役割の違う 4 人のレビュアー」に見立てて、SQL チューニングを分業させるワークフローです。ここではその設計思想と実装を順を追って共有します。

なぜ SQL チューニングを「マルチエージェント」で分業するのか

単一のチャットセッションに EXPLAIN ANALYZE の結果を貼って「速くしてください」と頼むと、AI は「とりあえずインデックスを足しましょう」と提案しがちです。これは AI が悪いのではなく、文脈が混在した指示に対して最も無難な答えを返しているだけです。

私が分業に切り替えた理由は次の三つです。

最初の理由は、プロンプトを混ぜるとレビュー観点が薄まることです。解析と改善案を同じセッションで投げると、AI は早く結論を出そうとして、根拠の薄いインデックスを推薦してきます。役割を分けると、解析担当には「事実だけ抽出する」、改善担当には「複数案を比較する」と指示が明確になります。

次の理由は、履歴を分けると検証可能性が上がることです。あとで「なぜこのインデックスを採用したのか」を読み返すとき、解析・改善・実装の出力が別ログとして残っていると、判断の根拠を 1 ステップずつ追いかけられます。

最後の理由は、Antigravity の Manager Surface との相性が良いことです。Manager から複数エージェントを並列に走らせて、人間は最終承認だけ担当する形にすると、個人開発でも疲労感なく回せます。Manager Surface の基本操作はAntigravity Manager Surface 完全ガイドで別途整理しているので、未読の方は先に目を通しておくと話が早いです。

ワークフロー全体像 — 4 つの専門エージェント

私が運用しているのは次の構成です。それぞれを Antigravity の Sub-Agent として登録し、Manager から順に呼び出します。

  • 解析エージェント (Analyzer): EXPLAIN (ANALYZE, BUFFERS, VERBOSE) の出力を構造化 JSON に落とす。ボトルネック行のノードタイプ、推定コスト、実測時間、行数誤差を抽出する
  • 改善案エージェント (Strategist): 構造化された解析結果から、インデックス追加・書き換え・スキーマ変更の 3 系統で複数案を提示します。各案にトレードオフ(書き込み速度・容量・既存クエリへの影響)を必ず添える
  • 実装エージェント (Implementer): 採択された案をマイグレーションファイルに変換します。CREATE INDEX CONCURRENTLY の利用、ロック範囲、ダウンタイム見積もりまで含める
  • 検証エージェント (Verifier): ステージングまたはローカルレプリカでベンチマークを実行し、改善前後の EXPLAIN ANALYZE を再取得して比較する

この 4 役は厳密に切り分けると、Antigravity の Custom Agents 設定でも数行ずつのシステムプロンプトで定義できます。下のサンプルは私が実際に使っているもののうち、Strategist を抜粋したものです。

You are a SQL Tuning Strategist for PostgreSQL 16.
You receive a JSON object describing the slowest node of an EXPLAIN ANALYZE plan.
 
Your output MUST be a JSON array of 3 candidates with the following shape:
 
  {
    "kind": "index" | "rewrite" | "schema",
    "summary": "...",
    "ddl_or_sql": "...",
    "tradeoffs": {
      "write_amplification": "low" | "medium" | "high",
      "storage_estimate_mb": number,
      "affects_other_queries": string[]
    },
    "confidence": 0.0 - 1.0
  }
 
Rules:
- Never propose more than one new index per candidate.
- If the plan shows a row-count estimation error >= 10x, ALWAYS include a "rewrite" candidate that fixes statistics or adds a manual hint.
- Reject any "schema" candidate whose downtime estimate is unknown.

このようにエージェントごとに「禁止事項」を仕込んでおくと、Antigravity の Browser Agent やコード編集エージェントと違って、SQL の領域に集中させやすくなります。Browser Agent との連携が気になる方はブラウザサブエージェント実践ガイドも参考になるはずです。

ステップ 1: 解析エージェントで EXPLAIN プランを構造化する

最初のステップは、人間がやると一番疲れる「プラン読解」です。Antigravity に渡すときは、生のテキストではなく、PostgreSQL の EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) を使うのが鉄則です。テキスト形式は AI にも人間にも誤読の温床になります。

-- 期待する出力: 各ノードのコスト・実測時間・バッファヒット率を含む JSON
EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON, VERBOSE)
SELECT u.id, u.email, COUNT(o.id) AS order_count
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
WHERE u.created_at >= NOW() - INTERVAL '90 days'
GROUP BY u.id, u.email
ORDER BY order_count DESC
LIMIT 50;

このクエリの結果を Analyzer に投げると、私のプロジェクトでは次のような構造化出力が返ってきました。重要なのは「どのノードで時間が溶けているか」を断定する一行です。

{
  "bottleneck_node": {
    "node_type": "Seq Scan",
    "relation": "orders",
    "actual_rows": 4821000,
    "expected_rows": 482100,
    "row_estimate_error": "10x underestimate",
    "actual_time_ms": 2840,
    "buffers_read": 187432
  },
  "summary": "JOIN 側の orders テーブルで全件スキャンが発生し、行数推定が 10 倍ずれている。user_id 単独インデックスが存在するか、または ANALYZE が古い可能性。",
  "next_question_for_strategist": "user_id + created_at の複合インデックスが既存クエリに与える影響を評価してほしい"
}

ここで注目すべきは、Analyzer が自分では結論を出さず、Strategist への引き継ぎ事項を最後に書いている点です。役割を厳密に分けるとはこういうことで、Analyzer に「だからこのインデックスを足しましょう」と書かせない設計が、後段の改善案の質を底上げします。

ステップ 2: 改善案エージェントに複数案を出させる

Strategist は Analyzer の JSON だけを入力として受け取ります。元のクエリ本文や ER 図は別チャネルで渡す設計にしているのは、Strategist が「人間に確認したい質問」を出力しやすくするためです。

実際に返ってきた候補の一例がこちらです。

[
  {
    "kind": "index",
    "summary": "orders(user_id, created_at DESC) の複合インデックス追加",
    "ddl_or_sql": "CREATE INDEX CONCURRENTLY idx_orders_user_id_created_at ON orders (user_id, created_at DESC);",
    "tradeoffs": {
      "write_amplification": "medium",
      "storage_estimate_mb": 240,
      "affects_other_queries": ["GET /users/:id/orders", "管理画面の月次レポート"]
    },
    "confidence": 0.72
  },
  {
    "kind": "rewrite",
    "summary": "ユーザー件数が少ない期間に絞ってからカウントするサブクエリ化",
    "ddl_or_sql": "WITH recent_users AS (SELECT id, email FROM users WHERE created_at >= NOW() - INTERVAL '90 days') SELECT u.id, u.email, COUNT(o.id) FROM recent_users u LEFT JOIN orders o ON o.user_id = u.id GROUP BY u.id, u.email ORDER BY 3 DESC LIMIT 50;",
    "tradeoffs": {
      "write_amplification": "low",
      "storage_estimate_mb": 0,
      "affects_other_queries": []
    },
    "confidence": 0.83
  },
  {
    "kind": "schema",
    "summary": "users テーブルに集計列 last_30d_order_count を非同期で更新",
    "ddl_or_sql": "ALTER TABLE users ADD COLUMN last_30d_order_count INT DEFAULT 0; -- 別途バッチで更新",
    "tradeoffs": {
      "write_amplification": "high",
      "storage_estimate_mb": 80,
      "affects_other_queries": ["users への一覧 SELECT"]
    },
    "confidence": 0.41
  }
]

この出力で気付くのは、書き換え案の confidence が一番高い点です。インデックス追加は手軽に見えて、既存クエリへの副作用を評価しきれない場合があります。私の経験でも、迷ったときはまず「クエリそのものを書き換える」案を試すほうが、トータルの安全度は高いです。Drizzle ORM を使っている場合の書き換えパターンはAntigravity と Drizzle ORM で型安全なデータベース開発に整理してあるので、ORM 経由の書き換えも参考になります。

ステップ 3: 実装エージェントが安全なマイグレーションに変換する

採択した案を Implementer に渡すと、PostgreSQL の流儀に沿ったマイグレーションファイルが生成されます。私のシステムプロンプトでは、次の三つを必ず守らせています。

  • インデックスは CREATE INDEX CONCURRENTLY を使い、ロック競合を避ける
  • マイグレーション内に BEGIN / COMMIT を置かない(CONCURRENTLY はトランザクション外でしか動かないため)
  • ダウンタイム見積もり、ロールバック手順、適用前にステージングで実行する EXPLAIN を必ずコメントとして添える

実際に出力された Drizzle 用マイグレーションがこれです。

-- Migration: 20260426_add_orders_user_created_idx.sql
-- Purpose: Speed up "recent active users with order count" report
-- Estimated downtime: 0s (CONCURRENTLY)
-- Estimated build time on staging (3M rows): ~45s
-- Rollback: DROP INDEX CONCURRENTLY IF EXISTS idx_orders_user_id_created_at;
-- Pre-deploy verification:
--   EXPLAIN (ANALYZE, BUFFERS) <<the original query>>
--   ↑ Bottleneck node should change from "Seq Scan on orders" to "Index Scan".
 
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_orders_user_id_created_at
  ON orders (user_id, created_at DESC);
 
-- ANALYZE so the planner picks up new statistics immediately.
ANALYZE orders;

IF NOT EXISTS を入れているのは、本番再適用時の冪等性を確保するためで、これは AI に任せると忘れがちなポイントです。Implementer のプロンプトに「再適用しても壊れないこと」と一行入れておくだけで、再現性が大きく変わります。

ステップ 4: 検証エージェントでベンチマークを取る

最後の Verifier は、ステージングまたはローカルのレプリカに対して、改善前後の EXPLAIN ANALYZE を取り直し、差分レポートを生成します。Antigravity の Terminal Tool を有効化しておけば、psql コマンドの実行結果まで一連のフローでまとめてくれます。

=== Verifier Report (2026-04-26 05:18 JST) ===
Query: monthly_active_users_top50
 
Before
  Bottleneck: Seq Scan on orders
  Total time: 2840 ms
  Buffers: shared hit=1182 read=187432
 
After
  Bottleneck: Index Scan using idx_orders_user_id_created_at
  Total time: 312 ms (-89%)
  Buffers: shared hit=4019 read=2840 (-98% read)
 
Verdict: APPROVED
  Notes:
    - 行数推定誤差は 10x → 1.2x に改善
    - 既存クエリ "GET /users/:id/orders" の plan は不変、追加コストは観測されず

この Verdict が APPROVED になった瞬間に、Manager から人間にレビュー依頼が届く設計にしています。ここで初めて私自身がプランを目視確認し、本番に流す判断をします。自動化のゴールは「人間が判断する場面を減らす」ことではなく、「人間が判断するべき場面まで上質な情報を運ぶ」ことだ、と最近強く感じています。

本番投入時に Antigravity 任せにできない 5 つのポイント

ここまで自動化しておきながら、それでも本番直前で人間が必ず確認するべきポイントがあります。これは AI が苦手というより、コンテキストが足りない領域です。

最初に確認するのは、インデックス追加によるディスク容量とバックアップ時間の影響です。240 MB と見積もられても、運用しているマネージド DB のディスク I/O やバックアップ window によっては想定より重いケースがあります。

次は、書き換え案がアプリケーションコードのキャッシュ前提を壊さないかです。クエリ本文が変わると、Redis や CDN にキャッシュしていたキー設計が破綻することがあります。Upstash Redis を使っている場合の整合性チェックはAntigravity と Upstash でエッジキャッシュとレートリミットにまとめてあります。

三つ目は、統計情報の鮮度です。ANALYZE orders; を入れているとはいえ、巨大テーブルの場合は default_statistics_target を上げてから再 ANALYZE する必要があります。AI はこの値を勝手に変更しないように設計しておくほうが安全です。

四つ目は、読み取り専用レプリカへの伝播時間です。マルチリージョン構成では、インデックスが伝播するまでクエリプランが揺れる時間帯があります。

最後は、ロールバック条件の明文化です。Verifier の Verdict が APPROVED であっても、本番投入後に CPU 使用率が想定外に上がったらどう戻すか、を手順書として残しておかないと、夜中に呼び出されたときに迷います。

よくある間違いと落とし穴

このワークフローを試すときに、私自身がやらかしたことを共有します。

ひとつ目は、Analyzer に生のテキストプランを投げることです。前述の通り、JSON 形式でないと AI も人間も誤読します。EXPLAIN ANALYZE の出力をそのままコピペしているうちは、ワークフローの効果が半分以下になると思ってください。

ふたつ目は、Strategist に元のクエリ本文を渡してしまうことです。Strategist は「ボトルネックの構造」を入力にしないと、書き換え案より「とりあえずインデックス」を選びがちになります。文脈を絞ることが、AI の判断品質を底上げする最短経路です。

三つ目は、confidence が高い案を盲信することです。0.83 と表示されても、それは AI 内部の確からしさであって、本番環境の実態とは切り離して考えるべきです。私はあくまで「優先順位の参考」として扱い、最終的には Verifier の数字を信じています。

四つ目は、Verifier をスキップすることです。Implementer の出力が綺麗だとつい本番に流したくなりますが、ステージングでの EXPLAIN ANALYZE 比較を必ず挟まないと、行数推定が改善しないままインデックスだけが太るパターンに陥ります。

五つ目は、マルチエージェントの履歴を捨てることです。私は各エージェントの出力を Markdown でログ保存し、四半期ごとに見返すようにしています。「あのときなぜこの案を採ったか」を後追いできることが、長期的なチューニング判断の精度を支えてくれます。

全体を振り返って — 明日試す一歩

このワークフローを丸ごと真似する必要はありません。読み終わったあとに、ひとつだけお願いがあります。今手元で一番遅いクエリを EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) で取得して、その JSON を Antigravity の新しいセッションに貼り付け、「ボトルネックノードだけを構造化して教えてください」と頼んでみてください。これだけで、Analyzer の役割を一人分こなしたことになります。

そこから少しずつ、Strategist、Implementer、Verifier と役割を切り出していくと、自然にマルチエージェント運用に近づきます。私はこの構造に切り替えてから、SQL チューニングを「気合と勘」ではなく「手順」として扱えるようになりました。一人で開発している方ほど、このワークフローの恩恵は大きいはずです。

データベース設計と運用の土台をもう一段深めたい方は、達人に学ぶSQL徹底指南書 第2版(ミック著)が、本記事で扱った EXPLAIN プラン読解と書き換え戦略の理論的背景を埋めてくれます。本番運用での失敗から

シェア

お読みいただきありがとうございます

Antigravity Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

アプリ開発2026-05-01
Antigravity で実装するゼロダウンタイム DB マイグレーション:Expand-Contract パターンと AI 駆動の安全策
本番DBの型変更・カラム名変更・テーブル分割を、ユーザー影響ゼロで遂行するための Expand-Contract パターンを Antigravity の AI 支援とともに完全実装する本番運用ガイドです。
アプリ開発2026-03-28
Antigravity × Supabase でリアルタイム対応フルスタックアプリを構築する実践ガイド
Antigravity IDE と Supabase を組み合わせて、認証・データベース・リアルタイム同期を備えたフルスタックアプリを構築する方法をステップバイステップで解説します。
アプリ開発2026-07-18
「Compose ファースト」の後で、View の画面をどの順に移すか — 画面数ではなく変更頻度で決める
Google が Android 開発を Compose ファーストと表明しました。View ベースの画面をどの順で移すかを、画面数ではなく git の変更履歴から決める判断軸と、実際に動かしたスコアリングスクリプトをまとめます。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →