@@ -839,70 +839,114 @@ impl LayerMap {
839839 /// we'll need to visit for any page reconstruction in this region.
840840 /// We use this heuristic to decide whether to create an image layer.
841841 pub fn count_deltas ( & self , key : & Range < Key > , lsn : & Range < Lsn > , limit : Option < usize > ) -> usize {
842- // We get the delta coverage of the region, and for each part of the coverage
843- // we recurse right underneath the delta. The recursion depth is limited by
844- // the largest result this function could return, which is in practice between
845- // 3 and 10 (since we usually try to create an image when the number gets larger).
846-
847842 if lsn. is_empty ( ) || key. is_empty ( ) || limit == Some ( 0 ) {
848843 return 0 ;
849844 }
850845
851- let version = match self . historic . get ( ) . unwrap ( ) . get_version ( lsn. end . 0 - 1 ) {
852- Some ( v) => v,
853- None => return 0 ,
854- } ;
846+ struct Frame {
847+ max_stacked_deltas : usize ,
848+ }
855849
856- let start = key. start . to_i128 ( ) ;
857- let end = key. end . to_i128 ( ) ;
850+ enum Task {
851+ Process {
852+ key : Range < Key > ,
853+ lsn : Range < Lsn > ,
854+ limit : Option < usize > ,
855+ } ,
856+ Accumulate {
857+ frame_idx : usize ,
858+ base_count : usize ,
859+ } ,
860+ FinishFrame {
861+ frame_idx : usize ,
862+ } ,
863+ }
858864
859- // Initialize loop variables
860- let mut max_stacked_deltas = 0 ;
861- let mut current_key = start;
862- let mut current_val = version. delta_coverage . query ( start) ;
863-
864- // Loop through the delta coverage and recurse on each part
865- for ( change_key, change_val) in version. delta_coverage . range ( start..end) {
866- // If there's a relevant delta in this part, add 1 and recurse down
867- if let Some ( val) = & current_val {
868- if val. get_lsn_range ( ) . end > lsn. start {
869- let kr = Key :: from_i128 ( current_key) ..Key :: from_i128 ( change_key) ;
870- let lr = lsn. start ..val. get_lsn_range ( ) . start ;
871- if !kr. is_empty ( ) {
872- let base_count = Self :: is_reimage_worthy ( val, key) as usize ;
873- let new_limit = limit. map ( |l| l - base_count) ;
874- let max_stacked_deltas_underneath = self . count_deltas ( & kr, & lr, new_limit) ;
875- max_stacked_deltas = std:: cmp:: max (
876- max_stacked_deltas,
877- base_count + max_stacked_deltas_underneath,
878- ) ;
865+ let mut last_result = 0 ;
866+ let mut frames: Vec < Frame > = Vec :: new ( ) ;
867+ let mut tasks = vec ! [ Task :: Process {
868+ key: key. clone( ) ,
869+ lsn: lsn. clone( ) ,
870+ limit,
871+ } ] ;
872+
873+ while let Some ( task) = tasks. pop ( ) {
874+ match task {
875+ Task :: Process { key, lsn, limit } => {
876+ if lsn. is_empty ( ) || key. is_empty ( ) || limit == Some ( 0 ) {
877+ last_result = 0 ;
878+ continue ;
879879 }
880- }
881- }
882880
883- current_key = change_key;
884- current_val. clone_from ( & change_val) ;
885- }
881+ let version = match self . historic . get ( ) . unwrap ( ) . get_version ( lsn. end . 0 - 1 ) {
882+ Some ( v) => v,
883+ None => {
884+ last_result = 0 ;
885+ continue ;
886+ }
887+ } ;
886888
887- // Consider the last part
888- if let Some ( val) = & current_val {
889- if val. get_lsn_range ( ) . end > lsn. start {
890- let kr = Key :: from_i128 ( current_key) ..Key :: from_i128 ( end) ;
891- let lr = lsn. start ..val. get_lsn_range ( ) . start ;
892-
893- if !kr. is_empty ( ) {
894- let base_count = Self :: is_reimage_worthy ( val, key) as usize ;
895- let new_limit = limit. map ( |l| l - base_count) ;
896- let max_stacked_deltas_underneath = self . count_deltas ( & kr, & lr, new_limit) ;
897- max_stacked_deltas = std:: cmp:: max (
898- max_stacked_deltas,
899- base_count + max_stacked_deltas_underneath,
889+ let start = key. start . to_i128 ( ) ;
890+ let end = key. end . to_i128 ( ) ;
891+
892+ let mut partitions = Vec :: new ( ) ;
893+ let mut current_key = start;
894+ let mut current_val = version. delta_coverage . query ( start) ;
895+
896+ let changes = version. delta_coverage . range ( start..end)
897+ . chain ( std:: iter:: once ( ( end, None ) ) ) ;
898+
899+ for ( change_key, next_val) in changes {
900+ if let Some ( val) = & current_val {
901+ if val. get_lsn_range ( ) . end > lsn. start {
902+ let kr = Key :: from_i128 ( current_key) ..Key :: from_i128 ( change_key) ;
903+ let lr = lsn. start ..val. get_lsn_range ( ) . start ;
904+ if !kr. is_empty ( ) {
905+ let base_count = Self :: is_reimage_worthy ( val, & key) as usize ;
906+ let new_limit = limit. map ( |l| l. saturating_sub ( base_count) ) ;
907+ partitions. push ( ( kr, lr, new_limit, base_count) ) ;
908+ }
909+ }
910+ }
911+ current_key = change_key;
912+ current_val = next_val;
913+ }
914+
915+ if partitions. is_empty ( ) {
916+ last_result = 0 ;
917+ continue ;
918+ }
919+
920+ let frame_idx = frames. len ( ) ;
921+ frames. push ( Frame { max_stacked_deltas : 0 } ) ;
922+
923+ tasks. push ( Task :: FinishFrame { frame_idx } ) ;
924+
925+ for ( kr, lr, new_limit, base_count) in partitions. into_iter ( ) . rev ( ) {
926+ tasks. push ( Task :: Accumulate {
927+ frame_idx,
928+ base_count,
929+ } ) ;
930+ tasks. push ( Task :: Process {
931+ key : kr,
932+ lsn : lr,
933+ limit : new_limit,
934+ } ) ;
935+ }
936+ }
937+ Task :: Accumulate { frame_idx, base_count } => {
938+ frames[ frame_idx] . max_stacked_deltas = std:: cmp:: max (
939+ frames[ frame_idx] . max_stacked_deltas ,
940+ base_count + last_result,
900941 ) ;
901942 }
943+ Task :: FinishFrame { frame_idx } => {
944+ last_result = frames[ frame_idx] . max_stacked_deltas ;
945+ }
902946 }
903947 }
904948
905- max_stacked_deltas
949+ last_result
906950 }
907951
908952 /* BEGIN_HADRON */
@@ -1808,6 +1852,34 @@ mod tests {
18081852 assert_eq ! ( Lsn ( 100 ) , image_consistent_lsn) ;
18091853 }
18101854
1855+ #[ test]
1856+ fn test_count_deltas_stack_overflow ( ) {
1857+ let mut layer_map = LayerMap :: default ( ) ;
1858+ let mut updates = layer_map. batch_update ( ) ;
1859+
1860+ let tenant_shard_id = TenantShardId :: unsharded ( TenantId :: generate ( ) ) ;
1861+ let timeline_id = TimelineId :: generate ( ) ;
1862+
1863+ // Insert 10,000 overlapping delta layers with increasing LSN ranges
1864+ // Use a non-L0 key range so that all layers are considered reimage-worthy at all partition ranges.
1865+ let key_range = Key :: from_i128 ( 0 ) ..Key :: from_i128 ( 100 ) ;
1866+ for i in 1 ..10000 {
1867+ let desc = PersistentLayerDesc :: new_delta (
1868+ tenant_shard_id,
1869+ timeline_id,
1870+ key_range. clone ( ) ,
1871+ Lsn ( i * 10 ) ..Lsn ( i * 10 + 5 ) ,
1872+ 1024 ,
1873+ ) ;
1874+ updates. insert_historic ( desc) ;
1875+ }
1876+ updates. flush ( ) ;
1877+
1878+ // This call will recursively traverse all 10,000 layers in the original code, causing stack overflow
1879+ let count = layer_map. count_deltas ( & key_range, & ( Lsn ( 0 ) ..Lsn ( 100000 ) ) , None ) ;
1880+ assert_eq ! ( count, 9999 ) ;
1881+ }
1882+
18111883 /* END_HADRON */
18121884}
18131885
0 commit comments