You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
🐛 Bug Report: Hardcoded memory_limit 512M crashes Realtime and drops all clients at ~3,000 connections per container #14437
Run Appwrite 2.4.0 self-hosted with the default Realtime setup: one worker (_APP_WORKERS_NUM unset, see realtime.php L367–L373) and the default pool size.
Create a table with read("any").
Open a few thousand WebSocket connections at once (a reconnect storm, as after a deploy or a network blip) to /v1/realtime?project=…&channels[]=tablesdb.<db>.tables.<table>.rows, e.g. with k6 k6/websockets.
Write one row every 2 s through the REST API and count the events per client.
Watch docker logs appwrite-realtime.
👍 Expected behavior
Realtime keeps the connections open and delivers events, or degrades gracefully (slower connects, higher latency, or rejecting new connections with a clear close code). A worker should not crash because it holds many connections.
👎 Actual Behavior
app/init.php sets \ini_set('memory_limit', '512M'); for every Appwrite PHP process (init.php#L17 on 2.4.0). It overrides php -d memory_limit=… and any ini file, so operators cannot raise it. A Realtime worker keeps all its connections and subscriptions in memory, and when it reaches 512 MB it dies with all of them.
Appwrite 2.4.0, default single worker (two Realtime nodes, all clients connect at once and hold for 30 s, one write every 2 s, nofile 65536):
Workers / pool
Clients (total, 2 nodes)
Result
Connect p50 / p99
Events delivered
1 / 16
4,000 (2,000 per node)
3,750 and 4,000 connected¹, no drops, 2 runs
2.7–3.7 s / 3.4–5.2 s
100 %
1 / 16
6,000 (3,000 per node)
crash on both nodes, all clients dropped, 3 of 3 runs
–
0 %
1 / 1
2,000 (1,000 per node)
1,750 connected¹, slow, 3 runs
16.3–18.7 s / 25.1–25.5 s
32–34 %
¹ One load-generator VU (250 clients) occasionally gets neither an error nor a response; this also happened on 2.3.0 in unrelated configurations and is an artefact of the generator.
The crash, on both nodes, every time:
Fatal error: Allowed memory size of 536870912 bytes exhausted (tried to allocate 20480 bytes) in /usr/src/code/packages/cache/src/Codec/Igbinary.php on line 41
ERROR php_swoole_server_rshutdown() (ERRNO 503): Fatal error: Allowed memory size of 536870912 bytes exhausted ...
The container restarts (RestartCount +1). With one worker per container, that means every client of the node is disconnected at once, and their reconnects form the next storm.
The comment in realtime.php explains the single-worker default: a worker with ~1,200 connections needs little CPU, and concurrency is the deployment's job. That's fair for CPU, but the memory limit caps each container at roughly 2,500–3,000 connections during a reconnect storm, independent of the host's RAM. Scaling out then needs more Realtime containers behind a load balancer, which the default setup doesn't provide.
With a pool of 1 (step 3 of the table), handshakes queue for the single database connection: connecting 1,000 clients per node takes 16–19 s and only a third of the events reach the clients in the window. On 2.3.0 the same setup crashed at 5,000 clients (below).
Appwrite 2.3.0, more workers and a raised limit (same cluster, 45 s hold; 2G = init.php patched to read the limit from an environment variable):
Workers / pool / memory_limit
Clients (total, 2 nodes)
Connected
Dropped after connect
Connect p50
Event latency p50
1 / 1 / 512M (default)
2 000
2 000
0
18.3 s
2.7 s
1 / 1 / 512M (default)
5 000
0
all, worker crashed, container restarted
–
–
2 / 16 / 512M
10 000
10 000
0
3.1 s
0.25 s
2 / 16 / 512M
20 000
≈ 3 000
≈ 19 000 (worker crash)
–
–
4 / 16 / 512M
20 000
20 000
0
3.2 s
0.34 s
4 / 16 / 512M
30 000
29 937
4 160 (worker crash)
10.5 s
0.46 s
2 / 16 / 2G (patched)
20 000
20 000
0
5.7 s
0.50 s
2 / 16 / 2G (patched)
40 000
40 000
0
17.3 s
1.2 s
4 / 16 / 2G (patched)
30 000
30 000
0
6.6 s
0.61 s
With the default single worker and pool 1, Realtime uses only 13–30 % CPU during the storm. It is waiting, not working.
Pool 16 alone makes the connect time of 2 000 clients go from 14.6 s to 1.3 s and the delivery within 40 s from 50 % to 100 % (separate run).
With the memory limit raised (patched init.php), no worker crashed up to 40 000 clients. A worker then used up to ≈ 1.8 GB RSS. Connect time and latency grow with the client count, but the connections are held.
2.4.0 (measured), also 2.3.0. The hardcoded limit has been there since at least 1.3 (#5528).
💻 Operating system
Linux (Debian 12), Docker
🧱 Your Environment
Self-hosted, Postgres adapter. Realtime on two nodes with 4 vCPU and 12 GB each, ulimits.nofile 65536 (with the image default of 1,024, Realtime stops accepting at ≈ 1,000 connections per node, without an error to the client). Postgres and Redis on a third node.
💡 Suggested fix
Make memory_limit configurable, e.g. _APP_MEMORY_LIMIT (default 512M), or don't override it in init.php and let the image's php.ini decide. Make the hardcoded PHP memory_limit configurable #13162 does this and has been open since August.
Size the Realtime pool for handshake storms: pass _APP_WORKER_MAX_COROUTINES (or a dedicated variable) to appwrite-realtime in the default docker-compose.yml.
Fail gracefully near the limit: reject new connections with a close code instead of crashing the worker and dropping every existing connection. fix(realtime): bound the per-connection output buffer #13415 (bounding the per-connection output buffer) helps with one source of memory growth.
Document the per-container connection limit and ulimits.nofile for Realtime.
Risks / things to consider for the fix
A higher limit means more RAM per worker. Operators need to size the host (we measured ≈ 2.3 GB RSS per 10 000 clients on one node).
More workers spread connections and memory, but on a shared API node they also take CPU from the API. With 8 workers on 4 vCPU, a storm of 40 000 clients took all CPU of the node and slowed the API to 4 s per request.
👀 Have you spent some time to check if this issue has been raised before?
Activity
ItsRoy69 commentedon Oct 13, 2026
Can work on this issue