Budzenie baz danych bez martwych połączeń
Opublikowane aplikacje w Mythex, i ich bazy danych, zasypiają, gdy nikt z nich nie korzysta. Gdy aplikacja się budziła, potrafiła trzymać pierwsze żądanie przez 10–12 sekund, bo próbowała ponownie użyć połączeń do bazy, które umarły, gdy wszystko spało. Teraz aplikacjom Spring Boot nadajemy ustawienia bazy bezpieczne po uśpieniu i mówimy agentowi, żeby budował aplikacje każdego stosu w ten sam sposób. Na platformie hostingowej pierwsze żądanie po wybudzeniu skróciło się z 12,7 sekundy do 2,3 sekundy.
Dlaczego aplikacje i bazy zasypiają
Opublikowana aplikacja Mythex działa na maszynie, która zawiesza się po kilku minutach bezczynności, a jej baza danych to bezserwerowy Postgres, który zasypia po pięciu minutach bezczynności i budzi się przy następnym zapytaniu. To utrzymuje cichą aplikację tanią: płacisz za to, co baza faktycznie zużyła, a śpiąca aplikacja nie zużywa niczego na obliczenia. O stronie rozliczeń pisaliśmy w rozliczaniu baz danych za to, czego faktycznie używają.
Haczyk polega na tym, że oba śpią osobno i nie zgadzają się co do tego, czym jest połączenie.
Objaw: wolna pierwsza wizyta
Większość aplikacji serwerowych trzyma pulę połączeń: garstkę otwartych połączeń do bazy, gotowych do użycia, żeby każde żądanie nie płaciło kosztu otwarcia nowego. To dobry projekt dla aplikacji zawsze włączonej.
Oto co się dzieje ze snem:
- Aplikacja działa, z kilkoma bezczynnymi połączeniami w puli.
- Nikt nie wchodzi. Maszyna aplikacji zawiesza się — zamrożona, z nienaruszoną pulą.
- Baza danych zasypia i zrywa swój koniec każdego połączenia.
- Przychodzi odwiedzający. Aplikacja budzi się dokładnie taka, jaka była, wciąż trzymając połączenia, o których baza już nie wie.
- Pula oddaje jedno z tych połączeń. Musi się dowiedzieć, że jest martwe — przez próbę i czekanie — zanim je porzuci.
W aplikacji Spring Boot walidacja tych martwych połączeń blokowała pierwsze żądanie przez około 10 sekund, zanim kończyło się błędem „Connection is not available”. Dla odwiedzającego strona po prostu się zawieszała, a potem być może pokazywała błąd.
Dlaczego aplikacje Spring dostawały najgorzej
Spring Boot to popularny framework Javy, a domyślnie używa puli połączeń o nazwie HikariCP. Spring potrafi skonfigurować tę pulę z ustawień, w tym tego, ile bezczynnych połączeń trzymać.
Ale Mythex daje każdej aplikacji bazę jako pojedynczą zmienną DATABASE_URL w postaci postgresql://user:pass@host/db. Spring nie czyta tego formatu bezpośrednio. Więc aplikacje Spring zbudowane w Mythex same konstruowały swoją pulę w kodzie, parsując ten URL — a pula zbudowana ręcznie ignoruje wszystkie ustawienia spring.datasource.* Springa. Nawet gdyby ktoś znał właściwe ustawienia puli, nie miały gdzie zadziałać.
Naprawa, część 1: ustawienia, których Spring może użyć
Gdy projekt ma bazę danych, dostaje teraz także zmienne środowiskowe, które Spring czyta natywnie:
SPRING_DATASOURCE_URL,SPRING_DATASOURCE_USERNAMEiSPRING_DATASOURCE_PASSWORD, przekonwertowane zDATABASE_URL(z wymaganym SSL, chyba że URL mówi inaczej).- Ustawienia puli, które przetrwają sen:
| Ustawienie | Wartość | Dlaczego |
|---|---|---|
| Minimalna liczba bezczynnych połączeń | 0 | Nie trzymaj niczego otwartego między żądaniami, więc nie ma czego unieważnić |
| Limit czasu bezczynności | 30 sekund | Zamykaj nieużywane połączenia szybko |
| Maksymalny czas życia | 2 minuty | Wycofuj każde połączenie, zanim się zestarzeje |
| Limit czasu połączenia | 5 sekund | Nie wstrzymuj żądania długo, jeśli baza wolno odpowiada |
Bez trzymania bezczynnych połączeń obudzona aplikacja otwiera jedno świeże połączenie zamiast przedzierać się przez martwe.
Gdy te zmienne są obecne, Spring sam konfiguruje pulę i ustawienia obowiązują. Cokolwiek projekt ustawił sam, zawsze wygrywa; my uzupełniamy tylko braki. Aplikacje niebędące Springiem ignorują te zmienne.
Szczegół, który prawie sprawił, że to nic nie robiło
Spring mapuje zmienne środowiskowe na swoje ustawienia według reguły, którą łatwo źle zrozumieć. Ustawienie minimum-idle trzeba zapisać jako SPRING_DATASOURCE_HIKARI_MINIMUMIDLE — myślniki pominięte, słowa sklejone. Zapisane jako SPRING_DATASOURCE_HIKARI_MINIMUM_IDLE, co wygląda naturalniej, mapuje się na inne, nieistniejące ustawienie (minimum.idle) i jest po cichu ignorowane.
To rodzaj błędu, który przechodzi każdy przegląd i nic nie robi na produkcji. Dlatego test sprawdza zarówno, czy wartości są ustawione, jak i czy żadna zmienna nie używa formy z podkreślnikami zamiast sklejonych słów.
Naprawa, część 2: każdy inny stos
Spring nie jest jedynym frameworkiem z pulą, a nie możemy wstępnie skonfigurować każdej biblioteki w każdym języku. Dlatego agent Mythex — AI, które pisze Twoją aplikację — ma teraz stałą instrukcję dla każdego stosu: opublikowane aplikacje i ich bazy zasypiają, gdy są bezczynne, więc połączenie trzymane otwarte może być martwe przy następnym żądaniu. Powinien:
- nie trzymać bezczynnych połączeń do bazy otwartych albo sprawdzać każde przed użyciem,
- ponowić raz przy błędzie połączenia, i
- używać ustawień bazy, które platforma podaje w środowisku, zamiast budować własną pulę przez parsowanie
DATABASE_URL.
Ostatni punkt zamyka pętlę z częścią 1: aplikacja zbudowana na dostarczonych ustawieniach automatycznie dostaje konfigurację bezpieczną po uśpieniu.
Wynik
Zmierzone na platformie hostingowej, na której uruchamiamy opublikowane aplikacje, pierwsze żądanie po wybudzeniu skróciło się z 12,7 sekundy do 2,3 sekundy.
Co z tego wynieśliśmy
- Każda warstwa, która zasypia, musi zgadzać się co do tego, co przetrwa. Zamrożona aplikacja i uśpiona baza podjęły każda rozsądną decyzję. Razem aplikacja obudziła się z stanem, który nie był już prawdziwy.
- Domyślne ustawienia zaprojektowane dla serwerów zawsze włączonych nie pasują do aplikacji skalujących się do zera. Utrzymywanie połączeń w cieple to dobry pomysł, dopóki wszystko wokół nie zimnieje.
- Dawaj frameworkom ich dane w ich własnym formacie. Jeden URL był dla nas prosty, ale zmuszał każdą aplikację Spring do ręcznego budowania puli, która ignorowała jej własną konfigurację.
- Testuj, czy ustawienie jest zapisane tak, jak framework je czyta. Poprawna wartość pod złą nazwą nie robi nic, po cichu.
Jeśli dopiero poznajesz, jak bazy danych działają w Mythex, zobacz dokumentację baz danych i nasze wyjaśnienie czym jest Postgres.