Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Off-chain] feat: in-memory query cache(s) #994

Open
wants to merge 24 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
830a7f6
fix: sessiontree bug
bryanchriswhite Dec 11, 2024
b0f08c3
chore: add QueryCache interface
bryanchriswhite Dec 10, 2024
8743246
chore: add HistoricalQueryCache interface
bryanchriswhite Dec 10, 2024
d895441
feat: add InMemoryCache implementation
bryanchriswhite Dec 10, 2024
23cc94a
chore: self-review improvements
bryanchriswhite Dec 12, 2024
366ab1d
chore: self-review improvements
bryanchriswhite Dec 12, 2024
4632c74
fix: historical pruning
bryanchriswhite Dec 12, 2024
a467428
Merge remote-tracking branch 'pokt/main' into issues/543/cache/memory
bryanchriswhite Dec 13, 2024
e48a2f2
chore: review feedback improvements
bryanchriswhite Dec 16, 2024
d5ce62f
chore: review feedback improvements
bryanchriswhite Dec 16, 2024
71da7f1
chore: review feedback improvements
bryanchriswhite Dec 18, 2024
6774d3a
fix: linter errors
bryanchriswhite Dec 18, 2024
086d219
chore: disable #Set() on historical cache
bryanchriswhite Dec 18, 2024
d6dbc44
Merge branch 'main' into issues/543/cache/memory
bryanchriswhite Dec 18, 2024
0414e9a
Merge branch 'main' into issues/543/cache/memory
Olshansk Dec 20, 2024
d1c024e
Merge branch 'main' into issues/543/cache/memory
Olshansk Feb 12, 2025
b221a55
Merge branch 'main' into issues/543/cache/memory
bryanchriswhite Feb 14, 2025
e136271
refactor: rename QueryCache to KeyValueCache
bryanchriswhite Feb 14, 2025
43fd1d8
refactor: rename inMemoryCache to inMemoryKVCache
bryanchriswhite Feb 14, 2025
f2f1d78
refactor: rename HistoricalQueryCache to HistoricalKeyValueCache
bryanchriswhite Feb 14, 2025
b6cff36
refactor: rename GetVersion, SetVersion, & add GetLatestVersion
bryanchriswhite Feb 14, 2025
0e761e5
chore: review improvements
bryanchriswhite Feb 14, 2025
dae1509
refactor: eviction
bryanchriswhite Feb 14, 2025
1900c99
Merge branch 'main' into issues/543/cache/memory
bryanchriswhite Feb 18, 2025
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
22 changes: 22 additions & 0 deletions pkg/client/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -370,3 +370,25 @@ type BankQueryClient interface {
// GetBalance queries the chain for the uPOKT balance of the account provided
GetBalance(ctx context.Context, address string) (*cosmostypes.Coin, error)
}

// KeyValueCache is a key/value store style interface for a cache of a single type.
// It is intended to be used to cache query responses (or derivatives thereof),
// where each key uniquely indexes the most recent query response.
type KeyValueCache[T any] interface {
Get(key string) (T, error)
Set(key string, value T) error
Delete(key string)
Clear()
}

// HistoricalKeyValueCache extends KeyValueCache to support getting and setting values
// at multiple heights for a given key.
type HistoricalKeyValueCache[T any] interface {
KeyValueCache[T]
// GetLatestVersion returns the value of the latest version for the given key.
GetLatestVersion(key string) (T, error)
// GetVersion retrieves the nearest value <= the specified version number.
GetVersion(key string, version int64) (T, error)
// SetVersion adds or updates a value at a specific version number.
SetVersion(key string, value T, version int64) error
}
100 changes: 100 additions & 0 deletions pkg/client/query/cache/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package cache

import (
"time"
)

// EvictionPolicy determines which values are removed when number of keys in the cache reaches maxKeys.
type EvictionPolicy int64

const (
FirstInFirstOut = EvictionPolicy(iota)
LeastRecentlyUsed
LeastFrequentlyUsed
)

