◉ANTIGRAVITY LABEN
記事一覧/アプリ開発
▣ アプリ開発/2026-07-10上級

エージェントの書いたORMコードでp95が5倍に — クエリ回数をテストで測り、CIで止める

エージェントの書いた ORM コードで一覧 API の p95 が約5倍に跳ねた経験から、クエリ回数をテストで測れる数値にして CI で止める仕組みを作りました。固定閾値が効かない理由と、データ量の傾きで判定するアサーションの設計を解説します。

Antigravity377パフォーマンス8ORMCI9テスト設計

✦ プレミアム記事

深夜、ダッシュボードの p95 レイテンシが跳ね上がっているのに気づきました。940ms。前日までは 180ms 台で安定していた一覧APIです。

障害ではありません。エラー率はゼロ、レスポンスの中身も正しい。ただ遅い。本番環境の数字だけが、約 5.2 倍に膨らんでいました。

原因を辿ると、その日の午後にエージェントへ委ねた小さな改修に行き当たりました。一覧に「各アイテムの最新コメント」を足すだけの変更です。差分は 12 行。レビューで私は「読みやすい」とだけ思い、そのまま通していました。

12 行のうちの 1 行が、ループの中でリレーションを引いていました。

正しいコードは、速いコードとは限らない

N+1 クエリの厄介さは、コードとして間違っていないことです。

型は通ります。テストも通ります。返る JSON も期待通りです。レビュアーが目にするのは for ループの中の await getComments(item.id) という、それ自体は何の問題もない一行だけ。呼び出しの回数がリクエストの中身に依存して増えるという事実は、差分のどこにも書かれていません。

エージェントに実装を任せるようになってから、この非対称性が一段と効いてくると感じています。人間なら「ここはループの中だから注意」と身体が反応する場面でも、生成されたコードは常に落ち着いて正しく見えます。私自身、差分の行数が少ないほど油断していました。個人開発で私はこの油断を何度も繰り返しています。

だからレビューの精度を上げる方向で解こうとするのは、筋が悪いと考えるようになりました。人間の注意力に依存しない指標が要ります。

観測できないものは、守れません。

クエリ回数を「テストで読める数値」にする

幸い、主要な ORM はどれもクエリ発行のフックを持っています。ここを使えば、テストの中でクエリ回数を数えられます。

まず Prisma。クライアントを query イベント付きで生成し、カウンタに積むだけです。

// test/support/query-counter.ts
import { PrismaClient } from "@prisma/client";
 
export const prisma = new PrismaClient({
  log: [{ emit: "event", level: "query" }],
});
 
let count = 0;
const seen: string[] = [];
 
prisma.$on("query", (e) => {
  count += 1;
  seen.push(e.query);
});
 
export function resetQueryCount(): void {
  count = 0;
  seen.length = 0;
}
 
export function getQueryCount(): number {
  return count;
}
 
export function getQueries(): readonly string[] {
  return seen;
}

Drizzle では logger を差し替えます。

// test/support/drizzle-counter.ts
import { drizzle } from "drizzle-orm/node-postgres";
import type { Logger } from "drizzle-orm/logger";
 
class CountingLogger implements Logger {
  count = 0;
  logQuery(query: string): void {
    this.count += 1;
  }
}
 
export const logger = new CountingLogger();
export const db = drizzle(pool, { logger });

Python / SQLAlchemy なら before_cursor_execute フックです。

# tests/support/query_counter.py
from contextlib import contextmanager
from sqlalchemy import event
 
@contextmanager
def count_queries(engine):
    counter = {"n": 0}
 
    def _on_execute(conn, cursor, statement, params, context, executemany):
        counter["n"] += 1
 
    event.listen(engine, "before_cursor_execute", _on_execute)
    try:
        yield counter
    finally:
        event.remove(engine, "before_cursor_execute", _on_execute)

ここまでは単なる計測です。問題は、この数値をどう判定に使うかでした。

✦

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

この記事の続きを読む

この先には、実装コードやベンチマーク結果など、実務でお役に立てる内容をご用意しています。このサイトは広告を掲載しておらず、サーバーや開発にかかる費用はメンバーの皆様のご支援で成り立っています。もしお役に立てていましたら、ご支援いただけますと大変ありがたいです。

この記事で得られること
✦クエリ回数をテストで観測し、入力件数に対する傾きで N+1 を機械判定する仕組み
✦Prisma / Drizzle / SQLAlchemy それぞれのフック実装と、CIに組み込む完全なコード
✦実測値で見る効果と限界: p95 940ms→190ms、ゲート追加コスト +11秒
Stripe による安全な決済 · いつでもキャンセル可能
✦

この記事を購入する

この先の内容をすべてお読みいただけます。一度のご購入で、いつでも何度でもアクセスできます。このサイトは広告を掲載しておらず、皆さまのご支援がサーバー費用などの運営を支えています。

または
メンバーシップなら全記事が読み放題 →
シェア

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

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

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

関連記事

▣ アプリ開発2026-09-03
musllinux ホイールが無い依存は、Alpine で失敗せずに遅くなります
Antigravity Python SDK の musllinux ホイール対応を機に、Alpine コンテナで C 拡張が純 Python 実装へ落ちる経路を実測しました。protobuf の直列化は 0.043ms から 36.2ms へ変わります。事前の棚卸しと検知の手順まで書き残します。
▣ アプリ開発2026-09-29
Cloudflare をパージしても古い壁紙が届き続けた原因は、オリジン側で JPEG を握っていた XPageSpeed
同じ URL に上書きした壁紙画像が、Cloudflare をパージしても古いまま届き続けた運用の記録です。ローカル・オリジン・エッジの三者を突き合わせて層を名指しする検査、プレフィックスパージの使いどころ、Xserver の XPageSpeed を .htaccess で安全に切る書き方まで扱います。
▣ アプリ開発2026-09-19
見積書 PDF の自動化を一度にまとめて頼み、4 つに割り直すまで
受託の見積書を毎回コピーして作っていた手作業を、Antigravity に頼んで小さな PDF 出力ツールへ移した記録です。最初に全部をまとめて頼んで作り直すことになった理由と、データ・計算・見た目・保存の 4 つに割って渡し直した順番を、動くコードと一緒に書き残します。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます