Jak jedna nieindeksowana kolumna zabiła naszą bazę danych

Jeden brakujący indeks zmienił API o czasie odpowiedzi 8 ms w 8-sekundowy koszmar.

Przeprowadziliśmy testy obciążeniowe przed ważnym wydaniem. W środowisku lokalnym 100 wierszy nie stanowiło problemu. W środowisku stagingowym zasymulowaliśmy 500 użytkowników operujących na 1,5 miliona wierszy.

Wszystko padło.

  • Czas odpowiedzi API skoczył z 45 ms do 8 000 ms.
  • Zużycie procesora bazy danych osiągnęło 100%.
  • Pule połączeń zostały wyczerpane.
  • Żądania kończyły się przekroczeniem czasu oczekiwania (timeout).

Winowajcą było proste zapytanie, które pobierało historię zamówień według user_id i status.

Uruchomienie EXPLAIN ANALYZE w PostgreSQL wykazało Sequential Scan. Z powodu braku indeksu na user_id, silnik musiał czytać wszystkie 1,5 miliona wierszy przy każdym zapytaniu.

Przy 100 jednoczesnych zapytaniach baza danych skanowała 150 milionów wierszy naraz.

Naprawa zajęła pięć minut. Utworzyliśmy indeks złożony na (user_id, status, created_at DESC), używając CREATE INDEX CONCURRENTLY, dzięki czemu tabela pozostała dostępna online.

Wyniki:

  • Czas zapytania spadł z 8 150 ms do 0,14 ms.
  • Opóźnienie API spadło z 8 sekund do 12 ms.
  • Zużycie procesora spadło ze 100% do poniżej 8%.

Wyciągnięte wnioski:

  • Testy lokalne mogą mylić; 100 wierszy to nie to samo co milion.
  • Indeksuj klucze obce — większość ORM-ów o tym zapomina.
  • Używaj EXPLAIN ANALYZE; baza danych sama wskaże problem.
  • Optymalizuj zapytania, zanim kupisz więcej sprzętu.

Nie skaluj najpierw serwerów. Skaluj zapytania.

Źródło: https://dev.to/mia_keller_ffd2584c046ecb/how-an-unindexed-column-silently-killed-our-database-under-load-and-the-5-minute-fix-m32