Scaling Meteor to 20,000+ Users in 7 Days

Woody

My phone vibrates. The familiar buzz for an email notification. Apple finally approved a critical last minute update to the BRAVE app. Our efforts over the past three and a half months has culminated to this day. Launch day. We beta tested the app with 100+ users, but we could not anticipate what was about to happen next.

The morning of launch, I nestle down on the couch with coffee in hand obsessively changing Chrome tabs between Kadira performance monitoring, Modulus Metrics, and Compose Visual Monitoring. I was ready for battle. The first wave of signups occurred shortly after 9:30am. Modulus is maxed out. 100% CPU across all servos. I try to double our capacity and realize our account will not allow it. We need more servos, a lot more servos, if we are going to have a chance at handling the next impending wave of user signups.

I start filling out a support request to Modulus when I hear a ding indicating I have a new message on Slack. It’s Ry. He noticed our servos were maxed out. I quickly explain the limit issue and he jumps in to contact Modulus directly to resolve the issue (We have a guy). Although our application server was being bogged down, Kadira and Compose showed our database was getting hammered with activity. The excitement was overwhelming.

By the end of the day, over 10,000 users had signed up, and by the end of the week that number would double. The largest observed database spike was north of 10,000 queries per second.

Through that process we learned some of the subtleties of scaling Meteor. Some things we anticipated before launch, and a few things we had to address in real-time.

5 Tips for Scaling Meteor

Although there are a number of strategies and techniques to scaling, I will mainly focus on some principles related to queries and database configuration. Meteor and MongoDB were made for scalability, but there are few key concepts to unlock their scaling potential.

Tip 1: Ensure all publish function queries are supported by an Index.

At a fundamental level, indexes help MongoDB more efficiently return the result of query by allowing it to setup a special data structure to represent the underlying documents. There are many types of indexes allowed by MongoDB, all of which can be found in their documentation of indexes. By default, the _id property of a document is supported by an index, however publish functions often use queries specifying fields other than the _id. For these queries, setting up an index will improve performance tremendously. At Differential, we like to put the ensureIndex (createIndex in MongoDB 3.0) calls directly after any publish function to show that the query is supported by an index. Here’s an example:

Meteor.publish('data', function (arg) {
return Data.find({level: arg});
});

Meteor.startup(function () {
Data._ensureIndex({level: 1});
});

Tip 2: Ensure you are using Oplog for all publish function queries

In a MongoDB replica set, the oplog (short for operation log) keeps a record of all operations that modify data inside of the replica set. This special log is used by secondary (slave) members in a replica set to stay synchronized with the primary (master). To allow Meteor to utilize this log for observe changes to data, you can simply set up a MONGO_OPLOG_URL environment variable. Further information on Meteor’s oplog integration an be found here, and below is an example of the environment variable structure used for BRAVE.

The MONGO_URL is provided directly from Compose as the Replica Set URI in the Admin section for a particular database.

// MONGO_URL
mongodb://[username]:[password]@[candidate_url]:[port_number]/[database_name]?replicaSet=set-[uniqueId]

As long as the username is setup to have oplog access, the key to setting up the oplog url is to update the database name to point to local and setting the authSource to the database name in the mongo url.

// MONGO_OPLOG_URL
mongodb://[username]:[password]@[candidate_url]:[port_number]/local?authSource=[database_name]&replicaSet=set-[uniqueId]

Tip 3: Use fields, limits, and sorts in publish function queries

Sending the minimum amount of data to the client helps make the sub/pub process faster, and helps protect against inadvertent data exposure. This is why specifying only the fields the client needs is a best practice for publish functions. Limits are wise to setup, especially for views that theoretically could have an infinite list. When limiting the result set of a query, typically the query is also sorted. So keeping indexing in mind is critical for performance.

Tip 4: Increase the memory for MongoDB

One key to determining whether or not your MongoDB deployment requires increased memory is by monitoring the page faults your database is experiencing. Page faults occur when MongoDB tries to read or write to parts of the database that are not already loaded into physical memory. Compose gives you insight into page faults visually within their Monitoring section. We saw spikes in page faults right after launching BRAVE and found that the memory allocation provided out of the box by Compose was not sufficient. Unfortunately, there is no way to increase memory from the dashboard view of Compose, so we had to contact their support team directly to get it setup.

Tip 5: Use Kadira and Compose to uncover bottlenecks

Kadira was instrumental in helping us fine-tune the performance of the BRAVE app. I highly recommend using it in any production Meteor app. I found one of the most useful feature to be the response time tracing. This feature allows you to drill down and really see what is slowing down the system.

Compose has a dedicated section for slow queries and an entire Monitoring dashboard. The slow queries section will mainly show you queries that are not supported by indexes. The Monitoring dashboard gives you more insight into the performance of the MongoDB deployment itself.

After five short weeks, BRAVE has facilitated 200,000+ social interactions (Messages, Comments, Likes, etc) and recorded over 2.9 million interactions with the app's content, such as responses to questions or reading/watching/listening to content.

Wow Beautiful app! ...So well designed and full of valuable content. Seriously bravo to the team that designed and built this app.

It was certainly brave to build an app for three platforms in a little over three months with hopes to reach thousands. Meteor not only made it possible to build the app in that time frame, but Meteor was able to scale wonderfully.

(For more about BRAVE, check out the case study.)