app.services.rate_limit_counter_backend module¶
Pluggable counter backends used by AbusePreventionService.
The DB-backed backend writes to abuse_limit_counter (preserves the
historical behavior and keeps existing migrations relevant). The Redis-backed
backend uses INCR + SET NX EX which is atomic, ~50 us per request,
and naturally distributed across API instances – the cost an UPDATE on
a hot row in Postgres does not pay.
Selection is driven by configs.ABUSE_PREVENTION_BACKEND and wired in
app/core/container.py.
- class app.services.rate_limit_counter_backend.RateLimitCounterBackend(*args, **kwargs)[source]¶
Bases:
ProtocolIncrement a per-bucket counter and return its new value.
- class app.services.rate_limit_counter_backend.DatabaseRateLimitCounterBackend(repository)[source]¶
Bases:
objectBackend that persists counters in
abuse_limit_counter.ttl_secondsis unused because the row’s bucket key already encodes window boundaries; expired rows can be reaped offline (a future periodic job, not the request path).- Parameters:
repository (AbuseLimitCounterRepository)
- async increment_and_get(scope_type, scope_value, window_name, window_start, ttl_seconds)[source]¶
Increment a DB-backed rate-limit counter and return its value.
ttl_secondsis ignored: the bucket’swindow_startalready encodes window boundaries, so expiry is handled offline.- Parameters:
scope_type (str) – Dimension being limited.
scope_value (str) – Concrete value within
scope_type.window_name (str) – Name of the time window/bucket.
window_start (datetime) – Start of the bucket window.
ttl_seconds (int) – Ignored by this backend.
- Returns:
int – The counter value after the increment.
- Return type:
int
- class app.services.rate_limit_counter_backend.RedisRateLimitCounterBackend(client, key_prefix='game:rl:')[source]¶
Bases:
objectBackend that uses Redis
INCR+SET NX EXfor per-bucket counters.Each bucket maps to one key with a TTL slightly longer than the window.
SET key 0 EX ttl NXplants a zeroed counter with a TTL atomically on the first request of a bucket; subsequentINCRcalls just bump it. The pipeline below batches both commands into a single round-trip and runs underMULTI/EXECso other clients cannot observe a key without a TTL.- Parameters:
key_prefix (str)
- async increment_and_get(scope_type, scope_value, window_name, window_start, ttl_seconds)[source]¶
Increment a Redis-backed rate-limit counter and return its value.
Uses a single
MULTI/EXECpipeline ofSET key 0 EX ttl NX(plant a TTL’d zero on the first hit) followed byINCRso a key is never visible without a TTL.- Parameters:
scope_type (str) – Dimension being limited.
scope_value (str) – Concrete value within
scope_type.window_name (str) – Name of the time window/bucket.
window_start (datetime) – Start of the bucket window.
ttl_seconds (int) – Key TTL (clamped to ≥ 1 second).
- Returns:
int – The counter value after the increment.
- Return type:
int
- app.services.rate_limit_counter_backend.build_redis_client_from_url(url)[source]¶
Build an async Redis client from a connection URL.
Imported lazily so the
redispackage is only required when the Redis backend is actually selected – callers running the DB backend never pay the dependency cost.- Parameters:
url (str)
- Return type:
Any
- app.services.rate_limit_counter_backend.build_rate_limit_counter_backend(repository, backend_name, redis_url, redis_key_prefix)[source]¶
Select the configured backend. Falls back to the DB backend with a warning when Redis is requested but
REDIS_URLis missing – so a misconfigured deploy still rate-limits (just slower) instead of opening the floodgates.- Parameters:
repository (AbuseLimitCounterRepository)
backend_name (str)
redis_url (str | None)
redis_key_prefix (str)
- Return type: