diff --git a/core/headerchain.go b/core/headerchain.go index 29cb55a212..477c629dda 100644 --- a/core/headerchain.go +++ b/core/headerchain.go @@ -620,7 +620,8 @@ func (hc *HeaderChain) setHead(headBlock uint64, headTime uint64, updateFn Updat // This step must be performed after synchronizing the key-value store; // otherwise, in the event of a panic, it's theoretically possible to // lose recent key-value store writes while the ancient store deletions - // remain, leading to data inconsistency. + // remain, leading to data inconsistency, e.g., the gap between the key + // value store and ancient can be created due to unclean shutdown. if delFn != nil { // Ignore the error here since light client won't hit this path frozen, _ := hc.chainDb.Ancients() diff --git a/ethdb/leveldb/leveldb.go b/ethdb/leveldb/leveldb.go index e8f15753ec..b583365ac2 100644 --- a/ethdb/leveldb/leveldb.go +++ b/ethdb/leveldb/leveldb.go @@ -329,7 +329,8 @@ func (db *Database) Path() string { func (db *Database) Sync() error { // In theory, the WAL (Write-Ahead Log) can be explicitly synchronized using // a write operation with SYNC=true. However, there is no dedicated key reserved - // for this purpose, and even a nil key (key=nil) is considered a valid database entry. + // for this purpose, and even a nil key (key=nil) is considered a valid + // database entry. // // In LevelDB, writes are blocked until the data is written to the WAL, meaning // recent writes won't be lost unless a power failure or system crash occurs. diff --git a/ethdb/pebble/pebble.go b/ethdb/pebble/pebble.go index dc6ab92db0..5b2918aa89 100644 --- a/ethdb/pebble/pebble.go +++ b/ethdb/pebble/pebble.go @@ -182,10 +182,18 @@ func New(file string, cache int, handles int, namespace string, readonly bool) ( memTableSize = maxMemTableSize - 1 } db := &Database{ - fn: file, - log: logger, - quitChan: make(chan chan error), - writeOptions: &pebble.WriteOptions{Sync: false}, + fn: file, + log: logger, + quitChan: make(chan chan error), + + // Use asynchronous write mode by default. Otherwise, the overhead of frequent fsync + // operations can be significant, especially on platforms with slow fsync performance + // (e.g., macOS) or less capable SSDs. + // + // Note that enabling async writes means recent data may be lost in the event of an + // application-level panic (writes will also be lost on a machine-level failure, + // of course). Geth is expected to handle recovery from an unclean shutdown. + writeOptions: pebble.NoSync, } opt := &pebble.Options{ // Pebble has a single combined cache area and the write @@ -426,12 +434,11 @@ func (d *Database) Path() string { // Sync flushes all pending writes in the write-ahead-log to disk, ensuring // data durability up to that point. func (d *Database) Sync() error { - b := d.db.NewBatch() - // The entry (value=nil) is not written to the database; it is only // added to the WAL. Writing this special log entry in sync mode // automatically flushes all previous writes, ensuring database // durability up to this point. + b := d.db.NewBatch() b.LogData(nil, nil) return d.db.Apply(b, pebble.Sync) } diff --git a/triedb/pathdb/buffer.go b/triedb/pathdb/buffer.go index 12c98f24f0..c4e081b973 100644 --- a/triedb/pathdb/buffer.go +++ b/triedb/pathdb/buffer.go @@ -135,8 +135,11 @@ func (b *buffer) flush(db ethdb.KeyValueStore, freezer ethdb.AncientWriter, node start = time.Now() batch = db.NewBatchWithSize(b.nodes.dbsize() * 11 / 10) // extra 10% for potential pebble internal stuff ) - // Explicitly sync the state freezer, ensuring that all written - // data is transferred to disk before updating the key-value store. + // Explicitly sync the state freezer to ensure all written data is persisted to disk + // before updating the key-value store. + // + // This step is crucial to guarantee that the corresponding state history remains + // available for state rollback. if freezer != nil { if err := freezer.SyncAncient(); err != nil { return err