|
101 | 101 | #include <float.h>
|
102 | 102 | #include <math.h>
|
103 | 103 |
|
| 104 | +#include "access/brin.h" |
104 | 105 | #include "access/gin.h"
|
105 | 106 | #include "access/htup_details.h"
|
106 | 107 | #include "access/sysattr.h"
|
@@ -7721,56 +7722,200 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
|
7721 | 7722 | {
|
7722 | 7723 | IndexOptInfo *index = path->indexinfo;
|
7723 | 7724 | List *indexQuals = path->indexquals;
|
7724 |
| - List *indexOrderBys = path->indexorderbys; |
7725 | 7725 | double numPages = index->pages;
|
7726 |
| - double numTuples = index->tuples; |
| 7726 | + RelOptInfo *baserel = index->rel; |
| 7727 | + RangeTblEntry *rte = planner_rt_fetch(baserel->relid, root); |
7727 | 7728 | List *qinfos;
|
7728 | 7729 | Cost spc_seq_page_cost;
|
7729 | 7730 | Cost spc_random_page_cost;
|
7730 |
| - double qual_op_cost; |
7731 | 7731 | double qual_arg_cost;
|
| 7732 | + double qualSelectivity; |
| 7733 | + BrinStatsData statsData; |
| 7734 | + double indexRanges; |
| 7735 | + double minimalRanges; |
| 7736 | + double estimatedRanges; |
| 7737 | + double selec; |
| 7738 | + Relation indexRel; |
| 7739 | + ListCell *l; |
| 7740 | + VariableStatData vardata; |
7732 | 7741 |
|
7733 |
| - /* Do preliminary analysis of indexquals */ |
7734 |
| - qinfos = deconstruct_indexquals(path); |
| 7742 | + Assert(rte->rtekind == RTE_RELATION); |
7735 | 7743 |
|
7736 |
| - /* fetch estimated page cost for tablespace containing index */ |
| 7744 | + /* fetch estimated page cost for the tablespace containing the index */ |
7737 | 7745 | get_tablespace_page_costs(index->reltablespace,
|
7738 | 7746 | &spc_random_page_cost,
|
7739 | 7747 | &spc_seq_page_cost);
|
7740 | 7748 |
|
7741 | 7749 | /*
|
7742 |
| - * BRIN indexes are always read in full; use that as startup cost. |
| 7750 | + * Obtain some data from the index itself. |
| 7751 | + */ |
| 7752 | + indexRel = index_open(index->indexoid, AccessShareLock); |
| 7753 | + brinGetStats(indexRel, &statsData); |
| 7754 | + index_close(indexRel, AccessShareLock); |
| 7755 | + |
| 7756 | + /* |
| 7757 | + * Compute index correlation |
7743 | 7758 | *
|
7744 |
| - * XXX maybe only include revmap pages here? |
| 7759 | + * Because we can use all index quals equally when scanning, we can use |
| 7760 | + * the largest correlation (in absolute value) among columns used by the |
| 7761 | + * query. Start at zero, the worst possible case. If we cannot find |
| 7762 | + * any correlation statistics, we will keep it as 0. |
| 7763 | + */ |
| 7764 | + *indexCorrelation = 0; |
| 7765 | + |
| 7766 | + qinfos = deconstruct_indexquals(path); |
| 7767 | + foreach(l, qinfos) |
| 7768 | + { |
| 7769 | + IndexQualInfo *qinfo = (IndexQualInfo *) lfirst(l); |
| 7770 | + AttrNumber attnum = index->indexkeys[qinfo->indexcol]; |
| 7771 | + |
| 7772 | + /* attempt to lookup stats in relation for this index column */ |
| 7773 | + if (attnum != 0) |
| 7774 | + { |
| 7775 | + /* Simple variable -- look to stats for the underlying table */ |
| 7776 | + if (get_relation_stats_hook && |
| 7777 | + (*get_relation_stats_hook) (root, rte, attnum, &vardata)) |
| 7778 | + { |
| 7779 | + /* |
| 7780 | + * The hook took control of acquiring a stats tuple. If it |
| 7781 | + * did supply a tuple, it'd better have supplied a freefunc. |
| 7782 | + */ |
| 7783 | + if (HeapTupleIsValid(vardata.statsTuple) && !vardata.freefunc) |
| 7784 | + elog(ERROR, |
| 7785 | + "no function provided to release variable stats with"); |
| 7786 | + } |
| 7787 | + else |
| 7788 | + { |
| 7789 | + vardata.statsTuple = |
| 7790 | + SearchSysCache3(STATRELATTINH, |
| 7791 | + ObjectIdGetDatum(rte->relid), |
| 7792 | + Int16GetDatum(attnum), |
| 7793 | + BoolGetDatum(false)); |
| 7794 | + vardata.freefunc = ReleaseSysCache; |
| 7795 | + } |
| 7796 | + } |
| 7797 | + else |
| 7798 | + { |
| 7799 | + /* |
| 7800 | + * Looks like we've found an expression column in the index. Let's |
| 7801 | + * see if there's any stats for it. |
| 7802 | + */ |
| 7803 | + |
| 7804 | + /* get the attnum from the 0-based index. */ |
| 7805 | + attnum = qinfo->indexcol + 1; |
| 7806 | + |
| 7807 | + if (get_index_stats_hook && |
| 7808 | + (*get_index_stats_hook) (root, index->indexoid, attnum, &vardata)) |
| 7809 | + { |
| 7810 | + /* |
| 7811 | + * The hook took control of acquiring a stats tuple. If it did |
| 7812 | + * supply a tuple, it'd better have supplied a freefunc. |
| 7813 | + */ |
| 7814 | + if (HeapTupleIsValid(vardata.statsTuple) && |
| 7815 | + !vardata.freefunc) |
| 7816 | + elog(ERROR, "no function provided to release variable stats with"); |
| 7817 | + } |
| 7818 | + else |
| 7819 | + { |
| 7820 | + vardata.statsTuple = SearchSysCache3(STATRELATTINH, |
| 7821 | + ObjectIdGetDatum(index->indexoid), |
| 7822 | + Int16GetDatum(attnum), |
| 7823 | + BoolGetDatum(false)); |
| 7824 | + vardata.freefunc = ReleaseSysCache; |
| 7825 | + } |
| 7826 | + } |
| 7827 | + |
| 7828 | + if (HeapTupleIsValid(vardata.statsTuple)) |
| 7829 | + { |
| 7830 | + float4 *numbers; |
| 7831 | + int nnumbers; |
| 7832 | + |
| 7833 | + if (get_attstatsslot(vardata.statsTuple, InvalidOid, 0, |
| 7834 | + STATISTIC_KIND_CORRELATION, |
| 7835 | + InvalidOid, |
| 7836 | + NULL, |
| 7837 | + NULL, NULL, |
| 7838 | + &numbers, &nnumbers)) |
| 7839 | + { |
| 7840 | + double varCorrelation = 0.0; |
| 7841 | + |
| 7842 | + if (nnumbers > 0) |
| 7843 | + varCorrelation = Abs(numbers[0]); |
| 7844 | + |
| 7845 | + if (varCorrelation > *indexCorrelation) |
| 7846 | + *indexCorrelation = varCorrelation; |
| 7847 | + |
| 7848 | + free_attstatsslot(InvalidOid, NULL, 0, numbers, nnumbers); |
| 7849 | + } |
| 7850 | + } |
| 7851 | + |
| 7852 | + ReleaseVariableStats(vardata); |
| 7853 | + } |
| 7854 | + |
| 7855 | + qualSelectivity = clauselist_selectivity(root, indexQuals, |
| 7856 | + baserel->relid, |
| 7857 | + JOIN_INNER, NULL, |
| 7858 | + baserel); |
| 7859 | + |
| 7860 | + /* work out the actual number of ranges in the index */ |
| 7861 | + indexRanges = Max(ceil((double) baserel->pages / statsData.pagesPerRange), |
| 7862 | + 1.0); |
| 7863 | + |
| 7864 | + /* |
| 7865 | + * Now calculate the minimum possible ranges we could match with if all of |
| 7866 | + * the rows were in the perfect order in the table's heap. |
7745 | 7867 | */
|
7746 |
| - *indexStartupCost = spc_seq_page_cost * numPages * loop_count; |
| 7868 | + minimalRanges = ceil(indexRanges * qualSelectivity); |
7747 | 7869 |
|
7748 | 7870 | /*
|
7749 |
| - * To read a BRIN index there might be a bit of back and forth over |
7750 |
| - * regular pages, as revmap might point to them out of sequential order; |
7751 |
| - * calculate this as reading the whole index in random order. |
| 7871 | + * Now estimate the number of ranges that we'll touch by using the |
| 7872 | + * indexCorrelation from the stats. Careful not to divide by zero |
| 7873 | + * (note we're using the absolute value of the correlation). |
7752 | 7874 | */
|
7753 |
| - *indexTotalCost = spc_random_page_cost * numPages * loop_count; |
| 7875 | + if (*indexCorrelation < 1.0e-10) |
| 7876 | + estimatedRanges = indexRanges; |
| 7877 | + else |
| 7878 | + estimatedRanges = Min(minimalRanges / *indexCorrelation, indexRanges); |
7754 | 7879 |
|
7755 |
| - *indexSelectivity = |
7756 |
| - clauselist_selectivity(root, indexQuals, |
7757 |
| - path->indexinfo->rel->relid, |
7758 |
| - JOIN_INNER, NULL, |
7759 |
| - path->indexinfo->rel); |
7760 |
| - *indexCorrelation = 1; |
| 7880 | + /* we expect to visit this portion of the table */ |
| 7881 | + selec = estimatedRanges / indexRanges; |
| 7882 | + |
| 7883 | + CLAMP_PROBABILITY(selec); |
| 7884 | + |
| 7885 | + *indexSelectivity = selec; |
7761 | 7886 |
|
7762 | 7887 | /*
|
7763 |
| - * Add on index qual eval costs, much as in genericcostestimate. |
| 7888 | + * Compute the index qual costs, much as in genericcostestimate, to add |
| 7889 | + * to the index costs. |
7764 | 7890 | */
|
7765 | 7891 | qual_arg_cost = other_operands_eval_cost(root, qinfos) +
|
7766 | 7892 | orderby_operands_eval_cost(root, path);
|
7767 |
| - qual_op_cost = cpu_operator_cost * |
7768 |
| - (list_length(indexQuals) + list_length(indexOrderBys)); |
7769 | 7893 |
|
| 7894 | + /* |
| 7895 | + * Compute the startup cost as the cost to read the whole revmap |
| 7896 | + * sequentially, including the cost to execute the index quals. |
| 7897 | + */ |
| 7898 | + *indexStartupCost = |
| 7899 | + spc_seq_page_cost * statsData.revmapNumPages * loop_count; |
7770 | 7900 | *indexStartupCost += qual_arg_cost;
|
7771 |
| - *indexTotalCost += qual_arg_cost; |
7772 |
| - *indexTotalCost += (numTuples * *indexSelectivity) * (cpu_index_tuple_cost + qual_op_cost); |
7773 |
| - *indexPages = index->pages; |
7774 | 7901 |
|
7775 |
| - /* XXX what about pages_per_range? */ |
| 7902 | + /* |
| 7903 | + * To read a BRIN index there might be a bit of back and forth over |
| 7904 | + * regular pages, as revmap might point to them out of sequential order; |
| 7905 | + * calculate the total cost as reading the whole index in random order. |
| 7906 | + */ |
| 7907 | + *indexTotalCost = *indexStartupCost + |
| 7908 | + spc_random_page_cost * (numPages - statsData.revmapNumPages) * loop_count; |
| 7909 | + |
| 7910 | + /* |
| 7911 | + * Charge a small amount per range tuple which we expect to match to. This |
| 7912 | + * is meant to reflect the costs of manipulating the bitmap. The BRIN scan |
| 7913 | + * will set a bit for each page in the range when we find a matching |
| 7914 | + * range, so we must multiply the charge by the number of pages in the |
| 7915 | + * range. |
| 7916 | + */ |
| 7917 | + *indexTotalCost += 0.1 * cpu_operator_cost * estimatedRanges * |
| 7918 | + statsData.pagesPerRange; |
| 7919 | + |
| 7920 | + *indexPages = index->pages; |
7776 | 7921 | }
|
0 commit comments