Skip to content

pool: bound insertion debt during concurrent enforcement - #39374

Merged
DAlperin merged 1 commit into
mainfrom
dov/pool-insertion-debt
Sep 30, 2026
Merged

DAlperin merged 1 commit into
mainfrom
dov/pool-insertion-debt

Conversation

@DAlperin

@DAlperin DAlperin commented Sep 30, 2026 •

Copy link
Copy Markdown
Member

Motivation

When pool enforcement could not make room, concurrent insertions queued unbounded resident bytes behind a single-flight enforcer. On a production upsert hydration with about 2 KB rows and 30x more state than pool budget, the pool ledger reached 7.3x its budget and drove the container to its working-set limit.

Description

Reserve resident bytes before filling pool slots. If enforcement cannot make room within the bounded allowance, an eighth of the budget, encode the insertion directly to an extent on the inserting thread and expose that fallback as mz_column_pool_direct_extent_inserts_total.

  • RSS target. The allowance counts against the RSS target only while insertions are using it, so the compressed-resident tier keeps its full capacity otherwise. Deployments with an RSS target see no change unless insertions outrun enforcement.
  • Fallback cost. The fallback compresses on the dataflow worker, which is the work the spill threads exist to offload. It fires only when spill threads are behind. On a synchronous-merge upsert hydration (192M rows, 400cc, four spill workers) it fired 0 times in 728,818 inserts over a 25M-row window. On an async-merge build, which is not in this stack, it fired on 15% of inserts. If it turns out to be common, a bounded wait for spill progress before falling back is the next step.

Verification

New unit tests cover a stalled enforcer with concurrent fills, payload round trips through the fallback, accounting after drops, and a compressed tier left intact by unused slack. Benchmark results for this change and the chunk bounds in #39042, which were measured together, are in this comment.

Release note: None.

Generated with Claude Code

@antiguru antiguru left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great, thank you!

Reserve resident bytes before filling pool slots. If enforcement cannot make
room within the bounded allowance, write the insertion directly to an extent
and expose that fallback in pool metrics. This prevents an occupied
single-flight enforcer from allowing an unbounded queue of resident
insertions. The allowance, an eighth of the budget, also counts against the
RSS target, so slots overshooting by it cannot push total RSS past the
target.

Add deterministic tests for a stalled enforcer and concurrent fills,
including payload round trips and accounting after drops.
@DAlperin
DAlperin force-pushed the dov/pool-insertion-debt branch from c227ba8 to 52adfc4 Compare September 30, 2026 16:07
@DAlperin
DAlperin merged commit de580d5 into main Sep 30, 2026
87 checks passed
@DAlperin
DAlperin deleted the dov/pool-insertion-debt branch September 30, 2026 19:33
@linear-code

linear-code Bot commented Oct 1, 2026

Copy link
Copy Markdown

CPU-279

DAlperin added a commit that referenced this pull request Oct 1, 2026
…9042)

### Motivation

Merged column chunks were emitted once they crossed 2 MiB rather than
bounded to it, and the check ran every 1024 records, so wide rows
routinely produced chunks well above the 2 MiB size class. Those chunks
take 4 MiB huge-page slots in the buffer pool, halving the effective
pool budget and doubling the bytes every eviction and read-back touches.
This showed up on a production upsert hydration with about 2 KB rows and
30x more state than pool budget.

### Description

- Settle and the chunker bound serialized column chunks to 2 MiB,
splitting oversized output while preserving order and generational
depth. Pieces are cut at the column's average record width, so uniform
rows fill their size class and leave one short remainder to coalesce. A
piece that still exceeds the bound, from uneven widths, is halved until
it fits, which bounds the recursion depth by log2 of the record count. A
single indivisible update may still exceed the bound.
- Settle fills an open carry toward the bound with a prefix of the next
chunk and returns the rest to the input, so a stream of chunks just over
half the bound does not commit each into a half-empty slot. An oversized
shared body is cut by borrow, copied once.
- Merges check their output size at a cadence set by observed row width
instead of every 1024 records, which keeps uniform rows within the ship
size, and reserve output capacity only when both inputs fit within two
ship-sized chunks.

The pool's bound on insertion debt, previously part of this PR, is split
out into #39374, below this one in the stack.

### Verification

New unit tests cover the chunker, merge output before settle, settle
byte bounds, depth preservation when a shared body is split and its last
piece coalesces with a shallower neighbor, carry filling, indivisible
oversized updates, and uneven record widths.

Release note: None.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants