Entender as consultas em tempo real e em grande escala

Neste documento, você encontra orientações sobre o escalonamento do seu app sem servidor, além de milhares de operações por segundo ou centenas de milhares de usuários simultâneos. Além disso, você vai ver tópicos avançados para ajudar a entender em detalhes o sistema. Se você está começando a usar o Firestore, consulte o guia de início rápido.

O Firestore e os SDKs para dispositivos móveis/Web do Firebase oferecem um modelo eficiente para desenvolvimento de apps sem servidor em que o código do lado do cliente acessa diretamente o banco de dados. Com os SDKs, os clientes podem detectar atualizações dos dados em tempo real. É possível usar atualizações em tempo real para criar apps responsivos que não exijam infraestrutura de servidor. Embora seja muito fácil instalar e executar algo, isso ajuda a entender as restrições nos sistemas que compõem o Firestore para que seu app sem servidor possa ser escalonado e ter um bom desempenho quando o tráfego aumentar.

Consulte as seções a seguir para ver dicas sobre como escalonar seu app.

Escolher um local de banco de dados próximo aos usuários

Veja no diagrama a seguir a arquitetura de um app em tempo real:

Exemplo de arquitetura de app em tempo real

Quando um app em execução no dispositivo de um usuário (dispositivo móvel ou Web) estabelece uma conexão com o Firestore, ela é roteada para um servidor de front-end do Firestore na mesma região em que seu banco de dados está localizado. Por exemplo, se o banco de dados estiver em us-east1, a conexão também vai para um front-end do Firestore também em us-east1. Essas conexões são de longa duração e permanecem abertas até que sejam explicitamente fechadas pelo app. O front-end lê dados dos sistemas de armazenamento subjacentes do Firestore.

A distância entre a localização física de usuários e o local do banco de dados do Firestore afeta a latência do usuário. Por exemplo, um usuário na Índia com um app que se comunica com um banco de dados em uma região do Google Cloud na América do Norte pode achar a experiência no app mais lenta do que se o banco de dados estivesse localizado mais perto, como na Índia ou em outra parte da Ásia.

Projeto com foco em confiabilidade

Os seguintes tópicos melhoram ou afetam a confiabilidade do seu app:

Ativar o modo off-line

Os SDKs do Firebase oferecem permanência de dados off-line. Se o app no dispositivo do usuário não se conectar ao Firestore, ele ainda poderá ser usado trabalhando com dados armazenados em cache local. Isso garante acesso aos dados mesmo quando os usuários têm uma conexão instável com a Internet ou perdem completamente o acesso por várias horas ou dias. Para mais detalhes sobre o modo off-line, consulte Ativar dados off-line.

Entender as novas tentativas automáticas

Os SDKs do Firebase repetem as operações e restabelecem conexões corrompidas. Isso ajuda a solucionar erros transitórios causados pela reinicialização de servidores ou problemas de rede entre o cliente e o banco de dados.

Escolher entre locais regionais e multirregionais

Há várias vantagens e desvantagens entre locais regionais e multirregionais. A principal diferença é a forma como os dados são replicados. Por isso, há garantias de disponibilidade do app. Uma instância de várias regiões oferece mais confiabilidade na veiculação e aumenta a durabilidade dos dados, mas tem um custo maior.

Entender o sistema de consulta em tempo real

Com as consultas em tempo real (também chamadas de listeners de snapshots) o app detecta mudanças no banco de dados e recebe notificações de baixa latência assim que os dados são alterados. Um app pode ter o mesmo resultado pesquisando periodicamente atualizações no banco de dados, mas geralmente isso é mais lento, mais caro e exige mais código. Para ver exemplos de como configurar e usar consultas em tempo real, consulte Receber atualizações em tempo real. Nas seções a seguir, você verá detalhes sobre como os listeners de snapshots funcionam, bem como algumas práticas recomendadas para escalonar consultas em tempo real mantendo o desempenho.

Imagine dois usuários que se conectam ao Firestore usando um app de mensagens criado com um dos SDKs para dispositivos móveis.

O cliente A grava no banco de dados para adicionar e atualizar documentos em uma coleção chamada chatroom:

collection chatroom:
    document message1:
      from: 'Sparky'
      message: 'Welcome to Firestore!'

    document message2:
      from: 'Santa'
      message: 'Presents are coming'

O cliente B detecta atualizações na mesma coleção usando um listener de snapshots. O Cliente B recebe uma notificação imediata sempre que alguém cria uma nova mensagem. O diagrama a seguir mostra a arquitetura de um listener de snapshot:

Arquitetura de uma conexão de listener de snapshots

