A high-performance distributed rate limiter in Go built using the Leaky Bucket algorithm and Redis as the backend.
Ideal for APIs, microservices, and distributed systems that need accurate request throttling across multiple instances.
⭐ If you find this library useful, please consider giving it a star! It helps others discover this project. ⭐
While there are several rate limiters available for Go, this library specifically implements a strict Leaky Bucket algorithm via highly optimized atomic Lua scripts in Redis. This provides:
- Exact Smooth Throttling: Token buckets allow bursts; leaky buckets strictly smooth out requests to a constant, predictable rate.
- Zero-Dependency Core: Only depends on the official
go-redisclient, keeping your modules clean. - Sub-Second Precision: Millisecond/nanosecond resolution ensures perfect timing between cross-server requests.
- Fail-Open Fallback: Designed so that if your Redis node suddenly becomes unreachable, it won't indefinitely block or take down your API.
- Redis-Backed Distributed Limiting – works across multiple Go servers
- Precise Timing – sub-second accuracy via nanosecond timestamps
- Atomic Lua Scripts – ensures consistent rate control in Redis
- Context-Aware – supports
context.Contextfor cancellation & timeouts - Production-Ready – robust error handling and full test coverage
- Lightweight – no dependencies beyond Go and Redis
📢 ANNOUNCEMENT: Version 2 is Here! 📢
We have officially released v2! This release introduces significant algorithm improvements (fully replacing the legacy logic with the Generic Cell Rate Algorithm (GCRA)) and new plug-and-play middleware adapters for popular frameworks like Gin and Echo.
To protect existing integrations from breaking, we have properly versioned the module.
- Legacy Users (v1): If you are experiencing breaking changes from fetching
lateston the old module path, you can safely continue using v1 by pinning your application to the stablev1.0.0tag:go get github.com/alibazlamit/leaky_bucket_redis@v1.0.0- New Users & Upgraders (v2): Use the new
/v2module path (see installation below) to enjoy all the new features.
# For the latest V2 features (Recommended)
go get github.com/alibazlamit/leaky_bucket_redis/v2@latest
# For the legacy V1 stable release
go get github.com/alibazlamit/leaky_bucket_redis@v1.0.0Requirements:
- Go 1.22+
- Redis 6.0+
This library uses the Generic Cell Rate Algorithm (GCRA), which is the industry standard for sophisticated rate limiting. It's more efficient than "fixed window" or "sliding log" algorithms because it requires only a single Redis key and provides nanosecond precision.
graph TD
A[Request Arrives] --> B{Calculate TAT}
B --> C["New TAT = max(Now, Old TAT) + Emission Interval"]
C --> D{New TAT > Now + Burst?}
D -- Yes --> E[DENIED: Wait until TAT - Burst]
D -- No --> F[ALLOWED: Update TAT in Redis]
Example (Plug & Play)
package main
import (
"context"
"fmt"
"time"
leaky_bucket "github.com/alibazlamit/leaky_bucket_redis/leaky_bucket"
"github.com/redis/go-redis/v9"
)
func main() {
// 1. Initialize Redis (UniversalClient supported)
client := redis.NewClient(&redis.Options{Addr: "localhost:6379"})
defer client.Close()
// 2. Create Limiter: 10 req/sec with burst of 5
limiter := leaky_bucket.New(client, 10.0, leaky_bucket.WithBurst(5))
ctx := context.Background()
// 3. Allow check
res, err := limiter.Allow(ctx, "user_123")
if err != nil {
panic(err)
}
if !res.Allowed {
fmt.Printf("Rate limited. Retry after %.2s\n", res.WaitTime)
return
}
fmt.Printf("Allowed! Remaining: %d\n", res.Remaining)
}Protect any route in your favorite framework with a single line:
limiter := leaky_bucket.New(redisClient, 10.0)
r := gin.Default()
r.Use(leaky_bucket.GinMiddleware(limiter, leaky_bucket.ExtractIP))limiter := leaky_bucket.New(redisClient, 10.0)
e := echo.New()
e.Use(leaky_bucket.EchoMiddleware(limiter, leaky_bucket.ExtractIP))Customize what happens when a user is rate limited (e.g., return JSON or a custom HTML page).
mw := leaky_bucket.Middleware(limiter, leaky_bucket.ExtractIP,
leaky_bucket.WithErrorHandler(func(w http.ResponseWriter, r *http.Request, res *leaky_bucket.Result) {
w.WriteHeader(http.StatusTooManyRequests)
fmt.Fprintf(w, "Chill out! Wait until %v", res.ResetTime)
}),
)Use the WithOnLimit hook to pipe data into Prometheus, Datadog, or your logs.
mw := leaky_bucket.Middleware(limiter, leaky_bucket.ExtractIP,
leaky_bucket.WithOnLimit(func(r *http.Request, res *leaky_bucket.Result) {
metrics.Incr("rate_limit.exceeded", []string{"path:" + r.URL.Path})
log.Printf("Rate limit hit by %s", r.RemoteAddr)
}),
)Extract keys from anywhere in the request:
// By Header (e.g., API Key)
mw := leaky_bucket.Middleware(limiter, leaky_bucket.ExtractHeader("X-API-Key"))
// By Cookie
mw := leaky_bucket.Middleware(limiter, leaky_bucket.ExtractCookie("session_id"))
// Custom logic (e.g., Auth Token)
mw := leaky_bucket.Middleware(limiter, func(r *http.Request) string {
return r.Context().Value("user_id").(string)
})Creates a new Redis-based rate limiter.
| Parameter | Type | Description |
|---|---|---|
client |
redis.UniversalClient |
Supports *redis.Client, *redis.ClusterClient, etc. |
rate |
float64 |
Allowed requests per second |
opts |
...Option |
Configure WithBurst(int) |
Checks if a request is allowed for a specific key.
- Returns
*ResultwithAllowed,WaitTime,Remaining, andLimit. - Fails open on Redis errors (returns
Allowed: true).
Blocks until the request is allowed or the context is cancelled. Ideal for background workers.
limiter := leaky_bucket.NewLeakyBucket(client, "api_global", 10.0)
wait := limiter.Allow(r.Context())key := fmt.Sprintf("user_limit:%s", userID)
limiter := leaky_bucket.NewLeakyBucket(client, key, 5.0)limiter := leaky_bucket.NewLeakyBucket(client, "batch_limit", 2.0)This library implements the Generic Cell Rate Algorithm (GCRA), which is a mathematically sound approach to the Leaky Bucket algorithm. It store a single Theoretical Arrival Time (TAT) in Redis, making it:
- Memory Efficient: Only 1 key per limit scope.
- Highly Precise: Handles floating-point rates and burst capacity with nanosecond precision.
- Atomic: Uses a single, high-performance Lua script.
Smooths bursts into a steady flow
Works across multiple servers
Atomic and thread-safe
| Benchmark | Ops/sec | Avg Time |
|---|---|---|
Allow |
10,000 | ~150 µs/op |
Concurrent |
5,000 | ~300 µs/op |
Tests are hermetic and run using an in-memory Redis (miniredis), so no external Redis server is required to run them.
go test ./leaky_bucket -v
go test ./leaky_bucket -coverTests cover:
- Allow/Deny behavior
- Redis failure tolerance
- Context cancellation
- Concurrency safety
In examples/ directory:
http_api.go→ API rate limitingper_user_limiting.go→ user-based limitsbatch_processing.go→ throttled batch jobs
Run an example:
go run examples/http_api.goTo ensure stability and predictability for all users, this repository strictly adheres to Semantic Versioning and a Pull Request-based workflow:
- No Direct Pushes to
main: All changes must be submitted via Pull Requests. - Conventional Commits: Please format your PR titles and commit messages using the Conventional Commits specification (e.g.,
feat: add new adapter,fix: resolve race condition). - Automated Releases: Upon merging to
main, a GitHub Action automatically calculates the next version (major, minor, or patch) based on your commit messages and automatically creates a new tagged Release.
Pull requests and issues are highly welcome! We'd love to have your contributions.
MIT License – see LICENSE for details