Home / Cosmos DB / Engineer Course
V11
🌊
Lesson · 5 min

Change Feed

Stream every insert/update from your container to downstream systems.

TL;DR

Cosmos's Change Feed is a built-in, durable, ordered stream of every insert and update on a container — no extra infrastructure. Use it to build materialized views, fan out to search indexes or caches, trigger downstream workflows, and replicate data without dual-writes. The catch — it doesn't include deletes by default (you fake those with soft-delete + TTL).

Key takeaways
  • Change Feed is **per partition**, ordered, durable — every doc that changed shows up exactly once per consumer.
  • Two consumption models — **push** (Change Feed Processor, scales automatically across hosts) and **pull** (manual, you control checkpoints).
  • Lease container — a separate Cosmos container that tracks each consumer's progress. One per processor group.
  • No deletes by default. Use soft-delete (an isDeleted flag) plus TTL on the soft-deleted docs to expire them after consumers have processed.
  • Latency from write to feed is < 1 second. Don't use it for "real-time" sub-100ms — use SignalR / WebSockets there. Do use it for everything async.

The killer feature you didn’t know you wanted. Change Feed turns Cosmos from a database into a building block for streaming architectures — every change you make becomes a durable event you can subscribe to, with no Kafka, no Event Hub, no dual-writes.

What it is

A persistent, per-partition log of every insert and update. Each entry has the new state of the document, sorted by a logical sequence number. You consume it via the SDK’s Change Feed Processor (push) or the raw pull API.

var processor = container
    .GetChangeFeedProcessorBuilder<Order>("orderProcessor", HandleChanges)
    .WithInstanceName(Environment.MachineName)
    .WithLeaseContainer(leaseContainer)
    .Build();

await processor.StartAsync();

async Task HandleChanges(IReadOnlyCollection<Order> orders, CancellationToken ct) {
  foreach (var order in orders) {
    // do something — update search index, send email, populate cache
  }
}

That’s the whole API. Cosmos handles partitioning, scaling, checkpointing, and retries.

Common patterns

Materialized views

Maintain a denormalized read-optimized view of your data. Orders container → on every change, update a ordersByCustomerSummary container with aggregated totals. The view is eventually-consistent with the source, but a single read instead of many.

Search index sync

Every change flows to Azure AI Search / Elasticsearch. Your transactional source of truth stays in Cosmos; full-text search lives elsewhere. No dual-write problem.

Cache invalidation

Change Feed pushes to Redis — write Cosmos, Redis sees the change within ~500ms, evicts or updates its cache entry. Beats TTL-based eviction for correctness.

Cross-region replication

For workloads where Cosmos’s built-in multi-region (lesson V12) doesn’t fit, run a Change Feed processor that writes to another Cosmos account. Lets you do filtered replication, schema transformations, or replication to non-Cosmos targets (Synapse, ADLS, S3).

Domain events

Treat every Cosmos change as a domain event — OrderPlaced, InventoryAdjusted. A handler triggers downstream workflows. This is a poor man’s event sourcing — it works surprisingly well.

Lease container — what it actually does

Each Change Feed processor instance reads from one or more leases. A lease is a small document in a separate container that tracks “I’m working on partition X, last checkpoint Y.”

When you scale out — add a second host with the same processor name — the leases redistribute (one host gets some, the other gets others). When a host dies, its leases are picked up by surviving hosts within ~30 seconds. This is built-in and zero-config.

You provision a small lease container — 400 RU/s is plenty for most workloads. One lease container can serve many processors if you give each a different name.

Push vs pull

Push (Change Feed Processor) — the SDK manages everything. You provide a callback, it provides scaling, checkpointing, error handling. The right answer 95% of the time.

Pull — raw API. You call GetChangeFeedIterator() and manage your own checkpoint. Useful for one-off backfills, batch jobs, or when integrating with a framework that has its own scaling model (Spark, Flink).

The deletes problem

Change Feed doesn’t emit delete events in the default mode. Two patterns to handle this:

Soft delete + TTL — instead of DELETE, set isDeleted: true + deletedAt: now on the document. Change Feed sees the update; consumers act on it. Set container-level TTL or per-doc TTL to physically purge after a buffer period (say, 7 days).

All-versions-and-deletes mode — if you don’t want soft-delete plumbing, opt into the all-versions feed. It emits delete tombstones. Slightly more expensive in storage and consumer complexity, but you get hard deletes.

Latency

Change Feed is consistent within ~1 second from write to consumer. Some are sub-200ms, some up to 2-3 seconds during partition splits. Don’t use it for sub-100ms real-time push — use SignalR / WebSockets / direct push for that. Use Change Feed for everything else async.

🎯 Common questions
Q1. Can I get the previous version of a document, or only the new one?

With "all versions and deletes" mode (in preview/GA depending on API), yes — the feed includes the prior version and a delete marker. With the default "latest version" mode, you only see the new state — store the prior version in your handler if you need diffs.

Q2. How do I scale Change Feed Processor?

Run multiple instances pointing at the same lease container with the same processor name. They auto-balance — each picks up some leases, each lease maps to one physical partition. To scale up, add hosts. The framework handles rebalancing on host churn.

Q3. What happens if my consumer is offline for a week?

The lease container holds your last checkpoint. When you restart, you resume from there — even a week of changes still flow through. There's no expiry on Change Feed itself; the storage cost is part of your container's normal storage charge.

📺 Video

The lesson video is on YouTube — coming once the upload goes public.

🧪 Simulator

A live simulator for this lesson's mechanic (e.g. RU calculator, partition-key picker). Coming in Phase 2.

🎨 Visualization

An interactive diagram of this lesson's core idea — coming as we build out the visualization library.

💻 Code

A copy-paste reference snippet plus a short build challenge.

Comments 0

Discuss this page. Markdown supported. Be kind.

Loading…
Loading comments…