26
26
27
27
#define LOOK_AHEAD_REQUIRED_RECHECKS 3
28
28
#define LOOK_AHEAD_DEFAULT_DISTANCE 5
29
+ #define NSKIPADVANCES_THRESHOLD 3
29
30
30
31
static inline int32 _bt_compare_array_skey (FmgrInfo * orderproc ,
31
32
Datum tupdatum , bool tupnull ,
@@ -41,7 +42,8 @@ static void _bt_array_set_low_or_high(Relation rel, ScanKey skey,
41
42
BTArrayKeyInfo * array , bool low_not_high );
42
43
static bool _bt_array_decrement (Relation rel , ScanKey skey , BTArrayKeyInfo * array );
43
44
static bool _bt_array_increment (Relation rel , ScanKey skey , BTArrayKeyInfo * array );
44
- static bool _bt_advance_array_keys_increment (IndexScanDesc scan , ScanDirection dir );
45
+ static bool _bt_advance_array_keys_increment (IndexScanDesc scan , ScanDirection dir ,
46
+ bool * skip_array_set );
45
47
static void _bt_rewind_nonrequired_arrays (IndexScanDesc scan , ScanDirection dir );
46
48
static bool _bt_tuple_before_array_skeys (IndexScanDesc scan , ScanDirection dir ,
47
49
IndexTuple tuple , TupleDesc tupdesc , int tupnatts ,
@@ -970,7 +972,8 @@ _bt_array_increment(Relation rel, ScanKey skey, BTArrayKeyInfo *array)
970
972
* advanced (every array remains at its final element for scan direction).
971
973
*/
972
974
static bool
973
- _bt_advance_array_keys_increment (IndexScanDesc scan , ScanDirection dir )
975
+ _bt_advance_array_keys_increment (IndexScanDesc scan , ScanDirection dir ,
976
+ bool * skip_array_set )
974
977
{
975
978
Relation rel = scan -> indexRelation ;
976
979
BTScanOpaque so = (BTScanOpaque ) scan -> opaque ;
@@ -985,6 +988,9 @@ _bt_advance_array_keys_increment(IndexScanDesc scan, ScanDirection dir)
985
988
BTArrayKeyInfo * array = & so -> arrayKeys [i ];
986
989
ScanKey skey = & so -> keyData [array -> scan_key ];
987
990
991
+ if (array -> num_elems == -1 )
992
+ * skip_array_set = true;
993
+
988
994
if (ScanDirectionIsForward (dir ))
989
995
{
990
996
if (_bt_array_increment (rel , skey , array ))
@@ -1460,6 +1466,7 @@ _bt_advance_array_keys(IndexScanDesc scan, BTReadPageState *pstate,
1460
1466
ScanDirection dir = so -> currPos .dir ;
1461
1467
int arrayidx = 0 ;
1462
1468
bool beyond_end_advance = false,
1469
+ skip_array_advanced = false,
1463
1470
has_required_opposite_direction_only = false,
1464
1471
all_required_satisfied = true,
1465
1472
all_satisfied = true;
@@ -1756,6 +1763,7 @@ _bt_advance_array_keys(IndexScanDesc scan, BTReadPageState *pstate,
1756
1763
/* Skip array's new element is tupdatum (or MINVAL/MAXVAL) */
1757
1764
_bt_skiparray_set_element (rel , cur , array , result ,
1758
1765
tupdatum , tupnull );
1766
+ skip_array_advanced = true;
1759
1767
}
1760
1768
else if (array -> cur_elem != set_elem )
1761
1769
{
@@ -1772,11 +1780,19 @@ _bt_advance_array_keys(IndexScanDesc scan, BTReadPageState *pstate,
1772
1780
* higher-order arrays (might exhaust all the scan's arrays instead, which
1773
1781
* ends the top-level scan).
1774
1782
*/
1775
- if (beyond_end_advance && !_bt_advance_array_keys_increment (scan , dir ))
1783
+ if (beyond_end_advance &&
1784
+ !_bt_advance_array_keys_increment (scan , dir , & skip_array_advanced ))
1776
1785
goto end_toplevel_scan ;
1777
1786
1778
1787
Assert (_bt_verify_keys_with_arraykeys (scan ));
1779
1788
1789
+ /*
1790
+ * Maintain a page-level count of the number of times the scan's array
1791
+ * keys advanced in a way that affected at least one skip array
1792
+ */
1793
+ if (sktrig_required && skip_array_advanced )
1794
+ pstate -> nskipadvances ++ ;
1795
+
1780
1796
/*
1781
1797
* Does tuple now satisfy our new qual? Recheck with _bt_check_compare.
1782
1798
*
@@ -1946,26 +1962,12 @@ _bt_advance_array_keys(IndexScanDesc scan, BTReadPageState *pstate,
1946
1962
* Being pessimistic would also give some scans with non-required arrays a
1947
1963
* perverse advantage over similar scans that use required arrays instead.
1948
1964
*
1949
- * You can think of this as a speculative bet on what the scan is likely
1950
- * to find on the next page. It's not much of a gamble, though, since the
1951
- * untruncated prefix of attributes must strictly satisfy the new qual.
1965
+ * This is similar to our scan-level heuristics, below. They also set
1966
+ * scanBehind to speculatively continue the primscan onto the next page.
1952
1967
*/
1953
1968
if (so -> scanBehind )
1954
1969
{
1955
- /*
1956
- * Truncated high key -- _bt_scanbehind_checkkeys recheck scheduled.
1957
- *
1958
- * Remember if recheck needs to call _bt_oppodir_checkkeys for next
1959
- * page's finaltup (see below comments about "Handle inequalities
1960
- * marked required in the opposite scan direction" for why).
1961
- */
1962
- so -> oppositeDirCheck = has_required_opposite_direction_only ;
1963
-
1964
- /*
1965
- * Make sure that any SAOP arrays that were not marked required by
1966
- * preprocessing are reset to their first element for this direction
1967
- */
1968
- _bt_rewind_nonrequired_arrays (scan , dir );
1970
+ /* Truncated high key -- _bt_scanbehind_checkkeys recheck scheduled */
1969
1971
}
1970
1972
1971
1973
/*
@@ -2006,6 +2008,10 @@ _bt_advance_array_keys(IndexScanDesc scan, BTReadPageState *pstate,
2006
2008
else if (has_required_opposite_direction_only && pstate -> finaltup &&
2007
2009
unlikely (!_bt_oppodir_checkkeys (scan , dir , pstate -> finaltup )))
2008
2010
{
2011
+ /*
2012
+ * Make sure that any SAOP arrays that were not marked required by
2013
+ * preprocessing are reset to their first element for this direction
2014
+ */
2009
2015
_bt_rewind_nonrequired_arrays (scan , dir );
2010
2016
goto new_prim_scan ;
2011
2017
}
@@ -2032,11 +2038,21 @@ _bt_advance_array_keys(IndexScanDesc scan, BTReadPageState *pstate,
2032
2038
2033
2039
if (so -> scanBehind )
2034
2040
{
2035
- /* Optimization: skip by setting "look ahead" mechanism's offnum */
2041
+ /*
2042
+ * Remember if recheck needs to call _bt_oppodir_checkkeys for next
2043
+ * page's finaltup (see above comments about "Handle inequalities
2044
+ * marked required in the opposite scan direction" for why).
2045
+ */
2046
+ so -> oppositeDirCheck = has_required_opposite_direction_only ;
2047
+
2048
+ _bt_rewind_nonrequired_arrays (scan , dir );
2049
+
2050
+ /*
2051
+ * skip by setting "look ahead" mechanism's offnum for forwards scans
2052
+ * (backwards scans check scanBehind flag directly instead)
2053
+ */
2036
2054
if (ScanDirectionIsForward (dir ))
2037
2055
pstate -> skip = pstate -> maxoff + 1 ;
2038
- else
2039
- pstate -> skip = pstate -> minoff - 1 ;
2040
2056
}
2041
2057
2042
2058
/* Caller's tuple doesn't match the new qual */
@@ -2059,19 +2075,31 @@ _bt_advance_array_keys(IndexScanDesc scan, BTReadPageState *pstate,
2059
2075
* This will in turn encourage _bt_readpage to apply the pstate.startikey
2060
2076
* optimization more often.
2061
2077
*
2062
- * Note: This heuristic isn't as aggressive as you might think. We're
2078
+ * Also continue the ongoing primitive index scan when it is still on the
2079
+ * first page if there have been more than NSKIPADVANCES_THRESHOLD calls
2080
+ * here that each advanced at least one of the scan's skip arrays
2081
+ * (deliberately ignore advancements that only affected SAOP arrays here).
2082
+ * A page that cycles through this many skip array elements is quite
2083
+ * likely to neighbor similar pages, that we'll also need to read.
2084
+ *
2085
+ * Note: These heuristics aren't as aggressive as you might think. We're
2063
2086
* conservative about allowing a primitive scan to step from the first
2064
2087
* leaf page it reads to the page's sibling page (we only allow it on
2065
- * first pages whose finaltup strongly suggests that it'll work out).
2088
+ * first pages whose finaltup strongly suggests that it'll work out, as
2089
+ * well as first pages that have a large number of skip array advances).
2066
2090
* Clearing this first page finaltup hurdle is a strong signal in itself.
2091
+ *
2092
+ * Note: The NSKIPADVANCES_THRESHOLD heuristic exists only to avoid
2093
+ * pathological cases. Specifically, cases where a skip scan should just
2094
+ * behave like a traditional full index scan, but ends up "skipping" again
2095
+ * and again, descending to the prior leaf page's direct sibling leaf page
2096
+ * each time. This misbehavior would otherwise be possible during scans
2097
+ * that never quite manage to "clear the first page finaltup hurdle".
2067
2098
*/
2068
- if (!pstate -> firstpage )
2099
+ if (!pstate -> firstpage || pstate -> nskipadvances > NSKIPADVANCES_THRESHOLD )
2069
2100
{
2070
2101
/* Schedule a recheck once on the next (or previous) page */
2071
2102
so -> scanBehind = true;
2072
- so -> oppositeDirCheck = has_required_opposite_direction_only ;
2073
-
2074
- _bt_rewind_nonrequired_arrays (scan , dir );
2075
2103
2076
2104
/* Continue the current primitive scan after all */
2077
2105
goto continue_scan ;
@@ -2441,7 +2469,9 @@ _bt_oppodir_checkkeys(IndexScanDesc scan, ScanDirection dir,
2441
2469
* the first page (first for the current primitive scan) avoids wasting cycles
2442
2470
* during selective point queries. They typically don't stand to gain as much
2443
2471
* when we can set pstate.startikey, and are likely to notice the overhead of
2444
- * calling here.
2472
+ * calling here. (Also, allowing pstate.forcenonrequired to be set on a
2473
+ * primscan's first page would mislead _bt_advance_array_keys, which expects
2474
+ * pstate.nskipadvances to be representative of every first page's key space.)
2445
2475
*
2446
2476
* Caller must reset startikey and forcenonrequired ahead of the _bt_checkkeys
2447
2477
* call for pstate.finaltup iff we set forcenonrequired=true. This will give
0 commit comments