Dzień dobry? Jestem Jayon.
Dzisiaj chciałbym wyjaśnić kryteria konfigurowania pamięci podręcznej. To wpis oparty na moim doświadczeniu w pracy, więc traktujcie go raczej jako inspirację ㅎㅎ
Czym jest pamięć podręczna?
Pamięć podręczna to mechanizm, który przechowuje wyniki przyszłych żądań, aby móc je szybko obsłużyć. Innymi słowy, przechowuje wyniki z góry, a gdy nadejdzie żądanie, zamiast odwoływać się do bazy danych (DB) lub interfejsu API, odwołuje się do pamięci podręcznej i przetwarza żądanie. Za powstaniem tego mechanizmu stoi zasada Pareto.
Zasada Pareto mówi, że 80% wyników jest generowanych przez 20% przyczyn. Dobrze jest zapoznać się z poniższym obrazkiem!
Oznacza to, że nie ma potrzeby buforowania wszystkich wyników, a jedynie 20% najczęściej używanych, co pozwala na ogólną poprawę wydajności.
Jakie dane należy buforować?
Zgodnie z zasadą Pareto, nie należy buforować dowolnych danych, a jedynie te, które są naprawdę potrzebne. Jakie więc dane należy buforować?
Dane, które są często odczytywane, ale rzadko zapisywane
W teorii często mówi się, że „należy buforować dane, które są często odczytywane, ale rzadko zapisywane”, ale kryteria „częstego odczytu” i „rzadkiego zapisu” były dość niejasne.
Dlatego ja badam dane do buforowania w następujących krokach.
- Za pomocą APM, takiego jak DataDog, sprawdzam 5 najczęstszych wywołań zapytań do RDB.
- Wśród nich znajduję zapytania odczytu i sprawdzam, z jakiej tabeli są pobierane dane.
- Sprawdzam, jak często wywoływane są zapytania aktualizujące daną tabelę.
W ten sposób sprawdzam, czy dane są często odczytywane, ale rzadko aktualizowane. W tabeli, którą sprawdziłem w praktyce, zapytania odczytu były wywoływane 1,74 miliona razy dziennie, a zapytania aktualizujące maksymalnie 500 razy. To idealne warunki do buforowania ㅎㅎ
Dane wrażliwe na aktualizacje
Dane wrażliwe na aktualizacje to takie, w których różnica między RDB a pamięcią podręczną musi być krótka. Na przykład w przypadku informacji związanych z płatnościami aktualizacja jest bardzo ważna, więc nawet jeśli spełnia powyższe warunki buforowania, należy rozważyć jej zastosowanie.
Musiałem zastosować buforowanie do tabel związanych z płatnościami, które spełniały oba powyższe warunki. Dlatego nie zastosowałem buforowania do wszystkich logik korzystających z tych tabel, ale zdecydowałem się na częściowe buforowanie w stosunkowo bezpiecznych logikach, w których płatności nie są wykonywane.
Buforowanie lokalne vs. globalne
Teraz już wiemy, jakie dane i w jakim zakresie należy buforować. Należy jednak zastanowić się, „gdzie” należy przechowywać buforowane dane. Zazwyczaj można je zapisywać w pamięci lokalnej lub na osobnym serwerze, takim jak Redis.
Buforowanie lokalne
Buforowanie lokalne polega na zapisywaniu buforowanych danych w pamięci serwera aplikacji. Często wykorzystuje się w tym celu biblioteki takie jak Guava Cache lub Caffeine Cache.
Zalety
- Ponieważ podczas wykonywania logiki aplikacji można od razu odwoływać się do pamięci podręcznej na tym samym serwerze, jest to bardzo szybkie.
- Łatwe w implementacji.
Wady
- W przypadku wielu instancji pojawia się kilka problemów.
Buforowanie globalne
Buforowanie globalne polega na użyciu osobnego serwera do przechowywania buforowanych danych, takiego jak Redis.
Zalety
- Ponieważ instancje współdzielą pamięć podręczną, zmiana pamięci podręcznej na jednej instancji jest odzwierciedlana na wszystkich innych instancjach.
- Po uruchomieniu nowej instancji wystarczy, że będzie ona odwoływać się do istniejącego magazynu pamięci podręcznej, nie ma potrzeby ponownego ładowania danych.
Wady
- Ponieważ konieczne jest przejście przez sieć, jest wolniejsze niż buforowanie lokalne.
- Wymaga użycia osobnego serwera pamięci podręcznej, co generuje dodatkowe koszty infrastruktury.
Którą opcję wybrałem?
Obecnie serwer aplikacji mojej firmy działa w strukturze z wieloma instancjami, ale wybrałem buforowanie lokalne.
Główne powody to trzy.
- Buforowanych danych w RDB jest nieco mniej niż 40 000, a załadowanie ich wszystkich do pamięci zajmuje mniej niż 4 MB.
- Wymagana była wysoka wydajność odczytu danych związanych z płatnościami.
- Chociaż Redis jest już dostępny, przechowywanie nowych danych w pamięci podręcznej w Redisie generuje dodatkowe koszty infrastruktury.
Jak aktualizować pamięć podręczną?
Jeśli serwer aplikacji ma wiele instancji, a zastosowano buforowanie lokalne, wartości pamięci podręcznej na każdej instancji mogą się różnić. Na przykład wartość danych w pamięci podręcznej na serwerze A może wynosić „1”, a na serwerze B „2”, ponieważ została zmieniona na serwerze B. W takiej sytuacji użytkownik wysyłający żądanie do load balancera otrzyma różne wartości z serwera A i B.
Dlatego należy skonfigurować automatyczne usuwanie pamięci podręcznej na każdej instancji i odwoływanie się do RDB w celu pobrania danych. W tym celu często stosuje się TTL.
Jaka powinna być wartość TTL?
TTL (Time To Live) to ustawienie automatycznego usuwania pamięci podręcznej po określonym czasie. Na przykład, jeśli ustawimy TTL na 5 sekund, dane w pamięci podręcznej zostaną automatycznie usunięte po 5 sekundach. Następnie, jeśli wystąpi chybione trafienie w pamięci podręcznej, dane zostaną pobrane z RDB i zapisane.
Jaka więc powinna być wartość TTL?
Odczyt/zapis na jednym serwerze pamięci podręcznej
Jeśli odczyt/zapis odbywa się na jednym serwerze pamięci podręcznej, takim jak Redis, lub na jednym serwerze aplikacji z włączonym buforowaniem lokalnym, wartość TTL może być większa niż jednostka czasu. I tak podczas zapisu zostanie zaktualizowana istniejąca pamięć podręczna, a serwer pobierający dane z tej pamięci podręcznej zawsze będzie otrzymywał zaktualizowane dane.
W takim przypadku można nie ustawiać TTL, a jedynie skonfigurować automatyczne usuwanie części pamięci podręcznej za pomocą algorytmu LRU, gdy pamięć podręczna jest pełna.
Odczyt/zapis na wielu serwerach pamięci podręcznej
Jeśli odczyt/zapis odbywa się na wielu serwerach pamięci podręcznej lub na wielu serwerach aplikacji z włączonym buforowaniem lokalnym, lepiej ustawić TTL na sekundy lub minuty. Dzieje się tak, ponieważ istnieje ryzyko odczytania nieaktualnych danych z serwera pamięci podręcznej, który jeszcze nie odzwierciedla zmian.
Wartość TTL zależy od różnych kontekstów. Im ważniejsza jest aktualizacja i większe prawdopodobieństwo zmiany wartości, tym krótszy powinien być TTL. Im mniej ważna jest aktualizacja i mniejsze prawdopodobieństwo zmiany wartości, tym dłuższy może być TTL.
Jak ustawiłem TTL?
Buforowane przeze mnie dane dotyczą płatności, a nawet jeśli nie zastosuję buforowania do ścisłych logik przetwarzania płatności, to ze względu na charakter płatności aktualizacja jest ważna. Jednak prawdopodobieństwo aktualizacji jest niskie, więc dla bezpieczeństwa ustawiłem TTL na 5 sekund.
Podsumowanie
Podsumowując, wybrany przeze mnie sposób buforowania wygląda następująco:
- Dane dotyczące płatności
- Bardzo częste odczyty, ale rzadkie aktualizacje.
- Buforowanie zastosowane tylko w logikach, w których płatności nie są wykonywane, ale występują odczyty.
- Zastosowano buforowanie lokalne z TTL ustawionym na 5 sekund.
Następnym krokiem jest przeprowadzenie testów wydajności dla wdrożonego sposobu buforowania. Nadal zastanawiam się, jak dokładnie przeprowadzić testy wydajności, więc opiszę to w kolejnym wpisie!
Komentarze0