Meteor - Real-time applications

meteor

What is a real-time web application?

In a real-time, single-page application (such as a Meteor app), not only can the client request more data, but the server can also push data to the client (for example, when that data has changed in the database).

This raises some interesting issues: how does the server know when data has changed? And what’s more, how does the server know which user to send those changes to? These questions are key to understanding the performance characteristics of real-time apps.

How does Meteor implement support for real-time web application?

Part of Meteor’s magic is an app’s ability to instantly know whenever data has changed. But what exactly is going on under the hood? To understand this, let’s take a look at the mechanism Meteor first used to monitor the database for changes, poll-and-diff.

Poll-and-diff is on its way out (soon to be replaced with Oplog tailing, as we’ll soon see), but it’s still important to understand how it works. Let’s consider a very simple publication:

Meteor.publish('posts', function() {
  return Posts.find();
});

When you publish this simple posts cursor, and a user first subscribed to it, a LiveResultsSet (LRS for brevity) is established. A LRS is the mechanism that Meteor uses to poll the database.

At this point, it’s important to remember that while data changes will usually happen through the Meteor app itself, it’s also possible for external data changes to occur. These could be triggered by another instance of the same app (when scaling horizontally), a different app altogether, or even manual changes to the database through the Mongo shell.

So every 10 seconds (or whenever the server thinks that the Posts collection may have changed), the LRS will re-run the Posts.find() query against Mongo (with a corresponding hit to your Mongo server).

We don’t want to send down all posts all over again to each client, so we need to determine what’s different about the new cursor. To do this, the app will diff (i.e. compare to find out the differences) the result of the query against what it knows about the Posts collection.

Unfortunately, in many cases, running a proper diff over many posts is a computationally expensive exercise. And if you happen to have have many such polls going on, things can quickly get out of hand.

So even though in the long term we’d probably anticipate that per-user memory usage will become the driving scale factor for production Meteor applications, in the short term most Meteor apps still seem to hit CPU bottlenecks first.

What is LRS Inflation?

There are two common reasons why would you have many diffs going on:

  1. Having a lot of different LRSes. Although Meteor tries it’s best to share LRSes between users (if two people subscribed to our posts publication above, they’d share a LRS), if a publication is user specific this won’t be possible.
  2. Writing a lot to the database, which in turn leads to a lot of polling.

The end result of all this is that the nature of your published data and the amount of data writes you make will end up dictating how badly this issue affects your app.

What is Oplog Tailing?

With the recent release of Meteor 0.7.0, a much better database monitoring technique has been developed: Oplog tailing. Oplog tailing does away with the LRS’ poll-and-diff dance. Instead, Meteor uses the Mongo Oplog (a feed of data that’s intended for synchronizing a Mongo Replica Set) to make Mongo a “pseudo-realtime” database.

Now if Mongo was a true real-time database, it would send out the added/changed/removed messages needed by Meteor for every query requiring real-time updates, and there would be far less work needed on the server to in turn transmit these changes to the client.

The Oplog doesn’t quite achieve this, as it instead tells Meteor about changes at the collection level. This means that Meteor still has to do a bunch of work to determine what has changed at the query level, and if that changes should be synchronized to the subscribing clients.

Although in general the Oplog tailing technique is much more efficient that the fairly naive poll-and-diff algorithm, there is still a CPU load required for each database change that will increase with the number of open subscriptions (since we need to check if each change matches each subscription).

So despite the very real improvements brought by Oplog tailing, it’s important to remembers that it still won’t dispense you from properly thinking about your pub/sub strategy. What’s more, even if you enable Oplog tailing, as of now only a subset of queries are candidates for the technique.

An oplog-based solution works with multiple Meteor instances without much effort. You can even write directly to the database outside of Meteor, and Meteor will notice the changes. MongoDB’s oplog is a log that records every database write operation. It’s so reliable that it’s used for MongoDB replication (Replica Sets). Also, the oplog is a capped collection, which can be configured to have a fixed size and can be tailed. Oplog tailing can be used to capture database changes in real-time.

How does Meteor figure out which users to send those changes to?

We’ve looked at how an app detects changes to the data, but the second part of the equation is figuring out which users to send those changes to. This means keeping a copy of each connected client’s “in-memory database” (in Meteor, the contents of Minimongo) in the server’s memory. In Meteor, the data structure that contains this is the Merge Box.

The direct implication is that each connected client takes up a certain amount of memory on the server. And the more data you publish to the client, the more memory you’ll need on the server to keep track of it.

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License