@@ -278,11 +278,11 @@ impl ScenarioHarness {
278278 }
279279
280280 async fn compact_l0 (
281- & self ,
281+ & mut self ,
282282 sst_ids : Vec < u64 > ,
283283 target_level : u32 ,
284284 ) -> Result < crate :: compaction:: executor:: CompactionOutcome , Box < dyn std:: error:: Error > > {
285- let start_id = 10_000 ;
285+ let start_id = self . next_sst_id . max ( 10_000 ) ;
286286 let outcome = compact_merge_l0 (
287287 self . db . inner ( ) . as_ref ( ) ,
288288 sst_ids,
@@ -291,6 +291,14 @@ impl ScenarioHarness {
291291 start_id,
292292 )
293293 . await ?;
294+ let next_generated_id = outcome
295+ . add_ssts
296+ . iter ( )
297+ . map ( |entry| entry. sst_id ( ) . raw ( ) )
298+ . max ( )
299+ . map ( |id| id. saturating_add ( 1 ) )
300+ . unwrap_or_else ( || start_id. saturating_add ( 1 ) ) ;
301+ self . next_sst_id = self . next_sst_id . max ( next_generated_id) ;
294302 Ok ( outcome)
295303 }
296304
@@ -636,6 +644,7 @@ struct ModelRunner {
636644 oracle : MvccOracle ,
637645 l0_ssts : Vec < u64 > ,
638646 active_snapshot_ts : Option < u64 > ,
647+ active_snapshot : Option < TxSnapshot > ,
639648 allow_reopen : bool ,
640649 eager_flush : bool ,
641650 allow_sst : bool ,
@@ -659,6 +668,7 @@ impl ModelRunner {
659668 oracle : MvccOracle :: default ( ) ,
660669 l0_ssts : Vec :: new ( ) ,
661670 active_snapshot_ts : None ,
671+ active_snapshot : None ,
662672 allow_reopen,
663673 eager_flush,
664674 allow_sst,
@@ -673,6 +683,11 @@ impl ModelRunner {
673683 }
674684 }
675685
686+ fn clear_active_snapshot ( & mut self ) {
687+ self . active_snapshot_ts = None ;
688+ self . active_snapshot = None ;
689+ }
690+
676691 async fn run ( & mut self ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
677692 for _ in 0 ..MODEL_OPS_PER_SEED {
678693 let op_kind = self . pick_op ( ) ;
@@ -703,6 +718,7 @@ impl ModelRunner {
703718 async fn apply_op ( & mut self , op_kind : OpKind ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
704719 match op_kind {
705720 OpKind :: Put => {
721+ self . clear_active_snapshot ( ) ;
706722 let key = self . pick_key ( ) ;
707723 let value = self . rng . next_i64 ( 10_000 ) ;
708724 self . trace . push ( Op :: Put {
@@ -720,6 +736,7 @@ impl ModelRunner {
720736 }
721737 }
722738 OpKind :: Delete => {
739+ self . clear_active_snapshot ( ) ;
723740 let key = self . pick_key ( ) ;
724741 self . trace . push ( Op :: Delete { key : key. clone ( ) } ) ;
725742 self . harness . ingest_delete ( & key, & mut self . oracle ) . await ?;
@@ -731,6 +748,7 @@ impl ModelRunner {
731748 }
732749 }
733750 OpKind :: Flush => {
751+ self . clear_active_snapshot ( ) ;
734752 self . trace . push ( Op :: Flush ) ;
735753 if self . allow_sst {
736754 if let Some ( sst_id) = self . harness . try_flush_immutables_to_l0 ( ) . await ? {
@@ -764,18 +782,7 @@ impl ModelRunner {
764782 if outcome. add_ssts . is_empty ( ) {
765783 return Err ( "compaction produced no output sst" . into ( ) ) ;
766784 }
767- let snapshot_ts = match self . active_snapshot_ts {
768- Some ( ts) => ts,
769- None => {
770- let snapshot = self . harness . db . begin_snapshot ( ) . await ?;
771- snapshot. read_view ( ) . read_ts ( ) . get ( )
772- }
773- } ;
774- let snapshot = self
775- . harness
776- . db
777- . snapshot_at ( Timestamp :: new ( snapshot_ts) )
778- . await ?;
785+ let ( snapshot, snapshot_ts) = self . read_snapshot ( ) . await ?;
779786 let ctx = self . failure_context ( Some ( snapshot_ts) ) ;
780787 assert_oracle_matches (
781788 "model_based_compaction" ,
@@ -836,6 +843,7 @@ impl ModelRunner {
836843 let snapshot = self . harness . db . begin_snapshot ( ) . await ?;
837844 let snapshot_ts = snapshot. read_view ( ) . read_ts ( ) . get ( ) ;
838845 self . active_snapshot_ts = Some ( snapshot_ts) ;
846+ self . active_snapshot = Some ( snapshot. clone ( ) ) ;
839847 self . trace . push ( Op :: Snapshot { snapshot_ts } ) ;
840848 let ctx = self . failure_context ( Some ( snapshot_ts) ) ;
841849 assert_oracle_matches (
@@ -868,15 +876,23 @@ impl ModelRunner {
868876 Some ( & ctx) ,
869877 )
870878 . await ?;
879+ self . active_snapshot = Some ( snapshot) ;
880+ } else {
881+ self . active_snapshot = None ;
871882 }
872883 }
873884 }
874885 Ok ( ( ) )
875886 }
876887
877888 async fn read_snapshot ( & mut self ) -> Result < ( TxSnapshot , u64 ) , Box < dyn std:: error:: Error > > {
878- if let Some ( ts) = self . active_snapshot_ts {
889+ if let Some ( snapshot) = self . active_snapshot . as_ref ( )
890+ && let Some ( ts) = self . active_snapshot_ts
891+ {
892+ Ok ( ( snapshot. clone ( ) , ts) )
893+ } else if let Some ( ts) = self . active_snapshot_ts {
879894 let snapshot = self . harness . db . snapshot_at ( Timestamp :: new ( ts) ) . await ?;
895+ self . active_snapshot = Some ( snapshot. clone ( ) ) ;
880896 Ok ( ( snapshot, ts) )
881897 } else {
882898 let snapshot = self . harness . db . begin_snapshot ( ) . await ?;
@@ -1236,9 +1252,6 @@ async fn compaction_correctness_reopen_snapshot_durability()
12361252 let snapshot_ts = snapshot. read_view ( ) . read_ts ( ) . get ( ) ;
12371253 assert_oracle_matches ( scenario, snapshot_ts, & snapshot, & oracle, & harness. db , None ) . await ?;
12381254
1239- // Note: avoid compaction here because some SSTs can be missing Parquet page indexes,
1240- // and the compaction read path currently requires them. Re-enable compaction coverage
1241- // once page index emission is consistent for all SSTs.
12421255 harness. reopen ( ) . await ?;
12431256 let reopened_snapshot = harness. db . snapshot_at ( Timestamp :: new ( snapshot_ts) ) . await ?;
12441257 assert_oracle_matches (
@@ -1254,6 +1267,85 @@ async fn compaction_correctness_reopen_snapshot_durability()
12541267 Ok ( ( ) )
12551268}
12561269
1270+ #[ tokio:: test( flavor = "multi_thread" , worker_threads = 2 ) ]
1271+ async fn compaction_correctness_delete_only_sst_reopen_and_compact ( )
1272+ -> Result < ( ) , Box < dyn std:: error:: Error > > {
1273+ let scenario = "delete_only_sst_reopen_and_compact" ;
1274+ let mut harness = ScenarioHarness :: new ( "compaction-correctness-delete-only-sst" ) . await ?;
1275+ let mut oracle = MvccOracle :: default ( ) ;
1276+
1277+ let _ts0 = harness. ingest_delete ( "k01" , & mut oracle) . await ?;
1278+ let sst0 = harness. flush_immutables_to_l0 ( ) . await ?;
1279+
1280+ let _ts1 = harness. ingest_put ( "k02" , 20 , & mut oracle) . await ?;
1281+ let sst1 = harness. flush_immutables_to_l0 ( ) . await ?;
1282+
1283+ let snapshot = harness. db . begin_snapshot ( ) . await ?;
1284+ let snapshot_ts = snapshot. read_view ( ) . read_ts ( ) . get ( ) ;
1285+ assert_oracle_matches ( scenario, snapshot_ts, & snapshot, & oracle, & harness. db , None ) . await ?;
1286+ assert_range_matches (
1287+ scenario,
1288+ snapshot_ts,
1289+ & snapshot,
1290+ & oracle,
1291+ & harness. db ,
1292+ "k01" ,
1293+ "k02" ,
1294+ None ,
1295+ )
1296+ . await ?;
1297+
1298+ harness. reopen ( ) . await ?;
1299+ let reopened = harness. db . snapshot_at ( Timestamp :: new ( snapshot_ts) ) . await ?;
1300+ assert_oracle_matches ( scenario, snapshot_ts, & reopened, & oracle, & harness. db , None ) . await ?;
1301+ assert_range_matches (
1302+ scenario,
1303+ snapshot_ts,
1304+ & reopened,
1305+ & oracle,
1306+ & harness. db ,
1307+ "k01" ,
1308+ "k02" ,
1309+ None ,
1310+ )
1311+ . await ?;
1312+
1313+ let outcome = harness. compact_l0 ( vec ! [ sst0, sst1] , 1 ) . await ?;
1314+ assert_eq ! (
1315+ outcome. remove_ssts. len( ) ,
1316+ 2 ,
1317+ "scenario={scenario} expected compaction to remove 2 SSTs"
1318+ ) ;
1319+ assert ! (
1320+ !outcome. add_ssts. is_empty( ) ,
1321+ "scenario={scenario} expected compaction to add SSTs"
1322+ ) ;
1323+
1324+ let post_snapshot = harness. db . snapshot_at ( Timestamp :: new ( snapshot_ts) ) . await ?;
1325+ assert_oracle_matches (
1326+ scenario,
1327+ snapshot_ts,
1328+ & post_snapshot,
1329+ & oracle,
1330+ & harness. db ,
1331+ None ,
1332+ )
1333+ . await ?;
1334+ assert_range_matches (
1335+ scenario,
1336+ snapshot_ts,
1337+ & post_snapshot,
1338+ & oracle,
1339+ & harness. db ,
1340+ "k01" ,
1341+ "k02" ,
1342+ None ,
1343+ )
1344+ . await ?;
1345+
1346+ Ok ( ( ) )
1347+ }
1348+
12571349#[ tokio:: test( flavor = "multi_thread" , worker_threads = 2 ) ]
12581350async fn compaction_correctness_iterator_seek_stability ( ) -> Result < ( ) , Box < dyn std:: error:: Error > >
12591351{
0 commit comments