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 use o Firestore no modo Datastore. Se estiver a começar a usar o modo 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 modo Datastore. Se for um novo utilizador, sugerimos que comece com o artigo Introdução ao Firestore no modo 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 modo 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.
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
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.
Consulte a secção sobre atualizações a uma entidade.
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 usa um nome personalizado, use sempre carateres UTF-8, exceto uma barra (
/
). Os carateres com codificação diferente de UTF-8 interferem com vários processos, como a importação de um ficheiro de exportação do modo Datastore para o 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 modo 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 modo 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 um
lookup()
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 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 de escrita 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 anteriormente, use o 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 modo 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.
Conceber para a escalabilidade
As seguintes práticas recomendadas descrevem como evitar situações que criam problemas de contenção.
Atualizações a uma entidade
À medida que cria a sua app, considere a rapidez com que a app atualiza entidades únicas. A melhor forma de caracterizar o desempenho da sua carga de trabalho é realizar testes de carga. A taxa máxima exata com que uma app pode atualizar uma única entidade depende muito da carga de trabalho. Os fatores incluem a taxa de gravação, a contenção entre pedidos e o número de índices afetados.
Uma operação de escrita de entidades atualiza a entidade e todos os índices associados, e o Firestore no modo Datastore aplica de forma síncrona a operação de escrita num quorum de réplicas. A taxas de gravação suficientemente elevadas, a base de dados começa a encontrar contenção, latência mais elevada ou outros erros.
Taxas de leitura/escrita elevadas para um intervalo de chaves restrito
Evite taxas de leitura ou escrita elevadas em documentos lexicograficamente próximos, ou a sua aplicação vai ter erros de contenção. Este problema é conhecido como hotspotting e a sua aplicação pode sofrer hotspotting se fizer alguma das seguintes ações:
Cria novas entidades a uma taxa muito elevada e atribui os seus próprios IDs monotonamente crescentes.
O modo Datastore atribui chaves através de um algoritmo de dispersão. Não deve encontrar hotspotting em gravações se criar novas entidades através da atribuição automática de IDs de entidades.
Cria novas entidades a uma taxa muito elevada através da política de atribuição de ID sequencial antiga.
Cria novas entidades a uma taxa elevada para um tipo com poucas entidades.
Cria novas entidades com um valor de propriedade indexado e monotonicamente crescente, como uma data/hora, a uma taxa muito elevada.
Elimina entidades de um tipo a uma taxa elevada.
Escreve na base de dados a uma taxa muito elevada sem aumentar gradualmente o tráfego.
Se tiver um aumento súbito na taxa de gravação para um pequeno intervalo de chaves, pode ter gravações lentas devido a um ponto crítico. O modo Datastore vai dividir 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.
Os pontos críticos podem aplicar-se a intervalos de chaves usados por chaves de entidades e índices.
Em alguns casos, um ponto crítico 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.
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) monotónico usando uma ordenação ou um filtro, pode, em alternativa, indexar uma nova propriedade, para a qual prefixa o valor monotónico 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 ainda permitiria consultas e resultados ordenados para esse utilizador, mas a presença do ID do utilizador garantiria que o próprio índice está bem dividido.
Aumentar o tráfego
Aumentar gradualmente o tráfego para novos tipos ou partes do espaço de chaves.
Deve aumentar gradualmente o tráfego para novos tipos de dados para dar ao Firestore no modo Datastore tempo suficiente para se preparar para o aumento do tráfego. Recomendamos um máximo de 500 operações por segundo para um novo tipo 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 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 evitar pontos críticos. 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 no modo Datastore.
Eliminações
Evite eliminar um grande número de entidades num pequeno intervalo de chaves.
O Firestore no modo Datastore 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 modo 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 lidar com pontos ativos.
Pode usar a replicação se precisar de ler uma parte do intervalo de chaves a uma taxa superior à permitida pelo Firestore no modo Datastore. 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 Firestore no modo Datastore. 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.
Próximos passos
- Saiba mais sobre os limites do Firestore no modo Datastore.
- Saiba mais sobre as práticas recomendadas para organizações empresariais.