AWS Solutions Architect · Lektion

Cachingstrategier: Lazy Loading og Write-Through

Implementér Lazy Loading for at udfylde cachen ved cache-mis, og Write-Through for at holde cachen konsistent med hver databaseskrivning

Lektion 3 af 413 trin

Cachingstrategier: Lazy Loading og Write-Through er en gratis AWS Solutions Architect-lektion på CoddyKit. Dette er lektion 3 af 4. Du kan læse alle 3 lektioner i dette læringsspor gratis i deres fulde længde — derefter låser CoddyKit PRO alle lektioner op samt praktiske øvelser med en indbygget kodeeditor og en AI-underviser døgnet rundt. Den er en del af læringsforløbet i AWS Solutions Architect, og dine fremskridt synkroniseres på tværs af nettet og CoddyKit-appen. AWS Solutions Architect-kurset indeholder 4 lektioner i alt.

Hvorfor cachestrategier er vigtige

Når du indsætter en cache mellem din applikation og database, har du brug for en cachestrategi — et sæt regler, der bestemmer, hvornår data lægges i cachen, hvornår de læses fra cachen, og hvornår de fjernes. Hvis du vælger den forkerte strategi, kan det føre til enten forældede data (cachen returnerer forældede værdier), cache-misses i en tom cache (cachen er tom, og hver forespørgsel rammer databasen) eller cache-stormløb (mange samtidige forespørgsler efter den samme manglende nøgle rammer databasen samtidig). De to grundlæggende strategier er lazy loading og write-through.

Lazy loading (cache-aside): Sådan fungerer det

Lazy loading (også kaldet cache-aside) er det mest almindelige cachemønster. Applikationen tjekker først cachen. Ved et cache-træf returneres dataene direkte fra cachen — den hurtige vej. Ved et cache-miss henter applikationen data fra databasen, skriver resultatet til cachen med en TTL og returnerer det til den kaldende. Cachen fyldes kun med data, der faktisk efterspørges — deraf betegnelsen »lazy«. Den næste forespørgsel efter den samme nøgle finder den i cachen.

# 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 user

Lazy loading: Fordele

Lazy loading har tre vigtige fordele: Kun efterspurgte data caches — cachen fyldes ikke med data, som ingen læser, så hukommelsen udnyttes effektivt. En tom cache får ikke applikationen til at gå ned — ved cache-misses falder applikationen tilbage til databasen, så applikationen fortsætter med at fungere, selv hvis ElastiCache genstartes, eller en node svigter (dog med højere latenstid). Cachen er altid eventual consistent med databasen, fordi forældede data udløber via TTL, selv hvis opdateringer blev overset.

Lazy loading: Ulemper

Lazy loading har tre vigtige ulemper: Cache-misses er dyre — tre handlinger (cache-tjek, databaselæsning og cacheskrivning) i stedet for én ved et cache-træf, hvilket giver højere latenstid for kolde forespørgsler. Forældede data — efter en databaseopdatering leverer cachen stadig den gamle værdi, indtil TTL udløber, eller nøglen udtrykkeligt ugyldiggøres (vinduet med inkonsistente cachedata). Cache-stormløb — hvis en populær nøgle udløber, oplever mange samtidige forespørgsler et cache-miss og rammer databasen samtidig, hvilket potentielt kan overbelaste den.

# 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: Sådan fungerer det

I strategien write-through skrives enhver skrivning til databasen også til cachen samtidig. Applikationen skriver til både cachen og databasen som en del af den samme handling (eller databasen udløser en cacheopdatering). Cachen er derfor altid synkroniseret med databasen — der er ikke noget vindue med forældede data. Write-through garanterer, at data i cachen altid er opdaterede, så efterfølgende læsninger altid giver cache-træf for data, der for nylig er skrevet.

# 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 data

Write-through: Fordele

Fordele ved write-through: Cachedata er altid aktuelle — der er ingen forældede data, fordi cachen opdateres ved hver skrivning. Læsninger er altid hurtige — populære data, der skrives ofte, er altid i cachen. Ingen cache-stormløb ved læsninger — fordi data udfyldes på forhånd, før de læses, er der ingen kolde cache-misses for nyligt skrevne data. Denne strategi er ideel til læsetunge arbejdsbelastninger med hyppige opdateringer, hvor dataaktualitet er afgørende, f.eks. produktkataloger, prissystemer eller caches med brugerprofiler.

Write-through: Ulemper

Ulemper ved write-through: Skriveomkostning — hver skrivning medfører to handlinger (database + cache), hvilket øger latenstiden for skrivninger. Cacheforurening — data caches, selv om de aldrig læses igen (skrevet én gang, aldrig efterspurgt), så cachehukommelse spildes. Genstart af cachen giver en tom cache — hvis cacheklyngen genstartes, går alle de proaktivt skrevne data tabt, og cachen skal udfyldes igen gennem skrivninger eller en opvarmningsproces. Kombinér write-through med en TTL for at forhindre ubegrænset vækst af sjældent læste cachedata.

Kombination af lazy loading og write-through

I praksis kombinerer mange produktionssystemer begge strategier: Brug write-through til data, der opdateres og læses ofte (f.eks. brugersessioner eller aktuelle priser) og lazy loading til data, der opdateres sjældent, men læses ofte (f.eks. produktbeskrivelser eller artikelindhold). Angiv passende TTL'er for begge: write-through-nøgler får en lang TTL, fordi dataene altid er aktuelle, mens lazy-loaded nøgler får en kortere TTL for at begrænse vinduet med forældede data. Denne hybride tilgang maksimerer cache-træffrekvensen og minimerer forældelse.

