storage: attempt to Clone

Allows to regain ~3-4% TPS, doesn't affect chain restore time much.
This commit is contained in:
Roman Khimov 2022-02-03 11:46:09 +03:00
parent 85cc5f4247
commit b224957677
4 changed files with 32 additions and 2 deletions

View file

@ -1116,7 +1116,7 @@ func TestVerifyHashAgainstScript(t *testing.T) {
bc := newTestChain(t)
cs, csInvalid := getTestContractState(t, 4, 5, random.Uint160()) // sender and IDs are not important for the test
ic := bc.newInteropContext(trigger.Verification, bc.dao, nil, nil)
ic := bc.newInteropContext(trigger.Verification, bc.dao.GetWrapped(), nil, nil)
require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs))
require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, csInvalid))

View file

@ -50,6 +50,7 @@ type DAO interface {
GetStorageItemsWithPrefix(id int32, prefix []byte) ([]state.StorageItemWithKey, error)
GetTransaction(hash util.Uint256) (*transaction.Transaction, uint32, error)
GetVersion() (Version, error)
GetCloned() DAO
GetWrapped() DAO
HasTransaction(hash util.Uint256) error
Persist() (int, error)
@ -102,6 +103,15 @@ func (dao *Simple) GetWrapped() DAO {
return d
}
// GetCloned returns new DAO instance with shared trie of MemCachedStore, use it for
// the latest layer that either doesn't need to Persist, or Persists to another well-known
// non-shared (!) layer.
func (dao *Simple) GetCloned() DAO {
d := *dao
d.Store = dao.Store.Clone()
return &d
}
// GetAndDecode performs get operation and decoding with serializable structures.
func (dao *Simple) GetAndDecode(entity io.Serializable, key []byte) error {
entityBytes, err := dao.Store.Get(key)

View file

@ -71,7 +71,7 @@ func NewContext(trigger trigger.Type, bc Ledger, d dao.DAO,
getContract func(dao.DAO, util.Uint160) (*state.Contract, error), natives []Contract,
block *block.Block, tx *transaction.Transaction, log *zap.Logger) *Context {
baseExecFee := int64(DefaultBaseExecFee)
dao := d.GetWrapped()
dao := d.GetCloned()
if bc != nil && (block == nil || block.Index != 0) {
baseExecFee = bc.GetBaseExecFee()

View file

@ -14,6 +14,9 @@ import (
type MemCachedStore struct {
MemoryStore
// lowerTrie stores lower level MemCachedStore trie for cloned MemCachedStore,
// which allows for much more efficient Persist.
lowerTrie *btree.BTree
// plock protects Persist from double entrance.
plock sync.Mutex
// Persistent Store.
@ -55,6 +58,16 @@ func NewMemCachedStore(lower Store) *MemCachedStore {
}
}
// NewClonedMemCachedStore creates a cloned MemCachedStore which shares the trie
// with another MemCachedStore (until you write into it).
func (s *MemCachedStore) Clone() *MemCachedStore {
return &MemCachedStore{
MemoryStore: MemoryStore{mem: *s.mem.Clone()}, // Shared COW trie.
lowerTrie: &s.mem,
ps: s.ps, // But the same PS.
}
}
// Get implements the Store interface.
func (s *MemCachedStore) Get(key []byte) ([]byte, error) {
s.mut.RLock()
@ -239,6 +252,13 @@ func (s *MemCachedStore) persist(isSync bool) (int, error) {
defer s.plock.Unlock()
s.mut.Lock()
if s.lowerTrie != nil {
keys = s.mem.Len() - s.lowerTrie.Len()
*s.lowerTrie = s.mem
s.mut.Unlock()
return keys, nil
}
keys = s.mem.Len()
if keys == 0 {
s.mut.Unlock()