Re: nbtree: opt-in leaf key prefix compression

From: Greg Burd <greg(at)burd(dot)me>
To: pgsql-hackers(at)lists(dot)postgresql(dot)org
Cc: Peter Geoghegan <pg(at)bowt(dot)ie>
Subject: Re: nbtree: opt-in leaf key prefix compression
Date: 2026-10-27 17:50:50
Message-ID: y8lIX15Mjse_ALeMMfsK1oakyoxjIU4p2HLHF8sQArBsYgFDvAZz7vVCW26vl6IP7AEMWQArSkqBxvyCa4OKdXe8eiZ6OxqgvIC0FnJEM7c=@burd.me
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

Hello,

v3 attached. This closes the two open items I flagged with v2 and reports back
on the nbtsplitloc.c design question, which turned out to have a clearer answer
than I expected once I measured it.

1. The forced-full-page-image fallbacks

XLH_SPLIT_RFPI and XLH_DEDUP_RFPI carry a coded page as a full-page image in
the one case redo can't rebuild from logged plain items: a page so
compressible that its decoded payload exceeds a block. I could not reach
that threshold with any realistic data in v2, which left the path
unexercised. I instrumented it on maximally compressible keys (a 125-byte
shared prefix, tiny suffixes), the right half of a split tops out around 6
KB of plain payload, which is close, but the split-point balance keeps a
half under a block, so the fallback never fires naturally.

It is reachable in principle (a different item-size mix could get there), so
I'm keeping it rather than demoting it to a can't-happen error. An error
there would be a rare production PANIC on data that is merely unusual, not
corrupt. To make it testable, there's now a nbtree-prefix-force-rfpi
injection point that forces the fallback for every prefix split and
deduplication, and a test in src/test/modules/nbtree that builds and
deduplicates a prefix index with it attached, then amchecks the result.
Writing that test immediately caught a bug (the injection-point check sat
inside the WAL critical section, where the first-use cache load palloc()s)-
which is now fixed (the decision is made before the critical section).
Byte-for-byte redo through the forced image is covered by the existing
crash-recovery and streaming runs under wal_consistency_checking.

2. Alternatives-considered

The README now spells out why this is a separate mechanism rather than an
extension of posting lists: deduplication collapses tuples whose whole key
is equal; prefix compression factors out a leading run shared by distinct
keys. They attack different redundancy and compose on one page (deduplicate
first, then code the survivors), which is why a build runs both.
Overloading the posting-list format to also carry a shared prefix was
considered and rejected; a posting list is one key plus many TIDs, prefix
coding is many keys that share a prefix, so they'd need different per-item
layouts anyway, and it would entangle the two paths this patch keeps apart.

3. The nbtsplitloc.c question

I proposed moving the degenerate-split guard (the one that rejects a split
leaving the left page with no data items) out of nbtsplitloc.c. I tried the
cleanest version of that; make the guard unconditional instead of
prefix-only, on the theory that upstream never chooses that split point for
a plain page anyway, so it would be a no-op there. It is not a no-op: a
probe showed _bt_recsplitloc() is called at that point for plain pages all
the time, and rejecting it unconditionally broke three test suites. The
guard genuinely has to be prefix-specific, because it exists precisely
because coded-size accounting makes a point attractive that plain-size
accounting rejects on its own via the leftfree check.

Moving it into _bt_split() instead of nbtsplitloc.c doesn't help either:
_bt_split() doesn't choose split points, so it would have to re-derive or
reject-and-retry, which is more prefix logic in a more dangerous place (next
to the critical section), not less. So I've left the guard where it is: one
well-commented "if" in _bt_recsplitloc(). The coded-size accounting stays
too, for the reason in v2 (a compressed leaf legitimately holds more than
two plain pages' worth, so plain-size accounting makes feasible splits look
infeasible). If you'd still rather see it elsewhere I'll do it, but I
wanted to report that the "obvious" relocation makes the code worse, not
better.

Everything else from v2 stands: one split-collection routine shared by
primary and redo, amcheck structural checks plus a prefix-index test,
docs matched to the tuple-rebuild WAL design.

I think v3 is in good shape, so I'll leave it here and pick it back up on
review feedback (or if cf-bot turns something up).

I might run more benchmarks, just for fun.

-greg

Attachment Content-Type Size
v3-0001-nbtree-opt-in-leaf-key-prefix-compression.patch text/x-patch 165.9 KB

In response to

Browse pgsql-hackers by date

  From Date Subject
Next Message Greg Burd 2026-10-27 17:56:32 Re: Let an ordering index scan hand its ORDER BY value to the target list
Previous Message Greg Burd 2026-10-27 17:18:39 Re: Clean up code comments for pg_wchar_tbl