# 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 product

Principper for TTL-design

Levetiden (TTL) for cachede data styrer det maksimale vindue med forældede data og cachehukommelsens pladsforbrug. Design TTL'er ud fra: dataenes opdateringsfrekvens (sessionsdata ændres ofte → kort TTL; statisk indhold ændres sjældent → lang TTL), tolerancen for forældelse (finansielle priser → meget kort; blogindlæg → timer eller dage) og cachehukommelsens kapacitet (lav hukommelse → kortere TTL, så forældede data fjernes hurtigere). Angiv altid en TTL — cachelagr aldrig data uden udløb, ellers vil cachen til sidst blive fyldt med forældede data.

# 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)

Ugyldiggørelse af cache ved opdatering

I stedet for udelukkende at stole på TTL-udløb kan du udtrykkeligt ugyldiggøre (slette) cachenøgler, når de underliggende data ændres. Det fjerner hele vinduet med forældede data. Almindelige mønstre er: sletning ved skrivning (slet cachenøglen efter hver databaseopdatering — den næste læsning udfylder den igen via lazy loading) og hændelsesdrevet ugyldiggørelse (DynamoDB Streams eller ændringsopsamling fra RDS udløser en Lambda, der sletter de berørte nøgler). Ugyldiggørelse af cache er et af de vanskeligste problemer i distribuerede systemer; jo enklere logikken for ugyldiggørelse er, desto mere pålidelig er din cache.

# 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 data

Write-behind-caching (write-back)

Et mindre almindeligt, men effektivt mønster er write-behind (write-back): Applikationen skriver kun til cachen, og cachen overfører asynkront data til databasen i baggrunden. Det giver ekstremt hurtige skrivninger (kun i hukommelsen) på bekostning af risikoen for datatab, hvis cachen svigter, før dataene er overført. Write-behind egner sig til hyppige skrivninger i stort omfang, hvor dataene kan genskabes, eller hvor små tab kan accepteres — f.eks. tællere for besøg, analysehændelser eller opdateringer af spilresultater. ElastiCache understøtter ikke write-behind direkte; det skal implementeres i applikationslaget.

# 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))

Hurtigt tjek

Test din forståelse af AWS Solutions Architect-koncepterne (SAA-C03) fra denne lektion.

Opsummering af lektionen

I denne lektion har du lært, at lazy loading udfylder cachen ved læsemisses og udnytter hukommelsen effektivt, men kan levere forældede data, indtil TTL udløber, at write-through opdaterer cachen ved hver skrivning, så der ikke er forældede data, men spilder hukommelse på ulæste data, og at udtrykkelig ugyldiggørelse sletter cachenøgler ved databaseopdateringer for at fjerne vinduer med forældede data. Næste gang undersøger vi mønstre for sessionslagring og ranglister med ElastiCache.

Gratis at komme i gang

Lær AWS Solutions Architect med en AI-underviser — gratis

Skriv og kør rigtig kode i din browser, få øjeblikkelig hjælp fra en AI-underviser døgnet rundt, og fortsæt, hvor du slap, på web eller i appen.

Kurser
30
Lektioner
120

Ofte stillede spørgsmål

Er lektionen “Cachingstrategier: Lazy Loading og Write-Through” gratis?

Ja — alle 3 lektioner i læringssporet AWS Solutions Architect, inklusive “Cachingstrategier: Lazy Loading og Write-Through”, kan læses gratis i deres fulde længde her på webstedet. Derefter låser CoddyKit PRO alle lektioner op samt interaktive øvelser med en indbygget kodeeditor og en AI-underviser døgnet rundt. AWS Solutions Architect-kurset indeholder 4 lektioner i alt.

Hvad lærer jeg i “Cachingstrategier: Lazy Loading og Write-Through”?

Implementér Lazy Loading for at udfylde cachen ved cache-mis, og Write-Through for at holde cachen konsistent med hver databaseskrivning Du øver dig i AWS Solutions Architect med praktisk kode, som du kører direkte i browseren, og en AI-vejleder døgnet rundt besvarer dine spørgsmål, mens du arbejder dig gennem lektionen.

Skal jeg have erfaring for at begynde på AWS Solutions Architect?

Der kræves ingen tidligere erfaring. AWS Solutions Architect på CoddyKit er tilrettelagt for både begyndere og øvede, så du kan starte her eller fra begyndelsen og lære i dit eget tempo. Dette er lektion 3 af 4.

Hvor lang tid tager lektionen “Cachingstrategier: Lazy Loading og Write-Through”?

De fleste CoddyKit-lektioner tager cirka 5–10 minutter. Hver lektion er kort og interaktiv, så du gør løbende fremskridt og kan fortsætte, hvor du slap – på både web og app.

Kan jeg skrive og køre kode i denne AWS Solutions Architect-lektion?

Ja. Alle AWS Solutions Architect-lektioner har en indbygget kodeeditor, så du kan skrive og køre rigtig kode direkte i din browser og få øjeblikkelig feedback fra AI – uden lokal opsætning.

Alle lektioner i dette kursus

  1. Redis kontra Memcached: Valg af den rigtige motor
  2. ElastiCache Redis-replikationsgrupper og klyngetilstand
  3. Cachingstrategier: Lazy Loading og Write-Through
  4. Sessionslagring og leaderboard-mønstre
← Tilbage til AWS Solutions Architect