Strategie buforowania: lazy loading i write-through
Implementować lazy loading w celu zapełniania pamięci podręcznej po nieudanym odczycie oraz write-through, aby pamięć podręczna pozostawała spójna przy każdym zapisie do bazy danych.
Strategie buforowania: lazy loading i write-through to bezpłatna lekcja AWS Solutions Architect na CoddyKit. To lekcja 3 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej AWS Solutions Architect, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs AWS Solutions Architect zawiera 4 lekcji w sumie.
Znaczenie strategii buforowania
Umieszczenie pamięci podręcznej między aplikacją a bazą danych wymaga zastosowania strategii buforowania — zestawu reguł określających, kiedy dane są umieszczane w pamięci podręcznej, kiedy są z niej odczytywane i kiedy są usuwane. Wybór niewłaściwej strategii prowadzi do wystąpienia nieaktualnych danych (pamięć podręczna zwraca przestarzałe wartości), chybionych odczytów w pustej pamięci podręcznej (pamięć podręczna jest pusta, więc każde żądanie trafia do bazy danych) albo lawiny żądań do pamięci podręcznej (wiele jednoczesnych żądań dotyczących tego samego brakującego klucza trafia jednocześnie do bazy danych). Dwie podstawowe strategie to lazy loading i write-through.
Lazy loading (cache-aside): działanie
Lazy loading (nazywane również cache-aside) to najczęściej stosowany wzorzec buforowania. Aplikacja najpierw sprawdza pamięć podręczną. W przypadku cache hit dane są zwracane bezpośrednio z pamięci podręcznej — jest to szybka ścieżka. W przypadku cache miss aplikacja pobiera dane z bazy danych, zapisuje wynik w pamięci podręcznej z ustawionym TTL-em i zwraca go wywołującemu. Pamięć podręczna jest zapełniana tylko danymi, o które rzeczywiście wystąpiono — stąd określenie „lazy”. Przy kolejnym żądaniu dotyczącym tego samego klucza dane będą już znajdować się w pamięci podręcznej.
# Lazy loading pattern (Python with redis-py)
def get_user(user_id, redis_client, db):
cache_key = f'user:{user_id}'
# 1. Check cache
cached = redis_client.get(cache_key)
if cached:
return json.loads(cached) # Cache HIT
# 2. Cache MISS: fetch from DB
user = db.query('SELECT * FROM users WHERE id = %s', user_id)
# 3. Populate cache with TTL of 300 seconds
redis_client.setex(cache_key, 300, json.dumps(user))
return userLazy loading: zalety
Lazy loading ma trzy kluczowe zalety: buforowane są tylko żądane dane — pamięć podręczna nie jest zapełniana danymi, których nikt nie odczytuje, dzięki czemu pamięć jest wykorzystywana efektywnie. Pusta pamięć podręczna nie powoduje awarii aplikacji — w przypadku cache miss aplikacja korzysta z bazy danych, więc nawet po ponownym uruchomieniu ElastiCache lub awarii węzła aplikacja nadal działa (choć z większym opóźnieniem). Pamięć podręczna jest zawsze ostatecznie spójna z bazą danych, ponieważ nieaktualne dane wygasają za pośrednictwem TTL, nawet jeśli nie zarejestrowano aktualizacji.
Lazy loading: wady
Lazy loading ma trzy kluczowe wady: cache miss jest kosztowny — obejmuje trzy operacje (sprawdzenie pamięci podręcznej, odczyt z bazy danych i zapis w pamięci podręcznej), podczas gdy cache hit wymaga tylko jednej, co powoduje większe opóźnienie przy pierwszych żądaniach. Nieaktualne dane — po aktualizacji bazy danych pamięć podręczna nadal zwraca starą wartość do czasu wygaśnięcia TTL lub jawnego unieważnienia klucza (jest to okno niespójności pamięci podręcznej). Lawina żądań do pamięci podręcznej — jeśli popularny klucz wygaśnie, wiele jednoczesnych żądań otrzyma cache miss i równocześnie odwoła się do bazy danych, potencjalnie ją przeciążając.
# Mitigating cache stampede with a probabilistic early expiration
# (Refresh the key before it expires to avoid simultaneous misses)
def get_with_stampede_protection(key, redis_client, db_fetch_fn, ttl=300):
value = redis_client.get(key)
ttl_remaining = redis_client.ttl(key)
# Probabilistically refresh before expiry
if value is None or (ttl_remaining < 30 and random.random() < 0.1):
value = db_fetch_fn()
redis_client.setex(key, ttl, json.dumps(value))
return json.loads(value)Write-through: działanie
W strategii write-through każdy zapis do bazy danych jest jednocześnie zapisywany w pamięci podręcznej. Aplikacja zapisuje dane zarówno w pamięci podręcznej, jak i w bazie danych w ramach tej samej operacji (albo baza danych wyzwala aktualizację pamięci podręcznej). Dzięki temu pamięć podręczna jest zawsze zsynchronizowana z bazą danych — nie występuje okno nieaktualności danych. Write-through gwarantuje, że dane w pamięci podręcznej są zawsze aktualne, dzięki czemu kolejne odczyty niedawno zapisanych danych zawsze kończą się wynikiem cache hit.
# Write-through pattern (Python)
def update_user(user_id, user_data, redis_client, db):
# 1. Write to database FIRST
db.execute('UPDATE users SET name=%s WHERE id=%s',
(user_data['name'], user_id))
# 2. Update cache immediately (write-through)
cache_key = f'user:{user_id}'
redis_client.setex(cache_key, 3600, json.dumps(user_data))
return user_data
# Every read is now a cache hit for recently updated dataWrite-through: zalety
Zalety strategii write-through: dane w pamięci podręcznej są zawsze aktualne — nie ma nieaktualnych danych, ponieważ pamięć podręczna jest aktualizowana przy każdym zapisie. Odczyty są zawsze szybkie — popularne dane, które są często zapisywane, zawsze znajdują się w pamięci podręcznej. Brak lawiny żądań przy odczytach — ponieważ dane są wstępnie umieszczane w pamięci podręcznej przed odczytem, niedawno zapisane dane nie powodują cache miss. Strategia ta doskonale sprawdza się w przypadku obciążeń z przewagą odczytów i częstymi aktualizacjami, gdy aktualność danych ma kluczowe znaczenie, na przykład w katalogach produktów, systemach cenowych lub pamięciach podręcznych profili użytkowników.
Write-through: wady
Wady strategii write-through: koszt zapisu — każdy zapis wymaga dwóch operacji (w bazie danych i w pamięci podręcznej), co zwiększa opóźnienie zapisów. Zaśmiecanie pamięci podręcznej — dane są buforowane nawet wtedy, gdy nigdy nie zostaną ponownie odczytane (zapisane raz i nigdy nieżądane), co marnuje pamięć podręczną. Ponowne uruchomienie pamięci podręcznej oznacza jej opróżnienie — po ponownym uruchomieniu klastra pamięci podręcznej wszystkie zapisane wstępnie dane zostają utracone, a pamięć podręczna musi zostać ponownie zapełniona przez zapisy lub proces rozgrzewania. Należy połączyć write-through z TTL-em, aby zapobiec nieograniczonemu wzrostowi ilości rzadko odczytywanych danych w pamięci podręcznej.
Łączenie lazy loading i write-through
W praktyce wiele systemów produkcyjnych łączy obie strategie: write-through dla danych często aktualizowanych i często odczytywanych (takich jak sesje użytkowników lub bieżące ceny) oraz lazy loading dla danych rzadko aktualizowanych i często odczytywanych (takich jak opisy produktów lub treść artykułów). Należy ustawić odpowiednie wartości TTL dla obu strategii: klucze write-through powinny mieć długi TTL, ponieważ dane są zawsze aktualne, natomiast klucze ładowane leniwie powinny mieć krótszy TTL, aby ograniczyć czas, przez który mogą występować nieaktualne dane. Takie podejście hybrydowe maksymalizuje współczynnik cache hit, jednocześnie minimalizując nieaktualność danych.
# Hybrid: write-through for sessions, lazy loading for product data
# Write-through for session data (always fresh, critical)
def save_session(session_id, data, redis_client, db):
db.upsert('sessions', session_id, data)
redis_client.setex(f'session:{session_id}', 3600, json.dumps(data))
# Lazy loading for product catalog (infrequent updates OK)
def get_product(product_id, redis_client, db):
cached = redis_client.get(f'product:{product_id}')
if cached:
return json.loads(cached)
product = db.query('SELECT * FROM products WHERE id=%s', product_id)
redis_client.setex(f'product:{product_id}', 86400, json.dumps(product))
return productZasady projektowania TTL
Time-To-Live (TTL) danych w pamięci podręcznej określa maksymalny czas, przez który dane mogą być nieaktualne, oraz zajętość pamięci podręcznej. Wartości TTL należy dobierać na podstawie: częstotliwości aktualizacji danych (dane sesji zmieniają się często → krótki TTL; treści statyczne zmieniają się rzadko → długi TTL), tolerancji na nieaktualność (ceny finansowe → bardzo krótki TTL; treść wpisu na blogu → godziny lub dni) oraz pojemności pamięci podręcznej (mało pamięci → krótszy TTL, aby szybciej usuwać nieaktualne dane). Zawsze należy ustawiać TTL — nigdy nie należy buforować danych bez terminu wygaśnięcia, ponieważ pamięć podręczna ostatecznie zapełni się nieaktualnymi danymi.
# TTL examples by data type
# API rate limit counter: 60 seconds
redis_client.setex(f'ratelimit:{ip}', 60, count)
# User session: 30 minutes
redis_client.setex(f'session:{id}', 1800, json.dumps(session))
# Product catalog: 24 hours
redis_client.setex(f'product:{id}', 86400, json.dumps(product))
# Stock price: 10 seconds
redis_client.setex(f'price:{symbol}', 10, price)
# Static site content: 7 days
redis_client.setex(f'page:{slug}', 604800, html_content)Unieważnianie pamięci podręcznej podczas aktualizacji
Zamiast polegać wyłącznie na wygaśnięciu TTL, mogą Państwo jawnie unieważniać (usuwać) klucze pamięci podręcznej, gdy zmieniają się dane bazowe. Eliminuje to całe okno nieaktualności danych. Typowe wzorce to: delete-on-write (usuwanie klucza pamięci podręcznej po każdej aktualizacji bazy danych — następny odczyt ponownie zapełnia pamięć podręczną za pomocą lazy loading) oraz unieważnianie sterowane zdarzeniami (DynamoDB Streams lub przechwytywanie zmian w RDS wyzwala funkcję Lambda, która usuwa zmienione klucze). Unieważnianie pamięci podręcznej jest jednym z najtrudniejszych problemów w systemach rozproszonych; im prostsza logika unieważniania, tym bardziej niezawodna pamięć podręczna.
# Delete-on-write invalidation pattern
def update_product(product_id, new_data, redis_client, db):
# Update the database
db.execute('UPDATE products SET ... WHERE id=%s', (product_id,))
# Invalidate the cache key
redis_client.delete(f'product:{product_id}')
# Also invalidate any list/search caches that may include this product
redis_client.delete('products:list:page:1')
redis_client.delete(f'products:category:{new_data["category_id"]}')
# Next read will trigger lazy loading with fresh dataBuforowanie write-behind (write-back)
Mniej powszechnym, ale skutecznym wzorcem jest write-behind (write-back): aplikacja zapisuje dane wyłącznie w pamięci podręcznej, a pamięć podręczna asynchronicznie przesyła je w tle do bazy danych. Zapewnia to niezwykle szybkie zapisy (wykonywane wyłącznie w pamięci), ale wiąże się z ryzykiem utraty danych, jeśli pamięć podręczna ulegnie awarii przed ich przesłaniem. Write-behind sprawdza się w przypadku częstych zapisów o dużej objętości, gdy dane można odtworzyć lub niewielkie straty są akceptowalne — na przykład w licznikach wyświetleń, zdarzeniach analitycznych lub aktualizacjach wyników w grach. ElastiCache nie obsługuje natywnie write-behind; należy zaimplementować je na poziomie aplikacji.
# Write-behind pattern: write to cache, flush to DB asynchronously
# Application writes:
# redis_client.incr('post:42:views') # fast, in-memory only
# Background job (runs every 60 seconds):
def flush_view_counts(redis_client, db):
for key in redis_client.scan_iter('post:*:views'):
count = redis_client.getdel(key) # atomic get-and-delete
post_id = key.split(':')[1]
db.execute('UPDATE posts SET views = views + %s WHERE id = %s',
(int(count), post_id))Szybkie sprawdzenie
Sprawdź swoją znajomość zagadnień AWS Solutions Architect (SAA-C03) omawianych w tej lekcji.
Podsumowanie lekcji
W tej lekcji nauczyli się Państwo, że lazy loading zapełnia pamięć podręczną po chybionych odczytach i efektywnie wykorzystuje pamięć, ale może zwracać nieaktualne dane do czasu wygaśnięcia TTL, write-through aktualizuje pamięć podręczną przy każdym zapisie, eliminując nieaktualne dane, ale marnuje pamięć na dane nieodczytywane, a jawne unieważnianie usuwa klucze pamięci podręcznej podczas aktualizacji bazy danych, eliminując okna nieaktualności. W następnej części omówimy przechowywanie sesji i wzorce tabel wyników z użyciem ElastiCache.
Często zadawane pytania
Czy lekcja „Strategie buforowania: lazy loading i write-through” jest bezpłatna?
Tak — pełny tekst „Strategie buforowania: lazy loading i write-through” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu AWS Solutions Architect, przejdź na CoddyKit PRO. Kurs AWS Solutions Architect zawiera 4 lekcji w sumie.
Co nauczysz się w „Strategie buforowania: lazy loading i write-through”?
Implementować lazy loading w celu zapełniania pamięci podręcznej po nieudanym odczycie oraz write-through, aby pamięć podręczna pozostawała spójna przy każdym zapisie do bazy danych. Ćwiczysz AWS Solutions Architect z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć AWS Solutions Architect?
Nie wymagamy żadnego doświadczenia. AWS Solutions Architect w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 3 z 4.
Ile czasu zajmuje lekcja „Strategie buforowania: lazy loading i write-through”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji AWS Solutions Architect?
Tak. Każda lekcja AWS Solutions Architect zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Redis a Memcached: wybór właściwego silnika
- Grupy replikacji Redis ElastiCache i tryb klastra
- Strategie buforowania: lazy loading i write-through
- Przechowywanie sesji i wzorce tabel wyników