>_

Scaling

The Open Privacy Suite supports horizontal scaling — multiple proxy instances sharing a single Postgres database and Redis instance. All instances serve identical requests with consistent RBAC, sessions, and caching. How traffic is routed to the instances (load balancer, DNS, Kubernetes Service, etc.) is up to your infrastructure.


Requirements

ComponentPurposeShared?
PostgresRBAC, contracts, users, audit logs — source of truthYes — all instances connect to the same database
RedisSessions, OAuth state, RBAC permission cacheYes — all instances connect to the same Redis
Ethereum nodeJSON-RPC endpoint (e.g. Geth, Anvil)Yes — all instances forward to the same node

No sticky sessions required

Any instance can serve any request. Sessions and RBAC state are stored in shared Redis and Postgres, so traffic routing does not need session affinity.


Configuration

Point each proxy instance to the same Postgres and Redis:

DATABASE_URL=postgres://user:pass@your-postgres:5432/privacy_proxy?sslmode=require
REDIS_URL=redis://:password@your-redis:6379/0
NODE_URL=http://your-ethereum-node:8545

All other environment variables (JWT_SECRET, ADMIN_API_TOKEN, etc.) must be identical across instances — they share the same user base and token validation.

JWT secrets must match

If instances have different JWT_SECRET or JWT_REFRESH_SECRET values, tokens issued by one instance will be rejected by another.


What is shared

StateStoreBehavior
User sessionsRedisLogin on instance A, use the token on instance B
OAuth flowsRedisStart auth on instance A, complete on instance B
RBAC permissionsPostgres + Redis cachePermission changes propagate to all instances
Contract grantsPostgresVisible to all instances immediately
Audit logsPostgresWritten by whichever instance handles the request

Connection resilience

Both Redis and Postgres connections handle startup ordering and transient failures:

  • On startup, the proxy retries the initial connection if the database or Redis is not yet reachable. This handles cases where the proxy starts before its dependencies are ready (common in container orchestration).
  • At runtime, if Redis becomes temporarily unavailable, cached operations fail fast instead of blocking on timeouts. The proxy continues serving requests using Postgres as the source of truth. When Redis recovers, caching resumes automatically.
  • Postgres reconnection is handled by the connection pool. If Postgres goes down briefly, the first query after recovery may fail, but subsequent queries succeed on fresh connections.

Redis down = auth disrupted

When Redis is unavailable, session validation and OAuth flows fail because session state is stored in Redis. RBAC permission checks fall back to Postgres (slower but functional). Plan Redis for the same availability as your database.


Testing locally

A docker-compose.scale.yml is provided for testing with two proxy instances:

docker compose -f docker-compose.scale.yml up --build -d

This starts shared Postgres, Redis, and Anvil with two proxy instances on ports 8091 and 8092. A verification script tests cross-instance state sharing:

./scripts/verify-scaling.sh http://localhost:8091 http://localhost:8092

Built-in vs external Redis

By default, docker compose up starts a built-in Redis container. To use an external Redis instead, set REDIS_URL in your .env file:

REDIS_URL=redis://:password@your-redis-host:6379/0

When REDIS_URL is set, make dev-stack automatically skips starting the built-in Redis container.

No setup required

Redis requires no schema, migrations, or initialization. Point the proxy at any Redis instance with authentication enabled and it works immediately.