Dynamic Policy Reload & Tiering
Traditional rate limiters require application restarts or global mutex locks to update rate limits. distlimit features a Lock-Free Dynamic Policy Engine powered by Go’s atomic.Pointer[Policy] for zero-downtime rule updates and multi-tenant tier resolution.
⚡ Runtime Policy Reload (UpdatePolicy)
Section titled “⚡ Runtime Policy Reload (UpdatePolicy)”Call limiter.UpdatePolicy(newLimit, newWindow) anywhere in your code to update global limits instantly:
package main
import ( "net/http" "time"
"github.com/balramadan/distlimit" "github.com/balramadan/distlimit/driver/memory")
func main() { memDriver := memory.New(5 * time.Minute)
// Initialize Limiter with default rule: 100 requests per minute limiter, _ := distlimit.New( memDriver, distlimit.WithLimit(100), distlimit.WithWindow(1*time.Minute), )
// Admin API endpoint to update rate limits dynamically http.HandleFunc("/admin/update-limits", func(w http.ResponseWriter, r *http.Request) { // Instantly update limit to 500 req/min at runtime with zero lock delay limiter.UpdatePolicy(500, 1*time.Minute) w.Write([]byte("Rate limit policy updated successfully!")) })}How It Works Under The Hood
Section titled “How It Works Under The Hood”sequenceDiagram
autonumber
actor Admin
participant Limiter as Limiter (atomic.Pointer)
actor Goroutine as HTTP Worker Goroutines
Admin->>Limiter: UpdatePolicy(500, 1m)
Limiter->>Limiter: Allocate new Policy struct
Limiter->>Limiter: atomic.Pointer.Store(&newPolicy)
Goroutine->>Limiter: AllowKey(ctx, key)
Limiter->>Goroutine: atomic.Pointer.Load() -> Evaluates 500 req/m
Because pointer replacement is executed via atomic CPU operations, active worker goroutines reading the policy experience zero lock contention ($O(1)$ nanosecond execution).
👑 Multi-Tenant Tier Resolution (PolicyResolver)
Section titled “👑 Multi-Tenant Tier Resolution (PolicyResolver)”In multi-tenant SaaS platforms, different user tiers (e.g. Free, Pro, Enterprise) require custom rate limits without instantiating separate limiter objects.
The PolicyResolver interface allows you to resolve dynamic Policy rules per key at runtime:
type PolicyResolver interface { ResolvePolicy(ctx context.Context, key string) (Policy, bool)}Implementation Example
Section titled “Implementation Example”package main
import ( "context" "strings" "time"
"github.com/balramadan/distlimit" "github.com/balramadan/distlimit/driver/memory")
// Custom Tier Resolver implementationtype CustomTierResolver struct{}
func (r *CustomTierResolver) ResolvePolicy(ctx context.Context, key string) (distlimit.Policy, bool) { // Enterprise VIP keys get 10,000 requests per minute if strings.HasPrefix(key, "vip:") { return distlimit.Policy{ Limit: 10000, Window: 1 * time.Minute, }, true }
// Pro Users get 1,000 requests per minute if strings.HasPrefix(key, "pro:") { return distlimit.Policy{ Limit: 1000, Window: 1 * time.Minute, }, true }
// Fallback: return false to use the Limiter's default policy return distlimit.Policy{}, false}
func main() { memDriver := memory.New(5 * time.Minute)
// Attach PolicyResolver to Limiter limiter, _ := distlimit.New( memDriver, distlimit.WithLimit(100), // Default policy for Free / Unauthenticated users distlimit.WithWindow(1*time.Minute), distlimit.WithPolicyResolver(&CustomTierResolver{}), )
// Evaluation examples: limiter.AllowKey(context.Background(), "user:123") // Uses default limit (100 req/m) limiter.AllowKey(context.Background(), "pro:456") // Resolved to Pro limit (1,000 req/m) limiter.AllowKey(context.Background(), "vip:789") // Resolved to VIP limit (10,000 req/m)}