Leggi i dati in tempo reale con i flussi di modifiche in tempo reale

I feed delle modifiche per Firestore con compatibilità MongoDB consentono alle applicazioni di accedere alle modifiche in tempo reale (inserimenti, aggiornamenti ed eliminazioni) apportate a una raccolta o a un intero database. Un flusso di modifiche in tempo reale ordina gli aggiornamenti in base all'ora della modifica.

Le modifiche in tempo reale sono accessibili tramite le API compatibili con MongoDB e i driver MongoDB tradizionali. L'implementazione delle modifiche in tempo reale di Firestore con compatibilità con MongoDB può gestire qualsiasi throughput di scrittura e lettura tramite un'implementazione unica del partizionamento automatico delle scritture e del parallelismo di lettura. In questo modo puoi creare workload con throughput elevato. Puoi anche migliorare l'infrastruttura di migrazione e sincronizzazione dei dati tra Cloud Firestore e altre soluzioni di archiviazione.

Oltre alla compatibilità con i driver MongoDB, puoi utilizzare Cloud Firestore per leggere le modifiche in tempo reale in parallelo. In questo modo puoi creare workload di lettura paralleli con throughput elevato. Ogni stream rappresenta una partizione ben distribuita dei risultati.

Le modifiche in tempo reale supportano le seguenti funzionalità:

  • Modifiche in tempo reale configurabili con ambito del database o della raccolta.
  • Un periodo di conservazione per un flusso di modifiche in tempo reale specificato al momento della creazione. La conservazione predefinita è di 7 giorni e quella minima è di 1 giorno. La conservazione deve essere un multiplo di 1 giorno, fino a un massimo di 7 giorni. La durata della conservazione non può essere modificata dopo la creazione. Per modificare il periodo di conservazione, devi eliminare e ricreare le modifiche in tempo reale.
  • Eventi di modifica delete, insert, update e drop osservabili utilizzando db.collection.watch() e db.watch().
  • updateDescription.updatedFields contiene le differenze di aggiornamento.
  • Tutte le opzioni fullDocument e fullDocumentBeforeChange.
    • Ricerca del documento completo per gli aggiornamenti.
    • Pre-immagine del documento prima che venisse sostituito, aggiornato o eliminato.
    • Post-immagine del documento dopo la sostituzione o l'aggiornamento.
    • Le immagini pre e post più vecchie di un'ora richiedono l'attivazione del recupero point-in-time (PITR).
  • Tutte le opzioni di ripresa, tra cui resumeAfter e startAfter.
  • Quando utilizzi watch() per osservare le modifiche, puoi concatenare fasi di aggregazione come $addFields, $match, $project, $replaceRoot, $replaceWith, $set e $unset.

Configura le modifiche in tempo reale

Per creare, eliminare o visualizzare i flussi di modifiche in tempo reale esistenti per un database, utilizza la console Google Cloud.

Ruoli e autorizzazioni

Per creare, eliminare ed elencare i flussi di modifiche in tempo reale, un'entità deve disporre rispettivamente delle autorizzazioni Identity and Access Management (IAM) datastore.schemas.create, datastore.schemas.delete e datastore.schemas.list.

Ad esempio, il ruolo Amministratore indice Datastore (roles/datastore.indexAdmin) concede queste autorizzazioni.

Crea un flusso di modifiche

Prima di poter aprire un cursore del flusso di modifiche corrispondente, devi creare un flusso di modifiche. L'attivazione automatica delle modifiche in tempo reale durante la creazione di una raccolta o di un database non è supportata.

Per creare modifiche in tempo reale, utilizza la console Google Cloud.

  1. Nella console Google Cloud, vai alla pagina Database.

    Vai a Database

  2. Seleziona un database Firestore con compatibilità MongoDB dall'elenco. Si apre il riquadro Firestore Studio.
  3. Nel riquadro Explorer, individua il nodo modifiche in tempo reale, fai clic su Altre azioni, quindi seleziona Crea modifiche in tempo reale.
  4. Inserisci un nome, un ambito e un periodo di conservazione univoci per le modifiche in tempo reale, quindi fai clic su Salva.

Visualizza i flussi di modifiche