// queryCacheConfig is the configuration for query caches.
// It is intended to be configured via QueryCacheOptionFn functions.
type queryCacheConfig struct {
// maxKeys is the maximum number of key/value pairs the cache can
// hold before it starts evicting.
maxKeys int64

// TODO_CONSIDERATION:
//
// maxValueSize is the maximum cumulative size of all values in the cache.
// maxValueSize int64
// maxCacheSize is the maximum cumulative size of all keys AND values in the cache.
// maxCacheSize int64

// evictionPolicy determines which values are removed when number of keys in the cache reaches maxKeys.
evictionPolicy EvictionPolicy
// ttl is how long values should remain valid in the cache. Items older than the
Olshansk marked this conversation as resolved.
Show resolved Hide resolved
// ttl MAY NOT be evicted immediately, but are NEVER considered as cache hits.
ttl time.Duration
// historical determines whether each key will point to a single values (false)
// or a history (i.e. reverse chronological list) of values (true).
historical bool
// maxVersionAge is the max difference between the latest known version and
bryanchriswhite marked this conversation as resolved.
Show resolved Hide resolved
// any other version, below which value versions are retained, and above which
// value versions are pruned.
// E.g.: Given a latest version of 100, and a maxVersionAge of 10, then the
// oldest version that is not pruned is 90 (100 - 10).
// If 0, no historical pruning is performed. It ONLY applies when historical is true.
maxVersionAge int64
}

// QueryCacheOptionFn is a function which receives a queryCacheConfig for configuration.
type QueryCacheOptionFn func(*queryCacheConfig)

// Validate ensures that the queryCacheConfig isn't configured with incompatible options.
func (cfg *queryCacheConfig) Validate() error {
switch cfg.evictionPolicy {
case FirstInFirstOut:
// TODO_IMPROVE: support LeastRecentlyUsed and LeastFrequentlyUsed policies.
default:
return ErrQueryCacheConfigValidation.Wrapf("eviction policy %d not imlemented", cfg.evictionPolicy)
}

if cfg.maxVersionAge > 0 && !cfg.historical {
return ErrQueryCacheConfigValidation.Wrap("maxVersionAge > 0 requires historical mode to be enabled")
}

if cfg.historical && cfg.maxVersionAge < 0 {
return ErrQueryCacheConfigValidation.Wrapf("maxVersionAge MUST be >= 0, got: %d", cfg.maxVersionAge)
}

return nil
bryanchriswhite marked this conversation as resolved.
Show resolved Hide resolved
}

// WithHistoricalMode enables historical caching with the given maxVersionAge
// configuration; if 0, no historical pruning is performed.
func WithHistoricalMode(numRetainedVersions int64) QueryCacheOptionFn {
return func(cfg *queryCacheConfig) {
cfg.historical = true
cfg.maxVersionAge = numRetainedVersions
}
}

// WithMaxKeys sets the maximum number of distinct key/value pairs the cache will
Olshansk marked this conversation as resolved.
Show resolved Hide resolved
// hold before evicting according to the configured eviction policy.
func WithMaxKeys(maxKeys int64) QueryCacheOptionFn {
return func(cfg *queryCacheConfig) {
cfg.maxKeys = maxKeys
}
}

// WithEvictionPolicy sets the eviction policy.
func WithEvictionPolicy(policy EvictionPolicy) QueryCacheOptionFn {
return func(cfg *queryCacheConfig) {
cfg.evictionPolicy = policy
}
}

// WithTTL sets the time-to-live for cached values. Values older than the TTL
// MAY NOT be evicted immediately, but are NEVER considered as cache hits.
func WithTTL(ttl time.Duration) QueryCacheOptionFn {
return func(cfg *queryCacheConfig) {
cfg.ttl = ttl
}
}
13 changes: 13 additions & 0 deletions pkg/client/query/cache/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package cache

import "cosmossdk.io/errors"

const codesace = "client/query/cache"

var (
ErrCacheMiss = errors.Register(codesace, 1, "cache miss")
ErrHistoricalModeNotEnabled = errors.Register(codesace, 2, "historical mode not enabled")
ErrQueryCacheConfigValidation = errors.Register(codesace, 3, "invalid query cache config")
ErrCacheInternal = errors.Register(codesace, 4, "cache internal error")
ErrUnsupportedHistoricalModeOp = errors.Register(codesace, 5, "operation not supported in historical mode")
)
Loading