cmd, core/state, eth, trie: implement memcache for the direct cache

This commit is contained in:
Péter Szilágyi 2016-11-02 13:36:13 +02:00
parent f40e3442a1
commit f8be5d9367
No known key found for this signature in database
GPG key ID: 119A76381CCB7DD2
4 changed files with 95 additions and 47 deletions

View file

@ -134,11 +134,13 @@ func importChain(ctx *cli.Context) error {
utils.Fatalf("Failed to read database stats: %v", err) utils.Fatalf("Failed to read database stats: %v", err)
} }
fmt.Println(stats) fmt.Println(stats)
fmt.Printf("Trie cache misses: %d\n", trie.CacheMisses()) fmt.Printf("Trie cache misses: %d\n", trie.CacheMisses())
fmt.Printf("Trie cache unloads: %d\n", trie.CacheUnloads()) fmt.Printf("Trie cache unloads: %d\n", trie.CacheUnloads())
fmt.Printf("Direct cache reads: %d\n", trie.DirectCacheReads()) fmt.Printf("Direct memory cache hits: %d\n", trie.DirectCacheMemcacheHits())
fmt.Printf("Direct cache writes: %d\n", trie.DirectCacheWrites()) fmt.Printf("Direct memory cache misses: %d\n", trie.DirectCacheMemcacheMisses())
fmt.Printf("Direct cache misses: %d\n\n", trie.DirectCacheMisses()) fmt.Printf("Direct database cache reads: %d\n", trie.DirectCacheReads())
fmt.Printf("Direct database cache writes: %d\n", trie.DirectCacheWrites())
fmt.Printf("Direct database cache misses: %d\n\n", trie.DirectCacheMisses())
// Print the memory statistics used by the importing // Print the memory statistics used by the importing
mem := new(runtime.MemStats) mem := new(runtime.MemStats)

View file

