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.
