Трафик и лимиты
Агент раз в 60 секунд читает счётчики Xray Stats API (без reset), считает дельты по каждой подписке (email-тег = id подписки) и накапливает их в локальном буфере, персистентном между рестартами агента. Буфер отправляется в стрим как UsageReport и очищается только после подтверждения backend.
sequenceDiagram
participant X as Xray
participant AG as Agent
participant BE as Backend
loop каждые 60 с
AG->>X: StatsQuery (без reset)
AG->>AG: дельта → локальный буфер (диск)
end
AG->>BE: UsageReport(report_id, entries[])
BE->>BE: транзакция: upsert traffic_usage + счётчики подписок
BE->>AG: Ack(report_id)
AG->>AG: очистить подтверждённое
Свойства схемы:
- Нет потери данных. Reset-счётчики Xray («прочитал — обнулил») теряют дельту, если запись в БД упала после reset. Здесь reset не используется: дельта вычисляется агентом, буфер переживает рестарт агента, неподтверждённое переотправляется.
- Идемпотентность.
report_id— монотонный счётчик на ноду. Backend хранит последний применённыйreport_idи молча подтверждает дубликаты — повторная доставка не удваивает трафик. - Push, не pull. Backend не опрашивает ноды по расписанию; офлайновая нода досылает накопленное при переподключении.
Рестарт Xray обнуляет его счётчики — агент это детектирует (счётчик уменьшился) и берёт новое значение как дельту от нуля.
Хранение
Section titled “Хранение”| Таблица | Гранулярность | Retention |
|---|---|---|
traffic_usage |
подписка × нода × час | 30 дней |
traffic_usage_daily |
подписка × нода × день | 12 месяцев |
Часовые записи апсертятся (bytes += delta) при приёме отчётов; фоновый процесс сворачивает их в суточные и удаляет истёкшие. Границы retention конфигурируемы.
subscriptions.traffic_used_bytes — материализованный счётчик расхода текущего периода, обновляется в той же транзакции, что и traffic_usage. Это единственное намеренно денормализованное значение в схеме: проверка лимита выполняется при каждом отчёте для каждой подписки, SUM() по time-series на этом пути неоправдан. Счётчик всегда пересчитываем из traffic_usage (recovery-джоб для сверки).
Лимиты
Section titled “Лимиты”Проверка — в транзакции приёма отчёта: если traffic_used_bytes >= traffic_limit_bytes, подписка переводится в suspended, ревизии её нод инкрементируются — доступ отзывается через обычный reconciliation.
Задержка отзыва ограничена интервалом отчётов (~60 с) плюс применение. Перерасход сверх лимита в этом окне возможен и принят как компромисс; окно на порядок меньше, чем при 5-минутном pull-опросе.
Сброс периода (traffic_reset = monthly): фоновый процесс по границе периода (якорь — дата активации подписки) обнуляет traffic_used_bytes и возвращает suspended → active, если причиной был лимит. Историческая статистика в traffic_usage не затрагивается.
Торрент-трафик
Section titled “Торрент-трафик”Sniffing Xray размечает BitTorrent-хендшейк после расшифровки, независимо от транспорта инбаунда. Sniffing включён во всех клиентских инбаундах всегда (только разметка, routeOnly); блокировкой управляет флаг ноды block_torrent (по умолчанию — включён):
true→ в правилах маршрутизации ноды первым идёт{protocol: bittorrent} → blackholeдля всех клиентских инбаундов;false→ правило не рендерится, торрент-трафик идёт как обычный.
Флаг per-node: юрисдикции нод различаются. Переключение — обычное изменение desired state.
Детекция — эвристика по хендшейку: обфусцированные клиенты могут проходить мимо. Это политика по умолчанию, а не гарантия. Автоматических банов по факту детекции нет: заблокированный трафик просто не проходит, инцидент виден в статистике ноды.