No mundo dos bancos NoSQL na nuvem, o Firestore se destaca como uma solução flexível e escalável para desenvolvimento mobile, web e server-side. Mesmo com todos os seus recursos, existe um mito comum de que o Firestore aguenta qualquer carga sem suar a camisa. Em teoria, sim — mas, na prática, a história tem nuances. Imagine que você está lançando um novo recurso e já enfrentou grandes picos de tráfego antes. Ou que conhece bem o comportamento dos seus usuários e sabe que, em determinado horário do dia, a carga aumenta drasticamente. Neste post, vamos explorar um problema comum de escalabilidade no Firestore e mostrar uma forma prática de testá-lo e evitá-lo.
A regra 500/50/5: uma introdução tranquila ao Firestore
O Firestore foi feito para escalar, mas, como qualquer sistema elástico e distribuído, precisa de tempo para se ajustar a cargas crescentes. É aí que entra a regra 500/50/5:
Comece com no máximo 500 operações por segundo em uma nova coleção e aumente o tráfego em 50% a cada 5 minutos.
Essa orientação garante que os mecanismos internos de escalonamento do Firestore consigam acompanhar o seu crescimento, evitando problemas comuns como alta latência ou erros DEADLINE_EXCEEDED.
Conheça o k6: seu aliado nos testes de carga
Para mostrar a importância da regra 500/50/5, criamos um script com o k6, uma ferramenta open-source de testes de carga. O k6 é uma ótima escolha por vários motivos:
- É simples de usar, com linguagem de scripting baseada em JavaScript.
- Oferece métricas de performance em tempo real e insights detalhados.
- É altamente escalável, capaz de gerar de 100.000 a 300.000 requisições por segundo a partir de uma única instância.
O script
Você encontra o script aqui. Veja uma visão geral do que ele faz:
Carga inicial e alvo:
- Começa em 500 requisições por segundo (RPS)
- Tem como meta atingir 1500 RPS (você pode ajustar esse valor, claro)
Estratégia de ramp-up:
- Mantém cada nível de carga por 5 minutos (300 segundos)
- Aumenta a carga em 50% em períodos de 1 minuto
- Repete o padrão até atingir ou ultrapassar o RPS alvo
Geração dinâmica de stages:
- Calcula automaticamente quantas stages são necessárias
- Cria uma série alternada de stages "estáveis" e de "ramp-up"
- Registra o RPS alvo e a duração de cada stage para deixar tudo claro
Seleção de documentos:
- Lê os IDs de documento a partir de um arquivo ("orders.txt")
- Seleciona aleatoriamente um ID de documento para cada requisição
- Você vai precisar obter esses IDs para o seu próprio caso de uso, já que aqui usei um conjunto de dados fictício
Execução das requisições:
- Faz requisições GET para a REST API do Firestore
- Inclui autenticação via bearer token
- Também incluí um script que gera o token para você
Monitoramento de performance:
- Acompanha leituras bem-sucedidas e erros
- Registra qualquer status code diferente de 200, com detalhes
Você pode rodar o script com k6 run warm-up.js depois de instalar o k6 (via brew, por exemplo, se estiver no Mac). Para gerar um token, use generate-firebase-token.py. Em ambos os scripts há algumas variáveis para atualizar — use o "Localizar" do seu editor e procure por INSERT.
O experimento: sucesso vs. falha
Rodamos dois experimentos para mostrar o impacto de seguir (ou ignorar) a regra 500/50/5, para você ver a diferença na prática:
Experimento 1: receita para o fracasso
Neste cenário, começamos com 2000 requisições por segundo (RPS) e subimos para 2500 RPS em 5 minutos, ignorando completamente a regra 500/50/5.
```js
// Warmup parameters
const initialRPS = 2000;
const targetRPS = 2500;
const stablePeriodSeconds = 300; // 5 minutes
const rampPeriodSeconds = 0;
const stageCount = Math.ceil(Math.log(targetRPS / initialRPS) / Math.log(1.5));
```
Executado em 1/1/10, entre 01h10 e 01h15 CEST
Resultados:
```bash
INFO[0335] Warmup Stages: source=console
INFO[0335] Stage 1: Target RPS: 2500, Duration: 300s source=console
✗ status is 200
↳ 4% — ✓ 6408 / ✗ 123474
checks.........................: 4.93% ✓ 6408 ✗ 123474
data_received..................: 23 MB 70 kB/s
data_sent......................: 4.6 MB 14 kB/s
dropped_iterations.............: 1 0.003028/s
errors.........................: 123474 373.866077/s
http_req_blocked...............: avg=473.49ms min=0s med=0s max=59.9s p(90)=0s p(95)=0s
http_req_connecting............: avg=287.6ms min=0s med=0s max=38.39s p(90)=0s p(95)=0s
http_req_duration..............: avg=806.08ms min=0s med=0s max=1m3s p(90)=0s p(95)=2.01s
{ expected_response:true }...: avg=12.62s min=311.44ms med=9.48s max=1m0s p(90)=30.92s p(95)=36.56s
http_req_failed................: 95.06% ✓ 123474 ✗ 6409
http_req_receiving.............: avg=82.85ms min=0s med=0s max=59.4s p(90)=0s p(95)=30µs
http_req_sending...............: avg=651.35µs min=0s med=0s max=8.82s p(90)=0s p(95)=92µs
http_req_tls_handshaking.......: avg=261.72ms min=0s med=0s max=57.6s p(90)=0s p(95)=0s
http_req_waiting...............: avg=722.58ms min=0s med=0s max=1m2s p(90)=0s p(95)=1.89s
http_reqs......................: 129883 393.271845/s
iteration_duration.............: avg=32.65s min=2.58µs med=33.98s max=1m12s p(90)=48.47s p(95)=51.64s
iterations.....................: 129883 393.271845/s
successful_reads...............: 4.93% ✓ 6408 ✗ 123474
vus............................: 47 min=0 max=25000
vus_max........................: 25000 min=4179 max=25000
running (5m30.3s), 00000/25000 VUs, 129882 complete and 21 interrupted iterations
firestore_warmup ✓ [======================================] 00021/25000 VUs 5m0s 2105.47 iters/s
```
Veja como isso aparece no Key Visualiser:

