[EDITED 2x]
Hey everyone! ![]()
I’ve been working on a long-awaited feature: support for MongoDB Change Streams in Meteor!
With this new capability, Meteor can listen to real-time events directly from MongoDB (inserts, updates, deletes, etc.) and use them to power reactivity.
I’ve just opened a PR in our GitHub repo. ![]()
Work status
POC
Oplog-compatible API
Stable implementation (all tests passing)
Reviewed by peers
Documentation
Betas & feedbacks
Why Change Streams?
TLDR;
Today, Meteor’s reactivity is based on two approaches:
1. Polling
Polling periodically checks for changes every X ms. It’s used for single Mongo instances or as a fallback when oplog is not available.
2. Oplog
With oplog, we listen to a special MongoDB collection that records all database operations. Meteor then filters those operations and forwards the relevant ones to clients via WebSocket.
For more details, @radekmie have an awesome blog post that mention the oplog issue
What’s the problem?
The oplog registers every operation in the database, even those your app doesn’t care about.
That means:
- Unnecessary load on the server
- More events to process and filter
- Higher memory usage over time
So when you call find inside a publication, Meteor ends up watching all DB operations, then filtering and forwarding only what matches your query.
How Change Streams help
Change Streams create a stream of events based on your query, not the entire database.
So now, when you return a find inside a publication:
- Meteor listens only to operations related to your query
- Less data is transferred
- Resource usage on the server is optimized
In short: more targeted reactivity, less noise.
How to use it
For now, this should be fully transparent from a DX perspective.
You just need to configure your reactivity options in your settings.json:
{
"packages": {
"mongo": {
"reactivity": ["changeStreams", "pooling", "oplog"]
// Meteor will try Change Streams first, then polling,
// and if that doesn't work, fall back to oplog.
}
}
}
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
I’d love your feedback
This is the first stable implementation, and while it’s already functional, I’d really appreciate feedback on the developer experience (DX) and any edge cases you might think of.
Let me know:
- What you think of this direction
- Any real-world use cases you’d like to see tested
- Any concerns or suggestions before this ships
Thanks a lot! ![]()
