Chapter 21 · TTL, Retention, Expiration, Backfills, and Lifecycle Automation

TTL Policies and Expiration Fields: Asynchronous Deletion Semantics and Non-Instant Guarantees

Understand TTL as asynchronous lifecycle cleanup, not an exact-time scheduler: eligibility, delayed deletion, latest-field semantics, query visibility, subcollections, listeners, functions, billing, and edition differences.

Advanced · 150–210 minutesTTL · retention · lifecycle · backfill · complianceNode 22+ · Firebase CLI 15.30.0 · Firestore emulator 127.0.0.1:8080Standard Native mandatory local lab · managed TTL optional/billedLast reviewed: 17 September 2026

1. AtlasMart problem: an expired session is still visible

AtlasMart wants abandoned sessions and transient event documents removed automatically. An engineer sets expireAt and assumes a document becomes unreadable at that exact instant. That assumption is wrong. TTL decides when a document becomes eligible for managed deletion; physical deletion is asynchronous, normally completed within about 24 hours, and an expired document can still appear in a query or direct lookup until the sweeper removes it. Product access and storage cleanup are therefore separate mechanisms.

Learning outcomes
  • Distinguish expiration eligibility from physical deletion and product visibility.
  • Explain the current Standard, Enterprise, and MongoDB-compatible TTL value/configuration differences.
  • Predict how queries, listeners, Cloud Functions, subcollections, and a changed TTL field behave around expiration.
  • Build a deterministic local lifecycle harness without pretending the emulator runs the production TTL sweeper.
  • Design exact-time behavior with explicit application logic instead of TTL timing assumptions.
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 21 reproducibility baseline · reviewed 17 September 2026

AtlasMart keeps the course-wide project identity demo-atlasmart-firestore. Mandatory work is local and no-cost: Node.js 22+, Firebase CLI 15.30.0, Firestore emulator 127.0.0.1:8080, Auth emulator 127.0.0.1:9099, Emulator UI 127.0.0.1:4000, and the same Standard-edition Native-mode mental model used by earlier chapters. The default database is (default). TTL sweeping itself is not treated as an emulator guarantee: the lab models eligibility and lifecycle rules deterministically, while an optional real-project check verifies actual managed TTL deletion only in an isolated billed project. TTL deletes are excluded from Firestore's free usage and require billing.

Core rule for the entire chapter

Expiration is a business fact; TTL deletion is an asynchronous storage-cleanup mechanism. A document whose expireAt is in the past may still exist and be returned by queries until the managed sweeper deletes it. Therefore product behavior must filter or reject expired state explicitly when timeliness matters. TTL is not a scheduler, not a transaction boundary, not a legal-hold engine, and not a substitute for backups/PITR.

2. Edition and mode boundary

TTL is available across current Firestore offerings, but configuration and billing surfaces differ. Standard Native uses a TTL policy on a field in a collection group. Enterprise Native and MongoDB-compatible documentation describe TTL index behavior and managed-delete billing. Enterprise can accept a Date/time value or an array containing Date/time values; Standard requires Date/time. MongoDB compatibility exposes familiar TTL-index syntax, but it is still Firestore-managed asynchronous deletion—not a self-managed MongoDB background thread.

Dimension Standard Native Enterprise Native MongoDB compatibility
TTL configuration TTL policy on one field per collection group; Standard value is Date/time TTL policy/index semantics; Enterprise may also accept arrays containing Date/time values TTL index per collection; MongoDB API supports expireAfterSeconds
Indexing Automatic single-field indexes by default; TTL field can be exempted Indexes are optional by default; TTL index is not a query-performance index TTL index is distinct from query-planning indexes
Billing TTL deletes are billed document deletes and have no free usage TTL uses managed delete units TTL uses managed delete units
Managed delay Typically deleted within 24 hours after expiration; not ordered or transactional Same asynchronous retention principle Same asynchronous retention principle
Local emulator Useful for document/rules/application lifecycle tests; do not claim it reproduces managed TTL sweeping Enterprise-specific managed behavior must be verified separately No Local Emulator Suite MongoDB wire-compatible TTL sweeper

