From da8ec5f21d5fd3c8aed5be993a7595d4a0245f3b Mon Sep 17 00:00:00 2001 From: Lukasz Raczylo Date: Wed, 3 Dec 2025 10:22:06 +0000 Subject: [PATCH] Add LRU cache support. --- README.md | 27 ++ admin_dashboard.go | 2 +- admin_dashboard_test.go | 2 + cache/cache.go | 55 +++-- cache/memory/lru_memory_cache.go | 5 + cache/memory/lru_memory_cache_test.go | 343 ++++++++++++++++++++++++++ errors.go | 109 -------- main.go | 8 + monitoring/defaults.go | 4 - struct_config.go | 1 + 10 files changed, 415 insertions(+), 141 deletions(-) create mode 100644 cache/memory/lru_memory_cache_test.go diff --git a/README.md b/README.md index 5cafd31..b766c0c 100644 --- a/README.md +++ b/README.md @@ -155,6 +155,7 @@ You can still use the non-prefixed environment variables in the spirit of the ba | `CACHE_TTL` | The cache TTL | `60` | | `CACHE_MAX_MEMORY_SIZE` | Maximum memory size for cache in MB | `100` | | `CACHE_MAX_ENTRIES` | Maximum number of entries in cache | `10000` | +| `CACHE_USE_LRU` | Use LRU eviction algorithm (see [Cache Eviction](#cache-eviction-algorithms)) | `false` | | `CACHE_PER_USER_DISABLED` | **⚠️ SECURITY**: Disable per-user cache isolation | `false` (**DO NOT** set to `true` in multi-user apps) | | `ENABLE_REDIS_CACHE` | Enable distributed Redis cache | `false` | | `CACHE_REDIS_URL` | URL to redis server / cluster endpoint | `localhost:6379` | @@ -439,6 +440,32 @@ These features ensure the cache runs efficiently even under high load and with l Since version `0.5.30` the cache is gzipped in the memory, which should optimise the memory usage quite significantly. Since version `0.15.48` the you can also use the distributed Redis cache. +#### Cache Eviction Algorithms + +The proxy supports two cache eviction strategies: + +**Standard (default):** Uses Go's `sync.Map` with approximate eviction. When memory limits are reached, entries are evicted based on iteration order (pseudo-random). This is memory-efficient and has excellent concurrent read performance. + +**LRU (Least Recently Used):** Uses a proper LRU algorithm with a linked list to track access order. When limits are reached, the least recently accessed entries are evicted first. Enable with `CACHE_USE_LRU=true`. + +| Feature | Standard | LRU | +|---------|----------|-----| +| Eviction order | Pseudo-random | Least recently used | +| Read performance | Excellent | Good | +| Memory tracking | Approximate | Precise | +| Best for | High read throughput | Cache hit optimization | + +*LRU cache configuration:* +```bash +GMP_ENABLE_GLOBAL_CACHE=true +GMP_CACHE_TTL=300 +GMP_CACHE_USE_LRU=true +GMP_CACHE_MAX_MEMORY_SIZE=200 +GMP_CACHE_MAX_ENTRIES=5000 +``` + +Use LRU when cache hit rate is critical and you want to ensure frequently accessed data stays cached. Use Standard (default) for maximum read throughput with less memory overhead. + #### Read-only endpoint You can now specify the read-only GraphQL endpoint by setting the `HOST_GRAPHQL_READONLY` environment variable. The default value is empty, preventing the proxy from using the read-only endpoint for the queries and directing all the requests to the main endpoint specified as `HOST_GRAPHQL`. If the `HOST_GRAPHQL_READONLY` is set, the proxy will use the read-only endpoint for the queries with the `query` type and the main endpoint for the `mutation` type queries. Format of the read-only endpoint is the same as `HOST_GRAPHQL` endpoint, for example `http://localhost:8080/`. diff --git a/admin_dashboard.go b/admin_dashboard.go index 7a6c2cb..a76f990 100644 --- a/admin_dashboard.go +++ b/admin_dashboard.go @@ -429,7 +429,7 @@ func (ad *AdminDashboard) getWebSocketStats(c *fiber.Ctx) error { // clearCache clears the cache func (ad *AdminDashboard) clearCache(c *fiber.Ctx) error { - // TODO: Implement cache clearing + libpack_cache.CacheClear() return c.JSON(map[string]interface{}{ "success": true, "message": "Cache cleared successfully", diff --git a/admin_dashboard_test.go b/admin_dashboard_test.go index eed7426..33ca05b 100644 --- a/admin_dashboard_test.go +++ b/admin_dashboard_test.go @@ -214,6 +214,7 @@ func TestAdminDashboard_GetCacheStats(t *testing.T) { CacheRedisEnable bool CacheMaxMemorySize int CacheMaxEntries int + CacheUseLRU bool GraphQLQueryCacheSize int PerUserCacheDisabled bool }{ @@ -221,6 +222,7 @@ func TestAdminDashboard_GetCacheStats(t *testing.T) { CacheTTL: 60, CacheMaxMemorySize: 100, CacheMaxEntries: 10000, + CacheUseLRU: false, PerUserCacheDisabled: false, }, } diff --git a/cache/cache.go b/cache/cache.go index 66ad890..a0ea50c 100644 --- a/cache/cache.go +++ b/cache/cache.go @@ -27,6 +27,7 @@ type CacheConfig struct { Memory struct { MaxMemorySize int64 `json:"max_memory_size"` // Maximum memory size in bytes MaxEntries int64 `json:"max_entries"` // Maximum number of entries + UseLRU bool `json:"use_lru"` // Use LRU eviction algorithm instead of random eviction } TTL int `json:"ttl"` IncludeUserContext bool `json:"include_user_context"` // Include user ID and role in cache key @@ -96,16 +97,6 @@ func CalculateHash(c *fiber.Ctx, userID string, userRole string) string { return strutil.Md5(cacheKeyData) } -// CalculateHashLegacy generates a cache hash using only the request body (DEPRECATED). -// This function exists for backward compatibility only and should NOT be used -// in production multi-user applications as it creates a security vulnerability -// where users can see each other's cached data. -// -// Deprecated: Use CalculateHash with user context instead. -func CalculateHashLegacy(c *fiber.Ctx) string { - return strutil.Md5(c.Body()) -} - func EnableCache(cfg *CacheConfig) { if cfg.Logger == nil { cfg.Logger = libpack_logger.New() @@ -134,34 +125,41 @@ func EnableCache(cfg *CacheConfig) { cfg.Client = libpack_cache_redis.NewCacheWrapper(redisClient, cfg.Logger) } } else { + // Calculate memory and entry limits + maxMemory := cfg.Memory.MaxMemorySize + if maxMemory <= 0 { + maxMemory = libpack_cache_memory.DefaultMaxMemorySize + } + + maxEntries := cfg.Memory.MaxEntries + if maxEntries <= 0 { + maxEntries = libpack_cache_memory.DefaultMaxCacheSize + } + + cacheType := "standard" + if cfg.Memory.UseLRU { + cacheType = "LRU" + } + cfg.Logger.Debug(&libpack_logger.LogMessage{ Message: "Using in-memory cache", Pairs: map[string]interface{}{ - "max_memory_size_bytes": cfg.Memory.MaxMemorySize, - "max_entries": cfg.Memory.MaxEntries, + "type": cacheType, + "max_memory_size_bytes": maxMemory, + "max_entries": maxEntries, }, }) - // Use memory size and entry limits if configured, otherwise use defaults - if cfg.Memory.MaxMemorySize > 0 || cfg.Memory.MaxEntries > 0 { - maxMemory := cfg.Memory.MaxMemorySize - if maxMemory <= 0 { - maxMemory = libpack_cache_memory.DefaultMaxMemorySize - } - - maxEntries := cfg.Memory.MaxEntries - if maxEntries <= 0 { - maxEntries = libpack_cache_memory.DefaultMaxCacheSize - } - + if cfg.Memory.UseLRU { + // Use LRU cache with proper eviction algorithm + cfg.Client = libpack_cache_memory.NewLRUMemoryCache(maxMemory, maxEntries) + } else { + // Use standard sync.Map-based cache cfg.Client = libpack_cache_memory.NewWithSize( time.Duration(cfg.TTL)*time.Second, maxMemory, maxEntries, ) - } else { - // Backward compatibility - cfg.Client = libpack_cache_memory.New(time.Duration(cfg.TTL) * time.Second) } } config = cfg @@ -271,6 +269,9 @@ func CacheGetQueries() int64 { } func CacheClear() { + if !IsCacheInitialized() { + return + } config.Client.Clear() cacheStats = &CacheStats{} } diff --git a/cache/memory/lru_memory_cache.go b/cache/memory/lru_memory_cache.go index 12371d9..f8e4c75 100644 --- a/cache/memory/lru_memory_cache.go +++ b/cache/memory/lru_memory_cache.go @@ -279,3 +279,8 @@ func (c *LRUMemoryCache) GetMemoryUsage() int64 { func (c *LRUMemoryCache) GetMaxMemorySize() int64 { return c.maxMemorySize } + +// CountQueries returns the number of entries in the cache +func (c *LRUMemoryCache) CountQueries() int64 { + return atomic.LoadInt64(&c.currentCount) +} diff --git a/cache/memory/lru_memory_cache_test.go b/cache/memory/lru_memory_cache_test.go new file mode 100644 index 0000000..011e2f6 --- /dev/null +++ b/cache/memory/lru_memory_cache_test.go @@ -0,0 +1,343 @@ +package libpack_cache_memory + +import ( + "fmt" + "sync" + "testing" + "time" + + "github.com/stretchr/testify/suite" +) + +type LRUMemoryCacheTestSuite struct { + suite.Suite +} + +func TestLRUMemoryCacheTestSuite(t *testing.T) { + suite.Run(t, new(LRUMemoryCacheTestSuite)) +} + +func (suite *LRUMemoryCacheTestSuite) TestNewLRUMemoryCache() { + cache := NewLRUMemoryCache(1024*1024, 100) // 1MB, 100 entries + suite.NotNil(cache) + suite.Equal(int64(0), cache.CountQueries()) + suite.Equal(int64(0), cache.GetMemoryUsage()) + suite.Equal(int64(1024*1024), cache.GetMaxMemorySize()) +} + +func (suite *LRUMemoryCacheTestSuite) TestSetAndGet() { + cache := NewLRUMemoryCache(1024*1024, 100) + + // Set a value + cache.Set("key1", []byte("value1"), 5*time.Second) + + // Get the value + val, found := cache.Get("key1") + suite.True(found) + suite.Equal([]byte("value1"), val) + + // Get non-existent key + val, found = cache.Get("nonexistent") + suite.False(found) + suite.Nil(val) +} + +func (suite *LRUMemoryCacheTestSuite) TestUpdateExisting() { + cache := NewLRUMemoryCache(1024*1024, 100) + + cache.Set("key1", []byte("value1"), 5*time.Second) + cache.Set("key1", []byte("value2"), 5*time.Second) + + val, found := cache.Get("key1") + suite.True(found) + suite.Equal([]byte("value2"), val) + suite.Equal(int64(1), cache.CountQueries()) +} + +func (suite *LRUMemoryCacheTestSuite) TestDelete() { + cache := NewLRUMemoryCache(1024*1024, 100) + + cache.Set("key1", []byte("value1"), 5*time.Second) + suite.Equal(int64(1), cache.CountQueries()) + + cache.Delete("key1") + suite.Equal(int64(0), cache.CountQueries()) + + val, found := cache.Get("key1") + suite.False(found) + suite.Nil(val) + + // Delete non-existent key should not panic + cache.Delete("nonexistent") +} + +func (suite *LRUMemoryCacheTestSuite) TestClear() { + cache := NewLRUMemoryCache(1024*1024, 100) + + cache.Set("key1", []byte("value1"), 5*time.Second) + cache.Set("key2", []byte("value2"), 5*time.Second) + cache.Set("key3", []byte("value3"), 5*time.Second) + suite.Equal(int64(3), cache.CountQueries()) + + cache.Clear() + suite.Equal(int64(0), cache.CountQueries()) + suite.Equal(int64(0), cache.GetMemoryUsage()) + + _, found := cache.Get("key1") + suite.False(found) +} + +func (suite *LRUMemoryCacheTestSuite) TestExpiration() { + cache := NewLRUMemoryCache(1024*1024, 100) + + cache.Set("key1", []byte("value1"), 100*time.Millisecond) + + // Should exist immediately + val, found := cache.Get("key1") + suite.True(found) + suite.Equal([]byte("value1"), val) + + // Wait for expiration + time.Sleep(150 * time.Millisecond) + + // Should be expired + val, found = cache.Get("key1") + suite.False(found) + suite.Nil(val) +} + +func (suite *LRUMemoryCacheTestSuite) TestEvictionByCount() { + cache := NewLRUMemoryCache(1024*1024, 3) // Max 3 entries + + cache.Set("key1", []byte("value1"), 5*time.Second) + cache.Set("key2", []byte("value2"), 5*time.Second) + cache.Set("key3", []byte("value3"), 5*time.Second) + + // All 3 should exist + _, found := cache.Get("key1") + suite.True(found) + _, found = cache.Get("key2") + suite.True(found) + _, found = cache.Get("key3") + suite.True(found) + + // Add 4th entry - should evict oldest (key1) + cache.Set("key4", []byte("value4"), 5*time.Second) + + suite.Equal(int64(3), cache.CountQueries()) + + // key1 should be evicted (it was least recently used) + _, found = cache.Get("key1") + suite.False(found) + + // Others should still exist + _, found = cache.Get("key2") + suite.True(found) + _, found = cache.Get("key3") + suite.True(found) + _, found = cache.Get("key4") + suite.True(found) +} + +func (suite *LRUMemoryCacheTestSuite) TestLRUOrder() { + cache := NewLRUMemoryCache(1024*1024, 3) // Max 3 entries + + cache.Set("key1", []byte("value1"), 5*time.Second) + cache.Set("key2", []byte("value2"), 5*time.Second) + cache.Set("key3", []byte("value3"), 5*time.Second) + + // Access key1 to make it recently used + cache.Get("key1") + + // Add key4 - should evict key2 (now least recently used) + cache.Set("key4", []byte("value4"), 5*time.Second) + + // key2 should be evicted + _, found := cache.Get("key2") + suite.False(found) + + // key1 should still exist (was accessed recently) + _, found = cache.Get("key1") + suite.True(found) +} + +func (suite *LRUMemoryCacheTestSuite) TestEvictionByMemory() { + // Small memory limit - 500 bytes + cache := NewLRUMemoryCache(500, 100) + + // Each entry has ~64 bytes overhead + key + value + cache.Set("key1", []byte("value1"), 5*time.Second) + cache.Set("key2", []byte("value2"), 5*time.Second) + cache.Set("key3", []byte("value3"), 5*time.Second) + + // Add large entry that should trigger eviction + largeValue := make([]byte, 200) + cache.Set("large", largeValue, 5*time.Second) + + // Memory should be under limit + suite.LessOrEqual(cache.GetMemoryUsage(), int64(500)) +} + +func (suite *LRUMemoryCacheTestSuite) TestCompression() { + cache := NewLRUMemoryCache(1024*1024, 100) + + // Create a compressible value (> 1KB to trigger compression) + largeValue := make([]byte, 2048) + for i := range largeValue { + largeValue[i] = 'A' // Highly compressible + } + + cache.Set("compressed", largeValue, 5*time.Second) + + // Should be able to retrieve it correctly + val, found := cache.Get("compressed") + suite.True(found) + suite.Equal(largeValue, val) +} + +func (suite *LRUMemoryCacheTestSuite) TestGetStats() { + cache := NewLRUMemoryCache(1024*1024, 100) + + cache.Set("key1", []byte("value1"), 5*time.Second) + cache.Set("key2", []byte("value2"), 5*time.Second) + + stats := cache.GetStats() + suite.Equal(int64(2), stats["entries"]) + suite.Equal(int64(1024*1024), stats["max_memory"]) + suite.Equal(int64(100), stats["max_entries"]) + suite.NotNil(stats["memory_bytes"]) + suite.NotNil(stats["fill_percent"]) +} + +func (suite *LRUMemoryCacheTestSuite) TestConcurrentAccess() { + cache := NewLRUMemoryCache(10*1024*1024, 1000) + const numGoroutines = 50 + const numOperations = 500 + + var wg sync.WaitGroup + wg.Add(numGoroutines * 3) // readers, writers, deleters + + // Writers + for i := 0; i < numGoroutines; i++ { + go func(id int) { + defer wg.Done() + for j := 0; j < numOperations; j++ { + key := fmt.Sprintf("key-%d-%d", id, j) + value := []byte(fmt.Sprintf("value-%d-%d", id, j)) + cache.Set(key, value, 5*time.Second) + } + }(i) + } + + // Readers + for i := 0; i < numGoroutines; i++ { + go func(id int) { + defer wg.Done() + for j := 0; j < numOperations; j++ { + key := fmt.Sprintf("key-%d-%d", id, j) + cache.Get(key) + } + }(i) + } + + // Deleters + for i := 0; i < numGoroutines; i++ { + go func(id int) { + defer wg.Done() + for j := 0; j < numOperations; j++ { + key := fmt.Sprintf("key-%d-%d", id, j%100) + cache.Delete(key) + } + }(i) + } + + wg.Wait() +} + +func (suite *LRUMemoryCacheTestSuite) TestCleanExpiredEntries() { + cache := NewLRUMemoryCache(1024*1024, 100) + + cache.Set("expire1", []byte("value1"), 50*time.Millisecond) + cache.Set("expire2", []byte("value2"), 50*time.Millisecond) + cache.Set("keep", []byte("value3"), 5*time.Second) + + suite.Equal(int64(3), cache.CountQueries()) + + // Wait for some to expire + time.Sleep(100 * time.Millisecond) + + // Clean expired entries + cache.CleanExpiredEntries() + + // Only "keep" should remain + suite.Equal(int64(1), cache.CountQueries()) + + _, found := cache.Get("keep") + suite.True(found) +} + +func (suite *LRUMemoryCacheTestSuite) TestCountQueries() { + cache := NewLRUMemoryCache(1024*1024, 100) + + suite.Equal(int64(0), cache.CountQueries()) + + cache.Set("key1", []byte("value1"), 5*time.Second) + suite.Equal(int64(1), cache.CountQueries()) + + cache.Set("key2", []byte("value2"), 5*time.Second) + suite.Equal(int64(2), cache.CountQueries()) + + cache.Delete("key1") + suite.Equal(int64(1), cache.CountQueries()) + + cache.Clear() + suite.Equal(int64(0), cache.CountQueries()) +} + +// Benchmarks + +func BenchmarkLRUMemoryCacheSet(b *testing.B) { + cache := NewLRUMemoryCache(100*1024*1024, 100000) + value := []byte("benchmark-value") + + b.ResetTimer() + for i := 0; i < b.N; i++ { + key := fmt.Sprintf("key-%d", i) + cache.Set(key, value, 5*time.Second) + } +} + +func BenchmarkLRUMemoryCacheGet(b *testing.B) { + cache := NewLRUMemoryCache(100*1024*1024, 100000) + value := []byte("benchmark-value") + + // Pre-populate + for i := 0; i < 10000; i++ { + key := fmt.Sprintf("key-%d", i) + cache.Set(key, value, 5*time.Minute) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + key := fmt.Sprintf("key-%d", i%10000) + cache.Get(key) + } +} + +func BenchmarkLRUMemoryCacheConcurrent(b *testing.B) { + cache := NewLRUMemoryCache(100*1024*1024, 100000) + value := []byte("benchmark-value") + + b.RunParallel(func(pb *testing.PB) { + i := 0 + for pb.Next() { + key := fmt.Sprintf("key-%d", i) + if i%2 == 0 { + cache.Set(key, value, 5*time.Second) + } else { + cache.Get(key) + } + i++ + } + }) +} diff --git a/errors.go b/errors.go index b47c038..7eb0c8d 100644 --- a/errors.go +++ b/errors.go @@ -106,117 +106,8 @@ func (e *ProxyError) WithMetadata(key string, value interface{}) *ProxyError { return e } -// Common error constructors - -// NewConnectionError creates a connection-related error -func NewConnectionError(err error) *ProxyError { - code := ErrCodeConnectionRefused - if err != nil { - errStr := err.Error() - if contains(errStr, "reset") { - code = ErrCodeConnectionReset - } - } - - return NewProxyError(code, "Failed to connect to backend", 502, true). - WithCause(err) -} - -// NewTimeoutError creates a timeout error -func NewTimeoutError(err error) *ProxyError { - return NewProxyError(ErrCodeTimeout, "Request timed out", 504, false). - WithCause(err) -} - -// NewCircuitOpenError creates a circuit breaker open error -func NewCircuitOpenError() *ProxyError { - return NewProxyError(ErrCodeCircuitOpen, "Service temporarily unavailable due to circuit breaker", 503, false). - WithDetails("The backend service is currently experiencing issues. Please try again later.") -} - -// NewRateLimitError creates a rate limit error -func NewRateLimitError(userID, role string) *ProxyError { - return NewProxyError(ErrCodeRateLimited, "Rate limit exceeded", 429, false). - WithDetails("You have exceeded the rate limit for your role"). - WithMetadata("user_id", userID). - WithMetadata("role", role) -} - -// NewBackendError creates a backend error from status code -func NewBackendError(statusCode int, body string) *ProxyError { - code := ErrCodeBackendError - message := "Backend returned an error" - retryable := false - - switch { - case statusCode == 429: - code = ErrCodeRateLimited - message = "Backend rate limit exceeded" - retryable = true - case statusCode == 503: - code = ErrCodeServiceUnavailable - message = "Backend service unavailable" - retryable = true - case statusCode == 502 || statusCode == 504: - code = ErrCodeBadGateway - message = "Bad gateway" - retryable = true - case statusCode >= 500: - code = ErrCodeBackendError - message = "Backend server error" - retryable = true - case statusCode == 404: - code = ErrCodeNotFound - message = "Resource not found" - case statusCode == 403: - code = ErrCodeForbidden - message = "Access forbidden" - case statusCode == 401: - code = ErrCodeUnauthorized - message = "Unauthorized" - case statusCode >= 400: - code = ErrCodeInvalidRequest - message = "Invalid request" - } - - return NewProxyError(code, message, statusCode, retryable). - WithMetadata("backend_status", statusCode). - WithMetadata("backend_body", truncateString(body, 500)) -} - -// NewInvalidResponseError creates an invalid response error -func NewInvalidResponseError(details string) *ProxyError { - return NewProxyError(ErrCodeInvalidResponse, "Backend returned invalid response", 502, false). - WithDetails(details) -} - -// NewInternalError creates an internal error -func NewInternalError(err error) *ProxyError { - return NewProxyError(ErrCodeInternalError, "Internal proxy error", 500, false). - WithCause(err) -} - -// NewContextCanceledError creates a context canceled error -func NewContextCanceledError() *ProxyError { - return NewProxyError(ErrCodeContextCanceled, "Request canceled", 499, false). - WithDetails("The request was canceled by the client") -} - // Helper functions -func contains(s, substr string) bool { - return len(s) > 0 && len(substr) > 0 && len(s) >= len(substr) && (s == substr || len(s) > len(substr) && (s[:len(substr)] == substr || s[len(s)-len(substr):] == substr || containsMiddle(s, substr))) -} - -func containsMiddle(s, substr string) bool { - for i := 0; i <= len(s)-len(substr); i++ { - if s[i:i+len(substr)] == substr { - return true - } - } - return false -} - func truncateString(s string, maxLen int) string { if len(s) <= maxLen { return s diff --git a/main.go b/main.go index 7406f55..824daf5 100644 --- a/main.go +++ b/main.go @@ -131,6 +131,7 @@ func parseConfig() { c.Cache.CacheTTL = getDetailsFromEnv("CACHE_TTL", 60) c.Cache.CacheMaxMemorySize = getDetailsFromEnv("CACHE_MAX_MEMORY_SIZE", 100) // Default 100MB c.Cache.CacheMaxEntries = getDetailsFromEnv("CACHE_MAX_ENTRIES", 10000) // Default 10000 entries + c.Cache.CacheUseLRU = getDetailsFromEnv("CACHE_USE_LRU", false) // Use LRU eviction algorithm // GraphQL query parsing cache - auto-calculate based on CPU cores if not set c.Cache.GraphQLQueryCacheSize = getDetailsFromEnv("GRAPHQL_QUERY_CACHE_SIZE", runtime.GOMAXPROCS(0)*250) @@ -390,9 +391,16 @@ func parseConfig() { // Memory cache configurations cacheConfig.Memory.MaxMemorySize = int64(cfg.Cache.CacheMaxMemorySize) * 1024 * 1024 // Convert MB to bytes cacheConfig.Memory.MaxEntries = int64(cfg.Cache.CacheMaxEntries) + cacheConfig.Memory.UseLRU = cfg.Cache.CacheUseLRU + + cacheType := "standard" + if cfg.Cache.CacheUseLRU { + cacheType = "LRU" + } cfg.Logger.Info(&libpack_logging.LogMessage{ Message: "Configuring memory cache with limits", Pairs: map[string]interface{}{ + "type": cacheType, "max_memory_mb": cfg.Cache.CacheMaxMemorySize, "max_entries": cfg.Cache.CacheMaxEntries, }, diff --git a/monitoring/defaults.go b/monitoring/defaults.go index f0e7ee7..08ecdb8 100644 --- a/monitoring/defaults.go +++ b/monitoring/defaults.go @@ -9,7 +9,3 @@ func (ms *MetricsSetup) RegisterDefaultMetrics() { ms.RegisterMetricsCounter(MetricsCacheMiss, nil) ms.RegisterMetricsCounter(MetricsQueriesCached, nil) } - -func (ms *MetricsSetup) RegisterGoMetrics() { - // TODO: metrics.WriteProcessMetrics(ms.metrics_set) -} diff --git a/struct_config.go b/struct_config.go index 78ce1fc..657009e 100644 --- a/struct_config.go +++ b/struct_config.go @@ -44,6 +44,7 @@ type config struct { CacheRedisEnable bool CacheMaxMemorySize int CacheMaxEntries int + CacheUseLRU bool // Use LRU eviction algorithm instead of random eviction GraphQLQueryCacheSize int // Max number of parsed GraphQL queries to cache PerUserCacheDisabled bool // Disable per-user cache isolation (SECURITY RISK - not recommended) }