Puoi visualizzare i dettagli sulle modifiche in tempo reale nella console Google Cloud.

  1. Nella console Google Cloud, vai alla pagina Database.

    Vai a Database

  2. Seleziona un database Firestore con compatibilità MongoDB dall'elenco. Si apre il riquadro Firestore Studio.
  3. Nel riquadro Explorer, individua il nodo Modifiche in tempo reale.
  4. Per aprire o chiudere il nodo, fai clic su Attiva/disattiva nodo.

Elimina un flusso di modifiche

Per eliminare un flusso di modifiche in tempo reale, utilizza la console Google Cloud.

  1. Nella console Google Cloud, vai alla pagina Database.

    Vai a Database

  2. Seleziona un database Firestore con compatibilità MongoDB dall'elenco. Si apre il riquadro Firestore Studio.
  3. Nel riquadro Explorer, individua il nodo Modifiche in tempo reale.
  4. Per aprire o chiudere il nodo, fai clic su Attiva/disattiva nodo.
  5. In Explorer, individua le modifiche in tempo reale che vuoi eliminare.
  6. Fai clic su Altre azioni, quindi seleziona Elimina modifiche in tempo reale.
  7. Nella finestra di dialogo, inserisci il nome del flusso di modifiche in tempo reale per confermare l'eliminazione, quindi fai clic su Elimina.

Apri o riprendi un cursore delle modifiche in tempo reale

I seguenti esempi mostrano come creare, riprendere e configurare un cursore delle modifiche in tempo reale.

Prima di creare un cursore delle modifiche in tempo reale, devi creare esplicitamente un feed delle modifiche per il database o la raccolta.

Crea un cursore delle modifiche in tempo reale

Per creare un nuovo cursore delle modifiche in tempo reale, utilizza il metodo watch nei driver MongoDB. Per ascoltare tutte le modifiche in un database, crea un flusso di modifiche con ambito database e chiama il metodo watch sull'oggetto db.

let cursor = db.watch()

Per creare un cursore con ambito limitato a una raccolta, devi prima creare un feed delle modifiche in tempo reale per quella raccolta. Quindi, chiama il metodo watch sulla raccolta corrispondente.

let cursor = db.my_collection.watch()

Ora che hai creato un cursore per le modifiche in tempo reale, puoi iniziare lo streaming. Ad esempio, se inserisci un documento e chiami tryNext sul cursore, vedrai la modifica apparire nel flusso di modifiche.

let doc = db.my_collection.insertOne({value: "hello world"})
console.log(cursor.tryNext())

Se aggiorni ed elimini il documento, queste modifiche vengono visualizzate nel feed delle modifiche:

db.my_collection.updateOne({"_id": doc.insertedId}, {$set: {value: "hello world!"}})
db.my_collection.deleteOne({"_id": doc.insertedId}})

// Prints the update event
console.log(cursor.tryNext())

// Prints the delete event
console.log(cursor.tryNext())

Riprendi un flusso di modifiche in tempo reale

Per riprendere un flusso di modifiche in tempo reale, utilizza le opzioni resumeAfter o startAfter. Per determinare da dove riprendere nel log delle modifiche per resumeAfter e startAfter, utilizza un token di ripresa.

// Create a cursor and add one event to the change stream.
let cursor = db.my_collection.watch();
db.my_collection.insertOne({value: "hello world"});
let event = cursor.tryNext();

// Get the resume token from the event.
let resumeToken = event._id;

// Add a new event to the change stream.
db.my_collection.insertOne({value: "foobar"});

// Create a new cursor by using the resume token as a starting point.
let newCursor = db.my_collection.watch({resumeAfter: resumeToken})

// Log the change event containing the "foobar" value.
console.log(newCursor.tryNext())

Per usare startAfter:

// Start after the resume token.
let startAfterCursor = db.my_collection.watch({startAfter: resumeToken})

Includi immagini pre e post negli aggiornamenti e nell'eliminazione

Se necessario, puoi includere immagini pre e post dei documenti negli eventi di modifica di aggiornamento ed eliminazione. La disponibilità delle immagini è soggetta alla finestra di recupero point-in-time (PITR) e, per leggere le immagini dei documenti risalenti a più di un'ora prima, devi abilitare il PITR.

Le modifiche in tempo reale sfruttano la finestra PITR per fornire una visualizzazione del documento prima e dopo l'evento di modifica specificato. Per impostazione predefinita, gli eventi di aggiornamento contengono un campo updateDescription che rappresenta il delta dei campi modificati dall'operazione di aggiornamento.

