Skip to content

Commit ffd6583

Browse files
committed
refactor: keep external sort heads in cursors
1 parent 936b1b7 commit ffd6583

1 file changed

Lines changed: 19 additions & 15 deletions

File tree

src/execution/dql/external_sort.rs

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -146,25 +146,35 @@ fn finish_sort<'on_flush>(
146146
struct RunCursor<'source> {
147147
remaining_segments: usize,
148148
reader: SegmentReader<'source, BufReader<File>, SortRow>,
149+
head: Option<SortRow>,
149150
}
150151

151152
impl<'source> RunCursor<'source> {
152153
fn new(source: &'source SpillReader<SortRow>, run: &Run) -> Result<Self, DatabaseError> {
153154
let mut reader = source.open_segment_reader()?;
154155
reader.reset(run.first_segment)?;
155-
Ok(Self {
156+
let mut cursor = Self {
156157
remaining_segments: run.segment_count - 1,
157158
reader,
158-
})
159+
head: None,
160+
};
161+
let _ = cursor.next()?;
162+
Ok(cursor)
163+
}
164+
165+
fn peek(&self) -> Option<&SortRow> {
166+
self.head.as_ref()
159167
}
160168

161169
fn next(&mut self) -> Result<Option<SortRow>, DatabaseError> {
170+
let head = self.head.take();
162171
loop {
163172
if let Some(row) = self.reader.next() {
164-
return row.map(Some);
173+
self.head = Some(row?);
174+
return Ok(head);
165175
}
166176
if self.remaining_segments == 0 {
167-
return Ok(None);
177+
return Ok(head);
168178
}
169179
if !self.reader.start_next_segment()? {
170180
return Err(DatabaseError::InvalidValue(
@@ -200,34 +210,29 @@ fn merge_pass<'on_flush>(
200210
let mut target = SpillVec::new();
201211
target_runs.clear();
202212
target_runs.reserve(source_runs.len().div_ceil(fan_in));
203-
let mut cursors = Vec::with_capacity(fan_in);
204213
// Perf: a loser tree or binary heap would reduce head selection from O(K) to O(log K), but
205214
// K is deliberately small, so a linear scan keeps the merge state and update path simpler.
206-
let mut heads = Vec::with_capacity(fan_in);
215+
let mut cursors = Vec::with_capacity(fan_in);
207216

208217
for run_group in source_runs.chunks(fan_in) {
209-
heads.clear();
210218
cursors.clear();
211219
for run in run_group {
212220
cursors.push(RunCursor::new(&source, run)?);
213221
}
214-
for cursor in &mut cursors {
215-
heads.push(cursor.next()?);
216-
}
217222
let mut first_segment = None;
218223
let mut segment_count = 0;
219224

220225
loop {
221226
let mut selected = None;
222-
for (index, row) in heads.iter().enumerate() {
223-
let Some(row) = row else {
227+
for (index, cursor) in cursors.iter().enumerate() {
228+
let Some(row) = cursor.peek() else {
224229
continue;
225230
};
226231
let Some(current) = selected else {
227232
selected = Some(index);
228233
continue;
229234
};
230-
let Some(current_row) = heads[current].as_ref() else {
235+
let Some(current_row) = cursors[current].peek() else {
231236
selected = Some(index);
232237
continue;
233238
};
@@ -244,7 +249,7 @@ fn merge_pass<'on_flush>(
244249
let Some(selected) = selected else {
245250
break;
246251
};
247-
let Some(row) = heads[selected].take() else {
252+
let Some(row) = cursors[selected].next()? else {
248253
return Err(DatabaseError::InvalidValue(
249254
"sort merge selected an empty run".to_string(),
250255
));
@@ -253,7 +258,6 @@ fn merge_pass<'on_flush>(
253258
first_segment.get_or_insert(segment);
254259
segment_count += 1;
255260
}
256-
heads[selected] = cursors[selected].next()?;
257261
}
258262

259263
if let Some(segment) = target.flush()? {

0 commit comments

Comments
 (0)