Pode usar as práticas recomendadas aqui indicadas como uma referência rápida do que deve ter em atenção ao criar uma aplicação que usa o Datastore. Se estiver a começar a usar o Datastore, esta página pode não ser o melhor ponto de partida, porque não lhe ensina os princípios básicos de como usar o Datastore. Se for um novo utilizador, sugerimos que comece por ler o artigo Introdução ao Datastore.
Geral
- Use sempre carateres UTF-8 para nomes de espaços de nomes, nomes de tipos, nomes de propriedades e nomes de chaves personalizadas. Os carateres que não sejam UTF-8 usados nestes nomes podem interferir com a funcionalidade do Datastore. Por exemplo, um caráter que não seja UTF-8 num nome de propriedade pode impedir a criação de um índice que use a propriedade.
- Não use uma barra (
/
) em nomes de tipos nem nomes de chaves personalizadas. As barras invertidas nestes nomes podem interferir com a funcionalidade futura. - Evite armazenar informações confidenciais num ID do projeto na nuvem. Um ID do projeto do Google Cloud pode ser retido após o fim do ciclo de vida do seu projeto.
- Como prática recomendada de conformidade com os dados, recomendamos que não armazene informações confidenciais nos nomes das entidades ou nos nomes das propriedades das entidades do Datastore.
Chamadas da API
- Use operações em lote para as suas leituras, escritas e eliminações em vez de operações únicas. As operações em lote são mais eficientes porque executam várias operações com a mesma sobrecarga que uma única operação.
- Se uma transação falhar, certifique-se de que tenta reverter a transação. A reversão minimiza a latência de repetição para um pedido diferente que concorre pelos mesmos recursos numa transação. Tenha em atenção que a reversão em si pode falhar, pelo que a reversão deve ser apenas uma tentativa com base no melhor esforço.
- Use chamadas assíncronas onde estiverem disponíveis em vez de chamadas síncronas.
As chamadas assíncronas minimizam o impacto da latência. Por exemplo, considere uma aplicação que precisa do resultado de um
lookup()
síncrono e dos resultados de uma consulta antes de poder renderizar uma resposta. Se olookup()
e a consulta não tiverem uma dependência de dados, não é necessário esperar sincronamente até que olookup()
seja concluído antes de iniciar a consulta.
Entidades
- Agrupe dados altamente relacionados em grupos de entidades. Os grupos de entidades permitem consultas de antecessores, que devolvem resultados fortemente consistentes. As consultas de antepassados também analisam rapidamente um grupo de entidades com um mínimo de E/S, porque as entidades num grupo de entidades são armazenadas em locais fisicamente próximos nos servidores do Datastore.
- Evite escrever num grupo de entidades mais de uma vez por segundo. Escrever a uma taxa sustentada acima desse limite torna as leituras eventualmente consistentes mais eventuais, leva a tempos limite para leituras fortemente consistentes e resulta num desempenho geral mais lento da sua aplicação. Uma gravação em lote ou transacional num grupo de entidades conta apenas como uma única gravação em relação a este limite.
- Não inclua a mesma entidade (por chave) várias vezes na mesma confirmação. A inclusão da mesma entidade várias vezes na mesma confirmação pode afetar a latência do Datastore.
Chaves
- Os nomes das chaves são gerados automaticamente se não forem fornecidos no momento da criação da entidade. São atribuídos de forma a serem distribuídos uniformemente no espaço de chaves.
- Para uma chave que use um nome personalizado, use sempre carateres UTF-8, exceto uma barra invertida (
/
). Os carateres com codificação diferente de UTF-8 interferem com vários processos, como a importação de uma cópia de segurança do Datastore para o Google BigQuery. Uma barra invertida pode interferir com a funcionalidade futura. - Para uma chave que usa um ID numérico:
- Não use um número negativo para o ID. Um ID negativo pode interferir com a ordenação.
- Não use o valor
0
(zero) para o ID. Se o fizer, recebe um ID atribuído automaticamente. - Se quiser atribuir manualmente os seus próprios IDs numéricos às entidades que
criar, faça com que a sua aplicação obtenha um bloco de IDs com o método
allocateIds()
. Isto impede que o Datastore atribua um dos seus IDs numéricos manuais a outra entidade.
Se atribuir o seu próprio ID numérico manual ou nome personalizado às entidades que criar, não use valores que aumentem monotonicamente, como:
1, 2, 3, …, "Customer1", "Customer2", "Customer3", …. "Product 1", "Product 2", "Product 3", ….
Se uma aplicação gerar um tráfego elevado, esta numeração sequencial pode originar pontos críticos que afetam a latência do Datastore. Para evitar o problema de IDs numéricos sequenciais, obtenha IDs numéricos através do método
allocateIds()
. O métodoallocateIds()
gera sequências bem distribuídas de IDs numéricos.Ao especificar uma chave ou armazenar o nome gerado, pode posteriormente executar uma
lookup()
consistente nessa entidade sem ter de emitir uma consulta para encontrar a entidade.
Índices
- Se nunca precisar de uma propriedade para uma consulta, exclua a propriedade dos índices. A indexação desnecessária de uma propriedade pode resultar num aumento da latência para alcançar a consistência e num aumento dos custos de armazenamento das entradas de índice.
- Evite ter demasiados índices compostos. A utilização excessiva de índices compostos pode resultar num aumento da latência para alcançar a consistência e num aumento dos custos de armazenamento das entradas de índice. Se precisar de executar consultas ad hoc em conjuntos de dados grandes sem índices definidos previamente, use o Google BigQuery.
- Não indexe propriedades com valores que aumentam monotonicamente (como uma data/hora
NOW()
). A manutenção de um índice deste tipo pode originar pontos críticos que afetam a latência do Datastore para aplicações com taxas de leitura e escrita elevadas. Para mais orientações sobre como lidar com propriedades monótonas, consulte a secção Taxas de leitura/escrita elevadas para um intervalo de chaves restrito abaixo.
Propriedades
- Use sempre carateres UTF-8 para propriedades do tipo string. Um caráter não UTF-8 numa propriedade do tipo string pode interferir com as consultas. Se precisar de guardar dados com carateres que não sejam UTF-8, use uma string de bytes.
- Não use pontos nos nomes das propriedades. Os pontos nos nomes das propriedades interferem com a indexação das propriedades de entidades incorporadas.
Consultas
- Se precisar de aceder apenas à chave dos resultados da consulta, use uma consulta apenas de chaves. Uma consulta apenas de chaves devolve resultados com uma latência e um custo inferiores aos da obtenção de entidades completas.
- Se precisar de aceder apenas a propriedades específicas de uma entidade, use uma consulta de projeção. Uma consulta de projeção devolve resultados com uma latência e um custo inferiores aos da obtenção de entidades inteiras.
- Da mesma forma, se precisar de aceder apenas às propriedades incluídas no filtro de consulta (por exemplo, as indicadas numa cláusula
order by
), use uma consulta de projeção. - Não use desvios. Em alternativa, use cursores. A utilização de um deslocamento apenas evita devolver as entidades ignoradas à sua aplicação, mas estas entidades continuam a ser obtidas internamente. As entidades ignoradas afetam a latência da consulta e a sua aplicação é faturada pelas operações de leitura necessárias para as obter.
- Se precisar de uma forte consistência para as suas consultas, use uma consulta de antepassados.
(Para usar consultas de antecessores, primeiro tem de
estruturar os dados para uma forte consistência.) Uma consulta de antepassados devolve resultados fortemente consistentes. Tenha em atenção que uma consulta keys-only sem antepassados, seguida de um
lookup()
, não devolve resultados fortes, porque a consulta keys-only sem antepassados pode obter resultados de um índice que não seja consistente no momento da consulta.
Conceber para a escalabilidade
Atualizações a um único grupo de entidades
Não deve atualizar um único grupo de entidades no Datastore demasiado rapidamente.
Se estiver a usar o Datastore, a Google recomenda que crie a sua aplicação de forma que não precise de atualizar um grupo de entidades mais do que uma vez por segundo. Tenha em atenção que uma entidade sem um elemento principal e sem elementos secundários é o seu próprio grupo de entidades. Se atualizar um grupo de entidades demasiado rapidamente, as suas escritas do Datastore vão ter uma latência mais elevada, limites de tempo e outros tipos de erro. Isto é conhecido como concorrência.
Por vezes, as taxas de gravação do Datastore num único grupo de entidades podem exceder o limite de uma por segundo, pelo que os testes de carga podem não mostrar este problema.
Taxas de leitura/escrita elevadas para um intervalo de chaves restrito
Evite taxas de leitura ou escrita elevadas para chaves do Datastore que estejam lexicograficamente próximas.
O Datastore é criado com base na base de dados NoSQL da Google, o Bigtable, e está sujeito às características de desempenho do Bigtable. O Bigtable é dimensionado dividindo as linhas em fragmentos em tablets separados, e estas linhas são ordenadas lexicograficamente por chave.
Se estiver a usar o Datastore, pode ter escritas lentas devido a um tablet ativo se tiver um aumento súbito na taxa de escrita para um pequeno intervalo de chaves que exceda a capacidade de um único servidor de tablets. Eventualmente, o Bigtable divide o espaço de chaves para suportar uma carga elevada.
Normalmente, o limite de leituras é muito superior ao de escritas, a menos que esteja a ler a partir de uma única chave a uma taxa elevada. O Bigtable não pode dividir uma única chave em mais do que um tablet.
As tabelas dinâmicas podem aplicar-se a intervalos de chaves usados por chaves de entidades e índices.
Em alguns casos, um ponto crítico do Datastore pode ter um impacto mais amplo numa aplicação do que impedir leituras ou escritas num pequeno intervalo de chaves. Por exemplo, as teclas de atalho podem ser lidas ou escritas durante o arranque da instância, o que faz com que os pedidos de carregamento falhem.
Por predefinição, o Datastore atribui chaves através de um algoritmo disperso. Assim, normalmente, não vai encontrar hotspotting em gravações do Datastore se criar novas entidades a uma taxa de gravação elevada através da política de atribuição de ID predefinida. Existem alguns casos extremos em que pode deparar-se com este problema:
Se criar novas entidades a uma taxa muito elevada através da política de atribuição de ID sequencial antiga.
Se criar novas entidades a uma taxa muito elevada e estiver a atribuir os seus próprios IDs, que estão a aumentar monotonicamente.
Se criar novas entidades a uma taxa muito elevada para um tipo que anteriormente tinha muito poucas entidades existentes. O Bigtable começa com todas as entidades no mesmo servidor de tablets e demora algum tempo a dividir o intervalo de chaves em servidores de tablets separados.
Também vai ver este problema se criar novas entidades a uma taxa elevada com uma propriedade indexada que aumenta monotonicamente, como uma data/hora, porque estas propriedades são as chaves para linhas nas tabelas de índice no Bigtable.
O Datastore antepõe o espaço de nomes e o tipo do grupo de entidades raiz à chave da linha do Bigtable. Pode atingir um ponto crítico se começar a escrever num novo espaço de nomes ou tipo sem aumentar gradualmente o tráfego.
Se tiver uma chave ou uma propriedade indexada que aumente monotonicamente, pode adicionar um hash aleatório para garantir que as chaves são divididas em vários tablets.
Da mesma forma, se precisar de consultar uma propriedade de aumento (ou diminuição) monótono usando uma ordenação ou um filtro, pode indexar uma nova propriedade, para a qual prefixa o valor monótono com um valor que tenha uma cardinalidade elevada no conjunto de dados, mas que seja comum a todas as entidades no âmbito da consulta que quer executar. Por exemplo, se quiser consultar entradas por data/hora, mas só precisar de devolver resultados para um único utilizador de cada vez, pode prefixar a data/hora com o ID do utilizador e indexar essa nova propriedade. Isto continuaria a permitir consultas e resultados ordenados para esse utilizador, mas a presença do ID do utilizador garantiria que o próprio índice está bem dividido.
Para uma explicação mais detalhada deste problema, consulte a publicação no blogue de Ikai Lan sobre como guardar valores que aumentam monotonicamente no Datastore.
Aumentar o tráfego
Aumentar gradualmente o tráfego para novos tipos de Datastore ou partes do espaço de chaves.
Deve aumentar gradualmente o tráfego para novos tipos de Datastore para dar ao Bigtable tempo suficiente para dividir as tabelas à medida que o tráfego aumenta. Recomendamos um máximo de 500 operações por segundo para um novo tipo do Datastore e, em seguida, aumentar o tráfego 50% a cada 5 minutos. Em teoria, pode aumentar para 740 mil operações por segundo após 90 minutos com este cronograma de aumento. Certifique-se de que as gravações são distribuídas de forma relativamente uniforme ao longo do intervalo de chaves. Os nossos SREs chamam a isto a regra "500/50/5".
Este padrão de aumento gradual é particularmente importante se alterar o código para deixar de usar o tipo A e, em vez disso, usar o tipo B. Uma forma simples de processar esta migração é alterar o código para ler o tipo B e, se não existir, ler o tipo A. No entanto, isto pode causar um aumento repentino no tráfego para um novo tipo com uma porção muito pequena do espaço de chaves. O Bigtable pode não conseguir dividir as tablets de forma eficiente se o espaço de chaves for esparso.
O mesmo problema também pode ocorrer se migrar as suas entidades para usar um intervalo diferente de chaves do mesmo tipo.
A estratégia que usa para migrar entidades para um novo tipo ou chave depende do seu modelo de dados. Segue-se um exemplo de estratégia, conhecido como "Leituras paralelas". Tem de determinar se esta estratégia é eficaz para os seus dados. Uma consideração importante é o impacto dos custos das operações paralelas durante a migração.
Leia primeiro a partir da entidade ou chave antiga. Se estiver em falta, pode ler a partir da nova entidade ou chave. Uma taxa elevada de leituras de entidades inexistentes pode levar a hotspotting, pelo que tem de se certificar de que aumenta gradualmente a carga. Uma estratégia melhor é copiar a entidade antiga para a nova e, em seguida, eliminar a antiga. Aumente as leituras paralelas gradualmente para garantir que o novo espaço de chaves está bem dividido.
Uma possível estratégia para aumentar gradualmente as leituras ou as escritas num novo tipo é usar um hash determinístico do ID do utilizador para obter uma percentagem aleatória de utilizadores que escrevem novas entidades. Certifique-se de que o resultado do hash do ID do utilizador não é distorcido pela sua função aleatória nem pelo comportamento do utilizador.
Entretanto, execute uma tarefa do Dataflow para copiar todos os dados das entidades ou chaves antigas para as novas. O seu trabalho em lote deve evitar escritas em chaves sequenciais para impedir pontos críticos do Bigtable. Quando a tarefa em lote estiver concluída, só pode ler a partir da nova localização.
Um refinamento desta estratégia é migrar pequenos lotes de utilizadores de uma só vez. Adicione um campo à entidade do utilizador que monitoriza o estado de migração desse utilizador. Selecione um lote de utilizadores para migrar com base num hash do ID do utilizador. Uma tarefa Mapreduce ou Dataflow migra as chaves para esse lote de utilizadores. Os utilizadores que têm uma migração em curso vão usar leituras paralelas.
Tenha em atenção que não pode reverter facilmente, a menos que faça escritas duplas das entidades antigas e novas durante a fase de migração. Isto aumentaria os custos incorridos do Datastore.
Eliminações
Evite eliminar um grande número de entidades do Datastore num pequeno intervalo de chaves.
O Bigtable reescreve periodicamente as respetivas tabelas para remover entradas eliminadas e reorganizar os seus dados de modo que as leituras e as escritas sejam mais eficientes. Este processo é conhecido como compactação.
Se eliminar um grande número de entidades do Datastore num pequeno intervalo de chaves, as consultas nesta parte do índice serão mais lentas até a compactação estar concluída. Em casos extremos, as suas consultas podem expirar antes de devolverem resultados.
É um antipadrão usar um valor de data/hora para um campo indexado para representar a hora de validade de uma entidade. Para obter entidades expiradas, tem de consultar este campo indexado, que provavelmente se encontra numa parte sobreposta do espaço de chaves com entradas de índice para as entidades eliminadas mais recentemente.
Pode melhorar o desempenho com "consultas fragmentadas", que antepõem uma string de comprimento fixo à data/hora de validade. O índice é ordenado pela string completa, para que as entidades com a mesma data/hora sejam localizadas ao longo do intervalo de chaves do índice. Executa várias consultas em paralelo para obter resultados de cada fragmento.
Uma solução mais completa para o problema da data/hora de validade é usar um "número de geração", que é um contador global que é atualizado periodicamente. O número de geração é adicionado antes da data/hora de expiração para que as consultas sejam ordenadas por número de geração, depois por fragmento e, em seguida, por data/hora. A eliminação de entidades antigas ocorre numa geração anterior. O número de geração de qualquer entidade não eliminada deve ser incrementado. Quando a eliminação estiver concluída, avança para a geração seguinte. As consultas de uma geração mais antiga têm um desempenho fraco até à conclusão da compactação. Pode ter de aguardar a conclusão de várias gerações antes de consultar o índice para obter a lista de entidades a eliminar, de modo a reduzir o risco de perder resultados devido à consistência eventual.
Divisão e replicação
Use a divisão ou a replicação para chaves do Datastore populares.
Pode usar a replicação se precisar de ler uma parte do intervalo de chaves a uma taxa superior à permitida pelo Bigtable. Com esta estratégia, armazenaria N cópias da mesma entidade, o que permitiria uma taxa de leituras N vezes superior à suportada por uma única entidade.
Pode usar a divisão em partições se precisar de escrever numa parte do intervalo de chaves a uma taxa superior à permitida pelo Bigtable. A fragmentação divide uma entidade em partes mais pequenas.
Seguem-se alguns erros comuns ao dividir em partições:
Dividir em partições com um prefixo de tempo. Quando o tempo passa para o prefixo seguinte, a nova parte não dividida torna-se um ponto de acesso. Em alternativa, deve transferir gradualmente uma parte das suas gravações para o novo prefixo.
Dividir apenas as entidades mais populares. Se dividir uma pequena proporção do número total de entidades, pode não haver linhas suficientes entre as entidades populares para garantir que permanecem em divisões diferentes.
O que se segue?
- Saiba mais sobre os limites do Datastore.
- Saiba mais sobre as práticas recomendadas para organizações empresariais da Google Cloud Platform.