Per includere le immagini pre e post in un evento di modifica, devi specificare le opzioni fullDocumentBeforeChange e fullDocument nella query del flusso di modifiche.

let cursor = db.my_collection.watch({
  "fullDocument": "required",
  "fullDocumentBeforeChange": "required"
})

Se la query tenta di leggere un documento al di fuori della finestra di conservazione PITR o se PITR non è abilitato, il valore required genera un messaggio di errore lato server.

In alternativa alla generazione di un errore, puoi utilizzare il valore whenAvailable per restituire un valore null se le immagini non sono più disponibili.

let cursor = db.my_collection.watch({
  "fullDocument": "whenAvailable",
  "fullDocumentBeforeChange": "whenAvailable"
})

Includi l'immagine attuale negli aggiornamenti

Per impostazione predefinita, gli eventi di aggiornamento contengono un campo updateDescription che rappresenta il delta dei campi modificati dall'operazione di aggiornamento. Per cercare invece la versione più recente dell'intero documento, utilizza il valore updateLookup nell'opzione fullDocument.

Questa funzionalità non richiede il PITR ed esegue una ricerca del documento.

let cursor = db.my_collection.watch({
  "fullDocument": "updateLookup",
})

Letture parallele

Per aumentare la velocità effettiva, puoi utilizzare l'opzione firestoreWorkerConfig per suddividere una query di modifiche in tempo reale tra più worker. Ogni worker è responsabile della pubblicazione delle modifiche per un insieme distinto di documenti. Devi creare un cursore parallelo tramite una query runCommand o aggregate.

Ad esempio, puoi distribuire un flusso di modifiche in tempo reale su 3 worker in questo modo:

let cursor1 = db.my_collection.aggregate([{
    "$changeStream": {
        "firestoreWorkerConfig": {numWorkers: 3, workerId: 0 }}
  }]);

let cursor2 = db.my_collection.aggregate([{
    "$changeStream": {
        "firestoreWorkerConfig": {numWorkers: 3, workerId: 1 }}
  }]);

let cursor3 = db.my_collection.aggregate([{
    "$changeStream": {
        "firestoreWorkerConfig": {numWorkers: 3, workerId: 2 }}
  }]);

Modifiche in tempo reale e backup

Né la configurazione né i dati delle modifiche in tempo reale sono disponibili nelle operazioni di backup e ripristino. Se ripristini un database con modifiche in tempo reale, devi ricreare queste modifiche in tempo reale nel database di destinazione per aprire i cursori in quel database.

Fatturazione

Differenze di comportamento

La sezione seguente descrive le differenze nei feed delle modifiche tra Firestore con compatibilità MongoDB e MongoDB.

updateDescription

updateDescription è un documento in un evento update che descrive i campi che sono stati aggiornati o rimossi dall'operazione di aggiornamento. In Cloud Firestore, le differenze principali sono:

  • In updateDescription, i campi truncatedArrays e disambiguatedPaths non vengono compilati.
  • updateDescription.updatedFields rappresentano una differenza canonica tra le immagini pre e post di un documento prima e dopo l'applicazione di una mutazione.

Considera il seguente stato iniziale di un documento:

db.my_collection.insertOne({
  _id: 1,
  root: {
    array: [{a: 1}, {b: 2}, {c: 3}]
  }
})

Scenario 1: modifica solo il primo elemento dell'array.

In questo scenario, il comportamento di Cloud Firestore corrisponde a quello di MongoDB.

db.my_collection.updateOne(
  {_id: 1},
  {'$set': {"root.array.0.a": 100}}
)

{
  updatedFields: {"root.array.0.a": 100},
  removedFields: []
}

Scenario 2: sovrascrittura con un intero array

In questo scenario, l'operazione aggiorna solo il primo campo dell'array, ma sovrascrive l'intero array.

La differenza di aggiornamento di Cloud Firestore non distingue tra questi due scenari e restituisce lo stesso updateDescription.updatedFields per entrambi:

db.my_collection.updateOne(
  {_id: 1},
  {'$set': {"root.array": [{a: 100}, {b: 2}, {c: 3}]}}
)

// In other implementations, updatedFields reflects the mutation itself
{
  updatedFields: {
    "root.array": [{a: 100}, {b: 2}, {c: 3}]
  },
  removedFields: []
}

// Firestore updatedFields is the diff between the before and after versions of the document
{
  updatedFields: {"root.array.0.a": 100},
  removedFields: []
}

Passaggi successivi