Cloud Intelligence™Cloud Intelligence™

Cloud Intelligence™

Escalado en Firestore: la regla 500/50/5 y cómo probarla

By Matthias BaetensOct 28, 20246 min read

Esta página también está disponible en English, Deutsch, Français, Italiano, 日本語 y Português.

En el mundo de las bases de datos NoSQL en la nube, Firestore destaca como una solución flexible y escalable para desarrollo móvil, web y de servidor. Sin embargo, pese a sus impresionantes capacidades, hay un mito muy extendido: que Firestore puede con cualquier carga sin despeinarse. En teoría es cierto, pero la realidad tiene matices. Imagina que vas a lanzar tu nueva gran funcionalidad y en el pasado has tenido picos enormes de tráfico. O que conoces los patrones de uso de tus usuarios y sabes que en cierto momento del día se dispara la carga. En este post analizamos un problema común de escalado en Firestore y te mostramos una forma práctica de probarlo y evitarlo.

La regla 500/50/5: una introducción amigable a Firestore

Firestore está diseñado para escalar, pero como cualquier sistema elástico y distribuido, necesita tiempo para ajustarse al aumento de carga. Aquí entra en juego la regla 500/50/5:

Comienza con un máximo de 500 operaciones por segundo en una colección nueva y luego aumenta el tráfico un 50 % cada 5 minutos.

Esta guía garantiza que los mecanismos internos de escalado de Firestore puedan seguirle el ritmo a tu crecimiento, evitando problemas comunes como alta latencia o errores DEADLINE_EXCEEDED.

Llega k6: tu aliado en pruebas de carga

Para ilustrar la importancia de la regla 500/50/5, creamos un script con k6, una herramienta open-source de pruebas de carga. k6 es una excelente opción por varias razones:

  • Es fácil de usar gracias a su lenguaje de scripting basado en JavaScript.
  • Entrega métricas de rendimiento en tiempo real e información detallada.
  • Es altamente escalable: puede generar entre 100.000 y 300.000 solicitudes por segundo desde una sola instancia.

El script

Puedes encontrar el script aquí. Un resumen de lo que hace:

Carga inicial y objetivo:

  • Arranca en 500 solicitudes por segundo (RPS)
  • Apunta a alcanzar 1500 RPS (puedes ajustarlo, claro)

Estrategia de ramp-up:

  • Mantiene cada nivel de carga durante 5 minutos (300 segundos)
  • Incrementa la carga un 50 % en periodos de 1 minuto
  • Mantiene este patrón hasta alcanzar o superar el RPS objetivo

Generación dinámica de etapas:

  • Calcula automáticamente la cantidad de etapas necesarias
  • Crea una serie alternada de etapas 'estables' y de 'ramp-up'
  • Registra el RPS objetivo y la duración de cada etapa para mayor claridad

Selección de documentos:

  • Lee los IDs de documento desde un archivo ('orders.txt')
  • Selecciona aleatoriamente un ID de documento para cada solicitud
  • Tendrás que conseguir esos IDs para tu propio caso de uso, ya que generé un dataset ficticio

Ejecución de solicitudes:

  • Ejecuta solicitudes GET a la API REST de Firestore
  • Incluye autenticación mediante un bearer token
  • También incluí un script que obtiene un token por ti

Monitoreo de rendimiento:

  • Hace seguimiento de las lecturas exitosas y los errores
  • Registra cualquier código de estado distinto de 200 con sus detalles

Puedes ejecutar el script con k6 run warm-up.js después de instalar k6 (con brew, por ejemplo, si estás en Mac). Para obtener un token usa generate-firebase-token.py. En ambos scripts hay algunas variables que actualizar; usa la función "Buscar" de tu editor con el término INSERT.

El experimento: éxito vs. fracaso

Hicimos dos experimentos para mostrar el impacto de seguir (o ignorar) la regla 500/50/5, y que veas la diferencia en acción:

Experimento 1: receta para el fracaso

En este escenario arrancamos con 2000 solicitudes por segundo (RPS) y subimos hasta 2500 RPS en 5 minutos, ignorando por completo la regla 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));
```
Ejecutado el 1/1/10 entre las 0110 y las 0115 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
```

Así se ve en Key Visualiser:

¿El resultado? Una tasa de éxito por debajo del 5 %. Ay.

Experimento 2: receta para el éxito

En esta prueba respetamos la regla 500/50/5: arrancamos en 500 RPS y subimos gradualmente hasta 1500 RPS a lo largo de unos 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));
```

Ejecutado el 1/1/10 entre las 0140 y las 0158 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
```

Así se ve en Key Visualiser:

Inicio del escalado

Final del escalado

¿El resultado? Un impresionante 99,97 % de tasa de éxito.

Ejecuta tus propias pruebas

El script se puede ejecutar con k6 de forma local, lo que tiene varias ventajas: configuración sencilla, sin costos, etc. Sin embargo, los recursos de tu máquina local pueden quedarse cortos, solo cuentas con una instancia y los resultados pueden verse afectados por las restricciones de tu red. Para resultados más precisos, conviene ejecutar el script en una VM (de Google Cloud).

La regla 500/50/5 no es solo una sugerencia: es una guía clave para que tu implementación de Firestore escale de forma fluida y eficiente. Al seguirla y apoyarte en herramientas como k6 para probar tus estrategias de escalado, evitas problemas de rendimiento y mantienes tu aplicación funcionando sin sobresaltos a medida que crece.

Recuerda: cuando se trata de escalar bases de datos, despacio se llega lejos. ¡Feliz escalado!

— -

¿Quieres profundizar en el escalado de Firestore o necesitas ayuda para optimizar tu infraestructura en la nube? Visita doit.com y descubre cómo podemos ayudarte a aprovechar al máximo tu nube.