Chapter 15 · Scaling and Hotspots: Key Distribution, Index Fan-Out, Sequential Values, and Ramp-Up

Sequential Document IDs / Timestamps, Monotonic Fields, High Write Rates, and Index Hotspots

Show how sequential document IDs and monotonically indexed fields create moving hotspots, then redesign AtlasMart event keys and timestamp indexing deliberately.

Advanced · 165–195 minutessequential keys · timestamps · index hotspotsFirebase JS 12.19.0 · Admin 14.4.0 · CLI 15.30.0Standard Native canonical lab · Enterprise differences explicitLast reviewed: September 2026

1. Why a timestamp can hotspot even when document IDs are random

Execution and safety note

Use the Emulator Suite, a Firebase demo project, or an isolated test project for destructive, security-sensitive, billing-sensitive, migration, backup/restore, or write-heavy exercises unless the lesson explicitly marks managed verification as required. Treat shown output as expected evidence unless it is explicitly identified as captured output, and re-check current Firebase/Google Cloud edition, mode, quota, pricing, and security documentation before production execution.

AtlasMart wisely stopped using event-000001, event-000002, … as document IDs and switched to Firestore auto IDs. Yet write latency rises as event volume grows. The overlooked field is ingestedAt: every new event receives a later timestamp, and Standard Native automatically indexes that field. The document keys are scattered, but the timestamp index entries arrive at one moving edge of an ordered index.

This is the key distinction: document-key hotspot and index-key hotspot are independent. Random IDs fix the first. A sequential indexed field can still create the second.

Chapter 15 reproducibility baseline · reviewed 17 September 2026

AtlasMart continues the same mandatory environment used in Chapters 01–14: project ID demo-atlasmart-firestore, Standard edition / Native mode / (default) database, Firestore emulator 127.0.0.1:8080, Authentication emulator 127.0.0.1:9099, Emulator UI 127.0.0.1:4000, Firebase CLI 15.30.0, Firebase JavaScript SDK 12.19.0, Firebase Admin Node.js SDK 14.4.0 carrying @google-cloud/firestore 9.1.0, @firebase/rules-unit-testing 5.0.2, and Node.js 22+. Mandatory benchmarks remain emulator-only and no-cost. They teach measurement mechanics and relative shapes; they do not certify production throughput, split behavior, Key Visualizer patterns, billing, or regional latency.

Current documentation check

Firebase JavaScript SDK 12.19.0 was released 9 September 2026. Firebase Admin Node.js 14.4.0 was released 10 September 2026 and uses @google-cloud/firestore 9.1.0. Firebase CLI 15.30.0 was released 9 September 2026. Standard Native documentation still describes 500/50/5 gradual warm-up and the 500 writes/s constraint for a collection with a monotonically changing indexed field. Enterprise Native indexes are optional rather than automatic; unindexed queries can scan, so index and cost reasoning is different even though document/key-range hotspots still exist.

Learning outcomes

01

Explain why lexicographically sequential document IDs concentrate document-key traffic even when each document is independent.

02

Explain why monotonically changing indexed timestamps or sequence numbers can create a moving index hotspot in Standard Native.

03

Apply the documented 500 writes/s sequential-index constraint only where its conditions actually hold.

04

Choose among index exemption, timestamp sharding and query redesign based on a query inventory rather than blindly removing indexes.

05

Separate Standard automatic indexes from Enterprise optional indexes and MongoDB-compatibility indexing.

2. Two moving edges: document IDs and indexed values

Pattern Key space receiving new writes Risk Safer shape
Sequential document IDs Document-key range near newest ID Moving document hotspot Auto/scatter IDs or application-generated random IDs
Random IDs + indexed sequential timestamp Index-key range near newest timestamp Moving index hotspot Exempt timestamp if not queried, or shard required timestamp index
Random IDs + non-indexed sequential timestamp Document keys remain scattered; no timestamp index entries Timestamp unavailable to Standard query ordering/filtering Use another query shape/materialized view if needed
Random IDs + required sharded timestamp Multiple index prefixes share the moving edge More read complexity but more write distribution Query each shard and merge bounded results

Standard Native documentation states that when a collection contains a monotonically increasing or decreasing indexed field, the maximum write rate to that collection is 500 writes per second. This is not “all timestamp fields” and it is not the 500/50/5 ramp guideline. If the timestamp is not indexed, that specific index constraint does not apply; if the field is required for a query, simply disabling the index breaks the access pattern.

