Skip to content
This repository was archived by the owner on Dec 23, 2024. It is now read-only.

Commit ea2e66a

Browse files
authored
trie/triedb/pathdb: improve dirty node flushing trigger (#28426)
* trie/triedb/pathdb: improve dirty node flushing trigger * trie/triedb/pathdb: add tests * trie/triedb/pathdb: address comment
1 parent 233db64 commit ea2e66a

File tree

3 files changed

+93
-42
lines changed

3 files changed

+93
-42
lines changed

trie/triedb/pathdb/database_test.go

+42-9
Original file line numberDiff line numberDiff line change
@@ -96,11 +96,15 @@ type tester struct {
9696
snapStorages map[common.Hash]map[common.Hash]map[common.Hash][]byte
9797
}
9898

99-
func newTester(t *testing.T) *tester {
99+
func newTester(t *testing.T, historyLimit uint64) *tester {
100100
var (
101101
disk, _ = rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false)
102-
db = New(disk, &Config{CleanCacheSize: 256 * 1024, DirtyCacheSize: 256 * 1024})
103-
obj = &tester{
102+
db = New(disk, &Config{
103+
StateHistory: historyLimit,
104+
CleanCacheSize: 256 * 1024,
105+
DirtyCacheSize: 256 * 1024,
106+
})
107+
obj = &tester{
104108
db: db,
105109
preimages: make(map[common.Hash]common.Address),
106110
accounts: make(map[common.Hash][]byte),
@@ -376,7 +380,7 @@ func (t *tester) bottomIndex() int {
376380

377381
func TestDatabaseRollback(t *testing.T) {
378382
// Verify state histories
379-
tester := newTester(t)
383+
tester := newTester(t, 0)
380384
defer tester.release()
381385

382386
if err := tester.verifyHistory(); err != nil {
@@ -402,7 +406,7 @@ func TestDatabaseRollback(t *testing.T) {
402406

403407
func TestDatabaseRecoverable(t *testing.T) {
404408
var (
405-
tester = newTester(t)
409+
tester = newTester(t, 0)
406410
index = tester.bottomIndex()
407411
)
408412
defer tester.release()
@@ -440,7 +444,7 @@ func TestDatabaseRecoverable(t *testing.T) {
440444
}
441445

442446
func TestDisable(t *testing.T) {
443-
tester := newTester(t)
447+
tester := newTester(t, 0)
444448
defer tester.release()
445449

446450
_, stored := rawdb.ReadAccountTrieNode(tester.db.diskdb, nil)
@@ -476,7 +480,7 @@ func TestDisable(t *testing.T) {
476480
}
477481

478482
func TestCommit(t *testing.T) {
479-
tester := newTester(t)
483+
tester := newTester(t, 0)
480484
defer tester.release()
481485

482486
if err := tester.db.Commit(tester.lastHash(), false); err != nil {
@@ -500,7 +504,7 @@ func TestCommit(t *testing.T) {
500504
}
501505

502506
func TestJournal(t *testing.T) {
503-
tester := newTester(t)
507+
tester := newTester(t, 0)
504508
defer tester.release()
505509

506510
if err := tester.db.Journal(tester.lastHash()); err != nil {
@@ -524,7 +528,7 @@ func TestJournal(t *testing.T) {
524528
}
525529

526530
func TestCorruptedJournal(t *testing.T) {
527-
tester := newTester(t)
531+
tester := newTester(t, 0)
528532
defer tester.release()
529533

530534
if err := tester.db.Journal(tester.lastHash()); err != nil {
@@ -553,6 +557,35 @@ func TestCorruptedJournal(t *testing.T) {
553557
}
554558
}
555559

560+
// TestTailTruncateHistory function is designed to test a specific edge case where,
561+
// when history objects are removed from the end, it should trigger a state flush
562+
// if the ID of the new tail object is even higher than the persisted state ID.
563+
//
564+
// For example, let's say the ID of the persistent state is 10, and the current
565+
// history objects range from ID(5) to ID(15). As we accumulate six more objects,
566+
// the history will expand to cover ID(11) to ID(21). ID(11) then becomes the
567+
// oldest history object, and its ID is even higher than the stored state.
568+
//
569+
// In this scenario, it is mandatory to update the persistent state before
570+
// truncating the tail histories. This ensures that the ID of the persistent state
571+
// always falls within the range of [oldest-history-id, latest-history-id].
572+
func TestTailTruncateHistory(t *testing.T) {
573+
tester := newTester(t, 10)
574+
defer tester.release()
575+
576+
tester.db.Close()
577+
tester.db = New(tester.db.diskdb, &Config{StateHistory: 10})
578+
579+
head, err := tester.db.freezer.Ancients()
580+
if err != nil {
581+
t.Fatalf("Failed to obtain freezer head")
582+
}
583+
stored := rawdb.ReadPersistentStateID(tester.db.diskdb)
584+
if head != stored {
585+
t.Fatalf("Failed to truncate excess history object above, stored: %d, head: %d", stored, head)
586+
}
587+
}
588+
556589
// copyAccounts returns a deep-copied account set of the provided one.
557590
func copyAccounts(set map[common.Hash][]byte) map[common.Hash][]byte {
558591
copied := make(map[common.Hash][]byte, len(set))

trie/triedb/pathdb/disklayer.go

+43-15
Original file line numberDiff line numberDiff line change
@@ -172,37 +172,65 @@ func (dl *diskLayer) commit(bottom *diffLayer, force bool) (*diskLayer, error) {
172172
dl.lock.Lock()
173173
defer dl.lock.Unlock()
174174

175-
// Construct and store the state history first. If crash happens
176-
// after storing the state history but without flushing the
177-
// corresponding states(journal), the stored state history will
178-
// be truncated in the next restart.
175+
// Construct and store the state history first. If crash happens after storing
176+
// the state history but without flushing the corresponding states(journal),
177+
// the stored state history will be truncated from head in the next restart.
178+
var (
179+
overflow bool
180+
oldest uint64
181+
)
179182
if dl.db.freezer != nil {
180-
err := writeHistory(dl.db.diskdb, dl.db.freezer, bottom, dl.db.config.StateHistory)
183+
err := writeHistory(dl.db.freezer, bottom)
181184
if err != nil {
182185
return nil, err
183186
}
187+
// Determine if the persisted history object has exceeded the configured
188+
// limitation, set the overflow as true if so.
189+
tail, err := dl.db.freezer.Tail()
190+
if err != nil {
191+
return nil, err
192+
}
193+
limit := dl.db.config.StateHistory
194+
if limit != 0 && bottom.stateID()-tail > limit {
195+
overflow = true
196+
oldest = bottom.stateID() - limit + 1 // track the id of history **after truncation**
197+
}
184198
}
185199
// Mark the diskLayer as stale before applying any mutations on top.
186200
dl.stale = true
187201

188-
// Store the root->id lookup afterwards. All stored lookups are
189-
// identified by the **unique** state root. It's impossible that
190-
// in the same chain blocks are not adjacent but have the same
191-
// root.
202+
// Store the root->id lookup afterwards. All stored lookups are identified
203+
// by the **unique** state root. It's impossible that in the same chain
204+
// blocks are not adjacent but have the same root.
192205
if dl.id == 0 {
193206
rawdb.WriteStateID(dl.db.diskdb, dl.root, 0)
194207
}
195208
rawdb.WriteStateID(dl.db.diskdb, bottom.rootHash(), bottom.stateID())
196209

197-
// Construct a new disk layer by merging the nodes from the provided
198-
// diff layer, and flush the content in disk layer if there are too
199-
// many nodes cached. The clean cache is inherited from the original
200-
// disk layer for reusing.
210+
// Construct a new disk layer by merging the nodes from the provided diff
211+
// layer, and flush the content in disk layer if there are too many nodes
212+
// cached. The clean cache is inherited from the original disk layer.
201213
ndl := newDiskLayer(bottom.root, bottom.stateID(), dl.db, dl.cleans, dl.buffer.commit(bottom.nodes))
202-
err := ndl.buffer.flush(ndl.db.diskdb, ndl.cleans, ndl.id, force)
203-
if err != nil {
214+
215+
// In a unique scenario where the ID of the oldest history object (after tail
216+
// truncation) surpasses the persisted state ID, we take the necessary action
217+
// of forcibly committing the cached dirty nodes to ensure that the persisted
218+
// state ID remains higher.
219+
if !force && rawdb.ReadPersistentStateID(dl.db.diskdb) < oldest {
220+
force = true
221+
}
222+
if err := ndl.buffer.flush(ndl.db.diskdb, ndl.cleans, ndl.id, force); err != nil {
204223
return nil, err
205224
}
225+
// To remove outdated history objects from the end, we set the 'tail' parameter
226+
// to 'oldest-1' due to the offset between the freezer index and the history ID.
227+
if overflow {
228+
pruned, err := truncateFromTail(ndl.db.diskdb, ndl.db.freezer, oldest-1)
229+
if err != nil {
230+
return nil, err
231+
}
232+
log.Debug("Pruned state history", "items", pruned, "tailid", oldest)
233+
}
206234
return ndl, nil
207235
}
208236

trie/triedb/pathdb/history.go

+8-18
Original file line numberDiff line numberDiff line change
@@ -512,38 +512,28 @@ func readHistory(freezer *rawdb.ResettableFreezer, id uint64) (*history, error)
512512
return &dec, nil
513513
}
514514

515-
// writeHistory writes the state history with provided state set. After
516-
// storing the corresponding state history, it will also prune the stale
517-
// histories from the disk with the given threshold.
518-
func writeHistory(db ethdb.KeyValueStore, freezer *rawdb.ResettableFreezer, dl *diffLayer, limit uint64) error {
515+
// writeHistory persists the state history with the provided state set.
516+
func writeHistory(freezer *rawdb.ResettableFreezer, dl *diffLayer) error {
519517
// Short circuit if state set is not available.
520518
if dl.states == nil {
521519
return errors.New("state change set is not available")
522520
}
523521
var (
524-
err error
525-
n int
526-
start = time.Now()
527-
h = newHistory(dl.rootHash(), dl.parentLayer().rootHash(), dl.block, dl.states)
522+
start = time.Now()
523+
history = newHistory(dl.rootHash(), dl.parentLayer().rootHash(), dl.block, dl.states)
528524
)
529-
accountData, storageData, accountIndex, storageIndex := h.encode()
525+
accountData, storageData, accountIndex, storageIndex := history.encode()
530526
dataSize := common.StorageSize(len(accountData) + len(storageData))
531527
indexSize := common.StorageSize(len(accountIndex) + len(storageIndex))
532528

533529
// Write history data into five freezer table respectively.
534-
rawdb.WriteStateHistory(freezer, dl.stateID(), h.meta.encode(), accountIndex, storageIndex, accountData, storageData)
530+
rawdb.WriteStateHistory(freezer, dl.stateID(), history.meta.encode(), accountIndex, storageIndex, accountData, storageData)
535531

536-
// Prune stale state histories based on the config.
537-
if limit != 0 && dl.stateID() > limit {
538-
n, err = truncateFromTail(db, freezer, dl.stateID()-limit)
539-
if err != nil {
540-
return err
541-
}
542-
}
543532
historyDataBytesMeter.Mark(int64(dataSize))
544533
historyIndexBytesMeter.Mark(int64(indexSize))
545534
historyBuildTimeMeter.UpdateSince(start)
546-
log.Debug("Stored state history", "id", dl.stateID(), "block", dl.block, "data", dataSize, "index", indexSize, "pruned", n, "elapsed", common.PrettyDuration(time.Since(start)))
535+
log.Debug("Stored state history", "id", dl.stateID(), "block", dl.block, "data", dataSize, "index", indexSize, "elapsed", common.PrettyDuration(time.Since(start)))
536+
547537
return nil
548538
}
549539

0 commit comments

Comments
 (0)