Перейти к содержанию

Аналитика туннельного трафика

Полное описание системы сбора, хранения и отображения статистики посещений туннельных сайтов (slug.dix.su).


Для пользователей

Доступность по тарифам

Тариф Аналитика Квота трафика
SIMPLE Нет Только расход / лимит
VIP Нет Только расход / лимит
PRO Да — полная детализация + отображение
PERS Да — полная детализация + отображение
GLAVA Да Без ограничений

Данные появляются с момента перехода на PRO/PERS — ретроспективы за предыдущие дни нет.

Что отображается

Суточная сводка (вкладка «Аналитика», выбор даты):

Метрика Описание
Трафик (байты) Входящий и исходящий объём за день
Запросы Общее число HTTP-запросов (включая ботов и статику)
Уникальные посетители Оценочное число уникальных людей (дедупликация по IP+UA)
Топ страниц Самые посещаемые пути вашего туннельного сайта
Источники трафика UTM-параметры + домены-рефереры
География Страны и города посетителей

Месячная сводка (вкладка «Использование»): суммарные байты и запросы за текущий месяц без разбивки по дням — доступна всем тарифам.

Уникальные посетители

Уникальность определяется по хэшу SHA-256(суточная_соль | IP | User-Agent). Соль ротируется раз в сутки и нигде не сохраняется — это гарантирует, что по данным в БД нельзя восстановить конкретный IP даже при наличии доступа к базе.

Это приблизительная метрика: несколько человек за общим NAT (корпоративная сеть, мобильный оператор) будут посчитаны как один посетитель. Такой же подход использует Plausible Analytics.

Фильтрация ботов

Категория запроса Трафик (байты/запросы) Посещения страниц Уникальные Гео
Реальные пользователи
Боты (Googlebot, Yandex, Facebook и др.)
Статические ресурсы (css/js/img/fonts)

Трафик от ботов включается в байты и запросы — они создают реальную нагрузку и влияют на месячную квоту.

UTM-метки и источники

Поддерживаемые параметры: utm_source, utm_medium, utm_campaign, utm_content, utm_term, ref.

Дополнительно автоматически фиксируется домен из заголовка Referer под именем referrer_domain — даёт органические переходы без UTM (из поиска, соцсетей, других сайтов).

Внутренние переходы (рефер с того же хоста туннеля) не считаются.

Хранение и экспорт

  • Данные хранятся 90 дней, затем удаляются автоматически (ежедневно в 00:10 UTC).
  • Экспорт в Excel: произвольный диапазон дат до 365 дней назад. Содержит все 4 метрики (трафик, пути, параметры, гео).

Что НЕ собирается

  • Содержимое запросов и ответов (тела HTTP)
  • Заголовки авторизации, куки, токены
  • В режиме E2E — абсолютно ничего (трафик зашифрован до устройства, сервер видит только зашифрованные байты)

Для технических специалистов

Архитектура: горячий путь через ETS

Прямые INSERT в PostgreSQL на каждый запрос были бы узким местом при тысячах запросов в секунду. Вместо этого:

HTTP-запрос на slug.dix.su
proxy_controller.ex
MetricsCollector.record_request/6   ← атомарный ETS-инкремент, наносекунды
    ↓ (каждые 60 сек)
flush_to_db/0
PostgreSQL UPSERT × 4 таблиц

MetricsCollector — это GenServer с четырьмя ETS-таблицами в RAM:

ETS-таблица Назначение
:tunnel_metrics Счётчики трафика/путей/гео/UTM за текущий флаш-интервал
:tunnel_unique_fingerprints Дедупликация посетителей — хранит хэши за сегодня и вчера
:tunnel_visitor_salt Суточные соли (хранятся только за сегодня и вчера)
:public_role_cache Кэш ролей пользователей (TTL 10 мин) — чтобы не идти в БД за ролью на каждый запрос

Флаш — best effort: если PostgreSQL недоступен, накопленное за 60 сек теряется. Прокси при этом не деградирует.

Схема PostgreSQL

Четыре таблицы, все с составным primary key и ON CONFLICT DO UPDATE:

-- Суммарный трафик за день
tunnel_traffic_daily (
    slug TEXT, date DATE,
    bytes_in BIGINT, bytes_out BIGINT,
    request_count BIGINT, unique_visitors BIGINT,
    PRIMARY KEY (slug, date)
)

-- Топ путей за день
tunnel_path_visits_daily (
    slug TEXT, date DATE, path TEXT,
    visit_count BIGINT,
    PRIMARY KEY (slug, date, path)
)

-- UTM-параметры и referrer_domain
tunnel_param_visits_daily (
    slug TEXT, date DATE, param_name TEXT, param_value TEXT,
    visit_count BIGINT,
    PRIMARY KEY (slug, date, param_name, param_value)
)

