-
-
Notifications
You must be signed in to change notification settings - Fork 530
Enhancement: Atomic operations for Litestar Stores #4668
Description
Summary
Currently, the Litestar Store protocol (and its implementations like RedisStore, MemoryStore, etc.) provides basic key-value operations like get, set, and delete. However, it lacks support for atomic operations such as incrementing/decrementing a numeric value (incr/decr) or setting a value only if it does not already exist (setnx).
When implementing features like rate limiting, brute-force protection, or circuit breakers, developers are forced to use a non-atomic get followed by a set. In highly concurrent async environments, this read-modify-write cycle leads to race conditions where simultaneous requests read the same initial state and overwrite each other, effectively bypassing the intended limits.
We need atomic primitives added to the Store protocol to support thread-safe and concurrency-safe state tracking.
Basic Example
To count failed attempts (e.g., OTP verification failures), developers currently have to do this:
async def record_failure(store: Store, key: str) -> int:
# ❌ RACE CONDITION: Multiple concurrent requests can read the same value
# before any of them update it.
raw = await store.get(key)
count = int(raw.decode()) + 1 if raw else 1
await store.set(key, str(count).encode(), expires_in=900)
return countDrawbacks and Impact
Drawbacks:
- Implementation Complexity across Backends: While implementing incr or setnx is trivial in Redis (INCR, SETNX), it might be more complex to implement atomically in other storage backends (like a database-backed store or a filesystem store) without introducing internal locking mechanisms.
- Protocol Expansion: It expands the footprint of the Store protocol. Every existing and future custom Store implementation will be required to implement these new atomic methods to satisfy the interface.
Impact:
- Security & Correctness: Eliminates critical race conditions in rate limiters, brute-force trackers, and circuit breakers, which are common patterns in modern web APIs.
- Performance: Atomic operations in systems like Redis are significantly faster than a full roundtrip of get -> compute in Python -> set.
- Developer Experience: Prevents developers from having to bypass Litestar's Store abstractions to use underlying driver features (e.g., importing redis-py directly) or write complex Lua scripts just to safely increment a counter.
Unresolved questions
No response