local key = KEYS[1]
local capacity = tonumber(ARGV[1])
local rate = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
local ttl = tonumber(ARGV[4])
local bucket = redis.call('HMGET', key, 'tokens', 'ts')
local tokens = tonumber(bucket[1])
local ts = tonumber(bucket[2])
if tokens == nil then
tokens = capacity
ts = now
end
-- lazy refill: accrue tokens for the elapsed interval
local elapsed = math.max(0, now - ts) / 1000.0
tokens = math.min(capacity, tokens + elapsed * rate)
local allowed = 0
if tokens >= 1 then
tokens = tokens - 1
allowed = 1
end
redis.call('HMSET', key, 'tokens', tokens, 'ts', now)
redis.call('PEXPIRE', key, ttl)
return { allowed, math.floor(tokens) }
package quota
import (
_ "embed"
"context"
"time"
"github.com/redis/go-redis/v9"
)
//go:embed bucket.lua
var bucketScript string
type Limiter struct {
rdb *redis.Client
script *redis.Script
Capacity int
Refill float64 // tokens per second
TTL time.Duration
}
type Result struct {
Allowed bool
Remaining int
}
func NewLimiter(rdb *redis.Client, capacity int, refill float64) *Limiter {
return &Limiter{
rdb: rdb,
script: redis.NewScript(bucketScript),
Capacity: capacity,
Refill: refill,
TTL: 10 * time.Minute,
}
}
func (l *Limiter) Allow(ctx context.Context, key string) (Result, error) {
now := time.Now().UnixMilli()
vals, err := l.script.Run(ctx, l.rdb,
[]string{"quota:" + key},
l.Capacity, l.Refill, now, l.TTL.Milliseconds(),
).Int64Slice()
if err != nil {
return Result{}, err
}
return Result{Allowed: vals[0] == 1, Remaining: int(vals[1])}, nil
}
package quota
import (
"log"
"net"
"net/http"
"strconv"
)
func clientIP(r *http.Request) string {
if host, _, err := net.SplitHostPort(r.RemoteAddr); err == nil {
return host
}
return r.RemoteAddr
}
func RateLimit(l *Limiter, next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
res, err := l.Allow(r.Context(), clientIP(r))
if err != nil {
// fail open: a Redis blip should not take down the API
log.Printf("rate limiter error: %v", err)
next.ServeHTTP(w, r)
return
}
w.Header().Set("X-RateLimit-Remaining", strconv.Itoa(res.Remaining))
if !res.Allowed {
retry := 1
if l.Refill > 0 {
retry = int(1/l.Refill) + 1
}
w.Header().Set("Retry-After", strconv.Itoa(retry))
http.Error(w, "rate limit exceeded", http.StatusTooManyRequests)
return
}
next.ServeHTTP(w, r)
})
}
This snippet implements a distributed token bucket rate limiter for API quota tracking, where the refill logic runs as an atomic Lua script inside Redis rather than as a background sweep job. The token bucket pattern models a bucket that holds up to capacity tokens and refills at a steady refillRate per second; each request consumes one token, and requests are rejected when the bucket is empty. Refilling lazily on each request — computing how many tokens should have accrued since the last access — avoids the classic pitfall of running a scheduler that touches millions of idle keys, and keeps the whole check-and-decrement operation race-free across many concurrent processes.
In bucket.lua, the script reads the stored tokens and ts (last refill timestamp) from a hash, calculates elapsed time against now, and adds elapsed * rate tokens capped at capacity. If at least one token remains it decrements and returns 1 (allowed) plus the current count; otherwise it returns 0. Because Redis executes Lua atomically, no other client can interleave between the read and the write, which is what makes the refill scheduler correct without locks. The script also sets a TTL so abandoned buckets self-expire, preventing unbounded key growth.
In limiter.go, the Limiter type loads the script once via redis.NewScript so Redis can cache it by SHA and invoke it with EVALSHA. Allow passes the four tuning parameters and the server-side time.Now().UnixMilli() as arguments, keeping every node's clock decision consistent by delegating time to the caller while state lives in Redis. The returned Result carries whether the request was permitted and the remaining tokens so callers can surface quota headers.
In middleware.go, RateLimit wires the limiter into an http.Handler, keying the bucket by client IP so each caller gets an independent quota. It writes X-RateLimit-Remaining and, on rejection, a Retry-After header plus a 429 status. A notable trade-off of lazy refill is that clock skew or a Redis failover can momentarily shift accrual, so the code fails open on Redis errors to avoid turning a cache blip into an outage. This design suits per-user or per-IP API quotas that must hold under horizontal scaling.
Related snips
export interface RetryOptions {
retries: number;
baseMs: number;
maxMs: number;
signal?: AbortSignal;
onRetry?: (attempt: number, delay: number, err: unknown) => void;
Exponential backoff with jitter for retries
package dbutil
import (
"context"
"github.com/jackc/pgconn"
Retry Postgres serialization failures with bounded attempts
use crossbeam::channel::unbounded;
use std::thread;
fn main() {
let (tx, rx) = unbounded();
Crossbeam for advanced concurrent data structures
// Creating a Promise
const myPromise = new Promise((resolve, reject) => {
const success = true;
setTimeout(() => {
if (success) {
Promises and async/await patterns for asynchronous JavaScript
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
Channels (mpsc) for message passing between threads
export type Settled<R> =
| { status: 'fulfilled'; value: R }
| { status: 'rejected'; reason: unknown };
export interface ConcurrencyOptions {
limit: number;
Simple concurrency limiter for batch operations
Share this code
Here's the card — post it anywhere.