php 48 lines · 8 steps

Sliding-window rate limiting with Redis sorted sets

Use a Redis sorted set of timestamps to count requests in a rolling window and enforce a per-user limit.

Explained by highlit
1<?php
2 
3namespace App\RateLimiting;
4 
5use Predis\Client;
6 
7final class SlidingWindowRateLimiter
8{
9 private const WINDOW_SECONDS = 3600;
10 
11 public function __construct(
12 private readonly Client $redis,
13 private readonly int $maxRequests = 1000,
14 ) {
15 }
16 
17 public function attempt(int $userId): RateLimitResult
18 {
19 $key = sprintf('ratelimit:user:%d', $userId);
20 $now = microtime(true);
21 $windowStart = $now - self::WINDOW_SECONDS;
22 $member = sprintf('%.6f:%s', $now, bin2hex(random_bytes(6)));
23 
24 $responses = $this->redis->transaction(function ($tx) use ($key, $windowStart, $member, $now) {
25 $tx->zremrangebyscore($key, '-inf', $windowStart);
26 $tx->zadd($key, [$member => $now]);
27 $tx->zcard($key);
28 $tx->expire($key, self::WINDOW_SECONDS);
29 });
30 
31 $count = (int) $responses[2];
32 
33 if ($count > $this->maxRequests) {
34 $this->redis->zrem($key, $member);
35 $oldest = $this->redis->zrange($key, 0, 0, ['withscores' => true]);
36 $retryAfter = self::WINDOW_SECONDS;
37 
38 if (!empty($oldest)) {
39 $oldestScore = (float) reset($oldest);
40 $retryAfter = (int) ceil($oldestScore + self::WINDOW_SECONDS - $now);
41 }
42 
43 return new RateLimitResult(false, 0, max($retryAfter, 1));
44 }
45 
46 return new RateLimitResult(true, $this->maxRequests - $count, 0);
47 }
48}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1A sorted set scored by timestamp turns rate limiting into a range-prune-then-count operation.
  2. 2Wrapping prune, add, count, and expire in one transaction keeps the count consistent under concurrency.
  3. 3Computing retry-after from the oldest surviving entry tells clients exactly when the window frees up.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Sliding-window rate limiting with Redis sorted sets — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code