ぱと隊長日誌

ブログ運用もエンジニアとしての生き方も模索中

PostgreSQL 内部での effective_cache_size の活用方法

はじめに

PostgreSQL の設定には effective_cache_size というディスクキャッシュのサイズを指定できるパラメータがあります。このパラメータはディスクキャッシュを実際に確保するために利用されることはなく、推定目的でのみ利用されます。

今回の記事では PostgreSQL がこのパラメータをどのように利用しているのか確認します。PostgreSQL 15 を対象に調査しています。

effective_cache_size の利用箇所

ソースコードから調査したところ、effective_cache_size は以下の2箇所で利用されていました。

(1) インデックススキャンのコスト計算

effective_cache_size (integer)
単一の問い合わせで利用できるディスクキャッシュの実効容量に関するプランナの条件を設定します。これは、インデックスを使用するコスト推定値の要素となります。より高い値にすれば、よりインデックススキャンが使用されるようになり、より小さく設定すれば、シーケンシャルスキャンがより使用されるようになります。
(後略)

20.7. 問い合わせ計画

(2) GiSTインデックスの処理モード決定

ソートが可能でない場合、デフォルトでは、インデックスのサイズがeffective_cache_sizeに達した時にGiSTインデックス構築はバッファ処理法に切り替わります。

68.4. 実装

インデックススキャンのコスト計算における effective_cache_size の利用

PostgreSQL のインデックススキャンのコストとして、インデックスページの読み込み (fetch) コストが含まれています。この読み込みページ数の計算は以下の論文がベースとなっています。
Mackert and Lohman, "Index Scans Using a Finite LRU Buffer: A Validated I/O Model", ACM Transactions on Database Systems, Vol. 14, No. 3, September 1989, Pages 401-424.
Index Scans Using a Finite LRU Buffer: A Validated I/O Model

PostgreSQLソースコードのコメントから計算式を引用します。
/src/backend/optimizer/path/costsize.c

PF =
	min(2TNs/(2T+Ns), T)    when T <= b
	2TNs/(2T+Ns)    when T > b and Ns <= 2Tb/(2T-b)
	b + (Ns - 2Tb/(2T-b))*(T-b)/T    when T > b and Ns > 2Tb/(2T-b)
where
	T = # pages in table
	N = # tuples in table
	s = selectivity = fraction of table to be scanned
	b = # buffer pages available (we include kernel space here)

また、以下の仮定を置いていることも示されています。

We assume that effective_cache_size is the total number of buffer pages available for the whole query, and pro-rate that space across all the tables in the query and the index currently under consideration. (This ignores space needed for other indexes used by the query, but since we don't know which indexes will get used, we can't estimate that very well; and in any case counting all the tables may well be an overestimate, since depending on the join plan not all the tables may be scanned concurrently.)

"We assume that effective_cache_size is the total number of buffer pages available for the whole query" という説明はマニュアルの下記に対応していると思われます。

effective_cache_size (integer)
単一の問い合わせで利用できるディスクキャッシュの実効容量に関するプランナの条件を設定します。(中略)また、利用可能な領域を共有しますので、異なるテーブルに対して同時に実行される問い合わせの想定数も考慮してください。

20.7. 問い合わせ計画

つまり、同時実行している他のクエリとのディスクキャッシュの奪い合いは考慮されていません。4GB と指定すれば、プランナが検討しているクエリで 4GB 全てを利用できる前提となっています。

effective_cache_size がコストにどの程度影響を与えるのか、またコストに影響を与えないケースを知るためには下記の記事が参考になります。
effective_cache_size: A practical example - CYBERTEC

シーケンシャルスキャンで effective_cache_size を利用しないのはなぜか?

シーケンシャルスキャンのコスト計算にもキャッシュの効果を織り込めないか?という改善提案と議論が以前に行われていました。

PostgreSQL: cache estimates, cache access cost

この議論での主な論点を挙げます。

  • どの程度の頻度でキャッシュヒット率を更新すれば妥当なのか?
    • ワークロードによっても変わるのではないか
    • キャッシュの状況は次の瞬間にも大幅に変わるかもしれない
    • クエリ処理の前半と後半でもキャッシュの状況は異なるかもしれない
  • 実行計画の安定性が低下するのではないか?
  • パフォーマンスが向上するという根拠は何か?

これらに応えることのできる改善提案が現れれば、シーケンシャルスキャンのコスト計算にもキャッシュ効果を織り込むことになるでしょう。

ただ、現状でもコスト計算でキャッシュ効果はある程度織り込まれています。random_page_cost の説明に以下の記述があります。

機械的ディスク記憶装置に対するランダムアクセスは通常はシーケンシャルアクセスの4倍よりもかなり高価です。しかし、より低いデフォルト(4.0)が使用されます。というのはインデックスのついた読み取りのようなディスクに対するランダムアクセスのほとんどはキャッシュにあると想定されるからです。このデフォルト値は、ランダムアクセスがシーケンシャルアクセスより40倍遅い一方で、ランダム読み込みの90%はキャッシュされていることが期待されるというモデルとして考えることができます。

20.7. 問い合わせ計画

インデックススキャンで effective_cache_size を考慮するのはなぜか?

インデックススキャンのコスト計算で effective_cache_size を利用しているのは、研究して理論として確立し、論文として発表されたという根拠があるためと思われます。

プランナのコスト計算で、インデックススキャンだけにキャッシュの効果を織り込むのはバランスが悪いようにも思えます。ですが、根拠を持って改善の余地があるならそれを取り込み、必要に応じて全体のバランスを調整していく、という発想なのかもしれません。実際に現在のコスト計算がこれまでの実運用に耐えていることは妥当性の根拠といえそうです。

まとめ

effective_cache_size は限定的に利用されています。他の処理でもキャッシュの効果を織り込むことで改善するかもしれません。こうした改善を取り込むためには理論と実績に基づいた根拠を提示することが必要です。