Skip to content

Horizontal Scaling

PyWire holds WebSocket session state in worker process memory. This is fast and simple, but has implications for scaling:

  • Single worker — everything works out of the box. Sessions persist for the connection lifetime.
  • Multiple workers — requests may hit different processes. Without shared state, sessions break.
  • Multiple machines — same issue, amplified across separate servers.

PyWire has built-in support for Redis and Redis-compatible stores like Valkey, Dragonfly, and KeyDB.

Three steps to enable multi-worker scaling:

  1. Install the Redis extra:

    Terminal window
    uv add pywire[redis]
  2. Set the REDIS_URL environment variable:

    Terminal window
    export REDIS_URL=redis://localhost:6379
  3. Increase workers:

    Terminal window
    pywire run --workers 4

That’s it. PyWire auto-detects REDIS_URL at startup and uses it for session persistence — no code changes needed.

After each event handler executes, PyWire serializes page state and persists it to Redis. On WebSocket reconnect (or when a different worker handles the next request), state is restored from Redis into a fresh page instance.

What gets persisted:

  • Wire values (via .peek())
  • Component state snapshots
  • errors and loading dicts
  • user attribute
  • Route path and page class

What gets rebuilt naturally:

  • Dependency tracking (_wire_subscribers, _region_dependencies)
  • Background tasks
  • Request object
  • Component instances (from snapshots)

Session TTL defaults to 30 minutes and is configurable:

app = PyWire(session_ttl=3600) # 1 hour

For full control, pass a session store directly instead of relying on auto-detection:

from pywire import PyWire
from pywire.runtime.redis_store import RedisSessionStore
app = PyWire(
session_store=RedisSessionStore("redis://localhost:6379"),
session_ttl=3600,
)
VariableDescription
REDIS_URLRedis/Valkey connection string (checked first)
FLY_REDIS_URLFly.io Upstash Redis URL (fallback if REDIS_URL unset)

For backends other than Redis, implement the SessionStore protocol:

from pywire.runtime.session_store import SessionStore
class MyStore(SessionStore):
async def get(self, session_id: str) -> dict | None: ...
async def set(self, session_id: str, data: dict, ttl: int | None = None) -> None: ...
async def delete(self, session_id: str) -> None: ...
async def exists(self, session_id: str) -> bool: ...
async def touch(self, session_id: str, ttl: int | None = None) -> None: ...
async def close(self) -> None: ...

Pass it to PyWire(session_store=MyStore()).

Single container with multiple workers:

Terminal window
docker run -p 8000:8000 -e REDIS_URL=redis://your-redis:6379 my-app

Multiple containers with a shared Redis instance (docker-compose.yaml):

services:
web:
build: .
ports: ['8000:8000']
environment:
REDIS_URL: redis://redis:6379
depends_on: [redis]
redis:
image: redis:7-alpine
ports: ['6379:6379']

Use pywire deploy --platform render --redis --workers 4 to generate a render.yaml with a KV store pre-configured. Render provisions the Redis instance and injects REDIS_URL automatically.

Or add one manually:

services:
- type: web
name: my-app
runtime: docker
plan: starter
envVars:
- key: REDIS_URL
fromService:
name: my-app-kv
type: keyvalue
property: connectionString
- type: keyvalue
name: my-app-kv
plan: starter
ipAllowList: []

Use plan: standard or higher for persistence across restarts.

Option A — Sticky sessions (simple, limited): Fly Proxy can route all requests from a session to the same machine using fly-replay. Works for light scaling but breaks with VPNs and corporate proxies.

Option B — Redis (recommended): Add Upstash Redis:

Terminal window
fly redis create
fly secrets set REDIS_URL=<connection-string>

Install pywire[redis] and increase workers in your Dockerfile.

Add a Redis addon via the Railway dashboard or railway add. Railway injects REDIS_URL automatically. Install pywire[redis] and increase --workers in your Dockerfile.

Any Redis-compatible KV store works:

  • AWS: ElastiCache for Redis
  • GCP: Memorystore for Redis
  • Azure: Azure Cache for Redis
  • Self-hosted: Redis, Valkey, Dragonfly, or KeyDB

Set REDIS_URL, install pywire[redis], and increase workers.

  • Single worker is free on most platforms and sufficient for small apps with few concurrent users.
  • Redis adds a separate service to your infrastructure. Most cloud providers charge for it separately.
  • Render: Free tier has no KV. starter plan KV starts at $7/month.
  • Fly.io: Upstash Redis has a free tier (limited commands/month).
  • Railway: Redis addon pricing varies by usage.

Only add Redis when you actually need multiple workers or machines. For most apps in early development, --workers 1 is the right choice.