Перейти к содержимому
ГоМастер

Поиск по урокам

Найдите урок, курс, термин или раздел

Сколько стоит мой запрос к базе

База медленная — и непонятно, с чего начинать. Обычно виновата не сама база, а запросы: программа читает слишком много строк, делает по запросу на каждую строку списка или забывает про индекс. Этот калькулятор переводит привычные слова «у нас 50 запросов в секунду» в мегабайты в секунду и показывает, где именно узкое место.

оценка для PostgreSQLN+1, индексы, кэш

Почему «просто добавить индекс» не работает

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

Второй по значимости источник проблем — количество запросов, а не размер выборки. Один HTTP-запрос, который внутри делает двадцать SQL-запросов, стоит базе в двадцать раз дороже, чем один запрос с JOIN. Третий — объём данных, которые вытаскиваются целиком, хотя нужны два поля.

Калькулятор помогает расставить приоритеты: сначала посчитайте, сколько данных в секунду читает сервис и укладывается ли это в возможности диска. Если данные не помещаются в оперативную память, а попаданий в кэш мало, — узкое место найдено. Дальше остаётся применить порядок оптимизации, который приведён в конце страницы.

Сразу оговорка про то, чего в расчёте нет: он не учитывает механизм многоверсионности PostgreSQL, журнал WAL, автовакуум, поведение кэша ОС и реальную селективность индексов. Он считает верхнюю оценку «сколько байт сервис читает из базы» — а именно эта величина объясняет большинство жалоб на медленную базу. Если после оптимизации цифры не сходятся с реальностью, смотрите план запроса: EXPLAIN (ANALYZE, BUFFERS) показывает, сколько страниц база прочитала на самом деле и почему решила идти последовательным сканированием.

Параметры нагрузки

Заполните цифры своего сервиса — калькулятор покажет объём данных и подскажет, что оптимизировать.

Размер строки

Что получится при такой нагрузке

Данных на запрос
48.8 КБ
1 SQL-запрос(ов)
Данных в секунду
1.9 МБ
после вычета попаданий в кэш
Время чтения
3.8 мс/с
оценка для SSD 500 МБ/с
Место на диске
762.9 МБ
таблица 476.8 МБ + индексы 286.1 МБ

Что делать с этим

  • По введённым числам явных проблем не видно: запрос читает немного, N+1 отсутствует, кэш работает. Стоит замерить на реальных данных — EXPLAIN ANALYZE покажет фактический план.

Откуда берутся типовые числа

Размер строки и индексов

В PostgreSQL строка хранится в странице по 8 КБ, и в строке есть накладные расходы: заголовок, указатели на колонки, выравнивание. Поэтому «узкая» строка занимает на диске заметно больше, чем сумма её полей. Индекс btree на одну колонку добавляет примерно 30% к объёму данных таблицы — это грубое, но полезное правило для прикидки места.

Пропускная способность

Для оценки взят SSD: последовательное чтение около 500 МБ/с, произвольное — в несколько раз меньше. Реальная скорость зависит от кэша ОС, настроек PostgreSQL, типа запроса и того, живут ли данные в памяти. Если данные помещаются в оперативную память, цифры будут сильно завышены.

Это оценка по типовым числам, а не измерение вашей базы. Реальные цифры отличаются сильно: их определяют план запроса, селективность индексов, размер рабочего множества, кэш и то, как часто данные меняются. Для точной картины нужен EXPLAIN (ANALYZE, BUFFERS) по реальному запросу и наблюдение за базой через pg_stat_statements.

Порядок оптимизации, который работает

  1. Уберите N+1. Это самая дешёвая победа: один запрос с IN вместо запроса на каждую строку.
  2. Добавьте индексы под фильтры и сортировки. Индекс должен покрывать условие WHERE и ORDER BY — тогда PostgreSQL читает страницы индекса, а не всю таблицу.
  3. Ограничьте выборку. LIMIT, пагинация курсором по ключу вместо OFFSET — на больших объёмах это разница между миллисекундами и секундами.
  4. Считайте меньше. Не выбирайте целые сущности, если нужны два поля: SELECT id, name вместо SELECT *.
  5. Кэшируйте горячее. Redis перед базой снимает самую частую нагрузку и даёт выигрыш там, где запрос идёт одинаково много раз.
