Caching Redis: l'architettura dei millisecondi
Perché il tuo database è il collo di bottiglia. Una guida tecnica approfondita ai pattern Redis (Cache-Aside, Write-Through), all'invalidazione dei tag e a HyperLogLog.
Nella gerarchia della latenza, la rete è lenta, il disco è lento, ma la RAM è istantanea. *Query PostgreSQL (SSD): 10 ms - 100 ms. *Query Redis (RAM): 0,2 ms.
Questa differenza di velocità di 500 volte è l’unico motivo per cui le applicazioni web su larga scala rimangono online. Se Amazon accedesse al proprio database SQL per ogni visualizzazione di prodotto nel Prime Day, l’intera Internet si bloccherebbe.
Noi di Maison Code Paris trattiamo Redis non solo come una “Cache”, ma come un server di struttura dati primario. È il livello tattico che protegge il livello strategico (il Database).
Perché Maison Code ne parla
In Maison Code Paris, agiamo come la coscienza architettonica dei nostri clienti. Spesso ereditiamo stack “moderni” costruiti senza una comprensione fondamentale della scala.
Discutiamo di questo argomento perché rappresenta un punto di svolta critico nella maturità ingegneristica. Implementarlo correttamente differenzia un MVP fragile da una piattaforma resiliente di livello aziendale.
I pattern: come memorizzare correttamente nella cache
La memorizzazione nella cache è semplice. Invalidare la cache è impossibile. Esistono due principali modelli di implementazione che utilizziamo.
1. Cache-Aside (caricamento lento)
Questa è l’impostazione predefinita. L’applicazione è responsabile della lettura/scrittura.
“dattiloscritto”. // db/repo.ts importa {redis} da ’./redis’;
funzione asincrona getProduct(id: stringa) {
chiave const = prodotto:€{id};
// 1. Controlla la cache const cached = attendono redis.get(chiave); if (memorizzato nella cache) restituisce JSON.parse(memorizzato nella cache);
// 2. Cache Miss -> Controlla DB const data = attendono db.product.findUnique({ id });
// 3. Compila la cache se (dati) { // Memorizza nella cache per 60 secondi (TTL) attendono redis.set(chiave, JSON.stringify(dati), ‘EX’, 60); }
restituire i dati; }
* **Pro**: Resiliente. Se Redis muore, l'app funziona (lentamente).
* **Contro**: finestra Dati non aggiornati. L'utente potrebbe vedere il vecchio prezzo per gli anni '60.
### 2. Write-Through (invalidazione guidata dagli eventi)
Lo preferiamo per i dati ad alta coerenza (come l'inventario).
Ascoltiamo le modifiche del database (CDC o eventi dell'applicazione) e nuclearizziamo la cache.
"dattiloscritto".
// servizi/prodotto.ts
funzione asincrona updatePrezzo(id: stringa, prezzo: numero) {
// 1. Aggiorna DB (fonte della verità)
attendono db.product.update({ dove: { id }, dati: { prezzo } });
// 2. Invalida immediatamente la cache
attendono redis.del(`prodotto:${id}`);
}
Il problema più difficile: invalidazione per relazione (tag)
Immagina di memorizzare nella cache:
- “prodotto:123”.
- “categoria:scarpe” (contiene il prodotto 123)
Se aggiorni il prodotto 123, elimini “prodotto:123”. Ma “categoria:scarpe” contiene ancora la vecchia versione del prodotto! Devi anche sapere quali categorie contengono la chiave 123.
Implementiamo l’invalidazione basata su tag utilizzando i set Redis.
- Quando creiamo “categoria:scarpe”, teniamo traccia delle dipendenze:
Tag SADD:prodotto:123 categoria:scarpe - Quando si aggiorna il Prodotto 123:
- Trova tutte le chiavi che dipendono da esso:
SMEMBERS tags:product:123-> Restituisce['category:shoes']. - Eliminateli:
DEL categoria:scarpe. - Pulisci il set di tag.
- Trova tutte le chiavi che dipendono da esso:
Questa logica è complessa ma risolve il problema “Perché la home page mostra ancora la vecchia immagine?” bug per sempre.
Serializzazione: il costo nascosto della CPU
La memorizzazione di stringhe JSON (JSON.stringify) è standard ma inefficiente.
- Archiviazione: JSON è dettagliato. “active”: true` occupa 15 byte.
- CPU:
JSON.parseblocca il loop degli eventi. Per un carico utile di 1 MB (home page di grandi dimensioni), l’analisi richiede 20 ms di tempo CPU.
Soluzione: MessagePack (msgpack). È un formato di serializzazione binario. È più veloce e più piccolo.
“dattiloscritto”. importa { pack, unpack } da ‘msgpackr’; importa Redis da ‘ioredis’;
const redis = nuovo Redis({ // ioredis supporta i buffer binari keyPrefisso: ‘v1:’, });
attendono redis.set(‘key’, pack(largeObject)); // Memorizza il buffer const buffer = attendono redis.getBuffer(‘chiave’); const oggetto = unpack(buffer);
I benchmark mostrano che **msgpack** è 3 volte più veloce da codificare/decodificare rispetto a JSON nativo per strutture di grandi dimensioni.
## Strutture avanzate: non solo chiavi e valori
Redis è un "server della struttura dati". Smetti di usarlo come `Map<String, String>`.
### 1. HyperLogLog (conteggio approssimativo)
Problema: "Conta i visitatori unici oggi."
Ingenuo: memorizza ogni IP in un set. `SADD {ip}`.
Per gli utenti da 100 milioni, sono necessari 1,5 GB di RAM.
**Soluzione Redis**: "Visitatori PFADD {ip}".
HyperLogLog utilizza la matematica probabilistica (hash). Conta 100 milioni di oggetti unici con **12KB** di RAM. L'affidabilità è del 99,19%.
Hai bisogno del conteggio esatto dei visitatori? No. Devi sapere "Sono 10k o 100k?". L'HLL è perfetto.
### 2. Indici geospaziali
Problema: "Trova negozi vicino a me."
Ingenuo: formula SQL Haversine (lenta).
**Soluzione Redis**: `GEOADD memorizza 2.3522 48.8566 "Parigi"`.
"GEORADIUS memorizza 2,35 48,85 10 km" restituisce risultati in microsecondi utilizzando l'ordinamento Geohash.
### 3. Limitazione della velocità (il bucket dei token)
Proteggiamo le nostre API da DDoS utilizzando i contatori Redis.
Utilizziamo uno script Lua (per garantire l'atomicità) che implementa l'algoritmo "Sliding Window".
"Tasto INCR". `SCADERE chiave 60`. Se `> 100`, Rifiuta.
## Il problema della mandria tonante
Se la funzionalità della cache è vitale, il suo fallimento è catastrofico.
Scenario:
1. La chiave `home_page` scade alle 12:00:00.
2. Alle 12:00:01, 5.000 utenti richiedono la home page.
3. 5.000 richieste ricevono una "Cache Miss".
4. 5.000 richieste hanno raggiunto contemporaneamente il database Postgres.
5. Il buffer di output del database si riempie. Il database si blocca.
6. Il sito web non funziona più.
**Soluzione: blocco della cache (Mutex)**.
Quando si verifica un "Miss", il codice tenta di acquisire un Lock (`SETNX lock:home_page`).
* **Vincitore**: genera la pagina. Scrive nella cache. Rilascia il blocco.
* **Perdenti**: Attendi 100 ms. Controlla di nuovo la cache.
Risultato: solo **1** query raggiunge il database.
## 10. Redis Cluster vs Sentinel (alta disponibilità)
Il nodo Redis singolo è un singolo punto di errore.
**Sentinella**: monitora il Maestro. Failover sulla replica.
**Cluster**: suddivide i dati su più nodi.
Per l'e-commerce, raramente abbiamo bisogno di Cluster (lo sharding è complesso). Un'istanza Redis può gestire 100.000 operazioni/sec.
Preferiamo **Sentinel**.
Ma attenzione: la configurazione del client per Sentinel è complicata.
`redis = new Redis({ sentinels: [{ host: 'sentinel-1', porta: 26379 }], nome: 'mymaster' })`.
Se si codifica l'IP master, il failover non funzionerà.
## 11. Scripting Lua: l'atomicità è re
Vuoi "Controllare il saldo, Detrarre denaro".
Fare questo in Node.js (Get -> Logic -> Set) è una Race Condition.
Scriviamo **Script Lua** che vengono eseguiti all'interno di Redis.
Gli script Lua sono atomici. Nessun altro comando viene eseguito durante l'esecuzione dello script.
Ciò garantisce che la "Detrazione dell'inventario" sia perfettamente coerente, anche con 500 acquirenti simultanei.
## 13. Strategie intelligenti di riscaldamento della cache
Aspettare che il primo utente colpisca un "Miss" e paghi la penalità di latenza è una cattiva UX.
Implementiamo il **Riscaldamento attivo della cache**.
1. **On Deploy**: attiva uno script `warm-cache.ts`.
2. **Logica**: recupera i primi 500 URL da Google Analytics (tramite API).
3. **Azione**: accede all'API interna `localhost:3000/api/render?url={top_url}`.
Ciò costringe il server a generare la pagina e popolare Redis *prima* che il traffico raggiunga.
Il risultato è una latenza di 0 ms per l'80% degli utenti immediatamente dopo una distribuzione.
## 14. Oltre il valore-chiave: stack Redis (ricerca e JSON)
Il nuovo **Redis Stack** ti consente di eseguire query di tipo SQL su documenti JSON.
`FT.SEARCH productIdx "@title:Scarpe @price:[0 100]"`
questo viene eseguito in memoria. È 50 volte più veloce di Elasticsearch per set di dati di piccole dimensioni (<1 milione di elementi).
Lo utilizziamo per le funzionalità di "Ricerca istantanea" in cui Algolia è troppo costosa o troppo lenta.
## 15. Conclusione
Redis è lo strumento più potente nella cintura di utilità dell'ingegnere backend.
Colma il divario tra il mondo rigido e lento dei database ACID e il volume di richieste caotico e veloce del web moderno.
Ma richiede disciplina. Una cache incontrollata (senza TTL, senza policy di eliminazione e chiavi enormi) è una perdita di memoria in attesa di mandare in crash il tuo server.
<hr style="margin: 1rem 0" />
**[Assumi i nostri architetti](/contact)**.