Compreenda as leituras e as escritas em grande escala
Leia este documento para tomar decisões informadas sobre a arquitetura das suas aplicações para um elevado desempenho e fiabilidade. Este documento inclui tópicos avançados do Firestore. Se está a começar a usar o Firestore, consulte o guia de início rápido.
Para garantir que as suas aplicações continuam a ter um bom desempenho à medida que o tamanho da base de dados e o tráfego aumentam, é útil compreender a mecânica das leituras e escritas no back-end do Firestore. Também tem de compreender a interação das suas leituras e escritas com a camada de armazenamento e as restrições subjacentes que podem afetar o desempenho.
Consulte as secções seguintes para conhecer as práticas recomendadas antes de criar a arquitetura da sua aplicação.
Compreenda os componentes de alto nível
O diagrama seguinte mostra os componentes de alto nível envolvidos num pedido da API Firestore.
SDKs, bibliotecas cliente e controladores
O Firestore suporta SDKs, bibliotecas cliente e controladores para diferentes plataformas.
Google Front End (GFE)
Este é um serviço de infraestrutura comum a todos os serviços Google Cloud . O GFE aceita pedidos recebidos e encaminha-os para o serviço Google relevante (serviço Firestore neste contexto).
Serviço do Firestore
O serviço Firestore realiza verificações no pedido da API, que incluem autenticação, autorização, verificações de quotas e também gere transações. Este serviço do Firestore inclui um cliente de armazenamento que interage com a camada de armazenamento para as leituras e escritas de dados.
Camada de armazenamento do Firestore
A camada de armazenamento do Firestore é responsável pelo armazenamento dos dados e metadados, bem como pelas funcionalidades da base de dados associadas fornecidas pelo Firestore. As secções seguintes descrevem como os dados são organizados na camada de armazenamento do Firestore e como o sistema é dimensionado. Saber como os dados estão organizados pode ajudar a criar um modelo de dados escalável e a compreender melhor as práticas recomendadas no Firestore.
Intervalos de chaves e divisões
O Firestore é uma base de dados NoSQL orientada para documentos. Armazena dados em documentos, que estão organizados em coleções. O nome da coleção e o ID do documento formam uma chave única para um documento. Os documentos na mesma coleção são armazenados em conjunto no keyspace. Nesse espaço de chaves, o ID do documento é calculado através de hash. O termo intervalo de chaves refere-se a um intervalo contíguo de chaves no armazenamento.
O Firestore divide automaticamente os dados nas coleções em vários servidores de armazenamento. Estas partições são denominadas divisões.
Os documentos podem gerar entradas de índice ordenadas lexicograficamente e participar no mesmo tipo de divisão e posicionamento que os dados do documento.
Replicação síncrona
Cada gravação é replicada de forma síncrona para a maioria das réplicas através do Paxos. Uma réplica por divisão é considerada um líder e coordena o processo de replicação. No caso de falha do líder, é eleito um novo líder. As réplicas estão localizadas em zonas diferentes para serem resilientes a uma potencial falha de zona. O resultado geral é um sistema escalável e altamente disponível que oferece latências baixas para leituras e escritas, independentemente de cargas de trabalho pesadas e em grande escala.
Região única versus multirregião
Quando cria uma base de dados, tem de selecionar uma localização regional única ou uma localização multirregional.
Uma única localização regional é uma localização geográfica específica, como us-west1. As divisões de dados de uma base de dados do Firestore têm réplicas em diferentes zonas na região selecionada, conforme explicado anteriormente.
Uma localização multirregional consiste num conjunto definido de regiões onde o Firestore armazena réplicas da base de dados. Numa implementação multirregional do Firestore, duas regiões têm réplicas completas de todos os dados na base de dados. Uma terceira região tem uma réplica de testemunho que não mantém um conjunto completo de dados, mas participa na replicação. Os dados estão disponíveis para serem escritos e lidos, mesmo com a perda de uma região inteira, porque o Firestore replica os dados entre várias regiões.
Para mais informações sobre as localizações de uma região, consulte o artigo Localizações do Firestore.
Compreenda o ciclo de vida de uma gravação
Um condutor pode escrever dados criando, atualizando ou eliminando um único documento. Uma escrita num único documento requer a atualização atómica do documento e das respetivas entradas de índice na camada de armazenamento. O Firestore também suporta operações atómicas que consistem em várias leituras e escritas num ou mais documentos.
Para todos os tipos de escritas, o Firestore oferece as propriedades ACID (atomicidade, consistência, isolamento e durabilidade) das bases de dados relacionais. O Firestore também oferece serialização, o que significa que todas as transações aparecem como se fossem executadas numa ordem de série.
Passos de alto nível numa transação de escrita
Quando o controlador emite uma gravação ou confirma uma transação, através de qualquer um dos métodos mencionados anteriormente, isto é executado internamente como uma transação de leitura/gravação da base de dados na camada de armazenamento. A transação permite que o Firestore forneça as propriedades ACID mencionadas anteriormente.
Como primeiro passo de uma transação, o Firestore lê o documento existente e determina as mutações a fazer aos dados no documento.
Isto também inclui a atualização de quaisquer índices relevantes:
- Os campos indexados que estão a ser adicionados aos documentos precisam de inserções correspondentes nos índices.
- Os campos indexados que estão a ser removidos dos documentos precisam de eliminações correspondentes nos índices.
- Os campos indexados que estão a ser modificados nos documentos precisam de eliminações (para valores antigos) e inserções (para novos valores) nos índices.
Para calcular as mutações mencionadas anteriormente, o Firestore lê a configuração de indexação do projeto. A configuração de indexação armazena informações sobre os índices de um projeto.
Assim que as mutações são calculadas, o Firestore recolhe-as numa transação e, em seguida, confirma-a.
Compreenda uma transação de escrita na camada de armazenamento
Conforme referido anteriormente, uma escrita no Firestore envolve uma transação de leitura/escrita na camada de armazenamento. Consoante a disposição dos dados, uma gravação pode envolver uma ou mais divisões.
No diagrama seguinte, a base de dados do Firestore tem oito divisões (marcadas de 1 a 8) alojadas em três servidores de armazenamento diferentes numa única zona, e cada divisão é replicada em 3(ou mais) zonas diferentes. Cada divisão tem um líder do Paxos, que pode estar numa zona diferente para diferentes divisões.
Considere uma base de dados do Firestore que tenha a coleção Restaurants
da seguinte forma:
O controlador pede a seguinte alteração a um documento na coleção Restaurant
atualizando o valor do campo priceCategory
.
Os seguintes passos de alto nível descrevem o que acontece como parte da gravação:
- Crie uma transação de leitura/escrita.
- Leia o documento
restaurant1
na coleçãoRestaurants
. - Ler os índices do documento.
- Calcular as mutações a fazer nos dados. Neste caso, existem cinco mutações:
- M1: atualize a linha para
restaurant1
de modo a refletir a alteração no valor do campopriceCategory
. - M2 e M3: elimine as entradas de índice antigas para
priceCategory
. - M4 e M5: adicione novas entradas de índice para
priceCategory
.
- M1: atualize a linha para
- Confirme estas mutações.
O cliente de armazenamento no serviço Firestore procura as divisões que detêm as chaves das linhas a alterar. Considere um caso em que a divisão 3 publica M1 e a divisão 6 publica M2 a M5. Existe uma transação distribuída que envolve todas estas divisões como participantes. As divisões de participantes também podem incluir qualquer outra divisão a partir da qual os dados foram lidos anteriormente como parte da transação de leitura/escrita.
Os passos seguintes descrevem o que acontece como parte da confirmação:
- O cliente de armazenamento emite uma confirmação. A confirmação contém as mutações M1 a M5.
- As divisões 3 e 6 são os participantes nesta transação. Um dos participantes é escolhido como coordenador, como a divisão 3. A tarefa do coordenador é garantir que a transação é confirmada ou anulada atomicamente em todos os participantes.
- As réplicas principais destas divisões são responsáveis pelo trabalho realizado pelos participantes e coordenadores.
- Cada participante e coordenador executa um algoritmo Paxos com as respetivas réplicas.
- O líder executa um algoritmo Paxos com as réplicas. O quórum é alcançado se a maioria das réplicas responder com uma resposta
ok to commit
ao líder. - Em seguida, cada participante notifica o coordenador quando está preparado (primeira fase da confirmação de duas fases). Se qualquer participante não conseguir confirmar a transação, toda a transação
aborts
.
- O líder executa um algoritmo Paxos com as réplicas. O quórum é alcançado se a maioria das réplicas responder com uma resposta
- Assim que o coordenador souber que todos os participantes, incluindo ele próprio, estão preparados, comunica o resultado da transação
accept
a todos os participantes (segunda fase da confirmação de duas fases). Nesta fase, cada participante regista a decisão de confirmação no armazenamento estável e a transação é confirmada. - O coordenador responde ao cliente de armazenamento no Firestore que a transação foi confirmada. Em paralelo, o coordenador e todos os participantes aplicam as mutações aos dados.
Quando a base de dados do Firestore é pequena, pode acontecer que uma única divisão seja proprietária de todas as chaves nas mutações M1-M5. Nesse caso, existe apenas um participante na transação e a confirmação em duas fases mencionada anteriormente não é necessária, o que torna as escritas mais rápidas.
Escrita em várias regiões
Numa implementação em várias regiões, a distribuição de réplicas por várias regiões aumenta a disponibilidade, mas tem um custo de desempenho. A comunicação entre réplicas em diferentes regiões demora mais tempo de ida e volta. Por conseguinte, a latência de base para as operações do Firestore é ligeiramente superior em comparação com as implementações de região única.
Configuramos as réplicas de forma que a liderança das divisões permaneça sempre na região principal. A região principal é aquela a partir da qual o tráfego está a entrar no servidor do Firestore. Esta decisão de liderança reduz o atraso de ida e volta na comunicação entre o cliente de armazenamento no Firestore e o líder da réplica (ou o coordenador para transações com várias divisões).
Compreenda o ciclo de vida de uma leitura
Esta secção aborda as leituras no Firestore. Em particular, as consultas consistem numa combinação de leituras de documentos e leituras de entradas de índice.
As leituras de dados da camada de armazenamento são feitas internamente através de uma transação de base de dados para garantir leituras consistentes. No entanto, ao contrário das transações usadas para escritas, estas transações não usam bloqueios. Em vez disso, funcionam escolhendo uma indicação de tempo e, em seguida, executando todas as leituras nessa indicação de tempo. Como não adquirem bloqueios, não bloqueiam transações de leitura/escrita simultâneas. Para executar esta transação, o cliente de armazenamento no Firestore especifica um limite de tempo, que indica à camada de armazenamento como escolher uma data/hora de leitura. O tipo de data/hora limite escolhido pelo cliente de armazenamento no Firestore é determinado pelas opções de leitura do pedido de leitura.
Compreenda uma transação de leitura na camada de armazenamento
Esta secção descreve os tipos de leituras e como são processadas na camada de armazenamento no Firestore.
Leituras fortes
Por predefinição, as leituras do Firestore são fortemente consistentes. Esta forte consistência significa que uma leitura do Firestore devolve a versão mais recente dos dados que reflete todas as escritas confirmadas até ao início da leitura.
Leitura de divisão única
O cliente de armazenamento no Firestore procura as divisões que detêm as chaves das linhas a serem lidas. Suponha que tem de fazer uma leitura da divisão 3 da secção anterior. O cliente envia o pedido de leitura para a réplica mais próxima para reduzir a latência de ida e volta.
Neste momento, podem ocorrer os seguintes casos, consoante a réplica escolhida:
- O pedido de leitura é enviado para uma réplica principal (zona A).
- Como o líder está sempre atualizado, a leitura pode prosseguir diretamente.
- O pedido de leitura é enviado para uma réplica não principal (por exemplo, a zona B)
- A divisão 3 pode saber pelo respetivo estado interno que tem informações suficientes para publicar a leitura e a divisão fá-lo.
- O Split 3 não tem a certeza se viu os dados mais recentes. Envia uma mensagem ao líder para pedir a data/hora da última transação que tem de aplicar para publicar a leitura. Assim que essa transação for aplicada, a leitura pode prosseguir.
Em seguida, o Firestore devolve a resposta ao respetivo cliente.
Leitura com várias divisões
Na situação em que as leituras têm de ser feitas a partir de várias divisões, o mesmo mecanismo ocorre em todas as divisões. Assim que os dados forem devolvidos de todas as divisões, o cliente de armazenamento no Firestore combina os resultados. Em seguida, o Firestore responde ao respetivo cliente com estes dados.
Evite pontos ativos
As divisões no Firestore são automaticamente divididas em partes mais pequenas para distribuir o trabalho de publicação de tráfego para mais servidores de armazenamento quando necessário ou quando o espaço de chaves se expande. As divisões criadas para processar o excesso de tráfego são mantidas durante cerca de 24 horas, mesmo que o tráfego desapareça. Assim, se existirem picos de tráfego recorrentes, as divisões são mantidas e são introduzidas mais divisões sempre que necessário. Estes mecanismos ajudam as bases de dados do Firestore a serem dimensionadas automaticamente sob uma carga de tráfego ou um tamanho da base de dados cada vez maior. No entanto, existem algumas limitações a ter em atenção.
A divisão do armazenamento e do carregamento demora tempo, e o aumento do tráfego demasiado rápido pode causar uma latência elevada ou erros de limite de tempo excedido, normalmente denominados hotspots, enquanto o serviço se ajusta. A prática recomendada é distribuir as operações pelo intervalo de chaves, ao mesmo tempo que aumenta gradualmente o tráfego numa coleção numa base de dados.
Embora as divisões sejam criadas automaticamente com o aumento da carga, o Firestore só pode dividir um intervalo de chaves até estar a publicar um único documento através de um conjunto dedicado de servidores de armazenamento replicados. Como resultado, volumes elevados e contínuos de operações simultâneas num único documento podem levar a um ponto crítico nesse documento. Se encontrar latências elevadas persistentes num único documento, deve considerar modificar o seu modelo de dados para dividir ou replicar os dados em vários documentos.
Os erros de contenção ocorrem quando várias operações tentam ler e escrever o mesmo documento em simultâneo.
Tenha em atenção que, ao seguir as práticas descritas nesta página, o Firestore pode ser dimensionado para publicar cargas de trabalho arbitrariamente grandes sem ter de ajustar nenhuma configuração.