1use std::{
5 io::{BufReader, BufWriter},
6 path::PathBuf,
7 sync::{Arc, Mutex},
8};
9
10use arrow::ipc::{reader::StreamReader, writer::StreamWriter};
11use arrow_array::RecordBatch;
12use arrow_schema::{ArrowError, Schema, SchemaRef};
13use datafusion::{
14 catalog::{TableProvider, streaming::StreamingTable},
15 execution::{SendableRecordBatchStream, TaskContext},
16 physical_plan::{stream::RecordBatchStreamAdapter, streaming::PartitionStream},
17};
18use datafusion_common::DataFusionError;
19use futures::StreamExt;
20use lance_arrow::memory::MemoryAccumulator;
21use lance_core::error::LanceOptionExt;
22use lance_core::utils::tempfile::TempDir;
23
24pub fn create_replay_spill(
44 path: std::path::PathBuf,
45 schema: Arc<Schema>,
46 memory_limit: usize,
47) -> (SpillSender, SpillReceiver) {
48 let initial_status = WriteStatus::default();
49 let (status_sender, status_receiver) = tokio::sync::watch::channel(initial_status);
50 let sender = SpillSender {
51 memory_limit,
52 path: path.clone(),
53 schema: schema.clone(),
54 state: SpillState::default(),
55 status_sender,
56 };
57
58 let receiver = SpillReceiver {
59 status_receiver,
60 path,
61 schema,
62 };
63
64 (sender, receiver)
65}
66
67pub async fn spilling_table_provider(
118 mut source: SendableRecordBatchStream,
119 memory_limit: usize,
120) -> Result<Arc<dyn TableProvider>, DataFusionError> {
121 let schema = source.schema();
122 let tmp_dir = tokio::task::spawn_blocking(TempDir::try_new)
123 .await
124 .map_err(|e| DataFusionError::Execution(format!("Failed to spawn temp dir task: {e}")))?
125 .map_err(|e| DataFusionError::Execution(format!("Failed to create temp dir: {e}")))?;
126 let tmp_path = tmp_dir.std_path().join("spill.arrows");
127 let (mut sender, receiver) = create_replay_spill(tmp_path, schema.clone(), memory_limit);
128
129 let drain_handle = tokio::task::spawn(async move {
133 let mut errored = false;
134 while let Some(res) = source.next().await {
135 match res {
136 Ok(batch) => {
137 if let Err(e) = sender.write(batch).await {
138 sender.send_error(e);
139 errored = true;
140 break;
141 }
142 }
143 Err(e) => {
144 sender.send_error(e);
145 errored = true;
146 break;
147 }
148 }
149 }
150 if !errored && let Err(err) = sender.finish().await {
154 sender.send_error(err);
155 }
156 sender
157 });
158
159 let partition = Arc::new(SpillPartition {
160 schema: schema.clone(),
161 receiver,
162 _tmp_dir: Arc::new(tmp_dir),
163 _drain_handle: Arc::new(drain_handle),
164 });
165 Ok(Arc::new(StreamingTable::try_new(schema, vec![partition])?))
166}
167
168struct SpillPartition {
174 schema: SchemaRef,
175 receiver: SpillReceiver,
176 _tmp_dir: Arc<TempDir>,
178 _drain_handle: Arc<tokio::task::JoinHandle<SpillSender>>,
182}
183
184impl std::fmt::Debug for SpillPartition {
185 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
186 f.debug_struct("SpillPartition")
187 .field("schema", &self.schema)
188 .finish()
189 }
190}
191
192impl PartitionStream for SpillPartition {
193 fn schema(&self) -> &SchemaRef {
194 &self.schema
195 }
196
197 fn execute(&self, _ctx: Arc<TaskContext>) -> SendableRecordBatchStream {
198 self.receiver.read()
199 }
200}
201
202#[derive(Clone)]
203pub struct SpillReceiver {
204 status_receiver: tokio::sync::watch::Receiver<WriteStatus>,
205 path: PathBuf,
206 schema: Arc<Schema>,
207}
208
209impl SpillReceiver {
210 pub fn read(&self) -> SendableRecordBatchStream {
218 let rx = self.status_receiver.clone();
219 let reader = SpillReader::new(rx, self.path.clone());
220
221 let stream = futures::stream::try_unfold(reader, move |mut reader| async move {
222 match reader.read().await {
223 Ok(None) => Ok(None),
224 Ok(Some(batch)) => Ok(Some((batch, reader))),
225 Err(err) => Err(err),
226 }
227 });
228
229 Box::pin(RecordBatchStreamAdapter::new(self.schema.clone(), stream))
230 }
231}
232
233struct SpillReader {
234 pub batches_read: usize,
235 receiver: tokio::sync::watch::Receiver<WriteStatus>,
236 state: SpillReaderState,
237}
238
239enum SpillReaderState {
240 Buffered { spill_path: PathBuf },
241 Reader { reader: AsyncStreamReader },
242}
243
244impl SpillReader {
245 fn new(receiver: tokio::sync::watch::Receiver<WriteStatus>, spill_path: PathBuf) -> Self {
246 Self {
247 batches_read: 0,
248 receiver,
249 state: SpillReaderState::Buffered { spill_path },
250 }
251 }
252
253 async fn wait_for_more_data(&mut self) -> Result<Option<Arc<[RecordBatch]>>, DataFusionError> {
254 let status = self
255 .receiver
256 .wait_for(|status| {
257 status.error.is_some()
258 || status.finished
259 || status.batches_written() > self.batches_read
260 })
261 .await
262 .map_err(|_| {
263 DataFusionError::Execution(
264 "Spill has been dropped before reader has finish.".into(),
265 )
266 })?;
267
268 if let Some(error) = &status.error {
269 let mut guard = error.lock().ok().expect_ok()?;
270 return Err(DataFusionError::from(&mut (*guard)));
271 }
272
273 if let DataLocation::Buffered { batches } = &status.data_location {
274 Ok(Some(batches.clone()))
275 } else {
276 Ok(None)
277 }
278 }
279
280 async fn get_reader(&mut self) -> Result<&AsyncStreamReader, ArrowError> {
281 if let SpillReaderState::Buffered { spill_path } = &self.state {
282 let reader = AsyncStreamReader::open(spill_path.clone()).await?;
283 for _ in 0..self.batches_read {
287 reader.read().await?;
288 }
289 self.state = SpillReaderState::Reader { reader };
290 }
291
292 if let SpillReaderState::Reader { reader } = &mut self.state {
293 Ok(reader)
294 } else {
295 unreachable!()
296 }
297 }
298
299 async fn read(&mut self) -> Result<Option<RecordBatch>, DataFusionError> {
300 let maybe_data = self.wait_for_more_data().await?;
301
302 if let Some(batches) = maybe_data {
303 if self.batches_read < batches.len() {
304 let batch = batches[self.batches_read].clone();
305 self.batches_read += 1;
306 Ok(Some(batch))
307 } else {
308 Ok(None)
309 }
310 } else {
311 let reader = self.get_reader().await?;
312 let batch = reader.read().await?;
313 if batch.is_some() {
314 self.batches_read += 1;
315 }
316 Ok(batch)
317 }
318 }
319}
320
321pub struct SpillSender {
326 memory_limit: usize,
327 schema: Arc<Schema>,
328 path: PathBuf,
329 state: SpillState,
330 status_sender: tokio::sync::watch::Sender<WriteStatus>,
331}
332
333enum SpillState {
334 Buffering {
335 batches: Vec<RecordBatch>,
336 memory_accumulator: MemoryAccumulator,
337 },
338 Spilling {
339 writer: AsyncStreamWriter,
340 batches_written: usize,
341 },
342 Finished {
343 batches: Option<Arc<[RecordBatch]>>,
344 batches_written: usize,
345 },
346 Errored {
347 error: Arc<Mutex<SpillError>>,
348 },
349}
350
351impl Default for SpillState {
352 fn default() -> Self {
353 Self::Buffering {
354 batches: Vec::new(),
355 memory_accumulator: MemoryAccumulator::default(),
356 }
357 }
358}
359
360#[derive(Clone, Debug, Default)]
361struct WriteStatus {
362 error: Option<Arc<Mutex<SpillError>>>,
363 finished: bool,
364 data_location: DataLocation,
365}
366
367impl WriteStatus {
368 fn batches_written(&self) -> usize {
369 match &self.data_location {
370 DataLocation::Buffered { batches } => batches.len(),
371 DataLocation::Spilled {
372 batches_written, ..
373 } => *batches_written,
374 }
375 }
376}
377
378#[derive(Clone, Debug)]
379enum DataLocation {
380 Buffered { batches: Arc<[RecordBatch]> },
381 Spilled { batches_written: usize },
382}
383
384impl Default for DataLocation {
385 fn default() -> Self {
386 Self::Buffered {
387 batches: Arc::new([]),
388 }
389 }
390}
391
392#[derive(Debug)]
396enum SpillError {
397 Original(DataFusionError),
398 Copy(DataFusionError),
399}
400
401impl From<DataFusionError> for SpillError {
402 fn from(err: DataFusionError) -> Self {
403 Self::Original(err)
404 }
405}
406
407impl From<&mut SpillError> for DataFusionError {
408 fn from(err: &mut SpillError) -> Self {
409 match err {
410 SpillError::Original(inner) => {
411 let copy = Self::Execution(inner.to_string());
412 let original = std::mem::replace(err, SpillError::Copy(copy));
413 if let SpillError::Original(inner) = original {
414 inner
415 } else {
416 unreachable!()
417 }
418 }
419 SpillError::Copy(Self::Execution(message)) => Self::Execution(message.clone()),
420 _ => unreachable!(),
421 }
422 }
423}
424
425impl From<&SpillState> for WriteStatus {
426 fn from(state: &SpillState) -> Self {
427 match state {
428 SpillState::Buffering { batches, .. } => Self {
429 finished: false,
430 data_location: DataLocation::Buffered {
431 batches: batches.clone().into(),
432 },
433 error: None,
434 },
435 SpillState::Spilling {
436 batches_written, ..
437 } => Self {
438 finished: false,
439 data_location: DataLocation::Spilled {
440 batches_written: *batches_written,
441 },
442 error: None,
443 },
444 SpillState::Finished {
445 batches_written,
446 batches,
447 } => {
448 let data_location = if let Some(batches) = batches {
449 DataLocation::Buffered {
450 batches: batches.clone(),
451 }
452 } else {
453 DataLocation::Spilled {
454 batches_written: *batches_written,
455 }
456 };
457 Self {
458 finished: true,
459 data_location,
460 error: None,
461 }
462 }
463 SpillState::Errored { error } => Self {
464 finished: true,
465 data_location: DataLocation::default(), error: Some(error.clone()),
467 },
468 }
469 }
470}
471
472impl SpillSender {
473 pub async fn write(&mut self, batch: RecordBatch) -> Result<(), DataFusionError> {
481 if let SpillState::Finished { .. } = self.state {
482 return Err(DataFusionError::Execution(
483 "Spill has already been finished".to_string(),
484 ));
485 }
486
487 if let SpillState::Errored { .. } = &self.state {
488 return Err(DataFusionError::Execution(
489 "Spill has sent an error".to_string(),
490 ));
491 }
492
493 let (writer, batches_written) = match &mut self.state {
494 SpillState::Buffering {
495 batches,
496 memory_accumulator,
497 } => {
498 memory_accumulator.record_batch(&batch);
499
500 if memory_accumulator.total() > self.memory_limit {
501 let writer =
502 AsyncStreamWriter::open(self.path.clone(), self.schema.clone()).await?;
503 let batches_written = batches.len();
504 for batch in batches.drain(..) {
505 writer.write(batch).await?;
506 }
507 self.state = SpillState::Spilling {
508 writer,
509 batches_written,
510 };
511 if let SpillState::Spilling {
512 writer,
513 batches_written,
514 } = &mut self.state
515 {
516 (writer, batches_written)
517 } else {
518 unreachable!()
519 }
520 } else {
521 batches.push(batch);
522 self.status_sender
523 .send_replace(WriteStatus::from(&self.state));
524 return Ok(());
525 }
526 }
527 SpillState::Spilling {
528 writer,
529 batches_written,
530 } => (writer, batches_written),
531 _ => unreachable!(),
532 };
533
534 writer.write(batch).await?;
535 *batches_written += 1;
536 self.status_sender
537 .send_replace(WriteStatus::from(&self.state));
538
539 Ok(())
540 }
541
542 pub fn send_error(&mut self, err: DataFusionError) {
545 let error = Arc::new(Mutex::new(err.into()));
546 self.state = SpillState::Errored { error };
547 self.status_sender
548 .send_replace(WriteStatus::from(&self.state));
549 }
550
551 pub async fn finish(&mut self) -> Result<(), DataFusionError> {
554 let tmp_state = SpillState::Finished {
558 batches_written: 0,
559 batches: None,
560 };
561 match std::mem::replace(&mut self.state, tmp_state) {
562 SpillState::Buffering { batches, .. } => {
563 let batches_written = batches.len();
564 self.state = SpillState::Finished {
565 batches_written,
566 batches: Some(batches.into()),
567 };
568 self.status_sender
569 .send_replace(WriteStatus::from(&self.state));
570 }
571 SpillState::Spilling {
572 writer,
573 batches_written,
574 } => {
575 writer.finish().await?;
576 self.state = SpillState::Finished {
577 batches_written,
578 batches: None,
579 };
580 self.status_sender
581 .send_replace(WriteStatus::from(&self.state));
582 }
583 SpillState::Finished { .. } => {
584 return Err(DataFusionError::Execution(
585 "Spill has already been finished".to_string(),
586 ));
587 }
588 SpillState::Errored { .. } => {
589 return Err(DataFusionError::Execution(
590 "Spill has sent an error".to_string(),
591 ));
592 }
593 };
594
595 Ok(())
596 }
597}
598
599struct AsyncStreamWriter {
602 writer: Arc<Mutex<StreamWriter<BufWriter<std::fs::File>>>>,
603}
604
605impl AsyncStreamWriter {
606 pub async fn open(path: PathBuf, schema: Arc<Schema>) -> Result<Self, ArrowError> {
607 let writer = tokio::task::spawn_blocking(move || {
608 let file = std::fs::File::create(&path).map_err(ArrowError::from)?;
609 let writer = BufWriter::new(file);
610 StreamWriter::try_new(writer, &schema)
611 })
612 .await
613 .unwrap()?;
614 let writer = Arc::new(Mutex::new(writer));
615 Ok(Self { writer })
616 }
617
618 pub async fn write(&self, batch: RecordBatch) -> Result<(), ArrowError> {
619 let writer = self.writer.clone();
620 tokio::task::spawn_blocking(move || {
621 let mut writer = writer.lock().unwrap();
622 writer.write(&batch)?;
623 writer.flush()
624 })
625 .await
626 .unwrap()
627 }
628
629 pub async fn finish(self) -> Result<(), ArrowError> {
630 let writer = self.writer.clone();
631 tokio::task::spawn_blocking(move || {
632 let mut writer = writer.lock().unwrap();
633 writer.finish()
634 })
635 .await
636 .unwrap()
637 }
638}
639
640struct AsyncStreamReader {
641 reader: Arc<Mutex<StreamReader<BufReader<std::fs::File>>>>,
642}
643
644impl AsyncStreamReader {
645 pub async fn open(path: PathBuf) -> Result<Self, ArrowError> {
646 let reader = tokio::task::spawn_blocking(move || {
647 let file = std::fs::File::open(&path).map_err(ArrowError::from)?;
648 let reader = BufReader::new(file);
649 StreamReader::try_new(reader, None)
650 })
651 .await
652 .unwrap()?;
653 let reader = Arc::new(Mutex::new(reader));
654 Ok(Self { reader })
655 }
656
657 pub async fn read(&self) -> Result<Option<RecordBatch>, ArrowError> {
658 let reader = self.reader.clone();
659 tokio::task::spawn_blocking(move || {
660 let mut reader = reader.lock().unwrap();
661 reader.next()
662 })
663 .await
664 .unwrap()
665 .transpose()
666 }
667}
668
669#[cfg(test)]
670mod tests {
671 use arrow_array::Int32Array;
672 use arrow_schema::{DataType, Field};
673 use futures::{StreamExt, TryStreamExt, poll};
674 use lance_core::utils::tempfile::{TempStdFile, TempStdPath};
675
676 use super::*;
677
678 #[tokio::test]
679 async fn test_spill() {
680 let schema = Arc::new(Schema::new(vec![Field::new("a", DataType::Int32, false)]));
681 let batches = [
682 RecordBatch::try_new(
683 schema.clone(),
684 vec![Arc::new(Int32Array::from(vec![1, 2, 3]))],
685 )
686 .unwrap(),
687 RecordBatch::try_new(
688 schema.clone(),
689 vec![Arc::new(Int32Array::from(vec![4, 5, 6]))],
690 )
691 .unwrap(),
692 ];
693
694 let path = TempStdFile::default();
696 let (mut spill, receiver) = create_replay_spill(path.to_owned(), schema.clone(), 0);
697
698 let mut stream_before = receiver.read();
700 let mut stream_before_next = stream_before.next();
701 let poll_res = poll!(&mut stream_before_next);
702 assert!(poll_res.is_pending());
703
704 spill.write(batches[0].clone()).await.unwrap();
706 let stream_before_batch1 = stream_before_next
707 .await
708 .expect("Expected a batch")
709 .expect("Expected no error");
710 assert_eq!(&stream_before_batch1, &batches[0]);
711 let mut stream_before_next = stream_before.next();
712 let poll_res = poll!(&mut stream_before_next);
713 assert!(poll_res.is_pending());
714
715 let mut stream_during = receiver.read();
718 let stream_during_batch1 = stream_during
719 .next()
720 .await
721 .expect("Expected a batch")
722 .expect("Expected no error");
723 assert_eq!(&stream_during_batch1, &batches[0]);
724 let mut stream_during_next = stream_during.next();
725 let poll_res = poll!(&mut stream_during_next);
726 assert!(poll_res.is_pending());
727
728 spill.write(batches[1].clone()).await.unwrap();
731 spill.finish().await.unwrap();
732
733 let stream_before_batch2 = stream_before_next
734 .await
735 .expect("Expected a batch")
736 .expect("Expected no error");
737 assert_eq!(&stream_before_batch2, &batches[1]);
738 assert!(stream_before.next().await.is_none());
739
740 let stream_during_batch2 = stream_during_next
741 .await
742 .expect("Expected a batch")
743 .expect("Expected no error");
744 assert_eq!(&stream_during_batch2, &batches[1]);
745 assert!(stream_during.next().await.is_none());
746
747 let stream_after = receiver.read();
749 let stream_after_batches = stream_after.try_collect::<Vec<_>>().await.unwrap();
750 assert_eq!(&stream_after_batches, &batches);
751
752 std::fs::remove_file(path).unwrap();
753 }
754
755 #[tokio::test]
756 async fn test_spill_error() {
757 let schema = Arc::new(Schema::new(vec![Field::new("a", DataType::Int32, false)]));
759 let path = TempStdFile::default();
760 let (mut spill, receiver) =
761 create_replay_spill(path.as_ref().to_owned(), schema.clone(), 0);
762 let batch = RecordBatch::try_new(
763 schema.clone(),
764 vec![Arc::new(Int32Array::from(vec![1, 2, 3]))],
765 )
766 .unwrap();
767
768 spill.write(batch.clone()).await.unwrap();
769
770 let mut stream = receiver.read();
771 let stream_batch = stream
772 .next()
773 .await
774 .expect("Expected a batch")
775 .expect("Expected no error");
776 assert_eq!(&stream_batch, &batch);
777
778 spill.send_error(DataFusionError::ResourcesExhausted("🥱".into()));
779 let stream_error = stream
780 .next()
781 .await
782 .expect("Expected an error")
783 .expect_err("Expected an error");
784 assert!(matches!(
785 stream_error,
786 DataFusionError::ResourcesExhausted(message) if message == "🥱"
787 ));
788
789 let err = spill.write(batch).await;
791 assert!(matches!(
792 err,
793 Err(DataFusionError::Execution(message)) if message == "Spill has sent an error"
794 ));
795
796 let err = spill.finish().await;
798 assert!(matches!(
799 err,
800 Err(DataFusionError::Execution(message)) if message == "Spill has sent an error"
801 ));
802
803 let mut stream = receiver.read();
805 let stream_error = stream
806 .next()
807 .await
808 .expect("Expected an error")
809 .expect_err("Expected an error");
810 assert!(matches!(
811 stream_error,
812 DataFusionError::Execution(message) if message.contains("🥱")
813 ));
814
815 std::fs::remove_file(path).unwrap();
816 }
817
818 #[tokio::test]
819 async fn test_spill_buffered() {
820 let schema = Arc::new(Schema::new(vec![Field::new("a", DataType::Int32, false)]));
821 let path = TempStdPath::default();
822 let memory_limit = 1024 * 1024; let (mut spill, receiver) = create_replay_spill(path.clone(), schema.clone(), memory_limit);
824
825 let batch = RecordBatch::try_new(
827 schema.clone(),
828 vec![Arc::new(Int32Array::from(vec![1; (512 * 1024) / 4]))],
829 )
830 .unwrap();
831 spill.write(batch.clone()).await.unwrap();
832 assert!(!std::fs::exists(&path).unwrap());
833
834 spill.finish().await.unwrap();
835 assert!(!std::fs::exists(&path).unwrap());
836
837 let mut stream = receiver.read();
838 let stream_batch = stream
839 .next()
840 .await
841 .expect("Expected a batch")
842 .expect("Expected no error");
843 assert_eq!(&stream_batch, &batch);
844
845 assert!(!std::fs::exists(&path).unwrap());
846 }
847
848 #[tokio::test]
849 async fn test_spill_buffered_transition() {
850 let schema = Arc::new(Schema::new(vec![Field::new("a", DataType::Int32, false)]));
852 let path = TempStdPath::default();
853 let memory_limit = 1024 * 1024; let (mut spill, receiver) = create_replay_spill(path.clone(), schema.clone(), memory_limit);
855
856 let batch = RecordBatch::try_new(
858 schema.clone(),
859 vec![Arc::new(Int32Array::from(vec![1; (768 * 1024) / 4]))],
860 )
861 .unwrap();
862 spill.write(batch.clone()).await.unwrap();
863 assert!(!std::fs::exists(&path).unwrap());
864
865 let mut stream = receiver.read();
866 let stream_batch = stream
867 .next()
868 .await
869 .expect("Expected a batch")
870 .expect("Expected no error");
871 assert_eq!(&stream_batch, &batch);
872 assert!(!std::fs::exists(&path).unwrap());
873
874 let batch = RecordBatch::try_new(
876 schema.clone(),
877 vec![Arc::new(Int32Array::from(vec![1; (512 * 1024) / 4]))],
878 )
879 .unwrap();
880 spill.write(batch.clone()).await.unwrap();
881 assert!(std::fs::exists(&path).unwrap());
882
883 let stream_batch = stream
884 .next()
885 .await
886 .expect("Expected a batch")
887 .expect("Expected no error");
888 assert_eq!(&stream_batch, &batch);
889 assert!(std::fs::exists(&path).unwrap());
890
891 spill.finish().await.unwrap();
892
893 assert!(stream.next().await.is_none());
894
895 std::fs::remove_file(path).unwrap();
896 }
897}