Distributed Rate Limiter
A Redis-backed sliding-window rate limiter for horizontally scaled Go services.
Retired
Part of Market-Data Ingestion Platform
This project implements a sliding-window rate limiter that keeps request timestamps in Redis sorted sets, so every application instance evaluates the same shared counter. Each decision trims expired entries, counts the remaining hits inside the active window, and records the current request with a TTL that bounds storage growth.
The design is intentionally small: Redis provides atomic-ish ordering through a transaction pipeline, while Go keeps the limiter interface synchronous and easy to wrap around HTTP handlers or RPC interceptors. Denied requests remove their tentative entry after the pipeline completes, keeping the window accurate without client-side coordination.
func Allow(ctx context.Context, rdb *redis.Client, key string, limit int, window time.Duration) (bool, error) {
now := time.Now()
cutoff := now.Add(-window).UnixMilli()
member := strconv.FormatInt(now.UnixNano(), 10)
pipe := rdb.TxPipeline()
pipe.ZRemRangeByScore(ctx, key, "0", strconv.FormatInt(cutoff, 10))
count := pipe.ZCard(ctx, key)
pipe.ZAdd(ctx, key, redis.Z{Score: float64(now.UnixMilli()), Member: member})
pipe.Expire(ctx, key, window)
if _, err := pipe.Exec(ctx); err != nil {
return false, err
}
if count.Val() >= int64(limit) {
_ = rdb.ZRem(ctx, key, member).Err()
return false, nil
}
return true, nil
}
Evolution
Fail-open local fallback when Redis is unreachable
Reworked the limiter so a Redis outage degrades to a bounded per-instance window instead of failing every request, keeping ingestion alive under a dependency loss.
Sliding-window accounting with Redis sorted sets
Replaced bucketed counters with a Redis sorted-set window so each decision trims expired hits, counts the live range, and records the current request in one transaction pipeline.