Как неиндексированный столбец убил нашу базу данных
Один пропущенный индекс превратил API с откликом в 8 мс в восьмисекундный кошмар.
Перед крупным релизом мы провели нагрузочное тестирование. На локальной разработке 100 строк обрабатывались без проблем. В стейджинге мы симулировали 500 пользователей на 1,5 миллионах строк.
Все сломалось.
- Время отклика API подскочило с 45 мс до 8 000 мс.
- Загрузка CPU базы данных достигла 100 %.
- Пулы соединений исчерпаны.
- Запросы завершались по таймауту.
Виновником оказался простой запрос, который выбирал историю заказов по user_id и status.
Запуск EXPLAIN ANALYZE в PostgreSQL показал Sequential Scan. Из-за отсутствия индекса по user_id движок считывал все 1,5 миллиона строк для каждого запроса.
При 100 одновременных запросах база данных сканировала 150 миллионов строк за раз.
Исправление заняло пять минут. Мы создали составной индекс на (user_id, status, created_at DESC) с помощью CREATE INDEX CONCURRENTLY, чтобы таблица оставалась доступной.
Результаты:
- Время выполнения запроса упало с 8 150 мс до 0,14 мс.
- Задержка API снизилась с 8 секунд до 12 мс.
- Использование CPU упало со 100 % до менее чем 8 %.
Извлеченные уроки:
- Локальные тесты вводят в заблуждение: 100 строк — это не замена миллиону.
- Индексируйте внешние ключи — большинство ORM этого не делают.
- Используйте
EXPLAIN ANALYZE; база данных сама укажет на проблему. - Оптимизируйте запросы, прежде чем покупать новое оборудование.
Не масштабируйте серверы в первую очередь. Масштабируйте запросы.
