Postgres: 「Sort Method: external merge Disk」は work_mem を全体ではなくクエリ単位で上げる
EXPLAIN ANALYZE に Sort Method: external merge Disk: ...kB と出たら、ソートが work_mem に収まらずディスクにあふれている。ただし work_mem は接続ごとではなく「ソートやハッシュの操作 1 つごと」の上限なので、サーバー全体で上げるとメモリ不足を招く。必要なクエリだけ SET LOCAL で上げる。
何が起きるか
複雑なクエリは、ソート・ハッシュ結合・ハッシュ集計を一度にいくつも実行する。それぞれが work_mem まで使えるうえ、ハッシュ系は hash_mem_multiplier(Postgres 15 以降の既定は 2.0)倍まで使える。パラレルワーカーがいればワーカーごとにさらに増える。「work_mem 256MB × 操作数 × 同時接続数」になるので、postgresql.conf で一律に上げると、ピーク時にメモリが一気に足りなくなる。
やり方
sql
-- まず確認
EXPLAIN (ANALYZE, BUFFERS) SELECT ... ORDER BY ...;
-- Sort Method: external merge Disk: 182344kB ← あふれている
-- Sort Method: quicksort Memory: 2048kB ← メモリ内で済んでいる
-- 重いレポートのクエリだけ、そのトランザクションの中で上げる
BEGIN;
SET LOCAL work_mem = '256MB';
SELECT ... ORDER BY ...;
COMMIT; -- ここで元の値に戻る- バッチ処理など特定の用途だけで必要なら、専用のロールに
ALTER ROLE report SET work_mem = '256MB';と付ける手もある(考え方は Postgres: statement_timeout はアプリ用ロールに ALTER ROLE で設定する と同じ)。 - 先にソート自体を減らせないかも見る。
ORDER BYの列にインデックスがあれば、ソートなしで並んだ順に読める。
注意点
SET(LOCAL なし)はセッションが終わるまで残る。PgBouncer の transaction モードでは別のクライアントに引き継がれてしまうので、必ずSET LOCALを使う(Postgres: PgBouncer の transaction モードではセッション状態が別の接続に漏れる。SET LOCAL と xact 系ロックを使う)。- 一時ファイルにあふれたクエリは
log_temp_files = 0にするとログに出る。どのクエリに効かせるべきか探すのに使える。