Медленные запросы к БД и N+1

Медленные запросы к базе данных в Наблюдателе видны через трассы OpenTelemetry. Каждый запрос к БД — отдельный спан с текстом SQL, длительностью и ошибкой внутри HTTP-запроса или задачи, которые его вызвали. Отдельный агент для базы ставить не нужно: достаточно трассировки в приложении и инструментации драйвера БД.

Так можно узнать:

  • самые медленные и самые частые запросы по таблицам: «PostgreSQL · SELECT orders», «MySQL · UPDATE carts»;
  • какой эндпоинт или задача их вызывает и сколько времени запрос занял внутри общего ответа;
  • запросы с ошибками — таймауты, дедлоки, нарушения ограничений;
  • N+1 — десятки одинаковых запросов в одной трассе;
  • как менялась задержка запроса, например до и после релиза.

Как включить мониторинг запросов к БД

  1. Подключите к приложению трассировку OpenTelemetry — SDK отправляет трассы на агента стека.
  2. Включите инструментацию драйвера базы — она создаёт спан на каждый запрос:
Стек Что подключить
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
  1. Проверьте, что инструментация пишет тип базы, операцию и текст запроса — атрибуты 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 — когда код в цикле делает отдельный запрос к базе на каждый элемент списка: один запрос за списком заказов и ещё по запросу за каждой позицией. По отдельности такие запросы быстрые, но вместе они растягивают ответ.

Признаки в Наблюдателе:

  1. В операциях у запроса к базе намного больше вызовов, чем у HTTP-операции, которая его вызывает. Например, у GET /api/orders 200 вызовов за час, а у «PostgreSQL · SELECT order_items» — 8 000. Сравните колонки «Вызовов» с фильтром «Запросы» и «База данных».
  2. В трассе такого запроса — десятки одинаковых спанов базы подряд, отличающихся только параметрами. Спаны запросов к базе не сворачиваются как служебные, поэтому повторы хорошо видны на шкале.

Обычное исправление — один запрос со списком (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 можно и вопросом ИИ-ассистенту: «какие запросы к базе самые медленные за сутки и откуда они вызываются».

Что дальше

Попробуйте Наблюдатель

Первый сервер или стек подключается за пару минут. Бесплатно в раннем доступе.

Создать аккаунт

Нашли неточность или не хватает ответа? Напишите на hello@nablserver.ru