4 czerwca 2026, Luboš Zápotočný
Dlaczego checkout pada przy 10% obciążenia serwera
Serwery mają rezerwy, checkout nie odpowiada. Blokady w bazie, cache stampede i synchroniczne wywołania płatności: awarie, których autoscaling nie rozwiąże.
To jeden z najbardziej dezorientujących incydentów, jakie może przechodzić zespół e-commerce. Startuje kampania, w checkoucie pojawiają się timeouty, a dashboardy pokazują prawidłowe wartości: CPU na 10%, pamięć w normie, duży zapas mocy. Pierwszą reakcją jest zwykle dodanie serwerów. Nowe instancje uruchamiają się, pozostają bezczynne obok dotychczasowych, a checkout nadal nie odpowiada.
Dezorientacja bierze się ze złego modelu myślowego. Obciążenie ma dwa składniki: pracę i czekanie. Twoje serwery stoją w kolejkach, a kolejek nie widać na wykresie CPU.
Najczęstsze przyczyny
Rywalizacja o blokady w bazie. Checkout to moment, w którym sklep przestaje czytać i zaczyna pisać: zarezerwuj stan, utwórz zamówienie, zapisz adres. Te zapisy serializują się na blokadach wierszy, a ruch z kampanii koncentruje się na tych samych kilku wierszach. Stu klientów finalizujących zakup podczas tej samej promocji serializuje się na tych samych gorących wierszach: licznikach sekwencji zamówień, tabelach zamówień i koszyków oraz rezerwowanym stanie magazynowym. Każda transakcja trzyma swoje blokady, czekając na zakończenie innych operacji. Przepustowość gwałtownie spada, a CPU pozostaje niskie. Na Magento ma to charakterystyczny objaw: komunikaty o oczekiwaniu na blokady i zakleszczeniach wypełniające logi rano po wyprzedaży.
Cache stampede. Gorący wpis w cache’u (strona kategorii, w którą celuje każda reklama) wygasa w szczycie ruchu. Każde żądanie, które nie znajduje wpisu, regeneruje teraz stronę równocześnie, obciążając bazę identycznymi, kosztownymi zapytaniami. Cache, który dotąd przyspieszał sklep, teraz go obciąża, i to dokładnie w chwili największego ruchu.
Synchroniczne wywołania firm trzecich na ścieżce checkoutu. Stawki wysyłki, autoryzacja płatności, wyliczenie podatku, kontrola antyfraudowa: każde z nich to wywołanie sieciowe do zewnętrznego systemu, wykonywane, podczas gdy Twój worker trzyma połączenie, a często i transakcję bazodanową. Kiedy jeden dostawca zwalnia z 200 ms do 10 sekund pod tym samym kampanijnym ruchem, kolejne workery jeden po drugim blokują się na tym wywołaniu. Przyczyna przestoju leży wtedy po stronie zewnętrznego dostawcy.
Wyczerpanie workerów i połączeń. Trzy powyższe problemy mają wspólny wzmacniacz: workery, które czekają, wciąż zajmują swój slot i swoje połączenie z bazą. Procesy PHP-FPM zwymiarowane na spokojny wtorek zapełniają się zablokowanymi żądaniami, nowe żądania w ogóle nie dostają ani workera, ani połączenia i niedostępne jest już wszystko, co wciąż potrzebuje workera PHP, choćby ta strona robiła jak najmniej.
Żaden z tych czterech problemów nie jest problemem sprzętowym, dlatego autoscaling ich nie rozwiązuje. Nowe instancje dołączają do tej samej kolejki po tę samą blokadę wiersza za tym samym wolnym dostawcą. W efekcie płacisz za to samo czekanie na większej liczbie maszyn.
Jak to zobaczyć zawczasu
W diagnozie trzeba przestać patrzeć na utylizację i zacząć patrzeć na czas: dokąd idą milisekundy żądania checkoutu, na pracę czy na czekanie na blokady, cache i zewnętrzne API? Najlepiej odpowiadają na to rozproszone trace’y, ale wiele powie już sam log wolnych zapytań i liczniki oczekiwania na blokady w bazie. Jeśli przed następną kampanią masz dodać tylko jeden wykres, niech to będą lock waity.
Poprawki wynikają z diagnozy i w większości są mało efektowne: timeouty na każdym wywołaniu firmy trzeciej i cache’owane fallbacki wszędzie tam, gdzie nieaktualna odpowiedź nie szkodzi (nieco nieaktualna stawka wysyłki nic nie psuje, ale autoryzacja płatności musi pójść na żywo), ochrona przed stampede, żeby gorący klucz odbudowywał jeden proces, a reszta serwowała starszą wersję, wyniesienie mniej ważnych zapisów z transakcji do kolejki i rezerwowanie stanu magazynowego tak późno na ścieżce zakupowej, jak pozwala na to biznes, zamiast blokowania go już od koszyka. Które z nich mają znaczenie dla Twojego sklepu, pokaże dopiero audyt wydajności wykonany na Twoich trace’ach; wdrożone poprawki utrwala potem nasz DevOps.
Awarie kampanijne to zwykle awarie współbieżności. Kolejny serwer ich nie naprawi; trzeba w trace’ach zobaczyć, na co żądania czekają.