なぜか遅いクエリ。プランナの「見積もり」を疑ったことはあるか?
やあ。現場でPostgreSQLと格闘しているエンジニアのみんな、お疲れ様。
今日は少し「通(つう)」な話をしよう。
大規模なテーブルに対するクエリが、インデックスを使っているはずなのに、なぜかフルスキャンを選択して爆死している……そんな経験はないかな?
「SQLは間違っていない」「インデックスも貼ってある」。じゃあ、犯人は誰か。
そう、PostgreSQLのクエリプランナだ。奴は時に、現実離れした見積もりをして、とんでもない実行計画を立ててくることがある。
そんなとき、多くのエンジニアは `random_page_cost` をいじったり、統計情報を更新したりする。だが、それでも改善しない「CPU負荷が重いクエリ」には、隠し球が必要だ。それが今回紹介する `cpu_tuple_cost` というパラメータだ。
—
cpu_tuple_cost とは何か?
PostgreSQLのプランナは、クエリを実行する前に「このクエリをどう処理するのが一番速いか」を計算する。その際、コスト計算の基準となる数字がいくつかあるんだ。
`cpu_tuple_cost` は、「1つのタプル(行)を処理する際にかかるCPUコスト」を定義している。
デフォルト値は `0.01` だ。
プランナはこれをもとに、クエリ全体で「どれくらいのCPUパワーが必要か」を予測している。
なぜデフォルト値ではいけないのか?
最近のサーバーはCPUがとにかく速い。一方で、メモリやディスクI/Oのボトルネックは昔とは様相が変わっている。
デフォルトの `0.01` という値は、非常に「保守的」な値なんだ。
もし、数百万行におよぶ大規模なテーブルをJOINしたり、複雑な集計をしたりする場合、プランナは「CPUコストが重すぎるから、フルスキャンしてソートした方がマシかもな」といった誤った判断を下すことがある。現実のCPUはもっとタフなのに、プランナが「CPUは非力だ」と思い込んでいるせいで、最適なインデックススキャンを避けてしまうわけだ。
—
実践:どんな時に調整すべきか
例えば、こんなケースだ。
- 大量のデータを持つテーブルに対する「範囲検索」
- Hash Join を多用する複雑なクエリ
- インデックスを使えるはずなのに、なぜか Seq Scan が選ばれる
そんな時、まずは `EXPLAIN ANALYZE` を叩いてみてほしい。
EXPLAIN ANALYZE
SELECT FROM orders WHERE created_at > ‘2023-01-01’;
もし、プランナが「インデックススキャンよりシーケンシャルスキャンの方がコストが低い」と判断しているなら、`cpu_tuple_cost` を少し下げてみる価値がある。
調整の手順
全体の設定を変えるのは怖いから、まずは特定のセッションやクエリ単位で実験するのが鉄則だ。
— トランザクション単位で調整して試してみる
BEGIN;
SET LOCAL cpu_tuple_cost = 0.005; — デフォルトの半分にしてみる
EXPLAIN ANALYZE
SELECT FROM orders WHERE created_at > ‘2023-01-01’;
— 実行計画が変わって速くなったら成功!
ROLLBACK;
このように、値を少しずつ下げていくことで、プランナの「CPUに対する偏見」を補正してやるんだ。
—
注意点:銀の弾丸ではない
勘違いしないでほしい。`cpu_tuple_cost` を下げれば全てが解決するわけじゃない。
- 副作用のリスク: この値を下げすぎると、今度は「インデックススキャンの方が速い」とプランナが判断しすぎて、本来シーケンシャルスキャンの方が適している場面でも無理やりインデックスを使ってしまい、かえって遅くなることがある。
- 優先順位: まずは `ANALYZE` で統計情報を最新にすること。それでも解決しない場合の「最後の砦」として使うのが、玄人のやり方だ。
—
現場のエンジニアへ
データベースのチューニングは、科学であると同時にアートでもある。
プランナという「優秀だが時々ドジを踏む助手」に、どうやって効率よく働いてもらうか。そのためのヒントが、こうした細かいパラメータ調整には詰まっている。
もし君が担当しているシステムで、「なぜかここだけ遅い」というクエリがあったら、ぜひ `cpu_tuple_cost` を少しだけいじってみてほしい。
「おっ、速くなった!」という瞬間、君はまた一つ、PostgreSQLという深淵を理解したことになる。
また現場で会おう。健闘を祈る。
コメント