public inbox for [email protected]help / color / mirror / Atom feed
[PATCH] cfe-12-gist_over_cfe-11-persistent squash commit 5+ messages / 3 participants [nested] [flat]
* [PATCH] cfe-12-gist_over_cfe-11-persistent squash commit @ 2021-03-12 01:02 Bruce Momjian <[email protected]> 0 siblings, 0 replies; 5+ messages in thread From: Bruce Momjian @ 2021-03-12 01:02 UTC (permalink / raw) --- src/backend/access/gist/README | 10 ++++++++++ src/backend/access/gist/gist.c | 21 +++++++++++++++------ src/backend/access/gist/gistbuild.c | 9 ++++++--- src/backend/access/gist/gistvacuum.c | 18 ++++++++++++------ 4 files changed, 43 insertions(+), 15 deletions(-) diff --git a/src/backend/access/gist/README b/src/backend/access/gist/README index 25cab0047b..6b1f84634f 100644 --- a/src/backend/access/gist/README +++ b/src/backend/access/gist/README @@ -461,6 +461,16 @@ value. The page is not recycled, until that XID is no longer visible to anyone. That's much more conservative than necessary, but let's keep it simple. +GiST and Encryption +------------------- + +GiST uses LSNs and NSNs for concurrency. This means that unlogged and +temporary relations also need LSNs. GiST has a mechanism to assign LSNs +to such relations, but sometimes uses fixed LSNs and or calls +gistGetFakeLSN(), which can cause duplicate LSNs to be used. Therefore, +when encryption is enabled and fake LSNs are needed, the GiST code calls +LSNForEncryption(). See src/backend/crypto/README for more details. + Authors: Teodor Sigaev <[email protected]> diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c index 0683f42c25..5ca5f17db3 100644 --- a/src/backend/access/gist/gist.c +++ b/src/backend/access/gist/gist.c @@ -501,13 +501,16 @@ gistplacetopage(Relation rel, Size freespace, GISTSTATE *giststate, * we don't need to be able to detect concurrent splits yet.) */ if (is_build) - recptr = GistBuildLSN; + recptr = !FileEncryptionEnabled ? GistBuildLSN : + LSNForEncryption(RelationIsPermanent(rel)); else { if (RelationNeedsWAL(rel)) recptr = gistXLogSplit(is_leaf, dist, oldrlink, oldnsn, leftchildbuf, markfollowright); + else if (FileEncryptionEnabled) + recptr = LSNForEncryption(RelationIsPermanent(rel)); else recptr = gistGetFakeLSN(rel); } @@ -568,7 +571,8 @@ gistplacetopage(Relation rel, Size freespace, GISTSTATE *giststate, MarkBufferDirty(leftchildbuf); if (is_build) - recptr = GistBuildLSN; + recptr = !FileEncryptionEnabled ? GistBuildLSN : + LSNForEncryption(RelationIsPermanent(rel)); else { if (RelationNeedsWAL(rel)) @@ -586,6 +590,8 @@ gistplacetopage(Relation rel, Size freespace, GISTSTATE *giststate, deloffs, ndeloffs, itup, ntup, leftchildbuf); } + else if (FileEncryptionEnabled) + recptr = LSNForEncryption(RelationIsPermanent(rel)); else recptr = gistGetFakeLSN(rel); } @@ -1665,6 +1671,8 @@ gistprunepage(Relation rel, Page page, Buffer buffer, Relation heapRel) if (ndeletable > 0) { + XLogRecPtr recptr; + TransactionId latestRemovedXid = InvalidTransactionId; if (XLogStandbyInfoActive() && RelationNeedsWAL(rel)) @@ -1690,16 +1698,17 @@ gistprunepage(Relation rel, Page page, Buffer buffer, Relation heapRel) /* XLOG stuff */ if (RelationNeedsWAL(rel)) { - XLogRecPtr recptr; - recptr = gistXLogDelete(buffer, deletable, ndeletable, latestRemovedXid); - PageSetLSN(page, recptr); } + else if (FileEncryptionEnabled) + recptr = LSNForEncryption(RelationIsPermanent(rel)); else - PageSetLSN(page, gistGetFakeLSN(rel)); + recptr = gistGetFakeLSN(rel); + + PageSetLSN(page, recptr); END_CRIT_SECTION(); } diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c index 1054f6f1f2..a8b82483e8 100644 --- a/src/backend/access/gist/gistbuild.c +++ b/src/backend/access/gist/gistbuild.c @@ -299,7 +299,8 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo) GISTInitBuffer(buffer, F_LEAF); MarkBufferDirty(buffer); - PageSetLSN(page, GistBuildLSN); + PageSetLSN(page, !FileEncryptionEnabled ? GistBuildLSN : + LSNForEncryption(RelationIsPermanent(index))); UnlockReleaseBuffer(buffer); @@ -451,7 +452,8 @@ gist_indexsortbuild(GISTBuildState *state) /* Write out the root */ RelationOpenSmgr(state->indexrel); - PageSetLSN(pagestate->page, GistBuildLSN); + PageSetLSN(pagestate->page, !FileEncryptionEnabled ? GistBuildLSN : + LSNForEncryption(RelationIsPermanent(state->indexrel))); PageSetChecksumInplace(pagestate->page, GIST_ROOT_BLKNO); smgrwrite(state->indexrel->rd_smgr, MAIN_FORKNUM, GIST_ROOT_BLKNO, pagestate->page, true); @@ -573,7 +575,8 @@ gist_indexsortbuild_flush_ready_pages(GISTBuildState *state) if (blkno != state->pages_written) elog(ERROR, "unexpected block number to flush GiST sorting build"); - PageSetLSN(page, GistBuildLSN); + PageSetLSN(page, !FileEncryptionEnabled ? GistBuildLSN : + LSNForEncryption(RelationIsPermanent(state->indexrel))); PageSetChecksumInplace(page, blkno); smgrextend(state->indexrel->rd_smgr, MAIN_FORKNUM, blkno, page, true); diff --git a/src/backend/access/gist/gistvacuum.c b/src/backend/access/gist/gistvacuum.c index 0663193531..f783edd627 100644 --- a/src/backend/access/gist/gistvacuum.c +++ b/src/backend/access/gist/gistvacuum.c @@ -174,6 +174,8 @@ gistvacuumscan(IndexVacuumInfo *info, IndexBulkDeleteResult *stats, vstate.callback_state = callback_state; if (RelationNeedsWAL(rel)) vstate.startNSN = GetInsertRecPtr(); + else if (FileEncryptionEnabled) + vstate.startNSN = LSNForEncryption(RelationIsPermanent(rel)); else vstate.startNSN = gistGetFakeLSN(rel); @@ -353,6 +355,8 @@ restart: */ if (ntodelete > 0) { + XLogRecPtr recptr; + START_CRIT_SECTION(); MarkBufferDirty(buffer); @@ -361,16 +365,15 @@ restart: GistMarkTuplesDeleted(page); if (RelationNeedsWAL(rel)) - { - XLogRecPtr recptr; - recptr = gistXLogUpdate(buffer, todelete, ntodelete, NULL, 0, InvalidBuffer); - PageSetLSN(page, recptr); - } + else if (FileEncryptionEnabled) + recptr = LSNForEncryption(RelationIsPermanent(rel)); else - PageSetLSN(page, gistGetFakeLSN(rel)); + recptr = gistGetFakeLSN(rel); + + PageSetLSN(page, recptr); END_CRIT_SECTION(); @@ -657,8 +660,11 @@ gistdeletepage(IndexVacuumInfo *info, IndexBulkDeleteResult *stats, if (RelationNeedsWAL(info->index)) recptr = gistXLogPageDelete(leafBuffer, txid, parentBuffer, downlink); + else if (FileEncryptionEnabled) + recptr = LSNForEncryption(RelationIsPermanent(info->index)); else recptr = gistGetFakeLSN(info->index); + PageSetLSN(parentPage, recptr); PageSetLSN(leafPage, recptr); -- 2.20.1 --LQksG6bCIzRHxTLp Content-Type: application/gzip Content-Disposition: attachment; filename="cfe-13-rel_over_cfe-12-gist.diff.gz" Content-Transfer-Encoding: base64 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 vb//X+BdTqWQfwAA --LQksG6bCIzRHxTLp-- ^ permalink raw reply [nested|flat] 5+ messages in thread
* Re: Missing update of all_hasnulls in BRIN opclasses @ 2023-03-03 12:14 Tomas Vondra <[email protected]> 0 siblings, 1 reply; 5+ messages in thread From: Tomas Vondra @ 2023-03-03 12:14 UTC (permalink / raw) To: Alvaro Herrera <[email protected]>; +Cc: Justin Pryzby <[email protected]>; Matthias van de Meent <[email protected]>; [email protected] On 3/3/23 11:32, Alvaro Herrera wrote: > > Thanks for doing all this. (Do I understand correctly that this patch > is not in the commitfest?) > > I think my mental model for this was that "allnulls" meant that either > there are no values for the column in question or that the values were > all nulls (For minmax without NULL handling, which is where this all > started, these two things are essentially the same: the range is not to > be returned. So this became a bug the instant I added handling for NULL > values.) I failed to realize that these were two different things, and > this is likely the origin of all these troubles. > > What do you think of using the unused bit in BrinTuple->bt_info to > denote a range that contains no heap tuples? This also means we need it > in BrinMemTuple, I think we can do this: > > @@ -44,6 +44,7 @@ typedef struct BrinValues > typedef struct BrinMemTuple > { > bool bt_placeholder; /* this is a placeholder tuple */ > + bool bt_empty_range; /* range has no tuples */ > BlockNumber bt_blkno; /* heap blkno that the tuple is for */ > MemoryContext bt_context; /* memcxt holding the bt_columns values */ > /* output arrays for brin_deform_tuple: */ > @@ -69,7 +70,7 @@ typedef struct BrinTuple > * > * 7th (high) bit: has nulls > * 6th bit: is placeholder tuple > - * 5th bit: unused > + * 5th bit: range has no tuples > * 4-0 bit: offset of data > * --------------- > */ > @@ -82,7 +83,7 @@ typedef struct BrinTuple > * bt_info manipulation macros > */ > #define BRIN_OFFSET_MASK 0x1F > -/* bit 0x20 is not used at present */ > +#define BRIN_EMPTY_RANGE 0x20 > #define BRIN_PLACEHOLDER_MASK 0x40 > #define BRIN_NULLS_MASK 0x80 > > (Note that bt_empty_range uses a hole in the struct, so there's no ABI > change.) > > This is BRIN-tuple-level, not column-level, so conceptually it seems > more appropriate. (In the case where both are empty in union_tuples, we > can return without entering the per-attribute loop at all, though I > admit it's not a very interesting case.) This approach avoids having to > invent the strange combination of all+has to mean empty. > Oh, that's an interesting idea! I haven't realized there's an unused bit at the tuple level, and I absolutely agree it'd be a better match than having this in individual summaries (like now). It'd mean we'd not have the option to fix this withing the opclasses, because we only pass them the BrinValue and not the tuple. But if you think that's reasonable, that'd be OK. The other thing I was unsure is if the bit could be set for any existing tuples, but AFAICS that shouldn't be possible - brin_form_tuple does palloc0, so it should be 0. I suspect doing this might make the patch quite a bit simpler, actually. > > On 2023-Feb-24, Tomas Vondra wrote: > >> I wonder what's the best >> way to test this in an automated way - it's very dependent on timing of >> the concurrent updated. For example we need to do something like this: >> >> T1: run pg_summarize_range() until it inserts the placeholder tuple >> T2: do an insert into the page range (updates placeholder) >> T1: continue pg_summarize_range() to merge into the placeholder >> >> But there are no convenient ways to do this, I think. I had to check the >> various cases using breakpoints in gdb etc. > > Yeah, I struggled with this during initial development but in the end > did nothing. I think we would need to introduce some new framework, > perhaps Korotkov stop-events stuff at > https://postgr.es/m/CAPpHfdsTeb+hBT5=qxghjNG_cHcJLDaNQ9sdy9vNwBF2E2PuZA@mail.gmail.com > which seemed to me a good fit -- we would add a stop point after the > placeholder tuple is inserted. > Yeah, but we don't have that at the moment. I actually ended up adding a couple sleeps during development, which allowed me to hit just the right order of operations - a poor-man's version of those stop-events. Did work but hardly an acceptable approach. >> I'm not very happy with the union_tuples() changes - it's quite verbose, >> perhaps a bit too verbose. We have to check for empty ranges first, and >> then various combinations of allnulls/hasnulls flags for both BRIN >> tuples. There are 9 combinations, and the current code just checks them >> one by one - I was getting repeatedly confused by the original code, but >> maybe it's too much. > > I think it's okay. I tried to make it more compact (by saying "these > two combinations here are case 2, and these two other are case 4", and > keeping each of the other combinations a separate case; so there are > really 7 cases). But that doesn't make it any easier to follow, on the > contrary it was more convoluted. I think a dozen extra lines of source > is not a problem. > OK >> The alternative is to apply the same fix to every BRIN_PROCNUM_UNION >> opclass procedure out there. I guess doing that for minmax+inclusion is >> not a huge deal, but what about external opclasses? And without the fix >> the indexes are effectively broken. Fixing this outside in brin.c (in >> the union procedure) fixes this for every opclass procedure, without any >> actual limitation of functinality (14+ does that anyway). > > About the hypothetical question, you could as well ask what about > unicorns. I have never seen any hint that any external opclass exist. > I am all for maintaining compatibility, but I think this concern is > overblown for BRIN. Anyway, I think your proposed fix is better than > changing individual 'union' support procs, so it doesn't matter. > OK > As far as I understood, you're now worried that there will be an > incompatibility because we will fail to call the 'union' procedure in > cases where we previously called it? In other words, you fear that some > hypothetical opclass was handling the NULL values in some way that's > incompatible with this? I haven't thought terribly hard about this, but > I can't see a way for this to cause incompatibilities. > Yeah, the possible incompatibility is one concern - I have a hard time imagining such an opclass, because it'd have to handle NULLs in some strange way. But and as you noted, we're not aware of any external BRIN opclasses, so maybe this is OK. The other concern is more generic - as I mentioned, moving the NULL handling from opclasses to brin.c is what we did in PG14, so this feels a bit like a backport, and I dislike that a little bit. >> But maybe someone thinks this is a bad idea and we should do something >> else in the backbranches? > > I think the new handling of NULLs in commit 72ccf55cb99c ("Move IS [NOT] > NULL handling from BRIN support functions") is better than what was > there before, so I don't object to backpatching it now that we know it's > necessary to fix a bug, and also we have field experience that the > approach is solid. > OK, good to hear. > The attached patch is just a pointer to comments that I think need light > edition. There's also a typo "bot" (for "both") in a comment that I > think would go away if you accept my suggestion to store 'empty' at the > tuple level. Note that I worked with the REL_14_STABLE sources, because > for some reason I thought that that was the newest that needed > backpatching of 72ccf55cb99c, but now that I'm finishing this email I > realize that I should have used 13 instead /facepalm > Thanks. I'll try to rework the patches to use the bt_info unused bit, and report back in a week or two. regards -- Tomas Vondra EnterpriseDB: http://www.enterprisedb.com The Enterprise PostgreSQL Company ^ permalink raw reply [nested|flat] 5+ messages in thread
* Re: Missing update of all_hasnulls in BRIN opclasses @ 2023-03-28 14:30 Tomas Vondra <[email protected]> parent: Tomas Vondra <[email protected]> 0 siblings, 1 reply; 5+ messages in thread From: Tomas Vondra @ 2023-03-28 14:30 UTC (permalink / raw) To: Alvaro Herrera <[email protected]>; +Cc: Justin Pryzby <[email protected]>; Matthias van de Meent <[email protected]>; [email protected] Hi, It took me a while but I finally got back to reworking this to use the bt_info bit, as proposed by Alvaro. And it turned out to work great, because (a) it's a tuple-level flag, i.e. the right place, and (b) it does not overload existing flags. This greatly simplified the code in add_values_to_range and (especially) union_tuples, making it much easier to understand, I think. One disadvantage is we are unable to see which ranges are empty in current pageinspect, but 0002 addresses that by adding "empty" column to the brin_page_items() output. That's a matter for master only, though. It's a trivial patch and it makes it easier/possible to test this, so we should consider to squeeze it into PG16. I did quite a bit of testing - the attached 0003 adds extra tests, but I don't propose to get this committed as is - it's rather overkill. Maybe some reduced version of it ... The hardest thing to test is the union_tuples() part, as it requires concurrent operations with "correct" timing. Easy to simulate by breakpoints in GDB, not so much in plain regression/TAP tests. There's also a stress tests, doing a lot of randomized summarizations, etc. Without the fix this failed in maybe 30% of runs, now I did ~100 runs without a single failure. I haven't done any backporting, but I think it should be simpler than with the earlier approach. I wonder if we need to care about starting to use the previously unused bit - I don't think so, in the worst case we'll just ignore it, but maybe I'm missing something (e.g. when using physical replication). regards -- Tomas Vondra EnterpriseDB: http://www.enterprisedb.com The Enterprise PostgreSQL Company Attachments: [text/x-patch] 0001-Fix-handling-of-NULLs-in-BRIN-indexes.patch (13.8K, ../../[email protected]/2-0001-Fix-handling-of-NULLs-in-BRIN-indexes.patch) download | inline diff: From 10efaf9964806e5a30818994e3cfda879bb90171 Mon Sep 17 00:00:00 2001 From: Tomas Vondra <[email protected]> Date: Sun, 8 Jan 2023 16:43:06 +0100 Subject: [PATCH 1/3] Fix handling of NULLs in BRIN indexes BRIN indexes did not properly distinguish between summaries for empty (no rows) and all-NULL ranges. All summaries were initialized with allnulls=true, and the opclasses simply reset allnulls to false when processing the first non-NULL value. This however fails if the range starts with a NULL value (or a sequence of NULL values), in which case we forget the range contains NULL values. This happens because the allnulls flag is used for two separate purposes - to mark empty ranges (not representing any rows yet) and ranges containing only NULL values. Opclasses don't know which of these cases it is, and so don't know whether to set hasnulls=true. Setting hasnulls=true in both cases would make it correct, but it would also make BRIN indexes useless for queries with IS NULL clauses - all ranges start empty (and thus allnulls=true), so all ranges would end up with either allnulls=true or hasnulls=true. The severity of the issue is somewhat reduced by the fact that it only happens when adding values to an existing summary with allnulls=true, not when the summarization is processing values in bulk (e.g. during CREATE INDEX or automatic summarization). In this case the flags were updated in a slightly different way, not forgetting the NULL values. This introduces a new a new flag marking index tuples representing ranges with no rows. Luckily we have an unused tuple in the BRIN tuple header that we can use for this. We still store index tuples for empty ranges, because otherwise we'd not be able to say whether a range is empty or not yet summarized, and we'd have to process them for any query. Backpatch to 11. The issue exists since BRIN indexes were introduced in 9.5, but older releases are already EOL. Backpatch-through: 11 Reviewed-by: Alvaro Herrera, Justin Pryzby, Matthias van de Meent Discussion: https://postgr.es/m/[email protected] --- src/backend/access/brin/brin.c | 115 +++++++++++++++++- src/backend/access/brin/brin_tuple.c | 15 ++- src/include/access/brin_tuple.h | 6 +- ...summarization-and-inprogress-insertion.out | 8 +- ...ummarization-and-inprogress-insertion.spec | 1 + 5 files changed, 137 insertions(+), 8 deletions(-) diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c index 53e4721a54e..162a0c052aa 100644 --- a/src/backend/access/brin/brin.c +++ b/src/backend/access/brin/brin.c @@ -592,6 +592,17 @@ bringetbitmap(IndexScanDesc scan, TIDBitmap *tbm) bval = &dtup->bt_columns[attno - 1]; + /* + * If the BRIN tuple indicates that this range is empty, + * we can skip it: there's nothing to match. We don't + * need to examine the next columns. + */ + if (dtup->bt_empty_range) + { + addrange = false; + break; + } + /* * First check if there are any IS [NOT] NULL scan keys, * and if we're violating them. In that case we can @@ -1590,6 +1601,8 @@ form_and_insert_tuple(BrinBuildState *state) /* * Given two deformed tuples, adjust the first one so that it's consistent * with the summary values in both. + * + * XXX I'm not sure we can actually get empty "b". */ static void union_tuples(BrinDesc *bdesc, BrinMemTuple *a, BrinTuple *b) @@ -1607,6 +1620,64 @@ union_tuples(BrinDesc *bdesc, BrinMemTuple *a, BrinTuple *b) db = brin_deform_tuple(bdesc, b, NULL); MemoryContextSwitchTo(oldcxt); + /* + * Check if the ranges are empty. + * + * If at least one of them is empty, we don't need to call per-key union + * functions at all. If "b" is empty, we just use "a" as the result (it + * might be empty fine, but that's fine). If "a" is empty but "b" is not, + * we use "b" as the result (but we have to copy the data into "a" first). + * + * Only when both ranges are non-empty, we actually do the per-key merge. + */ + + /* If "b" is empty - ignore it and just use "a" (even if it's empty etc.). */ + if (db->bt_empty_range) + { + /* skip the per-key merge */ + MemoryContextDelete(cxt); + return; + } + + /* + * Now we know "b" is not empty. If "a" is empty, then "b" is the result. + * But we need to copy the data from "b" to "a" first, because that's how + * we pass result out. + * + * We have to copy all the global/per-key flags etc. too. + */ + if (a->bt_empty_range) + { + for (keyno = 0; keyno < bdesc->bd_tupdesc->natts; keyno++) + { + int i; + BrinValues *col_a = &a->bt_columns[keyno]; + BrinValues *col_b = &db->bt_columns[keyno]; + BrinOpcInfo *opcinfo = bdesc->bd_info[keyno]; + + col_a->bv_allnulls = col_b->bv_allnulls; + col_a->bv_hasnulls = col_b->bv_hasnulls; + + /* If "b" has no data, we're done. */ + if (col_b->bv_allnulls) + continue; + + for (i = 0; i < opcinfo->oi_nstored; i++) + col_a->bv_values[i] = + datumCopy(col_b->bv_values[i], + opcinfo->oi_typcache[i]->typbyval, + opcinfo->oi_typcache[i]->typlen); + } + + /* "a" started empty, but "b" was not empty, so remember that */ + a->bt_empty_range = false; + + /* skip the per-key merge */ + MemoryContextDelete(cxt); + return; + } + + /* Now we know neither range is empty. */ for (keyno = 0; keyno < bdesc->bd_tupdesc->natts; keyno++) { FmgrInfo *unionFn; @@ -1704,7 +1775,9 @@ add_values_to_range(Relation idxRel, BrinDesc *bdesc, BrinMemTuple *dtup, Datum *values, bool *nulls) { int keyno; - bool modified = false; + + /* If the range starts empty, we're certainly going to modify it. */ + bool modified = dtup->bt_empty_range; /* * Compare the key values of the new tuple to the stored index values; our @@ -1718,9 +1791,24 @@ add_values_to_range(Relation idxRel, BrinDesc *bdesc, BrinMemTuple *dtup, Datum result; BrinValues *bval; FmgrInfo *addValue; + bool has_nulls; bval = &dtup->bt_columns[keyno]; + /* + * Does the range have actual NULL values? Either of the flags can + * be set, but we ignore the state before adding first row. + * + * We have to remember this, because we'll modify the flags and we + * need to know if the range started as empty. + */ + has_nulls = ((!dtup->bt_empty_range) && + (bval->bv_hasnulls || bval->bv_allnulls)); + + /* + * If the value we're adding is NULL, handle it locally. Otherwise + * call the BRIN_PROCNUM_ADDVALUE procedure. + */ if (bdesc->bd_info[keyno]->oi_regular_nulls && nulls[keyno]) { /* @@ -1746,8 +1834,33 @@ add_values_to_range(Relation idxRel, BrinDesc *bdesc, BrinMemTuple *dtup, nulls[keyno]); /* if that returned true, we need to insert the updated tuple */ modified |= DatumGetBool(result); + + /* + * If the range was had actual NULL values (i.e. did not start empty), + * make sure we don't forget about the NULL values. Either the allnulls + * flag is still set to true, or (if the opclass cleared it) we need to + * set hasnulls=true. + * + * XXX This can only happen when the opclass modified the tuple, so the + * modified flag should be set. + */ + if (has_nulls && !(bval->bv_hasnulls || bval->bv_allnulls)) + { + Assert(modified); + bval->bv_hasnulls = true; + } } + /* + * After updating summaries for all the keys, mark it as not empty. + * + * If we're actually changing the flag value (i.e. tuple started as empty), + * we should have modified the tuple. So we should not see empty range that + * was not modified. + */ + Assert(!dtup->bt_empty_range || modified); + dtup->bt_empty_range = false; + return modified; } diff --git a/src/backend/access/brin/brin_tuple.c b/src/backend/access/brin/brin_tuple.c index 84b79dbfc0d..b81247a262c 100644 --- a/src/backend/access/brin/brin_tuple.c +++ b/src/backend/access/brin/brin_tuple.c @@ -372,6 +372,9 @@ brin_form_tuple(BrinDesc *brdesc, BlockNumber blkno, BrinMemTuple *tuple, if (tuple->bt_placeholder) rettuple->bt_info |= BRIN_PLACEHOLDER_MASK; + if (tuple->bt_empty_range) + rettuple->bt_info |= BRIN_EMPTY_RANGE_MASK; + *size = len; return rettuple; } @@ -399,7 +402,7 @@ brin_form_placeholder_tuple(BrinDesc *brdesc, BlockNumber blkno, Size *size) rettuple = palloc0(len); rettuple->bt_blkno = blkno; rettuple->bt_info = hoff; - rettuple->bt_info |= BRIN_NULLS_MASK | BRIN_PLACEHOLDER_MASK; + rettuple->bt_info |= BRIN_NULLS_MASK | BRIN_PLACEHOLDER_MASK | BRIN_EMPTY_RANGE_MASK; bitP = ((bits8 *) ((char *) rettuple + SizeOfBrinTuple)) - 1; bitmask = HIGHBIT; @@ -489,6 +492,8 @@ brin_new_memtuple(BrinDesc *brdesc) dtup->bt_allnulls = palloc(sizeof(bool) * brdesc->bd_tupdesc->natts); dtup->bt_hasnulls = palloc(sizeof(bool) * brdesc->bd_tupdesc->natts); + dtup->bt_empty_range = true; + dtup->bt_context = AllocSetContextCreate(CurrentMemoryContext, "brin dtuple", ALLOCSET_DEFAULT_SIZES); @@ -527,6 +532,9 @@ brin_memtuple_initialize(BrinMemTuple *dtuple, BrinDesc *brdesc) currdatum += sizeof(Datum) * brdesc->bd_info[i]->oi_nstored; } + /* FIXME Shouldn't this reset bt_placeholder too? */ + dtuple->bt_empty_range = true; + return dtuple; } @@ -560,6 +568,11 @@ brin_deform_tuple(BrinDesc *brdesc, BrinTuple *tuple, BrinMemTuple *dMemtuple) if (BrinTupleIsPlaceholder(tuple)) dtup->bt_placeholder = true; + + /* ranges start as empty, depends on the BrinTuple */ + if (!BrinTupleIsEmptyRange(tuple)) + dtup->bt_empty_range = false; + dtup->bt_blkno = tuple->bt_blkno; values = dtup->bt_values; diff --git a/src/include/access/brin_tuple.h b/src/include/access/brin_tuple.h index 732f91edf11..c56747aca4a 100644 --- a/src/include/access/brin_tuple.h +++ b/src/include/access/brin_tuple.h @@ -44,6 +44,7 @@ typedef struct BrinValues typedef struct BrinMemTuple { bool bt_placeholder; /* this is a placeholder tuple */ + bool bt_empty_range; /* range represents no tuples */ BlockNumber bt_blkno; /* heap blkno that the tuple is for */ MemoryContext bt_context; /* memcxt holding the bt_columns values */ /* output arrays for brin_deform_tuple: */ @@ -69,7 +70,7 @@ typedef struct BrinTuple * * 7th (high) bit: has nulls * 6th bit: is placeholder tuple - * 5th bit: unused + * 5th bit: range is empty * 4-0 bit: offset of data * --------------- */ @@ -82,13 +83,14 @@ typedef struct BrinTuple * bt_info manipulation macros */ #define BRIN_OFFSET_MASK 0x1F -/* bit 0x20 is not used at present */ +#define BRIN_EMPTY_RANGE_MASK 0x20 #define BRIN_PLACEHOLDER_MASK 0x40 #define BRIN_NULLS_MASK 0x80 #define BrinTupleDataOffset(tup) ((Size) (((BrinTuple *) (tup))->bt_info & BRIN_OFFSET_MASK)) #define BrinTupleHasNulls(tup) (((((BrinTuple *) (tup))->bt_info & BRIN_NULLS_MASK)) != 0) #define BrinTupleIsPlaceholder(tup) (((((BrinTuple *) (tup))->bt_info & BRIN_PLACEHOLDER_MASK)) != 0) +#define BrinTupleIsEmptyRange(tup) (((((BrinTuple *) (tup))->bt_info & BRIN_EMPTY_RANGE_MASK)) != 0) extern BrinTuple *brin_form_tuple(BrinDesc *brdesc, BlockNumber blkno, diff --git a/src/test/modules/brin/expected/summarization-and-inprogress-insertion.out b/src/test/modules/brin/expected/summarization-and-inprogress-insertion.out index 2a4755d0998..584ac2602f7 100644 --- a/src/test/modules/brin/expected/summarization-and-inprogress-insertion.out +++ b/src/test/modules/brin/expected/summarization-and-inprogress-insertion.out @@ -4,7 +4,7 @@ starting permutation: s2check s1b s2b s1i s2summ s1c s2c s2check step s2check: SELECT * FROM brin_page_items(get_raw_page('brinidx', 2), 'brinidx'::regclass); itemoffset|blknum|attnum|allnulls|hasnulls|placeholder|value ----------+------+------+--------+--------+-----------+-------- - 1| 0| 1|f |f |f |{1 .. 1} + 1| 0| 1|f |t |f |{1 .. 1} (1 row) step s1b: BEGIN ISOLATION LEVEL REPEATABLE READ; @@ -26,7 +26,7 @@ step s2c: COMMIT; step s2check: SELECT * FROM brin_page_items(get_raw_page('brinidx', 2), 'brinidx'::regclass); itemoffset|blknum|attnum|allnulls|hasnulls|placeholder|value ----------+------+------+--------+--------+-----------+----------- - 1| 0| 1|f |f |f |{1 .. 1} + 1| 0| 1|f |t |f |{1 .. 1} 2| 1| 1|f |f |f |{1 .. 1000} (2 rows) @@ -35,7 +35,7 @@ starting permutation: s2check s1b s1i s2vacuum s1c s2check step s2check: SELECT * FROM brin_page_items(get_raw_page('brinidx', 2), 'brinidx'::regclass); itemoffset|blknum|attnum|allnulls|hasnulls|placeholder|value ----------+------+------+--------+--------+-----------+-------- - 1| 0| 1|f |f |f |{1 .. 1} + 1| 0| 1|f |t |f |{1 .. 1} (1 row) step s1b: BEGIN ISOLATION LEVEL REPEATABLE READ; @@ -45,7 +45,7 @@ step s1c: COMMIT; step s2check: SELECT * FROM brin_page_items(get_raw_page('brinidx', 2), 'brinidx'::regclass); itemoffset|blknum|attnum|allnulls|hasnulls|placeholder|value ----------+------+------+--------+--------+-----------+----------- - 1| 0| 1|f |f |f |{1 .. 1} + 1| 0| 1|f |t |f |{1 .. 1} 2| 1| 1|f |f |f |{1 .. 1000} (2 rows) diff --git a/src/test/modules/brin/specs/summarization-and-inprogress-insertion.spec b/src/test/modules/brin/specs/summarization-and-inprogress-insertion.spec index 19ac18a2e88..18ba92b7ba1 100644 --- a/src/test/modules/brin/specs/summarization-and-inprogress-insertion.spec +++ b/src/test/modules/brin/specs/summarization-and-inprogress-insertion.spec @@ -9,6 +9,7 @@ setup ) WITH (fillfactor=10); CREATE INDEX brinidx ON brin_iso USING brin (value) WITH (pages_per_range=1); -- this fills the first page + INSERT INTO brin_iso VALUES (NULL); DO $$ DECLARE curtid tid; BEGIN -- 2.39.2 [text/x-patch] 0002-Show-empty-ranges-in-brin_page_items.patch (6.9K, ../../[email protected]/3-0002-Show-empty-ranges-in-brin_page_items.patch) download | inline diff: From a7ed39285a7c8f3655091346755b81b0d79b2f3e Mon Sep 17 00:00:00 2001 From: Tomas Vondra <[email protected]> Date: Mon, 27 Mar 2023 22:47:12 +0200 Subject: [PATCH 2/3] Show empty ranges in brin_page_items Show which BRIN ranges are empty (no rows), as indicated by the newly introduced flag. --- contrib/pageinspect/brinfuncs.c | 10 ++++--- contrib/pageinspect/expected/brin.out | 6 ++-- .../pageinspect/pageinspect--1.11--1.12.sql | 17 +++++++++++ ...summarization-and-inprogress-insertion.out | 28 +++++++++---------- 4 files changed, 40 insertions(+), 21 deletions(-) diff --git a/contrib/pageinspect/brinfuncs.c b/contrib/pageinspect/brinfuncs.c index 000dcd8f5d8..a781f265514 100644 --- a/contrib/pageinspect/brinfuncs.c +++ b/contrib/pageinspect/brinfuncs.c @@ -201,8 +201,8 @@ brin_page_items(PG_FUNCTION_ARGS) dtup = NULL; for (;;) { - Datum values[7]; - bool nulls[7] = {0}; + Datum values[8]; + bool nulls[8] = {0}; /* * This loop is called once for every attribute of every tuple in the @@ -239,6 +239,7 @@ brin_page_items(PG_FUNCTION_ARGS) nulls[4] = true; nulls[5] = true; nulls[6] = true; + nulls[7] = true; } else { @@ -261,6 +262,7 @@ brin_page_items(PG_FUNCTION_ARGS) values[3] = BoolGetDatum(dtup->bt_columns[att].bv_allnulls); values[4] = BoolGetDatum(dtup->bt_columns[att].bv_hasnulls); values[5] = BoolGetDatum(dtup->bt_placeholder); + values[6] = BoolGetDatum(dtup->bt_empty_range); if (!dtup->bt_columns[att].bv_allnulls) { BrinValues *bvalues = &dtup->bt_columns[att]; @@ -286,12 +288,12 @@ brin_page_items(PG_FUNCTION_ARGS) } appendStringInfoChar(&s, '}'); - values[6] = CStringGetTextDatum(s.data); + values[7] = CStringGetTextDatum(s.data); pfree(s.data); } else { - nulls[6] = true; + nulls[7] = true; } } diff --git a/contrib/pageinspect/expected/brin.out b/contrib/pageinspect/expected/brin.out index e12fbeb4774..098ddc202f4 100644 --- a/contrib/pageinspect/expected/brin.out +++ b/contrib/pageinspect/expected/brin.out @@ -43,9 +43,9 @@ SELECT * FROM brin_revmap_data(get_raw_page('test1_a_idx', 1)) LIMIT 5; SELECT * FROM brin_page_items(get_raw_page('test1_a_idx', 2), 'test1_a_idx') ORDER BY blknum, attnum LIMIT 5; - itemoffset | blknum | attnum | allnulls | hasnulls | placeholder | value -------------+--------+--------+----------+----------+-------------+---------- - 1 | 0 | 1 | f | f | f | {1 .. 1} + itemoffset | blknum | attnum | allnulls | hasnulls | placeholder | empty | value +------------+--------+--------+----------+----------+-------------+-------+---------- + 1 | 0 | 1 | f | f | f | f | {1 .. 1} (1 row) -- Mask DETAIL messages as these are not portable across architectures. diff --git a/contrib/pageinspect/pageinspect--1.11--1.12.sql b/contrib/pageinspect/pageinspect--1.11--1.12.sql index 70c3abccf57..a20d67a9e82 100644 --- a/contrib/pageinspect/pageinspect--1.11--1.12.sql +++ b/contrib/pageinspect/pageinspect--1.11--1.12.sql @@ -21,3 +21,20 @@ CREATE FUNCTION bt_multi_page_stats(IN relname text, IN blkno int8, IN blk_count RETURNS SETOF record AS 'MODULE_PATHNAME', 'bt_multi_page_stats' LANGUAGE C STRICT PARALLEL RESTRICTED; + +-- +-- add information about BRIN empty ranges +-- +DROP FUNCTION brin_page_items(IN page bytea, IN index_oid regclass); +CREATE FUNCTION brin_page_items(IN page bytea, IN index_oid regclass, + OUT itemoffset int, + OUT blknum int8, + OUT attnum int, + OUT allnulls bool, + OUT hasnulls bool, + OUT placeholder bool, + OUT empty bool, + OUT value text) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'brin_page_items' +LANGUAGE C STRICT PARALLEL RESTRICTED; diff --git a/src/test/modules/brin/expected/summarization-and-inprogress-insertion.out b/src/test/modules/brin/expected/summarization-and-inprogress-insertion.out index 584ac2602f7..201786c82c0 100644 --- a/src/test/modules/brin/expected/summarization-and-inprogress-insertion.out +++ b/src/test/modules/brin/expected/summarization-and-inprogress-insertion.out @@ -2,9 +2,9 @@ Parsed test spec with 2 sessions starting permutation: s2check s1b s2b s1i s2summ s1c s2c s2check step s2check: SELECT * FROM brin_page_items(get_raw_page('brinidx', 2), 'brinidx'::regclass); -itemoffset|blknum|attnum|allnulls|hasnulls|placeholder|value -----------+------+------+--------+--------+-----------+-------- - 1| 0| 1|f |t |f |{1 .. 1} +itemoffset|blknum|attnum|allnulls|hasnulls|placeholder|empty|value +----------+------+------+--------+--------+-----------+-----+-------- + 1| 0| 1|f |t |f |f |{1 .. 1} (1 row) step s1b: BEGIN ISOLATION LEVEL REPEATABLE READ; @@ -24,18 +24,18 @@ brin_summarize_new_values step s1c: COMMIT; step s2c: COMMIT; step s2check: SELECT * FROM brin_page_items(get_raw_page('brinidx', 2), 'brinidx'::regclass); -itemoffset|blknum|attnum|allnulls|hasnulls|placeholder|value -----------+------+------+--------+--------+-----------+----------- - 1| 0| 1|f |t |f |{1 .. 1} - 2| 1| 1|f |f |f |{1 .. 1000} +itemoffset|blknum|attnum|allnulls|hasnulls|placeholder|empty|value +----------+------+------+--------+--------+-----------+-----+----------- + 1| 0| 1|f |t |f |f |{1 .. 1} + 2| 1| 1|f |f |f |f |{1 .. 1000} (2 rows) starting permutation: s2check s1b s1i s2vacuum s1c s2check step s2check: SELECT * FROM brin_page_items(get_raw_page('brinidx', 2), 'brinidx'::regclass); -itemoffset|blknum|attnum|allnulls|hasnulls|placeholder|value -----------+------+------+--------+--------+-----------+-------- - 1| 0| 1|f |t |f |{1 .. 1} +itemoffset|blknum|attnum|allnulls|hasnulls|placeholder|empty|value +----------+------+------+--------+--------+-----------+-----+-------- + 1| 0| 1|f |t |f |f |{1 .. 1} (1 row) step s1b: BEGIN ISOLATION LEVEL REPEATABLE READ; @@ -43,9 +43,9 @@ step s1i: INSERT INTO brin_iso VALUES (1000); step s2vacuum: VACUUM brin_iso; step s1c: COMMIT; step s2check: SELECT * FROM brin_page_items(get_raw_page('brinidx', 2), 'brinidx'::regclass); -itemoffset|blknum|attnum|allnulls|hasnulls|placeholder|value -----------+------+------+--------+--------+-----------+----------- - 1| 0| 1|f |t |f |{1 .. 1} - 2| 1| 1|f |f |f |{1 .. 1000} +itemoffset|blknum|attnum|allnulls|hasnulls|placeholder|empty|value +----------+------+------+--------+--------+-----------+-----+----------- + 1| 0| 1|f |t |f |f |{1 .. 1} + 2| 1| 1|f |f |f |f |{1 .. 1000} (2 rows) -- 2.39.2 [text/x-patch] 0003-extra-tests.patch (25.1K, ../../[email protected]/4-0003-extra-tests.patch) download | inline diff: From 3906763f4cd896c5fad68248eb77fbd59299f162 Mon Sep 17 00:00:00 2001 From: Tomas Vondra <[email protected]> Date: Mon, 27 Mar 2023 22:50:46 +0200 Subject: [PATCH 3/3] extra tests --- contrib/pageinspect/Makefile | 2 +- contrib/pageinspect/expected/brin-fails.out | 152 +++++++++++++++ contrib/pageinspect/expected/brin2.out | 201 ++++++++++++++++++++ contrib/pageinspect/sql/brin-fails.sql | 86 +++++++++ contrib/pageinspect/sql/brin2.sql | 117 ++++++++++++ 5 files changed, 557 insertions(+), 1 deletion(-) create mode 100644 contrib/pageinspect/expected/brin-fails.out create mode 100644 contrib/pageinspect/expected/brin2.out create mode 100644 contrib/pageinspect/sql/brin-fails.sql create mode 100644 contrib/pageinspect/sql/brin2.sql diff --git a/contrib/pageinspect/Makefile b/contrib/pageinspect/Makefile index 95e030b3969..6a5795fdfd9 100644 --- a/contrib/pageinspect/Makefile +++ b/contrib/pageinspect/Makefile @@ -22,7 +22,7 @@ DATA = pageinspect--1.11--1.12.sql pageinspect--1.10--1.11.sql \ pageinspect--1.0--1.1.sql PGFILEDESC = "pageinspect - functions to inspect contents of database pages" -REGRESS = page btree brin gin gist hash checksum oldextversions +REGRESS = page btree brin brin2 gin gist hash checksum brin-fails oldextversions ifdef USE_PGXS PG_CONFIG = pg_config diff --git a/contrib/pageinspect/expected/brin-fails.out b/contrib/pageinspect/expected/brin-fails.out new file mode 100644 index 00000000000..08479894ec3 --- /dev/null +++ b/contrib/pageinspect/expected/brin-fails.out @@ -0,0 +1,152 @@ +create table t (a int); +-- works +drop index if exists t_a_idx; +NOTICE: index "t_a_idx" does not exist, skipping +truncate t; +insert into t values (null), (1); +create index on t using brin (a); +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + itemoffset | blknum | attnum | allnulls | hasnulls | placeholder | empty | value +------------+--------+--------+----------+----------+-------------+-------+---------- + 1 | 0 | 1 | f | t | f | f | {1 .. 1} +(1 row) + +-- works +drop index if exists t_a_idx; +truncate t; +insert into t values (1), (null); +create index on t using brin (a); +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + itemoffset | blknum | attnum | allnulls | hasnulls | placeholder | empty | value +------------+--------+--------+----------+----------+-------------+-------+---------- + 1 | 0 | 1 | f | t | f | f | {1 .. 1} +(1 row) + +-- fails +drop index if exists t_a_idx; +truncate t; +insert into t values (null); +create index on t using brin (a); +insert into t values (1); +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + itemoffset | blknum | attnum | allnulls | hasnulls | placeholder | empty | value +------------+--------+--------+----------+----------+-------------+-------+---------- + 1 | 0 | 1 | f | t | f | f | {1 .. 1} +(1 row) + +-- fails +drop index if exists t_a_idx; +truncate t; +create index on t using brin (a); +insert into t values (null); +insert into t values (1); +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + itemoffset | blknum | attnum | allnulls | hasnulls | placeholder | empty | value +------------+--------+--------+----------+----------+-------------+-------+---------- + 1 | 0 | 1 | f | t | f | f | {1 .. 1} +(1 row) + +-- works +drop index if exists t_a_idx; +truncate t; +create index on t using brin (a) with (pages_per_range=1); +insert into t select null from generate_series(1,291); -- fill first page +insert into t values (null), (1); +select brin_summarize_new_values('t_a_idx'); + brin_summarize_new_values +--------------------------- + 1 +(1 row) + +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + itemoffset | blknum | attnum | allnulls | hasnulls | placeholder | empty | value +------------+--------+--------+----------+----------+-------------+-------+---------- + 1 | 0 | 1 | t | f | f | f | + 2 | 1 | 1 | f | t | f | f | {1 .. 1} +(2 rows) + +-- fails +drop index if exists t_a_idx; +truncate t; +create index on t using brin (a) with (pages_per_range=1); +insert into t select null from generate_series(1,291); -- fill first page +insert into t values (null); +select brin_summarize_new_values('t_a_idx'); + brin_summarize_new_values +--------------------------- + 1 +(1 row) + +insert into t values (1); +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + itemoffset | blknum | attnum | allnulls | hasnulls | placeholder | empty | value +------------+--------+--------+----------+----------+-------------+-------+---------- + 1 | 0 | 1 | t | f | f | f | + 2 | 1 | 1 | f | t | f | f | {1 .. 1} +(2 rows) + +-- works +drop index if exists t_a_idx; +truncate t; +create index on t using brin (a) with (pages_per_range=1); +insert into t select null from generate_series(1,291); -- fill first page +insert into t values (null); +insert into t values (1); +select brin_summarize_new_values('t_a_idx'); + brin_summarize_new_values +--------------------------- + 1 +(1 row) + +insert into t values (1); +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + itemoffset | blknum | attnum | allnulls | hasnulls | placeholder | empty | value +------------+--------+--------+----------+----------+-------------+-------+---------- + 1 | 0 | 1 | t | f | f | f | + 2 | 1 | 1 | f | t | f | f | {1 .. 1} +(2 rows) + +-- fails +drop index if exists t_a_idx; +truncate t; +create index on t using brin (a) with (pages_per_range=1); +insert into t select null from generate_series(1,291); -- fill first page +insert into t values (null); +insert into t values (null); +select brin_summarize_new_values('t_a_idx'); + brin_summarize_new_values +--------------------------- + 1 +(1 row) + +insert into t values (1); +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + itemoffset | blknum | attnum | allnulls | hasnulls | placeholder | empty | value +------------+--------+--------+----------+----------+-------------+-------+---------- + 1 | 0 | 1 | t | f | f | f | + 2 | 1 | 1 | f | t | f | f | {1 .. 1} +(2 rows) + +-- fails +drop index if exists t_a_idx; +truncate t; +create index on t using brin (a) with (pages_per_range=1); +insert into t select null from generate_series(1,291); -- fill first page +insert into t values (null); +insert into t values (null); +select brin_summarize_new_values('t_a_idx'); + brin_summarize_new_values +--------------------------- + 1 +(1 row) + +insert into t values (null); +insert into t values (1); +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + itemoffset | blknum | attnum | allnulls | hasnulls | placeholder | empty | value +------------+--------+--------+----------+----------+-------------+-------+---------- + 1 | 0 | 1 | t | f | f | f | + 2 | 1 | 1 | f | t | f | f | {1 .. 1} +(2 rows) + +drop table t; diff --git a/contrib/pageinspect/expected/brin2.out b/contrib/pageinspect/expected/brin2.out new file mode 100644 index 00000000000..8406a792cd3 --- /dev/null +++ b/contrib/pageinspect/expected/brin2.out @@ -0,0 +1,201 @@ +create table t (a int); +-- +drop index if exists t_a_idx; +NOTICE: index "t_a_idx" does not exist, skipping +truncate t; +create index on t using brin (a); +-- empty range, all_nulls=true (default) +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + itemoffset | blknum | attnum | allnulls | hasnulls | placeholder | empty | value +------------+--------+--------+----------+----------+-------------+-------+------- + 1 | 0 | 1 | t | f | f | t | +(1 row) + +-- insert NULL value, range no longer empty, all_nulls=true +insert into t values (NULL); +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + itemoffset | blknum | attnum | allnulls | hasnulls | placeholder | empty | value +------------+--------+--------+----------+----------+-------------+-------+------- + 1 | 0 | 1 | t | f | f | f | +(1 row) + +-- another NULL value, still all_nulls=true +insert into t values (NULL); +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + itemoffset | blknum | attnum | allnulls | hasnulls | placeholder | empty | value +------------+--------+--------+----------+----------+-------------+-------+------- + 1 | 0 | 1 | t | f | f | f | +(1 row) + +-- not-NULL value, switches to has_nulls=true +insert into t values (1); +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + itemoffset | blknum | attnum | allnulls | hasnulls | placeholder | empty | value +------------+--------+--------+----------+----------+-------------+-------+---------- + 1 | 0 | 1 | f | t | f | f | {1 .. 1} +(1 row) + +-- reinsert the not-NULL value, stays the same +insert into t values (1); +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + itemoffset | blknum | attnum | allnulls | hasnulls | placeholder | empty | value +------------+--------+--------+----------+----------+-------------+-------+---------- + 1 | 0 | 1 | f | t | f | f | {1 .. 1} +(1 row) + +-- another not-NULL value, still has_nulls=true, range extends +insert into t values (2); +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + itemoffset | blknum | attnum | allnulls | hasnulls | placeholder | empty | value +------------+--------+--------+----------+----------+-------------+-------+---------- + 1 | 0 | 1 | f | t | f | f | {1 .. 2} +(1 row) + +-- another NULL value, still has_nulls=true +insert into t values (NULL); +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + itemoffset | blknum | attnum | allnulls | hasnulls | placeholder | empty | value +------------+--------+--------+----------+----------+-------------+-------+---------- + 1 | 0 | 1 | f | t | f | f | {1 .. 2} +(1 row) + +-- +drop index if exists t_a_idx; +truncate t; +create index on t using brin (a); +-- empty range, all_nulls=true (default) +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + itemoffset | blknum | attnum | allnulls | hasnulls | placeholder | empty | value +------------+--------+--------+----------+----------+-------------+-------+------- + 1 | 0 | 1 | t | f | f | t | +(1 row) + +-- insert non-NULL value, range no longer empty, all_nulls=false +insert into t values (1); +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + itemoffset | blknum | attnum | allnulls | hasnulls | placeholder | empty | value +------------+--------+--------+----------+----------+-------------+-------+---------- + 1 | 0 | 1 | f | f | f | f | {1 .. 1} +(1 row) + +-- re-insert non-NULL value, stays the same +insert into t values (1); +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + itemoffset | blknum | attnum | allnulls | hasnulls | placeholder | empty | value +------------+--------+--------+----------+----------+-------------+-------+---------- + 1 | 0 | 1 | f | f | f | f | {1 .. 1} +(1 row) + +-- insert NULL value, has_nulls=true +insert into t values (NULL); +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + itemoffset | blknum | attnum | allnulls | hasnulls | placeholder | empty | value +------------+--------+--------+----------+----------+-------------+-------+---------- + 1 | 0 | 1 | f | t | f | f | {1 .. 1} +(1 row) + +-- another not-NULL value, still has_nulls=true, range expands +insert into t values (2); +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + itemoffset | blknum | attnum | allnulls | hasnulls | placeholder | empty | value +------------+--------+--------+----------+----------+-------------+-------+---------- + 1 | 0 | 1 | f | t | f | f | {1 .. 2} +(1 row) + +-- another NULL value, still has_nulls=true +insert into t values (NULL); +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + itemoffset | blknum | attnum | allnulls | hasnulls | placeholder | empty | value +------------+--------+--------+----------+----------+-------------+-------+---------- + 1 | 0 | 1 | f | t | f | f | {1 .. 2} +(1 row) + +-- +drop index if exists t_a_idx; +truncate t; +insert into t values (1); -- start with one non-NULL value +create index on t using brin (a); +-- non-empty range, all_nulls/has_nulls=false +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + itemoffset | blknum | attnum | allnulls | hasnulls | placeholder | empty | value +------------+--------+--------+----------+----------+-------------+-------+---------- + 1 | 0 | 1 | f | f | f | f | {1 .. 1} +(1 row) + +-- re-insert the non-NULL value, stays the same +insert into t values (1); +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + itemoffset | blknum | attnum | allnulls | hasnulls | placeholder | empty | value +------------+--------+--------+----------+----------+-------------+-------+---------- + 1 | 0 | 1 | f | f | f | f | {1 .. 1} +(1 row) + +-- insert another non-NULL value, null flags stay the same +insert into t values (2); +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + itemoffset | blknum | attnum | allnulls | hasnulls | placeholder | empty | value +------------+--------+--------+----------+----------+-------------+-------+---------- + 1 | 0 | 1 | f | f | f | f | {1 .. 2} +(1 row) + +-- insert NULL value, has_nulls=true +insert into t values (null); +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + itemoffset | blknum | attnum | allnulls | hasnulls | placeholder | empty | value +------------+--------+--------+----------+----------+-------------+-------+---------- + 1 | 0 | 1 | f | t | f | f | {1 .. 2} +(1 row) + +-- +drop index if exists t_a_idx; +truncate t; +insert into t values (NULL); -- start with one non-NULL value +create index on t using brin (a); +-- non-empty range, all_nulls=true +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + itemoffset | blknum | attnum | allnulls | hasnulls | placeholder | empty | value +------------+--------+--------+----------+----------+-------------+-------+------- + 1 | 0 | 1 | t | f | f | f | +(1 row) + +-- re-insert NULL, stays the same +insert into t values (NULL); +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + itemoffset | blknum | attnum | allnulls | hasnulls | placeholder | empty | value +------------+--------+--------+----------+----------+-------------+-------+------- + 1 | 0 | 1 | t | f | f | f | +(1 row) + +-- insert a non-NULL value, switches to has_nulls=true +insert into t values (1); +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + itemoffset | blknum | attnum | allnulls | hasnulls | placeholder | empty | value +------------+--------+--------+----------+----------+-------------+-------+---------- + 1 | 0 | 1 | f | t | f | f | {1 .. 1} +(1 row) + +-- re-insert the non-NULL value, stays the same +insert into t values (1); +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + itemoffset | blknum | attnum | allnulls | hasnulls | placeholder | empty | value +------------+--------+--------+----------+----------+-------------+-------+---------- + 1 | 0 | 1 | f | t | f | f | {1 .. 1} +(1 row) + +-- insert another the non-NULL value, stays the same, range updated +insert into t values (2); +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + itemoffset | blknum | attnum | allnulls | hasnulls | placeholder | empty | value +------------+--------+--------+----------+----------+-------------+-------+---------- + 1 | 0 | 1 | f | t | f | f | {1 .. 2} +(1 row) + +-- insert NULL value, stays the same +insert into t values (NULL); +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + itemoffset | blknum | attnum | allnulls | hasnulls | placeholder | empty | value +------------+--------+--------+----------+----------+-------------+-------+---------- + 1 | 0 | 1 | f | t | f | f | {1 .. 2} +(1 row) + +drop table t; diff --git a/contrib/pageinspect/sql/brin-fails.sql b/contrib/pageinspect/sql/brin-fails.sql new file mode 100644 index 00000000000..e5b37fa6b12 --- /dev/null +++ b/contrib/pageinspect/sql/brin-fails.sql @@ -0,0 +1,86 @@ +create table t (a int); + +-- works +drop index if exists t_a_idx; +truncate t; +insert into t values (null), (1); +create index on t using brin (a); +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + +-- works +drop index if exists t_a_idx; +truncate t; +insert into t values (1), (null); +create index on t using brin (a); +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + +-- fails +drop index if exists t_a_idx; +truncate t; +insert into t values (null); +create index on t using brin (a); +insert into t values (1); +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + +-- fails +drop index if exists t_a_idx; +truncate t; +create index on t using brin (a); +insert into t values (null); +insert into t values (1); +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + +-- works +drop index if exists t_a_idx; +truncate t; +create index on t using brin (a) with (pages_per_range=1); +insert into t select null from generate_series(1,291); -- fill first page +insert into t values (null), (1); +select brin_summarize_new_values('t_a_idx'); +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + +-- fails +drop index if exists t_a_idx; +truncate t; +create index on t using brin (a) with (pages_per_range=1); +insert into t select null from generate_series(1,291); -- fill first page +insert into t values (null); +select brin_summarize_new_values('t_a_idx'); +insert into t values (1); +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + +-- works +drop index if exists t_a_idx; +truncate t; +create index on t using brin (a) with (pages_per_range=1); +insert into t select null from generate_series(1,291); -- fill first page +insert into t values (null); +insert into t values (1); +select brin_summarize_new_values('t_a_idx'); +insert into t values (1); +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + +-- fails +drop index if exists t_a_idx; +truncate t; +create index on t using brin (a) with (pages_per_range=1); +insert into t select null from generate_series(1,291); -- fill first page +insert into t values (null); +insert into t values (null); +select brin_summarize_new_values('t_a_idx'); +insert into t values (1); +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + +-- fails +drop index if exists t_a_idx; +truncate t; +create index on t using brin (a) with (pages_per_range=1); +insert into t select null from generate_series(1,291); -- fill first page +insert into t values (null); +insert into t values (null); +select brin_summarize_new_values('t_a_idx'); +insert into t values (null); +insert into t values (1); +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + +drop table t; diff --git a/contrib/pageinspect/sql/brin2.sql b/contrib/pageinspect/sql/brin2.sql new file mode 100644 index 00000000000..501252e7b8c --- /dev/null +++ b/contrib/pageinspect/sql/brin2.sql @@ -0,0 +1,117 @@ +create table t (a int); + +-- +drop index if exists t_a_idx; +truncate t; +create index on t using brin (a); + +-- empty range, all_nulls=true (default) +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + +-- insert NULL value, range no longer empty, all_nulls=true +insert into t values (NULL); +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + +-- another NULL value, still all_nulls=true +insert into t values (NULL); +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + +-- not-NULL value, switches to has_nulls=true +insert into t values (1); +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + +-- reinsert the not-NULL value, stays the same +insert into t values (1); +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + +-- another not-NULL value, still has_nulls=true, range extends +insert into t values (2); +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + +-- another NULL value, still has_nulls=true +insert into t values (NULL); +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + + +-- +drop index if exists t_a_idx; +truncate t; +create index on t using brin (a); + +-- empty range, all_nulls=true (default) +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + +-- insert non-NULL value, range no longer empty, all_nulls=false +insert into t values (1); +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + +-- re-insert non-NULL value, stays the same +insert into t values (1); +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + +-- insert NULL value, has_nulls=true +insert into t values (NULL); +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + +-- another not-NULL value, still has_nulls=true, range expands +insert into t values (2); +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + +-- another NULL value, still has_nulls=true +insert into t values (NULL); +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + + +-- +drop index if exists t_a_idx; +truncate t; +insert into t values (1); -- start with one non-NULL value +create index on t using brin (a); + +-- non-empty range, all_nulls/has_nulls=false +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + +-- re-insert the non-NULL value, stays the same +insert into t values (1); +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + +-- insert another non-NULL value, null flags stay the same +insert into t values (2); +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + +-- insert NULL value, has_nulls=true +insert into t values (null); +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + + +-- +drop index if exists t_a_idx; +truncate t; +insert into t values (NULL); -- start with one non-NULL value +create index on t using brin (a); + +-- non-empty range, all_nulls=true +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + +-- re-insert NULL, stays the same +insert into t values (NULL); +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + +-- insert a non-NULL value, switches to has_nulls=true +insert into t values (1); +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + +-- re-insert the non-NULL value, stays the same +insert into t values (1); +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + +-- insert another the non-NULL value, stays the same, range updated +insert into t values (2); +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + +-- insert NULL value, stays the same +insert into t values (NULL); +select * from brin_page_items(get_raw_page('t_a_idx', 2), 't_a_idx'::regclass); + + +drop table t; -- 2.39.2 [application/x-shellscript] stress-test.sh (5.9K, ../../[email protected]/5-stress-test.sh) download ^ permalink raw reply [nested|flat] 5+ messages in thread
* Re: Missing update of all_hasnulls in BRIN opclasses @ 2023-04-23 20:43 Tomas Vondra <[email protected]> parent: Tomas Vondra <[email protected]> 0 siblings, 1 reply; 5+ messages in thread From: Tomas Vondra @ 2023-04-23 20:43 UTC (permalink / raw) To: Alvaro Herrera <[email protected]>; +Cc: Justin Pryzby <[email protected]>; Matthias van de Meent <[email protected]>; [email protected] Hi, here's an updated version of the patch, including a backport version. I ended up making the code yet a bit closer to master by introducing add_values_to_range(). The current pre-14 code has the loop adding data to the BRIN tuple in two places, but with the "fixed" logic handling NULLs and the empty_range flag the amount of duplicated code got too high, so this seem reasonable. Both cases have a patch extending pageinspect to show the new flag, but obviously we should commit that only in master. In backbranches it's meant only to make testing easier. I plan to do a bit more testing, I'd welcome some feedback - it's a long-standing bug, and it'd be good to finally get this fixed. I don't think the patch can be made any simpler. regards -- Tomas Vondra EnterpriseDB: http://www.enterprisedb.com The Enterprise PostgreSQL Company Attachments: [text/x-patch] 0001-Fix-handling-of-NULLs-in-BRIN-indexes-20230423-11-13.patch (18.3K, ../../[email protected]/2-0001-Fix-handling-of-NULLs-in-BRIN-indexes-20230423-11-13.patch) download | inline diff: From 42603e32bd0ad456a53a96ac2d05ce714f97e1ba Mon Sep 17 00:00:00 2001 From: Tomas Vondra <[email protected]> Date: Sun, 23 Apr 2023 19:26:18 +0200 Subject: [PATCH 1/2] Fix handling of NULLs in BRIN indexes BRIN indexes did not properly distinguish between summaries for empty (no rows) and all-NULL ranges. All summaries were initialized with allnulls=true, and the opclasses simply reset allnulls to false when processing the first non-NULL value. This however fails if the range starts with a NULL value (or a sequence of NULL values), in which case we forget the range contains NULL values. This happens because the allnulls flag is used for two separate purposes - to mark empty ranges (not representing any rows yet) and ranges containing only NULL values. Opclasses don't know which of these cases it is, and so don't know whether to set hasnulls=true. Setting hasnulls=true in both cases would make it correct, but it would also make BRIN indexes useless for queries with IS NULL clauses - all ranges start empty (and thus allnulls=true), so all ranges would end up with either allnulls=true or hasnulls=true. The severity of the issue is somewhat reduced by the fact that it only happens when adding values to an existing summary with allnulls=true, not when the summarization is processing values in bulk (e.g. during CREATE INDEX or automatic summarization). In this case the flags were updated in a slightly different way, not forgetting the NULL values. The best solution would be to introduce a new flag marking index tuples representing ranges with no rows, but that would break on-disk format and/or ABI, depending on where we put the flag. Considering we need to backpatch this, that's not acceptable. So instead we use an "impossible" combination of both flags (allnulls and hasnulls) set to true, to mark "empty" ranges with no rows. In principle "empty" is a feature of the whole index tuple, which may contain multiple summaries in a multi-column index, but this is where the flags are, unfortunately. We could also skip storing index tuples for empty summaries, but then we'd have to always process such ranges - even if there are no rows in large parts of the table (e.g. after a bulk DELETE), it would still require reading the pages etc. So we store them, but ignore them when building the bitmap. Backpatch to 11. The issue exists since BRIN indexes were introduced in 9.5, but older releases are already EOL. Backpatch-through: 11 Reviewed-by: Justin Pryzby, Matthias van de Meent Discussion: https://postgr.es/m/[email protected] --- src/backend/access/brin/brin.c | 226 ++++++++++++++---- src/backend/access/brin/brin_tuple.c | 15 +- src/include/access/brin_tuple.h | 6 +- ...summarization-and-inprogress-insertion.out | 8 +- ...ummarization-and-inprogress-insertion.spec | 1 + 5 files changed, 201 insertions(+), 55 deletions(-) diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c index 0becfde1133..f99a9ba5cf9 100644 --- a/src/backend/access/brin/brin.c +++ b/src/backend/access/brin/brin.c @@ -35,6 +35,7 @@ #include "storage/freespace.h" #include "utils/acl.h" #include "utils/builtins.h" +#include "utils/datum.h" #include "utils/index_selfuncs.h" #include "utils/memutils.h" #include "utils/rel.h" @@ -77,7 +78,8 @@ static void form_and_insert_tuple(BrinBuildState *state); static void union_tuples(BrinDesc *bdesc, BrinMemTuple *a, BrinTuple *b); static void brin_vacuum_scan(Relation idxrel, BufferAccessStrategy strategy); - +static bool add_values_to_range(Relation idxRel, BrinDesc *bdesc, + BrinMemTuple *dtup, Datum *values, bool *nulls); /* * BRIN handler function: return IndexAmRoutine with access method parameters @@ -173,11 +175,10 @@ brininsert(Relation idxRel, Datum *values, bool *nulls, for (;;) { - bool need_insert = false; + bool need_insert; OffsetNumber off; BrinTuple *brtup; BrinMemTuple *dtup; - int keyno; CHECK_FOR_INTERRUPTS(); @@ -241,31 +242,7 @@ brininsert(Relation idxRel, Datum *values, bool *nulls, dtup = brin_deform_tuple(bdesc, brtup, NULL); - /* - * Compare the key values of the new tuple to the stored index values; - * our deformed tuple will get updated if the new tuple doesn't fit - * the original range (note this means we can't break out of the loop - * early). Make a note of whether this happens, so that we know to - * insert the modified tuple later. - */ - for (keyno = 0; keyno < bdesc->bd_tupdesc->natts; keyno++) - { - Datum result; - BrinValues *bval; - FmgrInfo *addValue; - - bval = &dtup->bt_columns[keyno]; - addValue = index_getprocinfo(idxRel, keyno + 1, - BRIN_PROCNUM_ADDVALUE); - result = FunctionCall4Coll(addValue, - idxRel->rd_indcollation[keyno], - PointerGetDatum(bdesc), - PointerGetDatum(bval), - values[keyno], - nulls[keyno]); - /* if that returned true, we need to insert the updated tuple */ - need_insert |= DatumGetBool(result); - } + need_insert = add_values_to_range(idxRel, bdesc, dtup, values, nulls); if (!need_insert) { @@ -508,6 +485,17 @@ bringetbitmap(IndexScanDesc scan, TIDBitmap *tbm) CurrentMemoryContext); } + /* + * If the BRIN tuple indicates that this range is empty, + * we can skip it: there's nothing to match. We don't + * need to examine the next columns. + */ + if (dtup->bt_empty_range) + { + addrange = false; + break; + } + /* * Check whether the scan key is consistent with the page * range values; if so, have the pages in the range added @@ -611,7 +599,6 @@ brinbuildCallback(Relation index, { BrinBuildState *state = (BrinBuildState *) brstate; BlockNumber thisblock; - int i; thisblock = ItemPointerGetBlockNumber(tid); @@ -640,25 +627,8 @@ brinbuildCallback(Relation index, } /* Accumulate the current tuple into the running state */ - for (i = 0; i < state->bs_bdesc->bd_tupdesc->natts; i++) - { - FmgrInfo *addValue; - BrinValues *col; - Form_pg_attribute attr = TupleDescAttr(state->bs_bdesc->bd_tupdesc, i); - - col = &state->bs_dtuple->bt_columns[i]; - addValue = index_getprocinfo(index, i + 1, - BRIN_PROCNUM_ADDVALUE); - - /* - * Update dtuple state, if and as necessary. - */ - FunctionCall4Coll(addValue, - attr->attcollation, - PointerGetDatum(state->bs_bdesc), - PointerGetDatum(col), - values[i], isnull[i]); - } + (void) add_values_to_range(index, state->bs_bdesc, state->bs_dtuple, + values, isnull); } /* @@ -1448,6 +1418,8 @@ form_and_insert_tuple(BrinBuildState *state) /* * Given two deformed tuples, adjust the first one so that it's consistent * with the summary values in both. + * + * XXX I'm not sure we can actually get empty "b". */ static void union_tuples(BrinDesc *bdesc, BrinMemTuple *a, BrinTuple *b) @@ -1465,6 +1437,64 @@ union_tuples(BrinDesc *bdesc, BrinMemTuple *a, BrinTuple *b) db = brin_deform_tuple(bdesc, b, NULL); MemoryContextSwitchTo(oldcxt); + /* + * Check if the ranges are empty. + * + * If at least one of them is empty, we don't need to call per-key union + * functions at all. If "b" is empty, we just use "a" as the result (it + * might be empty fine, but that's fine). If "a" is empty but "b" is not, + * we use "b" as the result (but we have to copy the data into "a" first). + * + * Only when both ranges are non-empty, we actually do the per-key merge. + */ + + /* If "b" is empty - ignore it and just use "a" (even if it's empty etc.). */ + if (db->bt_empty_range) + { + /* skip the per-key merge */ + MemoryContextDelete(cxt); + return; + } + + /* + * Now we know "b" is not empty. If "a" is empty, then "b" is the result. + * But we need to copy the data from "b" to "a" first, because that's how + * we pass result out. + * + * We have to copy all the global/per-key flags etc. too. + */ + if (a->bt_empty_range) + { + for (keyno = 0; keyno < bdesc->bd_tupdesc->natts; keyno++) + { + int i; + BrinValues *col_a = &a->bt_columns[keyno]; + BrinValues *col_b = &db->bt_columns[keyno]; + BrinOpcInfo *opcinfo = bdesc->bd_info[keyno]; + + col_a->bv_allnulls = col_b->bv_allnulls; + col_a->bv_hasnulls = col_b->bv_hasnulls; + + /* If "b" has no data, we're done. */ + if (col_b->bv_allnulls) + continue; + + for (i = 0; i < opcinfo->oi_nstored; i++) + col_a->bv_values[i] = + datumCopy(col_b->bv_values[i], + opcinfo->oi_typcache[i]->typbyval, + opcinfo->oi_typcache[i]->typlen); + } + + /* "a" started empty, but "b" was not empty, so remember that */ + a->bt_empty_range = false; + + /* skip the per-key merge */ + MemoryContextDelete(cxt); + return; + } + + /* Now we know neither range is empty. */ for (keyno = 0; keyno < bdesc->bd_tupdesc->natts; keyno++) { FmgrInfo *unionFn; @@ -1523,3 +1553,103 @@ brin_vacuum_scan(Relation idxrel, BufferAccessStrategy strategy) */ FreeSpaceMapVacuum(idxrel); } + +static bool +add_values_to_range(Relation idxRel, BrinDesc *bdesc, BrinMemTuple *dtup, + Datum *values, bool *nulls) +{ + int keyno; + + /* If the range starts empty, we're certainly going to modify it. */ + bool modified = dtup->bt_empty_range; + + /* + * Compare the key values of the new tuple to the stored index values; + * our deformed tuple will get updated if the new tuple doesn't fit + * the original range (note this means we can't break out of the loop + * early). Make a note of whether this happens, so that we know to + * insert the modified tuple later. + */ + for (keyno = 0; keyno < bdesc->bd_tupdesc->natts; keyno++) + { + Datum result; + BrinValues *bval; + FmgrInfo *addValue; + bool has_nulls; + + bval = &dtup->bt_columns[keyno]; + + /* + * Does the range have actual NULL values? Either of the flags can + * be set, but we ignore the state before adding first row. + * + * We have to remember this, because we'll modify the flags and we + * need to know if the range started as empty. + */ + has_nulls = ((!dtup->bt_empty_range) && + (bval->bv_hasnulls || bval->bv_allnulls)); + + /* + * If the value we're adding is NULL, handle it locally. Otherwise + * call the BRIN_PROCNUM_ADDVALUE procedure. + */ + if (nulls[keyno]) + { + /* + * We can't check "bv_hasnulls" because then we might end up with + * both flags set to true, which is interpreted as empty range. + * But that'd be wrong, because we've just added a value. + * + * So either the range has allnulls=true, or we have to set the + * hasnulls flag. Check if we're changing the value to determine + * if the index tuple was modified. + */ + if (!bval->bv_allnulls) + { + /* Are we changing the tuple? */ + modified |= (!bval->bv_hasnulls); + bval->bv_hasnulls = true; + } + } + else + { + addValue = index_getprocinfo(idxRel, keyno + 1, + BRIN_PROCNUM_ADDVALUE); + result = FunctionCall4Coll(addValue, + idxRel->rd_indcollation[keyno], + PointerGetDatum(bdesc), + PointerGetDatum(bval), + values[keyno], + nulls[keyno]); + /* if that returned true, we need to insert the updated tuple */ + modified |= DatumGetBool(result); + } + + /* + * If the range was had actual NULL values (i.e. did not start empty), + * make sure we don't forget about the NULL values. Either the allnulls + * flag is still set to true, or (if the opclass cleared it) we need to + * set hasnulls=true. + * + * XXX This can only happen when the opclass modified the tuple, so the + * modified flag should be set. + */ + if (has_nulls && !(bval->bv_hasnulls || bval->bv_allnulls)) + { + Assert(modified); + bval->bv_hasnulls = true; + } + } + + /* + * After updating summaries for all the keys, mark it as not empty. + * + * If we're actually changing the flag value (i.e. tuple started as + * empty), we should have modified the tuple. So we should not see + * empty range that was not modified. + */ + Assert(!dtup->bt_empty_range || modified); + dtup->bt_empty_range = false; + + return modified; +} diff --git a/src/backend/access/brin/brin_tuple.c b/src/backend/access/brin/brin_tuple.c index b3b453aed12..f9877980f4d 100644 --- a/src/backend/access/brin/brin_tuple.c +++ b/src/backend/access/brin/brin_tuple.c @@ -349,6 +349,9 @@ brin_form_tuple(BrinDesc *brdesc, BlockNumber blkno, BrinMemTuple *tuple, if (tuple->bt_placeholder) rettuple->bt_info |= BRIN_PLACEHOLDER_MASK; + if (tuple->bt_empty_range) + rettuple->bt_info |= BRIN_EMPTY_RANGE_MASK; + *size = len; return rettuple; } @@ -376,7 +379,7 @@ brin_form_placeholder_tuple(BrinDesc *brdesc, BlockNumber blkno, Size *size) rettuple = palloc0(len); rettuple->bt_blkno = blkno; rettuple->bt_info = hoff; - rettuple->bt_info |= BRIN_NULLS_MASK | BRIN_PLACEHOLDER_MASK; + rettuple->bt_info |= BRIN_NULLS_MASK | BRIN_PLACEHOLDER_MASK | BRIN_EMPTY_RANGE_MASK; bitP = ((bits8 *) ((char *) rettuple + SizeOfBrinTuple)) - 1; bitmask = HIGHBIT; @@ -466,6 +469,8 @@ brin_new_memtuple(BrinDesc *brdesc) dtup->bt_allnulls = palloc(sizeof(bool) * brdesc->bd_tupdesc->natts); dtup->bt_hasnulls = palloc(sizeof(bool) * brdesc->bd_tupdesc->natts); + dtup->bt_empty_range = true; + dtup->bt_context = AllocSetContextCreate(CurrentMemoryContext, "brin dtuple", ALLOCSET_DEFAULT_SIZES); @@ -499,6 +504,9 @@ brin_memtuple_initialize(BrinMemTuple *dtuple, BrinDesc *brdesc) currdatum += sizeof(Datum) * brdesc->bd_info[i]->oi_nstored; } + /* FIXME Shouldn't this reset bt_placeholder too? */ + dtuple->bt_empty_range = true; + return dtuple; } @@ -532,6 +540,11 @@ brin_deform_tuple(BrinDesc *brdesc, BrinTuple *tuple, BrinMemTuple *dMemtuple) if (BrinTupleIsPlaceholder(tuple)) dtup->bt_placeholder = true; + + /* ranges start as empty, depends on the BrinTuple */ + if (!BrinTupleIsEmptyRange(tuple)) + dtup->bt_empty_range = false; + dtup->bt_blkno = tuple->bt_blkno; values = dtup->bt_values; diff --git a/src/include/access/brin_tuple.h b/src/include/access/brin_tuple.h index a9ccc3995b4..5280872707a 100644 --- a/src/include/access/brin_tuple.h +++ b/src/include/access/brin_tuple.h @@ -36,6 +36,7 @@ typedef struct BrinValues typedef struct BrinMemTuple { bool bt_placeholder; /* this is a placeholder tuple */ + bool bt_empty_range; /* range represents no tuples */ BlockNumber bt_blkno; /* heap blkno that the tuple is for */ MemoryContext bt_context; /* memcxt holding the bt_columns values */ /* output arrays for brin_deform_tuple: */ @@ -61,7 +62,7 @@ typedef struct BrinTuple * * 7th (high) bit: has nulls * 6th bit: is placeholder tuple - * 5th bit: unused + * 5th bit: range is empty * 4-0 bit: offset of data * --------------- */ @@ -74,13 +75,14 @@ typedef struct BrinTuple * bt_info manipulation macros */ #define BRIN_OFFSET_MASK 0x1F -/* bit 0x20 is not used at present */ +#define BRIN_EMPTY_RANGE_MASK 0x20 #define BRIN_PLACEHOLDER_MASK 0x40 #define BRIN_NULLS_MASK 0x80 #define BrinTupleDataOffset(tup) ((Size) (((BrinTuple *) (tup))->bt_info & BRIN_OFFSET_MASK)) #define BrinTupleHasNulls(tup) (((((BrinTuple *) (tup))->bt_info & BRIN_NULLS_MASK)) != 0) #define BrinTupleIsPlaceholder(tup) (((((BrinTuple *) (tup))->bt_info & BRIN_PLACEHOLDER_MASK)) != 0) +#define BrinTupleIsEmptyRange(tup) (((((BrinTuple *) (tup))->bt_info & BRIN_EMPTY_RANGE_MASK)) != 0) extern BrinTuple *brin_form_tuple(BrinDesc *brdesc, BlockNumber blkno, diff --git a/src/test/modules/brin/expected/summarization-and-inprogress-insertion.out b/src/test/modules/brin/expected/summarization-and-inprogress-insertion.out index 2a4755d0998..584ac2602f7 100644 --- a/src/test/modules/brin/expected/summarization-and-inprogress-insertion.out +++ b/src/test/modules/brin/expected/summarization-and-inprogress-insertion.out @@ -4,7 +4,7 @@ starting permutation: s2check s1b s2b s1i s2summ s1c s2c s2check step s2check: SELECT * FROM brin_page_items(get_raw_page('brinidx', 2), 'brinidx'::regclass); itemoffset|blknum|attnum|allnulls|hasnulls|placeholder|value ----------+------+------+--------+--------+-----------+-------- - 1| 0| 1|f |f |f |{1 .. 1} + 1| 0| 1|f |t |f |{1 .. 1} (1 row) step s1b: BEGIN ISOLATION LEVEL REPEATABLE READ; @@ -26,7 +26,7 @@ step s2c: COMMIT; step s2check: SELECT * FROM brin_page_items(get_raw_page('brinidx', 2), 'brinidx'::regclass); itemoffset|blknum|attnum|allnulls|hasnulls|placeholder|value ----------+------+------+--------+--------+-----------+----------- - 1| 0| 1|f |f |f |{1 .. 1} + 1| 0| 1|f |t |f |{1 .. 1} 2| 1| 1|f |f |f |{1 .. 1000} (2 rows) @@ -35,7 +35,7 @@ starting permutation: s2check s1b s1i s2vacuum s1c s2check step s2check: SELECT * FROM brin_page_items(get_raw_page('brinidx', 2), 'brinidx'::regclass); itemoffset|blknum|attnum|allnulls|hasnulls|placeholder|value ----------+------+------+--------+--------+-----------+-------- - 1| 0| 1|f |f |f |{1 .. 1} + 1| 0| 1|f |t |f |{1 .. 1} (1 row) step s1b: BEGIN ISOLATION LEVEL REPEATABLE READ; @@ -45,7 +45,7 @@ step s1c: COMMIT; step s2check: SELECT * FROM brin_page_items(get_raw_page('brinidx', 2), 'brinidx'::regclass); itemoffset|blknum|attnum|allnulls|hasnulls|placeholder|value ----------+------+------+--------+--------+-----------+----------- - 1| 0| 1|f |f |f |{1 .. 1} + 1| 0| 1|f |t |f |{1 .. 1} 2| 1| 1|f |f |f |{1 .. 1000} (2 rows) diff --git a/src/test/modules/brin/specs/summarization-and-inprogress-insertion.spec b/src/test/modules/brin/specs/summarization-and-inprogress-insertion.spec index 19ac18a2e88..18ba92b7ba1 100644 --- a/src/test/modules/brin/specs/summarization-and-inprogress-insertion.spec +++ b/src/test/modules/brin/specs/summarization-and-inprogress-insertion.spec @@ -9,6 +9,7 @@ setup ) WITH (fillfactor=10); CREATE INDEX brinidx ON brin_iso USING brin (value) WITH (pages_per_range=1); -- this fills the first page + INSERT INTO brin_iso VALUES (NULL); DO $$ DECLARE curtid tid; BEGIN -- 2.40.0 [text/x-patch] 0002-pageinspect-tweak-20230423-11-13.patch (2.5K, ../../[email protected]/3-0002-pageinspect-tweak-20230423-11-13.patch) download | inline diff: From 157923fb3d55095d8d45fb1e7fe56a992d667b32 Mon Sep 17 00:00:00 2001 From: Tomas Vondra <[email protected]> Date: Sun, 23 Apr 2023 22:19:05 +0200 Subject: [PATCH 2/2] pageinspect tweak --- contrib/pageinspect/brinfuncs.c | 10 ++++++---- contrib/pageinspect/pageinspect--1.7--1.8.sql | 20 +++++++++++++++++++ 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/contrib/pageinspect/brinfuncs.c b/contrib/pageinspect/brinfuncs.c index 04a90c4782d..8db6f1bcc7d 100644 --- a/contrib/pageinspect/brinfuncs.c +++ b/contrib/pageinspect/brinfuncs.c @@ -227,8 +227,8 @@ brin_page_items(PG_FUNCTION_ARGS) dtup = NULL; for (;;) { - Datum values[7]; - bool nulls[7]; + Datum values[8]; + bool nulls[8]; /* * This loop is called once for every attribute of every tuple in the @@ -267,6 +267,7 @@ brin_page_items(PG_FUNCTION_ARGS) nulls[4] = true; nulls[5] = true; nulls[6] = true; + nulls[7] = true; } else { @@ -278,6 +279,7 @@ brin_page_items(PG_FUNCTION_ARGS) values[3] = BoolGetDatum(dtup->bt_columns[att].bv_allnulls); values[4] = BoolGetDatum(dtup->bt_columns[att].bv_hasnulls); values[5] = BoolGetDatum(dtup->bt_placeholder); + values[6] = BoolGetDatum(dtup->bt_empty_range); if (!dtup->bt_columns[att].bv_allnulls) { BrinValues *bvalues = &dtup->bt_columns[att]; @@ -303,12 +305,12 @@ brin_page_items(PG_FUNCTION_ARGS) } appendStringInfoChar(&s, '}'); - values[6] = CStringGetTextDatum(s.data); + values[7] = CStringGetTextDatum(s.data); pfree(s.data); } else { - nulls[6] = true; + nulls[7] = true; } } diff --git a/contrib/pageinspect/pageinspect--1.7--1.8.sql b/contrib/pageinspect/pageinspect--1.7--1.8.sql index 45031a026a6..edfb580a4ec 100644 --- a/contrib/pageinspect/pageinspect--1.7--1.8.sql +++ b/contrib/pageinspect/pageinspect--1.7--1.8.sql @@ -67,3 +67,23 @@ CREATE FUNCTION bt_page_items(IN page bytea, RETURNS SETOF record AS 'MODULE_PATHNAME', 'bt_page_items_bytea' LANGUAGE C STRICT PARALLEL SAFE; + +-- +-- add information about BRIN empty ranges +-- +DROP FUNCTION brin_page_items(IN page bytea, IN index_oid regclass); +-- +-- brin_page_items() +-- +CREATE FUNCTION brin_page_items(IN page bytea, IN index_oid regclass, + OUT itemoffset int, + OUT blknum int, + OUT attnum int, + OUT allnulls bool, + OUT hasnulls bool, + OUT placeholder bool, + OUT empty bool, + OUT value text) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'brin_page_items' +LANGUAGE C STRICT PARALLEL SAFE; -- 2.40.0 [text/x-patch] 0001-Fix-handling-of-NULLs-in-BRIN-indexe-20230423-master.patch (13.8K, ../../[email protected]/4-0001-Fix-handling-of-NULLs-in-BRIN-indexe-20230423-master.patch) download | inline diff: From 75ca742082cd50cef4609ecf0021e7bf69f34de6 Mon Sep 17 00:00:00 2001 From: Tomas Vondra <[email protected]> Date: Sun, 8 Jan 2023 16:43:06 +0100 Subject: [PATCH 1/2] Fix handling of NULLs in BRIN indexes BRIN indexes did not properly distinguish between summaries for empty (no rows) and all-NULL ranges. All summaries were initialized with allnulls=true, and the opclasses simply reset allnulls to false when processing the first non-NULL value. This however fails if the range starts with a NULL value (or a sequence of NULL values), in which case we forget the range contains NULL values. This happens because the allnulls flag is used for two separate purposes - to mark empty ranges (not representing any rows yet) and ranges containing only NULL values. Opclasses don't know which of these cases it is, and so don't know whether to set hasnulls=true. Setting hasnulls=true in both cases would make it correct, but it would also make BRIN indexes useless for queries with IS NULL clauses - all ranges start empty (and thus allnulls=true), so all ranges would end up with either allnulls=true or hasnulls=true. The severity of the issue is somewhat reduced by the fact that it only happens when adding values to an existing summary with allnulls=true, not when the summarization is processing values in bulk (e.g. during CREATE INDEX or automatic summarization). In this case the flags were updated in a slightly different way, not forgetting the NULL values. This introduces a new a new flag marking index tuples representing ranges with no rows. Luckily we have an unused tuple in the BRIN tuple header that we can use for this. We still store index tuples for empty ranges, because otherwise we'd not be able to say whether a range is empty or not yet summarized, and we'd have to process them for any query. Backpatch to 11. The issue exists since BRIN indexes were introduced in 9.5, but older releases are already EOL. Backpatch-through: 11 Reviewed-by: Alvaro Herrera, Justin Pryzby, Matthias van de Meent Discussion: https://postgr.es/m/[email protected] --- src/backend/access/brin/brin.c | 115 +++++++++++++++++- src/backend/access/brin/brin_tuple.c | 15 ++- src/include/access/brin_tuple.h | 6 +- ...summarization-and-inprogress-insertion.out | 8 +- ...ummarization-and-inprogress-insertion.spec | 1 + 5 files changed, 137 insertions(+), 8 deletions(-) diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c index 41bf950a4af..2e20f318e95 100644 --- a/src/backend/access/brin/brin.c +++ b/src/backend/access/brin/brin.c @@ -592,6 +592,17 @@ bringetbitmap(IndexScanDesc scan, TIDBitmap *tbm) bval = &dtup->bt_columns[attno - 1]; + /* + * If the BRIN tuple indicates that this range is empty, + * we can skip it: there's nothing to match. We don't + * need to examine the next columns. + */ + if (dtup->bt_empty_range) + { + addrange = false; + break; + } + /* * First check if there are any IS [NOT] NULL scan keys, * and if we're violating them. In that case we can @@ -1589,6 +1600,8 @@ form_and_insert_tuple(BrinBuildState *state) /* * Given two deformed tuples, adjust the first one so that it's consistent * with the summary values in both. + * + * XXX I'm not sure we can actually get empty "b". */ static void union_tuples(BrinDesc *bdesc, BrinMemTuple *a, BrinTuple *b) @@ -1606,6 +1619,64 @@ union_tuples(BrinDesc *bdesc, BrinMemTuple *a, BrinTuple *b) db = brin_deform_tuple(bdesc, b, NULL); MemoryContextSwitchTo(oldcxt); + /* + * Check if the ranges are empty. + * + * If at least one of them is empty, we don't need to call per-key union + * functions at all. If "b" is empty, we just use "a" as the result (it + * might be empty fine, but that's fine). If "a" is empty but "b" is not, + * we use "b" as the result (but we have to copy the data into "a" first). + * + * Only when both ranges are non-empty, we actually do the per-key merge. + */ + + /* If "b" is empty - ignore it and just use "a" (even if it's empty etc.). */ + if (db->bt_empty_range) + { + /* skip the per-key merge */ + MemoryContextDelete(cxt); + return; + } + + /* + * Now we know "b" is not empty. If "a" is empty, then "b" is the result. + * But we need to copy the data from "b" to "a" first, because that's how + * we pass result out. + * + * We have to copy all the global/per-key flags etc. too. + */ + if (a->bt_empty_range) + { + for (keyno = 0; keyno < bdesc->bd_tupdesc->natts; keyno++) + { + int i; + BrinValues *col_a = &a->bt_columns[keyno]; + BrinValues *col_b = &db->bt_columns[keyno]; + BrinOpcInfo *opcinfo = bdesc->bd_info[keyno]; + + col_a->bv_allnulls = col_b->bv_allnulls; + col_a->bv_hasnulls = col_b->bv_hasnulls; + + /* If "b" has no data, we're done. */ + if (col_b->bv_allnulls) + continue; + + for (i = 0; i < opcinfo->oi_nstored; i++) + col_a->bv_values[i] = + datumCopy(col_b->bv_values[i], + opcinfo->oi_typcache[i]->typbyval, + opcinfo->oi_typcache[i]->typlen); + } + + /* "a" started empty, but "b" was not empty, so remember that */ + a->bt_empty_range = false; + + /* skip the per-key merge */ + MemoryContextDelete(cxt); + return; + } + + /* Now we know neither range is empty. */ for (keyno = 0; keyno < bdesc->bd_tupdesc->natts; keyno++) { FmgrInfo *unionFn; @@ -1703,7 +1774,9 @@ add_values_to_range(Relation idxRel, BrinDesc *bdesc, BrinMemTuple *dtup, Datum *values, bool *nulls) { int keyno; - bool modified = false; + + /* If the range starts empty, we're certainly going to modify it. */ + bool modified = dtup->bt_empty_range; /* * Compare the key values of the new tuple to the stored index values; our @@ -1717,9 +1790,24 @@ add_values_to_range(Relation idxRel, BrinDesc *bdesc, BrinMemTuple *dtup, Datum result; BrinValues *bval; FmgrInfo *addValue; + bool has_nulls; bval = &dtup->bt_columns[keyno]; + /* + * Does the range have actual NULL values? Either of the flags can + * be set, but we ignore the state before adding first row. + * + * We have to remember this, because we'll modify the flags and we + * need to know if the range started as empty. + */ + has_nulls = ((!dtup->bt_empty_range) && + (bval->bv_hasnulls || bval->bv_allnulls)); + + /* + * If the value we're adding is NULL, handle it locally. Otherwise + * call the BRIN_PROCNUM_ADDVALUE procedure. + */ if (bdesc->bd_info[keyno]->oi_regular_nulls && nulls[keyno]) { /* @@ -1745,8 +1833,33 @@ add_values_to_range(Relation idxRel, BrinDesc *bdesc, BrinMemTuple *dtup, nulls[keyno]); /* if that returned true, we need to insert the updated tuple */ modified |= DatumGetBool(result); + + /* + * If the range was had actual NULL values (i.e. did not start empty), + * make sure we don't forget about the NULL values. Either the allnulls + * flag is still set to true, or (if the opclass cleared it) we need to + * set hasnulls=true. + * + * XXX This can only happen when the opclass modified the tuple, so the + * modified flag should be set. + */ + if (has_nulls && !(bval->bv_hasnulls || bval->bv_allnulls)) + { + Assert(modified); + bval->bv_hasnulls = true; + } } + /* + * After updating summaries for all the keys, mark it as not empty. + * + * If we're actually changing the flag value (i.e. tuple started as empty), + * we should have modified the tuple. So we should not see empty range that + * was not modified. + */ + Assert(!dtup->bt_empty_range || modified); + dtup->bt_empty_range = false; + return modified; } diff --git a/src/backend/access/brin/brin_tuple.c b/src/backend/access/brin/brin_tuple.c index 84b79dbfc0d..b81247a262c 100644 --- a/src/backend/access/brin/brin_tuple.c +++ b/src/backend/access/brin/brin_tuple.c @@ -372,6 +372,9 @@ brin_form_tuple(BrinDesc *brdesc, BlockNumber blkno, BrinMemTuple *tuple, if (tuple->bt_placeholder) rettuple->bt_info |= BRIN_PLACEHOLDER_MASK; + if (tuple->bt_empty_range) + rettuple->bt_info |= BRIN_EMPTY_RANGE_MASK; + *size = len; return rettuple; } @@ -399,7 +402,7 @@ brin_form_placeholder_tuple(BrinDesc *brdesc, BlockNumber blkno, Size *size) rettuple = palloc0(len); rettuple->bt_blkno = blkno; rettuple->bt_info = hoff; - rettuple->bt_info |= BRIN_NULLS_MASK | BRIN_PLACEHOLDER_MASK; + rettuple->bt_info |= BRIN_NULLS_MASK | BRIN_PLACEHOLDER_MASK | BRIN_EMPTY_RANGE_MASK; bitP = ((bits8 *) ((char *) rettuple + SizeOfBrinTuple)) - 1; bitmask = HIGHBIT; @@ -489,6 +492,8 @@ brin_new_memtuple(BrinDesc *brdesc) dtup->bt_allnulls = palloc(sizeof(bool) * brdesc->bd_tupdesc->natts); dtup->bt_hasnulls = palloc(sizeof(bool) * brdesc->bd_tupdesc->natts); + dtup->bt_empty_range = true; + dtup->bt_context = AllocSetContextCreate(CurrentMemoryContext, "brin dtuple", ALLOCSET_DEFAULT_SIZES); @@ -527,6 +532,9 @@ brin_memtuple_initialize(BrinMemTuple *dtuple, BrinDesc *brdesc) currdatum += sizeof(Datum) * brdesc->bd_info[i]->oi_nstored; } + /* FIXME Shouldn't this reset bt_placeholder too? */ + dtuple->bt_empty_range = true; + return dtuple; } @@ -560,6 +568,11 @@ brin_deform_tuple(BrinDesc *brdesc, BrinTuple *tuple, BrinMemTuple *dMemtuple) if (BrinTupleIsPlaceholder(tuple)) dtup->bt_placeholder = true; + + /* ranges start as empty, depends on the BrinTuple */ + if (!BrinTupleIsEmptyRange(tuple)) + dtup->bt_empty_range = false; + dtup->bt_blkno = tuple->bt_blkno; values = dtup->bt_values; diff --git a/src/include/access/brin_tuple.h b/src/include/access/brin_tuple.h index 732f91edf11..c56747aca4a 100644 --- a/src/include/access/brin_tuple.h +++ b/src/include/access/brin_tuple.h @@ -44,6 +44,7 @@ typedef struct BrinValues typedef struct BrinMemTuple { bool bt_placeholder; /* this is a placeholder tuple */ + bool bt_empty_range; /* range represents no tuples */ BlockNumber bt_blkno; /* heap blkno that the tuple is for */ MemoryContext bt_context; /* memcxt holding the bt_columns values */ /* output arrays for brin_deform_tuple: */ @@ -69,7 +70,7 @@ typedef struct BrinTuple * * 7th (high) bit: has nulls * 6th bit: is placeholder tuple - * 5th bit: unused + * 5th bit: range is empty * 4-0 bit: offset of data * --------------- */ @@ -82,13 +83,14 @@ typedef struct BrinTuple * bt_info manipulation macros */ #define BRIN_OFFSET_MASK 0x1F -/* bit 0x20 is not used at present */ +#define BRIN_EMPTY_RANGE_MASK 0x20 #define BRIN_PLACEHOLDER_MASK 0x40 #define BRIN_NULLS_MASK 0x80 #define BrinTupleDataOffset(tup) ((Size) (((BrinTuple *) (tup))->bt_info & BRIN_OFFSET_MASK)) #define BrinTupleHasNulls(tup) (((((BrinTuple *) (tup))->bt_info & BRIN_NULLS_MASK)) != 0) #define BrinTupleIsPlaceholder(tup) (((((BrinTuple *) (tup))->bt_info & BRIN_PLACEHOLDER_MASK)) != 0) +#define BrinTupleIsEmptyRange(tup) (((((BrinTuple *) (tup))->bt_info & BRIN_EMPTY_RANGE_MASK)) != 0) extern BrinTuple *brin_form_tuple(BrinDesc *brdesc, BlockNumber blkno, diff --git a/src/test/modules/brin/expected/summarization-and-inprogress-insertion.out b/src/test/modules/brin/expected/summarization-and-inprogress-insertion.out index 2a4755d0998..584ac2602f7 100644 --- a/src/test/modules/brin/expected/summarization-and-inprogress-insertion.out +++ b/src/test/modules/brin/expected/summarization-and-inprogress-insertion.out @@ -4,7 +4,7 @@ starting permutation: s2check s1b s2b s1i s2summ s1c s2c s2check step s2check: SELECT * FROM brin_page_items(get_raw_page('brinidx', 2), 'brinidx'::regclass); itemoffset|blknum|attnum|allnulls|hasnulls|placeholder|value ----------+------+------+--------+--------+-----------+-------- - 1| 0| 1|f |f |f |{1 .. 1} + 1| 0| 1|f |t |f |{1 .. 1} (1 row) step s1b: BEGIN ISOLATION LEVEL REPEATABLE READ; @@ -26,7 +26,7 @@ step s2c: COMMIT; step s2check: SELECT * FROM brin_page_items(get_raw_page('brinidx', 2), 'brinidx'::regclass); itemoffset|blknum|attnum|allnulls|hasnulls|placeholder|value ----------+------+------+--------+--------+-----------+----------- - 1| 0| 1|f |f |f |{1 .. 1} + 1| 0| 1|f |t |f |{1 .. 1} 2| 1| 1|f |f |f |{1 .. 1000} (2 rows) @@ -35,7 +35,7 @@ starting permutation: s2check s1b s1i s2vacuum s1c s2check step s2check: SELECT * FROM brin_page_items(get_raw_page('brinidx', 2), 'brinidx'::regclass); itemoffset|blknum|attnum|allnulls|hasnulls|placeholder|value ----------+------+------+--------+--------+-----------+-------- - 1| 0| 1|f |f |f |{1 .. 1} + 1| 0| 1|f |t |f |{1 .. 1} (1 row) step s1b: BEGIN ISOLATION LEVEL REPEATABLE READ; @@ -45,7 +45,7 @@ step s1c: COMMIT; step s2check: SELECT * FROM brin_page_items(get_raw_page('brinidx', 2), 'brinidx'::regclass); itemoffset|blknum|attnum|allnulls|hasnulls|placeholder|value ----------+------+------+--------+--------+-----------+----------- - 1| 0| 1|f |f |f |{1 .. 1} + 1| 0| 1|f |t |f |{1 .. 1} 2| 1| 1|f |f |f |{1 .. 1000} (2 rows) diff --git a/src/test/modules/brin/specs/summarization-and-inprogress-insertion.spec b/src/test/modules/brin/specs/summarization-and-inprogress-insertion.spec index 19ac18a2e88..18ba92b7ba1 100644 --- a/src/test/modules/brin/specs/summarization-and-inprogress-insertion.spec +++ b/src/test/modules/brin/specs/summarization-and-inprogress-insertion.spec @@ -9,6 +9,7 @@ setup ) WITH (fillfactor=10); CREATE INDEX brinidx ON brin_iso USING brin (value) WITH (pages_per_range=1); -- this fills the first page + INSERT INTO brin_iso VALUES (NULL); DO $$ DECLARE curtid tid; BEGIN -- 2.40.0 [text/x-patch] 0002-Show-empty-ranges-in-brin_page_items-20230423-master.patch (6.9K, ../../[email protected]/5-0002-Show-empty-ranges-in-brin_page_items-20230423-master.patch) download | inline diff: From da108ee9bcfd4b8a4ac8b9fba2cc8b6ab4f757f7 Mon Sep 17 00:00:00 2001 From: Tomas Vondra <[email protected]> Date: Mon, 27 Mar 2023 22:47:12 +0200 Subject: [PATCH 2/2] Show empty ranges in brin_page_items Show which BRIN ranges are empty (no rows), as indicated by the newly introduced flag. --- contrib/pageinspect/brinfuncs.c | 10 ++++--- contrib/pageinspect/expected/brin.out | 6 ++-- .../pageinspect/pageinspect--1.11--1.12.sql | 17 +++++++++++ ...summarization-and-inprogress-insertion.out | 28 +++++++++---------- 4 files changed, 40 insertions(+), 21 deletions(-) diff --git a/contrib/pageinspect/brinfuncs.c b/contrib/pageinspect/brinfuncs.c index 000dcd8f5d8..a781f265514 100644 --- a/contrib/pageinspect/brinfuncs.c +++ b/contrib/pageinspect/brinfuncs.c @@ -201,8 +201,8 @@ brin_page_items(PG_FUNCTION_ARGS) dtup = NULL; for (;;) { - Datum values[7]; - bool nulls[7] = {0}; + Datum values[8]; + bool nulls[8] = {0}; /* * This loop is called once for every attribute of every tuple in the @@ -239,6 +239,7 @@ brin_page_items(PG_FUNCTION_ARGS) nulls[4] = true; nulls[5] = true; nulls[6] = true; + nulls[7] = true; } else { @@ -261,6 +262,7 @@ brin_page_items(PG_FUNCTION_ARGS) values[3] = BoolGetDatum(dtup->bt_columns[att].bv_allnulls); values[4] = BoolGetDatum(dtup->bt_columns[att].bv_hasnulls); values[5] = BoolGetDatum(dtup->bt_placeholder); + values[6] = BoolGetDatum(dtup->bt_empty_range); if (!dtup->bt_columns[att].bv_allnulls) { BrinValues *bvalues = &dtup->bt_columns[att]; @@ -286,12 +288,12 @@ brin_page_items(PG_FUNCTION_ARGS) } appendStringInfoChar(&s, '}'); - values[6] = CStringGetTextDatum(s.data); + values[7] = CStringGetTextDatum(s.data); pfree(s.data); } else { - nulls[6] = true; + nulls[7] = true; } } diff --git a/contrib/pageinspect/expected/brin.out b/contrib/pageinspect/expected/brin.out index e12fbeb4774..098ddc202f4 100644 --- a/contrib/pageinspect/expected/brin.out +++ b/contrib/pageinspect/expected/brin.out @@ -43,9 +43,9 @@ SELECT * FROM brin_revmap_data(get_raw_page('test1_a_idx', 1)) LIMIT 5; SELECT * FROM brin_page_items(get_raw_page('test1_a_idx', 2), 'test1_a_idx') ORDER BY blknum, attnum LIMIT 5; - itemoffset | blknum | attnum | allnulls | hasnulls | placeholder | value -------------+--------+--------+----------+----------+-------------+---------- - 1 | 0 | 1 | f | f | f | {1 .. 1} + itemoffset | blknum | attnum | allnulls | hasnulls | placeholder | empty | value +------------+--------+--------+----------+----------+-------------+-------+---------- + 1 | 0 | 1 | f | f | f | f | {1 .. 1} (1 row) -- Mask DETAIL messages as these are not portable across architectures. diff --git a/contrib/pageinspect/pageinspect--1.11--1.12.sql b/contrib/pageinspect/pageinspect--1.11--1.12.sql index 70c3abccf57..a20d67a9e82 100644 --- a/contrib/pageinspect/pageinspect--1.11--1.12.sql +++ b/contrib/pageinspect/pageinspect--1.11--1.12.sql @@ -21,3 +21,20 @@ CREATE FUNCTION bt_multi_page_stats(IN relname text, IN blkno int8, IN blk_count RETURNS SETOF record AS 'MODULE_PATHNAME', 'bt_multi_page_stats' LANGUAGE C STRICT PARALLEL RESTRICTED; + +-- +-- add information about BRIN empty ranges +-- +DROP FUNCTION brin_page_items(IN page bytea, IN index_oid regclass); +CREATE FUNCTION brin_page_items(IN page bytea, IN index_oid regclass, + OUT itemoffset int, + OUT blknum int8, + OUT attnum int, + OUT allnulls bool, + OUT hasnulls bool, + OUT placeholder bool, + OUT empty bool, + OUT value text) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'brin_page_items' +LANGUAGE C STRICT PARALLEL RESTRICTED; diff --git a/src/test/modules/brin/expected/summarization-and-inprogress-insertion.out b/src/test/modules/brin/expected/summarization-and-inprogress-insertion.out index 584ac2602f7..201786c82c0 100644 --- a/src/test/modules/brin/expected/summarization-and-inprogress-insertion.out +++ b/src/test/modules/brin/expected/summarization-and-inprogress-insertion.out @@ -2,9 +2,9 @@ Parsed test spec with 2 sessions starting permutation: s2check s1b s2b s1i s2summ s1c s2c s2check step s2check: SELECT * FROM brin_page_items(get_raw_page('brinidx', 2), 'brinidx'::regclass); -itemoffset|blknum|attnum|allnulls|hasnulls|placeholder|value -----------+------+------+--------+--------+-----------+-------- - 1| 0| 1|f |t |f |{1 .. 1} +itemoffset|blknum|attnum|allnulls|hasnulls|placeholder|empty|value +----------+------+------+--------+--------+-----------+-----+-------- + 1| 0| 1|f |t |f |f |{1 .. 1} (1 row) step s1b: BEGIN ISOLATION LEVEL REPEATABLE READ; @@ -24,18 +24,18 @@ brin_summarize_new_values step s1c: COMMIT; step s2c: COMMIT; step s2check: SELECT * FROM brin_page_items(get_raw_page('brinidx', 2), 'brinidx'::regclass); -itemoffset|blknum|attnum|allnulls|hasnulls|placeholder|value -----------+------+------+--------+--------+-----------+----------- - 1| 0| 1|f |t |f |{1 .. 1} - 2| 1| 1|f |f |f |{1 .. 1000} +itemoffset|blknum|attnum|allnulls|hasnulls|placeholder|empty|value +----------+------+------+--------+--------+-----------+-----+----------- + 1| 0| 1|f |t |f |f |{1 .. 1} + 2| 1| 1|f |f |f |f |{1 .. 1000} (2 rows) starting permutation: s2check s1b s1i s2vacuum s1c s2check step s2check: SELECT * FROM brin_page_items(get_raw_page('brinidx', 2), 'brinidx'::regclass); -itemoffset|blknum|attnum|allnulls|hasnulls|placeholder|value -----------+------+------+--------+--------+-----------+-------- - 1| 0| 1|f |t |f |{1 .. 1} +itemoffset|blknum|attnum|allnulls|hasnulls|placeholder|empty|value +----------+------+------+--------+--------+-----------+-----+-------- + 1| 0| 1|f |t |f |f |{1 .. 1} (1 row) step s1b: BEGIN ISOLATION LEVEL REPEATABLE READ; @@ -43,9 +43,9 @@ step s1i: INSERT INTO brin_iso VALUES (1000); step s2vacuum: VACUUM brin_iso; step s1c: COMMIT; step s2check: SELECT * FROM brin_page_items(get_raw_page('brinidx', 2), 'brinidx'::regclass); -itemoffset|blknum|attnum|allnulls|hasnulls|placeholder|value -----------+------+------+--------+--------+-----------+----------- - 1| 0| 1|f |t |f |{1 .. 1} - 2| 1| 1|f |f |f |{1 .. 1000} +itemoffset|blknum|attnum|allnulls|hasnulls|placeholder|empty|value +----------+------+------+--------+--------+-----------+-----+----------- + 1| 0| 1|f |t |f |f |{1 .. 1} + 2| 1| 1|f |f |f |f |{1 .. 1000} (2 rows) -- 2.40.0 ^ permalink raw reply [nested|flat] 5+ messages in thread
* Re: Missing update of all_hasnulls in BRIN opclasses @ 2023-04-24 15:36 Alvaro Herrera <[email protected]> parent: Tomas Vondra <[email protected]> 0 siblings, 0 replies; 5+ messages in thread From: Alvaro Herrera @ 2023-04-24 15:36 UTC (permalink / raw) To: Tomas Vondra <[email protected]>; +Cc: Justin Pryzby <[email protected]>; Matthias van de Meent <[email protected]>; [email protected] On 2023-Apr-23, Tomas Vondra wrote: > here's an updated version of the patch, including a backport version. I > ended up making the code yet a bit closer to master by introducing > add_values_to_range(). The current pre-14 code has the loop adding data > to the BRIN tuple in two places, but with the "fixed" logic handling > NULLs and the empty_range flag the amount of duplicated code got too > high, so this seem reasonable. In backbranches, the new field to BrinMemTuple needs to be at the end of the struct, to avoid ABI breakage. There's a comment in add_values_to_range with a typo "If the range was had". Also, "So we should not see empty range that was not modified" should perhaps be "should not see an empty range". (As for your FIXME comment in brin_memtuple_initialize, I think you're correct: we definitely need to reset bt_placeholder. Otherwise, we risk places that call it in a loop using a BrinMemTuple with one range with the flag set, in a range where that doesn't hold. It might be impossible for this to happen, given how narrow the conditions are on which bt_placeholder is used; but it seems safer to reset it anyway.) Some pgindent noise would be induced by this patch. I think it's worth cleaning up ahead of time. I did a quick experiment of turning the patches over -- applying test first, code fix after (requires some conflict fixing). With that I verified that the behavior of 'hasnulls' indeed changes with the code fix. > Both cases have a patch extending pageinspect to show the new flag, but > obviously we should commit that only in master. In backbranches it's > meant only to make testing easier. In backbranches, I think it should be reasonably easy to add a --1.7--1.7.1.sql file and set the default version to 1.7.1; that then enables us to have the functionality (and the tests) in older branches too. If you just add a --1.X.1--1.12.sql version to each branch that's identical to that branch's current pageinspect version upgrade script, that would let us have working upgrade paths for all branches. This is a bit laborious but straightforward enough. If you don't feel like adding that, I volunteer to add the necessary scripts to all branches after you commit the bugfix, and ensure that all upgrade paths work correctly. > I plan to do a bit more testing, I'd welcome some feedback - it's a > long-standing bug, and it'd be good to finally get this fixed. I don't > think the patch can be made any simpler. The approach looks good to me. -- Álvaro Herrera 48°01'N 7°57'E — https://www.EnterpriseDB.com/ "Oh, great altar of passive entertainment, bestow upon me thy discordant images at such speed as to render linear thought impossible" (Calvin a la TV) ^ permalink raw reply [nested|flat] 5+ messages in thread
end of thread, other threads:[~2023-04-24 15:36 UTC | newest] Thread overview: 5+ messages (download: mbox mbox.gz follow: Atom feed) -- links below jump to the message on this page -- 2021-03-12 01:02 [PATCH] cfe-12-gist_over_cfe-11-persistent squash commit Bruce Momjian <[email protected]> 2023-03-03 12:14 Re: Missing update of all_hasnulls in BRIN opclasses Tomas Vondra <[email protected]> 2023-03-28 14:30 ` Re: Missing update of all_hasnulls in BRIN opclasses Tomas Vondra <[email protected]> 2023-04-23 20:43 ` Re: Missing update of all_hasnulls in BRIN opclasses Tomas Vondra <[email protected]> 2023-04-24 15:36 ` Re: Missing update of all_hasnulls in BRIN opclasses Alvaro Herrera <[email protected]>
This inbox is served by agora; see mirroring instructions for how to clone and mirror all data and code used for this inbox