
Introduction
Firebase Firestore is designed to scale automatically, but “auto-scaling” does not mean “infinite performance.” If your application depends on Firestore for user profiles, product catalogs, chat messages, order processing, or analytics events, you need to understand how it behaves under real traffic. A proper Firebase Firestore load testing strategy helps you measure document read and write throughput, latency under concurrency, and how your data model performs during traffic spikes.
With LoadForge, you can run cloud-based load testing against Firestore-backed workloads using Locust scripts, then analyze response times, failure rates, and throughput in real time. This is especially useful when you want to validate performance before a launch, stress test a new feature, or compare the impact of indexing and query changes. Because LoadForge supports distributed testing, global test locations, and CI/CD integration, it’s a practical way to run repeatable Firestore performance testing at scale.
In this guide, you’ll learn how to load test Firebase Firestore using realistic Locust scripts that simulate:
- Document reads by ID
- Collection queries with filters and ordering
- Authenticated writes using Firebase ID tokens
- Mixed read/write workloads
- Batch-style operations and contention scenarios
The examples use actual Firestore REST API patterns so you can adapt them directly to your environment.
Prerequisites
Before you begin load testing Firebase Firestore, make sure you have the following:
- A Firebase project with Firestore enabled
- A test dataset in Firestore, ideally separate from production
- A Firebase Authentication setup if your app requires authenticated access
- A service account or test user strategy for generating valid Firebase ID tokens
- Your Firebase project ID
- Knowledge of your target collections, fields, and common query patterns
- A LoadForge account for running distributed load tests in the cloud
You should also decide whether you are testing:
- Firestore directly via its REST API
- Your own backend API that reads and writes Firestore
- A hybrid flow involving Firebase Authentication and Firestore access
For direct Firestore load testing, common endpoints include:
- Document read:
GET https://firestore.googleapis.com/v1/projects/PROJECT_ID/databases/(default)/documents/users/user_123 - Document write:
PATCH https://firestore.googleapis.com/v1/projects/PROJECT_ID/databases/(default)/documents/users/user_123 - Structured query:
POST https://firestore.googleapis.com/v1/projects/PROJECT_ID/databases/(default)/documents:runQuery
If your Firestore rules require authentication, your load test must include a valid Authorization: Bearer <ID_TOKEN> header. For realistic performance testing, use the same auth model your application uses in production.
Understanding Firebase Firestore Under Load
Firestore is a document database optimized for flexible schema design and horizontal scaling, but performance depends heavily on access patterns. Load testing Firestore is not just about generating traffic; it’s about validating how your data model, indexes, and query design behave under concurrency.
Key Firestore behaviors to understand
Document reads
Single-document reads are typically fast and predictable, especially when fetching by document path. These are good baseline operations for measuring low-latency performance.
Collection and filtered queries
Queries can become slower when they rely on composite indexes, scan large collections, or return too many documents. Even if Firestore supports the query, response size and index usage affect latency.
Writes and contention
Firestore handles high write throughput well, but hot documents or hot key ranges can become bottlenecks. If many virtual users update the same document, you may see increased latency, retries, or contention-related issues.
Index overhead
Every write may update one or more indexes. A document with many indexed fields can cost more to write than expected. Load testing helps reveal whether write-heavy workloads are being slowed by indexing strategy.
Security rules impact
Firestore security rules add request evaluation overhead. This usually isn’t a major problem, but under high load it can contribute to latency. Testing with realistic authentication and rules is important.
Common Firestore bottlenecks
When running performance testing on Firebase Firestore, these are the most common issues teams uncover:
- Repeated queries against large collections without pagination
- Missing or inefficient composite indexes
- Too many writes to the same document
- Large document payloads increasing network and serialization costs
- Overly chatty application patterns with many small requests
- Authentication token refresh issues during sustained load
- Unrealistic test data that doesn’t reflect production cardinality
The goal of load testing is to reproduce these conditions safely and measure where the system begins to degrade.
Writing Your First Load Test
Let’s start with a simple Firestore load test that reads user profile documents by ID. This is a good baseline because it isolates document-read latency without introducing query complexity.
Basic Firestore document read test
from locust import HttpUser, task, between
import random
PROJECT_ID = "my-firebase-project"
DATABASE_PATH = f"/v1/projects/{PROJECT_ID}/databases/(default)/documents"
USER_IDS = [f"user_{i}" for i in range(1, 1001)]
class FirestoreReadUser(HttpUser):
wait_time = between(1, 3)
host = "https://firestore.googleapis.com"
@task
def read_user_document(self):
user_id = random.choice(USER_IDS)
path = f"{DATABASE_PATH}/users/{user_id}"
with self.client.get(
path,
name="Firestore GET /users/:id",
catch_response=True
) as response:
if response.status_code == 200:
response.success()
elif response.status_code == 404:
response.failure(f"User document not found: {user_id}")
else:
response.failure(f"Unexpected status: {response.status_code} - {response.text}")What this test does
This script simulates users reading profile documents from a users collection. It randomly selects IDs from a pool of 1,000 documents, which is more realistic than repeatedly hitting the same record.
Why this is useful
This test helps you answer basic performance questions such as:
- What is the average latency for document reads?
- How many reads per second can Firestore sustain for this collection?
- Are there regional latency issues from different LoadForge test locations?
- Do errors appear as concurrency increases?
When you run this in LoadForge, you can scale up to thousands of virtual users and observe how read performance changes in real time.
Advanced Load Testing Scenarios
Once you’ve validated baseline reads, the next step is to simulate more realistic Firestore workloads. Most production systems involve authentication, filtered queries, and writes. The following examples reflect those patterns.
Scenario 1: Authenticated Firestore reads with Firebase ID tokens
If your Firestore security rules require authenticated access, your load test should include Firebase Authentication. In many environments, you will generate test user ID tokens ahead of time and store them as environment variables or test data inputs.
This example uses a pre-generated list of Firebase ID tokens and performs authenticated reads against an orders collection.
from locust import HttpUser, task, between
import random
import os
PROJECT_ID = "my-firebase-project"
DATABASE_PATH = f"/v1/projects/{PROJECT_ID}/databases/(default)/documents"
ORDER_IDS = [f"order_{i}" for i in range(1000, 2000)]
FIREBASE_TOKENS = [
os.getenv("FIREBASE_TOKEN_1"),
os.getenv("FIREBASE_TOKEN_2"),
os.getenv("FIREBASE_TOKEN_3"),
]
class AuthenticatedFirestoreReads(HttpUser):
wait_time = between(1, 2)
host = "https://firestore.googleapis.com"
def on_start(self):
self.token = random.choice([t for t in FIREBASE_TOKENS if t])
@task
def read_order_document(self):
order_id = random.choice(ORDER_IDS)
path = f"{DATABASE_PATH}/orders/{order_id}"
headers = {
"Authorization": f"Bearer {self.token}"
}
with self.client.get(
path,
headers=headers,
name="Firestore Auth GET /orders/:id",
catch_response=True
) as response:
if response.status_code == 200:
response.success()
elif response.status_code in (401, 403):
response.failure(f"Auth failure: {response.status_code} - {response.text}")
else:
response.failure(f"Unexpected status: {response.status_code} - {response.text}")Why this scenario matters
Authenticated traffic is often slower than public reads because it includes token validation and security rule evaluation. If your real application uses Firestore rules to restrict access, this is the only meaningful way to benchmark performance.
For more realistic stress testing, use a larger pool of tokens mapped to actual test users with distinct data access patterns.
Scenario 2: Structured queries for product search and pagination
Many Firestore applications rely on filtered and sorted queries instead of direct document reads. This example simulates an e-commerce workload where users browse active products by category and price range.
from locust import HttpUser, task, between
import random
import json
PROJECT_ID = "my-firebase-project"
RUN_QUERY_PATH = f"/v1/projects/{PROJECT_ID}/databases/(default)/documents:runQuery"
CATEGORIES = ["electronics", "books", "home", "fitness", "toys"]
class FirestoreProductQueries(HttpUser):
wait_time = between(2, 4)
host = "https://firestore.googleapis.com"
@task
def query_active_products(self):
category = random.choice(CATEGORIES)
max_price = random.choice([25, 50, 100, 250])
payload = {
"structuredQuery": {
"from": [
{
"collectionId": "products"
}
],
"where": {
"compositeFilter": {
"op": "AND",
"filters": [
{
"fieldFilter": {
"field": {"fieldPath": "category"},
"op": "EQUAL",
"value": {"stringValue": category}
}
},
{
"fieldFilter": {
"field": {"fieldPath": "active"},
"op": "EQUAL",
"value": {"booleanValue": True}
}
},
{
"fieldFilter": {
"field": {"fieldPath": "price"},
"op": "LESS_THAN_OR_EQUAL",
"value": {"integerValue": str(max_price)}
}
}
]
}
},
"orderBy": [
{
"field": {"fieldPath": "price"},
"direction": "ASCENDING"
}
],
"limit": 20
}
}
headers = {
"Content-Type": "application/json"
}
with self.client.post(
RUN_QUERY_PATH,
data=json.dumps(payload),
headers=headers,
name="Firestore POST runQuery products",
catch_response=True
) as response:
if response.status_code == 200:
try:
results = response.json()
if isinstance(results, list):
response.success()
else:
response.failure("Unexpected query response format")
except Exception as e:
response.failure(f"JSON parse error: {e}")
else:
response.failure(f"Query failed: {response.status_code} - {response.text}")What this test reveals
This query-focused load test is useful for identifying:
- Slow queries caused by poor indexing
- Performance differences between categories with different document counts
- Latency increases as virtual users scale up
- Whether result size and ordering are affecting response times
If Firestore returns index-related errors during testing, that’s a strong signal that your production query path is not fully prepared for load.
Scenario 3: Mixed read/write workload for carts and checkout activity
Real applications rarely do only reads or only writes. A more realistic Firestore performance testing scenario mixes document fetches, cart updates, and order creation. This example simulates a retail workload with authenticated users.
from locust import HttpUser, task, between
import random
import json
import os
import uuid
from datetime import datetime, timezone
PROJECT_ID = "my-firebase-project"
DATABASE_PATH = f"/v1/projects/{PROJECT_ID}/databases/(default)/documents"
USER_IDS = [f"user_{i}" for i in range(1, 501)]
PRODUCT_IDS = [f"prod_{i}" for i in range(1, 2001)]
TOKENS = [os.getenv("FIREBASE_TOKEN_1"), os.getenv("FIREBASE_TOKEN_2"), os.getenv("FIREBASE_TOKEN_3")]
class FirestoreMixedWorkload(HttpUser):
wait_time = between(1, 3)
host = "https://firestore.googleapis.com"
def on_start(self):
self.user_id = random.choice(USER_IDS)
self.token = random.choice([t for t in TOKENS if t])
def auth_headers(self):
return {
"Authorization": f"Bearer {self.token}",
"Content-Type": "application/json"
}
@task(5)
def read_cart(self):
path = f"{DATABASE_PATH}/carts/{self.user_id}"
with self.client.get(
path,
headers={"Authorization": f"Bearer {self.token}"},
name="Firestore GET /carts/:userId",
catch_response=True
) as response:
if response.status_code == 200:
response.success()
elif response.status_code == 404:
response.success() # Empty cart is valid in many apps
else:
response.failure(f"Cart read failed: {response.status_code} - {response.text}")
@task(3)
def update_cart(self):
product_id = random.choice(PRODUCT_IDS)
quantity = random.randint(1, 3)
path = f"{DATABASE_PATH}/carts/{self.user_id}?updateMask.fieldPaths=items&updateMask.fieldPaths=updatedAt"
payload = {
"fields": {
"items": {
"arrayValue": {
"values": [
{
"mapValue": {
"fields": {
"productId": {"stringValue": product_id},
"quantity": {"integerValue": str(quantity)}
}
}
}
]
}
},
"updatedAt": {
"timestampValue": datetime.now(timezone.utc).isoformat()
}
}
}
with self.client.patch(
path,
data=json.dumps(payload),
headers=self.auth_headers(),
name="Firestore PATCH /carts/:userId",
catch_response=True
) as response:
if response.status_code == 200:
response.success()
else:
response.failure(f"Cart update failed: {response.status_code} - {response.text}")
@task(1)
def create_order(self):
order_id = str(uuid.uuid4())
path = f"{DATABASE_PATH}/orders/{order_id}"
payload = {
"fields": {
"userId": {"stringValue": self.user_id},
"status": {"stringValue": "pending"},
"totalAmount": {"doubleValue": random.uniform(20.0, 250.0)},
"createdAt": {"timestampValue": datetime.now(timezone.utc).isoformat()},
"source": {"stringValue": "load-test"}
}
}
with self.client.patch(
path,
data=json.dumps(payload),
headers=self.auth_headers(),
name="Firestore PATCH /orders/:id",
catch_response=True
) as response:
if response.status_code == 200:
response.success()
else:
response.failure(f"Order creation failed: {response.status_code} - {response.text}")Why mixed workloads are critical
This is often the most useful kind of load test because it reflects actual application behavior. Reads dominate traffic, writes happen regularly, and a small percentage of users perform more expensive operations like checkout.
This kind of script is ideal for LoadForge because you can run it from multiple cloud regions, compare response times geographically, and watch error rates in real time as traffic ramps up.
Optional: generating Firebase ID tokens for test users
If you use email/password test accounts, you can generate a token with the Firebase Identity Toolkit API.
curl -X POST "https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=YOUR_WEB_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"email": "loadtest1@example.com",
"password": "StrongPassword123!",
"returnSecureToken": true
}'The response includes an idToken you can use in your Locust headers. In larger test environments, teams usually pre-generate and rotate many tokens rather than signing in during the test itself.
Analyzing Your Results
After running your Firebase Firestore load test in LoadForge, focus on a few key metrics.
Response time percentiles
Average latency is useful, but percentiles are more important. Watch:
- P50 for normal user experience
- P95 for degraded but still common experiences
- P99 for tail latency under stress
Firestore may look healthy at the average level while still producing unacceptable P95 or P99 latency.
Requests per second
Track throughput for each operation type:
- Single-document reads
- Structured queries
- Writes and updates
If throughput plateaus while latency rises sharply, you may be approaching a practical limit in your workload design.
Error rates
Pay close attention to:
- 401 or 403 authentication and rules issues
- 404 errors due to invalid test data
- 429 or quota-related behavior
- 5xx responses from dependent services or gateways
- Query/index errors returned by Firestore
A realistic performance testing run should distinguish between expected functional issues and genuine scaling problems.
Endpoint-level comparison
Name your Locust requests clearly, as shown in the examples. This lets you compare:
Firestore GET /users/:idFirestore POST runQuery productsFirestore PATCH /orders/:id
In LoadForge’s real-time reporting, these labels make it much easier to pinpoint which Firestore operation becomes the bottleneck.
Ramp behavior
Do not just run a flat test. Observe what happens during:
- Ramp-up
- Sustained steady state
- Spike traffic
- Ramp-down
Firestore often behaves differently during traffic spikes than during gradual growth. LoadForge’s distributed testing makes it easier to simulate both patterns.
Performance Optimization Tips
If your Firebase Firestore load testing reveals issues, these are the first areas to review.
Design for query efficiency
Use targeted queries with filters and limits. Avoid reading large collections when users only need a small subset of documents.
Validate indexes early
If structured queries are slow or failing, review your composite indexes. Index design is one of the most important factors in Firestore performance testing.
Reduce hot document contention
Avoid having many users update the same document, such as a global counter or shared cart state. Shard counters or redesign write-heavy flows where needed.
Keep documents lean
Large documents increase network overhead and serialization time. Split infrequently used fields into separate documents if necessary.
Use realistic pagination
Never load test with artificially tiny result sets if production queries return much more data. Pagination patterns should match real user behavior.
Separate test and production data
Use a dedicated Firestore dataset for stress testing. This prevents noisy metrics and protects production workloads.
Test from multiple regions
If your users are global, run tests from different LoadForge locations to understand network impact and regional latency differences.
Common Pitfalls to Avoid
Testing only happy-path reads
A Firestore system that performs well on direct document reads may still struggle with filtered queries or write contention. Include a mix of operations.
Reusing the same document IDs
If every virtual user hits the same few documents, you create unrealistic hotspots. Use broad, production-like datasets.
Ignoring authentication overhead
If production traffic is authenticated, unauthenticated tests will understate real latency. Always include realistic token usage and security rules evaluation.
Using synthetic queries that don’t match the app
Load testing is only useful when it reflects actual user behavior. Query the same collections, fields, and sort patterns your application uses.
Not accounting for quotas and billing
Firestore load testing can generate significant read and write volume. Make sure you understand the cost implications and project quotas before running large tests.
Logging in during every request
Authentication flows can dominate the test if done incorrectly. In most cases, pre-generate Firebase ID tokens and reuse them for a realistic session duration.
Skipping steady-state testing
Spike tests are valuable, but steady-state load testing is where memory, connection, and indexing issues often become visible over time.
Conclusion
Firebase Firestore is powerful, but its real-world performance depends on your document model, indexing strategy, authentication rules, and traffic patterns. By running structured Firebase Firestore load testing with realistic Locust scripts, you can measure read and write latency, uncover scaling bottlenecks, and validate that your application is ready for production traffic.
LoadForge makes this process easier with cloud-based infrastructure, distributed testing, real-time reporting, global test locations, and CI/CD integration for repeatable performance testing. Whether you’re validating a new launch, stress testing a write-heavy workload, or optimizing query performance, Firestore load testing gives you the data you need to improve confidently.
Try LoadForge to run your Firebase Firestore load tests at scale and see how your database performs before your users do.
LoadForge Team
LoadForge is a load and performance testing platform built on Locust. Our team has been shipping load tests against production systems since 2018, and we write these guides from real customer engagements.
Related guides
Keep going with more guides from the same category.

CockroachDB Load Testing with LoadForge
Load test CockroachDB with LoadForge to evaluate SQL performance, transaction latency, and horizontal scalability.

DynamoDB Load Testing with LoadForge
Load test DynamoDB with LoadForge to validate read and write capacity, throttling behavior, and performance at scale.

How to Load Test Databases with LoadForge
Discover how to load test databases with LoadForge, from SQL to NoSQL, and identify bottlenecks before production.