A sequência de eventos a seguir ocorre quando o Cliente B conecta um listener de snapshots ao banco de dados:

  1. O cliente B abre uma conexão com o Firestore e registra um listener fazendo uma chamada para onSnapshot(collection("chatroom")) pelo SDK do Firebase. Esse listener pode ficar ativo por horas.
  2. O front-end do Firestore consulta o sistema de armazenamento subjacente para inicializar o conjunto de dados. O conjunto de resultados de documentos correspondentes é carregado. Esse processo é chamado de pesquisa de consulta. Em seguida, o sistema avalia as Regras de segurança do Firebase para verificar se os usuários têm acesso a esses dados. Se os usuários estiverem autorizados, o banco de dados retornará os dados.
  3. A consulta do Cliente B passa para o modo de detecção. O listener é registrado com um gerenciador de assinatura e aguarda atualizações dos dados.
  4. O Cliente A agora envia uma operação de gravação para modificar um documento.
  5. O banco de dados confirma a alteração no documento no sistema de armazenamento.
  6. Transacionalmente, o sistema confirma a mesma atualização em um registro de alterações interno. À medida que as alterações acontecem, esse registro estabelece uma ordem estrita para elas.
  7. O registro, por sua vez, distribui os dados atualizados para um pool de gerenciadores de assinaturas.
  8. Um correspondente de consulta reversa é executado para conferir se o documento atualizado corresponde a algum listener de snapshot registrado atualmente. Neste exemplo, o documento corresponde ao listener de snapshot do Cliente B. Como o nome indica, é possível pensar no correspondente de consulta reversa como uma consulta normal de banco de dados, mas feito de maneira inversa. Em vez de pesquisar documentos para encontrar aqueles que correspondam a uma consulta, ele pesquisa com eficiência as consultas para encontrar aquelas que correspondam a um documento recebido. Ao encontrar uma correspondência, o sistema encaminha o documento em questão para os listeners de snapshots. Em seguida, o sistema avalia as Regras de segurança do Firebase para garantir que apenas usuários autorizados recebam os dados.
  9. O sistema encaminha a atualização do documento para o SDK no dispositivo do cliente B, e o callback onSnapshot é acionado. Se a persistência local estiver ativada, o SDK também aplicará a atualização ao cache local.

Uma parte essencial da escalonabilidade do Firestore depende da distribuição de dados do registro de alterações para os gerenciadores de assinatura e os servidores de front-end. A distribuição de dados permite que um único dado seja propagado de maneira eficiente para atender a milhões de consultas em tempo real e usuários conectados. Ao executar muitas réplicas de todos esses componentes em várias zonas (ou várias regiões, no caso de uma implantação multirregional), o Firestore alcança alta disponibilidade e escalonabilidade.

As operações de leitura emitidas por SDKs para dispositivos móveis e para Web seguem o modelo acima. Elas executam uma consulta de pesquisa seguida pelo modo de detecção para manter as garantias de consistência. Isso também se aplica a listeners em tempo real, chamadas para recuperar um documento e consultas únicas. Pense nas recuperações de documentos únicos e consultas únicas como listeners de snapshots de curta duração com restrições semelhantes ao desempenho.

Aplicar as práticas recomendadas para escalonar consultas em tempo real

Aplique as práticas recomendadas a seguir para criar consultas escalonáveis em tempo real.

Entender o tráfego de gravação alto no sistema

Nesta seção, você verá como o sistema responde a um número crescente de solicitações de gravação.

Os registros de alterações do Firestore que geram consultas em tempo real são escalonados automaticamente e horizontalmente à medida que o tráfego de gravação aumenta. À medida que a taxa de gravação de um banco de dados aumenta além do que um servidor único pode processar, o registro de alterações é dividido em vários servidores. Além disso, o processamento de consultas começa a consumir dados de vários gerenciadores de assinaturas em vez de um. Do ponto de vista do SDK e do cliente, tudo é transparente e não é necessário fazer nenhuma ação quando há divisões. Veja no diagrama a seguir como as consultas em tempo real são escalonadas:

Arquitetura de distribuição de registros de alterações

Com o escalonamento automático. é possível aumentar o tráfego de gravação sem limites. No entanto, conforme o tráfego aumenta, o sistema pode demorar um pouco para responder. Siga as recomendações da regra 5-5-5 para evitar a criação de um ponto de acesso de gravação. O Key Visualizer é uma ferramenta útil para analisar pontos de acesso de gravação.

Muitos apps têm um crescimento orgânico previsível, que o Firestore pode acomodar sem precauções. No entanto, cargas de trabalho em lote, como a importação de um grande conjunto de dados, podem aumentar muito as gravações. Ao projetar seu app, analise a origem do tráfego de gravação.

Entender como as gravações e as leituras interagem

Pense no sistema de consulta em tempo real como um pipeline que conecta operações de gravação com leitores. Sempre que um documento é criado, atualizado ou excluído, a alteração é propagada do sistema de armazenamento para os listeners registrados atualmente. A estrutura do registro de alterações do Firestore garante forte consistência, o que significa que seu app nunca recebe notificações de atualizações fora de ordem em comparação com quando o banco de dados confirmou as mudanças nos dados. Isso simplifica o desenvolvimento de apps removendo casos extremos relacionados à consistência de dados.

