Skip to content

fix(rfc): honour the row budget for single-column scans - #138

Merged
jrosskopf merged 1 commit into
masterfrom
claude/fix-single-column-batch-budget
Sep 4, 2026
Merged

fix(rfc): honour the row budget for single-column scans#138
jrosskopf merged 1 commit into
masterfrom
claude/fix-single-column-batch-budget

Conversation

@jrosskopf

@jrosskopf jrosskopf commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

sap_read_table's fetch_size and partitions did nothing to the batch size of a
single-column scan.

MaxBatchSizeForColumnCount short-circuited to MAX_BATCH_SIZE whenever a scan projected
one column, discarding the budget ResolveEffectiveMaxBatchSize had just divided across
the partition workers. Every worker issued ROWCOUNT=32768 however erpl_rfc_fetch_size
and partitions were set.

The exemption was deliberate, and its comment said why: a narrow scan is already under any
sane budget. That is true of the undivided budget and stops being true the moment it is
divided by the worker count — which is the case partitioning creates. Narrow scans are what
partitioning is for, so this was the one shape where the knobs mattered most and did
nothing.

How it was found

Profiling the two RFC backends against each other. heaptrack showed 32768-row batches on
every worker at partitions=8, on both backends, regardless of the settings — which
is not what the tuning API advertises.

The single-column bypass was first spotted by a multi-agent review of the two codebases;
the profiler then confirmed it and refuted that review's wider model.

Measured

300,000 rows of REPOSRC, one column, partitions=8, release build, a4h:

peak RSS wall
before 405 MB 2.17s
after 307 MB (−24%) 2.07s

No throughput cost at this shape. Checksums identical before and after, on both backends.

Also

A partitioned scan never returned its allocator arenas. The serial path calls
malloc_trim(0) when the scan finishes (#69), but a partition worker retires through a
different branch that did not, so each worker thread's glibc arena kept its high-water
mark — which is the number getrusage reports.

What this does NOT fix

The erpl-proto backend is unchanged at 691 MB. Its peak scales with rows read rather
than rows in flight — a 16× smaller batch does not move it, while total rows do
(293 / 389 / 694 MB for 75k / 150k / 300k rows, ~2.3 KB retained per row). That is a
separate defect, reported with its heaptrack attribution as DataZooDE/erpl-proto#61.

Tests

  • test_read_table_batching.cpp: a new case asserting the budget binds for one column —
    red before this change (32768 == 2048 failed), green after. The existing wide-scan
    assertions are unchanged and still pass, because a large budget still yields the full
    batch; only a small (divided) budget now binds.
  • Offline: batching 7/7, partition scheduler 11/11.
  • Live: RFC 32/32 on nwrfc, 31 + 1 known gap on erpl-proto.

Behaviour note

A serial single-column scan now uses a 16384-row batch rather than 32768, because the
default budget is 16384 concurrent rows and it is now honoured rather than bypassed. That
is the documented contract; raise erpl_rfc_fetch_size to restore the old batch.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

`MaxBatchSizeForColumnCount` short-circuited to MAX_BATCH_SIZE whenever a scan
projected one column, so the budget `ResolveEffectiveMaxBatchSize` had just divided
across the partition workers was thrown away. Every worker issued ROWCOUNT=32768
regardless of erpl_rfc_fetch_size or partitions.

The exemption was deliberate and its comment said so -- a narrow scan is already
under any sane budget. That holds for the undivided budget and stops holding the
moment it is divided by the worker count, which is exactly the case partitioning
creates. Narrow scans are what partitioning is for, so this was the one shape where
the knobs mattered most and did nothing.

Found by profiling the two RFC backends: heaptrack showed 32768-row batches on every
worker at partitions=8, on both backends, whatever the settings said.

Measured on 300,000 rows of REPOSRC, one column, partitions=8, release build:

    nwrfc   peak RSS 405 MB -> 307 MB (-24%),  wall 2.17s -> 2.07s

No throughput cost at this shape; the smaller batch is offset by the workers no
longer contending for one oversized SDK buffer each.

Also trims the allocator arena when a partitioned worker retires. The serial path
already does this when the scan finishes (issue #69); the partitioned path returns
through a different branch that did not, so each worker thread's glibc arena kept its
high-water mark -- which is the number getrusage reports.

Note this does NOT move the erpl-proto backend (691 MB, unchanged), whose peak scales
with rows read rather than rows in flight. That is a separate defect, reported with
its heaptrack attribution as DataZooDE/erpl-proto#61.
@jrosskopf
jrosskopf merged commit 4f8ec02 into master Sep 4, 2026
53 checks passed
@jrosskopf
jrosskopf deleted the claude/fix-single-column-batch-budget branch September 4, 2026 05:13
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.

1 participant