24. PostgreSQL のコスト計算について - PostgreSQL のソースコードを読む
PostgreSQL の Bitmap Scan コスト計算で、怪しいところを見つけたので報告の前に下調べ。
基礎編
コスト計算は教科書通り行われる。Database Management system の 8, 14 章を一通り読めば理解できると思う。
- コストは Explain で見られる
- start-up コストとは?
- output scan の前にかかるコスト。
- bitmap を作る、ソートする、オンメモりハッシュ作るなど。
- total コストとは?
- 全ての行が取り出されると仮定した場合のコスト
- コストには、クライアントに行を返す部分は含まれない
- 伝統的には全てのコストは、ページのフェッチ I/O コスト(seq_page_cost) を 1 として他のパラメータを相対で決める
- コストパラメータ。教科書通り。
- seq_page_cost: ディスクから 1 ページ フェッチするのにかかるコスト。
- random_page_cost: シーケンシャルではない場合のページフェッチコスト
- cpu_tuple_cost: 1行を CPU であれこれするコスト。例えば取り出したタプルが filer にマッチするか調べるとか。
- cpu_index_tuple_cost: 索引の1回処理
- 必ず事前に VACUUM ANALYZE を
- 行数の見積もりなどの情報
- 2回続けて同じクエリを発行するとキャッシュに載る。キャッシュに載ったあとのコストを見た方が良い。
コードの話
backend/optimizer/costsize.c にコスト計算の関数がまとまっている。
SEQ Scan を強制的にオフにするためのコードが面白かった。
Cost disable_cost = 100000000.0; if (!enable_seqscan) startup_cost += disable_cost;
cost_bitmap_heap_scan
お。 where 句が 1つのときに、Index Scan と Bitmap Scan のどちらがコストが高いのかに関して決定的なコード見つけた。
/* * Charge a small amount per retrieved tuple to reflect the costs of * manipulating the bitmap. This is mostly to make sure that a bitmap * scan doesn't look to be the same cost as an indexscan to retrieve a * single tuple. */ *cost += 0.1 * cpu_operator_cost * ((IndexPath *) path)->rows;
Index Scan の方が若干コストが高いという事になる。
この辺りも面白い。
/* * For small numbers of pages we should charge random_page_cost apiece, * while if nearly all the table's pages are being read, it's more * appropriate to charge seq_page_cost apiece. The effect is nonlinear, * too. For lack of a better idea, interpolate like this to determine the * cost per page. */ if (pages_fetched >= 2.0) cost_per_page = random_page_cost - (random_page_cost - seq_page_cost) * sqrt(pages_fetched / T);
ここがかなり怪しい。あとで読み返そう。
/* * For a single scan, the number of heap pages that need to be fetched * is the same as the Mackert and Lohman formula for the case T <= b * (ie, no re-reads needed). */ pages_fetched = (2.0 * T * tuples_fetched) / (2.0 * T + tuples_fetched);
sort 関連
sort のコスト計算ではオンメモリでソートできるかが大きな鍵となる。
- 想定される tuple の量が work_mem より小さい場合は、オンメモリソート
- 上記以外は external ソート となる(Disk上の merge ソートになる)
Bitmap Heap Scan のメモ
lossy モードは、tid ではなくて、このページを visit しなさいという情報しか残らない。
lossy モードへの移行は、index を読みながら動的に行われる。
具体的には index scan して tid をつっこんでいき、maxentries を超えたら、bitmap 全体を動的に再構築している。
if (tbm->nentries > tbm->maxentries)
tbm_lossify(tbm);
というわけで、
nodeBitmapIndexscan.c で maxentries = work_mem * 1024L となっているので
tbm = tbm_create(work_mem * 1024L);
見積もり tuple 数 > work_mem * 1024L
が成り立つ場合にコストペナルティを課すべき。