Chapter 06 · Indexes: Automatic/Composite/Collection-Group/Vector, Exemptions, and Index Cost

Audit Index Usage, Remove Redundant Structures, and Validate Query Latency / Cost Before and After Changes

Audit Firestore index ownership, remove redundant structures safely, capture before/after Query Explain evidence, and maintain rollback-ready configuration-as-code.

Intermediate120–150 minutesIndex audit + rollbackFirebase JS 12.19.0 · CLI 15.30.0Last reviewed: September 2026

Learning outcomes

After several teams add screens, AtlasMart's index file can accumulate “just in case” composites, abandoned experiments, field exemptions with no explanation, and future vector definitions. The final lesson establishes an audit loop: every index has an owner, every deletion has dependency tests, and every performance/cost claim has measured evidence from the environment that can actually prove it.

01

Create an index manifest that links query contracts to exact index definitions, edition, scope, owner, lifecycle state, and rollback.

02

Identify redundant or expensive indexes without deleting them first, using static config checks plus query-owner tests.

03

Compare query behavior and managed Query Explain evidence before and after index changes while separating emulator semantics from production performance.

04

Audit vector, collection-group, exemptions, and TTL-related field configurations alongside ordinary composites.

05

Define a reversible removal workflow that prevents an index cleanup from becoming an application outage.

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.

Chapter 06 reproducibility baseline · reviewed 15 September 2026

AtlasMart continues the same environment used in Chapters 01–05: project ID demo-atlasmart-firestore, Standard edition / Native mode / (default) database for the main labs, 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 SDK 14.4.0 (with @google-cloud/firestore 9.1.0), @firebase/rules-unit-testing 5.0.2, and Node.js 22+. The Enterprise-only exercise in Lesson 4 uses an isolated emulator configuration rather than mutating the Standard lab.

Evidence boundary for this generated lesson

The authoring environment did not execute Firebase emulators or a billed Firestore project. Local commands below are deterministic exercises to run on your machine; any shown output is labeled as an expected invariant, not captured benchmark evidence. The Firestore emulator does not reproduce production composite-index enforcement, managed index build/backfill state, billing, or production Query Explain metrics. Those observations are separated into optional managed-project checks.

1. An index inventory needs ownership, not just JSON

firestore.indexes.json tells Firestore what to build, but it does not tell humans why the structure exists. Add a neighboring manifest that records the owning query/test, edition, scope, data surface, risk, and rollback. This is the bridge between product access patterns from Chapter 03, query contracts from Chapter 05, and index implementation from this chapter.

ID Definition Owner Decision
IDX-CATALOG-CATEGORY-PRICE catalogItems / COLLECTION / category ASC + price ASC Catalog cameras-by-price contract Keep; active query owner
IDX-ORDERS-TENANT-CREATED orders / COLLECTION_GROUP / tenantId ASC + createdAt DESC Seller order-history contract Keep; active cross-parent query owner
IDX-TAGS-RATING-EXPERIMENT catalogItems / COLLECTION / tags CONTAINS + rating DESC No current Chapter 05 owner Remove after dependency proof
EXEMPT-PRODUCT-DESCRIPTION catalogItems.description no automatic indexes Display-only long text Keep; query contract forbids filter/order
EXEMPT-PRODUCT-ATTRIBUTES catalogItems.attributes no automatic indexes Display-only vendor metadata Keep; promote governed query fields separately
TTL-CATALOG-EVENT catalogEvents.expiresAt TTL + no index Future lifecycle contract Configuration example only until Chapter 21
VECTOR-PRODUCT-EMBEDDING catalogItems.embedding dimension/model-specific Future semantic retrieval Do not deploy until Chapter 17 owns model + tests

2. Canonicalize definitions so duplicate structures are detectable

audit-indexes.mjs · static manifest audit
import fs from "node:fs";import assert from "node:assert/strict";const cfg=JSON.parse(fs.readFileSync("firestore.indexes.json","utf8"));const normalized = cfg.indexes.map(x=>JSON.stringify({  collectionGroup:x.collectionGroup,  queryScope:x.queryScope,  fields:x.fields}));const duplicates = normalized.filter((x,i,a)=>a.indexOf(x)!==i);assert.deepEqual(duplicates,[],"duplicate manual index definitions found");const experimental=cfg.indexes.find(x=>x.collectionGroup==="catalogItems" &&  x.fields?.some(f=>f.fieldPath==="tags"&&f.arrayConfig==="CONTAINS") &&  x.fields?.some(f=>f.fieldPath==="rating"&&f.order==="DESCENDING"));console.log({manualIndexes:cfg.indexes.length, fieldOverrides:cfg.fieldOverrides?.length??0,  experimentalPresent:Boolean(experimental)});

