amcostestimate関数には、インデックスと共に使用できることが決まっているWHERE句およびORDER BY句のリストを含む、インデックススキャンの可能性を記述する情報が与えられます。
この関数はインデックスにアクセスするコストの概算とWHERE句の選択度(つまりインデックススキャンにて抽出される行の親テーブルにおける割合)を返さなくてはなりません。
単純な場合だと、ほとんどすべてのコスト概算の作業は、オプティマイザの標準ルーチンを呼び出すことで行われます。
amcostestimate関数を持つことの意味は、標準の概算を改善することができる場合に、インデックスアクセスメソッドがインデックス型固有の知識体系を提供することができるということです。
それぞれのamcostestimate関数は以下のシグネチャを持たなければいけません。
void
amcostestimate (PlannerInfo *root,
IndexPath *path,
double loop_count,
Cost *indexStartupCost,
Cost *indexTotalCost,
Selectivity *indexSelectivity,
double *indexCorrelation,
double *indexPages);
最初の3つのパラメータは入力です。
root処理されている問い合わせに関するプランナの情報。
path考慮されるインデックスアクセスパス。 コストと選択性値を除くすべてのフィールドが有効です。
loop_count
コスト概算の算出対象となるインデックススキャンが繰り返された回数です。
これは通常、ネステッドループ結合の内部で利用されるパラメータ化されたスキャンの回数よりも大きい値になります。
コスト概算は1回のスキャンのための値であることに注意してください。loop_countがより大きい場合、複数のスキャンにより得られる効果をみるには十分な値といえるでしょう。
最後の5つのパラメータは参照渡しの出力です。
*indexStartupCostインデックスの起動処理にかかるコストに設定されます。
*indexTotalCostインデックス処理の全体のコストに設定されます。
*indexSelectivityインデックスの選択度に設定されます。
*indexCorrelationインデックススキャンの順番と背後のテーブルの順番間の相関係数に設定されます。
*indexPagesインデックスのリーフページ数が設定されます。
コスト概算関数は、SQLやその他の手続き言語ではなく、C言語で書かれなければいけないことに注意してください。 理由はプランナ/オプティマイザの内部データ構造にアクセスしなければいけないためです。
インデックスアクセスコストはsrc/backend/optimizer/path/costsize.cで使われる、逐次的なディスクブロックの取り出しにはseq_page_costのコストが、順不同の取り出しにはrandom_page_costのコストが、そして、1つのインデックス行の処理には通常cpu_index_tuple_costというコストがかかる、というパラメータで計算されなければなりません。
さらに、インデックス処理(特にindexquals自体の評価)の間に呼び出される比較演算すべてに対して、cpu_operator_costに適当な係数をかけたコストがかかります。
アクセスコストは、インデックス自身のスキャンと関係するすべてのディスクとCPUコストも含むべきですが、インデックスで識別される親テーブルの行の処理や抽出にかかるコストは含めてはいけません。
「起動用コスト」は、最初の行を取り出し始めることができるようになる前に費やされなければならない総スキャンコストの一部です。 ほとんどのインデックスでは、これはゼロとすることができます。 しかし、高い起動用コストを持つインデックス種類ではこれを非ゼロにすることを勧めます。
indexSelectivityは、インデックススキャンの間に抽出される親テーブルの行の概算された割合として設定されるべきです。
非可逆問い合わせの場合はこの値が、与えられた制約条件を実際に通過する行の割合よりも高くなることがよくあります。
indexCorrelationは、インデックスの順番とテーブルの順番の間の(-1.0から1.0までの間の値を取る)相関として設定されるべきです。
この値は、メインテーブルから行を取り出すためのコスト概算を調整するために使用されます。
indexPagesは、リーフページ数が設定されるべきです。
これは、パラレルインデックススキャンのワーカー数の見積もりに使用されます。
loop_countの値が1より大きい場合、戻り値はインデックスを利用した1回のスキャンを想定した平均値であるべきです。
コスト概算
典型的なコスト概算は次のように進められます。
与えられた制約条件に基づいて訪れられるメインテーブルの行の割合を概算して返します。
インデックス型固有の知識体系を持たない場合、標準のオプティマイザの関数であるclauselist_selectivity()を使用してください。
*indexSelectivity = clauselist_selectivity(root, path->indexquals,
path->indexinfo->rel->relid,
JOIN_INNER, NULL);
スキャン中に訪れられるインデックスの行数を概算します。
多くのインデックス種類では、これはindexSelectivityとインデックスの中にある行数を掛けたものと等しいですが、それより多い場合もあります。
(ページおよび行内のインデックスのサイズはpath->indexinfo構造体から得ることができることに注意してください。)
スキャン中に抽出されるインデックスページ数を概算します。
これは単にindexSelectivityにページ内のインデックスのサイズを掛けたものになるでしょう。
インデックスアクセスコストを計算します。 汎用的な概算においては以下のように行うでしょう。
/*
* 一般的な仮定は、インデックスページは逐次的に読まれるので、
* random_page_costではなく、それぞれseq_page_costが掛かるというものです。
* 各インデックス行でのindexqualsの評価にもコストが掛かります。
* コストはすべてスキャンの間に徐々に支払われると仮定します。
*/
cost_qual_eval(&index_qual_cost, path->indexquals, root);
*indexStartupCost = index_qual_cost.startup;
*indexTotalCost = seq_page_cost * numIndexPages +
(cpu_index_tuple_cost + index_qual_cost.per_tuple) * numIndexTuples;
しかし、上では繰り返されるインデックススキャンにかかるインデックス読み込みについて減価償却を考慮していません。
インデックスの相関を概算します。 1つのフィールドに対する単純な順番のインデックスでは、これはpg_statisticから入手することができます。 相関が未知の場合、概算を用心深く考えるとゼロ(無相関)となります。
コスト概算関数の例はsrc/backend/utils/adt/selfuncs.cにあります。