🔧 ProgrammierungGitHub Release: rust-lang/rust v1.98.1 (03.09.2026)(03.09.2026 um 15:14 Uhr)
🔧 AI Nachrichten GitHub Release: openai/codex vrust-v0.155.0-alpha.3.1 (11.09.2026)(11.09.2026 um 05:17 Uhr)
🔧 AI Nachrichten GitHub Release: openai/codex vrust-v0.155.0-alpha.3.2 (11.09.2026)(11.09.2026 um 06:11 Uhr)
🔧 AI Nachrichten GitHub Release: openai/codex vrust-v0.155.0-alpha.3.3 (11.09.2026)(11.09.2026 um 06:23 Uhr)
🔧 AI Nachrichten GitHub Release: openai/codex vrust-v0.155.0-alpha.3.4 (11.09.2026)(11.09.2026 um 06:44 Uhr)
🔧 AI Nachrichten GitHub Release: openai/codex vrust-v0.155.0-alpha.3.5 (11.09.2026)(11.09.2026 um 07:07 Uhr)
🔧 AI Nachrichten GitHub Release: openai/codex vrust-v0.155.0-alpha.3.6 (11.09.2026)(11.09.2026 um 08:08 Uhr)
🔧 AI Nachrichten GitHub Release: openai/codex vrust-v0.155.0-alpha.3.7 (11.09.2026)(11.09.2026 um 10:17 Uhr)
🔧 AI Nachrichten GitHub Release: openai/codex vrust-v0.155.0-alpha.3.8 (11.09.2026)(11.09.2026 um 12:30 Uhr)
🔧 AI Nachrichten GitHub Release: openai/codex vrust-v0.155.0-alpha.3.9 (11.09.2026)(11.09.2026 um 14:01 Uhr)
🔧 ProgrammierungGitHub Release: rust-lang/rust v1.98.1 (03.09.2026)(03.09.2026 um 15:14 Uhr)
🔧 AI Nachrichten GitHub Release: openai/codex vrust-v0.155.0-alpha.3.1 (11.09.2026)(11.09.2026 um 05:17 Uhr)
🔧 AI Nachrichten GitHub Release: openai/codex vrust-v0.155.0-alpha.3.2 (11.09.2026)(11.09.2026 um 06:11 Uhr)
🔧 AI Nachrichten GitHub Release: openai/codex vrust-v0.155.0-alpha.3.3 (11.09.2026)(11.09.2026 um 06:23 Uhr)
🔧 AI Nachrichten GitHub Release: openai/codex vrust-v0.155.0-alpha.3.4 (11.09.2026)(11.09.2026 um 06:44 Uhr)
🔧 AI Nachrichten GitHub Release: openai/codex vrust-v0.155.0-alpha.3.5 (11.09.2026)(11.09.2026 um 07:07 Uhr)
🔧 AI Nachrichten GitHub Release: openai/codex vrust-v0.155.0-alpha.3.6 (11.09.2026)(11.09.2026 um 08:08 Uhr)
🔧 AI Nachrichten GitHub Release: openai/codex vrust-v0.155.0-alpha.3.7 (11.09.2026)(11.09.2026 um 10:17 Uhr)
🔧 AI Nachrichten GitHub Release: openai/codex vrust-v0.155.0-alpha.3.8 (11.09.2026)(11.09.2026 um 12:30 Uhr)
🔧 AI Nachrichten GitHub Release: openai/codex vrust-v0.155.0-alpha.3.9 (11.09.2026)(11.09.2026 um 14:01 Uhr)

🔧 Programmierung 🕛 vor 3 Monaten 14 Min Lesezeit
0

Rate Limiting Strategies in Go: Token Bucket, Leaky Bucket, and Sliding Window

↗ Quelle (dev.to)
🗣️ Stimme:
📑 Inhaltsübersicht

Hello, I'm Maneshwar. I'm building git-lrc, a Micro AI code reviewer that runs on every commit. It is free and source-available on Github.



Limiter gives you three methods, and the difference between them is the whole point of the package:





  • Allow() — returns true if a token is available right now, otherwise false. Non-blocking. Use this when you want to drop excess requests (e.g. return 429 Too Many Requests).


  • Wait(ctx) — blocks until a token is available. Respects context cancellation. Use this for background workers that should slow down, not fail.


  • Reserve() — returns a Reservation telling you how long to wait. Use this when you want to make the decision yourself — for example, fail fast if the delay exceeds some threshold.





Drop-style: HTTP middleware with Allow()



The most common use case is "limit incoming HTTP requests and reject the overflow." Here is the middleware:



CODE
package main

import (
"encoding/json"
"net/http"

"golang.org/x/time/rate"
)

func rateLimitMiddleware(next http.Handler) http.Handler {
// 10 req/sec sustained, 20 burst
limiter := rate.NewLimiter(10, 20)

return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !limiter.Allow() {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusTooManyRequests)
json.NewEncoder(w).Encode(map[string]string{
"error": "rate limit exceeded, please retry later",
})
return
}
next.ServeHTTP(w, r)
})
}

func main() {
mux := http.NewServeMux()
mux.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("hello\n"))
})

http.ListenAndServe(":8080", rateLimitMiddleware(mux))
}







A few things to note. First, the janitor goroutine: without it the map grows forever as new IPs keep showing up.



