Skip to content

db/etl: give both merges the prefix, the sift and the in-place advance - #23616

Draft
AskAlexSharov wants to merge 1 commit into
alex/etl_chunk_sort_37from
alex/etl_merge_prefix_37
Draft

db/etl: give both merges the prefix, the sift and the in-place advance#23616
AskAlexSharov wants to merge 1 commit into
alex/etl_chunk_sort_37from
alex/etl_merge_prefix_37

Conversation

@AskAlexSharov

@AskAlexSharov AskAlexSharov commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator

Stacked on #23599.

context: reducing amount of expensive re-allocs inside SD.mu critical section (part of release/3.6 _newPayload perf jumps problem)

problem: etl has two k-way merges — the one mergeSortFiles runs over data providers, and the one #23599 adds to read a buffer's sorted chunks. Both are the shape heap.go says it was copy-pasted from: a full bytes.Compare on every comparison, two of them per level, and for the provider heap a heapPop then a heapPush per entry, which is two traversals where one does.

solution: both compare a cached 8-byte big-endian key prefix first, so a comparison reads the key only when two cursors agree on it. Both sift by sinking a hole to a leaf and climbing back — one compare a level — which also serves as the heapify step. The provider heap keeps its root in place while loadFunc runs and takes the next key there, so one sift replaces the pop and the push.

All five variants (today, and each trick added in turn) were built side by side and checked to give byte-identical output before any was measured.

Provider heap alone, 200k keys over k sorted runs (M4): k4 -48%, k8 -55%, k16 -46%.

The chunk merge, n5 (EPYC 4344P, 6 interleaved rounds), vs #23599:

PutSortLoad/random_500k -19.7%
PutSortLoad/random_100k -9.9%
LoadOnly/random_500k -4.6%

of which the prefix alone is -9.6% and the sift alone -5.3%. Keys arriving in order are unaffected by either — chunks already in order end to end skip the heap.

End to end, both together: MergeSortFiles/file_only_100k -17.3%, LoadOnly/random_500k -18.0%, sorted_500k -17.0%. mem_only_10k is -1.4%, the control: one provider, never merges.

With #23599 this takes LoadOnly/random_500k from +32.6% against main to +3.4%, and the geomean from -13.1% to -16.2%.

AskAlexSharov added a commit that referenced this pull request Aug 27, 2026
Brings the chunk merge's move into heap.go. The provider-heap speedups
that were on this branch a moment ago moved out to #23616, so they are
not here; db/etl/collector.go keeps this branch's dbg buffer-sizing
metrics, which the merge left untouched.
AskAlexSharov added a commit that referenced this pull request Aug 27, 2026
The merge compared cached 8-byte key prefixes before full keys, and its
sift sank a hole to a leaf and climbed back to spend one compare a level
instead of two. Both are speed, not correctness, and they belong with
the same two tricks #23616 gives the provider merge - one PR about the
chunk format, one about making both merges fast.

less does a bytes.Compare now, and siftRoot is the plain top-down sift.
On EPYC that costs PutSortLoad/random_500k 19.7% and random_100k 9.9%;
keys arriving in order are unaffected, since chunks already in order end
to end skip the heap. Sort, Put and Collect do not change.
@AskAlexSharov
AskAlexSharov force-pushed the alex/etl_merge_prefix_37 branch from 7eb5b86 to 9a92a02 Compare August 27, 2026 05:25
@AskAlexSharov AskAlexSharov changed the title db/etl: give the provider merge the tricks the chunk merge uses db/etl: give both merges the prefix, the sift and the in-place advance Aug 27, 2026
@AskAlexSharov
AskAlexSharov force-pushed the alex/etl_merge_prefix_37 branch from 9a92a02 to 243aa49 Compare August 27, 2026 06:22
@AskAlexSharov
AskAlexSharov force-pushed the alex/etl_chunk_sort_37 branch from 114d702 to b606ae8 Compare August 27, 2026 08:09
etl has two k-way merges: the one mergeSortFiles runs over data
providers, and the one that reads a buffer's sorted chunks. Neither
needs to be the container/heap shape heap.go says it was copy-pasted
from - a full bytes.Compare on every comparison, two of them per level,
and for the provider heap a pop followed by a push per entry.

Both now compare a cached 8-byte big-endian key prefix first, so a
comparison reads the key only when two cursors agree on it. Both sift by
sinking a hole to a leaf and climbing back, one compare a level rather
than two, which also serves as the heapify step. The provider heap keeps
its root in place while loadFunc runs and takes the next key there, so
one sift replaces the pop and the push, and the initial fill is one
heapInit over k appended elements rather than k pushes.

The five variants - today, and each trick added in turn - were built
side by side and checked to give byte-identical output before any was
measured.

Provider heap alone, 200k keys over k runs (M4): k4 -48%, k8 -55%,
k16 -46%. The chunk merge, on EPYC: PutSortLoad/random_500k -19.7% and
random_100k -9.9%, of which the prefix is -9.6% and the sift -5.3%.
End to end: MergeSortFiles/file_only_100k -17.3%,
SortableBufferLoadOnly/random_500k -18.0%, sorted_500k -17.0%. Keys
arriving in order are unaffected either way, since chunks already in
order end to end skip the heap.
@AskAlexSharov
AskAlexSharov force-pushed the alex/etl_merge_prefix_37 branch from 243aa49 to d4f20ee Compare August 27, 2026 08:10
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