Są rzeczy, które są nieoczywiste i dopiero po kilku chwilach głębszego zastanowienia można zrozumieć nieco szerszy kontekst. Niektóre rzeczy nie są tym, czym się wydają na pierwszy rzut oka, a inne okazują się działać nie dokońca tak, jak wydawało się, że powinny działać. Zastanawiająco słaba wydajność jednego z moich serwisów kazała mi się bliżej przyjrzeć temu, jak działa AppEngine.
Wiedziałem już wcześniej, że wydajność aplikacji na AppEngine rośnie w miarę otrzymywania coraz większego ruchu — maszyneria AppEngine przydziela coraz więcej zasobów do obsługi żądań, co się bezpośrednio przekłada na to, jak szybko zostanie wyprodukowana odpowiedź. W związku z tym, że aplikacja na GAE jest uruchamiana przez CGI, to cały cykl, składający się z uruchomienia interpretera, zaimportowania tego co trzeba i wyprodukowania odpowiedzi powtarzałby się przy kazdym żądaniu, więc aby zminimalizować narzut na czynności administracyjne, AppEngine ma pewien bufor zaimportowanych modułów (i interpreter chyba też). Dzięki temu kolejne nadchodzące żądanie ma szansę skorzystać z już zaimportowanego kodu, co ma dać zmniejszenie kosztu obsługi żądania. Wszystko na papierze wygląda świetnie, ale obecnie ten bufor importu jest czyszczony po... 2-3 sekundach! Aplikacja, która dostaje żądanie co 5 sekund za każdym żądaniem będzie miała zimny start — nie tylko zużyje bardzo dużo zasobów (według Google ~1200ms czasu procesora to jest dużo), ale i obsługa żądania będzie trwała dużo dłuzej. Jedyna nadzieja w sprytnym napakowaniu memcache tym, co się może przydać. A jakie są tego implikacje?
Wyobraźmy sobie sytuację, w której zrobiłem super-czaderską aplikację i chcę ją sprzedać. Znajduję kupca, który już się podjarał ideą, cena jest dla niego do przyjęcia, więc zasiadamy do prezentacji. Kupiec otwiera stronę początkową serwisu i... kicha. Ślimaczy się. Tłumaczę mu, że taka jest specyfika aplikacji na AppEngine, gość przełknął, ale z pewnym niedowierzaniem (a tym czasem na pewno minęło ponad 5 sekund...). Następna strona i to samo, pomimo że używa praktycznie tych samych bibliotek, co poprzednia — ale przecież czas minął i znowu mamy zimny start. Potencjalny nabywca już nie jest tak podjarany, bo widzi, że aplikacja chodzi w żółwim tempie, a jedyne, na co można liczyć to to że Google mówi prawdę i pod rzeczywistym obciążeniem będzie ona miała lepszą wydajność. Tyle, że żeby miała ten normalny ruch, najpierw musi przejść swój okres rozbudzania zainteresowania, kiedy ruch będzie niewielki. Ludzie nie będą chcieli używać serwisu, który się ślimaczy, więc aplikacja nie dostanie ruchu wystarczającego, żeby przyspieszyć — i w tym momencie kółko się zamknęło.
I kolejny minus, jak stąd do Stambułu: jedyne serwery, na których uruchomione jest AppEngine, w tej chwili znajdują się w USA, co oznacza że klienci z Polski (dla mnie pokarm naturalny) będą mieli opóźnienia w serwowaniu co najmniej 10x większe, niż z Polski i około 5x większe, niż z Niemiec czy Francji. Dla żądań dynamicznych to akurat jest pomijalne, ale statyki przydałoby się dostarczać jak najszybciej.
Czy rzeczywiście AppEngine jest niedrogą, przyjemną, edukacyjną, ale w końcu bez praktycznego znaczenia — zabawką? Jak można zapobiec takiemu rozwojowi wypadków? Zastanawiałem się nad tym poważnie i nie znalazłem zadowalającego rozwiązania:
-
generowanie sztucznego ruchu celem utrzymania bufora modułów, np. 1 żądanie na sekundę tylko po to, żeby wykonały się niezbędne importy, może być drogie jeżeli tych importów jest odpowiednio dużo;
-
specjalna wersja aplikacji na czas rozruchowy, uruchamiana w zwyczajny sposób i na normalnej platformie, wydaje się niepotrzebnie pracochłonne;
-
serwowanie statyków z europejskich serwerów Amazon S3, może poprawić wrażenie za stosunkowo niewielkie pieniądze, ale nie rozwiązuje problemu zimnego startu aplikacji.
Jeżeli ktoś ma jeszcze jakieś pomysły, to chętnie je poznam.