mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-20 10:52:25 +00:00
trie: background pruner
This commit is contained in:
parent
7d437426af
commit
25a4d2e067
2 changed files with 79 additions and 45 deletions
|
|
@ -539,8 +539,8 @@ func (db *Database) Nodes() []string {
|
|||
// to break genericity here and assume that parent nodes are not owned (account
|
||||
// trie) whereas child nodes may be owned (storage trie or bytecode).
|
||||
func (db *Database) Reference(owner common.Hash, child common.Hash, parent common.Hash) {
|
||||
db.lock.RLock()
|
||||
defer db.lock.RUnlock()
|
||||
db.lock.Lock()
|
||||
defer db.lock.Unlock()
|
||||
|
||||
// If the node does not exist, it's a node pulled from disk, skip
|
||||
childKey := makeNodeKey(owner, child)
|
||||
|
|
@ -566,17 +566,19 @@ func (db *Database) Dereference(root common.Hash, prune bool) error {
|
|||
log.Error("Attempted to dereference the trie cache meta root")
|
||||
return nil
|
||||
}
|
||||
// Obtain the write lock and garbage collect in-memory
|
||||
db.lock.Lock()
|
||||
defer db.lock.Unlock()
|
||||
|
||||
nodes, storage, start := len(db.dirties), db.dirtiesSize, time.Now()
|
||||
prunetime, prunenodes, prunesize := db.prunetime, db.prunenodes, db.prunesize
|
||||
|
||||
pruner := db.newPruner(root)
|
||||
if err := db.dereference(common.Hash{}, root, common.Hash{}, common.Hash{}, prune, nil, pruner); err != nil {
|
||||
return err
|
||||
// Dereference the trie and accumulate prune targets if needed
|
||||
var pruner *pruner
|
||||
if prune {
|
||||
pruner = db.newPruner()
|
||||
}
|
||||
if err := pruner.flush(); err != nil {
|
||||
if err := db.dereference(common.Hash{}, root, common.Hash{}, common.Hash{}, nil, pruner); err != nil {
|
||||
return err
|
||||
}
|
||||
db.gcnodes += uint64(nodes - len(db.dirties))
|
||||
|
|
@ -587,19 +589,31 @@ func (db *Database) Dereference(root common.Hash, prune bool) error {
|
|||
memcacheGCSizeMeter.Mark(int64(storage - db.dirtiesSize))
|
||||
memcacheGCNodesMeter.Mark(int64(nodes - len(db.dirties)))
|
||||
|
||||
memcachePruneTimeTimer.Update(db.prunetime - prunetime)
|
||||
memcachePruneNodesMeter.Mark(int64(db.prunenodes - prunenodes))
|
||||
memcachePruneSizeMeter.Mark(int64(db.prunesize - prunesize))
|
||||
|
||||
log.Debug("Dereferenced trie from memory database", "nodes", nodes-len(db.dirties), "size", storage-db.dirtiesSize, "time", common.PrettyDuration(time.Since(start)),
|
||||
"gcnodes", db.gcnodes, "gcsize", db.gcsize, "gctime", common.PrettyDuration(db.gctime), "prnodes", db.prunenodes, "prsize", db.prunesize, "prtime", common.PrettyDuration(db.prunetime),
|
||||
"livenodes", len(db.dirties), "livesize", db.dirtiesSize)
|
||||
// If pruning was requested, execute on a background thread
|
||||
go func() {
|
||||
db.lock.RLock()
|
||||
defer db.lock.RUnlock()
|
||||
|
||||
if pruner != nil {
|
||||
start := time.Now()
|
||||
pruner.execute()
|
||||
go func() {
|
||||
if err := pruner.flush(); err != nil {
|
||||
log.Crit("Failed to prune database", "err", err)
|
||||
}
|
||||
}()
|
||||
db.prunetime += time.Since(start) // TODO(karalabe): unsafe, stats are off too
|
||||
}
|
||||
// Pruned or not, update the stats and log
|
||||
memcachePruneTimeTimer.Update(db.prunetime - prunetime)
|
||||
memcachePruneNodesMeter.Mark(int64(db.prunenodes - prunenodes))
|
||||
memcachePruneSizeMeter.Mark(int64(db.prunesize - prunesize))
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
// dereference is the private locked version of Dereference.
|
||||
func (db *Database) dereference(childOwner common.Hash, childHash common.Hash, parentOwner common.Hash, parentHash common.Hash, prune bool, path []byte, pruner *pruner) error {
|
||||
func (db *Database) dereference(childOwner common.Hash, childHash common.Hash, parentOwner common.Hash, parentHash common.Hash, path []byte, pruner *pruner) error {
|
||||
// Dereference the parent-child
|
||||
parentKey := makeNodeKey(parentOwner, parentHash)
|
||||
parent := db.dirties[parentKey]
|
||||
|
|
@ -614,10 +628,8 @@ func (db *Database) dereference(childOwner common.Hash, childHash common.Hash, p
|
|||
// If the child does not exist, it's a previously committed node.
|
||||
child, ok := db.dirties[childKey]
|
||||
if !ok {
|
||||
if prune {
|
||||
start := time.Now()
|
||||
pruner.prune(childOwner, childHash, path)
|
||||
db.prunetime += time.Since(start)
|
||||
if pruner != nil {
|
||||
pruner.mark(childOwner, childHash, path)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -644,12 +656,12 @@ func (db *Database) dereference(childOwner common.Hash, childHash common.Hash, p
|
|||
}
|
||||
// Dereference all children and delete the node
|
||||
child.iterateRefs(path, func(path []byte, hash common.Hash) error {
|
||||
db.dereference(childOwner, hash, childOwner, childHash, prune, path, pruner)
|
||||
db.dereference(childOwner, hash, childOwner, childHash, path, pruner)
|
||||
return nil
|
||||
})
|
||||
for key := range child.children {
|
||||
owner, hash := splitNodeKey(key)
|
||||
db.dereference(owner, hash, childOwner, childHash, prune, nil, pruner)
|
||||
db.dereference(owner, hash, childOwner, childHash, nil, pruner)
|
||||
}
|
||||
delete(db.dirties, childKey)
|
||||
db.dirtiesSize -= common.StorageSize(common.HashLength + int(child.size))
|
||||
|
|
|
|||
|
|
@ -33,40 +33,60 @@ import (
|
|||
type pruner struct {
|
||||
db *Database // Trie database for accessing dirty and clean data
|
||||
tries []*traverser // Individual stateful trie traversers for fast liveness checks
|
||||
batch ethdb.Batch // Write batch to minimize database trashing
|
||||
|
||||
marks []*prunerTarget // Nodes marked for potential pruning
|
||||
batch ethdb.Batch // Write batch to minimize database trashing
|
||||
}
|
||||
|
||||
// prunerTarget represents a single marked target for potential pruning.
|
||||
type prunerTarget struct {
|
||||
owner common.Hash // Owner account hash of the node to delete
|
||||
path []byte // Patricia path leading to this node
|
||||
hash common.Hash // Hash of the node to delete
|
||||
}
|
||||
|
||||
// newPruner creates a new trie pruner tied to the liveness of all the currently
|
||||
// referenced in-memory nodes, except the specified one (currently being pruned).
|
||||
func (db *Database) newPruner(skip common.Hash) *pruner {
|
||||
// Create the set of traversers based on the live tries
|
||||
var traversers []*traverser
|
||||
for key := range db.dirties[metaRoot].children {
|
||||
if _, root := splitNodeKey(key); root != skip {
|
||||
traversers = append(traversers, &traverser{
|
||||
db: db,
|
||||
state: &traverserState{
|
||||
node: hashNode(root[:]),
|
||||
hash: root,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
// Assemble and return the pruner
|
||||
// referenced in-memory nodes.
|
||||
func (db *Database) newPruner() *pruner {
|
||||
return &pruner{
|
||||
db: db,
|
||||
tries: traversers,
|
||||
batch: db.diskdb.NewBatch(),
|
||||
}
|
||||
}
|
||||
|
||||
// flush commits any pending database writes.
|
||||
func (p *pruner) flush() error {
|
||||
if err := p.batch.Write(); err != nil {
|
||||
return err
|
||||
// mark adds a new prune target to be deleted on the pruning run.
|
||||
func (p *pruner) mark(owner common.Hash, hash common.Hash, path []byte) {
|
||||
p.marks = append(p.marks, &prunerTarget{
|
||||
owner: owner,
|
||||
hash: hash,
|
||||
path: common.CopyBytes(path),
|
||||
})
|
||||
}
|
||||
|
||||
// execute runs the pruning procedure, deleting everything that has no live
|
||||
// reference any more.
|
||||
func (p *pruner) execute() {
|
||||
// Create the set of traversers based on the live tries
|
||||
for key := range p.db.dirties[metaRoot].children {
|
||||
_, root := splitNodeKey(key)
|
||||
p.tries = append(p.tries, &traverser{
|
||||
db: p.db,
|
||||
state: &traverserState{
|
||||
node: hashNode(root[:]),
|
||||
hash: root,
|
||||
},
|
||||
})
|
||||
}
|
||||
p.batch.Reset()
|
||||
return nil
|
||||
// Iterate over all the nodes marked for pruning and delete them
|
||||
for _, mark := range p.marks {
|
||||
p.prune(mark.owner, mark.hash, mark.path)
|
||||
}
|
||||
}
|
||||
|
||||
// flush commits any pending database writes. It does not reset the batch since
|
||||
// we only ever supposed to commit once per prune run.
|
||||
func (p *pruner) flush() error {
|
||||
return p.batch.Write()
|
||||
}
|
||||
|
||||
// prune deletes a trie node from disk if there are no more live references to
|
||||
|
|
@ -186,7 +206,9 @@ func (t *traverser) live(owner common.Hash, hash common.Hash, path []byte, unref
|
|||
} else {
|
||||
blob, err := t.db.diskdb.Get([]byte(key))
|
||||
if blob == nil || err != nil {
|
||||
panic(fmt.Sprintf("missing referenced node %x (searching for %x:%x at %x%x)", key, owner, t.state.hash, t.state.prefix, path))
|
||||
log.Error("Missing referenced node", "owner", owner, "hash", t.state.hash, "path", fmt.Sprintf("%x%x", t.state.prefix, path))
|
||||
return false
|
||||
//panic(fmt.Sprintf("missing referenced node %x (searching for %x:%x at %x%x)", key, owner, t.state.hash, t.state.prefix, path))
|
||||
}
|
||||
t.state.node = mustDecodeNode(t.state.hash[:], blob, 0)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue