Gek/cache bot search page (#3530)
* feat: add general purpose key/val caching layer * feat: cache bot/metadata response page for 10 seconds
This commit is contained in:
82
core/cache/cache.go
vendored
Normal file
82
core/cache/cache.go
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/jellydator/ttlcache/v3"
|
||||
)
|
||||
|
||||
// CacheContainer is a container for all caches.
|
||||
type CacheContainer struct {
|
||||
caches map[string]*CacheInstance
|
||||
}
|
||||
|
||||
// CacheInstance is a single cache instance.
|
||||
type CacheInstance struct {
|
||||
cache *ttlcache.Cache[string, []byte]
|
||||
}
|
||||
|
||||
// This is the global singleton instance. (To be removed after refactor).
|
||||
var _instance *CacheContainer
|
||||
|
||||
// NewCache creates a new cache instance.
|
||||
func NewGlobalCache() *CacheContainer {
|
||||
_instance = &CacheContainer{
|
||||
caches: make(map[string]*CacheInstance),
|
||||
}
|
||||
|
||||
return _instance
|
||||
}
|
||||
|
||||
// GetCache returns the cache instance.
|
||||
func GetGlobalCache() *CacheContainer {
|
||||
if _instance != nil {
|
||||
return _instance
|
||||
}
|
||||
return NewGlobalCache()
|
||||
}
|
||||
|
||||
// GetOrCreateCache returns the cache instance or creates a new one.
|
||||
func (c *CacheContainer) GetOrCreateCache(name string, expiration time.Duration) *CacheInstance {
|
||||
if _, ok := c.caches[name]; !ok {
|
||||
c.CreateCache(name, expiration)
|
||||
}
|
||||
return c.caches[name]
|
||||
}
|
||||
|
||||
// CreateCache creates a new cache instance.
|
||||
func (c *CacheContainer) CreateCache(name string, expiration time.Duration) *CacheInstance {
|
||||
cache := ttlcache.New[string, []byte](
|
||||
ttlcache.WithTTL[string, []byte](expiration),
|
||||
ttlcache.WithDisableTouchOnHit[string, []byte](),
|
||||
)
|
||||
ci := &CacheInstance{cache: cache}
|
||||
c.caches[name] = ci
|
||||
go cache.Start()
|
||||
return ci
|
||||
}
|
||||
|
||||
// GetCache returns the cache instance.
|
||||
func (c *CacheContainer) GetCache(name string) *CacheInstance {
|
||||
return c.caches[name]
|
||||
}
|
||||
|
||||
// GetValueForKey returns the value for the given key.
|
||||
func (ci *CacheInstance) GetValueForKey(key string) []byte {
|
||||
value := ci.cache.Get(key, ttlcache.WithDisableTouchOnHit[string, []byte]())
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if value.IsExpired() {
|
||||
return nil
|
||||
}
|
||||
|
||||
val := value.Value()
|
||||
return val
|
||||
}
|
||||
|
||||
// Set sets the value for the given key..
|
||||
func (ci *CacheInstance) Set(key string, value []byte) {
|
||||
ci.cache.Set(key, value, 0)
|
||||
}
|
||||
72
core/cache/cache_test.go
vendored
Normal file
72
core/cache/cache_test.go
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCache(t *testing.T) {
|
||||
expiration := 5 * time.Second
|
||||
globalCache := GetGlobalCache()
|
||||
assert.NotNil(t, globalCache, "NewGlobalCache should return a non-nil instance")
|
||||
assert.Equal(t, globalCache, GetGlobalCache(), "GetGlobalCache should return the created instance")
|
||||
|
||||
cacheName := "testCache"
|
||||
globalCache.CreateCache(cacheName, expiration)
|
||||
|
||||
createdCache := globalCache.GetCache(cacheName)
|
||||
assert.NotNil(t, createdCache, "GetCache should return a non-nil cache")
|
||||
|
||||
key := "testKey"
|
||||
value := []byte("testValue")
|
||||
createdCache.Set(key, value)
|
||||
|
||||
// Wait for cache to expire
|
||||
time.Sleep(expiration + 1*time.Second)
|
||||
|
||||
// Verify that the cache has expired
|
||||
ci := globalCache.GetCache(cacheName)
|
||||
cachedValue := ci.GetValueForKey(key)
|
||||
assert.Nil(t, cachedValue, "Cache should not contain the value after expiration")
|
||||
}
|
||||
|
||||
func TestConcurrentAccess(t *testing.T) {
|
||||
// Test concurrent access to the cache
|
||||
globalCache := NewGlobalCache()
|
||||
cacheName := "concurrentCache"
|
||||
expiration := 5 * time.Second
|
||||
globalCache.CreateCache(cacheName, expiration)
|
||||
|
||||
// Start multiple goroutines to access the cache concurrently
|
||||
numGoroutines := 10
|
||||
keyPrefix := "key"
|
||||
valuePrefix := "value"
|
||||
|
||||
done := make(chan struct{})
|
||||
for i := 0; i < numGoroutines; i++ {
|
||||
go func(index int) {
|
||||
defer func() { done <- struct{}{} }()
|
||||
|
||||
cache := globalCache.GetCache(cacheName)
|
||||
key := keyPrefix + strconv.Itoa(index)
|
||||
value := valuePrefix + strconv.Itoa(index)
|
||||
|
||||
cache.Set(key, []byte(value))
|
||||
|
||||
// Simulate some work
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
ci := globalCache.GetCache(cacheName)
|
||||
cachedValue := string(ci.GetValueForKey(key))
|
||||
assert.Equal(t, value, cachedValue, "Cached value should match the set value")
|
||||
}(i)
|
||||
}
|
||||
|
||||
// Wait for all goroutines to finish
|
||||
for i := 0; i < numGoroutines; i++ {
|
||||
<-done
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user