{"id":100,"date":"2026-06-02T12:15:39","date_gmt":"2026-06-02T19:15:39","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/documentdb\/?p=100"},"modified":"2026-06-02T11:06:33","modified_gmt":"2026-06-02T18:06:33","slug":"change-streams-in-azure-documentdb-richer-events-historical-replay-and-multi-node-change-streams-public-preview","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/documentdb\/change-streams-in-azure-documentdb-richer-events-historical-replay-and-multi-node-change-streams-public-preview\/","title":{"rendered":"Change Streams in Azure DocumentDB: Richer Events, Historical Replay, and Multi-Node Change Streams (Public Preview)"},"content":{"rendered":"<p class=\"code-line\" dir=\"auto\" data-line=\"2\">Real-time, event-driven applications are now the expectation, not the exception. Teams want dashboards that update the instant something happens, microservices that react the moment data lands, and pipelines that move changes downstream without polling a database on a timer. Since\u00a0<strong>Change Streams reached General Availability<\/strong>\u00a0in Azure DocumentDB, that pattern has been production-ready and the feature has kept getting better.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"4\">Here\u2019s a quick tour of what\u2019s new since GA, with the headline being three of the most requested capabilities:\u00a0<strong>richer change events<\/strong>,\u00a0<strong>historical replay<\/strong>, and\u00a0<strong>Multi-Node Change Streams, now in Public Preview.<\/strong><\/p>\n<hr class=\"code-line\" dir=\"auto\" data-line=\"6\" \/>\n<h2 id=\"the-idea-in-one-picture\" class=\"code-line\" dir=\"auto\" data-line=\"8\">The Idea in One Picture<\/h2>\n<p class=\"code-line\" dir=\"auto\" data-line=\"10\">Change streams turn your database into an event source. A\u00a0<strong>producer<\/strong>\u00a0writes data, the database\u00a0<strong>publishes<\/strong>\u00a0each change, and one or more\u00a0<strong>consumers<\/strong>\u00a0subscribe with a single\u00a0<code class=\" prettyprinted\"><span class=\"pln\">watch<\/span><span class=\"pun\">()<\/span><\/code>\u00a0call and react in real time, no polling required.<\/p>\n<p dir=\"auto\" data-line=\"10\"><a tabindex=\"0\" href=\"https:\/\/devblogs.microsoft.com\/cosmosdb\/wp-content\/uploads\/sites\/52\/2026\/05\/change-streams-diagram-2.png\"><img decoding=\"async\" class=\"lazyloaded alignnone wp-image-12258 size-full\" src=\"https:\/\/devblogs.microsoft.com\/cosmosdb\/wp-content\/uploads\/sites\/52\/2026\/05\/change-streams-diagram-2-png.webp\" sizes=\"(max-width: 1400px) 100vw, 1400px\" srcset=\"https:\/\/devblogs.microsoft.com\/cosmosdb\/wp-content\/uploads\/sites\/52\/2026\/05\/change-streams-diagram-2-png.webp 1400w, https:\/\/devblogs.microsoft.com\/cosmosdb\/wp-content\/uploads\/sites\/52\/2026\/05\/change-streams-diagram-2-300x138.png 300w, https:\/\/devblogs.microsoft.com\/cosmosdb\/wp-content\/uploads\/sites\/52\/2026\/05\/change-streams-diagram-2-1024x471.png 1024w, https:\/\/devblogs.microsoft.com\/cosmosdb\/wp-content\/uploads\/sites\/52\/2026\/05\/change-streams-diagram-2-768x353.png 768w\" alt=\"iagram titled 'Change Streams in Azure DocumentDB' with subtitle 'Subscribe once with watch() and react to changes in real time.' Three boxes flow left to right: a Producer App that writes documents (insert\/update\/delete), labeled 'your application or service'; Azure DocumentDB in the center, showing 'Single-node' and 'Multi-node (Preview)' options and a Change Stream component that publishes change events; and a Consumer App that subscribes via watch(), reacts and persists a token, labeled 'dashboard, pipeline, service.' Arrows show 'write data' from producer to DocumentDB and 'change event' from DocumentDB to consumer. A dashed line from the consumer back to DocumentDB is labeled 'resume token \u2014 pick up exactly where it left off after a restart.' At the bottom, 'Common consumers' lists Live dashboards, Kafka (Debezium), Azure Functions, and Microservices.\" width=\"1400\" height=\"644\" data-src=\"https:\/\/devblogs.microsoft.com\/cosmosdb\/wp-content\/uploads\/sites\/52\/2026\/05\/change-streams-diagram-2-png.webp\" \/><\/a><\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"14\">The consumer does two things: process each event, and\u00a0<strong>persist a resume token<\/strong>\u00a0so it can pick up exactly where it left off after a restart, deploy, or scale event, no missed changes.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"16\">Getting started is genuinely a one-liner:<\/p>\n<div class=\"evo-codeheader\">\n<div class=\"code-header d-flex align-items-center justify-content-end gap-4\"><button class=\"copy-button d-flex align-items-center gap-4 fs-14 fw-600\">Copy<\/button><\/div>\n<\/div>\n<pre class=\"prettyprint language-js prettyprinted\" tabindex=\"0\"><code class=\"language-js hljs language-javascript\" data-highlighted=\"yes\"><span class=\"hljs-keyword\">const<\/span> changeStream = db.<span class=\"hljs-property\">exampleCollection<\/span>.<span class=\"hljs-title function_\">watch<\/span>();\r\n<span class=\"hljs-keyword\">for<\/span> (<span class=\"hljs-keyword\">const<\/span> change <span class=\"hljs-keyword\">of<\/span> changeStream) {\r\n  <span class=\"hljs-title function_\">processEvent<\/span>(change);   <span class=\"hljs-comment\">\/\/ each event carries operationType, fullDocument, ns, and a resume token<\/span>\r\n}<\/code><\/pre>\n<h2 id=\"what\u2019s-new-since-ga\" class=\"code-line\" dir=\"auto\" data-line=\"27\">What\u2019s New Since GA<\/h2>\n<h3 id=\"richer-change-events:-update-descriptions-and-pre-images\" class=\"code-line\" dir=\"auto\" data-line=\"27\">Richer change events: update descriptions and pre-images<\/h3>\n<p class=\"code-line\" dir=\"auto\" data-line=\"27\">Change events now tell you more about\u00a0<em>what<\/em>\u00a0changed, not just the end state. Update descriptions surface the exact fields that were added, removed, or modified, ideal for change-data-capture and audit pipelines that only want to propagate deltas. And pre-images let an event include the full document\u00a0<em>as it looked before<\/em>\u00a0the change, so you can answer \u201cwhat was the old value?\u201d without a separate lookup.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"27\">Pre-images are opt-in per collection and come with flexible modes (include-if-available vs. strictly required), since capturing the prior version of each document adds some storage and processing overhead. The official docs cover the modes and the trade-offs in detail.<\/p>\n<h3 id=\"historical-change-streams:-replay-the-past\" class=\"code-line\" dir=\"auto\" data-line=\"27\">Historical Change Streams: replay the past<\/h3>\n<p class=\"code-line\" dir=\"auto\" data-line=\"27\">Standard change streams live within an active change log, so once it rotates, older events are out of reach.\u00a0<strong>Historical Change Streams<\/strong>\u00a0remove that ceiling, you can replay or resume from a past point in time, with point-in-time-restore integration extending recovery to roughly the last month of activity. This is a big deal for backfilling a new downstream system, recovering a consumer that fell behind, or reprocessing after a bug fix.<\/p>\n<h3 id=\"broader-driver-compatibility-across-languages\" class=\"code-line\" dir=\"auto\" data-line=\"27\">Broader driver compatibility across languages<\/h3>\n<p class=\"code-line\" dir=\"auto\" data-line=\"27\">Change streams use the standard\u00a0<code class=\" prettyprinted\"><span class=\"pln\">watch<\/span><span class=\"pun\">()<\/span><\/code>\u00a0API, no special SDK or proprietary library. The companion\u00a0<a href=\"https:\/\/github.com\/AzureCosmosDB\/changestream-driver-compatibility\" target=\"_blank\" rel=\"noopener\" data-href=\"https:\/\/github.com\/AzureCosmosDB\/changestream-driver-compatibility\">driver-compatibility repo<\/a>\u00a0ships runnable consumer examples in six languages, each following the same resilient shape: resume if a token exists, otherwise start fresh, and persist the token after every processed event.<\/p>\n<div class=\"table-responsive\">\n<table class=\"code-line\" dir=\"auto\" data-line=\"43\">\n<thead class=\"code-line\" dir=\"auto\" data-line=\"43\">\n<tr class=\"code-line\" dir=\"auto\" data-line=\"43\">\n<th>Language<\/th>\n<th>Driver<\/th>\n<\/tr>\n<\/thead>\n<tbody class=\"code-line\" dir=\"auto\" data-line=\"45\">\n<tr class=\"code-line\" dir=\"auto\" data-line=\"45\">\n<td>Python<\/td>\n<td>PyMongo<\/td>\n<\/tr>\n<tr class=\"code-line\" dir=\"auto\" data-line=\"46\">\n<td>Java<\/td>\n<td>MongoDB Java Driver<\/td>\n<\/tr>\n<tr class=\"code-line\" dir=\"auto\" data-line=\"47\">\n<td>Node.js<\/td>\n<td>Mongoose<\/td>\n<\/tr>\n<tr class=\"code-line\" dir=\"auto\" data-line=\"48\">\n<td>C#<\/td>\n<td>MongoDB .NET Driver<\/td>\n<\/tr>\n<tr class=\"code-line\" dir=\"auto\" data-line=\"49\">\n<td>Go<\/td>\n<td>MongoDB Go Driver<\/td>\n<\/tr>\n<tr class=\"code-line\" dir=\"auto\" data-line=\"50\">\n<td>Ruby<\/td>\n<td>MongoDB Ruby Driver<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<p class=\"prettyprint language-js\"><span style=\"font-family: georgia, palatino, serif;\"><code class=\"language-js prettyprinted\"><span class=\"typ\">The<\/span><span class=\"pln\">\u00a0pattern looks the same everywhere<\/span><span class=\"pun\">,<\/span><span class=\"pln\">\u00a0here it is in\u00a0<\/span><span class=\"typ\">Python<\/span><span class=\"pun\">:<\/span><\/code><\/span><\/p>\n<div class=\"evo-codeheader\">\n<div class=\"code-header d-flex align-items-center justify-content-end gap-4\"><button class=\"copy-button d-flex align-items-center gap-4 fs-14 fw-600\">Copy<\/button><\/div>\n<\/div>\n<pre class=\"prettyprint language-js prettyprinted\" tabindex=\"0\"><code class=\"language-js hljs language-javascript\" data-highlighted=\"yes\">resume_token = <span class=\"hljs-title function_\">load_resume_token<\/span>()\r\nstream = collection.<span class=\"hljs-title function_\">watch<\/span>(resume_after=resume_token) <span class=\"hljs-keyword\">if<\/span> resume_token <span class=\"hljs-keyword\">else<\/span> collection.<span class=\"hljs-title function_\">watch<\/span>()\r\n\r\n<span class=\"hljs-keyword\">with<\/span> <span class=\"hljs-attr\">stream<\/span>:\r\n<span class=\"hljs-keyword\">for<\/span> change <span class=\"hljs-keyword\">in<\/span> <span class=\"hljs-attr\">stream<\/span>:\r\n<span class=\"hljs-title function_\">process<\/span>(change)\r\n<span class=\"hljs-title function_\">save_resume_token<\/span>(change[<span class=\"hljs-string\">\"_id\"<\/span>]) # persist after each event<\/code><\/pre>\n<h3 id=\"kafka-integration-via-the-debezium-connector\" class=\"code-line\" dir=\"auto\" data-line=\"64\">Kafka integration via the Debezium connector<\/h3>\n<p class=\"code-line\" dir=\"auto\" data-line=\"66\">For larger event-driven architectures, change events can stream straight into\u00a0<strong>Apache Kafka topics<\/strong>\u00a0through the\u00a0<strong>Debezium connector<\/strong>, turning your database into a first-class event source for distributed pipelines, no bespoke glue code. A runnable\u00a0<a href=\"https:\/\/github.com\/Azure-Samples\/Delivery-tracking-vCore-debezium\" target=\"_blank\" rel=\"noopener\" data-href=\"https:\/\/github.com\/Azure-Samples\/Delivery-tracking-vCore-debezium\">delivery-tracking sample app<\/a>\u00a0shows the end-to-end flow.<\/p>\n<h3 id=\"customizable-event-pipelines\" class=\"code-line\" dir=\"auto\" data-line=\"68\">Customizable event pipelines<\/h3>\n<p class=\"code-line\" dir=\"auto\" data-line=\"70\">You can shape each consumer\u2019s feed by passing aggregation stages to\u00a0<code class=\" prettyprinted\"><span class=\"pln\">watch<\/span><span class=\"pun\">(),<\/span><\/code>filtering to only the operations and fields you care about keeps payloads lean and consumers focused.<\/p>\n<div class=\"evo-codeheader\">\n<div class=\"code-header d-flex align-items-center justify-content-end gap-4\"><button class=\"copy-button d-flex align-items-center gap-4 fs-14 fw-600\">Copy<\/button><\/div>\n<\/div>\n<pre class=\"prettyprint language-js prettyprinted\" tabindex=\"0\"><code class=\"language-js hljs language-javascript\" data-highlighted=\"yes\"><span class=\"hljs-keyword\">const<\/span> stream = db.<span class=\"hljs-property\">exampleCollection<\/span>.<span class=\"hljs-title function_\">watch<\/span>([\r\n  { <span class=\"hljs-attr\">$match<\/span>: { <span class=\"hljs-attr\">operationType<\/span>: <span class=\"hljs-string\">\"insert\"<\/span>, <span class=\"hljs-string\">\"fullDocument.department\"<\/span>: <span class=\"hljs-string\">\"IT\"<\/span> } },\r\n  { <span class=\"hljs-attr\">$project<\/span>: { <span class=\"hljs-string\">\"fullDocument.name\"<\/span>: <span class=\"hljs-number\">1<\/span>, <span class=\"hljs-string\">\"fullDocument.position\"<\/span>: <span class=\"hljs-number\">1<\/span> } }\r\n]);<\/code><\/pre>\n<h2 id=\"multi-node-change-streams-(public-preview)\" class=\"code-line\" dir=\"auto\" data-line=\"85\">Multi-Node Change Streams\u00a0<em>(Public Preview)<\/em><\/h2>\n<p class=\"code-line\" dir=\"auto\" data-line=\"87\">Until recently, change streams were supported on\u00a0<strong>single-shard clusters<\/strong>\u00a0only, which left the largest, highest-throughput deployments without real-time change capture unless they engineered workarounds. That\u2019s the gap\u00a0<strong>Multi-Node Change Streams<\/strong>\u00a0close, and they\u2019re now in\u00a0<strong>Public Preview.<\/strong><\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"89\">As your data grows, you scale horizontally across multiple shards. Multi-Node Change Streams let you capture changes across that sharded, multi-node topology while keeping the exact same\u00a0<code class=\" prettyprinted\"><span class=\"pln\">watch<\/span><span class=\"pun\">()<\/span><\/code>\u00a0programming model \u2013 consumer code, resume-token handling, and driver support all stay identical. Only the topology underneath changes.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"91\"><strong>A few things to design for during preview:<\/strong>\u00a0ordering is guaranteed per shard key rather than globally across the cluster, a cluster\u2019s transition from single-shard to multi-shard affects resumability, and cursors reinitialize after a failover (resume capability is preserved). Because it\u2019s a preview, it\u2019s a great moment to validate against your workloads in non-production, exercise resume and failover handling on a multi-node topology, and share feedback that shapes the road to GA.<\/p>\n<h3 id=\"get-started-today\"><b><span data-contrast=\"auto\">Get Started Today<\/span><\/b><span data-ccp-props=\"{&quot;134233117&quot;:false,&quot;134233118&quot;:false,&quot;335559738&quot;:240,&quot;335559739&quot;:240}\">\u00a0<\/span><\/h3>\n<p><span data-contrast=\"auto\">With this GA release, Change Streams is ready for production workloads. Explore the full potential of this feature through the\u00a0<\/span><a href=\"https:\/\/learn.microsoft.com\/azure\/documentdb\/change-streams\" target=\"_blank\" rel=\"noopener\"><span data-contrast=\"none\">official documentation.<\/span><\/a><\/p>\n<p><span data-contrast=\"auto\">We\u2019re excited to see what you\u2019ll build next with\u00a0<\/span><span data-contrast=\"auto\">Azure DocumentDB<\/span>\u00a0<span data-contrast=\"auto\">and Change Streams!<\/span><span data-ccp-props=\"{&quot;134233117&quot;:false,&quot;134233118&quot;:false,&quot;335559738&quot;:240,&quot;335559739&quot;:240}\">\u00a0<\/span><\/p>\n<h3><strong>About Azure DocumentDB<\/strong><\/h3>\n<p>Azure DocumentDB is a fully managed document database service for building and modernizing MongoDB-compatible applications. Powered by the open-source DocumentDB engine, it combines familiar APIs, tools, and workflows with Azure&#8217;s security, scalability, and operational simplicity. Whether you&#8217;re developing new applications or migrating existing MongoDB workloads, Azure DocumentDB helps you get started quickly and scale with confidence.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Real-time, event-driven applications are now the expectation, not the exception. Teams want dashboards that update the instant something happens, microservices that react the moment data lands, and pipelines that move changes downstream without polling a database on a timer. Since\u00a0Change Streams reached General Availability\u00a0in Azure DocumentDB, that pattern has been production-ready and the feature has [&hellip;]<\/p>\n","protected":false},"author":175387,"featured_media":113,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[],"class_list":["post-100","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-azure-document-db"],"acf":[],"blog_post_summary":"<p>Real-time, event-driven applications are now the expectation, not the exception. Teams want dashboards that update the instant something happens, microservices that react the moment data lands, and pipelines that move changes downstream without polling a database on a timer. Since\u00a0Change Streams reached General Availability\u00a0in Azure DocumentDB, that pattern has been production-ready and the feature has [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/documentdb\/wp-json\/wp\/v2\/posts\/100","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/documentdb\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/documentdb\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/documentdb\/wp-json\/wp\/v2\/users\/175387"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/documentdb\/wp-json\/wp\/v2\/comments?post=100"}],"version-history":[{"count":2,"href":"https:\/\/devblogs.microsoft.com\/documentdb\/wp-json\/wp\/v2\/posts\/100\/revisions"}],"predecessor-version":[{"id":114,"href":"https:\/\/devblogs.microsoft.com\/documentdb\/wp-json\/wp\/v2\/posts\/100\/revisions\/114"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/documentdb\/wp-json\/wp\/v2\/media\/113"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/documentdb\/wp-json\/wp\/v2\/media?parent=100"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/documentdb\/wp-json\/wp\/v2\/categories?post=100"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/documentdb\/wp-json\/wp\/v2\/tags?post=100"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}