1use std::fmt;
23use std::fmt::Debug;
24use std::sync::Arc;
25
26use arrow::array::RecordBatch;
27use arrow::datatypes::{Schema, SchemaRef};
28use async_trait::async_trait;
29use datafusion_common::config::TableParquetOptions;
30use datafusion_common::{DataFusionError, HashMap, Result, internal_datafusion_err};
31use datafusion_common_runtime::{JoinSet, SpawnedTask};
32use datafusion_datasource::display::FileGroupDisplay;
33use datafusion_datasource::file_compression_type::FileCompressionType;
34use datafusion_datasource::file_sink_config::{FileSink, FileSinkConfig};
35use datafusion_datasource::sink::DataSink;
36#[cfg(feature = "proto")]
37use datafusion_datasource::sink::DataSinkExec;
38use datafusion_datasource::write::demux::DemuxedStreamReceiver;
39use datafusion_datasource::write::{
40 ObjectWriterBuilder, SharedBuffer, get_writer_schema,
41};
42use datafusion_execution::memory_pool::{MemoryConsumer, MemoryPool, MemoryReservation};
43use datafusion_execution::runtime_env::RuntimeEnv;
44use datafusion_execution::{SendableRecordBatchStream, TaskContext};
45#[cfg(feature = "proto")]
46use datafusion_physical_plan::ExecutionPlan;
47use datafusion_physical_plan::metrics::{
48 ElapsedComputeFutureExt, ExecutionPlanMetricsSet, MetricBuilder, MetricCategory,
49 MetricsSet, Time,
50};
51use datafusion_physical_plan::{DisplayAs, DisplayFormatType};
52use object_store::ObjectStore;
53use object_store::buffered::BufWriter;
54use object_store::path::Path;
55use parquet::arrow::arrow_writer::{
56 ArrowColumnChunk, ArrowColumnWriter, ArrowLeafColumn, ArrowRowGroupWriterFactory,
57 ArrowWriterOptions, compute_leaves,
58};
59use parquet::arrow::{ArrowWriter, AsyncArrowWriter};
60#[cfg(feature = "parquet_encryption")]
61use parquet::encryption::encrypt::FileEncryptionProperties;
62use parquet::file::metadata::{ParquetMetaData, SortingColumn};
63use parquet::file::properties::{
64 DEFAULT_MAX_ROW_GROUP_ROW_COUNT, WriterProperties, WriterPropertiesBuilder,
65};
66use parquet::file::writer::SerializedFileWriter;
67use tokio::io::{AsyncWrite, AsyncWriteExt};
68use tokio::sync::mpsc::{self, Receiver, Sender};
69
70const INITIAL_BUFFER_BYTES: usize = 1048576;
73
74const BUFFER_FLUSH_BYTES: usize = 1024000;
77
78pub struct ParquetSink {
80 config: FileSinkConfig,
82 parquet_options: TableParquetOptions,
84 written: Arc<parking_lot::Mutex<HashMap<Path, ParquetMetaData>>>,
87 sorting_columns: Option<Vec<SortingColumn>>,
89 metrics: ExecutionPlanMetricsSet,
91}
92
93impl Debug for ParquetSink {
94 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95 f.debug_struct("ParquetSink").finish()
96 }
97}
98
99impl DisplayAs for ParquetSink {
100 fn fmt_as(&self, t: DisplayFormatType, f: &mut fmt::Formatter<'_>) -> fmt::Result {
101 match t {
102 DisplayFormatType::Default | DisplayFormatType::Verbose => {
103 write!(f, "ParquetSink(file_groups=",)?;
104 FileGroupDisplay(&self.config.file_group).fmt_as(t, f)?;
105 write!(f, ")")
106 }
107 DisplayFormatType::TreeRender => {
108 write!(f, "")
110 }
111 }
112 }
113}
114
115impl ParquetSink {
116 pub fn new(config: FileSinkConfig, parquet_options: TableParquetOptions) -> Self {
118 Self {
119 config,
120 parquet_options,
121 written: Default::default(),
122 sorting_columns: None,
123 metrics: ExecutionPlanMetricsSet::new(),
124 }
125 }
126
127 pub fn with_sorting_columns(
129 mut self,
130 sorting_columns: Option<Vec<SortingColumn>>,
131 ) -> Self {
132 self.sorting_columns = sorting_columns;
133 self
134 }
135
136 pub fn written(&self) -> HashMap<Path, ParquetMetaData> {
139 self.written.lock().clone()
140 }
141
142 async fn create_writer_props(
145 &self,
146 runtime: &Arc<RuntimeEnv>,
147 path: &Path,
148 ) -> Result<WriterProperties> {
149 let schema = self.config.output_schema();
150
151 let mut parquet_opts = self.parquet_options.clone();
154 if !self.parquet_options.global.skip_arrow_metadata {
155 parquet_opts.arrow_schema(schema);
156 }
157
158 let mut builder = WriterPropertiesBuilder::try_from(&parquet_opts)?;
159
160 if let Some(ref sorting_columns) = self.sorting_columns {
162 builder = builder.set_sorting_columns(Some(sorting_columns.clone()));
163 }
164
165 builder = set_writer_encryption_properties(
166 builder,
167 runtime,
168 parquet_opts,
169 schema,
170 path,
171 )
172 .await?;
173 Ok(builder.build())
174 }
175
176 fn create_async_arrow_writer(
179 &self,
180 location: &Path,
181 object_store: Arc<dyn ObjectStore>,
182 context: &Arc<TaskContext>,
183 parquet_props: WriterProperties,
184 ) -> Result<AsyncArrowWriter<BufWriter>> {
185 let buf_writer = BufWriter::with_capacity(
186 object_store,
187 location.clone(),
188 context
189 .session_config()
190 .options()
191 .execution
192 .objectstore_writer_buffer_size,
193 );
194 let options = ArrowWriterOptions::new()
195 .with_properties(parquet_props)
196 .with_skip_arrow_metadata(self.parquet_options.global.skip_arrow_metadata);
197
198 let writer = AsyncArrowWriter::try_new_with_options(
199 buf_writer,
200 get_writer_schema(&self.config),
201 options,
202 )?;
203 Ok(writer)
204 }
205
206 pub fn parquet_options(&self) -> &TableParquetOptions {
208 &self.parquet_options
209 }
210}
211
212#[cfg(feature = "parquet_encryption")]
213async fn set_writer_encryption_properties(
214 builder: WriterPropertiesBuilder,
215 runtime: &Arc<RuntimeEnv>,
216 parquet_opts: TableParquetOptions,
217 schema: &Arc<Schema>,
218 path: &Path,
219) -> Result<WriterPropertiesBuilder> {
220 if let Some(file_encryption_properties) = parquet_opts.crypto.file_encryption {
221 return Ok(builder.with_file_encryption_properties(Arc::new(
223 FileEncryptionProperties::try_from(file_encryption_properties)?,
224 )));
225 } else if let Some(encryption_factory_id) = &parquet_opts.crypto.factory_id.as_ref() {
226 let encryption_factory =
228 runtime.parquet_encryption_factory(encryption_factory_id)?;
229 let file_encryption_properties = encryption_factory
230 .get_file_encryption_properties(
231 &parquet_opts.crypto.factory_options,
232 schema,
233 path,
234 )
235 .await?;
236 if let Some(file_encryption_properties) = file_encryption_properties {
237 return Ok(
238 builder.with_file_encryption_properties(file_encryption_properties)
239 );
240 }
241 }
242 Ok(builder)
243}
244
245#[cfg(not(feature = "parquet_encryption"))]
246#[expect(clippy::unused_async)]
247async fn set_writer_encryption_properties(
248 builder: WriterPropertiesBuilder,
249 _runtime: &Arc<RuntimeEnv>,
250 _parquet_opts: TableParquetOptions,
251 _schema: &Arc<Schema>,
252 _path: &Path,
253) -> Result<WriterPropertiesBuilder> {
254 Ok(builder)
255}
256
257#[async_trait]
258impl FileSink for ParquetSink {
259 fn config(&self) -> &FileSinkConfig {
260 &self.config
261 }
262
263 async fn spawn_writer_tasks_and_join(
264 &self,
265 context: &Arc<TaskContext>,
266 demux_task: SpawnedTask<Result<()>>,
267 mut file_stream_rx: DemuxedStreamReceiver,
268 object_store: Arc<dyn ObjectStore>,
269 ) -> Result<u64> {
270 let rows_written_counter = MetricBuilder::new(&self.metrics)
271 .with_category(MetricCategory::Rows)
272 .global_counter("rows_written");
273 let bytes_written_counter = MetricBuilder::new(&self.metrics)
277 .with_category(MetricCategory::Bytes)
278 .global_counter("bytes_written");
279 let elapsed_compute = MetricBuilder::new(&self.metrics).elapsed_compute(0);
280
281 let parquet_opts = &self.parquet_options;
282
283 let mut file_write_tasks: JoinSet<
284 std::result::Result<(Path, ParquetMetaData), DataFusionError>,
285 > = JoinSet::new();
286
287 let runtime = context.runtime_env();
288 let parallel_options = ParallelParquetWriterOptions {
289 max_parallel_row_groups: parquet_opts
290 .global
291 .maximum_parallel_row_group_writers,
292 max_buffered_record_batches_per_stream: parquet_opts
293 .global
294 .maximum_buffered_record_batches_per_stream,
295 };
296
297 while let Some((path, mut rx)) = file_stream_rx.recv().await {
298 let parquet_props = self.create_writer_props(&runtime, &path).await?;
299 if !parquet_opts.global.allow_single_file_parallelism
302 || parquet_opts.global.content_defined_chunking.enabled
303 {
304 let mut writer = self.create_async_arrow_writer(
305 &path,
306 Arc::clone(&object_store),
307 context,
308 parquet_props.clone(),
309 )?;
310 let reservation = MemoryConsumer::new(format!("ParquetSink[{path}]"))
311 .register(context.memory_pool());
312 file_write_tasks.spawn(
313 async move {
314 while let Some(batch) = rx.recv().await {
315 writer.write(&batch).await?;
316 reservation.try_resize(writer.memory_size())?;
317 }
318 let parquet_meta_data = writer
319 .close()
320 .await
321 .map_err(|e| DataFusionError::ParquetError(Box::new(e)))?;
322 Ok((path, parquet_meta_data))
323 }
324 .with_elapsed_compute(elapsed_compute.clone()),
325 );
326 } else {
327 let writer = ObjectWriterBuilder::new(
328 FileCompressionType::UNCOMPRESSED,
331 &path,
332 Arc::clone(&object_store),
333 )
334 .with_buffer_size(Some(
335 context
336 .session_config()
337 .options()
338 .execution
339 .objectstore_writer_buffer_size,
340 ))
341 .build()?;
342 let ctx = ParquetFileWriteContext {
343 schema: get_writer_schema(&self.config),
344 props: Arc::new(parquet_props),
345 skip_arrow_metadata: self.parquet_options.global.skip_arrow_metadata,
346 parallel_options: Arc::new(parallel_options.clone()),
347 pool: Arc::clone(context.memory_pool()),
348 };
349 let encoding_time = elapsed_compute.clone();
350 file_write_tasks.spawn(async move {
351 let parquet_meta_data = output_single_parquet_file_parallelized(
352 writer,
353 rx,
354 ctx,
355 encoding_time,
356 )
357 .await?;
358 Ok((path, parquet_meta_data))
359 });
360 }
361 }
362
363 while let Some(result) = file_write_tasks.join_next().await {
364 match result {
365 Ok(r) => {
366 let (path, parquet_meta_data) = r?;
367 let file_rows = parquet_meta_data.file_metadata().num_rows() as usize;
368 let file_bytes: usize = parquet_meta_data
369 .row_groups()
370 .iter()
371 .map(|rg| rg.compressed_size() as usize)
372 .sum();
373 rows_written_counter.add(file_rows);
374 bytes_written_counter.add(file_bytes);
375 let mut written_files = self.written.lock();
376 written_files
377 .try_insert(path.clone(), parquet_meta_data)
378 .map_err(|e| internal_datafusion_err!("duplicate entry detected for partitioned file {path}: {e}"))?;
379 drop(written_files);
380 }
381 Err(e) => {
382 if e.is_panic() {
383 std::panic::resume_unwind(e.into_panic());
384 } else {
385 unreachable!();
386 }
387 }
388 }
389 }
390
391 demux_task
392 .join_unwind()
393 .await
394 .map_err(|e| DataFusionError::ExecutionJoin(Box::new(e)))??;
395
396 Ok(rows_written_counter.value() as u64)
397 }
398}
399
400#[async_trait]
401impl DataSink for ParquetSink {
402 fn metrics(&self) -> Option<MetricsSet> {
403 Some(self.metrics.clone_inner())
404 }
405
406 fn schema(&self) -> &SchemaRef {
407 self.config.output_schema()
408 }
409
410 async fn write_all(
411 &self,
412 data: SendableRecordBatchStream,
413 context: &Arc<TaskContext>,
414 ) -> Result<u64> {
415 FileSink::write_all(self, data, context).await
416 }
417
418 #[cfg(feature = "proto")]
419 fn try_to_proto(
420 &self,
421 exec: &DataSinkExec,
422 ctx: &datafusion_physical_plan::proto::ExecutionPlanEncodeCtx<'_>,
423 ) -> Result<Option<datafusion_proto_models::protobuf::PhysicalPlanNode>> {
424 use datafusion_proto_models::protobuf;
425 use protobuf::physical_plan_node::PhysicalPlanType;
426
427 let input = ctx.encode_child(exec.input())?;
428 let sort_order = exec.encode_sort_order(ctx)?;
429 let sink = protobuf::ParquetSink::try_from(self)?;
430 let node = protobuf::ParquetSinkExecNode {
431 input: Some(Box::new(input)),
432 sink: Some(sink),
433 sink_schema: Some(exec.schema().as_ref().try_into()?),
434 sort_order,
435 };
436 Ok(Some(protobuf::PhysicalPlanNode {
437 physical_plan_type: Some(PhysicalPlanType::ParquetSink(Box::new(node))),
438 }))
439 }
440}
441
442#[cfg(feature = "proto")]
443impl TryFrom<&ParquetSink> for datafusion_proto_models::protobuf::ParquetSink {
444 type Error = DataFusionError;
445
446 fn try_from(value: &ParquetSink) -> Result<Self> {
447 Ok(Self {
448 config: Some(value.config().try_into()?),
449 parquet_options: Some(value.parquet_options().try_into()?),
450 })
451 }
452}
453
454#[cfg(feature = "proto")]
455impl TryFrom<&datafusion_proto_models::protobuf::ParquetSink> for ParquetSink {
456 type Error = DataFusionError;
457
458 fn try_from(value: &datafusion_proto_models::protobuf::ParquetSink) -> Result<Self> {
459 let config =
460 FileSinkConfig::try_from(value.config.as_ref().ok_or_else(|| {
461 datafusion_common::internal_datafusion_err!(
462 "ParquetSink is missing required field 'config'"
463 )
464 })?)?;
465 let parquet_options = value
466 .parquet_options
467 .as_ref()
468 .ok_or_else(|| {
469 datafusion_common::internal_datafusion_err!(
470 "ParquetSink is missing required field 'parquet_options'"
471 )
472 })?
473 .try_into()?;
474
475 Ok(Self::new(config, parquet_options))
476 }
477}
478
479#[cfg(feature = "proto")]
480impl ParquetSink {
481 pub fn try_from_proto(
483 node: &datafusion_proto_models::protobuf::PhysicalPlanNode,
484 ctx: &datafusion_physical_plan::proto::ExecutionPlanDecodeCtx<'_>,
485 ) -> Result<Arc<dyn ExecutionPlan>> {
486 use datafusion_proto_models::protobuf;
487
488 let sink_node = datafusion_physical_plan::expect_plan_variant!(
489 node,
490 protobuf::physical_plan_node::PhysicalPlanType::ParquetSink,
491 "ParquetSink",
492 );
493 let input = ctx.decode_required_child(
494 sink_node.input.as_deref(),
495 "ParquetSinkExecNode",
496 "input",
497 )?;
498 let proto_sink = sink_node.sink.as_ref().ok_or_else(|| {
499 datafusion_common::internal_datafusion_err!(
500 "ParquetSinkExecNode is missing required field 'sink'"
501 )
502 })?;
503 let data_sink = ParquetSink::try_from(proto_sink)?;
504 let sort_order = DataSinkExec::decode_sort_order(
505 sink_node.sort_order.as_ref(),
506 ctx,
507 input.schema().as_ref(),
508 )?;
509
510 Ok(Arc::new(DataSinkExec::new(
511 input,
512 Arc::new(data_sink),
513 sort_order,
514 )))
515 }
516}
517
518async fn column_serializer_task(
521 mut rx: Receiver<ArrowLeafColumn>,
522 mut writer: ArrowColumnWriter,
523 reservation: MemoryReservation,
524 encoding_time: Time,
525) -> Result<(ArrowColumnWriter, MemoryReservation)> {
526 while let Some(col) = rx.recv().await {
527 let _timer = encoding_time.timer();
528 writer.write(&col)?;
529 reservation.try_resize(writer.memory_size())?;
530 }
531 Ok((writer, reservation))
532}
533
534type ColumnWriterTask = SpawnedTask<Result<(ArrowColumnWriter, MemoryReservation)>>;
535type ColSender = Sender<ArrowLeafColumn>;
536
537fn spawn_column_parallel_row_group_writer(
541 col_writers: Vec<ArrowColumnWriter>,
542 max_buffer_size: usize,
543 pool: &Arc<dyn MemoryPool>,
544 encoding_time: &Time,
545) -> Result<(Vec<ColumnWriterTask>, Vec<ColSender>)> {
546 let num_columns = col_writers.len();
547
548 let mut col_writer_tasks = Vec::with_capacity(num_columns);
549 let mut col_array_channels = Vec::with_capacity(num_columns);
550 for writer in col_writers.into_iter() {
551 let (send_array, receive_array) =
553 mpsc::channel::<ArrowLeafColumn>(max_buffer_size);
554 col_array_channels.push(send_array);
555
556 let reservation =
557 MemoryConsumer::new("ParquetSink(ArrowColumnWriter)").register(pool);
558 let task = SpawnedTask::spawn(column_serializer_task(
559 receive_array,
560 writer,
561 reservation,
562 encoding_time.clone(),
563 ));
564 col_writer_tasks.push(task);
565 }
566
567 Ok((col_writer_tasks, col_array_channels))
568}
569
570#[derive(Clone)]
572struct ParallelParquetWriterOptions {
573 max_parallel_row_groups: usize,
574 max_buffered_record_batches_per_stream: usize,
575}
576
577#[derive(Clone)]
585struct ParquetFileWriteContext {
586 schema: Arc<Schema>,
587 props: Arc<WriterProperties>,
588 skip_arrow_metadata: bool,
589 parallel_options: Arc<ParallelParquetWriterOptions>,
590 pool: Arc<dyn MemoryPool>,
591}
592
593type RBStreamSerializeResult = Result<(Vec<ArrowColumnChunk>, MemoryReservation, usize)>;
596
597async fn send_arrays_to_col_writers(
600 col_array_channels: &[ColSender],
601 rb: &RecordBatch,
602 schema: Arc<Schema>,
603) -> Result<()> {
604 let mut next_channel = 0;
606 for (array, field) in rb.columns().iter().zip(schema.fields()) {
607 for c in compute_leaves(field, array)? {
608 if col_array_channels[next_channel].send(c).await.is_err() {
611 return Ok(());
612 }
613
614 next_channel += 1;
615 }
616 }
617
618 Ok(())
619}
620
621fn spawn_rg_join_and_finalize_task(
624 column_writer_tasks: Vec<ColumnWriterTask>,
625 rg_rows: usize,
626 pool: &Arc<dyn MemoryPool>,
627 encoding_time: Time,
628) -> SpawnedTask<RBStreamSerializeResult> {
629 let rg_reservation =
630 MemoryConsumer::new("ParquetSink(SerializedRowGroupWriter)").register(pool);
631
632 SpawnedTask::spawn(async move {
633 let num_cols = column_writer_tasks.len();
634 let mut finalized_rg = Vec::with_capacity(num_cols);
635 for task in column_writer_tasks.into_iter() {
636 let (writer, _col_reservation) = task
637 .join_unwind()
638 .await
639 .map_err(|e| DataFusionError::ExecutionJoin(Box::new(e)))??;
640 let encoded_size = writer.get_estimated_total_bytes();
641 rg_reservation.grow(encoded_size);
642 let _timer = encoding_time.timer();
643 finalized_rg.push(writer.close()?);
644 }
645
646 Ok((finalized_rg, rg_reservation, rg_rows))
647 })
648}
649
650fn spawn_parquet_parallel_serialization_task(
659 row_group_writer_factory: ArrowRowGroupWriterFactory,
660 mut data: Receiver<RecordBatch>,
661 serialize_tx: Sender<SpawnedTask<RBStreamSerializeResult>>,
662 ctx: ParquetFileWriteContext,
663 encoding_time: Time,
664) -> SpawnedTask<Result<(), DataFusionError>> {
665 SpawnedTask::spawn(async move {
666 let max_buffer_rb = ctx.parallel_options.max_buffered_record_batches_per_stream;
667 let max_row_group_rows = ctx
668 .props
669 .max_row_group_row_count()
670 .unwrap_or(DEFAULT_MAX_ROW_GROUP_ROW_COUNT);
671 let mut row_group_index = 0;
672 let col_writers =
673 row_group_writer_factory.create_column_writers(row_group_index)?;
674 let (mut column_writer_handles, mut col_array_channels) =
675 spawn_column_parallel_row_group_writer(
676 col_writers,
677 max_buffer_rb,
678 &ctx.pool,
679 &encoding_time,
680 )?;
681 let mut current_rg_rows = 0;
682
683 while let Some(mut rb) = data.recv().await {
684 loop {
688 if current_rg_rows + rb.num_rows() < max_row_group_rows {
689 send_arrays_to_col_writers(
690 &col_array_channels,
691 &rb,
692 Arc::clone(&ctx.schema),
693 )
694 .await?;
695 current_rg_rows += rb.num_rows();
696 break;
697 } else {
698 let rows_left = max_row_group_rows - current_rg_rows;
699 let a = rb.slice(0, rows_left);
700 send_arrays_to_col_writers(
701 &col_array_channels,
702 &a,
703 Arc::clone(&ctx.schema),
704 )
705 .await?;
706
707 drop(col_array_channels);
711 let finalize_rg_task = spawn_rg_join_and_finalize_task(
712 column_writer_handles,
713 max_row_group_rows,
714 &ctx.pool,
715 encoding_time.clone(),
716 );
717
718 if serialize_tx.send(finalize_rg_task).await.is_err() {
721 return Ok(());
722 }
723
724 current_rg_rows = 0;
725 rb = rb.slice(rows_left, rb.num_rows() - rows_left);
726
727 row_group_index += 1;
728 let col_writers = row_group_writer_factory
729 .create_column_writers(row_group_index)?;
730 (column_writer_handles, col_array_channels) =
731 spawn_column_parallel_row_group_writer(
732 col_writers,
733 max_buffer_rb,
734 &ctx.pool,
735 &encoding_time,
736 )?;
737 }
738 }
739 }
740
741 drop(col_array_channels);
742 if current_rg_rows > 0 {
744 let finalize_rg_task = spawn_rg_join_and_finalize_task(
745 column_writer_handles,
746 current_rg_rows,
747 &ctx.pool,
748 encoding_time.clone(),
749 );
750
751 if serialize_tx.send(finalize_rg_task).await.is_err() {
754 return Ok(());
755 }
756 }
757
758 Ok(())
759 })
760}
761
762async fn concatenate_parallel_row_groups(
765 mut parquet_writer: SerializedFileWriter<SharedBuffer>,
766 merged_buff: SharedBuffer,
767 mut serialize_rx: Receiver<SpawnedTask<RBStreamSerializeResult>>,
768 mut object_store_writer: Box<dyn AsyncWrite + Send + Unpin>,
769 pool: Arc<dyn MemoryPool>,
770) -> Result<ParquetMetaData> {
771 let file_reservation =
772 MemoryConsumer::new("ParquetSink(SerializedFileWriter)").register(&pool);
773
774 while let Some(task) = serialize_rx.recv().await {
775 let result = task.join_unwind().await;
776 let (serialized_columns, rg_reservation, _cnt) =
777 result.map_err(|e| DataFusionError::ExecutionJoin(Box::new(e)))??;
778
779 let mut rg_out = parquet_writer.next_row_group()?;
780 for chunk in serialized_columns {
781 chunk.append_to_row_group(&mut rg_out)?;
782 rg_reservation.free();
783
784 let mut buff_to_flush = merged_buff.buffer.try_lock().unwrap();
785 file_reservation.try_resize(buff_to_flush.len())?;
786
787 if buff_to_flush.len() > BUFFER_FLUSH_BYTES {
788 object_store_writer
789 .write_all(buff_to_flush.as_slice())
790 .await?;
791 buff_to_flush.clear();
792 file_reservation.try_resize(buff_to_flush.len())?; }
794 }
795 rg_out.close()?;
796 }
797
798 let parquet_meta_data = parquet_writer.close()?;
799 let final_buff = merged_buff.buffer.try_lock().unwrap();
800
801 object_store_writer.write_all(final_buff.as_slice()).await?;
802 object_store_writer.shutdown().await?;
803 file_reservation.free();
804
805 Ok(parquet_meta_data)
806}
807
808async fn output_single_parquet_file_parallelized(
813 object_store_writer: Box<dyn AsyncWrite + Send + Unpin>,
814 data: Receiver<RecordBatch>,
815 ctx: ParquetFileWriteContext,
816 encoding_time: Time,
817) -> Result<ParquetMetaData> {
818 let max_rowgroups = ctx.parallel_options.max_parallel_row_groups;
819 let (serialize_tx, serialize_rx) =
821 mpsc::channel::<SpawnedTask<RBStreamSerializeResult>>(max_rowgroups);
822
823 let merged_buff = SharedBuffer::new(INITIAL_BUFFER_BYTES);
824 let options = ArrowWriterOptions::new()
825 .with_properties((*ctx.props).clone())
826 .with_skip_arrow_metadata(ctx.skip_arrow_metadata);
827 let writer = ArrowWriter::try_new_with_options(
828 merged_buff.clone(),
829 Arc::clone(&ctx.schema),
830 options,
831 )?;
832 let (writer, row_group_writer_factory) = writer.into_serialized_writer()?;
833
834 let pool = Arc::clone(&ctx.pool);
835 let launch_serialization_task = spawn_parquet_parallel_serialization_task(
836 row_group_writer_factory,
837 data,
838 serialize_tx,
839 ctx,
840 encoding_time,
841 );
842 let parquet_meta_data = concatenate_parallel_row_groups(
843 writer,
844 merged_buff,
845 serialize_rx,
846 object_store_writer,
847 pool,
848 )
849 .await?;
850
851 launch_serialization_task
852 .join_unwind()
853 .await
854 .map_err(|e| DataFusionError::ExecutionJoin(Box::new(e)))??;
855 Ok(parquet_meta_data)
856}