-- Гео-посещения
tunnel_geo_visits_daily (
    slug TEXT, date DATE, country TEXT, city TEXT,
    visit_count BIGINT,
    PRIMARY KEY (slug, date, country, city)
)

Миграции: V29__Tunnel_analytics.sql, V30__Tunnel_unique_visitors.sql, V31__Tunnel_geo_visits.sql.

Геолокация

Страна и город определяются из IP-адреса через оффлайн MaxMind GeoLite2 базу (DixuProxy.GeoIp). Никаких внешних API-запросов в горячем пути нет. Если страна не определена (localhost, VPN без записи, не загружена база) — строка геолокации не создаётся.

Уникальные посетители: реализация

# MetricsCollector.ex — дедупликация
defp track_unique_visitor(slug, date, ip, user_agent) do
  salt = daily_salt(date)
  fingerprint = :crypto.hash(:sha256, [salt, "|", ip, "|", user_agent])
  if :ets.insert_new(@uniq_table, {{slug, date, fingerprint}, true}) do
    bump({:unique, slug, date}, 1)
  end
end

Соль ротируется: при первом обращении за дату генерируется strong_rand_bytes(16), сохраняется в @salt_table. Финпринты и соли старше вчера удаляются каждые 30 флаш-циклов (~30 мин).

API-эндпоинты

Все эндпоинты — авторизованные (сессия), доступны только владельцу туннеля.

Метод Путь Минимальный тариф Описание
GET /api/analytics/my?date=YYYY-MM-DD&page=N&size=N PRO Суточная аналитика с пагинацией топ-путей
GET /api/analytics/monthly Все Суммарный трафик за текущий месяц
GET /api/billing/traffic Все Расход квоты (байты) с лимитом и датой сброса
GET /api/analytics/my/export?from=YYYY-MM-DD&to=YYYY-MM-DD PRO Скачать Excel (до 365 дней)

Ответ /api/analytics/my (PRO+):

{
  "bytesIn": 1234567,
  "bytesOut": 9876543,
  "requestCount": 4200,
  "uniqueVisitors": 37,
  "topPaths": {
    "rows": [{"path": "/", "visitCount": 120}, ...],
    "total": 42
  },
  "topParams": [
    {"paramName": "utm_source", "paramValue": "telegram", "visitCount": 55},
    {"paramName": "referrer_domain", "paramValue": "google.com", "visitCount": 18}
  ],
  "topGeo": [
    {"country": "Russia", "city": "Moscow", "visitCount": 30},
    ...
  ]
}

Администраторский доступ

Через AdminListsController:

Путь Описание
GET /admin/analytics/list?date=&page=&size= Постраничный список туннелей с метриками за день
GET /admin/analytics/top-paths?slug=&date= Топ путей конкретного туннеля
GET /admin/analytics/geo?slug=&date= Гео-статистика конкретного туннеля

Доступно только сессиям с ролью GLAVA (сессионная Spring Security).

Очистка устаревших данных

AnalyticsCleanupScheduler — Spring @Scheduled(cron = "0 10 0 * * *"):

DELETE FROM tunnel_traffic_daily     WHERE date < CURRENT_DATE - INTERVAL '90 days';
DELETE FROM tunnel_path_visits_daily WHERE date < CURRENT_DATE - INTERVAL '90 days';
DELETE FROM tunnel_param_visits_daily WHERE date < CURRENT_DATE - INTERVAL '90 days';
DELETE FROM tunnel_geo_visits_daily  WHERE date < CURRENT_DATE - INTERVAL '90 days';

E2E-туннели и аналитика

Для E2E-туннелей (e2e-<token>.dix.su) MetricsCollector.record_request не вызывается — прокси работает как слепой TCP-ретранслятор. Зато e2e_traffic_counter.ex фиксирует только байты (in/out) в отдельной таблице — для биллинга. Никаких путей, гео и UTM для E2E нет.

Лимиты трафика и предупреждения

MetricsCollector.check_public_limit/1 вызывается перед каждым запросом на туннель:

  • Суммирует ETS-счётчики за текущий месяц + кэшированные данные из БД (TTL 5 мин)
  • При ≥ 80% квоты отправляет email-предупреждение (не чаще 1 раза в день)
  • При ≥ 100% блокирует туннель до 1-го числа следующего месяца

Масштабирование (будущее)

При росте нагрузки план из SCALING.md:

  • Read replica для AnalyticsController — аналитические SELECT не идут на master
  • Партиционирование tunnel_path_visits_daily / tunnel_geo_visits_daily по date — дешёвый DROP PARTITION вместо DELETE
  • Event streaming (Kafka/Kinesis) — замена прямых UPSERT в dixu_proxy, буфер на случай пиков

Смотрите также