Best practice

Puoi utilizzare le best practice elencate qui come riferimento rapido per gli aspetti da conservare quando crei un'applicazione che utilizza Firestore in modalità Datastore. Se ha appena iniziato con la modalità Datastore, questa pagina potrebbe non essere un punto di partenza, perché non insegna le nozioni di base Modalità Datastore. Se sei un nuovo utente, ti consigliamo di iniziare con Introduzione a Firestore in modalità Datastore.

Generale

  • Utilizza sempre i caratteri UTF-8 per i nomi degli spazi dei nomi, i nomi dei tipi, i nomi delle proprietà e nomi di chiavi personalizzati. I caratteri non UTF-8 utilizzati in questi nomi possono interferire con la funzionalità in modalità Datastore. Ad esempio, un carattere non UTF-8 nel nome di una proprietà può impedire la creazione di un indice che utilizza la proprietà.
  • Non utilizzare una barra (/) nei nomi di tipo o in nomi di chiavi personalizzate. Ala Le barre presenti in questi nomi possono interferire con le funzionalità future.
  • Evita di archiviare informazioni sensibili in un ID progetto Cloud. Un ID progetto cloud potrebbero essere conservati per tutta la durata del progetto.

Chiamate API

  • Utilizza operazioni batch per letture, scritture ed eliminazioni al posto di le singole operazioni. Le operazioni in batch sono più efficienti perché eseguono più operazioni con lo stesso overhead di una singola operazione.
  • Se una transazione non va a buon fine, assicurati di provare a eseguirne il rollback. La il rollback riduce al minimo la latenza dei nuovi tentativi per una diversa richiesta che si contende lo stesso risorse in una transazione. Tieni presente che il rollback stesso potrebbe non riuscire, quindi il rollback dovrebbe essere eseguito solo con il criterio del "best effort".
  • Utilizza le chiamate asincrone, se disponibili, anziché le chiamate sincrone. Le chiamate asincrone riducono al minimo l'impatto della latenza. Ad esempio, considera un'applicazione che richiede il risultato di un lookup() sincrono e i risultati di una query prima che possa restituire una risposta. Se lookup() e la query non hanno un di dipendenza dai dati, non è necessario attendere in modo sincrono fino all'lookup() prima di avviare la query.

Entità

  • Non includere la stessa entità (per chiave) più volte nello stesso commit. L'inclusione della stessa entità più volte nello stesso commit potrebbe influire una latenza di pochi millisecondi.

  • Consulta la sezione sugli aggiornamenti di un'entità.

Chiavi

  • I nomi delle chiavi vengono generati automaticamente se non vengono forniti al momento della creazione dell'entità. Sono allocati in modo da essere distribuiti uniformemente nello spazio delle chiavi.
  • Per una chiave che utilizza un nome personalizzato, utilizza sempre i caratteri UTF-8, tranne un barra (/). I caratteri non UTF-8 interferiscono con vari processi ad esempio l'importazione di un file di esportazione in modalità Datastore in BigQuery. R La barra potrebbe interferire con le funzionalità future.
  • Per una chiave che utilizza un ID numerico:
    • Non utilizzare un numero negativo per l'ID. Un ID negativo potrebbe interferire grazie all'ordinamento.
    • Non utilizzare il valore 0(zero) per l'ID. In questo caso, si otterrà l'ID assegnato automaticamente.
    • Se vuoi assegnare manualmente i tuoi ID numerici alle entità crea, chiedi alla tua applicazione di ottenere un blocco di ID con allocateIds() . Ciò impedirà alla modalità Datastore di assegnare uno dei gli ID numerici manuali a un'altra entità.
  • Se assegni un ID numerico manuale o un nome personalizzato alle entità crei, non utilizzare valori monotonici crescenti come:

    1, 2, 3, ,
    "Customer1", "Customer2", "Customer3", .
    "Product 1", "Product 2", "Product 3", .
    

    Se un'applicazione genera molto traffico, la numerazione sequenziale potrebbe generano hotspot che incidono sulla latenza in modalità Datastore. Per evitare degli ID numerici sequenziali, ottieni gli ID numerici dal allocateIds() . Il metodo allocateIds() genera sequenze ben distribuite di ID numerici.

  • Se specifichi una chiave o archivi il nome generato, in un secondo momento potrai eseguire una lookup() su quell'entità senza dover eseguire una query per trovarla.