3. The state machine: LIVE → EXPIRED-ELIGIBLE → PHYSICALLY-DELETED

Keep three timestamps separate: businessExpiresAt or the policy-derived expiration, the instant the document becomes eligible, and the eventual managed deletion time. The middle state matters because reads can still return the document. If the application promises “a session is invalid after 30 minutes,” enforcement must compare the business clock on read/action. Waiting for a TTL delete would make authorization depend on an intentionally non-instant background process.

State Document exists? Should product treat it active? TTL action
LIVE Yes Usually yes None
EXPIRED-ELIGIBLE Yes, possibly for hours No if policy says expired Queued/eligible for low-priority managed deletion
PHYSICALLY-DELETED No No Complete; listeners/functions see deletion event where configured

4. Exact deletion semantics that change designs

  • Expired documents continue to appear in queries and lookups until deletion occurs.
  • Applying a new TTL policy to existing data can create a large backlog of already-expired documents.
  • TTL deletion order is not guaranteed to match expiration-time order.
  • Documents with the same expiration timestamp are not deleted atomically or necessarily together.
  • Deleting a parent through TTL does not cascade to subcollections.
  • If an expired-but-not-yet-deleted document gets a later expiration value, Firestore honors the latest value and it can become non-expired.
  • TTL-driven deletion notifies active snapshot listeners and triggers Firestore delete-trigger functions.

5. Mandatory local lab: model eligibility and product visibility

Seed four documents with a fixed clock so results are reproducible. The emulator stores the documents; your application decides which are expired. Do not sleep and claim the emulator is waiting for the managed sweeper.

seed-retention.mjs
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, Timestamp } from "firebase-admin/firestore";initializeApp({ projectId: process.env.GCLOUD_PROJECT });const db = getFirestore();const now = Timestamp.fromMillis(Date.UTC(2026, 8, 17, 2, 0, 0));const hour = 60 * 60 * 1000;const docs = [  ["sessions/s-expired", {kind:"session", ownerId:"u-a", retentionClass:"session-30d", expireAt:Timestamp.fromMillis(now.toMillis()-hour), legalHold:false}],  ["sessions/s-active", {kind:"session", ownerId:"u-a", retentionClass:"session-30d", expireAt:Timestamp.fromMillis(now.toMillis()+hour), legalHold:false}],  ["events/e-expired", {kind:"event", tenantId:"tenant-a", retentionClass:"event-90d", expireAt:Timestamp.fromMillis(now.toMillis()-hour), legalHold:false}],  ["audit/a-hold", {kind:"audit", tenantId:"tenant-a", retentionClass:"audit-7y", expireAt:null, legalHold:true}],];for (const [path,data] of docs) await db.doc(path).set(data);console.log(JSON.stringify({seeded:docs.length, clock:now.toDate().toISOString()}));
eligibility.mjs
export function retentionDecision(doc, nowMs) {  if (doc.legalHold === true) return { eligible:false, reason:"LEGAL_HOLD" };  if (doc.retentionClass === "audit-7y") return { eligible:false, reason:"POLICY_REQUIRES_ARCHIVE_OR_HOLD_REVIEW" };  const expireMs = doc.expireAt?.toMillis?.() ?? null;  if (expireMs === null) return { eligible:false, reason:"NO_EXPIRATION" };  return expireMs <= nowMs    ? { eligible:true, reason:"EXPIRED" }    : { eligible:false, reason:"NOT_YET_EXPIRED" };}// Product visibility should use the same business clock semantics; it must not wait for physical TTL deletion.
verify-eligibility.mjs
import assert from "node:assert/strict";// Assume seed-retention.mjs has run and nowMs is fixed.const nowMs = Date.UTC(2026,8,17,2,0,0);const expired = retentionDecision({legalHold:false,retentionClass:"session-30d",expireAt:{toMillis:()=>nowMs-1}}, nowMs);const active = retentionDecision({legalHold:false,retentionClass:"session-30d",expireAt:{toMillis:()=>nowMs+1}}, nowMs);const hold = retentionDecision({legalHold:true,retentionClass:"audit-7y",expireAt:null}, nowMs);assert.equal(expired.eligible, true);assert.equal(active.eligible, false);assert.equal(hold.reason, "LEGAL_HOLD");console.log("eligibility-contract: PASS");

