Skip to content

Трафик и лимиты

Агент раз в 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 обнуляет его счётчики — агент это детектирует (счётчик уменьшился) и берёт новое значение как дельту от нуля.

Таблица Гранулярность Retention
traffic_usage подписка × нода × час 30 дней
traffic_usage_daily подписка × нода × день 12 месяцев

Часовые записи апсертятся (bytes += delta) при приёме отчётов; фоновый процесс сворачивает их в суточные и удаляет истёкшие. Границы retention конфигурируемы.

subscriptions.traffic_used_bytes — материализованный счётчик расхода текущего периода, обновляется в той же транзакции, что и traffic_usage. Это единственное намеренно денормализованное значение в схеме: проверка лимита выполняется при каждом отчёте для каждой подписки, SUM() по time-series на этом пути неоправдан. Счётчик всегда пересчитываем из traffic_usage (recovery-джоб для сверки).

Проверка — в транзакции приёма отчёта: если traffic_used_bytes >= traffic_limit_bytes, подписка переводится в suspended, ревизии её нод инкрементируются — доступ отзывается через обычный reconciliation.

Задержка отзыва ограничена интервалом отчётов (~60 с) плюс применение. Перерасход сверх лимита в этом окне возможен и принят как компромисс; окно на порядок меньше, чем при 5-минутном pull-опросе.

Сброс периода (traffic_reset = monthly): фоновый процесс по границе периода (якорь — дата активации подписки) обнуляет traffic_used_bytes и возвращает suspended → active, если причиной был лимит. Историческая статистика в traffic_usage не затрагивается.

Sniffing Xray размечает BitTorrent-хендшейк после расшифровки, независимо от транспорта инбаунда. Sniffing включён во всех клиентских инбаундах всегда (только разметка, routeOnly); блокировкой управляет флаг ноды block_torrent (по умолчанию — включён):

  • true → в правилах маршрутизации ноды первым идёт {protocol: bittorrent} → blackhole для всех клиентских инбаундов;
  • false → правило не рендерится, торрент-трафик идёт как обычный.

Флаг per-node: юрисдикции нод различаются. Переключение — обычное изменение desired state.

Детекция — эвристика по хендшейку: обфусцированные клиенты могут проходить мимо. Это политика по умолчанию, а не гарантия. Автоматических банов по факту детекции нет: заблокированный трафик просто не проходит, инцидент виден в статистике ноды.