summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--conn.go62
-rw-r--r--conn_test.go145
-rw-r--r--encoding_test.go2
-rw-r--r--evict.go27
-rw-r--r--examples/basic_usage/main.go42
-rw-r--r--examples/eviction_policy/main.go39
-rw-r--r--store.go27
-rw-r--r--store_test.go66
8 files changed, 377 insertions, 33 deletions
diff --git a/conn.go b/conn.go
index 065bf7a..911c2bc 100644
--- a/conn.go
+++ b/conn.go
@@ -11,6 +11,7 @@ import (
"github.com/vmihailenco/msgpack/v5"
)
+ // db represents a cache database with file-backed storage and in-memory operation.
type db struct {
File io.WriteSeeker
Store store
@@ -18,8 +19,10 @@ type db struct {
wg sync.WaitGroup
}
+ // Option is a function type for configuring the db.
type Option func(*db) error
+ // openFile opens a file-backed cache database with the given options.
func openFile(filename string, options ...Option) (*db, error) {
ret, err := openMem(options...)
if err != nil {
@@ -47,6 +50,7 @@ func openFile(filename string, options ...Option) (*db, error) {
return ret, nil
}
+ // openMem initializes an in-memory cache database with the given options.
func openMem(options ...Option) (*db, error) {
ret := &db{}
ret.Store.Init()
@@ -54,12 +58,14 @@ func openMem(options ...Option) (*db, error) {
return ret, nil
}
+ // Start begins the background worker for periodic tasks.
func (d *db) Start() {
d.Stop = make(chan struct{})
d.wg.Add(1)
go d.backgroundWorker()
}
+ // SetConfig applies configuration options to the db.
func (d *db) SetConfig(options ...Option) error {
d.Store.mu.Lock()
defer d.Store.mu.Unlock()
@@ -72,12 +78,14 @@ func (d *db) SetConfig(options ...Option) error {
return nil
}
+ // WithPolicy sets the eviction policy for the cache.
func WithPolicy(e EvictionPolicyType) Option {
return func(d *db) error {
return d.Store.Policy.SetPolicy(e)
}
}
+ // WithMaxCost sets the maximum cost for the cache.
func WithMaxCost(maxCost uint64) Option {
return func(d *db) error {
d.Store.MaxCost = maxCost
@@ -85,6 +93,7 @@ func WithMaxCost(maxCost uint64) Option {
}
}
+ // SetSnapshotTime sets the interval for taking snapshots of the cache.
func SetSnapshotTime(t time.Duration) Option {
return func(d *db) error {
d.Store.SnapshotTicker.Reset(t)
@@ -92,6 +101,7 @@ func SetSnapshotTime(t time.Duration) Option {
}
}
+ // SetCleanupTime sets the interval for cleaning up expired entries.
func SetCleanupTime(t time.Duration) Option {
return func(d *db) error {
d.Store.CleanupTicker.Reset(t)
@@ -99,6 +109,7 @@ func SetCleanupTime(t time.Duration) Option {
}
}
+ // backgroundWorker performs periodic tasks such as snapshotting and cleanup.
func (d *db) backgroundWorker() {
defer d.wg.Done()
@@ -121,6 +132,7 @@ func (d *db) backgroundWorker() {
}
}
+ // Close stops the background worker and cleans up resources.
func (d *db) Close() {
close(d.Stop)
d.wg.Wait()
@@ -134,6 +146,7 @@ func (d *db) Close() {
}
}
+ // Flush writes the current state of the store to the file.
func (d *db) Flush() error {
if d.File != nil {
return d.Store.Snapshot(d.File)
@@ -141,17 +154,20 @@ func (d *db) Flush() error {
return nil
}
+ // Clear removes all entries from the in-memory store.
func (d *db) Clear() {
d.Store.Clear()
}
-var ErrKeyNotFound = errors.New("key not found")
+var ErrKeyNotFound = errors.New("key not found") // ErrKeyNotFound is returned when a key is not found in the cache.
// The Cache database. Can be initialized by either OpenFile or OpenMem. Uses per DB Locks.
+ // DB represents a generic cache database with key-value pairs.
type DB[K any, V any] struct {
*db
}
+ // OpenFile opens a file-backed cache database with the specified options.
func OpenFile[K any, V any](filename string, options ...Option) (DB[K, V], error) {
ret, err := openFile(filename, options...)
if err != nil {
@@ -161,7 +177,8 @@ func OpenFile[K any, V any](filename string, options ...Option) (DB[K, V], error
return DB[K, V]{db: ret}, nil
}
-func OpenMem[K any, V any](filename string, options ...Option) (DB[K, V], error) {
+ // OpenMem initializes an in-memory cache database with the specified options.
+func OpenMem[K any, V any](options ...Option) (DB[K, V], error) {
ret, err := openMem(options...)
if err != nil {
return zero[DB[K, V]](), err
@@ -170,33 +187,49 @@ func OpenMem[K any, V any](filename string, options ...Option) (DB[K, V], error)
return DB[K, V]{db: ret}, nil
}
-func (h *DB[K, V]) Get(key K, value V) (V, time.Duration, error) {
- keyData, err := msgpack.Marshal(key)
+ // marshal serializes a value using msgpack.
+func marshal[T any](v T) ([]byte, error) {
+ return msgpack.Marshal(v)
+}
+
+ // unmarshal deserializes data into a value using msgpack.
+func unmarshal[T any](data []byte, v *T) error {
+ return msgpack.Unmarshal(data, v)
+}
+
+ // Get retrieves a value from the cache by key and returns its TTL.
+func (h *DB[K, V]) Get(key K, value *V) (time.Duration, error) {
+ keyData, err := marshal(key)
if err != nil {
- return value, 0, err
+ return 0, err
}
v, ttl, ok := h.Store.Get(keyData)
if !ok {
- return value, 0, ErrKeyNotFound
+ return 0, ErrKeyNotFound
}
-
- if err = msgpack.Unmarshal(v, value); err != nil {
- return value, 0, err
+ if v != nil {
+ if err = unmarshal(v, value); err != nil {
+ return 0, err
+ }
}
- return value, ttl, err
+ return ttl, err
}
+ // GetValue retrieves a value from the cache by key and returns the value and its TTL.
func (h *DB[K, V]) GetValue(key K) (V, time.Duration, error) {
- return h.Get(key, zero[V]())
+ value := zero[V]()
+ ttl, err := h.Get(key, &value)
+ return value, ttl, err
}
+ // Set adds a key-value pair to the cache with a specified TTL.
func (h *DB[K, V]) Set(key K, value V, ttl time.Duration) error {
- keyData, err := msgpack.Marshal(key)
+ keyData, err := marshal(key)
if err != nil {
return err
}
- valueData, err := msgpack.Marshal(value)
+ valueData, err := marshal(value)
if err != nil {
return err
}
@@ -204,8 +237,9 @@ func (h *DB[K, V]) Set(key K, value V, ttl time.Duration) error {
return nil
}
+ // Delete removes a key-value pair from the cache.
func (h *DB[K, V]) Delete(key K) error {
- keyData, err := msgpack.Marshal(key)
+ keyData, err := marshal(key)
if err != nil {
return err
}
diff --git a/conn_test.go b/conn_test.go
new file mode 100644
index 0000000..d1581f3
--- /dev/null
+++ b/conn_test.go
@@ -0,0 +1,145 @@
+package cache
+
+import (
+ "fmt"
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func setupTestDB[K any, V any](t testing.TB) *DB[K, V] {
+ t.Helper()
+
+ db, err := OpenMem[K, V]()
+ assert.NoError(t, err)
+ t.Cleanup(func() {
+ db.Close()
+ })
+ return &db
+}
+
+func TestDBGetSet(t *testing.T) {
+ t.Parallel()
+
+ t.Run("Exists", func(t *testing.T) {
+ t.Parallel()
+
+ db := setupTestDB[string, string](t)
+
+ want := "Value"
+ err := db.Set("Key", want, 1*time.Hour)
+ assert.NoError(t, err)
+
+ got, ttl, err := db.GetValue("Key")
+ assert.NoError(t, err)
+ assert.Equal(t, want, got)
+
+ now := time.Now()
+ assert.WithinDuration(t, now.Add(ttl), now.Add(1*time.Hour), 1*time.Millisecond)
+ })
+
+ t.Run("Not Exists", func(t *testing.T) {
+ t.Parallel()
+
+ db := setupTestDB[string, string](t)
+
+ _, _, err := db.GetValue("Key")
+ assert.ErrorIs(t, err, ErrKeyNotFound)
+ })
+
+ t.Run("Update", func(t *testing.T) {
+ t.Parallel()
+
+ db := setupTestDB[string, string](t)
+
+ err := db.Set("Key", "Other", 0)
+ assert.NoError(t, err)
+
+ want := "Value"
+ err = db.Set("Key", want, 0)
+ assert.NoError(t, err)
+
+ got, _, err := db.GetValue("Key")
+ assert.NoError(t, err)
+ assert.Equal(t, want, got)
+ })
+}
+
+func TestDBDelete(t *testing.T) {
+ t.Parallel()
+
+ t.Run("Exists", func(t *testing.T) {
+ t.Parallel()
+
+ db := setupTestDB[string, string](t)
+ want := "Value"
+ err := db.Set("Key", want, 0)
+ assert.NoError(t, err)
+
+ err = db.Delete("Key")
+ assert.NoError(t, err)
+
+ _, _, err = db.GetValue("Key")
+ assert.ErrorIs(t, err, ErrKeyNotFound)
+ })
+
+ t.Run("Not Exists", func(t *testing.T) {
+ t.Parallel()
+
+ db := setupTestDB[string, string](t)
+
+ err := db.Delete("Key")
+ assert.ErrorIs(t, err, ErrKeyNotFound)
+ })
+}
+
+func BenchmarkDBGet(b *testing.B) {
+ for n := 1; n <= 10000; n *= 10 {
+ b.Run(fmt.Sprint(n), func(b *testing.B) {
+ db := setupTestDB[int, int](b)
+ for i := 0; i < n; i++ {
+ db.Set(i, i, 0)
+ }
+ b.ReportAllocs()
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ db.GetValue(n - 1)
+ }
+ })
+ }
+}
+
+func BenchmarkDBSet(b *testing.B) {
+ for n := 1; n <= 10000; n *= 10 {
+ b.Run(fmt.Sprint(n), func(b *testing.B) {
+ db := setupTestDB[int, int](b)
+ for i := 0; i < n-1; i++ {
+ db.Set(i, i, 0)
+ }
+ b.ReportAllocs()
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ db.Set(n, n, 0)
+ }
+ })
+ }
+}
+
+func BenchmarkDBDelete(b *testing.B) {
+ for n := 1; n <= 10000; n *= 10 {
+ b.Run(fmt.Sprint(n), func(b *testing.B) {
+ db := setupTestDB[int, int](b)
+ for i := 0; i < n-1; i++ {
+ db.Set(i, i, 0)
+ }
+ b.ReportAllocs()
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ db.Set(n, n, 0)
+ db.Delete(n)
+ }
+ })
+ }
+}
diff --git a/encoding_test.go b/encoding_test.go
index 6ca50cb..5fc4c9a 100644
--- a/encoding_test.go
+++ b/encoding_test.go
@@ -288,7 +288,7 @@ func BenchmarkDecoder_DecodeStore(b *testing.B) {
for n := 1; n <= 10000; n *= 10 {
b.Run(fmt.Sprint(n), func(b *testing.B) {
want := setupTestStore(b)
- for i := range 100 {
+ for i := range n {
buf := make([]byte, 8)
binary.LittleEndian.PutUint64(buf, uint64(i))
want.Set(buf, buf, 0)
diff --git a/evict.go b/evict.go
index 73f2cec..ec4e848 100644
--- a/evict.go
+++ b/evict.go
@@ -7,7 +7,8 @@ import (
// EvictionPolicyType defines the type of eviction policy.
type EvictionPolicyType int
-const (
+const (
+ // PolicyNone indicates no eviction policy.
PolicyNone EvictionPolicyType = iota
PolicyFIFO
PolicyLRU
@@ -16,6 +17,7 @@ const (
)
// evictionStrategies interface defines the methods for eviction strategies.
+ // evictionStrategies interface defines the methods for eviction strategies.
type evictionStrategies interface {
OnInsert(n *node)
OnUpdate(n *node)
@@ -24,12 +26,14 @@ type evictionStrategies interface {
}
// evictionPolicy struct holds the eviction strategy and its type.
+ // evictionPolicy struct holds the eviction strategy and its type.
type evictionPolicy struct {
evictionStrategies
Type EvictionPolicyType
evict *node
}
+ // pushEvict adds a node to the eviction list.
func pushEvict(node *node, sentinnel *node) {
node.EvictPrev = sentinnel
node.EvictNext = node.EvictPrev.EvictNext
@@ -38,6 +42,7 @@ func pushEvict(node *node, sentinnel *node) {
}
// SetPolicy sets the eviction policy based on the given type.
+ // SetPolicy sets the eviction policy based on the given type.
func (e *evictionPolicy) SetPolicy(y EvictionPolicyType) error {
store := map[EvictionPolicyType]func() evictionStrategies{
PolicyNone: func() evictionStrategies {
@@ -65,25 +70,30 @@ func (e *evictionPolicy) SetPolicy(y EvictionPolicyType) error {
}
// fifoPolicy struct represents the First-In-First-Out eviction policy.
+ // fifoPolicy struct represents the First-In-First-Out eviction policy.
type fifoPolicy struct {
evict *node
shouldEvict bool
}
// OnInsert adds a node to the eviction list.
+ // OnInsert adds a node to the eviction list.
func (s fifoPolicy) OnInsert(node *node) {
pushEvict(node, s.evict)
}
// OnAccess is a no-op for fifoPolicy.
+ // OnAccess is a no-op for fifoPolicy.
func (fifoPolicy) OnAccess(n *node) {
}
// OnUpdate is a no-op for fifoPolicy.
+ // OnUpdate is a no-op for fifoPolicy.
func (fifoPolicy) OnUpdate(n *node) {
}
// Evict returns the oldest node for fifoPolicy.
+ // Evict returns the oldest node for fifoPolicy.
func (s fifoPolicy) Evict() *node {
if s.shouldEvict && s.evict.EvictPrev != s.evict {
return s.evict.EvictPrev
@@ -93,20 +103,24 @@ func (s fifoPolicy) Evict() *node {
}
// lruPolicy struct represents the Least Recently Used eviction policy.
+ // lruPolicy struct represents the Least Recently Used eviction policy.
type lruPolicy struct {
evict *node
}
// OnInsert adds a node to the eviction list.
+ // OnInsert adds a node to the eviction list.
func (s lruPolicy) OnInsert(node *node) {
pushEvict(node, s.evict)
}
+ // OnUpdate moves the accessed node to the front of the eviction list.
func (s lruPolicy) OnUpdate(node *node) {
s.OnAccess(node)
}
// OnAccess moves the accessed node to the front of the eviction list.
+ // OnAccess moves the accessed node to the front of the eviction list.
func (s lruPolicy) OnAccess(node *node) {
node.EvictNext.EvictPrev = node.EvictPrev
node.EvictPrev.EvictNext = node.EvictNext
@@ -114,6 +128,7 @@ func (s lruPolicy) OnAccess(node *node) {
}
// Evict returns the least recently used node for lruPolicy.
+ // Evict returns the least recently used node for lruPolicy.
func (s lruPolicy) Evict() *node {
if s.evict.EvictPrev != s.evict {
return s.evict.EvictPrev
@@ -123,21 +138,25 @@ func (s lruPolicy) Evict() *node {
}
// lfuPolicy struct represents the Least Frequently Used eviction policy.
+ // lfuPolicy struct represents the Least Frequently Used eviction policy.
type lfuPolicy struct {
evict *node
}
// OnInsert adds a node to the eviction list and initializes its access count.
+ // OnInsert adds a node to the eviction list and initializes its access count.
func (s lfuPolicy) OnInsert(node *node) {
pushEvict(node, s.evict)
}
// OnUpdate increments the access count of the node and reorders the list.
+ // OnUpdate increments the access count of the node and reorders the list.
func (s lfuPolicy) OnUpdate(node *node) {
s.OnAccess(node)
}
// OnAccess increments the access count of the node and reorders the list.
+ // OnAccess increments the access count of the node and reorders the list.
func (s lfuPolicy) OnAccess(node *node) {
node.Access++
@@ -163,6 +182,7 @@ func (s lfuPolicy) OnAccess(node *node) {
}
// Evict returns the least frequently used node for LFU.
+ // Evict returns the least frequently used node for LFU.
func (s lfuPolicy) Evict() *node {
if s.evict.EvictPrev != s.evict {
return s.evict.EvictPrev
@@ -172,6 +192,7 @@ func (s lfuPolicy) Evict() *node {
}
// ltrPolicy struct represents the Least Remaining Time eviction policy.
+ // ltrPolicy struct represents the Least Remaining Time eviction policy.
type ltrPolicy struct {
evict *node
evictZero bool
@@ -179,6 +200,7 @@ type ltrPolicy struct {
// OnInsert adds a node to the eviction list based on its TTL (Time To Live).
// It places the node in the correct position in the list based on TTL.
+ // OnInsert adds a node to the eviction list based on its TTL (Time To Live).
func (s ltrPolicy) OnInsert(node *node) {
pushEvict(node, s.evict)
@@ -187,11 +209,13 @@ func (s ltrPolicy) OnInsert(node *node) {
// OnAccess is a no-op for ltrPolicy.
// It does not perform any action when a node is accessed.
+ // OnAccess is a no-op for ltrPolicy.
func (s ltrPolicy) OnAccess(node *node) {
}
// OnUpdate updates the position of the node in the eviction list based on its TTL.
// It reorders the list to maintain the correct order based on TTL.
+ // OnUpdate updates the position of the node in the eviction list based on its TTL.
func (s ltrPolicy) OnUpdate(node *node) {
if node.TTL() == 0 {
return
@@ -230,6 +254,7 @@ func (s ltrPolicy) OnUpdate(node *node) {
// Evict returns the node with the least remaining time to live for ltrPolicy.
// It returns the node at the end of the eviction list.
+ // Evict returns the node with the least remaining time to live for ltrPolicy.
func (s ltrPolicy) Evict() *node {
if s.evict.EvictPrev != s.evict && (s.evict.EvictPrev.TTL() != 0 || s.evictZero) {
return s.evict.EvictPrev
diff --git a/examples/basic_usage/main.go b/examples/basic_usage/main.go
new file mode 100644
index 0000000..0582770
--- /dev/null
+++ b/examples/basic_usage/main.go
@@ -0,0 +1,42 @@
+package main
+
+import (
+ "fmt"
+ "time"
+
+ "github.com/marcthe12/cache"
+)
+
+func main() {
+ // Create an in-memory cache
+ db, err := cache.OpenMem[string, string]("example")
+ if err != nil {
+ fmt.Println("Error:", err)
+ return
+ }
+ defer db.Close()
+
+ // Set a value with a TTL of 5 seconds
+ err = db.Set("key1", "value1", 5*time.Second)
+ if err != nil {
+ fmt.Println("Set Error:", err)
+ return
+ }
+
+ // Get the value
+ value, ttl, err := db.GetValue("key1")
+ if err != nil {
+ fmt.Println("Get Error:", err)
+ } else {
+ fmt.Printf("Got value: %s, TTL: %s\n", value, ttl)
+ }
+
+ // Wait for 6 seconds and try to get the value again
+ time.Sleep(6 * time.Second)
+ value, ttl, err = db.GetValue("key1")
+ if err != nil {
+ fmt.Println("Get Error after TTL:", err)
+ } else {
+ fmt.Printf("Got value after TTL: %s, TTL: %s\n", value, ttl)
+ }
+}
diff --git a/examples/eviction_policy/main.go b/examples/eviction_policy/main.go
new file mode 100644
index 0000000..a9c8577
--- /dev/null
+++ b/examples/eviction_policy/main.go
@@ -0,0 +1,39 @@
+package main
+
+import (
+ "fmt"
+
+ "code.qburst.com/marcpervaz//cache"
+)
+
+func main() {
+ // Create an in-memory cache with LRU eviction policy
+ db, err := cache.OpenMem[string, string]("example", cache.WithPolicy(cache.PolicyLRU))
+ if err != nil {
+ fmt.Println("Error:", err)
+ return
+ }
+ defer db.Close()
+
+ // Set values
+ db.Set("key1", "value1", 0)
+ db.Set("key2", "value2", 0)
+ db.Set("key3", "value3", 0)
+
+ // Access some keys
+ db.GetValue("key1")
+ db.GetValue("key2")
+
+ // Add another key to trigger eviction
+ db.Set("key4", "value4", 0)
+
+ // Check which keys are present
+ for _, key := range []string{"key1", "key2", "key3", "key4"} {
+ value, _, err := db.GetValue(key)
+ if err != nil {
+ fmt.Printf("Key %s not found\n", key)
+ } else {
+ fmt.Printf("Key %s found with value: %s\n", key, value)
+ }
+ }
+}
diff --git a/store.go b/store.go
index f4ba952..8747cd2 100644
--- a/store.go
+++ b/store.go
@@ -10,6 +10,7 @@ import (
const initialBucketSize uint64 = 8
+ // node represents an entry in the cache with metadata for eviction and expiration.
type node struct {
Hash uint64
Expiration time.Time
@@ -22,10 +23,12 @@ type node struct {
EvictPrev *node
}
+ // IsValid checks if the node is still valid based on its expiration time.
func (n *node) IsValid() bool {
return n.Expiration.IsZero() || n.Expiration.After(time.Now())
}
+ // TTL returns the time-to-live of the node.
func (n *node) TTL() time.Duration {
if n.Expiration.IsZero() {
return 0
@@ -34,6 +37,7 @@ func (n *node) TTL() time.Duration {
}
}
+ // store represents the in-memory cache with eviction policies and periodic tasks.
type store struct {
Bucket []node
Length uint64
@@ -46,6 +50,7 @@ type store struct {
mu sync.Mutex
}
+ // Init initializes the store with default settings.
func (s *store) Init() {
s.Clear()
s.Policy.evict = &s.Evict
@@ -54,6 +59,7 @@ func (s *store) Init() {
s.Policy.SetPolicy(PolicyNone)
}
+ // Clear removes all entries from the store.
func (s *store) Clear() {
s.mu.Lock()
defer s.mu.Unlock()
@@ -66,11 +72,13 @@ func (s *store) Clear() {
s.Evict.EvictPrev = &s.Evict
}
+ // lookup calculates the hash and index for a given key.
func lookup(s *store, key []byte) (uint64, uint64) {
hash := hash(key)
return hash % uint64(len(s.Bucket)), hash
}
+ // lazyInitBucket initializes the hash bucket if it hasn't been initialized yet.
func lazyInitBucket(n *node) {
if n.HashNext == nil {
n.HashNext = n
@@ -78,6 +86,7 @@ func lazyInitBucket(n *node) {
}
}
+ // lookup finds a node in the store by key.
func (s *store) lookup(key []byte) (*node, uint64, uint64) {
idx, hash := lookup(s, key)
@@ -94,6 +103,7 @@ func (s *store) lookup(key []byte) (*node, uint64, uint64) {
return nil, idx, hash
}
+ // get retrieves a value from the store by key.
func (s *store) get(key []byte) ([]byte, time.Duration, bool) {
v, _, _ := s.lookup(key)
if v != nil {
@@ -108,6 +118,7 @@ func (s *store) get(key []byte) ([]byte, time.Duration, bool) {
return nil, 0, false
}
+ // Get retrieves a value from the store by key with locking.
func (s *store) Get(key []byte) ([]byte, time.Duration, bool) {
s.mu.Lock()
defer s.mu.Unlock()
@@ -115,14 +126,15 @@ func (s *store) Get(key []byte) ([]byte, time.Duration, bool) {
return s.get(key)
}
+ // resize doubles the size of the hash table and rehashes all entries.
func resize(s *store) {
bucket := make([]node, 2*len(s.Bucket))
for v := s.Evict.EvictNext; v != &s.Evict; v = v.EvictNext {
- if !v.IsValid() {
- deleteNode(s, v)
- continue
- }
+ // if !v.IsValid() {
+ // deleteNode(s, v)
+ // continue
+ // }
idx := v.Hash % uint64(len(bucket))
n := &bucket[idx]
@@ -137,6 +149,7 @@ func resize(s *store) {
s.Bucket = bucket
}
+ // cleanup removes expired entries from the store.
func cleanup(s *store) {
for v := s.Evict.EvictNext; v != &s.Evict; v = v.EvictNext {
if !v.IsValid() {
@@ -145,6 +158,7 @@ func cleanup(s *store) {
}
}
+ // evict removes entries from the store based on the eviction policy.
func evict(s *store) bool {
for s.MaxCost != 0 && s.MaxCost < s.Cost {
n := s.Policy.Evict()
@@ -156,6 +170,7 @@ func evict(s *store) bool {
return true
}
+ // set adds or updates a key-value pair in the store.
func (s *store) set(key []byte, value []byte, ttl time.Duration) {
v, idx, hash := s.lookup(key)
if v != nil {
@@ -195,6 +210,7 @@ func (s *store) set(key []byte, value []byte, ttl time.Duration) {
s.Length = s.Length + 1
}
+ // Set adds or updates a key-value pair in the store with locking.
func (s *store) Set(key []byte, value []byte, ttl time.Duration) {
s.mu.Lock()
defer s.mu.Unlock()
@@ -202,6 +218,7 @@ func (s *store) Set(key []byte, value []byte, ttl time.Duration) {
s.set(key, value, ttl)
}
+ // deleteNode removes a node from the store.
func deleteNode(s *store, v *node) {
v.HashNext.HashPrev = v.HashPrev
v.HashPrev.HashNext = v.HashNext
@@ -217,6 +234,7 @@ func deleteNode(s *store, v *node) {
s.Length = s.Length - 1
}
+ // delete removes a key-value pair from the store.
func (s *store) delete(key []byte) bool {
v, _, _ := s.lookup(key)
if v != nil {
@@ -227,6 +245,7 @@ func (s *store) delete(key []byte) bool {
return false
}
+ // Delete removes a key-value pair from the store with locking.
func (s *store) Delete(key []byte) bool {
s.mu.Lock()
defer s.mu.Unlock()
diff --git a/store_test.go b/store_test.go
index 3ad33e5..9b802bf 100644
--- a/store_test.go
+++ b/store_test.go
@@ -2,6 +2,7 @@ package cache
import (
"encoding/binary"
+ "fmt"
"testing"
"time"
@@ -132,24 +133,63 @@ func TestStoreClear(t *testing.T) {
}
func BenchmarkStoreGet(b *testing.B) {
- store := setupTestStore(b)
+ for n := 1; n <= 10000; n *= 10 {
+ b.Run(fmt.Sprint(n), func(b *testing.B) {
+ want := setupTestStore(b)
+ for i := range n - 1 {
+ buf := make([]byte, 8)
+ binary.LittleEndian.PutUint64(buf, uint64(i))
+ want.Set(buf, buf, 0)
+ }
+ key := []byte("Key")
+ want.Set(key, []byte("Store"), 0)
+ b.ReportAllocs()
- key := []byte("Key")
- store.Set(key, []byte("Store"), 0)
- b.ReportAllocs()
-
- for i := 0; i < b.N; i++ {
- store.Get(key)
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ want.Get(key)
+ }
+ })
}
}
func BenchmarkStoreSet(b *testing.B) {
- store := setupTestStore(b)
-
- key := []byte("Key")
- b.ReportAllocs()
+ for n := 1; n <= 10000; n *= 10 {
+ b.Run(fmt.Sprint(n), func(b *testing.B) {
+ want := setupTestStore(b)
+ for i := range n - 1 {
+ buf := make([]byte, 8)
+ binary.LittleEndian.PutUint64(buf, uint64(i))
+ want.Set(buf, buf, 0)
+ }
+ key := []byte("Key")
+ store := []byte("Store")
+ b.ReportAllocs()
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ want.Set(key, store, 0)
+ }
+ })
+ }
+}
- for i := 0; i < b.N; i++ {
- store.Set(key, []byte("Store"), 0)
+func BenchmarkStoreDelete(b *testing.B) {
+ for n := 1; n <= 10000; n *= 10 {
+ b.Run(fmt.Sprint(n), func(b *testing.B) {
+ want := setupTestStore(b)
+ for i := range n - 1 {
+ buf := make([]byte, 8)
+ binary.LittleEndian.PutUint64(buf, uint64(i))
+ want.Set(buf, buf, 0)
+ }
+ key := []byte("Key")
+ store := []byte("Store")
+ b.ReportAllocs()
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ want.Set(key, store, 0)
+ want.Delete(key)
+ }
+ })
}
}