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.