Skip to content

Commit

Permalink
Merge pull request #15 from will-2012/perf-trienode-read-new
Browse files Browse the repository at this point in the history
chore: updated by review tips
  • Loading branch information
will-2012 authored Jun 12, 2024
2 parents aec5cfc + 625f31a commit 6b3c713
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 49 deletions.
4 changes: 4 additions & 0 deletions eth/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,10 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
// Optimize memory distribution by reallocating surplus allowance from the
// dirty cache to the clean cache.
if config.StateScheme == rawdb.PathScheme && config.TrieDirtyCache > pathdb.MaxDirtyBufferSize/1024/1024 {
log.Info("Capped dirty cache size", "provided", common.StorageSize(config.TrieDirtyCache)*1024*1024,
"adjusted", common.StorageSize(pathdb.MaxDirtyBufferSize))
log.Info("Clean cache size", "provided", common.StorageSize(config.TrieCleanCache)*1024*1024,
"adjusted", common.StorageSize(config.TrieCleanCache+config.TrieDirtyCache-pathdb.MaxDirtyBufferSize/1024/1024)*1024*1024)
config.TrieCleanCache += config.TrieDirtyCache - pathdb.MaxDirtyBufferSize/1024/1024
config.TrieDirtyCache = pathdb.MaxDirtyBufferSize / 1024 / 1024
}
Expand Down
3 changes: 1 addition & 2 deletions triedb/pathdb/asyncnodebuffer.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,8 +226,7 @@ func (nc *nodecache) node(owner common.Hash, path []byte, hash common.Hash) (*tr
}
if n.Hash != hash {
dirtyFalseMeter.Mark(1)
// difflayer cache miss maybe hit this, which is normal case, and the caller can retry it.
log.Debug("Unexpected trie node in async node buffer", "owner", owner, "path", path, "expect", hash, "got", n.Hash)
log.Error("Unexpected trie node in async node buffer", "owner", owner, "path", path, "expect", hash, "got", n.Hash)
return nil, newUnexpectedNodeError("dirty", hash, n.Hash, owner, path, n.Blob)
}
return n, nil
Expand Down
104 changes: 70 additions & 34 deletions triedb/pathdb/difflayer.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,17 @@ import (
"github.com/ethereum/go-ethereum/trie/triestate"
)

