Unix Timestamps nella cache e TTL — Come funzionano realmente i tempi di scadenza

Se ti sei mai trovato a fissare una voce di cache scaduta chiedendoti perché non sta scadendo quando dovrebbe, la risposta di solito si trova in un timestamp. I sistemi di cache — da Redis ai CDN agli header HTTP — usano pesantemente i timestamp Unix, e sbagliare il calcolo anche di pochi secondi può causare bug difficili da riprodurre.

Usa il Unix Timestamp Converter per convertire qualsiasi timestamp di scadenza della cache in una data leggibile, o per ottenere il timestamp attuale per il debug dei calcoli TTL.

Cosa significa TTL e come si esprime

TTL sta per time-to-live. È il tempo per cui un valore in cache dovrebbe essere considerato valido prima di dover essere aggiornato o scartato.

TTL può essere espresso in due modi a seconda del sistema:

TTL relativo: Il numero di secondi per cui la voce di cache dovrebbe rimanere dal momento in cui è stata memorizzata. Redis e Memcached funzionano entrambi così per impostazione predefinita. Se imposti una chiave con TTL di 3600, scade 3600 secondi (un'ora) da quando è stata impostata, indipendentemente da quando accade.

Timestamp di scadenza assoluto: Il timestamp Unix al quale il valore in cache scade. Gli header HTTP Cache-Control con max-age funzionano come TTL relativo, ma gli header Expires usano una data-ora assoluta. I CDN e alcune cache a livello di applicazione memorizzano un timestamp di scadenza assoluto — l'ora attuale più il TTL — e controllano se ora_attuale > scadenza per determinare se è stale.

Entrambi gli approcci si traducono nella stessa logica sottostante: confrontare il timestamp Unix attuale con il timestamp di scadenza memorizzato. La differenza è dove avviene quell'aritmetica — al momento della scrittura o al momento della lettura.

Come Redis gestisce la scadenza delle chiavi

Redis è la cache in memoria più comune, e il suo comportamento TTL vale la pena di essere compreso in dettaglio.

Quando imposti una chiave con EXPIRE key 3600, Redis registra il timestamp Unix assoluto al quale la chiave dovrebbe scadere: ora_attuale + 3600. Puoi vederlo con EXPIRETIME key, che restituisce il timestamp Unix della scadenza (Redis 7.0+). TTL key restituisce i secondi rimanenti.

Redis usa la scadenza pigra combinata con sweep periodici. Una chiave non viene eliminata nell'istante in cui scade — viene eliminata quando viene successivamente accessata (e risulta scaduta) o quando lo sweep in background di Redis la raccoglie. Questo significa che una chiave con TTL di 0 potrebbe comunque apparire in scenari di DEBUG SLEEP o quando lo sweep non è ancora stato eseguito.

L'implicazione pratica: se la tua applicazione legge una chiave, controlla che non sia nil, e poi la usa — e c'è persino una piccola finestra tra il GET e il TTL check — potresti leggere un valore che è appena scaduto. Nei sistemi ad alto throughput, questo può causare cache stampede quando molte richieste contemporaneamente trovano una chiave scaduta e tentano tutte di rigenerarla.

HTTP Caching e Unix Timestamps

La cache HTTP usa un mix di valori di tempo relativo e assoluto, e l'interazione tra loro causa molta confusione.

Cache-Control: max-age=3600 comunica a un browser o CDN che la risposta è valida per 3600 secondi da quando è stata ricevuta. Questo è relativo — ogni client traccia il suo orologio da quando ha ricevuto la risposta.

Expires: Thu, 17 Apr 2026 10:00:00 GMT è un timestamp assoluto. Comunica al client di non usare la risposta in cache dopo quel momento. Il problema è che richiede che l'orologio del sistema del client sia accurato. Se l'orologio di sistema di un client è sbagliato di un'ora, il comportamento della cache è sfasato di un'ora.

Header Age: Quando un CDN ha già avuto una risposta in cache per 600 secondi, invia Age: 600 al client. Combinato con max-age, il client sa che il tempo di freschezza rimanente è max-age - Age. Se Age supera max-age, la risposta è già stale.

Last-Modified e ETag: Questi sono header di validazione — permettono al client di chiedere "è ancora fresco?" piuttosto che usare semplicemente un TTL. Il server restituisce un 304 Not Modified (nessun corpo, solo header) se il contenuto non è cambiato, il che è molto più veloce che inviare la risposta completa.

Logica di scadenza e purge dei CDN

I CDN come Cloudflare, Fastly e CloudFront mettono in cache le risposte al margine e usano TTL per determinare quanto a lungo servire una copia in cache senza tornare al server di origine.

I CDN in genere memorizzano un timestamp di scadenza assoluto calcolato al momento in cui la risposta viene messa in cache: momento_cache + max_age. Su ogni richiesta, controllano ora_attuale > scadenza. Se vero, recuperano una copia fresca dall'origine.

Il caso limite che confonde le persone: un CDN potrebbe aver messo in cache una risposta in più posizioni edge in momenti leggermente diversi. Il CDN a Francoforte ha messo in cache la risposta 12 secondi prima di quello a Singapore. I loro timestamp di scadenza differiscono di 12 secondi. Se fai un "cache purge", invalida tutti contemporaneamente indipendentemente dal TTL. Ma se fai affidamento solo sulla scadenza TTL, potresti ottenere una risposta stale da una posizione edge diversi secondi dopo che un'altra ha già effettuato l'aggiornamento.

Debug della cache stale con i timestamp

Quando sospetti che una cache stia restituendo dati stale, la prima cosa da fare è ottenere i timestamp non elaborati:

1. Ottieni il timestamp Unix attuale — usa il Unix Timestamp Converter o esegui date +%s nel tuo terminale.

2. Trova il timestamp di scadenza della voce in cache — in Redis, EXPIRETIME key; nelle risposte HTTP, analizza l'header Expires o calcola da Date + max-age - Age; nella tua cache dell'applicazione, registra il valore di scadenza memorizzato.

3. Confrontali — se ora_attuale > scadenza, la voce dovrebbe essere scaduta. Se non viene evict, controlla la tua politica di eviction, le impostazioni di scadenza pigra vs eager, e se la voce viene aggiornata prima che scada.

4. Controlla lo skew dell'orologio — se il tuo server di applicazione e il server di cache hanno orologi di sistema diversi, i calcoli TTL saranno disallineati. Un bug di produzione comune è un server di cache il cui orologio si è sfasato di 5 minuti indietro. Le voci che dovrebbero aver scaduto 4 minuti fa vengono ancora restituite come fresche.

La sincronizzazione NTP previene lo sfasamento dell'orologio, ma non lo elimina completamente. Per la logica di scadenza critica, usa timestamp generati dal server da una singola fonte piuttosto che affidarti al fatto che tutti gli host concordino sull'ora attuale.

Memorizzazione dei timestamp di scadenza nella tua applicazione

Quando costruisci caching a livello di applicazione — memorizzando risultati calcolati in un database o key-value store con una scadenza — devi decidere come rappresentare la scadenza.

Opzione 1: Memorizza il TTL (relativo). La voce sa che dovrebbe vivere per 3600 secondi, ma non quando è stata creata. Hai bisogno di un campo created_at separato per calcolare se è scaduta.

Opzione 2: Memorizza il timestamp di scadenza assoluto. Al momento della scrittura, calcola expires_at = timestamp_unix_attuale + ttl. Al momento della lettura, controlla timestamp_unix_attuale > expires_at. Più semplice da ragionare — hai bisogno di un solo campo.

L'approccio del timestamp assoluto è quasi sempre più pulito. Memorizzare expires_at: 1775000000 è più chiaro che memorizzare ttl: 3600 separatamente da created_at: 1774996400. Sopravvive anche ai riavvii in modo pulito — un TTL relativo non significa nulla se non sai quando la voce è stata creata.

Cache stampede e come i timestamp aiutano a prevenirlo

Cache stampede accade quando molte richieste contemporaneamente trovano che un valore in cache è scaduto e tentano tutte di rigenerarlo in una volta. Per una query di database costosa o una chiamata API esterna, 50 richieste di rigenerazione simultanee possono sopraffar il backend.

La soluzione consapevole dei timestamp è la scadenza anticipata probabilistica (chiamata anche cache warming): invece di far scadere la voce esattamente a tempo_scadenza, inizia a rinfrescare probabilisticamente prima. Una versione semplice:

if current_time > (expiry_time - random_factor * remaining_ttl):
    refresh the cache

Dove random_factor è tipicamente 0–1. Questo distribuisce i refresh della cache piuttosto che raggrupparli nel momento esatto di scadenza.

Un approccio più semplice: aggiungi jitter ai TTL. Invece di impostare ogni voce a scadere esattamente in 3600 secondi, impostala a scadere in 3600 + random(-300, 300) secondi. Questo desincronizza i tempi di scadenza tra diverse voci e impedisce loro di scadere tutte contemporaneamente.

Timestamp con segno e senza segno e il problema del 2038

La maggior parte dei sistemi di cache memorizza i valori TTL come interi a 32 bit. Per i valori TTL (durate relative), questo va bene — nessuno sta impostando un TTL di cache di 2 miliardi di secondi. Ma per i timestamp di scadenza assoluti, un intero con segno a 32 bit va in overflow il 19 gennaio 2038.

Se la tua infrastruttura di cache memorizza timestamp Unix assoluti in interi a 32 bit, le voci impostate a scadere dopo gennaio 2038 avranno valori di scadenza non corretti. Questo è un caso limite oggi — la maggior parte dei TTL di produzione sono minuti o ore, non anni — ma i sistemi che memorizzano token di lunga durata, certificati o dati di configurazione con scadenza pluriennale potrebbero incappare in questo problema.

I sistemi e i linguaggi moderni gestiscono questo correttamente con interi a 64 bit. Vale la pena controllare se stai lavorando con sistemi embedded più vecchi, codice C legacy, o qualsiasi sistema che gestisce i timestamp come int piuttosto che int64 o long long.