Медленные запросы к БД и N+1
Медленные запросы к базе данных в Наблюдателе видны через трассы OpenTelemetry. Каждый запрос к БД — отдельный спан с текстом SQL, длительностью и ошибкой внутри HTTP-запроса или задачи, которые его вызвали. Отдельный агент для базы ставить не нужно: достаточно трассировки в приложении и инструментации драйвера БД.
Так можно узнать:
- самые медленные и самые частые запросы по таблицам: «PostgreSQL · SELECT orders», «MySQL · UPDATE carts»;
- какой эндпоинт или задача их вызывает и сколько времени запрос занял внутри общего ответа;
- запросы с ошибками — таймауты, дедлоки, нарушения ограничений;
- N+1 — десятки одинаковых запросов в одной трассе;
- как менялась задержка запроса, например до и после релиза.
Как включить мониторинг запросов к БД
- Подключите к приложению трассировку OpenTelemetry — SDK отправляет трассы на агента стека.
- Включите инструментацию драйвера базы — она создаёт спан на каждый запрос:
| Стек | Что подключить |
|---|---|
| Node.js | getNodeAutoInstrumentations() уже включает pg, mysql/mysql2, mongodb, redis/ioredis; TypeORM, Sequelize и Knex работают через них. Prisma — отдельно, пакет @prisma/instrumentation |
| Python | opentelemetry-bootstrap -a install ставит инструментации найденных библиотек: psycopg/psycopg2, asyncpg, PyMySQL, SQLAlchemy, pymongo, redis. Запуск — через opentelemetry-instrument |
| Go | database/sql — github.com/XSAM/otelsql; pgx — github.com/exaring/otelpgx; GORM — gorm.io/plugin/opentelemetry/tracing. Запросы обязательно с контекстом (QueryContext, db.WithContext(ctx)), иначе спан не привяжется к трассе |
| Java | OpenTelemetry Java agent инструментирует JDBC, Hibernate, R2DBC, MongoDB и Redis-клиенты без изменения кода |
| .NET | SQL Server — OpenTelemetry.Instrumentation.SqlClient; PostgreSQL — пакет Npgsql.OpenTelemetry и .AddNpgsql() в WithTracing; EF Core — OpenTelemetry.Instrumentation.EntityFrameworkCore |
- Проверьте, что инструментация пишет тип базы, операцию и текст запроса — атрибуты
db.system,db.operationиdb.statement(илиdb.query.text). Без текста запрос виден, но без SQL и без таблицы в названии. В некоторых инструментациях запись текста включается отдельной опцией — загляните в её настройки.
Важно. Значения в SQL — то, что стоит в
WHEREиVALUES, — агент маскирует до отправки. В Наблюдатель уходит структура запроса без данных ваших пользователей.
Где смотреть запросы к базе
Всё находится на вкладке «Трассировка» стека. Сначала выберите период — 1 ч, 6 ч, 24 ч или 7 дн — и при необходимости сервис.
Плитка «База данных» в блоке «Обзор» показывает, сколько запросов к базе в секунду и их p95, и как это изменилось к прошлому периоду. Под плитками может быть строка «Больше всего нагружает БД: …» — клик открывает эту операцию.
Операции с фильтром «База данных». В таблице операций выберите тип «База данных». Запросы сгруппированы по системе, операции и таблице: «PostgreSQL · SELECT orders». Таблица берётся из атрибутов инструментации или из текста SQL (FROM, INTO, UPDATE). Одинаковые запросы к схемам разных клиентов (project_333d…entity_crud_outbox, project_0449…entity_crud_outbox) собираются в одну строку project_*.entity_crud_outbox.
| Что найти | Как |
|---|---|
| Самые медленные запросы | сортировка по колонке «p95» |
| Самые частые запросы | сортировка по колонке «Вызовов» |
| Запросы с ошибками | сортировка по колонке «Ошибки» |
| Конкретную таблицу или операцию | поиск над таблицей: orders, SELECT, UPDATE carts |
Клик по строке раскрывает графики операции за период — время ответа p95, число вызовов, ошибки — и кнопку «Показать трассы этой операции».
Трасса запроса. В список трасс попадут запросы, в которых выполнялась эта операция. Откройте самую долгую: на временной шкале видно, какой HTTP-запрос или задача вызвали обращение к базе и какую долю общего времени оно заняло. Клик по спану базы показывает блок SQL с текстом запроса и атрибуты. В одну группу попадают все запросы этого типа к таблице, поэтому какой именно запрос медленный, видно по SQL в трассе.
Как найти N+1
N+1 — когда код в цикле делает отдельный запрос к базе на каждый элемент списка: один запрос за списком заказов и ещё по запросу за каждой позицией. По отдельности такие запросы быстрые, но вместе они растягивают ответ.
Признаки в Наблюдателе:
- В операциях у запроса к базе намного больше вызовов, чем у HTTP-операции, которая его вызывает. Например, у
GET /api/orders200 вызовов за час, а у «PostgreSQL · SELECT order_items» — 8 000. Сравните колонки «Вызовов» с фильтром «Запросы» и «База данных». - В трассе такого запроса — десятки одинаковых спанов базы подряд, отличающихся только параметрами. Спаны запросов к базе не сворачиваются как служебные, поэтому повторы хорошо видны на шкале.
Обычное исправление — один запрос со списком (WHERE id IN (…)) или JOIN, либо жадная загрузка связей в ORM. После выкладки число вызовов операции на графике должно упасть.
Наблюдатель сохраняет все трассы без выборки, поэтому число вызовов в операциях — по всем запросам, а не по их доле.
Ограничения
- Видны только запросы из приложения с трассировкой. Запросы из cron-скриптов, миграций, сервисов без OpenTelemetry и ручные запросы — нет.
- Это время запроса со стороны приложения: в него входят сеть и ожидание соединения из пула. Планы выполнения, блокировки и нагрузку самой базы трассы не показывают.
- Трассы без ошибок хранятся 7 дней, с ошибками — 30 дней. Число вызовов, ошибки и p95 операций хранятся 3 месяца.
- На вкладке доступны периоды до 7 дней.
Состояние самой базы: свои метрики
То, что видит сама база, — подключения по состояниям, долгие запросы, ожидание блокировок, размер — можно отдавать своими метриками: небольшим эндпоинтом в приложении, который раз в интервал выполняет несколько запросов к БД. Для PostgreSQL:
| Метрика | Запрос |
|---|---|
| подключения по состоянию (active, idle, idle in transaction) | SELECT state, count(*) FROM pg_stat_activity WHERE datname = current_database() GROUP BY state |
| запросы дольше 30 секунд | SELECT count(*) FROM pg_stat_activity WHERE state = 'active' AND now() - query_start > interval '30 seconds' |
| ждут блокировку | SELECT count(*) FROM pg_locks WHERE NOT granted |
| размер базы | SELECT pg_database_size(current_database()) |
Это 5–10 значений, они легко укладываются в пределы метрик. Готовый postgres_exporter или pg_stat_statements целиком подключать не стоит: это сотни рядов, а текст запроса в метке — и персональные данные, и тысячи значений.
В Docker Compose агент стека к тому же сам показывает размер баз PostgreSQL, MySQL, MongoDB, Redis и ClickHouse на графиках — подробнее в статье как работает агент.
Найти медленные запросы и N+1 можно и вопросом ИИ-ассистенту: «какие запросы к базе самые медленные за сутки и откуда они вызываются».
Что дальше
- Трассировка OpenTelemetry — как подключить SDK.
- Карта сервисов — какие сервисы ходят в какую базу.
- Свои метрики — состояние базы и пулов соединений.
- Локальная разработка — найти N+1 ещё до выкладки.
Первый сервер или стек подключается за пару минут. Бесплатно в раннем доступе.
Нашли неточность или не хватает ответа? Напишите в поддержку