Unter den NoSQL-Cloud-Datenbanken sticht Firestore als flexible und skalierbare Lösung für Mobile-, Web- und Server-Entwicklung hervor. Trotz dieser beeindruckenden Fähigkeiten hält sich ein hartnäckiger Irrtum: dass Firestore jede beliebige Last mühelos verkraftet. Theoretisch stimmt das – in der Praxis sieht es etwas differenzierter aus. Stellen Sie sich vor, Sie veröffentlichen Ihr neuestes Feature und hatten in der Vergangenheit bereits massive Traffic-Spitzen. Oder Sie kennen Ihre Nutzungsmuster genau und erwarten zu einer bestimmten Tageszeit einen drastischen Lastanstieg. In diesem Beitrag beleuchten wir ein typisches Skalierungsproblem von Firestore und zeigen Ihnen einen praxistauglichen Weg, es zu testen und zu vermeiden.
Die 500/50/5-Regel: ein sanfter Einstieg in Firestore
Firestore ist auf Skalierung ausgelegt, doch wie jedes elastische und verteilte System braucht es Zeit, um sich an steigende Lasten anzupassen. Genau hier kommt die 500/50/5-Regel ins Spiel:
Beginnen Sie mit maximal 500 Operationen pro Sekunde auf einer neuen Collection und steigern Sie den Traffic anschließend alle 5 Minuten um 50 %.
Diese Faustregel sorgt dafür, dass die internen Skalierungsmechanismen von Firestore mit Ihrem Wachstum Schritt halten und sich typische Probleme wie hohe Latenzen oder DEADLINE_EXCEEDED-Fehler vermeiden lassen.
Auftritt k6: Ihr Verbündeter beim Lasttest
Um die Bedeutung der 500/50/5-Regel zu veranschaulichen, haben wir ein Skript mit k6 erstellt, einem Open-Source-Tool für Lasttests. k6 ist aus mehreren Gründen eine ausgezeichnete Wahl:
- Einfache Bedienung dank einer JavaScript-basierten Skriptsprache.
- Performancemetriken in Echtzeit und detaillierte Auswertungen.
- Hochgradig skalierbar – eine einzelne Instanz schafft 100.000 bis 300.000 Requests pro Sekunde.
Das Skript
Das Skript finden Sie hier. Ein Überblick, was es tut:
Anfangs- und Ziellast:
- Startet bei 500 Requests pro Sekunde (RPS)
- Strebt 1500 RPS an (selbstverständlich anpassbar)
Ramp-up-Strategie:
- Hält jede Laststufe 5 Minuten (300 Sekunden) konstant
- Steigert die Last innerhalb von 1-Minuten-Intervallen um 50 %
- Setzt dieses Muster fort, bis die Ziel-RPS erreicht oder überschritten sind
Dynamische Stage-Generierung:
- Berechnet automatisch die Anzahl der benötigten Stages
- Erzeugt eine Abfolge abwechselnder \"Stable\"- und \"Ramp-up\"-Stages
- Protokolliert Ziel-RPS und Dauer jeder Stage zur besseren Nachvollziehbarkeit
Dokumentauswahl:
- Liest Dokument-IDs aus einer Datei (\"orders.txt\")
- Wählt für jeden Request zufällig eine Dokument-ID aus
- Sie müssen die Dokument-IDs für Ihren eigenen Anwendungsfall selbst beschaffen – ich habe für die Demo einen fiktiven Datensatz generiert
Request-Ausführung:
- Führt GET-Requests gegen die Firestore-REST-API aus
- Inklusive Authentifizierung per Bearer-Token
- Ein Skript zum Erzeugen eines Tokens ist ebenfalls enthalten
Performance-Monitoring:
- Erfasst erfolgreiche Reads und Fehler
- Loggt alle Status-Codes ungleich 200 inklusive Details
Nach der Installation von k6 (z. B. via brew auf dem Mac) starten Sie das Skript mit k6 run warm-up.js. Ein Token holen Sie sich mit generate-firebase-token.py. In beiden Skripten müssen einige Variablen angepasst werden – nutzen Sie dazu die Suchfunktion Ihres Editors und suchen Sie nach INSERT.
Das Experiment: Erfolg vs. Misserfolg
Wir haben zwei Experimente durchgeführt, die zeigen, was passiert, wenn man die 500/50/5-Regel befolgt – oder eben nicht:
Experiment 1: Auf Misserfolg programmiert
In diesem Szenario starteten wir mit 2000 Requests pro Sekunde (RPS) und erhöhten innerhalb von 5 Minuten auf 2500 RPS – die 500/50/5-Regel wurde dabei vollständig ignoriert.
```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));
```
Ran between 1/1/10 between 0110 and 0115 CEST
Results:
```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
```
So sieht das im Key Visualiser aus:

Das Ergebnis? Eine Erfolgsquote unter 5 %. Autsch.
Experiment 2: Auf Erfolgskurs
Für diesen Test haben wir uns an die 500/50/5-Regel gehalten, mit 500 RPS gestartet und über rund 20 Minuten schrittweise auf 1500 RPS hochgefahren.
```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));
```
Ran between 1/1/10 between 0140 and 0158 CEST
Results:
```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
```
So sieht das im Key Visualiser aus:

Beginn der Skalierung

Ende der Skalierung
Das Ergebnis? Beeindruckende 99,97 % Erfolgsquote.
Eigene Tests durchführen
Das Skript lässt sich lokal mit k6 ausführen – das hat einige Vorteile: schnelles Setup, keine Kosten und so weiter. Allerdings sind Sie dabei durch die Ressourcen Ihres lokalen Rechners limitiert, haben nur eine Instanz und die Ergebnisse können durch Ihre Netzwerkanbindung beeinflusst werden. Für aussagekräftigere Resultate ist es oft sinnvoll, das Skript auf einer (Google-Cloud-)VM laufen zu lassen.
Die 500/50/5-Regel ist nicht bloß eine Empfehlung – sie ist eine entscheidende Leitlinie, damit Ihre Firestore-Implementierung reibungslos und effizient skaliert. Wer sich an diese Regel hält und Tools wie k6 für Skalierungstests einsetzt, umschifft Performance-Fallen und hält die Anwendung auch bei steigender Last stabil am Laufen.
Denken Sie daran: Beim Skalieren von Datenbanken gewinnt, wer mit Bedacht vorgeht. Viel Erfolg beim Skalieren!

— -
Möchten Sie tiefer in die Firestore-Skalierung einsteigen oder benötigen Sie Unterstützung bei der Optimierung Ihrer Cloud-Infrastruktur? Auf doit.com erfahren Sie, wie wir Ihnen helfen, das volle Potenzial Ihrer Cloud auszuschöpfen.