Comprendere le query in tempo reale su larga scala
Leggi questo documento per indicazioni sulla scalabilità della tua app serverless oltre migliaia di operazioni al secondo o centinaia di migliaia di utenti simultanei. Questo documento include argomenti avanzati per aiutarti a comprendere il sistema in modo approfondito. Se hai appena iniziato a utilizzare Firestore, consulta la guida rapida.
Firestore e gli SDK web/mobile Firebase forniscono un modello potente per lo sviluppo di app serverless in cui il codice lato client accede direttamente al database. Gli SDK consentono ai client di ascoltare gli aggiornamenti dei dati in tempo reale. Puoi utilizzare gli aggiornamenti in tempo reale per creare app reattive che non richiedono infrastrutture server. Sebbene sia molto facile avviare un'attività, è utile comprendere i vincoli dei sistemi che compongono Firestore in modo che l'app serverless venga scalata e funzioni bene quando il traffico aumenta.
Per suggerimenti su come scalare la tua app, consulta le sezioni seguenti.
Scegliere una posizione del database vicina ai tuoi utenti
Il seguente diagramma mostra l'architettura di un'app in tempo reale:
Quando un'app in esecuzione sul dispositivo (mobile o web) di un utente stabilisce una
connessione a Firestore, la connessione viene indirizzata a un
server frontend Firestore nella stessa
regione in cui si trova il database. Ad esempio,
se il tuo database si trova in us-east1
, la connessione va anche a un
frontend Firestore anch'esso in us-east1
. Queste connessioni sono
di lunga durata e rimangono aperte finché non vengono chiuse esplicitamente dall'app. Il
frontend legge i dati dai sistemi di archiviazione Firestore sottostanti.
La distanza tra la posizione fisica di un utente e la posizione del database Firestore influisce sulla latenza riscontrata dall'utente. Ad esempio, un utente in India la cui app comunica con un database in una regione Google Cloud del Nord America potrebbe trovare l'esperienza più lenta e l'app meno reattiva rispetto a se il database si trovasse più vicino, ad esempio in India o in un'altra parte dell'Asia.
Progettazione per l'affidabilità
I seguenti argomenti migliorano o influiscono sull'affidabilità della tua app:
Attivare la modalità offline
Gli SDK Firebase forniscono la persistenza dei dati offline. Se l'app sul dispositivo dell'utente non riesce a connettersi a Firestore, l'app rimane utilizzabile grazie ai dati memorizzati nella cache locale. In questo modo, l'accesso ai dati è garantito anche quando gli utenti riscontrano connessioni a internet instabili o perdono completamente l'accesso per diverse ore o giorni. Per maggiori dettagli sulla modalità offline, consulta Attivare i dati offline.
Informazioni sui nuovi tentativi automatici
Gli SDK Firebase si occupano di riprovare le operazioni e di ristabilire le connessioni interrotte. Ciò consente di aggirare gli errori temporanei causati dal riavvio dei server o da problemi di rete tra il client e il database.
Scegliere tra località regionali e multiregionali
Esistono diversi compromessi quando si sceglie tra località regionali e multiregionali. La differenza principale è il modo in cui i dati vengono replicati. Ciò determina le garanzie di disponibilità della tua app. Un'istanza multiregionale offre una maggiore affidabilità di pubblicazione e aumenta la durabilità dei dati, ma il compromesso è il costo.
Informazioni sul sistema di query in tempo reale
Le query in tempo reale, chiamate anche listener snapshot, consentono all'app di ascoltare le modifiche al database e ricevere notifiche a bassa latenza non appena i dati cambiano. Un'app può ottenere lo stesso risultato eseguendo periodicamente il polling del database per gli aggiornamenti, ma spesso è più lenta, più costosa e richiede più codice. Per esempi di come configurare e utilizzare le query in tempo reale, consulta Ricevere aggiornamenti in tempo reale. Le sezioni seguenti descrivono in dettaglio il funzionamento dei listener di snapshot e alcune delle best practice per scalare le query in tempo reale mantenendo le prestazioni.
Immagina due utenti che si connettono a Firestore tramite un'app di messaggistica creata con uno degli SDK mobile.
Il client A scrive nel database per aggiungere e aggiornare i documenti in una raccolta
denominata chatroom
:
collection chatroom:
document message1:
from: 'Sparky'
message: 'Welcome to Firestore!'
document message2:
from: 'Santa'
message: 'Presents are coming'
Il client B ascolta gli aggiornamenti nella stessa raccolta utilizzando un listener snapshot. Il cliente B riceve una notifica immediata ogni volta che qualcuno crea un nuovo messaggio. Il seguente diagramma mostra l'architettura di un listener di snapshot:
Quando il client B connette un listener di snapshot al database, si verifica la seguente sequenza di eventi:
- Il client B apre una connessione a Firestore e registra un
listener chiamando
onSnapshot(collection("chatroom"))
tramite l'SDK Firebase. Questo ascoltatore può rimanere attivo per ore. - Il frontend Firestore esegue query sul sistema di archiviazione sottostante per eseguire il bootstrap del set di dati. Carica l'intero set di risultati dei documenti corrispondenti. Ci riferiamo a questa situazione come query di polling. Il sistema valuta le regole di sicurezza Firebase del database per verificare che l'utente possa accedere a questi dati. Se l'utente è autorizzato, il database restituisce i dati all'utente.
- La query del client B passa quindi alla modalità di ascolto. Il listener si registra con un gestore di abbonamenti e attende gli aggiornamenti dei dati.
- Il client A ora invia un'operazione di scrittura per modificare un documento.
- Il database esegue il commit della modifica del documento nel sistema di archiviazione.
- A livello transazionale, il sistema esegue lo stesso aggiornamento in un changelog interno. Il log delle modifiche stabilisce un ordine rigoroso delle modifiche man mano che vengono apportate.
- Il log delle modifiche distribuisce a sua volta i dati aggiornati a un pool di gestori di abbonamenti.
- Viene eseguito un corrispondente di query inversa per verificare se il documento aggiornato corrisponde a uno dei listener di snapshot attualmente registrati. In questo esempio, il documento corrisponde al listener snapshot del cliente B. Come suggerisce il nome, puoi considerare il matcher di query inversa come una normale query di database, ma eseguita al contrario. Anziché cercare nei documenti quelli che corrispondono a una query, esegue una ricerca efficiente nelle query per trovare quelle che corrispondono a un documento in arrivo. Una volta trovata una corrispondenza, il sistema inoltra il documento in questione ai listener degli snapshot. Il sistema valuta quindi le regole di sicurezza di Firebase del database per garantire che solo gli utenti autorizzati ricevano i dati.
- Il sistema inoltra l'aggiornamento del documento all'SDK sul dispositivo del client B e
viene attivato il callback
onSnapshot
. Se la persistenza locale è abilitata, l'SDK applica l'aggiornamento anche alla cache locale.
Una parte fondamentale della scalabilità di Firestore dipende dalla distribuzione del log delle modifiche ai gestori delle iscrizioni e ai server di frontend. La distribuzione consente a una singola modifica dei dati di propagarsi in modo efficiente per servire milioni di query in tempo reale e utenti connessi. Eseguendo molte repliche di tutti questi componenti in più zone (o più regioni nel caso di un deployment multiregionale), Firestore raggiunge un'elevata disponibilità e scalabilità.
È importante notare che tutte le operazioni di lettura eseguite dagli SDK per dispositivi mobili e web seguono il modello riportato sopra. Eseguono una query di polling seguita dalla modalità di ascolto per mantenere le garanzie di coerenza. Ciò vale anche per i listener in tempo reale, le chiamate per recuperare un documento e le query one-shot. Puoi considerare i recuperi di singoli documenti e le query one-shot come listener di snapshot di breve durata che presentano vincoli simili in termini di prestazioni.
Applica le best practice per scalare le query in tempo reale
Applica le seguenti best practice per progettare query scalabili in tempo reale.
Comprendere il traffico di scrittura elevato nel sistema
Questa sezione ti aiuta a capire come risponde il sistema a un numero crescente di richieste di scrittura.
I log delle modifiche di Firestore che gestiscono le query in tempo reale vengono scalati automaticamente in orizzontale man mano che il traffico di scrittura aumenta. Man mano che la velocità di scrittura per un database aumenta oltre ciò che un singolo server può gestire, il changelog viene suddiviso su più server e l'elaborazione delle query inizia a utilizzare i dati di più gestori di abbonamenti anziché di uno solo. Dal punto di vista del client e dell'SDK, tutto questo è trasparente e non è richiesta alcuna azione da parte dell'app quando si verificano le suddivisioni. Il seguente diagramma mostra come vengono scalate le query in tempo reale:
La scalabilità automatica ti consente di aumentare il traffico di scrittura senza limiti, ma man mano che il traffico aumenta, il sistema potrebbe impiegare un po' di tempo per rispondere. Segui i consigli della regola 5-5-5 per evitare di creare un hotspot di scrittura. Key Visualizer è uno strumento utile per analizzare gli hotspot di scrittura.
Molte app hanno una crescita organica prevedibile, che Firestore può gestire senza precauzioni. I carichi di lavoro batch come l'importazione di un set di dati di grandi dimensioni, tuttavia, possono aumentare le scritture troppo rapidamente. Durante la progettazione dell'app, tieni presente la provenienza del traffico di scrittura.
Comprendere l'interazione tra scritture e letture
Puoi pensare al sistema di query in tempo reale come a una pipeline che collega le operazioni di scrittura con i lettori. Ogni volta che un documento viene creato, aggiornato o eliminato, la modifica viene propagata dal sistema di archiviazione ai listener attualmente registrati. La struttura del log delle modifiche di Firestore garantisce una forte coerenza, il che significa che la tua app non riceve mai notifiche di aggiornamenti fuori ordine rispetto al momento in cui il database ha eseguito il commit delle modifiche ai dati. In questo modo, lo sviluppo delle app viene semplificato rimuovendo i casi limite relativi alla coerenza dei dati.
Questa pipeline connessa significa che un'operazione di scrittura che causa hotspot o contesa di blocchi può influire negativamente sulle operazioni di lettura. Quando le operazioni di scrittura non vanno a buon fine o subiscono una limitazione, una lettura potrebbe bloccarsi in attesa di dati coerenti dal log delle modifiche. Se ciò accade nella tua app, potresti notare sia operazioni di scrittura lente sia tempi di risposta lenti e correlati per le query. Evitare gli hotspot è la chiave per evitare questo problema.
Mantieni le operazioni di scrittura e i documenti di dimensioni ridotte
Quando crei app con listener snapshot, in genere vuoi che gli utenti vengano informati rapidamente delle modifiche ai dati. Per farlo, cerca di mantenere le dimensioni ridotte. Il sistema può inviare documenti di piccole dimensioni con decine di campi molto rapidamente. I documenti più grandi con centinaia di campi e grandi quantità di dati richiedono più tempo per l'elaborazione.
Allo stesso modo, privilegia le operazioni di commit e scrittura brevi e veloci per mantenere bassa la latenza. Batch di grandi dimensioni potrebbero offrire una velocità effettiva maggiore dal punto di vista dell'autore, ma potrebbero effettivamente aumentare il tempo di notifica per gli ascoltatori di snapshot. Spesso questo approccio è controintuitivo rispetto all'utilizzo di altri sistemi di database in cui potresti utilizzare il batching per migliorare le prestazioni.
Utilizzare listener efficienti
Man mano che i tassi di scrittura per il tuo database aumentano, Firestore suddivide l'elaborazione dei dati su più server. L'algoritmo di sharding di Firestore tenta di collocare i dati della stessa raccolta o dello stesso gruppo di raccolte sullo stesso server changelog. Il sistema tenta di massimizzare il throughput di scrittura possibile mantenendo il numero di server coinvolti nell'elaborazione di una query il più basso possibile.
Tuttavia, alcuni pattern potrebbero comunque portare a un comportamento non ottimale per i listener degli snapshot. Ad esempio, se la tua app archivia la maggior parte dei dati in una raccolta di grandi dimensioni, il listener potrebbe dover connettersi a molti server per ricevere tutti i dati di cui ha bisogno. Ciò vale anche se applichi un filtro query. La connessione a molti server aumenta il rischio di risposte più lente.
Per evitare queste risposte più lente, progetta lo schema e l'app in modo che il sistema possa servire gli ascoltatori senza dover accedere a molti server diversi. Potrebbe essere meglio suddividere i dati in raccolte più piccole con tassi di scrittura inferiori.
È come pensare alle query sulle prestazioni in un database relazionale che richiedono scansioni complete delle tabelle. In un database relazionale, una query che richiede una scansione completa della tabella equivale a un listener snapshot che monitora una raccolta con un churn elevato. Potrebbe essere eseguita lentamente rispetto a una query che il database può gestire utilizzando un indice più specifico. Una query con un indice più specifico è come un listener di snapshot che monitora un singolo documento o una raccolta che cambia meno spesso. Devi caricare testare la tua app per comprendere al meglio il comportamento e le esigenze del tuo caso d'uso.
Mantenere la velocità delle query di polling
Un'altra parte fondamentale delle query in tempo reale reattive consiste nell'assicurarsi che la query di polling per il bootstrap dei dati sia rapida ed efficiente. La prima volta che si connette un nuovo listener di snapshot, questo deve caricare l'intero insieme di risultati e inviarlo al dispositivo dell'utente. Le query lente rendono la tua app meno reattiva. Ciò include, ad esempio, query che tentano di leggere molti documenti o query che non utilizzano gli indici appropriati.
In alcune circostanze, un ascoltatore potrebbe anche passare da uno stato di ascolto a uno stato di polling. Questo avviene automaticamente ed è trasparente per gli SDK e la tua app. Le seguenti condizioni potrebbero attivare uno stato di polling:
- Il sistema ribilancia un changelog a causa di variazioni del carico.
- Gli hotspot causano scritture non riuscite o ritardate nel database.
- I riavvii temporanei del server influiscono temporaneamente sugli ascoltatori.
Se le query di polling sono abbastanza veloci, lo stato di polling diventa trasparente per gli utenti della tua app.
Privilegia gli ascoltatori di lunga data
Aprire e mantenere attivi i listener il più a lungo possibile è spesso il modo più economico per creare un'app che utilizza Firestore. Quando utilizzi Firestore, ti vengono addebitati i documenti restituiti alla tua app e non il mantenimento di una connessione aperta. Un listener di snapshot di lunga durata legge solo i dati necessari per eseguire la query per tutta la sua durata. Ciò include un'operazione di polling iniziale seguita da notifiche quando i dati cambiano effettivamente. Le query una tantum, invece, rileggono i dati che potrebbero non essere stati modificati dall'ultima esecuzione della query da parte dell'app.
Nei casi in cui la tua app deve utilizzare una velocità elevata di dati, i listener di snapshot potrebbero non essere appropriati. Ad esempio, se il tuo caso d'uso invia molti documenti al secondo tramite una connessione per un periodo di tempo prolungato, potrebbe essere meglio optare per query one-shot eseguite a una frequenza inferiore.
Passaggi successivi
- Scopri come utilizzare i listener snapshot.
- Scopri altre best practice.