Vegas Engineering by FORBIS 記事一覧
← 記事一覧へ戻る
Performance ・ 第19回

本番規模で測る — DBベンチマークの落とし穴と、"相関×直積"という真のボトルネック

静的な見た目や1回の計測は、しばしば嘘をつく。計測の作法と、実際に効く2つのボトルネック、その定石を共有する。

Vegas 開発チーム ・ 2026-07-03 ・ 読了 8分

MEASURE計測の作法 — まず「測り方」を疑う

大規模なリレーショナルDB(例:クラウドのMySQL互換)でクエリを最適化するとき、最初の敵は遅いクエリではなく、信頼できない計測結果である。改善の前に、次の3点を守りたい。

FIGURE計測の順序を図にする

データ準備 索引の効く条件で分離 捨て計測 コールドは破棄 本計測・小バッチ ウォーム / 1〜数件 本計測・大バッチ ウォーム / 実規模 判断 両規模で評価
図:計測は「準備の分離 → 捨て計測 → ウォームで小/大バッチ」の順で行う。片方の規模だけで判断しない。

CAUSE真のボトルネックは主に2種

結合の「数」を静的に数えて重さを判定すると、過検出になりがちだ。実際に効くのは次の2つである。

逆に、単純な親→子→孫の連鎖(直積にならない)や、COUNT(DISTINCT)・GROUP_CONCAT で畳むだけの処理は、本番規模でも十分速い。「結合が多い=遅い」ではなく、「直積になっているか」を見る。

DIAGRAM直積の爆発と、集約による回避

悪い例:枝A × 枝B(直積) 親(1件) 枝A:m行 枝B:n行 結果 = m × n 行に爆発 良い例:枝ごとに事前集約 → 結合 親(1件) tmpA:1キー1行 事前集約 tmpB:1キー1行 事前集約 結果 = 1 行(掛け算にならない)
図:1対多の枝は「子ごとに1キー1行へ事前集約」してから LEFT JOIN すると、直積の掛け算爆発を断ち切れる。

FIX修正の定石

CODE擬似SQL — 悪い例と良い例

correlated_fanout.sqlSQL
SELECT
  p.id,
  -- 相関サブクエリを直積の内側でインライン(毎行 再評価)
  (SELECT COUNT(*) FROM child_c c WHERE c.pid = p.id) AS c_cnt
FROM parent p
JOIN branch_a a ON a.pid = p.id   -- 枝A:m行
JOIN branch_b b ON b.pid = p.id   -- 枝B:n行 → m×n に爆発
WHERE p.id IN (:ids);
preaggregated.sqlSQL
-- 枝ごとに 1キー1行へ事前集約
CREATE TEMPORARY TABLE tmp_a AS
  SELECT pid, GROUP_CONCAT(val) AS a_vals FROM branch_a
  WHERE pid IN (:ids) GROUP BY pid;
CREATE TEMPORARY TABLE tmp_b AS
  SELECT pid, COUNT(DISTINCT val) AS b_cnt FROM branch_b
  WHERE pid IN (:ids) GROUP BY pid;

SELECT
  p.id, a.a_vals, b.b_cnt,
  -- 保有判定は EXISTS(索引参照)で
  EXISTS(SELECT 1 FROM child_c c WHERE c.pid = p.id) AS has_c
FROM parent p
LEFT JOIN tmp_a a ON a.pid = p.id   -- 1キー1行なので直積にならない
LEFT JOIN tmp_b b ON b.pid = p.id
WHERE p.id IN (:ids);
TAKEAWAY

計測は「準備の分離・ウォーム・実運用規模」の3点を守り、静的な結合数ではなく直積の有無を疑う。相関サブクエリのインラインと独立した1対多の直接結合という2つの直積を、枝ごとの事前集約と EXISTS で断てば、本番規模でも安定して速い。