【実務・中級編】 work_memの最適化 – PostgreSQL

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`を試す。

そんな一歩ずつの積み重ねが、結局一番の近道になるはずです。また何か詰まったら、いつでも聞きに来てくださいね!

コメント

タイトルとURLをコピーしました