PostgreSQLの`work_mem`、適当に設定して沼にハマってない?現場で役立つチューニングの極意
「クエリが妙に遅いな」と思って`EXPLAIN ANALYZE`を叩いたら、一番下の行に『Disk: XXXkB』の文字。
これ、PostgreSQLを触るエンジニアなら一度は冷や汗をかいたことがあるはずです。そう、`work_mem`不足による「一時ファイル(Temporary File)」の生成ですね。今回は、この`work_mem`とどう向き合っていくべきか、現場の視点から少し突っ込んだ話をしようと思います。
—
そもそも`work_mem`って何者?
簡単に言うと、PostgreSQLがソート(ORDER BYやDISTINCT)やハッシュ結合(JOIN)を行うために使う「作業机の広さ」です。
この机が広ければ、クエリはメモリ上で爆速で処理を終えます。でも、机が狭くて作業が溢れると、PostgreSQLは仕方なくディスク(一時ファイル)にデータを書き出して、「読み書き」という一番やりたくない低速な作業を強いられるわけです。
「じゃあ、`work_mem`を巨大に設定すれば最強じゃん!」
そう思ったあなた、ちょっと待って。それは危険な落とし穴です。
—
なぜ「大きくすればいい」わけじゃないのか
`work_mem`の設定値は、「1つの操作」に対して割り当てられるメモリです。
例えば、`work_mem`を100MBに設定したとしましょう。もし、そのクエリの中で「3つのJOIN」と「2つのソート」が同時に走るような複雑なクエリが来たらどうなるか?
- 1つのクエリで 5つの操作(5 × 100MB = 500MB)が消費される
- 同時に10人がそのクエリを投げたら? → 5GBのメモリが瞬時に吹っ飛ぶ
これにOSのキャッシュや他の接続プロセスが加わると、最悪の場合、OSがメモリ不足を検知してOOM Killerが発動し、PostgreSQLそのものが強制終了されるという悪夢が待っています。だからこそ、闇雲に値を大きくするのはNGなんです。
—
実践:適切な`work_mem`を見つけるステップ
僕が現場でよくやる手順をシェアしますね。
1. 「一時ファイル」の発生を監視する
まずは、本当にメモリが足りているかをログで確認しましょう。`postgresql.conf`で以下を設定して、一時ファイルが生成されたらログに出すようにします。
0にすると全ての一時ファイルがログに出る(検証用)
10MB以上の一時ファイルが出たらログに出す(本番運用の目安)
log_temp_files = 10MB
ログに「temporary file: …」というメッセージが頻発しているなら、それが改善のサインです。
2. クエリ単位でチューニングする
`work_mem`はセッション単位で変更可能です。これがPostgreSQLのめちゃくちゃ便利なところ。
「普段は標準の4MBでいいけど、この重いレポート集計だけはガッツリメモリを使わせたい」という時は、クエリの直前でこう書き換えます。
— このトランザクション内だけメモリを増やす
SET LOCAL work_mem = ’64MB’;
SELECT FROM heavy_join_table … ;
これなら、システム全体への影響を最小限に抑えつつ、特定のボトルネックだけをピンポイントで解消できます。
—
現場で意識している「鉄則」
最後に、僕が後輩によく伝えている鉄則を3つだけ。
- デフォルト値を過信しない: デフォルトの4MBはあくまで「枯渇させないための安全策」。余裕があるサーバーなら、最初は8MB〜16MB程度から様子を見るのが現実的です。
- まずはクエリの書き換えを疑う: `work_mem`を増やす前に、インデックスは効いているか? 不要なカラムを`SELECT `していないか? を確認しましょう。メモリを増やすのは、チューニングの「最終手段」です。
- `EXPLAIN ANALYZE`を友達にする: `EXPLAIN (ANALYZE, BUFFERS)`を実行して、実際にメモリ内で処理が終わったのか、ディスクに書き出したのかを必ず自分の目で確認すること。数字は嘘をつきません。
—
まとめ
`work_mem`のチューニングは、まさに「バランス感覚」の世界です。メモリを贅沢に使うか、それとも慎重に節約するか。
もし今、あなたのデータベースで「Disk」という文字が見えたら、まずはログを確認して、どのクエリが悪さをしているのか突き止めてみてください。焦ってサーバー全体の数値をいじる前に、まずはそのクエリ単体に`SET LOCAL`を試す。
そんな一歩ずつの積み重ねが、結局一番の近道になるはずです。また何か詰まったら、いつでも聞きに来てくださいね!
コメント