Improve cache mechanism using sync map

This commit is contained in:
2023-10-13 15:37:57 +01:00
parent 7293583a99
commit 089d05b7c3
2 changed files with 45 additions and 23 deletions
+1 -1
View File
@@ -11,7 +11,7 @@ help: ## display this help
.PHONY: run .PHONY: run
run: build ## run application run: build ## run application
@LOG_LEVEL=debug BLOCK_SCHEMA_INTROSPECTION=false JWT_ROLE_RATE_LIMIT=false JWT_ROLE_CLAIM_PATH="Hasura.x-hasura-default-role" JWT_USER_CLAIM_PATH="Hasura.x-hasura-user-id" HOST_GRAPHQL=https://hasura8.lan/ ./graphql-proxy @LOG_LEVEL=debug BLOCK_SCHEMA_INTROSPECTION=false CACHE_TTL=10 JWT_ROLE_RATE_LIMIT=false JWT_ROLE_CLAIM_PATH="Hasura.x-hasura-default-role" JWT_USER_CLAIM_PATH="Hasura.x-hasura-user-id" HOST_GRAPHQL=https://hasura8.lan/ ./graphql-proxy
.PHONY: build .PHONY: build
build: ## build the binary build: ## build the binary
+44 -22
View File
@@ -12,16 +12,21 @@ type CacheEntry struct {
type Cache struct { type Cache struct {
sync.RWMutex sync.RWMutex
entries map[string]CacheEntry entries sync.Map
globalTTL time.Duration globalTTL time.Duration
bytePool sync.Pool
} }
func New(globalTTL time.Duration) *Cache { func New(globalTTL time.Duration) *Cache {
cache := &Cache{ cache := &Cache{
entries: make(map[string]CacheEntry),
globalTTL: globalTTL, globalTTL: globalTTL,
} }
// Initialize the byte pool.
cache.bytePool.New = func() interface{} {
return make([]byte, 0)
}
// Start the cache cleanup. // Start the cache cleanup.
go cache.cleanupRoutine(globalTTL) go cache.cleanupRoutine(globalTTL)
return cache return cache
@@ -46,47 +51,64 @@ func (c *Cache) Set(key string, value []byte, ttl time.Duration) {
expiresAt = now.Add(c.globalTTL) expiresAt = now.Add(c.globalTTL)
} }
c.entries[key] = CacheEntry{ // Allocate a byte slice from the pool.
Value: value, entry := CacheEntry{
Value: c.bytePool.Get().([]byte),
ExpiresAt: expiresAt, ExpiresAt: expiresAt,
} }
// Copy the value into the byte slice.
copy(entry.Value, value)
// Set the entry in the cache.
c.entries.Store(key, entry)
} }
func (c *Cache) Get(key string) ([]byte, bool) { func (c *Cache) Get(key string) ([]byte, bool) {
c.RLock() c.RLock()
defer c.RUnlock() defer c.RUnlock()
entry, ok := c.entries[key] entry, ok := c.entries.Load(key)
if !ok || entry.ExpiresAt.Before(time.Now()) { if !ok || entry.(CacheEntry).ExpiresAt.Before(time.Now()) {
return nil, false return nil, false
} }
return entry.Value, true // Copy the value from the byte slice.
value := make([]byte, len(entry.(CacheEntry).Value))
copy(value, entry.(CacheEntry).Value)
return value, true
} }
func (c *Cache) Delete(key string) { func (c *Cache) Delete(key string) {
c.Lock() c.Lock()
defer c.Unlock() defer c.Unlock()
delete(c.entries, key) entry, ok := c.entries.Load(key)
if !ok {
return
}
// Return the byte slice to the pool.
c.bytePool.Put(entry.(CacheEntry).Value)
// Delete the entry from the cache.
c.entries.Delete(key)
} }
func (c *Cache) CleanExpiredEntries() { func (c *Cache) CleanExpiredEntries() {
now := time.Now() now := time.Now()
toDelete := make([]string, 0) c.entries.Range(func(key, value interface{}) bool {
entry := value.(CacheEntry)
c.RLock()
for key, entry := range c.entries {
if entry.ExpiresAt.Before(now) { if entry.ExpiresAt.Before(now) {
toDelete = append(toDelete, key) // Return the byte slice to the pool.
} c.bytePool.Put(entry.Value)
}
c.RUnlock()
// Separate the deletion to its own critical section to reduce lock contention. // Delete the entry from the cache.
c.Lock() c.entries.Delete(key)
for _, key := range toDelete { }
delete(c.entries, key)
} // Return true to continue iterating over the map.
c.Unlock() return true
})
} }