Document version 2026-05-29 · Goal: the current single VPS should hold up to 100k MAU without falling over, and the app should move to a CDN seamlessly when the time comes.
| Metric | Value | Source |
|---|---|---|
| MAU | 100,000 | the goal |
| DAU | 30,000 | 30% of MAU, normal for a utility app |
| Concurrent WS | 10,000 (peak 15k) | ~10% of MAU during the day, 15% at peak |
| Actions per day | 90,000 | 3 openings or calls per daily user |
| HTTP API requests per second (average) | 3-5 | mostly key_create, host_sync and crash_report |
| HTTP API requests per second (peak) | 30-50 | after a mass release that updates numbers |
count=1 — a single PHP process holds ALL WebSocket connections in the arrays $hosts, $guests, $calls in-memory.File: HostService.kt:1424-1432. Today syncPendingHostKeys() is called every 60 s and hits Api.keyCreate for every pending key with no backoff.
Store in Prefs per-localId: attempt_at_id and attempt_count_id. Backoff: 30 s → 60 s → 2 min → 5 min → 15 min → 30 min (capped). Reset on success.
File: Vault.kt:213. The same logic.
File: GuestConn.kt (5+ places). Compare the new parseNumbers with the previous one, by hash or directly. If they are identical, do not bump AppState.overridesVersion. This cuts the recompose waves — 80 SharedPreferences reads × 20 cards — on every WebSocket initialisation.
In server.php case 'host_hello' / 'guest_hello': if the same device_id / user_key sent a hello less than N seconds ago, close the connection without touching the database. N = 5 s.
Protection in case a client with broken logic sends a hundred hellos a second.
In enqueueHostMsg: if this host_id already has more than 200 rows, delete the oldest with DELETE FROM pending_host_msgs WHERE host_id=? ORDER BY id ASC LIMIT 1. Protection against endless queue growth under malicious activity.
In _config.php:
$GLOBALS['apk_url'] = 'https://entrixy.com/entrixy.apk';
$GLOBALS['download_page_url'] = 'https://entrixy.com/download';
In api/version_check.php: 'download_url' => $GLOBALS['apk_url'].
When the move to a CDN happens, exactly one line changes in _config.php to https://apk.entrixy.com/entrixy.apk (Cloudflare R2 with a custom domain). No app release involved.
The client already verifies the APK signature on download (debug keystore, see build.gradle.kts:23-29). That is enough for a CDN: even if a CDN node swaps the file, the signature will not match and Android refuses to install it. No extra work is needed on the client.
count=1 Workerman holds around 10k concurrent WebSockets without trouble — server.php line 40 already starts with it. The first bottleneck will be PHP's CPU time during peak message exchange: 10k pongs a second plus the actions.
Today $hosts, $guests, $calls are in-memory arrays inside a single process. To run several processes they have to move into Redis:
When a message arrives at worker W1 but the recipient is on W2, relay it through Redis pub/sub.
Effect: linear scale-out up to the VPS core count. On four cores that is roughly 40k concurrent.
Less flexible and needs no Redis. Each user always lands on the same worker. But it breaks down when sender and recipient end up on different workers.
entrixy.apk.apk.entrixy.com → connect it through the Cloudflare CDN._config.php change $apk_url to the new one.assembleRelease add r2 cp app-release.apk apk-bucket/entrixy.apk.