-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add cache interface and implementation for redis,inmemory (#2155)
- Loading branch information
1 parent
7086f7e
commit 6614cd3
Showing
14 changed files
with
683 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
package cache | ||
|
||
import ( | ||
"os" | ||
"time" | ||
|
||
inmemory "go.signoz.io/signoz/pkg/query-service/cache/inmemory" | ||
redis "go.signoz.io/signoz/pkg/query-service/cache/redis" | ||
"go.signoz.io/signoz/pkg/query-service/cache/status" | ||
v3 "go.signoz.io/signoz/pkg/query-service/model/v3" | ||
"gopkg.in/yaml.v2" | ||
) | ||
|
||
type Options struct { | ||
Name string `yaml:"-"` | ||
Provider string `yaml:"provider"` | ||
Redis *redis.Options `yaml:"redis,omitempty"` | ||
InMemory *inmemory.Options `yaml:"inmemory,omitempty"` | ||
} | ||
|
||
// Cache is the interface for the storage backend | ||
type Cache interface { | ||
Connect() error | ||
Store(cacheKey string, data []byte, ttl time.Duration) error | ||
Retrieve(cacheKey string, allowExpired bool) ([]byte, status.RetrieveStatus, error) | ||
SetTTL(cacheKey string, ttl time.Duration) | ||
Remove(cacheKey string) | ||
BulkRemove(cacheKeys []string) | ||
Close() error | ||
} | ||
|
||
// KeyGenerator is the interface for the key generator | ||
// The key generator is used to generate the cache keys for the cache entries | ||
type KeyGenerator interface { | ||
// GenerateKeys generates the cache keys for the given query range params | ||
// The keys are returned as a map where the key is the query name and the value is the cache key | ||
GenerateKeys(*v3.QueryRangeParamsV3) map[string]string | ||
} | ||
|
||
// LoadFromYAMLCacheConfig loads the cache options from the given YAML config bytes | ||
func LoadFromYAMLCacheConfig(yamlConfig []byte) (*Options, error) { | ||
var options Options | ||
err := yaml.Unmarshal(yamlConfig, &options) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return &options, nil | ||
} | ||
|
||
// LoadFromYAMLCacheConfigFile loads the cache options from the given YAML config file | ||
func LoadFromYAMLCacheConfigFile(configFile string) (*Options, error) { | ||
bytes, err := os.ReadFile(configFile) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return LoadFromYAMLCacheConfig(bytes) | ||
} | ||
|
||
// NewCache creates a new cache based on the given options | ||
func NewCache(options *Options) Cache { | ||
switch options.Provider { | ||
case "redis": | ||
return redis.New(options.Redis) | ||
case "inmemory": | ||
return inmemory.New(options.InMemory) | ||
default: | ||
return nil | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package cache | ||
|
||
import "testing" | ||
|
||
func TestNewCacheUnKnownProvider(t *testing.T) { | ||
c := NewCache(&Options{ | ||
Name: "test", | ||
Provider: "unknown", | ||
}) | ||
|
||
if c != nil { | ||
t.Fatalf("expected nil, got %v", c) | ||
} | ||
} | ||
|
||
func TestNewCacheInMemory(t *testing.T) { | ||
c := NewCache(&Options{ | ||
Name: "test", | ||
Provider: "inmemory", | ||
}) | ||
|
||
if c == nil { | ||
t.Fatalf("expected non-nil, got nil") | ||
} | ||
} | ||
|
||
func TestNewCacheRedis(t *testing.T) { | ||
c := NewCache(&Options{ | ||
Name: "test", | ||
Provider: "redis", | ||
}) | ||
|
||
if c == nil { | ||
t.Fatalf("expected non-nil, got nil") | ||
} | ||
} | ||
|
||
func TestLoadFromYAMLCacheConfig(t *testing.T) { | ||
_, err := LoadFromYAMLCacheConfig([]byte(` | ||
provider: inmemory | ||
`)) | ||
if err != nil { | ||
t.Fatalf("unexpected error: %s", err) | ||
} | ||
} | ||
|
||
func TestLoadFromYAMLCacheConfigFile(t *testing.T) { | ||
_, err := LoadFromYAMLCacheConfigFile("testdata/cache.yaml") | ||
if err != nil { | ||
t.Fatalf("unexpected error: %s", err) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
package inmemory | ||
|
||
import ( | ||
"time" | ||
|
||
go_cache "github.com/patrickmn/go-cache" | ||
"go.signoz.io/signoz/pkg/query-service/cache/status" | ||
) | ||
|
||
// cache implements the Cache interface | ||
type cache struct { | ||
cc *go_cache.Cache | ||
} | ||
|
||
// New creates a new in-memory cache | ||
func New(opts *Options) *cache { | ||
if opts == nil { | ||
opts = defaultOptions() | ||
} | ||
return &cache{cc: go_cache.New(opts.TTL, opts.CleanupInterval)} | ||
} | ||
|
||
// Connect does nothing | ||
func (c *cache) Connect() error { | ||
return nil | ||
} | ||
|
||
// Store stores the data in the cache | ||
func (c *cache) Store(cacheKey string, data []byte, ttl time.Duration) error { | ||
c.cc.Set(cacheKey, data, ttl) | ||
return nil | ||
} | ||
|
||
// Retrieve retrieves the data from the cache | ||
func (c *cache) Retrieve(cacheKey string, allowExpired bool) ([]byte, status.RetrieveStatus, error) { | ||
data, found := c.cc.Get(cacheKey) | ||
if !found { | ||
return nil, status.RetrieveStatusKeyMiss, nil | ||
} | ||
|
||
return data.([]byte), status.RetrieveStatusHit, nil | ||
} | ||
|
||
// SetTTL sets the TTL for the cache entry | ||
func (c *cache) SetTTL(cacheKey string, ttl time.Duration) { | ||
item, found := c.cc.Get(cacheKey) | ||
if !found { | ||
return | ||
} | ||
c.cc.Replace(cacheKey, item, ttl) | ||
} | ||
|
||
// Remove removes the cache entry | ||
func (c *cache) Remove(cacheKey string) { | ||
c.cc.Delete(cacheKey) | ||
} | ||
|
||
// BulkRemove removes the cache entries | ||
func (c *cache) BulkRemove(cacheKeys []string) { | ||
for _, cacheKey := range cacheKeys { | ||
c.cc.Delete(cacheKey) | ||
} | ||
} | ||
|
||
// Close does nothing | ||
func (c *cache) Close() error { | ||
return nil | ||
} | ||
|
||
// Configuration returns the cache configuration | ||
func (c *cache) Configuration() *Options { | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
package inmemory | ||
|
||
import ( | ||
"testing" | ||
"time" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"go.signoz.io/signoz/pkg/query-service/cache/status" | ||
) | ||
|
||
// TestNew tests the New function | ||
func TestNew(t *testing.T) { | ||
opts := &Options{ | ||
TTL: 10 * time.Second, | ||
CleanupInterval: 10 * time.Second, | ||
} | ||
c := New(opts) | ||
assert.NotNil(t, c) | ||
assert.NotNil(t, c.cc) | ||
} | ||
|
||
// TestConnect tests the Connect function | ||
func TestConnect(t *testing.T) { | ||
c := New(nil) | ||
assert.NoError(t, c.Connect()) | ||
} | ||
|
||
// TestStore tests the Store function | ||
func TestStore(t *testing.T) { | ||
c := New(nil) | ||
assert.NoError(t, c.Store("key", []byte("value"), 10*time.Second)) | ||
} | ||
|
||
// TestRetrieve tests the Retrieve function | ||
func TestRetrieve(t *testing.T) { | ||
c := New(nil) | ||
assert.NoError(t, c.Store("key", []byte("value"), 10*time.Second)) | ||
data, retrieveStatus, err := c.Retrieve("key", false) | ||
assert.NoError(t, err) | ||
assert.Equal(t, retrieveStatus, status.RetrieveStatusHit) | ||
assert.Equal(t, data, []byte("value")) | ||
} | ||
|
||
// TestSetTTL tests the SetTTL function | ||
func TestSetTTL(t *testing.T) { | ||
c := New(&Options{TTL: 10 * time.Second, CleanupInterval: 1 * time.Second}) | ||
assert.NoError(t, c.Store("key", []byte("value"), 2*time.Second)) | ||
time.Sleep(3 * time.Second) | ||
data, retrieveStatus, err := c.Retrieve("key", false) | ||
assert.NoError(t, err) | ||
assert.Equal(t, retrieveStatus, status.RetrieveStatusKeyMiss) | ||
assert.Nil(t, data) | ||
|
||
assert.NoError(t, c.Store("key", []byte("value"), 2*time.Second)) | ||
c.SetTTL("key", 4*time.Second) | ||
time.Sleep(3 * time.Second) | ||
data, retrieveStatus, err = c.Retrieve("key", false) | ||
assert.NoError(t, err) | ||
assert.Equal(t, retrieveStatus, status.RetrieveStatusHit) | ||
assert.Equal(t, data, []byte("value")) | ||
} | ||
|
||
// TestRemove tests the Remove function | ||
func TestRemove(t *testing.T) { | ||
c := New(nil) | ||
assert.NoError(t, c.Store("key", []byte("value"), 10*time.Second)) | ||
c.Remove("key") | ||
|
||
data, retrieveStatus, err := c.Retrieve("key", false) | ||
assert.NoError(t, err) | ||
assert.Equal(t, retrieveStatus, status.RetrieveStatusKeyMiss) | ||
assert.Nil(t, data) | ||
} | ||
|
||
// TestBulkRemove tests the BulkRemove function | ||
func TestBulkRemove(t *testing.T) { | ||
c := New(nil) | ||
assert.NoError(t, c.Store("key1", []byte("value"), 10*time.Second)) | ||
assert.NoError(t, c.Store("key2", []byte("value"), 10*time.Second)) | ||
c.BulkRemove([]string{"key1", "key2"}) | ||
|
||
data, retrieveStatus, err := c.Retrieve("key1", false) | ||
assert.NoError(t, err) | ||
assert.Equal(t, retrieveStatus, status.RetrieveStatusKeyMiss) | ||
assert.Nil(t, data) | ||
|
||
data, retrieveStatus, err = c.Retrieve("key2", false) | ||
assert.NoError(t, err) | ||
assert.Equal(t, retrieveStatus, status.RetrieveStatusKeyMiss) | ||
assert.Nil(t, data) | ||
} | ||
|
||
// TestCache tests the cache | ||
func TestCache(t *testing.T) { | ||
c := New(nil) | ||
assert.NoError(t, c.Store("key", []byte("value"), 10*time.Second)) | ||
data, retrieveStatus, err := c.Retrieve("key", false) | ||
assert.NoError(t, err) | ||
assert.Equal(t, retrieveStatus, status.RetrieveStatusHit) | ||
assert.Equal(t, data, []byte("value")) | ||
c.Remove("key") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package inmemory | ||
|
||
import ( | ||
"time" | ||
|
||
go_cache "github.com/patrickmn/go-cache" | ||
) | ||
|
||
const ( | ||
defaultTTL = go_cache.NoExpiration | ||
defaultCleanupInterval = 1 * time.Minute | ||
) | ||
|
||
// Options holds the options for the in-memory cache | ||
type Options struct { | ||
// TTL is the time to live for the cache entries | ||
TTL time.Duration `yaml:"ttl,omitempty"` | ||
CleanupInterval time.Duration `yaml:"cleanupInterval,omitempty"` | ||
} | ||
|
||
func defaultOptions() *Options { | ||
return &Options{TTL: defaultTTL, CleanupInterval: defaultCleanupInterval} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package redis | ||
|
||
const ( | ||
defaultHost = "localhost" | ||
defaultPort = 6379 | ||
defaultPassword = "" | ||
defaultDB = 0 | ||
) | ||
|
||
type Options struct { | ||
Host string `yaml:"host,omitempty"` | ||
Port int `yaml:"port,omitempty"` | ||
Password string `yaml:"password,omitempty"` | ||
DB int `yaml:"db,omitempty"` | ||
} | ||
|
||
func defaultOptions() *Options { | ||
return &Options{ | ||
Host: defaultHost, | ||
Port: defaultPort, | ||
Password: defaultPassword, | ||
DB: defaultDB, | ||
} | ||
} |
Oops, something went wrong.