Skip to content
This repository has been archived by the owner on Aug 19, 2024. It is now read-only.

Optimize traceBlock with fastCache/Persistent DB #546

Merged
merged 5 commits into from
Jun 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion blockchain/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ func NewBlockChain(db database.DBManager, cacheConfig *CacheConfig, chainConfig
db: db,
triegc: prque.New(),
chBlock: make(chan gcBlock, 1000),
stateCache: state.NewDatabaseWithCache(db, cacheConfig.TrieCacheLimit),
stateCache: state.NewDatabaseWithNewCache(db, cacheConfig.TrieCacheLimit),
quit: make(chan struct{}),
futureBlocks: futureBlocks,
engine: engine,
Expand Down
32 changes: 23 additions & 9 deletions blockchain/state/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ package state

import (
"fmt"
"github.com/VictoriaMetrics/fastcache"
"github.com/klaytn/klaytn/common"
"github.com/klaytn/klaytn/storage/database"
"github.com/klaytn/klaytn/storage/statedb"
Expand Down Expand Up @@ -103,15 +104,12 @@ type Trie interface {

// NewDatabase creates a backing store for state. The returned database is safe for
// concurrent use, but does not retain any recent trie nodes in memory. To keep some
// historical state in memory, use the NewDatabaseWithCache constructor.
// historical state in memory, use the NewDatabaseWithNewCache constructor.
func NewDatabase(db database.DBManager) Database {
return NewDatabaseWithCache(db, 0)
return NewDatabaseWithNewCache(db, 0)
}

// NewDatabaseWithCache creates a backing store for state. The returned database
// is safe for concurrent use and retains a lot of collapsed RLP trie nodes in a
// large memory cache.
func NewDatabaseWithCache(db database.DBManager, cacheSize int) Database {
func getCodeSizeCache() common.Cache {
var cacheConfig common.CacheConfiger
switch common.DefaultCacheType {
case common.LRUShardCacheType:
Expand All @@ -123,11 +121,27 @@ func NewDatabaseWithCache(db database.DBManager, cacheSize int) Database {
default:
cacheConfig = common.FIFOCacheConfig{CacheSize: codeSizeCacheSize}
}
csc := common.NewCache(cacheConfig)

return common.NewCache(cacheConfig)
}

// NewDatabaseWithNewCache creates a backing store for state. The returned database
// is safe for concurrent use and retains a lot of collapsed RLP trie nodes in a
// large memory cache.
func NewDatabaseWithNewCache(db database.DBManager, cacheSize int) Database {
return &cachingDB{
db: statedb.NewDatabaseWithNewCache(db, cacheSize),
codeSizeCache: getCodeSizeCache(),
}
}

// NewDatabaseWithCache creates a backing store for state with given cache. The returned database
// is safe for concurrent use and retains a lot of collapsed RLP trie nodes in a
// large memory cache.
func NewDatabaseWithCache(db database.DBManager, cache *fastcache.Cache) Database {
return &cachingDB{
db: statedb.NewDatabaseWithCache(db, cacheSize),
codeSizeCache: csc,
db: statedb.NewDatabaseWithCache(db, cache),
codeSizeCache: getCodeSizeCache(),
}
}

Expand Down
37 changes: 23 additions & 14 deletions node/cn/api_tracer.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl

// Ensure we have a valid starting state before doing any work
origin := start.NumberU64()
database := state.NewDatabaseWithCache(api.cn.ChainDB(), 16) // Chain tracing will probably start at genesis
database := state.NewDatabaseWithCache(api.cn.ChainDB(), api.cn.blockchain.StateCache().TrieDB().TrieNodeCache()) // Chain tracing will probably start at genesis
aidan-kwon marked this conversation as resolved.
Show resolved Hide resolved

if number := start.NumberU64(); number > 0 {
start = api.cn.blockchain.GetBlock(start.ParentHash(), start.NumberU64()-1)
Expand Down Expand Up @@ -655,7 +655,7 @@ func (api *PrivateDebugAPI) standardTraceBlockToFile(ctx context.Context, block
func (api *PrivateDebugAPI) computeStateDB(block *types.Block, reexec uint64) (*state.StateDB, error) {
// try to reexec blocks until we find a state or reach our limit
origin := block.NumberU64()
database := state.NewDatabaseWithCache(api.cn.ChainDB(), 16)
database := state.NewDatabaseWithCache(api.cn.ChainDB(), api.cn.blockchain.StateCache().TrieDB().TrieNodeCache())

var statedb *state.StateDB
var err error
Expand Down Expand Up @@ -802,22 +802,31 @@ func (api *PrivateDebugAPI) traceTx(ctx context.Context, message blockchain.Mess
func (api *PrivateDebugAPI) stateAt(block *types.Block, reexec uint64) (*state.StateDB, func(), error) {
var stateDB *state.StateDB

// If we have the state fully available, use that.
// If we have the state fully available in cachedNode, use that.
stateDB, err := api.cn.blockchain.StateAtWithGCLock(block.Root())
if err != nil {
emptyFn := func() {}
if err == nil {
logger.Debug("Get stateDB from stateCache", "block", block.NumberU64())
// During this processing, this lock will prevent to evict the state.
return stateDB, stateDB.UnlockGCCachedNode, nil
}

// If no state is locally available, the desired state will be generated.
stateDB, err = api.computeStateDB(block, reexec)
if err != nil {
return nil, emptyFn, err
}
logger.Debug("Get stateDB by computeStateDB", "block", block.NumberU64())
emptyFn := func() {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about declaring this as a static variable?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean a static variable?
It means to declare a global variable? :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, a package variable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is only used in this function. So I think we don't need to declare it as a package variable. :)


// If we have the state fully available in persistent, use that.
stateDB, err = api.cn.blockchain.StateAt(block.Root())
if err == nil {
logger.Debug("Get stateDB from persistent DB or its cache", "block", block.NumberU64())
return stateDB, emptyFn, nil
}
logger.Debug("Get stateDB from stateCache", "block", block.NumberU64(), "reexec", reexec)
// During this processing, this lock will prevent to evict the state.
return stateDB, stateDB.UnlockGCCachedNode, nil

// If no state is locally available, the desired state will be generated.
stateDB, err = api.computeStateDB(block, reexec)
aidan-kwon marked this conversation as resolved.
Show resolved Hide resolved
if err == nil {
logger.Debug("Get stateDB by computeStateDB", "block", block.NumberU64(), "reexec", reexec)
return stateDB, emptyFn, nil
}

return nil, emptyFn, err
}

// computeTxEnv returns the execution environment of a certain transaction.
Expand Down
23 changes: 20 additions & 3 deletions storage/statedb/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -310,13 +310,13 @@ func expandNode(hash hashNode, n node) node {
// NewDatabase creates a new trie database to store ephemeral trie content before
// its written out to disk or garbage collected.
func NewDatabase(diskDB database.DBManager) *Database {
joowon-byun marked this conversation as resolved.
Show resolved Hide resolved
return NewDatabaseWithCache(diskDB, 0)
return NewDatabaseWithNewCache(diskDB, 0)
}

// NewDatabaseWithCache creates a new trie database to store ephemeral trie content
// NewDatabaseWithNewCache creates a new trie database to store ephemeral trie content
// before its written out to disk or garbage collected. It also acts as a read cache
// for nodes loaded from disk.
func NewDatabaseWithCache(diskDB database.DBManager, cacheSizeMB int) *Database {
func NewDatabaseWithNewCache(diskDB database.DBManager, cacheSizeMB int) *Database {
var trieNodeCache *fastcache.Cache
if cacheSizeMB == AutoScaling {
cacheSizeMB = getTrieNodeCacheSizeMB()
Expand All @@ -335,6 +335,18 @@ func NewDatabaseWithCache(diskDB database.DBManager, cacheSizeMB int) *Database
}
}

// NewDatabaseWithCache creates a new trie database to store ephemeral trie content
// before its written out to disk or garbage collected. It also acts as a read cache
// for nodes loaded from disk.
func NewDatabaseWithCache(diskDB database.DBManager, cache *fastcache.Cache) *Database {
joowon-byun marked this conversation as resolved.
Show resolved Hide resolved
return &Database{
diskDB: diskDB,
nodes: map[common.Hash]*cachedNode{{}: {}},
preimages: make(map[common.Hash][]byte),
trieNodeCache: cache,
}
}

func getTrieNodeCacheSizeMB() int {
totalPhysicalMemMB := float64(memory.TotalMemory() / 1024 / 1024)

Expand All @@ -357,6 +369,11 @@ func (db *Database) DiskDB() database.DBManager {
return db.diskDB
}

// TrieNodeCache retrieves the trieNodeCache of the trie database.
func (db *Database) TrieNodeCache() *fastcache.Cache {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not related to this PR, however, it would be better if returning an interface, not a pointer of struct.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. We don't have an interface for trieNodeCache now.
Let's make a cache interface for trieNodeCache later. :)

return db.trieNodeCache
}

// RLockGCCachedNode locks the GC lock of CachedNode.
func (db *Database) RLockGCCachedNode() {
db.gcLock.RLock()
Expand Down
10 changes: 5 additions & 5 deletions storage/statedb/database_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ var parentHash = common.HexToHash("1343A3F") // 20199999 in hexadecimal

func TestDatabase_Reference(t *testing.T) {
memDB := database.NewMemoryDBManager()
db := NewDatabaseWithCache(memDB, 128)
db := NewDatabaseWithNewCache(memDB, 128)

assert.Equal(t, memDB, db.DiskDB())
assert.Equal(t, 1, len(db.nodes)) // {} : {}
Expand Down Expand Up @@ -57,7 +57,7 @@ func TestDatabase_Reference(t *testing.T) {

func TestDatabase_DeReference(t *testing.T) {
memDB := database.NewMemoryDBManager()
db := NewDatabaseWithCache(memDB, 128)
db := NewDatabaseWithNewCache(memDB, 128)
assert.Equal(t, 1, len(db.nodes)) // {} : {}

db.Dereference(parentHash)
Expand Down Expand Up @@ -87,7 +87,7 @@ func TestDatabase_DeReference(t *testing.T) {

func TestDatabase_Size(t *testing.T) {
memDB := database.NewMemoryDBManager()
db := NewDatabaseWithCache(memDB, 128)
db := NewDatabaseWithNewCache(memDB, 128)

totalMemorySize, preimagesSize := db.Size()
assert.Equal(t, common.StorageSize(0), totalMemorySize)
Expand All @@ -112,7 +112,7 @@ func TestDatabase_Size(t *testing.T) {

func TestDatabase_SecureKey(t *testing.T) {
memDB := database.NewMemoryDBManager()
db := NewDatabaseWithCache(memDB, 128)
db := NewDatabaseWithNewCache(memDB, 128)

secKey1 := db.secureKey(childHash[:])
copiedSecKey := make([]byte, 0, len(secKey1))
Expand All @@ -133,7 +133,7 @@ func makeRandomByte(n int) []byte {
func TestCache(t *testing.T) {
memDB := database.NewMemoryDBManager()
cacheSizeMB := 10
db := NewDatabaseWithCache(memDB, cacheSizeMB)
db := NewDatabaseWithNewCache(memDB, cacheSizeMB)

for i := 0; i < 100; i++ {
key, value := makeRandomByte(256), makeRandomByte(63*1024) // fastcache can store entrie under 64KB
Expand Down
Loading