type HashIndex struct {
type RefTrieNode struct {
refCount uint32
node *trienode.Node
}

type HashNodeCache struct {
lock sync.RWMutex
cache map[common.Hash]*trienode.Node
cache map[common.Hash]*RefTrieNode
}

func (h *HashIndex) length() int {
func (h *HashNodeCache) length() int {
if h == nil {
return 0
}
Expand All @@ -40,54 +45,68 @@ func (h *HashIndex) length() int {
return len(h.cache)
}

func (h *HashIndex) set(hash common.Hash, node *trienode.Node) {
func (h *HashNodeCache) set(hash common.Hash, node *trienode.Node) {
if h == nil {
return
}
h.lock.Lock()
defer h.lock.Unlock()
h.cache[hash] = node
if n, ok := h.cache[hash]; ok {
n.refCount++
} else {
h.cache[hash] = &RefTrieNode{1, node}
}
}

func (h *HashIndex) Get(hash common.Hash) *trienode.Node {
func (h *HashNodeCache) Get(hash common.Hash) *trienode.Node {
if h == nil {
return nil
}
h.lock.RLock()
defer h.lock.RUnlock()
if n, ok := h.cache[hash]; ok {
return n
return n.node
}
return nil
}

func (h *HashIndex) del(hash common.Hash) {
func (h *HashNodeCache) del(hash common.Hash) {
if h == nil {
return
}
h.lock.Lock()
defer h.lock.Unlock()
delete(h.cache, hash)
n, ok := h.cache[hash]
if !ok {
return
}
if n.refCount > 0 {
n.refCount--
}
if n.refCount == 0 {
delete(h.cache, hash)
}
}

func (h *HashIndex) Add(ly layer) {
func (h *HashNodeCache) Add(ly layer) {
if h == nil {
return
}
dl, ok := ly.(*diffLayer)
if !ok {
return
}
beforeAdd := h.length()
for _, subset := range dl.nodes {
for _, node := range subset {
h.set(node.Hash, node)
}
}
diffHashCacheLengthGauge.Update(int64(h.length()))
log.Debug("Add difflayer to hash map", "root", ly.rootHash(), "map_len", h.length())
log.Debug("Add difflayer to hash map", "root", ly.rootHash(), "block_number", dl.block, "map_len", h.length(), "add_delta", h.length()-beforeAdd)
}

func (h *HashIndex) Remove(ly layer) {
func (h *HashNodeCache) Remove(ly layer) {
if h == nil {
return
}
Expand All @@ -96,13 +115,14 @@ func (h *HashIndex) Remove(ly layer) {
return
}
go func() {
beforeDel := h.length()
for _, subset := range dl.nodes {
for _, node := range subset {
h.del(node.Hash)
}
}
diffHashCacheLengthGauge.Update(int64(h.length()))
log.Debug("Remove difflayer from hash map", "root", ly.rootHash(), "map_len", h.length())
log.Debug("Remove difflayer from hash map", "root", ly.rootHash(), "block_number", dl.block, "map_len", h.length(), "del_delta", beforeDel-h.length())
}()
}

Expand All @@ -119,8 +139,10 @@ type diffLayer struct {
nodes map[common.Hash]map[string]*trienode.Node // Cached trie nodes indexed by owner and path
states *triestate.Set // Associated state change set for building history
memory uint64 // Approximate guess as to how much memory we use
cache *HashIndex // trienode cache by hash key.
cache *HashNodeCache // trienode cache by hash key. cache is immutable, but cache's item can be add/del.

// mutables
origin *diskLayer // The current difflayer corresponds to the underlying disklayer and is updated during cap.
parent layer // Parent layer modified by this one, never nil, **can be changed**
lock sync.RWMutex // Lock used to protect parent
}
Expand All @@ -139,13 +161,20 @@ func newDiffLayer(parent layer, root common.Hash, id uint64, block uint64, nodes
states: states,
parent: parent,
}
if pdl, ok := parent.(*diffLayer); ok && pdl.cache != nil {
dl.cache = pdl.cache
} else {
dl.cache = &HashIndex{
cache: make(map[common.Hash]*trienode.Node),

switch l := parent.(type) {
case *diskLayer:
dl.origin = l
dl.cache = &HashNodeCache{
cache: make(map[common.Hash]*RefTrieNode),
}
case *diffLayer:
dl.origin = l.originDiskLayer()
dl.cache = l.cache
default:
panic("unknown parent type")
}

for _, subset := range nodes {
for path, n := range subset {
dl.memory += uint64(n.Size() + len(path))
Expand All @@ -163,6 +192,12 @@ func newDiffLayer(parent layer, root common.Hash, id uint64, block uint64, nodes
return dl
}

func (dl *diffLayer) originDiskLayer() *diskLayer {
dl.lock.RLock()
defer dl.lock.RUnlock()
return dl.origin
}

// rootHash implements the layer interface, returning the root hash of
// corresponding state.
func (dl *diffLayer) rootHash() common.Hash {
Expand Down Expand Up @@ -230,23 +265,24 @@ func (dl *diffLayer) Node(owner common.Hash, path []byte, hash common.Hash) ([]b
}
diffHashCacheMissMeter.Mark(1)

parent := dl.parent
for {
if disk, ok := parent.(*diskLayer); ok {
blob, err := disk.NodeByLogger(owner, path, hash, log.Debug)
if err != nil {
// This is a bad case with a very low probability. The same trienode exists
// in different difflayers and can be cleared from the map in advance. In
// this case, the 128-layer difflayer is queried again.
diffHashCacheSlowPathMeter.Mark(1)
log.Debug("Hash map and disklayer mismatch, retry difflayer", "owner", owner, "path", path, "hash", hash.String())
return dl.node(owner, path, hash, 0)
} else {
return blob, nil
}
persistLayer := dl.originDiskLayer()
if persistLayer != nil {
blob, err := persistLayer.Node(owner, path, hash)
if err != nil {
// This is a bad case with a very low probability.
// r/w the difflayer cache and r/w the disklayer are not in the same lock,
// so in extreme cases, both reading the difflayer cache and reading the disklayer may fail, eg, disklayer is stale.
// In this case, fallback to the original 128-layer recursive difflayer query path.
diffHashCacheSlowPathMeter.Mark(1)
log.Debug("Retry difflayer due to query origin failed", "owner", owner, "path", path, "hash", hash.String(), "error", err)
return dl.node(owner, path, hash, 0)
} else { // This is the fastpath.
return blob, nil
}
parent = parent.parentLayer()
}
diffHashCacheSlowPathMeter.Mark(1)
log.Debug("Retry difflayer due to origin is nil", "owner", owner, "path", path, "hash", hash.String())
return dl.node(owner, path, hash, 0)
}

// update implements the layer interface, creating a new layer on top of the
Expand Down
18 changes: 6 additions & 12 deletions triedb/pathdb/disklayer.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,9 +150,9 @@ func (dl *diskLayer) markStale() {
dl.stale = true
}

type loggerFunc func(string, ...interface{})

func (dl *diskLayer) NodeByLogger(owner common.Hash, path []byte, hash common.Hash, logger loggerFunc) ([]byte, error) {
// Node implements the layer interface, retrieving the trie node with the
// provided node info. No error will be returned if the node is not found.
func (dl *diskLayer) Node(owner common.Hash, path []byte, hash common.Hash) ([]byte, error) {
dl.lock.RLock()
defer dl.lock.RUnlock()

Expand All @@ -165,7 +165,7 @@ func (dl *diskLayer) NodeByLogger(owner common.Hash, path []byte, hash common.Ha
// layer as stale.
n, err := dl.buffer.node(owner, path, hash)
if err != nil {
logger("Unexpected trie node in clean cache", "error", err)
log.Error("Unexpected trie node in clean cache", "error", err)
return nil, err
}
if n != nil {
Expand All @@ -189,7 +189,7 @@ func (dl *diskLayer) NodeByLogger(owner common.Hash, path []byte, hash common.Ha
return blob, nil
}
cleanFalseMeter.Mark(1)
logger("Unexpected trie node in clean cache", "owner", owner, "path", path, "expect", hash, "got", got)
log.Error("Unexpected trie node in clean cache", "owner", owner, "path", path, "expect", hash, "got", got)
}
cleanMissMeter.Mark(1)
}
Expand All @@ -205,7 +205,7 @@ func (dl *diskLayer) NodeByLogger(owner common.Hash, path []byte, hash common.Ha
}
if nHash != hash {
diskFalseMeter.Mark(1)
logger("Unexpected trie node in disk", "owner", owner, "path", path, "expect", hash, "got", nHash)
log.Error("Unexpected trie node in disk", "owner", owner, "path", path, "expect", hash, "got", nHash)
return nil, newUnexpectedNodeError("disk", hash, nHash, owner, path, nBlob)
}
if dl.cleans != nil && len(nBlob) > 0 {
Expand All @@ -215,12 +215,6 @@ func (dl *diskLayer) NodeByLogger(owner common.Hash, path []byte, hash common.Ha
return nBlob, nil
}

// Node implements the layer interface, retrieving the trie node with the
// provided node info. No error will be returned if the node is not found.
func (dl *diskLayer) Node(owner common.Hash, path []byte, hash common.Hash) ([]byte, error) {
return dl.NodeByLogger(owner, path, hash, log.Error)
}

// update implements the layer interface, returning a new diff layer on top
// with the given state set.
func (dl *diskLayer) update(root common.Hash, id uint64, block uint64, nodes map[common.Hash]map[string]*trienode.Node, states *triestate.Set) *diffLayer {
Expand Down
44 changes: 43 additions & 1 deletion triedb/pathdb/layertree.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,20 @@ func (tree *layerTree) reset(head layer) {
tree.lock.Lock()
defer tree.lock.Unlock()

for _, ly := range tree.layers {
if dl, ok := ly.(*diffLayer); ok {
// Clean up the hash cache of difflayers due to reset.
dl.cache.Remove(dl)
}
}

var layers = make(map[common.Hash]layer)
for head != nil {
layers[head.rootHash()] = head
if dl, ok := head.(*diffLayer); ok {
// Add the hash cache of difflayers due to reset.
dl.cache.Add(dl)
}
head = head.parentLayer()
}
tree.layers = layers
Expand Down Expand Up @@ -98,6 +109,10 @@ func (tree *layerTree) add(root common.Hash, parentRoot common.Hash, block uint6
if root == parentRoot {
return errors.New("layer cycle")
}
if tree.get(root) != nil {
log.Info("Skip add repeated difflayer", "root", root.String(), "block_id", block)
return nil
}
parent := tree.get(parentRoot)
if parent == nil {
return fmt.Errorf("triedb parent [%#x] layer missing", parentRoot)
Expand Down Expand Up @@ -135,8 +150,15 @@ func (tree *layerTree) cap(root common.Hash, layers int) error {
if err != nil {
return err
}
for _, ly := range tree.layers {
if dl, ok := ly.(*diffLayer); ok {
dl.cache.Remove(dl)
log.Debug("Cleanup difflayer hash cache due to cap all", "diff_root", dl.root.String(), "diff_block_number", dl.block)
}
}
// Replace the entire layer tree with the flat base
tree.layers = map[common.Hash]layer{base.rootHash(): base}
log.Debug("Cap all difflayers to disklayer", "disk_root", base.rootHash().String())
return nil
}
// Dive until we run out of layers or reach the persistent database
Expand All @@ -149,6 +171,7 @@ func (tree *layerTree) cap(root common.Hash, layers int) error {
return nil
}
}
var persisted *diskLayer
// We're out of layers, flatten anything below, stopping if it's the disk or if
// the memory limit is not yet exceeded.
switch parent := diff.parentLayer().(type) {
Expand All @@ -169,6 +192,7 @@ func (tree *layerTree) cap(root common.Hash, layers int) error {
diff.parent = base

diff.lock.Unlock()
persisted = base.(*diskLayer)

default:
panic(fmt.Sprintf("unknown data layer in triedb: %T", parent))
Expand All @@ -185,8 +209,9 @@ func (tree *layerTree) cap(root common.Hash, layers int) error {
remove = func(root common.Hash) {
if df, exist := tree.layers[root]; exist {
if dl, ok := df.(*diffLayer); ok {
// Clean up the hash cache of the child difflayer corresponding to the stale parent.
// Clean up the hash cache of the child difflayer corresponding to the stale parent, include the re-org case.
dl.cache.Remove(dl)
log.Debug("Cleanup difflayer hash cache due to reorg", "diff_root", dl.root.String(), "diff_block_number", dl.block)
}
}
delete(tree.layers, root)
Expand All @@ -198,8 +223,25 @@ func (tree *layerTree) cap(root common.Hash, layers int) error {
for root, layer := range tree.layers {
if dl, ok := layer.(*diskLayer); ok && dl.isStale() {
remove(root)
log.Debug("Remove stale the disklayer", "disk_root", dl.root.String())
}
}

if persisted != nil {
var updateOriginFunc func(root common.Hash)
updateOriginFunc = func(root common.Hash) {
if diff, ok := tree.layers[root].(*diffLayer); ok {
diff.lock.Lock()
diff.origin = persisted
diff.lock.Unlock()
}
for _, child := range children[root] {
updateOriginFunc(child)
}
}
updateOriginFunc(persisted.root)
}

return nil
}

Expand Down

0 comments on commit 6b3c713

Please sign in to comment.