3. Start with the Chapter 5 query contracts

AtlasMart already treats queries as named contracts. Before changing indexing, add an inventory entry for every event query that consumes occurredAt or ingestedAt. A field can be safely exempted only when no required Standard query depends on it.

firestore.indexes.json · keep only query-backed indexes
{  "indexes": [    {      "collectionGroup": "events",      "queryScope": "COLLECTION",      "fields": [        { "fieldPath": "tenantId", "order": "ASCENDING" },        { "fieldPath": "kind", "order": "ASCENDING" },        { "fieldPath": "occurredAt", "order": "DESCENDING" }      ]    }  ],  "fieldOverrides": [    {      "collectionGroup": "events",      "fieldPath": "ingestedAt",      "indexes": []    },    {      "collectionGroup": "events",      "fieldPath": "rawPayload",      "indexes": []    },    {      "collectionGroup": "events",      "fieldPath": "debugAttributes",      "indexes": []    }  ]}
Why this example exempts ingestedAt but keeps occurredAt

ingestedAt exists for operations/audit sequencing and is not a user query key in this model. occurredAt is part of the product query contract, so its composite index remains. If occurredAt itself reaches a sequential-index throughput problem, the model must change rather than silently remove the index.

4. When a required timestamp query must scale beyond one ordered index edge

The sharded timestamp pattern deliberately adds a small random shard value and indexes (timestampShard, occurredAt) instead of relying on one globally ordered timestamp edge. Writes are spread across shard-prefixed index ranges. Reads query each shard and merge the top results in application code. This trades write scalability for read amplification and application complexity; choose a shard count from measured workload, not folklore.