Indici

Proprietà

  • Utilizza sempre i caratteri UTF-8 per le proprietà del tipo string. Una codifica non UTF-8 in una proprietà di tipo stringa potrebbe interferire con le query. Se hai bisogno Per salvare i dati con caratteri non UTF-8, utilizza una stringa di byte.
  • Non utilizzare punti nei nomi delle proprietà. I punti nei nomi delle proprietà interferiscono con Indicizzazione delle proprietà delle entità incorporate.

Query

  • Se devi accedere solo alla chiave dai risultati della query, utilizza un query basata solo con le chiavi. Una query basata solo su chiavi restituisce risultati con una latenza più bassa rispetto al recupero di intere entità.
  • Se devi accedere solo a proprietà specifiche da un'entità, utilizza una query di proiezione. Una query di proiezione restituisce risultati con una latenza inferiore rispetto al recupero di intere entità.
  • Allo stesso modo, se devi accedere solo alle proprietà che sono incluse nel filtro query (ad esempio quelli elencati in una clausola order by), utilizza un query di proiezione.
  • Non utilizzare gli offset. Utilizza invece i cursori. L'utilizzo di un offset evita solo restituendo le entità ignorate all'applicazione, ma queste entità sono comunque recuperate internamente. Le entità ignorate influiscono sulla latenza della query e all'applicazione vengono addebitate le operazioni di lettura necessarie per recuperarle.

Progettazione per la scalabilità

Le best practice riportate di seguito descrivono come evitare situazioni che creano contese.

Aggiornamenti a un'entità

Durante la progettazione dell'app, valuta la rapidità con cui l'app aggiorna le singole entità. Il modo migliore per caratterizzare le prestazioni del carico di lavoro è eseguire il carico test. La frequenza massima esatta con cui un'app può aggiornare una singola entità dipende molto dal carico di lavoro. I fattori includono la frequenza di scrittura, il conflitto richieste e il numero degli indici interessati.

Un'operazione di scrittura delle entità aggiorna l'entità e gli eventuali indici associati e Firestore in modalità Datastore applica in modo sincrono l'operazione di scrittura un quorum di repliche. Se le velocità di scrittura sono sufficientemente elevate, il database inizierà quando si verificano conflitti, latenza più elevata o altri errori.

Elevate velocità di lettura/scrittura con un intervallo di chiavi ristretto

Evita alte velocità di lettura o scrittura per la chiusura lessicografica dei documenti. l'applicazione riscontrerà errori di contesa. Questo problema è noto come hotspotting e la tua applicazione può rilevare l'hotspot se esegue una delle le seguenti:

  • Crea nuove entità a una frequenza molto elevata e alloca la propria aumentare in modo monotonico gli ID.

    La modalità Datastore alloca le chiavi utilizzando un algoritmo a dispersione. Tu non dovrebbero rilevare hotspot sulle scritture se crei nuove entità utilizzando l'allocazione automatica degli ID entità.

  • Crea nuove entità a una velocità molto elevata utilizzando l'ID sequenziale precedente criteri di allocazione.

  • Crea nuove entità ad alta velocità per un tipo con poche entità.

  • Crea nuove entità con una proprietà indicizzata e che aumenta monotonicamente come un timestamp, con una frequenza molto alta.

  • Elimina le entità da un tipo con frequenza elevata.

  • Scrive nel database a una velocità molto elevata senza aumentando il traffico.

Se si verifica un aumento improvviso della velocità di scrittura a un di chiavi che possono essere scritture lente a causa un hotspot. La modalità Datastore suddividerà lo spazio delle chiavi per supportare un carico elevato.

