@@ -25,10 +25,10 @@ type QuotaCache interface {
2525}
2626
2727type UsageCache interface {
28- SetUsage(ctx context.Context, redisKey string, amount int64) error
29- ClearUsage(ctx context.Context, redisKey string) (bool, error)
30- PeekUsage(ctx context.Context, redisKey string) (int64, error)
31- SpendUsage(ctx context.Context, redisKey string, amount, limit int64) (int64, error)
28+ SetUsage(ctx context.Context, key string, amount int64) error
29+ ClearUsage(ctx context.Context, key string) (bool, error)
30+ PeekUsage(ctx context.Context, key string) (int64, error)
31+ SpendUsage(ctx context.Context, key string, amount, limit int64) (int64, error)
3232}
3333
3434type PermissionCache interface {
4545)
4646
4747const (
48- redisQuotaPrefix = "quota:"
49- redisRLPrefix = "rl:"
48+ redisRLPrefix = "rl:"
5049)
5150
5251var (
@@ -92,8 +91,27 @@ func NewLimitCounter(svc Service, cfg RedisConfig, logger *slog.Logger) httprate
9291const (
9392 defaultExpRedis = time.Hour
9493 defaultExpLRU = time.Minute
94+ cacheVersion = "v1"
9595)
9696
97+ // usageKey returns the redis key for storing usage amount.
98+ // It does not include version because usage is just a number, and it's safe to share across versions.
99+ func usageKey(key string) string {
100+ return fmt.Sprintf("usage:%s", key)
101+ }
102+
103+ // quotaKey returns the redis key for storing AccessQuota.
104+ // It includes version to avoid conflicts when the structure changes.
105+ func quotaKey(key string) string {
106+ return fmt.Sprintf("quota:%s:%s", cacheVersion, key)
107+ }
108+
109+ // permissionKey returns the redis key for storing user permission for a project.
110+ // It includes version to avoid conflicts when the structure changes.
111+ func permissionKey(projectID uint64, userID string) string {
112+ return fmt.Sprintf("perm:%s:project:%d:user:%s", cacheVersion, projectID, userID)
113+ }
114+
97115func NewRedisCache(redisClient *redis.Client, ttl time.Duration) *RedisCache {
98116 if ttl <= 0 {
99117 ttl = defaultExpRedis
@@ -134,8 +152,7 @@ func (s *RedisCache) SetProjectQuota(ctx context.Context, quota *proto.AccessQuo
134152}
135153
136154func (s *RedisCache) getQuota(ctx context.Context, key string) (*proto.AccessQuota, error) {
137- cacheKey := fmt.Sprintf("%s%s", redisQuotaPrefix, key)
138- raw, err := s.client.Get(ctx, cacheKey).Bytes()
155+ raw, err := s.client.Get(ctx, quotaKey(key)).Bytes()
139156 if err != nil {
140157 if err == redis.Nil {
141158 return nil, proto.ErrAccessKeyNotFound
@@ -150,8 +167,7 @@ func (s *RedisCache) getQuota(ctx context.Context, key string) (*proto.AccessQuo
150167}
151168
152169func (s *RedisCache) deleteQuota(ctx context.Context, key string) error {
153- cacheKey := fmt.Sprintf("%s%s", redisQuotaPrefix, key)
154- if err := s.client.Del(ctx, cacheKey).Err(); err != nil {
170+ if err := s.client.Del(ctx, quotaKey(key)).Err(); err != nil {
155171 return fmt.Errorf("delete quota: %w", err)
156172 }
157173 return nil
@@ -162,33 +178,31 @@ func (s *RedisCache) setQuota(ctx context.Context, key string, quota *proto.Acce
162178 if err != nil {
163179 return fmt.Errorf("marshal quota: %w", err)
164180 }
165- cacheKey := fmt.Sprintf("%s%s", redisQuotaPrefix, key)
166- if err := s.client.Set(ctx, cacheKey, raw, s.ttl).Err(); err != nil {
181+ if err := s.client.Set(ctx, quotaKey(key), raw, s.ttl).Err(); err != nil {
167182 return fmt.Errorf("set quota: %w", err)
168183 }
169184 return nil
170185}
171186
172- func (s *RedisCache) SetUsage(ctx context.Context, redisKey string, amount int64) error {
173- cacheKey := fmt.Sprintf("%s%s", redisQuotaPrefix, redisKey)
174- if err := s.client.Set(ctx, cacheKey, amount, s.ttl).Err(); err != nil {
187+ func (s *RedisCache) SetUsage(ctx context.Context, key string, amount int64) error {
188+ if err := s.client.Set(ctx, usageKey(key), amount, s.ttl).Err(); err != nil {
175189 return fmt.Errorf("set usage: %w", err)
176190 }
177191 return nil
178192}
179193
180- func (s *RedisCache) ClearUsage(ctx context.Context, redisKey string) (bool, error) {
181- cacheKey := fmt.Sprintf("%s%s", redisQuotaPrefix, redisKey)
182- count, err := s.client.Del(ctx, cacheKey).Result()
194+ func (s *RedisCache) ClearUsage(ctx context.Context, key string) (bool, error) {
195+ count, err := s.client.Del(ctx, usageKey(key)).Result()
183196 if err != nil {
184197 return false, fmt.Errorf("clear usage: %w", err)
185198 }
186199 return count != 0, nil
187200}
188201
189- func (s *RedisCache) PeekUsage(ctx context.Context, redisKey string) (int64, error) {
202+ func (s *RedisCache) PeekUsage(ctx context.Context, key string) (int64, error) {
190203 const SpecialValue = -1
191- cacheKey := fmt.Sprintf("%s%s", redisQuotaPrefix, redisKey)
204+ cacheKey := usageKey(key)
205+
192206 v, err := s.client.Get(ctx, cacheKey).Int64()
193207 if err == nil {
194208 if v == SpecialValue {
@@ -209,17 +223,17 @@ func (s *RedisCache) PeekUsage(ctx context.Context, redisKey string) (int64, err
209223 return 0, errCacheReady
210224}
211225
212- func (s *RedisCache) SpendUsage(ctx context.Context, redisKey string, amount, limit int64) (int64, error) {
213- // NOTE: skip redisKeyPrefix as it's already in PeekCost
214- v, err := s.PeekUsage(ctx, redisKey )
226+ func (s *RedisCache) SpendUsage(ctx context.Context, key string, amount, limit int64) (int64, error) {
227+ // NOTE: don't use usageKey yet, PeekUsage is doing that
228+ v, err := s.PeekUsage(ctx, key )
215229 if err != nil {
216230 return 0, fmt.Errorf("spend usage - peek: %w", err)
217231 }
218232 if v >= limit {
219233 return v, proto.ErrQuotaExceeded
220234 }
221- cacheKey := fmt.Sprintf("%s%s", redisQuotaPrefix, redisKey)
222- value, err := s.client.IncrBy(ctx, cacheKey , amount).Result()
235+
236+ value, err := s.client.IncrBy(ctx, usageKey(key) , amount).Result()
223237 if err != nil {
224238 return v, fmt.Errorf("spend usage - incrby: %w", err)
225239 }
@@ -232,8 +246,7 @@ type cacheUserPermission struct {
232246}
233247
234248func (s *RedisCache) GetUserPermission(ctx context.Context, projectID uint64, userID string) (proto.UserPermission, *proto.ResourceAccess, error) {
235- cacheKey := fmt.Sprintf("%s%s", redisQuotaPrefix, getUserPermKey(projectID, userID))
236- raw, err := s.client.Get(ctx, cacheKey).Bytes()
249+ raw, err := s.client.Get(ctx, permissionKey(projectID, userID)).Bytes()
237250 if err != nil {
238251 if err == redis.Nil {
239252 return proto.UserPermission_UNAUTHORIZED, nil, nil // not found, without error
@@ -258,13 +271,11 @@ func (s *RedisCache) SetUserPermission(ctx context.Context, projectID uint64, us
258271 }
259272
260273 // cache userPermissions for 10 seconds
261- cacheKey := fmt.Sprintf("%s%s", redisQuotaPrefix, getUserPermKey(projectID, userID))
262- return s.client.Set(ctx, cacheKey, raw, 10*time.Second).Err()
274+ return s.client.Set(ctx, permissionKey(projectID, userID), raw, 10*time.Second).Err()
263275}
264276
265277func (s *RedisCache) DeleteUserPermission(ctx context.Context, projectID uint64, userID string) error {
266- cacheKey := fmt.Sprintf("%s%s", redisQuotaPrefix, getUserPermKey(projectID, userID))
267- return s.client.Del(ctx, cacheKey).Err()
278+ return s.client.Del(ctx, permissionKey(projectID, userID)).Err()
268279}
269280
270281type LRU struct {
@@ -336,7 +347,3 @@ func (s *LRU) deleteQuota(ctx context.Context, key string) error {
336347func getProjectKey(projectID uint64) string {
337348 return fmt.Sprintf("project:%d", projectID)
338349}
339-
340- func getUserPermKey(projectID uint64, userID string) string {
341- return fmt.Sprintf("project:%d:userPerm:%s", projectID, userID)
342- }
0 commit comments