Puoi utilizzare le best practice elencate qui come riferimento rapido per gli aspetti da conservare quando si crea un'applicazione che utilizza Datastore. Se che ha appena iniziato con Datastore, questa pagina potrebbe non essere un punto di partenza, perché non insegna le nozioni di base Datastore. Se sei un nuovo utente, ti consigliamo di iniziare con la sezione Introduzione a Datastore.
Generale
- Utilizza sempre caratteri UTF-8 per i nomi di spazi dei nomi, tipi, proprietà e chiavi personalizzate. I caratteri non UTF-8 utilizzati in questi nomi possono interferire con la funzionalità Datastore. Ad esempio, un carattere non UTF-8 in un nome di 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. I trattini diagonali in questi nomi potrebbero interferire con le funzionalità future. - Evita di archiviare informazioni sensibili in un ID progetto Cloud. Un ID progetto Cloud potrebbe essere conservato oltre la durata del progetto.
- Come best practice per la conformità dei dati, consigliamo di non archiviare dati informazioni nei nomi delle entità o nei nomi delle proprietà delle entità Datastore.
Chiamate API
- Utilizza le operazioni collettive per le letture, le scritture ed eliminazioni anziché 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 diversa richiesta che si contende lo stesso risorse in una transazione. Tieni presente che un rollback potrebbe non riuscire, pertanto il rollback deve essere solo un tentativo secondo il criterio del massimo impegno.
- Utilizza chiamate asincrone, se disponibili, anziché chiamate sincrone.
Le chiamate asincrone riducono al minimo l'impatto della latenza. Ad esempio, prendiamo in considerazione un'applicazione che necessita del risultato di un
lookup()
sincrono e dei risultati di una query per poter restituire una risposta. Selookup()
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à
- Raggruppa i dati altamente correlati in gruppi di entità. Attivazione dei gruppi di entità query predecessore, che restituiscono risultati a elevata coerenza. Le query ancestor esaminano rapidamente anche un gruppo di entità con I/O minime perché le entità in un gruppo di entità sono archiviate in luoghi fisicamente vicini sui server Datastore.
- Evita di scrivere a un gruppo di entità più di una volta al secondo. La scrittura a una frequenza costante superiore a questo limite rende le letture eventualmente coerenti più eventuali, porta a timeout per le letture fortemente coerenti e comporta un rallentamento complessivo delle prestazioni dell'applicazione. Una scrittura batch o transazionale in un'entità viene conteggiato come una singola scrittura rispetto a questo limite.
- Non includere la stessa entità (per chiave) più volte nello stesso commit. L'inclusione della stessa entità più volte nello stesso commit potrebbe influire sulla latenza di Datastore.
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 caratteri UTF-8, ad eccezione della barra (
/
). I caratteri non UTF-8 interferiscono con vari processi, come l'importazione di un backup di Datastore in Google 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, riceverai un 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()
. In questo modo, Datastore non assegnerà uno dei tuoi 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 generare hotspot che incidono sulla latenza di Datastore. Per evitare degli ID numerici sequenziali, ottieni gli ID numerici dal
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 coerente
lookup()
su quell'entità senza dover generare una query per trovare l'entità.
Indici
- Se non serve mai una proprietà per una query, escludere la proprietà dagli indici. L'indicizzazione inutilmente di una proprietà può comportano una maggiore latenza per ottenere coerenza e un aumento costi di archiviazione delle voci di indice.
- Evita di avere troppi indici composti. L'uso eccessivo di indici compositi potrebbe comportare un aumento della latenza per ottenere la coerenza e un aumento dei costi di archiviazione delle voci dell'indice. Se devi eseguire query ad hoc sulle grandi set di dati senza indici definiti in precedenza, utilizza Google BigQuery.
- Non indicizzare le proprietà con valori monotonici crescenti (ad esempio,
NOW()
). La gestione di un indice di questo tipo potrebbe portare a hotspot che influiscono sulla latenza di Datastore per le applicazioni con alte velocità di lettura e scrittura. Per ulteriori indicazioni su come gestire le proprietà monotone, consulta Tariffe di lettura/scrittura elevate per un intervallo di chiavi ristretto di seguito.
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 devi 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 l'indicizzazione delle proprietà delle entità incorporate.
Query
- Se devi accedere solo alla chiave dai risultati della query, utilizza una query solo per chiavi. Una query basata solo su chiavi restituisce risultati con latenza e costi inferiori rispetto al recupero di intere entità.
- Se devi accedere solo a proprietà specifiche di 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 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.
- Se hai bisogno di elevata coerenza per le tue query, utilizza una query sull'antenato.
Per utilizzare le query sugli antenati, devi prima
strutturare i dati per una coerenza elevata. Una query da predecessore restituisce
a elevata coerenza. Tieni presente che una query solo chiavi non principale followed by a
lookup()
non restituisce risultati affidabili, perché la query solo chiavi non principale potrebbe ottenere risultati da un indice non coerente al momento della query.
Progettazione per la scalabilità
Aggiornamenti a un singolo gruppo di entità
Un singolo gruppo di entità in Datastore non deve essere aggiornato troppo rapidamente.
Se utilizzi Datastore, Google consiglia di progettare in modo da non dover aggiornare un gruppo di entità più di una volta al secondo. Ricorda che un'entità senza elementi principali e secondari è un gruppo di entità a sé stante. Se aggiorni un gruppo di entità troppo rapidamente, le scritture in Datastore avranno una latenza più elevata, timeout e altri tipi di errori. Questo sistema è noto come conflittuale.
Le velocità di scrittura del datastore in un singolo gruppo di entità a volte possono superare quelle al secondo pertanto i test di carico potrebbero non evidenziare questo problema.
Alte frequenze di lettura/scrittura per un intervallo di chiavi ristretto
Evita frequenze di lettura o scrittura elevate per le chiavi di Datastore che sono alfabeticamente vicine.
Datastore si basa sul database NoSQL di Google, Bigtable ed è soggetto alle prestazioni di Bigtable caratteristiche. Bigtable esegue la scalabilità suddividendo le righe in tablet distinti e queste righe sono ordinate lessicograficamente in base alla chiave.
Se utilizzi Datastore, puoi riscontrare scritture lente a causa di un tablet caldo se si verifica un aumento improvviso della frequenza di scrittura per un piccolo intervallo di chiavi che supera la capacità di un singolo server tablet. Alla fine, Bigtable 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. Bigtable non può suddividere una singola chiave su più di un tablet.
I tablet attivi possono essere applicati agli intervalli di chiavi usati sia dalle chiavi entità che indici di appartenenza.
In alcuni casi, un hotspot di Datastore può avere un impatto più ampio su un'applicazione rispetto all'impedire letture o scritture in un piccolo intervallo di chiavi. Ad esempio, i tasti di scelta rapida potrebbero essere letti o scritti durante l'avvio dell'istanza, causando il fallimento delle richieste di caricamento.
Per impostazione predefinita, Datastore alloca le chiavi utilizzando un algoritmo distribuito. Di conseguenza, solitamente non incontrerai hotspotting su Datastore scrive se crei nuove entità con un'elevata frequenza di scrittura utilizzando il criterio predefinito di allocazione degli ID. Ci sono alcuni casi d'angolo in cui puoi risolvere questo problema:
Se crei nuove entità a una frequenza molto elevata utilizzando il precedente criterio di allocazione degli ID sequenziali.
Se crei nuove entità con un tasso molto alto e stai allocando le tue ID che aumentano monotonicamente.
Se crei nuove entità a una frequenza molto elevata per un tipo che in precedenza aveva pochissime entità esistenti. Bigtable inizia con tutte le entità sullo stesso tablet server e impiegherà un po' di tempo per suddividere l'intervallo di chiavi separati per il tablet.
Questo problema si verifica anche se crei nuove entità a una frequenza elevata con una proprietà indicizzata in modo monotonamente crescente, come un timestamp, perché queste proprietà sono le chiavi per le righe nelle tabelle di indicizzazione di Bigtable.
Datastore antepone lo spazio dei nomi e il tipo del gruppo di entità principale alla chiave di riga Bigtable. Puoi raggiungere un hotspot se inizi a scrivere in un nuovo ambito o tipo senza aumentare gradualmente il traffico.
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.
Allo stesso modo, se devi eseguire una query su una query in aumento (o diminuzione) monotonico proprietà utilizzando un ordinamento o un filtro, potresti invece eseguire l'indicizzazione in base a una nuova proprietà, per la quale far precedere il valore monotonico con un valore che ha una cardinalità elevata in tutto il set di dati, ma è comune a tutte le entità nell'ambito della query che vuoi eseguire. Ad esempio, se vuoi eseguire una query sulle voci in base al timestamp, restituire risultati per un singolo utente alla volta, puoi anteporre al timestamp il l'ID utente e l'indicizzazione della nuova proprietà. Ciò consentirebbe comunque di eseguire query risultati ordinati per quell'utente, ma la presenza dell'ID utente garantisce che che l'indice di per sé disponga di uno sharding adeguato.
Per una spiegazione più dettagliata di questo problema, vedi Post del blog di Ikai Lan sul salvataggio dei valori con aumento monotonico in Datastore.
Aumento del traffico
Incrementa gradualmente il traffico verso nuovi tipi o porzioni di Datastore dello spazio delle chiavi.
Devi aumentare gradualmente il traffico verso i nuovi tipi di Datastore per dare a Bigtable il tempo sufficiente per suddividere i tablet man mano che il traffico cresce. Consigliamo un massimo di 500 operazioni al secondo a un nuovo Datastore, aumentando il traffico del 50% ogni 5 minuti. In teoria, puoi arrivare a 740.000 operazioni al secondo dopo 90 minuti utilizzando questa pianificazione di aumento. Assicurati che le scritture siano distribuite in modo relativamente uniforme nell'intervallo di chiavi. I nostri SRE la chiamano "regola 500/50/5".
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 modo che sia di 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. Bigtable potrebbe non essere in grado di suddividere in modo efficiente i tablet se lo spazio delle chiavi è sparso.
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 è riportata una strategia di esempio, 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 dall'entità o dalla chiave precedente. Se non è presente, puoi 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. Aumenta gradualmente le letture parallele per assicurarti che lo spazio delle nuove 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 dal comportamento dell'utente.
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 in chiavi sequenziali per evitare hotspot di Bigtable. 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 monitori 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 batch 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 di Datastore.
Eliminazioni
Evita di eliminare un numero elevato di entità Datastore in un piccolo intervallo di chiavi.
Bigtable riscrive periodicamente le tabelle per rimuovere le voci eliminate e riorganizzare i dati in modo che le operazioni di lettura e scrittura siano più efficienti. Questo processo è noto come compattazione.
Se elimini un numero elevato di entità 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 antipattern utilizzare un valore timestamp per un campo indicizzato per rappresentare la data e l'ora 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 le "query suddivise in parti", 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'intero 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 viene anteposto al timestamp di scadenza in modo che le query vengano ordinate in base al numero di generazione, allo shard e al timestamp. L'eliminazione delle entità precedenti avviene in una generazione precedente. Qualsiasi entità non eliminata deve avere il proprio numero di generazione incrementato. Una volta completata l'eliminazione, passerai al di nuova generazione. Le query relative a una generazione precedente avranno un cattivo rendimento 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 le chiavi Datastore ad accesso frequente.
Puoi utilizzare la replica se devi leggere una parte della chiave con una frequenza maggiore rispetto a quella consentita da Bigtable. Con questa strategia, immagazzinerai N copie della stessa entità, consentendo un tasso di letture N volte superiore rispetto a quello supportato da una singola entità.
Puoi utilizzare lo sharding se devi scrivere in una parte dell'intervallo di chiavi a una frequenza superiore a quella consentita da Bigtable. Lo sharding suddivide un'entità in parti più piccole.
Alcuni errori comuni durante lo sharding includono:
Sharding utilizzando un prefisso di tempo. Quando l'ora passa al prefisso successivo, la nuova parte non suddivisa diventa un hotspot. Dovresti invece eseguire gradualmente il roll-over di una parte delle scritture nel nuovo prefisso.
Eseguire il partizionamento orizzontale delle entità più interessanti. Se esegui lo sharding di una piccola parte numero totale di entità, le righe potrebbero non essere sufficienti per assicurarsi che rimangano su divisioni diverse.
Passaggi successivi
- Scopri di più sui limiti di Datastore.
- Scopri di più sulla Google Cloud Platform Best practice per organizzazioni aziendali.