Часть 1

Всем привет! Я Артём Седых, работаю ведущим разработчиком команды банковского сопровождения. Проекту уже девятый год, накопилась большая функциональная база и много легаси‑кода с набором узких мест для возможной оптимизации. В наборе статей рассказываю о нашем опыте разработки и внедрения собственной системы мониторинга бэкенда PHP с возможностью просмотреть, как именно проседают экшены, и даже попытаться спрогнозировать поведение. Если не читали предыдущую часть — ссылка выше. Сегодня в продолжение предыдущей статьи рассмотрим оставшуюся архитектурную часть — о некоторых методах сбора метрик производительности и месте для хранения. Остальной же материал будет посвящен самому интересному — получившимся дашбордам Grafana. Каждый дашборд сопровождается конкретными примерами, какие проблемы удалось обнаружить и какие рекомендации по оптимизации получаем с каждого элемента. Не буду томить, впереди много картинок, поехали!

Отправка метрик

В прошлой части определились с конкретными метриками, разработали и внедрили механизм сбора. Теперь возникает вопрос: как можно без боли отправить данные в хранилище? Попробовали несколько подходов, у каждого свои плюсы и минусы, вылилось в несколько вариаций SDK. 

Различные варианты отправки собранных метрик в хранилище
Различные варианты отправки собранных метрик в хранилище

В самом базовом случае — отправлять метрики сразу напрямую из живого процесса в базу данных. Это не быстро, рискуем нарваться на недоступность БД, тратится время на подключение и открытие коннекта.

Самым быстрым решением будет файловая система — время записи в районе нескольких мс. Можно использовать запись в pipe‑файл и читать из него параллельно работающим демоном, который будет кешировать и отправлять логи в хранилище в своем темпе. Но и возможные места для сбоев расширяются: за демоном и трубой нужно следить. Также получаем состояние гонки при параллельном выполнении запросов, которое решается различными методами блокировки.

Вариант без демона — выбираем папку, в которой будем писать в рандомный свободный файл, не забывая о блокировках. Файл наполняется до нужного объема (например, 1 МБ), писателю туда запрещается писать, и такой файл обрабатывает воркер‑читатель, массово загружая строки с метриками в хранилище. Здесь встречаемся с проблемами файловой системы, может не хватить места, параллельные процессы повлекут состояние гонки и задержат отправку, файлы могут пропасть или повредиться.

Третий вариант — использовать сервис, специально предназначенный для сбора статистики, например Open Telemetry Collector. Поддерживается передача по протоколам gRPC либо HTTP с использованием формата protobuf. Перестаем волноваться насчет параллельных запросов, пусть волнуется коллектор. Легко сконфигурировать одним файлом: настроить входящие потоки, методы обработки, выходящие потоки. Жертвуем же тем, что библиотека коллектора требует свои форматы для передачи, нужно создать Trace / Log / Metric и наполнить атрибутами, инициализировать сетевые функции и направить сетевой запрос. В этом варианте добавляются возможные проблемы с сетью и время на передачу данных. Зато получаем довольно стабильную передачу метрик без суеты с файлами и параллельными запросами. Остановились на этом варианте.

Из альтернатив можно также рассмотреть отправку в рэббит, или кэшсервис типа redis, откуда воркер растащит по нужным местам. Это всё еще сетевой запрос, и дополнительно понадобится воркер. Как видим, способов много и точек оптимизации еще больше. Итого держим три разных варианта SDK, каждая команда может решить и выбрать, какой метод подходит именно им (а можно взять SDK в качестве примера и реализовать свой вариант).

Хранение

Метрики собрали, куда складывать?

Для задач мониторинга и анализа используют формулировку OLAP (Online Analytical Processing). OLAP — технология, позволяющая в режиме реального времени получить определенный срез данных для их последующего анализа. Такие БД и инструменты специально оптимизированы под задачи сбора, хранения и выборки метрик — в основном это временные ряды, их довольно выгодно хранить в столбцовом формате. Из столбцовых СУБД наиболее подходит ClickHouse (см отличительные преимущества).

