Cloud Intelligence™Cloud Intelligence™

Cloud Intelligence™

Scalabilité Firestore : maîtriser la règle 500/50/5

By Matthias BaetensOct 28, 20246 min read

Cette page est également disponible en English, Deutsch, Español, Italiano, 日本語 et Português.

Dans l'univers des bases de données NoSQL cloud, Firestore s'impose comme une solution flexible et scalable pour le développement mobile, web et serveur. Pourtant, malgré ses capacités impressionnantes, une idée reçue persiste : Firestore encaisserait n'importe quelle charge sans broncher. En théorie, c'est vrai ; en pratique, la réalité est plus nuancée. Imaginez que vous lancez votre nouvelle fonctionnalité phare, sachant que vous avez déjà connu d'importants pics de trafic par le passé. Ou que vous connaissez si bien les habitudes de vos utilisateurs que vous anticipez une charge nettement supérieure à un moment précis de la journée. Dans cet article, nous explorons un problème courant de scalabilité avec Firestore et présentons une méthode concrète pour le tester et l'éviter.

La règle 500/50/5 : Firestore en douceur

Firestore est conçu pour la montée en charge mais, comme tout système élastique et distribué, il lui faut du temps pour s'adapter à des charges croissantes. C'est là qu'intervient la règle 500/50/5 :

Démarrez avec un maximum de 500 opérations par seconde sur une nouvelle collection, puis augmentez le trafic de 50 % toutes les 5 minutes.

Cette recommandation permet aux mécanismes internes de scalabilité de Firestore de suivre votre croissance et d'éviter les écueils classiques : latence élevée ou erreurs DEADLINE_EXCEEDED.

k6 : votre allié pour les tests de charge

Pour illustrer l'importance de la règle 500/50/5, nous avons écrit un script avec k6, un outil open source de test de charge. k6 est un excellent choix, et ce pour plusieurs raisons :

  • Il est simple à utiliser, avec un langage de scripting basé sur JavaScript.
  • Il fournit des métriques de performance en temps réel et des analyses détaillées.
  • Il est très scalable : il peut générer entre 100 000 et 300 000 requêtes par seconde depuis une seule instance.

Le script

Vous trouverez le script ici. Voici un aperçu de son fonctionnement :

Charge initiale et cible :

  • Démarre à 500 requêtes par seconde (RPS)
  • Vise 1500 RPS (libre à vous d'ajuster ces valeurs)

Stratégie de montée en charge :

  • Maintient chaque palier pendant 5 minutes (300 secondes)
  • Augmente la charge de 50 % sur des périodes d'1 minute
  • Poursuit ce schéma jusqu'à atteindre ou dépasser le RPS cible

Génération dynamique des étapes :

  • Calcule automatiquement le nombre d'étapes nécessaires
  • Crée une série d'étapes alternées stable et ramp-up
  • Journalise le RPS cible et la durée de chaque étape pour plus de clarté

Sélection des documents :

  • Lit les identifiants de documents depuis un fichier (orders.txt)
  • Sélectionne aléatoirement un identifiant pour chaque requête
  • À vous de récupérer ces identifiants pour votre propre cas d'usage : ici, j'ai généré un jeu de données fictif

Exécution des requêtes :

  • Effectue des requêtes GET vers l'API REST de Firestore
  • Inclut une authentification via un bearer token
  • J'ai aussi ajouté un script pour récupérer un token

Suivi des performances :

  • Comptabilise les lectures réussies et les erreurs
  • Journalise tous les codes de statut différents de 200, avec leurs détails

Vous pouvez exécuter le script avec k6 run warm-up.js après avoir installé k6 (avec brew par exemple sur Mac). Pour obtenir un token, utilisez generate-firebase-token.py. Dans les deux scripts, quelques variables doivent être mises à jour : utilisez la fonction Rechercher de votre éditeur pour repérer INSERT.

L'expérience : succès contre échec

Nous avons mené deux expériences pour illustrer concrètement l'impact du respect (ou non) de la règle 500/50/5 :

Expérience 1 : la recette de l'échec

Dans ce scénario, nous démarrons à 2000 requêtes par seconde (RPS) et montons à 2500 RPS sur 5 minutes, en ignorant totalement la règle 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));
```
Exécuté entre 1/1/10 entre 0110 et 0115 CEST
Résultats :
```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
```

Voici le rendu dans Key Visualiser :

Le verdict ? Un taux de succès inférieur à 5 %. Aïe.

Expérience 2 : la recette du succès

Cette fois, nous appliquons la règle 500/50/5 : démarrage à 500 RPS et montée progressive jusqu'à 1500 RPS sur environ 20 minutes.

```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));
```

Exécuté entre 1/1/10 entre 0140 et 0158 CEST

Résultats :
```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
```

Voici le rendu dans Key Visualiser :

Début de la montée en charge

Fin de la montée en charge

Le verdict ? Un taux de succès impressionnant de 99,97 %.

Lancez vos propres tests

Le script peut être exécuté localement avec k6, ce qui présente plusieurs avantages : mise en place rapide, aucun coût, etc. Vous serez toutefois limité par les ressources de votre machine, vous ne disposerez que d'une seule instance et les résultats pourront pâtir des contraintes de votre réseau. Pour des mesures plus fiables, mieux vaut exécuter le script sur une VM (Google Cloud).

La règle 500/50/5 n'est pas une simple suggestion : c'est une recommandation essentielle pour que votre implémentation Firestore monte en charge de façon fluide et efficace. En la respectant et en utilisant des outils comme k6 pour valider vos stratégies de scalabilité, vous éviterez les écueils de performance et garderez une application réactive à mesure qu'elle grandit.

Rappelez-vous : en matière de scalabilité de bases de données, c'est la régularité qui paie. Bonne montée en charge !

— -

Vous voulez approfondir la scalabilité Firestore ou avez besoin d'aide pour optimiser votre infrastructure cloud ? Rendez-vous sur doit.com pour découvrir comment nous pouvons vous accompagner pour exploiter pleinement le potentiel de votre cloud.