Сотни миллионов лишних запросов в неделю: что кодинг-агент нашёл в трейсах Sentry

—

от автора

Автор оригинала: Scott Cooper

Последние несколько недель я с помощью кодинг-агента искал и чинил проблемы с производительностью бэкенда Sentry. Отправной точкой служили трейсы, которые Sentry отправляет сам себе.

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

Как я направил агента на наши трейсы

Я подключил агента (в данном случае Claude с Opus 5.5) к Sentry MCP, указал ему нашу организацию и проект в Sentry и попросил найти проблемы на горячих путях, похожие на небольшой PR про производительность, который я подготовил сам:

поищи через sentry mcp ещё похожие быстрые победы вокруг проблем на горячих путях

По моему опыту, с последними несколькими релизами моделей уже не нужно так тщательно выстраивать промпт или контролировать контекст агента (например, подсовывать ему конкретные спаны). Начать можно с такого общего запроса:

Найди все критические пути в моём приложении. Через Sentry MCP найди их трейсы и способы их оптимизировать.

После моего первого промпта агент отсортировал спаны по тому, как часто они выполнялись и сколько длились. Для каждого кандидата он давал файл и строку, оценку недельного объёма и ссылку на пример трейса. Когда список редел, я направлял его в новое место. Промпты были короткими и в основном сводились к вопросам вроде этих:

  • «поищи проблемы с большой нагрузкой на базу»

  • «это изменение чем-нибудь рискованно?»

  • «ты проверил новый запрос через EXPLAIN, как мы делали раньше?»

  • «rpc тоже может быть интересен»

  • «кажется, тут есть чей-то PR, который уже может это чинить»

  • «давай не будем трогать эту часть»

Затем я выбирал проблему и просил агента разобраться глубже, прежде чем что-либо менять. Он читал код и историю git и рассказывал, что может сломаться. Если исправление выдерживало проверку, он открывал черновик PR.

Кодовая база Sentry большая, и есть области, которые я знаю недостаточно хорошо, чтобы уверенно их менять. Я уводил агента от таких мест, проверял, не идёт ли там уже работа, и продолжал искать что-нибудь поменьше. Результаты становятся намного лучше, если агент может проверять изменения на базе в режиме только для чтения, а не гадать.

Инструментация Django SDK как отправная точка

Значительная часть кода Sentry написана на Django, и автоматическая инструментация Sentry Django SDK дала нам хорошую основу:

  • В описании спанов запросов Django лежит сам SQL. Кроме того, в наши трейсы попадают обращения к кэшу, RPC между силосами, задачи и маршруты API. Группировка спанов по описанию позволила агенту превратить данные трейсов в ранжированный список дорогой работы.

  • Если у спана базы данных были атрибуты источника запроса, они давали агенту файл и строку, откуда запрос выполнялся.

  • Атрибут user_agent показал, что один нагруженный эндпоинт в основном дёргает sentry-cli set-commits --auto, постранично проходя по репозиториям организации при каждой сборке. У одной организации было около 16 тысяч репозиториев, и делала она это примерно каждые 40 секунд. Отсюда появились правка в CLI, которая избегает постраничного обхода, и индекс на сервере, удешевляющий запросы для уже установленных версий CLI.

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

Водопад трейса Sentry: вебхук дважды выполняет один и тот же запрос SELECT DISTINCT sentry_grouplink, за 137 мс и 140 мс

Водопад трейса Sentry: вебхук дважды выполняет один и тот же запрос SELECT DISTINCT sentry_grouplink, за 137 мс и 140 мс

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

Проверка изменений в запросах

При изменениях в SQL агент запускал EXPLAIN ANALYZE до и после на зеркале базы, доступном только для чтения, и сравнивал результаты. Нагляднее всего получилось с выборкой из GroupHistory (GroupHistory хранит каждую смену статуса у issue). Запрос искал самую новую строку сразу по нескольким статусам, поэтому Postgres читал и сортировал все подходящие строки:

-- доSELECT * FROM sentry_grouphistoryWHERE group_id = %s AND status IN (1, 2, 11, 12, 13)ORDER BY date_added DESC LIMIT 1-- после: по одной выборке на статус, максимум берём в Python(SELECT date_added FROM sentry_grouphistory WHERE group_id = %s AND status = 1 ORDER BY date_added DESC LIMIT 1)UNION ALL ...

Для самой загруженной группы на зеркале время упало с 935 мс до 0,4 мс при прогретом кэше.

Прим. пер.: в описании PR условия замера другие. Для группы со 130 тысячами строк истории старый запрос занимал примерно 935 мс на холодном кэше и примерно 274 мс на прогретом, новый занял 0,4 мс. Оценка «в тысячи раз» из начала статьи держится на холодном замере. Группы для проверки выбирали среди худших: у обычных запрос и раньше занимал около 2 мс, а теперь укладывается в миллисекунду.

Там же точнее названа причина. Индекс (group, status, date_added) не умеет отдавать строки сразу нескольких статусов от новых к старым. У групп, которые постоянно переходят между resolved и regressed, таких строк десятки тысяч. Запрос при этом остаётся одним.

Результаты

Примечание. Объёмы запросов и суточная экономия — это оценки по сэмплированным спанам и наблюдаемому трафику. Я сравнивал работу базы в расчёте на запрос, чтобы изменения трафика нельзя было ошибочно принять за рост производительности.

Прим. пер.: по статистике GitHub размер перечисленных правок заметно различается: от +1/−3 строки у PR 127494 до +54/−22 у PR 127496.

Как попробовать на своём приложении

Агенту нужен доступ к вашим трейсам. Дальше попросите его найти проблемы с производительностью в приложении. Можно начать с общего запроса и спускаться к конкретным областям:

  • «Найди транзакции, где один и тот же запрос с разными параметрами выполняется в одном трейсе больше 10 раз (N+1)».

  • «Найди SELECT * и широкие запросы на горячих путях, где используется всего несколько колонок».

  • «Найди медленные запросы с ORDER BY ... LIMIT, постраничным выводом через OFFSET или LIKE '%...', которые, скорее всего, не используют индекс».

Выберите проблему и попросите агента разобраться глубже, прежде чем что-либо менять. Пусть он прочитает код и историю git и оценит, что может сломаться. После этого пусть открывает PR.

ссылка на оригинал статьи https://habr.com/ru/articles/1103628/