Сколько стоит мой запрос к базе
База медленная — и непонятно, с чего начинать. Обычно виновата не сама база, а запросы: программа читает слишком много строк, делает по запросу на каждую строку списка или забывает про индекс. Этот калькулятор переводит привычные слова «у нас 50 запросов в секунду» в мегабайты в секунду и показывает, где именно узкое место.
Почему «просто добавить индекс» не работает
Индекс — полезный инструмент, но не волшебная палочка. Он помогает, когда запрос умеет им воспользоваться: если условие фильтра покрыт индексом и селективность достаточно высокая, база читает страницы индекса вместо всей таблицы. Но если запрос сначала делает десять joins, а потом фильтрует, индекс на последней колонке ничего не спасёт.
Второй по значимости источник проблем — количество запросов, а не размер выборки. Один HTTP-запрос, который внутри делает двадцать SQL-запросов, стоит базе в двадцать раз дороже, чем один запрос с JOIN. Третий — объём данных, которые вытаскиваются целиком, хотя нужны два поля.
Калькулятор помогает расставить приоритеты: сначала посчитайте, сколько данных в секунду читает сервис и укладывается ли это в возможности диска. Если данные не помещаются в оперативную память, а попаданий в кэш мало, — узкое место найдено. Дальше остаётся применить порядок оптимизации, который приведён в конце страницы.
Сразу оговорка про то, чего в расчёте нет: он не учитывает механизм многоверсионности PostgreSQL, журнал WAL, автовакуум, поведение кэша ОС и реальную селективность индексов. Он считает верхнюю оценку «сколько байт сервис читает из базы» — а именно эта величина объясняет большинство жалоб на медленную базу. Если после оптимизации цифры не сходятся с реальностью, смотрите план запроса: EXPLAIN (ANALYZE, BUFFERS) показывает, сколько страниц база прочитала на самом деле и почему решила идти последовательным сканированием.
Параметры нагрузки
Заполните цифры своего сервиса — калькулятор покажет объём данных и подскажет, что оптимизировать.
Что получится при такой нагрузке
Что делать с этим
- По введённым числам явных проблем не видно: запрос читает немного, N+1 отсутствует, кэш работает. Стоит замерить на реальных данных — EXPLAIN ANALYZE покажет фактический план.
Откуда берутся типовые числа
Размер строки и индексов
В PostgreSQL строка хранится в странице по 8 КБ, и в строке есть накладные расходы: заголовок, указатели на колонки, выравнивание. Поэтому «узкая» строка занимает на диске заметно больше, чем сумма её полей. Индекс btree на одну колонку добавляет примерно 30% к объёму данных таблицы — это грубое, но полезное правило для прикидки места.
Пропускная способность
Для оценки взят SSD: последовательное чтение около 500 МБ/с, произвольное — в несколько раз меньше. Реальная скорость зависит от кэша ОС, настроек PostgreSQL, типа запроса и того, живут ли данные в памяти. Если данные помещаются в оперативную память, цифры будут сильно завышены.
EXPLAIN (ANALYZE, BUFFERS) по реальному запросу и наблюдение за базой через pg_stat_statements.Порядок оптимизации, который работает
- Уберите N+1. Это самая дешёвая победа: один запрос с IN вместо запроса на каждую строку.
- Добавьте индексы под фильтры и сортировки. Индекс должен покрывать условие WHERE и ORDER BY — тогда PostgreSQL читает страницы индекса, а не всю таблицу.
- Ограничьте выборку. LIMIT, пагинация курсором по ключу вместо OFFSET — на больших объёмах это разница между миллисекундами и секундами.
- Считайте меньше. Не выбирайте целые сущности, если нужны два поля: SELECT id, name вместо SELECT *.
- Кэшируйте горячее. Redis перед базой снимает самую частую нагрузку и даёт выигрыш там, где запрос идёт одинаково много раз.
Частые вопросы о нагрузке на базу
Насколько точны эти расчёты?
Это оценка по типовым числам: размер строки, 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: двадцать строк в цикле — это двадцать обращений, а не одно.
Это не «счётчик производительности», а оценка порядка величин: она отвечает на вопрос «растёт ли нагрузка линейно или квадратично» и «кто первым упрётся в лимит соединений».
Почему индекс не всегда спасает
Индекс ускоряет выборку по индексу, но не ускоряет запись: вставка и обновление индексированной колонки дороже, а составной индекс может не покрывать запрос и тянуть за собой чтение таблицы. Кэш помогает, только если данные читаются чаще, чем меняются, и только если ключ кэша совпадает с параметрами запроса.
Как пользоваться
- Возьмите реальные числа из прод-метрик. RPS и p95 из мониторинга дадут честную оценку; выдуманные «100 запросов в секунду» приведут к выводу, который ничего не значит.
- Проверьте, есть ли N+1. Посмотрите в логах базы, сколько запросов приходит на один HTTP-запрос: если больше одного — вот и источник.
- Сравните варианты. Поменяйте размер пула соединений, добавьте индекс или кэш и посмотрите, как меняется картина.
Когда это пригождается
База под нагрузкой, CPU в норме
Скорее всего, упирается в ожидание соединений или в блокировки. Смотрите p95 по пулу и время ожидания, а не на средний RPS.
Растёт время ответа, а трафик тот же
Смотрите на план запроса: без индекса БД сканирует таблицу, и на большой таблице это видно как рост p95 без изменения RPS.
Планируете переезд на новую СУБД
Оценка помогает понять, какие запросы — самые дорогие: их и стоит оптимизировать первыми, до переезда.
Оценка приблизительная: реальные задержки зависят от оборудования, настроек PostgreSQL и плана запроса. Берите её как направление поиска, а не как обоснование бюджета.