Skip to content

Commit 2aab4f8

Browse files
committed
style(backend/redis): gofumpt formatting and whitespace fixes in redis_common.go
1 parent 3b9ebc7 commit 2aab4f8

7 files changed

Lines changed: 438 additions & 107 deletions

File tree

pkg/backend/backend.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import (
2323
// It restricts the generic type parameter to supported backend types, ensuring
2424
// type safety and proper implementation at compile time.
2525
type IBackendConstrain interface {
26-
InMemory | Redis
26+
InMemory | Redis | RedisCluster
2727
}
2828

2929
// IBackend defines the contract that all cache backends must implement.

pkg/backend/options.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ func (rb *Redis) setCapacity(capacity int) {
2222
rb.capacity = capacity
2323
}
2424

25+
// setCapacity sets the `Capacity` field of the `RedisCluster` backend.
26+
func (rc *RedisCluster) setCapacity(capacity int) {
27+
rc.capacity = capacity
28+
}
29+
2530
// Option is a function type that can be used to configure the `HyperCache` struct.
2631
type Option[T IBackendConstrain] func(*T)
2732

@@ -64,3 +69,24 @@ func WithSerializer[T Redis](serializer serializer.ISerializer) Option[Redis] {
6469
backend.Serializer = serializer
6570
}
6671
}
72+
73+
// WithRedisClusterClient sets the redis cluster client to use.
74+
func WithRedisClusterClient[T RedisCluster](client *redis.ClusterClient) Option[RedisCluster] {
75+
return func(backend *RedisCluster) {
76+
backend.rdb = client
77+
}
78+
}
79+
80+
// WithClusterKeysSetName sets the name of the set for cluster backend keys.
81+
func WithClusterKeysSetName[T RedisCluster](keysSetName string) Option[RedisCluster] {
82+
return func(backend *RedisCluster) {
83+
backend.keysSetName = keysSetName
84+
}
85+
}
86+
87+
// WithClusterSerializer sets the serializer for the cluster backend.
88+
func WithClusterSerializer[T RedisCluster](ser serializer.ISerializer) Option[RedisCluster] {
89+
return func(backend *RedisCluster) {
90+
backend.Serializer = ser
91+
}
92+
}

pkg/backend/redis.go

