Durable Objects o D1: dove mettere lo stato di un'app su Workers

D1 e Durable Objects vengono confrontati come due database, ma non lo sono: uno è SQL condiviso dall'app, l'altro è stato privato e coordinato per una singola entità. Consistenza, concorrenza, costi dai listini del 29 ottobre 2026, limiti e quando usarli insieme. Dalla documentazione ufficiale di Cloudflare, senza test né benchmark.

Francesco Pio Nocerino9 minCloudflare
Durable Objects o D1: dove mettere lo stato di un'app su Workers
Indice

Questo pezzo è per chi ha un backend su Cloudflare Workers e deve decidere dove tenere lo stato. Le due risposte che tornano sempre sono D1 e Durable Objects. Spesso vengono messe sullo stesso piano, come due database tra cui scegliere. Non lo sono. Il confronto è costruito solo sulla documentazione ufficiale di Cloudflare letta il 29 ottobre 2026: niente test, niente benchmark, nessun numero mio.

In breve:

  • D1 è un database SQL gestito: un posto unico per i dati relazionali dell'app, interrogabile da qualunque Worker con un binding. Paghi righe lette, righe scritte e storage, senza costi di compute.
  • Un Durable Object è un'istanza di codice unica al mondo per un dato nome, a thread singolo, con il suo SQLite privato. Serve a coordinare: una stanza, un documento, un utente, un tenant.
  • La differenza che decide è chi vede il dato. In D1 lo vedono tutti i Worker. In un Durable Object lo vede solo quell'oggetto.
  • Sul costo le righe valgono uguale. I Durable Objects aggiungono richieste e tempo di esecuzione; D1 costa di più per GB conservato.
  • Spesso la risposta giusta è usarli tutti e due, ognuno per la parte che gli spetta.

La domanda giusta

Non "quale database è meglio su Workers?". È: lo stato che stai salvando va interrogato da tutta l'app, o va protetto e coordinato per una singola entità?

Un dettaglio della documentazione chiarisce perché la confusione è comprensibile. Ogni database D1 è sostenuto da un singolo Durable Object. D1, quindi, è un prodotto costruito sopra i Durable Objects. Ti dà un database SQL già pronto, con binding, migrazioni, repliche e backup. Un Durable Object ti dà il mattone: codice e storage nello stesso posto, e il compito di decidere tu come dividere i dati.

La pagina Choose a data or storage product lo dice in modo netto. D1 è per applicazioni serverless leggere e con molte letture. I Durable Objects sono per carichi con stato, SQL per utente o per cliente e sistemi distribuiti, dove serve un ordine globale delle richieste.

Cosa sono, in una frase ciascuno

D1 è il database SQL serverless di Cloudflare, con semantica SQLite. Lo crei, gli dai uno schema con le migrazioni e lo interroghi da un Worker con un binding. La pagina sulle query elenca tre strade: binding dei Workers, REST API e comandi Wrangler. La REST API lavora soprattutto sul piano di controllo.

Durable Objects sono istanze di una classe che scrivi tu. Ognuna ha un'identità unica, gira in un solo posto alla volta ed è a thread singolo. Ognuna ha uno storage privato, transazionale e fortemente consistente. Con il backend SQLite, che Cloudflare raccomanda per ogni nuova classe, quello storage è un database SQLite dentro l'oggetto.

// D1: un database condiviso, interrogato da qualunque Worker
const ordini = await env.DB
  .prepare("SELECT id, totale FROM ordini WHERE utente_id = ?")
  .bind(utenteId)
  .all();

// Durable Object: un'istanza per entità, con il suo SQLite privato
export class Carrello extends DurableObject {
  aggiungi(prodottoId: string, qta: number) {
    this.ctx.storage.sql.exec(
      "INSERT INTO righe (prodotto_id, qta) VALUES (?, ?)",
      prodottoId, qta
    );
  }
}

// dal Worker: stesso nome, stessa istanza, ovunque arrivi la richiesta
const stub = env.CARRELLO.get(env.CARRELLO.idFromName(utenteId));
await stub.aggiungi("sku-42", 1);

Nel primo caso tutte le richieste di tutti gli utenti finiscono nello stesso database. Nel secondo ogni utente ha il suo oggetto, e il dato del carrello esiste solo lì dentro.