Оптимальная массовая вставка данных, поддержка индексов, агрегатных функций и фильтров. Интеграция с open telemetry collector (ссылка), есть плагин для Grafana и — самый важный для нашей команды аргумент — поддержка языка SQL. Все разработчики бэкенда знакомы с его синтаксисом, можно строить наши графики и отчеты как обычные SQL‑запросы, поддерживаются CTE (Common Table Expressions), фильтры сортировка — и всё в привычном синтаксисе. Клик предлагает богатый набор агрегатных функций, что сильно помогло при разработке дашбордов.

Завели таблицу в ClickHouse с ENGINE = MergeTree и составным индексом по environment+start_datetime. Настроили интеграцию между SDK, сервисом otel collector и связали с ClickHouse. Метрики с живого проекта полетели в БД.

Интересно, сколько в итоге наши данные занимают места? Заглянем в графану, за последний месяц собрали такие показатели (в среднем 60 запросов в минуту).

Суммарный объем занимаемых данных в таблице логов ClickHouse за 1 месяц
Суммарный объем занимаемых данных в таблице логов ClickHouse за 1 месяц

Большая часть объема занимает метрика parameters, т. к. это JSON с лимитом в 1 024 символа. Если не логировать параметры, счет будет идти на десятки мегабайт в месяц, что впечатляет и увеличивает шансы на доступность использования в высоконагруженных сервисах. Также в ClickHouse можно задать Time to live как на строки, так и на отдельные колонки. В нашем случае очищаем значения parameters и data спустя 1 месяц с помощью TTL — исследование параметров актуально в момент анализа текущей оптимизации и не так важно в далекой перспективе.

Визуализация и алерты

Где проводить мониторинг и анализ? Grafana победила безальтернативно: имеет все нужные возможности, уже используется в компании, есть плагин для ClickHouse. Grafana предоставляет множество инструментов: легко разрабатывать графики и дашборды, поддерживаются time series, столбчатые диаграммы, диаграммы состояния и многое другое.

Заготовленные дашборды можно легко экспортировать в JSON, хранить в том же репозиториии импортировать в ручном либо автоматическом режиме, используя provisioning. Огромный плюс — поддерживаются алерты с гибкой настройкой триггеров и направлений для рассылки.

Примеры дашбордов Grafana

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

Каждый дашборд использует стандартный фильтр графаны – период выборки справа вверху. Слева вверху – основные фильтры (где это применимо): среда, тег, контроллер, экшен, активная роль.

Анализ экшенов

Выполняется без агрегации (кроме диаграммы ошибок) – видно показатели по каждому запросу.

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

Дашборд анализа экшенов
Дашборд анализа экшенов

Рассмотрим пример получения информации по заявке. Виден пик в восемь секунд, по памяти всё нормально, а вот на количестве SQL‑запросов уже видно, что именно заняло 8 секунд: навели курсор — получили результат — экшен выполнил 10 419 SQL‑запросов, что, очевидно, не самый эффективный способ доставать кучу данных из БД. Скорее всего, внутри используются запросы в цикле, которые можно оптимизировать, переделав на один запрос, и не тратить кучу времени на инфраструктурные задержки обслуживания каждого из 10 тысяч запросов.

В конце — квинтэссенция всех наших трудов по сбору дополнительных метрик: благодаря тому, что мы замеряем общее время на обращения к ресурсам в формате Duration, можно их содержимое отобразить в красивом графике, на котором видно, что конкретно заняло и сколько времени. Посмотрим поближе.

Мини-профайлер

График по секундам: сколько ушло на запросы к БД, сколько на внешние запросы и оставшееся время выполнения кода (пометим как cpu_time). При добавлении еще метрик типа Duration разбивка будет еще точнее.

Разбивка затраченного времени на обработку запроса клиента
Разбивка затраченного времени на обработку запроса клиента

