Comprendi le query in tempo reale su larga scala

Leggi questo documento per avere indicazioni su come scalare la 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 per il web e il mobile di Firebase forniscono un modello avanzato per lo sviluppo di app serverless in cui il codice lato client accede direttamente al database. Gli SDK consentono ai clienti di ascoltare gli aggiornamenti dei dati in tempo reale. Puoi utilizzare gli aggiornamenti in tempo reale per creare app adattabili che non richiedono un'infrastruttura server. Sebbene sia molto facile avviare un'applicazione, è utile comprendere i vincoli nei sistemi che compongono Firestore in modo che la tua app serverless sia scalabile e funzioni bene quando il traffico aumenta.

Consulta le sezioni seguenti per consigli su come scalare la tua app.

Scegli una posizione del database vicina agli utenti

Il seguente diagramma illustra l'architettura di un'app in tempo reale:

Esempio di architettura dell'app in tempo reale

Quando un'app in esecuzione sul dispositivo di un utente (mobile o web) stabilisce una connessione a Firestore, la connessione viene instradata a un server frontend di Firestore nella stessa regione in cui si trova il database. Ad esempio, se il database si trova in us-east1, la connessione passa anche a un frontend Firestore anche in us-east1. Queste connessioni hanno durata prolungata e rimangono aperte fino alla chiusura esplicita dell'app. Il frontend legge i dati dai sistemi di archiviazione Firestore sottostanti.

La distanza tra la località fisica di un utente e la località del database Firestore influisce sulla latenza sperimentata dall'utente. Ad esempio, per un utente in India la cui app comunica con un database in un'area geografica di Google Cloud in Nord America, l'esperienza potrebbe risultare più lenta e meno rapida rispetto a quando il database si trovasse più vicino, ad esempio in India o in un'altra parte dell'Asia.

Progetta per l'affidabilità

I seguenti argomenti migliorano o influiscono sull'affidabilità della tua app:

Attiva modalità offline

Gli SDK Firebase forniscono persistenza dei dati offline. Se l'app sul dispositivo dell'utente non riesce a connettersi a Firestore, l'app rimane utilizzabile lavorando con i dati memorizzati nella cache locale. Ciò garantisce l'accesso ai dati 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 tentativi automatici

Gli SDK Firebase si occupano di riprovare a eseguire le operazioni e a 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à a singola regione e a più regioni

La scelta tra località a singola regione e località multiregionali comporta una serie di compromessi. La differenza principale è il modo in cui i dati vengono replicati. Questo garantisce la disponibilità garantita per la tua app. Un'istanza multiregionale offre una maggiore affidabilità di gestione 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 di snapshot, consentono all'app di rimanere in ascolto delle modifiche nel database e di 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 su come configurare e utilizzare query in tempo reale, consulta Ricevere aggiornamenti in tempo reale. Le seguenti sezioni illustrano nel dettaglio il funzionamento dei listener di snapshot e descrivono 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 per dispositivi mobili.

Il Client A scrive nel database per aggiungere e aggiornare 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 rimane in ascolto degli aggiornamenti nella stessa raccolta utilizzando un listener di snapshot. Il Cliente B riceve una notifica immediata ogni volta che qualcuno crea un nuovo messaggio. Il seguente diagramma mostra l'architettura alla base di un listener di snapshot:

Architettura di una connessione listener di snapshot