Lines changed: 3 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -125,120 +125,17 @@ func (cacheBackend *Redis) Get(ctx context.Context, key string) (*cache.Item, bo
125125

126126
// Set stores the Item in the cacheBackend.
127127
func (cacheBackend *Redis) Set(ctx context.Context, item *cache.Item) error {
128-
pipe := cacheBackend.rdb.TxPipeline()
129-
130-
// Check if the item is valid
131-
err := item.Valid()
132-
if err != nil {
133-
// Return the item to the pool
134-
return err
135-
}
136-
137-
// Serialize the item
138-
data, err := cacheBackend.Serializer.Marshal(item)
139-
if err != nil {
140-
// Return the item to the pool
141-
return err
142-
}
143-
144-
expiration := item.Expiration.String()
145-
146-
// Store the item in the cacheBackend
147-
err = pipe.HSet(ctx, item.Key, map[string]any{
148-
"data": data,
149-
"expiration": expiration,
150-
}).Err()
151-
if err != nil {
152-
return ewrap.Wrap(err, "failed to set item in redis")
153-
}
154-
// Add the key to the set of keys associated with the cache prefix
155-
pipe.SAdd(ctx, cacheBackend.keysSetName, item.Key)
156-
// Set the expiration if it is not zero
157-
if item.Expiration > 0 {
158-
pipe.Expire(ctx, item.Key, item.Expiration)
159-
}
160-
161-
_, err = pipe.Exec(ctx)
162-
if err != nil {
163-
return ewrap.Wrap(err, "failed to execute redis pipeline")
164-
}
165-
166-
return nil
128+
return redisSet(ctx, cacheBackend.rdb, cacheBackend.keysSetName, item, cacheBackend.Serializer)
167129
}
168130

169131
// List returns a list of all the items in the cacheBackend that match the given filter options.
170132
func (cacheBackend *Redis) List(ctx context.Context, filters ...IFilter) ([]*cache.Item, error) {
171-
// Get the set of keys held in the cacheBackend with the given `keysSetName`
172-
keys, err := cacheBackend.rdb.SMembers(ctx, cacheBackend.keysSetName).Result()
173-
if err != nil {
174-
return nil, ewrap.Wrap(err, "failed to get keys from redis")
175-
}
176-
177-
// Execute the Redis commands in a pipeline transaction to improve performance and reduce the number of round trips
178-
cmds, err := cacheBackend.rdb.Pipelined(ctx, func(pipe redis.Pipeliner) error {
179-
// Get the items from the cacheBackend
180-
for _, key := range keys {
181-
pipe.HGetAll(ctx, key)
182-
}
183-
184-
return nil
185-
})
186-
if err != nil {
187-
return nil, ewrap.Wrap(err, "failed to execute redis pipeline while listing")
188-
}
189-
190-
// Create a slice to hold the items
191-
items := make([]*cache.Item, 0, len(keys))
192-
193-
// Deserialize the items and add them to the slice of items to return
194-
for _, cmd := range cmds {
195-
command, ok := cmd.(*redis.MapStringStringCmd)
196-
if !ok {
197-
continue
198-
}
199-
200-
data, err := command.Result() // Change the type assertion to match HGetAll
201-
if err != nil {
202-
return nil, ewrap.Wrap(err, "failed to get item data from redis")
203-
}
204-
205-
item := cacheBackend.itemPoolManager.Get()
206-
// Return the item to the pool
207-
defer cacheBackend.itemPoolManager.Put(item)
208-
209-
err = cacheBackend.Serializer.Unmarshal([]byte(data["data"]), item)
210-
if err == nil {
211-
items = append(items, item)
212-
}
213-
}
214-
215-
// Apply the filters
216-
if len(filters) > 0 {
217-
for _, filter := range filters {
218-
items, err = filter.ApplyFilter(constants.RedisBackend, items)
219-
}
220-
}
221-
222-
return items, err
133+
return redisList(ctx, cacheBackend.rdb, cacheBackend.keysSetName, cacheBackend.Serializer, cacheBackend.itemPoolManager, filters...)
223134
}
224135

225136
// Remove removes an item from the cache with the given key.
226137
func (cacheBackend *Redis) Remove(ctx context.Context, keys ...string) error {
227-
pipe := cacheBackend.rdb.TxPipeline()
228-
229-
_, err := pipe.SRem(ctx, cacheBackend.keysSetName, keys).Result()
230-
if err != nil {
231-
return ewrap.Wrap(err, "removing keys from set")
232-
}
233-
234-
_, err = pipe.Del(ctx, keys...).Result()
235-
if err != nil {
236-
return ewrap.Wrap(err, "removing keys")
237-
}
238-
239-
_, err = pipe.Exec(ctx)
240-
241-
return ewrap.Wrap(err, "executing pipeline")
138+
return redisRemove(ctx, cacheBackend.rdb, cacheBackend.keysSetName, keys...)
242139
}
243140

244141
// Clear removes all items from the cache.

pkg/backend/redis_cluster.go

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
package backend
2+
3+
import (
4+
"context"
5+
"errors"
6+
"time"
7+
8+
"github.com/redis/go-redis/v9"
9+
10+
"github.com/hyp3rd/ewrap"
11+
12+
"github.com/hyp3rd/hypercache/internal/constants"
13+
"github.com/hyp3rd/hypercache/internal/libs/serializer"
14+
"github.com/hyp3rd/hypercache/internal/sentinel"
15+
"github.com/hyp3rd/hypercache/pkg/cache"
16+
)
17+
18+
const (
19+
clusterMaxRetries = 3
20+
clusterRetriesDelay = 100 * time.Millisecond
21+
)
22+
23+
// RedisCluster is a cache backend that stores items in a Redis Cluster.
24+
// It mirrors the single-node Redis backend semantics but uses go-redis ClusterClient.
25+
type RedisCluster struct {
26+
rdb *redis.ClusterClient // redis cluster client
27+
capacity int // capacity of the cache
28+
itemPoolManager *cache.ItemPoolManager
29+
keysSetName string
30+
Serializer serializer.ISerializer
31+
}
32+
33+
// NewRedisCluster creates a new Redis Cluster backend with the given options.
34+
func NewRedisCluster(redisOptions ...Option[RedisCluster]) (IBackend[RedisCluster], error) {
35+
rc := &RedisCluster{itemPoolManager: cache.NewItemPoolManager()}
36+
37+
ApplyOptions(rc, redisOptions...)
38+
39+
if rc.rdb == nil {
40+
return nil, sentinel.ErrNilClient
41+
}
42+
43+
if rc.capacity < 0 {
44+
return nil, sentinel.ErrInvalidCapacity
45+
}
46+
47+
if rc.keysSetName == "" {
48+
rc.keysSetName = constants.RedisBackend
49+
}
50+
51+
if rc.Serializer == nil {
52+
var err error
53+
54+
rc.Serializer, err = serializer.New("msgpack")
55+
if err != nil {
56+
return nil, err
57+
}
58+
}
59+
60+
return rc, nil
61+
}
62+
63+
// SetCapacity sets the capacity of the cluster backend.
64+
func (cacheBackend *RedisCluster) SetCapacity(capacity int) {
65+
if capacity < 0 {
66+
return
67+
}
68+
69+
cacheBackend.capacity = capacity
70+
}
71+
72+
// Capacity returns the capacity of the cluster backend.
73+
func (cacheBackend *RedisCluster) Capacity() int {
74+
return cacheBackend.capacity
75+
}
76+
77+
// Count returns the number of keys stored.
78+
func (cacheBackend *RedisCluster) Count(ctx context.Context) int {
79+
count, err := cacheBackend.rdb.DBSize(ctx).Result()
80+
if err != nil {
81+
return 0
82+
}
83+
84+
return int(count)
85+
}
86+
87+
// Get retrieves an item by key.
88+
func (cacheBackend *RedisCluster) Get(ctx context.Context, key string) (*cache.Item, bool) {
89+
isMember, err := cacheBackend.rdb.SIsMember(ctx, cacheBackend.keysSetName, key).Result()
90+
if err != nil || !isMember {
91+
return nil, false
92+
}
93+
94+
item := cacheBackend.itemPoolManager.Get()
95+
defer cacheBackend.itemPoolManager.Put(item)
96+
97+
data, err := cacheBackend.rdb.HGet(ctx, key, "data").Bytes()
98+
if err != nil {
99+
if errors.Is(err, redis.Nil) {
100+
return nil, false
101+
}
102+
103+
return nil, false
104+
}
105+
106+
err = cacheBackend.Serializer.Unmarshal(data, item)
107+
if err != nil {
108+
return nil, false
109+
}
110+
111+
return item, true
112+
}
113+
114+
// Set stores an item in the cluster.
115+
func (cacheBackend *RedisCluster) Set(ctx context.Context, item *cache.Item) error {
116+
return redisSet(ctx, cacheBackend.rdb, cacheBackend.keysSetName, item, cacheBackend.Serializer)
117+
}
118+
119+
// List returns items matching optional filters.
120+
func (cacheBackend *RedisCluster) List(ctx context.Context, filters ...IFilter) ([]*cache.Item, error) {
121+
return redisList(ctx, cacheBackend.rdb, cacheBackend.keysSetName, cacheBackend.Serializer, cacheBackend.itemPoolManager, filters...)
122+
}
123+
124+
// Remove deletes the specified keys.
125+
func (cacheBackend *RedisCluster) Remove(ctx context.Context, keys ...string) error {
126+
return redisRemove(ctx, cacheBackend.rdb, cacheBackend.keysSetName, keys...)
127+
}
128+
129+
// Clear flushes the database.
130+
func (cacheBackend *RedisCluster) Clear(ctx context.Context) error {
131+
_, err := cacheBackend.rdb.FlushDB(ctx).Result()
132+
133+
return ewrap.Wrap(err, "flushing database", ewrap.WithRetry(clusterMaxRetries, clusterRetriesDelay))
134+
}

0 commit comments

Comments
 (0)