🐢

バイブコーディングした管理画面が遅い。クエリを半分にしても体感は変わらなかった

に公開

AIに指示を出して、社内向けの管理画面を作りました。ちゃんと動きます。手元では一覧が一瞬で出ます。本番だけ、開くたびに数秒なにも出ません。

そこで測って、いちばん重いクエリを 375ms から 169ms に縮めました。体感はまるで変わりませんでした。 結論から言うと、真因はコードの中にありませんでした。誰も触っていない既定値です。AIは動くコードを書きますが、既定値は直しません。指示していないからです。

「動いてはいるけど遅い」で止まっている人の時短になれば嬉しいです。

つくっていたもの

受発注まわりの社内システムです。マスタを登録し、イベントごとに原価を集計し、発注書を出します。画面は 30 ルートほどあり、ほぼすべてが一覧と編集です。

  • アプリ: Next.js 16(App Router)on Vercel
  • データ: Supabase(Postgres)+ Prisma
  • 書いた人: ほぼAI

要件を伝えて、出てきたコードを読んで、直して、マージする。いわゆるバイブコーディングです。動くものは驚くほど速く出てきました。

そして最初の報告は、具体的ではありませんでした。「少し重たいです、全体的に」です。

なぜ「あるある」なのか

バイブコーディングは、動くところまでがとても速いです。そして動いた時点で、完成に見えます

罠はここです。速さに関わる設定は、間違っていても画面に出ません。エラーも出ません。テストも通ります。ただ遅いだけです。症状が「動かない」ではないので、直す指示が一度も出ないのです。

もうひとつあります。既定値は「いちばん速い値」ではありません。**「どこで動かしても壊れない値」**です。本来は作る側が選ぶものを、選ばないまま本番に出していました。

ただ、これに気づくのはずっと後です。このときの私は、素直にコードを疑いに行きました。

1 回目:コードの中を疑う

まずコードを読み、遅そうな場所を 8 つ挙げました。そのうち 1 つは誤診でした。

誤診:関連テーブルの数だけ往復しているはず

Prisma で関連テーブルをまとめて取ると、関連の数だけSQLが飛ぶ、と書きました。イベント詳細で 15〜18 往復している、とも書きました。

実測したら違いました。

イベント詳細の同じ取得 クエリ数 時間
指定なし(既定) 1 82ms
relationLoadStrategy: "join" 1 81ms
relationLoadStrategy: "query" 18 35ms

Prisma 7 は既定で 1 本のJOINを投げていました。私が足した指定は、完全に何もしていません。入れたプレビュー機能ごと取り消しました。

ここは既定値が正解だった例です。既定値は常に悪者、というわけではありません。だから厄介なのだと、あとで思い知ります。

なお手元では、18 往復する "query" のほうが 2 倍速く出ています。JOINで行が膨らむコストが、往復のコストを上回るためです。本番はDBが遠いので、この順位はそのまま持ち込めません。

実際に効いたもの

製品 1000 件・イベント 6 件・原価行 6000 行のデータを作って測りました。

Before After
イベント一覧のクエリ 375ms 169ms
製品追加ダイアログの候補取得 35ms 9ms
型番の突合(JSの処理) 8.2ms 0.3ms

中身は地味です。全部の列を取っていたのを、使う列だけに絞る。ダイアログの候補のために製品マスタを全件引いていたのをやめ、4 列だけの専用クエリにする。二重ループを Map に置き換える。

数字はきれいに出ました。この時点では、勝ったと思っていました。

画像は、毎回ダウンロードし直されていた

表示側でも 1 つ見つけました。こちらは素直に効いた実感があります。

画像の署名付きURLは、発行のたびにトークンが変わります。 URLが変われば、ブラウザから見ると別の画像です。つまり一覧を開くたびに、同じサムネイルを全部ダウンロードし直していました。

期限内は同じURLを返すようにして、2 回目以降をブラウザのキャッシュに任せます。ついでに、足りない分だけを発行しに行くのでストレージへの往復も減ります。

「なぜか画像だけ毎回もたつく」は、署名付きURLを使っていると高確率で踏みます。

やらかし:計測のつもりで、他人のDBを触った

計測のために supabase start を叩き、ポート 54322 につなぎました。そこは別のワークツリーが握っているスタックでした。気づく前に、他プロジェクトの開発用DBへ検証用の組織を作っています。

作った行を id 指定で消して片付けました。以降は使い捨てのデータベースを作って測り、最後に落としています。