Il confronto in una tabella

Dati letti il 29 ottobre 2026 dalle pagine ufficiali: prezzi D1, limiti D1, prezzi Durable Objects, limiti Durable Objects. Prezzi in dollari, piano Workers Paid, Durable Objects con backend SQLite.

D1

Durable Objects (SQLite)

Cos'è

Database SQL gestito, condiviso dall'app

Istanza di codice con storage privato, una per entità

Consistenza

Primario unico; repliche di lettura asincrone, consistenza sequenziale solo con la Sessions API

Storage fortemente consistente e transazionale; ordine globale delle richieste per oggetto

Concorrenza

Ogni database è single-threaded: una query alla volta

Ogni oggetto è single-threaded; scali creando più oggetti

Chi vede il dato

Ogni Worker con il binding

Solo il codice di quell'oggetto

Dimensione massima

10 GB per database, non aumentabile

10 GB per oggetto, oggetti illimitati

Righe lette

25 miliardi/mese incluse, poi $0,001 per milione

Uguale a D1

Righe scritte

50 milioni/mese incluse, poi $1 per milione

Uguale a D1

Storage

5 GB inclusi, poi $0,75 per GB-mese

5 GB-mese inclusi, poi $0,20 per GB-mese

Richieste e compute

Nessun costo di compute

1 milione di richieste/mese, poi $0,15 per milione; 400.000 GB-s, poi $12,50 per milione di GB-s

Backup

Time Travel sempre attivo, 30 giorni

API di point-in-time recovery, 30 giorni, da codice

Quando usarlo

Dati relazionali dell'app, query trasversali, letture frequenti

Coordinamento, stato per utente o tenant, WebSocket, timer per entità

Le stesse due righe di prezzo non sono un caso. La pagina prezzi dei Durable Objects dice che righe lette e scritte seguono il listino di D1. La differenza sta nel resto.

Consistenza: cosa ti garantiscono

Un Durable Object ha uno storage transazionale e fortemente consistente, privato all'istanza. Tutte le richieste per un nome passano dallo stesso oggetto. La pagina sulle opzioni di storage parla di strict serializability: c'è un ordine globale tra richieste e operazioni sullo storage.

D1 senza repliche si comporta in modo simile: tutte le query vanno al database primario. Con la read replication le cose cambiano. Le repliche ricevono le modifiche in modo asincrono, e la documentazione avverte che una replica può essere indietro di un tempo arbitrario.

La soluzione di D1 è la Sessions API. Ogni sessione porta un bookmark, e le query della sessione leggono una versione del database almeno aggiornata a quel punto. Ottieni consistenza sequenziale dentro la sessione, e le tue scritture le rileggi. Se non usi la Sessions API, le repliche non vengono usate e tutto va al primario.

Concorrenza: un thread per volta, in entrambi

Qui i due sono più simili di quanto sembri. Ogni database D1 è single-threaded ed elabora una query alla volta. La documentazione fa il conto: con query da 1 ms in media, al massimo 1.000 query al secondo; con query da 100 ms, 10. Oltre, il database mette in coda e poi risponde con un errore di sovraccarico.

Un Durable Object è single-threaded allo stesso modo. Il limite indicativo è 1.000 richieste al secondo per oggetto, ma gli oggetti per namespace sono illimitati. La scala orizzontale la fai tu: un oggetto per stanza, per documento, per utente.

La differenza sta nel modello di programmazione. In un Durable Object il runtime usa input gate e output gate per evitare le race condition sulle operazioni di storage. Una serie di scritture senza un await in mezzo viene inviata in modo atomico. Leggere un valore e riscriverlo incrementato è sicuro senza lock.

In D1 la transazione la esprimi con batch(): le istruzioni del batch sono una transazione SQL, e se una fallisce si annulla tutto. Ma la logica tra una query e l'altra gira nel Worker, che non ha stato condiviso. Per prenotare un posto senza vendere lo stesso posto due volte devi affidarti a vincoli e condizioni nell'SQL.

La pagina Rules of Durable Objects usa proprio una prenotazione di posti come esempio d'uso: tutte le richieste per lo stesso evento passano da un oggetto, e il controllo più l'inserimento non si sovrappongono.

Costo: dove si separano