Expected state: expired documents still exist in the emulator, but the product-visibility decision rejects them. That proves your policy logic; it does not prove production deletion lag, billing, listener delivery timing, or TTL metrics.

6. Controlled failure: use TTL as a reservation timer

Suppose inventory is reserved until 14:05 and the system expects TTL to delete the reservation at 14:05:00 so stock is released. Because TTL is asynchronous, inventory can remain reserved long after the business deadline. The repair is to make expiresAt <= now part of the reservation state machine and run an idempotent expiry/compensation worker for prompt business action; TTL then removes stale reservation records later.

What TTL proves

TTL proves eventual storage cleanup after eligibility. It does not prove exact scheduling, ordered execution, transactional multi-document changes, or business compensation.

7. Optional managed verification

Use a throwaway billed project only. Enable a TTL policy, write a tiny bounded set of documents, record expiredAt and actual disappearance time, and query Cloud Monitoring for deletion count and expiration-to-deletion delay. Do not infer a universal p95/p99 from a handful of documents; record the sample as evidence for that environment only.

gcloud · optional real-project TTL policy
gcloud firestore fields ttls update expireAt \  --collection-group=sessions \  --enable-ttl \  --project=YOUR_ISOLATED_BILLED_PROJECTgcloud firestore fields ttls list --collection-group=sessions \  --project=YOUR_ISOLATED_BILLED_PROJECTgcloud firestore operations list --project=YOUR_ISOLATED_BILLED_PROJECT

Production judgment

Use TTL when deletion can be eventual and policy can be expressed by a field. If the requirement is “stop access at an exact instant,” enforce that in application authorization/query logic. If it is “delete these records together,” use an explicit transactional/batch workflow where feasible. If it is “retain until litigation hold clears,” TTL should be disabled or withheld for held data until governance approves deletion.

Verification checklist and cleanup

  • Expiration and physical deletion are documented as distinct states.
  • Product reads/actions reject expired state without waiting for TTL.
  • Subcollections are included in lifecycle design.
  • Delete-trigger side effects are idempotent.
  • Real-project TTL verification is isolated, billed, bounded, and removed after evidence capture.
  • Local emulator data can be reset safely.

Bridge to Lesson 2

Lesson 2 moves from lifecycle semantics to write-path mechanics: TTL timestamps are often monotonically increasing, and indexing them unnecessarily can create the same sequential-index hotspot problem introduced in Chapter 15.

Knowledge check

  1. Does a past expireAt make a document disappear immediately?
  2. Can an expired document still appear in a query?
  3. Does TTL cascade to subcollections?
  4. What if an expired-but-not-yet-deleted document gets a later TTL value?
  5. Why is the emulator lab valuable if it does not sweep TTL?
Review the answers

1. No. It becomes eligible; managed deletion is asynchronous and typically occurs within about 24 hours.

2. Yes, until the TTL process actually deletes it. Product logic must not equate existence with validity.

3. No. Parent deletion does not automatically delete nested subcollections.

4. Firestore honors the latest TTL value, so the document can become non-expired before deletion.

5. It proves deterministic application lifecycle/visibility rules without fabricating managed deletion behavior.

Summary and next step

This lesson established the working contract for TTL Policies and Expiration Fields: Asynchronous Deletion Semantics and Non-Instant Guarantees. Keep its edition/mode assumptions, trust boundary, verification evidence, and operational constraints explicit when reusing the pattern.

Next, continue to Index Exemptions for TTL Fields, Sequential Timestamp Costs, and Write Throughput Considerations.

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.