CTS-KB

パフォーマンス編 — 「数えるだけ」が 52.7 秒かかり SAGA を止めた。そして最適化が障害の元凶になった【実践編 第9章】

⏱ 約 7 分で読めます
#ステアリング駆動開発#パフォーマンス#PostgreSQL#pgvector#キャッシュ設計#技術的負債#SAGA#ベクター検索

📖 はじめに — 性能の負債は、データ量が増えるまで沈黙する

性能負債の厄介さは、開発環境では正しく動いてしまうことだ。100 件のテストデータでは何も起きない。AI が書いたコードはレビューでも「ロジックは正しい」と通る。そして本番相当のデータ量が入った瞬間、別の顔を見せる。

本章は 2 つの実録を扱う。前半は「件数を数えるだけの処理」が分散処理全体を止めた障害の解剖。後半は、性能改善として導入したキャッシュが別の障害の元凶になり、アーキテクチャ契約で正しい場所に戻されるまで — 第I部でおそらく一番「因果が一周する」章である。

💥 障害の解剖 — COUNT のつもりが、全件運んでいた

ステージング環境で、AI マッピング SAGA が Queue = Running のまま永久停止した。処理は進まず、エラーも見えない。調査で判明した因果連鎖はこうだ。

💥 障害の解剖 — COUNT のつもりが、全件運んでいた

GetTargetCountAsync対象件数を数えるだけの処理

Where(...).ToListAsync()10 万件を全件マテリアライズ

実測 52,742ms 実行→ statement_timeout 超過(57014 query_canceled)

ハンドラ失敗 → 1 分毎リトライ→ 毎回 52 秒かけて毎回タイムアウト

リトライ上限 → DLQ 行きSAGA Queue は Running のまま停止

根本原因は 1 行に凝縮できる。件数が欲しいだけなのに、Where(...).ToListAsync() で全レコードをアプリ側に運んでから数えていた。ORM / LINQ の古典的な罠で、DB 側で COUNT(*) に落とせば済む処理だ。是正後の実測は 50 秒超 → 100ms 未満、およそ 500 倍の改善だった。

この障害から持ち帰るべきは「COUNT を使え」ではない。3 つある。

  1. 性能負債はデータ量というディメンションで顕在化する。 ロジックレビューでは捕まらない。「この処理は N に対してどうスケールするか」は、独立した確認観点として明示的に持つ必要がある
  2. 分散系では、1 つの遅いクエリが障害に増幅される。 タイムアウト → リトライ → 毎回同じ理由で失敗 → DLQ という連鎖は、リトライ機構が「回復」ではなく「同じ失敗の反復実行」になる典型。リトライ設計は「時間で失敗する処理」と相性が最悪だ
  3. 教訓は NFR として明文化する。 このステアリングは是正後、「DB のステートメントタイムアウトを超えない」を非機能要件として文書に固定した。障害の記憶は風化するが、要件は残る

🧠 キャッシュ刷新 — 完全一致から「意味の近さ」へ

後半の主役は、AI マッピングのコスト最適化だ。カテゴリマッピングで LLM 呼び出しを減らすため、当初は「大分類・中分類・小分類・性別・検索ワードの 5 属性完全一致」をキーにしたキャッシュがあった。だが完全一致キーは表記が 1 文字揺れれば別キーになり、実運用のヒット効率と再利用品質に限界があった。

刷新の設計は「文字列の一致」から「意味の近さ」への転換だ。

  • カテゴリ情報を埋め込みベクトル化し、コサイン類似度 ≥ 0.95 でキャッシュヒットと判定(pgvector で近傍検索)
  • 埋め込みモデルと次元数は、既存の別ジョブが生成済みの資産と同一(1536 次元)に統一 — 新しい埋め込み空間を増やさない
  • 成功指標を先に定義: AI 呼び出し 50% 以上削減、キャッシュヒット時のマッピング精度 90% 超

型 3(定量が意思決定を支える)はここでは「導入判断」に効いている。「キャッシュを賢くしました」ではなく、削減率と精度という 2 軸の数値目標を立ててから作る。片方だけなら簡単なのだ — 閾値を緩めれば削減率は上がるが精度が落ちる。2 軸で縛るから、閾値 0.95 という設計点に意味が出る

なお埋め込みベクトルの扱いには本シリーズ外でも落とし穴を踏んでおり、それはベクター検索の task_type 落とし穴として別記事にまとめてある。

🔄 伏線回収 — その最適化が、障害の元凶と名指しされる

ここからが本章の核心だ。第4章で解剖した SAGA の障害リストに、こういう項目があったのを覚えているだろうか — 「Create Chunk ハンドラに独自の埋め込みキャッシュ類似検索を実装し、statement_timeout を構造的に超過」。

