✨ Add Redis backend support (optional)
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 0
- Forks
- 0
- Avg merge
- 6h 51m
- Merged PRs (30d)
- 104
Description
Summary
Add an optional Redis backend as an alternative to DynamoDB for users who need sub-millisecond latency or already have Redis infrastructure.
Motivation
| Aspect | DynamoDB | Redis |
|---|---|---|
| Latency (p50) | 36-51ms | <1ms |
| Distributed | ✅ Built-in | ✅ Cluster mode |
| AWS-native | ✅ Yes | ⚠️ ElastiCache or self-hosted |
| Serverless | ✅ On-demand billing | ❌ Always-on instances |
| Cost model | Pay per request | Pay per hour |
Redis wins when:
- Sub-millisecond latency is critical
- High-frequency rate limiting (10k+ RPS)
- Already running Redis in your stack
- Running on EKS with existing Redis infrastructure
DynamoDB wins when:
- LLM workloads (latency doesn't matter, 36-51ms << 100ms+ LLM calls)
- Serverless / pay-per-use cost model
- No Redis infrastructure to manage
- Audit logging / compliance needs
Non-Breaking Implementation
1. Define Repository Protocol
# repository_protocol.py (new file)
from typing import Protocol
class RepositoryProtocol(Protocol):
async def get_entity(self, entity_id: str) -> Entity | None: ...
async def create_entity(self, entity_id: str, ...) -> Entity: ...
async def get_buckets(self, entity_id: str, resource: str) -> list[BucketState]: ...
async def transact_consume(self, entries: list[ConsumeEntry]) -> None: ...
async def close(self) -> None: ...
# ... other methods needed by RateLimiter
2. Accept optional repository parameter in RateLimiter
class RateLimiter:
def __init__(
self,
name: str = "limiter",
region: str | None = None,
endpoint_url: str | None = None,
stack_options: StackOptions | None = None,
# NEW - optional, defaults to DynamoDB
repository: RepositoryProtocol | None = None,
...
):
if repository is not None:
self._repo = repository
else:
# Current behavior - create DynamoDB repository
self._repo = Repository(...)
3. Usage (backwards compatible)
# Existing code - unchanged, still works
limiter = RateLimiter(
name="my-app",
region="us-east-1",
)
# New option for Redis users
from zae_limiter.backends.redis import RedisRepository
limiter = RateLimiter(
name="my-app",
repository=RedisRepository(url="redis://localhost:6379"),
)
Package Structure
Recommended: Optional dependency in main package
# pyproject.toml
[project.optional-dependencies]
redis = ["redis>=5.0"]
# Users install
pip install zae-limiter[redis]
from zae_limiter.backends.redis import RedisRepository
Benefits:
- Single package, simpler for users
- Shared test infrastructure
- Easier to maintain feature parity
- No breaking changes
Redis Implementation Details
Key Structure
ratelimit:{name}:entity:{entity_id}:meta # JSON: {parent_id, name, cascade, created_at}
ratelimit:{name}:entity:{entity_id}:{resource}:{limit_name} # JSON: {tokens_milli, last_update_ms, version}
ratelimit:{name}:children:{parent_id} # SET: child entity IDs
Cascade Support (Lua Script)
-- acquire.lua
local entity_key = KEYS[1]
local bucket_keys = {KEYS[2], KEYS[3], ...}
-- 1. Get entity meta
local meta = redis.call('GET', entity_key)
local parent_id = cjson.decode(meta).parent_id
-- 2. If cascade, get parent buckets too
if parent_id and cascade then
-- Add parent bucket keys to check
end
-- 3. Check all limits (token bucket math)
-- 4. MULTI/EXEC to update all buckets atomically
Feature Parity
| Feature | DynamoDB | Redis | Notes |
|---|---|---|---|
| Token bucket | ✅ | ✅ | Lua script |
| Hierarchical limits | ✅ | ✅ | Key patterns + Lua |
| Cascade | ✅ | ✅ | Lua script handles parent lookup |
| Audit logging | ✅ | ⚠️ | Redis Streams (different API) |
| Usage snapshots | ✅ | ⚠️ | Would need separate consumer |
| TTL cleanup | ✅ | ✅ | Native EXPIRE |
| Transactions | ✅ | ⚠️ | Lua scripts are atomic |
What's NOT included initially
- Audit logging (could use Redis Streams, but different pattern)
- Usage snapshots (requires stream consumer, not Lambda)
- Infrastructure management (no CloudFormation equivalent)
Acceptance Criteria
Phase 1: Protocol Extraction
- Define
RepositoryProtocolwith all methods used byRateLimiter - Existing
Repositoryimplements the protocol (implicit, via duck typing) - Add optional
repositoryparameter toRateLimiter.__init__ - No changes to existing behavior when
repository=None - Update
SyncRateLimitersimilarly
Phase 2: Redis Backend
-
RedisRepositoryimplementsRepositoryProtocol - Token bucket via Lua scripts
- Entity CRUD operations
- Cascade support via Lua
- Connection pooling
- Proper async support (redis-py async)
Phase 3: Testing
- Unit tests with mocked Redis
- Integration tests with real Redis (Docker)
- Benchmark comparisons vs DynamoDB
- Feature parity tests (same tests run against both backends)
Phase 4: Documentation
- Backend selection guide in docs
- Redis-specific configuration options
- Performance comparison with real numbers
- Migration guide (DynamoDB → Redis, if applicable)
Effort Estimate
| Phase | Effort |
|---|---|
| Protocol extraction | 1-2 days |
| Redis backend (basic) | 1 week |
| Cascade support | 2-3 days |
| Testing | 1 week |
| Documentation | 2-3 days |
| Total | 2-4 weeks |
Questions to Resolve
- Should audit logging be supported? (Redis Streams vs skip entirely)
- Connection pooling strategy for Redis?
- Cluster mode support from day one, or single-node first?
- Should
StackOptionsbe backend-agnostic, or DynamoDB-specific?
Dependencies
- #150 - Repository Protocol extraction (must be completed first)
Related
- #49 - v1.0.0 Release (this is post-1.0.0)
- #148 - Comparison page (will reference Redis as alternative)
Decision Log
- 2025-01-15: Confirmed this can be done without breaking changes via optional
repositoryparameter - 2025-01-15: DAX ruled out as optimization path (TransactWriteItems invalidates cache)
- 2025-01-15: No current user demand, but keeping as option for EKS/LiteLLM integration scenarios
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with dependency #150, then inspect the existing RateLimiter, SyncRateLimiter, and Repository entry points before defining the protocol in repository_protocol.py. Review pyproject.toml for optional dependencies and the listed Redis key, Lua, and testing requirements. Done means the phased acceptance criteria are resolved, including backend behavior, tests, and documentation, but several design questions remain open.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- docker, lua, python, redis
- Domain
- backend, databases, testing
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 25/100