Skip to content

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.


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!"))
})
}
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)
}
package main
import (
"context"
"strings"
"time"
"github.com/balramadan/distlimit"
"github.com/balramadan/distlimit/driver/memory"
)
// Custom Tier Resolver implementation
type 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)
}