O resultado? Taxa de sucesso abaixo de 5%. Doeu.
Experimento 2: caminho para o sucesso
Neste teste, seguimos a regra 500/50/5, começando em 500 RPS e subindo gradualmente até 1500 RPS ao longo de cerca de 20 minutos.
```js
// Warmup parameters
const initialRPS = 500;
const targetRPS = 1500;
const stablePeriodSeconds = 300; // 5 minutes
const rampPeriodSeconds = 60; // 1 minute
const stageCount = Math.ceil(Math.log(targetRPS / initialRPS) / Math.log(1.5));
```
Executado em 1/1/10, entre 01h40 e 01h58 CEST
Resultados:
```bash
INFO[1111] Warmup Stages: source=console
INFO[1111] Stage 1: Target RPS: 500, Duration: 300s source=console
INFO[1111] Stage 2: Target RPS: 750, Duration: 60s source=console
INFO[1111] Stage 3: Target RPS: 750, Duration: 300s source=console
INFO[1111] Stage 4: Target RPS: 1125, Duration: 60s source=console
INFO[1111] Stage 5: Target RPS: 1125, Duration: 300s source=console
INFO[1111] Stage 6: Target RPS: 1500, Duration: 60s source=console
✗ status is 200
↳ 99% — ✓ 863739 / ✗ 231
checks.........................: 99.97% ✓ 863739 ✗ 231
data_received..................: 1.5 GB 1.4 MB/s
data_sent......................: 173 MB 156 kB/s
dropped_iterations.............: 20999 18.915827/s
errors.........................: 231 0.208084/s
http_req_blocked...............: avg=50.75ms min=0s med=0s max=41.09s p(90)=1µs p(95)=1µs
http_req_connecting............: avg=35.83ms min=0s med=0s max=29.93s p(90)=0s p(95)=0s
http_req_duration..............: avg=554.84ms min=0s med=334.66ms max=1m0s p(90)=728.85ms p(95)=1.29s
{ expected_response:true }...: avg=554.07ms min=304.44ms med=334.66ms max=59.46s p(90)=728.84ms p(95)=1.29s
http_req_failed................: 0.02% ✓ 231 ✗ 863739
http_req_receiving.............: avg=68.05ms min=0s med=6.92ms max=59.42s p(90)=21.82ms p(95)=160.12ms
http_req_sending...............: avg=288.4µs min=0s med=32µs max=12.11s p(90)=89µs p(95)=150µs
http_req_tls_handshaking.......: avg=16.93ms min=0s med=0s max=46.68s p(90)=0s p(95)=0s
http_req_waiting...............: avg=486.5ms min=0s med=327.66ms max=1m0s p(90)=615.6ms p(95)=943.67ms
http_reqs......................: 863970 778.261194/s
iteration_duration.............: avg=609.81ms min=2.2µs med=334.94ms max=1m0s p(90)=748.42ms p(95)=1.35s
iterations.....................: 863970 778.261194/s
successful_reads...............: 99.97% ✓ 863739 ✗ 231
vus............................: 14 min=14 max=5720
vus_max........................: 5849 min=1000 max=5849
running (18m30.1s), 00000/05849 VUs, 863970 complete and 14 interrupted iterations
firestore_warmup ✓ [======================================] 00014/05849 VUs 18m0s 1499.93 iters/s
```
Veja como isso aparece no Key Visualiser:

Início do escalonamento

Fim do escalonamento
O resultado? Uma taxa de sucesso impressionante de 99,97%.
Rodando seus próprios testes
Dá para rodar o script com o k6 localmente, o que traz algumas vantagens, como configuração simples e custo zero. Por outro lado, você pode esbarrar nos limites de recursos da sua máquina, ficar restrito a uma única instância e ter os resultados afetados pela sua rede. Para resultados mais precisos, pode fazer sentido rodar o script em uma VM (no Google Cloud).
A regra 500/50/5 não é só uma sugestão — é uma orientação fundamental para que sua implementação do Firestore escale de forma suave e eficiente. Seguindo essa regra e usando ferramentas como o k6 para testar suas estratégias de escalonamento, você evita armadilhas de performance e mantém sua aplicação rodando bem à medida que cresce.
Lembre-se: quando o assunto é escalar bancos de dados, devagar e sempre é o que vence a corrida. Bom escalonamento!

— -
Quer se aprofundar no escalonamento do Firestore ou precisa de ajuda para otimizar sua infraestrutura na nuvem? Acesse doit.com e descubra como podemos ajudar você a extrair o máximo do potencial da sua nuvem.