Upstash Redis: i 5 pattern che usiamo in ogni SaaS nel 2026
Rate limiting, sessioni, cache-aside, lock distribuiti e code QStash. I cinque pattern Upstash Redis che cabliamo in ogni SaaS, con i numeri di costo.
Upstash Redis è un Redis serverless pay-per-request con accesso HTTP che gira dentro Vercel, Cloudflare Workers e AWS Lambda senza connection pooling. Lo usiamo in ogni SaaS che costruiamo perché cinque pattern si ripetono in ogni progetto: rate limiting, sessioni, cache-aside in lettura, lock distribuiti per l’idempotenza dei webhook e code QStash per i job in background. I pattern non sono esotici. Quello che cambia con Upstash è che ciascuno si scrive in poche righe senza provisioning, e il costo si muove solo quando si muove il traffico.
Cosa abbiamo misurato prima di sceglierne cinque
Usiamo Redis su una dozzina di SaaS in produzione nel 2026. I pattern qui sotto sono quelli che compaiono in almeno otto di essi. Tutto il resto (pub/sub, leaderboard, indici vettoriali) lo teniamo su una mensola a parte. I criteri di selezione erano semplici: il pattern deve risolvere un problema reale in ogni SaaS, andare in produzione in meno di un giorno e o far risparmiare più di quanto costa o sbloccare una funzione che richiederebbe un servizio dedicato.
1. Rate limiting a finestra scorrevole con @upstash/ratelimit
Ogni endpoint pubblico ha bisogno di un tetto. Senza, un singolo client malfunzionante (o uno scraper, o lo stress test di un competitor) ti svuota l’origin, la pool di connessioni al database e la bolletta. La libreria ufficiale, @upstash/ratelimit, offre algoritmi sliding-window, token-bucket e fixed-window con circa 100 byte per chiave: anche un’istanza Upstash da 256MB regge milioni di chiavi attive.
Quello che cabliamo davvero:
- Tre livelli per famiglia di endpoint: anonimo (tetto basso, reject veloce), autenticato free (medio), autenticato pagante (alto). L’identificatore è l’IP per l’anonimo, lo user id per gli autenticati.
- Finestra scorrevole, perché quelle fisse lasciano che un burst al confine raddoppi di fatto il limite.
- Cache effimera in memoria per le chiavi già bloccate: quando una chiave è sopra soglia, ricordiamo il reset time in memoria di processo e respingiamo la richiesta successiva senza fare round-trip a Redis. Su un endpoint sotto attacco, riduce i comandi Upstash dell’80% e oltre.
- Il controllo gira nel middleware Next.js: il rifiuto arriva prima del route handler, del database e di qualsiasi chiamata LLM.
Forma del costo: un controllo è due comandi Redis. A 0,2 dollari ogni 100K comandi in pay-per-request, il conto resta irrisorio finché non superi circa 5 milioni di controlli al giorno, oltre i quali un piano fisso inizia a vincere.
2. Sessioni con TTL
Se autentichi gli utenti sull’edge, il session store non può essere Postgres. Andare a battere sull’origin da un Cloudflare Worker o da una Edge Function annulla il senso di girare lì. Upstash tiene la sessione sotto una chiave hashata del token, mette un TTL con EXPIRE e serve la lookup in pochi millisecondi da qualunque regione.
La forma che usiamo:
- Chiave:
session:{token_hash}. Il token è un valore random di 32 byte salvato in un cookie HTTP-only. Salviamo l’hash, mai il token grezzo. - Valore: un JSON da 1 a 4 KB con user id, ruolo, id organizzazione e feature flag risolti al login. Tutto ciò che pesa di più torna a Postgres on demand.
- TTL: 30 giorni per “ricordami”, 24 ore di default. Refresh scorrevole a ogni richiesta autenticata via
EXPIRE, con un massimo duro per non far vivere all’infinito un token rubato. - Logout è un
DEL. Sign-out forzato su tutti i dispositivi è unoSCANper prefisso dell’utente, tenuto fuori dal percorso caldo.
Capienza: a circa 2 KB per sessione, un’istanza Upstash da 1 GB tiene nell’ordine di 500.000 sessioni attive. Quasi tutti i SaaS B2B che spediamo restano sul piano gratuito per il primo anno.
3. Cache-aside per le letture calde
Il terzo pattern è cache-aside da manuale: in lettura, controlli Redis; in caso di miss, vai su Postgres e riscrivi il risultato con un TTL. Lo usiamo per entità lette spesso e scritte raramente: configurazioni di feature flag, pagine profilo pubbliche, cataloghi prodotto e il payload “current user” che idrata la navbar a ogni page load.
La disciplina che fa sopravvivere cache-aside in produzione:
- TTL su ogni chiave. Senza eccezioni. Una cache senza TTL diventa una fabbrica di dati stantii il giorno in cui qualcosa va storto nell’invalidazione.
- Invalidazione esplicita sui write path: quando cambia la riga sottostante,
DELsulla chiave di cache dalla stessa transazione o dal worker della coda che finalizza la scrittura. - Stale-while-revalidate dove la freschezza può aspettare. Restituisci subito il valore cachato e aggiorni in background, con una finestra di staleness breve. Le letture restano sotto i 50ms anche quando la query d’origine ne fa 300.
- Non cachare mai nulla che varia per utente sotto una chiave condivisa. Il numero di incident da cache poisoning che iniziano con “ma in staging funzionava” è, per nostra esperienza, esattamente il numero di volte in cui qualcuno scorda questa regola.
4. Lock distribuito per l’idempotenza dei webhook
Stripe, Resend, Clerk e ogni altro vendor ritrasmetteranno un webhook se l’endpoint va in timeout, fallisce o risponde col codice sbagliato. Se l’handler fa qualcosa di non idempotente (crea una riga, manda un’email, addebita un conto), una ritrasmissione senza guardia raddoppia l’effetto. La risposta classica è un vincolo unique su una chiave di idempotenza in Postgres. La risposta più veloce, in serverless, è un lock Redis.
Usiamo @upstash/lock, che incapsula la primitiva SET key value NX EX seconds in un’API tipata. Alla ricezione del webhook proviamo ad acquisire un lock con chiave sull’event id e TTL di 60 secondi. Se il lock è già preso, restituiamo 200 subito e lasciamo lavorare l’handler in volo. Se lo acquisiamo, eseguiamo l’handler, persistiamo l’esito e rilasciamo il lock.
Due cautele contano:
- Upstash Redis fa replica asincrona fra le repliche. Su una partizione di rete, due client possono tenere brevemente lo stesso lock. Il pattern va bene per performance e per gran parte dei casi di idempotenza, non per chiavi primarie finanziarie. Quando serve mutua esclusione stretta, il lock è il check ottimistico e il vincolo unique sul database resta la guardia autoritativa.
- Il TTL del lock deve superare il runtime peggiore dell’handler. Altrimenti un handler lento rilascia il proprio lock a metà corsa e una ritrasmissione passa.
La stessa primitiva serve per prevenire il cache stampede, debouncare azioni utente come “rinvia l’invito” e serializzare job di background che non devono sovrapporsi.
5. Job in background con QStash
QStash non è Redis. È un prodotto fratello: una message queue HTTP con retry, scheduling, fan-out e dead-letter. Sta in questa lista perché la domanda “come faccio un job in background da una funzione serverless” arriva su ogni SaaS, e QStash è la risposta a cui torniamo sempre.
Perché partiamo da QStash invece che da una coda Redis che ci manteniamo da soli:
- Niente worker da ospitare. QStash fa POST a un endpoint HTTPS che già gestisci. L’endpoint è un normale route handler Next.js, un Cloudflare Worker, una Lambda. L’autenticazione è un JWT firmato nell’header.
- Retry, backoff esponenziale e DLQ inclusi. Non scriviamo più logica di retry.
- Lo scheduling è di prima classe: cron o timestamp specifici, senza un servizio separato.
- Il timeout endpoint di 60 secondi è l’unico vincolo vero. Qualsiasi cosa più lunga (ETL grossi, elaborazione video, fine-tuning di modelli) va su un altro runtime, di solito un container long-lived.
Il prezzo è 1 dollaro ogni 100K messaggi sopra il piano gratuito di 500 al giorno. Il default giusto per lavoro in background serverless sotto 1M di job al giorno.
Dove la forma del costo si ribalta
La domanda più ripetuta su Upstash è “quando smette di vincere il pay-per-request?”. La nostra euristica, validata sui SaaS che gestiamo:
- Sotto i 500K comandi al giorno fra tutti i pattern, pay-per-request è più conveniente di qualsiasi alternativa managed che abbiamo prezzato.
- Fra 500K e 5M comandi al giorno iniziano a vincere i piani fissi. Passiamo a un’istanza Upstash a piano fisso da 10 dollari al mese e ci restiamo.
- Sopra i 10M comandi al giorno su un singolo workload, Redis managed tradizionale (ElastiCache, Redis Cloud o un’alternativa regionale) diventa più economico, a volte di un ordine di grandezza. DanubeData ha pubblicato la matematica del breakpoint a maggio 2026: un rate-limiter da 10M comandi al giorno costa circa 600 dollari al mese in pay-per-request contro circa 10 euro al mese su una piccola istanza fissa.
Guarda il volume per-pattern, non l’aggregato. Rate limiting e cache-aside passano la soglia per primi; sessioni e lock quasi mai.
Cosa non mettiamo su Upstash, di proposito
Tre cose le teniamo fuori da Redis anche quando sarebbe tecnicamente possibile: qualunque cosa richieda ACID stretto, qualunque cosa di cui il dato sia la fonte autoritativa, e qualunque carico misurato in latenza p99 a una cifra di millisecondi che attraversi regioni a ogni chiamata. Upstash è veloce, non magico. I primi due vivono in Postgres. Il terzo spesso vive in una cache embedded colocata con la funzione.
Cinque pattern, una sola dipendenza, una forma di costo prevedibile. Quando facciamo l’onboarding di un nuovo SaaS, cablare questi cinque è mezza giornata di lavoro e toglie di mezzo una classe ricorrente di decisioni per il resto del progetto.
Domande frequenti
- When does Upstash Redis stop being cheaper than managed Redis?
- Pay-per-request wins below roughly 500K commands per day across all patterns. Between 500K and 5M, a $10/month Upstash fixed-plan instance is the better fit. Above 10M commands per day on a single workload, traditional managed Redis (ElastiCache, Redis Cloud, regional alternatives) becomes cheaper, sometimes by an order of magnitude. Track the per-pattern volume, not the aggregate: rate limiting and cache-aside cross the threshold first.
- Is an Upstash Redis lock safe for financial idempotency?
- Not on its own. Upstash replicates asynchronously between replicas, so on a network partition two clients can briefly hold the same lock. The pattern is fine for performance and most idempotency cases. For financial primary keys (charges, payouts, refunds), use the lock as an optimistic check and a Postgres unique constraint on the idempotency key as the authoritative guard. The lock prevents the work; the constraint prevents the duplicate.
- Why pick QStash over a Redis-based queue we run ourselves?
- Three reasons. There are no workers to host: QStash POSTs to an HTTPS endpoint you already operate (a Next.js route, a Worker, a Lambda). Retries, exponential backoff, and dead-letter queues are built in, so you stop writing retry code. Scheduling is first-class through cron or specific timestamps, removing the need for a separate scheduler. The 60-second endpoint timeout is the one real constraint; jobs that need longer should run on a long-lived container, not a queue.
- What should we deliberately keep off Upstash Redis?
- Three things. Anything that needs strict ACID transactions belongs in Postgres. Anything where Redis would be the system of record belongs in Postgres, because cache data can vanish on eviction or a TTL bug. Any workload measured in single-digit-millisecond p99 latency that crosses regions on every call belongs in an embedded cache colocated with the function, not in a network-bound store. Upstash is fast, not magic.
Studio
Inizia un progetto.
Un partner unico per il prodotto digitale che devi costruire. Produzione più veloce, tecnologie moderne, costi ridotti. Un team, una fattura.