limits

repository·master·Indexed 20 days ago

https://github.com/alisaifee/limits

A Python library for rate limiting that supports multiple strategies, including Fixed Window, Moving Window, and Sliding Window Counter. It provides identical APIs for synchronous and asynchronous codebases and integrates with various storage backends such as Redis, Memcached, and MongoDB.

Tokens
14.4K
Snippets
46
Records
73
Agent score
69%

What's inside limits

  1. Overview of limits

    master

    limits is a Python library designed for rate limiting using multiple strategies and common storage backends. It is built to provide identical APIs for both synchronous and asynchronous codebases, making it versatile for different Python application architectures.

    Key Features

    • Multiple Strategies: Supports various rate-limiting algorithms (e.g., Fixed Window, Moving Window, Sliding Window Counter).
    • Storage Backends: Integrates with Redis, Memcached, and MongoDB.
    • Sync & Async Support: Provides consistent APIs for both execution models.
  2. How to choose a rate limiting strategy

    master

    Choose a strategy based on your requirements for memory usage, performance, and accuracy:

    • Fixed Window: Best for high performance and low memory usage. Use this when occasional bursts at window boundaries are acceptable. You can mitigate burstiness by combining a large window limit with a finer-grained one (e.g., 10 requests per minute AND 2 requests per second).
    • Moving Window: Best for absolute accuracy. Use this when you need exact rate limiting and can afford the extra memory overhead required to store request timestamps.
    • Sliding Window Counter: Best for a balance between memory efficiency and accuracy. It smooths transitions between time periods with less overhead than a Moving Window, though it may trade off some precision near bucket boundaries.
  3. How to create a custom storage backend

    master

    The limits package uses a registry pattern to allow you to add custom storage backends. To create a custom backend, you must follow these steps:

    1. Subclass a base storage class: Subclass limits.storage.Storage (for synchronous use) or limits.aio.storage.Storage (for asynchronous use) and implement the required abstract methods. This enables support for the fixed window strategy.
    2. Add Moving Window support (Optional): If your storage can support the moving window strategy, implement the methods from limits.storage.MovingWindowSupport.
    3. Add Sliding Window Counter support (Optional): If your storage can support the sliding window counter strategy, implement the methods from limits.storage.SlidingWindowCounterSupport.
    4. Register the storage: Define a STORAGE_SCHEME class variable containing a list of strings. These strings act as the URI schemes used to look up your custom storage in the registry via storage_from_string.
  4. Fixed Window strategy

    master

    The Fixed Window strategy is the most memory-efficient option because it only requires a single counter per resource and rate limit.

    How it works:

    1. When the first request arrives, a window starts for a fixed duration (e.g., 60 seconds for a 'per minute' limit).
    2. All requests within that window increment the counter.
    3. Once the window expires, the counter resets and a new window begins.

    Trade-offs:

    • Pros: Extremely low memory usage and high performance.
    • Cons: Can allow burst traffic at the boundaries of windows (e.g., if the limit is 10/min, a user could theoretically send 10 requests at the very end of one window and 10 at the very start of the next).
  5. Sliding Window Counter strategy

    master

    The Sliding Window Counter strategy (added in version 4.1) approximates a Moving Window using significantly less memory by maintaining two counters instead of a full log of timestamps.

    How it works: It uses a weighted sum of the Current bucket (ongoing period) and the Previous bucket (immediately preceding period).

    The formula for the weighted count is: C_weighted = floor(C_current + (C_prev * w))

    Where the weight w is calculated as: w = (T_exp - T_elapsed) / T_exp

    • T_exp: The bucket duration.
    • T_elapsed: Time elapsed since the current bucket started.
    • C_prev: Count from the previous bucket.
    • C_current: Count from the current bucket.

    Trade-offs:

    • Pros: Good balance of memory efficiency and accuracy.
    • Cons: Less precise than a Moving Window near bucket boundaries.
    • Implementation Note: In memcached and in-memory implementations, buckets may align with clock intervals, which could potentially allow attackers to bypass limits during initial sampling periods.
  6. Moving Window strategy

    master

    The Moving Window strategy provides exact rate limiting by tracking individual request timestamps.

    How it works:

    1. The system maintains a log of request timestamps.
    2. When a new request arrives, the system checks the timestamp of the nth oldest entry (where n is your limit).
    3. If that entry is older than the window duration (or doesn't exist), the request is allowed and the new timestamp is added to the log.
    4. Expired entries are truncated from the log.

    Trade-offs:

    • Pros: Perfectly accurate; no burstiness at window boundaries.
    • Cons: Higher memory overhead because it must store a timestamp for every request within the window.
  7. Understand performance implications of rate limiting strategies

    master

    The performance and storage costs of limits depend on the chosen strategy and storage backend.

    Strategy Comparison

    • Fixed Window and Sliding Window Counter: Both strategies maintain relatively constant storage costs and operation performance, regardless of the window size or the limit amount.
    • Moving Window: This strategy maintains a complete log of all successful requests within the rate limit window. Consequently, its storage cost and computational overhead increase as the limit size or the load increases.

    When choosing a strategy, consider if your application requires the precision of a Moving Window or if the constant-cost characteristics of Fixed Window or Sliding Window Counter are more suitable for your scale.

  8. Evaluate performance of RateLimiter methods

    master

    The performance of the limits library is measured across three primary method categories. When benchmarking or selecting a strategy, consider the throughput and latency of these specific operations:

    1. hit(): The primary method for incrementing the counter and checking the limit.
    2. test(): Checks if a request would exceed the limit without actually incrementing the counter.
    3. get_window_stats(): Retrieves metadata and statistics regarding the current rate limit window.

    Performance varies significantly based on the combination of the storage backend (e.g., Redis, Memcached, MongoDB) and the rate-limiting strategy used.

  9. Initialize storage backends in limits

    master

    You can use several storage backends to persist rate limit data. The library supports In-Memory, Memcached, Redis, and MongoDB. You can initialize them by calling the specific storage class or by using the storage_from_string factory method with a connection URI.

    Supported connection URI formats:

    • Memcached: memcached://localhost:11211
    • Redis: redis://localhost:6379
    • MongoDB: mongodb://localhost:27017
    from limits import storage
    
    # Specific classes
    backend = storage.MemoryStorage()
    backend = storage.MemcachedStorage("memcached://localhost:11211")
    backend = storage.RedisStorage("redis://localhost:6379")
    backend = storage.MongoDbStorage("mongodb://localhost:27017")
    
    # Using the factory
    storage_uri = "redis://localhost:6379"
    backend = storage.storage_from_string(storage_uri)
  10. Initialize a rate limiter strategy

    master

    A rate limiter requires a storage backend and a strategy. Choose a strategy based on your requirements:

    • FixedWindowRateLimiter: Resets the window at fixed intervals.
    • MovingWindowRateLimiter: A sliding window approach. Caution: If the storage backend does not support this, a NotImplementedError will be raised.
    • SlidingWindowCounterRateLimiter: Uses a sliding window counter. Caution: If the storage backend does not support this, a NotImplementedError will be raised.
    from limits import strategies
    
    # Fixed window
    limiter = strategies.FixedWindowRateLimiter(limits_storage)
    
    # Moving window
    limiter = strategies.MovingWindowRateLimiter(limits_storage)
    
    # Sliding window counter
    limiter = strategies.SlidingWindowCounterRateLimiter(limits_storage)
  11. Install limits with synchronous storage backends

    master

    You can install specific storage backend dependencies using pip extras:

    • Redis: $ pip install limits[redis]
    • RedisCluster: $ pip install limits[rediscluster]
    • Memcached: $ pip install limits[memcached]
    • MongoDB: $ pip install limits[mongodb]
    • Valkey: $ pip install limits[valkey]
    $ pip install limits[redis]
    $ pip install limits[rediscluster]
    $ pip install limits[memcached]
    $ pip install limits[mongodb]
    $ pip install limits[valkey]
  12. Initialize a rate limiter with a strategy

    master

    To use rate limiting, you must pair a storage backend with a specific strategy. The library provides three main strategies:

    1. Fixed Window (FixedWindowRateLimiter): Most memory-efficient. Uses a single counter per resource. A window starts on the first request and resets after the duration expires. Note: Burst traffic may occur at window boundaries.
    2. Moving Window (MovingWindowRateLimiter): More precise. Maintains a log of timestamps for each request. It checks if the $n^{th}$ oldest entry is outside the current window duration.
    3. Sliding Window Counter (SlidingWindowCounterRateLimiter): An approximation of the Moving Window that uses less memory by maintaining two counters (current bucket and previous bucket) and calculating a weighted count.
    from limits import strategies
    
    # Initialize with a backend
    strategy = strategies.MovingWindowRateLimiter(backend)
    strategy = strategies.FixedWindowRateLimiter(backend)
    strategy = strategies.SlidingWindowCounterRateLimiter(backend)