mirror of
https://github.com/postgres/postgres.git
synced 2025-10-25 13:17:41 +03:00
Further optimize nbtree search scan key comparisons.
Postgres 17 commite0b1ee17added two complementary optimizations to nbtree: the "prechecked" and "firstmatch" optimizations. _bt_readpage was made to avoid needlessly evaluating keys that are guaranteed to be satisfied by applying page-level context. "prechecked" did this for keys required in the current scan direction, while "firstmatch" did it for keys required in the opposite-to-scan direction only. The "prechecked" design had a number of notable issues. It didn't account for the fact that an = array scan key's sk_argument field might need to advance at the point of the page precheck (it didn't check the precheck tuple against the key's array, only the key's sk_argument, which needlessly made it ineffective in cases involving stepping to a page having advanced the scan's arrays using a truncated high key). "prechecked" was also completely ineffective when only one scan key wasn't guaranteed to be satisfied by every tuple (it didn't recognize that it was still safe to avoid evaluating other, earlier keys). The "firstmatch" optimization had similar limitations. It could only be applied after _bt_readpage found its first matching tuple, regardless of why any earlier tuples failed to satisfy the scan's index quals. This allowed unsatisfied non-required scan keys to impede the optimization. Replace both optimizations with a new optimization, without any of these limitations: the "startikey" optimization. Affected _bt_readpage calls generate a page-level key offset ("startikey"), that their _bt_checkkeys calls can then start at. This is an offset to the first key that isn't known to be satisfied by every tuple on the page. Although this is independently useful work, its main goal is to avoid performance regressions with index scans that use skip arrays, but still never manage to skip over irrelevant leaf pages. We must avoid wasting CPU cycles on overly granular skip array maintenance in these cases. The new "startikey" optimization helps with this by selectively disabling array maintenance for the duration of a _bt_readpage call. This has no lasting consequences for the scan's array keys (they'll still reliably track the scan's progress through the index's key space whenever the scan is "between pages"). Skip scan adds skip arrays during preprocessing using simple, static rules, and decides how best to navigate/apply the scan's skip arrays dynamically, at runtime. The "startikey" optimization enables this approach. As a result of all this, the planner doesn't need to generate distinct, competing index paths (one path for skip scan, another for an equivalent traditional full index scan). The overall effect is to make scan runtime close to optimal, even when the planner works off an incorrect cardinality estimate. Scans will also perform well given a skipped column with data skew: individual groups of pages with many distinct values (in respect of a skipped column) can be read about as efficiently as before -- without the scan being forced to give up on skipping over other groups of pages that are provably irrelevant. Many scans that cannot possibly skip will still benefit from the use of skip arrays, since they'll allow the "startikey" optimization to be as effective as possible (by allowing preprocessing to mark all the scan's keys as required). A scan that uses a skip array on "a" for a qual "WHERE a BETWEEN 0 AND 1_000_000 AND b = 42" is often much faster now, even when every tuple read by the scan has its own distinct "a" value. However, there are still some remaining regressions, affecting certain trickier cases. Scans whose index quals have several range skip arrays, each on some high cardinality column, can still be slower than they were before the introduction of skip scan -- even with the new "startikey" optimization. There are also known regressions affecting very selective index scans that use a skip array. The underlying issue with such selective scans is that they never get as far as reading a second leaf page, and so will never get a chance to consider applying the "startikey" optimization. In principle, all regressions could be avoided by teaching preprocessing to not add skip arrays whenever they aren't expected to help, but it seems best to err on the side of robust performance. Follow-up to commit92fe23d9, which added nbtree skip scan. Author: Peter Geoghegan <pg@bowt.ie> Reviewed-By: Heikki Linnakangas <heikki.linnakangas@iki.fi> Reviewed-By: Masahiro Ikeda <ikedamsh@oss.nttdata.com> Reviewed-By: Matthias van de Meent <boekewurm+postgres@gmail.com> Discussion: https://postgr.es/m/CAH2-Wz=Y93jf5WjoOsN=xvqpMjRy-bxCE037bVFi-EasrpeUJA@mail.gmail.com Discussion: https://postgr.es/m/CAH2-WznWDK45JfNPNvDxh6RQy-TaCwULaM5u5ALMXbjLBMcugQ@mail.gmail.com
This commit is contained in:
@@ -1059,6 +1059,7 @@ typedef struct BTScanOpaqueData
|
||||
|
||||
/* workspace for SK_SEARCHARRAY support */
|
||||
int numArrayKeys; /* number of equality-type array keys */
|
||||
bool skipScan; /* At least one skip array in arrayKeys[]? */
|
||||
bool needPrimScan; /* New prim scan to continue in current dir? */
|
||||
bool scanBehind; /* Check scan not still behind on next page? */
|
||||
bool oppositeDirCheck; /* scanBehind opposite-scan-dir check? */
|
||||
@@ -1105,6 +1106,8 @@ typedef struct BTReadPageState
|
||||
IndexTuple finaltup; /* Needed by scans with array keys */
|
||||
Page page; /* Page being read */
|
||||
bool firstpage; /* page is first for primitive scan? */
|
||||
bool forcenonrequired; /* treat all keys as nonrequired? */
|
||||
int startikey; /* start comparisons from this scan key */
|
||||
|
||||
/* Per-tuple input parameters, set by _bt_readpage for _bt_checkkeys */
|
||||
OffsetNumber offnum; /* current tuple's page offset number */
|
||||
@@ -1113,13 +1116,6 @@ typedef struct BTReadPageState
|
||||
OffsetNumber skip; /* Array keys "look ahead" skip offnum */
|
||||
bool continuescan; /* Terminate ongoing (primitive) index scan? */
|
||||
|
||||
/*
|
||||
* Input and output parameters, set and unset by both _bt_readpage and
|
||||
* _bt_checkkeys to manage precheck optimizations
|
||||
*/
|
||||
bool prechecked; /* precheck set continuescan to 'true'? */
|
||||
bool firstmatch; /* at least one match so far? */
|
||||
|
||||
/*
|
||||
* Private _bt_checkkeys state used to manage "look ahead" optimization
|
||||
* (only used during scans with array keys)
|
||||
@@ -1327,6 +1323,7 @@ extern bool _bt_checkkeys(IndexScanDesc scan, BTReadPageState *pstate, bool arra
|
||||
IndexTuple tuple, int tupnatts);
|
||||
extern bool _bt_scanbehind_checkkeys(IndexScanDesc scan, ScanDirection dir,
|
||||
IndexTuple finaltup);
|
||||
extern void _bt_set_startikey(IndexScanDesc scan, BTReadPageState *pstate);
|
||||
extern void _bt_killitems(IndexScanDesc scan);
|
||||
extern BTCycleId _bt_vacuum_cycleid(Relation rel);
|
||||
extern BTCycleId _bt_start_vacuum(Relation rel);
|
||||
|
||||
Reference in New Issue
Block a user