Как неиндексированный столбец убил нашу базу данных

Один пропущенный индекс превратил API с откликом в 8 мс в восьмисекундный кошмар.

Перед крупным релизом мы провели нагрузочное тестирование. В локальной среде всё работало отлично на 100 записях. Затем мы симулировали 500 пользователей на 1,5 миллионах строк, соответствующих объему реальных данных.

Результаты были плохими:

  • Время отклика API подскочило с 45 мс до 8 000 мс.
  • Загрузка процессора базы данных достигла 100 %.
  • Пулы соединений исчерпаны, что привело к ошибкам по таймауту.

Мы проверили лог медленных запросов (slow-query log) и обнаружили эндпоинт, который извлекает историю заказов по ID пользователя и статусу.

Запуск EXPLAIN ANALYZE в PostgreSQL показал Sequential Scan. Движок считывал все 1,5 миллиона строк для каждого запроса, так как для user_id не было индекса.

При 100 одновременных запросах система сканировала 150 миллионов строк за раз.

Исправление заняло пять минут.

Вместо простого индекса по user_id мы создали составной индекс на (user_id, status, created_at DESC). Это позволило базе данных:

  • Фильтровать по user_id.
  • Фильтровать по status.
  • Мгновенно возвращать самые новые строки.
  • Пропускать лишние этапы сортировки.

Мы использовали CREATE INDEX CONCURRENTLY, чтобы таблица оставалась доступной (не блокировалась) во время операции.

После исправления:

  • Время выполнения запроса упало с 8 150 мс до 0,14 мс.
  • Задержка API снизилась с 8 секунд до 12 мс.
  • Загрузка процессора упала со 100 % до менее чем 8 %.

Извлеченные уроки:

  • Локальное тестирование может вводить в заблуждение: 100 строк не заменят миллион.
  • Индексируйте внешние ключи — большинство ORM этого не делают.
  • Запускайте EXPLAIN ANALYZE, прежде чем покупать новое оборудование.
  • Проектируйте индексы под те запросы, которые вы действительно выполняете.

Не масштабируйте серверы в первую очередь. Масштабируйте запросы.

Источник: https://dev.to/mia_keller_ffd2584c046ecb/how-an-unindexed-column-silently-killed-our-database-under-load-and-the-5-minute-fix-m32