それは、この類似度キャッシュだった。

キャッシュ自体の設計は正しい。問題は置き場所だ。SAGA の Create Chunk ハンドラは「対象 ID を取得してチャンクに割るだけ」の軽量処理であるべき場所(キュー排他制御の中で走るため、ここが重いと SAGA 全体が詰まる)。そこに「N 件の対象それぞれに埋め込み生成 + 3N 回の類似検索」というキャッシュフィルタリングが実装され、タイムアウトを構造的に超過した。前半の障害が「不注意な 1 行」だったのに対し、こちらは正しい機能の間違った配置である。

帰結が SAGA 同型性契約への明文化だ。

  • C-1: Create Chunk ハンドラでの外部 API 呼出・埋め込み生成・類似検索・キャッシュフィルタを禁止行為として列挙
  • C-8: キャッシュの保存は SAGA チャンク完了後の非同期 bulk 処理のみ — 重い処理は、重くてよい場所へ

「性能改善」と「アーキテクチャ契約」は独立に評価してはならない、というのが一周した因果の結論だ。最適化は局所では常に正しく見える。だが分散処理の排他制御の中では、速くするための機能が全体を止める。パフォーマンス改善の設計レビューには、「その処理はどの実行コンテキストで走るか」という配置の観点が要る — そして配置の規約は、第4章の検査スクリプトが機械で守っている。

🧾 まとめ

  • 「数えるだけ」の全件マテリアライズが 52.7 秒 → timeout → リトライ → DLQ と増幅して SAGA を止めた。COUNT 化で 500 倍。性能はロジックレビューと別の観点(N へのスケール)で確認する
  • 障害の教訓は NFR として文書に固定する(statement_timeout を超えない、等)
  • キャッシュ刷新は完全一致から類似度 0.95 へ。削減率 50% と精度 90% の 2 軸で縛るから設計点に意味が出る
  • そしてその最適化が Create Chunk 重量化の元凶に — 正しい機能でも、置き場所を間違えると障害になる。配置の規約(C-1 / C-8)まで含めて初めて性能改善は完成する

次章は第I部の最終章。「ローカルで緑でも本番で 400」という .NET の実挙動の罠を含む、セキュリティ編へ。

📚 シリーズ記事(ステアリング駆動開発・実践編)

序章

  1. AI 駆動開発が積み上げる技術的負債

第I部: 技術的負債ドメイン別の実録(総点検ガイド + 8 ドメイン・全 9 回)

  1. リファクタリング総点検ガイド — 7 つの観点と進め方
  2. DDD/SOLID/BC 編 — god class 一掃と境界の機械ガード
  3. SAGA 編 — 新アーキテクチャ挑戦と再発ゲート
  4. Atomic Design リファクタ編 — 47 page 新規移植
  5. Storybook × a11y 実機編
  6. テスト品質・網羅性編
  7. Python ジョブ群編
  8. パフォーマンス編(本記事)
  9. セキュリティ編

第II部: 検証エンジン(クロスファミリー検証・全 6 回)

  1. 総論 — マルチ LLM の 2 系統と見取り図
  2. 裏取り編 — +18.1pt 論文の検証
  3. 設計編 — finder/verifier 分業と逆順禁止
  4. TDD×ハーネス編 — テスト保護と三層ゲート
  5. 実装編 — Claude Code の中から Codex を動かす
  6. モデル戦略編 — ティア割当と「買うか組むか」

終章

  1. クロスファミリー検証の最終型 —「正しく作る」から「自分で正しさを測り直す」へ — 検証エンジンに「計測」の層をはめ、自己修正ループを閉じる

番外編

  1. CI テスト 29 分 → 5.6 分 — 検証エンジンの平時運用実録 — 実測が有力仮説を殺し、退行前より速くなった 1 日
  2. GPT-5.6 sol 切替の当日実録 — もう一つのデフォルト追従 — CLI 更新が黙って替える finder と、当日中の再計測
  3. 遊休 90% の枠に仕事を振る — Codex 上流調査と verifier バッチ化 — 逆順禁止の境界を ADR で確定し、読む仕事を遊休枠へ移した 1 日
  4. ハーネスが単一プロジェクトを卒業する — プラグイン化と二層配布 — 4 プロジェクト展開への切り出しと、version + SHA ピンによる非強制追従
  5. 69分で5リリース — Opus 5 当日対応が暴いた共通ハーネスの死角 — 新モデル対応を起点に、配布・文書・CI の回帰を二消費者で検出した 69 分

関連記事