-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathredis.go
More file actions
150 lines (121 loc) · 4.38 KB
/
redis.go
File metadata and controls
150 lines (121 loc) · 4.38 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
package backend
import (
"context"
"errors"
"time"
"github.com/hyp3rd/ewrap"
"github.com/redis/go-redis/v9"
"github.com/hyp3rd/hypercache/internal/constants"
"github.com/hyp3rd/hypercache/internal/libs/serializer"
"github.com/hyp3rd/hypercache/internal/sentinel"
cache "github.com/hyp3rd/hypercache/pkg/cache/v2"
)
const (
maxRetries = 3
retriesDelay = 100 * time.Millisecond
)
// Redis is a cache backend that stores the items in a redis implementation.
type Redis struct {
rdb *redis.Client // redis client to interact with the redis server
capacity int // capacity of the cache, limits the number of items that can be stored in the cache
itemPoolManager *cache.ItemPoolManager // itemPoolManager is used to manage the item pool for memory efficiency
keysSetName string // keysSetName is the name of the set that holds the keys of the items in the cache
Serializer serializer.ISerializer // Serializer is the serializer used to serialize the items before storing them in the cache
}
// NewRedis creates a new redis cache with the given options.
func NewRedis(redisOptions ...Option[Redis]) (IBackend[Redis], error) {
rb := &Redis{
itemPoolManager: cache.NewItemPoolManager(),
}
// Apply the backend options
ApplyOptions(rb, redisOptions...)
// Check if the client is nil
if rb.rdb == nil {
return nil, sentinel.ErrNilClient
}
// Check if the `capacity` is valid
if rb.capacity < 0 {
return nil, sentinel.ErrInvalidCapacity
}
// Check if the `keysSetName` is empty
if rb.keysSetName == "" {
rb.keysSetName = constants.RedisBackend
}
// Check if the serializer is nil
if rb.Serializer == nil {
var err error
// Set a the serializer to default to `msgpack`
rb.Serializer, err = serializer.New("msgpack")
if err != nil {
return nil, err
}
}
// return the new backend
return rb, nil
}
// SetCapacity sets the capacity of the cache.
func (cacheBackend *Redis) SetCapacity(capacity int) {
if capacity < 0 {
return
}
cacheBackend.capacity = capacity
}
// Capacity returns the maximum number of items that can be stored in the cache.
func (cacheBackend *Redis) Capacity() int {
return cacheBackend.capacity
}
// Count returns the number of items in the cache.
func (cacheBackend *Redis) Count(ctx context.Context) int {
count, err := cacheBackend.rdb.DBSize(ctx).Result()
if err != nil {
return 0
}
return int(count)
}
// Get retrieves the Item with the given key from the cacheBackend. If the item is not found, it returns nil.
func (cacheBackend *Redis) Get(ctx context.Context, key string) (*cache.Item, bool) {
// Check if the key is in the set of keys
isMember, err := cacheBackend.rdb.SIsMember(ctx, cacheBackend.keysSetName, key).Result()
if err != nil {
return nil, false
}
if !isMember {
return nil, false
}
// Get a transient item from pool, but clone before returning to caller
pooled := cacheBackend.itemPoolManager.Get()
data, err := cacheBackend.rdb.HGet(ctx, key, "data").Bytes()
if err != nil {
// Check if the item is not found
if errors.Is(err, redis.Nil) {
return nil, false
}
return nil, false
}
// Deserialize the item
err = cacheBackend.Serializer.Unmarshal(data, pooled)
if err != nil {
return nil, false
}
// Clone into a new heap object to avoid returning a pooled pointer
out := *pooled
cacheBackend.itemPoolManager.Put(pooled)
return &out, true
}
// Set stores the Item in the cacheBackend.
func (cacheBackend *Redis) Set(ctx context.Context, item *cache.Item) error {
return redisSet(ctx, cacheBackend.rdb, cacheBackend.keysSetName, item, cacheBackend.Serializer)
}
// List returns a list of all the items in the cacheBackend that match the given filter options.
func (cacheBackend *Redis) List(ctx context.Context, filters ...IFilter) ([]*cache.Item, error) {
return redisList(ctx, cacheBackend.rdb, cacheBackend.keysSetName, cacheBackend.Serializer, cacheBackend.itemPoolManager, filters...)
}
// Remove removes an item from the cache with the given key.
func (cacheBackend *Redis) Remove(ctx context.Context, keys ...string) error {
return redisRemove(ctx, cacheBackend.rdb, cacheBackend.keysSetName, keys...)
}
// Clear removes all items from the cache.
func (cacheBackend *Redis) Clear(ctx context.Context) error {
_, err := cacheBackend.rdb.FlushDB(ctx).Result()
return ewrap.Wrap(err, "flushing database", ewrap.WithRetry(maxRetries, retriesDelay))
}