Перейти к основному содержимому

Компоненты

Основные компоненты StatsHouse изображены на рисунке:

А вот их описания:

Агент

Агент

  • валидирует метрику (например, проверяет, существует ли она),
  • агрегирует данные в пределах секунды,
  • шардирует данные
  • и отправляет их на агрегаторы.

Если агрегаторы недоступны, агент хранит данные на локальном диске в пределах квоты и отправляет их позже.

Агент получает данные по протоколу UDP. Поддерживаются следующие форматы:

note

Агенты поддерживают IPv6.

Получение данных по UDP

StatsHouse получает данные по UDP в форматах MessagePack, Protocol Buffers, JSON и TL — они семантически идентичны. Формат определяется автоматически по первым байтам в пакете.

Посмотрите схемы для TL, MessagePack и Protocol Buffers:

---types---

statshouse.metric#3325d884 fields_mask:#
name: string
tags: (dictionary string)
counter: fields_mask.0?double
ts: fields_mask.4?# // UNIX timestamp UTC
value: fields_mask.1?(vector double)
unique: fields_mask.2?(vector long)

= statshouse.Metric;

---functions---

// for smooth JSON interoperability, first byte of tag must not be 0x5b or 0x7b ("[" or "{")
// for smooth MessagePack interoperability, first byte of tag must be less than 0x80
// for smooth ProtoBuf interoperability, first byte must be as large as possible.

@write statshouse.addMetricsBatch#56580239 fields_mask:# metrics:(vector statshouse.metric) = True;

Пакет — это объект, содержащий массив метрик:

    {"metrics":[ ... ]}

Каждый элемент этого массива представляет собой объект с полями:

{
"name":"rpc_call_latency", // metric name (obligatory)
"tags":{"protocol": "tcp"}, // tags
"ts": 1630000000, // timestamp; 0 and no timestamp means "now"
"counter": 6, // event counter
"value": [1, 2.0, -3.0], // values if any (do not use with "unique")
"unique": [15, 18, -60] // unique counters if any (do not use with "value")
}

Например, можно отправить такой пакет:

{"metrics":[
{"name":"rpc_call_latency",
"tags":{"protocol": "tcp"},
"value": [15, 18, 60]},
{"name": "rpc_call_errors",
"tags":{"protocol": "udp","error_code": "-3000"},
"counter": 5}
]}
{"metrics":[
{"name": "external_landings",
"tags":{"country": "ru","gender": "m","skey": "lenta.ru"},
"counter": 1}
]}

