fix: reduce peak memory usage when round robin tiebreaker is disabled - #23606
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #23606 +/- ##
==========================================
+ Coverage 80.89% 80.90% +0.01%
==========================================
Files 1102 1101 -1
Lines 376111 377044 +933
Branches 376111 377044 +933
==========================================
+ Hits 304251 305052 +801
- Misses 53753 53795 +42
- Partials 18107 18197 +90 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
| if self.enable_round_robin_tie_breaker { | ||
| self.prev_cursors.as_mut().expect("prev_cursor should be set when round robin tie breaker is enabled")[stream_idx] = taken; | ||
| } |
There was a problem hiding this comment.
The expect() check seems a bit redundant, just match on the option directly
| if self.enable_round_robin_tie_breaker { | |
| self.prev_cursors.as_mut().expect("prev_cursor should be set when round robin tie breaker is enabled")[stream_idx] = taken; | |
| } | |
| if let Some(prev_cursors) = self.prev_cursors.as_mut() { | |
| prev_cursors[stream_idx] = taken; | |
| } |
There was a problem hiding this comment.
this would silently break if for some reason a None would end up in prev_cursors, I prefer to fail loudly
95a0830 to
d018aa3
Compare
|
coverage report useless because of #23647 |
|
This PR is a prerequisite for: @kumarUjjawal @rluvaton @alamb could you help me get this PR merged? |
|
Thank you @ariel-miculas for working on this. I will review it today. |
kumarUjjawal
left a comment
There was a problem hiding this comment.
Overall this looks good. I noticed a related existing issue the round-robin setting can be lost when we rebuilds the merge operator, it can remove the memory benefit added here.
Can you look into this?
| // Interleave streams: stream 0 → even slots [0,200,400,...], | ||
| // stream 1 → odd slots [100,300,500,...] so the merge | ||
| // alternates between them on every batch. | ||
| let base = ((b * 2 + stream_idx) * num_rows_per_batch) as i32; |
There was a problem hiding this comment.
Could we add a small test where both input streams contain the same sort values across multiple batches?
There was a problem hiding this comment.
added a test where the sort column is a constant value across all batches
This PR doesn't really bring any memory benefit (since round robin tie breaking is enabled by default), its purpose is to show the memory overhead caused by the exising design. My goal with this PR is to make it clear how the next PR reduces the memory overhead with round robin tie breaking enabled |
Thanks for clarification. |
82e7472 to
4bf99ec
Compare
|
I had to switch to PeakRecordingPool instead of TrackConsumersPool because the latter was reporting 0 bytes as the peak, the reason being that the memory consumer was being unregistered before we could get the actual value. This used to work previously, before the refactor to SortPreservingMergeStream in #23407 and #23976 Longer explanation: Two commits on main rewrote SortPreservingMergeStream from a manual poll_next state machine into an async generator:
Before: SortPreservingMergeStream implemented Stream directly ( After: the implementation moved to: self (and everything it owns, including the reservation) is now captured inside the generator's That drop cascades: MemoryReservation::drop → SharedRegistration::drop (once the last Arc clone goes |
|
I've fixed the existings issues and the failing tests, @kumarUjjawal could you please take another look? |
| let mut partition_batches: Vec<Vec<RecordBatch>> = Vec::new(); | ||
|
|
||
| for stream_idx in 0..2usize { | ||
| // Each stream covers a non-overlapping key range so both are individually |
There was a problem hiding this comment.
what about alternating ranges, stream 0 uses the even slots and stream 1 uses the odd slots?
There was a problem hiding this comment.
what's the rationale for adding all these edge cases? is it trying to figure out whether SortPreservingMerge over-reserves memory with certain data inputs?
There was a problem hiding this comment.
My bad, I should have been clear what I meant. I wasn’t asking for another test case. I was saying that the comment above says stream 0 covers [0,1000) and stream 1 covers [1000,2000). But the code does
stream 0 → [0, 100), [200, 300), ...
stream 1 → [100, 200), [300, 400), ...
There was a problem hiding this comment.
I'll fix the comment in the next PR, since it'll touch these tests anyway.
kumarUjjawal
left a comment
There was a problem hiding this comment.
Thanks for all the follow ups.
I will leave this open for 1-2 days if anyone else has any opinions.
…apache#23606) ## Which issue does this PR close? - First part of apache#23604 ## Rationale for this change Don't pay the memory price when the tie breaking feature is disabled. ## What changes are included in this PR? Mostly tests to show the issue ## Are these changes tested? Yes ## Are there any user-facing changes? No
…tie-breaking purposes (apache#23619) ## Which issue does this PR close? - Closes apache#23606. ## Rationale for this change See the linked issue. Note that this PR also contains the changes in apache#23606, so this will have to be rebased ## What changes are included in this PR? Store only the last row in prev_cursors instead of keeping the entire cursor ## Are these changes tested? Added a test to show the improvement ## Are there any user-facing changes? No
Which issue does this PR close?
Rationale for this change
Don't pay the memory price when the tie breaking feature is disabled.
What changes are included in this PR?
Mostly tests to show the issue
Are these changes tested?
Yes
Are there any user-facing changes?
No