原因を追うと、これも既定値でした。このプロジェクトのDBはポート 54422 です。.env.example に書いてある 54322 を、疑わずにそのまま使ったのが元でした。サンプルの値は、動く値であって、正しい値ではありません。

そして、体感は変わらなかった

クエリは実際に半分になりました。数字も残っています。それでも「全体的に重い」は、そのまま残りました。

ここで手が止まります。

測って、直して、速くなったのに、なぜ変わらないのか。

答えは単純でした。私が縮めたのは、支配項ではなかったのです。

2 回目:コードの外を疑う

きっかけは、別で触っている管理画面でした。同じ Supabase を使っているのに、明らかに速い。差を調べてほしい、という依頼が来ます。

そちらは、ブラウザから Supabase を直接呼ぶ作りでした。画面の骨組みはCDNから即座に出て、データは日本のブラウザから東京へ 1 往復です。こちらはサーバーで画面を組むので、ブラウザからサーバー、サーバーからDBと段を踏みます。

そのうえで、一度も触っていない既定値が 3 つありました。

  1. サーバーが動く場所vercel.json がなく、リージョンの指定もゼロ。つまり既定の iad1、米国東部です。Supabase は東京にありました。1 往復ごとに、太平洋を越えていたわけです
  2. DBへの接続の張り方 — 直接接続のままでした。サーバーレスは実行のたびに新しい箱が立つので、毎回かけ直して名乗り直すところからやっています
  3. キャッシュ — 有効化する設定だけ入っていて、use cache を grep した結果は 0 件でした。設定を入れた時点で満足していたやつがいます。私です

一覧 1 枚あたり 3 往復、1 往復が 150〜200ms なら、それだけで 500ms 以上が待ち時間で消えます。私が削った 200ms は、まるごとこの中に埋もれていました。

この 2 回目の調査と、リージョン・接続・キャッシュをどう直したかは、別記事に詳しく書いています。キャッシュに何を載せて何を載せないか、?pgbouncer=true が要るという思い込みを型定義で潰した話も、そちらにあります。

https://zenn.dev/yoshinani_dev/articles/3f9a1c7e08b24d

学び

1. AIは「動くもの」を書く。速いものは、頼まないと出てこない

リージョンも接続方式もキャッシュも、一度も指示していません。指示していない項目は、既定値のまま本番に出ます。 そして既定値は、動作確認では絶対に落ちません。

2. 既定値は「安全な値」であって「速い値」ではない

iad1 も直接接続も、壊れません。ただ遅いだけです。一方で relationLoadStrategy のように、既定が正解のこともあります。既定値は敵でも味方でもなく、単に「誰も選んでいない」という状態です。

3. 半分にしても体感が変わらないなら、支配項が別にある

375ms を 169ms にして、何も変わりませんでした。半分にしたのに効かない、は「そこは支配項ではない」という立派な観測結果です。 ここで喜ばずに引き返せるかどうかが分かれ目でした。

4. 「効くはず」を実測せずに入れると、効いていないことにも気づけない

relationLoadStrategy: "join" は何もしていませんでした。測っていなければ、勘違いしたまま次の判断へ進んでいます。前と後を測らない改善は、改善ではなく願望です。

5. 手元の計測は、本番の順位を保証しない

手元では 18 往復のほうが 2 倍速く出ました。DBが同じマシンにいるからです。本番はDBが遠いので、この順位は逆転します。計測環境と本番で「何が高くつくか」が違うなら、順位もそのままでは持ち込めません。

6. サンプルの値を、そのまま信じない

.env.example のポートを疑わずに使い、他プロジェクトのDBを触りました。サンプルは動く値であって、いまのプロジェクトの正しい値ではありません。 既定値を疑う話は、コードの外だけでなく手元の設定にも同じく効きます。

おわりに

バイブコーディングだと、疑う先がまずコードになります。目の前にあるのがコードだからです。しかも読めば必ず改善点は見つかるので、直せば少し速くなり、少し速くなるぶん、思い込みが延命します。

今回そこに 18 日かけました。1 回目の改善から、外を疑うまでの日数です。

AIが触っていない場所こそ、既定値のまま残ります。ダッシュボードの設定と、接続文字列と、有効化しただけの機能。もし手元のアプリがなんとなく遅いなら、コードを開く前に、そのあたりを一度見てください。

参考

GitHubで編集を提案
YOSHINANI

Discussion