Skip to main content

index/inverted_index/create/sort/
external_sort.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::collections::{HashMap, VecDeque};
16use std::mem;
17use std::num::NonZeroUsize;
18use std::ops::RangeInclusive;
19use std::sync::Arc;
20use std::sync::atomic::{AtomicUsize, Ordering};
21
22use async_trait::async_trait;
23use common_telemetry::{debug, error};
24use futures::stream;
25use roaring::RoaringBitmap;
26use snafu::ResultExt;
27
28use crate::bitmap::Bitmap;
29use crate::external_provider::ExternalTempFileProvider;
30use crate::inverted_index::create::sort::intermediate_rw::{
31    IntermediateReader, IntermediateWriter,
32};
33use crate::inverted_index::create::sort::merge_stream::MergeSortedStream;
34use crate::inverted_index::create::sort::{SortOutput, SortedStream, Sorter};
35use crate::inverted_index::create::sort_create::SorterFactory;
36use crate::inverted_index::error::{IntermediateSnafu, Result};
37use crate::{Bytes, BytesRef};
38
39/// Segments of one value. Rows arrive in order, so segment ids only grow.
40struct Posting {
41    segments: RoaringBitmap,
42    last_segment: u32,
43}
44
45/// Estimated cost of one segment id in a roaring array container.
46const SEGMENT_SIZE: usize = size_of::<u16>();
47
48impl Posting {
49    /// Adds `start..=end` and returns the estimated memory growth.
50    fn push(&mut self, start: u32, end: u32) -> usize {
51        if end <= self.last_segment {
52            return 0;
53        }
54        let start = start.max(self.last_segment + 1);
55        self.segments.insert_range(start..=end);
56        self.last_segment = end;
57        (end - start + 1) as usize * SEGMENT_SIZE
58    }
59}
60
61/// `ExternalSorter` manages the sorting of data using both in-memory structures and external files.
62/// It dumps data to external files when the in-memory buffer crosses a certain memory threshold.
63pub struct ExternalSorter {
64    /// The index name associated with the sorting operation
65    index_name: String,
66
67    /// Manages creation and access to external temporary files
68    temp_file_provider: Arc<dyn ExternalTempFileProvider>,
69
70    /// Bitmap indicating which segments have null values
71    segment_null_bitmap: Bitmap,
72
73    /// In-memory buffer to hold values and their corresponding bitmaps until memory threshold is exceeded
74    values_buffer: HashMap<Bytes, Posting, ahash::RandomState>,
75
76    /// Count of all rows ingested so far
77    total_row_count: usize,
78
79    /// The number of rows per group for bitmap indexing which determines how rows are
80    /// batched for indexing. It is used to determine which segment a row belongs to.
81    segment_row_count: NonZeroUsize,
82
83    /// Tracks memory usage of the buffer
84    current_memory_usage: usize,
85
86    /// The threshold of current memory usage below which the buffer is not dumped, even if the global memory
87    /// usage exceeds `global_memory_usage_sort_limit`. This allows for smaller buffers to remain in memory,
88    /// providing a buffer against unnecessary dumps to external files, which can be costly in terms of performance.
89    /// `None` indicates that only the global memory usage threshold is considered for dumping the buffer.
90    current_memory_usage_threshold: Option<usize>,
91
92    /// Tracks the global memory usage of all sorters
93    global_memory_usage: Arc<AtomicUsize>,
94
95    /// The memory usage limit that, when exceeded by the global memory consumption of all sorters, necessitates
96    /// a reassessment of buffer retention. Surpassing this limit signals that there is a high overall memory pressure,
97    /// potentially requiring buffer dumping to external storage for memory relief.
98    /// `None` value indicates that no specific global memory usage threshold is established for triggering buffer dumps.
99    global_memory_usage_sort_limit: Option<usize>,
100}
101
102#[async_trait]
103impl Sorter for ExternalSorter {
104    /// Pushes n identical values into the in-memory buffer; returns whether it should be
105    /// spilled.
106    fn push_n(&mut self, value: Option<BytesRef<'_>>, n: usize) -> bool {
107        if n == 0 {
108            return false;
109        }
110
111        let segment_index_range = self.segment_index_range(n);
112        self.total_row_count += n;
113
114        if let Some(value) = value {
115            let memory_diff = self.push_not_null(value, segment_index_range);
116            self.account_memory(memory_diff)
117        } else {
118            self.segment_null_bitmap.insert_range(segment_index_range);
119            false
120        }
121    }
122
123    async fn spill(&mut self) -> Result<()> {
124        self.dump_buffer().await
125    }
126
127    /// Finalizes the sorting operation, merging data from both in-memory buffer and external files
128    /// into a sorted stream
129    async fn output(&mut self) -> Result<SortOutput> {
130        let readers = self
131            .temp_file_provider
132            .read_all(&self.index_name)
133            .await
134            .context(IntermediateSnafu)?;
135
136        // TODO(zhongzc): k-way merge instead of 2-way merge
137
138        let mut tree_nodes: VecDeque<SortedStream> = VecDeque::with_capacity(readers.len() + 1);
139        tree_nodes.push_back(Box::new(stream::iter(
140            Self::sorted(mem::take(&mut self.values_buffer)).map(Ok),
141        )));
142        for (_, reader) in readers {
143            tree_nodes.push_back(IntermediateReader::new(reader).into_stream().await?);
144        }
145
146        while tree_nodes.len() >= 2 {
147            // every turn, the length of tree_nodes will be reduced by 1 until only one stream left
148            let stream1 = tree_nodes.pop_front().unwrap();
149            let stream2 = tree_nodes.pop_front().unwrap();
150            let merged_stream = MergeSortedStream::merge(stream1, stream2);
151            tree_nodes.push_back(merged_stream);
152        }
153
154        Ok(SortOutput {
155            segment_null_bitmap: mem::take(&mut self.segment_null_bitmap),
156            sorted_stream: tree_nodes.pop_front().unwrap(),
157            total_row_count: self.total_row_count,
158        })
159    }
160}
161
162impl ExternalSorter {
163    /// Constructs a new `ExternalSorter`
164    pub fn new(
165        index_name: String,
166        temp_file_provider: Arc<dyn ExternalTempFileProvider>,
167        segment_row_count: NonZeroUsize,
168        current_memory_usage_threshold: Option<usize>,
169        global_memory_usage: Arc<AtomicUsize>,
170        global_memory_usage_sort_limit: Option<usize>,
171    ) -> Self {
172        Self {
173            index_name,
174            temp_file_provider,
175
176            segment_null_bitmap: Bitmap::new_bitvec(), // bitvec is more efficient for many null values
177            values_buffer: HashMap::default(),
178
179            total_row_count: 0,
180            segment_row_count,
181
182            current_memory_usage: 0,
183            current_memory_usage_threshold,
184            global_memory_usage,
185            global_memory_usage_sort_limit,
186        }
187    }
188
189    /// Generates a factory function that creates new `ExternalSorter` instances
190    pub fn factory(
191        temp_file_provider: Arc<dyn ExternalTempFileProvider>,
192        current_memory_usage_threshold: Option<usize>,
193        global_memory_usage: Arc<AtomicUsize>,
194        global_memory_usage_sort_limit: Option<usize>,
195    ) -> SorterFactory {
196        Box::new(move |index_name, segment_row_count| {
197            Box::new(Self::new(
198                index_name,
199                temp_file_provider.clone(),
200                segment_row_count,
201                current_memory_usage_threshold,
202                global_memory_usage.clone(),
203                global_memory_usage_sort_limit,
204            ))
205        })
206    }
207
208    /// Pushes the non-null values to the values buffer and sets the bits within
209    /// the specified range in the given bitmap to true.
210    /// Returns the memory usage difference of the buffer after the operation.
211    fn push_not_null(
212        &mut self,
213        value: BytesRef<'_>,
214        segment_index_range: RangeInclusive<usize>,
215    ) -> usize {
216        let (start, end) = (
217            *segment_index_range.start() as u32,
218            *segment_index_range.end() as u32,
219        );
220        match self.values_buffer.get_mut(value) {
221            Some(posting) => posting.push(start, end),
222            None => {
223                let mut segments = RoaringBitmap::new();
224                segments.insert_range(start..=end);
225                let posting = Posting {
226                    segments,
227                    last_segment: end,
228                };
229                self.values_buffer.insert(value.to_vec(), posting);
230                value.len() + (end - start + 1) as usize * SEGMENT_SIZE
231            }
232        }
233    }
234
235    /// Drains `values` sorted by value.
236    fn sorted(
237        values: HashMap<Bytes, Posting, ahash::RandomState>,
238    ) -> impl Iterator<Item = (Bytes, Bitmap)> {
239        let mut values = values.into_iter().collect::<Vec<_>>();
240        values.sort_unstable_by(|a, b| a.0.cmp(&b.0));
241        values
242            .into_iter()
243            .map(|(value, posting)| (value, Bitmap::Roaring(posting.segments)))
244    }
245
246    /// Records `memory_diff` and returns whether the buffer exceeds the thresholds and
247    /// should be offloaded to external storage.
248    fn account_memory(&mut self, memory_diff: usize) -> bool {
249        self.current_memory_usage += memory_diff;
250        let memory_usage = self.current_memory_usage;
251        self.global_memory_usage
252            .fetch_add(memory_diff, Ordering::Relaxed);
253
254        let Some(limit) = self.global_memory_usage_sort_limit else {
255            return false;
256        };
257        if self.global_memory_usage.load(Ordering::Relaxed) < limit {
258            return false;
259        }
260        if let Some(current_threshold) = self.current_memory_usage_threshold
261            && memory_usage < current_threshold
262        {
263            return false;
264        }
265        true
266    }
267
268    async fn dump_buffer(&mut self) -> Result<()> {
269        // A second spill request before any new value would write an empty file with the
270        // same id and replace the first one.
271        if self.values_buffer.is_empty() {
272            return Ok(());
273        }
274        let memory_usage = self.current_memory_usage;
275        let file_id = &format!("{:012}", self.total_row_count);
276        let index_name = &self.index_name;
277        let writer = self
278            .temp_file_provider
279            .create(index_name, file_id)
280            .await
281            .context(IntermediateSnafu)?;
282
283        let values = mem::take(&mut self.values_buffer);
284        self.global_memory_usage
285            .fetch_sub(memory_usage, Ordering::Relaxed);
286        self.current_memory_usage = 0;
287
288        let entries = values.len();
289        IntermediateWriter::new(writer).write_all(Self::sorted(values)).await.inspect(|_|
290            debug!("Dumped {entries} entries ({memory_usage} bytes) to intermediate file {file_id} for index {index_name}")
291        ).inspect_err(|e|
292            error!(e; "Failed to dump {entries} entries to intermediate file {file_id} for index {index_name}")
293        )
294    }
295
296    /// Determines the segment index range for the row index range
297    /// `[row_begin, row_begin + n - 1]`
298    fn segment_index_range(&self, n: usize) -> RangeInclusive<usize> {
299        let row_begin = self.total_row_count;
300        let start = self.segment_index(row_begin);
301        let end = self.segment_index(row_begin + n - 1);
302        start..=end
303    }
304
305    /// Determines the segment index for the given row index
306    fn segment_index(&self, row_index: usize) -> usize {
307        row_index / self.segment_row_count
308    }
309}
310
311#[cfg(test)]
312mod tests {
313    use std::collections::{BTreeMap, HashMap};
314    use std::iter;
315    use std::sync::Mutex;
316
317    use futures::{AsyncRead, StreamExt};
318    use rand::Rng;
319    use tokio::io::duplex;
320    use tokio_util::compat::{TokioAsyncReadCompatExt, TokioAsyncWriteCompatExt};
321
322    use super::*;
323    use crate::external_provider::MockExternalTempFileProvider;
324
325    async fn test_external_sorter(
326        current_memory_usage_threshold: Option<usize>,
327        global_memory_usage_sort_limit: Option<usize>,
328        segment_row_count: usize,
329        row_count: usize,
330        batch_push: bool,
331    ) {
332        let mut mock_provider = MockExternalTempFileProvider::new();
333
334        let mock_files: Arc<Mutex<HashMap<String, Box<dyn AsyncRead + Unpin + Send>>>> =
335            Arc::new(Mutex::new(HashMap::new()));
336
337        mock_provider.expect_create().returning({
338            let files = Arc::clone(&mock_files);
339            move |index_name, file_id| {
340                assert_eq!(index_name, "test");
341                let mut files = files.lock().unwrap();
342                let (writer, reader) = duplex(1024 * 1024);
343                files.insert(file_id.to_string(), Box::new(reader.compat()));
344                Ok(Box::new(writer.compat_write()))
345            }
346        });
347
348        mock_provider.expect_read_all().returning({
349            let files = Arc::clone(&mock_files);
350            move |index_name| {
351                assert_eq!(index_name, "test");
352                let mut files = files.lock().unwrap();
353                Ok(files.drain().collect::<Vec<_>>())
354            }
355        });
356
357        let mut sorter = ExternalSorter::new(
358            "test".to_owned(),
359            Arc::new(mock_provider),
360            NonZeroUsize::new(segment_row_count).unwrap(),
361            current_memory_usage_threshold,
362            Arc::new(AtomicUsize::new(0)),
363            global_memory_usage_sort_limit,
364        );
365
366        let mut sorted_result = if batch_push {
367            let (dic_values, sorted_result) =
368                dictionary_values_and_sorted_result(row_count, segment_row_count);
369
370            for (value, n) in dic_values {
371                if sorter.push_n(value.as_deref(), n) {
372                    sorter.spill().await.unwrap();
373                }
374            }
375
376            sorted_result
377        } else {
378            let (mock_values, sorted_result) =
379                shuffle_values_and_sorted_result(row_count, segment_row_count);
380
381            for value in mock_values {
382                if sorter.push_n(value.as_deref(), 1) {
383                    sorter.spill().await.unwrap();
384                }
385            }
386
387            sorted_result
388        };
389
390        let SortOutput {
391            segment_null_bitmap,
392            mut sorted_stream,
393            total_row_count,
394        } = sorter.output().await.unwrap();
395        assert_eq!(total_row_count, row_count);
396        let n = sorted_result.remove(&None);
397        assert_eq!(
398            segment_null_bitmap.iter_ones().collect::<Vec<_>>(),
399            n.unwrap_or_default()
400        );
401        for (value, offsets) in sorted_result {
402            let item = sorted_stream.next().await.unwrap().unwrap();
403            assert_eq!(item.0, value.unwrap());
404            assert_eq!(item.1.iter_ones().collect::<Vec<_>>(), offsets);
405        }
406    }
407
408    #[tokio::test]
409    async fn test_external_sorter_pure_in_memory() {
410        let current_memory_usage_threshold = None;
411        let global_memory_usage_sort_limit = None;
412        let total_row_count_cases = vec![0, 100, 1000, 10000];
413        let segment_row_count_cases = vec![1, 10, 100, 1000];
414        let batch_push_cases = vec![false, true];
415
416        for total_row_count in total_row_count_cases {
417            for segment_row_count in &segment_row_count_cases {
418                for batch_push in &batch_push_cases {
419                    test_external_sorter(
420                        current_memory_usage_threshold,
421                        global_memory_usage_sort_limit,
422                        *segment_row_count,
423                        total_row_count,
424                        *batch_push,
425                    )
426                    .await;
427                }
428            }
429        }
430    }
431
432    #[tokio::test]
433    async fn test_external_sorter_pure_external() {
434        let current_memory_usage_threshold = None;
435        let global_memory_usage_sort_limit = Some(0);
436        let total_row_count_cases = vec![0, 100, 1000, 10000];
437        let segment_row_count_cases = vec![1, 10, 100, 1000];
438        let batch_push_cases = vec![false, true];
439
440        for total_row_count in total_row_count_cases {
441            for segment_row_count in &segment_row_count_cases {
442                for batch_push in &batch_push_cases {
443                    test_external_sorter(
444                        current_memory_usage_threshold,
445                        global_memory_usage_sort_limit,
446                        *segment_row_count,
447                        total_row_count,
448                        *batch_push,
449                    )
450                    .await;
451                }
452            }
453        }
454    }
455
456    #[tokio::test]
457    async fn test_external_sorter_mixed() {
458        let current_memory_usage_threshold = vec![None, Some(2048)];
459        let global_memory_usage_sort_limit = Some(1024);
460        let total_row_count_cases = vec![0, 100, 1000, 10000];
461        let segment_row_count_cases = vec![1, 10, 100, 1000];
462        let batch_push_cases = vec![false, true];
463
464        for total_row_count in total_row_count_cases {
465            for segment_row_count in &segment_row_count_cases {
466                for batch_push in &batch_push_cases {
467                    for current_memory_usage_threshold in &current_memory_usage_threshold {
468                        test_external_sorter(
469                            *current_memory_usage_threshold,
470                            global_memory_usage_sort_limit,
471                            *segment_row_count,
472                            total_row_count,
473                            *batch_push,
474                        )
475                        .await;
476                    }
477                }
478            }
479        }
480    }
481
482    fn random_option_bytes(size: usize) -> Option<Vec<u8>> {
483        let mut rng = rand::rng();
484
485        if rng.random() {
486            let mut buffer = vec![0u8; size];
487            rng.fill(&mut buffer[..]);
488            Some(buffer)
489        } else {
490            None
491        }
492    }
493
494    type Values = Vec<Option<Bytes>>;
495    type DictionaryValues = Vec<(Option<Bytes>, usize)>;
496    type ValueSegIds = BTreeMap<Option<Bytes>, Vec<usize>>;
497
498    fn shuffle_values_and_sorted_result(
499        row_count: usize,
500        segment_row_count: usize,
501    ) -> (Values, ValueSegIds) {
502        let mock_values = iter::repeat_with(|| random_option_bytes(100))
503            .take(row_count)
504            .collect::<Vec<_>>();
505
506        let sorted_result = sorted_result(&mock_values, segment_row_count);
507        (mock_values, sorted_result)
508    }
509
510    fn dictionary_values_and_sorted_result(
511        row_count: usize,
512        segment_row_count: usize,
513    ) -> (DictionaryValues, ValueSegIds) {
514        let mut n = row_count;
515        let mut rng = rand::rng();
516        let mut dic_values = Vec::new();
517
518        while n > 0 {
519            let size = rng.random_range(1..=n);
520            let value = random_option_bytes(100);
521            dic_values.push((value, size));
522            n -= size;
523        }
524
525        let mock_values = dic_values
526            .iter()
527            .flat_map(|(value, size)| std::iter::repeat_n(value.clone(), *size))
528            .collect::<Vec<_>>();
529
530        let sorted_result = sorted_result(&mock_values, segment_row_count);
531        (dic_values, sorted_result)
532    }
533
534    fn sorted_result(values: &Values, segment_row_count: usize) -> ValueSegIds {
535        let mut sorted_result = BTreeMap::new();
536        for (row_index, value) in values.iter().enumerate() {
537            let to_add_segment_index = row_index / segment_row_count;
538            let indices = sorted_result.entry(value.clone()).or_insert_with(Vec::new);
539
540            if indices.last() != Some(&to_add_segment_index) {
541                indices.push(to_add_segment_index);
542            }
543        }
544
545        sorted_result
546    }
547}