По сути, у нас вышел мини-профайлер, очень пригодились колонки со счетчиками. Видим, что тот 8-секундный запрос как раз ждал результатов из БД 10 тысяч запросов, занял целых 6 секунд (помечено синим как sql_time) и 2 секунды на обработку данных в коде (зеленым как cpu_time), также для ресурса внешних систем (желтый external_time). Получаем наглядное отображение составляющих по времени для каждого запроса: сразу видим слабыеместа — куда целиться при оптимизации. В Grafana используется панель типа Time series с опцией Stack series = Normal.

Пример запроса для построения данного графика:

SELECT
   start_datetime AS time,
   controller_name || '.' || action_name,
   duration_sql_s AS sql_time,
   duration_external_request_s AS external_time,
   duration_s - duration_sql_s - duration_external_request_s AS cpu_time
FROM logs
WHERE $__timeFilter(start_datetime)
 AND environment = '${environment}'
 AND ('all' = '${tag}' OR action_group_tag = '${tag}')
 AND ('all' = '${controller}' OR controller_name = '${controller}')
 AND ('all' = '${action}' OR action_name = '${action}')
 AND ('-1' = toString(${active_role}) OR active_role = toString(${active_role}))
ORDER BY time;

Благодаря поддержке SQL в ClickHouse очень просто получается разрабатывать подобные графики, работаем как с обычными таблицами в том же Postgres. Есть несколько макросов, один из них $__timeFilter — туда подставляется фильтр по времени из графаны. Его и другие фильтры из шапки дашборда располагаем в секции WHERE. Фильтр по роли дает возможность смотреть, насколько сильно отличается быстродействие в системе для разных ролей пользователей.

Нагрузка на систему

В предыдущем дашборде сложно будет развернуться, если количество экшенов достигает 1 000. Сложно отследить каждый — повесим систему при выборке и отображении большого набора данных. Поэтому разработан дашборд с группировкой по тегам (группам экшенов) — мониторинг состояния системы, а также обнаружение критичных точек. Слева в виде графиков, агрегация sum/max/avg покажет динамику по тегам, справа — в основном экстремумы max в виде диаграммы состояния (если красное — значение отклоняется от заданного порога threshold). Для поиска экстремумов также могут подойти p95/p99.

Агрегированные данные по интервалам, длительность интервала можно настроить в параметрах дашборда.

Дашборд нагрузки на систему, данные после активного теста одного из стендов
Дашборд нагрузки на систему, данные после активного теста одного из стендов

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

Фактически получаем суперспособность видеть систему насквозь! На указанном примере уже накопилось с десяток задач под оптимизацию узких мест:

  • Время обработки (median) — 30 секунд на экспорт данных в Excel (его же видно на графике «Время выполнения БД avg» — сразу знаем какой‑то медленный SQL‑запрос попался в экспорте).

  • Количество SQL‑запросов (max) — под 90 тысяч при интеграции и 60 тысяч при сохранении. Запросы в цикле, требующие группировку.

  • ошибок — 25% файлов не удалось скачать или загрузить в 08:50 — проблемы с ФС?

  • Время выполнения БД (max) — неудовлетворительное время на запросы к БД в экшенах получения списка (70 секунд при наведении мышкой), а получение списка сущностей — одна из важнейших операций при работе с системой: нужно ускорять.

  • Количество исх. запросов (sum) — видно, в какие периоды проявляют активность интеграционные запросы. Стоит присмотреться: если будет аномальное поведение, регулировать доступ к внешним системам по API.

  • Время ожидания исх. запросов (max) — а вот и аномалии, если слева могла естественным образом накопиться сумма (например, много мелких запросов), то справа каждая красная секция показывает 160 секунд ожидания внешних систем! Нужно разобраться, а что такого тяжелого передают друг другу сервисы, и стоит рассмотреть переход на асинхронную обработку запросов.

  • Потребление памяти (avg) — в среднем 125 MB потреблял экшен типа export в 8:40, пока бить тревогу не стоит, но можно и оптимизировать.

  • Потребление памяти (max) — а вот и экстремумы, в лидерах export и integration показывают >600 MB, однозначно стоит заняться оптимизацией потребления памяти, как только обнаружим конкретный экшен по другим дашбордам, зная провисающий тег.

  • Потребление памяти (sum) покажет, если много экшенов выполняются одновременно и суммарно могут до отвала загрузить память в короткий промежуток времени. Пока в пределах нормы.

