Phil Karlton ha affermato che ci sono solo due cose difficili nell'informatica: l'invalidazione della cache e la denominazione delle cose. KV rende il primo ancora più difficile, perché non controlli quando arriva l'invalidazione in ogni punto di presenza.
L'eliminazione di una chiave in KV non la rimuove immediatamente dal mondo. La cancellazione va allo store centrale e si propaga ai PoP con la stessa dinamica di consistenza finale delle scritture: fino a 60 secondi per raggiungere tutti i punti di presenza. Durante questa finestra, i PoP che non hanno ancora ricevuto l'eliminazione continuano a fornire il vecchio valore a tutte le richieste in arrivo. Non sai quali PoP lo hanno ricevuto e quali no. Non c'è modo di forzare la propagazione immediata.
Ciò rende l'invalidazione della cache in KV un problema di progettazione, non un problema operativo. Non lo risolvi con un pulsante "invalida ora": lo risolvi scegliendo un modello chiave che riduca al minimo l'impatto del ritardo di propagazione.
Chiavi con versione: la soluzione più robusta
Lo standard più solido per il contenuto che deve essere invalidato è mantenere un riferimento indiretto esplicito tra il nome logico dei dati e la chiave fisica in cui sono archiviati.
Invece di scrivere l'HTML renderizzato di una pagina su homepage-html e poi cancellarlo quando il contenuto cambia, lo scrivi su homepage-html-v42. Un secondo tasto, homepage-html-version, contiene solo la stringa v42. Il Worker legge prima la chiave della versione, costruisce il nome della chiave del contenuto e recupera il valore.
Quando il contenuto cambia, scrivi il nuovo codice HTML in homepage-html-v43 e aggiorna homepage-html-version a v43. Non è necessario eliminare immediatamente la vecchia chiave: semplicemente smette di essere referenziata. Il suo costo di archiviazione continua ad esistere fino alla pulizia, ma l'incoerenza dell'invalidamento non è più un problema: qualsiasi PoP che ha ricevuto l'aggiornamento homepage-html-version riceverà già la nuova chiave. Il vecchio contenuto viene fornito solo ai PoP che non hanno ancora ricevuto l'aggiornamento della chiave di versione e ciò avviene entro un periodo di 60 secondi, indipendentemente da ciò che fai.
Il costo di questo approccio è una lettura extra per richiesta: prima la chiave della versione, poi il contenuto. Nella maggior parte dei casi, questo costo è irrilevante in termini di latenza: le due letture sono parallele se il Worker le esegue con Promise.all, ed entrambe arrivano dalla cache PoP in meno di un millisecondo quando sono calde.
Coordina KV con l'eliminazione del CDN
Per i contenuti serviti dal CDN Cloudflare (non direttamente dal Worker), è presente un ulteriore livello di memorizzazione nella cache sopra il KV. La CDN potrebbe aver memorizzato nella cache una risposta generata con un vecchio valore KV e, anche se il KV ha già il nuovo valore in tutti i PoP, la risposta memorizzata nella cache della CDN verrà comunque servita fino alla sua scadenza.
La soluzione è coordinare l'aggiornamento KV con un'eliminazione della CDN. L'API Cache Purge di Cloudflare ti consente di invalidare URL specifici o tag di cache tramite API. Scrivi il nuovo valore nel KV e, nella stessa operazione amministrativa, chiami l'API di eliminazione per gli URL interessati. Dal punto di vista dei clienti che passano attraverso la CDN, la nuova risposta appare immediatamente dopo l'eliminazione, indipendentemente da quanti PoP stanno ancora propagando il valore KV.
Questo modello richiede il controllo del processo di aggiornamento del contenuto e l'accesso all'API di eliminazione. Per i flussi CMS in cui un editore pubblica contenuti, si tratta di un'integrazione ragionevole: il webhook di pubblicazione aggiorna il KV e attiva l'eliminazione.
Stale- while-revalidate con waitUntil
Il modello stantio durante la riconvalida fornisce contenuti potenzialmente obsoleti mentre attiva un aggiornamento in background. Nel contesto di Workers, ctx.waitUntil() consente di eseguire lavoro asincrono dopo che la risposta è stata inviata al client.
Implementazione tipica: il lavoratore legge il valore KV corrente e serve immediatamente. Allo stesso tempo, tramite ctx.waitUntil() attiva una funzione che verifica se il valore deve essere aggiornato, ad esempio consultando una fonte, e, se necessario, scrive il nuovo valore nel KV. Il client riceve la risposta senza attendere l'aggiornamento. La richiesta successiva potrebbe già ricevere il nuovo valore, a seconda di quando viene completata la propagazione.
Il compromesso è esplicito: si scambia una latenza zero con il client per una finestra in cui è possibile fornire contenuti obsoleti. Per la maggior parte degli scenari di memorizzazione nella cache dei contenuti, questo compromesso è accettabile. Per i dati in cui la obsolescenza ha conseguenze operative (prezzi, disponibilità di stock, autorizzazioni di accesso) lo standard non è appropriato.
TTL in sostituzione dell'invalidazione esplicita
Per i casi in cui non è necessaria un'invalidazione precisa, il TTL è il meccanismo più semplice. KV supporta expirationTtl (secondi da adesso) e expiration (timestamp Unix) definiti al momento della scrittura.
Un flag di funzionalità con un TTL di 60 secondi scade automaticamente. La richiesta successiva alla scadenza preleverà l'archivio centrale e restituirà il valore più recente o un valore vuoto, che il lavoratore può interpretare come "flag disabilitato". Non è necessario eliminare o tenere traccia in modo esplicito dello stato.
Per i contenuti con frequenza di aggiornamento prevedibile, il TTL allineato al ciclo di aggiornamento elimina la necessità di invalidazione attiva. Un report generato ogni volta può avere un TTL di 3600 secondi. Il valore più vecchio possibile che verrà visualizzato da un utente è di circa un'ora e si tratta di una scelta di progettazione, non di un difetto di coerenza.
Cosa non funziona: cancellazione e riscrittura immediata
Uno schema che appare frequentemente e che non risolve il problema è quello di eliminare la vecchia chiave e scrivere in sequenza quella nuova. Ciò non elimina la finestra di incoerenza: le due operazioni si propagano indipendentemente ai PoP. Un PoP potrebbe ricevere l'eliminazione ma non aver ancora ricevuto la nuova scrittura e durante quella finestra restituirà not-found per quella chiave. A seconda di come il lavoratore gestisce questo caso, ciò potrebbe comportare un errore o un fallback imprevisto.
Cancellazione e riscrittura comportano il doppio delle operazioni di propagazione con il triplo dei possibili stati intermedi: vecchio, non trovato, nuovo. Sono sempre preferibili le chiavi con versione o TTL.
Come la coerenza finale cambia il design
Il modello più salutare per lavorare con KV è accettare la coerenza finale come una caratteristica del sistema, non come una limitazione su cui aggirare. Ciò significa progettare flussi in cui una finestra di incoerenza fino a 60 secondi è tollerabile e utilizzare strumenti diversi quando non lo è.
Per i dati che richiedono coerenza immediata tra tutti i PoP, KV non è lo strumento giusto. D1 con letture dirette al primario, o Oggetti durevoli per stati con accesso serializzato, coprono i casi in cui il modello di coerenza eventuale KV non funziona.
L'invalidazione elegante in KV è quella che non ha bisogno di avvenire urgentemente, perché la progettazione del sistema è stata realizzata per tollerare la finestra di propagazione. Qualsiasi tentativo di imporre una coerenza immediata funzionerà contro l'architettura, non con essa.
Leggi anche
- Cloudflare KV: cosa significa distribuzione globale quando è necessario scrivere
- KV per limitazione della velocità, flag di funzionalità e configurazione distribuita: dove funziona e dove si interrompe
- KV nella produzione: gli schemi che funzionano e quelli che ingannano all'inizio
- KV vs R2 vs Cache API: quando utilizzare ogni livello di archiviazione di Cloudflare
- Cosa fanno solo i Lavoratori, cosa fanno solo le Pagine e dove i due si incontrano
- Cloudflare D1: Il database SQLite all'edge — e perché "edge" non significa quello che sembra