La seguente sequenza di eventi si verifica quando il Client B connette un listener di snapshot al database:

  1. Il Client B apre una connessione a Firestore e registra un ascoltatore effettuando una chiamata a onSnapshot(collection("chatroom")) tramite l'SDK Firebase. Questo listener può rimanere attivo per ore.
  2. Il frontend di Firestore esegue query sul sistema di archiviazione sottostante per eseguire il bootstrap del set di dati. Carica l'intero set di risultati di documenti corrispondenti. Lo chiamiamo query di sondaggio. Il sistema quindi valuta le regole di sicurezza di Firebase del database per verificare che l'utente possa accedere a questi dati. Se l'utente è autorizzato, il database restituisce i dati all'utente.
  3. 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.
  4. Il client A ora invia un'operazione di scrittura per modificare un documento.
  5. Il database esegue il commit della modifica del documento nel proprio sistema di archiviazione.
  6. Dal punto di vista transazionale, il sistema esegue il commit dello stesso aggiornamento in un log delle modifiche interno. Il log delle modifiche stabilisce un ordine rigoroso delle modifiche nel momento in cui avvengono.
  7. Il log delle modifiche a sua volta mostra i dati aggiornati a un pool di gestori degli abbonamenti.
  8. Viene eseguito un matcher di query inverso per verificare se il documento aggiornato corrisponde a uno o più listener di snapshot attualmente registrati. In questo esempio, il documento corrisponde al listener di snapshot del Client B. Come suggerisce il nome, il matcher di query inverso può essere considerato come una normale query di database, ma eseguita in senso inverso. Anziché cercare tra i documenti per trovare quelli che corrispondono a una query, esegue una ricerca efficiente nelle query per trovare quelli che corrispondono a un documento in arrivo. Dopo aver trovato una corrispondenza, il sistema inoltra il documento in questione ai listener degli snapshot. Quindi, il sistema valuta le regole di sicurezza di Firebase del database per garantire che solo gli utenti autorizzati ricevano i dati.
  9. 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 dal fan-out del log delle modifiche ai gestori degli abbonamenti e ai server frontend. Il fan-out consente a una singola modifica dei dati di propagarsi in modo efficiente per gestire 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 su più regioni), Firestore raggiunge disponibilità e scalabilità elevate.

Vale la pena notare che tutte le operazioni di lettura emesse da SDK web e per dispositivi mobili seguono il modello riportato sopra. Esegue una query di polling seguita dalla modalità di ascolto per mantenere le garanzie di coerenza. Questo vale anche per i listener in tempo reale, le chiamate per recuperare un documento e le query one-shot. I recuperi di singoli documenti e le query one-shot possono essere considerati come listener di snapshot di breve durata che hanno vincoli simili riguardo alle prestazioni.

Applicare le best practice per la scalabilità delle query in tempo reale

Applica le best practice riportate di seguito per progettare query scalabili in tempo reale.

Comprensione del traffico di scrittura elevato nel sistema

Questa sezione ti aiuta a capire in che modo il sistema risponde a un numero crescente di richieste di scrittura.

I log delle modifiche di Firestore che indirizzano le query in tempo reale scalano automaticamente orizzontalmente con l'aumento del traffico di scrittura. Man mano che la frequenza di scrittura di un database supera ciò che può gestire un singolo server, il log delle modifiche viene suddiviso tra più server e l'elaborazione delle query inizia a consumare i dati di più gestori degli abbonamenti anziché uno. Dal punto di vista del client e dell'SDK, tutto è trasparente e non è richiesta alcuna azione da parte dell'app quando si verificano le suddivisioni. Il seguente diagramma mostra la scalabilità delle query in tempo reale:

Architettura del fan-out del log delle modifiche

La scalabilità automatica consente di aumentare il traffico in scrittura senza limiti, ma con l'aumento del traffico, il sistema potrebbe impiegare del 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ò accogliere senza precauzioni. I carichi di lavoro batch come l'importazione di set di dati di grandi dimensioni, tuttavia, possono aumentare le scritture. Quando progetti l'app, cerca di conoscere la provenienza del traffico di scrittura.

Comprendere come interagiscono le operazioni di scrittura e lettura

Il sistema di query in tempo reale può essere considerato come una pipeline che collega le operazioni di scrittura ai lettori. Ogni volta che un documento viene creato, aggiornato o eliminato, la modifica si propaga dal sistema di archiviazione agli ascoltatori attualmente registrati. La struttura del log delle modifiche di Firestore garantisce un'elevata coerenza, il che significa che la tua app non riceve mai notifiche di aggiornamenti non in ordine rispetto a quando il database ha eseguito il commit delle modifiche ai dati. Questo semplifica lo sviluppo delle app rimuovendo i casi limite in merito alla coerenza dei dati.

Questa pipeline connessa significa che un'operazione di scrittura che causa hotspot o contesa dei blocchi può influire negativamente sulle operazioni di lettura. Quando le operazioni di scrittura hanno esito negativo o si verificano limitazioni, una lettura potrebbe bloccarsi in attesa di dati coerenti dal log delle modifiche. Se questo accade nella tua app, potresti vedere sia operazioni di scrittura lente sia tempi di risposta lenti correlati per le query. Evitare le aree sensibili è la chiave per evitare questo problema.

Riduci le dimensioni delle operazioni di scrittura e dei documenti

