Repository: cockroachdb/pebble

Issue:
db: data race with manifest.L0Organizer 
github.com/cockroachdb/pebble/internal/metamorphic.TestMeta [failed](https://teamcity.cockroachdb.com/buildConfiguration/Cockroach_Nightlies_NightlyPebbleMetamorphicRace/19304138?buildTab=log) with [artifacts](https://teamcity.cockroachdb.com/buildConfiguration/Cockroach_Nightlies_NightlyPebbleMetamorphicRace/19304138?buildTab=artifacts#meta) on refs/heads/master @ [c0b18bc8970e](https://github.com/cockroachdb/pebble/commits/c0b18bc8970e):


```
        db1.Set("ltfiguyav", "givpi") // <nil> #1037
        db1.SingleDelete("hoedxsh@2", false /* maybeReplaceDelete */) // <nil> #1038
        db1.RangeKeyUnset("fygfkcwsj", "ialfiyohhcd", "@7") // <nil> #1039
        db1.SingleDelete("bxbgtwq@5", false /* maybeReplaceDelete */) // <nil> #1040
        db1.DeleteRange("ltfiguyav", "oqyvflhu") // <nil> #1041
        db1.Merge("aswdunglrchp@5", "bhhyudiiszlkjiti") // <nil> #1042
        db1.Set("fbqryqypc", "wpql") // <nil> #1043
        db1.Merge("cplcdi@6", "keng") // <nil> #1044
        db1.Delete("bxbgtwq@4") // <nil> #1045
        db1.Set("dqccfcxuax", "ivepevotxj") // <nil> #1046
        db1.Merge("tpnhsbn", "assjfrtwobwbdt") // <nil> #1047
        db1.LogData("n") // <nil> #1048
        // 04:08:50.022 INFO: possible API misuse: ineffectual SINGLEDEL (key="xihzpiiott@3")
        // 04:08:50.024 INFO: [JOB 38] compacted(elision-only) L6 [000021] (3.2KB) Score=0.00 + L6 [] (0B) Score=0.00 -> L6 [000025] (1.4KB), in 0.0s (0.0s total), output rate 208KB/s
        snap9.Get("wzfowhh@1") // [""] pebble: not found #1049
        snap9.Get("kvshykocdfgs") // [""] pebble: not found #1050
        db1.Set("uvdmbswxx@9", "dzpcgesfewkp") // <nil> #1051
        db1.Delete("hoedxsh@2") // <nil> #1052
        db1.Delete("owquuxvdwsq@1") // <nil> #1053
        db1.Delete("dwoadyz@1") // <nil> #1054
        db1.Delete("bqex@5") // <nil> #1055
        db1.RangeKeySet("ntpbp", "ujvmmflmf", "@9", "oapwbvv") // <nil> #1056
        db1.RangeKeyUnset("kgudeihxl", "xsgyuijvc", "@6") // <nil> #1057
        db1.SingleDelete("vjlyodk@7", false /* maybeReplaceDelete */) // <nil> #1058
        db1.RangeKeySet("ewgw", "pfusfqpzvq", "@5", "qrkhvfbsyczerqbufur") // <nil> #1059
        db1.Delete("ffhkc") // <nil> #1060
        db1.Delete("ltfiguyav@6") // <nil> #1061
        db1.RangeKeyUnset("ntpbp", "owquuxvdwsq", "") // <nil> #1062
        db1.Get("oqyvflhu@6") // [""] pebble: not found #1063
        db1.Delete("yepfpcoemt@5") // <nil> #1064
        db1.LogData("cb") // <nil> #1065
        batch7 = db1.NewBatch() #1066
        batch7.Merge("bqex@5", "ysvnujdfjl") // <nil> #1068
        batch7.Merge("vnoz@9", "zhfky") // <nil> #1075
        // 04:08:50.027 INFO: [JOB 35] flushed 1 memtable (2.3KB) to L0 [000026] (1.7KB), in 0.0s (0.0s total), output rate 90KB/s
        // 04:08:50.027 INFO: [JOB 39] flushing 1 ingested table
        snap9.Get("xihzpiiott@2") // [""] pebble: not found #1067
        snap9.Get("tpnhsbn") // [""] pebble: not found #1069
        db1.Delete("wzfowhh@1") // <nil> #1070
        db1.SingleDelete("dwoadyz@1", false /* maybeReplaceDelete */) // <nil> #1071
        db1.Set("rsyypv@9", "riuzbyqrbjsgub") // <nil> #1072
        db1.RangeKeySet("jmqyjo", "omjr", "@8", "") // <nil> #1073
        db1.Set("cplcdi@6", "zdkcdksthsmhc") // <nil> #1074
        // 04:08:50.035 INFO: [JOB 38] sstable deleted 000021
        // 04:08:50.035 INFO: [JOB 35] WAL deleted 000018
        // 04:08:50.037 INFO: [JOB 41] ingesting: sstable created 000027
        // 04:08:50.038 INFO: [JOB 42] WAL created 000028
        
        To reduce:  go test ./internal/metamorphic -tags invariants -run 'TestMeta$' --run-dir /artifacts/meta/250403-040838.6934054690118/standard-014 --try-to-reduce -v
--- FAIL: TestMeta/execution/standard-014 (0.73s)
```
<details><summary>Help</summary>
<p>
To reproduce, try:

```bash
go test -tags 'invariants' -exec 'stress -p 1' -timeout 0 -test.v -run 'TestMeta$' ./internal/metamorphic -seed 1743653318693259594 -ops "uniform:5000-10000"
```
</p>
</details>
<sub>

[This test on roachdash](https://roachdash.crdb.dev/?filter=status:open%20t:.*TestMeta.*&sort=title+created&display=lastcommented+project) | [Improve this report!](https://github.com/cockroachdb/cockroach/tree/master/pkg/cmd/bazci/githubpost/issues)

</sub>


Jira issue: PEBBLE-379

Hints from issue comments:
@jbowens: ```
=== NAME  TestMeta/execution/random-027
    meta.go:244: 
        ===== SEED =====
        1743653318693259594
        ===== ERR =====
        exit status 66
        ===== OUT =====
        ==================
        WARNING: DATA RACE
        Write at 0x00c000036410 by goroutine 941:
          github.com/cockroachdb/pebble/internal/manifest.(*L0Organizer).Update()
              external/com_github_cockroachdb_pebble/internal/manifest/l0_sublevels.go:2178 +0x35d
          github.com/cockroachdb/pebble/internal/manifest.(*BulkVersionEdit).Apply()
              external/com_github_cockroachdb_pebble/internal/manifest/version_edit.go:1296 +0x12c7
          github.com/cockroachdb/pebble.(*versionSet).logAndApply.func1()
              external/com_github_cockroachdb_pebble/version_set.go:576 +0x27a
          github.com/cockroachdb/pebble.(*versionSet).logAndApply()
              external/com_github_cockroachdb_pebble/version_set.go:626 +0xad7
          github.com/cockroachdb/pebble.(*DB).compact1.func2()
              external/com_github_cockroachdb_pebble/compaction.go:2474 +0x384
          github.com/cockroachdb/pebble.(*DB).compact1()
              external/com_github_cockroachdb_pebble/compaction.go:2477 +0x56f
          github.com/cockroachdb/pebble.(*DB).compact.func1()
              external/com_github_cockroachdb_pebble/compaction.go:2330 +0xb1
          runtime/pprof.Do()
              GOROOT/src/runtime/pprof/runtime.go:51 +0x117
          github.com/cockroachdb/pebble.(*DB).compact()
              external/com_github_cockroachdb_pebble/compaction.go:2327 +0xe4
          github.com/cockroachdb/pebble.(*DB).runPickedCompaction.func1()
              external/com_github_cockroachdb_pebble/compaction.go:1842 +0x46
        
        Previous read at 0x00c000036410 by goroutine 1049:
          github.com/cockroachdb/pebble.newFlush()
              external/com_github_cockroachdb_pebble/compaction.go:579 +0x964
          github.com/cockroachdb/pebble.(*DB).flush1()
              external/com_github_cockroachdb_pebble/compaction.go:1468 +0x6a4
          github.com/cockroachdb/pebble.(*DB).flush.func1()
              external/com_github_cockroachdb_pebble/compaction.go:1230 +0x124
          runtime/pprof.Do()
              GOROOT/src/runtime/pprof/runtime.go:51 +0x117
          github.com/cockroachdb/pebble.(*DB).flush()
              external/com_github_cockroachdb_pebble/compaction.go:1223 +0x91
          github.com/cockroachdb/pebble.(*DB).maybeScheduleFlush.gowrap1()
              external/com_github_cockroachdb_pebble/compaction.go:1139 +0x33
        
        Goroutine 941 (running) created at:
          github.com/cockroachdb/pebble.(*DB).runPickedCompaction()
              external/com_github_cockroachdb_pebble/compaction.go:1841 +0x744
          github.com/cockroachdb/pebble.(*DB).maybeScheduleCompaction()
              external/com_github_cockroachdb_pebble/compaction.go:1772 +0x996
          github.com/cockroachdb/pebble.(*DB).collectTableStats()
              external/com_github_cockroachdb_pebble/table_stats.go:161 +0x71c
          github.com/cockroachdb/pebble.(*DB).maybeCollectTableStatsLocked.gowrap1()
              external/com_github_cockroachdb_pebble/table_stats.go:51 +0x33
        
        Goroutine 1049 (running) created at:
          github.com/cockroachdb/pebble.(*DB).maybeScheduleFlush()
              external/com_github_cockroachdb_pebble/compaction.go:1139 +0x186
          github.com/cockroachdb/pebble.(*DB).rotateMemtable()
              external/com_github_cockroachdb_pebble/db.go:2659 +0x1f6
          github.com/cockroachdb/pebble.(*DB).handleIngestAsFlushable()
              external/com_github_cockroachdb_pebble/ingest.go:1425 +0xb2c
          github.com/cockroachdb/pebble.(*DB).ingest.func1()
              external/com_github_cockroachdb_pebble/ingest.go:1642 +0x1265
          github.com/cockroachdb/pebble.(*commitPipeline).AllocateSeqNum()
              external/com_github_cockroachdb_pebble/commit.go:416 +0x709
          github.com/cockroachdb/pebble.(*DB).ingest()
              external/com_github_cockroachdb_pebble/ingest.go:1704 +0xf3d
          github.com/cockroachdb/pebble.(*DB).Ingest()
              external/com_github_cockroachdb_pebble/ingest.go:1147 +0x184
          github.com/cockroachdb/pebble/metamorphic.(*ingestExternalFilesOp).run()
              external/com_github_cockroachdb_pebble/metamorphic/ops.go:1045 +0x5b8
          github.com/cockroachdb/pebble/metamorphic.(*Test).runOp()
              external/com_github_cockroachdb_pebble/metamorphic/test.go:447 +0x282
          github.com/cockroachdb/pebble/metamorphic.Execute.func1()
              external/com_github_cockroachdb_pebble/metamorphic/meta.go:639 +0x224
          golang.org/x/sync/errgroup.(*Group).Go.func1()
              external/org_golang_x_sync/errgroup/errgroup.go:78 +0x91
        ==================
    
```
@RaduBerinde @RaduBerinde: Interesting.. I believe that this race existed already at a conceptual level, but in practice we ended up having to regenerate L0 sublevels completely after each compaction so we didn't clobber the previous L0Sublevels data.

Solution patch:
diff --git a/internal/manifest/l0_sublevels.go b/internal/manifest/l0_sublevels.go
index 86bcc419..de0e6ec8 100644
--- a/internal/manifest/l0_sublevels.go
+++ b/internal/manifest/l0_sublevels.go
@@ -18,10 +18,6 @@ import (
 	"github.com/cockroachdb/pebble/internal/invariants"
 )
 
-// errInvalidL0SublevelsOpt is for use in addL0Files when the incremental
-// sublevel generation optimization failed, and newL0Sublevels must be called.
-var errInvalidL0SublevelsOpt = errors.New("pebble: L0 sublevel generation optimization cannot be used")
-
 // Intervals are of the form [start, end) with no gap between intervals. Each
 // file overlaps perfectly with a sequence of intervals. This perfect overlap
 // occurs because the union of file boundary keys is used to pick intervals.
@@ -314,9 +310,7 @@ func newL0Sublevels(
 	// Initialize minIntervalIndex and maxIntervalIndex for each file, and use that
 	// to update intervals.
 	for f := iter.First(); f != nil; f = iter.Next() {
-		if err := s.addFileToSublevels(f, false /* checkInvariant */); err != nil {
-			return nil, err
-		}
+		s.addFileToSublevels(f)
 	}
 	// Sort each sublevel in increasing key order.
 	for i := range s.levelFiles {
@@ -409,51 +403,20 @@ func mergeIntervals(
 	return result, oldToNewMap
 }
 
-// addL0Files incrementally builds a new l0Sublevels for when the only change
-// since the receiver l0Sublevels was an addition of the specified tables, with
-// no L0 deletions. The common case of this is an ingestion or a flush. These
-// files can "sit on top" of existing sublevels, creating at most one new
-// sublevel for a flush (and possibly multiple for an ingestion), and at most
-// 2*len(files) additions to s.orderedIntervals. No files must have been deleted
-// from L0, and the added files must all be newer in sequence numbers than
-// existing files in l0Sublevels. The levelMetadata parameter corresponds to the
-// new L0 post addition of files. This method is meant to be significantly more
-// performant than newL0Sublevels.
-//
-// Note that this function can only be called once on a given receiver; it
-// appends to some slices in s which is only safe when done once. This is okay,
-// as the common case (generating a new l0Sublevels after a flush/ingestion) is
-// only going to necessitate one call of this method on a given receiver. The
-// returned value, if non-nil, can then have [*l0Sublevels.addL0Files] called on
-// it again, and so on. If [errInvalidL0SublevelsOpt] is returned as an error,
-// it means the optimization could not be applied (i.e. files added were older
-// than files already in the sublevels, which is possible around ingestions and
-// in tests). Eg. it can happen when an ingested file was ingested without
-// queueing a flush since it did not actually overlap with any keys in the
-// memtable. Later on the memtable was flushed, and the memtable had keys
-// spanning around the ingested file, producing a flushed file that overlapped
-// with the ingested file in file bounds but not in keys. It's possible for that
-// flushed file to have a lower LargestSeqNum than the ingested file if all the
-// additions after the ingestion were to another flushed file that was split
-// into a separate sstable during flush. Any other non-nil error means
-// [l0Sublevels] generation failed in the same way as [newL0Sublevels] would
-// likely fail.
-func (s *l0Sublevels) addL0Files(
-	addedTables map[base.FileNum]*TableMetadata,
-	flushSplitMaxBytes int64,
-	levelMetadata *LevelMetadata,
-) (*l0Sublevels, error) {
+func (s *l0Sublevels) canUseAddL0Files(
+	addedTables map[base.FileNum]*TableMetadata, levelMetadata *LevelMetadata,
+) (filesToAddInOrder []*TableMetadata, ok bool) {
 	if s.addL0FilesCalled {
 		if invariants.Enabled {
 			panic("addL0Files called twice on the same receiver")
 		}
-		return nil, errInvalidL0SublevelsOpt
+		return nil, false
 	}
 	if s.levelMetadata.Len()+len(addedTables) != levelMetadata.Len() {
 		if invariants.Enabled {
 			panic("levelMetadata mismatch")
 		}
-		return nil, errInvalidL0SublevelsOpt
+		return nil, false
 	}
 
 	// addL0Files only works when the files we are adding match exactly the last
@@ -465,12 +428,40 @@ func (s *l0Sublevels) addL0Files(
 		if addedTables[t.FileNum] == nil {
 			// t is an existing table that sorts after some of the new tables
 			// (specifically the ones we haven't yet seen).
-			return nil, errInvalidL0SublevelsOpt
+			return nil, false
 		}
 		files[i] = t
 		t = iter.Prev()
 	}
-	// Note: files now contains addedTables in increasing seqnum order.
+	return files, true
+}
+
+// addL0Files incrementally builds a new l0Sublevels for when the only change
+// since the receiver l0Sublevels was an addition of the specified tables, with
+// no L0 deletions. The common case of this is an ingestion or a flush. These
+// files can "sit on top" of existing sublevels, creating at most one new
+// sublevel for a flush (and possibly multiple for an ingestion), and at most
+// 2*len(files) additions to s.orderedIntervals. No files must have been deleted
+// from L0, and the added files must all be newer in sequence numbers than
+// existing files in l0Sublevels. The levelMetadata parameter corresponds to the
+// new L0 post addition of files. This method is meant to be significantly more
+// performant than newL0Sublevels.
+//
+// This function is intended to be called with the result of canUseAddL0Files(),
+// which is the list of new L0 tables in increasing L0 order.
+//
+// Note that this function can only be called once on a given receiver; it
+// appends to some slices in s which is only safe when done once. This is okay,
+// as the common case (generating a new l0Sublevels after a flush/ingestion) is
+// only going to necessitate one call of this method on a given receiver. The
+// returned value, if non-nil, can then have [*l0Sublevels.addL0Files] called on
+// it again, and so on.
+func (s *l0Sublevels) addL0Files(
+	files []*TableMetadata, flushSplitMaxBytes int64, levelMetadata *LevelMetadata,
+) *l0Sublevels {
+	if s.addL0FilesCalled {
+		panic("addL0Files called twice on the same receiver")
+	}
 	s.addL0FilesCalled = true
 
 	// Start with a shallow copy of s.
@@ -618,9 +609,7 @@ func (s *l0Sublevels) addL0Files(
 	// Update interval indices for new files.
 	for i, f := range files {
 		f.L0Index = s.levelMetadata.Len() + i
-		if err := newVal.addFileToSublevels(f, true /* checkInvariant */); err != nil {
-			return nil, err
-		}
+		newVal.addFileToSublevels(f)
 		updatedSublevels = append(updatedSublevels, f.SubLevel)
 	}
 
@@ -658,16 +647,14 @@ func (s *l0Sublevels) addL0Files(
 	newVal.flushSplitUserKeys = nil
 	newVal.calculateFlushSplitKeys(flushSplitMaxBytes)
 	newVal.Check()
-	return newVal, nil
+	return newVal
 }
 
 // addFileToSublevels is called during l0Sublevels generation, and adds f to the
 // correct sublevel's levelFiles, the relevant intervals' files slices, and sets
 // interval indices on f. This method, if called successively on multiple files,
-// _must_ be called on successively newer files (by seqnum). If checkInvariant
-// is true, it could check for this in some cases and return
-// [errInvalidL0SublevelsOpt] if that invariant isn't held.
-func (s *l0Sublevels) addFileToSublevels(f *TableMetadata, checkInvariant bool) error {
+// _must_ be called on successively newer files (by seqnum).
+func (s *l0Sublevels) addFileToSublevels(f *TableMetadata) {
 	// This is a simple and not very accurate estimate of the number of
 	// bytes this SSTable contributes to the intervals it is a part of.
 	//
@@ -680,10 +667,8 @@ func (s *l0Sublevels) addFileToSublevels(f *TableMetadata, checkInvariant bool)
 	for i := f.minIntervalIndex; i <= f.maxIntervalIndex; i++ {
 		interval := &s.orderedIntervals[i]
 		if len(interval.files) > 0 {
-			if checkInvariant && interval.files[len(interval.files)-1].LargestSeqNum > f.LargestSeqNum {
-				// We are sliding this file "underneath" an existing file. Throw away
-				// and start over in newL0Sublevels.
-				return errInvalidL0SublevelsOpt
+			if interval.files[len(interval.files)-1].LargestSeqNum > f.LargestSeqNum {
+				panic(errors.AssertionFailedf("addFileToSublevels found existing newer file"))
 			}
 			// interval.files is sorted by sublevels, from lowest to highest.
 			// addL0Files can only add files at sublevels higher than existing files
@@ -703,14 +688,13 @@ func (s *l0Sublevels) addFileToSublevels(f *TableMetadata, checkInvariant bool)
 	}
 	f.SubLevel = subLevel
 	if subLevel > len(s.levelFiles) {
-		return errors.Errorf("chose a sublevel beyond allowed range of sublevels: %d vs 0-%d", subLevel, len(s.levelFiles))
+		panic(errors.AssertionFailedf("chose a sublevel beyond allowed range of sublevels: %d vs 0-%d", subLevel, len(s.levelFiles)))
 	}
 	if subLevel == len(s.levelFiles) {
 		s.levelFiles = append(s.levelFiles, []*TableMetadata{f})
 	} else {
 		s.levelFiles[subLevel] = append(s.levelFiles[subLevel], f)
 	}
-	return nil
 }
 
 func (s *l0Sublevels) calculateFlushSplitKeys(flushSplitMaxBytes int64) {
@@ -2089,6 +2073,7 @@ type L0Organizer struct {
 	cmp             base.Compare
 	formatKey       base.FormatKey
 	flushSplitBytes int64
+	generation      int64
 
 	// levelMetadata is the current L0.
 	levelMetadata LevelMetadata
@@ -2124,71 +2109,108 @@ func NewL0Organizer(comparer *base.Comparer, flushSplitBytes int64) *L0Organizer
 	return o
 }
 
-// SublevelFiles returns the sublevels as LevelSlices. The returned value (both
-// the slice and each LevelSlice) is immutable. The L0Organizer creates new
-// slices every time L0 changes.
-func (o *L0Organizer) SublevelFiles() []LevelSlice {
-	return o.l0Sublevels.Levels
-}
-
-// Update the L0 organizer with the given L0 changes.
-func (o *L0Organizer) Update(
-	addedL0Tables map[base.FileNum]*TableMetadata,
-	deletedL0Tables map[base.FileNum]*TableMetadata,
-	newLevelMeta *LevelMetadata,
-) {
+// PrepareUpdate is the first step in the two-step process to update the
+// L0Organizer. This first step performs as much work as it can without
+// modifying the L0Organizer.
+//
+// This method can be called concurrently with other methods (other than
+// PerformUpdate). It allows doing most of the update work outside an important
+// lock.
+func (o *L0Organizer) PrepareUpdate(bve *BulkVersionEdit, newVersion *Version) L0PreparedUpdate {
+	addedL0Tables := bve.AddedTables[0]
+	deletedL0Tables := bve.DeletedTables[0]
+	newLevelMeta := &newVersion.Levels[0]
 	if invariants.Enabled && invariants.Sometimes(10) {
 		// Verify that newLevelMeta = m.levelMetadata + addedL0Tables - deletedL0Tables.
 		verifyLevelMetadataTransition(&o.levelMetadata, newLevelMeta, addedL0Tables, deletedL0Tables)
 	}
-	o.levelMetadata = *newLevelMeta
+
 	if len(addedL0Tables) == 0 && len(deletedL0Tables) == 0 {
-		return
+		return L0PreparedUpdate{
+			generation:   o.generation,
+			newSublevels: o.l0Sublevels,
+		}
 	}
-	// If we only added tables, try to use addL0Files.
+
 	if len(deletedL0Tables) == 0 {
-		newSublevels, err := o.l0Sublevels.addL0Files(addedL0Tables, o.flushSplitBytes, newLevelMeta)
-		if err == nil {
-			// In invariants mode, sometimes rebuild from scratch to verify that
-			// AddL0Files did the right thing. Note that NewL0Sublevels updates
-			// fields in TableMetadata like L0Index, so we don't want to do this
-			// every time.
-			if invariants.Enabled && invariants.Sometimes(10) {
-				expectedSublevels, err := newL0Sublevels(newLevelMeta, o.cmp, o.formatKey, o.flushSplitBytes)
-				if err != nil {
-					panic(fmt.Sprintf("error when regenerating sublevels: %s", err))
-				}
-				s1 := describeSublevels(o.formatKey, false /* verbose */, expectedSublevels.Levels)
-				s2 := describeSublevels(o.formatKey, false /* verbose */, newSublevels.Levels)
-				if s1 != s2 {
-					// Add verbosity.
-					s1 := describeSublevels(o.formatKey, true /* verbose */, expectedSublevels.Levels)
-					s2 := describeSublevels(o.formatKey, true /* verbose */, newSublevels.Levels)
-					panic(fmt.Sprintf("incremental L0 sublevel generation produced different output than regeneration: %s != %s", s1, s2))
-				}
+		if files, ok := o.l0Sublevels.canUseAddL0Files(addedL0Tables, newLevelMeta); ok {
+			return L0PreparedUpdate{
+				generation: o.generation,
+				addL0Files: files,
 			}
-			o.l0Sublevels = newSublevels
-			return
-		}
-		if !errors.Is(err, errInvalidL0SublevelsOpt) {
-			panic(errors.AssertionFailedf("error generating L0Sublevels: %s", err))
 		}
 	}
-	var err error
-	o.l0Sublevels, err = newL0Sublevels(newLevelMeta, o.cmp, o.formatKey, o.flushSplitBytes)
+	newSublevels, err := newL0Sublevels(newLevelMeta, o.cmp, o.formatKey, o.flushSplitBytes)
 	if err != nil {
 		panic(errors.AssertionFailedf("error generating L0Sublevels: %s", err))
 	}
+
+	return L0PreparedUpdate{
+		generation:   o.generation,
+		newSublevels: newSublevels,
+	}
+}
+
+// L0PreparedUpdate is returned by L0Organizer.PrepareUpdate(), to be passed to
+// PerformUpdate().
+type L0PreparedUpdate struct {
+	generation int64
+
+	// Exactly one of the following fields will be set.
+	addL0Files   []*TableMetadata
+	newSublevels *l0Sublevels
+}
+
+// PerformUpdate applies an update the L0 organizer which was previously
+// prepared using PrepareUpdate.
+//
+// Sets newVersion.L0SublevelFiles (which is immutable once set).
+//
+// This method cannot be called concurrently with any other methods.
+func (o *L0Organizer) PerformUpdate(prepared L0PreparedUpdate, newVersion *Version) {
+	if prepared.generation != o.generation {
+		panic("invalid L0 update generation")
+	}
+	o.levelMetadata = newVersion.Levels[0]
+	o.generation++
+	if prepared.addL0Files != nil {
+		newSublevels := o.l0Sublevels.addL0Files(prepared.addL0Files, o.flushSplitBytes, &o.levelMetadata)
+		// In invariants mode, sometimes rebuild from scratch to verify that
+		// AddL0Files did the right thing. Note that NewL0Sublevels updates
+		// fields in TableMetadata like L0Index, so we don't want to do this
+		// every time.
+		if invariants.Enabled && invariants.Sometimes(10) {
+			expectedSublevels, err := newL0Sublevels(&o.levelMetadata, o.cmp, o.formatKey, o.flushSplitBytes)
+			if err != nil {
+				panic(fmt.Sprintf("error when regenerating sublevels: %s", err))
+			}
+			s1 := describeSublevels(o.formatKey, false /* verbose */, expectedSublevels.Levels)
+			s2 := describeSublevels(o.formatKey, false /* verbose */, newSublevels.Levels)
+			if s1 != s2 {
+				// Add verbosity.
+				s1 := describeSublevels(o.formatKey, true /* verbose */, expectedSublevels.Levels)
+				s2 := describeSublevels(o.formatKey, true /* verbose */, newSublevels.Levels)
+				panic(fmt.Sprintf("incremental L0 sublevel generation produced different output than regeneration: %s != %s", s1, s2))
+			}
+		}
+		o.l0Sublevels = newSublevels
+	} else {
+		o.l0Sublevels = prepared.newSublevels
+	}
+	newVersion.L0SublevelFiles = o.l0Sublevels.Levels
 }
 
-// ResetForTesting reinitializes the L0Organizer to reflect a given L0 level.
-func (o *L0Organizer) ResetForTesting(levelMetadata *LevelMetadata) {
-	o.levelMetadata = *levelMetadata
+// ResetForTesting reinitializes the L0Organizer to reflect the given version.
+// Sets v.L0SublevelFiles.
+func (o *L0Organizer) ResetForTesting(v *Version) {
+	o.levelMetadata = v.Levels[0]
+	o.generation = 0
 	var err error
-	o.l0Sublevels, err = newL0Sublevels(levelMetadata, o.cmp, o.formatKey, o.flushSplitBytes)
+	o.l0Sublevels, err = newL0Sublevels(&v.Levels[0], o.cmp, o.formatKey, o.flushSplitBytes)
 	if err != nil {
 		panic(errors.AssertionFailedf("error generating L0Sublevels: %s", err))
 	}
+	v.L0SublevelFiles = o.l0Sublevels.Levels
 }
 
 // verifyLevelMetadataTransition verifies that newLevel matches oldLevel after
diff --git a/internal/manifest/version.go b/internal/manifest/version.go
index 531827c3..6630844d 100644
--- a/internal/manifest/version.go
+++ b/internal/manifest/version.go
@@ -1214,8 +1214,7 @@ func NewVersionForTesting(
 			v.Levels[l].totalSize += f.Size
 		}
 	}
-	l0Organizer.ResetForTesting(&v.Levels[0])
-	v.L0SublevelFiles = l0Organizer.SublevelFiles()
+	l0Organizer.ResetForTesting(v)
 	return v
 }
 
diff --git a/internal/manifest/version_edit.go b/internal/manifest/version_edit.go
index c2b67b64..baf151fa 100644
--- a/internal/manifest/version_edit.go
+++ b/internal/manifest/version_edit.go
@@ -1149,9 +1149,10 @@ func (b *BulkVersionEdit) Accumulate(ve *VersionEdit) error {
 // the levels.
 //
 // curr may be nil, which is equivalent to a pointer to a zero version.
-func (b *BulkVersionEdit) Apply(
-	curr *Version, l0Organizer *L0Organizer, readCompactionRate int64,
-) (*Version, error) {
+//
+// Not that L0SublevelFiles is not initialized in the returned version; it is
+// the caller's responsibility to set it using L0Organizer.PerformUpdate().
+func (b *BulkVersionEdit) Apply(curr *Version, readCompactionRate int64) (*Version, error) {
 	comparer := curr.cmp
 	v := &Version{
 		cmp: comparer,
@@ -1293,9 +1294,6 @@ func (b *BulkVersionEdit) Apply(
 		}
 	}
 
-	l0Organizer.Update(b.AddedTables[0], b.DeletedTables[0], &v.Levels[0])
-	v.L0SublevelFiles = l0Organizer.SublevelFiles()
-
 	// We maintain stats about active references in blob files and can infer
 	// when a blob file has become a 'zombie,' and is no longer referenced in
 	// the resulting version. However, we expect the caller to explicitly list
diff --git a/record/record.go b/record/record.go
index aaa1dbd0..65f840e8 100644
--- a/record/record.go
+++ b/record/record.go
@@ -250,6 +250,13 @@ type Reader struct {
 	// that had garbage values. It is used to clarify whether or not a garbage chunk
 	// encountered during WAL replay was the logical EOF or confirmed corruption.
 	invalidOffset uint64
+
+	// loggerForTesting is a logging helper used by the Reader to accumulate log messages.
+	loggerForTesting loggerForTesting
+}
+
+type loggerForTesting interface {
+	logf(format string, args ...interface{})
 }
 
 // NewReader returns a new reader. If the file contains records encoded using
@@ -422,11 +429,18 @@ func (r *Reader) Next() (io.Reader, error) {
 // if there is confirmation of a corruption, otherwise ErrUnexpectedEOF is
 // returned after reading all the blocks without corruption confirmation.
 func (r *Reader) readAheadForCorruption() error {
+	if r.loggerForTesting != nil {
+		r.loggerForTesting.logf("Starting read ahead for corruption. Block corrupted %d.\n", r.blockNum)
+	}
+
 	for {
 		// Load the next block into r.buf.
 		n, err := io.ReadFull(r.r, r.buf[:])
 		r.begin, r.end, r.n = 0, 0, n
 		r.blockNum++
+		if r.loggerForTesting != nil {
+			r.loggerForTesting.logf("Read block %d with %d bytes\n", r.blockNum, n)
+		}
 
 		if errors.Is(err, io.EOF) {
 			// io.ErrUnexpectedEOF is returned instead of
@@ -439,6 +453,9 @@ func (r *Reader) readAheadForCorruption() error {
 			// invalid chunk should have been valid, the chunk represents
 			// an abrupt, unclean termination of the logical log. This
 			// abrupt end of file represented by io.ErrUnexpectedEOF.
+			if r.loggerForTesting != nil {
+				r.loggerForTesting.logf("\tEncountered io.EOF; returning io.ErrUnexpectedEOF since no sync offset found.\n")
+			}
 			return io.ErrUnexpectedEOF
 		}
 		// The last block of a log can be less than 32KiB, which is
@@ -447,6 +464,9 @@ func (r *Reader) readAheadForCorruption() error {
 		// However, if the error is not ErrUnexpectedEOF, then this
 		// error should be surfaced.
 		if err != nil && err != io.ErrUnexpectedEOF {
+			if r.loggerForTesting != nil {
+				r.loggerForTesting.logf("\tError reading block %d: %v", r.blockNum, err)
+			}
 			return err
 		}
 
@@ -454,24 +474,44 @@ func (r *Reader) readAheadForCorruption() error {
 			checksum := binary.LittleEndian.Uint32(r.buf[r.end+0 : r.end+4])
 			length := binary.LittleEndian.Uint16(r.buf[r.end+4 : r.end+6])
 			chunkEncoding := r.buf[r.end+6]
+
+			if r.loggerForTesting != nil {
+				r.loggerForTesting.logf("\tBlock %d: Processing chunk at offset %d, checksum=%d, length=%d, encoding=%d\n", r.blockNum, r.end, checksum, length, chunkEncoding)
+			}
+
 			if int(chunkEncoding) >= len(headerFormatMappings) {
+				if r.loggerForTesting != nil {
+					r.loggerForTesting.logf("\tInvalid chunk encoding encountered (value: %d); stopping chunk scan in block %d\n", chunkEncoding, r.blockNum)
+				}
 				break
 			}
 
 			headerFormat := headerFormatMappings[chunkEncoding]
 			chunkPosition, wireFormat, headerSize := headerFormat.chunkPosition, headerFormat.wireFormat, headerFormat.headerSize
 			if checksum == 0 && length == 0 && chunkPosition == invalidChunkPosition {
+				if r.loggerForTesting != nil {
+					r.loggerForTesting.logf("\tFound invalid chunk marker at block %d offset %d; aborting this block scan\n", r.blockNum, r.end)
+				}
 				break
 			}
 			if wireFormat == invalidWireFormat {
+				if r.loggerForTesting != nil {
+					r.loggerForTesting.logf("\tInvalid wire format detected in block %d at offset %d\n", r.blockNum, r.end)
+				}
 				break
 			}
 			if wireFormat == recyclableWireFormat || wireFormat == walSyncWireFormat {
 				if r.end+headerSize > r.n {
+					if r.loggerForTesting != nil {
+						r.loggerForTesting.logf("\tIncomplete header in block %d at offset %d; breaking out\n", r.blockNum, r.end)
+					}
 					break
 				}
 				logNum := binary.LittleEndian.Uint32(r.buf[r.end+7 : r.end+11])
 				if logNum != r.logNum {
+					if r.loggerForTesting != nil {
+						r.loggerForTesting.logf("\tMismatch log number in block %d at offset %d (expected %d, got %d)\n", r.blockNum, r.end, r.logNum, logNum)
+					}
 					break
 				}
 			}
@@ -480,18 +520,30 @@ func (r *Reader) readAheadForCorruption() error {
 			r.end = r.begin + int(length)
 			if r.end > r.n {
 				// The chunk straddles a 32KB boundary (or the end of file).
+				if r.loggerForTesting != nil {
+					r.loggerForTesting.logf("\tChunk in block %d spans beyond block boundaries (begin=%d, end=%d, n=%d)\n", r.blockNum, r.begin, r.end, r.n)
+				}
 				break
 			}
 			if checksum != crc.New(r.buf[r.begin-headerSize+6:r.end]).Value() {
+				if r.loggerForTesting != nil {
+					r.loggerForTesting.logf("\tChecksum mismatch in block %d at offset %d; potential corruption\n", r.blockNum, r.end)
+				}
 				break
 			}
 
 			// Decode offset in header when chunk has the WAL Sync wire format.
 			if wireFormat == walSyncWireFormat {
 				syncedOffset := binary.LittleEndian.Uint64(r.buf[r.begin-headerSize+11 : r.begin-headerSize+19])
+				if r.loggerForTesting != nil {
+					r.loggerForTesting.logf("\tBlock %d: Found WAL sync chunk with syncedOffset=%d (invalidOffset=%d)\n", r.blockNum, syncedOffset, r.invalidOffset)
+				}
 				// If the encountered chunk offset promises durability beyond the invalid offset,
 				// the invalid offset must have been corruption.
 				if syncedOffset > r.invalidOffset {
+					if r.loggerForTesting != nil {
+						r.loggerForTesting.logf("\tCorruption confirmed: syncedOffset %d exceeds invalidOffset %d\n", syncedOffset, r.invalidOffset)
+					}
 					return r.err
 				}
 			}
diff --git a/replay/replay.go b/replay/replay.go
index 415e4869..90e19c5e 100644
--- a/replay/replay.go
+++ b/replay/replay.go
@@ -802,10 +802,11 @@ func (r *Runner) prepareWorkloadSteps(ctx context.Context) error {
 				}
 
 				// Apply the edit.
-				v, err = bve.Apply(v, l0Organizer, r.Opts.Experimental.ReadCompactionRate)
+				v, err = bve.Apply(v, r.Opts.Experimental.ReadCompactionRate)
 				if err != nil {
 					return err
 				}
+				l0Organizer.PerformUpdate(l0Organizer.PrepareUpdate(&bve, v), v)
 				// AddedTablesByFileNum maps file number to table metadata for all added
 				// sstables from accumulated version edits so we must retain it.
 				bve = manifest.BulkVersionEdit{AddedTablesByFileNum: bve.AddedTablesByFileNum}
diff --git a/tool/db.go b/tool/db.go
index 33b19967..d4ea2b41 100644
--- a/tool/db.go
+++ b/tool/db.go
@@ -723,10 +723,11 @@ func (d *dbT) runProperties(cmd *cobra.Command, args []string) {
 		}
 		l0Organizer := manifest.NewL0Organizer(cmp, d.opts.FlushSplitBytes)
 		emptyVersion := manifest.NewInitialVersion(cmp)
-		v, err := bve.Apply(emptyVersion, l0Organizer, d.opts.Experimental.ReadCompactionRate)
+		v, err := bve.Apply(emptyVersion, d.opts.Experimental.ReadCompactionRate)
 		if err != nil {
 			return err
 		}
+		l0Organizer.PerformUpdate(l0Organizer.PrepareUpdate(&bve, v), v)
 
 		objProvider, err := objstorageprovider.Open(objstorageprovider.DefaultSettings(d.opts.FS, dirname))
 		if err != nil {
diff --git a/tool/manifest.go b/tool/manifest.go
index 378953df..4285ed6a 100644
--- a/tool/manifest.go
+++ b/tool/manifest.go
@@ -243,11 +243,12 @@ func (m *manifestT) runDump(cmd *cobra.Command, args []string) {
 			if comparer != nil {
 				l0Organizer := manifest.NewL0Organizer(comparer, 0 /* flushSplitBytes */)
 				emptyVersion := manifest.NewInitialVersion(comparer)
-				v, err := bve.Apply(emptyVersion, l0Organizer, m.opts.Experimental.ReadCompactionRate)
+				v, err := bve.Apply(emptyVersion, m.opts.Experimental.ReadCompactionRate)
 				if err != nil {
 					fmt.Fprintf(stdout, "%s\n", err)
 					return
 				}
+				l0Organizer.PerformUpdate(l0Organizer.PrepareUpdate(&bve, v), v)
 				m.printLevels(comparer.Compare, stdout, v)
 			}
 		}()
@@ -628,7 +629,7 @@ func (m *manifestT) runCheck(cmd *cobra.Command, args []string) {
 					l0Organizer = manifest.NewL0Organizer(cmp, 0 /* flushSplitBytes */)
 					v = manifest.NewInitialVersion(cmp)
 				}
-				newv, err := bve.Apply(v, l0Organizer, m.opts.Experimental.ReadCompactionRate)
+				newv, err := bve.Apply(v, m.opts.Experimental.ReadCompactionRate)
 				if err != nil {
 					fmt.Fprintf(stdout, "%s: offset: %d err: %s\n",
 						arg, offset, err)
@@ -648,6 +649,7 @@ func (m *manifestT) runCheck(cmd *cobra.Command, args []string) {
 					ok = false
 					break
 				}
+				l0Organizer.PerformUpdate(l0Organizer.PrepareUpdate(&bve, newv), newv)
 				v = newv
 			}
 		}()
diff --git a/version_set.go b/version_set.go
index dd5e509b..e5a0fd52 100644
--- a/version_set.go
+++ b/version_set.go
@@ -342,10 +342,11 @@ func (vs *versionSet) load(
 	}
 
 	emptyVersion := manifest.NewInitialVersion(opts.Comparer)
-	newVersion, err := bve.Apply(emptyVersion, vs.l0Organizer, opts.Experimental.ReadCompactionRate)
+	newVersion, err := bve.Apply(emptyVersion, opts.Experimental.ReadCompactionRate)
 	if err != nil {
 		return err
 	}
+	vs.l0Organizer.PerformUpdate(vs.l0Organizer.PrepareUpdate(&bve, newVersion), newVersion)
 	vs.l0Organizer.InitCompactingFileInfo(nil /* in-progress compactions */)
 	vs.append(newVersion)
 
@@ -561,6 +562,7 @@ func (vs *versionSet) logAndApply(
 	zombieBackings, removedVirtualBackings, localLiveSizeDelta :=
 		getZombiesAndUpdateVirtualBackings(ve, &vs.virtualBackings, vs.provider)
 
+	var l0Update manifest.L0PreparedUpdate
 	if err := func() error {
 		vs.mu.Unlock()
 		defer vs.mu.Lock()
@@ -568,17 +570,16 @@ func (vs *versionSet) logAndApply(
 		if vs.getFormatMajorVersion() < FormatVirtualSSTables && len(ve.CreatedBackingTables) > 0 {
 			return base.AssertionFailedf("MANIFEST cannot contain virtual sstable records due to format major version")
 		}
-		var b bulkVersionEdit
-		err := b.Accumulate(ve)
+		var bulkEdit bulkVersionEdit
+		err := bulkEdit.Accumulate(ve)
 		if err != nil {
 			return errors.Wrap(err, "MANIFEST accumulate failed")
 		}
-		newVersion, err = b.Apply(
-			currentVersion, vs.l0Organizer, vs.opts.Experimental.ReadCompactionRate,
-		)
+		newVersion, err = bulkEdit.Apply(currentVersion, vs.opts.Experimental.ReadCompactionRate)
 		if err != nil {
 			return errors.Wrap(err, "MANIFEST apply failed")
 		}
+		l0Update = vs.l0Organizer.PrepareUpdate(&bulkEdit, newVersion)
 
 		if newManifestFileNum != 0 {
 			if err := vs.createManifest(vs.dirname, newManifestFileNum, minUnflushedLogNum, nextFileNum, newManifestVirtualBackings); err != nil {
@@ -642,6 +643,7 @@ func (vs *versionSet) logAndApply(
 	// L0Sublevels.
 	inProgress := inProgressCompactions()
 
+	vs.l0Organizer.PerformUpdate(l0Update, newVersion)
 	vs.l0Organizer.InitCompactingFileInfo(inProgressL0Compactions(inProgress))
 
 	// Update the zombie tables set first, as installation of the new version



Test patch:
diff --git a/internal/keyspan/keyspanimpl/level_iter_test.go b/internal/keyspan/keyspanimpl/level_iter_test.go
index ee5e04e2..06e83b10 100644
--- a/internal/keyspan/keyspanimpl/level_iter_test.go
+++ b/internal/keyspan/keyspanimpl/level_iter_test.go
@@ -314,8 +314,9 @@ func TestLevelIterEquivalence(t *testing.T) {
 			b.AddedTables[6] = amap
 			l0Organizer := manifest.NewL0Organizer(base.DefaultComparer, 0 /* flushSplitBytes */)
 			emptyVersion := manifest.NewInitialVersion(base.DefaultComparer)
-			v, err := b.Apply(emptyVersion, l0Organizer, 0)
+			v, err := b.Apply(emptyVersion, 0)
 			require.NoError(t, err)
+			l0Organizer.PerformUpdate(l0Organizer.PrepareUpdate(b, v), v)
 			levelIter.Init(
 				context.Background(),
 				keyspan.SpanIterOptions{}, base.DefaultComparer.Compare, tableNewIters,
diff --git a/internal/manifest/l0_sublevels_test.go b/internal/manifest/l0_sublevels_test.go
index 207d301d..481fa9ee 100644
--- a/internal/manifest/l0_sublevels_test.go
+++ b/internal/manifest/l0_sublevels_test.go
@@ -19,6 +19,7 @@ import (
 	"time"
 
 	"github.com/cockroachdb/datadriven"
+	"github.com/cockroachdb/errors"
 	"github.com/cockroachdb/pebble/internal/base"
 	"github.com/cockroachdb/pebble/internal/testkeys"
 	"github.com/cockroachdb/pebble/record"
@@ -53,9 +54,10 @@ func readManifest(filename string) (*Version, error) {
 		if err := bve.Accumulate(&ve); err != nil {
 			return nil, err
 		}
-		if v, err = bve.Apply(v, l0Organizer, 32000); err != nil {
+		if v, err = bve.Apply(v, 32000); err != nil {
 			return nil, err
 		}
+		l0Organizer.PerformUpdate(l0Organizer.PrepareUpdate(&bve, v), v)
 	}
 	return v, nil
 }
@@ -294,10 +296,13 @@ func TestL0Sublevels(t *testing.T) {
 			levelMetadata := MakeLevelMetadata(base.DefaultComparer.Compare, 0, fileMetas[0])
 			if initialize {
 				if addL0FilesOpt {
-					sublevels, err = sublevels.addL0Files(addedL0Files, int64(flushSplitMaxBytes), &levelMetadata)
-					// Check if the output matches a full initialization.
-					sublevels2, _ := newL0Sublevels(&levelMetadata, base.DefaultComparer.Compare, base.DefaultFormatter, int64(flushSplitMaxBytes))
-					if sublevels != nil && sublevels2 != nil {
+					files, ok := sublevels.canUseAddL0Files(addedL0Files, &levelMetadata)
+					if !ok {
+						err = errors.Newf("pebble: L0 sublevel generation optimization cannot be used")
+					} else {
+						sublevels = sublevels.addL0Files(files, int64(flushSplitMaxBytes), &levelMetadata)
+						// Check if the output matches a full initialization.
+						sublevels2, _ := newL0Sublevels(&levelMetadata, base.DefaultComparer.Compare, base.DefaultFormatter, int64(flushSplitMaxBytes))
 						require.Equal(t, sublevels.flushSplitUserKeys, sublevels2.flushSplitUserKeys)
 						require.Equal(t, sublevels.levelFiles, sublevels2.levelFiles)
 					}
@@ -555,8 +560,9 @@ func TestAddL0FilesEquivalence(t *testing.T) {
 			// that of the previous L0Sublevels. So it must be called before
 			// newL0Sublevels; calling it the other way around results in
 			// out-of-bounds panics.
-			s2, err = s2.addL0Files(filesToAdd, flushSplitMaxBytes, &levelMetadata)
-			require.NoError(t, err)
+			files, ok := s2.canUseAddL0Files(filesToAdd, &levelMetadata)
+			require.True(t, ok)
+			s2 = s2.addL0Files(files, flushSplitMaxBytes, &levelMetadata)
 		}
 
 		s, err = newL0Sublevels(&levelMetadata, testkeys.Comparer.Compare, testkeys.Comparer.FormatKey, flushSplitMaxBytes)
diff --git a/internal/manifest/manifest_test.go b/internal/manifest/manifest_test.go
index 1ea14d06..eba9f2f1 100644
--- a/internal/manifest/manifest_test.go
+++ b/internal/manifest/manifest_test.go
@@ -128,7 +128,8 @@ func replayManifest(
 	}
 	l0Organizer := manifest.NewL0Organizer(cmp, opts.FlushSplitBytes)
 	emptyVersion := manifest.NewInitialVersion(cmp)
-	v, err := bve.Apply(emptyVersion, l0Organizer, opts.Experimental.ReadCompactionRate)
+	v, err := bve.Apply(emptyVersion, opts.Experimental.ReadCompactionRate)
 	require.NoError(t, err)
+	l0Organizer.PerformUpdate(l0Organizer.PrepareUpdate(&bve, v), v)
 	return v, l0Organizer
 }
diff --git a/internal/manifest/version_edit_test.go b/internal/manifest/version_edit_test.go
index 4ae9eac4..38891fd2 100644
--- a/internal/manifest/version_edit_test.go
+++ b/internal/manifest/version_edit_test.go
@@ -449,10 +449,11 @@ func TestVersionEditApply(t *testing.T) {
 				if l0Organizer == nil {
 					d.Fatalf(t, "no L0 organizer")
 				}
-				newv, err := bve.Apply(v, l0Organizer, readCompactionRate)
+				newv, err := bve.Apply(v, readCompactionRate)
 				if err != nil {
 					return err.Error()
 				}
+				l0Organizer.PerformUpdate(l0Organizer.PrepareUpdate(&bve, newv), newv)
 				if saveName != "" {
 					versions[saveName] = newv
 					l0Organizers[saveName] = l0Organizer
@@ -461,7 +462,7 @@ func TestVersionEditApply(t *testing.T) {
 				// Reinitialize the L0 organizer in case we want to use the same version
 				// again (l0Organizer now reflects newv).
 				l0Organizer = NewL0Organizer(base.DefaultComparer, flushSplitBytes)
-				l0Organizer.ResetForTesting(&v.Levels[0])
+				l0Organizer.ResetForTesting(v)
 				l0Organizers[name] = l0Organizer
 
 				return newv.DebugString()
diff --git a/record/record_test.go b/record/record_test.go
index 87fe33e7..d38d830a 100644
--- a/record/record_test.go
+++ b/record/record_test.go
@@ -884,6 +884,20 @@ func TestRecycleLogWithPartialRecord(t *testing.T) {
 	require.Equal(t, err, io.ErrUnexpectedEOF)
 }
 
+type readerLogger struct {
+	builder strings.Builder
+}
+
+var _ loggerForTesting = (*readerLogger)(nil)
+
+func (l *readerLogger) getLog() string {
+	return l.builder.String()
+}
+
+func (l *readerLogger) logf(format string, args ...interface{}) {
+	fmt.Fprintf(&l.builder, format, args...)
+}
+
 func TestWALSync(t *testing.T) {
 	var buffer bytes.Buffer
 	result := make([]byte, 0)
@@ -894,6 +908,7 @@ func TestWALSync(t *testing.T) {
 		case "init":
 			buffer.Reset()
 			result = result[:0]
+			corruptChunkNumbers = corruptChunkNumbers[:0]
 
 			for chunkNumber, line := range strings.Split(d.Input, "\n") {
 				switch {
@@ -956,16 +971,17 @@ func TestWALSync(t *testing.T) {
 
 		case "read":
 			r := NewReader(bytes.NewBuffer(buffer.Bytes()), 1)
-
+			r.loggerForTesting = &readerLogger{}
 			for {
 				reader, err := r.Next()
 				if err != nil {
-					return fmt.Sprintf("error reading next: %v\nfinal blockNum: %d\nbytes read: %d", err, r.blockNum, len(result))
+					r.loggerForTesting.logf("error reading next: %v\nfinal blockNum: %d\nbytes read: %d\n", err, r.blockNum, len(result))
+					return r.loggerForTesting.(*readerLogger).getLog()
 				}
-
 				data, err := io.ReadAll(reader)
 				if err != nil {
-					return fmt.Sprintf("error reading all: %v\nfinal blockNum: %d\nbytes read: %d", err, r.blockNum, len(result))
+					r.loggerForTesting.logf("error reading all: %v\nfinal blockNum: %d\nbytes read: %d\n", err, r.blockNum, len(result))
+					return r.loggerForTesting.(*readerLogger).getLog()
 				}
 				result = append(result, data...)
 			}
diff --git a/record/testdata/walSync b/record/testdata/walSync
index f594ff4a..e335d6f0 100644
--- a/record/testdata/walSync
+++ b/record/testdata/walSync
@@ -45,6 +45,9 @@ Blocks
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 0 bytes
+	Encountered io.EOF; returning io.ErrUnexpectedEOF since no sync offset found.
 error reading next: unexpected EOF
 final blockNum: 1
 bytes read: 0
@@ -61,6 +64,9 @@ EOF logNum=2
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 0 bytes
+	Encountered io.EOF; returning io.ErrUnexpectedEOF since no sync offset found.
 error reading next: unexpected EOF
 final blockNum: 1
 bytes read: 0
@@ -73,38 +79,119 @@ EOF logNum=2
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 19 bytes
+	Block 1: Processing chunk at offset 0, checksum=0, length=0, encoding=9
+	Mismatch log number in block 1 at offset 0 (expected 1, got 2)
+Read block 2 with 0 bytes
+	Encountered io.EOF; returning io.ErrUnexpectedEOF since no sync offset found.
 error reading next: unexpected EOF
 final blockNum: 2
 bytes read: 0
 
+describe
+----
+Blocks
+ ├── Block #0
+ │    └── Chunk #0 (offset 0, corrupt)
+ │         ├── Checksum: 3178158960
+ │         ├── Encoded Length: 32749
+ │         ├── Chunk encoding: 9 (chunkType: 1, wireFormat: 3)
+ │         ├── Log Num: 1
+ │         └── Synced Offset: 0
+ ├── Block #1
+ │    └── Chunk #0 (offset 32768)
+ │         └── EOF
+ └── 
+
 # Multiple corruption with no confirmation
 init
 writeChunk encodedLength=32749 chunkLength=32749 encoding=9 logNum=1 offset=0 corrupt=true
 writeChunk encodedLength=32749 chunkLength=32749 encoding=9 logNum=1 offset=0 corrupt=true
-
 EOF logNum=2
 ----
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 32768 bytes
+	Block 1: Processing chunk at offset 0, checksum=3178158960, length=32749, encoding=9
+	Checksum mismatch in block 1 at offset 32768; potential corruption
+Read block 2 with 19 bytes
+	Block 2: Processing chunk at offset 0, checksum=0, length=0, encoding=9
+	Mismatch log number in block 2 at offset 0 (expected 1, got 2)
+Read block 3 with 0 bytes
+	Encountered io.EOF; returning io.ErrUnexpectedEOF since no sync offset found.
 error reading next: unexpected EOF
 final blockNum: 3
 bytes read: 0
 
+describe
+----
+Blocks
+ ├── Block #0
+ │    └── Chunk #0 (offset 0, corrupt)
+ │         ├── Checksum: 3178158960
+ │         ├── Encoded Length: 32749
+ │         ├── Chunk encoding: 9 (chunkType: 1, wireFormat: 3)
+ │         ├── Log Num: 1
+ │         └── Synced Offset: 0
+ ├── Block #1
+ │    └── Chunk #0 (offset 32768, corrupt)
+ │         ├── Checksum: 3178158960
+ │         ├── Encoded Length: 32749
+ │         ├── Chunk encoding: 9 (chunkType: 1, wireFormat: 3)
+ │         ├── Log Num: 1
+ │         └── Synced Offset: 0
+ ├── Block #2
+ │    └── Chunk #0 (offset 65536)
+ │         └── EOF
+ └── 
+
 # Multiple corruption but large offset is also corrupt, no confirmation
 init
 writeChunk encodedLength=32749 chunkLength=32749 encoding=9 logNum=1 offset=0 corrupt=true
 writeChunk encodedLength=32749 chunkLength=32749 encoding=9 logNum=1 offset=100000 corrupt=true
-
 EOF logNum=2
 ----
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 32768 bytes
+	Block 1: Processing chunk at offset 0, checksum=2875854606, length=32749, encoding=9
+	Checksum mismatch in block 1 at offset 32768; potential corruption
+Read block 2 with 19 bytes
+	Block 2: Processing chunk at offset 0, checksum=0, length=0, encoding=9
+	Mismatch log number in block 2 at offset 0 (expected 1, got 2)
+Read block 3 with 0 bytes
+	Encountered io.EOF; returning io.ErrUnexpectedEOF since no sync offset found.
 error reading next: unexpected EOF
 final blockNum: 3
 bytes read: 0
 
+describe
+----
+Blocks
+ ├── Block #0
+ │    └── Chunk #0 (offset 0, corrupt)
+ │         ├── Checksum: 3178158960
+ │         ├── Encoded Length: 32749
+ │         ├── Chunk encoding: 9 (chunkType: 1, wireFormat: 3)
+ │         ├── Log Num: 1
+ │         └── Synced Offset: 0
+ ├── Block #1
+ │    └── Chunk #0 (offset 32768, corrupt)
+ │         ├── Checksum: 2875854606
+ │         ├── Encoded Length: 32749
+ │         ├── Chunk encoding: 9 (chunkType: 1, wireFormat: 3)
+ │         ├── Log Num: 1
+ │         └── Synced Offset: 100000
+ ├── Block #2
+ │    └── Chunk #0 (offset 65536)
+ │         └── EOF
+ └── 
+
 # Simple corruption with confirmation
 init
 writeChunk encodedLength=32749 chunkLength=32749 encoding=9 logNum=1 offset=0 corrupt=true
@@ -114,10 +201,36 @@ EOF logNum=2
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 39 bytes
+	Block 1: Processing chunk at offset 0, checksum=2230101255, length=1, encoding=9
+	Block 1: Found WAL sync chunk with syncedOffset=30000 (invalidOffset=19)
+	Corruption confirmed: syncedOffset 30000 exceeds invalidOffset 19
 error reading next: pebble/record: invalid chunk
 final blockNum: 1
 bytes read: 0
 
+describe
+----
+Blocks
+ ├── Block #0
+ │    └── Chunk #0 (offset 0, corrupt)
+ │         ├── Checksum: 3178158960
+ │         ├── Encoded Length: 32749
+ │         ├── Chunk encoding: 9 (chunkType: 1, wireFormat: 3)
+ │         ├── Log Num: 1
+ │         └── Synced Offset: 0
+ ├── Block #1
+ │    ├── Chunk #0 (offset 32768)
+ │    │    ├── Checksum: 2230101255
+ │    │    ├── Encoded Length: 1
+ │    │    ├── Chunk encoding: 9 (chunkType: 1, wireFormat: 3)
+ │    │    ├── Log Num: 1
+ │    │    └── Synced Offset: 30000
+ │    └── Chunk #1 (offset 32788)
+ │         └── EOF
+ └── 
+
 # Corrupt the first block with confirming chunks after. However, these next chunks
 # in the same block cannot be used to confirm because reading ahead jumps to the next
 # block which has a confirmation chunk. Observe that final blockNum == 1
@@ -131,10 +244,49 @@ EOF logNum=2
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 32768 bytes
+	Block 1: Processing chunk at offset 0, checksum=400517472, length=32749, encoding=9
+	Block 1: Found WAL sync chunk with syncedOffset=32000 (invalidOffset=19)
+	Corruption confirmed: syncedOffset 32000 exceeds invalidOffset 19
 error reading next: pebble/record: invalid chunk
 final blockNum: 1
 bytes read: 0
 
+describe
+----
+Blocks
+ ├── Block #0
+ │    ├── Chunk #0 (offset 0, corrupt)
+ │    │    ├── Checksum: 3716504721
+ │    │    ├── Encoded Length: 1
+ │    │    ├── Chunk encoding: 9 (chunkType: 1, wireFormat: 3)
+ │    │    ├── Log Num: 1
+ │    │    └── Synced Offset: 0
+ │    ├── Chunk #1 (offset 20)
+ │    │    ├── Checksum: 3591204049
+ │    │    ├── Encoded Length: 1
+ │    │    ├── Chunk encoding: 9 (chunkType: 1, wireFormat: 3)
+ │    │    ├── Log Num: 1
+ │    │    └── Synced Offset: 60
+ │    └── Chunk #2 (offset 40)
+ │         ├── Checksum: 649293827
+ │         ├── Encoded Length: 32709
+ │         ├── Chunk encoding: 9 (chunkType: 1, wireFormat: 3)
+ │         ├── Log Num: 1
+ │         └── Synced Offset: 60
+ ├── Block #1
+ │    └── Chunk #0 (offset 32768)
+ │         ├── Checksum: 400517472
+ │         ├── Encoded Length: 32749
+ │         ├── Chunk encoding: 9 (chunkType: 1, wireFormat: 3)
+ │         ├── Log Num: 1
+ │         └── Synced Offset: 32000
+ ├── Block #2
+ │    └── Chunk #0 (offset 65536)
+ │         └── EOF
+ └── 
+
 # Corrupt the first block with confirming chunks after. However, these next chunks
 # in the same block cannot be used to confirm because reading ahead jumps to the next
 # block which has a corrupt chunk, leading to EOF
@@ -148,10 +300,53 @@ EOF logNum=2
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 32768 bytes
+	Block 1: Processing chunk at offset 0, checksum=383674465, length=32749, encoding=9
+	Checksum mismatch in block 1 at offset 32768; potential corruption
+Read block 2 with 19 bytes
+	Block 2: Processing chunk at offset 0, checksum=0, length=0, encoding=9
+	Mismatch log number in block 2 at offset 0 (expected 1, got 2)
+Read block 3 with 0 bytes
+	Encountered io.EOF; returning io.ErrUnexpectedEOF since no sync offset found.
 error reading next: unexpected EOF
 final blockNum: 3
 bytes read: 0
 
+describe
+----
+Blocks
+ ├── Block #0
+ │    ├── Chunk #0 (offset 0, corrupt)
+ │    │    ├── Checksum: 3716504721
+ │    │    ├── Encoded Length: 1
+ │    │    ├── Chunk encoding: 9 (chunkType: 1, wireFormat: 3)
+ │    │    ├── Log Num: 1
+ │    │    └── Synced Offset: 0
+ │    ├── Chunk #1 (offset 20)
+ │    │    ├── Checksum: 3591204049
+ │    │    ├── Encoded Length: 1
+ │    │    ├── Chunk encoding: 9 (chunkType: 1, wireFormat: 3)
+ │    │    ├── Log Num: 1
+ │    │    └── Synced Offset: 60
+ │    └── Chunk #2 (offset 40)
+ │         ├── Checksum: 649293827
+ │         ├── Encoded Length: 32709
+ │         ├── Chunk encoding: 9 (chunkType: 1, wireFormat: 3)
+ │         ├── Log Num: 1
+ │         └── Synced Offset: 60
+ ├── Block #1
+ │    └── Chunk #0 (offset 32768, corrupt)
+ │         ├── Checksum: 383674465
+ │         ├── Encoded Length: 32749
+ │         ├── Chunk encoding: 9 (chunkType: 1, wireFormat: 3)
+ │         ├── Log Num: 1
+ │         └── Synced Offset: 32000
+ ├── Block #2
+ │    └── Chunk #0 (offset 65536)
+ │         └── EOF
+ └── 
+
 # Complex multiple corruption with no confirmation
 init
 writeChunk encodedLength=1 chunkLength=1 encoding=9 logNum=1 offset=0 corrupt=true
@@ -167,10 +362,85 @@ EOF logNum=2
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 32768 bytes
+	Block 1: Processing chunk at offset 0, checksum=383674465, length=32749, encoding=9
+	Checksum mismatch in block 1 at offset 32768; potential corruption
+Read block 2 with 32768 bytes
+	Block 2: Processing chunk at offset 0, checksum=614591033, length=1, encoding=9
+	Checksum mismatch in block 2 at offset 20; potential corruption
+Read block 3 with 32768 bytes
+	Block 3: Processing chunk at offset 0, checksum=1645328786, length=32749, encoding=9
+	Checksum mismatch in block 3 at offset 32768; potential corruption
+Read block 4 with 19 bytes
+	Block 4: Processing chunk at offset 0, checksum=0, length=0, encoding=9
+	Mismatch log number in block 4 at offset 0 (expected 1, got 2)
+Read block 5 with 0 bytes
+	Encountered io.EOF; returning io.ErrUnexpectedEOF since no sync offset found.
 error reading next: unexpected EOF
 final blockNum: 5
 bytes read: 0
 
+describe
+----
+Blocks
+ ├── Block #0
+ │    ├── Chunk #0 (offset 0, corrupt)
+ │    │    ├── Checksum: 3716504721
+ │    │    ├── Encoded Length: 1
+ │    │    ├── Chunk encoding: 9 (chunkType: 1, wireFormat: 3)
+ │    │    ├── Log Num: 1
+ │    │    └── Synced Offset: 0
+ │    ├── Chunk #1 (offset 20)
+ │    │    ├── Checksum: 3591204049
+ │    │    ├── Encoded Length: 1
+ │    │    ├── Chunk encoding: 9 (chunkType: 1, wireFormat: 3)
+ │    │    ├── Log Num: 1
+ │    │    └── Synced Offset: 60
+ │    └── Chunk #2 (offset 40)
+ │         ├── Checksum: 649293827
+ │         ├── Encoded Length: 32709
+ │         ├── Chunk encoding: 9 (chunkType: 1, wireFormat: 3)
+ │         ├── Log Num: 1
+ │         └── Synced Offset: 60
+ ├── Block #1
+ │    └── Chunk #0 (offset 32768, corrupt)
+ │         ├── Checksum: 383674465
+ │         ├── Encoded Length: 32749
+ │         ├── Chunk encoding: 9 (chunkType: 1, wireFormat: 3)
+ │         ├── Log Num: 1
+ │         └── Synced Offset: 32000
+ ├── Block #2
+ │    ├── Chunk #0 (offset 65536, corrupt)
+ │    │    ├── Checksum: 614591033
+ │    │    ├── Encoded Length: 1
+ │    │    ├── Chunk encoding: 9 (chunkType: 1, wireFormat: 3)
+ │    │    ├── Log Num: 1
+ │    │    └── Synced Offset: 32000
+ │    ├── Chunk #1 (offset 65556, corrupt)
+ │    │    ├── Checksum: 614591033
+ │    │    ├── Encoded Length: 1
+ │    │    ├── Chunk encoding: 9 (chunkType: 1, wireFormat: 3)
+ │    │    ├── Log Num: 1
+ │    │    └── Synced Offset: 32000
+ │    └── Chunk #2 (offset 65576)
+ │         ├── Checksum: 1960442165
+ │         ├── Encoded Length: 32709
+ │         ├── Chunk encoding: 9 (chunkType: 1, wireFormat: 3)
+ │         ├── Log Num: 1
+ │         └── Synced Offset: 32000
+ ├── Block #3
+ │    └── Chunk #0 (offset 98304, corrupt)
+ │         ├── Checksum: 1645328786
+ │         ├── Encoded Length: 32749
+ │         ├── Chunk encoding: 9 (chunkType: 1, wireFormat: 3)
+ │         ├── Log Num: 1
+ │         └── Synced Offset: 64000
+ ├── Block #4
+ │    └── Chunk #0 (offset 131072)
+ │         └── EOF
+ └── 
+
 # Complex multiple corruption with confirmation in blockNum 3
 init
 writeChunk encodedLength=1 chunkLength=1 encoding=9 logNum=1 offset=0 corrupt=true
@@ -186,10 +456,81 @@ EOF logNum=2
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 32768 bytes
+	Block 1: Processing chunk at offset 0, checksum=383674465, length=32749, encoding=9
+	Checksum mismatch in block 1 at offset 32768; potential corruption
+Read block 2 with 32768 bytes
+	Block 2: Processing chunk at offset 0, checksum=3716504721, length=1, encoding=9
+	Checksum mismatch in block 2 at offset 20; potential corruption
+Read block 3 with 32768 bytes
+	Block 3: Processing chunk at offset 0, checksum=400517472, length=32749, encoding=9
+	Block 3: Found WAL sync chunk with syncedOffset=32000 (invalidOffset=19)
+	Corruption confirmed: syncedOffset 32000 exceeds invalidOffset 19
 error reading next: pebble/record: invalid chunk
 final blockNum: 3
 bytes read: 0
 
+describe
+----
+Blocks
+ ├── Block #0
+ │    ├── Chunk #0 (offset 0, corrupt)
+ │    │    ├── Checksum: 3716504721
+ │    │    ├── Encoded Length: 1
+ │    │    ├── Chunk encoding: 9 (chunkType: 1, wireFormat: 3)
+ │    │    ├── Log Num: 1
+ │    │    └── Synced Offset: 0
+ │    ├── Chunk #1 (offset 20)
+ │    │    ├── Checksum: 3591204049
+ │    │    ├── Encoded Length: 1
+ │    │    ├── Chunk encoding: 9 (chunkType: 1, wireFormat: 3)
+ │    │    ├── Log Num: 1
+ │    │    └── Synced Offset: 60
+ │    └── Chunk #2 (offset 40)
+ │         ├── Checksum: 649293827
+ │         ├── Encoded Length: 32709
+ │         ├── Chunk encoding: 9 (chunkType: 1, wireFormat: 3)
+ │         ├── Log Num: 1
+ │         └── Synced Offset: 60
+ ├── Block #1
+ │    └── Chunk #0 (offset 32768, corrupt)
+ │         ├── Checksum: 383674465
+ │         ├── Encoded Length: 32749
+ │         ├── Chunk encoding: 9 (chunkType: 1, wireFormat: 3)
+ │         ├── Log Num: 1
+ │         └── Synced Offset: 32000
+ ├── Block #2
+ │    ├── Chunk #0 (offset 65536, corrupt)
+ │    │    ├── Checksum: 3716504721
+ │    │    ├── Encoded Length: 1
+ │    │    ├── Chunk encoding: 9 (chunkType: 1, wireFormat: 3)
+ │    │    ├── Log Num: 1
+ │    │    └── Synced Offset: 0
+ │    ├── Chunk #1 (offset 65556, corrupt)
+ │    │    ├── Checksum: 3607915984
+ │    │    ├── Encoded Length: 1
+ │    │    ├── Chunk encoding: 9 (chunkType: 1, wireFormat: 3)
+ │    │    ├── Log Num: 1
+ │    │    └── Synced Offset: 60
+ │    └── Chunk #2 (offset 65576)
+ │         ├── Checksum: 649293827
+ │         ├── Encoded Length: 32709
+ │         ├── Chunk encoding: 9 (chunkType: 1, wireFormat: 3)
+ │         ├── Log Num: 1
+ │         └── Synced Offset: 60
+ ├── Block #3
+ │    └── Chunk #0 (offset 98304)
+ │         ├── Checksum: 400517472
+ │         ├── Encoded Length: 32749
+ │         ├── Chunk encoding: 9 (chunkType: 1, wireFormat: 3)
+ │         ├── Log Num: 1
+ │         └── Synced Offset: 32000
+ ├── Block #4
+ │    └── Chunk #0 (offset 131072)
+ │         └── EOF
+ └── 
+
 # Complex multiple corruption with confirmation offset too small
 init
 writeChunk encodedLength=32749 chunkLength=32749 encoding=9 logNum=1 offset=0 corrupt=false
@@ -203,10 +544,70 @@ EOF logNum=2
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 1.
+Read block 2 with 32768 bytes
+	Block 2: Processing chunk at offset 0, checksum=3716504721, length=1, encoding=9
+	Checksum mismatch in block 2 at offset 20; potential corruption
+Read block 3 with 32768 bytes
+	Block 3: Processing chunk at offset 0, checksum=3770313817, length=32749, encoding=9
+	Block 3: Found WAL sync chunk with syncedOffset=100 (invalidOffset=32787)
+Read block 4 with 19 bytes
+	Block 4: Processing chunk at offset 0, checksum=0, length=0, encoding=9
+	Mismatch log number in block 4 at offset 0 (expected 1, got 2)
+Read block 5 with 0 bytes
+	Encountered io.EOF; returning io.ErrUnexpectedEOF since no sync offset found.
 error reading next: unexpected EOF
 final blockNum: 5
 bytes read: 32749
 
+describe
+----
+Blocks
+ ├── Block #0
+ │    └── Chunk #0 (offset 0)
+ │         ├── Checksum: 3161447025
+ │         ├── Encoded Length: 32749
+ │         ├── Chunk encoding: 9 (chunkType: 1, wireFormat: 3)
+ │         ├── Log Num: 1
+ │         └── Synced Offset: 0
+ ├── Block #1
+ │    └── Chunk #0 (offset 32768, corrupt)
+ │         ├── Checksum: 3178158960
+ │         ├── Encoded Length: 32749
+ │         ├── Chunk encoding: 9 (chunkType: 1, wireFormat: 3)
+ │         ├── Log Num: 1
+ │         └── Synced Offset: 0
+ ├── Block #2
+ │    ├── Chunk #0 (offset 65536, corrupt)
+ │    │    ├── Checksum: 3716504721
+ │    │    ├── Encoded Length: 1
+ │    │    ├── Chunk encoding: 9 (chunkType: 1, wireFormat: 3)
+ │    │    ├── Log Num: 1
+ │    │    └── Synced Offset: 0
+ │    ├── Chunk #1 (offset 65556, corrupt)
+ │    │    ├── Checksum: 3607915984
+ │    │    ├── Encoded Length: 1
+ │    │    ├── Chunk encoding: 9 (chunkType: 1, wireFormat: 3)
+ │    │    ├── Log Num: 1
+ │    │    └── Synced Offset: 60
+ │    └── Chunk #2 (offset 65576)
+ │         ├── Checksum: 1521797213
+ │         ├── Encoded Length: 32709
+ │         ├── Chunk encoding: 9 (chunkType: 1, wireFormat: 3)
+ │         ├── Log Num: 1
+ │         └── Synced Offset: 100
+ ├── Block #3
+ │    └── Chunk #0 (offset 98304)
+ │         ├── Checksum: 3770313817
+ │         ├── Encoded Length: 32749
+ │         ├── Chunk encoding: 9 (chunkType: 1, wireFormat: 3)
+ │         ├── Log Num: 1
+ │         └── Synced Offset: 100
+ ├── Block #4
+ │    └── Chunk #0 (offset 131072)
+ │         └── EOF
+ └── 
+
 ###########################
 ###### log num cases ######
 ###########################
@@ -222,6 +623,9 @@ EOF logNum=2
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 0 bytes
+	Encountered io.EOF; returning io.ErrUnexpectedEOF since no sync offset found.
 error reading next: unexpected EOF
 final blockNum: 1
 bytes read: 0
@@ -234,6 +638,12 @@ EOF logNum=2
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 19 bytes
+	Block 1: Processing chunk at offset 0, checksum=0, length=0, encoding=9
+	Mismatch log number in block 1 at offset 0 (expected 1, got 2)
+Read block 2 with 0 bytes
+	Encountered io.EOF; returning io.ErrUnexpectedEOF since no sync offset found.
 error reading next: unexpected EOF
 final blockNum: 2
 bytes read: 0
@@ -247,6 +657,15 @@ EOF logNum=2
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 32768 bytes
+	Block 1: Processing chunk at offset 0, checksum=2371060608, length=32749, encoding=9
+	Mismatch log number in block 1 at offset 0 (expected 1, got 0)
+Read block 2 with 19 bytes
+	Block 2: Processing chunk at offset 0, checksum=0, length=0, encoding=9
+	Mismatch log number in block 2 at offset 0 (expected 1, got 2)
+Read block 3 with 0 bytes
+	Encountered io.EOF; returning io.ErrUnexpectedEOF since no sync offset found.
 error reading next: unexpected EOF
 final blockNum: 3
 bytes read: 0
@@ -261,6 +680,15 @@ EOF logNum=2
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 32768 bytes
+	Block 1: Processing chunk at offset 0, checksum=3034178164, length=32749, encoding=9
+	Mismatch log number in block 1 at offset 0 (expected 1, got 100)
+Read block 2 with 19 bytes
+	Block 2: Processing chunk at offset 0, checksum=0, length=0, encoding=9
+	Mismatch log number in block 2 at offset 0 (expected 1, got 2)
+Read block 3 with 0 bytes
+	Encountered io.EOF; returning io.ErrUnexpectedEOF since no sync offset found.
 error reading next: unexpected EOF
 final blockNum: 3
 bytes read: 0
@@ -274,6 +702,11 @@ EOF logNum=2
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 39 bytes
+	Block 1: Processing chunk at offset 0, checksum=2230101255, length=1, encoding=9
+	Block 1: Found WAL sync chunk with syncedOffset=30000 (invalidOffset=0)
+	Corruption confirmed: syncedOffset 30000 exceeds invalidOffset 0
 error reading next: pebble/record: invalid chunk
 final blockNum: 1
 bytes read: 0
@@ -291,6 +724,11 @@ EOF logNum=2
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 32768 bytes
+	Block 1: Processing chunk at offset 0, checksum=400517472, length=32749, encoding=9
+	Block 1: Found WAL sync chunk with syncedOffset=32000 (invalidOffset=0)
+	Corruption confirmed: syncedOffset 32000 exceeds invalidOffset 0
 error reading next: pebble/record: invalid chunk
 final blockNum: 1
 bytes read: 0
@@ -308,6 +746,15 @@ EOF logNum=2
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 32768 bytes
+	Block 1: Processing chunk at offset 0, checksum=685962385, length=32749, encoding=9
+	Mismatch log number in block 1 at offset 0 (expected 1, got 0)
+Read block 2 with 19 bytes
+	Block 2: Processing chunk at offset 0, checksum=0, length=0, encoding=9
+	Mismatch log number in block 2 at offset 0 (expected 1, got 2)
+Read block 3 with 0 bytes
+	Encountered io.EOF; returning io.ErrUnexpectedEOF since no sync offset found.
 error reading next: unexpected EOF
 final blockNum: 3
 bytes read: 0
@@ -317,13 +764,10 @@ init
 writeChunk encodedLength=1 chunkLength=1 encoding=9 logNum=0 offset=0 corrupt=false
 writeChunk encodedLength=1 chunkLength=1 encoding=9 logNum=1 offset=60 corrupt=false
 writeChunk encodedLength=32709 chunkLength=32709 encoding=9 logNum=1 offset=60 corrupt=false
-
 writeChunk encodedLength=32749 chunkLength=32749 encoding=9 logNum=0 offset=32000 corrupt=false
-
 writeChunk encodedLength=1 chunkLength=1 encoding=9 logNum=0 offset=32000 corrupt=false
 writeChunk encodedLength=1 chunkLength=1 encoding=9 logNum=0 offset=32000 corrupt=false
 writeChunk encodedLength=32709 chunkLength=32709 encoding=9 logNum=1 offset=32000 corrupt=false
-
 writeChunk encodedLength=32749 chunkLength=32749 encoding=9 logNum=0 offset=64000 corrupt=false
 
 EOF logNum=2
@@ -331,6 +775,21 @@ EOF logNum=2
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 32768 bytes
+	Block 1: Processing chunk at offset 0, checksum=685962385, length=32749, encoding=9
+	Mismatch log number in block 1 at offset 0 (expected 1, got 0)
+Read block 2 with 32768 bytes
+	Block 2: Processing chunk at offset 0, checksum=2522303644, length=1, encoding=9
+	Mismatch log number in block 2 at offset 0 (expected 1, got 0)
+Read block 3 with 32768 bytes
+	Block 3: Processing chunk at offset 0, checksum=3593774434, length=32749, encoding=9
+	Mismatch log number in block 3 at offset 0 (expected 1, got 0)
+Read block 4 with 19 bytes
+	Block 4: Processing chunk at offset 0, checksum=0, length=0, encoding=9
+	Mismatch log number in block 4 at offset 0 (expected 1, got 2)
+Read block 5 with 0 bytes
+	Encountered io.EOF; returning io.ErrUnexpectedEOF since no sync offset found.
 error reading next: unexpected EOF
 final blockNum: 5
 bytes read: 0
@@ -341,13 +800,10 @@ init
 writeChunk encodedLength=1 chunkLength=1 encoding=9 logNum=0 offset=0 corrupt=false
 writeChunk encodedLength=1 chunkLength=1 encoding=9 logNum=1 offset=60 corrupt=false
 writeChunk encodedLength=32709 chunkLength=32709 encoding=9 logNum=1 offset=60 corrupt=false
-
 writeChunk encodedLength=32749 chunkLength=32749 encoding=9 logNum=0 offset=32000 corrupt=false
-
 writeChunk encodedLength=1 chunkLength=1 encoding=9 logNum=0 offset=0 corrupt=false
 writeChunk encodedLength=1 chunkLength=1 encoding=9 logNum=1 offset=60 corrupt=false
 writeChunk encodedLength=32709 chunkLength=32709 encoding=9 logNum=1 offset=60 corrupt=false
-
 writeChunk encodedLength=32749 chunkLength=32749 encoding=9 logNum=1 offset=32000 corrupt=false
 
 EOF logNum=2
@@ -355,6 +811,17 @@ EOF logNum=2
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 32768 bytes
+	Block 1: Processing chunk at offset 0, checksum=685962385, length=32749, encoding=9
+	Mismatch log number in block 1 at offset 0 (expected 1, got 0)
+Read block 2 with 32768 bytes
+	Block 2: Processing chunk at offset 0, checksum=3981311476, length=1, encoding=9
+	Mismatch log number in block 2 at offset 0 (expected 1, got 0)
+Read block 3 with 32768 bytes
+	Block 3: Processing chunk at offset 0, checksum=400517472, length=32749, encoding=9
+	Block 3: Found WAL sync chunk with syncedOffset=32000 (invalidOffset=0)
+	Corruption confirmed: syncedOffset 32000 exceeds invalidOffset 0
 error reading next: pebble/record: invalid chunk
 final blockNum: 3
 bytes read: 0
@@ -377,6 +844,18 @@ EOF logNum=2
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 1.
+Read block 2 with 32768 bytes
+	Block 2: Processing chunk at offset 0, checksum=3981311476, length=1, encoding=9
+	Mismatch log number in block 2 at offset 0 (expected 1, got 0)
+Read block 3 with 32768 bytes
+	Block 3: Processing chunk at offset 0, checksum=3770313817, length=32749, encoding=9
+	Block 3: Found WAL sync chunk with syncedOffset=100 (invalidOffset=32768)
+Read block 4 with 19 bytes
+	Block 4: Processing chunk at offset 0, checksum=0, length=0, encoding=9
+	Mismatch log number in block 4 at offset 0 (expected 1, got 2)
+Read block 5 with 0 bytes
+	Encountered io.EOF; returning io.ErrUnexpectedEOF since no sync offset found.
 error reading next: unexpected EOF
 final blockNum: 5
 bytes read: 32749
@@ -395,6 +874,9 @@ EOF logNum=2
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 0 bytes
+	Encountered io.EOF; returning io.ErrUnexpectedEOF since no sync offset found.
 error reading next: unexpected EOF
 final blockNum: 1
 bytes read: 0
@@ -407,6 +889,12 @@ EOF logNum=2
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 19 bytes
+	Block 1: Processing chunk at offset 0, checksum=0, length=0, encoding=9
+	Mismatch log number in block 1 at offset 0 (expected 1, got 2)
+Read block 2 with 0 bytes
+	Encountered io.EOF; returning io.ErrUnexpectedEOF since no sync offset found.
 error reading next: unexpected EOF
 final blockNum: 2
 bytes read: 0
@@ -414,14 +902,21 @@ bytes read: 0
 # Multiple wire issues with no confirmation
 init
 writeChunk encodedLength=32749 chunkLength=32749 encoding=0 logNum=1 offset=0 corrupt=false
-
 writeChunk encodedLength=32749 chunkLength=32749 encoding=0 logNum=1 offset=0 corrupt=false
-
 EOF logNum=2
 ----
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 32768 bytes
+	Block 1: Processing chunk at offset 0, checksum=2710594051, length=32749, encoding=0
+	Invalid wire format detected in block 1 at offset 0
+Read block 2 with 19 bytes
+	Block 2: Processing chunk at offset 0, checksum=0, length=0, encoding=9
+	Mismatch log number in block 2 at offset 0 (expected 1, got 2)
+Read block 3 with 0 bytes
+	Encountered io.EOF; returning io.ErrUnexpectedEOF since no sync offset found.
 error reading next: unexpected EOF
 final blockNum: 3
 bytes read: 0
@@ -429,7 +924,6 @@ bytes read: 0
 # Multiple wire issues but large offset is also has issues, no confirmation
 init
 writeChunk encodedLength=32749 chunkLength=32749 encoding=0 logNum=1 offset=0 corrupt=false
-
 writeChunk encodedLength=32749 chunkLength=32749 encoding=0 logNum=1 offset=100000 corrupt=false
 
 EOF logNum=2
@@ -437,6 +931,15 @@ EOF logNum=2
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 32768 bytes
+	Block 1: Processing chunk at offset 0, checksum=2207032925, length=32749, encoding=0
+	Invalid wire format detected in block 1 at offset 0
+Read block 2 with 19 bytes
+	Block 2: Processing chunk at offset 0, checksum=0, length=0, encoding=9
+	Mismatch log number in block 2 at offset 0 (expected 1, got 2)
+Read block 3 with 0 bytes
+	Encountered io.EOF; returning io.ErrUnexpectedEOF since no sync offset found.
 error reading next: unexpected EOF
 final blockNum: 3
 bytes read: 0
@@ -444,13 +947,17 @@ bytes read: 0
 # Simple wire issues with confirmation
 init
 writeChunk encodedLength=32749 chunkLength=32749 encoding=0 logNum=1 offset=0 corrupt=false
-
 writeChunk encodedLength=1 chunkLength=1 encoding=9 logNum=1 offset=30000 corrupt=false
 EOF logNum=2
 ----
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 39 bytes
+	Block 1: Processing chunk at offset 0, checksum=2230101255, length=1, encoding=9
+	Block 1: Found WAL sync chunk with syncedOffset=30000 (invalidOffset=0)
+	Corruption confirmed: syncedOffset 30000 exceeds invalidOffset 0
 error reading next: pebble/record: invalid chunk
 final blockNum: 1
 bytes read: 0
@@ -462,13 +969,17 @@ init
 writeChunk encodedLength=1 chunkLength=1 encoding=0 logNum=1 offset=0 corrupt=false
 writeChunk encodedLength=1 chunkLength=1 encoding=9 logNum=1 offset=60 corrupt=false
 writeChunk encodedLength=32709 chunkLength=32709 encoding=9 logNum=1 offset=60 corrupt=false
-
 writeChunk encodedLength=32749 chunkLength=32749 encoding=9 logNum=1 offset=32000 corrupt=false
 EOF logNum=2
 ----
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 32768 bytes
+	Block 1: Processing chunk at offset 0, checksum=400517472, length=32749, encoding=9
+	Block 1: Found WAL sync chunk with syncedOffset=32000 (invalidOffset=0)
+	Corruption confirmed: syncedOffset 32000 exceeds invalidOffset 0
 error reading next: pebble/record: invalid chunk
 final blockNum: 1
 bytes read: 0
@@ -480,14 +991,21 @@ init
 writeChunk encodedLength=1 chunkLength=1 encoding=0 logNum=1 offset=0 corrupt=false
 writeChunk encodedLength=1 chunkLength=1 encoding=9 logNum=1 offset=60 corrupt=false
 writeChunk encodedLength=32709 chunkLength=32709 encoding=9 logNum=1 offset=60 corrupt=false
-
 writeChunk encodedLength=32749 chunkLength=32749 encoding=0 logNum=1 offset=32000 corrupt=false
-
 EOF logNum=2
 ----
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 32768 bytes
+	Block 1: Processing chunk at offset 0, checksum=910217490, length=32749, encoding=0
+	Invalid wire format detected in block 1 at offset 0
+Read block 2 with 19 bytes
+	Block 2: Processing chunk at offset 0, checksum=0, length=0, encoding=9
+	Mismatch log number in block 2 at offset 0 (expected 1, got 2)
+Read block 3 with 0 bytes
+	Encountered io.EOF; returning io.ErrUnexpectedEOF since no sync offset found.
 error reading next: unexpected EOF
 final blockNum: 3
 bytes read: 0
@@ -497,20 +1015,31 @@ init
 writeChunk encodedLength=1 chunkLength=1 encoding=0 logNum=1 offset=0 corrupt=false
 writeChunk encodedLength=1 chunkLength=1 encoding=9 logNum=1 offset=60 corrupt=false
 writeChunk encodedLength=32709 chunkLength=32709 encoding=9 logNum=1 offset=60 corrupt=false
-
 writeChunk encodedLength=32749 chunkLength=32749 encoding=0 logNum=1 offset=32000 corrupt=false
-
 writeChunk encodedLength=1 chunkLength=1 encoding=0 logNum=1 offset=32000 corrupt=false
 writeChunk encodedLength=1 chunkLength=1 encoding=0 logNum=1 offset=32000 corrupt=false
 writeChunk encodedLength=32709 chunkLength=32709 encoding=9 logNum=1 offset=32000 corrupt=false
-
 writeChunk encodedLength=32749 chunkLength=32749 encoding=0 logNum=1 offset=64000 corrupt=false
-
 EOF logNum=2
 ----
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 32768 bytes
+	Block 1: Processing chunk at offset 0, checksum=910217490, length=32749, encoding=0
+	Invalid wire format detected in block 1 at offset 0
+Read block 2 with 32768 bytes
+	Block 2: Processing chunk at offset 0, checksum=1220209282, length=1, encoding=0
+	Invalid wire format detected in block 2 at offset 0
+Read block 3 with 32768 bytes
+	Block 3: Processing chunk at offset 0, checksum=3371122657, length=32749, encoding=0
+	Invalid wire format detected in block 3 at offset 0
+Read block 4 with 19 bytes
+	Block 4: Processing chunk at offset 0, checksum=0, length=0, encoding=9
+	Mismatch log number in block 4 at offset 0 (expected 1, got 2)
+Read block 5 with 0 bytes
+	Encountered io.EOF; returning io.ErrUnexpectedEOF since no sync offset found.
 error reading next: unexpected EOF
 final blockNum: 5
 bytes read: 0
@@ -535,6 +1064,17 @@ EOF logNum=2
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 32768 bytes
+	Block 1: Processing chunk at offset 0, checksum=910217490, length=32749, encoding=0
+	Invalid wire format detected in block 1 at offset 0
+Read block 2 with 32768 bytes
+	Block 2: Processing chunk at offset 0, checksum=3249240138, length=1, encoding=0
+	Invalid wire format detected in block 2 at offset 0
+Read block 3 with 32768 bytes
+	Block 3: Processing chunk at offset 0, checksum=400517472, length=32749, encoding=9
+	Block 3: Found WAL sync chunk with syncedOffset=32000 (invalidOffset=0)
+	Corruption confirmed: syncedOffset 32000 exceeds invalidOffset 0
 error reading next: pebble/record: invalid chunk
 final blockNum: 3
 bytes read: 0
@@ -543,20 +1083,28 @@ bytes read: 0
 # Complex multiple wire issue with confirmation offset too small
 init
 writeChunk encodedLength=32749 chunkLength=32749 encoding=9 logNum=1 offset=0 corrupt=false
-
 writeChunk encodedLength=32749 chunkLength=32749 encoding=0 logNum=1 offset=0 corrupt=false
-
 writeChunk encodedLength=1 chunkLength=1 encoding=0 logNum=1 offset=0 corrupt=false
 writeChunk encodedLength=1 chunkLength=1 encoding=0 logNum=1 offset=60 corrupt=false
 writeChunk encodedLength=32709 chunkLength=32709 encoding=9 logNum=1 offset=100 corrupt=false
-
 writeChunk encodedLength=32749 chunkLength=32749 encoding=9 logNum=1 offset=100 corrupt=false
-
 EOF logNum=2
 ----
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 1.
+Read block 2 with 32768 bytes
+	Block 2: Processing chunk at offset 0, checksum=3249240138, length=1, encoding=0
+	Invalid wire format detected in block 2 at offset 0
+Read block 3 with 32768 bytes
+	Block 3: Processing chunk at offset 0, checksum=3770313817, length=32749, encoding=9
+	Block 3: Found WAL sync chunk with syncedOffset=100 (invalidOffset=32768)
+Read block 4 with 19 bytes
+	Block 4: Processing chunk at offset 0, checksum=0, length=0, encoding=9
+	Mismatch log number in block 4 at offset 0 (expected 1, got 2)
+Read block 5 with 0 bytes
+	Encountered io.EOF; returning io.ErrUnexpectedEOF since no sync offset found.
 error reading next: unexpected EOF
 final blockNum: 5
 bytes read: 32749
@@ -575,6 +1123,12 @@ EOF logNum=2
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 19 bytes
+	Block 1: Processing chunk at offset 0, checksum=0, length=0, encoding=9
+	Mismatch log number in block 1 at offset 0 (expected 1, got 2)
+Read block 2 with 0 bytes
+	Encountered io.EOF; returning io.ErrUnexpectedEOF since no sync offset found.
 error reading next: unexpected EOF
 final blockNum: 2
 bytes read: 32730
@@ -590,6 +1144,17 @@ EOF logNum=2
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 32768 bytes
+	Block 1: Processing chunk at offset 0, checksum=4108154917, length=32730, encoding=9
+	Block 1: Found WAL sync chunk with syncedOffset=0 (invalidOffset=0)
+	Block 1: Processing chunk at offset 32749, checksum=0, length=0, encoding=0
+	Found invalid chunk marker at block 1 offset 32749; aborting this block scan
+Read block 2 with 19 bytes
+	Block 2: Processing chunk at offset 0, checksum=0, length=0, encoding=9
+	Mismatch log number in block 2 at offset 0 (expected 1, got 2)
+Read block 3 with 0 bytes
+	Encountered io.EOF; returning io.ErrUnexpectedEOF since no sync offset found.
 error reading next: unexpected EOF
 final blockNum: 3
 bytes read: 0
@@ -605,6 +1170,15 @@ EOF logNum=2
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 32768 bytes
+	Block 1: Processing chunk at offset 0, checksum=0, length=0, encoding=0
+	Found invalid chunk marker at block 1 offset 0; aborting this block scan
+Read block 2 with 19 bytes
+	Block 2: Processing chunk at offset 0, checksum=0, length=0, encoding=9
+	Mismatch log number in block 2 at offset 0 (expected 1, got 2)
+Read block 3 with 0 bytes
+	Encountered io.EOF; returning io.ErrUnexpectedEOF since no sync offset found.
 error reading next: unexpected EOF
 final blockNum: 3
 bytes read: 32730
@@ -620,6 +1194,11 @@ EOF logNum=2
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 39 bytes
+	Block 1: Processing chunk at offset 0, checksum=2654412641, length=1, encoding=9
+	Block 1: Found WAL sync chunk with syncedOffset=100000 (invalidOffset=32749)
+	Corruption confirmed: syncedOffset 100000 exceeds invalidOffset 32749
 error reading next: pebble/record: zeroed chunk
 final blockNum: 1
 bytes read: 32730
@@ -633,6 +1212,11 @@ EOF logNum=2
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 39 bytes
+	Block 1: Processing chunk at offset 0, checksum=2654412641, length=1, encoding=9
+	Block 1: Found WAL sync chunk with syncedOffset=100000 (invalidOffset=0)
+	Corruption confirmed: syncedOffset 100000 exceeds invalidOffset 0
 error reading next: pebble/record: zeroed chunk
 final blockNum: 1
 bytes read: 0
@@ -650,6 +1234,11 @@ EOF logNum=2
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 32768 bytes
+	Block 1: Processing chunk at offset 0, checksum=2529962274, length=32749, encoding=9
+	Block 1: Found WAL sync chunk with syncedOffset=1000000 (invalidOffset=20)
+	Corruption confirmed: syncedOffset 1000000 exceeds invalidOffset 20
 error reading next: pebble/record: zeroed chunk
 final blockNum: 1
 bytes read: 1
@@ -668,6 +1257,15 @@ EOF logNum=2
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 32768 bytes
+	Block 1: Processing chunk at offset 0, checksum=0, length=0, encoding=0
+	Found invalid chunk marker at block 1 offset 0; aborting this block scan
+Read block 2 with 19 bytes
+	Block 2: Processing chunk at offset 0, checksum=0, length=0, encoding=9
+	Mismatch log number in block 2 at offset 0 (expected 1, got 2)
+Read block 3 with 0 bytes
+	Encountered io.EOF; returning io.ErrUnexpectedEOF since no sync offset found.
 error reading next: unexpected EOF
 final blockNum: 3
 bytes read: 0
@@ -686,6 +1284,16 @@ EOF logNum=2
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 32768 bytes
+	Block 1: Processing chunk at offset 0, checksum=957580701, length=1, encoding=9
+	Block 1: Found WAL sync chunk with syncedOffset=1 (invalidOffset=20)
+	Block 1: Processing chunk at offset 20, checksum=0, length=0, encoding=0
+	Found invalid chunk marker at block 1 offset 20; aborting this block scan
+Read block 2 with 32768 bytes
+	Block 2: Processing chunk at offset 0, checksum=1662040211, length=32749, encoding=9
+	Block 2: Found WAL sync chunk with syncedOffset=64000 (invalidOffset=20)
+	Corruption confirmed: syncedOffset 64000 exceeds invalidOffset 20
 error reading next: pebble/record: zeroed chunk
 final blockNum: 2
 bytes read: 1
@@ -705,6 +1313,20 @@ EOF logNum=2
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 1.
+Read block 2 with 32768 bytes
+	Block 2: Processing chunk at offset 0, checksum=957580701, length=1, encoding=9
+	Block 2: Found WAL sync chunk with syncedOffset=1 (invalidOffset=32788)
+	Block 2: Processing chunk at offset 20, checksum=0, length=0, encoding=0
+	Found invalid chunk marker at block 2 offset 20; aborting this block scan
+Read block 3 with 32768 bytes
+	Block 3: Processing chunk at offset 0, checksum=3770313817, length=32749, encoding=9
+	Block 3: Found WAL sync chunk with syncedOffset=100 (invalidOffset=32788)
+Read block 4 with 19 bytes
+	Block 4: Processing chunk at offset 0, checksum=0, length=0, encoding=9
+	Mismatch log number in block 4 at offset 0 (expected 1, got 2)
+Read block 5 with 0 bytes
+	Encountered io.EOF; returning io.ErrUnexpectedEOF since no sync offset found.
 error reading next: unexpected EOF
 final blockNum: 5
 bytes read: 32750
@@ -722,6 +1344,11 @@ EOF logNum=2
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 32768 bytes
+	Block 1: Processing chunk at offset 0, checksum=400517472, length=32749, encoding=9
+	Block 1: Found WAL sync chunk with syncedOffset=32000 (invalidOffset=19)
+	Corruption confirmed: syncedOffset 32000 exceeds invalidOffset 19
 error reading next: pebble/record: invalid chunk
 final blockNum: 1
 bytes read: 0
@@ -734,6 +1361,9 @@ EOF logNum=2
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 0 bytes
+	Encountered io.EOF; returning io.ErrUnexpectedEOF since no sync offset found.
 error reading next: unexpected EOF
 final blockNum: 1
 bytes read: 0
@@ -747,6 +1377,12 @@ EOF logNum=2
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 19 bytes
+	Block 1: Processing chunk at offset 0, checksum=0, length=0, encoding=9
+	Mismatch log number in block 1 at offset 0 (expected 1, got 2)
+Read block 2 with 0 bytes
+	Encountered io.EOF; returning io.ErrUnexpectedEOF since no sync offset found.
 error reading next: unexpected EOF
 final blockNum: 2
 bytes read: 0
@@ -761,6 +1397,15 @@ EOF logNum=2
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 32768 bytes
+	Block 1: Processing chunk at offset 0, checksum=3161447025, length=64000, encoding=9
+	Chunk in block 1 spans beyond block boundaries (begin=19, end=64019, n=32768)
+Read block 2 with 19 bytes
+	Block 2: Processing chunk at offset 0, checksum=0, length=0, encoding=9
+	Mismatch log number in block 2 at offset 0 (expected 1, got 2)
+Read block 3 with 0 bytes
+	Encountered io.EOF; returning io.ErrUnexpectedEOF since no sync offset found.
 error reading next: unexpected EOF
 final blockNum: 3
 bytes read: 0
@@ -775,6 +1420,15 @@ EOF logNum=2
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 32768 bytes
+	Block 1: Processing chunk at offset 0, checksum=2172933859, length=64000, encoding=9
+	Chunk in block 1 spans beyond block boundaries (begin=19, end=64019, n=32768)
+Read block 2 with 19 bytes
+	Block 2: Processing chunk at offset 0, checksum=0, length=0, encoding=9
+	Mismatch log number in block 2 at offset 0 (expected 1, got 2)
+Read block 3 with 0 bytes
+	Encountered io.EOF; returning io.ErrUnexpectedEOF since no sync offset found.
 error reading next: unexpected EOF
 final blockNum: 3
 bytes read: 0
@@ -788,6 +1442,11 @@ EOF logNum=2
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 39 bytes
+	Block 1: Processing chunk at offset 0, checksum=2230101255, length=1, encoding=9
+	Block 1: Found WAL sync chunk with syncedOffset=30000 (invalidOffset=19)
+	Corruption confirmed: syncedOffset 30000 exceeds invalidOffset 19
 error reading next: pebble/record: invalid chunk
 final blockNum: 1
 bytes read: 0
@@ -805,6 +1464,11 @@ EOF logNum=2
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 32768 bytes
+	Block 1: Processing chunk at offset 0, checksum=2172933859, length=32749, encoding=9
+	Block 1: Found WAL sync chunk with syncedOffset=1000 (invalidOffset=19)
+	Corruption confirmed: syncedOffset 1000 exceeds invalidOffset 19
 error reading next: pebble/record: invalid chunk
 final blockNum: 1
 bytes read: 0
@@ -822,6 +1486,15 @@ EOF logNum=2
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 32768 bytes
+	Block 1: Processing chunk at offset 0, checksum=400517472, length=64000, encoding=9
+	Chunk in block 1 spans beyond block boundaries (begin=19, end=64019, n=32768)
+Read block 2 with 19 bytes
+	Block 2: Processing chunk at offset 0, checksum=0, length=0, encoding=9
+	Mismatch log number in block 2 at offset 0 (expected 1, got 2)
+Read block 3 with 0 bytes
+	Encountered io.EOF; returning io.ErrUnexpectedEOF since no sync offset found.
 error reading next: unexpected EOF
 final blockNum: 3
 bytes read: 0
@@ -841,6 +1514,21 @@ EOF logNum=2
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 32768 bytes
+	Block 1: Processing chunk at offset 0, checksum=685962385, length=32749, encoding=9
+	Mismatch log number in block 1 at offset 0 (expected 1, got 0)
+Read block 2 with 32768 bytes
+	Block 2: Processing chunk at offset 0, checksum=2522303644, length=1, encoding=9
+	Mismatch log number in block 2 at offset 0 (expected 1, got 0)
+Read block 3 with 32768 bytes
+	Block 3: Processing chunk at offset 0, checksum=3593774434, length=32749, encoding=9
+	Mismatch log number in block 3 at offset 0 (expected 1, got 0)
+Read block 4 with 19 bytes
+	Block 4: Processing chunk at offset 0, checksum=0, length=0, encoding=9
+	Mismatch log number in block 4 at offset 0 (expected 1, got 2)
+Read block 5 with 0 bytes
+	Encountered io.EOF; returning io.ErrUnexpectedEOF since no sync offset found.
 error reading next: unexpected EOF
 final blockNum: 5
 bytes read: 0
@@ -860,6 +1548,17 @@ EOF logNum=2
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 32768 bytes
+	Block 1: Processing chunk at offset 0, checksum=400517472, length=64000, encoding=9
+	Chunk in block 1 spans beyond block boundaries (begin=19, end=64019, n=32768)
+Read block 2 with 32768 bytes
+	Block 2: Processing chunk at offset 0, checksum=3699662224, length=200, encoding=9
+	Checksum mismatch in block 2 at offset 219; potential corruption
+Read block 3 with 32768 bytes
+	Block 3: Processing chunk at offset 0, checksum=400517472, length=32749, encoding=9
+	Block 3: Found WAL sync chunk with syncedOffset=32000 (invalidOffset=39)
+	Corruption confirmed: syncedOffset 32000 exceeds invalidOffset 39
 error reading next: pebble/record: invalid chunk
 final blockNum: 3
 bytes read: 1
@@ -880,6 +1579,21 @@ EOF logNum=2
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 1.
+Read block 2 with 32768 bytes
+	Block 2: Processing chunk at offset 0, checksum=400517472, length=64000, encoding=9
+	Chunk in block 2 spans beyond block boundaries (begin=19, end=64019, n=32768)
+Read block 3 with 32768 bytes
+	Block 3: Processing chunk at offset 0, checksum=3699662224, length=200, encoding=9
+	Checksum mismatch in block 3 at offset 219; potential corruption
+Read block 4 with 32768 bytes
+	Block 4: Processing chunk at offset 0, checksum=3770313817, length=32749, encoding=9
+	Block 4: Found WAL sync chunk with syncedOffset=100 (invalidOffset=32807)
+Read block 5 with 19 bytes
+	Block 5: Processing chunk at offset 0, checksum=0, length=0, encoding=9
+	Mismatch log number in block 5 at offset 0 (expected 1, got 2)
+Read block 6 with 0 bytes
+	Encountered io.EOF; returning io.ErrUnexpectedEOF since no sync offset found.
 error reading next: unexpected EOF
 final blockNum: 6
 bytes read: 32750
@@ -897,6 +1611,9 @@ EOF logNum=2
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 0 bytes
+	Encountered io.EOF; returning io.ErrUnexpectedEOF since no sync offset found.
 error reading next: unexpected EOF
 final blockNum: 1
 bytes read: 0
@@ -909,6 +1626,12 @@ EOF logNum=2
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 19 bytes
+	Block 1: Processing chunk at offset 0, checksum=0, length=0, encoding=9
+	Mismatch log number in block 1 at offset 0 (expected 1, got 2)
+Read block 2 with 0 bytes
+	Encountered io.EOF; returning io.ErrUnexpectedEOF since no sync offset found.
 error reading next: unexpected EOF
 final blockNum: 2
 bytes read: 0
@@ -922,6 +1645,15 @@ EOF logNum=2
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 32768 bytes
+	Block 1: Processing chunk at offset 0, checksum=4212587649, length=32749, encoding=100
+	Invalid chunk encoding encountered (value: 100); stopping chunk scan in block 1
+Read block 2 with 19 bytes
+	Block 2: Processing chunk at offset 0, checksum=0, length=0, encoding=9
+	Mismatch log number in block 2 at offset 0 (expected 1, got 2)
+Read block 3 with 0 bytes
+	Encountered io.EOF; returning io.ErrUnexpectedEOF since no sync offset found.
 error reading next: unexpected EOF
 final blockNum: 3
 bytes read: 0
@@ -935,6 +1667,15 @@ EOF logNum=2
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 32768 bytes
+	Block 1: Processing chunk at offset 0, checksum=3910387679, length=32749, encoding=100
+	Invalid chunk encoding encountered (value: 100); stopping chunk scan in block 1
+Read block 2 with 19 bytes
+	Block 2: Processing chunk at offset 0, checksum=0, length=0, encoding=9
+	Mismatch log number in block 2 at offset 0 (expected 1, got 2)
+Read block 3 with 0 bytes
+	Encountered io.EOF; returning io.ErrUnexpectedEOF since no sync offset found.
 error reading next: unexpected EOF
 final blockNum: 3
 bytes read: 0
@@ -948,6 +1689,11 @@ EOF logNum=2
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 39 bytes
+	Block 1: Processing chunk at offset 0, checksum=2230101255, length=1, encoding=9
+	Block 1: Found WAL sync chunk with syncedOffset=30000 (invalidOffset=0)
+	Corruption confirmed: syncedOffset 30000 exceeds invalidOffset 0
 error reading next: pebble/record: invalid chunk
 final blockNum: 1
 bytes read: 0
@@ -965,6 +1711,11 @@ EOF logNum=2
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 32768 bytes
+	Block 1: Processing chunk at offset 0, checksum=400517472, length=32749, encoding=9
+	Block 1: Found WAL sync chunk with syncedOffset=32000 (invalidOffset=0)
+	Corruption confirmed: syncedOffset 32000 exceeds invalidOffset 0
 error reading next: pebble/record: invalid chunk
 final blockNum: 1
 bytes read: 0
@@ -982,6 +1733,15 @@ EOF logNum=2
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 32768 bytes
+	Block 1: Processing chunk at offset 0, checksum=3601296272, length=32749, encoding=100
+	Invalid chunk encoding encountered (value: 100); stopping chunk scan in block 1
+Read block 2 with 19 bytes
+	Block 2: Processing chunk at offset 0, checksum=0, length=0, encoding=9
+	Mismatch log number in block 2 at offset 0 (expected 1, got 2)
+Read block 3 with 0 bytes
+	Encountered io.EOF; returning io.ErrUnexpectedEOF since no sync offset found.
 error reading next: unexpected EOF
 final blockNum: 3
 bytes read: 0
@@ -1001,6 +1761,21 @@ EOF logNum=2
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 32768 bytes
+	Block 1: Processing chunk at offset 0, checksum=3601296272, length=32749, encoding=100
+	Invalid chunk encoding encountered (value: 100); stopping chunk scan in block 1
+Read block 2 with 32768 bytes
+	Block 2: Processing chunk at offset 0, checksum=3330109772, length=1, encoding=100
+	Invalid chunk encoding encountered (value: 100); stopping chunk scan in block 2
+Read block 3 with 32768 bytes
+	Block 3: Processing chunk at offset 0, checksum=611851875, length=32749, encoding=100
+	Invalid chunk encoding encountered (value: 100); stopping chunk scan in block 3
+Read block 4 with 19 bytes
+	Block 4: Processing chunk at offset 0, checksum=0, length=0, encoding=9
+	Mismatch log number in block 4 at offset 0 (expected 1, got 2)
+Read block 5 with 0 bytes
+	Encountered io.EOF; returning io.ErrUnexpectedEOF since no sync offset found.
 error reading next: unexpected EOF
 final blockNum: 5
 bytes read: 0
@@ -1020,6 +1795,17 @@ EOF logNum=2
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 32768 bytes
+	Block 1: Processing chunk at offset 0, checksum=3601296272, length=32749, encoding=100
+	Invalid chunk encoding encountered (value: 100); stopping chunk scan in block 1
+Read block 2 with 32768 bytes
+	Block 2: Processing chunk at offset 0, checksum=1030283140, length=1, encoding=100
+	Invalid chunk encoding encountered (value: 100); stopping chunk scan in block 2
+Read block 3 with 32768 bytes
+	Block 3: Processing chunk at offset 0, checksum=400517472, length=32749, encoding=9
+	Block 3: Found WAL sync chunk with syncedOffset=32000 (invalidOffset=0)
+	Corruption confirmed: syncedOffset 32000 exceeds invalidOffset 0
 error reading next: pebble/record: invalid chunk
 final blockNum: 3
 bytes read: 0
@@ -1037,6 +1823,18 @@ EOF logNum=2
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 1.
+Read block 2 with 32768 bytes
+	Block 2: Processing chunk at offset 0, checksum=1030283140, length=1, encoding=100
+	Invalid chunk encoding encountered (value: 100); stopping chunk scan in block 2
+Read block 3 with 32768 bytes
+	Block 3: Processing chunk at offset 0, checksum=3770313817, length=32749, encoding=9
+	Block 3: Found WAL sync chunk with syncedOffset=100 (invalidOffset=32768)
+Read block 4 with 19 bytes
+	Block 4: Processing chunk at offset 0, checksum=0, length=0, encoding=9
+	Mismatch log number in block 4 at offset 0 (expected 1, got 2)
+Read block 5 with 0 bytes
+	Encountered io.EOF; returning io.ErrUnexpectedEOF since no sync offset found.
 error reading next: unexpected EOF
 final blockNum: 5
 bytes read: 32749
@@ -1056,6 +1854,11 @@ EOF logNum=2
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 32768 bytes
+	Block 1: Processing chunk at offset 0, checksum=2928164217, length=32749, encoding=9
+	Block 1: Found WAL sync chunk with syncedOffset=60000 (invalidOffset=32757)
+	Corruption confirmed: syncedOffset 60000 exceeds invalidOffset 32757
 error reading next: pebble/record: invalid chunk
 final blockNum: 1
 bytes read: 32738
@@ -1069,6 +1872,12 @@ EOF logNum=2
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 19 bytes
+	Block 1: Processing chunk at offset 0, checksum=0, length=0, encoding=9
+	Mismatch log number in block 1 at offset 0 (expected 1, got 2)
+Read block 2 with 0 bytes
+	Encountered io.EOF; returning io.ErrUnexpectedEOF since no sync offset found.
 error reading next: unexpected EOF
 final blockNum: 2
 bytes read: 32738
@@ -1084,6 +1893,17 @@ EOF logNum=2
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 32768 bytes
+	Block 1: Processing chunk at offset 0, checksum=1047728140, length=32738, encoding=9
+	Block 1: Found WAL sync chunk with syncedOffset=0 (invalidOffset=32757)
+	Block 1: Processing chunk at offset 32757, checksum=16843009, length=257, encoding=9
+	Incomplete header in block 1 at offset 32757; breaking out
+Read block 2 with 19 bytes
+	Block 2: Processing chunk at offset 0, checksum=0, length=0, encoding=9
+	Mismatch log number in block 2 at offset 0 (expected 1, got 2)
+Read block 3 with 0 bytes
+	Encountered io.EOF; returning io.ErrUnexpectedEOF since no sync offset found.
 error reading next: unexpected EOF
 final blockNum: 3
 bytes read: 32738
@@ -1099,6 +1919,11 @@ EOF logNum=2
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 32768 bytes
+	Block 1: Processing chunk at offset 0, checksum=1660446616, length=32738, encoding=9
+	Block 1: Found WAL sync chunk with syncedOffset=1000000 (invalidOffset=32757)
+	Corruption confirmed: syncedOffset 1000000 exceeds invalidOffset 32757
 error reading next: pebble/record: invalid chunk
 final blockNum: 1
 bytes read: 32738
@@ -1113,6 +1938,11 @@ EOF logNum=2
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 39 bytes
+	Block 1: Processing chunk at offset 0, checksum=2654412641, length=1, encoding=9
+	Block 1: Found WAL sync chunk with syncedOffset=100000 (invalidOffset=32757)
+	Corruption confirmed: syncedOffset 100000 exceeds invalidOffset 32757
 error reading next: pebble/record: invalid chunk
 final blockNum: 1
 bytes read: 32738
@@ -1130,6 +1960,11 @@ EOF logNum=2
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 32768 bytes
+	Block 1: Processing chunk at offset 0, checksum=2529962274, length=32749, encoding=9
+	Block 1: Found WAL sync chunk with syncedOffset=1000000 (invalidOffset=20)
+	Corruption confirmed: syncedOffset 1000000 exceeds invalidOffset 20
 error reading next: pebble/record: invalid chunk
 final blockNum: 1
 bytes read: 1
@@ -1148,6 +1983,15 @@ EOF logNum=2
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 32768 bytes
+	Block 1: Processing chunk at offset 0, checksum=1306192168, length=32738, encoding=0
+	Invalid wire format detected in block 1 at offset 0
+Read block 2 with 19 bytes
+	Block 2: Processing chunk at offset 0, checksum=0, length=0, encoding=9
+	Mismatch log number in block 2 at offset 0 (expected 1, got 2)
+Read block 3 with 0 bytes
+	Encountered io.EOF; returning io.ErrUnexpectedEOF since no sync offset found.
 error reading next: unexpected EOF
 final blockNum: 3
 bytes read: 1
@@ -1166,6 +2010,16 @@ EOF logNum=2
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 0.
+Read block 1 with 32768 bytes
+	Block 1: Processing chunk at offset 0, checksum=957580701, length=1, encoding=9
+	Block 1: Found WAL sync chunk with syncedOffset=1 (invalidOffset=20)
+	Block 1: Processing chunk at offset 20, checksum=16843009, length=257, encoding=9
+	Mismatch log number in block 1 at offset 20 (expected 1, got 0)
+Read block 2 with 32768 bytes
+	Block 2: Processing chunk at offset 0, checksum=1662040211, length=32749, encoding=9
+	Block 2: Found WAL sync chunk with syncedOffset=64000 (invalidOffset=20)
+	Corruption confirmed: syncedOffset 64000 exceeds invalidOffset 20
 error reading next: pebble/record: invalid chunk
 final blockNum: 2
 bytes read: 1
@@ -1186,6 +2040,20 @@ EOF logNum=2
 
 read
 ----
+Starting read ahead for corruption. Block corrupted 1.
+Read block 2 with 32768 bytes
+	Block 2: Processing chunk at offset 0, checksum=957580701, length=1, encoding=9
+	Block 2: Found WAL sync chunk with syncedOffset=1 (invalidOffset=32788)
+	Block 2: Processing chunk at offset 20, checksum=16843009, length=257, encoding=9
+	Mismatch log number in block 2 at offset 20 (expected 1, got 0)
+Read block 3 with 32768 bytes
+	Block 3: Processing chunk at offset 0, checksum=3770313817, length=32749, encoding=9
+	Block 3: Found WAL sync chunk with syncedOffset=100 (invalidOffset=32788)
+Read block 4 with 19 bytes
+	Block 4: Processing chunk at offset 0, checksum=0, length=0, encoding=9
+	Mismatch log number in block 4 at offset 0 (expected 1, got 2)
+Read block 5 with 0 bytes
+	Encountered io.EOF; returning io.ErrUnexpectedEOF since no sync offset found.
 error reading next: unexpected EOF
 final blockNum: 5
 bytes read: 32750