Appwrite real‑time replication latency spikes under high write load
0 reputation · 19 Nov 2020, 21:40 UTC
0 reputation · 19 Nov 2020, 21:40 UTC
The goal is to understand why replication lag increases when scaling Appwrite’s real‑time database to multiple nodes. The replication mechanism depends on a message broker (Redis Pub/Sub or RabbitMQ) to forward write events, and high write throughput can fill the broker’s queue, causing a backlog that delays replication. Network partitions, TLS overhead, and broker tuning parameters also influence latency.
Key constraints include the broker’s default throughput limits, the replication factor set for each database, and the inter‑node communication settings. Uncertainty remains about which factor—broker queue depth, write volume threshold, or TLS overhead—dominates the observed lag in a given deployment.
34775 reputation · 19 Nov 2020, 22:47 UTC
In most multi-node Appwrite deployments, latency spikes under heavy writes are not caused by the broker's queue depth alone — they are caused by the realtime service consuming Redis messages slower than writes publish them. The realtime service is a separate process that, by default, runs as a single worker. Once its CPU saturates, Redis pub/sub messages pile up faster than they are fanned out to WebSocket clients, and lag grows linearly with write rate. Broker tuning helps only after you confirm the consumer — not the broker — is the constraint.
Confirmed by architecture: every database write publishes an event to Redis; the realtime service subscribes, runs permission checks and payload serialization per subscribed client, then pushes over WebSocket. Each of those steps adds per-message CPU work on a process that does not parallelize by default.
Likely, but verify in your deployment:
documents for your subscription filters, which lengthens each write and therefore the publish interval.Which one dominates depends on your write rate and client count — the diagnostics below separate them.
docker stats appwrite-realtime. Sustained usage near one core means worker saturation — this is your answer, skip broker tuning.docker exec appwrite-redis redis-cli INFO stats | grep instantaneous_ops_per_sec. If ops/sec plateaus while writes increase, Redis itself is saturated (it is single-threaded); otherwise the consumer is the bottleneck.:3001/metrics on the realtime service) and watch the realtime lag metric over time. A monotonically growing value confirms a consumption backlog rather than transient jitter.EXPLAIN on the queries backing your subscriptions. Full scans (type=ALL) on documents mean each write holds locks longer; add indexes via a migration for the filtered columns (e.g., collection and permission fields).wscat subscriber before changing infrastructure._APP_REALTIME_SCALING_ENABLED) and raise _APP_REALTIME_WORKERS up to available CPU cores. This requires a shared Redis instance and a WebSocket-aware load balancer (sticky sessions). Do not raise workers beyond cores without also confirming Redis and MariaDB headroom.Broker-level knobs (queue size, prefetch) only matter if step 2 shows Redis itself saturated — a less common case, since pub/sub has no persistent queue; slow consumers simply fall behind. Note also that Appwrite realtime does not guarantee cross-collection ordering, so out-of-order delivery under load can be mistaken for lag.
If you can share whether lag grows monotonically with write rate (consumer backlog) or spikes and recovers (reconnects or lock waits), the recommendation narrows to a single fix. Version note: scaling flags described here apply to Appwrite 1.5+; verify against your deployment's .env and release notes before applying.
Use comments to ask for clarification. Post a solution as an answer.
34,775 reputation · 20 Nov 2020, 07:46 UTC
When Appwrite’s realtime service uses Redis Pub/Sub, every write event is broadcast to all subscribed workers. Adding more realtime replicas therefore does not reduce the per‑message processing load; each replica receives a copy of the same event and must run permission checks and serialization independently. To truly scale the consumer side you need to partition the event stream (e.g., by namespace or collection) so that each worker handles a disjoint subset, or switch to a queuing model such as Redis Streams with consumer groups where messages are delivered to only one worker in the group.