From 84e03074630fd749472db360e1ffc52711741728 Mon Sep 17 00:00:00 2001 From: pdrobnjak Date: Wed, 18 Feb 2026 15:13:40 +0100 Subject: [PATCH 1/4] perf: use plain-map FastStore for giga executor snapshots Replace sync.Map-based cachekv stores with plain-map FastStore in the giga executor snapshot path. Since each OCC task's snapshot stores are owned by a single goroutine, sync.Map's thread-safety overhead is unnecessary waste. Changes: - Add cachekv.FastStore and gigacachekv.FastStore using plain maps - Add CacheMultiStoreGiga() to cachemulti.Store for lightweight CMS - DBImpl.Snapshot() uses CacheMultiStoreGiga() when available - Lazy store creation uses FastStore when CMS is in fast mode Co-Authored-By: Claude Opus 4.6 --- giga/deps/store/fast_cachekv.go | 156 +++++++++++++++++++++++++ giga/deps/xevm/state/state.go | 16 ++- sei-cosmos/store/cachekv/fast_store.go | 153 ++++++++++++++++++++++++ sei-cosmos/store/cachemulti/store.go | 89 +++++++++++++- 4 files changed, 412 insertions(+), 2 deletions(-) create mode 100644 giga/deps/store/fast_cachekv.go create mode 100644 sei-cosmos/store/cachekv/fast_store.go diff --git a/giga/deps/store/fast_cachekv.go b/giga/deps/store/fast_cachekv.go new file mode 100644 index 0000000000..5d10750e28 --- /dev/null +++ b/giga/deps/store/fast_cachekv.go @@ -0,0 +1,156 @@ +package store + +import ( + "bytes" + "io" + "sort" + + "github.com/cosmos/cosmos-sdk/store/tracekv" + "github.com/cosmos/cosmos-sdk/store/types" +) + +// FastStore is a single-goroutine giga cachekv store using plain maps instead of sync.Map. +// It MUST NOT be used from multiple goroutines concurrently. +// Use this for snapshot stores in the giga executor path where each store is +// owned by a single task goroutine. +type FastStore struct { + cache map[string]*types.CValue + deleted map[string]struct{} + parent types.KVStore + storeKey types.StoreKey +} + +var _ types.CacheKVStore = (*FastStore)(nil) + +// NewFastStore creates a new FastStore backed by plain maps. +func NewFastStore(parent types.KVStore, storeKey types.StoreKey) *FastStore { + return &FastStore{ + cache: make(map[string]*types.CValue), + deleted: make(map[string]struct{}), + parent: parent, + storeKey: storeKey, + } +} + +func (store *FastStore) GetWorkingHash() ([]byte, error) { + panic("should never attempt to get working hash from cache kv store") +} + +func (store *FastStore) GetStoreType() types.StoreType { + return store.parent.GetStoreType() +} + +func (store *FastStore) Get(key []byte) []byte { + types.AssertValidKey(key) + keyStr := UnsafeBytesToStr(key) + if cv, ok := store.cache[keyStr]; ok { + return cv.Value() + } + return store.parent.Get(key) +} + +func (store *FastStore) Set(key []byte, value []byte) { + types.AssertValidKey(key) + types.AssertValidValue(value) + store.setCacheValue(key, value, false, true) +} + +func (store *FastStore) Has(key []byte) bool { + return store.Get(key) != nil +} + +func (store *FastStore) Delete(key []byte) { + types.AssertValidKey(key) + store.setCacheValue(key, nil, true, true) +} + +func (store *FastStore) Write() { + keys := make([]string, 0, len(store.cache)) + for k, v := range store.cache { + if v.Dirty() { + keys = append(keys, k) + } + } + sort.Strings(keys) + + for _, key := range keys { + if _, del := store.deleted[key]; del { + store.parent.Delete([]byte(key)) + continue + } + if cv, ok := store.cache[key]; ok && cv.Value() != nil { + store.parent.Set([]byte(key), cv.Value()) + } + } + + // Mark all entries as clean instead of clearing — parent store may not + // make writes visible until Commit(). + for k, v := range store.cache { + store.cache[k] = types.NewCValue(v.Value(), false) + } + clear(store.deleted) +} + +func (store *FastStore) CacheWrap(storeKey types.StoreKey) types.CacheWrap { + return NewFastStore(store, storeKey) +} + +func (store *FastStore) CacheWrapWithTrace(storeKey types.StoreKey, w io.Writer, tc types.TraceContext) types.CacheWrap { + return NewFastStore(tracekv.NewStore(store, w, tc), storeKey) +} + +func (store *FastStore) VersionExists(version int64) bool { + return store.parent.VersionExists(version) +} + +func (store *FastStore) setCacheValue(key, value []byte, deleted bool, dirty bool) { + types.AssertValidKey(key) + keyStr := UnsafeBytesToStr(key) + store.cache[keyStr] = types.NewCValue(value, dirty) + if deleted { + store.deleted[keyStr] = struct{}{} + } else { + delete(store.deleted, keyStr) + } +} + +func (store *FastStore) GetParent() types.KVStore { + return store.parent +} + +func (store *FastStore) DeleteAll(start, end []byte) error { + for _, k := range store.GetAllKeyStrsInRange(start, end) { + store.Delete([]byte(k)) + } + return nil +} + +func (store *FastStore) GetAllKeyStrsInRange(start, end []byte) (res []string) { + keyStrs := map[string]struct{}{} + for _, pk := range store.parent.GetAllKeyStrsInRange(start, end) { + keyStrs[pk] = struct{}{} + } + for k, v := range store.cache { + kbz := []byte(k) + if bytes.Compare(kbz, start) < 0 || bytes.Compare(kbz, end) >= 0 { + continue + } + if v.Value() == nil { + delete(keyStrs, k) + } else { + keyStrs[k] = struct{}{} + } + } + for k := range keyStrs { + res = append(res, k) + } + return res +} + +func (store *FastStore) Iterator(start, end []byte) types.Iterator { + panic("unexpected iterator call on fast giga cachekv store") +} + +func (store *FastStore) ReverseIterator(start, end []byte) types.Iterator { + panic("unexpected reverse iterator call on fast giga cachekv store") +} diff --git a/giga/deps/xevm/state/state.go b/giga/deps/xevm/state/state.go index e57e962221..70f048fb3e 100644 --- a/giga/deps/xevm/state/state.go +++ b/giga/deps/xevm/state/state.go @@ -101,8 +101,22 @@ func (s *DBImpl) HasSelfDestructed(acc common.Address) bool { return bytes.Equal(val, AccountDeleted) } +// gigaCacheMultiStorer is implemented by cachemulti.Store to provide a +// lightweight CMS that uses plain maps instead of sync.Map. This is safe +// for single-goroutine use in the giga executor snapshot path. +type gigaCacheMultiStorer interface { + CacheMultiStoreGiga() storetypes.CacheMultiStore +} + func (s *DBImpl) Snapshot() int { - newCtx := s.ctx.WithMultiStore(s.ctx.MultiStore().CacheMultiStore()).WithEventManager(sdk.NewEventManager()) + ms := s.ctx.MultiStore() + var cms storetypes.CacheMultiStore + if gms, ok := ms.(gigaCacheMultiStorer); ok { + cms = gms.CacheMultiStoreGiga() + } else { + cms = ms.CacheMultiStore() + } + newCtx := s.ctx.WithMultiStore(cms).WithEventManager(sdk.NewEventManager()) s.snapshottedCtxs = append(s.snapshottedCtxs, s.ctx) s.ctx = newCtx version := len(s.snapshottedCtxs) - 1 diff --git a/sei-cosmos/store/cachekv/fast_store.go b/sei-cosmos/store/cachekv/fast_store.go new file mode 100644 index 0000000000..8e7c42d614 --- /dev/null +++ b/sei-cosmos/store/cachekv/fast_store.go @@ -0,0 +1,153 @@ +package cachekv + +import ( + "bytes" + "io" + "sort" + + "github.com/cosmos/cosmos-sdk/internal/conv" + "github.com/cosmos/cosmos-sdk/store/tracekv" + "github.com/cosmos/cosmos-sdk/store/types" +) + +// FastStore is a single-goroutine cachekv store using plain maps instead of sync.Map. +// It MUST NOT be used from multiple goroutines concurrently. +// Use this for snapshot stores in the giga executor path where each store is +// owned by a single task goroutine. +type FastStore struct { + cache map[string]*types.CValue + deleted map[string]struct{} + parent types.KVStore + storeKey types.StoreKey +} + +var _ types.CacheKVStore = (*FastStore)(nil) + +// NewFastStore creates a new FastStore backed by plain maps. +func NewFastStore(parent types.KVStore, storeKey types.StoreKey) *FastStore { + return &FastStore{ + cache: make(map[string]*types.CValue), + deleted: make(map[string]struct{}), + parent: parent, + storeKey: storeKey, + } +} + +func (store *FastStore) GetWorkingHash() ([]byte, error) { + panic("should never attempt to get working hash from cache kv store") +} + +func (store *FastStore) GetStoreType() types.StoreType { + return store.parent.GetStoreType() +} + +func (store *FastStore) Get(key []byte) []byte { + types.AssertValidKey(key) + keyStr := conv.UnsafeBytesToStr(key) + if cv, ok := store.cache[keyStr]; ok { + return cv.Value() + } + return store.parent.Get(key) +} + +func (store *FastStore) Set(key []byte, value []byte) { + types.AssertValidKey(key) + types.AssertValidValue(value) + store.setCacheValue(key, value, false, true) +} + +func (store *FastStore) Has(key []byte) bool { + return store.Get(key) != nil +} + +func (store *FastStore) Delete(key []byte) { + types.AssertValidKey(key) + store.setCacheValue(key, nil, true, true) +} + +func (store *FastStore) Write() { + keys := make([]string, 0, len(store.cache)) + for k, v := range store.cache { + if v.Dirty() { + keys = append(keys, k) + } + } + sort.Strings(keys) + + for _, key := range keys { + if _, del := store.deleted[key]; del { + store.parent.Delete([]byte(key)) + continue + } + if cv, ok := store.cache[key]; ok && cv.Value() != nil { + store.parent.Set([]byte(key), cv.Value()) + } + } + + clear(store.cache) + clear(store.deleted) +} + +func (store *FastStore) CacheWrap(storeKey types.StoreKey) types.CacheWrap { + return NewFastStore(store, storeKey) +} + +func (store *FastStore) CacheWrapWithTrace(storeKey types.StoreKey, w io.Writer, tc types.TraceContext) types.CacheWrap { + return NewFastStore(tracekv.NewStore(store, w, tc), storeKey) +} + +func (store *FastStore) Iterator(start, end []byte) types.Iterator { + panic("unexpected iterator call on fast cachekv store") +} + +func (store *FastStore) ReverseIterator(start, end []byte) types.Iterator { + panic("unexpected reverse iterator call on fast cachekv store") +} + +func (store *FastStore) VersionExists(version int64) bool { + return store.parent.VersionExists(version) +} + +func (store *FastStore) setCacheValue(key, value []byte, deleted bool, dirty bool) { + types.AssertValidKey(key) + keyStr := conv.UnsafeBytesToStr(key) + store.cache[keyStr] = types.NewCValue(value, dirty) + if deleted { + store.deleted[keyStr] = struct{}{} + } else { + delete(store.deleted, keyStr) + } +} + +func (store *FastStore) GetParent() types.KVStore { + return store.parent +} + +func (store *FastStore) DeleteAll(start, end []byte) error { + for _, k := range store.GetAllKeyStrsInRange(start, end) { + store.Delete([]byte(k)) + } + return nil +} + +func (store *FastStore) GetAllKeyStrsInRange(start, end []byte) (res []string) { + keyStrs := map[string]struct{}{} + for _, pk := range store.parent.GetAllKeyStrsInRange(start, end) { + keyStrs[pk] = struct{}{} + } + for k, v := range store.cache { + kbz := []byte(k) + if bytes.Compare(kbz, start) < 0 || bytes.Compare(kbz, end) >= 0 { + continue + } + if v.Value() == nil { + delete(keyStrs, k) + } else { + keyStrs[k] = struct{}{} + } + } + for k := range keyStrs { + res = append(res, k) + } + return res +} diff --git a/sei-cosmos/store/cachemulti/store.go b/sei-cosmos/store/cachemulti/store.go index eb4f8bce05..24697f9965 100644 --- a/sei-cosmos/store/cachemulti/store.go +++ b/sei-cosmos/store/cachemulti/store.go @@ -37,6 +37,8 @@ type Store struct { materializeOnce *sync.Once closers []io.Closer + + fast bool // when true, lazy store creation uses FastStore (plain maps) } var _ types.CacheMultiStore = Store{} @@ -132,6 +134,79 @@ func newCacheMultiStoreFromCMS(cms Store) Store { return NewFromKVStore(cms.db, stores, gigaStores, cms.keys, cms.gigaKeys, cms.traceWriter, cms.traceContext) } +// newFastCacheMultiStoreFromCMS creates a lightweight child CMS using plain-map +// based stores instead of sync.Map-based stores. The child CMS is intended for +// single-goroutine use only (giga executor snapshot path). +func newFastCacheMultiStoreFromCMS(cms Store) Store { + // Trigger materialization of parent stores exactly like the normal path. + cms.materializeOnce.Do(func() { + cms.mu.Lock() + for k := range cms.parents { + parent := cms.parents[k] + var cw types.CacheWrapper = parent + if cms.TracingEnabled() { + cw = tracekv.NewStore(parent.(types.KVStore), cms.traceWriter, cms.traceContext) + } + cms.stores[k] = cachekv.NewStore(cw.(types.KVStore), k, types.DefaultCacheSizeLimit) + delete(cms.parents, k) + } + cms.mu.Unlock() + }) + + // Collect parent store references. + cms.mu.RLock() + stores := make(map[types.StoreKey]types.CacheWrapper, len(cms.stores)) + for k, v := range cms.stores { + stores[k] = v + } + cms.mu.RUnlock() + + gigaStores := make(map[types.StoreKey]types.KVStore, len(cms.gigaStores)) + for k, v := range cms.gigaStores { + gigaStores[k] = v + } + + return newFastFromKVStore(cms.db, stores, gigaStores, cms.keys, cms.gigaKeys, cms.traceWriter, cms.traceContext) +} + +// newFastFromKVStore is like NewFromKVStore but uses plain-map FastStore types +// instead of sync.Map-based stores. Single-goroutine use only. +func newFastFromKVStore( + store types.KVStore, stores map[types.StoreKey]types.CacheWrapper, + gigaStores map[types.StoreKey]types.KVStore, + keys map[string]types.StoreKey, gigaKeys []types.StoreKey, + traceWriter io.Writer, traceContext types.TraceContext, +) Store { + cms := Store{ + db: cachekv.NewFastStore(store, nil), + stores: make(map[types.StoreKey]types.CacheWrap, len(stores)), + parents: make(map[types.StoreKey]types.CacheWrapper, len(stores)), + keys: keys, + gigaKeys: gigaKeys, + traceWriter: traceWriter, + traceContext: traceContext, + mu: &sync.RWMutex{}, + materializeOnce: &sync.Once{}, + fast: true, + } + + for key, s := range stores { + cms.parents[key] = s + } + + cms.gigaStores = make(map[types.StoreKey]types.KVStore, len(gigaKeys)) + for _, key := range gigaKeys { + if gigaStore, ok := gigaStores[key]; ok { + cms.gigaStores[key] = gigacachekv.NewFastStore(gigaStore, key) + } else { + parent := stores[key].(types.KVStore) + cms.gigaStores[key] = gigacachekv.NewFastStore(parent, key) + } + } + + return cms +} + // getOrCreateStore lazily creates a cachekv store from its parent on first access. // Thread-safe: concurrent callers (e.g. slashing BeginBlocker goroutines) may // call GetKVStore on the same CMS simultaneously. @@ -160,7 +235,12 @@ func (cms Store) getOrCreateStore(key types.StoreKey) types.CacheWrap { if cms.TracingEnabled() { cw = tracekv.NewStore(parent.(types.KVStore), cms.traceWriter, cms.traceContext) } - s := cachekv.NewStore(cw.(types.KVStore), key, types.DefaultCacheSizeLimit) + var s types.CacheWrap + if cms.fast { + s = cachekv.NewFastStore(cw.(types.KVStore), key) + } else { + s = cachekv.NewStore(cw.(types.KVStore), key, types.DefaultCacheSizeLimit) + } cms.stores[key] = s delete(cms.parents, key) return s @@ -228,6 +308,13 @@ func (cms Store) CacheMultiStore() types.CacheMultiStore { return newCacheMultiStoreFromCMS(cms) } +// CacheMultiStoreGiga creates a lightweight CMS using plain-map stores. +// Only safe when the returned CMS will be used by a single goroutine (e.g. +// giga executor snapshots within a single OCC task). +func (cms Store) CacheMultiStoreGiga() types.CacheMultiStore { + return newFastCacheMultiStoreFromCMS(cms) +} + // CacheMultiStoreWithVersion implements the MultiStore interface. It will panic // as an already cached multi-store cannot load previous versions. // From 8d79fb0076016eeadacba9d7ceaec6d7be258002 Mon Sep 17 00:00:00 2001 From: pdrobnjak Date: Wed, 18 Feb 2026 15:26:46 +0100 Subject: [PATCH 2/4] perf: use clear() in giga FastStore.Write() instead of marking clean Replace the per-entry CValue allocation loop with clear() on both cache and deleted maps. In the FastStore snapshot chain, parents (FastStore or VIS) make writes immediately visible, so reads after Write() correctly fall through to the parent. This eliminates ~18M CValue object allocations per 30s benchmark run that were the main contributor to the memclrNoHeapPointers increase from the FastStore change. Co-Authored-By: Claude Opus 4.6 --- giga/deps/store/fast_cachekv.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/giga/deps/store/fast_cachekv.go b/giga/deps/store/fast_cachekv.go index 5d10750e28..13e9eed8fe 100644 --- a/giga/deps/store/fast_cachekv.go +++ b/giga/deps/store/fast_cachekv.go @@ -83,11 +83,10 @@ func (store *FastStore) Write() { } } - // Mark all entries as clean instead of clearing — parent store may not - // make writes visible until Commit(). - for k, v := range store.cache { - store.cache[k] = types.NewCValue(v.Value(), false) - } + // Clear both maps — in the FastStore snapshot chain, parents are either + // another FastStore or a VIS, both of which make writes immediately + // visible. Reads after Write() fall through to the parent. + clear(store.cache) clear(store.deleted) } From d1f0593d359824824d55c169e59e7fdc09458c76 Mon Sep 17 00:00:00 2001 From: pdrobnjak Date: Wed, 18 Feb 2026 15:42:07 +0100 Subject: [PATCH 3/4] =?UTF-8?q?refactor:=20unify=20FastStore=20=E2=80=94?= =?UTF-8?q?=20use=20giga=20FastStore=20for=20all=20CMS=20stores?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove the duplicate sei-cosmos cachekv.FastStore and use the giga FastStore (giga/deps/store.FastStore) for both regular module stores and giga stores in the fast CMS path. cachemulti already imports the giga store package, so this adds no new dependency. Co-Authored-By: Claude Opus 4.6 --- sei-cosmos/store/cachekv/fast_store.go | 153 ------------------------- sei-cosmos/store/cachemulti/store.go | 4 +- 2 files changed, 2 insertions(+), 155 deletions(-) delete mode 100644 sei-cosmos/store/cachekv/fast_store.go diff --git a/sei-cosmos/store/cachekv/fast_store.go b/sei-cosmos/store/cachekv/fast_store.go deleted file mode 100644 index 8e7c42d614..0000000000 --- a/sei-cosmos/store/cachekv/fast_store.go +++ /dev/null @@ -1,153 +0,0 @@ -package cachekv - -import ( - "bytes" - "io" - "sort" - - "github.com/cosmos/cosmos-sdk/internal/conv" - "github.com/cosmos/cosmos-sdk/store/tracekv" - "github.com/cosmos/cosmos-sdk/store/types" -) - -// FastStore is a single-goroutine cachekv store using plain maps instead of sync.Map. -// It MUST NOT be used from multiple goroutines concurrently. -// Use this for snapshot stores in the giga executor path where each store is -// owned by a single task goroutine. -type FastStore struct { - cache map[string]*types.CValue - deleted map[string]struct{} - parent types.KVStore - storeKey types.StoreKey -} - -var _ types.CacheKVStore = (*FastStore)(nil) - -// NewFastStore creates a new FastStore backed by plain maps. -func NewFastStore(parent types.KVStore, storeKey types.StoreKey) *FastStore { - return &FastStore{ - cache: make(map[string]*types.CValue), - deleted: make(map[string]struct{}), - parent: parent, - storeKey: storeKey, - } -} - -func (store *FastStore) GetWorkingHash() ([]byte, error) { - panic("should never attempt to get working hash from cache kv store") -} - -func (store *FastStore) GetStoreType() types.StoreType { - return store.parent.GetStoreType() -} - -func (store *FastStore) Get(key []byte) []byte { - types.AssertValidKey(key) - keyStr := conv.UnsafeBytesToStr(key) - if cv, ok := store.cache[keyStr]; ok { - return cv.Value() - } - return store.parent.Get(key) -} - -func (store *FastStore) Set(key []byte, value []byte) { - types.AssertValidKey(key) - types.AssertValidValue(value) - store.setCacheValue(key, value, false, true) -} - -func (store *FastStore) Has(key []byte) bool { - return store.Get(key) != nil -} - -func (store *FastStore) Delete(key []byte) { - types.AssertValidKey(key) - store.setCacheValue(key, nil, true, true) -} - -func (store *FastStore) Write() { - keys := make([]string, 0, len(store.cache)) - for k, v := range store.cache { - if v.Dirty() { - keys = append(keys, k) - } - } - sort.Strings(keys) - - for _, key := range keys { - if _, del := store.deleted[key]; del { - store.parent.Delete([]byte(key)) - continue - } - if cv, ok := store.cache[key]; ok && cv.Value() != nil { - store.parent.Set([]byte(key), cv.Value()) - } - } - - clear(store.cache) - clear(store.deleted) -} - -func (store *FastStore) CacheWrap(storeKey types.StoreKey) types.CacheWrap { - return NewFastStore(store, storeKey) -} - -func (store *FastStore) CacheWrapWithTrace(storeKey types.StoreKey, w io.Writer, tc types.TraceContext) types.CacheWrap { - return NewFastStore(tracekv.NewStore(store, w, tc), storeKey) -} - -func (store *FastStore) Iterator(start, end []byte) types.Iterator { - panic("unexpected iterator call on fast cachekv store") -} - -func (store *FastStore) ReverseIterator(start, end []byte) types.Iterator { - panic("unexpected reverse iterator call on fast cachekv store") -} - -func (store *FastStore) VersionExists(version int64) bool { - return store.parent.VersionExists(version) -} - -func (store *FastStore) setCacheValue(key, value []byte, deleted bool, dirty bool) { - types.AssertValidKey(key) - keyStr := conv.UnsafeBytesToStr(key) - store.cache[keyStr] = types.NewCValue(value, dirty) - if deleted { - store.deleted[keyStr] = struct{}{} - } else { - delete(store.deleted, keyStr) - } -} - -func (store *FastStore) GetParent() types.KVStore { - return store.parent -} - -func (store *FastStore) DeleteAll(start, end []byte) error { - for _, k := range store.GetAllKeyStrsInRange(start, end) { - store.Delete([]byte(k)) - } - return nil -} - -func (store *FastStore) GetAllKeyStrsInRange(start, end []byte) (res []string) { - keyStrs := map[string]struct{}{} - for _, pk := range store.parent.GetAllKeyStrsInRange(start, end) { - keyStrs[pk] = struct{}{} - } - for k, v := range store.cache { - kbz := []byte(k) - if bytes.Compare(kbz, start) < 0 || bytes.Compare(kbz, end) >= 0 { - continue - } - if v.Value() == nil { - delete(keyStrs, k) - } else { - keyStrs[k] = struct{}{} - } - } - for k := range keyStrs { - res = append(res, k) - } - return res -} diff --git a/sei-cosmos/store/cachemulti/store.go b/sei-cosmos/store/cachemulti/store.go index 24697f9965..f6d9dc2ccc 100644 --- a/sei-cosmos/store/cachemulti/store.go +++ b/sei-cosmos/store/cachemulti/store.go @@ -178,7 +178,7 @@ func newFastFromKVStore( traceWriter io.Writer, traceContext types.TraceContext, ) Store { cms := Store{ - db: cachekv.NewFastStore(store, nil), + db: gigacachekv.NewFastStore(store, nil), stores: make(map[types.StoreKey]types.CacheWrap, len(stores)), parents: make(map[types.StoreKey]types.CacheWrapper, len(stores)), keys: keys, @@ -237,7 +237,7 @@ func (cms Store) getOrCreateStore(key types.StoreKey) types.CacheWrap { } var s types.CacheWrap if cms.fast { - s = cachekv.NewFastStore(cw.(types.KVStore), key) + s = gigacachekv.NewFastStore(cw.(types.KVStore), key) } else { s = cachekv.NewStore(cw.(types.KVStore), key, types.DefaultCacheSizeLimit) } From 26e85c7f70d883c22e4ccd4de579726d222d4700 Mon Sep 17 00:00:00 2001 From: pdrobnjak Date: Tue, 17 Feb 2026 15:53:25 +0100 Subject: [PATCH 4/4] perf: lazy store creation in Snapshot CMS (skip eager materialization) newFastCacheMultiStoreFromCMS was eagerly materializing all parent stores via cachekv.NewStore for ~50 store keys on every Snapshot, even though only 3-4 stores are accessed per EVM transaction. Now passes both stores and parents through to the child CMS, letting getOrCreateStore create FastStore wrappers lazily on demand. Co-Authored-By: Claude Opus 4.6 --- sei-cosmos/store/cachemulti/store.go | 34 ++++++++++++---------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/sei-cosmos/store/cachemulti/store.go b/sei-cosmos/store/cachemulti/store.go index f6d9dc2ccc..bfa03b3c28 100644 --- a/sei-cosmos/store/cachemulti/store.go +++ b/sei-cosmos/store/cachemulti/store.go @@ -137,27 +137,23 @@ func newCacheMultiStoreFromCMS(cms Store) Store { // newFastCacheMultiStoreFromCMS creates a lightweight child CMS using plain-map // based stores instead of sync.Map-based stores. The child CMS is intended for // single-goroutine use only (giga executor snapshot path). +// +// Stores are created lazily: only when actually accessed by the transaction. +// This avoids allocating ~50 cachekv/FastStore wrappers per Snapshot when +// only 3-4 stores are typically touched. func newFastCacheMultiStoreFromCMS(cms Store) Store { - // Trigger materialization of parent stores exactly like the normal path. - cms.materializeOnce.Do(func() { - cms.mu.Lock() - for k := range cms.parents { - parent := cms.parents[k] - var cw types.CacheWrapper = parent - if cms.TracingEnabled() { - cw = tracekv.NewStore(parent.(types.KVStore), cms.traceWriter, cms.traceContext) - } - cms.stores[k] = cachekv.NewStore(cw.(types.KVStore), k, types.DefaultCacheSizeLimit) - delete(cms.parents, k) - } - cms.mu.Unlock() - }) - - // Collect parent store references. + // Collect both materialized stores and unmaterialized parents as parents + // for the child CMS. The child's getOrCreateStore will create FastStores + // lazily on demand, avoiding the cost of eagerly creating wrappers for + // all ~50 store keys. cms.mu.RLock() - stores := make(map[types.StoreKey]types.CacheWrapper, len(cms.stores)) + parentCount := len(cms.stores) + len(cms.parents) + parents := make(map[types.StoreKey]types.CacheWrapper, parentCount) for k, v := range cms.stores { - stores[k] = v + parents[k] = v + } + for k, v := range cms.parents { + parents[k] = v } cms.mu.RUnlock() @@ -166,7 +162,7 @@ func newFastCacheMultiStoreFromCMS(cms Store) Store { gigaStores[k] = v } - return newFastFromKVStore(cms.db, stores, gigaStores, cms.keys, cms.gigaKeys, cms.traceWriter, cms.traceContext) + return newFastFromKVStore(cms.db, parents, gigaStores, cms.keys, cms.gigaKeys, cms.traceWriter, cms.traceContext) } // newFastFromKVStore is like NewFromKVStore but uses plain-map FastStore types