Skip to content

Commit bd2c6e6

Browse files
benbjohnsonclaude
andauthored
perf(db): cache Pos() result to avoid repeated disk reads (#1192)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 5531fab commit bd2c6e6

File tree

2 files changed

+52
-1
lines changed

2 files changed

+52
-1
lines changed

db.go

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,13 @@ type DB struct {
101101
m map[int]*ltx.FileInfo
102102
}
103103

104+
// Cached position from the latest L0 LTX file.
105+
// nil means cache is invalid; non-nil is the cached position.
106+
pos struct {
107+
sync.Mutex
108+
value *ltx.Pos
109+
}
110+
104111
fileInfo os.FileInfo // db info cached during init
105112
dirInfo os.FileInfo // parent dir info cached during init
106113

@@ -300,6 +307,8 @@ func (db *DB) ResetLocalState(ctx context.Context) error {
300307
db.maxLTXFileInfos.m = make(map[int]*ltx.FileInfo)
301308
db.maxLTXFileInfos.Unlock()
302309

310+
db.invalidatePosCache()
311+
303312
db.Logger.Info("local state reset complete, next sync will create fresh snapshot")
304313
return nil
305314
}
@@ -330,6 +339,9 @@ func (db *DB) deleteLocalLTXFile(level int, minTXID, maxTXID ltx.TXID) error {
330339
if err := os.Remove(path); err != nil && !os.IsNotExist(err) {
331340
return err
332341
}
342+
if level == 0 {
343+
db.invalidatePosCache()
344+
}
333345
return nil
334346
}
335347

@@ -364,7 +376,15 @@ func (db *DB) DirInfo() os.FileInfo {
364376
}
365377

366378
// Pos returns the current replication position of the database.
379+
// The result is cached and invalidated when L0 LTX files change.
367380
func (db *DB) Pos() (ltx.Pos, error) {
381+
db.pos.Lock()
382+
defer db.pos.Unlock()
383+
384+
if db.pos.value != nil {
385+
return *db.pos.value, nil
386+
}
387+
368388
minTXID, maxTXID, err := db.MaxLTX()
369389
if err != nil {
370390
return ltx.Pos{}, err
@@ -382,7 +402,20 @@ func (db *DB) Pos() (ltx.Pos, error) {
382402
if err := dec.Verify(); err != nil {
383403
return ltx.Pos{}, fmt.Errorf("ltx verification failed: %w", err)
384404
}
385-
return dec.PostApplyPos(), nil
405+
406+
pos := dec.PostApplyPos()
407+
db.pos.value = &pos
408+
409+
return pos, nil
410+
}
411+
412+
// invalidatePosCache clears the cached position so the next call to Pos()
413+
// recomputes it from disk. Call this when L0 LTX files are deleted or
414+
// when the L0 directory is cleared.
415+
func (db *DB) invalidatePosCache() {
416+
db.pos.Lock()
417+
db.pos.value = nil
418+
db.pos.Unlock()
386419
}
387420

388421
// Notify returns a channel that closes when the shadow WAL changes.
@@ -1195,6 +1228,7 @@ func (db *DB) checkDatabaseBehindReplica(ctx context.Context) error {
11951228
if err := os.RemoveAll(l0Dir); err != nil && !os.IsNotExist(err) {
11961229
return fmt.Errorf("remove L0 directory: %w", err)
11971230
}
1231+
db.invalidatePosCache()
11981232
if err := internal.MkdirAll(l0Dir, db.dirInfo); err != nil {
11991233
return fmt.Errorf("recreate L0 directory: %w", err)
12001234
}
@@ -1235,6 +1269,7 @@ func (db *DB) checkDatabaseBehindReplica(ctx context.Context) error {
12351269
if err := os.Rename(tmpPath, localPath); err != nil {
12361270
return fmt.Errorf("rename L0 file: %w", err)
12371271
}
1272+
db.invalidatePosCache()
12381273

12391274
db.Logger.Info("fetched latest L0 file from replica",
12401275
"min_txid", minTXID,
@@ -1632,6 +1667,7 @@ func (db *DB) sync(ctx context.Context, checkpointing bool, info syncInfo) (sync
16321667
db.maxLTXFileInfos.Lock()
16331668
delete(db.maxLTXFileInfos.m, 0) // clear cache if in unknown state
16341669
db.maxLTXFileInfos.Unlock()
1670+
db.invalidatePosCache()
16351671
return false, fmt.Errorf("rename ltx file: %w", err)
16361672
}
16371673

@@ -1646,6 +1682,12 @@ func (db *DB) sync(ctx context.Context, checkpointing bool, info syncInfo) (sync
16461682
}
16471683
db.maxLTXFileInfos.Unlock()
16481684

1685+
// Update cached position from the encoder.
1686+
encPos := enc.PostApplyPos()
1687+
db.pos.Lock()
1688+
db.pos.value = &encPos
1689+
db.pos.Unlock()
1690+
16491691
// Track the logical end of WAL content for checkpoint decisions.
16501692
// This is the WALOffset + WALSize from the LTX we just created.
16511693
// Using this instead of file size prevents issue #997 where stale
@@ -2177,6 +2219,9 @@ func (db *DB) EnforceL0RetentionByTime(ctx context.Context) error {
21772219
db.Logger.Error("failed to remove local l0 file", "path", localPath, "error", err)
21782220
}
21792221
}
2222+
if len(deleted) > 0 {
2223+
db.invalidatePosCache()
2224+
}
21802225

21812226
db.Logger.Info("l0 retention enforced", "deleted_count", len(deleted), "max_l1_txid", maxL1TXID)
21822227

db_internal_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,9 @@ func TestDB_Verify_WALOffsetAtHeader(t *testing.T) {
656656
t.Fatal(err)
657657
}
658658

659+
// Invalidate cached position since we wrote an L0 file directly.
660+
db.invalidatePosCache()
661+
659662
// Now call verify - before the fix, this would fail with:
660663
// "prev WAL offset is less than the header size: -4088"
661664
info, err := db.verify(context.Background())
@@ -776,6 +779,9 @@ func TestDB_Verify_WALOffsetAtHeader_SaltMismatch(t *testing.T) {
776779
t.Fatal(err)
777780
}
778781

782+
// Invalidate cached position since we wrote an L0 file directly.
783+
db.invalidatePosCache()
784+
779785
// Call verify - should succeed but indicate snapshotting due to salt mismatch
780786
info, err := db.verify(context.Background())
781787
if err != nil {

0 commit comments

Comments
 (0)