Il minimo è lo stesso. Il piano Workers Paid costa $5 al mese per account e include Workers, KV, Hyperdrive e Durable Objects. D1 ha il suo listino a consumo, con le soglie incluse nella tabella sopra.

Su D1 paghi solo righe e storage. La pagina prezzi lo chiama scale-to-zero: se non fai query, non paghi compute. Non ci sono costi di egress, e le repliche di lettura non costano in più.

Su un Durable Object paghi anche due voci di compute. Contano come richieste le chiamate HTTP, le sessioni RPC, gli alarm e i messaggi WebSocket in entrata. Questi ultimi valgono con un rapporto di 20 a 1. Poi c'è la durata: il tempo di orologio in cui l'oggetto è attivo o resta in memoria senza poter andare in ibernazione. Viene calcolata sui 128 MB assegnati all'oggetto, anche se ne usi meno.

Due conseguenze pratiche, dalla documentazione. Un oggetto che tiene aperti WebSocket con accept() paga la durata per tutto il tempo della connessione: per questo esiste la WebSocket Hibernation API. E ogni operazione di I/O pendente può tenere un oggetto in memoria fino a 15 minuti.

Lo storage va nella direzione opposta: $0,75 per GB-mese su D1, $0,20 su un Durable Object SQLite. Per molti dati conservati e poco interrogati, la voce storage pesa meno sui Durable Objects. Per molte richieste brevi senza stato da coordinare, D1 non ti fa pagare richieste né durata.

Limiti: 10 GB, ma in due modi diversi

Il numero è lo stesso, il significato no. Un database D1 arriva al massimo a 10 GB, e Cloudflare scrive che il limite non si può alzare. La strada indicata è dividere: un database per utente, per tenant o per entità. Su Workers Paid puoi averne fino a 50.000 per account, aumentabili su richiesta.

Un Durable Object SQLite ha 10 GB per oggetto, ma gli oggetti non hanno limite e lo storage per account su Paid è illimitato. Se dividi i dati per entità, il tetto per singolo oggetto raramente è il vincolo.

Il prezzo della divisione è un altro. Lo storage di un Durable Object è accessibile solo dall'oggetto stesso. Una query su tutti gli utenti, un report o un'esportazione non sono una SELECT: devi passare da ogni oggetto, o tenere una copia dei dati che ti servono altrove. Con D1 una query trasversale è una riga di SQL.

Backup, migrazioni e posizione dei dati

Backup. D1 ha Time Travel sempre attivo: ripristino a qualunque minuto degli ultimi 30 giorni su Paid, 7 su Free, da Wrangler. I dettagli sono in Backup di D1. Un Durable Object SQLite ha un'API di point-in-time recovery sugli stessi 30 giorni, ma la chiami dal codice dell'oggetto, con i bookmark. Un ripristino per un utente è quindi possibile, ma è codice che devi scrivere.

Migrazioni. Su D1 lo schema si gestisce con le migrazioni di Wrangler, e un ORM come Drizzle o Prisma ci lavora sopra: il confronto è in Drizzle vs Prisma con D1 e SQLite. In un Durable Object la documentazione consiglia di creare le tabelle e applicare le migrazioni nel costruttore, dentro blockConcurrencyWhile(). Ogni oggetto aggiorna il suo schema quando si risveglia.

Posizione. Entrambi accettano una giurisdizione, per esempio eu, che vincola dove i dati girano e vengono salvati. Su D1 va scelta alla creazione del database. Sui Durable Objects si applica al namespace con jurisdiction("eu"). I location hint, invece, sono indicativi e valgono solo alla prima creazione dell'oggetto.

Quando usare l'uno, l'altro o entrambi

Usa D1 quando:

  • i dati sono relazionali e condivisi: utenti, prodotti, ordini, contenuti;
  • ti servono query trasversali, report e join su tutto il dataset;
  • il carico è soprattutto di lettura e ti interessano le repliche vicino agli utenti;
  • vuoi un database che si gestisce con migrazioni, ORM e backup già pronti.

Usa un Durable Object quando:

  • più client devono agire sullo stesso stato in tempo reale: chat, giochi, documenti condivisi;
  • un'operazione non deve mai sovrapporsi a un'altra sulla stessa entità: prenotazioni, inventario, quote;
  • ogni utente o tenant ha un suo database isolato;
  • ti servono WebSocket di lunga durata o un timer per entità con gli alarm.

