0Pricing
AWS Solutions Architect · Lezione

Strategie di caching: lazy loading e write-through

Implementare il lazy loading per popolare la cache in caso di miss e il write-through per mantenere la cache coerente a ogni scrittura nel database

Strategie di caching: lazy loading e write-through è una lezione AWS Solutions Architect gratuita su CoddyKit. Questa è la lezione 3 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento AWS Solutions Architect, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso AWS Solutions Architect include 4 lezioni in totale.

Perché le strategie di caching sono importanti

Inserire una cache tra l'applicazione e il database richiede una strategia di caching, ovvero un insieme di regole che stabilisce quando i dati vengono inseriti nella cache, quando vengono letti dalla cache e quando vengono rimossi. La scelta della strategia sbagliata può causare dati obsoleti (la cache restituisce valori non aggiornati), cache miss a cache fredda (la cache è vuota e ogni richiesta raggiunge il database) oppure un cache stampede (molte richieste simultanee per la stessa chiave mancante raggiungono contemporaneamente il database). Le due strategie fondamentali sono lazy loading e write-through.

Lazy loading (cache-aside): funzionamento

Il lazy loading (chiamato anche cache-aside) è il pattern di caching più comune. L'applicazione controlla prima la cache. In caso di cache hit, i dati vengono restituiti direttamente dalla cache, seguendo il percorso più rapido. In caso di cache miss, l'applicazione recupera i dati dal database, scrive il risultato nella cache con un TTL e lo restituisce al chiamante. La cache viene popolata solo con i dati effettivamente richiesti, da cui il termine «lazy». La richiesta successiva per la stessa chiave troverà i dati nella cache.

# 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: vantaggi

Il lazy loading presenta tre vantaggi principali: vengono memorizzati nella cache solo i dati richiesti, quindi la cache non si riempie di dati che nessuno legge e la memoria viene utilizzata in modo efficiente. Una cache fredda non compromette il funzionamento dell'applicazione: in caso di cache miss, l'applicazione ricorre al database; pertanto, anche se ElastiCache viene riavviato o un nodo si guasta, l'applicazione continua a funzionare, seppure con una latenza maggiore. Infine, la cache è sempre eventualmente coerente con il database, perché i dati obsoleti scadono tramite TTL anche se alcuni aggiornamenti non sono stati intercettati.

Lazy loading: svantaggi

Il lazy loading presenta tre svantaggi principali: i cache miss sono costosi: richiedono tre operazioni (controllo della cache, lettura del database e scrittura nella cache) rispetto a una sola in caso di hit, con una latenza maggiore per le richieste a cache fredda. Dati obsoleti: dopo un aggiornamento del database, la cache continua a restituire il vecchio valore fino alla scadenza del TTL o all'invalidazione esplicita della chiave, creando una finestra di incoerenza. Cache stampede: se una chiave molto richiesta scade, molte richieste simultanee registrano un cache miss e raggiungono contemporaneamente il database, rischiando di sovraccaricarlo.

# 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: funzionamento

Nella strategia write-through, ogni scrittura nel database viene eseguita contemporaneamente anche nella cache. L'applicazione scrive sia nella cache sia nel database come parte della stessa operazione, oppure il database attiva un aggiornamento della cache. La cache è quindi sempre sincronizzata con il database e non esiste una finestra in cui i dati siano obsoleti. Write-through garantisce che i dati presenti nella cache siano sempre aggiornati, rendendo le letture successive dei dati scritti di recente sempre dei 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 data

Write-through: vantaggi

Vantaggi di write-through: i dati nella cache sono sempre aggiornati, perché la cache viene aggiornata a ogni scrittura e non sono presenti dati obsoleti. Le letture sono sempre rapide: i dati più richiesti e aggiornati frequentemente si trovano sempre nella cache. Non si verificano cache stampede durante le letture: poiché i dati vengono precaricati prima di essere letti, non si verificano cache miss a cache fredda per i dati scritti di recente. Questa strategia è ideale per carichi di lavoro con molte letture e aggiornamenti frequenti in cui la freschezza dei dati è fondamentale, ad esempio cataloghi di prodotti, sistemi di prezzi o cache dei profili utente.

Write-through: svantaggi

Svantaggi di write-through: penalizzazione delle scritture: ogni scrittura comporta due operazioni (database + cache), aumentando la latenza delle scritture. Inquinamento della cache: i dati vengono memorizzati nella cache anche se non saranno mai più letti, sprecando memoria. Il riavvio della cache produce una cache fredda: se il cluster della cache viene riavviato, tutti i dati scritti preventivamente vanno persi e la cache deve essere ripopolata tramite nuove scritture o mediante una procedura di riscaldamento. Combini write-through con un TTL per evitare la crescita illimitata di dati memorizzati nella cache ma raramente letti.

Combinare lazy loading e write-through

