Жалоба, от которой нельзя отмахнуться
«Нода грузится непропорционально числу юзеров» — частая и обманчивая претензия. Отмахнуться «всё ок» нельзя, но и валить на «природу транспорта» без цифр — тоже. Нужен протокол досмотра, который даёт вывод на цифрах: это объём трафика, число соединений, шторм переподключений, шаринг подписок — или скрытая утечка.
Эталон нормы: здоровая нода добавляет примерно +1–2% нагрузки на 100 онлайн-пользователей. Отклонение в разы — аномалия, а не «природа».
Шаг 0: исключить утечку gRPC к внутреннему API
Ядро ноды держит gRPC-сессию к своему же локальному API — служебному порту статистики. В некоторых версиях образа эти сессии текут: за 15 минут натекают десятки тысяч висящих коннектов, счётчик файловых дескрипторов растёт к сотням тысяч, RAM ползёт к OOM. Клиентский трафик при этом нормальный — если смотреть только на трафик и юзеров, утечку не увидишь.
ss -tn state established dst 127.0.0.1:<api_port> | wc -l # норма = 1 (один штатный поллер)
ls /proc/$(pgrep -x <процесс-ядра>)/fd | wc -l # FD сопоставим с числом клиентских коннектов
Лечение — только полный рестарт процесса (docker restart контейнера ноды). Панельный hot-reload делает reload конфигурации, PID не меняется, утечка не чистится. Проверка: ps -o lstart= -p <PID> — старое время старта означает, что рестарт не сработал. После настоящего рестарта established/FD/RAM падают на порядок. Хороший кандидат на watchdog: FD выше порога → авторестарт.
Развилка: объём или соединения
- Кто ест CPU/RAM — верить
top -bn2(мгновенный срез), а неps -eo %cpu: последний врёт на короткоживущих процессах, ssh-сессия покажет 40% как средний за жизнь. LoadAvg делить наnproc. - Сколько соединений и куда —
ss -tn state established; обычно почти всё на XHTTP-порту. - Объём или количество? Топ пользователей по трафику за сутки из статистики панели. Топ качает десятки-сотни ГБ → виноват трафик. Топ ровный, единицы ГБ → грузит число соединений, идём дальше.
- Стабильные соединения или шторм? Замер
ssна порту с интервалом 10 секунд, плюс TIME-WAIT и счётчик mux-ошибок. Стабильно, TIME-WAIT низкий → долгоживущие открытые стримы. Резкий рост, большой TIME-WAIT, тысячи mux-ошибок → churn-шторм, копать клиента. - Шаринг подписок? Среднее число устройств на пользователя (норма около 1.7, максимум в пределах десятка).
- Запас conntrack —
nf_conntrack_countпротивnf_conntrack_max.
Подводные камни
- XHTTP-множитель. VLESS-Vision — 1–2 коннекта на клиента; XHTTP — около 6 постоянных стримов на клиента. Поэтому «при 100 юзерах не грузило, а при 38 грузит» после смены основного транспорта — не мистика: множитель соединений на пользователя вырос в разы. Это природа XHTTP, но она объясняет ровную повышенную нагрузку, а не десятикратную аномалию — та почти наверняка утечка из шага 0.
ps -eo %cpuобманывает — толькоtop -bn2.- Прежде чем резать конкурентность XHTTP (
maxConcurrency,scMaxConcurrentPosts) — сверьтесь с эталоном. Если нагрузка в пределах +1–2% на 100 онлайн и запас есть (load меньше 0.25 на ядро, conntrack около 5%) — не трогать. Рычаги применять только со снапшотом.
Вывод. Досматривать нагрузку ноды надо на цифрах и с эталоном +1–2% на 100 онлайн. Первым шагом — исключить утечку gRPC к внутреннему API (лечится только полным рестартом процесса, не hot-reload). Дальше развилка: объём, соединения, churn или шаринг. Ровная повышенная нагрузка на XHTTP — множитель стримов, норма. Нагрузка в разы выше эталона при спокойном трафике — ищите утечку, а не списывайте на транспорт.