Comprendere le query in tempo reale su larga scala

Leggi questo documento per 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 in modo approfondito il sistema. Se hai appena iniziato a utilizzare Firestore, consulta la guida rapida.

Firestore e gli SDK mobile/web di Firebase forniscono un modello efficace 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 usare gli aggiornamenti in tempo reale per creare app adattabili che non richiedono l'infrastruttura del server. Sebbene sia molto facile avviare e utilizzare qualcosa, è utile comprendere i vincoli dei sistemi che compongono Firestore in modo che la tua app serverless possa scalare e avere un buon rendimento quando il traffico aumenta.

Per consigli su come scalare la tua app, consulta le sezioni seguenti.

Scegli una posizione del database vicina agli utenti

Il seguente diagramma mostra 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 indirizzata a un server frontend Firestore nella stessa regione in cui si trova il database. Ad esempio, se il database è in us-east1, la connessione va anche a un frontend Firestore 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 in Nord America potrebbe trovare l'esperienza più lenta e l'app meno scattante 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 garantiscono la persistenza dei dati offline. Se l'app sul dispositivo dell'utente non riesce a connettersi a Firestore, rimane utilizzabile lavorando con i dati memorizzati nella cache locale. In questo modo, gli utenti possono accedere ai dati anche quando la connessione a internet è intermittente o quando perdono completamente l'accesso per diverse ore o giorni. Per ulteriori dettagli sulla modalità offline, vedi Attivare i dati offline.

Informazioni sui nuovi tentativi automatici

Gli SDK Firebase si occupano di ripetere le operazioni e di ristabilire le connessioni interrotte. In questo modo puoi aggirare gli errori temporanei causati dall'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 su più regioni. La differenza principale è il modo in cui i dati vengono replicati. In questo modo, viene migliorata la disponibilità della tua app. Un'istanza multiregione offre una maggiore affidabilità di pubblicazione e aumenta la durabilità dei dati, ma il costo è maggiore.

Informazioni sul sistema di query in tempo reale

Le query in tempo reale, chiamate anche ascoltatori di snapshot, consentono all'app di ascoltare le modifiche nel database e di ricevere notifiche a bassa latenza non appena i dati subiscono modifiche. Un'app può ottenere lo stesso risultato eseguendo periodicamente il polling del database per verificare la presenza di aggiornamenti, ma spesso è più lenta, più costosa e richiede più codice. Per esempi su come configurare e utilizzare le query in tempo reale, consulta Ricevere aggiornamenti in tempo reale. Le sezioni seguenti illustrano in dettaglio il funzionamento degli ascoltatori di istantanee 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 mobile.

Il client A scrive nel database per aggiungere e aggiornare i documenti in una raccolta chiamata chatroom:

collection chatroom:
    document message1:
      from: 'Sparky'
      message: 'Welcome to Firestore!'

    document message2:
      from: 'Santa'
      message: 'Presents are coming'

Il cliente B ascolta gli aggiornamenti nella stessa raccolta utilizzando un ascoltatore di istantanee. 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 ascoltatore di istantanee:

Architettura di una connessione del listener snapshot

Quando il client B connette un ascoltatore di snapshot al database, si verifica la seguente sequenza di eventi:

  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 ascoltatore può rimanere attivo per ore.
  2. Il frontend di Firestore esegue query sul sistema di archiviazione sottostante per avviare il set di dati. Carica l'intero set di risultati dei documenti corrispondenti. Si tratta di una query di polling. Il sistema poi 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.
  3. La query del client B passa quindi alla modalità di ascolto. L'ascoltatore si registra con un gestore degli 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 suo sistema di archiviazione.
  6. A livello di transazioni, il sistema esegue il commit dello stesso aggiornamento in un log delle modifiche interno. Il log delle modifiche stabilisce un ordine rigoroso delle modifiche man mano che si verificano.
  7. Il log delle modifiche, a sua volta, distribuisce i dati aggiornati a un pool di gestori di iscrizioni.
  8. Viene eseguito un corrispondenza di query inversa per verificare se il documento aggiornato corrisponde a eventuali listener di istantanee attualmente registrati. In questo esempio, il documento corrisponde all'ascoltatore di snapshot del cliente B. Come suggerisce il nome, puoi considerare il correlatore di query inversa come una normale query sul database, ma eseguita in modo inverso. Invece di cercare tra i documenti quelli corrispondenti a una query, esegue ricerche efficienti tra le query per trovare quelle corrispondenti a un documento in entrata. Una volta trovata una corrispondenza, il sistema inoltra il documento in questione agli ascoltatori degli snapshot. Il sistema valuta quindi le regole di sicurezza di Firebase del database per assicurarsi che solo gli utenti autorizzati ricevano i dati.
  9. Il sistema inoltra l'aggiornamento del documento all'SDK sul dispositivo del cliente B e viene attivato il callback onSnapshot. Se la persistenza locale è attivata, l'SDK applica l'aggiornamento anche alla cache locale.

Un aspetto fondamentale della scalabilità di Firestore dipende dalla distribuzione dal log delle modifiche agli handler delle iscrizioni e ai server frontend. La distribuzione 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 su più zone (o più regioni nel caso di un deployment multiregionale), Firestore raggiunge un'alta disponibilità e scalabilità.