Nella pratica, molti sistemi di produzione combinano entrambe le strategie: utilizzano write-through per i dati aggiornati e letti frequentemente, come le sessioni utente o i prezzi correnti, e lazy loading per i dati aggiornati raramente ma letti frequentemente, come le descrizioni dei prodotti o il contenuto degli articoli. Imposti TTL appropriati per entrambe le strategie: alle chiavi write-through viene assegnato un TTL lungo, poiché i dati sono sempre aggiornati; alle chiavi caricate tramite lazy loading viene assegnato un TTL più breve, per limitare la finestra dei dati obsoleti. Questo approccio ibrido massimizza il tasso di cache hit riducendo al minimo l'obsolescenza.

# 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

Principi di progettazione del TTL

Il Time-To-Live (TTL) dei dati memorizzati nella cache determina la durata massima della finestra dei dati obsoleti e l'occupazione di memoria della cache. Progetti i TTL in base alla frequenza di aggiornamento dei dati (i dati di sessione cambiano spesso → TTL breve; il contenuto statico cambia raramente → TTL lungo), alla tolleranza all'obsolescenza (prezzi finanziari → molto breve; testo di un post di blog → ore o giorni) e alla capacità di memoria della cache (memoria ridotta → TTL più breve per rimuovere più rapidamente i dati obsoleti). Imposti sempre un TTL: non memorizzi mai dati nella cache senza scadenza, altrimenti la cache finirà per riempirsi di dati obsoleti.

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

Invalidazione della cache durante gli aggiornamenti

Anziché affidarsi esclusivamente alla scadenza del TTL, è possibile invalidare esplicitamente (eliminare) le chiavi della cache quando cambiano i dati sottostanti. In questo modo si elimina completamente la finestra dei dati obsoleti. Pattern comuni: delete-on-write (eliminare la chiave della cache dopo ogni aggiornamento del database; la lettura successiva la ripopola tramite lazy loading) e invalidazione guidata dagli eventi (DynamoDB Streams o l'acquisizione delle modifiche di RDS attivano una funzione Lambda che elimina le chiavi interessate). L'invalidazione della cache è uno dei problemi più complessi dei sistemi distribuiti; più semplice è la logica di invalidazione, più affidabile sarà la 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

Caching write-behind (write-back)

Un pattern meno comune ma potente è il write-behind (write-back): l'applicazione scrive solo nella cache, che scarica poi i dati nel database in modo asincrono e in background. Ciò consente scritture estremamente rapide (solo in memoria), al costo di una possibile perdita di dati se la cache si guasta prima dello scaricamento. Write-behind è adatto a scritture frequenti e ad alto volume quando i dati possono essere ricostruiti o sono accettabili perdite limitate, ad esempio per contatori di visualizzazioni, eventi di analisi o aggiornamenti dei punteggi di gioco. ElastiCache non supporta nativamente write-behind; deve essere implementato a livello applicativo.

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

Verifica rapida

Verifichi la Sua comprensione dei concetti di AWS Solutions Architect (SAA-C03) presentati in questa lezione.

Riepilogo della lezione

In questa lezione ha appreso che il lazy loading popola la cache in caso di cache miss durante una lettura e utilizza la memoria in modo efficiente, ma può restituire dati obsoleti fino alla scadenza del TTL; write-through aggiorna la cache a ogni scrittura, evitando i dati obsoleti ma sprecando memoria per i dati non letti; infine, l'invalidazione esplicita elimina le chiavi della cache quando il database viene aggiornato, rimuovendo le finestre di dati obsoleti. Nella prossima lezione esamineremo l'archiviazione delle sessioni e i pattern delle classifiche con ElastiCache.

Domande Frequenti

La lezione «Strategie di caching: lazy loading e write-through» è gratuita?

Sì — il testo completo di «Strategie di caching: lazy loading e write-through» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso AWS Solutions Architect, passa a CoddyKit PRO. Il corso AWS Solutions Architect include 4 lezioni in totale.

Cosa imparerò in «Strategie di caching: lazy loading e write-through»?

Implementare il lazy loading per popolare la cache in caso di miss e il write-through per mantenere la cache coerente a ogni scrittura nel database Eserciti AWS Solutions Architect con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.

Ho bisogno di esperienza per iniziare AWS Solutions Architect?

Non è richiesta alcuna esperienza precedente. AWS Solutions Architect su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 3 di 4.

Quanto tempo richiede la lezione «Strategie di caching: lazy loading e write-through»?

La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.

Posso scrivere ed eseguire codice in questa lezione AWS Solutions Architect?

Sì. Ogni lezione AWS Solutions Architect include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.

Tutte le lezioni di questo corso

  1. Redis e Memcached: scegliere il motore giusto
  2. Gruppi di replica Redis e modalità cluster di ElastiCache
  3. Strategie di caching: lazy loading e write-through
  4. Archiviazione delle sessioni e pattern per le classifiche
← Torna a AWS Solutions Architect