{
  "schema_version": 2,
  "id": "develop/use-cases/rate-limiter/go",
  "title": "Token bucket rate limiter with Redis and Go",
  "url": "https://redis.io/docs/latest/develop/use-cases/rate-limiter/go/",
  "summary": "Implement a token bucket rate limiter using Redis and Lua scripts in Go",
  "aliases": [
    "/develop/use-cases/rate-limiter/go-redis"
  ],
  "tags": [
    "docs",
    "develop",
    "stack",
    "oss",
    "rs",
    "rc"
  ],
  "last_updated": "2026-09-29T14:34:47+01:00",
  "children": [],
  "page_type": "content",
  "content_hash": "fb15ac31a18b2f5376c028f48a8da54be875985e3b08edebdc538bad4734ab05",
  "sections": [
    {
      "id": "overview-1",
      "title": "Overview",
      "role": "overview",
      "text": "This guide shows you how to implement a distributed token bucket rate limiter using Redis and Lua scripts in Go with the [`go-redis`](https://redis.io/docs/latest/develop/clients/go) client library."
    },
    {
      "id": "overview",
      "title": "Overview",
      "role": "overview",
      "text": "Rate limiting is a critical technique for controlling the rate at which operations are performed. Common use cases include:\n\n* Limiting API requests per user or IP address\n* Preventing abuse and protecting against denial-of-service attacks\n* Ensuring fair resource allocation across multiple clients\n* Throttling background jobs or batch operations\n\nThe **token bucket algorithm** is a popular rate limiting approach that allows bursts of traffic while maintaining an average rate limit over time. This guide covers the Go implementation using the [`go-redis`](https://redis.io/docs/latest/develop/clients/go) client library, taking advantage of Go's `context.Context` for cancellation and timeouts, explicit error handling, and goroutine safety."
    },
    {
      "id": "how-it-works",
      "title": "How it works",
      "role": "content",
      "text": "The token bucket algorithm works like a bucket that holds tokens:\n\n1. **Initialization**: The bucket starts with a maximum capacity of tokens\n2. **Refill**: Tokens are added to the bucket at a constant rate (for example, 1 token per second)\n3. **Consumption**: Each request consumes one token from the bucket\n4. **Decision**: If tokens are available, the request is allowed; otherwise, it's denied\n5. **Capacity limit**: The bucket never exceeds its maximum capacity\n\nThis approach allows for burst traffic (using accumulated tokens) while enforcing an average rate limit over time."
    },
    {
      "id": "why-use-redis",
      "title": "Why use Redis?",
      "role": "content",
      "text": "Redis is ideal for distributed rate limiting because:\n\n* **Atomic operations**: Lua scripts execute atomically, preventing race conditions\n* **Shared state**: Multiple application servers can share the same rate limit counters\n* **High performance**: In-memory operations provide microsecond latency\n* **Automatic expiration**: Keys can be set to expire automatically (though not used in this implementation)"
    },
    {
      "id": "the-lua-script",
      "title": "The Lua script",
      "role": "content",
      "text": "The core of this implementation is a Lua script that runs atomically on the Redis server. This ensures that checking and updating the token bucket happens in a single operation, preventing race conditions in distributed environments.\n\nHere's how the script works:\n\n[code example]"
    },
    {
      "id": "script-breakdown",
      "title": "Script breakdown",
      "role": "content",
      "text": "1. **State retrieval**: Uses [`HMGET`](https://redis.io/docs/latest/commands/hmget) to fetch the current token count and last refill time from a hash\n2. **Initialization**: On first use, sets tokens to full capacity\n3. **Token refill calculation**: Computes how many tokens should be added based on elapsed time\n4. **Capacity enforcement**: Uses `math.min()` to ensure tokens never exceed capacity\n5. **Token consumption**: Decrements the token count if available\n6. **State update**: Uses [`HMSET`](https://redis.io/docs/latest/commands/hmset) to save the new state\n7. **Return value**: Returns both the decision (allowed/denied) and remaining tokens"
    },
    {
      "id": "why-atomicity-matters",
      "title": "Why atomicity matters",
      "role": "content",
      "text": "Without atomic execution, race conditions could occur:\n\n* **Double spending**: Two requests could read the same token count and both succeed when only one should\n* **Lost updates**: Concurrent updates could overwrite each other's changes\n* **Inconsistent state**: Token count and refill time could become desynchronized\n\nUsing [`EVAL`](https://redis.io/docs/latest/commands/eval) or [`EVALSHA`](https://redis.io/docs/latest/commands/evalsha) ensures the entire operation executes atomically, making it safe for distributed systems."
    },
    {
      "id": "installation",
      "title": "Installation",
      "role": "setup",
      "text": "Install the `go-redis` package:\n\n[code example]"
    },
    {
      "id": "using-the-go-package",
      "title": "Using the Go package",
      "role": "content",
      "text": "The `TokenBucket` struct provides a concurrency-safe interface for rate limiting\n([source](https://github.com/redis/docs/blob/main/content/develop/use-cases/rate-limiter/go/token_bucket.go)):\n\n[code example]\n\nGo's `context.Context` is passed to every call, allowing you to set deadlines and handle cancellation. The `Allow` method returns an explicit `error` value following Go conventions."
    },
    {
      "id": "configuration-parameters",
      "title": "Configuration parameters",
      "role": "configuration",
      "text": "* **Capacity**: Maximum number of tokens in the bucket (controls burst size)\n* **RefillRate**: Number of tokens added per refill interval\n* **RefillInterval**: Time in seconds between refills\n\nFor example:\n* `Capacity: 10, RefillRate: 1, RefillInterval: 1.0` allows 1 request per second with bursts up to 10\n* `Capacity: 100, RefillRate: 10, RefillInterval: 1.0` allows 10 requests per second with bursts up to 100\n* `Capacity: 60, RefillRate: 1, RefillInterval: 60.0` allows 1 request per minute with bursts up to 60"
    },
    {
      "id": "rate-limit-keys",
      "title": "Rate limit keys",
      "role": "content",
      "text": "The `key` parameter identifies what you're rate limiting. Common patterns:\n\n* **Per user**: `user:{userID}` - Limit each user independently\n* **Per IP address**: `ip:{ipAddress}` - Limit by client IP\n* **Per API endpoint**: `api:{endpoint}:{userID}` - Different limits per endpoint\n* **Global**: `global:api` - Single limit shared across all requests"
    },
    {
      "id": "script-caching-with-evalsha",
      "title": "Script caching with EVALSHA",
      "role": "content",
      "text": "The Go implementation uses [`EVALSHA`](https://redis.io/docs/latest/commands/evalsha) for optimal performance. On first use, the Lua script is loaded into Redis with `SCRIPT LOAD`, and subsequent calls use the cached SHA1 hash. If the script is evicted from the cache, the module automatically falls back to [`EVAL`](https://redis.io/docs/latest/commands/eval) and reloads the script. The script loading is protected with a `sync.Once` to ensure thread safety across goroutines.\n\n[code example]"
    },
    {
      "id": "goroutine-safety",
      "title": "Goroutine safety",
      "role": "content",
      "text": "The `TokenBucket` struct is safe for concurrent use from multiple goroutines. You can share a single limiter instance across your application:\n\n[code example]"
    },
    {
      "id": "running-the-demo",
      "title": "Running the demo",
      "role": "content",
      "text": ""
    },
    {
      "id": "get-the-source-files",
      "title": "Get the source files",
      "role": "content",
      "text": "The demo consists of four files. Download them from the [`go` source folder](https://github.com/redis/docs/tree/main/content/develop/use-cases/rate-limiter/go) on GitHub, or grab them with `curl`:\n\n[code example]"
    },
    {
      "id": "start-the-demo-server",
      "title": "Start the demo server",
      "role": "content",
      "text": "A demonstration HTTP server is included to show the rate limiter in action\n([source](https://github.com/redis/docs/blob/main/content/develop/use-cases/rate-limiter/go/demo_server.go)):\n\nTo run the demo, create a `main.go` file that calls the exported `RunDemoServer()` function:\n\n[code example]\n\nThen build and run:\n\n[code example]\n\nThe demo provides an interactive web interface where you can:\n\n* Submit requests and see them allowed or denied in real-time\n* View the current token count\n* Adjust rate limit parameters dynamically\n* Test different rate limiting scenarios\n\nThe demo assumes Redis is running on `localhost:6379` but you can specify a different host and port using the `--redis-host` and `--redis-port` command-line arguments. Visit `http://localhost:8080` in your browser to try it out."
    },
    {
      "id": "response-headers",
      "title": "Response headers",
      "role": "returns",
      "text": "It's common to include rate limit information in HTTP response headers:\n\n[code example]"
    },
    {
      "id": "customization",
      "title": "Customization",
      "role": "content",
      "text": ""
    },
    {
      "id": "using-as-http-middleware",
      "title": "Using as HTTP middleware",
      "role": "content",
      "text": "You can wrap the rate limiter as HTTP middleware for use with the standard library or any compatible router:\n\n[code example]"
    },
    {
      "id": "error-handling",
      "title": "Error handling",
      "role": "errors",
      "text": "The `Allow` method returns an error if the Redis connection is lost. Decide whether to fail open (allow requests) or fail closed (deny requests) based on your requirements:\n\n[code example]"
    },
    {
      "id": "using-with-context-timeouts",
      "title": "Using with context timeouts",
      "role": "content",
      "text": "Go's `context.Context` lets you set deadlines for rate limit checks, which is useful when you don't want a slow Redis connection to block request processing:\n\n[code example]"
    },
    {
      "id": "alternative-rate-limiting-algorithms",
      "title": "Alternative rate limiting algorithms",
      "role": "content",
      "text": "The token bucket algorithm above handles most use cases but Redis supports\nother rate limiter patterns that might fit your requirements better. The table\nbelow lists four other algorithms alongside token bucket and summarizes\ntheir features:\n\n| Algorithm | Memory | Accuracy | Burst behavior | Best for |\n|---|---|---|---|---|\n| [Token bucket](#how-it-works) | 1 key (hash) | Exact | Controlled bursts | APIs with bursty traffic |\n| [Fixed window counter](#fixed-window-counter) | 1 key (string) | Approximate | 2x burst at boundaries | Simple API limits |\n| [Sliding window log](#sliding-window-log) | O(n) entries | Exact | No bursts | High-value APIs, audit trails |\n| [Sliding window counter](#sliding-window-counter) | 2 keys (string) | Near-exact | Smoothed boundaries | General-purpose APIs |\n| [Leaky bucket (policing)](#leaky-bucket-policing) | 1 key (hash) | Exact | No bursts | Strict no-burst enforcement |\n\nThe sections below give example implementations of these other algorithms.\nThe three time-based algorithms call `redis.call('TIME')` inside the Lua\nscript to derive the current timestamp from the Redis server clock. This\neliminates clock drift when the limiter runs across multiple application\nservers. The fixed window counter reads no clock: the key's TTL defines\nthe window."
    },
    {
      "id": "fixed-window-counter",
      "title": "Fixed window counter",
      "role": "content",
      "text": "Counts requests within discrete, non-overlapping time intervals.\nSimplest algorithm — one key per window, one `EVAL` round trip.\n\n[code example]\n\n**Trade-off**: A client can make 2x requests by sending `limit` requests\nat the end of one window and `limit` requests at the start of the next."
    },
    {
      "id": "sliding-window-log",
      "title": "Sliding window log",
      "role": "content",
      "text": "Records the exact timestamp of every request in a sorted set.\nProvides a true rolling window with no boundary bursts.\n\n[code example]\n\n**Trade-off**: Memory grows O(n) with request volume. Not ideal for\nhigh-volume, high-cardinality rate limiting."
    },
    {
      "id": "sliding-window-counter",
      "title": "Sliding window counter",
      "role": "content",
      "text": "Blends two fixed-window counters using a weighted average to approximate\na true sliding window. Near-exact accuracy with the same low memory\nfootprint as a fixed window. The two keys use hash tags so they map\nto the same slot in Redis Cluster.\n\n[code example]\n\n**Trade-off**: The weighted estimate may let slightly more or fewer\nrequests through than the exact limit. Negligible for most apps."
    },
    {
      "id": "leaky-bucket-policing",
      "title": "Leaky bucket (policing)",
      "role": "content",
      "text": "A virtual bucket fills with incoming requests and drains at a fixed rate.\nIf the bucket is full, requests are rejected immediately. This is the\npolicing variant — requests are allowed or denied instantly with no delay.\n\n[code example]\n\n**Trade-off**: Overflow traffic is rejected immediately. Clients must\nhandle `429 Too Many Requests` and retry with backoff."
    },
    {
      "id": "learn-more",
      "title": "Learn more",
      "role": "related",
      "text": "* [EVAL command](https://redis.io/docs/latest/commands/eval) - Execute Lua scripts\n* [EVALSHA command](https://redis.io/docs/latest/commands/evalsha) - Execute cached Lua scripts\n* [Lua scripting](https://redis.io/docs/latest/develop/programmability/eval-intro) - Introduction to Redis Lua scripting\n* [HMGET command](https://redis.io/docs/latest/commands/hmget) - Get multiple hash fields\n* [HMSET command](https://redis.io/docs/latest/commands/hmset) - Set multiple hash fields\n* [Go client](https://redis.io/docs/latest/develop/clients/go) - Redis Go client documentation"
    }
  ],
  "examples": [
    {
      "id": "the-lua-script-ex0",
      "language": "lua",
      "code": "local key = KEYS[1]\nlocal capacity = tonumber(ARGV[1])\nlocal refill_rate = tonumber(ARGV[2])\nlocal refill_interval = tonumber(ARGV[3])\nlocal now = tonumber(ARGV[4])\n\n-- Get current state or initialize\nlocal bucket = redis.call('HMGET', key, 'tokens', 'last_refill')\nlocal tokens = tonumber(bucket[1])\nlocal last_refill = tonumber(bucket[2])\n\n-- Initialize if this is the first request\nif tokens == nil then\n    tokens = capacity\n    last_refill = now\nend\n\n-- Calculate token refill\nlocal time_passed = now - last_refill\nlocal refills = math.floor(time_passed / refill_interval)\n\nif refills > 0 then\n    tokens = math.min(capacity, tokens + (refills * refill_rate))\n    last_refill = last_refill + (refills * refill_interval)\nend\n\n-- Try to consume a token\nlocal allowed = 0\nif tokens >= 1 then\n    tokens = tokens - 1\n    allowed = 1\nend\n\n-- Update state\nredis.call('HMSET', key, 'tokens', tokens, 'last_refill', last_refill)\n\n-- Return result: allowed (1 or 0) and remaining tokens\nreturn {allowed, tokens}",
      "section_id": "the-lua-script"
    },
    {
      "id": "installation-ex0",
      "language": "bash",
      "code": "go get github.com/redis/go-redis/v9",
      "section_id": "installation"
    },
    {
      "id": "using-the-go-package-ex0",
      "language": "go",
      "code": "package main\n\nimport (\n    \"context\"\n    \"fmt\"\n    \"log\"\n    \"time\"\n\n    \"github.com/redis/go-redis/v9\"\n)\n\nfunc main() {\n    ctx := context.Background()\n\n    // Create a Redis connection\n    rdb := redis.NewClient(&redis.Options{\n        Addr: \"localhost:6379\",\n    })\n\n    // Create a rate limiter: 10 requests per second\n    limiter := NewTokenBucket(TokenBucketConfig{\n        Client:         rdb,\n        Capacity:       10,           // Maximum burst size\n        RefillRate:     1,            // Add 1 token per interval\n        RefillInterval: time.Second,  // Every 1 second\n    })\n\n    // Check if a request should be allowed\n    allowed, remaining, err := limiter.Allow(ctx, \"user:123\")\n    if err != nil {\n        log.Fatalf(\"rate limiter error: %v\", err)\n    }\n\n    if allowed {\n        fmt.Printf(\"Request allowed. %.0f tokens remaining.\\n\", remaining)\n        // Process the request\n    } else {\n        fmt.Println(\"Request denied. Rate limit exceeded.\")\n        // Return 429 Too Many Requests\n    }\n}",
      "section_id": "using-the-go-package"
    },
    {
      "id": "script-caching-with-evalsha-ex0",
      "language": "go",
      "code": "// The package handles script caching automatically.\n// First call loads the script, subsequent calls use EVALSHA.\nallowed1, remaining1, _ := limiter.Allow(ctx, \"user:123\") // Uses EVAL + caches\nallowed2, remaining2, _ := limiter.Allow(ctx, \"user:123\") // Uses EVALSHA (faster)",
      "section_id": "script-caching-with-evalsha"
    },
    {
      "id": "goroutine-safety-ex0",
      "language": "go",
      "code": "limiter := NewTokenBucket(TokenBucketConfig{\n    Client:         rdb,\n    Capacity:       10,\n    RefillRate:     1,\n    RefillInterval: time.Second,\n})\n\n// Safe to call from multiple goroutines\nvar wg sync.WaitGroup\nfor i := 0; i < 20; i++ {\n    wg.Add(1)\n    go func(id int) {\n        defer wg.Done()\n        allowed, remaining, err := limiter.Allow(ctx, \"shared:resource\")\n        if err != nil {\n            log.Printf(\"goroutine %d: error: %v\", id, err)\n            return\n        }\n        log.Printf(\"goroutine %d: allowed=%v remaining=%.0f\", id, allowed, remaining)\n    }(i)\n}\nwg.Wait()",
      "section_id": "goroutine-safety"
    },
    {
      "id": "get-the-source-files-ex0",
      "language": "bash",
      "code": "mkdir rate-limiter-demo && cd rate-limiter-demo\nBASE=https://raw.githubusercontent.com/redis/docs/main/content/develop/use-cases/rate-limiter/go\ncurl -O $BASE/token_bucket.go\ncurl -O $BASE/demo_server.go\ncurl -O $BASE/go.mod\ncurl -O $BASE/go.sum",
      "section_id": "get-the-source-files"
    },
    {
      "id": "start-the-demo-server-ex0",
      "language": "go",
      "code": "package main\n\nimport \"ratelimiter\"\n\nfunc main() { ratelimiter.RunDemoServer() }",
      "section_id": "start-the-demo-server"
    },
    {
      "id": "start-the-demo-server-ex1",
      "language": "bash",
      "code": "# Install dependencies\ngo get github.com/redis/go-redis/v9\n\n# Build and run the demo server\ngo build -o demo ./...\n./demo",
      "section_id": "start-the-demo-server"
    },
    {
      "id": "response-headers-ex0",
      "language": "go",
      "code": "// Store config values for use in response headers\ncapacity := 10\nrefillInterval := time.Second\n\nlimiter := NewTokenBucket(TokenBucketConfig{\n    Client:         rdb,\n    Capacity:       capacity,\n    RefillRate:     1,\n    RefillInterval: refillInterval,\n})\n\nallowed, remaining, err := limiter.Allow(ctx, fmt.Sprintf(\"user:%s\", userID))\nif err != nil {\n    http.Error(w, \"Internal Server Error\", http.StatusInternalServerError)\n    return\n}\n\n// Add standard rate limit headers\nw.Header().Set(\"X-RateLimit-Limit\", strconv.Itoa(capacity))\nw.Header().Set(\"X-RateLimit-Remaining\", strconv.FormatFloat(remaining, 'f', 0, 64))\nw.Header().Set(\"X-RateLimit-Reset\", strconv.FormatInt(\n    time.Now().Add(refillInterval).Unix(), 10))\n\nif !allowed {\n    w.Header().Set(\"Retry-After\", strconv.FormatFloat(refillInterval.Seconds(), 'f', 0, 64))\n    http.Error(w, \"Too Many Requests\", http.StatusTooManyRequests)\n    return\n}",
      "section_id": "response-headers"
    },
    {
      "id": "using-as-http-middleware-ex0",
      "language": "go",
      "code": "func RateLimitMiddleware(limiter *TokenBucket, keyFn func(*http.Request) string) func(http.Handler) http.Handler {\n    return func(next http.Handler) http.Handler {\n        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n            key := keyFn(r)\n            allowed, remaining, err := limiter.Allow(r.Context(), key)\n            if err != nil {\n                http.Error(w, \"Internal Server Error\", http.StatusInternalServerError)\n                return\n            }\n\n            w.Header().Set(\"X-RateLimit-Remaining\", strconv.FormatFloat(remaining, 'f', 0, 64))\n\n            if !allowed {\n                http.Error(w, \"Rate limit exceeded\", http.StatusTooManyRequests)\n                return\n            }\n            next.ServeHTTP(w, r)\n        })\n    }\n}\n\n// Apply per-IP rate limiting\nmux := http.NewServeMux()\nhandler := RateLimitMiddleware(limiter, func(r *http.Request) string {\n    return fmt.Sprintf(\"ip:%s\", r.RemoteAddr)\n})(mux)",
      "section_id": "using-as-http-middleware"
    },
    {
      "id": "error-handling-ex0",
      "language": "go",
      "code": "allowed, remaining, err := limiter.Allow(ctx, \"user:123\")\nif err != nil {\n    log.Printf(\"rate limiter error: %v\", err)\n    // Fail open: allow the request when Redis is unavailable\n    allowed = true\n    // Or fail closed: deny the request\n    // allowed = false\n}",
      "section_id": "error-handling"
    },
    {
      "id": "using-with-context-timeouts-ex0",
      "language": "go",
      "code": "// Set a 100ms timeout for the rate limit check\nctx, cancel := context.WithTimeout(r.Context(), 100*time.Millisecond)\ndefer cancel()\n\nallowed, remaining, err := limiter.Allow(ctx, \"user:123\")\nif err != nil {\n    // Context deadline exceeded or Redis error\n    log.Printf(\"rate limit check timed out: %v\", err)\n    allowed = true // fail open\n}",
      "section_id": "using-with-context-timeouts"
    },
    {
      "id": "fixed-window-counter-ex0",
      "language": "go",
      "code": "import (\n\t\"context\"\n\n\t\"github.com/redis/go-redis/v9\"\n)\n\nconst fixedWindowScript = `\nlocal key    = KEYS[1]\nlocal limit  = tonumber(ARGV[1])\nlocal window = tonumber(ARGV[2])\n\nlocal count = redis.call('INCR', key)\nif count == 1 then\n    redis.call('EXPIRE', key, window)\nend\n\nlocal ttl = redis.call('PTTL', key)\n\nif count > limit then\n    return {0, ttl}\nend\nreturn {1, ttl}\n`\n\n// FixedWindowAllow reports whether key is within limit for the current window.\n// When a request is denied, it also returns the milliseconds until the window resets.\nfunc FixedWindowAllow(ctx context.Context, rdb *redis.Client, key string,\n\tlimit, windowSeconds int) (bool, int64, error) {\n\n\tscript := redis.NewScript(fixedWindowScript)\n\tres, err := script.Run(ctx, rdb, []string{key}, limit, windowSeconds).Int64Slice()\n\tif err != nil {\n\t\treturn false, 0, err\n\t}\n\n\tallowed := res[0] == 1\n\tif allowed {\n\t\treturn true, 0, nil\n\t}\n\treturn false, res[1], nil\n}",
      "section_id": "fixed-window-counter"
    },
    {
      "id": "sliding-window-log-ex0",
      "language": "go",
      "code": "import (\n\t\"context\"\n\t\"crypto/rand\"\n\t\"encoding/hex\"\n\n\t\"github.com/redis/go-redis/v9\"\n)\n\nconst slidingWindowLogScript = `\nlocal key    = KEYS[1]\nlocal limit  = tonumber(ARGV[1])\nlocal window = tonumber(ARGV[2])\nlocal member = ARGV[3]\n\nlocal t      = redis.call('TIME')\nlocal now    = tonumber(t[1]) + tonumber(t[2]) / 1e6\nlocal cutoff = now - window\n\nredis.call('ZREMRANGEBYSCORE', key, '-inf', cutoff)\n\nlocal count = redis.call('ZCARD', key)\n\nif count < limit then\n    redis.call('ZADD', key, now, member)\n    redis.call('EXPIRE', key, window * 2)\n    return {1, 0}\nend\n\nlocal oldest = redis.call('ZRANGE', key, 0, 0, 'WITHSCORES')\nlocal retry_after_ms = 0\nif oldest[2] then\n    retry_after_ms = math.floor((tonumber(oldest[2]) + window - now) * 1000)\nend\n\nreturn {0, retry_after_ms}\n`\n\n// SlidingWindowLogAllow reports whether key is within limit over the trailing\n// window, and the milliseconds until the oldest entry leaves it.\nfunc SlidingWindowLogAllow(ctx context.Context, rdb *redis.Client, key string,\n\tlimit, windowSeconds int) (bool, int64, error) {\n\n\tbuf := make([]byte, 16)\n\tif _, err := rand.Read(buf); err != nil {\n\t\treturn false, 0, err\n\t}\n\tmember := hex.EncodeToString(buf)\n\n\tscript := redis.NewScript(slidingWindowLogScript)\n\tres, err := script.Run(ctx, rdb, []string{key}, limit, windowSeconds, member).Int64Slice()\n\tif err != nil {\n\t\treturn false, 0, err\n\t}\n\treturn res[0] == 1, res[1], nil\n}",
      "section_id": "sliding-window-log"
    },
    {
      "id": "sliding-window-counter-ex0",
      "language": "go",
      "code": "import (\n\t\"context\"\n\n\t\"github.com/redis/go-redis/v9\"\n)\n\nconst slidingWindowCounterScript = `\nlocal base   = KEYS[1]\nlocal limit  = tonumber(ARGV[1])\nlocal window = tonumber(ARGV[2])\n\nlocal t   = redis.call('TIME')\nlocal now = tonumber(t[1]) + tonumber(t[2]) / 1e6\n\nlocal window_num = math.floor(now / window)\nlocal elapsed     = (now % window) / window\n\nlocal curr_key = base .. ':' .. window_num\nlocal prev_key = base .. ':' .. (window_num - 1)\n\nlocal prev = tonumber(redis.call('GET', prev_key) or 0)\nlocal curr = tonumber(redis.call('GET', curr_key) or 0)\n\nlocal estimate = prev * (1 - elapsed) + curr\n\nif estimate >= limit then\n    return {0, 0}\nend\n\nlocal new_count = redis.call('INCR', curr_key)\nif new_count == 1 then\n    redis.call('EXPIRE', curr_key, window * 2)\nend\n\nreturn {1, 0}\n`\n\n// SlidingWindowCounterAllow reports whether key is within limit, weighting the\n// previous window's count by how far the current window has progressed.\nfunc SlidingWindowCounterAllow(ctx context.Context, rdb *redis.Client, key string,\n\tlimit, windowSeconds int) (bool, error) {\n\n\tscript := redis.NewScript(slidingWindowCounterScript)\n\tres, err := script.Run(ctx, rdb, []string{\"{\" + key + \"}\"}, limit, windowSeconds).Int64Slice()\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treturn res[0] == 1, nil\n}",
      "section_id": "sliding-window-counter"
    },
    {
      "id": "leaky-bucket-policing-ex0",
      "language": "go",
      "code": "import (\n\t\"context\"\n\n\t\"github.com/redis/go-redis/v9\"\n)\n\nconst leakyBucketScript = `\nlocal key       = KEYS[1]\nlocal capacity  = tonumber(ARGV[1])\nlocal leak_rate = tonumber(ARGV[2])\n\nlocal t   = redis.call('TIME')\nlocal now = tonumber(t[1]) + tonumber(t[2]) / 1e6\n\nlocal data      = redis.call('HGETALL', key)\nlocal level     = 0\nlocal last_leak = now\n\nif #data > 0 then\n    for i = 1, #data, 2 do\n        if data[i] == 'level' then\n            level = tonumber(data[i+1])\n        elseif data[i] == 'last_leak' then\n            last_leak = tonumber(data[i+1])\n        end\n    end\nend\n\nlocal elapsed = now - last_leak\nlevel = math.max(0, level - elapsed * leak_rate)\n\nif level + 1 > capacity then\n    return {0, math.floor((level + 1 - capacity) / leak_rate * 1000)}\nend\n\nlevel = level + 1\nlocal ttl = math.ceil(capacity / leak_rate) + 1\n\nredis.call('HSET', key, 'level', level, 'last_leak', now)\nredis.call('EXPIRE', key, ttl)\n\nreturn {1, 0}\n`\n\n// LeakyBucketAllow adds one request to the bucket for key and reports whether it\n// fits. When the bucket is full it returns the milliseconds until it drains enough.\nfunc LeakyBucketAllow(ctx context.Context, rdb *redis.Client, key string,\n\tcapacity int, leakRate float64) (bool, int64, error) {\n\n\tscript := redis.NewScript(leakyBucketScript)\n\tres, err := script.Run(ctx, rdb, []string{key}, capacity, leakRate).Int64Slice()\n\tif err != nil {\n\t\treturn false, 0, err\n\t}\n\treturn res[0] == 1, res[1], nil\n}",
      "section_id": "leaky-bucket-policing"
    }
  ]
}
