Эксплуатация нод

Утечки RAM в Xray-ноде: три корня и точечные фиксы

Token-bucket rate-limit, CLOSE-WAIT без keepalive и висячие HTTP/2-стримы XHTTP. С замерами до/после — RAM падает с гигабайта до сотни мегабайт.

10 мин чтения

Симптом

RAM ядра xray (Go-процесс ноды) линейно растёт на сотни мегабайт в час, watchdog рубит ноду каждые 1–8 часов, xrayUptime не держится. Частый диагноз — «транспорт плохой, сокеты висят», и люди меняют протокол. Зря: за симптомом стоят три независимых корня, и каждый лечится точечно на уровне инбаунда или policy, без смены архитектуры.

Корень 1. Rate-limit через per-user token-bucket

Самый жёсткий. В policy.levels.0 включены uplinkOnly/downlinkOnly — лимит скорости на пользователя. Xray реализует их через token-bucket на каждого юзера, который держится в памяти процесса и не очищается ни по idle, ни при дисконнекте — только при reload. Тысяча пользователей плюс мобильный churn — тысячи бакетов, каждый со своим mutex и state.

Фикс: убрать uplinkOnly/downlinkOnly из policy.levels.0. Эффект наступает за 5–15 минут, RAM падает на 80–89% — в замере с 1.10 ГБ до 187 МБ. Если rate-limit действительно нужен, закладывайте обязательный рестарт xray раз в ~12 часов. Альтернатива против перекачки и торрентов — torrent-blocker плюс маршрут служебных портов BitTorrent в DIRECT мимо туннеля.

Корень 2. CLOSE-WAIT в TCP-Reality: keepalive на инбаунде выключен

На xray-инбаунде TCP keepalive по умолчанию отключён (в отличие от outbound). Когда мобильный клиент теряет сеть без FIN, сервер держит ESTABLISHED-сокет до ~2 часов. Каждый такой «живой» с точки зрения ядра сокет — метаданные в памяти. Это не утечка в коде: память законно держится под зомби-сокеты. Исправлять нужно ядро, через sockopt инбаунда:

"sockopt": {
  "tcpKeepAliveIdle": 30,
  "tcpKeepAliveInterval": 15,
  "tcpUserTimeout": 10000,
  "tcpFastOpen": true,
  "tcpcongestion": "bbr"
}

Главный гвоздь — tcpUserTimeout: 10000 (RFC 5482): нет ACK на отправленные данные 10 секунд — рвём сокет. Замер: ESTABLISHED с 13134 до 765, RSS с 1006 МБ до 115 МБ. Применимо только к network: tcp + reality.

Корень 3. Висячие HTTP/2-стримы в XHTTP: sockopt бесполезен

В XHTTP один TCP-сокет несёт N логических HTTP/2-стримов. Стрим повис (клиент пропал), а TCP под ним живой — mux его переиспользует, keepalive ОС видит живой TCP и не вмешивается. Копятся зомби-стримы: в замере 3660 ESTABLISHED на 9 пользователей. Лечится на уровне xray, через xhttpSettings.extra:

"extra": {
  "scMaxBufferedPosts": 30,
  "scStreamUpServerSecs": "20-80",
  "xmux": {
    "maxConcurrency": "16-32",
    "hMaxRequestTimes": "600-900",
    "hMaxReusableSecs": "1800-3000",
    "hKeepAlivePeriod": 0,
    "cMaxLifetimeMs": 0
  }
}

scMaxBufferedPosts — убийца висячих стримов: превышение буфера, и клиент дисконнектится. hMaxReusableSecs принудительно рециклит «вечные» TCP-соединения каждые 30–50 минут. scStreamUpServerSecs шлёт padding, чтобы CDN не обрезал idle-стрим. Замер: ESTABLISHED на пользователя с ~407 до ~81, RSS с 979 до 340 МБ, throughput вырос на 131%. Полный эффект — через 30–50 минут после рестарта. sockopt при этом добавляют как вторую линию защиты физического TCP.

Подводные камни

Вывод. Линейный рост RAM у xray-ноды — не «плохой транспорт», а один из трёх конкретных дефектов конфигурации: per-user token-bucket rate-limit, выключенный keepalive на TCP-инбаунде или висячие HTTP/2-стримы в XHTTP. Диагностируйте по числу ESTABLISHED на пользователя и типу транспорта, лечите точечно — и рестарты по watchdog прекращаются.

Как подключиться за 2 минуты
Частые вопросы

Частые вопросы

Сколько независимых причин утечки RAM на Xray-ноде и нужно ли менять протокол?
За симптомом растущей RAM стоят три независимых корня, и каждый лечится точечно на уровне инбаунда или policy — менять транспорт или протокол не требуется.
Как rate-limit на пользователя приводит к росту RAM?
Включённые в policy.levels.0 uplinkOnly/downlinkOnly реализуются через token-bucket на каждого юзера, который держится в памяти процесса и не очищается ни по idle, ни при дисконнекте — только при reload. При тысяче пользователей и мобильном churn накапливаются тысячи таких бакетов.
Почему CLOSE-WAIT сокеты на TCP-Reality держат память до двух часов?
TCP keepalive на xray-инбаунде по умолчанию отключён. Когда мобильный клиент теряет сеть без FIN, сервер держит ESTABLISHED-сокет до примерно 2 часов, и каждый такой сокет — это метаданные в памяти. Лечится настройкой sockopt с tcpUserTimeout: 10000.
Почему sockopt не помогает от висячих стримов в XHTTP?
В XHTTP один TCP-сокет несёт несколько логических HTTP/2-стримов. Если стрим повис, а TCP под ним живой, keepalive ОС видит живой TCP и не вмешивается — зомби-стримы копятся. Лечится на уровне xray через xhttpSettings.extra, например scMaxBufferedPosts и hMaxReusableSecs.
Помогает ли флаг --max-old-space-size от утечек памяти в xray-ноде?
Нет, этот флаг относится к Node.js, а ядро ноды написано на Go — он не действует на процесс xray.
Похожее

Ещё в базе знаний

Подключите рабочий VPN

Инструкции базы знаний написаны под ключи VPN PRO: ключ выдаёт Telegram-бот за минуту, работоспособность гарантируем. Промокод VPN10 — 10 дней бесплатно, без карты.

Промокод VPN10 — 10 дней бесплатно · до 31 августа · передавайте друзьям