1use std::collections::HashMap;
18use std::future::Future;
19use std::mem;
20use std::pin::Pin;
21use std::sync::Arc;
22use std::sync::atomic::{AtomicUsize, Ordering};
23use std::task::{Context, Poll};
24use std::time::Instant;
25
26use bytes::Bytes;
27use common_telemetry::debug;
28use common_time::Timestamp;
29use datatypes::arrow::array::{
30 ArrayRef, BinaryArray, TimestampMicrosecondArray, TimestampMillisecondArray,
31 TimestampNanosecondArray, TimestampSecondArray, UInt32Array,
32};
33use datatypes::arrow::compute::{max, min};
34use datatypes::arrow::datatypes::{DataType, SchemaRef, TimeUnit};
35use datatypes::arrow::record_batch::RecordBatch;
36use datatypes::extension::json::is_json2_extension_type;
37use object_store::{FuturesAsyncWriter, ObjectStore};
38use parquet::arrow::AsyncArrowWriter;
39use parquet::arrow::async_writer::AsyncFileWriter;
40use parquet::basic::{Compression, Encoding, ZstdLevel};
41use parquet::file::metadata::{FileMetaData, KeyValue, ParquetMetaData};
42use parquet::file::properties::WriterProperties;
43use smallvec::smallvec;
44use snafu::{OptionExt, ResultExt, ensure};
45use store_api::metadata::RegionMetadataRef;
46use store_api::storage::{FileId, SequenceNumber};
47use tokio::io::AsyncWrite;
48use tokio_util::compat::{Compat, FuturesAsyncWriteCompatExt};
49
50use crate::access_layer::{FilePathProvider, Metrics, SstInfoArray, TempFileCleaner};
51use crate::config::{IndexBuildMode, IndexConfig};
52use crate::error::{
53 InvalidMetadataSnafu, InvalidRecordBatchSnafu, OpenDalSnafu, Result, UnexpectedSnafu,
54 WriteParquetSnafu,
55};
56use crate::read::FlatSource;
57use crate::sst::file::RegionFileId;
58use crate::sst::index::{IndexOutput, Indexer, IndexerBuilder};
59use crate::sst::parquet::flat_format::{
60 FlatWriteFormat, primary_key_column_index, time_index_column_index,
61};
62use crate::sst::parquet::format::{PrimaryKeyArray, PrimaryKeyWriteFormat};
63use crate::sst::parquet::parallel_writer::ParallelArrowWriter;
64use crate::sst::parquet::{
65 COLUMN_INDEX_TRUNCATE_LENGTH, PARQUET_METADATA_KEY, SstInfo, WriteOptions,
66 apply_column_compression, apply_float_field_encoding, apply_internal_column_encoding,
67};
68use crate::sst::{
69 DEFAULT_WRITE_CONCURRENCY, FlatSchemaOptions, SeriesEstimator, maybe_wrap_schema,
70};
71
72enum FlatBatchConverter {
74 Flat(FlatWriteFormat),
76 PrimaryKey {
78 format: PrimaryKeyWriteFormat,
79 num_fields: usize,
80 },
81}
82
83impl FlatBatchConverter {
84 fn convert_batch(&self, batch: &RecordBatch) -> Result<RecordBatch> {
85 match self {
86 FlatBatchConverter::Flat(f) => f.convert_batch(batch),
87 FlatBatchConverter::PrimaryKey { format, num_fields } => {
88 format.convert_flat_batch(batch, *num_fields)
89 }
90 }
91 }
92}
93
94enum SeriesBoundarySplit {
96 Continue(RecordBatch),
98 Split {
100 current_file_tail: Option<RecordBatch>,
102 next_file_head: RecordBatch,
104 },
105}
106
107fn encoded_primary_keys(batch: &RecordBatch) -> Result<(&UInt32Array, &BinaryArray)> {
109 let column = batch.column(primary_key_column_index(batch.num_columns()));
110 let primary_keys = column
111 .as_any()
112 .downcast_ref::<PrimaryKeyArray>()
113 .with_context(|| InvalidRecordBatchSnafu {
114 reason: format!(
115 "expected dictionary primary key column, got {:?}",
116 column.data_type()
117 ),
118 })?;
119 let values = primary_keys
120 .values()
121 .as_any()
122 .downcast_ref::<BinaryArray>()
123 .with_context(|| InvalidRecordBatchSnafu {
124 reason: format!(
125 "expected binary primary key values, got {:?}",
126 primary_keys.values().data_type()
127 ),
128 })?;
129 Ok((primary_keys.keys(), values))
130}
131
132fn split_at_next_series(
141 batch: RecordBatch,
142 current_primary_key: &[u8],
143) -> Result<SeriesBoundarySplit> {
144 let (keys, values) = encoded_primary_keys(&batch)?;
145 let Some(offset) = (0..batch.num_rows())
146 .find(|&row| values.value(keys.value(row) as usize) != current_primary_key)
147 else {
148 return Ok(SeriesBoundarySplit::Continue(batch));
149 };
150
151 let next_file_head = batch.slice(offset, batch.num_rows() - offset);
152 let current_file_tail = (offset > 0).then(|| batch.slice(0, offset));
153 Ok(SeriesBoundarySplit::Split {
154 current_file_tail,
155 next_file_head,
156 })
157}
158
159enum SstArrowWriter<W> {
161 Serial(AsyncArrowWriter<W>),
163 Parallel(ParallelArrowWriter<W>),
165}
166
167impl<W: AsyncFileWriter> SstArrowWriter<W> {
168 async fn write(&mut self, batch: &RecordBatch) -> parquet::errors::Result<()> {
169 match self {
170 SstArrowWriter::Serial(writer) => writer.write(batch).await,
171 SstArrowWriter::Parallel(writer) => writer.write(batch).await,
172 }
173 }
174
175 async fn flush(&mut self) -> parquet::errors::Result<()> {
176 match self {
177 SstArrowWriter::Serial(writer) => writer.flush().await,
178 SstArrowWriter::Parallel(writer) => writer.flush().await,
179 }
180 }
181
182 async fn close(self) -> parquet::errors::Result<ParquetMetaData> {
183 match self {
184 SstArrowWriter::Serial(writer) => writer.close().await,
185 SstArrowWriter::Parallel(writer) => writer.close().await,
186 }
187 }
188
189 async fn abort(self) {
190 if let SstArrowWriter::Parallel(writer) = self {
191 writer.abort().await;
192 }
193 }
194}
195
196#[derive(Clone, Copy)]
199pub(crate) struct SstWriteLimits {
200 pub(crate) working_bytes: usize,
201 pub(crate) metadata_bytes: usize,
202}
203
204#[derive(Default)]
206struct WriteMemory {
207 limits: Option<SstWriteLimits>,
208 completed: usize,
209 current: usize,
210 row_groups: usize,
211}
212
213impl WriteMemory {
214 fn check(&self, bytes: usize) -> Result<()> {
215 if let Some(limits) = self.limits {
216 ensure!(
217 bytes <= limits.metadata_bytes,
218 crate::error::SstWriteMemoryExceededSnafu {
219 kind: "metadata",
220 bytes,
221 limit: limits.metadata_bytes,
222 }
223 );
224 }
225 Ok(())
226 }
227}
228
229pub struct ParquetWriter<'a, F: WriterFactory, I: IndexerBuilder, P: FilePathProvider> {
231 path_provider: P,
233 writer: Option<SstArrowWriter<SizeAwareWriter<F::Writer>>>,
234 current_file: FileId,
236 writer_factory: F,
237 metadata: RegionMetadataRef,
239 index_config: IndexConfig,
241 indexer_builder: I,
243 current_indexer: Option<Indexer>,
245 bytes_written: Arc<AtomicUsize>,
246 file_cleaner: Option<TempFileCleaner>,
248 metrics: &'a mut Metrics,
250 write_guard: Option<Arc<dyn SstWriteGuard>>,
251 memory: WriteMemory,
252}
253
254#[async_trait::async_trait]
256pub trait SstWriteGuard: Send + Sync {
257 async fn before_create(&self, file_id: RegionFileId) -> Result<()>;
258}
259
260pub(crate) struct SstWriteContext {
262 pub(crate) guard: Arc<dyn SstWriteGuard>,
263 pub(crate) limits: SstWriteLimits,
264}
265
266pub trait WriterFactory {
267 type Writer: AsyncWrite + Send + Unpin;
268 fn create(
269 &mut self,
270 file_path: &str,
271 write_buffer_size: usize,
272 ) -> impl Future<Output = Result<Self::Writer>>;
273}
274
275pub struct ObjectStoreWriterFactory {
276 object_store: ObjectStore,
277 concurrency: usize,
278}
279
280impl WriterFactory for ObjectStoreWriterFactory {
281 type Writer = Compat<FuturesAsyncWriter>;
282
283 async fn create(&mut self, file_path: &str, write_buffer_size: usize) -> Result<Self::Writer> {
284 self.object_store
285 .writer_with(file_path)
286 .chunk(write_buffer_size)
287 .concurrent(self.concurrency)
288 .await
289 .map(|v| v.into_futures_async_write().compat_write())
290 .context(OpenDalSnafu)
291 }
292}
293
294impl<'a, I, P> ParquetWriter<'a, ObjectStoreWriterFactory, I, P>
295where
296 P: FilePathProvider,
297 I: IndexerBuilder,
298{
299 pub(crate) fn with_write_concurrency(mut self, concurrency: usize) -> Self {
300 self.writer_factory.concurrency = concurrency;
301 self
302 }
303
304 pub async fn new_with_object_store(
305 object_store: ObjectStore,
306 metadata: RegionMetadataRef,
307 index_config: IndexConfig,
308 indexer_builder: I,
309 path_provider: P,
310 metrics: &'a mut Metrics,
311 ) -> ParquetWriter<'a, ObjectStoreWriterFactory, I, P> {
312 ParquetWriter::new(
313 ObjectStoreWriterFactory {
314 object_store,
315 concurrency: DEFAULT_WRITE_CONCURRENCY,
316 },
317 metadata,
318 index_config,
319 indexer_builder,
320 path_provider,
321 metrics,
322 )
323 .await
324 }
325
326 pub(crate) fn with_file_cleaner(mut self, cleaner: TempFileCleaner) -> Self {
327 self.file_cleaner = Some(cleaner);
328 self
329 }
330}
331
332impl<'a, F, I, P> ParquetWriter<'a, F, I, P>
333where
334 F: WriterFactory,
335 I: IndexerBuilder,
336 P: FilePathProvider,
337{
338 pub async fn new(
340 factory: F,
341 metadata: RegionMetadataRef,
342 index_config: IndexConfig,
343 indexer_builder: I,
344 path_provider: P,
345 metrics: &'a mut Metrics,
346 ) -> ParquetWriter<'a, F, I, P> {
347 let init_file = FileId::random();
348
349 ParquetWriter {
350 path_provider,
351 writer: None,
352 current_file: init_file,
353 writer_factory: factory,
354 metadata,
355 index_config,
356 indexer_builder,
357 current_indexer: None,
359 memory: WriteMemory::default(),
360 bytes_written: Arc::new(AtomicUsize::new(0)),
361 file_cleaner: None,
362 metrics,
363 write_guard: None,
364 }
365 }
366
367 pub(crate) fn with_write_guard(mut self, guard: Option<Arc<dyn SstWriteGuard>>) -> Self {
368 self.write_guard = guard;
369 self
370 }
371
372 pub(crate) fn with_memory_limits(mut self, limits: Option<SstWriteLimits>) -> Self {
373 self.memory.limits = limits;
374 self
375 }
376
377 async fn check_memory(&mut self) -> Result<()> {
381 let Some(limits) = self.memory.limits else {
382 return Ok(());
383 };
384 if let Some(SstArrowWriter::Serial(writer)) = &mut self.writer {
385 ensure!(
386 writer.memory_size() <= limits.working_bytes,
387 crate::error::SstWriteMemoryExceededSnafu {
388 kind: "encoder",
389 bytes: writer.memory_size(),
390 limit: limits.working_bytes,
391 }
392 );
393 for group in &writer.flushed_row_groups()[self.memory.row_groups..] {
394 let file = FileMetaData::new(1, 0, None, None, group.schema_descr_ptr(), None);
395 let metadata = ParquetMetaData::new(file, vec![group.clone()]);
398 self.memory.current = self.memory.current.saturating_add(metadata.memory_size());
399 }
400 self.memory.row_groups = writer.flushed_row_groups().len();
401 }
402 self.memory
403 .check(self.memory.completed.saturating_add(self.memory.current))
404 }
405
406 async fn finish_current_file(
408 &mut self,
409 ssts: &mut SstInfoArray,
410 stats: &mut SourceStats,
411 ) -> Result<()> {
412 if let Some(mut current_writer) = mem::take(&mut self.writer) {
414 let mut stats = mem::take(stats);
415 assert!(stats.num_rows > 0);
417
418 debug!(
419 "Finishing current file {}, file size: {}, num rows: {}",
420 self.current_file,
421 self.bytes_written.load(Ordering::Relaxed),
422 stats.num_rows
423 );
424
425 let mut index_output = IndexOutput::default();
428 match self.index_config.build_mode {
429 IndexBuildMode::Sync => {
430 index_output = self.current_indexer.as_mut().unwrap().finish().await;
431 }
432 IndexBuildMode::Async => {
433 debug!(
434 "Index for file {} will be built asynchronously later",
435 self.current_file
436 );
437 }
438 }
439 current_writer.flush().await.context(WriteParquetSnafu)?;
440
441 let parquet_metadata = current_writer.close().await.context(WriteParquetSnafu)?;
442 let file_size = self.bytes_written.load(Ordering::Relaxed) as u64;
443 if self.memory.limits.is_some() {
444 self.memory.completed = self.memory.completed.saturating_add(
445 parquet_metadata.memory_size().saturating_mul(2)
446 + std::mem::size_of::<SstInfo>(),
447 );
448 self.memory.check(self.memory.completed)?;
449 self.memory.current = 0;
450 self.memory.row_groups = 0;
451 }
452
453 let time_range = stats.time_range.unwrap();
455
456 let max_row_group_uncompressed_size: u64 = parquet_metadata
457 .row_groups()
458 .iter()
459 .map(|rg| {
460 rg.columns()
461 .iter()
462 .map(|c| c.uncompressed_size() as u64)
463 .sum::<u64>()
464 })
465 .max()
466 .unwrap_or(0);
467 let num_series = stats.series_estimator.finish();
468 ssts.push(SstInfo {
469 file_id: self.current_file,
470 time_range,
471 file_size,
472 max_row_group_uncompressed_size,
473 num_rows: stats.num_rows,
474 num_row_groups: parquet_metadata.num_row_groups() as u64,
475 file_metadata: Some(Arc::new(parquet_metadata)),
476 index_metadata: index_output,
477 num_series,
478 });
479 self.current_file = FileId::random();
480 self.bytes_written.store(0, Ordering::Relaxed)
481 };
482
483 Ok(())
484 }
485
486 pub async fn write_all_flat(
497 &mut self,
498 source: FlatSource,
499 override_sequence: Option<SequenceNumber>,
500 opts: &WriteOptions,
501 ) -> Result<SstInfoArray> {
502 let mut options = FlatSchemaOptions::from_encoding(self.metadata.primary_key_encoding);
503
504 if source.schema().fields().iter().any(is_json2_extension_type) {
505 options.concretized_json_types = source
506 .schema()
507 .fields()
508 .iter()
509 .filter(|&field| is_json2_extension_type(field))
510 .map(|field| (field.name().clone(), field.data_type().clone()))
511 .collect::<HashMap<_, _>>();
512 }
513
514 let converter = FlatBatchConverter::Flat(
515 FlatWriteFormat::new(self.metadata.clone(), &options)
516 .with_override_sequence(override_sequence),
517 );
518 let res = self.write_all_flat_inner(source, &converter, opts).await;
519 if res.is_err() {
520 self.abort_current_file().await;
521 }
522 res
523 }
524
525 pub async fn write_all_flat_as_primary_key(
532 &mut self,
533 source: FlatSource,
534 override_sequence: Option<SequenceNumber>,
535 opts: &WriteOptions,
536 ) -> Result<SstInfoArray> {
537 let num_fields = self.metadata.field_columns().count();
538 let converter = FlatBatchConverter::PrimaryKey {
539 format: PrimaryKeyWriteFormat::new(self.metadata.clone())
540 .with_override_sequence(override_sequence),
541 num_fields,
542 };
543 let res = self.write_all_flat_inner(source, &converter, opts).await;
544 if res.is_err() {
545 self.abort_current_file().await;
546 }
547 res
548 }
549
550 async fn write_all_flat_inner(
551 &mut self,
552 mut source: FlatSource,
553 converter: &FlatBatchConverter,
554 opts: &WriteOptions,
555 ) -> Result<SstInfoArray> {
556 let mut results = smallvec![];
557 let mut stats = SourceStats::default();
558
559 loop {
560 let start = Instant::now();
561 let batch = match source.next_batch().await {
562 Ok(Some(batch)) => batch,
563 Ok(None) => break,
564 Err(e) => {
565 self.abort_current_indexer().await;
566 return Err(e);
567 }
568 };
569 self.metrics.iter_source += start.elapsed();
570
571 if self.metadata.primary_key.is_empty() {
572 self.append_flat_batch(&batch, converter, opts, &mut stats)
573 .await?;
574 if self.exceeds_max_file_size(opts) {
575 self.finish_current_file(&mut results, &mut stats).await?;
576 }
577 } else if self.exceeds_max_file_size(opts)
578 && let Some(current_primary_key) = stats.last_primary_key.as_deref()
579 {
580 let series_split = match split_at_next_series(batch, current_primary_key) {
581 Ok(series_split) => series_split,
582 Err(e) => {
583 self.abort_current_indexer().await;
584 return Err(e);
585 }
586 };
587 match series_split {
588 SeriesBoundarySplit::Continue(batch) => {
589 self.append_flat_batch(&batch, converter, opts, &mut stats)
590 .await?;
591 }
592 SeriesBoundarySplit::Split {
593 current_file_tail,
594 next_file_head,
595 } => {
596 if let Some(tail) = current_file_tail {
597 self.append_flat_batch(&tail, converter, opts, &mut stats)
598 .await?;
599 }
600 self.finish_current_file(&mut results, &mut stats).await?;
601 self.append_flat_batch(&next_file_head, converter, opts, &mut stats)
602 .await?;
603 }
604 }
605 } else {
606 self.append_flat_batch(&batch, converter, opts, &mut stats)
607 .await?;
608 }
609 }
610
611 self.finish_current_file(&mut results, &mut stats).await?;
612
613 Ok(results)
615 }
616
617 async fn append_flat_batch(
618 &mut self,
619 batch: &RecordBatch,
620 converter: &FlatBatchConverter,
621 opts: &WriteOptions,
622 stats: &mut SourceStats,
623 ) -> Result<()> {
624 let result = async {
625 let arrow_batch = converter.convert_batch(batch)?;
626 let start = Instant::now();
627 self.maybe_init_writer(arrow_batch.schema_ref(), opts)
628 .await?
629 .write(&arrow_batch)
630 .await
631 .context(WriteParquetSnafu)?;
632 self.metrics.write_batch += start.elapsed();
633
634 stats.update_flat(batch)?;
635 if matches!(self.index_config.build_mode, IndexBuildMode::Sync) {
636 let start = Instant::now();
637 self.current_indexer
639 .as_mut()
640 .unwrap()
641 .update_flat(batch)
642 .await;
643 self.metrics.update_index += start.elapsed();
644 }
645 Ok(())
646 }
647 .await;
648
649 let result = match result {
650 Ok(()) => self.check_memory().await,
651 Err(error) => Err(error),
652 };
653 if result.is_err() {
654 self.abort_current_indexer().await;
655 }
656 result
657 }
658
659 fn exceeds_max_file_size(&self, opts: &WriteOptions) -> bool {
660 opts.max_file_size
661 .is_some_and(|max_size| self.bytes_written.load(Ordering::Relaxed) >= max_size)
662 }
663
664 async fn abort_current_file(&mut self) {
666 if let Some(writer) = self.writer.take() {
667 writer.abort().await;
668 }
669 if let Some(cleaner) = &self.file_cleaner {
670 cleaner.clean_by_file_id(self.current_file).await;
671 }
672 }
673
674 async fn abort_current_indexer(&mut self) {
675 if let Some(indexer) = &mut self.current_indexer {
676 indexer.abort().await;
677 }
678 }
679
680 async fn maybe_init_writer(
681 &mut self,
682 schema: &SchemaRef,
683 opts: &WriteOptions,
684 ) -> Result<&mut SstArrowWriter<SizeAwareWriter<F::Writer>>> {
685 if let Some(ref mut w) = self.writer {
686 Ok(w)
687 } else {
688 let json = self.metadata.to_json().context(InvalidMetadataSnafu)?;
689 let key_value_meta = KeyValue::new(PARQUET_METADATA_KEY.to_string(), json);
690
691 let props_builder = WriterProperties::builder()
693 .set_key_value_metadata(Some(vec![key_value_meta]))
694 .set_compression(Compression::ZSTD(ZstdLevel::default()))
695 .set_encoding(Encoding::PLAIN)
696 .set_max_row_group_row_count(Some(opts.row_group_size))
697 .set_column_index_truncate_length(COLUMN_INDEX_TRUNCATE_LENGTH)
698 .set_statistics_truncate_length(None);
699 let props_builder = apply_internal_column_encoding(props_builder, &self.metadata);
700 let props_builder = apply_float_field_encoding(
701 props_builder,
702 &self.metadata,
703 opts.float_field_encoding,
704 );
705 let props_builder = apply_column_compression(props_builder, &self.metadata)?;
706 let writer_props = props_builder.build();
707
708 let sst_file_path = self.path_provider.build_sst_file_path(RegionFileId::new(
709 self.metadata.region_id,
710 self.current_file,
711 ));
712 if let Some(guard) = &self.write_guard {
713 guard
714 .before_create(RegionFileId::new(
715 self.metadata.region_id,
716 self.current_file,
717 ))
718 .await?;
719 }
720 let writer = SizeAwareWriter::new(
721 self.writer_factory
722 .create(&sst_file_path, opts.write_buffer_size.as_bytes() as usize)
723 .await?,
724 self.bytes_written.clone(),
725 );
726 let schema = maybe_wrap_schema(schema)?;
727 let arrow_writer = if self.memory.limits.is_none()
728 && opts.encode_parallelism > 1
729 && ParallelArrowWriter::<SizeAwareWriter<F::Writer>>::supports(&writer_props)
730 {
731 SstArrowWriter::Parallel(
732 ParallelArrowWriter::try_new(
733 writer,
734 schema,
735 writer_props,
736 opts.encode_parallelism,
737 opts.encode_runtime,
738 )
739 .context(WriteParquetSnafu)?,
740 )
741 } else {
742 SstArrowWriter::Serial(
743 AsyncArrowWriter::try_new(writer, schema, Some(writer_props))
744 .context(WriteParquetSnafu)?,
745 )
746 };
747 self.writer = Some(arrow_writer);
748
749 let indexer = self
750 .indexer_builder
751 .build(
752 RegionFileId::new(self.metadata.region_id, self.current_file),
753 0,
754 Some(opts.row_group_size),
755 )
756 .await;
757 self.current_indexer = Some(indexer);
758
759 Ok(self.writer.as_mut().unwrap())
761 }
762 }
763}
764
765#[derive(Default)]
766struct SourceStats {
767 num_rows: usize,
769 time_range: Option<(Timestamp, Timestamp)>,
771 last_primary_key: Option<Bytes>,
773 series_estimator: SeriesEstimator,
775}
776
777impl SourceStats {
778 fn update_flat(&mut self, record_batch: &RecordBatch) -> Result<()> {
779 if record_batch.num_rows() == 0 {
780 return Ok(());
781 }
782
783 self.num_rows += record_batch.num_rows();
784 self.series_estimator.update_flat(record_batch);
785 let (keys, values) = encoded_primary_keys(record_batch)?;
786 let key = keys.value(record_batch.num_rows() - 1);
787 self.last_primary_key = Some(Bytes::copy_from_slice(values.value(key as usize)));
788
789 let time_index_col_idx = time_index_column_index(record_batch.num_columns());
791 let timestamp_array = record_batch.column(time_index_col_idx);
792
793 if let Some((min_in_batch, max_in_batch)) = timestamp_range_from_array(timestamp_array)? {
794 if let Some(time_range) = &mut self.time_range {
795 time_range.0 = time_range.0.min(min_in_batch);
796 time_range.1 = time_range.1.max(max_in_batch);
797 } else {
798 self.time_range = Some((min_in_batch, max_in_batch));
799 }
800 }
801
802 Ok(())
803 }
804}
805
806fn timestamp_range_from_array(
808 timestamp_array: &ArrayRef,
809) -> Result<Option<(Timestamp, Timestamp)>> {
810 let (min_ts, max_ts) = match timestamp_array.data_type() {
811 DataType::Timestamp(TimeUnit::Second, _) => {
812 let array = timestamp_array
813 .as_any()
814 .downcast_ref::<TimestampSecondArray>()
815 .unwrap();
816 let min_val = min(array).map(Timestamp::new_second);
817 let max_val = max(array).map(Timestamp::new_second);
818 (min_val, max_val)
819 }
820 DataType::Timestamp(TimeUnit::Millisecond, _) => {
821 let array = timestamp_array
822 .as_any()
823 .downcast_ref::<TimestampMillisecondArray>()
824 .unwrap();
825 let min_val = min(array).map(Timestamp::new_millisecond);
826 let max_val = max(array).map(Timestamp::new_millisecond);
827 (min_val, max_val)
828 }
829 DataType::Timestamp(TimeUnit::Microsecond, _) => {
830 let array = timestamp_array
831 .as_any()
832 .downcast_ref::<TimestampMicrosecondArray>()
833 .unwrap();
834 let min_val = min(array).map(Timestamp::new_microsecond);
835 let max_val = max(array).map(Timestamp::new_microsecond);
836 (min_val, max_val)
837 }
838 DataType::Timestamp(TimeUnit::Nanosecond, _) => {
839 let array = timestamp_array
840 .as_any()
841 .downcast_ref::<TimestampNanosecondArray>()
842 .unwrap();
843 let min_val = min(array).map(Timestamp::new_nanosecond);
844 let max_val = max(array).map(Timestamp::new_nanosecond);
845 (min_val, max_val)
846 }
847 _ => {
848 return UnexpectedSnafu {
849 reason: format!(
850 "Unexpected data type of time index: {:?}",
851 timestamp_array.data_type()
852 ),
853 }
854 .fail();
855 }
856 };
857
858 Ok(min_ts.zip(max_ts))
860}
861
862struct SizeAwareWriter<W> {
865 inner: W,
866 size: Arc<AtomicUsize>,
867}
868
869impl<W> SizeAwareWriter<W> {
870 fn new(inner: W, size: Arc<AtomicUsize>) -> Self {
871 Self {
872 inner,
873 size: size.clone(),
874 }
875 }
876}
877
878impl<W> AsyncWrite for SizeAwareWriter<W>
879where
880 W: AsyncWrite + Unpin,
881{
882 fn poll_write(
883 mut self: Pin<&mut Self>,
884 cx: &mut Context<'_>,
885 buf: &[u8],
886 ) -> Poll<std::result::Result<usize, std::io::Error>> {
887 let this = self.as_mut().get_mut();
888
889 match Pin::new(&mut this.inner).poll_write(cx, buf) {
890 Poll::Ready(Ok(bytes_written)) => {
891 this.size.fetch_add(bytes_written, Ordering::Relaxed);
892 Poll::Ready(Ok(bytes_written))
893 }
894 other => other,
895 }
896 }
897
898 fn poll_flush(
899 mut self: Pin<&mut Self>,
900 cx: &mut Context<'_>,
901 ) -> Poll<std::result::Result<(), std::io::Error>> {
902 Pin::new(&mut self.inner).poll_flush(cx)
903 }
904
905 fn poll_shutdown(
906 mut self: Pin<&mut Self>,
907 cx: &mut Context<'_>,
908 ) -> Poll<std::result::Result<(), std::io::Error>> {
909 Pin::new(&mut self.inner).poll_shutdown(cx)
910 }
911}
912
913#[cfg(test)]
914mod tests {
915 use std::sync::atomic::AtomicBool;
916 use std::time::Duration;
917
918 use common_runtime::runtime::{BuilderBuild, RuntimeTrait};
919 use parquet::arrow::ArrowWriter;
920 use parquet::errors::ParquetError;
921 use store_api::region_request::PathType;
922 use tokio::sync::oneshot;
923 use tokio::time::timeout;
924
925 use super::*;
926 use crate::access_layer::{RegionFilePathFactory, WriteType};
927 use crate::compaction::memory_manager::new_compaction_memory_manager;
928 use crate::error::Error;
929 use crate::sst::FormatType;
930 use crate::sst::parquet::tests::NoopIndexBuilder;
931 use crate::test_util::TestEnv;
932 use crate::test_util::sst_util::{new_record_batch_by_range, sst_region_metadata};
933
934 #[rstest::rstest]
935 #[case::flat(FormatType::Flat)]
936 #[case::primary_key(FormatType::PrimaryKey)]
937 #[tokio::test]
938 async fn test_source_error_waits_for_encoding_cleanup(#[case] format: FormatType) {
939 let mut env = TestEnv::new().await;
940 let metadata = Arc::new(sst_region_metadata());
941 let mut metrics = Metrics::new(WriteType::Compaction);
942 let mut writer = ParquetWriter::new_with_object_store(
943 env.init_object_store_manager(),
944 metadata.clone(),
945 IndexConfig::default(),
946 NoopIndexBuilder,
947 RegionFilePathFactory::new("".to_string(), PathType::Bare),
948 &mut metrics,
949 )
950 .await;
951 let opts = WriteOptions {
952 row_group_size: 8,
953 encode_parallelism: 2,
954 ..Default::default()
955 };
956 let batch = new_record_batch_by_range(&["a", "b"], 0, 4);
957 let retained_field = Arc::downgrade(batch.column(2));
958 let converter = match format {
959 FormatType::Flat => FlatBatchConverter::Flat(FlatWriteFormat::new(
960 metadata.clone(),
961 &FlatSchemaOptions::default(),
962 )),
963 FormatType::PrimaryKey => FlatBatchConverter::PrimaryKey {
964 format: PrimaryKeyWriteFormat::new(metadata.clone()),
965 num_fields: metadata.field_columns().count(),
966 },
967 };
968 let encoding_batch = converter.convert_batch(&batch).unwrap();
969 let encoding_schema = encoding_batch.schema();
970 let (_, factory) = ArrowWriter::try_new(Vec::new(), encoding_schema.clone(), None)
971 .unwrap()
972 .into_serialized_writer()
973 .unwrap();
974 let columns = factory.create_column_writers(0).unwrap();
975 let runtime: common_runtime::Runtime = common_runtime::Builder::default()
976 .worker_threads(1)
977 .max_blocking_threads(1)
978 .build()
979 .unwrap();
980 let (started_tx, started_rx) = oneshot::channel();
981 let (release_tx, release_rx) = oneshot::channel();
982 let queued_batch = encoding_batch.clone();
983 let running = runtime.spawn_blocking(move || {
984 started_tx.send(()).unwrap();
985 release_rx.blocking_recv().unwrap();
986 drop((encoding_batch, columns));
987 Err(ParquetError::General(
988 "cleanup encoding failure".to_string(),
989 ))
990 });
991 timeout(Duration::from_secs(10), started_rx)
992 .await
993 .unwrap()
994 .unwrap();
995 let queued_started = Arc::new(AtomicBool::new(false));
996 let queued_started_task = queued_started.clone();
997 let queued = runtime.spawn_blocking(move || {
998 queued_started_task.store(true, Ordering::SeqCst);
999 drop(queued_batch);
1000 Ok((Vec::new(), Vec::new()))
1001 });
1002 let SstArrowWriter::Parallel(parallel) = writer
1003 .maybe_init_writer(&encoding_schema, &opts)
1004 .await
1005 .unwrap()
1006 else {
1007 panic!("expected parallel encoding");
1008 };
1009 parallel.set_encoding_tasks_for_test(vec![running, queued]);
1011 let source = FlatSource::new_stream(
1012 batch.schema(),
1013 Box::pin(futures::stream::iter(vec![
1014 Ok(batch),
1015 InvalidRecordBatchSnafu {
1016 reason: "source read failed",
1017 }
1018 .fail(),
1019 ])),
1020 );
1021 let memory = new_compaction_memory_manager(1024 * 1024);
1022 let budget = memory.try_acquire(memory.limit_bytes()).unwrap();
1023 let mut write = Box::pin(async {
1024 let _budget = budget;
1025 match format {
1026 FormatType::Flat => writer.write_all_flat(source, None, &opts).await,
1027 FormatType::PrimaryKey => {
1028 writer
1029 .write_all_flat_as_primary_key(source, None, &opts)
1030 .await
1031 }
1032 }
1033 });
1034 assert!(futures::poll!(write.as_mut()).is_pending());
1037 assert!(memory.try_acquire(memory.limit_bytes()).is_none());
1038 assert!(retained_field.upgrade().is_some());
1039 release_tx.send(()).unwrap();
1040 let error = timeout(Duration::from_secs(10), write)
1041 .await
1042 .unwrap()
1043 .unwrap_err();
1044 assert!(
1045 matches!(error, Error::InvalidRecordBatch { reason, .. } if reason == "source read failed")
1046 );
1047 assert!(!queued_started.load(Ordering::SeqCst));
1048 assert!(retained_field.upgrade().is_none());
1049 assert!(memory.try_acquire(memory.limit_bytes()).is_some());
1050 assert!(writer.writer.is_none());
1051 assert_eq!(0, writer.bytes_written.load(Ordering::Relaxed));
1052 }
1053}