.
The headline is change streams by default: reactivity now works on managed and serverless MongoDB (Atlas Shared, Atlas Serverless) where the oplog was never available, so each instance does less work per subscription, your fleet gets smaller, and so does the bill. Around that sits the rest of the real-time modernization, with DDP session resumption that survives a dropped connection, a pluggable DDP transport with a uws option, a fully functional DISABLE_SOCKJS, and EJSON optimizations that trim allocations on the hot path. If you build APIs and auth, there is accounts-express for first-class authenticated routes, async DDPRateLimiter matchers, and client logins that are fully promise-based end to end. Underneath it all, the platform moves to Node.js 24, with a handful of dependencies (MongoDB collation, OTPAuth for 2FA, http-proxy-3) quietly modernized so you don't have to think about them.
None of these are vendor benchmarks; the numbers in this post come from developers testing their own apps in public. And 3.5 is the release where the performance thread that ran through the 3.x line finally reaches runtime.
Change streams are where it starts.
The problem we kept running into
For years, scaling real-time in Meteor came with a quiet catch, one a lot of teams hit without talking about it much.
Meteor's real-time updates rode on Mongo's oplog: to know when data changed, every app server tailed the database's replication log (a running record of every write to every collection) and filtered it, in your Node process, down to just the queries it cared about. It worked, but it carried two costs.
The first is that the oplog is the whole database's write log, not just yours. Meteor tails all of it and throws away what it doesn't need. The moment something outside Meteor wrote to the same database (a nightly ETL job, a data migration, a third-party service injecting records), every app server still had to read and discard all of that traffic. Your Meteor CPU bill went up because of writes your app never even made.
The second is that it tied you to self-managed Mongo. On managed and serverless tiers like Atlas Shared, the oplog is off-limits. So the moment you moved to the hosting most teams actually want, real-time broke, and your only fallback was polling: asking the database the same questions over and over, burning CPU and money to fake what should be instant.
Meteor 3.5 changes that. Change streams are now the default, real-time works on managed and serverless Mongo, and there's no config to write.
And real apps are already reporting the difference, in public, with numbers.
The headline: MongoDB Change Streams
Do more with fewer servers. In Meteor 3.5, reactivity is powered by MongoDB change streams by default, and the practical result is simple: each app instance does far less work per subscription because change detection now lives on the database server instead of your Node process. That translates into more concurrent users, subscriptions, and methods per instance, smaller fleets, and a smaller bill.
There is a second payoff that matters just as much. Real-time now works on managed and serverless MongoDB tiers, like Atlas Shared and Atlas Serverless, where oplog access simply is not available. Until now, users on those tiers were pushed into polling, the slowest and most expensive fallback. With change streams, they get efficient real-time updates without paying for a dedicated cluster just to tail the oplog.
There is one requirement, and Meteor handles it for you: change streams need MongoDB 6+ running as a replica set or a sharded cluster. On older or standalone MongoDB, Meteor detects that it can't use them and automatically falls back (to oplog, and then to polling). The same fallback covers cursors that use skip or limit and selectors change streams cannot serve. So the default is safe: you get the fast path where it works and a working path everywhere else, with no code changes and nothing to configure.
If you ever need the previous behavior, you can pin the pre-3.5 path for the whole app by setting packages.mongo.reactivity to ["oplog", "polling"] in settings.json (more on that, and the rest of the driver's tuning, below).
For the full picture, including how the observer driver selects and switches strategies, see the .
The proof: real-world numbers
We could tell you change streams are faster. Instead, here is what the community measured. These are not vendor benchmarks run on hand-picked hardware to make a launch look good. They come from Meteor developers testing their own apps and load harnesses, and posting the results in public. Read them that way.
More connection capacity under load. In the official shared a load test comparing the two reactivity backends on the same app. Change streams sustained roughly 40% higher connection capacity than oplog. Under the same ramp, oplog hit out-of-memory crashes around 1,200 virtual users, while change streams stayed stable up to about 1,680 virtual users, degrading only to timeouts rather than crashing. The post includes a montiAPM chart of both runs; see the charts in the
A real app, not a benchmark. One of the most striking reports came from a production user. In the same thread, @mvogt22 (Michael) reported that after switching his app to change streams, average Event Loop Delay dropped from 97ms on v3.4 to 18ms on v3.5-beta.4, roughly a 5x (about 81%) reduction (). Event Loop Delay is a direct measure of how starved your Node process is; cutting it that far means far more headroom before an instance falls over. He also posted monitoring screenshots marking the v3.5-beta.4 deploy, with the improvement visible right at the deploy line.
Directional, but pointing the right way. thread. The resource picture is striking. Across the ddp-reactive-light, fanout-light, and reactive-crud scenarios, 3.5 cut average app-server CPU by roughly 32–67%, average RAM by up to about 73%, and total garbage-collector pause time by 81–94%, with GC event counts down a comparable amount. Treat these as directional rather than final (the author noted that server configuration changed between runs, and wall-clock time was mixed across scenarios), but the direction is unmistakable: with change streams, the app server simply does far less work.
.
How a feature that was ready took four and a half months to ship
Change streams were essentially done before the first beta. When 3.5-beta.0 went out on 2026-02-11, the feature worked. The four and a half months that followed were not spent finishing it. They were spent earning the right to make it the default.
None of this was for lack of wanting it. Change streams had come up in the community for years as the obvious answer to the oplog's limits. Issue fixed ObjectID fields being delivered to the client as binary when a query used a projection. ), a client that drops and reconnects within a grace period (default 15s) picks up its existing session instead of tearing everything down. Pending method calls are not lost, and active subscriptions resume where they left off. The payoff is felt on mobile handoffs, sleeping tabs, and flaky Wi-Fi: smoother UX for users, and far less server CPU during reconnect storms because you are not re-running every subscription from scratch. The grace period and message-queue limit are tunable per app.
Pick your transport. DDP is now pluggable (.
Drop SockJS entirely when you don't need it. DISABLE_SOCKJS is now fully functional (), copy-on-write toJSONValue/fromJSONValue (, ). The net effect on a busy server: fewer allocations in DDP serialization, and so less GC pressure.
For the API and auth builders
If you have ever bolted a REST endpoint onto a Meteor app, you know the drill: pull the token off the Authorization header yourself, hash it, look up the user in services.resume.loginTokens, check expiry, and hope you got it right. Every protected route reinvented that plumbing, and none of it talked to the rest of Meteor's account system.
The new accounts-express package (.
Two more wins for this crowd. DDPRateLimiter rule matchers can now be async () round out the asyncified client accounts (, ), a healthier foundation with no change to your code.
The dev proxy moved from
http-proxy to the maintained http-proxy-3 () brings reliable case-insensitive search and locale-aware sorting, consistent across client and server, without silently falling back to polling.None of these will change how you write an app. That is exactly the idea: the platform stays current so you do not have to think about it.
Where 3.5 fits in the Meteor 3 story
Meteor 3.0 (July 2024) was about survival: tearing out Fibers and moving the entire framework to async/await, the deepest migration in Meteor history. 3.1 (November 2024) stabilized that new foundation: Node 22, the v6 MongoDB driver, Express 5, roles folded into core.
What came next is easy to miss as a pattern, but it is the whole point. From 3.2 on, every release chipped away at performance. 3.2 (March 2025) shipped the meteor profile command and fixed a long-standing oplog data-loss bug. 3.3 (June 2025) went after build and dev speed: the SWC transpiler and minifier, parcel/watcher, a modern-by-default architecture. 3.4 (January 2026) modernized the build stack itself with the Rspack integration and tools-core, cutting both bundle sizes and build times.
Notice where all of that work lived: in the build and the tooling. Faster rebuilds, smaller bundles, better profiling. The one place it had not reached was the part of Meteor that runs in production around the clock, the reactive data layer, and specifically how Meteor watches MongoDB for changes.
That is what 3.5 is. Change streams move the performance story from build time to runtime. It is the same multi-release obsession, now made measurable and public through the meteor/performance benchmark suite and the community performance to-do list, finally aimed at the single most expensive thing a real-time app does.
3.5 is not a new direction. It is the moment the performance thread that started with meteor profile in 3.2 reaches the core.
A note on how 3.5 got built
There is one more thing worth naming, because it quietly shaped this entire release: how it came together.
Over the past year, the number of pull requests landing on Meteor has climbed sharply. A large part of that surge is LLM-assisted contribution: people who might once have filed an issue and waited now arrive with a working patch. The barrier between "I noticed something" and "here is a fix" is lower than it has ever been, and it shows. More contributors, more ideas in flight, more of the codebase being touched by more hands.
That is a genuinely good problem to have. A livelier community is the whole point, and the throughput of ideas 3.5 absorbed would have been hard to imagine a couple of years ago.
Throughput on one side of the table is capacity on the other. Every incoming change still has to be read, understood, tested against edge cases, and reconciled with everything else in flight, real work, gladly done. This is not a question of contribution quality; it is a shared resource called review bandwidth, and 3.5 invested heavily in it while re-wiring the heart of Meteor's reactivity.
The right response is not to slow contribution down; it is to make correctness cheaper to prove. That is why 3.5 leans harder than any release before it on measurable performance, and why the community now has a public benchmark suite to point real numbers at real claims.
That is one answer, aimed at the code. The other answer, aimed at the organization around the code, has been playing out in the open in , "AI made PRs, issues, and security reports cheap. Maintainer trust, review time, and ownership stayed expensive." The proposal, the first governance document Meteor has ever had, tries to make that expensive part legible: named areas of specialization with a clear owner for review (Accounts, DDP, MongoDB, Reactivity, the build system), and explicit credit for the non-code work (reviewing, triaging, answering in the forum, writing docs) that holds a project together.
It is worth being honest that this is unresolved, and that the disagreement is the point. The sharpest pushback argued that a publicly documented, vote-gated path to write access on core is not a perk but a supply-chain risk: Meteor sits under thousands of production systems, defenders have to be right on every vote, and an adversary only has to pass once, which is roughly the shape of the xz/liblzma backdoor. Others pushed to separate recognition from merge rights and to treat areas as stewardship rather than ownership boxes. That argument left a mark: the proposal has since moved toward giving Core Committers triage permissions rather than merge rights. None of it has landed yet, and it may change again. Doing that reasoning in public, warmly and in disagreement, is itself part of how 3.5 got built.
More people building, a shared way to check the result, and a place, still being argued over in the open, for that effort to land. That is the direction, and 3.5 is the first release shaped by it.
Upgrade, what is next, and thanks
Ready to try it? Upgrade with a single command:
meteor update --release 3.5
Change streams are on by default, so there is nothing else to configure. One requirement to keep in mind: change streams need MongoDB 6+ running as a replica set or a sharded cluster. If you are on older or standalone Mongo, Meteor automatically falls back to oplog or polling, so meteor update stays safe either way.
Prefer the legacy behavior for now? Set packages.mongo.reactivity to ["oplog", "polling"] in your settings.json and you are back to how 3.4 worked.
What is next. The work is already underway. A is just getting started, and it points where the , , @Eshaan-byte, , and @vlasky. Real-time on managed Mongo (long asked for, finally the default) is yours because of them.
SOCIAL SHARE CARD GENERATOR