Second, r.RemoteAddr is host:port, so if you use it as the map key directly, the same client on a different ephemeral port gets a fresh bucket — which is not what you want. net.SplitHostPort fixes that.



Third, in any real deployment behind a load balancer or CDN, even the host portion of RemoteAddr is your proxy's IP — you need to extract the client IP from X-Forwarded-For or X-Real-IP (and verify the proxy is trusted, or you have just made spoofing trivial).





Wait-style: throttling outbound calls



When you are the client hitting some upstream API with a 100 req/sec ceiling, you want to slow down, not error out.



Wait is the right tool here.



CODE
func fetchAll(ctx context.Context, urls []string) {
// ~100/sec, burst of 1 (no headroom beyond the steady rate)
limiter := rate.NewLimiter(rate.Every(10*time.Millisecond), 1)

var wg sync.WaitGroup
for _, u := range urls {
if err := limiter.Wait(ctx); err != nil {
break // context cancelled
}
wg.Add(1)
go func(u string) {
defer wg.Done()
fetch(u)
}(u)
}
wg.Wait()
}







If you run this, every iteration after the first will print roughly 10ms.



The limiter does not give you 100 in a burst and then make you wait — it spaces them out exactly.



That is the leaky bucket guarantee.





Slack: a controlled amount of burstiness



Pure leaky bucket can be too strict.



If your producer is slightly bursty by nature, you may not want every single hiccup to cause queueing.



Uber's library has a "slack" knob for this.



With slack, the limiter can accumulate a small number of unspent requests during idle periods and let you burn through them in a burst later.



Worth being precise about what slack is, because it is not the same thing as a token bucket's burst capacity.



A token bucket refills continuously up to its capacity, so even a steady stream of requests can build up headroom if it briefly outpaces the consumer.



Slack only accumulates during idle time — if you are calling Take() continuously, slack does nothing.



It is a one-shot "you went quiet, so we will let you catch up" allowance, not an ongoing buffer.



CODE
// Default: allows up to 10 slack tokens (small built-in burst tolerance after idle)
rl := ratelimit.New(100)

// Strict mode: zero slack, perfectly even spacing
rl := ratelimit.New(100, ratelimit.WithoutSlack)

// Custom slack
rl := ratelimit.New(100, ratelimit.WithSlack(50))







This is the "sliding log" variant — exact, but memory is O(limit × active_keys).



For high limits (say, 10k req/min across many keys), that becomes real memory.



The standard fix is the sliding window counter variant: keep two adjacent fixed windows (the current and previous minute), each with a single integer count, and estimate the rolling count as count_current + count_previous × (1 - elapsed_fraction_of_current_window).



You lose a bit of precision near window boundaries but drop from O(limit) per key to O(1).



For most real workloads, the simple log version above is fine; reach for the counter version when memory becomes a problem.



You also want a janitor here, same as the IP limiter, to evict keys nobody has hit in a while.





A word on distributed rate limiting



Everything above runs in a single process.



The moment you scale to two instances of your service behind a load balancer, your limits are effectively doubled — each instance has its own bucket.



For a single-instance side project this does not matter.



For anything serious, you need a shared store.



The standard answer is Redis.



The github.com/go-redis/redis_rate package implements GCRA (a leaky-bucket variant) on top of Redis with a single Lua script per check, which keeps it atomic and fast. Roughly:



CODE
limiter := redis_rate.NewLimiter(rdb)
res, _ := limiter.Allow(ctx, "user:42", redis_rate.PerSecond(10))
if res.Allowed == 0 {
// reject
}





I will cover the Redis side of this — including the Lua scripts, why GCRA wins over naive sliding windows at scale, and how to handle Redis going down — in a follow-up post.



For now, just know that when you outgrow single-node limiting, this is the next stop.





Which one should you use?



If you remember one thing from this post, remember this:





  • Default to golang.org/x/time/rate. It is well-tested, context-aware, and covers 90% of cases. Use Allow for HTTP servers, Wait for outbound clients.


  • Reach for go.uber.org/ratelimit when you specifically need evenly spaced output and no bursts.


  • Roll a sliding window when your requirement is genuinely "no more than N in any rolling window of duration W" and average-rate enforcement is not enough.



The biggest mistake I see is people writing their own token bucket from scratch and getting the math subtly wrong — off-by-one on the burst size, races on the refill, drift over long runs. The libraries exist. Use them.



Now go put a 429 in front of whatever is currently melting your server.



/ | | | | |



git-lrc





Free, Micro AI Code Reviews That Run on Commit






  







AI agents write code fast. They also silently remove logic, change behavior, and introduce bugs -- without telling you. You often find out in production.


git-lrc fixes this. It hooks into git commit and reviews every diff before it lands. 60-second setup. Completely free.



See It In Action




See git-lrc catch serious security issues such as leaked credentials, expensive cloud
operations, and sensitive material in log statements








git-lrc-intro-60s.mp4









Why





  • 🤖 AI agents silently break things. Code removed. Logic changed. Edge cases gone. You won't notice until production.

  • 🔍 Catch it before it ships. AI-powered inline comments show you exactly what changed and what looks wrong.

  • 🔁 Build a





Vollständiger Original-Bericht
Ausführliche Details, Code-Beispiele & Hersteller-Stellungnahme auf dev.to.
↗ Original-Artikel auf dev.to lesen