Il limite per le letture è in genere molto più elevato rispetto a quello per le scritture, a meno che tu non stia leggendo ad alta velocità da un singolo tasto.

Gli hotspot possono essere applicati agli intervalli di chiavi utilizzati sia dalle chiavi delle entità sia dagli indici.

In alcuni casi, un hotspot può avere un impatto più ampio su una anziché impedire le letture o le scritture su un piccolo intervallo chiave. Ad esempio, le chiavi di scelta rapida potrebbero essere lette o scritte durante l'istanza causando il mancato caricamento delle richieste.

Se disponi di una chiave o di una proprietà indicizzata monotonica, aumentando, puoi anteporre un hash casuale per assicurarti che le chiavi con sharding su più tablet.

Analogamente, se devi eseguire una query su una query in aumento (o diminuzione) monotonica utilizzando un ordinamento o un filtro, puoi invece indicizzare in base a una nuova proprietà, che anteponi al valore monotonico un valore con cardinalità elevata nel set di dati, ma è comune a tutte le entità nell'ambito della query che vuoi eseguire. Ad esempio, se vuoi eseguire una query per le voci in base al timestamp, ma devono restituire risultati solo per un singolo utente alla volta, puoi anteporre il timestamp con l'ID utente e indicizzare la nuova proprietà. Questo potrebbe consentire query e risultati ordinati per l'utente, ma la presenza del user id assicurerà che l'indice stesso abbia uno sharding adeguato.

Aumento del traffico

Incrementare gradualmente il traffico verso nuovi tipi o porzioni dello spazio delle chiavi.

Dovresti aumentare gradualmente il traffico verso nuovi tipi in per dare a Firestore in modalità Datastore tempo sufficiente per prepararsi all'aumento per via del traffico. Consigliamo un massimo di 500 operazioni al secondo a un nuovo aumentando il traffico del 50% ogni 5 minuti. Nella In teoria, puoi raggiungere le 740.000 operazioni al secondo dopo 90 minuti utilizzando questa pianificazione di applicazione graduale. Assicurati che le scritture siano distribuite in modo relativamente uniforme nell'intervallo di chiavi. I nostri SRE la chiamano "500/50/5" personalizzata.

Questo modello di adattamento graduale è particolarmente importante se cambia il tuo codice per smettere di usare il tipo A e usare invece il tipo B. Ingenuo per gestire la migrazione è cambiare il codice in tipo B, Se non esiste, leggi il tipo A. Tuttavia, questo potrebbe causare improvviso aumento del traffico verso un nuovo tipo con una porzione molto ridotta di nello spazio delle chiavi.

Lo stesso problema può verificarsi anche se esegui la migrazione delle entità per utilizzare un diverso intervallo di chiavi nello stesso tipo.

La strategia che utilizzi per eseguire la migrazione delle entità a un nuovo tipo o chiave dipenderà dal modello dei dati. Di seguito è riportato un esempio di strategia, nota come "letture parallele". Dovrai stabilire se questo sia efficace per i tuoi dati. Una considerazione importante l'impatto sui costi delle operazioni parallele durante la migrazione.

Leggi prima l'entità o la chiave precedente. Se manca, allora potrebbe leggere dalla nuova entità o chiave. Un'alta percentuale di letture entità inesistenti possono portare all'hotspotting, quindi occorre assicurarsi per aumentare gradualmente il carico. Una strategia migliore è copiare il vecchio nuova entità alla nuova, quindi elimina la precedente. Incrementare le letture parallele gradualmente per garantire che il nuovo spazio per le chiavi sia ben suddiviso.

Una possibile strategia per aumentare gradualmente le letture o le scritture in un nuovo tipo è l'uso di un hash deterministico dell'ID utente per ottenere un percentuale di utenti che scrivono nuove entità. Assicurati che il risultato dell'hash dell'ID utente non sia distorto dalla funzione casuale o il comportamento degli utenti.

Nel frattempo, esegui un job Dataflow per copiare tutti i dati dalle vecchie entità o le nuove chiavi. Il job batch deve evitare le scritture sulle chiavi sequenziali per evitare gli hotspot. Una volta completato il job batch, puoi leggere solo dalla nuova posizione.

