diff --git a/surrealdb/README.md b/surrealdb/README.md index 698ddb3f..ec32da04 100644 --- a/surrealdb/README.md +++ b/surrealdb/README.md @@ -62,6 +62,7 @@ Password: "root", Access: "full", Scope: "all", DefaultTable: "fiber_storage", +GCInterval: time.Second * 10, }) ``` @@ -92,6 +93,9 @@ Scope string // The default table used to store key-value records DefaultTable string + +// Optional. Default is 10 * time.Second +GCInterval time.Duration } ``` @@ -108,5 +112,6 @@ Password: "root", Access: "full", Scope: "all", DefaultTable: "fiber_storage", +GCInterval: time.Second * 10, } ``` diff --git a/surrealdb/config.go b/surrealdb/config.go index 7fcf1224..7179ccbb 100644 --- a/surrealdb/config.go +++ b/surrealdb/config.go @@ -3,6 +3,7 @@ package surrealdb import ( "log" "strings" + "time" ) // Config holds the configuration required to initialize and connect to a SurrealDB instance. @@ -41,6 +42,9 @@ type Config struct { // The default table used to store key-value records DefaultTable string + + // Optional. Default is 10 * time.Second + GCInterval time.Duration } var ConfigDefault = Config{ @@ -52,6 +56,7 @@ var ConfigDefault = Config{ Access: "full", Scope: "all", DefaultTable: "fiber_storage", + GCInterval: time.Second * 10, } func configDefault(config ...Config) Config { @@ -100,5 +105,9 @@ func configDefault(config ...Config) Config { log.Printf("Warning: ConnectionString %s doesn't start with ws://, wss://, http:// or https://", cfg.ConnectionString) } + if int(cfg.GCInterval.Seconds()) <= 0 { + cfg.GCInterval = ConfigDefault.GCInterval + } + return cfg } diff --git a/surrealdb/surrealdb.go b/surrealdb/surrealdb.go index 5f5382fa..0d5f23a2 100644 --- a/surrealdb/surrealdb.go +++ b/surrealdb/surrealdb.go @@ -10,8 +10,10 @@ import ( // Storage interface that is implemented by storage providers type Storage struct { - db *surrealdb.DB - table string + db *surrealdb.DB + table string + stopGC chan struct{} + interval time.Duration } // model represents a key-value storage record used in SurrealDB. @@ -54,10 +56,15 @@ func New(config ...Config) *Storage { panic(err) } - return &Storage{ - db: db, - table: cfg.DefaultTable, + storage := &Storage{ + db: db, + table: cfg.DefaultTable, + stopGC: make(chan struct{}), + interval: cfg.GCInterval, } + + go storage.gc() + return storage } func (s *Storage) Get(key string) ([]byte, error) { @@ -113,6 +120,7 @@ func (s *Storage) Reset() error { } func (s *Storage) Close() error { + close(s.stopGC) return s.db.Close() } @@ -139,3 +147,30 @@ func (s *Storage) List() ([]byte, error) { return json.Marshal(data) } + +func (s *Storage) gc() { + ticker := time.NewTicker(s.interval) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + s.cleanupExpired() + case <-s.stopGC: + return + } + } +} + +func (s *Storage) cleanupExpired() { + records, err := surrealdb.Select[[]model, models.Table](s.db, models.Table(s.table)) + if err != nil { + return + } + now := time.Now().Unix() + for _, item := range *records { + if item.Exp > 0 && now > item.Exp { + _ = s.Delete(item.Key) + } + } +} diff --git a/surrealdb/surrealdb_test.go b/surrealdb/surrealdb_test.go index 339cb3e5..7bf353b8 100644 --- a/surrealdb/surrealdb_test.go +++ b/surrealdb/surrealdb_test.go @@ -182,6 +182,27 @@ func Test_Surrealdb_ListSkipsExpired(t *testing.T) { require.NotContains(t, result, "expired") } +func Test_Surrealdb_GarbageCollector_RemovesExpiredKeys(t *testing.T) { + testStore, err := newTestStore(t) + require.NoError(t, err) + defer testStore.Close() + + err = testStore.Set("temp_key", []byte("temp_value"), 1*time.Second) + require.NoError(t, err) + + val, err := testStore.Get("temp_key") + require.NoError(t, err) + require.NotNil(t, val) + + time.Sleep(3 * time.Second) + + require.Eventually(t, func() bool { + val, err = testStore.Get("temp_key") + require.NoError(t, err) + return val == nil + }, 3*time.Second, 300*time.Millisecond) +} + func Benchmark_SurrealDB_Set(b *testing.B) { testStore, err := newTestStore(b) require.NoError(b, err)