diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..3dd5614 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,41 @@ + + +# Changelog + +## [Unreleased] + +### KVIndexer breaking + +* (cache) [#48](https://github.com/initia-labs/kvindexer/pull/48) Replace record-count-based lru cache with capacity-based one diff --git a/config/config.go b/config/config.go index babaa33..76d0a58 100644 --- a/config/config.go +++ b/config/config.go @@ -11,9 +11,9 @@ import ( ) const ( - flagIndexerEnable = "indexer.enable" - flagIndexerBackend = "indexer.backend" - flagIndexerCacheSize = "indexer.cache-size" + flagIndexerEnable = "indexer.enable" + flagIndexerBackend = "indexer.backend" + flagIndexerCacheCapacity = "indexer.cache-capacity" ) func NewConfig(appOpts servertypes.AppOptions) (*IndexerConfig, error) { @@ -23,7 +23,7 @@ func NewConfig(appOpts servertypes.AppOptions) (*IndexerConfig, error) { if !cfg.Enable { return cfg, nil } - cfg.CacheSize = cast.ToUint(appOpts.Get(flagIndexerCacheSize)) + cfg.CacheCapacity = cast.ToInt(appOpts.Get(flagIndexerCacheCapacity)) cfg.BackendConfig = viper.New() err := cfg.BackendConfig.MergeConfigMap(cast.ToStringMap(appOpts.Get(flagIndexerBackend))) @@ -31,7 +31,7 @@ func NewConfig(appOpts servertypes.AppOptions) (*IndexerConfig, error) { return nil, fmt.Errorf("failed to merge backend config: %w", err) } - cfg.CacheSize = cast.ToUint(appOpts.Get(flagIndexerCacheSize)) + cfg.CacheCapacity = cast.ToInt(appOpts.Get(flagIndexerCacheCapacity)) return cfg, nil } @@ -41,8 +41,8 @@ func (c IndexerConfig) Validate() error { return nil } - if c.CacheSize == 0 { - return fmt.Errorf("cache size must be greater than 0") + if c.CacheCapacity == 0 { + return fmt.Errorf("cache capacity must be greater than 0") } if c.BackendConfig == nil { @@ -59,7 +59,7 @@ func (c IndexerConfig) IsEnabled() bool { func DefaultConfig() IndexerConfig { return IndexerConfig{ Enable: true, - CacheSize: 100_000, + CacheCapacity: 500 * 1024 * 1024, // 500MiB BackendConfig: store.DefaultConfig(), } } diff --git a/config/types.go b/config/types.go index 342a53f..dc43bfe 100644 --- a/config/types.go +++ b/config/types.go @@ -6,7 +6,7 @@ import ( type IndexerConfig struct { Enable bool `mapstructure:"indexer.enable"` - CacheSize uint `mapstructure:"indexer.cache-size"` + CacheCapacity int `mapstructure:"indexer.cache-capacity"` BackendConfig *viper.Viper `mapstructure:"indexer.backend"` } @@ -20,8 +20,8 @@ const DefaultConfigTemplate = ` # Enable defines whether the indexer is enabled. enable = {{ .IndexerConfig.Enable }} -# CacheSize defines the size of the cache. -cache-size = {{ .IndexerConfig.CacheSize }} +# CacheCapacity defines the size of the cache. (unit: bytes) +cache-capacity = {{ .IndexerConfig.CacheCapacity }} # Backend defines the type of the backend store and its options. # It should have a key-value pair named 'type', and the value should exist in store supported by cosmos-db. diff --git a/go.mod b/go.mod index fe16827..24b0220 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( cosmossdk.io/core v0.11.0 cosmossdk.io/log v1.3.1 cosmossdk.io/store v1.0.2 + github.com/allegro/bigcache/v3 v3.1.0 github.com/cometbft/cometbft v0.38.5 github.com/cosmos/cosmos-db v1.0.2 github.com/cosmos/cosmos-sdk v0.50.5 @@ -24,10 +25,12 @@ require ( google.golang.org/grpc v1.62.0 ) +require github.com/hashicorp/golang-lru v1.0.2 // indirect + require ( cosmossdk.io/api v0.7.3 // indirect cosmossdk.io/depinject v1.0.0-alpha.4 // indirect - cosmossdk.io/errors v1.0.1 // indirect + cosmossdk.io/errors v1.0.1 cosmossdk.io/math v1.3.0 // indirect cosmossdk.io/x/tx v0.13.1 // indirect filippo.io/edwards25519 v1.0.0 // indirect @@ -90,7 +93,6 @@ require ( github.com/hashicorp/go-metrics v0.5.2 // indirect github.com/hashicorp/go-plugin v1.5.2 // indirect github.com/hashicorp/go-uuid v1.0.2 // indirect - github.com/hashicorp/golang-lru v1.0.2 github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/yamux v0.1.1 // indirect github.com/hdevalence/ed25519consensus v0.1.0 // indirect diff --git a/go.sum b/go.sum index 3312dd4..571f09a 100644 --- a/go.sum +++ b/go.sum @@ -42,6 +42,8 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/allegro/bigcache/v3 v3.1.0 h1:H2Vp8VOvxcrB91o86fUSVJFqeuz8kpyyB02eH3bSzwk= +github.com/allegro/bigcache/v3 v3.1.0/go.mod h1:aPyh7jEvrog9zAwx5N7+JUQX5dZTSGpxF1LAR4dr35I= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= diff --git a/store/cache_kvstore.go b/store/cache_kvstore.go index a816f10..18a2477 100644 --- a/store/cache_kvstore.go +++ b/store/cache_kvstore.go @@ -1,22 +1,28 @@ package store import ( - "fmt" + "context" + "cosmossdk.io/errors" cachekv "cosmossdk.io/store/cachekv" "cosmossdk.io/store/types" - lru "github.com/hashicorp/golang-lru" + bigcache "github.com/allegro/bigcache/v3" ) type CacheStore struct { store types.CacheKVStore - cache *lru.ARCCache + cache *bigcache.BigCache } -func NewCacheStore(store types.KVStore, size uint) *CacheStore { - cache, err := lru.NewARC(int(size)) +func NewCacheStore(store types.KVStore, capacity int) *CacheStore { + // default with no eviction and custom hard max cache capacity + cacheCfg := bigcache.DefaultConfig(0) + cacheCfg.Verbose = false + cacheCfg.HardMaxCacheSize = capacity + + cache, err := bigcache.New(context.Background(), cacheCfg) if err != nil { - panic(fmt.Errorf("failed to create KVStore cache: %s", err)) + panic(err) } return &CacheStore{ @@ -28,36 +34,45 @@ func NewCacheStore(store types.KVStore, size uint) *CacheStore { func (c CacheStore) Get(key []byte) ([]byte, error) { types.AssertValidKey(key) - v, ok := c.cache.Get(string(key)) - if ok { - // cache hit - return v.([]byte), nil + v, err := c.cache.Get(string(key)) + // cache hit + if err == nil { + return v, nil } - // write to cache + // get from store and write to cache value := c.store.Get(key) - c.cache.Add(string(key), value) + err = c.cache.Set(string(key), value) + if err != nil { + return nil, errors.Wrap(err, "failed to set cache") + } return value, nil } func (c CacheStore) Has(key []byte) (bool, error) { - _, ok := c.cache.Get(string(key)) - return ok, nil + _, err := c.cache.Get(string(key)) + return err == nil, err } func (c CacheStore) Set(key, value []byte) error { types.AssertValidKey(key) types.AssertValidValue(value) - c.cache.Add(string(key), value) + err := c.cache.Set(string(key), value) + if err != nil { + return errors.Wrap(err, "failed to set cache") + } c.store.Set(key, value) return nil } func (c CacheStore) Delete(key []byte) error { - c.cache.Remove(string(key)) + err := c.cache.Delete(string(key)) + if err != nil && errors.IsOf(err, bigcache.ErrEntryNotFound) { + return errors.Wrap(err, "failed to delete cache") + } c.store.Delete(key) return nil diff --git a/x/kvindexer/keeper/keeper.go b/x/kvindexer/keeper/keeper.go index dc90250..1beb020 100644 --- a/x/kvindexer/keeper/keeper.go +++ b/x/kvindexer/keeper/keeper.go @@ -108,7 +108,7 @@ func (k *Keeper) Seal() error { k.db = db k.schema = &schema - k.store = store.NewCacheStore(dbadapter.Store{DB: db}, k.config.CacheSize) + k.store = store.NewCacheStore(dbadapter.Store{DB: db}, k.config.CacheCapacity) k.sealed = true return nil