Un perfezionamento di questa strategia consiste nella migrazione di piccoli gruppi di utenti contemporaneamente. Aggiungere un campo all'entità utente che monitora la migrazione stato di quell'utente. Seleziona un batch di utenti di cui eseguire la migrazione in base a dell'ID utente. Un job Mapreduce o Dataflow eseguirà la migrazione delle chiavi per quel gruppo di utenti. Gli utenti con una migrazione in corso userà letture parallele.

Tieni presente che non puoi eseguire facilmente il rollback se non esegui doppie scritture sia la vecchia che la nuova entità durante la fase di migrazione. Questo potrebbe aumentare i costi sostenuti per la modalità Datastore.

Eliminazioni

Evita di eliminare un numero elevato di entità in un intervallo ridotto di chiavi.

Firestore in modalità Datastore riscrive periodicamente le relative tabelle per rimuovere le tabelle eliminate e per riorganizzare i dati in modo che le letture e le scritture siano in modo efficace. Questo processo è noto come compattazione.

Se elimini un numero elevato di entità in modalità Datastore su un di chiavi, quindi le query in questa parte dell'indice saranno più lentamente fino al completamento della compattazione. In casi estremi, le query potrebbero scadere prima di restituire i risultati.

È un anti-pattern che prevede l'utilizzo di un valore timestamp per un elemento per rappresentare la data di scadenza di un'entità. Per recuperare scadute, devi eseguire una query su questo campo indicizzato, che probabilmente si trova in una parte sovrapposta dello spazio delle chiavi con indice per le entità eliminate più di recente.

Puoi migliorare le prestazioni con "query segmentate", che antepongono una stringa di lunghezza fissa al timestamp di scadenza. L'indice è ordinato sulla stringa completa, in modo che le entità nello stesso timestamp nell'intervallo di chiavi dell'indice. Esegui più volte in parallelo per recuperare i risultati da ogni shard.

Una soluzione più completa per il problema del timestamp di scadenza è utilizzare un "numero generazione" un contatore globale che viene periodicamente aggiornato. Il numero di generazione è anteposto alla scadenza timestamp in modo che le query siano ordinate per numero di generazione, poi per shard e poi timestamp. L'eliminazione delle entità precedenti avviene a una data di classificazione. Qualsiasi entità non eliminata deve avere il proprio numero di generazione incrementato. Una volta completata l'eliminazione, passerai al di nuova generazione. Le query su una generazione precedente verranno eseguite in modo scadente fino al completamento della compattazione. Potresti dover attendere diversi generazioni da completare prima di eseguire una query sull'indice per ottenere l'elenco entità da eliminare, al fine di ridurre il rischio di mancanza di risultati a causa per ottenere coerenza finale.

Sharding e replica

Utilizza lo sharding o la replica per gestire gli hotspot.

Puoi utilizzare la replica se devi leggere una parte della chiave con una velocità superiore a quella consentita da Firestore in modalità Datastore. Con questa strategia, memorizzerebbe N copie della stessa entità consentendo una frequenza N volte superiore di letture rispetto a quelle supportate da una singola entità.

Puoi utilizzare lo sharding se devi scrivere su una parte della chiave con una velocità superiore a quella consentita da Firestore in modalità Datastore. Il partizionamento orizzontale rompe l'entità in parti più piccole.

Alcuni errori comuni durante lo sharding includono:

  • partizionamento orizzontale con un prefisso temporale. Quando l'ora passa al prefisso successivo, la nuova parte non divisa diventa un hotspot. Piuttosto, dovresti eseguire gradualmente il lancio su una parte delle scritture al nuovo prefisso.

  • Eseguire il partizionamento orizzontale delle entità più interessanti. Se esegui lo sharding di una piccola parte numero totale di entità, allora le righe potrebbero non essere sufficienti per assicurarsi che rimangano su divisioni diverse.

Passaggi successivi