È importante notare che tutte le operazioni di lettura emesse dagli SDK mobile e web seguono il modello riportato sopra. Eseguono una query di polling seguita dalla modalità di ascolto per mantenere le garanzie di coerenza. Questo vale anche per gli ascoltatori in tempo reale, le chiamate per recuperare un documento e le query una tantum. Puoi considerare i singoli recupero di documenti e le query one-shot come ascoltatori di snapshot di breve durata che presentano vincoli simili in termini di rendimento.

Applica le best practice per scalare le query in tempo reale

Applica le seguenti best practice per progettare query in tempo reale scalabili.

Informazioni sul 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 generano le query in tempo reale si scalano automaticamente in orizzontale con l'aumento del traffico di scrittura. Quando la frequenza di scrittura per un database aumenta oltre il limite che un singolo server può gestire, il log delle modifiche viene suddiviso su 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 come vengono scalate le query in tempo reale:

Architettura della distribuzione del log delle modifiche

La scalabilità automatica ti consente di aumentare il traffico di scrittura senza limiti, ma con l'aumento del traffico, 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. Tuttavia, i carichi di lavoro batch come l'importazione di un set di dati di grandi dimensioni possono aumentare troppo rapidamente le scritture. Durante la progettazione dell'app, tieni conto della provenienza del traffico in scrittura.

In che modo le scritture e le letture interagiscono

Puoi pensare al sistema di query in tempo reale come a 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 una forte coerenza, il che significa che la tua app non riceve mai notifiche di aggiornamenti fuori sequenza 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 collegata significa che un'operazione di scrittura che causa hotspot o contese per i blocchi può influire negativamente sulle operazioni di lettura. Quando le operazioni di scrittura non riescono o vengono sottoposte a throttling, una lettura potrebbe bloccarsi in attesa di dati coerenti dal log delle modifiche. Se questo accade nella tua app, potresti notare sia operazioni di scrittura lente sia tempi di risposta correlati lenti per le query. Evitare gli hotspot è la chiave per evitare questo problema.

Mantieni i documenti e le operazioni di scrittura di piccole dimensioni

Quando crei app con ascoltatori di istantanee, in genere vuoi che gli utenti vengano informati rapidamente delle modifiche ai dati. Per farlo, cerca di mantenere le cose semplici. Il sistema può inviare piccoli documenti con decine di campi molto rapidamente. I documenti più grandi con centinaia di campi e dati di grandi dimensioni richiedono più tempo per essere elaborati.

Allo stesso modo, favorisci operazioni di commit e scrittura brevi e veloci per mantenere bassa la latenza. I batch di grandi dimensioni potrebbero offrire un throughput più elevato dal punto di vista dell'autore, ma potrebbero effettivamente aumentare il tempo di notifica per gli ascoltatori degli snapshot. Spesso questo approccio è controintuitivo rispetto all'utilizzo di altri sistemi di database in cui potresti utilizzare il batching per migliorare le prestazioni.

Utilizza listener efficienti

Con l'aumento delle frequenze di scrittura del database, Firestore suddivide l'elaborazione dei dati su molti server. L'algoritmo di suddivisione di Firestore tenta di collocare i dati della stessa raccolta o dello stesso gruppo di raccolte nello stesso server di log delle modifiche. Il sistema tenta di massimizzare il possibile throughput di scrittura 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 gli ascoltatori delle istantanee. Ad esempio, se la tua app memorizza la maggior parte dei dati in una raccolta di grandi dimensioni, l'ascoltatore potrebbe dover connettersi a molti server per ricevere tutti i dati di cui ha bisogno. Questo vale anche se applichi un filtro alla 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.

È simile alle query sul rendimento in un database relazionale che richiedono scansioni complete della tabella. In un database relazionale, una query che richiede una scansione completa della tabella è equivalente a un ascoltatore di snapshot che monitora una raccolta con un elevato tasso di turnover. L'esecuzione potrebbe essere lenta rispetto a una query che il database può eseguire utilizzando un indice più specifico. Una query con un indice più specifico è come un ascoltatore di snapshot che monitora un singolo documento o una raccolta che cambia meno spesso. Devi eseguire il test di caricamento della tua app per comprendere meglio il comportamento e le esigenze del tuo caso d'uso.

Mantieni le query di polling rapide

Un'altra parte fondamentale delle query in tempo reale responsive consiste nell'assicurarsi che la query di polling per l'avvio dei dati sia rapida ed efficiente. La prima volta che un nuovo ascoltatore di istantanee si connette, deve caricare l'intero insieme di risultati e inviarlo al dispositivo dell'utente. Le query lente rendono la tua app meno reattiva. Sono incluse, ad esempio, le query che tentano di leggere molti documenti o quelle che non utilizzano gli indici appropriati.

In alcune circostanze, un ascoltatore potrebbe anche passare da uno stato di ascolto a uno 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 riequilibra un log delle modifiche a causa di variazioni del carico.
  • Gli hotspot causano scritture non riuscite o in ritardo nel database.
  • I riavvii temporanei del server influiscono temporaneamente sugli ascoltatori.

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

Favorire gli ascoltatori di lunga durata

Aprire e mantenere attivi gli ascoltatori il più a lungo possibile è spesso il modo più economico per creare un'app che utilizza Firestore. Quando utilizzi Firestore, ti viene addebitato il costo dei documenti restituiti alla tua app e non per il mantenimento di una connessione aperta. Un ascoltatore di snapshot a lungo termine legge solo i dati di cui ha bisogno 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.

Se la tua app deve consumare una frequenza elevata di dati, gli ascoltatori 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 una tantum eseguite a una frequenza inferiore.

Passaggi successivi