Cloudflare
Durable Objects
Arquitetura
Trade-offs
Decisão Técnica

Quando gli oggetti durevoli sono la risposta sbagliata

Un'analisi onesta su quando non utilizzare gli Oggetti Durevoli, con le alternative corrette per ogni caso d'uso e il quesito diagnostico che semplifica la decisione.

Gli oggetti durevoli acquisiscono importanza perché risolvono qualcosa di difficile (stato coerente con accesso serializzato al perimetro) e questo crea un pregiudizio: gli ingegneri che hanno appena appreso lo strumento tendono ad applicarlo a problemi che non è necessario risolvere. Il risultato non è un codice difettoso. È un codice corretto, funzionante, più costoso e più complesso di quanto dovrebbe essere. Per un semplice CRUD basato su DO quando D1 sarebbe sufficiente, il conto potrebbe essere da dieci a cinquanta volte più grande, senza alcun vantaggio di coerenza richiesto dal caso d’uso.

La domanda che determina se hai bisogno di DO

Prima di decidere sugli oggetti durevoli, c'è una domanda concreta: lo stato che devi gestire richiede l'accesso serializzato da più client simultanei?

Serializzato significa che l'ordine delle operazioni è importante e che due operazioni simultanee sugli stessi dati devono escludersi a vicenda. Più client concorrenti significano che più di un agente può tentare di modificare gli stessi dati contemporaneamente ed entrambi gli agenti devono vedere un risultato coerente.

Se la risposta è no, se i dati vengono letti da molti ma scritti raramente o se le scritture simultanee sullo stesso record sono improbabili o gestite da un altro livello, un DO aggiunge complessità e costi senza aggiungere garanzie utili.

Dove D1 è la risposta giusta

Per i dati relazionali, D1 è il posto naturale all'interno dello stack Cloudflare. Ha SQL completo, supporta JOIN, indici, transazioni, query ad hoc: tutto ciò che D1 ha e DO non ha. Un DO è un archivio di valori-chiave linearizzabile con API procedurale. Non ha modo di rispondere "elenca tutti gli utenti che hanno effettuato l'accesso negli ultimi 7 giorni e hanno ancora un saldo positivo" senza che tu abbia precalcolato e archiviato esplicitamente questi dati.

D1 ha un costo molto più basso per i carichi di lettura: $ 0,001/milione di righe lette, $ 1,00/milione di righe scritte. La latenza di scrittura aggiuntiva nella regione della banca primaria, che è lo svantaggio più citato di D1, influisce solo sulle operazioni di scrittura. Per le applicazioni in cui prevalgono le letture, questo compromesso è ampiamente a favore di D1.

Il caso confuso: "ma ho bisogno che le scritture siano atomiche". D1 ha transazioni. BEGIN; UPDATE a...; UPDATE b...; COMMIT; garantisce l'atomicità. La differenza è che D1 utilizza lock del database – con il potenziale di conflitto in caso di concorrenza elevata – mentre un DO utilizza la serializzazione in base alla progettazione. Per la stragrande maggioranza delle applicazioni con carichi di scrittura normali, D1 con transazioni è sufficiente e molto più economico.

Dove KV è la risposta giusta

KV è ottimizzato per il modello che domina la maggior parte dei casi d'uso edge: molte letture, scritture occasionali, nessun requisito di forte coerenza tra diversi PoP. Cache di configurazione, risultati di calcolo precalcolati, sessioni utente in cui la coerenza finale è accettabile: KV risponde alle letture della cache locale nel PoP con una latenza inferiore al millisecondo e un modello di prezzo che favorisce la lettura pesante.

L'errore è presumere che, poiché KV non dispone di operazioni di lettura-modifica-scrittura atomiche, è necessario DO per tutto ciò che cambia. La stragrande maggioranza dei dati nelle applicazioni web cambia con schemi che non richiedono una rigorosa atomicità: il profilo utente che viene aggiornato quando l’utente modifica le proprie preferenze non necessita della mutua esclusione con un altro script concorrente, perché nessun altro sta modificando lo stesso profilo allo stesso tempo.

DO inizia ad avere senso quando la frequenza delle scritture simultanee sugli stessi dati è sufficientemente elevata da far sì che l'eventuale coerenza di KV produca risultati errati visibili all'utente.

La limitazione della velocità non è un caso d'uso DO

