The magic of Meteor oplog tailing

We’ve covered the server side of things in a previous post, so in this blog post we’ll talk a bit about the oplog. We’ll dig into what exactly it is, and what you’ll need on the Mongo side of things to make it happen.

For Meteor to work it’s magic, it needs to keep an eye on the various Collections you’ve constructed in order to see when things have changed so that it can alert its subscribers (clients) of those changes.

Meteor has two ways of going about this: it can literally run Mongo queries to detect changes to data (doing diffs in Node.js), or it can simply monitor a mongodb replica set and detect changes in a more optimized and efficient manner (in other words: oplog tailing).

Believe it or not, when you run Meteor locally in your development environment, it actually spins up a Mongo instance that provides an oplog by default. However when you move to production, the Meteor app will look for a MONGO_OPLOG_URL environment variable. If it doesn’t find one, it will revert to a non optimized method of detecting database changes - a method which is much less efficient.

Op(log)ulence. I has it.

The easiest way to get up and running with oplog is to use a cloud based Mongo provider that offers a Mongo oplog like Compose. With a service like this, all you really need to do is ensure that your Meteor app is pointed to the replica sets provided by the service provider and you’re good to go.

Rolling your own

The details of properly setting up a Mongo environment is well beyond the scope of this article, but assuming a fairly typical default install, the basic idea is you’ll have at least three mongo instances running - preferably on separate machines. One acts as a master, and the other two act as backup “replica sets”. The replica sets also publish a binary oplog which the Meteor app can monitor for db updates (in addition to the master).

We’ll assume this setup is done on a single server for simplicity’s sake below. If you perform this on separate servers, you’ll need to transfer the databases from server to server, and update the firewall rules to allow connections between the various machines.

INSTRUCTIONS

#Copy the mongo db files to two new directories

sudo cp -r .meteor/local/db/ .meteor/local/db2
sudo cp -r .meteor/local/db/ .meteor/local/db3

#Start mongo manually

mongod --port PORT_1 --dbpath PATH_TO_DB_DIR_1 --replSet REPLICA_SET_NAME --fork --logpath LOG_PATH_1
mongod --port PORT_2 --dbpath PATH_TO_DB_DIR_2 --replSet REPLICA_SET_NAME --fork --logpath LOG_PATH_2
mongod --port PORT_3 --dbpath PATH_TO_DB_DIR_3 --replSet REPLICA_SET_NAME --fork --logpath LOG_PATH_3

#e.g.

mongod --port 27017 --dbpath /path-to-databases/db/ --replSet meteor --fork --logpath /path-to-databases/db/db.log
mongod --port 27018 --dbpath /path-to-databases/db2/ --replSet meteor --fork --logpath /path-to-databases/db2/db.log
mongod --port 27019 --dbpath /path-to-databases/db3/ --replSet meteor --fork --logpath /path-to-databases/db3/db.log

#Connect to master mongo db

mongo localhost:27017

#Add the replica set configuration

config = {_id: 'meteor', members: [{_id: 0, host: 'HOST:PORT_1'}, {_id: 1, host: 'HOST:PORT_2'},{_id: 2, host: 'HOST:PORT_3'}]};

#e.g.

config = {_id: 'meteor', members: [{_id: 0, host: 'localhost:27017'}, {_id: 1, host: 'localhost:27018'},{_id: 2, host: 'localhost:27019'}]};

#Force apply the configuration (to prevent errors)

rs.reconfig(config, {force: true});

#Check that it was applied successfully

rs.status();

#Connect to each slave server and allow it to read from master

mongo HOST:PORT_1
rs.slaveOk();
mongo HOST:PORT_2
rs.slaveOk();

#e.g.

mongo localhost:27018
rs.slaveOk();

#Update the mongo url (in environment file)

export MONGO_URL=’mongodb://HOST:PORT_1/DB’
export MONGO_OPLOG_URL='mongodb://HOST:PORT_1,HOST:PORT_2,HOST:PORT_3/DB'

#e.g.

export MONGO_URL=’mongodb://localhost:27017/meteor’
export MONGO_OPLOG_URL='mongodb://localhost:27017,localhost:27018,localhost:27019/meteor'

#Test by running kill -9 [pid] and watch one of the slaves become master
#Can be verified by running 

rs.status()

#from the mongo cli

Note: there is also a trick for getting an oplog out of a single Mongo instance (in the event you want the oplog, but want to avoid the maintenance of two extra mongos). An example might be a development or test environment.

Setting the OPLOG variable for Meteor

When you set the Mongo oplog variable, take note the structure is slightly different. In the traditional MONGO_URL, you connect to the database directly, here you connect to the local database, using the database name as the authentication source.

MONGO_OPLOG_URL=mongodb://ppp:PASS@HOST:PORT,HOST:PORT/local?authSource=pppdev