Обратите внимание на требования к использованию форматов.

  • Для TL: тело пакета должно быть Boxed-сериализацией объекта statshouse.addMetricsBatch.
  • Для JSON: первым символом должна быть фигурная скобка { (для корректного определения формата).
  • Для Protocol Buffers: не добавляйте поля в объект MetricBatch (для корректного определения формата).

Установка агентов в подах Kubernetes

Не устанавливайте агенты в подах Kubernetes. Мы настоятельно рекомендуем устанавливать их только на реальных серверах (обязательно указывайте порт 13337).

Число агентов не должно колебаться. Агенты отправляют агрегаторам посекундные отчёты. Если число агентов постоянно, значит, они подключены к агрегаторам. Из-за остановки подов число агентов уменьшается, и срабатывает главный алерт StatsHouse.

Подробнее

При шифровании в VK RPC для вывода эфемерных ключей используются удалённый и локальный IP-адреса соединений (как их видят клиент и сервер). Маршрутизация пакетов от одного адаптера к другому через брандмауэр делает установление соединений невозможным. Для соединения компонентов в таком случае понадобится создать виртуальные сетевые адаптеры и соединить их средствами Linux network namespaces.

Агрегатор

Агрегирует посекундные данные от всех агентов и вставляет результат в базу данных ClickHouse.

Число агрегаторов должно быть равно числу шардов ClickHouse с репликами. Каждый агрегатор вставляет данные в реплику базы данных, развёрнутую на той же машине. Например: 3 шарда × 3 реплики = 9 агрегаторов.

Актуальные и "исторические" данные

У агрегатора есть два режима работы:

  • работа с актуальными данными,
  • работа с "историческими" данными. Данные считаются "историческими", если их не удалось отправить сразу после создания.

Вставка актуальных данных — приоритет для агрегатора.

Представьте: возник сбой. Долгое время вставить данные было невозможно, затем система восстановилась. StatsHouse немедленно начинает вставлять актуальные данные. А вот "исторические" данные StatsHouse вставит при первой возможности, если только это не помешает вставке актуальных данных.

Подробнее

Вставка актуальных данных имеет приоритет, поскольку "исторические" данные вставляются в базу ClickHouse довольно медленно.

Актуальные данные

Агрегатор позволяет агентам вставлять данные за последние 5 минут — это "короткое" окно вставки (его можно настроить). Если агент не успел вставить данные вовремя, он отправит данные как "исторические".

Для каждой актуальной секунды агрегатор хранит контейнер со статистикой. В этот контейнер агрегируются данные от агентов. Как только наступает следующая секунда, агрегатор вставляет данные для неё (из "короткого" окна) в базу. А агенты получают ответ с результатом вставки.

"Короткое" окно распространяется на две секунды в будущее, чтобы нормально работали агенты, у которых часы немного спешат.

"Исторические" данные

Агрегатор позволяет агентам вставлять данные за последние 48 часов — это "длинное" окно вставки (его можно настроить).

Если данные старше 48 часов, StatsHouse записывает метастатистику и выбрасывает этот фрагмент данных. Агент получает ответ OK.

Агрегация между хостами очень важна, поэтому StatsHouse делает всё возможное, чтобы она состоялась:

  • каждый агент делает запрос на вставку нескольких десятков "исторических" секунд, начиная с самой "старой";
  • агрегатор получает эти запросы и выбирает самую "старую" секунду;
  • он агрегирует данные, вставляет их в базу данных и отправляет ответ;
  • затем снова выбирает "самую старую" секунду и т.д.
  • Этот алгоритм помогает наиболее удалённым ("старым") секундам оказаться рядом с самыми "новыми". Он делает возможным агрегирование исторических данных и помогает вставлять данные одновременно.

    Работа при отказе агрегатора

    Если агрегатор недоступен или отвечает с ошибкой, агент хранит данные на локальном диске. Хранение данных на диске ограничено в байтах. Оно также ограничено по времени — в пределах "длинного" (48-часового) окна вставки.

    Подробнее

    Распределение данных между репликами

    Если доступ к диску нежелателен или невозможен, можно запустить агент с пустым аргументом --cache-dir. StatsHouse не будет использовать диск. "Исторические" данные будут храниться в памяти, пока агрегаторы недоступны, т. е. в течение нескольких минут.

    Если агрегатор недоступен, агенты отправляют данные репликам. Данные распределяются в соответствии с порядковыми номерами секунд: чётные секунды отправляются в одну из реплик, нечётные — в другую. Таким образом, нагрузка на обе реплики увеличивается на 50 %. Это одна из причин, по которой StatsHouse записывает данные ровно в три реплики ClickHouse.

    Предотвращение двойной вставки

    Если агрегатор отвечает с ошибкой, агент отправляет данные другому агрегатору (другой реплике) на другом хосте. Для дедупликации нужно использовать алгоритм консенсуса, а это довольно сложно.

    В StatsHouse основной и резервный агрегаторы могут вставлять данные от одного и того же агента в одну и ту же секунду. Это происходит редко, и для такого случая мы отслеживаем двойные вставки с помощью метаметрики __heartbeat_version, которая показывает количество агентов, отправляющих данные в текущую секунду. Чтобы эта метаметрика была стабильной при нормальной работе агрегаторов, агенты отправляют данные каждую секунду, даже если в данный момент нет реальных пользовательских данных.

    База данных

    В базе данных ClickHouse хранятся агрегированные данные метрик.

    StatsHouse вставляет данные в таблицу ClickHouse, которая определяется следущим образом:

    CREATE TABLE statshouse2_value_1s (
    `time` DateTime,
    `metric` Int32,
    `tag0` Int32,
    `tag1` Int32,
    ...
    `tag15` Int32,
    `stag` String,
    `count` SimpleAggregateFunction(sum, Float64),
    `min` SimpleAggregateFunction(min, Float64),
    `max` SimpleAggregateFunction(max, Float64),
    `sum` SimpleAggregateFunction(sum, Float64),
    `max_host` AggregateFunction(argMax, Int32, Float32),
    `percentiles` AggregateFunction(quantilesTDigest(0.5), Float32),
    `uniq_state` AggregateFunction(uniq, Int64)
    ) ENGINE = *MergeTree
    PARTITION BY toDate(time) ORDER BY (metric, time,tag0,tag1, ...,tag15, stag);

    Если для метрики не включена запись перцентилей или метрика не имеет тип unique counter, соответствующие столбцы таблицы (percentiles или uniq_state) будут пустыми.

    Если метрика представляет собой простой счётчик, все столбцы будут пустыми, кроме count. Колонка stag не пуста, только если в метрике используется тег String top.

    Чтобы получить данные за интервал, превышающий секунду, StatsHouse агрегирует данные и создает поминутные и часовые агрегаты.

    Подробнее

    Данные распределяются между шардами ClickHouse с помощью хэша metric, key0, ... , key15. Если в метрике используется несколько тегов, то данные, относящиеся к конкретному тегу (например, "protocol": "tcp"), обычно хранятся на разных шардах. Чтобы получить полную статистику, всегда нужно делать распределённые запросы ко всему набору шардов.

    Почему это так? Набор значений тегов имеет определенную кардинальность: существует конечное число возможных комбинаций значений тегов для метрики. Если мы достигаем предела кардинальности, то есть отправляем все эти комбинации значений тегов, объём данных перестает увеличиваться из-за агрегации — StatsHouse объединяет события с одним и тем же сочетанием значений тегов.

    Чтобы хранить выборку данных для всей метрики, каждый шард должен хранить столько рядов, сколько существует комбинаций значений тегов для метрики, а не долю, пропорциональную числу шардов.

    StatsHouse не использует буферные таблицы: каждый агрегатор вставляет данные раз в секунду в incoming-таблицу. Данные фильтруются по time в пределах окна приёма (48 часов) и копируются через материализованное представление. Это защищает StatsHouse от вставки "мусорных" данных. В противном случае ClickHouse должен был бы читать данные не из одного или двух шардов, а из всех.

    Шард должен иметь три или более реплик. Агрегаторы вставляют данные в первые три реплики. Остальные являются read-only репликами — их можно использовать, чтобы масштабировать нагрузку на чтение.

    Количество шардов может быть любым. Для предотвращения неправильной конфигурации и непоследовательного шардирования, которое может привести к резкому увеличению объёма данных из-за слабой агрегации, агенты отправляют агрегатору номер реплики шарда. Если агрегатор "видит", что данные предназначаются не ему, то отвечает ошибкой. Этот же номер позволяет прокси направить данные нужному агрегатору.

    API

    Ознакомьтесь со спецификацией OpenAPI для StatsHouse.

    Тонкий API-клиент позволяет StatsHouse отправлять эффективные запросы к базе данных. Сервис кэширует данные, чтобы минимизировать нагрузку на базу данных. Мы ограничиваем получение данных непосредственно из ClickHouse, поскольку неэффективные запросы могут негативно повлиять на кластер ClickHouse.

    Пользовательский интерфейс (UI)

    Пользовательский интерфейс получает данные из StatsHouse API и отображает данные метрик в виде графика.

    Прокси

    Прокси получает данные от агентов, которые находятся за пределами защищённого периметра (т.е. за пределами датацентра) и отправляет их в агрегаторы.

    Агенты и агрегаторы используют протокол TL/RPC с ключом шифрования датацентра. Таким образом, агенты, находящиеся за пределами датацентра, не могут подключаться к агрегаторам напрямую, поскольку это требовало бы раскрытия или копирования ключа для внешних систем.

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

    Проекси не имеет состояния. Чтобы снизить вероятность атаки, он проксирует только подмножество TL/RPC типов запросов, используемых агрегаторами.

    Требуется ровно три прокси. Каждый из них является прокси для соответствующей реплики шарда. Недоступный прокси эквивалентен выходу из строя реплики одного шарда и не влияет на работу StatsHouse.

    Три экземпляра прокси имитируют агрегаторы. Можно установить ещё один уровень проксирования за имеющимися прокси. Этот уровень будет использовать предыдущие прокси в качестве агрегаторов.

    Не рекомендуется устанавливать прокси в подах Kubernetes.

    Подробнее

    Криптоключи

    StatsHouse использует протокол VK RPC с (опциональным) шифрованием для общения компонентов.

    Согласно протоколу VK RPC криптоключ является одновременно логином для получения доступа и секретом для получения эфемерных ключей соединения. Чтобы установить соединение, клиент должен использовать один из ключей, известных серверу. Центральным компонентом системы являются агрегаторы. При запуске они получают единственный "главный" криптоключ датацентра.

    Для подключения к агрегаторам агенты должны получить следующие параметры:

  • -agg-addr — адреса первого шарда агрегаторов;
  • -aes-pwd-file — "главный" криптоключ датацентра.
  • Описанный механизм безопасен только внутри защищённого периметра. Для подключения извне используйте прокси, установленный на границе.

    Прокси, стоящий на границе, состоит из двух частей:

  • RPC-сервера для подключения агентов извне,
  • RPC-клиента для подключения самого прокси к агрегаторам внутри периметра.
  • Для прокси необходимо настроить следующие параметры:

  • -ingress-external-addr — внешние адреса прокси-серверов, которые агенты используют для подключения;
  • -ingress-addr — параметр для управления интерфейсами, на которые подключаются агенты;
  • -aes-pwd-file — внутренний криптоключ для отправки данных агрегаторам;
  • -ingress-pwd-dir — набор внешних ключей для агентов с удаленных площадок.
  • Параметр -ingress-addr обычно имеет значение :8128, что равнозначно 0.0.0.0:8128. Он также может содержать адрес подсети сетевого адаптера, чтобы разрешить подключение только через него. Порт в параметре -ingress-addr должен совпадать с одним из портов в параметре -ingress-external-addr. "Внешняя" часть входящего прокси должна быть доступна агентам через эти порты.

    Каждый из этих файлов содержит криптоключ; имя файла игнорируется и считается комментарием. Ключи имеют произвольную длину — не менее четырех байт. Первые четыре байта служат для идентификации ключа, поэтому они не должны быть одинаковыми.

    Если внешние ключи в папке изменились, перезапустите прокси. Прокси не следит за этой папкой, так как набор ключей меняются редко.

    Каждый агент получает один из ключей, указанных у прокси в папке -ingress-pwd-dir, в качестве параметра -aes-pwd-file.

    Сервис метаданных

    Сервис метаданных хранит глобальный маппинг stringint32: именно здесь имена метрик и значения тегов, которые являются строками, отображаются в целые числа.

    StatsHouse предоставляет данные в режиме реального времени. Чтобы гарантировать минимальную задержку, StatsHouse отображает строковые значения тегов (а также названия метрик) в int32:

        'iphone' <=> 12
    'null' <=> 26

    Этот огромный маппинг общий для всех метрик. Элементы маппинга никогда не удаляются.

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

    Бюджет на создание значений тегов

    Чтобы предотвратить неконтролируемый рост маппинга stringint32, мы ограничиваем бюджет на создание значений тегов до 300 в день. Если бюджет исчерпан, новые строки (значения тегов) можно добавлять в маппинг дважды в час (правило настраивается).

    При превышении бюджета возникают ошибки типа "mapping flood". Когда бюджет исчерпан и добавлять новые строки в маппинг нельзя, StatsHouse вставляет значение mapping flood в колонку тега, чтобы не потерять событие целиком.

    Чтобы создавать теги с большим количеством разных значений, но избежать ошибок mapping flood, можно использовать тег String top (Топ строк) и Raw теги (теги с "сырыми" значениями).

    Если вам нужны теги с большим количеством значений, которые являются 32-битными числами, (например, тег user_ID), используйте Raw теги, чтобы избежать ошибок mapping flood.

    Если вам нужен тег с разнообразными строковыми значениями (например, тег search_request), используйте тег String top (Топ строк).

    Тег String top (Топ строк)

    Тег String top отличается от других тем, что его значения не добавляются в маппинг stringint32. Это отдельный столбец tag_s в таблице ClickHouse:

    timestampmetrictag_1tag_2tag_scountersumminmax
    13:45:05toy_packets_sizeJSON
    mapped to int32
    ok
    mapped to int32
    my-tag-value
    NOT mapped to int32
    1001300201200

    Поскольку строки, не добавленные в маппинг, занимают много места и дольше читаются, StatsHouse ограничивает их количество (например, до сотни). Это ограничение настраивать нельзя. Что же происходит с остальными строками, не поместившимися в эту сотню?

    important

    StatsHouse сохраняет только строки с наиболее часто используемыми значениями тега String top — строки с наибольшим счётчиком. Остальные значения тега String top превращаются в empty и агрегируются.

    Как это работает? Посмотрим на примере. Представим, что ограничение на тег String top равно не сотне, а четырём. А данные для нашей метрики выглядят так:

    timestampmetrictag_1tag_2tag_scountersumminmax
    13:45:05toy_metric......a100.........
    13:45:05toy_mextric......b3.........
    13:45:05toy_metric......c100.........
    13:45:05toy_metric......d88.........

    Приходят новые данные: строка со значением тега String top, равным e, и счётчиком, равным 55. Механизм String top выбирает значение тега с наименьшим счётчиком (b — менее популярный) и превращает его в empty:

    timestampmetrictag_1tag_2tag_scountersumminmax
    13:45:05toy_metric......a100.........
    13:45:05toy_metric......bempty string3.........
    13:45:05toy_metric......c100.........
    13:45:05toy_metric......d88.........
    13:45:05toy_metric......e55.........

    Затем добавляется строка со значением f в теге и счётчиком, равным 2.

    timestampmetrictag_1tag_2tag_scountersumminmax
    13:45:05toy_metric......a100.........
    13:45:05toy_metric......empty string3.........
    13:45:05toy_metric......c100.........
    13:45:05toy_metric......d88.........
    13:45:05toy_metric......e55.........
    13:45:05toy_metric......fempty string2.........

    Поскольку значение f используется редко (значение счётчика меньше, чем у других), оно тоже превращается в empty и агрегируется с предыдущей пустой строкой:

    timestampmetrictag_1tag_2tag_scountersumminmax
    13:45:05toy_metric......a100.........
    13:45:05toy_metric......empty string3+2.........
    13:45:05toy_metric......c100.........
    13:45:05toy_metric......d88.........
    13:45:05toy_metric......e55.........

    "Сырые" (Raw) теги

    Если значения тегов изначально являются 32-битными числами, вы можете пометить их как "сырые" (Raw), чтобы избежать переполнения маппинга. Такие значения тегов (Raw) StatsHouse будет воспринимать как (u)int32 (возможные значения: -2^31..2^32-1) и вставлять в базу ClickHouse, не добавляя в маппинг. Узнайте, как настроить "сырые" (Raw) теги.

    Бюджет на создание метрик

    Пользователи могут создавать сколько угодно метрик — вручную через пользовательский интерфейс StatsHouse. Как правило, автоматизировать создание метрик нельзя.

    StatsHouse предполагает, что метрик не так много: сотни тысяч. Система не защищёна от неконтролируемого роста числа метрик.

    tip

    Если вы переходите на StatsHouse с другой системы мониторинга, обратитесь к администраторам StatsHouse в вашей организации, чтобы включить режим автосоздания (по завершении миграции его нужно отключить).

    Подробнее

    Получение свойств метрики из сервиса метаданных

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

    При первом запуске агенты используют специальный bootstrap-запрос, чтобы получить 100 000 наиболее часто используемых строк из маппинга. В противном случае при развертывании большого количества агентов StatsHouse должен был бы загрузить огромное чилос значений одно за другим. Это заняло бы много времени, в течение которого StatsHouse не мог бы записывать данные.

    Агрегаторы используют TL/RPC long polling для получения информации о метриках из сервиса метаданных. Агенты используют long polling для получения информации от агрегаторов. Таким образом, все агенты получают информацию об изменениях в свойствах метрик практически мгновенно (за секунду).

    Удаление метрик

    Удалить метрику нельзя, потому что в базе данных ClickHouse нет эффективного способа сделать это. StatsHouse использует флаг visible для отключения метрики, т.е. для скрытия метрики из списка метрик (это действие обратимо). Отключение метрики прекращает запись данных для нее в базу данных.