@@ -23,6 +23,7 @@ use arrow_schema::{Field, Fields, Schema};
2323use chrono:: { NaiveDateTime , Timelike , Utc } ;
2424use derive_more:: derive:: { Deref , DerefMut } ;
2525use itertools:: Itertools ;
26+ use once_cell:: sync:: Lazy ;
2627use parquet:: {
2728 arrow:: ArrowWriter ,
2829 basic:: Encoding ,
@@ -69,6 +70,17 @@ use super::{
6970 staging:: { StagingError , reader:: MergedReverseRecordReader , writer:: Writer } ,
7071} ;
7172
73+ const DISK_WRITE_BATCH_ROWS_VAR : & str = "DISK_WRITE_BATCH_ROWS" ;
74+ static DISK_WRITE_BATCH_ROWS : Lazy < usize > = Lazy :: new ( || {
75+ if let Ok ( var) = std:: env:: var ( DISK_WRITE_BATCH_ROWS_VAR )
76+ && let Ok ( var) = var. parse :: < usize > ( )
77+ {
78+ var
79+ } else {
80+ 1
81+ }
82+ } ) ;
83+
7284const INPROCESS_DIR_PREFIX : & str = "processing_" ;
7385const METRIC_ROW_GROUP_PREP_IN_FLIGHT : usize = 1 ;
7486
@@ -115,6 +127,7 @@ pub struct Stream {
115127 pub data_path : PathBuf ,
116128 pub options : Arc < Options > ,
117129 pub writer : Mutex < Writer > ,
130+ schema_writer : Mutex < ( ) > ,
118131 pub ingestor_id : Option < String > ,
119132}
120133impl Stream {
@@ -134,6 +147,7 @@ impl Stream {
134147 data_path,
135148 options,
136149 writer : Mutex :: new ( Writer :: default ( ) ) ,
150+ schema_writer : Mutex :: new ( ( ) ) ,
137151 ingestor_id,
138152 } )
139153 }
@@ -181,7 +195,7 @@ impl Stream {
181195 ) ;
182196 let file_path = self . data_path . join ( & filename) ;
183197
184- guard. push_disk ( filename, record, file_path, range) ?;
198+ guard. push_disk ( filename, record, file_path, range, * DISK_WRITE_BATCH_ROWS ) ?;
185199 }
186200
187201 guard. mem . push ( schema_key, record) ?;
@@ -435,32 +449,22 @@ impl Stream {
435449 . collect ( )
436450 }
437451
438- pub fn get_schemas_if_present ( & self ) -> Option < Vec < Schema > > {
439- let Ok ( dir) = self . data_path . read_dir ( ) else {
440- return None ;
441- } ;
452+ pub fn get_schemas_if_present ( & self ) -> Result < Vec < Schema > , StagingError > {
453+ let dir = self . data_path . read_dir ( ) ?;
442454
443455 let mut schemas: Vec < Schema > = Vec :: new ( ) ;
444456
445457 for file in dir. flatten ( ) {
446458 if let Some ( ext) = file. path ( ) . extension ( )
447459 && ext. eq ( "schema" )
448460 {
449- let file = File :: open ( file. path ( ) ) . expect ( "Schema File should exist" ) ;
461+ let file = File :: open ( file. path ( ) ) ? ;
450462
451- let schema = match serde_json:: from_reader ( file) {
452- Ok ( schema) => schema,
453- Err ( _) => continue ,
454- } ;
455- schemas. push ( schema) ;
463+ schemas. push ( serde_json:: from_reader ( file) ?) ;
456464 }
457465 }
458466
459- if !schemas. is_empty ( ) {
460- Some ( schemas)
461- } else {
462- None
463- }
467+ Ok ( schemas)
464468 }
465469
466470 /// Converts arrow files in staging into parquet files, does so only for past minutes when run with `!shutdown_signal`
@@ -497,28 +501,48 @@ impl Stream {
497501 // check if there is already a schema file in staging pertaining to this stream
498502 // if yes, then merge them and save
499503
500- if let Some ( mut schema) = schema {
504+ if let Some ( schema) = schema {
501505 let static_schema_flag = self . get_static_schema_flag ( ) ;
502506 if !static_schema_flag {
503- // schema is dynamic, read from staging and merge if present
507+ self . stage_schema_file ( schema) ?;
508+ }
509+ }
510+
511+ Ok ( ( ) )
512+ }
504513
505- // need to add something before .schema to make the file have an extension of type `schema`
506- let path = RelativePathBuf :: from_iter ( [ format ! ( "{}.schema" , self . stream_name) ] )
507- . to_path ( & self . data_path ) ;
514+ pub fn stage_schema_file ( & self , mut schema : Schema ) -> Result < ( ) , StagingError > {
515+ let _schema_writer = self . schema_writer . lock ( ) . map_err ( |poisoned| {
516+ StagingError :: PoisonError ( PoisonError :: new ( format ! (
517+ "Schema writer lock poisoned while staging schema for stream {} - {}" ,
518+ self . stream_name, poisoned
519+ ) ) )
520+ } ) ?;
508521
509- let staging_schemas = self . get_schemas_if_present ( ) ;
510- if let Some ( mut staging_schemas) = staging_schemas {
511- staging_schemas. push ( schema) ;
512- schema = Schema :: try_merge ( staging_schemas) ?;
513- }
522+ // schema is dynamic, read from staging and merge if present
523+ fs:: create_dir_all ( & self . data_path ) ?;
514524
515- // save the merged schema on staging disk
516- // the path should be stream/.ingestor.{id}.schema
517- info ! ( "writing schema to path - {path:?}" ) ;
518- write ( path, to_bytes ( & schema) ) ?;
519- }
525+ // need to add something before .schema to make the file have an extension of type `schema`
526+ let file_name = self . ingestor_id . as_ref ( ) . map_or_else (
527+ || ".schema" . to_owned ( ) ,
528+ |id| format ! ( ".ingestor.{id}.schema" ) ,
529+ ) ;
530+ let path = RelativePathBuf :: from_iter ( [ file_name] ) . to_path ( & self . data_path ) ;
531+ let tmp_path = path. with_extension ( "schema.tmp" ) ;
532+
533+ let staging_schemas = self . get_schemas_if_present ( ) ?;
534+ if !staging_schemas. is_empty ( ) {
535+ let mut staging_schemas = staging_schemas;
536+ staging_schemas. push ( schema) ;
537+ schema = Schema :: try_merge ( staging_schemas) ?;
520538 }
521539
540+ // save the merged schema on staging disk
541+ // the path should be stream/.ingestor.{id}.schema
542+ info ! ( "writing schema to path - {path:?}" ) ;
543+ write ( & tmp_path, to_bytes ( & schema) ) ?;
544+ fs:: rename ( tmp_path, path) ?;
545+
522546 Ok ( ( ) )
523547 }
524548
0 commit comments