План: 100 тысяч пользователей на одном VPS, с заделом под CDN

Версия документа 2026-05-29 · Цель: нынешний единственный VPS должен держать до 100 тысяч MAU не падая, а приложение — переехать на CDN бесшовно, когда придёт время.

Целевые цифры

MetricЗначениеSource
MAU100,000цель
DAU30,00030% от MAU — норма для утилитарного приложения
Одновременных WS10 000 (пик 15 тысяч)~10% от MAU днём, 15% на пике
Действий в сутки90,0003 открытия или звонка на дневного пользователя
Запросов HTTP API в секунду (в среднем)3-5в основном key_create, host_sync и crash_report
Запросов HTTP API в секунду (на пике)30-50после массового выпуска, обновляющего номера

Архитектура сегодня

Фаза A — клиент Android СЕЙЧАС, v6.66

Goal: сократить число лишних запросов от клиентов, чтобы серверу досталось меньше работы.

A1. Экспоненциальный откат повторов HTTP для ожидающих владельческих ключей

File: HostService.kt:1424-1432. Сегодня syncPendingHostKeys() вызывается каждые 60 с и бьёт в Api.keyCreate по каждому ожидающему ключу без всякого отката.

Хранить в Prefs per-localId: attempt_at_id и attempt_count_id. Откат: 30 с → 60 с → 2 мин → 5 мин → 15 мин → 30 мин (потолок). При успехе сбрасывается.

A2. Откат повторов HTTP для ожидающих гостевых бандлов

File: Vault.kt:213. Логика та же.

A3. Сверка перед инкрементом overridesVersion

File: GuestConn.kt (5+ мест). Сравнить новое parseNumbers с предыдущим, по хешу или напрямую. Совпали — не инкрементировать AppState.overridesVersion. Это убирает волны перерисовки — 80 чтений SharedPreferences × 20 карточек — при каждой инициализации WebSocket.

Фаза B — защита сервера и готовность к CDN NOW

Goal: защитить сервер от шторма даже при неисправном клиенте и отвязать адрес APK от домена.

B1. Ограничение частоты host_hello и guest_hello

In server.php case 'host_hello' / 'guest_hello': если тот же device_id / user_key присылал hello меньше N секунд назад, закрыть соединение, не трогая базу. N = 5 с.

Защита на случай, если клиент со сломанной логикой шлёт сотню hello в секунду.

B2. Потолок pending_host_msgs на host_id

In enqueueHostMsg: если у этого host_id уже больше 200 строк, удалять самую старую через DELETE FROM pending_host_msgs WHERE host_id=? ORDER BY id ASC LIMIT 1. Защита от бесконечного роста очереди при злонамеренной активности.

B3. Готовность к CDN: адрес APK — в конфиг

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'].

Когда состоится переезд на CDN, изменится ровно одна строка в _config.php to https://apk.entrixy.com/entrixy.apk (Cloudflare R2 со своим доменом). Выпуск приложения ни при чём.

B4. APK на CDN — проверка целостности

Клиент уже проверяет подпись APK при загрузке (debug-хранилище, см. build.gradle.kts:23-29). Для CDN этого достаточно: даже если узел CDN подменит файл, подпись не сойдётся и Android откажется его ставить. На клиенте ничего доделывать не нужно.

Фаза C — масштабирование Workerman КОГДА УПРЁМСЯ В ПОТОЛОК

Нынешний count=1 Workerman без труда держит порядка 10 тысяч одновременных WebSocket — server.php стартует с ним уже на строке 40. Первым узким местом станет процессорное время PHP на пике обмена: 10 тысяч понгов в секунду плюс действия.

C1. Workerman count=2–4 с Redis pub/sub для общего состояния

Today $hosts, $guests, $calls — массивы в памяти одного процесса. Чтобы запустить несколько процессов, их надо перенести в Redis:

Сообщение пришло на воркер W1, а получатель на W2 — передаём через Redis pub/sub.

Effect: линейное масштабирование до числа ядер VPS. На четырёх ядрах это примерно 40 тысяч одновременно.

C2. Альтернатива: одна машина, nginx или HAProxy перед Workerman, шардирование по client_ip

Менее гибко и не требует Redis. Каждый пользователь всегда попадает на один и тот же воркер. Но ломается, когда отправитель и получатель оказываются на разных воркерах.

Фаза D — когда переезжать на CDN LATER

  1. Бакет Cloudflare R2 → загрузить entrixy.apk.
  2. Свой домен apk.entrixy.com → подключить его через CDN Cloudflare.
  3. In _config.php change $apk_url на новый.
  4. В скрипте выкладки, после assembleRelease add r2 cp app-release.apk apk-bucket/entrixy.apk.

Чего сейчас делать НЕ надо

Что делается прямо сейчас

  1. Фаза A целиком → выпуск v6.66.
  2. Фаза B целиком, без перезапуска сервера WebSocket, для проверки до подтверждения.
  3. Фаза C делается в отдельной ветке, требует Redis на VPS и является самостоятельной задачей.
  4. Фаза D описана выше и выполняется при необходимости.