50.6. インデックスコスト推定関数

amcostestimate関数には、インデックスとともに使えることが決まっているWHERE句のリストが与えられます。 この関数はインデックスにアクセスするコストの概算とWHERE句の選択度(つまりインデックススキャンにて抽出される行の親テーブルにおける割合)を返さなくてはなりません。 単純な場合だと、ほとんど全てのコスト概算の作業は、オプティマイザの標準ルーチンを呼び出すことで行われます。 amcostestimate関数を持つことの意味は、標準的概算を改善することが可能な場合に、インデックスアクセスメソッドがインデックス型固有の知識体系を供給することができるということです。

それぞれのamcostestimate関数は以下のシグネチャを持たなければいけません。

void
amcostestimate (PlannerInfo *root,
                IndexOptInfo *index,
                List *indexQuals,
                RelOptInfo *outer_rel,
                Cost *indexStartupCost,
                Cost *indexTotalCost,
                Selectivity *indexSelectivity,
                double *indexCorrelation);

最初の4つのパラメータは入力です。

root

処理されている問い合わせに関するプランナの情報。

index

対象のインデックス。

indexQuals

インデックス制約句のリスト(暗黙的に論理積されます)。 NILリストは使用可能な制約がないことを表します。 リストに式ツリーが含まれることに注意してください。スキャンキーではありません。

outer_rel

インデックスが内部インデックススキャンで使用されるものとみなされている場合、結合の外側に関するプランナの情報です。 さもなくばNULLです。 非NULLならば、条件句の一部は単なる制限句ではなく、このリレーションとの結合句となります。 また、コスト推定器は、インデックススキャンが外側のリレーションの各行に対して繰り返されるものと想定しなければなりません。

最後の4つのパラメータは参照渡しの出力です。

*indexStartupCost

インデックスの起動処理にかかるコストに設定されます。

*indexTotalCost

インデックス処理の全体のコストに設定されます。

*indexSelectivity

インデックスの選択度に設定されます。

*indexCorrelation

インデックススキャンの順番と背後のテーブルの順番間の相関係数に設定されます。

コスト概算関数は、SQLやその他の手続き言語ではなく、C言語で書かれなければいけないことに注意してください。 理由はプランナ/オプティマイザの内部データ構造にアクセスしなければいけないためです。

インデックスアクセスコストはsrc/backend/optimizer/path/costsize.cで使われる、順番に並んだディスクブロックの取り出しにはseq_page_costのコストが、順不同の取り出しにはrandom_page_costのコストが、そして、1つのインデックス行の処理には通常 cpu_index_tuple_costというコストがかかる、というパラメータで計算されなければなりません。 さらに、インデックス処理(特にindexQuals自身の評価)の間に呼び出される比較演算全てに対して、cpu_operator_costに適当な係数をかけたコストがかかります。

アクセスコストは、インデックス自身のスキャンと関係する全てのディスクとCPUコストも含むべきですが、インデックスで識別される親テーブルの行の処理や抽出にかかるコストは含めてはいけません

"起動用コスト"は、最初の行を取り出し始めることができるようになる前に費やされなければならない総スキャンコストの一部です。 ほとんどのインデックスでは、これは0とすることができます。 しかし、高い起動用コストを持つインデックス種類ではこれを非0にすることを勧めます。

indexSelectivityは、インデックススキャンの間に抽出される親テーブルの行の概算された割合として設定されるべきです。 無駄の多いインデックスの場合はこの値が、与えられた制約条件を実際に通過する行の割合よりも高くなることがよくあります。

indexCorrelationは、インデックスの順番とテーブルの順番の間の(-1.0から1.0までの間の値を取る)相関として設定されるべきです。 この値は、メインテーブルから行を取り出すためのコスト概算を調整するために使用されます。

結合の場合、返される数はインデックスの1スキャンで想定される平均値でなければなりません。

コスト概算

典型的なコスト概算は次のように進められます。

  1. 与えられた制約条件に基づいて訪れられるメインテーブルの行の割合を概算して返します。 インデックス型固有の知識体系を持たない場合、標準のオプティマイザの関数であるclauselist_selectivity()を使用してください。

    *indexSelectivity = clauselist_selectivity(root, indexQuals,
                                               index->rel->relid, JOIN_INNER);

  2. スキャン中に訪れられるインデックスの行数を概算します。 多くのインデックス型では、これはindexSelectivityとインデックスの中にある行数を掛けたものと等しいですが、それより多い場合もあります (ページおよび行内のインデックスのサイズはIndexOptInfo構造体から得ることができます)。

  3. スキャン中に抽出されるインデックスページ数を概算します。 これは単にindexSelectivityにページ内のインデックスのサイズを掛けたものになるでしょう。

  4. インデックスアクセスコストを計算します。 汎用的な概算においては以下のように行うでしょう。

        /*
         * Our generic assumption is that the index pages will be read
         * sequentially, so they cost seq_page_cost each, not random_page_cost.
         * sequentially, so they have cost 1.0 each, not random_page_cost.
         * Also, we charge for evaluation of the indexquals at each index row.
         * All the costs are assumed to be paid incrementally during the scan.
         */
        cost_qual_eval(&index_qual_cost, indexQuals, root);
        *indexStartupCost = index_qual_cost.startup;
        *indexTotalCost = seq_page_cost * numIndexPages +
            (cpu_index_tuple_cost + index_qual_cost.per_tuple) * numIndexTuples;

    しかし、上では結合時の繰り返されるインデックススキャンにかかるインデックス読み込みについて割賦弁済を考慮していません。

  5. インデックスの相関を概算します。1つのフィールドに対する単純な順番のインデックスでは、これはpg_statisticから入手することができます。 相関が未知の場合、概算を用心深く考えると0(無相関)となります。

コスト概算関数の例はsrc/backend/utils/adt/selfuncs.cにあります。