Таблицы ТОП по метрикам

Возможность мониторить состояние системы — это круто, однако не всегда получится держать руку на пульсе и следить, чтобы не выскочил конкретный показатель в красную зону. Можем пойти дальше — попросить БД с помощью SQL и сделать готовые таблички с конкретными экшенами, которые провисают по определенным критериям. Отсюда вышла идея создания дашборда ТОПов экшенов по метрикам — готовые таблицы экшенов для оптимизации и другая полезная информация. Узнать больше о влиянии конкретных экшенов на работоспособность системы в самых разных аспектах. В Grafana это всё строится на стандартной таблице с разными настройками по колонкам (единицы измерения, state gauge, thresholds).

Группировка за выбранный период и сортировка по максимальной метрике, чтобы показать TOP_count записей.

Есть колонка diff — разница с показателем данной метрики в данном экшене за предыдущий период, а также переменная diff_gap — на сколько интервалов назад смотрим, чтобы вычислить diff. Интервал — время между «от» и «до», настроенные справа. Пример — если дашборд настроен на последние сутки, желательно посмотреть разницу, как экшен вел себя те же сутки, но неделю назад (т. е. diff_gap=7).    

Составили множество ТОПов по самым разнообразным аспектам, разделили на секции.

Секция «Нагрузка»

ТОПы общей нагрузки по системе. Анализируется метрика длительности duration_s и количество запросов с количеством ошибок.

Дашборд ТОПов экшенов. Секция «Нагрузка»
Дашборд ТОПов экшенов. Секция «Нагрузка»

Это те же экстремумы с прошлого дашборда, но уже без группировки по тегам – видно каждый экшен в удобном списочном формате. Видно, какой экшен больше всего вызывали, что больше всего вернуло ошибки, каждый экстремум по длительности и медиану по длительности.

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

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

Секция «БД»

Дашборд ТОПов экшенов. Секция «БД»
Дашборд ТОПов экшенов. Секция «БД»

Аналогичным образом эта секция ТОПов посвящена базе данных. На каждую метрику типа Duration (количество и длительность операций) получится сделать такую секцию.

Выделяющиеся значения по количеству SQL‑запросов, скорее всего, требуют оптимизации, переход на BULK SELECT / INSERT / UPDATE, а экстремумы по длительности — оптимизации самих SQL‑запросов. Настройка показателей thresholds в графане позволяет раскрашивать Table Gauge в яркие цвета — когда стоит обратить внимание и подумать об оптимизации. Если не задать thresholds, графана раскрасит в подходящие цвета исходя из самих данных.

Внизу результаты ТОПов по мини‑профайлеру относительно суммарной длительности именно SQL‑запросов. Используется сумма — сколько заняли SQL‑запросы в целом за период по системе, и выбираются самые тяжелые экшены с дальнейшим раскрытием на составляющие. Отображается количество запросов и процентовка, на что потратили время, т. е. собрали всю статистику по мини‑профайлеру с первого дашборда и составили табличку с конкретными показателями самых тяжелых относительно времени базы данных, заодно со статистикой по другим составляющим и по количеству запросов. payment.list особенно необходимо оптимизировать: 3 часа в день крутились запросы БД, обрабатывающие этот экшен, также его было видно и в секции общей нагрузки.

Секция RAM

Дашборд ТОПов экшенов. Секция RAM
Дашборд ТОПов экшенов. Секция RAM