Esse pipeline conectado significa que uma operação de gravação que causa pontos de acesso ou contenção de bloqueio pode afetar negativamente as operações de leitura. Quando há falha nas operações de gravação ou na limitação, a leitura pode ficar esperando por dados consistentes do registro de alterações. Se isso acontecer no app, você vai conferir operações de gravação lentas e tempos de resposta lentos correlacionados para consultas. Para evitar isso, evite pontos de acesso.

Manter documentos e gravações pequenos

Normalmente, ao criar apps com listeners de snapshots, você quer que os usuários descubram mudanças de dados rapidamente. Para isso, pense nos detalhes. O sistema pode enviar documentos pequenos com dezenas de campos pelo sistema muito rapidamente. Documentos maiores com centenas de campos e dados grandes levam mais tempo para serem processados.

Da mesma forma, dê preferência a operações curtas, de confirmação rápidas e de gravação para manter a latência baixa. Lotes grandes podem proporcionar uma capacidade maior do ponto de vista do gravador. No entanto, eles aumentam o tempo de notificação para listeners de snapshots. Isso geralmente não é intuitivo se comparado ao uso de outros sistemas de banco de dados em que é possível usar lotes para melhorar o desempenho.

Usar listeners eficientes

À medida que as taxas de gravação do banco de dados aumentam, o Firestore divide o processamento de dados em vários servidores. O algoritmo de fragmentação do Firestore tenta colocar os dados da mesma coleção ou grupo de coleções no mesmo servidor de registro de alterações. O sistema tenta maximizar a capacidade de gravação, mantendo o menor número possível de servidores envolvidos no processamento de uma consulta.

No entanto, alguns padrões ainda podem levar a um comportamento abaixo do ideal para listeners de snapshots. Por exemplo, se o app armazenar a maioria dos dados em uma grande coleção, o listener precisará se conectar a muitos servidores para receber todos os dados necessários. Isso continua sendo válido mesmo se você aplicar um filtro de consulta. A conexão com muitos servidores aumenta o risco de respostas mais lentas.

Para evitar essas respostas mais lentas, crie seu esquema e seu app para que o sistema possa exibir listeners sem acessar muitos servidores diferentes. É recomendável dividir os dados em coleções menores com taxas de gravação menores.

Isso é semelhante a pensar nas consultas de desempenho em um banco de dados relacional que exige verificações completas de tabelas. Em um banco de dados relacional, uma consulta que requer uma verificação completa da tabela é equivalente a um listener de snapshots que acompanha uma coleção de alta desistência de usuários. Essa consulta pode ter um desempenho lento em comparação com uma consulta que o banco de dados pode exibir resultados usando um índice mais específico. Uma consulta com um índice mais específico é como um listener de snapshot que monitora um único documento ou uma coleção que muda com menos frequência. É preciso testar o carregamento do app para entender melhor o comportamento e a necessidade do caso de uso.

Continuar pesquisando consultas rapidamente

Outra parte importante das consultas responsivas em tempo real envolve garantir que a consulta de pesquisa para inicializar os dados seja rápida e eficiente. Na primeira vez que um listener de snapshot é conectado, ele precisa carregar todo o conjunto de resultados e enviá-lo ao dispositivo dos usuários. Consultas lentas tornam o app menos responsivo. Isso inclui, por exemplo, consultas que tentam ler muitos documentos ou consultas que não usam os índices apropriados.

Um listener também pode passar de um estado de detecção para um estado de pesquisa em algumas circunstâncias. Isso acontece automaticamente e é transparente para os SDKs e o app. As seguintes condições podem acionar um estado de pesquisa:

  • O sistema reequilibra um registro de alterações devido a alterações na carga.
  • Os pontos de acesso geram gravações com falha ou atrasadas no banco de dados.
  • As reinicializações temporárias do servidor afetam temporariamente os listeners.

Se as consultas de pesquisa forem rápidas o suficiente, um estado de pesquisa ficará transparente para os usuários do seu app.

Favorecer listeners de longa duração

Muitas vezes, abrir e manter os listeners ativos pelo maior tempo possível é a maneira mais econômica de criar um app que usa o Firestore. Ao usar o Firestore, você vai pagar pelos documentos retornados ao app, e não pela manutenção de uma conexão aberta. Um listener de snapshot de longa duração lê apenas os dados necessários para exibir a consulta ao longo do ciclo de vida. Isso inclui uma operação de pesquisa inicial seguida por notificações quando os dados são alterados. Por outro lado, as consultas únicas geram uma nova leitura de dados que não foram alterados desde a última execução do app.

Nos casos em que o app precisa consumir uma alta taxa de dados, os listeners de snapshots não são adequados. Por exemplo, se o caso de uso enviar muitos documentos por segundo por uma conexão durante um período prolongado, é recomendável escolher consultas únicas executadas em uma frequência mais baixa.

Próximas etapas