docs/multi-replica.md
Rama main · réplica de lectura del repositorio original.
# Multi-replica deployment
PicImpact can run as multiple replicas behind a load balancer (e.g. several Node
containers or a Kubernetes Deployment with `replicas > 1`). Most state is already
shared and safe across replicas:
- **Sessions** are persisted in PostgreSQL (better-auth Prisma adapter), so login
state is consistent across replicas.
- **The image-preprocessing task queue** is claimed with a PostgreSQL advisory
lock + a database lease, so two replicas (or a replica and an external cron)
can never process the same task run concurrently.
A few per-instance concerns need configuration when you scale past one replica.
## 1. Background preprocess ticker — run a single driver
Each replica starts the background preprocess ticker by default in production
(`NODE_ENV=production`). The advisory lock means N replicas will not double-process
work, but they will all poll every ~10s, so N−1 replicas just contend for the lock
and waste CPU.
Recommended for multi-replica: disable the in-process ticker on every replica and
drive the queue with a single external scheduler.
```bash
# On every replica:
PREPROCESS_TICKER_ENABLED=false
```
Then have one external cron (Kubernetes CronJob, systemd timer, etc.) hit the tick
endpoint every 10–60s:
```bash
curl -X POST https://your-host/api/v1/preprocess-tasks/tick
```
Since this endpoint can be reached publicly, you can optionally protect it with a
shared secret. Set `PREPROCESS_TICK_SECRET` and the cron must send it in the
`x-preprocess-tick-secret` header; calls without a matching secret get `401`:
```bash
curl -X POST https://your-host/api/v1/preprocess-tasks/tick \
-H "x-preprocess-tick-secret: $PREPROCESS_TICK_SECRET"
```
When `PREPROCESS_TICK_SECRET` is unset the endpoint stays open (so single-instance
and internal-ticker deployments are unaffected).
> Note: `/api/v1/*` admin endpoints currently rely on client-side auth checks
> only (server-side enforcement is a separate, tracked hardening item), so the
> tick secret is the one explicit server-side gate added here for the public cron
> path. If you expose the app publicly, also restrict the admin API at the
> network layer until server-side auth lands.
Alternatively, enable the ticker on exactly one replica (e.g. a dedicated worker
pod) and set `PREPROCESS_TICKER_ENABLED=false` on the rest.
> `PREPROCESS_TICKER_ENABLED` is also useful on serverless, where there is no
> long-lived process to run the ticker — disable it and use the external cron.
## 2. Database connections
Each replica opens its own Prisma connection pool. With N replicas the total
connection count is roughly `N × pool_size`. Make sure your PostgreSQL
`max_connections` covers that, or put a pooler such as PgBouncer (transaction
mode) in front of the database.
## 3. Session revocation latency
Sessions are stored in the database and shared across replicas, but better-auth
keeps a short-lived signed **cookie cache** in each instance's memory. The cache
TTL is 60s, so a sign-out or session revocation propagates to every replica
within ~60s. Lower it further in `server/auth/index.ts` (`session.cookieCache.maxAge`)
if you need faster propagation.
## 4. Public data cache (current behavior)
The public read paths (gallery listings, album nav, public config) are cached
with Next.js' Data Cache and invalidated on admin writes via `revalidateTag`.
That cache is **per-replica in-memory** by default, so a `revalidateTag` call only
busts the cache on the replica that handled the write. Other replicas pick up the
change when their cache entry's safety-net TTL expires:
- gallery listings: ~60s
- album nav / public config: ~1h (admin writes still bust them instantly on the
writing replica; the TTL only bounds cross-replica propagation)
If you need instant cross-replica consistency for admin changes, a shared
PostgreSQL-backed cache handler is the planned next step (it routes the Data Cache
through Postgres + `LISTEN/NOTIFY` so an invalidation on one replica is seen by all).
## 5. In-flight task cancellation
Cancelling a running preprocess/metadata task signals the replica that owns the
run. If the cancel request lands on a different replica, it is still recorded, and
the running replica stops at the next lease checkpoint (within the lease window).
For rare admin actions this lag is acceptable; a fully cross-replica cancel signal
is a planned follow-up.