Redis Sorted Set: Rate Limit (Detailed Guide w/ Code Examples)

Use Case(s)

Redis Sorted Sets can be effectively used to implement rate limiting by keeping track of timestamps of actions performed by a user. This ensures that users do not exceed a predefined number of actions in a given time frame.

Code Examples

Python Example

import redis import time client = redis.StrictRedis(host='localhost', port=6379, db=0) def is_rate_limited(user_id, limit, interval): now = time.time() key = f"rate_limit:{user_id}" # Remove old entries client.zremrangebyscore(key, '-inf', now - interval) # Add new entry client.zadd(key, {now: now}) # Check the count count = client.zcard(key) if count > limit: return True return False # Usage example user_id = 12345 if is_rate_limited(user_id, 5, 60): # 5 actions per minute allowed print("Rate limit exceeded") else: print("Action allowed")

Node.js Example

const redis = require('redis'); const client = redis.createClient(); function isRateLimited(userId, limit, interval, callback) { const now = Date.now(); const key = `rate_limit:${userId}`; client.zremrangebyscore(key, '-inf', now - interval, () => { client.zadd(key, now, now, () => { client.zcard(key, (err, count) => { if (count > limit) { callback(true); } else { callback(false); } }); }); }); } // Usage example const userId = 12345; isRateLimited(userId, 5, 60000, (isLimited) => { if (isLimited) { console.log("Rate limit exceeded"); } else { console.log("Action allowed"); } });

Go Example

package main import ( "fmt" "github.com/go-redis/redis/v8" "context" "time" ) var ctx = context.Background() func isRateLimited(client *redis.Client, userID string, limit int64, interval time.Duration) (bool, error) { now := time.Now().Unix() key := fmt.Sprintf("rate_limit:%s", userID) // Remove old entries err := client.ZRemRangeByScore(ctx, key, "-inf", fmt.Sprint(now-int64(interval.Seconds()))).Err() if err != nil { return false, err } // Add new entry err = client.ZAdd(ctx, key, &redis.Z{Score: float64(now), Member: now}).Err() if err != nil { return false, err } // Check the count count, err := client.ZCard(ctx, key).Result() if err != nil { return false, err } return count > limit, nil } func main() { client := redis.NewClient(&redis.Options{ Addr: "localhost:6379", }) userID := "12345" limited, err := isRateLimited(client, userID, 5, 60*time.Second) if err != nil { panic(err) } if limited { fmt.Println("Rate limit exceeded") } else { fmt.Println("Action allowed") } }

Best Practices

  • Ensure that old entries are regularly cleaned up using ZREMRANGEBYSCORE to avoid memory bloat.
  • Use appropriate expirations for keys if they only need to exist temporarily.
  • Monitor and handle Redis connection errors gracefully to maintain application stability.

Common Mistakes

  • Forgetting to clean up old entries which can lead to incorrect rate limit calculations and increased memory usage.
  • Not handling edge cases where multiple actions happen at virtually the same timestamp, potentially causing unexpected rate limits.

FAQs

Q: How does Redis handle concurrency for rate limiting? A: Redis operations like ZADD and ZCARD are atomic, which makes rate limiting safe even under high concurrency.

Q: Can I use this method for more complex rate limiting rules? A: Yes, you can customize the logic to handle more sophisticated rules, such as different limits for different actions or adaptive rate limiting based on user behavior.

Was this content helpful?

Start building today

Dragonfly is fully compatible with the Redis ecosystem and requires no code changes to implement.