Skip to content
Guide

Shared Quotas and Sessions

Coordinate route quotas and revocable sessions across workers with optional Redis or Valkey storage.

Install the optional storage dependency:

Terminal window
uv add flasgo --extra redis

The optional extra is named redis for both Redis and Valkey. Shared storage requires Flasgo 0.9.0 or newer; see version checks and upgrades if your project already uses an older Flasgo release.

import os
from flasgo import Flasgo, RedisRateLimiter, RedisStore, ServerSideSessions
store = RedisStore.from_url(os.environ["FLASGO_REDIS_URL"], namespace="billing")
app = Flasgo(
settings={"SECRET_KEY": os.environ["FLASGO_SECRET_KEY"]},
rate_limiter=RedisRateLimiter(store),
session_backend=ServerSideSessions(store),
)
@app.lifespan
async def lifespan(app: Flasgo):
try:
yield
finally:
await store.aclose()

Use a TLS rediss:// URL and appropriate credentials for a remote service. Redis and Valkey use the same adapter. Connection acquisition and operations have finite timeouts; the convenience constructor caps the pool at 100 connections. Both RedisStore(...) and RedisStore.from_url(...) accept max_value_bytes=65536 to bound each stored value. RedisStore(client, ...) can wrap an application-owned async Redis-compatible client, whose lifecycle remains application-owned. aclose() closes only a client created by from_url. Lua scripts run with EVALSHA, falling back to EVAL when the server replies NOSCRIPT, so the script body is not resent on every request. A wrapped client must provide both evalsha and eval.

Give each application a distinct stable namespace and use the same namespace and signing secret across its workers. A namespace’s keys share a cluster hash slot to keep multi-key Lua operations atomic; this deliberately trades cross-slot scaling for consistent accounting. Use a dedicated, authenticated store with noeviction, suitable persistence/availability controls, and restrictive access. If a server evicts or loses security state, an application cannot reconstruct previous quotas. Never use a cache configured to discard live keys for this purpose.

RedisRateLimiter implements shared sliding windows with server time, atomic evaluation of all rules in one phase, and a bounded number of active quota keys (max_keys=10000). Capacity pressure rejects new keys without evicting existing quotas; Retry-After then reports when the earliest active quota key expires. It supports up to 64 rules per phase. Its default identities use protocol, route path/methods, and rule position, never process-local object IDs. Explicit scope= shares a quota across routes; when a shared scope denies a request, Retry-After waits until enough entries expire to fall back under that rule’s limit. HTTP methods belonging to separate route registrations have separate default quotas; GET/HEAD in the same registration share one. Keep rule configuration consistent across workers. The existing in-process limiter retains its callable-based behavior. Authentication-failure throttling and per-WebSocket message throttling remain process-local; shared storage here coordinates decorated route quotas. Keep edge protections for aggregate abuse control.

Storage errors return 503, without falling back to independent per-worker quotas or accepting unauthenticated session state. A distributed timeout may occur after the server applied an operation; retryable callers must account for that uncertainty. Quotas are abuse controls, not an exactly-once application transaction mechanism.

ServerSideSessions stores bounded JSON behind a random 256-bit opaque cookie identifier. Session.regenerate() rotates the identifier on the next save; call it after login or privilege changes. Session.clear() deletes the session when saved. await backend.revoke(session_id) supports explicit revocation; treat Session.session_id as a credential and never log it. Session metadata is private to response serialization.

Updates compare the previously loaded stored value before replacing it. Rotation atomically replaces the identifier. A stale concurrent write, expired session, or revoked session produces 409 rather than recreating old authenticated state. Applications must not blindly replay side effects after a session conflict: arrange session changes before irreversible work or use application transactions/idempotency as appropriate. Expiry uses SESSION_COOKIE_MAX_AGE; modified saves refresh the storage TTL. Revocation affects subsequent loads, not requests/connections already using an acquired identity. WebSocket handlers do not persist session changes.

Failed dispatch does not save session mutations. A successful custom error handler receives the restored session and may deliberately clear or change it. Response validation also precedes session writes. An explicitly returned error status still permits normal persistence, and business side effects are not rolled back. See Session Changes and Failures.

Duplicate session cookies are treated as an absent credential without loading either identifier from storage. WebSocket head, Host, Origin, and default client-IP route checks run before session loading.

Signed-cookie sessions remain the default. Switching an existing deployment to server-side sessions intentionally requires users with old signed cookies to log in again; no implicit cookie-format migration is attempted. CSRF remains bound to the actual session cookie, including identifier rotation. MemoryStore supplies bounded process-local storage for development/tests. New sessions are created only when modified, with a default maximum serialized size of 64 KiB. Monitor storage availability and response-preparation failures through structured events. StoreUnavailable is the backend exception type.

For process-local session tests, configure session_backend=ServerSideSessions(MemoryStore()). MemoryStore(max_keys=10000) is bounded and does not coordinate workers. It is a session store, not a Redis quota emulator.

The framework’s shared-storage integration tests use FLASGO_TEST_REDIS_URL when set. Otherwise they launch an installed redis-server or valkey-server over an isolated temporary Unix socket; these tests are skipped if neither a server nor a URL is available. Use a dedicated test service and namespace. CI supplies pinned Redis and Valkey services.

Test quota sharing, outage responses, expiry, revocation, identifier rotation, concurrent session conflicts, and CSRF binding. See Testing and Security and Authentication.