I lavoratori sono apolidi per definizione. Ogni richiesta arriva in modo pulito e isolato, senza memoria di ciò che è accaduto prima, senza stato condiviso con altre istanze in esecuzione in parallelo. Questo isolamento è esattamente ciò che ti consente di scalare milioni di richieste senza coordinamento tra le istanze. Gli oggetti durevoli rompono intenzionalmente questo contratto e capire perché e cosa cambia esattamente determina se li utilizzerai correttamente o ne soffrirai.
Cos'è realmente un Oggetto Durevole
Un oggetto durevole è una classe JavaScript con archiviazione persistente e un dettaglio di esecuzione che cambia tutto: le richieste arrivano in serie. Non c'è competizione all'interno di un'istanza. Mentre il metodo fetch() di un DO elabora una richiesta, tutti gli altri che arrivano allo stesso DO vengono messi in coda all'esterno.
Ciò elimina un'intera categoria di bug che derivano dall'accesso simultaneo allo stato condiviso. Non esistono condizioni di competizione all'interno di un DO. Se si incrementa un contatore, si mantiene il valore e si risponde, non è possibile intercalare altre richieste tra queste operazioni. La sequenza è garantita dalla piattaforma.
Ogni istanza DO esiste in un singolo PoP Cloudflare, il punto di presenza più vicino alla prima richiesta che l'ha creata. Le richieste successive allo stesso DO vengono instradate a quello specifico PoP, indipendentemente da dove si trovi il client. Se la tua istanza è stata creata a Francoforte e un cliente di San Paolo le presenta una richiesta, la richiesta viaggia a Francoforte. Ciò ha implicazioni reali in termini di latenza per casi d'uso distribuiti geograficamente ed è importante conoscerlo prima della progettazione.
Perché KV e D1 non risolvono lo stesso problema
Il confronto naturale è con KV e D1, le altre opzioni di persistenza sulla piattaforma. La differenza non è di convenienza, ma di coerenza del modello.
Alla fine KV è coerente. La scrittura su un PoP si propaga agli altri con un ritardo misurabile, in genere compreso tra pochi millisecondi e un minuto. Le letture in PoP diversi potrebbero visualizzare versioni diverse dello stesso valore. Per la cache di lettura, per impostazioni che cambiano raramente, KV funziona perfettamente. Per qualsiasi operazione che richiede "lettura, calcolo, scrittura" con la garanzia che non sia avvenuta nessun'altra scrittura nel mezzo, non funziona.
D1 con transazioni risolve l'atomicità per letture e scritture, ma introduce la latenza tra regioni per le scritture. Tutta la scrittura va alla regione primaria della banca, che potrebbe trovarsi in un PoP diverso rispetto al tuo lavoratore. Per molte applicazioni questo è accettabile. Per lo stato che deve essere modificato con bassa latenza e alta frequenza o per il coordinamento in tempo reale tra client, il costo di latenza di ogni scrittura su un'area primaria modifica il problema.
Un DO mantiene lo stato in memoria e persiste atomicamente tramite l'API di archiviazione. L'operazione await this.ctx.storage.put('count', this.count) è linearizzabile: ogni successiva richiesta allo stesso DO vedrà il valore che è stato scritto, senza eccezioni. Questa garanzia, combinata con l'esecuzione seriale, è ciò che rende possibile creare contatori atomici, blocchi distribuiti, registri eventi ordinati e coordinamento tra client simultanei senza la complessità dell'implementazione del controllo della concorrenza nell'applicazione.
In che modo l'esecuzione seriale influisce sul throughput
L’esecuzione in serie è la garanzia e il collo di bottiglia allo stesso tempo. Un DO che elabora ogni richiesta in 5 ms può gestire circa 200 richieste al secondo. Se la tua applicazione instrada 500 req/s allo stesso DO, 300 di essi vengono messi in coda e aggiungono latenza.
Questo soffitto esiste in base alla progettazione. Se un singolo DO diventa un collo di bottiglia, la soluzione è lo sharding: invece di un ID fisso per una risorsa, distribuirla per suffisso numerico. Per una risorsa identificata da userId, la strategia user-{userId}-shard-{userId.charCodeAt(0) % 10} distribuisce il carico tra dieci istanze, ciascuna con il proprio limite di throughput. La scelta di quale frammento utilizzare deve essere deterministica in modo che le letture e le scritture dallo stesso client raggiungano sempre la stessa istanza.
L'ibernazione modifica il modello di costo per mantenere attive molte istanze. Quando un DO non ha richieste in sospeso, va automaticamente in ibernazione, senza costi di elaborazione durante l'ibernazione. Il risveglio dall'ibernazione richiede meno di un millisecondo. Per le applicazioni con molti DO sparsi (uno per utente, uno per sala, uno per sessione), il costo di elaborazione effettivo è proporzionale all'utilizzo attivo, non al numero totale di istanze.
Cosa ti richiede la fascia di prezzo
Gli oggetti durevoli richiedono il piano Workers Paid, con un minimo di $ 5 al mese. Da lì, il costo ha tre componenti: richieste (0,15 dollari/milione, con 1 milione gratuito/mese), elaborazione in GB-secondo ($12,50/milione GB-secondo, con 400mila gratuiti/mese) e spazio di archiviazione (0,20 $/GB-mese, con 1 GB gratuito).
Il GB-secondo è la parte che crea confusione. Un DO in esecuzione con 128 MB di memoria per 1 secondo consuma 0,125 GB al secondo. Con 400.000 GB al secondo gratuiti al mese, ciò equivale a 3,2 milioni di secondi di esecuzione con un ingombro minimo. Una richiesta media di 10 ms a 128 MB consuma 0,00125 GB al secondo, quindi il livello gratuito copre circa 320 milioni di richieste di memoria minima di 10 ms. Se il tuo DO esegue calcoli pesanti, mantiene uno stato di memoria di grandi dimensioni o elabora molte richieste al secondo, il consumo di GB al secondo aumenta proporzionalmente.
Un altro costo: API di allarme. DO può programmare l'esecuzione di un metodo alarm() in una data futura, anche se nel frattempo entra in ibernazione. Il costo è di 0,15 dollari per milione di chiamate di allarme, la stessa tabella delle richieste normali.
Quando scegliere gli Oggetti Durevoli
La domanda che determina se DO è lo strumento giusto è semplice: lo stato che devi gestire richiede un accesso serializzato da client concorrenti? Se sì, FARE. In caso contrario, esiste un'opzione più semplice ed economica.
Casi in cui DO risolve qualcosa che nessun altro sulla piattaforma risolve con la stessa garanzia: contatori atomici con elevata frequenza di scrittura, coordinamento della presenza in tempo reale (chi è connesso a una stanza), code di eventi ordinate per arrivo, lock distribuiti con timeout tramite Alarm API e sessioni di editing collaborativo dove più client modificano lo stesso documento.
Cosa non è un caso d'uso DO: archiviazione di dati relazionali con query ad hoc (D1 è molto più adatto), caching di lettura con scritture occasionali (KV è più economico e distribuito a livello globale), archiviazione di file (R2) e qualsiasi stato che si associa naturalmente a una singola scrittura senza letture simultanee che dipendono dallo stato precedente.
Cosa monitorare in produzione
Due indicatori che appaiono precocemente nei problemi con i DO: l'aumento della latenza della coda (un segno che un DO è diventato un collo di bottiglia e necessita di sharding) e il consumo di GB-secondi che cresce oltre le aspettative (un segno che i DO mantengono lo stato in memoria più a lungo del necessario o vengono mantenuti attivi con lavoro non necessario).
L'API di archiviazione ha un dettaglio operativo che ti sorprende: list() restituisce per impostazione predefinita un massimo di 128 voci per chiamata. Per set di dati più grandi, l'iterazione deve utilizzare il cursore. Ignorare questo in fase di sviluppo e scoprirlo in produzione con mille chiavi ha un costo reale in termini di richieste e tempi di risposta.
La decisione di utilizzare DO o meno riguarda meno le prestazioni e più la garanzia di coerenza richiesta dall'applicazione. Comprendere questo requisito prima di scegliere lo strumento evita una costosa migrazione successiva.
Leggi anche
- Il modello di programmazione degli oggetti durevoli: cosa è diverso da qualsiasi cosa tu abbia mai usato
- Oggetti durevoli in produzione: come sarà la bolletta e i limiti che sorprendono
- Oggetti durevoli e WebSocket: multiplayer senza server dedicato
- Quando gli oggetti durevoli sono la risposta sbagliata
- Cloudflare KV: cosa significa distribuzione globale quando è necessario scrivere
- Cloudflare Workers vs Pages: la differenza che conta prima di scegliere