La limitazione della velocità è l'esempio più comune di "sembra che sia necessario fare ma non è così". L'intuizione è corretta: il rate limiting richiede un contatore per utente che viene incrementato atomicamente ad ogni richiesta. Se due Worker incrementano lo stesso contatore contemporaneamente, potrebbe verificarsi una race condition che consente a un utente di effettuare più richieste rispetto al limite.

L'API Rate Limiting di Cloudflare risolve questo problema in modo nativo, senza codice, senza DO, senza costi aggiuntivi oltre al piano. È disponibile su tutti i piani a pagamento, supporta limiti per IP, per utente autenticato, per percorso, per intestazione e con periodi configurabili. Per la limitazione della velocità all'edge, l'utilizzo dell'API nativa è più semplice, economico e affidabile rispetto all'implementazione di un contatore DO.

Il caso legittimo di DO per la limitazione della velocità è quando si hanno requisiti che l'API nativa non copre: logica della finestra scorrevole personalizzata con precisione esatta, limiti che dipendono dai dati della sessione utente che non si trovano nelle intestazioni o limitazione della velocità che deve essere coerente con altri dati che già risiedono in un'applicazione DO.

Code di lavoratori per l'elaborazione asincrona

Un altro modello che a volte appare come candidato al DO: una coda di lavoro in cui più produttori mettono in coda le attività e più consumatori elaborano. DO potrebbe modellarlo come un oggetto con un elenco di attività e un ciclo di elaborazione.

Workers Queues risolve questo problema in modo nativo: i produttori guadagnano env.QUEUE.send(message), i consumatori ricevono batch tramite il gestore, con nuovi tentativi automatici, coda di messaggi non recapitabili e addebito separato di 0,40 dollari per milione di messaggi. Per l'elaborazione asincrona con nuovi tentativi e backoff, le code sono più adatte, più economiche e non hanno il limite massimo di throughput seriale di un DO.

DO per l'elaborazione asincrona ha senso quando il consumo deve essere serializzato per identità (elaborando tutte le azioni di un utente in ordine, senza parallelismo) e quando tale elaborazione deve accedere comunque a uno stato locale rispetto al DO.

L'archiviazione dei file è R2, non l'archiviazione DO

L'archiviazione DO è un archivio di valori-chiave ottimizzato per piccoli oggetti: impostazioni, contatori, stato della sessione, messaggi. Non ha limiti documentati in termini di valore, ma è progettato per dati che possono ragionevolmente rientrare in una richiesta. Per file, immagini, video e qualsiasi blob di grandi dimensioni, R2 è il posto giusto: 0,015 USD per GB al mese di storage (meno di un decimo del costo dello storage DO), nessun costo in uscita per i Worker, con API compatibile con S3.

L'archiviazione DO non sostituisce l'archiviazione di oggetti: è un archivio di stati transazionali per DO stesso.

Diagnosi prima di scegliere

Quattro casi d'uso in cui DO risolve qualcosa che le alternative non risolvono con le stesse garanzie: editing collaborativo con più client che modificano lo stesso documento contemporaneamente, coordinamento della presenza in tempo reale dove l'elenco di chi è online deve essere coerente, blocchi distribuiti con timeout dove la scadenza deve essere garantita anche se il titolare si blocca e log degli eventi ordinati per arrivo dove l'ordine di inserimento è semanticamente importante.

Per tutto ciò che non rientra in questo elenco, esiste un'alternativa all'interno della piattaforma che è più economica, più semplice o entrambe le cose. La decisione di utilizzare DO inizia con la questione della serializzazione. Se non riesci a spiegare perché la serializzazione dell'accesso ai dati è necessaria per il caso d'uso, DO probabilmente non è lo strumento adatto.

Il rischio di utilizzare DO dove non è necessario non è che si rompa qualcosa: il codice funzionerà. Il costo è quello di sostenere una complessità non necessaria: un'astrazione che impone limiti di throughput seriale, richiede un piano Workers Paid come prerequisito, ha una curva di apprendimento distinta e costa più delle alternative per i casi che le alternative risolvono bene. Per un team che utilizza già DO altrove nel sistema, il costo marginale di un DO in più è inferiore. Per un team che inizia in Cloudflare con un semplice CRUD, iniziare con D1 e KV e aggiungere DO dove la serializzazione è veramente necessaria è il giusto ordine di complessità.

Leggi anche