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
|
// to break genericity here and assume that parent nodes are not owned (account
|
||||||
// trie) whereas child nodes may be owned (storage trie or bytecode).
|
// trie) whereas child nodes may be owned (storage trie or bytecode).
|
||||||
func (db *Database) Reference(owner common.Hash, child common.Hash, parent common.Hash) {
|
func (db *Database) Reference(owner common.Hash, child common.Hash, parent common.Hash) {
|
||||||
db.lock.RLock()
|
db.lock.Lock()
|
||||||
defer db.lock.RUnlock()
|
defer db.lock.Unlock()
|
||||||
|
|
||||||
// If the node does not exist, it's a node pulled from disk, skip
|
// If the node does not exist, it's a node pulled from disk, skip
|
||||||
childKey := makeNodeKey(owner, child)
|
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")
|
log.Error("Attempted to dereference the trie cache meta root")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
// Obtain the write lock and garbage collect in-memory
|
||||||
db.lock.Lock()
|
db.lock.Lock()
|
||||||
defer db.lock.Unlock()
|
defer db.lock.Unlock()
|
||||||
|
|
||||||
nodes, storage, start := len(db.dirties), db.dirtiesSize, time.Now()
|
nodes, storage, start := len(db.dirties), db.dirtiesSize, time.Now()
|
||||||
prunetime, prunenodes, prunesize := db.prunetime, db.prunenodes, db.prunesize
|
prunetime, prunenodes, prunesize := db.prunetime, db.prunenodes, db.prunesize
|
||||||
|
|
||||||
pruner := db.newPruner(root)
|
// Dereference the trie and accumulate prune targets if needed
|
||||||
if err := db.dereference(common.Hash{}, root, common.Hash{}, common.Hash{}, prune, nil, pruner); err != nil {
|
var pruner *pruner
|
||||||
return err
|
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
|
return err
|
||||||
}
|
}
|
||||||
db.gcnodes += uint64(nodes - len(db.dirties))
|
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))
|
memcacheGCSizeMeter.Mark(int64(storage - db.dirtiesSize))
|
||||||
memcacheGCNodesMeter.Mark(int64(nodes - len(db.dirties)))
|
memcacheGCNodesMeter.Mark(int64(nodes - len(db.dirties)))
|
||||||
|
|
||||||
|
// 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)
|
memcachePruneTimeTimer.Update(db.prunetime - prunetime)
|
||||||
memcachePruneNodesMeter.Mark(int64(db.prunenodes - prunenodes))
|
memcachePruneNodesMeter.Mark(int64(db.prunenodes - prunenodes))
|
||||||
memcachePruneSizeMeter.Mark(int64(db.prunesize - prunesize))
|
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)
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// dereference is the private locked version of Dereference.
|
// 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
|
// Dereference the parent-child
|
||||||
parentKey := makeNodeKey(parentOwner, parentHash)
|
parentKey := makeNodeKey(parentOwner, parentHash)
|
||||||
parent := db.dirties[parentKey]
|
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.
|
// If the child does not exist, it's a previously committed node.
|
||||||
child, ok := db.dirties[childKey]
|
child, ok := db.dirties[childKey]
|
||||||
if !ok {
|
if !ok {
|
||||||
if prune {
|
if pruner != nil {
|
||||||
start := time.Now()
|
pruner.mark(childOwner, childHash, path)
|
||||||
pruner.prune(childOwner, childHash, path)
|
|
||||||
db.prunetime += time.Since(start)
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -644,12 +656,12 @@ func (db *Database) dereference(childOwner common.Hash, childHash common.Hash, p
|
||||||
}
|
}
|
||||||
// Dereference all children and delete the node
|
// Dereference all children and delete the node
|
||||||
child.iterateRefs(path, func(path []byte, hash common.Hash) error {
|
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
|
return nil
|
||||||
})
|
})
|
||||||
for key := range child.children {
|
for key := range child.children {
|
||||||
owner, hash := splitNodeKey(key)
|
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)
|
delete(db.dirties, childKey)
|
||||||
db.dirtiesSize -= common.StorageSize(common.HashLength + int(child.size))
|
db.dirtiesSize -= common.StorageSize(common.HashLength + int(child.size))
|
||||||
|
|
|
||||||
|
|
@ -33,40 +33,60 @@ import (
|
||||||
type pruner struct {
|
type pruner struct {
|
||||||
db *Database // Trie database for accessing dirty and clean data
|
db *Database // Trie database for accessing dirty and clean data
|
||||||
tries []*traverser // Individual stateful trie traversers for fast liveness checks
|
tries []*traverser // Individual stateful trie traversers for fast liveness checks
|
||||||
|
|
||||||
|
marks []*prunerTarget // Nodes marked for potential pruning
|
||||||
batch ethdb.Batch // Write batch to minimize database trashing
|
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
|
// 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).
|
// referenced in-memory nodes.
|
||||||
func (db *Database) newPruner(skip common.Hash) *pruner {
|
func (db *Database) newPruner() *pruner {
|
||||||
// Create the set of traversers based on the live tries
|
return &pruner{
|
||||||
var traversers []*traverser
|
|
||||||
for key := range db.dirties[metaRoot].children {
|
|
||||||
if _, root := splitNodeKey(key); root != skip {
|
|
||||||
traversers = append(traversers, &traverser{
|
|
||||||
db: db,
|
db: db,
|
||||||
|
batch: db.diskdb.NewBatch(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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{
|
state: &traverserState{
|
||||||
node: hashNode(root[:]),
|
node: hashNode(root[:]),
|
||||||
hash: root,
|
hash: root,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
// Iterate over all the nodes marked for pruning and delete them
|
||||||
// Assemble and return the pruner
|
for _, mark := range p.marks {
|
||||||
return &pruner{
|
p.prune(mark.owner, mark.hash, mark.path)
|
||||||
db: db,
|
|
||||||
tries: traversers,
|
|
||||||
batch: db.diskdb.NewBatch(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// flush commits any pending database writes.
|
// 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 {
|
func (p *pruner) flush() error {
|
||||||
if err := p.batch.Write(); err != nil {
|
return p.batch.Write()
|
||||||
return err
|
|
||||||
}
|
|
||||||
p.batch.Reset()
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// prune deletes a trie node from disk if there are no more live references to
|
// 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 {
|
} else {
|
||||||
blob, err := t.db.diskdb.Get([]byte(key))
|
blob, err := t.db.diskdb.Get([]byte(key))
|
||||||
if blob == nil || err != nil {
|
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)
|
t.state.node = mustDecodeNode(t.state.hash[:], blob, 0)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue