11 listopada 2009
Duże portale - zaawansowane strategie wykorzystania cache
Chciałbym poruszyć temat sztuczek z cache podnoszące User Experience.
Typowy sposób w jaki 99% programistów wykorzystuje cache jest następujący:
- Sprawdź czy strona jest w cache, jeśli tak to -> 4
- Pobierz treści ze źródła danych (np.: baza danych)
- Zaktualizuj cache na podstawie nowych treści
- Zwróć dane do użytkownika
Mechanizm ten jest najprostszy i jak sprawdza się znakomicie, poza kilkoma przypadkami o których chciałbym tutaj napisać.
Namalujmy problem

Poniżej przykładowy wykres obciążenia dla serwisu - oczywiście włączenie cache wygrywa. Niestety wykres przedstawia stan ustalony, czyli serwis działa i ma dużą liczbę odwiedzin (ktoś regularnie odwiedza jego strony). Trzecia linia przedstawia przykładowy czas oczekiwania na odpowiedź serwera dla jednego użytkownika (dodałem jeszcze minimalne opóźnienie transferu). Problemem jest gdy serwis:
- Właśnie został uruchomiony i cache musi się zapełnić (dojść do stanu ustalonego)
- Jest mały i nie ma jeszcze dużej liczby użytkowników.
- Jest uruchomiony na słabym serwerze.
- Ma dużo stron (wyszukiwarka, porównywarka produktów, katalog stron, portal społecznościowy, itd).
Hipotetyczna sytuacja:
Otwieramy agregator wiadomości RSS, który dynamicznie dołącza źródła do swojej strony ('robot' znajduje linki do kanałów RSS, automatycznie je kategoryzuje i dodaje do bazy). Aktualizacja odbywa się co 1h, co oznacza stosunkowo dużą ilość danych do przetworzenia. Użytkownik wchodzi na stronę główną, po czym przegląda wpisy w 20 kategoriach. Przy małej ilości odwiedzin i skompilowanych zapytaniach portal będzie miał 'szybką' stronę główną i wolne podstrony (bardzo rzadko dane będą pobierane z cache, ponieważ dane w cache będą miały krótką ważność).
Zastosuj pewną losowość
Dane w cache posiadają swoją ważność: jeśli ważność jest mniejsza od (aktualny znacznik czasu - znacznik czasu wygenerowany podczas umieszczania danych) to będzie następowała aktualizacja danych w cache. Dla każdego elementu możemy definiować go oddzielnie i tak powinniśmy robić - tylko wtedy możemy sensownie określać jak często dane się zmieniają. Stała ważność ma jedną poważną wadę - jeśli 1000 użytkowników wejdzie na stronę w chwili kiedy upłynie ważność danych w cache, to dany cache będzie 1000 razy aktualizowany, co jest mało pożądaną sytuacją. Ja zakładałem zawsze 10% rozbieżnści (wartość przypadkowa, ale wydaje mi się logiczna) przy definiowaniu ważności informacji: podczas sprawdzania ważności losowo zmieniałem jej wartość +/- 10%. Jest to dodatkowe zabezpieczenie warte uwagi.
Aktualizuj cache 'w tle'
Znając ważność danej informacji możemy łatwo przewidzieć kiedy aktualizacja danych jest wskazana, tak aby dane w cache zaktualizować odpowiednio wcześniej, zanim ich ważność całkiem wygaśnie. Korzyści są obustronne:
- Użytkownik otrzyma dane bardziej aktualne (zaktualizowane wcześniej), niż w przypadku kiedy cache aktualizujemy dopiero po wygaśnięciu danych. Czyli poprawiamy user experiences
- Użytkownik ma większą szansę, że jego dane są w cache, dzięki czemu dostęp do nich jest szybszy. Czyli poprawiamy user experiences.
- Administrator/programista portalu uniknie nagłych skoków obciążenia (może uzależnić szybkość aktualizacji cache od aktualnego obciążenia bazy)
- Administrator/programista portalu nie straci obecnej funkcjonalności cache.
Przyjżyjmy się poniższemu schematowi:

Dotychczasowe działanie aplikacji nie ulega zmianie:
- Użytkownik wysyła zapytanie do serwera.
- Serwer sprawdza, czy ma wymagane informacje w cache.
- Jeśli jest taka potrzeba, to aktualizuje informacje z bazy danych.
- Wysyła odpowiedź do użytkownika.
Dodatkowy mechanizm aktualizacji cache można zrealizować przynajmniej na dwa sposoby:
- Podczas działania aplikacji sprawdzamy czy ważność informacji nie kończy się i jeśli tak, to uruchamiamy 'w tle' (jako osobny wątek) aktualizację cache'a. Jednocześnie użytkownik otrzymuje dane z cache i połączenie jest zakańczane - sam proces zakończy się kiedy dane w cache zostaną zaktualizowane przez dodatkowy wątek.
- Korzystamy za aplikacji działającej niezależnie od aplikacji wysyłającej odpowiedzi do użytkownika. Monitoruje ona ważność informacji w cache i na bieżąco je aktualizuje.
Korzyści z dodatkowej aplikacji są następujące:
- Nie ingeruje w proces przesyłania odpowiedzi do użytkownika.
- Może aktualizować więcej niż jeden cache (jeśli mamy farmę serwerów cache).
- Jak wspominałem: może uwzględniać bieżące obciążenie serwerów i np.: priorytety aktualizacji.
W najlepszej sytuacji są tutaj projekty korzystające z testów jednostkowych, ponieważ automatycznie ich aplikacja jest przygotowana do wdrożenia aktualizacji cache'a w tle.
Powiąż dane w cache i ustal priorytety
Warto powiązać dane znajdujące się w cache za pomocą tagów lub identyfikatorów - dzięki temu aktualizując jakąś część strony możemy automatycznie zaktualizować cache całej strony.
Uwaga: nie polecam tego rozwiązania np.: w przypadku menu - jeśli portal ma dużo stron, to wyrządzimy więcej szkody niż pożytku takim zachowaniem.
Podsumowanie
Nikt nie lubi czekać na otwarcie się stron (kiedy Google zaczęło zwracać 30 wyników zamiast 10, to czas oczekiwania na wynik zwiększył się z 0.4s do 0.9s, a ruch zmniejszył się o 20%). Często ten problem nie jest pokazywany w statystykach (skoro strona główna jest szybka, to współczynnik odrzuceń będzie niewielki) i dopiero statystyki pokazujące osiągnięcie wyznaczonych celów pokazują wydajnościowe problemy portalu. Dopracowanie strategii cachowania pozwali na poprawę odbioru naszego serwisu i pozwala na skupieniu się na ciekawszych rzeczach, czyli wymyślaniu nowych funkcjonalności :-)
Postaram się umieścić przykłady działających implementacji, ale nie mogę obiecać, że nastąpi to szybko.
Zapraszam do komentowania i dyskusji.
5 komentarzy
Wszystko co 'o mnie' znajdziesz na tym blogu :-) Więcej informacji w
U mnie w firmie wykorzystuje się jeszcze inny sposób - "sliding cache". Gdy jeden wątek trafi na nieaktualny cache, "odświeża" (przesuwa TTLa w przyszłość) kesza dla starych danych, a później pobiera nowe i wrzuca je ponownie do keszy.
Ta metoda sprawdziła się, choć zawsze jest sytuacja, że w tej samej chwili x wątków będzie odświeżało dane. Okazało się, że mimo tego nie sprawia to większego problemu w funkcjonowaniu serwisu
@radmen: dokładnie ! Choć lepiej przenieść te operacje na inny komputer, żeby nie obciążać serwera aplikacji.
Zostaje jeszcze problem przepełnienia cache'a (bo przecież normalnie dane by wygasły), ale o tym już w kolejnych wpisach :-)
Ech... miałam nadzieję, że przeczytam coś czego nie wiem/nie zaimplementowaliśmy i będzie można radośnie wdrażać... ;)
Najprostsze co można było wymyślić. Proponuje poczytać o tym co stosuje się w aplikacjach które są naprawdę obciążane...
Polecam http://highscalability.com/
proponuje lekture dwoch draftow RFC:
http://www.mnot.net/drafts/draft-nottingham-http-stale-while-revalidate-00.txt
http://www.mnot.net/drafts/draft-nottingham-http-stale-if-error-00.txt
Jak widac problem ".. jeśli 1000 użytkowników wejdzie na stronę w chwili kiedy upłynie ważność danych w cache, to dany cache będzie 1000 razy aktualizowany .." jest juz rozwiazany i np. taki nginx lub squid posiada support dla tych flag.