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 collettive 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 richiesta diversa 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. Selookup()
e la query non hanno una dipendenza di dati, non è necessario attendere in modo sincrono il completamento dilookup()
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 allocato in modo da essere distribuito 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 con l'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à che crei, fai in modo che la tua applicazione ottenga un blocco di ID con il metodo
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à che crei, non utilizzare valori in aumento monotono 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 il problema degli ID numerici sequenziali, ottieni gli ID numerici dal metodo
allocateIds()
. Il metodoallocateIds()
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
- Se non serve mai una proprietà per una query, escludere la proprietà dagli indici. L'indicizzazione inutilmente di una proprietà può comportano un aumento della latenza costi di archiviazione delle voci di indice.
- Evita di avere troppi indici composti. Uso eccessivo di materiali composti indici potrebbe comportare un aumento della latenza di scrittura e un aumento costi di archiviazione delle voci di indice. Se devi eseguire query ad hoc su set di dati di grandi dimensioni senza indici definiti in precedenza, utilizza BigQuery.
- Non indicizzare le proprietà con valori monotonici crescenti (ad esempio,
NOW()
). Mantenere un indice di questo tipo potrebbe portare a hotspot che hanno un impatto Latenza in modalità Datastore per applicazioni con elevata lettura e scrittura tariffe. Per ulteriori indicazioni su come gestire le proprietà monotonico, consulta: Di seguito, velocità di lettura/scrittura elevate per un intervallo di chiavi ristretto.
Proprietà
- Utilizza sempre caratteri UTF-8 per le proprietà di tipo stringa. 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 inferiore 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 più bassa 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 di restituire le entità saltate all'applicazione, ma queste entità vengono 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 velocità 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 dell'entità aggiorna l'entità e gli eventuali indici associati e Firestore in modalità Datastore applica in modo sincrono l'operazione di scrittura a 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 assegna i propri ID in modo monotonicamente crescente.
La modalità Datastore alloca le chiavi utilizzando un algoritmo a dispersione. Non dovresti riscontrare hotspot sulle scritture se crei nuove entità utilizzando la distribuzione 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 frequenza molto elevata senza aumentare gradualmente 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 suddivide 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 hai una chiave o una proprietà indicizzata che aumenterà in modo monotonico, puoi anteporre un hash casuale per assicurarti che le chiavi vengano suddivise in 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 l'indicizzazione della 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
Aumentare gradualmente il traffico verso nuovi tipi o parti dello spazio 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. Nel 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, ciò potrebbe causare un aumento improvviso del traffico verso un nuovo tipo con una porzione molto piccola dello spazio 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 utilizzata per eseguire la migrazione delle entità a un nuovo tipo o chiave dipende dal modello di dati. Di seguito è riportato un esempio di strategia, nota come "letture parallele". Dovrai stabilire se questa strategia è 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 l'entità vecchia in quella nuova ed eliminare la vecchia. 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 di un nuovo tipo è utilizzare un hash deterministico dell'ID utente per ottenere una percentuale aleatoria 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. Al termine del 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 in un piccolo intervallo di chiavi, le query in questa parte dell'indice saranno più lente fino al completamento del consolidamento. 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 entità 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à con lo stesso timestamp vengano locate 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 che non siano 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. Lo sharding interrompe 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.
Esegui lo sharding solo delle entità più richieste. 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
- Scopri di più sui limiti di Firestore in modalità Datastore.
- Scopri di più sulle best practice per le organizzazioni aziendali.