以下の内容はhttps://ishikawa-pro.hatenablog.com/entry/2025/09/17/113858より取得しました。


LLM時代のパフォーマンスチューニング:MongoDB運用で試したコンテキスト活用の工夫

このエントリは「第35回 中国地方DB勉強会 in 岡山」で「LLM時代のパフォーマンスチューニング:MongoDB運用で試したコンテキスト活用の工夫」というテーマで登壇した内容をブログ化したものです。発表スライドと発表時の文字起こしをベースに、読み物として違和感のないように整え直しました。図版はスライドのものを流用しています。

dbstudychugoku.connpass.com

speakerdeck.com

背景と目的

  • AIコーディングエージェントを“機能開発”だけでなく“性能改善”にも活用した実践記を共有します。
  • コードだけでは見えない現実(スロークエリやアクセス分布)を「コンテキスト」として渡し、提案→実装→再測定のループで改善幅を可視化します。
  • 今回の事例は MongoDB + Node/Express + Mongoose のミニECですが、RDB環境でも同じ発想を応用できます。

題材アプリの概要

フロントエンド

  • サーバーは Node.js + Express、ODM は Mongoose、データベースは Docker 上の MongoDB を利用しています。
  • フロントは商品一覧と詳細のみを持つ簡易構成で、本記事ではバックエンド寄りの視点に集中します。
  • プロダクトスキーマの主要フィールドは title, description, category, price, stock, tags, createdAt, popularity です。

システム構成図

ER図

パフォーマンス計測設計

  • データは 10 万件のサンプルを事前に投入してから計測
  • 負荷は k6ramping-vus を用い、同時接続 5→25 を 3 分でランプさせたあと 2 分維持し、30 秒でクールダウンさせる
  • リクエスト配分は一覧が 7 割、詳細が 3 割で、ソートは popularity 40%、price 30%、createdAt 30% に振り分ける。昇順と降順は半々、ページは 1〜5、件数は 12/24/48 をランダムに選択し、適宜 keyword や category、min/maxPrice を付与
  • 計測対象は API レイテンシ(平均値、95 パーセンタイル)と、100ms 以上の MongoDB スロークエリ

k6シナリオは以下のような設定です(抜粋)。

export const options = {
  scenarios: {
    baseline: {
      executor: 'ramping-vus', startVUs: 5,
      stages: [ { duration: '3m', target: 25 }, { duration: '2m', target: 25 } ],
      gracefulRampDown: '30s',
    },
  },
  thresholds: {
    'http_req_duration{endpoint:GET /products}': ['p(95)<500'],
    'http_req_duration{endpoint:GET /products/:id}': ['p(95)<300'],
  },
};
// 7:3で一覧と詳細を叩き、ソート・ページ・条件を都度ランダム化

下記は負荷試験アーキテクチャ図です。

負荷試験アーキテクチャ

改善前の状況(ベースライン)

  • /products の平均は 57.90ms、p95 は 149ms(n=16,150)
  • /products/:id の平均は 5.85ms、p95 は 35ms(n=6,951)
  • スロークエリは findcountDocuments が中心で、p95 はそれぞれ 194ms と 179ms

API の平均レイテンシーは次の図のとおりです。

API平均 (前)

API の p95 レイテンシーは次の図のとおりです。

API p95 (前)

MongoDB のスロークエリ散布図は以下のとおりです。

MongoDB 散布図(前)

スロークエリログの収集と集計フロー

  • サーバー側では Mongoose の自作プラグイン(slowQueryLogger)を挿入し、100ms を超えたクエリを NDJSON に 1 行追記するようにしました。
  • サンプル(整形した slow query log)は次のとおりです。
{
  "ts": "2025-09-12T15:48:45.906Z",
  "layer": "mongoose",
  "op": "countDocuments",
  "model": "Product",
  "collection": "products",
  "ms": 100,
  "filter": { "$or": [ { "title": {} }, { "description": {} } ] },
  "options": {}
}

Slow Query Log をリポジトリに commit して、Codex で slow query log を集計させました。

Codexでslow log集計

jq で数値を ? に正規化し、(op, model, filter, options) 単位でグルーピングして件数と p95 を CSV に出力しました。

jqスクリプト生成

集計CSV(抜粋):

"op","model","collection","filter","options","n","p95_ms"
"countDocuments","Product","products","{}","{}",134,143
"find","Product","products","{}","{""sort"":{""createdAt"":""?""},""skip"":""?"",""limit"":""?""}",70,151
"find","Product","products","{}","{""sort"":{""popularity"":""?""},""skip"":""?"",""limit"":""?""}",91,148
"find","Product","products","{}","{""sort"":{""price"":""?""},""skip"":""?"",""limit"":""?""}",70,150
"countDocuments","Product","products","{""$or"":[{""title"":{}},{""description"":{}}]}","{}",707,188
"find","Product","products","{""$or"":[{""title"":{}},{""description"":{}}]}","{""sort"":{""popularity"":""?""},""skip"":""?"",""limit"":""?""}",355,193

エージェントへのコンテキストと提案

Codexにはコードベースと集計した CSV をコンテキストとして渡しました。

codex で分析

下記のように提案してきました。

提案要約

提案の核:

  • title + description のテキストインデックスを追加
  • popularitycreatedAt に単一インデックスを追加
  • price+popularityprice+createdat の複合インデックスを追加
    • こちらはそのまま採用せず、まずは単一インデックスで効果を確認する。

下記は分析結果をもとに Codex が提案してきた推奨タスクです。

推奨タスクUI

実装ダイジェスト

テキストインデックス(title, description)の追加
Codex のタスク画面は次のとおりです。

text index タスク

実際に作成した Pull Request のスクリーンショットです。
正規表現での検索から全文検索への query 修正もやってくれています。

Text index PR

単一インデックス(popularity, createdAt, price)の追加
こちらも Codex のタスク画面です。

単一indexタスク

対応する Pull Request のスクリーンショットです。

単一index PR

追加後のインデックス一覧(_id, category 以外で 4 つ追加)です。

after index一覧

再計測の結果

  • /products の平均は 29.05ms、p95 は 141ms(n=17,812)
  • /products/:id の平均は 0.896ms、p95 は 2ms(n=7,454)

API平均
一覧取得の平均が約半減しており、ソート軸(popularity / createdAt)の単一インデックスで走査が軽くなった影響が大きいです。

API平均(後)

API p95
詳細取得は p95 が 35ms → 2ms と顕著に改善しており、find のスロークエリが消えたことが寄与しています。 一覧の p95 が伸び切らない要因は countDocuments で、ページングのためのカウントがボトルネック化していると考えられます。

API p95 (後)

slow query MongoDB のスロークエリは find がほぼ消失し、countDocuments が残存しています。

MongoDB散布図 (後)

まとめ

  • コード外の情報(slow query やアクセスログなど)を渡すと、性能改善の精度が一気に上がります。
  • ログはリポジトリにコミットする必要はなく、都度適切な形で共有・要約できれば十分に活用できます。
    • 実務では Observability ツールや計測基盤から得た指標を組み合わせることで、ボトルネックの特定と検証をさらに効率化できると思います。
  • 値はマスキングしても問題ありません。どんなクエリ項目の組み合わせでリクエストされるかという構造だけでも有用なコンテキストになります。
  • 提案は鵜呑みにせず、変更は小さく入れてメトリクスと再測定で自分の目で確認・検証し、効果が曖昧ならロールバックも検討しましょう。



以上の内容はhttps://ishikawa-pro.hatenablog.com/entry/2025/09/17/113858より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14