This check catches exact duplicates and the known experiment. A mature audit should also compare semantically overlapping indexes, but automated deletion based on set similarity is dangerous: direction, field order, scope, array/vector mode, density, API scope, and the implicit/explicit document-name dimension can all matter.

3. Prove dependencies before removing anything

Start with the query-owner suite, not a delete command. Search application code/tests for the exact access pattern, run all query-contract tests, inspect saved dashboards/Query Insights where available, and ask whether background jobs or old clients still depend on the structure. For a public mobile application, version skew matters: an older client can keep issuing a query after the newest code stops.

removal-gate.md · required evidence
Candidate: IDX-TAGS-RATING-EXPERIMENTCurrent definition: tags CONTAINS + rating DESC, COLLECTION scopeKnown query owner: noneStatic config audit: PASSRepository query search: no owner foundEmulator semantic regression suite: PASS (does not prove managed index dependency)Managed Query Insights / logs checked: [record real evidence if available]Old-client compatibility window: [record policy]Rollback JSON preserved: YESRemoval approved by: [owner]Post-removal observation window: [define before change]
Never delete first and use the outage as your dependency detector.

In Standard, a previously supported query can fail once its required index is gone. Recreating the index is not instantaneous because the managed service must build/backfill it. Rollback therefore has a readiness delay.

4. Define before/after metrics that the environment can actually prove

Use the emulator for correctness and Rules regression. Use managed Firestore for index readiness, Query Explain, actual scan/index evidence, storage/usage, and real latency. Query Explain planning mode can show index selection without executing the full query and has its own documented charge; analyze mode executes the query and is billed normally. One analyzed request is not a percentile benchmark.

managed-evidence.json · fill with your captured values
{  "environment": {    "project": "DISPOSABLE_PROJECT_ID",    "database": "(default)",    "edition": "standard",    "location": "RECORD_ACTUAL_LOCATION",    "sdk": "@google-cloud/firestore 9.1.0"  },  "fixture": { "documents": "RECORD_ACTUAL_COUNT", "averageBytes": "RECORD_ACTUAL" },  "query": "RECORD_EXACT_FILTERS_AND_ORDER",  "before": { "indexState": "RECORD", "explain": "PASTE_SANITIZED_REAL_METRICS" },  "after":  { "indexState": "RECORD", "explain": "PASTE_SANITIZED_REAL_METRICS" },  "latencyBenchmark": "separate repeated-run distribution; do not infer p95/p99 from Query Explain once"}

For Standard cost, include document reads, applicable index-entry reads, storage, and network. For Enterprise, use read/write units and index-write implications instead. Do not merge the two models into one spreadsheet row labeled “Firestore cost.”

5. Remove the deliberate experiment in configuration-as-code

The end-state file removes tags + rating because no current query owner justifies it. It keeps the two Chapter 05 query-owned composites and the two proven field exemptions. TTL and vector examples remain documented outside the deployable baseline until their future chapters create real owners.

firestore.indexes.json · audited Chapter 06 end state
{  "indexes": [    {      "collectionGroup": "catalogItems",      "queryScope": "COLLECTION",      "fields": [        { "fieldPath": "category", "order": "ASCENDING" },        { "fieldPath": "price", "order": "ASCENDING" }      ]    },    {      "collectionGroup": "orders",      "queryScope": "COLLECTION_GROUP",      "fields": [        { "fieldPath": "tenantId", "order": "ASCENDING" },        { "fieldPath": "createdAt", "order": "DESCENDING" }      ]    }  ],  "fieldOverrides": [    { "collectionGroup": "catalogItems", "fieldPath": "description", "indexes": [] },    { "collectionGroup": "catalogItems", "fieldPath": "attributes", "indexes": [] }  ]}
commands · validate, deploy, list
node audit-indexes.mjs# local semantic/rules suite firstnpx firebase-tools@15.30.0 emulators:exec --project demo-atlasmart-firestore --only firestore,auth "node seed.mjs && node query-contracts.mjs"# OPTIONAL MANAGED PROJECT ONLY — requires authenticated IAM and may use quota/billingnpx firebase-tools@15.30.0 deploy --only firestore:indexes --project DISPOSABLE_PROJECT_IDnpx firebase-tools@15.30.0 firestore:indexes --database="(default)" --project DISPOSABLE_PROJECT_ID

