triedb/pathdb: polish

This commit is contained in:
Gary Rong 2025-06-18 15:58:56 +08:00
parent 761ac5d45f
commit dbf6d0460b
6 changed files with 56 additions and 57 deletions

View file

@ -168,6 +168,7 @@ func newHelper(scheme string) *testHelper {
if scheme == rawdb.PathScheme { if scheme == rawdb.PathScheme {
config.PathDB = &pathdb.Config{ config.PathDB = &pathdb.Config{
SnapshotNoBuild: true, SnapshotNoBuild: true,
NoAsyncFlush: true,
} // disable caching } // disable caching
} else { } else {
config.HashDB = &hashdb.Config{} // disable caching config.HashDB = &hashdb.Config{} // disable caching
@ -242,18 +243,6 @@ func (t *testHelper) Commit() common.Hash {
} }
t.triedb.Update(root, types.EmptyRootHash, 0, t.nodes, t.states) t.triedb.Update(root, types.EmptyRootHash, 0, t.nodes, t.states)
t.triedb.Commit(root, false) t.triedb.Commit(root, false)
// re-open the trie database to ensure the frozen buffer
// is not referenced
//config := &triedb.Config{}
//if t.triedb.Scheme() == rawdb.PathScheme {
// config.PathDB = &pathdb.Config{
// SnapshotNoBuild: true,
// } // disable caching
//} else {
// config.HashDB = &hashdb.Config{} // disable caching
//}
//t.triedb = triedb.NewDatabase(t.triedb.Disk(), config)
return root return root
} }

View file

@ -39,8 +39,12 @@ type buffer struct {
nodes *nodeSet // Aggregated trie node set nodes *nodeSet // Aggregated trie node set
states *stateSet // Aggregated state set states *stateSet // Aggregated state set
done chan struct{} // notifier whether the content in buffer has been flushed or not // done is the notifier whether the content in buffer has been flushed or not.
flushErr error // error if any exception occurs during flushing // This channel is nil if the buffer is not frozen.
done chan struct{}
// flushErr memorizes the error if any exception occurs during flushing
flushErr error
} }
// newBuffer initializes the buffer with the provided states and trie nodes. // newBuffer initializes the buffer with the provided states and trie nodes.
@ -65,7 +69,7 @@ func (b *buffer) account(hash common.Hash) ([]byte, bool) {
return b.states.account(hash) return b.states.account(hash)
} }
// storage retrieves the storage slot with account address hash and slot key. // storage retrieves the storage slot with account address hash and slot key hash.
func (b *buffer) storage(addrHash common.Hash, storageHash common.Hash) ([]byte, bool) { func (b *buffer) storage(addrHash common.Hash, storageHash common.Hash) ([]byte, bool) {
return b.states.storage(addrHash, storageHash) return b.states.storage(addrHash, storageHash)
} }
@ -134,6 +138,8 @@ func (b *buffer) flush(root common.Hash, db ethdb.KeyValueStore, freezer ethdb.A
} }
b.done = make(chan struct{}) // allocate the channel for notification b.done = make(chan struct{}) // allocate the channel for notification
// Schedule the background thread to construct the batch, which usually
// take a few seconds.
go func() { go func() {
defer func() { defer func() {
if postFlush != nil { if postFlush != nil {
@ -185,7 +191,9 @@ func (b *buffer) flush(root common.Hash, db ethdb.KeyValueStore, freezer ethdb.A
// The content in the frozen buffer is kept for consequent state access, // The content in the frozen buffer is kept for consequent state access,
// TODO (rjl493456442) measure the gc overhead for holding this struct. // TODO (rjl493456442) measure the gc overhead for holding this struct.
// TODO (rjl493456442) can we somehow get rid of it after flushing?? // TODO (rjl493456442) can we somehow get rid of it after flushing??
b.reset() // TODO (rjl493456442) buffer itself is not thread-safe, add the lock
// protection if try to reset the buffer here.
// b.reset()
log.Debug("Persisted buffer content", "nodes", nodes, "accounts", accounts, "slots", slots, "bytes", common.StorageSize(size), "elapsed", common.PrettyDuration(time.Since(start))) log.Debug("Persisted buffer content", "nodes", nodes, "accounts", accounts, "slots", slots, "bytes", common.StorageSize(size), "elapsed", common.PrettyDuration(time.Since(start)))
}() }()
} }

View file

@ -437,12 +437,9 @@ func (db *Database) Disable() error {
// Terminate the state generator if it's active and mark the disk layer // Terminate the state generator if it's active and mark the disk layer
// as stale to prevent access to persistent state. // as stale to prevent access to persistent state.
disk := db.tree.bottom() disk := db.tree.bottom()
if err := disk.waitFlush(); err != nil { if err := disk.terminate(); err != nil {
return err return err
} }
if disk.generator != nil {
disk.generator.stop()
}
disk.markStale() disk.markStale()
// Write the initial sync flag to persist it across restarts. // Write the initial sync flag to persist it across restarts.
@ -602,13 +599,9 @@ func (db *Database) Close() error {
// be done before terminating the potential background snapshot // be done before terminating the potential background snapshot
// generator. // generator.
dl := db.tree.bottom() dl := db.tree.bottom()
if err := dl.waitFlush(); err != nil { if err := dl.terminate(); err != nil {
return err return err
} }
// Terminate the background generation if it's active
if dl.generator != nil {
dl.generator.stop()
}
dl.resetCache() // release the memory held by clean cache dl.resetCache() // release the memory held by clean cache
// Close the attached state history freezer. // Close the attached state history freezer.
@ -677,6 +670,9 @@ func (db *Database) HistoryRange() (uint64, uint64, error) {
// waitGeneration waits until the background generation is finished. It assumes // waitGeneration waits until the background generation is finished. It assumes
// that the generation is permitted; otherwise, it will block indefinitely. // that the generation is permitted; otherwise, it will block indefinitely.
func (db *Database) waitGeneration() { func (db *Database) waitGeneration() {
db.lock.RLock()
defer db.lock.RUnlock()
gen := db.tree.bottom().generator gen := db.tree.bottom().generator
if gen == nil || gen.completed() { if gen == nil || gen.completed() {
return return
@ -693,7 +689,7 @@ func (db *Database) AccountIterator(root common.Hash, seek common.Hash) (Account
if wait { if wait {
return nil, errDatabaseWaitSync return nil, errDatabaseWaitSync
} }
if gen := db.tree.bottom().generator; gen != nil && !gen.completed() { if !db.tree.bottom().genComplete() {
return nil, errNotConstructed return nil, errNotConstructed
} }
return newFastAccountIterator(db, root, seek) return newFastAccountIterator(db, root, seek)
@ -708,7 +704,7 @@ func (db *Database) StorageIterator(root common.Hash, account common.Hash, seek
if wait { if wait {
return nil, errDatabaseWaitSync return nil, errDatabaseWaitSync
} }
if gen := db.tree.bottom().generator; gen != nil && !gen.completed() { if !db.tree.bottom().genComplete() {
return nil, errNotConstructed return nil, errNotConstructed
} }
return newFastStorageIterator(db, root, account, seek) return newFastStorageIterator(db, root, account, seek)

View file

@ -49,6 +49,7 @@ type diskLayer struct {
// The generator is set if the state snapshot was not fully completed, // The generator is set if the state snapshot was not fully completed,
// regardless of whether the background generation is running or not. // regardless of whether the background generation is running or not.
// It should only be unset if the generation completes.
generator *generator generator *generator
} }
@ -121,7 +122,7 @@ func (dl *diskLayer) node(owner common.Hash, path []byte, depth int) ([]byte, co
// (both the live one and the frozen one). Note the buffer is lock free since // (both the live one and the frozen one). Note the buffer is lock free since
// it's impossible to mutate the buffer before tagging the layer as stale. // it's impossible to mutate the buffer before tagging the layer as stale.
for _, buffer := range []*buffer{dl.buffer, dl.frozen} { for _, buffer := range []*buffer{dl.buffer, dl.frozen} {
if buffer != nil && !buffer.empty() { if buffer != nil {
n, found := buffer.node(owner, path) n, found := buffer.node(owner, path)
if found { if found {
dirtyNodeHitMeter.Mark(1) dirtyNodeHitMeter.Mark(1)
@ -177,7 +178,7 @@ func (dl *diskLayer) account(hash common.Hash, depth int) ([]byte, error) {
// (both the live one and the frozen one). Note the buffer is lock free since // (both the live one and the frozen one). Note the buffer is lock free since
// it's impossible to mutate the buffer before tagging the layer as stale. // it's impossible to mutate the buffer before tagging the layer as stale.
for _, buffer := range []*buffer{dl.buffer, dl.frozen} { for _, buffer := range []*buffer{dl.buffer, dl.frozen} {
if buffer != nil && !buffer.empty() { if buffer != nil {
blob, found := buffer.account(hash) blob, found := buffer.account(hash)
if found { if found {
dirtyStateHitMeter.Mark(1) dirtyStateHitMeter.Mark(1)
@ -255,7 +256,7 @@ func (dl *diskLayer) storage(accountHash, storageHash common.Hash, depth int) ([
// (both the live one and the frozen one). Note the buffer is lock free since // (both the live one and the frozen one). Note the buffer is lock free since
// it's impossible to mutate the buffer before tagging the layer as stale. // it's impossible to mutate the buffer before tagging the layer as stale.
for _, buffer := range []*buffer{dl.buffer, dl.frozen} { for _, buffer := range []*buffer{dl.buffer, dl.frozen} {
if buffer != nil && !buffer.empty() { if buffer != nil {
if blob, found := buffer.storage(accountHash, storageHash); found { if blob, found := buffer.storage(accountHash, storageHash); found {
dirtyStateHitMeter.Mark(1) dirtyStateHitMeter.Mark(1)
dirtyStateReadMeter.Mark(int64(len(blob))) dirtyStateReadMeter.Mark(int64(len(blob)))
@ -403,7 +404,6 @@ func (dl *diskLayer) commit(bottom *diffLayer, force bool) (*diskLayer, error) {
// If the snapshot has been fully generated, unset the generator // If the snapshot has been fully generated, unset the generator
if progress == nil { if progress == nil {
gen = nil
dl.setGenerator(nil) dl.setGenerator(nil)
} else { } else {
log.Info("Paused snapshot generation") log.Info("Paused snapshot generation")
@ -425,18 +425,12 @@ func (dl *diskLayer) commit(bottom *diffLayer, force bool) (*diskLayer, error) {
} }
}) })
// Block until the frozen buffer is fully flushed out if the async flushing // Block until the frozen buffer is fully flushed out if the async flushing
// is not allowed. // is not allowed, or if the oldest history surpasses the persisted state ID.
if dl.db.config.NoAsyncFlush { if dl.db.config.NoAsyncFlush || persistedID < oldest {
if err := dl.frozen.waitFlush(); err != nil {
return nil, err
}
}
// Block until the frozen buffer is fully flushed out if the oldest history
// surpasses the persisted state ID.
if persistedID < oldest {
if err := dl.frozen.waitFlush(); err != nil { if err := dl.frozen.waitFlush(); err != nil {
return nil, err return nil, err
} }
dl.frozen = nil
} }
combined = newBuffer(dl.db.config.WriteBufferSize, nil, nil, 0) combined = newBuffer(dl.db.config.WriteBufferSize, nil, nil, 0)
} }
@ -512,7 +506,9 @@ func (dl *diskLayer) revert(h *history) (*diskLayer, error) {
dl.frozen = nil dl.frozen = nil
} }
// Terminate the generation before writing any data into database // Terminate the generator before writing any data to the database.
// This must be done after flushing the frozen buffer, as the generator
// may be restarted at the end of the flush process.
var progress []byte var progress []byte
if dl.generator != nil { if dl.generator != nil {
dl.generator.stop() dl.generator.stop()
@ -576,11 +572,29 @@ func (dl *diskLayer) genMarker() []byte {
return dl.generator.progressMarker() return dl.generator.progressMarker()
} }
// waitFlush blocks until the buffer has been fully flushed and returns any // genComplete returns a flag indicating whether the state snapshot has been
// stored errors that occurred during the process. // fully generated.
func (dl *diskLayer) waitFlush() error { func (dl *diskLayer) genComplete() bool {
if dl.frozen == nil { dl.lock.RLock()
defer dl.lock.RUnlock()
return dl.genMarker() == nil
}
// terminate releases the frozen buffer if it's not nil and terminates the
// background state generator.
func (dl *diskLayer) terminate() error {
dl.lock.Lock()
defer dl.lock.Unlock()
if dl.frozen != nil {
if err := dl.frozen.waitFlush(); err != nil {
return err
}
dl.frozen = nil
}
if dl.generator != nil {
dl.generator.stop()
}
return nil return nil
} }
return dl.frozen.waitFlush()
}

View file

@ -304,13 +304,9 @@ func (db *Database) Journal(root common.Hash) error {
// Block until the background flushing is finished. It must // Block until the background flushing is finished. It must
// be done before terminating the potential background snapshot // be done before terminating the potential background snapshot
// generator. // generator.
if err := disk.waitFlush(); err != nil { if err := disk.terminate(); err != nil {
return err return err
} }
// Terminate the background state generation if it's active
if disk.generator != nil {
disk.generator.stop()
}
start := time.Now() start := time.Now()
// Run the journaling // Run the journaling

View file

@ -195,10 +195,6 @@ func (tree *layerTree) cap(root common.Hash, layers int) error {
} }
tree.base = base tree.base = base
// Block until the frozen buffer is fully flushed
if err := base.waitFlush(); err != nil {
return err
}
// Reset the layer tree with the single new disk layer // Reset the layer tree with the single new disk layer
tree.layers = map[common.Hash]layer{ tree.layers = map[common.Hash]layer{
base.rootHash(): base, base.rootHash(): base,