Professional Documents
Culture Documents
Pro Meteor
Pro Meteor
Arunoda Susiripala
Figure 1: Meteor has 2 parts, which run on the browser and the server
HTTP Requests While there is no ocial documentation, Meteor can handle HTTP Requests similar to other traditional applications. For example, le uploads to a Meteor app are sent as HTTP Requests. See this StackOverow question for details on how to manually handle HTTP requests.
HTTP Server The HTTP server is used to serve static les and HTTP requests. Meteor uses the connect Node.js module for this purpose. DDP Server The DDP server handles all publications, MongoDB operations and Meteor methods. Meteor uses SockJS as the transport protocol. In actuality, the DDP server is a SockJS server customized for Meteor. 3
Future guides will detail how to scale these two servers separately.
Instead, we provide a callback function that is run after the I/O completes, and the rest of the program continues to run. Heres a psuedo-code example showing two dierent tasks. // Call functions. fetchTwitterFollowers( arunoda ); createThumbnail( /tmp/files/arunoda.png ,
/opt/data/arunoda.thumb.png );
// Define functions. function fetchTwitterFollowers(username) { TwitterAPI.getProfile(username, function(){ Model.setFollowers(profile.username, profile.followers, function() { console.log( profile saved! ); }); }); } function createThumbnail(imageLocation, newLocation) { File.getFile(imageLocation, function(err, fileData) { var newImage = ImageModule.resize(fileData); File.saveFile(newLocation, function() { console.log( image saved ); }); }); } Now lets see how the above two functions get executed over time. Tasks in fetchTwitterFollowers are marked in green, and tasks in createThumbnail are marked in orange. Dark colors show CPU time, and light colors show I/O time. The Blue Bar shows waitTime in the queue and the Red Bar shows idleTime. Observations The diagram above shows us a few interesting things. First, there is no particular program execution order. I/O activities can take any amount of time to complete, and they wont block the program from executing other tasks. For example, ImageModule.resize does not need to wait for Twitter.getProfile to be completed before it can be run. Second, CPU-bound activities do block program execution. In the middle of the diagram you can see a blue bar where Model.setFollowers cannot get started 5
even though TwitterAPI.getProfile has completed. ImageModule.resize is the reason for that. It is a CPU-bound task, so it blocks the Event Loop. As mentioned earlier, Node.js runs in a single thread. Thats why Node.js is not the best choice for CPU-bound activities like image processing and video conversion. You can also see there are three red bars indicating idleTime. If our app had more functionality, it could use this time to execute it.
Fibers
Now you know how the Event Loop works, and how ecient it is. But there is a problem: Callbacks. Callbacks make Node.js code dicult to reason about (some describe it as callback soup ). Error handling and nested callbacks are uncomfortable to write, and their existence makes code dicult to maintain and scale. Thats why some say Node.js is hard to learn (and use). Luckily, several techniques exist to overcome this issue. Fibers, Promises, and Generator-based coroutines are some of them. Meteor uses Fibers, and implements APIs on top of it. But before going into it any further, lets see how Fibers works. See the diagram below. Fibers provides an abstraction layer for the Event Loop that allows us to execute functions (tasks) in sequence. It allows us to write asynchronous code without callbacks. We get the best of both worldsasynchronous eciency with synchronous-style coding. Behind the scenes, Fibers takes care of dealing with the Event Loop. Fibers is really good if you use it correctly (Meteor does it well). Also, the overhead caused by Fibers is negligible.
We cant use callbacks like above. We cant return the prole to the client from the callback, because the Meteor method wont wait for the callback before returning. Now we need to learn how to deal with Fibers. Or do we? Meteor foresaw this problem and provided us with a very simple API to get around it. Its not documented yet, but heres how you can use it. meteor-npm also comes with a set of async-utilities to work with npm modules. function getUserProfile(req, callback) { ghapi.user.getFrom(req, callback); } var wrappedGetProfile = Meteor._wrapAsync(getUserProfile); Meteor.methods({ getProfile: function(username) { return wrappedGetProfile({user: username}); } }); The code above is simple to understand. We wrapped the ghapi.user.get method in a function, and called that function with Meteor._wrapAsync to 9
make it Fibers aware. Now we can use it inside Meteor methods and other Meteor APIs. If you know how bind works, you can do the wrapping in a single line as shown below. var wrappedGetProfile = Meteor._wrapAsync(ghapi.user.getFrom.bind(ghapi.user));
Finally
Now you have a better knowledge of the Event Loop, Fibers, and how Meteor uses Fibers. And you know how to use asynchronous functions with Meteor using Meteor._wrapAsync. Its time to supercharge your app with this knowledge.
Additional Notes
If you are looking to learn more about Fibers and related technology, please refer to the following great screencasts by EventedMind. Introducing Fibers Using Futures Meteor._wrapAsync Understanding Event Loop Async and Fibers
Scaling Myth
With the emergence of cloud computing (and specically Heroku), many have come under the impression that scaling means adding more instances of an application. Thats not completely true. Routing, sessions, job processing, static le serving, and database optimization are key concerns when it comes to scaling, among other things. Any Meteor app that intends to deploy into production needs to addressor at least considerthese issues. On top of that, Meteor has its own issues.
10
11
12
without much eort. You can even write directly to the database outside of Meteor, and Meteor will notice the changes. Oplog integration removes the bottleneck of MongoDB polling. MongoDBs oplog is a log that records every database write operation. Its so reliable that its used for MongoDB replication (Replica Sets ). Also, the oplog is a capped collection, which can be congured to have a xed size and can be tailed. Oplog tailing can be used to capture database changes in real-time. See following illustration to see how Meteor gets database changes with the oplog.
Smart Caching Its a good idea to put a caching server in front of Meteor. This will reduce the load on Meteor to serve static content. Node.js (where Meteor runs) does not work well when it comes to static le serving, so using a caching server improves performance. The caching server shouldnt cache the rst HTML page loaded from Meteor into the browser. This is the only HTML content Meteor loads, and it contains a JavaScript variable called serverId that is used to compare versions in hot code reload logic. Improved Application & MongoDB Performance Fixing the most common performance issues helps a lot in the scaling process. The rst thing to optimize is database indexes. Meteor doesnt auto-magically add indexesthey need to be added explicitly. Indexes can provide a large performance gain. 13
Subscriptions and queries can also be optimized, with notable performance gains. Scaling MongoDB MongoDB is a core component of Meteor, so it needs to be prioritized where scaling and performance are concerned. Generally, MongoDB performs quite well. It supports both vertical and horizontal scaling. It can be run on a more powerful server, or use MongoDB sharding to scale horizontally. Although MongoDB comes with good sharding tools, care needs to be taken when using sharding. Use of a CDN If your application is heavy on static content like images and video, you must not host these les using Meteor. Nowadays, using a CDN (Content Delivery Network) is standard practice, and its not very hard.
Scaling Set-up
Our component diagram is: There are three Meteor servers, one MongoDB server and a HaProxy server as the load balancer. For SSL support, we will use Stud in front of HaProxy. Lets discuss these components and congurations.
Conguring MongoDB
Im using a single-server replicaSet for MongoDB, which supports the oplog. It is better to use a multiserver replica set, but I will be using a single server to keep things simple. 14
Figure 6: Meteor Scaling Setup - Components Conguring a Single-Server Replica First, we need to start our MongoDB server with replicaSet aware. Use the following command to start MongoDB with replicaSet meteor: mongod --replSet meteor Then, open a Mongo shell and add the following to congure the single-server replicaSet: var config = {_id: "meteor", members: [{_id: 0, host: "127.0.0.1:27017"}]} rs.initiate(config) It is wise to run a 3 node MongoDB ReplicaSet for your app. I highly recommend using MongoHQ Dedicated Servers, if you dont have the expertise. Access Control Since we are using a separate MongoDB server, we need to prevent unauthorized access of it. We can congure a rewall or use MongoDBs role-based access control. To make the set-up simple, I assume weve congured the rewall properly. If it is not possible to congure a rewall, try using MongoDBs role-based access control. Well be using app as the database for our Meteor app. For the oplog integration, we will be using the local database that contains the oplog.
15
Conguring Meteor
We need to keep an eye on a few things when we are planning a scalable Meteor deployment. Ill show these in this section. Oplog Support I already mentioned in the previous section how oplog can help Meteor to scale horizontally. All youve to do is, simply expose the MongoDB URL of your local database as MONGO_OPLOG_URL. MONGO_OPLOG_URL=mongodb://localhost/local (Of course, you need to set MONGO_URL as usual) IE 8 and 9 Sticky Session Support IE 8 and 9 do not send cookies with Ajax requests; so this breaks our load balancer, which well be exploring in the next section. Fortunately, SockJS has a solution for that, but it is turned o by default with Meteor. To turn it on, you need to export the following environmental variable: export USE_JSESSIONID=1 Selecting Servers It is very important that you choose identical servers for Meteor. They should be in the same data center and the performance, operating systems and architecture should also all be the same; otherwise well have an unbalanced load across our Meteor servers. In this setup, I am using only a single process on the server. So a server with multiple cores will not be much help. So try to pick single core server instances. Ill cover this further in the next section. Deploying It is very important to deploy your Meteor app correctly and congure the servers carefully. If possible, try consulting someone who knows how. Otherwise you can use Meteor Up, which I built to deploy production-quality Meteor apps.
16
10s 10s
frontend public #binding port 80 bind *:80 default_backend apps backend apps #load balancing algorithm balance leastconn #using JSESSIONID as the cookie cookie JSESSIONID insert nocache #adding server server host1 host1.example.com cookie host1 server host2 host2.example.com cookie host2 server host3 host3.example.com cookie host3 Ive removed some parts of the cong le to keep it simple. You can get the full cong le from here.
18
Click here to get the complete conguration le. Stud needs your SSL certicate and the private key in a single .pem le. See these instructions for how to create a .pem le.
Enjoy
I hope this section will help you to scale your Meteor app horizontally.
Figure 7: How cluster works Make sure to add a dierent cookie name for the load balancer used here and the load balancer used for scaling.
In addition to these features, Cloudares pricing model is extremely attractive. Cloudare does not charge for the usage: instead, it oers an aordable perapplication at fee. Of course, a free tier is oered as well.
20
Using Meteor with Cloudare is a bit tricky, as Meteors DDP connection uses WebSockets, which is not supported by Cloudare yet. But with few simple tweaks, you will be able to use Meteor with Cloudare. This is not a guide on how to use Cloudares features, but on how to use Cloudare with Meteor
Figure 8: Using Cloudare with Disabling WebSockets This is the simplest and the best option. All you have to do is export the following environment variable before starting your Meteor app. export DISABLE_WEBSOCKETS=1 Option 2: Use a separate subdomain for the DDP connection With this option, you can continue to use WebSockets with your Meteor app, but you will not be able to use some of the Cloudares features. All you need to do is add a separate DDP connection to your Meteor app, which will bypass Cloudare. Follow the steps below: Add a CNAME or A record called ddp pointing to the your Meteor App
21
Figure 9: Using Cloudare with WebSockets Bypass Cloudare by not clicking the cloud icon on the DNS manager. (It needs to be grey.) Add the above subdomain as your default DDP connection by exporting the following environmental variable before starting your Meteor app. export DDP_DEFAULT_CONNECTION_URL=http://ddp.yourdomain.com Now your DDP connection is bypassing Cloudare and your Meteor can use WebSockets.
22
As your SSL terminator + SSL provider As Ive also previously mentioned, NodeJS is not good at serving SSL, and Meteor has no option to congure SSL. Therefore, we need to use a separate SSL terminator such as stud or nginx. Cloudare has a very interesting SSL service that acts as both an SSL certicate provider and as an SSL terminator. Simply put, you dont need to buy an SSL certicate and make any congurations; you just need to click a button. Unfortunately, if youve used Option 2 to allow DDP support, you cant enjoy this feature, as now your DDP connection is bypassing Cloudare. To use SSL support, you need to use the Cloudare Pro subscription plan Turn o Minication and the Rocket Loader Meteor does already minify all your JS and CSS les. There is therefore no reason to do so inside Cloudare. However, minifying multiple times does not break anything. Cloudare also has a feature called RocketLoader, which loads JavaScript asynchronously. But Meteors JavaScript (just a single le) needs to be loaded synchronously, so you will need to turn this o. Cloudare has some security options, which asks users to enter a CAPTCHA before entering the site. This is used to block malicious users. Sometimes, your users may be using a shared Internet connection, or the ISP is using a transparent proxy or something similar. This might cause Cloudare to trigger the CAPTCHA, which might confuse the users of your app. I really cant say whether it is a good option to turn this o or not. But keep in mind that there is a situation like this also. DDOS Protection First of all, if you are considering this, your app is popular :) Cloudare does a good job at handling DDOS, and it has prevented some major attacks. This is how you can gain its benet To obtain the DDOS protection, you need to hide the IP address (or direct access) to your Meteor app from the public. This relates to both your main website and the DDP connection. If you are using Option 1 with disabling WebSockets, you are already behind Cloudare, and direct access to your Meteor app is hidden from the public. So whenever you need DDOS protection, you can simply turn it on. 23
But if you are using Option 2 with a separate DDP connection, your DDP connection is exposing direct access to your site. This allows the attacker to bypass Cloudare and directly attack your app. If you are keep using this option, and if you decided to use DDOS protection at a later point, migrate your app (or load balancer) into a new server. Then apply Option 1 and turn on the DDOS protection. Hope this section helps you to use Cloudare with Meteor correctly and handover some responsibilities to it and keep focus on building your app.
Finally
Now, youve learn about How Meteor actually works and how it can run as a production deployment. This is not the end, there are some other topic which will be available later. Make sure you are subscribing to the Pro Meteor guide on MeteorHacks.
24