Всем привет!


Я уже рассказывал в этом блоге об организации модульной системы мониторинга для микросервисной архитектуры и о переходе с Graphite+Whisper на Graphite+ClickHouse для хранения метрик в условиях высоких нагрузок. После чего мой коллега Сергей Носков писал о самом первом звене нашей системы мониторинга — разработанном нами Bioyino, распределённом масштабируемом агрегаторе метрик.


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



Но давайте начнем по порядку.


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



С того момента произошло следующее.


  • Количество серверов в кластере Graphite выросло с 3 до 6.
    (56 CPU 2.60GHz, 384GB RAM, 10 SSD SAS 745GB, Raid 6, 10GBit/s Net).


  • Мы заменили brubeck на bioyino — нашу собственную имплементацию StatsD на Rust, и даже целую статью про это написали. Правда, после выхода статьи мы подвезли в него поддержку тегов (Graphite) и Raft для выбора лидера.


  • Мы проработали возможность использовать bioyino в качестве StatsD-агента и разместили такие агенты рядом с инстансами монолита, а также там, где это было необходимо в k8s.


  • Мы наконец-то избавились от старой системы мониторинга Munin (формально она у нас ещё есть, но её данные уже нигде не используются).


  • Сбор данных из кластеров Kubernetes был организован через Prometheus/Federations, так как Heapster в новых версиях Kubernetes не поддержали.



Мониторинг


За два прошедших года количество принимаемых и обрабатываемых метрик выросло примерно в 9 раз.



Процент занимаемого места на серверах также неумолимо ползёт вверх, и мы предпринимаем различные шаги по его понижению. Это хорошо видно на графике.



Что именно мы делаем?


  • Начали применять комплексные методы сжатия на колонки с данными.
    ALTER TABLE graphite.data MODIFY COLUMN Time UInt32 CODEC(Delta, ZSTD)
  • Продолжаем схлопывать данные старше пяти недель с тридцатисекундных интервалов до пятиминутных. Для этого у нас есть задание cron, которое раз в месяц запускает (примерно) следующую процедуру:

10 10 10 * * clickhouse-client -q "select distinct partition from system.parts where active=1 and database='graphite' and table='data' and max_date between today()-55 AND today()-35;" | while read PART; do clickhouse-client -u systemXXX --password XXXXXXX -q "OPTIMIZE TABLE graphite.data PARTITION ('"$PART"') FINAL";done

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

Схема Distributed таблицы выглядит следующим образом.


CREATE TABLE graphite.data_all (
`Path` String, 
`Value` Float64, 
`Time` UInt32, 
`Date` Date, 
`Timestamp` UInt32
) 
ENGINE = Distributed (
'graphite_cluster',
'graphite',
'data', 
jumpConsistentHash(cityHash64(Path), 3)
) 

Мы также назначили пользователю "default" readonly-права и перекинули выполнение процедур записи в таблицы на отдельного пользователя systemXXX.


Конфигурация кластера Graphite в ClickHouse выглядит следующим образом.


    <remote_servers>
        <graphite_cluster>
            <shard>
                <internal_replication>true</internal_replication>
                <replica>
                    <host>graphite-clickhouse01</host>
                    <port>9000</port>
                    <user>systemXXX</user>
                    <password>XXXXXX</password>
                </replica>
                <replica>
                    <host>graphite-clickhouse04</host>
                    <port>9000</port>
                    <user>systemXXX</user>
                    <password>XXXXXX</password>
                </replica>
            </shard>
            <shard>
                <internal_replication>true</internal_replication>
                <replica>
                    <host>graphite-clickhouse02</host>
                    <port>9000</port>
                    <user>systemXXX</user>
                    <password>XXXXXX</password>
                </replica>
                <replica>
                    <host>graphite-clickhouse05</host>
                    <port>9000</port>
                    <user>systemXXX</user>
                    <password>XXXXXX</password>
                </replica>
            </shard>
            <shard>
                <internal_replication>true</internal_replication>
                <replica>
                    <host>graphite-clickhouse03</host>
                    <port>9000</port>
                    <user>systemXXX</user>
                    <password>XXXXXX</password>
                </replica>
                <replica>
                    <host>graphite-clickhouse06</host>
                    <port>9000</port>
                    <user>systemXXX</user>
                    <password>XXXXXX</password>
                </replica>
            </shard>
        </graphite_cluster>
    </remote_servers>

Помимо нагрузки на запись, выросло количество запросов на чтение данных из Graphite. Эти данные используются для:


  • обработки триггеров и формирования алертов;
  • отображения графиков на мониторах в офисе и экранах ноутбуков и ПК растущего числа сотрудников компании.


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


CREATE TABLE graphite.data_reverse (
`Path` String, 
`Value` Float64, 
`Time` UInt32 CODEC(Delta(4), ZSTD(1)),
 `Date` Date, 
`Timestamp` UInt32
) 
ENGINE = ReplicatedGraphiteMergeTree (
'/clickhouse/tables/{cluster}/data_reverse', 
'{replica}', 
'graphite_rollup'
) PARTITION BY Date 
ORDER BY (Path, Time) 
SETTINGS index_granularity = 4096

Чтобы направлять в неё данные, мы добавили в конфигурационный файл приложения carbon-clickhouse новую секцию.


[upload.graphite_reverse]
type = "points-reverse"
table = "graphite.data_reverse"
threads = 2
url = "http://systemXXX:XXXXXXX@localhost:8123/"
timeout = "60s"
cache-ttl = "6h0m0s"
zero-timestamp = true

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