PostgreSQLN+1индексыкэш

Частые вопросы о нагрузке на базу

Насколько точны эти расчёты?

Это оценка по типовым числам: размер строки, 30% места под индекс, 500 МБ/с для SSD. Реальные значения зависят от данных, плана запроса, кэша ОС и настроек PostgreSQL, поэтому цифры могут отличаться в разы. Пользуйтесь калькулятором для прикидки «много или мало», а точные цифры берите из EXPLAIN (ANALYZE, BUFFERS) и pg_stat_statements.

Что такое проблема N+1 и как её заметить?

N+1 — это когда один бизнес-запрос порождает один запрос за списком и ещё по одному запросу на каждую строку списка. На twenty элементах это двадцать один round-trip к базе вместо одного. Заметить просто: включите логирование SQL и посмотрите, сколько запросов приходит на один HTTP-запрос. Лечится загрузкой связей одним запросом: JOIN, IN или preload в ORM.

Почему индексы занимают так много места?

Индекс — это отдельная структура данных, которая хранит копию значений колонок плюс ссылки на строки. Для btree на одну колонку это порядка 30% от объёма таблицы, а составной индекс может быть больше самой таблицы. Плата за индексы: замедление INSERT и UPDATE, более долгий VACUUM и больше места на диске. Поэтому индексы добавляют под конкретные запросы, а не «на всякий случай».

С чего начать, если база тормозит?

С EXPLAIN ANALYZE: он показывает фактический план запроса, число прочитанных строк и время. Дальше по типовым сценариям: нет индекса — добавить индекс; идёт последовательное сканирование — проверить селективность условия; сканируется много строк при малом результате — нужен индекс или LIMIT; запросов слишком много — искать N+1. Оптимизация по чужим догадкам, как правило, не работает.

Где разобрать работу с базой

Что внутри

Из чего складывается нагрузка

Калькулятор считает четыре величины: сколько строк проходит через соединение за секунду, сколько индексов работает на каждое чтение, сколько раз приложение обращается к базе за один пользовательский запрос и сколько места занимают индексы и данные. Отдельно показывается эффект N+1: двадцать строк в цикле — это двадцать обращений, а не одно.

Это не «счётчик производительности», а оценка порядка величин: она отвечает на вопрос «растёт ли нагрузка линейно или квадратично» и «кто первым упрётся в лимит соединений».

Почему индекс не всегда спасает

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

Как пользоваться

  1. Возьмите реальные числа из прод-метрик. RPS и p95 из мониторинга дадут честную оценку; выдуманные «100 запросов в секунду» приведут к выводу, который ничего не значит.
  2. Проверьте, есть ли N+1. Посмотрите в логах базы, сколько запросов приходит на один HTTP-запрос: если больше одного — вот и источник.
  3. Сравните варианты. Поменяйте размер пула соединений, добавьте индекс или кэш и посмотрите, как меняется картина.

Когда это пригождается

База под нагрузкой, CPU в норме

Скорее всего, упирается в ожидание соединений или в блокировки. Смотрите p95 по пулу и время ожидания, а не на средний RPS.

Растёт время ответа, а трафик тот же

Смотрите на план запроса: без индекса БД сканирует таблицу, и на большой таблице это видно как рост p95 без изменения RPS.

Планируете переезд на новую СУБД

Оценка помогает понять, какие запросы — самые дорогие: их и стоит оптимизировать первыми, до переезда.

Оценка приблизительная: реальные задержки зависят от оборудования, настроек PostgreSQL и плана запроса. Берите её как направление поиска, а не как обоснование бюджета.