Meteor 3.5-beta: Performance & Change Streams
Hello everyone! We are excited to announce the first beta of Meteor 3.5. This release is heavily focused on introducing MongoDB Change Streams to the ecosystem.
For a deep dive into the “why” behind Change Streams, check out our detailed discussion here.
Experimenting with 3.5-beta
To start testing the new features, use the following commands:
Create a New App
# Create a new Meteor app using Meteor 3.5-beta
meteor create my-app --release 3.5-beta.6
Note: Improvements in this beta are not enabled by default for new apps. See the Setup section below for instructions.
Update Your App
# Update your existing Meteor app to version 3.5-beta
meteor update --release 3.5-beta.6
Change Streams Setup
To enable MongoDB optimizations, follow these steps:
- Database Requirement: Ensure you are using a Replica Set or a Sharded Cluster. See the MongoDB documentation for more details.
- Configuration: Add the following to your
settings.json:
{
"packages": {
"mongo": {
"reactivity": ["changeStreams", "oplog", "polling"]
*// Meteor will try Change Streams first, then oplog,*
*// and if that doesn't work, fall back to pooling.*
}
}
}
Refer to the docs for further technical details.
ChangeStreams feature is transparent for you
After update the meteor version and setup the setting.json to use change streams, Meteor will care the rest for you automagically ![]()
Meteor.publish('links', function () {
// This uses Change Streams ONLY for the LinksCollection
// and ONLY for documents matching { my: 'query' }
return LinksCollection.find({ my: 'query' });
});
different from oplog that observe ALL changes from a collection even you are using it or not, change streams just take care about the changes you are using/querying.
Highlights
40% More Scalability with Change Streams (#13787)
The traditional Oplog driver works by tailing all database changes and performing a “diff” process against client-side documents. As connections scale, this architecture hits a critical bottleneck: processing capacity.
In our tests, we discovered that while Oplog manages memory well under normal loads, it struggles to deliver data to clients fast enough during high traffic. This creates a backlog of pending queries and connections that eventually leads to Out of Memory (OOM) crashes.
Change Streams solve this by handling data on-demand (streaming). Instead of accumulating data locally, it processes it as it flows, allowing the system to maintain stability. Even under extreme stress, the system experiences simple timeouts rather than a fatal process crash.
Preliminary Benchmarks
The following data was captured using an artillery script in a dedicated machine (2 vcpu with 8 GB of RAM) running a standard Meteor app (otel) hosted in galaxy (using premium instance - 1 container of 8 vcpus with 8GB ) with high-frequency add/delete operations:
Oplog: Maxed out at 1200 VUs (10 VUs/s for 120s). Beyond this, the process suffered from persistent timeouts followed by an OOM crash.
Change Streams: Handled up to 1680 VUs (14 VUs/s for 120s). Beyond this, we observed only timeouts, but the process remained stable with no OOM.
Each VU ran 10 connections, one connection each 0.5s doing a insert
VU = Artillery Virtual User
Conclusion: Change Streams offer a 40% increase in connection capacity and significantly better resilience, effectively eliminating OOM errors caused by high data transfer volume.
(Image from montiAPM)
Reproducing the benchmark
> git clone git@github.com:meteor/performance.git
> cd otel
> git checkout otel
> docker-compose up -d mongo-replica
# edit your settings.json enabling changeStreams or Oplog
# then run the meteor app
> MONGO_OPLOG_URL="mongodb://localhost:27017,localhost:27018,localhost:27019/local?replicaSet=rs0" MONGO_URL="mongodb://localhost:27017,localhost:27018,localhost:27019/?replicaSet=rs0" METEOR_NO_DEPRECATION=true meteor run --settings ./settings.json --port 8080 --inspect
# in another shell, run the artillery
> npx artillery run tests/artillery/add-task.yml
Faster CI/CD (#14177 and #13787)
We have removed Travis CI from our GitHub pipelines.
- Before: ~40+ minutes waiting for PR checks.
- Now: ~10 minutes via GitHub Actions.
DDP Session Resumption (#14051)
When a client loses its network connection and reconnects within the grace period (default: 15 seconds), Meteor now resumes the existing DDP session instead of creating a brand new one.
What this means in practice:
- onConnection callbacks are not re-triggered on resume
- The client keeps its original connection ID
- No full session re-initialization and data re-fetch — significantly reducing CPU spikes on reconnect (e.g., after load balancer timeouts on platforms like Google Cloud Run)
- Only ungraceful disconnects (network drops, browser close) are resumable. Intentional disconnects (explicit logout, server kick) are not.
Two new server-side options are available:
Meteor.server.options.disconnectGracePeriod(default: 15000ms)Meteor.server.options.maxMessageQueueLength(default: 100)
Big thanks to @vlasky for the comprehensive implementation and test coverage.
DDPRateLimiter Now Supports Async Rule Matchers (#14182)
DDPRateLimiter rule matchers can now be asynchronous functions, enabling use cases like database lookups inside rate limiting rules, something that wasn’t possible before.
As a bonus, the internal logic was refactored to evaluate matching rules only once instead of twice, making rate limit checks slightly faster when your matchers do async work.
DDPRateLimiter.addRule({
type: 'method',
name: 'sendMessage',
async userId(userId) {
const user = await Meteor.users.findOneAsync(userId);
return user && user.role !== 'admin';
}
}, 10, 1000);
TypeScript type definitions and documentation examples have also been updated to reflect the new async-first approach.
Thanks to @9Morello for this contribution.
Email Warning When Accounts.emailTemplates.from Is Not Set (#14044)
Meteor has historically used Accounts Example no-reply@example.com as a default sender when Accounts.emailTemplates.from is not configured. Since example.com is a reserved domain, most SMTP providers silently reject these emails, making it very hard to debug.
Meteor now logs a clear warning at startup when this default is detected, helping developers catch misconfigured email setups early.
Thanks to @harry97 for tracking down this long-standing pain point.
Node.js 24.14.0 & NPM 11.10.1 (#14176)
Meteor 3.5 ships with Node.js 24.14.0 (LTS) and NPM 11.10.1, bringing all the stability, performance, and security improvements from the Node 24.x line. This has been a long-running effort led by @storyteller, huge thanks for keeping the runtime up to date.
Other PRs and improvments
Other PRs and improvments you can see here
Comming soon
- Mongo Collation Support
- Asyncify client side calls
- uWebSockets support
- Fix for (node:75001) Warning (beta.6)
Big Thanks to Our Contributors
Community contributions are the backbone of Meteor 3.5.
- Special Mention: Huge thanks to @radekmie for the surgical review on the Change Streams implementation.
- Core Contributors: @italojs, @radekmie.
- Contributors: @mvogttech @9Morello @StorytellerCZ @harryadel @vlasky
Your Feedback
Your feedback is crucial in adjusting this beta before the official launch. Community testing is key to ensuring Meteor 3.5 and the Change streams integration is stable and flexible.
You can use this thread to ask questions.
You can also check the existing Change Streams integration forum post for more details and to share your feedback. The post will also serve as a place to add insights on the integration and explain further options and ways it can be used.