1 12 * * * clickhouse-client -q "select distinct partition from system.parts where active=1 and database='graphite' and table='data_reverse' and max_date<today()-2;" | while read PART; do clickhouse-client -u systemXXX --password XXXXXXX -q "ALTER TABLE graphite.data_reverse DROP PARTITION ('"$PART"')";done

Чтобы читать из таблицы данные, в конфигурационном файле graphite-clickhouse добавили секцию:


[[data-table]]
table = "graphite.data_reverse"
max-age = "48h"
reverse = true

В результате мы имеем таблицу с 100% данных, реплицированных на все шесть серверов, которые обрабатывают всю читающую нагрузку от запросов с окном менее двух суток (а таких у нас 95%). А также мы имеем шардированную таблицу с 1/3 данных на каждом шарде, обеспечивающую чтение всех исторических данных. И пусть таких запросов кратно меньше, нагрузка от них значительно выше.


Что же происходит с CPU?! В результате роста объемов записываемых и читаемых данных в кластере Graphite, выросла и суммарная CPU-нагрузка на серверы. Выглядит она примерно так.



Хочется обратить внимание на следующий нюанс: половина CPU уходит на парсинг и первичную обработку метрик в carbon-c-relay (v3.2 от 2018-09-05, отвечает за транспорт метрик), который размещен на трёх серверах из шести. Как видно по графику, именно эти три сервера и находятся в ТОПе.


Алертинг


В качестве системы алертинга у нас по-прежнему Moira и написанный под неё moira-client. Для гибкого управления триггерами, нотификациями и эскалациями, мы используем декларативное описание, которое назвали alert.yaml. Оно генерируется автоматически при создании сервиса через PaaS (подробнее об этом можно почитать в статье Вадима Мадисона «Что мы знаем о микросервисах») и размещается в его репозитории. Для работы с alert.yaml мы сделали обвязку над moira-client и назвали её alert-autoconf (планируем заопенсорсить). В сборке сервиса в TeamCity есть шаг с экспортом триггеров и нотификаций в Moira через alert-autoconf. При коммите изменений в alert.yaml запускаются автотесты, которые проверяют валидность yaml-файла, а также делают запросы в Graphite по каждому шаблону метрик с целью проверить их корректность.


Для инфраструктурных команд, не использующих PaaS, мы организовали отдельный репозиторий под названием Alerting. В нем сделали структуру вида: Команда/Проект/alert.yaml. К каждому alert.yaml мы генерируем отдельную сборку в TeamCity, которая прогоняет тесты и пушит содержимое alert.yaml в Moira.


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


Так как раньше у нас уже были триггеры, заведённые через GUI, мы реализовали возможность выгрузить их в формате yaml. Содержимое полученного yaml-документа можно вставить в alert.yaml практически без дополнительных преобразований, после чего запушить изменения в мастер. В ходе сборки alert-autoconf поймет, что такой триггер уже существует и зарегистрирует его в нашем реестре в Redis.


А ещё не так давно мы обзавелись дежурной сменой инженеров 24х7. Для того, чтобы передать триггеры им на обслуживание, достаточно в своем alert.yaml корректно заполнить описание «что делать, если ты это видишь», поставить тег [24x7] и запушить изменения в мастер. После раскатки alert.yaml'а все описанные в нем триггеры автоматически попадут под круглосуточное наблюдение смены 24х7. У — Упрощай! Красота!


Сбор бизнес-метрик


С момента выхода прошлой статьи про сбор и обработку бизнес-метрик наш bioyino стал ещё лучше.


  1. Вместо выбора лидера через Consul используется встроенный Raft.
  2. Корректно обрабатываются теги в формате Graphite.
  3. Появилась возможность использовать bioyino (StatsD-server) в качестве агента.
  4. Для подсчета уникальных значений поддерживается формат "set".
  5. Финальную агрегацию метрик можно делать в несколько потоков.
  6. Данные можно отправлять в Graphite чанками в несколько параллельных соединений.
  7. Исправлены все найденные баги.

Сейчас это работает так.


Мы стали активно внедрять StatsD-агенты рядом со всеми крупными крупными генераторами метрик: в контейнерах с инстансами монолита, в подах k8s рядом с сервисами, на хостах с инфраструктурными компонентами и т. д.
Statsd-агент размещается рядом с приложением. Он принимает метрики от этого приложения всё так же по UDP, но уже не используя сетевую подсистему (за счёт оптимизаций в ядре Linux). Все события предагрегируются, и собранные данные ежесекундно (интервал можно настроить) отправляются в основной кластер серверов StatsD (bioyino0[1-3]) в формате Cap’n Proto.


Дальнейшая обработка и агрегация метрик, выбор лидера в StatsD-кластере и отправка лидером метрик в Graphite практически не изменились. Про это вы можете подробно прочитать в нашей прошлой статье.


Что же касается цифр, то они следующие.


График полученных StatsD-событий


График метрик, отправленных из StatsD в Graphite


Итого


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



Суммарное количество значений метрик: 2 189 484 898 474.
Общая глубина хранения метрик: 3 года.
Количество уникальных имен метрик: 6 585 413 171.
Количество триггеров: 1053, они обслуживают от 1 до 15k метрик.


Планы на ближайшее будущее:


  • начать переводить продуктовые сервисы на тегированную схему хранения метрик;
  • добавить в кластер Graphite еще три сервера;
  • подружить Moira с персистентной тканью;
  • найти ещё одного разработчика в команду мониторинга.

Буду рад комментариям и вопросам здесь — пишите. А ещё я буду выступать 7 ноября на Highload++, если будете там, можем пообщаться.