Quando crei app con listener di snapshot, in genere vuoi che gli utenti trovino rapidamente le modifiche ai dati. Per raggiungere questo obiettivo, cerca di limitare le cose. Il sistema può eseguire rapidamente il push di documenti di piccole dimensioni con decine di campi. L'elaborazione di documenti più grandi, con centinaia di campi e dati di grandi dimensioni, richiede più tempo.

Allo stesso modo, prediligi operazioni di commit e scrittura brevi e veloci per mantenere bassa la latenza. I batch di grandi dimensioni potrebbero offrire una velocità effettiva superiore dal punto di vista dell'autore, ma potrebbero effettivamente aumentare il tempo di notifica per i listener di snapshot. Questo spesso è controintuitivo rispetto all'uso di altri sistemi di database in cui potresti utilizzare il batch per migliorare le prestazioni.

Usa ascoltatori efficienti

Con l'aumento delle frequenze di scrittura del database, Firestore suddivide l'elaborazione dei dati tra più server. L'algoritmo dello sharding di Firestore tenta di collocare i dati della stessa raccolta o dello stesso gruppo di raccolte sullo stesso server del log delle modifiche. Il sistema cerca di massimizzare la possibile velocità effettiva di scrittura mantenendo il più basso possibile il numero di server coinvolti nell'elaborazione di una query.

Tuttavia, alcuni pattern potrebbero comunque portare a un comportamento non ottimale per gli ascoltatori degli snapshot. Ad esempio, se la tua app archivia la maggior parte dei dati in un'unica raccolta di grandi dimensioni, il listener potrebbe dover connettersi a più server per ricevere tutti i dati di cui ha bisogno. Questo vale anche se applichi un filtro di 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 gestire i listener senza passare a molti server diversi. La soluzione migliore per suddividere i dati in raccolte più piccole con frequenze di scrittura inferiori.

Si tratta di un'operazione simile alle query sulle prestazioni in un database relazionale che richiede analisi complete delle tabelle. In un database relazionale, una query che richiede una scansione completa della tabella è l'equivalente di un listener di snapshot che controlla una raccolta con tasso di abbandono elevato. Potrebbe essere eseguito 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 controlla un singolo documento o una raccolta che viene modificata con minore frequenza. Dovresti caricare la tua app per capire meglio il comportamento e le esigenze del tuo caso d'uso.

Mantieni veloci le query di polling

Un altro aspetto fondamentale delle query adattabili in tempo reale consiste nell'assicurarsi che la query di polling per eseguire il bootstrap dei dati sia veloce ed efficiente. La prima volta che si connette un nuovo listener di snapshot, deve caricare l'intero set di risultati e inviarlo al dispositivo dell'utente. Le query lente rendono la tua app meno reattiva. Sono incluse, ad esempio, le query che provano a leggere molti documenti o query che non utilizzano gli indici appropriati.

In alcune circostanze, un listener potrebbe anche tornare dallo stato di ascolto a uno stato di polling. Questa operazione avviene automaticamente ed è trasparente per gli SDK e la tua app. Le seguenti condizioni potrebbero attivare uno stato di polling:

  • Il sistema riequilibra un log delle modifiche a causa delle modifiche al carico.
  • Gli hotspot causano scritture non riuscite o ritardate nel database.
  • I riavvii temporanei del server influiscono temporaneamente sui listener.

Se le query di polling sono abbastanza veloci, uno stato di polling diventa trasparente per gli utenti della tua app.

Preferisci gli ascoltatori di lunga data

Aprire e mantenere attivi gli ascoltatori il più a lungo possibile è spesso il modo più conveniente per creare un'app che utilizza Firestore. Quando utilizzi Firestore, ti vengono addebitati i documenti restituiti alla tua app e non per il mantenimento di una connessione aperta. Un listener di snapshot di lunga durata legge solo i dati necessari per gestire la query. Ciò include un'operazione di polling iniziale seguita da notifiche quando i dati cambiano effettivamente. Le query one-shot, invece, rileggi i dati che potrebbero non essere cambiati dall'ultima volta che l'app ha eseguito la query.

Nei casi in cui l'app deve consumare una frequenza elevata di dati, i listener di snapshot potrebbero non essere appropriati. Ad esempio, se il tuo caso d'uso invia molti documenti al secondo attraverso una connessione per un periodo di tempo prolungato, potrebbe essere preferibile optare per query one-shot che vengono eseguite a una frequenza inferiore.

Passaggi successivi