@@ -35,7 +35,7 @@ use std::task::{Context, Poll};
3535
3636use arrow:: array:: {
3737 Array , ArrayRef , BinaryViewArray , BufferSpec , GenericByteViewArray , StringViewArray ,
38- layout, make_array,
38+ builder :: GenericByteViewBuilder , layout, make_array,
3939} ;
4040use arrow:: buffer:: Buffer ;
4141use arrow:: datatypes:: DataType ;
@@ -800,6 +800,20 @@ pub(crate) fn gc_view_arrays(batch: &RecordBatch) -> Result<RecordBatch> {
800800 }
801801}
802802
803+ /// Garbage collect and deduplicate a string view before writing it out to disk
804+ ///
805+ /// This is to avoid inflating disk usage and also to ensure that deduplication reduces memory pressure when reading back.
806+ fn gc_dedup_view < T : ByteViewType > (
807+ array : & GenericByteViewArray < T > ,
808+ ) -> GenericByteViewArray < T > {
809+ let mut builder = GenericByteViewBuilder :: < T > :: with_capacity ( array. len ( ) )
810+ . with_deduplicate_strings ( ) ;
811+ for v in array. iter ( ) {
812+ builder. append_option ( v) ;
813+ }
814+ builder. finish ( )
815+ }
816+
803817fn gc_array ( array : & ArrayRef ) -> Result < ( ArrayRef , bool ) > {
804818 match array. data_type ( ) {
805819 DataType :: Utf8View => {
@@ -808,7 +822,7 @@ fn gc_array(array: &ArrayRef) -> Result<(ArrayRef, bool)> {
808822 . downcast_ref :: < StringViewArray > ( )
809823 . expect ( "Utf8View array should downcast to StringViewArray" ) ;
810824 if should_gc_view_array ( string_view) {
811- Ok ( ( Arc :: new ( string_view . gc ( ) ) as ArrayRef , true ) )
825+ Ok ( ( Arc :: new ( gc_dedup_view ( string_view ) ) as ArrayRef , true ) )
812826 } else {
813827 Ok ( ( Arc :: clone ( array) , false ) )
814828 }
@@ -819,7 +833,7 @@ fn gc_array(array: &ArrayRef) -> Result<(ArrayRef, bool)> {
819833 . downcast_ref :: < BinaryViewArray > ( )
820834 . expect ( "BinaryView array should downcast to BinaryViewArray" ) ;
821835 if should_gc_view_array ( binary_view) {
822- Ok ( ( Arc :: new ( binary_view . gc ( ) ) as ArrayRef , true ) )
836+ Ok ( ( Arc :: new ( gc_dedup_view ( binary_view ) ) as ArrayRef , true ) )
823837 } else {
824838 Ok ( ( Arc :: clone ( array) , false ) )
825839 }
@@ -2381,7 +2395,7 @@ mod tests {
23812395 let size_without_gc: usize = array_without_gc
23822396 . data_buffers ( )
23832397 . iter ( )
2384- . map ( |buffer| buffer. capacity ( ) )
2398+ . map ( |buffer| buffer. len ( ) )
23852399 . sum ( ) ;
23862400
23872401 let gc_batch = gc_view_arrays ( & sliced_batch) ?;
@@ -2393,7 +2407,7 @@ mod tests {
23932407 let size_with_gc: usize = array_with_gc
23942408 . data_buffers ( )
23952409 . iter ( )
2396- . map ( |buffer| buffer. capacity ( ) )
2410+ . map ( |buffer| buffer. len ( ) )
23972411 . sum ( ) ;
23982412
23992413 let reduction_percent =
@@ -2412,9 +2426,18 @@ mod tests {
24122426 use arrow:: array:: { DictionaryArray , Int32Array } ;
24132427 use arrow:: buffer:: Buffer ;
24142428
2415- let strings: Vec < String > = ( 0 ..200 )
2429+ // A small pool of distinct, non-inlined (> 12 byte) strings.
2430+ let distinct: Vec < String > = ( 0 ..8 )
24162431 . map ( |i| format ! ( "http://example.com/nested/path/that/is/not/inlined/{i}" ) )
24172432 . collect ( ) ;
2433+
2434+ // Bytes stored once each string is deduplicated (all references collapse
2435+ // to a single copy of every distinct value).
2436+ let distinct_bytes: usize = distinct. iter ( ) . map ( |s| s. len ( ) ) . sum ( ) ;
2437+
2438+ let strings: Vec < String > = ( 0 ..200 )
2439+ . map ( |i| distinct[ i % distinct. len ( ) ] . clone ( ) )
2440+ . collect ( ) ;
24182441 let string_values = Arc :: new ( StringViewArray :: from ( strings) ) as ArrayRef ;
24192442
24202443 let list_data = ArrayDataBuilder :: new ( DataType :: List ( Arc :: new (
@@ -2454,9 +2477,11 @@ mod tests {
24542477 . as_any ( )
24552478 . downcast_ref :: < StringViewArray > ( )
24562479 . unwrap ( ) ;
2457- assert ! (
2458- calculate_string_view_waste_ratio( gc_list_values) < 0.2 ,
2459- "GC should compact nested List child views"
2480+ let list_stored_bytes: usize =
2481+ gc_list_values. data_buffers ( ) . iter ( ) . map ( |b| b. len ( ) ) . sum ( ) ;
2482+ assert_eq ! (
2483+ list_stored_bytes, distinct_bytes,
2484+ "GC should deduplicate nested List child views (regression: dedup not applied)"
24602485 ) ;
24612486
24622487 let gc_dictionary_values = gc_batch. column ( 1 ) . to_data ( ) . child_data ( ) [ 0 ] . clone ( ) ;
@@ -2465,9 +2490,14 @@ mod tests {
24652490 . as_any ( )
24662491 . downcast_ref :: < StringViewArray > ( )
24672492 . unwrap ( ) ;
2468- assert ! (
2469- calculate_string_view_waste_ratio( gc_dictionary_values) < 0.2 ,
2470- "GC should compact nested Dictionary values"
2493+ let dictionary_stored_bytes: usize = gc_dictionary_values
2494+ . data_buffers ( )
2495+ . iter ( )
2496+ . map ( |b| b. len ( ) )
2497+ . sum ( ) ;
2498+ assert_eq ! (
2499+ dictionary_stored_bytes, distinct_bytes,
2500+ "GC should deduplicate nested Dictionary values (regression: dedup not applied)"
24712501 ) ;
24722502
24732503 Ok ( ( ) )
0 commit comments