Nie piszę tego jako negatywnej recenzji REGXA i nie próbuję nikomu mówić, czy powinien kupować u nich VPS. To po prostu jeden z moich dni jako programisty.
Przeniosłem normalne obciążenie na VPS KVM z 2 vCPU, 2 GB RAM i 60 GB NVMe. Nginx działał. Backend działał. Mimo to maszyna zachowywała się jak skrajnie przeciążona: żądania się kumulowały, operacje TLS zwalniały, połączenia pozostawały otwarte, a część żądań kończyła się HTTP 504 dopiero po ponad dwóch minutach.
Rozliczanie czasu CPU zmieniło kierunek diagnozy
Pierwsze założenie było oczywiste: coś wewnątrz VPS-a zużywa procesor. Uruchomiłem więc mpstat.
Average CPU steal: 92.58%
CPU 0 steal: 90.62%
CPU 1 steal: 94.57%
CPU user: 4.02%
CPU system: 1.47%
CPU iowait: 0.41%
CPU idle: 0.53%
Pojedyncze próbki regularnie pokazywały około 89–98% steal. Presja CPU również była ekstremalna:
CPU PSI some avg10: 79.47
CPU PSI some avg60: 75.32
CPU PSI some avg300: 76.31
Load average: 5.85 / 5.75 / 5.73
To była maszyna z zaledwie dwoma vCPU. Linux miał procesy gotowe do wykonania, które czekały na CPU. Najważniejszym wskaźnikiem nie było zwykłe użycie procesora przez aplikację, lecz %steal.
Wysokie użycie CPU i CPU steal to dwa różne problemy
Gdyby moja aplikacja naprawdę obciążała procesor, oczekiwałbym wysokich wartości user lub system. Tymczasem VPS wyglądał mniej więcej tak:
user: 4%
system: 1%
steal: 93%
CPU steal oznacza czas, w którym wirtualny procesor jest gotowy do pracy, ale hypervisor go nie planuje. Kiedy więc piszę, że VPS tracił czas CPU, mam na myśli właśnie to techniczne znaczenie z wirtualizacji. Nie mogę udowodnić, że REGXA celowo przekazała innym klientom fizyczne rdzenie przeznaczone wyłącznie dla mnie, ani wywnioskować intencji z metryk systemu gościa. Mogę natomiast wykazać, że gość wielokrotnie miał pracę gotową do wykonania, ale nie otrzymywał czasu planowania CPU.
Usunąłem cały ruch produkcyjny
Pozostawał rozsądny kontrargument: być może to moje obciążenie powodowało problem. Usunąłem więc tę zmienną. Przeniosłem aktywny workload gdzie indziej, zatrzymałem ruch produkcyjny do tego VPS-a, poczekałem aż kolejki się opróżnią i powtórzyłem pomiar.
Average CPU steal: 94.17%
CPU 0 steal: 95.56%
CPU 1 steal: 92.83%
CPU user: 1.95%
CPU system: 0.59%
CPU iowait: 0.59%
CPU idle: 2.18%
Wynik był jeszcze gorszy. Pojedyncze próbki vmstat nadal pokazywały 91–98% steal, a nawet 15 procesów gotowych do pracy czekało na CPU. Kluczowe zestawienie wyglądało tak:
user: 1.95%
system: 0.59%
steal: 94.17%
Moje aplikacje prawie nic już nie robiły, a VM nadal traciła niemal cały czas planowania. Normalny ruch produkcyjny przestał być wiarygodnym wyjaśnieniem.
Nawet localhost stał się absurdalnie wolny
Przetestowałem też HTTPS przez 127.0.0.1, eliminując z testu publiczny DNS, mojego ISP, odległość geograficzną i zewnętrzną trasę sieciową.
Przy ruchu cztery z dziesięciu prób HTTPS przez localhost kończyły się błędem podczas TLS handshake. Udane żądania trwały 29,30, 22,77, 12,25, 11,87, 11,12 i 9,40 sekundy. Sam TLS handshake potrafił zająć około dziewięciu sekund.
Po usunięciu ruchu produkcyjnego localhost przyspieszył, ale nadal był niestabilny: 0,061, 0,745, 0,830, 0,873, 1,010, 1,117, 1,121, 2,188 i 3,355 sekundy. Ta sama lokalna operacja mogła raz potrwać około 61 ms, a następnym razem ponad 3,3 sekundy.
Reszta stosu pokazywała skutki
W pewnym momencie widziałem około 450 zestawionych połączeń, 122 połączenia orphaned, 110 FIN-WAIT-1 i 33 CLOSE-WAIT. Kolejka nasłuchu lokalnego backendu dochodziła do około 14–15, a HTTPS do około 40.
Nginx zapisywał odpowiedzi HTTP 504 po 142,857, 138,902, 135,064, 129,819 i 128,657 sekundy. Inne żądania pozostawały otwarte przez około 67–130 sekund. Widziałem również secure-connect timeouts około 35–41 sekund, timeouty sieciowe bazy danych i opóźnione operacje TLS.
Każdy z tych objawów z osobna mógł wyglądać jak oddzielny problem Nginx, bazy danych, sieci albo backendu. Ale zwiększenie timeoutów nie tworzy czasu CPU, którego hypervisor nie planuje.
RAM ani dysk nie wyjaśniały problemu
VPS nadal miał około 1,0–1,1 GiB dostępnego RAM-u, praktycznie nie korzystał ze swapu, nie było zdarzeń OOM ani aktywności OOM killera. System plików był zajęty tylko w około 20%, z mniej więcej 44 GB wolnego miejsca. I/O wait pozostawał poniżej 1% podczas najważniejszych pomiarów CPU.
Pamięć nie była wyczerpana, dysk nie był pełny, a moje aplikacje nie zużywały brakującego CPU. Dominującą metryką pozostawał %steal.
Zdrowy VPS KVM wyglądał zupełnie inaczej
Uruchomiłem ten sam rodzaj diagnostyki na innym VPS-ie KVM, który obsługiwał normalny ruch:
Average CPU steal: 0.02%
CPU idle: 87.86%
CPU PSI avg10: 0.29
CPU PSI avg60: 0.63
CPU PSI avg300: 0.49
Load average: 0.47 / 0.33 / 0.14
Dziesięć lokalnych żądań HTTPS kończyło się w około 37–69 ms. Później na innym, rzeczywiście mocno obciążonym VPS-ie produkcyjnym zmierzyłem 61,71% user CPU, 5,08% system CPU, 24,09% idle i zaledwie 0,13% steal.
To rozróżnienie jest dla mnie dziś najważniejsze. VPS może być obciążony dlatego, że moje oprogramowanie faktycznie korzysta z procesora. To zupełnie co innego niż system gościa, który ponad 90% zmierzonego czasu CPU spędza, czekając na hypervisor.
REGXA ostatecznie potwierdziła contention w infrastrukturze
Z wnętrza VM mogłem mierzyć system gościa, ale nie widziałem fizycznego hosta, konfiguracji schedulera, limitów CPU ani sąsiednich VM. REGXA miała do tego dostęp.
Support ostatecznie poinformował mnie, że VPS działa na shared CPU infrastructure, zasoby CPU są współdzielone przez wiele maszyn wirtualnych, a wydajność może zależeć od obciążenia fizycznego noda. Dodali, że infrastruktura we Frankfurt była szczególnie mocno obciążona i wprost przypisali mój wysoki CPU steal do resource contention on the underlying infrastructure.
Powiedziano mi również, że na obecnej infrastrukturze nie mogą zmienić CPU quota ani zasad schedulingu ani przydzielić dodatkowych dedykowanych zasobów CPU temu współdzielonemu VPS-owi. Proponowanym rozwiązaniem technicznym było przeniesienie serwera do lokalizacji z mniejszym wykorzystaniem zasobów.
Trudno było mi to pogodzić z językiem, który wcześniej widziałem wokół dedicated CPU cores i guaranteed resources. Nie mam wglądu w dokładną konfigurację hosta, więc nie mogę stwierdzić, czy bezpośrednim mechanizmem był CPU overcommitment, quota, scheduler weighting, throttling czy ich kombinacja. Nie mogę też dowieść intencji. Nie muszę: Linux stale pokazywał 92–94% steal, a sama REGXA powiązała podwyższony steal z contention na współdzielonej infrastrukturze.
Zwrot pieniędzy również wymagał dodatkowej presji
Kiedy problem infrastruktury stał się jasny, nie chciałem dalej przenosić VPS-a i testować kolejnych nodów. Chciałem anulować usługę i odzyskać pieniądze.
Początkowo zaoferowano mi tylko część kwoty, a proponowany zwrot miał trafić na saldo konta REGXA zamiast na kartę, z której zapłaciłem. Dla mnie kredyt u dostawcy nie jest równoważny zwrotowi pieniędzy, gdy chcę zakończyć korzystanie z usługi.
Dlatego dalej odpowiadałem i prosiłem o zwrot 100% płatności na pierwotną metodę płatności. Ostatecznie REGXA się zgodziła i zwróciła całą kwotę na pierwotny sposób płatności, określając pełny refund jako wyjątek.
Doceniam, że ostatecznie wszystko zostało zwrócone. Ale fakt, że po potwierdzeniu problemu infrastrukturalnego musiałem jeszcze naciskać w sprawie rozliczenia finansowego, również jest częścią mojego doświadczenia.
Wniosek, który ze mną został
Najłatwiejszym błędem byłoby dalsze optymalizowanie aplikacji. Mogłem zmieniać Nginx, zwiększać timeouty, ograniczać concurrency, dodawać retry, analizować MongoDB albo przepisywać backend. Niektóre objawy być może wyglądałyby lepiej. Żadne z tych działań nie odpowiadałoby jednak na najważniejsze pytanie: dlaczego CPU steal przekraczał 90%?
Dziś nie kończę testowania nowego VPS-a na tym, że działa SSH, Nginx się uruchamia, a health endpoint zwraca 200. Sprawdzam również %user, %system, %iowait, %idle, %steal, CPU PSI, run queue i opóźnienie localhost.
A gdy coś wygląda podejrzanie, usuwam workload i mierzę ponownie. W tym przypadku dało to najczytelniejszy wynik całego incydentu:
CPU user: 1.95%
CPU system: 0.59%
CPU steal: 94.17%
Nie twierdzę, że każdy VPS REGXA zachowuje się w ten sposób. Przetestowałem jeden serwer i opisałem to, co wydarzyło się właśnie na nim. Jednak w tym przypadku dowody były wyjątkowo jednoznaczne: moje aplikacje praktycznie nie zużywały CPU, podczas gdy Linux wskazywał, że zdecydowana większość wirtualnego czasu CPU była tracona na oczekiwanie na scheduling przez hypervisor. REGXA później przypisała dokładnie to zachowanie rywalizacji o zasoby na współdzielonej infrastrukturze.
Dlatego to opisuję. Nie jako ocenę ani rekomendację, tylko jako jeden dzień z życia programisty — i jako metrykę, której już nigdy nie zignoruję na VPS-ie.