Skip to content

Commit

Permalink
fix(redis): Correctly set expiry in seconds, drop usage of deprecated…
Browse files Browse the repository at this point in the history
… `SETEX` (#48)

Previously the expiration was not properly set in seconds, therefore keys basically never expired.
Replaced the SETEX with SET and PX argument since SETEX is deprecated. Therefore we can now have expiries even in milliseconds.

Covered expiration with tests for all backends.
  • Loading branch information
carstendietrich authored Oct 4, 2023
1 parent a649854 commit 0eacac0
Show file tree
Hide file tree
Showing 5 changed files with 36 additions and 7 deletions.
11 changes: 11 additions & 0 deletions backendTestCase_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ func NewBackendTestCase(t *testing.T, backend httpcache.Backend, tagsInResult bo
func (tc *BackendTestCase) RunTests() {
tc.testSetGetPurge()

tc.testSetTTL()

tc.testFlush()

if _, ok := tc.backend.(httpcache.TagSupporting); ok {
Expand Down Expand Up @@ -178,3 +180,12 @@ func (tc *BackendTestCase) buildEntry(content string, tags []string) httpcache.E
Body: []byte(content),
}
}

func (tc *BackendTestCase) testSetTTL() {
entry := tc.buildEntry("expires quickly", nil)
entry.Meta.GraceTime = time.Now().Add(200 * time.Millisecond)
entry.Meta.LifeTime = entry.Meta.GraceTime
tc.setEntry("EXPIRED_ENTRY", entry)
time.Sleep(300 * time.Millisecond)
tc.shouldNotExist("EXPIRED_ENTRY")
}
19 changes: 17 additions & 2 deletions inMemoryBackend.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ import (
lru "github.com/hashicorp/golang-lru/v2"
)

const lurkerPeriod = 1 * time.Minute
const defaultLurkerPeriod = 1 * time.Minute

type (
// MemoryBackend implements the cache backend interface with an "in memory" solution
MemoryBackend struct {
cacheMetrics Metrics
pool *lru.TwoQueueCache[string, inMemoryCacheEntry]
lurkerPeriod time.Duration
}

// MemoryBackendConfig config
Expand All @@ -25,6 +26,7 @@ type (
InMemoryBackendFactory struct {
config MemoryBackendConfig
frontendName string
lurkerPeriod time.Duration
}

inMemoryCacheEntry struct {
Expand All @@ -41,6 +43,12 @@ func (f *InMemoryBackendFactory) SetConfig(config MemoryBackendConfig) *InMemory
return f
}

// SetLurkerPeriod sets the timeframe how often expired cache entries should be checked/cleaned up, if 0 is provided the default period of 1 minute is taken
func (f *InMemoryBackendFactory) SetLurkerPeriod(period time.Duration) *InMemoryBackendFactory {
f.lurkerPeriod = period
return f
}

// SetFrontendName used in Metrics
func (f *InMemoryBackendFactory) SetFrontendName(frontendName string) *InMemoryBackendFactory {
f.frontendName = frontendName
Expand All @@ -51,10 +59,17 @@ func (f *InMemoryBackendFactory) SetFrontendName(frontendName string) *InMemoryB
func (f *InMemoryBackendFactory) Build() (Backend, error) {
cache, _ := lru.New2Q[string, inMemoryCacheEntry](f.config.Size)

lurkerPeriod := defaultLurkerPeriod
if f.lurkerPeriod > 0 {
lurkerPeriod = f.lurkerPeriod
}

memoryBackend := &MemoryBackend{
pool: cache,
cacheMetrics: NewCacheMetrics("memory", f.frontendName),
lurkerPeriod: lurkerPeriod,
}

go memoryBackend.lurker()

return memoryBackend, nil
Expand Down Expand Up @@ -115,7 +130,7 @@ func (m *MemoryBackend) Flush() error {
}

func (m *MemoryBackend) lurker() {
for range time.Tick(lurkerPeriod) {
for range time.Tick(m.lurkerPeriod) {
m.cacheMetrics.recordEntries(int64(m.pool.Len()))

for _, key := range m.pool.Keys() {
Expand Down
3 changes: 2 additions & 1 deletion inMemoryBackend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package httpcache_test

import (
"testing"
"time"

"flamingo.me/httpcache"
)
Expand All @@ -10,7 +11,7 @@ func Test_RunDefaultBackendTestCase_InMemoryBackend(t *testing.T) {
t.Parallel()

f := httpcache.InMemoryBackendFactory{}
backend, _ := f.SetConfig(httpcache.MemoryBackendConfig{Size: 100}).SetFrontendName("default").Build()
backend, _ := f.SetConfig(httpcache.MemoryBackendConfig{Size: 100}).SetFrontendName("default").SetLurkerPeriod(100 * time.Millisecond).Build()

testCase := NewBackendTestCase(t, backend, true)
testCase.RunTests()
Expand Down
7 changes: 4 additions & 3 deletions redisBackend.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,16 +217,17 @@ func (b *RedisBackend) Set(key string, entry Entry) error {
}

err = conn.Send(
"SETEX",
"SET",
b.createPrefixedKey(key, valuePrefix),
int(entry.Meta.GraceTime.Sub(time.Now().Round(time.Second))),
buffer,
"PX",
time.Until(entry.Meta.GraceTime).Round(time.Millisecond).Milliseconds(),
)
if err != nil {
b.cacheMetrics.countError("SetFailed")
b.logger.Error(fmt.Sprintf("Error setting key %q with timeout %v and buffer %v", key, entry.Meta.GraceTime, buffer))

return fmt.Errorf("redis SETEX failed: %w", err)
return fmt.Errorf("redis SET PX failed: %w", err)
}

for _, tag := range entry.Meta.Tags {
Expand Down
3 changes: 2 additions & 1 deletion twoLevelBackend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package httpcache_test

import (
"testing"
"time"

"flamingo.me/flamingo/v3/framework/flamingo"
"github.com/stretchr/testify/assert"
Expand All @@ -12,7 +13,7 @@ import (
func createInMemoryBackend() httpcache.Backend {
return func() httpcache.Backend {
f := httpcache.InMemoryBackendFactory{}
backend, _ := f.SetConfig(httpcache.MemoryBackendConfig{Size: 100}).SetFrontendName("default").Build()
backend, _ := f.SetConfig(httpcache.MemoryBackendConfig{Size: 100}).SetFrontendName("default").SetLurkerPeriod(100 * time.Millisecond).Build()

return backend
}()
Expand Down

0 comments on commit 0eacac0

Please sign in to comment.