github.com/zuoyebang/bitalostable@v1.0.1-0.20240229032404-e3b99a834294/internal/replay/replay.go (about) 1 // Copyright 2020 The LevelDB-Go and Pebble Authors. All rights reserved. Use 2 // of this source code is governed by a BSD-style license that can be found in 3 // the LICENSE file. 4 5 // Package replay implements facilities for replaying writes to a database. 6 package replay 7 8 import ( 9 "sort" 10 11 "github.com/zuoyebang/bitalostable" 12 "github.com/zuoyebang/bitalostable/internal/manifest" 13 "github.com/zuoyebang/bitalostable/internal/private" 14 ) 15 16 // Table describes a sstable from the reference database whose writes are 17 // being replayed. 18 type Table struct { 19 Path string 20 FileMetadata *manifest.FileMetadata 21 } 22 23 type bySeqNum []Table 24 25 func (s bySeqNum) Len() int { return len(s) } 26 func (s bySeqNum) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 27 func (s bySeqNum) Less(i, j int) bool { 28 return s[i].FileMetadata.SmallestSeqNum < s[j].FileMetadata.SmallestSeqNum 29 } 30 31 // Open opens a database for replaying flushed and ingested tables. It's 32 // intended for use by the `bitalostable bench compact` command. 33 func Open(dirname string, opts *bitalostable.Options) (*DB, error) { 34 d, err := bitalostable.Open(dirname, opts) 35 if err != nil { 36 return nil, err 37 } 38 return &DB{d: d}, nil 39 } 40 41 // A DB is a wrapper around a Pebble database for replaying table-level 42 // writes to a database. It is not safe for concurrent access. 43 type DB struct { 44 done bool 45 d *bitalostable.DB 46 } 47 48 // Ingest ingests a set of sstables into the DB. 49 func (d *DB) Ingest(tables []Table) error { 50 if d.done { 51 panic("replay already finished") 52 } 53 if len(tables) == 0 { 54 return nil 55 } 56 57 // Sort the tables by their assigned sequence numbers so that we pass them 58 // to Ingest in the same order and ultimately assign sequence numbers in 59 // the same order. 60 sort.Sort(bySeqNum(tables)) 61 62 // The replay database's current allocated sequence number might be less 63 // than it was at the time of this ingest in the reference database 64 // because only overlapping memtables are flushed before an ingest. 65 // We need the ingested files to adopt the same sequence numbers as they 66 // originally did to ensure the sequence number invariants still hold. 67 // To accommodate this, we bump the sequence number up to just below the 68 // sequence number these files' were originally ingested at. 69 smallest := tables[0].FileMetadata.SmallestSeqNum 70 private.RatchetSeqNum(d.d, smallest) 71 72 paths := make([]string, len(tables)) 73 for i, tbl := range tables { 74 paths[i] = tbl.Path 75 } 76 return d.d.Ingest(paths) 77 } 78 79 // FlushExternal simulates a flush of the table, linking it directly 80 // into level zero. 81 func (d *DB) FlushExternal(tbl Table) error { 82 if d.done { 83 panic("replay already finished") 84 } 85 return private.FlushExternalTable(d.d, tbl.Path, tbl.FileMetadata) 86 } 87 88 // Metrics returns the underlying DB's Metrics. 89 func (d *DB) Metrics() *bitalostable.Metrics { 90 if d.done { 91 panic("replay already finished") 92 } 93 return d.d.Metrics() 94 } 95 96 // Done finishes a replay, returning the underlying database. All of the 97 // *replay.DB's methods except Close will error if called after Done. 98 func (d *DB) Done() *bitalostable.DB { 99 if d.done { 100 panic("replay already finished") 101 } 102 d.done = true 103 return d.d 104 } 105 106 // Close closes a replay and the underlying database. 107 // If Close is called after Done, Close does nothing. 108 func (d *DB) Close() error { 109 if d.done { 110 // Allow clients to defer Close() 111 return nil 112 } 113 d.done = true 114 return d.d.Close() 115 }