★PostgreSQLカンファレンス2024 12月6日開催/チケット販売中★
他のバージョンの文書 16 | 15 | 14 | 13 | 12 | 11 | 10 | 9.6 | 9.5 | 9.4 | 9.3 | 9.2 | 9.1 | 9.0 | 8.4 | 8.3 | 8.2 | 8.1 | 8.0 | 7.4 | 7.3 | 7.2

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

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

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

void
amcostestimate (PlannerInfo *root,
                IndexPath *path,
                double loop_count,
                Cost *indexStartupCost,
                Cost *indexTotalCost,
                Selectivity *indexSelectivity,
                double *indexCorrelation);

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

root

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

path

インデックスアクセスパスが考慮されます。 コストと選択性値を除くすべてのフィールドが有効です。

loop_count

コスト概算の算出対象となるインデックススキャンが繰り返された回数です。 これは通常、ネストループジョインの内部で利用されるスキャンの回数よりも大きい値になります。 コスト概算は1回のスキャンのための値であることに注意してください。loop_countがより大きい場合、複数のスキャンにより得られる効果をみるには十分な値といえるでしょう。

最後の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コストも含むべきですが、インデックスで識別される親テーブルの行の処理や抽出にかかるコストは含めてはいけません

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

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

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

loop_countの値が1より大きい場合、戻り値はインデックスを利用した1回のスキャンを想定した平均値であるべきです。

コスト概算

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

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

    *indexSelectivity = clauselist_selectivity(root, path->indexquals,
                                               path->indexinfo->rel->relid,
                                               JOIN_INNER, NULL);

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

  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.
     * 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, path->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から入手することができます。 相関が未知の場合、概算を用心深く考えるとゼロ(無相関)となります。

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