Before the optional deploy, diff the generated plan/config so you know which index will be deleted or created. Do not run these managed commands against an unrelated production project simply because your shell has a default Firebase alias.

6. Rollback is re-add + wait ready + verify—not “git revert completed”

Source control rollback is necessary but not sufficient. If removing an index breaks a Standard query, restoring the JSON and redeploying starts a managed index build. The application should not resume the dependent traffic until the index reaches a usable state and the owning query passes. This is why risky index removals need feature gates, old-client awareness, and an observation window.

rollback procedure
1. Freeze/gate the affected query path if errors or latency regression appear.2. Restore the exact prior index definition from version control.3. Deploy the index to the correct project/database.4. Monitor managed index build status until READY.5. Run the owning query contract against the managed database.6. Capture Explain/latency/error evidence.7. Re-enable traffic gradually and monitor.8. Record the incident and prevent the same unowned deletion path.

7. Audit cadence and ownership

Run the static manifest audit in CI on every index change. Perform a deeper usage audit when major screens, query shapes, tenant cardinality, or editions change—not on an arbitrary “delete indexes every month” schedule. A configuration that was redundant at 10,000 documents may become valuable after a new query arrives; an index that was critical can become dead after an API version retires.

Include index configuration in backup/recovery/runbook thinking. Firestore data backup does not replace source-controlled Rules/index definitions and application deployment metadata. Recovery drills should verify that query-supporting configuration is restored alongside data.

Hands-on lab and acceptance checklist

  1. Start from the experimental manifest and confirm the audit reports experimentalPresent: true.
  2. Run all Chapter 05/06 emulator query contracts and static configuration contracts.
  3. Create the removal-gate record for IDX-TAGS-RATING-EXPERIMENT.
  4. Replace the experimental file with the audited end-state baseline and rerun static + emulator suites.
  5. Confirm the two field exemptions remain and no vector/TTL experimental definition accidentally entered the deployable baseline.
  6. Optional managed disposable project: deploy, wait for readiness/deletion state, rerun query owners, capture real Explain/usage evidence, and practice restoring the removed index definition in a non-production environment.
  7. Archive the manifest with exact CLI/SDK versions, edition, mode, database ID, location, billing status, fixture shape, and what was/was not executed.

Production judgment

Good index hygiene is conservative: remove structures only with evidence, because cleanup has a correctness blast radius in Standard and a performance blast radius in Enterprise. Cost optimization that creates incident risk is not optimization. Treat index changes like schema/API migrations with tests, staged rollout, observability, and rollback time.

Knowledge check

  1. Why is firestore.indexes.json alone an incomplete index inventory?
  2. What evidence should precede deleting a Standard composite index?
  3. Why is “git revert” not an instantaneous index rollback?
  4. Which evidence belongs to the emulator versus managed Firestore?
  5. Why keep the vector example out of the final deployable baseline?
Review the answers

1. It describes structures but not their owning query, product requirement, lifecycle, risk, old-client dependency, or rollback context.

2. Query-owner tests, code/usage search, old-client review, managed usage/Insights where available, preserved rollback definition, and an approved observation plan.

3. The managed service must rebuild/backfill the restored index and it must become ready before dependent queries are safe again.

4. Emulator: semantics/Rules/integration. Managed: index enforcement/build state, Query Explain scans, actual billing/storage and production-like latency.

5. No Chapter 06 production retrieval contract owns a model/dimension yet; deploying an unowned index would add cost/configuration without validated value.

Summary and next step

Chapter 06 ends with a minimal, owned index baseline, explicit exemptions, and an evidence/rollback workflow. Chapter 07 adds realtime listeners; indexing remains underneath those queries, while listener lifecycle, reconnects, metadata, and read cost introduce a new operational dimension.

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.