From c93a15d76539720a8564de1b0a1100c4734389a7 Mon Sep 17 00:00:00 2001 From: Andres Freund Date: Thu, 17 Oct 2024 13:16:36 -0400 Subject: [PATCH v6 11/14] heapam: Add batch mode mvcc check and use it in page mode There are two reasons for doing so: 1) It is generally faster to perform checks in a batched fashion and making sequential scans faster is nice. 2) We would like to stop setting hint bits while pages are being written out. The necessary locking becomes visible for page mode scans if done for every tuple. With batching the overhead can be amortized to only happen once per page. There are substantial further optimization opportunities along these lines: - Right now HeapTupleSatisfiesMVCCBatch() simply uses the single-tuple HeapTupleSatisfiesMVCC(), relying on the compiler to inline it. We could instead write an explicitly optimized version that avoids repeated xid tests. - Introduce batched version of the serializability test - Introduce batched version of HeapTupleSatisfiesVacuum Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/include/access/heapam.h | 28 +++++++ src/backend/access/heap/heapam.c | 91 ++++++++++++++++----- src/backend/access/heap/heapam_visibility.c | 47 +++++++++++ src/tools/pgindent/typedefs.list | 1 + 4 files changed, 147 insertions(+), 20 deletions(-) diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h index 909db73b7bb..13e4a4096c3 100644 --- a/src/include/access/heapam.h +++ b/src/include/access/heapam.h @@ -410,6 +410,34 @@ extern bool HeapTupleHeaderIsOnlyLocked(HeapTupleHeader tuple); extern bool HeapTupleIsSurelyDead(HeapTuple htup, GlobalVisState *vistest); +/* + * FIXME: define to be removed + * + * Without this I see worse performance. But it's a bit ugly, so I thought + * it'd be useful to leave a way in for others to experiment with this. + */ +#define BATCHMVCC_FEWER_ARGS + +#ifdef BATCHMVCC_FEWER_ARGS +typedef struct BatchMVCCState +{ + HeapTupleData tuples[MaxHeapTuplesPerPage]; + bool visible[MaxHeapTuplesPerPage]; +} BatchMVCCState; +#endif + +extern int HeapTupleSatisfiesMVCCBatch(Snapshot snapshot, Buffer buffer, + int ntups, +#ifdef BATCHMVCC_FEWER_ARGS + BatchMVCCState *batchmvcc, +#else + HeapTupleData *tuples, + bool *visible, +#endif + OffsetNumber *vistuples_dense); + + + /* * To avoid leaking too much knowledge about reorderbuffer implementation * details this is implemented in reorderbuffer.c not heapam_visibility.c diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index 4b0c49f4bb0..ddabd1a3ec3 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -504,42 +504,93 @@ page_collect_tuples(HeapScanDesc scan, Snapshot snapshot, BlockNumber block, int lines, bool all_visible, bool check_serializable) { + Oid relid = RelationGetRelid(scan->rs_base.rs_rd); +#ifdef BATCHMVCC_FEWER_ARGS + BatchMVCCState batchmvcc; + HeapTupleData *tuples = batchmvcc.tuples; + bool *visible = batchmvcc.visible; +#else + HeapTupleData tuples[MaxHeapTuplesPerPage]; + bool visible[MaxHeapTuplesPerPage]; +#endif int ntup = 0; - OffsetNumber lineoff; + int nvis = 0; - for (lineoff = FirstOffsetNumber; lineoff <= lines; lineoff++) + /* page at a time should have been disabled otherwise */ + Assert(IsMVCCSnapshot(snapshot)); + + /* first find all tuples on the page */ + for (OffsetNumber lineoff = FirstOffsetNumber; lineoff <= lines; lineoff++) { ItemId lpp = PageGetItemId(page, lineoff); - HeapTupleData loctup; - bool valid; + HeapTuple tup; - if (!ItemIdIsNormal(lpp)) + if (unlikely(!ItemIdIsNormal(lpp))) continue; - loctup.t_data = (HeapTupleHeader) PageGetItem(page, lpp); - loctup.t_len = ItemIdGetLength(lpp); - loctup.t_tableOid = RelationGetRelid(scan->rs_base.rs_rd); - ItemPointerSet(&(loctup.t_self), block, lineoff); + /* + * If the page is not all-visible or we need to check serializability, + * maintain enough state to be able to refind the tuple efficiently, + * without again needing to extract it from the page. + */ + if (!all_visible || check_serializable) + { + tup = &tuples[ntup]; + tup->t_data = (HeapTupleHeader) PageGetItem(page, lpp); + tup->t_len = ItemIdGetLength(lpp); + tup->t_tableOid = relid; + ItemPointerSet(&(tup->t_self), block, lineoff); + } + + /* + * If the page is all visible, these fields won'otherwise wont be + * populated in loop below. + */ if (all_visible) - valid = true; - else - valid = HeapTupleSatisfiesVisibility(&loctup, snapshot, buffer); - - if (check_serializable) - HeapCheckForSerializableConflictOut(valid, scan->rs_base.rs_rd, - &loctup, buffer, snapshot); - - if (valid) { + if (check_serializable) + { + visible[ntup] = true; + } scan->rs_vistuples[ntup] = lineoff; - ntup++; } + + ntup++; } Assert(ntup <= MaxHeapTuplesPerPage); - return ntup; + /* unless the page is all visible, test visibility for all tuples one go */ + if (all_visible) + nvis = ntup; + else + nvis = HeapTupleSatisfiesMVCCBatch(snapshot, buffer, + ntup, +#ifdef BATCHMVCC_FEWER_ARGS + &batchmvcc, +#else + tuples, visible, +#endif + scan->rs_vistuples + ); + + /* + * So far we don't have batch API for testing serializabilty, so do so + * one-by-one. + */ + if (check_serializable) + { + for (int i = 0; i < ntup; i++) + { + HeapCheckForSerializableConflictOut(visible[i], + scan->rs_base.rs_rd, + &tuples[i], + buffer, snapshot); + } + } + + return nvis; } /* diff --git a/src/backend/access/heap/heapam_visibility.c b/src/backend/access/heap/heapam_visibility.c index 762538a2040..5645cfd8a49 100644 --- a/src/backend/access/heap/heapam_visibility.c +++ b/src/backend/access/heap/heapam_visibility.c @@ -1584,6 +1584,53 @@ HeapTupleSatisfiesHistoricMVCC(HeapTuple htup, Snapshot snapshot, return true; } +/* + * Perform HeaptupleSatisfiesMVCC() on each passed in tuple. This is more + * efficient than doing HeapTupleSatisfiesMVCC() one-by-one. + * + * To be checked tuples are passed via BatchMVCCState->tuples. Each tuple's + * visibility is set in batchmvcc->visible[]. In addition, ->vistuples_dense + * is set to contain the offsets of visible tuples. + * + * Returns the number of visible tuples. + */ +int +HeapTupleSatisfiesMVCCBatch(Snapshot snapshot, Buffer buffer, + int ntups, +#ifdef BATCHMVCC_FEWER_ARGS + BatchMVCCState *batchmvcc, +#else + HeapTupleData *tuples, + bool *visible, +#endif + OffsetNumber *vistuples_dense) +{ + int nvis = 0; +#ifdef BATCHMVCC_FEWER_ARGS + HeapTupleData *tuples = batchmvcc->tuples; + bool *visible = batchmvcc->visible; +#endif + + Assert(IsMVCCSnapshot(snapshot)); + + for (int i = 0; i < ntups; i++) + { + bool valid; + HeapTuple tup = &tuples[i]; + + valid = HeapTupleSatisfiesMVCC(tup, snapshot, buffer); + visible[i] = valid; + + if (likely(valid)) + { + vistuples_dense[nvis] = tup->t_self.ip_posid; + nvis++; + } + } + + return nvis; +} + /* * HeapTupleSatisfiesVisibility * True iff heap tuple satisfies a time qual. diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 9a89e68c59c..9d14239b4c4 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -249,6 +249,7 @@ Barrier BaseBackupCmd BaseBackupTargetHandle BaseBackupTargetType +BatchMVCCState BeginDirectModify_function BeginForeignInsert_function BeginForeignModify_function -- 2.48.1.76.g4e746b1a31.dirty