API & Automation·7 min read·

High-Concurrency API Rate Limiting Using Redis Generic Cell Rate Algorithm

Protect API endpoints from abuse using the Generic Cell Rate Algorithm (GCRA) and Redis/Valkey, delivering millisecond enforcement and RFC 7807 headers.

NB

Netbay Developer Relations

Netbay Engineering

On this page

Rate limiting is an essential defense against brute-force authentication attacks, denial-of-service attempts, and runaway scraping scripts. While simple fixed-window rate limiters (e.g., INCR and EXPIRE keys per minute) are straightforward to implement, they suffer from boundary bursts: a client can exhaust their entire quota in the final second of minute one and immediately exhaust another full quota in the first second of minute two, generating a 2x traffic spike.

The Generic Cell Rate Algorithm (GCRA)—an implementation of the leaky bucket algorithm originally standardized in ATM telecommunications—solves this problem. By tracking a single theoretical arrival time (TAT) per client, GCRA enforces smooth rate limiting with sub-millisecond overhead and atomic Lua scripts in Valkey or Redis.

How GCRA Works Under the Hood

Unlike token bucket implementations that track both a token count and a last-refilled timestamp, GCRA tracks a single value: the Theoretical Arrival Time (TAT).

  1. When a request arrives, GCRA compares the current time (now) with the stored TAT.
  2. If now is past TAT, the bucket is completely empty; the new TAT is set to now + emission_interval.
  3. If now is before TAT, the request is consuming burst allowance. As long as TAT - now is less than the max_burst_limit, the request is permitted, and TAT is incremented by emission_interval.
  4. If TAT - now exceeds max_burst_limit, the request is rejected with HTTP 429 Too Many Requests.

This approach provides smooth continuous pacing rather than abrupt cliff-edge drops.

Atomic GCRA Lua Script in Valkey

Because rate-limiting checks must be atomic across concurrent application threads, execute GCRA logic directly inside Valkey using an optimized Lua script:

lua
-- gcra.lua
-- KEYS[1]: Rate limit key (e.g. rate:user:12345)
-- ARGV[1]: Max burst capacity (e.g. 10 requests)
-- ARGV[2]: Emission interval in seconds (e.g. 1 second per request)
-- ARGV[3]: Current epoch timestamp in seconds

local key = KEYS[1]
local max_burst = tonumber(ARGV[1])
local emission_interval = tonumber(ARGV[2])
local now = tonumber(ARGV[3])

local burst_offset = max_burst * emission_interval
local tat = redis.call('GET', key)

if not tat then
    tat = now
else
    tat = tonumber(tat)
end

local new_tat = math.max(tat, now) + emission_interval
local allow_at = new_tat - burst_offset

if allow_at <= now then
    redis.call('SET', key, new_tat, 'EX', math.ceil(burst_offset + emission_interval))
    local remaining = math.floor((burst_offset - (new_tat - now)) / emission_interval)
    return { 1, math.max(0, remaining), math.ceil(new_tat - now) }
else
    local retry_after = math.ceil(allow_at - now)
    return { 0, 0, retry_after }
end

The script returns an array with three values:

  1. Allowed flag (1 for allowed, 0 for blocked).
  2. Remaining tokens in the current burst window.
  3. Reset/Retry-After seconds until quota becomes available.
Expand
GCRA Rate Limiting
Generic Cell Rate Algorithm Rate Limiter

Express / Node.js Middleware Implementation

Integrate the GCRA Lua script into an API gateway or web framework:

typescript
// rate-limit-middleware.ts
import { Request, Response, NextFunction } from "express";
import Redis from "ioredis";
import { readFileSync } from "node:fs";

const redis = new Redis("redis://10.0.0.4:6379");
const gcraScript = readFileSync("./gcra.lua", "utf8");

export function rateLimiter(capacity = 30, intervalSeconds = 1) {
  return async (req: Request, res: Response, next: NextFunction) => {
    const clientId = req.headers["x-api-key"] || req.ip;
    const key = "ratelimit:" + clientId;
    const now = Date.now() / 1000;

    try {
      const [allowed, remaining, resetTime] = (await redis.eval(
        gcraScript,
        1,
        key,
        capacity,
        intervalSeconds,
        now
      )) as [number, number, number];

      // Standard IETF RateLimit Headers
      res.setHeader("RateLimit-Limit", capacity);
      res.setHeader("RateLimit-Remaining", remaining);
      res.setHeader("RateLimit-Reset", resetTime);

      if (allowed === 1) {
        return next();
      }

      res.setHeader("Retry-After", resetTime);
      return res.status(429).json({
        type: "https://example.com/errors/rate-limit-exceeded",
        title: "Too Many Requests",
        status: 429,
        detail: "API quota exceeded. Retry in " + resetTime + " seconds.",
        retry_after: resetTime,
      });
    } catch (err) {
      console.error("Rate limiter error, failing open:", err);
      return next();
    }
  };
}
Generic Cell Rate Algorithm (GCRA) vs Fixed Window GCRA Theoretical Arrival Time - Continuous sliding rate control - Single integer stored per key (TAT) - Zero boundary burst vulnerability - Standard RateLimit HTTP headers - Evaluated atomically in Valkey Fixed Window Counter (Flawed) - INCR key + EXPIRE 60 - 100 req at 00:59 + 100 req at 01:00 - 200 requests within 2 seconds - High database spikes - Discontinuous throttling GCRA smoothly paces API client throughput with minimal Redis memory overhead
Algorithms and Data Structures for System Design and Concurrency
YouTube

Benchmarking Rate Enforcement

Test rate limiter throughput and verify HTTP 429 response formatting using curl or load generators:

bash
# Send rapid requests in succession
for i in {1..35}; do
  curl -s -o /dev/null -w "%{http_code} RateLimit-Remaining: %{header_json}
"     http://localhost:3000/api/data
done

Requests 1 to 30 return HTTP 200 OK, while requests 31 to 35 immediately return HTTP 429 Too Many Requests with accurate Retry-After values.

Deploy high-throughput Valkey and API microservices on Netbay Linux VPS in under 60 seconds with High-Speed SSD in Lucknow DC01 — netbayhosts.in.

Keep reading

Follow along on a real VPS

Deploy Linux in under 60 seconds

These guides are written against Ubuntu, Debian, and RHEL-family images — the same ones on NetBay.

Deploy an instance