write-event.mjs · random document IDs plus optional timestamp shard
import { randomInt } from "node:crypto";import { Timestamp } from "firebase-admin/firestore";export async function writeEvent(db, event) {  const shard = randomInt(0, 16);  const occurredAt = Timestamp.fromDate(event.occurredAt);  const ref = db.collection("events").doc(); // Firestore-style scatter ID  await ref.set({    tenantId: event.tenantId,    kind: event.kind,    occurredAt,    timestampShard: shard,    ingestedAt: Timestamp.now(),    payload: event.payload  });  return ref.id;}// If a required query must order by a sequential timestamp at a write rate// beyond Standard's sequential-index constraint, query by shard + timestamp// and merge a bounded number of shard result sets in application code.

The query side must issue one bounded query per shard, preserve the same filters, then merge-sort by occurredAt. Pagination becomes a multi-cursor problem because each shard has its own continuation state. That complexity is the cost of distributing the ordered index.

5. Deliberately wrong approach: encode time in the document ID

20260917T010000Z-000001 looks operationally convenient because a console listing appears chronologically ordered. At high write rates it puts new document keys next to each other. Worse, if ingestedAt is also indexed, the same write can create both a document-key moving hotspot and an index-key moving hotspot.

The repair is to keep identity independent of sort order. Use scattered document IDs. Store time as data. Exempt unused sequential time fields in Standard, or shard the time index only when a required access pattern justifies the added complexity.

6. Edition-aware decision table

Decision Standard Native Enterprise Native MongoDB compatibility
Automatic single-field index on timestamp Yes unless exempted No; indexes are optional Use compatibility-mode index semantics
Unindexed query behavior Required query generally fails and points to index creation May scan instead of failing; can become slower/costlier Mode-specific query planner behavior
Sequential index hotspot Relevant when sequential field is indexed; documented 500 writes/s constraint Created sequential indexes can still concentrate index writes; optional indexing changes the starting point Consult mode-specific scale/index docs
Random document IDs Still recommended for high-rate key distribution Still recommended Avoid narrow/sequential hot keys

7. Observable lab: compare key-generation shapes, not production capacity

package.json · local Chapter 15 harness
{  "name": "atlasmart-firestore-ch15",  "private": true,  "type": "module",  "engines": { "node": ">=22" },  "dependencies": {    "firebase-admin": "14.4.0"  },  "devDependencies": {    "firebase-tools": "15.30.0"  },  "scripts": {    "emulators": "firebase emulators:start --only firestore --project demo-atlasmart-firestore",    "bench": "node bench-writes.mjs",    "reset": "node reset-ch15.mjs"  }}
bench-writes.mjs · measure observed latency, never invent it
process.env.FIRESTORE_EMULATOR_HOST = "127.0.0.1:8080";process.env.GCLOUD_PROJECT = "demo-atlasmart-firestore";import { initializeApp } from "firebase-admin/app";import { getFirestore, FieldValue } from "firebase-admin/firestore";import { randomUUID } from "node:crypto";initializeApp({ projectId: "demo-atlasmart-firestore" });const db = getFirestore();const percentile = (sorted, p) => {  if (!sorted.length) return null;  const index = Math.min(sorted.length - 1, Math.ceil((p / 100) * sorted.length) - 1);  return sorted[index];};async function runStage({ mode, writes, concurrency }) {  const latencies = [];  let next = 0;  let errors = 0;  async function worker(workerId) {    while (true) {      const i = next++;      if (i >= writes) return;      const started = performance.now();      try {        if (mode === "hot-document") {          await db.doc("benchmarks/ch15-hot").set({            n: FieldValue.increment(1),            updatedAt: FieldValue.serverTimestamp()          }, { merge: true });        } else {          const id = mode === "sequential"            ? `evt-${String(i).padStart(10, "0")}`            : `evt-${randomUUID()}`;          await db.doc(`benchmarks/ch15-${mode}/events/${id}`).set({            mode,            ordinal: i,            observedAt: new Date().toISOString(),            workerId          });        }      } catch (error) {        errors++;        console.error(JSON.stringify({ mode, i, code: error.code, message: error.message }));      } finally {        latencies.push(performance.now() - started);      }    }  }  const t0 = performance.now();  await Promise.all(Array.from({ length: concurrency }, (_, i) => worker(i)));  const elapsedMs = performance.now() - t0;  latencies.sort((a, b) => a - b);  return {    mode, writes, concurrency, elapsedMs,    observedOpsPerSecond: writes / (elapsedMs / 1000),    errors,    latencyMs: {      p50: percentile(latencies, 50),      p95: percentile(latencies, 95),      p99: percentile(latencies, 99),      max: latencies.at(-1)    }  };}for (const stage of [  { mode: "random", writes: 600, concurrency: 12 },  { mode: "sequential", writes: 600, concurrency: 12 },  { mode: "hot-document", writes: 250, concurrency: 12 }]) {  console.log(JSON.stringify(await runStage(stage)));}

Run the random and sequential stages several times after resetting emulator state. Preserve every JSON record. The lesson goal is to make the distinction between identifier strategies explicit and to verify that your harness records tail latency and errors. If one local mode looks faster, do not generalize it into a production claim; the emulator does not model managed key-range splitting.

8. Verification and rollback

  • No product query relies on a field before you exempt its index.
  • Sequential IDs are removed from high-rate event writes.
  • If timestamp sharding is introduced, the shard count is configuration with a migration plan, not a magic constant.
  • Pagination tests cover all shard cursors and duplicate/tie handling.
  • The old query/index path remains available until new query-contract tests pass.

Production judgment and bridge to Lesson 3

Random IDs solve document-key distribution; index design solves index-key distribution. Both must match the query contract. Lesson 3 adds the time dimension: even a good key/index design can suffer if a new collection or migration receives full production traffic before the storage layer has time to adapt.

Knowledge check

  1. Does every timestamp field impose a 500 writes/s limit in Standard Native?
  2. Why not sort by timestamp encoded in the document ID?
  3. When is an index exemption safe?
  4. What does timestamp sharding trade?
  5. How does Enterprise change the problem?
Review the answers

1. No. The documented constraint applies to a collection with a monotonically changing field when that field is indexed.

2. High-rate sequential IDs can concentrate new document writes into a moving key range; identity and ordering should be modeled separately.

3. Only when the query inventory proves no required query depends on that field or a replacement access path is already tested.

4. It distributes index writes at the cost of multiple bounded reads, merge logic, more complex pagination and potentially more read cost.

5. Indexes are optional rather than automatic, so you decide which ordered structures to create; document hotspots remain and unindexed scans can be expensive.

Summary

AtlasMart now treats document IDs and indexed field values as separate key spaces. Scattered IDs prevent one class of moving hotspot; disciplined indexing or sharded required indexes address another.

Authoritative references

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only Ethereum or ERC-20 compatible assets to this address.