@ -46,6 +46,9 @@ const (
// Number of codehash->size associations to keep. // Number of codehash->size associations to keep.
codeSizeCacheSize = 100000 codeSizeCacheSize = 100000
// Number of recent accessed accounts to keep in memory
accountsMemoryCacheSize = 4096
) )
type revision struct { type revision struct {
@ -59,12 +62,13 @@ type revision struct {
// * Contracts // * Contracts
// * Accounts // * Accounts
type StateDB struct { type StateDB struct {
db ethdb.Database db ethdb.Database
trie *trie.Trie trie *trie.Trie
storage *trie.SecureTrie storage *trie.SecureTrie
cacheComplete bool // True if we know that the directcache is complete cacheComplete bool // True if we know that the directcache is complete
pastTries []*trie.Trie pastTries []*trie.Trie
codeSizeCache *lru.Cache codeSizeCache *lru.Cache
accountsMemCache *lru.ARCCache
// This map holds 'live' objects, which will get modified while processing a state transition. // This map holds 'live' objects, which will get modified while processing a state transition.
stateObjects map[common.Address]*StateObject stateObjects map[common.Address]*StateObject
@ -95,10 +99,12 @@ func New(root common.Hash, db ethdb.Database) (*StateDB, error) {
} }
csc, _ := lru.New(codeSizeCacheSize) csc, _ := lru.New(codeSizeCacheSize)
amc, _ := lru.NewARC(accountsMemoryCacheSize)
ret := &StateDB{ ret := &StateDB{
db: db, db: db,
trie: tr, trie: tr,
codeSizeCache: csc, codeSizeCache: csc,
accountsMemCache: amc,
stateObjects: make(map[common.Address]*StateObject), stateObjects: make(map[common.Address]*StateObject),
stateObjectsDirty: make(map[common.Address]struct{}), stateObjectsDirty: make(map[common.Address]struct{}),
refund: new(big.Int), refund: new(big.Int),
@ -122,6 +128,7 @@ func (self *StateDB) New(root common.Hash) (*StateDB, error) {
db: self.db, db: self.db,
trie: tr, trie: tr,
codeSizeCache: self.codeSizeCache, codeSizeCache: self.codeSizeCache,
accountsMemCache: self.accountsMemCache,
stateObjects: make(map[common.Address]*StateObject), stateObjects: make(map[common.Address]*StateObject),
stateObjectsDirty: make(map[common.Address]struct{}), stateObjectsDirty: make(map[common.Address]struct{}),
refund: new(big.Int), refund: new(big.Int),
@ -193,7 +200,7 @@ func (self *StateDB) SetBlockContext(blockHash common.Hash, blockNum uint64, val
} }
} }
storage := trie.NewDirectCache(self.trie, self.db, DirectCachePrefix, blockNum, blockHash, validator, self.cacheComplete) storage := trie.NewDirectCache(self.trie, self.db, self.accountsMemCache, DirectCachePrefix, blockNum, blockHash, validator, self.cacheComplete)
self.storage = trie.NewSecure(storage, self.db) self.storage = trie.NewSecure(storage, self.db)
} }

View file

@ -371,7 +371,7 @@ func populateDirectCache(db ethdb.Database, bc *core.BlockChain) error {
if err != nil { if err != nil {
return err return err
} }
dc := trie.NewDirectCache(tr, db, state.DirectCachePrefix, blockNum, blockHash, bc, false) dc := trie.NewDirectCache(tr, db, nil, state.DirectCachePrefix, blockNum, blockHash, bc, false)
go func() { go func() {
for core.GetBlockNumber(db, core.GetHeadBlockHash(db)) < blockNum+8 { for core.GetBlockNumber(db, core.GetHeadBlockHash(db)) < blockNum+8 {
time.Sleep(10 * time.Second) time.Sleep(10 * time.Second)

View file

@ -17,27 +17,31 @@
package trie package trie
import ( import (
"fmt"
"errors" "errors"
"time" "fmt"
"sync" "sync"
"time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
lru "github.com/hashicorp/golang-lru"
) )
var ( var (
directCacheLock = &sync.Mutex{} directCacheLock = &sync.Mutex{}
directCacheLocked = false directCacheLocked = false
directCacheWrites = metrics.NewCounter("directcache/writes") directCacheWrites = metrics.NewCounter("directcache/writes")
directCacheHits = metrics.NewCounter("directcache/hits") directCacheHits = metrics.NewCounter("directcache/hits")
directCacheMisses = metrics.NewCounter("directcache/misses") directCacheMisses = metrics.NewCounter("directcache/misses")
directCacheTimer = metrics.NewTimer("directcache/timer") directCacheMemHits = metrics.NewCounter("directcache/memcache/hits")
NotFound = errors.New("Cache entry not found") directCacheMemMisses = metrics.NewCounter("directcache/memcache/misses")
MigrationPrefix = []byte("directstatecachemigration:") directCacheTimer = metrics.NewTimer("directcache/timer")
NotFound = errors.New("Cache entry not found")
MigrationPrefix = []byte("directstatecachemigration:")
) )
type MigrationStatus int type MigrationStatus int
@ -54,8 +58,10 @@ type CacheValidator interface {
} }
type DirectCache struct { type DirectCache struct {
data PersistentMap data PersistentMap
db Database db Database
memcache *lru.ARCCache
keyPrefix []byte keyPrefix []byte
blockNum uint64 blockNum uint64
blockHash common.Hash blockHash common.Hash
@ -70,10 +76,14 @@ func (cv *NullCacheValidator) IsCanonChainBlock(num uint64, hash common.Hash) bo
return false return false
} }
func NewDirectCache(pm PersistentMap, db Database, keyPrefix []byte, blockNum uint64, blockHash common.Hash, validator CacheValidator, complete bool) *DirectCache { func NewDirectCache(pm PersistentMap, db Database, memcache *lru.ARCCache, keyPrefix []byte, blockNum uint64, blockHash common.Hash, validator CacheValidator, complete bool) *DirectCache {
if memcache == nil {
memcache, _ = lru.NewARC(0)
}
return &DirectCache{ return &DirectCache{
data: pm, data: pm,
db: db, db: db,
memcache: memcache,
keyPrefix: keyPrefix, keyPrefix: keyPrefix,
blockNum: blockNum, blockNum: blockNum,
blockHash: blockHash, blockHash: blockHash,
@ -125,17 +135,28 @@ func (dc *DirectCache) TryGet(key []byte) ([]byte, error) {
} }
func (dc *DirectCache) getCached(key []byte) ([]byte, bool) { func (dc *DirectCache) getCached(key []byte) ([]byte, bool) {
data, blockNum, blockHash, err := GetDirectCache(dc.keyPrefix, key, dc.db) // Try to retrieve the object from the memcache
if err != nil { var entry *cachedValue
if err == NotFound { if cached, ok := dc.memcache.Get(string(key)); ok {
return nil, dc.complete directCacheMemHits.Inc(1)
} entry = cached.(*cachedValue)
glog.Errorf("Error retrieving direct cache data: %v", err)
return nil, false
} }
// If the memcache missed, reach down to the database
var err error
if entry == nil {
directCacheMemMisses.Inc(1)
canonical := dc.blockNum > 0 && blockNum < dc.blockNum && dc.validator.IsCanonChainBlock(blockNum, blockHash) if entry, err = getDirectCache(dc.keyPrefix, key, dc.db); err != nil {
return data, canonical if err == NotFound {
return nil, dc.complete
}
glog.Errorf("Error retrieving direct cache data: %v", err)
return nil, false
}
dc.memcache.Add(string(key), entry)
}
canonical := dc.blockNum > 0 && entry.BlockNum < dc.blockNum && dc.validator.IsCanonChainBlock(entry.BlockNum, entry.BlockHash)
return entry.Value, canonical
} }
func (dc *DirectCache) Update(key, value []byte) { func (dc *DirectCache) Update(key, value []byte) {
@ -170,6 +191,7 @@ func (dc *DirectCache) CommitTo(dbw DatabaseWriter) (root common.Hash, err error
if err := WriteDirectCache(dc.keyPrefix, []byte(k), v, dc.blockNum, dc.blockHash, dbw); err != nil { if err := WriteDirectCache(dc.keyPrefix, []byte(k), v, dc.blockNum, dc.blockHash, dbw); err != nil {
return err return err
} }
dc.memcache.Add(k, &cachedValue{v, dc.blockNum, dc.blockHash})
} }
return nil return nil
}); err != nil { }); err != nil {
@ -201,7 +223,7 @@ func (dc *DirectCache) Populate() (err error) {
} }
i += 1 i += 1
if i % 10000 == 0 && glog.V(logger.Info) { if i%10000 == 0 && glog.V(logger.Info) {
glog.V(logger.Info).Infof("Constructing direct cache: processed %v entries, written %v", i, writes) glog.V(logger.Info).Infof("Constructing direct cache: processed %v entries, written %v", i, writes)
} }
} }
@ -214,7 +236,7 @@ type cachedValue struct {
BlockHash common.Hash BlockHash common.Hash
} }
func DirectCacheTransaction(tx func() (error)) error { func DirectCacheTransaction(tx func() error) error {
directCacheLock.Lock() directCacheLock.Lock()
directCacheLocked = true directCacheLocked = true
defer func() { defer func() {
@ -241,22 +263,31 @@ func WriteDirectCache(prefix, key, value []byte, number uint64, hash common.Hash
return dbw.Put(append(prefix, key...), enc) return dbw.Put(append(prefix, key...), enc)
} }
// getDirectCache retrieves a value node directly from the database along with
// block metadata to validate its relevancy.
func getDirectCache(prefix, key []byte, db Database) (*cachedValue, error) {
defer func(start time.Time) { directCacheTimer.UpdateSince(start) }(time.Now())
enc, _ := db.Get(append(prefix, key...))
if len(enc) == 0 {
return nil, NotFound
}
data := new(cachedValue)
if err := rlp.DecodeBytes(enc, data); err != nil {
return nil, fmt.Errorf("Can't decode cached object at %x: %v", key, err)
}
return data, nil
}
// GetDirectCache retrieves a value node directly from the database along with // GetDirectCache retrieves a value node directly from the database along with
// block metadata to validate its relevancy. // block metadata to validate its relevancy.
// //
// The method is meant to be used by code that circumvents the state database // The method is meant to be used by code that circumvents the state database
// and its integrated cache, namely during fast sync and database upgrades. // and its integrated cache, namely during fast sync and database upgrades.
func GetDirectCache(prefix, key []byte, db Database) ([]byte, uint64, common.Hash, error) { func GetDirectCache(prefix, key []byte, db Database) ([]byte, uint64, common.Hash, error) {
defer func(start time.Time) { directCacheTimer.UpdateSince(start) }(time.Now()) data, err := getDirectCache(prefix, key, db)
if err != nil {
enc, _ := db.Get(append(prefix, key...)) return nil, 0, common.Hash{}, err
if len(enc) == 0 {
return nil, 0, common.Hash{}, NotFound
}
var data cachedValue
if err := rlp.DecodeBytes(enc, &data); err != nil {
return nil, 0, common.Hash{}, fmt.Errorf("Can't decode cached object at %x: %v", key, err)
} }
return data.Value, data.BlockNum, data.BlockHash, nil return data.Value, data.BlockNum, data.BlockHash, nil
} }
@ -317,3 +348,11 @@ func DirectCacheWrites() int64 {
func DirectCacheMisses() int64 { func DirectCacheMisses() int64 {
return directCacheMisses.Count() return directCacheMisses.Count()
} }
func DirectCacheMemcacheHits() int64 {
return directCacheMemHits.Count()
}
func DirectCacheMemcacheMisses() int64 {
return directCacheMemMisses.Count()
}