{"id":29117199,"url":"https://github.com/webriots/rate","last_synced_at":"2026-01-12T02:59:40.572Z","repository":{"id":293138644,"uuid":"983070020","full_name":"webriots/rate","owner":"webriots","description":"A high-performance rate limiter library for Go applications","archived":false,"fork":false,"pushed_at":"2025-05-21T05:41:05.000Z","size":68,"stargazers_count":131,"open_issues_count":0,"forks_count":2,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-05-21T06:31:11.642Z","etag":null,"topics":["concurrency","go","golang","high-performance","lock-free","rate-limiting","thread-safe","token-bucket","zero-allocation"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/webriots.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-05-13T20:40:24.000Z","updated_at":"2025-05-21T05:35:01.000Z","dependencies_parsed_at":"2025-05-13T21:45:15.776Z","dependency_job_id":null,"html_url":"https://github.com/webriots/rate","commit_stats":null,"previous_names":["webriots/rate"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/webriots/rate","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webriots%2Frate","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webriots%2Frate/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webriots%2Frate/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webriots%2Frate/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/webriots","download_url":"https://codeload.github.com/webriots/rate/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webriots%2Frate/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":262581514,"owners_count":23331925,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["concurrency","go","golang","high-performance","lock-free","rate-limiting","thread-safe","token-bucket","zero-allocation"],"created_at":"2025-06-29T11:14:13.851Z","updated_at":"2026-01-12T02:59:40.566Z","avatar_url":"https://github.com/webriots.png","language":"Go","readme":"# Rate\n\n[![Go Reference](https://pkg.go.dev/badge/github.com/webriots/rate.svg)](https://pkg.go.dev/github.com/webriots/rate)\n[![Go Report Card](https://goreportcard.com/badge/github.com/webriots/rate)](https://goreportcard.com/report/github.com/webriots/rate)\n[![Coverage Status](https://coveralls.io/repos/github/webriots/rate/badge.svg?branch=main)](https://coveralls.io/github/webriots/rate?branch=main)\n\nA high-performance rate limiter library for Go applications with multiple rate limiting strategies.\n\n## Features\n\n- **Ultra-Fast**: Core operations execute in single digit *nanoseconds* with zero allocations, enabling hundreds of millions of operations per second\n- **Thread-Safe**: All operations are designed to be thread-safe using optimized atomic operations\n- **Zero External Dependencies**: Relies solely on the Go standard library\n- **Memory Efficient**: Uses compact data structures and optimized storage\n  - Packed representations for token buckets\n  - Custom 56-bit timestamp implementation\n  - Efficient atomic slice implementations\n- **Multiple Rate Limiting Strategies**:\n  - TokenBucketLimiter: Classic token bucket algorithm with multiple buckets\n  - AIMDTokenBucketLimiter: Additive-Increase/Multiplicative-Decrease algorithm inspired by TCP congestion control\n  - RotatingTokenBucketLimiter: Collision-resistant rate limiter with periodic hash seed rotation\n- **Highly Scalable**: Designed for high-throughput concurrent systems\n  - Multiple buckets distribute load across different request IDs\n  - Low contention design for concurrent access patterns\n\n## Installation\n\n```shell\ngo get github.com/webriots/rate\n```\n\n## Quick Start\n\n### Token Bucket Rate Limiter\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/webriots/rate\"\n)\n\nfunc main() {\n\t// Create a new token bucket limiter:\n\t// - 1024 buckets (automatically rounded to nearest power of 2 if\n\t//   not already a power of 2)\n\t// - 10 tokens burst capacity\n\t// - 100 tokens per second refill rate\n\tlimiter, err := rate.NewTokenBucketLimiter(1024, 10, 100, time.Second)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\t// Try to take a token for a specific ID\n\tid := []byte(\"user-123\")\n\n\tif limiter.TakeToken(id) {\n\t\tfmt.Println(\"Token acquired, proceeding with request\")\n\t\t// ... process the request\n\t} else {\n\t\tfmt.Println(\"Rate limited, try again later\")\n\t\t// ... return rate limit error\n\t}\n\n\t// Check without consuming\n\tif limiter.CheckToken(id) {\n\t\tfmt.Println(\"Token would be available\")\n\t}\n\n\t// Take multiple tokens at once\n\tif limiter.TakeTokens(id, 5) {\n\t\tfmt.Println(\"Successfully took 5 tokens\")\n\t}\n\n\t// Check if multiple tokens would be available\n\tif limiter.CheckTokens(id, 3) {\n\t\tfmt.Println(\"3 tokens would be available\")\n\t}\n}\n```\n\n[Go Playground](https://go.dev/play/p/oDQlxRe75g_H)\n\n### AIMD Rate Limiter\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/webriots/rate\"\n)\n\nfunc main() {\n\t// Create an AIMD token bucket limiter:\n\t// - 1024 buckets (automatically rounded to nearest power of 2 if\n\t//   not already a power of 2)\n\t// - 10 tokens burst capacity\n\t// - Min rate: 1 token/s, Max rate: 100 tokens/s, Initial rate: 10\n\t//   tokens/s\n\t// - Increase by 1 token/s on success, decrease by factor of 2 on\n\t//   failure\n\tlimiter, err := rate.NewAIMDTokenBucketLimiter(\n\t\t1024,  // numBuckets\n\t\t10,    // burstCapacity\n\t\t1.0,   // rateMin\n\t\t100.0, // rateMax\n\t\t10.0,  // rateInit\n\t\t1.0,   // rateAdditiveIncrease\n\t\t2.0,   // rateMultiplicativeDecrease\n\t\ttime.Second,\n\t)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tid := []byte(\"api-client-123\")\n\n\t// Try to take a token\n\tif limiter.TakeToken(id) {\n\t\t// Process the request\n\t\tsuccess := processRequest()\n\n\t\tif success {\n\t\t\t// If successful, increase the rate\n\t\t\tlimiter.IncreaseRate(id)\n\t\t} else {\n\t\t\t// If failed (e.g., downstream service is overloaded), decrease\n\t\t\t// the rate to back off\n\t\t\tlimiter.DecreaseRate(id)\n\t\t}\n\t} else {\n\t\t// We're being rate limited\n\t\tfmt.Println(\"Rate limited, try again later\")\n\t}\n}\n\nfunc processRequest() bool {\n\t// Your request processing logic\n\tfmt.Println(\"Processing request\")\n\treturn true\n}\n```\n\n[Go Playground](https://go.dev/play/p/UDslvGAPG-O)\n\n### Rotating Token Bucket Rate Limiter\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/webriots/rate\"\n)\n\nfunc main() {\n\t// Create a rotating token bucket limiter:\n\t// - 1024 buckets (automatically rounded to nearest power of 2 if\n\t//   not already a power of 2)\n\t// - 10 tokens burst capacity\n\t// - 100 tokens per second refill rate\n\t// - Rotation interval automatically calculated: (10/100)*5 = 0.5\n\t//   seconds\n\tlimiter, err := rate.NewRotatingTokenBucketLimiter(\n\t\t1024,        // numBuckets\n\t\t10,          // burstCapacity\n\t\t100,         // refillRate\n\t\ttime.Second, // refillRateUnit\n\t)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\t// Use just like TokenBucketLimiter\n\tid := []byte(\"user-456\")\n\n\tif limiter.TakeToken(id) {\n\t\tfmt.Println(\"Token acquired, proceeding with request\")\n\t\t// ... process the request\n\t} else {\n\t\tfmt.Println(\"Rate limited, try again later\")\n\t\t// ... return rate limit error\n\t}\n\n\t// Check without consuming\n\tif limiter.CheckToken(id) {\n\t\tfmt.Println(\"Token would be available\")\n\t}\n\n\t// The rotating limiter also supports multi-token operations\n\tif limiter.TakeTokens(id, 3) {\n\t\tfmt.Println(\"Successfully took 3 tokens\")\n\t}\n}\n```\n\n[Go Playground](https://go.dev/play/p/nTlCGSHtQ7j)\n\n## Detailed Usage\n\n### Common Interface\n\nAll rate limiters in this package implement the `Limiter` interface:\n\n```go\ntype Limiter interface {\n\tCheckToken([]byte) bool         // Check if a single token is available\n\tCheckTokens([]byte, uint8) bool // Check if n tokens are available\n\tTakeToken([]byte) bool          // Take a single token\n\tTakeTokens([]byte, uint8) bool  // Take n tokens atomically\n}\n```\n\nThis allows you to write code that works with any rate limiter implementation:\n\n```go\nfunc processRequest(limiter rate.Limiter, clientID []byte, tokensNeeded uint8) error {\n\t// Check if we have enough tokens without consuming them\n\tif !limiter.CheckTokens(clientID, tokensNeeded) {\n\t\treturn fmt.Errorf(\"rate limited: need %d tokens\", tokensNeeded)\n\t}\n\n\t// Actually take the tokens\n\tif !limiter.TakeTokens(clientID, tokensNeeded) {\n\t\treturn fmt.Errorf(\"rate limited: tokens no longer available\")\n\t}\n\n\t// Process the request...\n\treturn nil\n}\n```\n\n### TokenBucketLimiter\n\nThe token bucket algorithm is a common rate limiting strategy that allows for controlled bursting. It maintains a \"bucket\" of tokens that refills at a constant rate, and each request consumes a token.\n\n```go\nlimiter, err := rate.NewTokenBucketLimiter(\n    numBuckets,     // Number of buckets (automatically rounded to nearest power of 2 if not already a power of 2)\n    burstCapacity,  // Maximum number of tokens in each bucket\n    refillRate,     // Rate at which tokens are refilled\n    refillRateUnit, // Time unit for refill rate\n)\n```\n\n#### Parameters:\n\n- `numBuckets`: Number of token buckets (automatically rounded up to the nearest power of two if not already a power of two)\n- `burstCapacity`: Maximum number of tokens that can be consumed at once\n- `refillRate`: Rate at which tokens are refilled\n- `refillRateUnit`: Time unit for refill rate calculations (e.g., time.Second)\n\n#### Input Validation:\n\nThe constructor performs validation on all parameters and returns descriptive errors:\n\n- `refillRate` must be a positive, finite number (not NaN, infinity, zero, or negative)\n- `refillRateUnit` must represent a positive duration\n- The product of `refillRate` and `refillRateUnit` must not overflow when converted to nanoseconds\n\n#### Methods:\n\n- `CheckToken(id []byte) bool`: Checks if a token would be available without consuming it\n- `CheckTokens(id []byte, n uint8) bool`: Checks if n tokens would be available without consuming them\n- `TakeToken(id []byte) bool`: Attempts to take a single token, returns true if successful\n- `TakeTokens(id []byte, n uint8) bool`: Attempts to take n tokens atomically, returns true if all n tokens were taken\n\n**Multi-token operations:**\nThe `TakeTokens` and `CheckTokens` methods support atomic multi-token operations. When requesting multiple tokens:\n- The operation is all-or-nothing: either all requested tokens are taken/available, or none are\n- The maximum number of tokens that can be requested is limited by the burst capacity\n- These methods are useful for operations that require multiple \"units\" of rate limiting\n\n#### Token Bucket Algorithm Explained\n\nThe token bucket algorithm uses a simple metaphor of a bucket that holds tokens:\n\n```\n┌─────────────────────────────────────┐\n│                                     │\n│  ┌───┐  ┌───┐  ┌───┐  ┌   ┐  ┌   ┐  │\n│  │ T │  │ T │  │ T │  │   │  │   │  │\n│  └───┘  └───┘  └───┘  └   ┘  └   ┘  │\n│    │      │      │                  │\n│    ▼      ▼      ▼                  │\n│  avail  avail  avail                │\n│                                     │\n│  Available: 3 tokens │ Capacity: 5  │\n└─────────────────────────────────────┘\n      ▲\n      │ Refill rate: 100 tokens/second\n    ┌───┐  ┌───┐\n    │ T │  │ T │  ...  ← New tokens added at constant rate\n    └───┘  └───┘\n```\n\n**How it works:**\n\n1. **Bucket Initialization**:\n   - Each bucket starts with `burstCapacity` tokens (full)\n   - A timestamp is recorded when the bucket is created\n\n2. **Token Consumption**:\n   - When a request arrives, it attempts to take a token\n   - If a token is available, it's removed from the bucket and the request proceeds\n   - If no tokens are available, the request is rate-limited\n\n3. **Token Refill**:\n   - Tokens are conceptually added to the bucket at a constant rate (`refillRate` per `refillRateUnit`)\n   - In practice, the refill is calculated lazily only when tokens are requested\n   - The formula is: `refill = (currentTime - lastUpdateTime) * refillRate`\n   - Tokens are never added beyond the maximum `burstCapacity`\n\n4. **Burst Handling**:\n   - The bucket can temporarily allow higher rates up to `burstCapacity`\n   - This accommodates traffic spikes while maintaining a long-term average rate\n\n**Implementation Details:**\n\n- This library uses a sharded approach with multiple buckets to reduce contention\n- Each request ID is consistently hashed to the same bucket\n- The bucket stores both token count and timestamp in a single uint64 for efficiency\n\n### AIMDTokenBucketLimiter\n\nThe AIMD (Additive Increase, Multiplicative Decrease) algorithm provides dynamic rate adjustments inspired by TCP congestion control. It gradually increases token rates during successful operations and quickly reduces rates when encountering failures.\n\n```go\nlimiter, err := rate.NewAIMDTokenBucketLimiter(\n    numBuckets,                 // Number of buckets (automatically rounded to nearest power of 2 if not already a power of 2)\n    burstCapacity,              // Maximum tokens per bucket\n    rateMin,                    // Minimum token refill rate\n    rateMax,                    // Maximum token refill rate\n    rateInit,                   // Initial token refill rate\n    rateAdditiveIncrease,       // Amount to increase rate on success\n    rateMultiplicativeDecrease, // Factor to decrease rate on failure\n    rateUnit,                   // Time unit for rate calculations\n)\n```\n\n#### Parameters:\n\n- `numBuckets`: Number of token buckets (automatically rounded up to the nearest power of two if not already a power of two)\n- `burstCapacity`: Maximum number of tokens that can be consumed at once\n- `rateMin`: Minimum token refill rate\n- `rateMax`: Maximum token refill rate\n- `rateInit`: Initial token refill rate\n- `rateAdditiveIncrease`: Amount to increase rate by on success\n- `rateMultiplicativeDecrease`: Factor to decrease rate by on failure\n- `rateUnit`: Time unit for rate calculations (e.g., time.Second)\n\n#### Input Validation:\n\nThe constructor performs comprehensive validation on all parameters and returns descriptive errors:\n\n- `rateInit` must be a positive, finite number (not NaN, infinity, zero, or negative)\n- `rateUnit` must represent a positive duration\n- `rateMin` must be a positive, finite number\n- `rateMax` must be a positive, finite number\n- `rateMin` must be less than or equal to `rateMax`\n- `rateInit` must be between `rateMin` and `rateMax` (inclusive)\n- `rateAdditiveIncrease` must be a positive, finite number\n- `rateMultiplicativeDecrease` must be a finite number greater than or equal to 1.0\n- The product of `rateInit`, `rateMin`, `rateMax`, or `rateAdditiveIncrease` with `rateUnit` must not overflow when converted to nanoseconds\n\n#### Methods:\n\n- `CheckToken(id []byte) bool`: Checks if a token would be available without consuming it\n- `CheckTokens(id []byte, n uint8) bool`: Checks if n tokens would be available without consuming them\n- `TakeToken(id []byte) bool`: Attempts to take a single token, returns true if successful\n- `TakeTokens(id []byte, n uint8) bool`: Attempts to take n tokens atomically, returns true if all n tokens were taken\n- `IncreaseRate(id []byte)`: Additively increases the rate for the given ID (on success)\n- `DecreaseRate(id []byte)`: Multiplicatively decreases the rate for the given ID (on failure)\n\n#### AIMD Algorithm Explained\n\nThe AIMD algorithm dynamically adjusts token refill rates based on success or failure feedback, similar to how TCP adjusts its congestion window:\n\n```\n   Rate\n   ▲\n   │\n   │\n   │\nrateMax ─────────────────────────────────────────\n   │                       ╱       │\n   │                     ╱         │\n   │                   ╱           │           ╱\n   │                 ╱             │         ╱\n   │               ╱               │       ╱\n   │             ╱                 │     ╱\n   │           ╱                   │   ╱\n   │         ╱                     │ ╱\n   │       ╱                       ▼ Multiplicative\n   │     ╱                           Decrease\n   │   ╱                             (rate / 2)\n   │ ╱\nrateInit\n   │ ↑\n   │ Additive\n   │ Increase\n   │ (+1)\nrateMin ─────────────────────────────────────────\n   │\n   └─────────────────────────────────────────────► Time\n              Success           Failure\n```\n\n**How it works:**\n\n1. **Initialization**:\n   - Each bucket starts with a rate of `rateInit` tokens per time unit\n   - Token buckets are created with `burstCapacity` tokens\n\n2. **Adaptive Rate Adjustment**:\n   - When operations succeed, the rate increases linearly by `rateAdditiveIncrease`\n     - `newRate = currentRate + rateAdditiveIncrease`\n   - When operations fail, the rate decreases multiplicatively by `rateMultiplicativeDecrease`\n     - `newRate = rateMin + (currentRate - rateMin) / rateMultiplicativeDecrease`\n   - Rates are bounded between `rateMin` (lower bound) and `rateMax` (upper bound)\n\n3. **Practical Applications**:\n   - Gracefully adapts to changing capacity of backend systems\n   - Quickly backs off when systems are overloaded (multiplicative decrease)\n   - Slowly probes for increased capacity when systems are healthy (additive increase)\n   - Maintains stability even under varying load conditions\n\n**Implementation Details:**\n\n- Each ID has its own dedicated rate that adjusts independently\n- The actual token bucket mechanics remain the same as TokenBucketLimiter\n- Rate changes are applied atomically using lock-free operations\n- Rate information is stored alongside token information in the bucket\n\n### RotatingTokenBucketLimiter\n\nThe Rotating Token Bucket Rate Limiter addresses a fundamental limitation of hash-based rate limiters: hash collisions between different IDs can cause unfair rate limiting. This limiter maintains two TokenBucketLimiters with different hash seeds and automatically rotates between them to minimize collision impact while ensuring correctness.\n\n```go\nlimiter, err := rate.NewRotatingTokenBucketLimiter(\n    numBuckets,     // Number of buckets (automatically rounded to nearest power of 2 if not already a power of 2)\n    burstCapacity,  // Maximum tokens per bucket\n    refillRate,     // Rate at which tokens are refilled\n    refillRateUnit, // Time unit for refill rate\n)\n```\n\n#### Parameters:\n\n- `numBuckets`: Number of token buckets per limiter (automatically rounded up to the nearest power of two if not already a power of two)\n- `burstCapacity`: Maximum number of tokens that can be consumed at once\n- `refillRate`: Rate at which tokens are refilled\n- `refillRateUnit`: Time unit for refill rate calculations (e.g., time.Second)\n\n#### Automatic Rotation Calculation:\n\nThe rotation interval is automatically calculated to ensure 99.99% statistical convergence of all token buckets to steady state before rotation occurs. This eliminates state inconsistency issues and guarantees correctness:\n\n```\nrotationInterval = (burstCapacity / refillRate * refillRateUnit) * 5.0\n```\n\nFor example:\n- `burstCapacity=100, refillRate=250/second` → rotation every 2 seconds\n- `burstCapacity=10, refillRate=1/second` → rotation every 50 seconds\n\n#### Input Validation:\n\nThe constructor performs validation on all parameters and returns descriptive errors:\n\n- `refillRate` must be a positive, finite number (not NaN, infinity, zero, or negative)\n- `refillRateUnit` must represent a positive duration\n- The product of `refillRate` and `refillRateUnit` must not overflow when converted to nanoseconds\n\n#### Methods:\n\n- `CheckToken(id []byte) bool`: Checks if a token would be available without consuming it\n- `CheckTokens(id []byte, n uint8) bool`: Checks if n tokens would be available without consuming them\n- `TakeToken(id []byte) bool`: Attempts to take a single token, returns true if successful\n- `TakeTokens(id []byte, n uint8) bool`: Attempts to take n tokens atomically, returns true if all n tokens were taken\n- `RotationInterval() time.Duration`: Returns the automatically calculated rotation interval\n\n#### Collision-Resistant Algorithm Explained\n\nThe rotating limiter solves the hash collision problem by maintaining two token bucket limiters and periodically rotating their roles:\n\n```\nTime: 0s                    Time: 30s (rotation)           Time: 60s (rotation)\n┌─────────────────────────┐ ┌─────────────────────────┐ ┌─────────────────────────┐\n│ Limiter A (checked)     │ │ Limiter B (checked)     │ │ Limiter C (checked)     │\n│ - seed: 0x1234          │→│ - seed: 0x5678          │→│ - seed: 0x9ABC          │\n│ - decision: ✓ enforced  │ │ - decision: ✓ enforced  │ │ - decision: ✓ enforced  │\n│                         │ │                         │ │                         │\n│ Limiter B (ignored)     │ │ Limiter C (ignored)     │ │ Limiter D (ignored)     │\n│ - seed: 0x5678          │ │ - seed: 0x9ABC          │ │ - seed: 0xDEF0          │\n│ - decision: ✗ discarded │ │ - decision: ✗ discarded │ │ - decision: ✗ discarded │\n└─────────────────────────┘ └─────────────────────────┘ └─────────────────────────┘\n\nHash Collision Scenario:\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│ ID \"user-123\" and \"user-456\" both hash to bucket 42 in Limiter A (collision!)   │\n│ They compete for the same tokens → unfair rate limiting                         │\n│                                                                                 │\n│ After rotation (30s later):                                                     │\n│ - Limiter B becomes \"checked\" with seed 0x5678                                  │\n│ - Very likely: \"user-123\" → bucket 15, \"user-456\" → bucket 73 (no collision!)   │\n│ - Collision resolved! Each ID now has independent rate limiting                 │\n└─────────────────────────────────────────────────────────────────────────────────┘\n```\n\n**How it works:**\n\n1. **Dual Limiter Architecture**:\n   - Maintains two identical TokenBucketLimiters with different hash seeds\n   - \"Checked\" limiter: its result determines rate limiting decisions\n   - \"Ignored\" limiter: maintains state but its result is discarded\n\n2. **Token Consumption Strategy**:\n   - Each `TakeToken()` call consumes tokens from **both** limiters\n   - Only the \"checked\" limiter's success/failure determines the result\n   - **Trade-off**: 2x token consumption rate for collision resistance\n\n3. **Periodic Rotation**:\n   - Every `rotationRate` duration, the limiters rotate roles:\n     - Old \"ignored\" becomes new \"checked\" (with established state)\n     - Copy of new \"checked\" becomes new \"ignored\" (with fresh hash seed)\n   - Hash collisions are broken by the new hash seed\n\n4. **Collision Impact Mitigation**:\n   - Hash collisions only persist for maximum one `rotationRate` period\n   - Different hash seeds make collisions probabilistically unlikely after rotation\n   - Provides better long-term fairness compared to static hash seeds\n\n**Benefits:**\n- ✅ **Transient Collisions**: Hash collisions are temporary (max duration = rotation period)\n- ✅ **Better Fairness**: Different IDs are less likely to be permanently penalized\n- ✅ **Automatic Recovery**: System self-heals from collision scenarios\n- ✅ **Drop-in Replacement**: Same interface as TokenBucketLimiter\n\n**Trade-offs:**\n- ❌ **Higher Resource Usage**: 2x token consumption and memory usage\n- ❌ **Slight Performance Overhead**: ~2x slower than single TokenBucketLimiter\n\n#### Implementation Details:\n\n- Uses atomic operations for thread-safe rotation\n- Both limiters are always synchronized with the same timestamp\n- Rotation is triggered lazily during token operations\n- Memory usage is approximately 2x that of TokenBucketLimiter\n- Hash seed generation uses Go's `hash/maphash.MakeSeed()` for quality randomness\n\n#### When to Use Rotating Token Bucket Limiter:\n\n**Use when:**\n- Hash collision fairness is critical for your application\n- You have many distinct IDs with similar access patterns\n- Temporary rate limit inaccuracy is acceptable for better long-term fairness\n- You can afford 2x resource usage for improved collision resistance\n\n**Don't use when:**\n- Resource efficiency is more important than collision fairness\n- You have very few distinct IDs (low collision probability)\n- Your rate limits are very loose (collisions don't significantly impact users)\n- Real-time precision is more important than long-term fairness\n\n## Performance\n\nThe library is optimized for high performance and efficiency:\n\n- Core operations complete in 1-15ns with zero allocations\n- Efficient memory usage through packed representations\n- Minimal contention in multi-threaded environments\n\n### Benchmarks\n\nRun the benchmarks on your machine with:\n\n```shell\n# Run all benchmarks with memory allocation stats\ngo test -bench=. -benchmem\n\n# Run specific benchmark\ngo test -bench=BenchmarkTokenBucketCheck -benchmem\n```\n\nHere are the results from an Apple M1 Pro:\n\n```\n# Core operations\nBenchmarkTokenBucketCheck-10                    203429409                5.919 ns/op           0 B/op          0 allocs/op\nBenchmarkTokenBucketTakeToken-10                165718064                7.240 ns/op           0 B/op          0 allocs/op\nBenchmarkTokenBucketParallel-10                 841380494                1.419 ns/op           0 B/op          0 allocs/op\nBenchmarkTokenBucketContention-10               655109293                1.827 ns/op           0 B/op          0 allocs/op\nBenchmarkTokenBucketWithRefill-10               165840525                7.110 ns/op           0 B/op          0 allocs/op\n\n# Bucket creation (different sizes)\nBenchmarkTokenBucketCreateSmall-10               16929116               71.00 ns/op          192 B/op          2 allocs/op\nBenchmarkTokenBucketCreateMedium-10                664831             1820 ns/op            8256 B/op          2 allocs/op\nBenchmarkTokenBucketCreateLarge-10                  45537            25471 ns/op          131137 B/op          2 allocs/op\n\n# Internals\nBenchmarkTokenBucketPacked-10                   1000000000               0.3103 ns/op          0 B/op          0 allocs/op\nBenchmarkTokenBucketUnpack-10                   1000000000               0.3103 ns/op          0 B/op          0 allocs/op\nBenchmarkTokenBucketIndex-10                     265611426               4.510 ns/op           0 B/op          0 allocs/op\nBenchmarkTokenBucketRefill-10                    591397021               2.023 ns/op           0 B/op          0 allocs/op\n\n# Real-world scenarios\nBenchmarkTokenBucketManyIDs-10                   139485549               8.590 ns/op           0 B/op          0 allocs/op\nBenchmarkTokenBucketDynamicID-10                 93835521               12.87 ns/op            0 B/op          0 allocs/op\nBenchmarkTokenBucketRealWorldRequestRate-10      853565757               1.401 ns/op           0 B/op          0 allocs/op\nBenchmarkTokenBucketHighContention-10            579507058               2.068 ns/op           0 B/op          0 allocs/op\nBenchmarkTokenBucketWithSystemClock-10           459682273               2.605 ns/op           0 B/op          0 allocs/op\n\n# Rotating limiter operations\nBenchmarkRotatingTokenBucketLimiterTakeToken-10   80090768              14.93 ns/op            0 B/op          0 allocs/op\nBenchmarkRotatingTokenBucketLimiterCheck-10       91934064              13.16 ns/op            0 B/op          0 allocs/op\nBenchmarkRotatingTokenBucketLimiterConcurrent-10 687345256               1.744 ns/op           0 B/op          0 allocs/op\n```\n\n## Advanced Usage\n\n### Choosing the Optimal Number of Buckets\n\nThe `numBuckets` parameter is automatically rounded up to the nearest power of two if not already a power of two (e.g., 256, 1024, 4096) for efficient hashing and is a critical factor in the limiter's performance and fairness.\n\n#### How Bucket Selection Works\n\nWhen you call `TakeToken(id)` or similar methods, the library:\n\n1. Hashes the ID using Go's built-in hash/maphash algorithm (non-cryptographic, high performance)\n2. Uses the hash value \u0026 (numBuckets-1) to determine the bucket index\n3. Takes/checks a token from that specific bucket\n\n```go\n// Simplified version of the internal hashing mechanism\nfunc index(id []byte) int {\n    return int(maphash.Bytes(seed, id) \u0026 (numBuckets - 1))\n}\n```\n\n#### Understanding Collision Probability\n\nA collision occurs when two different IDs map to the same bucket. When this happens:\n- Those IDs share the same rate limit, which may be undesirable\n- Higher contention can occur on that bucket when accessed concurrently\n\nThe probability of collision depends on:\n1. Number of buckets (`numBuckets`)\n2. Number of distinct IDs being rate limited\n\nThe birthday paradox formula gives us the probability of *at least one* collision:\n\n| Number of Distinct IDs | Buckets=1024 | Buckets=4096 | Buckets=16384 | Buckets=65536 |\n|------------------------|--------------|--------------|---------------|---------------|\n| 10                     | 4.31%        | 1.09%        | 0.27%         | 0.07%         |\n| 100                    | 99.33%       | 70.43%       | 26.12%        | 7.28%         |\n| 1,000                  | 100.00%      | 100.00%      | 100.00%       | 99.95%        |\n| 10,000                 | 100.00%      | 100.00%      | 100.00%       | 100.00%       |\n| 100,000                | 100.00%      | 100.00%      | 100.00%       | 100.00%       |\n\nHowever, the probability of collision doesn't tell the whole story. What matters more is the **percentage of IDs involved in collisions** and the **expected number of collisions**:\n\n| Configuration | Load Factor | Expected Collisions | % of IDs in Collisions |\n|---------------|------------|---------------------|------------------------|\n| 100 IDs in 65,536 buckets | 0.0015 | ~0.08 | ~0.15% |\n| 1,000 IDs in 65,536 buckets | 0.0153 | ~7.6 | ~1.5% |\n| 10,000 IDs in 65,536 buckets | 0.1526 | ~763 | ~7.3% |\n\n#### Guidelines for Choosing `numBuckets`\n\nIt's important to understand that even though the probability of having *at least one* collision approaches 100% quickly as the number of IDs increases, the **percentage of IDs affected by collisions** grows much more slowly.\n\nBased on the percentage of IDs involved in collisions:\n\n- For **small** systems (≤100 IDs):\n  - 4,096 buckets: ~2.4% of IDs will be involved in collisions\n  - 16,384 buckets: ~0.6% of IDs will be involved in collisions\n\n- For **medium** systems (101-1,000 IDs):\n  - 16,384 buckets: ~6% of IDs will be involved in collisions\n  - 65,536 buckets: ~1.5% of IDs will be involved in collisions\n\n- For **large** systems (1,001-10,000 IDs):\n  - 65,536 buckets: ~7.3% of IDs will be involved in collisions\n  - 262,144 buckets (2^18): ~1.9% of IDs will be involved in collisions\n\n- For **very large** systems (\u003e10,000 IDs):\n  - 1,048,576 buckets (2^20): ~4.7% of IDs among 100,000 will be involved in collisions\n\n**General Rule of Thumb**: To keep collision impact below 5% of IDs, maintain a load factor (IDs/buckets) below 0.1.\n\n**Memory Impact**:\n- Each bucket uses 8 bytes (uint64)\n- 65,536 buckets = 512 KB of memory\n- 1,048,576 buckets = 8 MB of memory\n\nFor systems with many distinct IDs where even low collision percentages are unacceptable, consider:\n- Sharding rate limiters by logical partitions (e.g., user type, region)\n- Using multiple rate limiters with different hashing algorithms\n- Implementing a consistent hashing approach\n\n\u003e **Note**: While having some collisions is usually acceptable for rate limiting (it just means some IDs might share limits), the key is to keep the percentage of affected IDs low enough for your use case.\n\nMemory usage scales linearly with `numBuckets`, so there's a trade-off between collision probability and memory consumption.\n\n```go\n// Example for a system with ~5,000 distinct IDs\nlimiter, err := rate.NewTokenBucketLimiter(\n    65536,    // numBuckets (will remain 2^16 since it's already a power of 2) to maintain \u003c10% collision probability\n    10,       // burstCapacity\n    100,      // refillRate\n    time.Second,\n)\n```\n\n\u003e **Note**: If you observe uneven rate limiting across different IDs, consider increasing the number of buckets to reduce collision probability.\n\n### Custom Rate Limiting Scenarios\n\n#### High-Volume API Rate Limiting\n\n```go\n// API rate limiting with 100 requests per second, 10 burst capacity\nlimiter, _ := rate.NewTokenBucketLimiter(1024, 10, 100, time.Second)\n\n// Take a token for a specific API key\napiKey := []byte(\"customer-api-key\")\nif limiter.TakeToken(apiKey) {\n    // Process API request\n} else {\n    // Return 429 Too Many Requests\n}\n```\n\n#### Adaptive Backend Protection with AIMD\n\n```go\n// Create AIMD limiter for dynamic backend protection\nlimiter, _ := rate.NewAIMDTokenBucketLimiter(\n    4096,   // numBuckets\n    20,     // burstCapacity\n    1.0,    // rateMin\n    1000.0, // rateMax\n    100.0,  // rateInit\n    10.0,   // rateAdditiveIncrease\n    2.0,    // rateMultiplicativeDecrease\n    time.Second,\n)\n\nfunc handleRequest(backendID []byte) {\n    if limiter.TakeToken(backendID) {\n        err := callBackendService()\n        if err == nil {\n            // Backend is healthy, increase rate\n            limiter.IncreaseRate(backendID)\n        } else if isOverloadError(err) {\n            // Backend is overloaded, decrease rate\n            limiter.DecreaseRate(backendID)\n        }\n    } else {\n        // Circuit breaking, return error immediately\n    }\n}\n```\n\n## Design Philosophy\n\nThe library is designed with these principles in mind:\n\n1. **Performance First**: Every operation is optimized for minimal CPU and memory usage\n2. **Thread Safety**: All operations are thread-safe for concurrent environments without lock contention\n3. **Memory Efficiency**: Uses packed representation and custom implementations for efficient storage\n4. **Simplicity**: Provides simple interfaces for common rate limiting patterns\n5. **Flexibility**: Multiple strategies to handle different rate limiting requirements\n\n## Thread-Safety Guarantees\n\nThe `rate` package is designed for high-concurrency environments with the following guarantees:\n\n### Atomic Operations\n\n- All token bucket operations use lock-free atomic operations\n- Token count updates and timestamp operations use atomic Compare-And-Swap (CAS)\n- No mutexes or traditional locks are used, eliminating lock contention\n- Bucket selection uses thread-safe hash calculations to map IDs to buckets\n\n### Concurrent Access Patterns\n\n- **Read Operations**: Multiple goroutines can safely check token availability concurrently\n- **Write Operations**: Multiple goroutines can safely take tokens and update rates concurrently\n- **Creation**: Limiter creation is thread-safe and can be performed from any goroutine\n- **Refill**: Token refill happens automatically during token operations without requiring explicit locks\n\n### Memory Ordering\n\n- All atomic operations enforce appropriate memory ordering guarantees\n- Reads and writes are properly synchronized across multiple cores/CPUs\n- Operations follow Go's memory model for concurrent access\n\n### Contention Handling\n\n- Multiple buckets distribute load to minimize atomic operation contention\n- Rate updates for one ID don't block operations on other IDs\n- The implementation uses a sharded approach where each ID maps to a specific bucket\n\n### Limitations\n\n- The library doesn't guarantee perfect fairness across all IDs\n- When multiple IDs hash to the same bucket (collision), they share the same rate limit\n- Very high contention on a single ID might experience CAS retry loops\n\n## Contributing\n\nContributions are welcome! Please feel free to submit a Pull Request.\n\n## License\n\nThis project is licensed under the [MIT License](LICENSE).\n","funding_links":[],"categories":["Utilities","公用事业公司"],"sub_categories":["Utility/Miscellaneous","实用程序/Miscellaneous"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwebriots%2Frate","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwebriots%2Frate","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwebriots%2Frate/lists"}