Быстрый код может отвечать три секунды

public

Автор: Александр Луценко · HighLoad Channel


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

Александр Луценко рассказывает, как команда перенесла профилирование с dev-окружения в production и превратила его в постоянный observability-сигнал. Мотивация проста: stage отличается от production железом, данными и формой нагрузки. Даже хороший нагрузочный тест не воспроизводит все сочетания условий, которые возникают в реальной системе.

Для сбора команда выбрала sampling profiler в отдельном sidecar-контейнере. Он не требовал менять код приложения или устанавливать расширение внутрь процесса. Профили отправлялись в коллектор, фильтровались, агрегировались и дополнялись метками сервиса, окружения и транзакции. Так отдельный flame graph превращался в историю поведения приложения между релизами.

Но постоянный сбор не делает профилирование полной моделью запроса. В одном из примеров endpoint внутри приложения выполнялся за миллисекунды, тогда как внешние измерения показывали ответ до трёх секунд. Причиной оказался недостаток воркеров: запросы долго ждали очереди перед тем, как попасть в быстрый обработчик.

Кому смотреть: backend- и platform-командам, которые хотят профилировать production или уже собирают профили, но рассматривают их отдельно от метрик и distributed tracing.

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


Обычное профилирование часто запускают после появления проблемы: воспроизводят нагрузку на dev или stage, снимают профиль и ищут дорогую функцию. Луценко предлагает оставить тот же инструмент постоянно работающим в production. Тогда у команды появляется не реконструкция инцидента в похожей среде, а профиль приложения на реальном железе, с настоящими данными и фактической нагрузкой.

Выбранная архитектура отражает ограничения конкретной команды. Она не хотела зависеть от расширения для PHP, которое может задержать обновление языка, помещать неизвестный бинарный агент внутрь приложения или запускать привилегированный контейнер ради eBPF. Вместо этого использовался sampling profiler PHP Spy в sidecar-контейнере. Это частное решение, а не универсальный рецепт: в другой инфраструктуре соотношение безопасности, совместимости и стоимости может привести к другому выбору.

Профили отправлялись в центральный коллектор по push-модели. Такой подход избавлял коллектор от поиска динамически появляющихся экземпляров приложений. Недоступность коллектора означала потерю нескольких минут данных, и команда приняла это сознательно: профили собирались непрерывно, а краткий разрыв считался менее опасным, чем усложнение всей системы доставки.

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

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

Именно последняя граница проявилась в примере с медленным endpoint. В профиле обработчик был быстрым, но внешняя метрика показывала задержку до трёх секунд. Запросы ожидали свободных воркеров ещё до начала исполнения прикладного кода. Оптимизация функции ничего бы не исправила: проблема находилась в управлении конкурентностью и очередью.

Поэтому continuous profiling не становится заменой метрикам, логам и tracing. Он добавляет ещё один вопрос: что именно делал процесс, пока система обслуживала нагрузку? В 2026 году этот сигнал заметно лучше поддерживается готовыми продуктами и eBPF-инструментами, хотя стандартизация продолжается — протокол OpenTelemetry Profiles пока имеет статус development. Главный вывод доклада от этого только усиливается: профиль полезен как часть общей картины, а не как самодостаточное объяснение производительности.