Секция, посвященная оценке потребления памяти. Слева max, справа медиана — наиболее влияющие на RAM экшены. Третьей идет экспериментальная таблица, попробовали подсчитать, сколько экшен занимает памяти в секунду, чтобы не мешал другим экшенам. Здесь, естественно, информация не точная, мы работаем с метрикой «макс. потребление памяти», а ведь память можно быстро занять и быстро освободить, а потом крутить еще минуту, но обычно экшен всё же занимает память и долгое время не отпускает. 250 МБ в секунду — однозначно стоит присмотреться к методам оптимизации использования RAM, особенно исходя из того, что он же на первом месте в четвертой таблице, — сколько экшен отобрал памяти за весь период суммарно.

Секция CPU

Дашборд ТОПов экшенов. Секция CPU
Дашборд ТОПов экшенов. Секция CPU

Покажет самые тяжелые экшены с точки зрения времени на оставшийся код помимо БД и HTTP‑запросов — обычно это и есть выполнение кода, неэффективные алгоритмы, лишние проверки, преобразования. Отслеживаем максимальное и медианное время, также наибольшее суммарное время CPU экшена с разбивкой по остальным %.

История разбора и оптимизации кейса с прода

Пришел алерт на высокое потребление CPU.

Скрин с дашборда мониторинга железа
Скрин с дашборда мониторинга железа

С помощью ТОПов легко узнать, что именно вызывает нагрузку, уже в общей н��грузке видно виновников — запрос на удаление файлов по интеграции и запрос на обновление статуса документов.

Воспроизведение ситуации на тестовом стенде
Воспроизведение ситуации на тестовом стенде

Они же и отобразились в нижней таблице «ТОП по времени CPU (sum)».

Отсюда можно понять, что наибольший вклад в нагрузку процессора вызвал аномальный рост вызова метода doc.update — 17 часов времени CPU в сутки! С 89% — это приговор для процессора.

В результате анализа выяснилось, что методы действительно оказались связаны, и по одному из типов документов вызывалось обновление во время каждого вызова removedoc, а сам update вызывал кучу другого кода для пересчета наборов данных, связанных с конкретным документом. Решили внедрить кеширование статуса документов на микросервисе, чтобы не было обновления каждый раз, а статус обновлялся массово.

Вывели на прод и пронаблюдали моментальное снижение количества вызовов doc.update — заметно на графике дашборда «Нагрузки на систему»: теги integration и other шли стык в стык, а с 17:40 запросы тега other существенно сократились.

Наглядное изображение работы хотфикса с 17:40
Наглядное изображение работы хотфикса с 17:40

Секция «Обращения к внешним системам»

Дашборд ТОПов экшенов. Секция «Обращения к внешним системам»
Дашборд ТОПов экшенов. Секция «Обращения к внешним системам»

Все таблицы аналогично секции БД. Видно, все медленные и часто запрашиваемые интеграционные запросы.

Большое количество запросов в одном экшене (savecontracts, uploaddoc), возможно, нуждается в объединении (если позволяет API) или разделении в рамках самих экшенов (по n вызовов на каждый набор), тем самым получим больше контроля и стабильное время интеграционной составляющей.

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

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

Конец второй части

Сегодня завершили технологическую часть и прошлись по основным разработанным дашбордам графаны. Это основной костяк, через который быстро и просто получается узнать об узких местах системы и поставить задачи на оптимизацию. Через неделю в третьей (заключительной) части рассмотрим применение со стороны devops на дашбордах SLI/Apdex, поколдуем над статистическими методами для прогноза снижения производительности, немного про автоматические уведомления, перспективы развития, и в заключении сравнительный анализ выбранного подхода и выводы в целом о нашем опыте.

Пока же в комментариях предлагаю поделиться впечатлениями по теме, очень приветствуются свои истории медленных и тяжелых запросов, расскажите, как решали проблемы производительности на проектах? Какие инструменты используете, какие мысли по поводу выбранного здесь подхода? Есть ли метрики, которые особенно хотелось бы отслеживать у себя? Какие дашборды могли бы ещё пригодиться? Большое спасибо за внимание! Жду вас в следующий четверг на заключительной части =)

Комментарии (0)