Il caso del limite di richieste è già su fnbit: nel confronto sul rate limiting su Workers il Durable Object è l'opzione esatta, da usare per le quote che finiscono in fattura.

Usarli insieme è spesso la scelta più semplice. D1 tiene il catalogo e i dati che vuoi interrogare con SQL. I Durable Objects tengono lo stato vivo di una stanza, un carrello o un documento. Su D1 scrivono solo quello che serve al resto dell'app.

Un errore da evitare in ogni caso: un solo Durable Object per tutta l'app. La documentazione lo chiama esplicitamente un anti-pattern, perché tutto il traffico passa da un'istanza single-threaded. Un contatore globale o un rate limiter globale in un solo oggetto diventano il collo di bottiglia.

Resta il caso del dato che supera i 10 GB e non si divide. Lì la strada è un Postgres gestito: il confronto è in Turso, D1 o Postgres gestito, e il ragionamento di partenza in SQLite vs Postgres per un prodotto indie.

Cosa conviene fare adesso

  • Per ogni tipo di dato, scrivi chi lo deve leggere. Se la risposta è "tutta l'app", parti da D1.
  • Cerca nel codice i punti dove due richieste possono sovrapporsi sulla stessa entità: prenotazioni, contatori, quote. Sono i candidati per un Durable Object.
  • Scegli l'"atomo" di coordinamento prima di scrivere la classe: una stanza, un documento, un utente. Mai un oggetto unico per tutto.
  • Se usi Durable Objects con WebSocket, parti dalla Hibernation API: la durata è la voce che cresce più in fretta.
  • Se attivi le repliche di D1, usa la Sessions API ovunque leggi subito dopo una scrittura.
  • Rileggi i listini il giorno in cui decidi. Questi sono del 29 ottobre 2026.

Se stai ancora decidendo la piattaforma, il punto di partenza è Vercel vs Cloudflare vs VPS. Per lo stack completo su Cloudflare c'è Workers, D1 e R2, e i termini come binding e Time Travel sono nel glossario.

[ DOMANDE FREQUENTI ]

Domande frequenti

01Durable Objects e D1 sono alternative?

Non proprio. D1 è un database SQL condiviso dall'app, mentre un Durable Object è un'istanza di codice con storage privato per una singola entità. Ogni database D1 è sostenuto da un Durable Object. Si scelgono in base a chi deve vedere il dato e a quanto serve coordinare le scritture.

02Quanto costano D1 e Durable Objects su Workers Paid?

Il piano parte da $5 al mese per account. Righe lette e scritte hanno lo stesso listino: 25 miliardi e 50 milioni al mese incluse, poi $0,001 e $1 per milione. Lo storage costa $0,75 per GB-mese su D1 e $0,20 sui Durable Objects SQLite. I Durable Objects pagano anche richieste e durata.

03I Durable Objects sono disponibili sul piano Free?

Sì, ma solo con il backend SQLite. Sul piano Free ci sono soglie giornaliere: 100.000 richieste, 13.000 GB-s di durata, 5 milioni di righe lette e 100.000 scritte, 5 GB di storage in totale. Superata una soglia, le operazioni di quel tipo falliscono con un errore.

04Posso fare una query su tutti i Durable Objects come su un database?

No. Lo storage di un Durable Object è accessibile solo dall'oggetto stesso. Per un report su tutti gli utenti devi chiamare ogni oggetto o tenere una copia dei dati aggregati altrove, per esempio in D1. Per query trasversali frequenti D1 è la scelta più semplice.

05D1 è fortemente consistente?

Il database primario sì: tutte le query passano da lì. Con le repliche di lettura attive, le repliche si aggiornano in modo asincrono e possono essere indietro. La Sessions API, con i bookmark, garantisce consistenza sequenziale dentro una sessione, così rileggi le tue scritture.

06Qual è il limite di dimensione di D1 e di un Durable Object?

10 GB in entrambi i casi. Per D1 è il massimo per database e non si può alzare; su Paid puoi avere fino a 50.000 database per account. Per i Durable Objects è il massimo per oggetto, ma il numero di oggetti e lo storage per account su Paid sono illimitati.

Condividi