Aug 3, 2013

Node.js + MongoDB, part 3: exit memcached, enter ElastiCache

In a previous post, we added caching to our Node.js + MongoDB web application, thanks to an EC2 instance running memcached.

As you may know, AWS support its own brand of caching, aka ElastiCache: "Amazon ElastiCache is protocol-compliant with Memcached, a widely adopted memory object caching system, so code, applications, and popular tools that you use today with existing Memcached environments will work seamlessly with the service".

Seamlessly? Hmmm. Alright, let's take a look :)

First, let's use the AWS console to create an ElastiCache cluster. We'll start small, with 1 single micro node supporting a little over 200MB of cache. More than we need... and for free :)

Wait a couple of minutes and the cluster will come online. Let's check that the single node is accessible from our EC2 instance (no need to mess with the security group, port 11211 is open by default):
ubuntu@ip-10-48-249-35:~$ echo stats| nc memcache.9vficl.0001.euw1.cache.amazonaws.com 11211

This looks like memcached stats alright. Ok, now let's change a single line in our app, to use ElastiCache instead of our homemade instance:

// MemcacheClient = new mc.Client("10.234.177.74");
MemcacheClient = new mc.Client("memcache.9vficl.0001.euw1.cache.amazonaws.com");

Now, let's run the app:
ubuntu@ip-10-48-249-35:~$ node www4.js 
Web server started
Connected to memcache
Connected to database

Nice. Time for queries and console output!
Mac:~ julien$ curl http://ec2-54-216-3-139.eu-west-1.compute.amazonaws.com:8080/?id=51e3ce08915082db3df32bfc

Request received, id=51e3ce08915082db3df32bfc
Cache miss, key 51e3ce08915082db3df32bfc. Querying...
Item found: {"_id":"51e3ce08915082db3df32bfc","x":13}
Stored key=51e3ce08915082db3df32bfc, value=13

Let's replay the request quickly:
Request received, id=51e3ce08915082db3df32bfc
Cache hit,  key=51e3ce08915082db3df32bfc, value=13

And once more after 60 seconds (the item TTL):
Request received, id=51e3ce08915082db3df32bfc
Cache miss, key 51e3ce08915082db3df32bfc. Querying...
Item found: {"_id":"51e3ce08915082db3df32bfc","x":13}
Stored key=51e3ce08915082db3df32bfc, value=13

Let's stop the web app and look at the cache stats:
ubuntu@ip-10-48-249-35:~$ echo stats| nc memcache.9vficl.cfg.euw1.cache.amazonaws.com 11211|grep [g,s]et
STAT cmd_get 3
STAT cmd_set 2
STAT cmd_config_get 134
STAT cmd_config_set 1
STAT get_hits 1
STAT get_misses 2

God knows that 'seamlessly' is one of the most overused words in the IT business, but I've got to admit, this is truly seamless :)

As usual with AWS, you also get a bunch of graphs out of the box. That's a nice plus.


So that's the "cache" part of ElastiCache and it does what it's supposed to do. What about the 'elastic' part'?

Adding more nodes to the cluster is literally one click away: "Add node" :) After a few minutes, the new node is added to cluster.

Let's update our web app and relaunch it:
// MemcacheClient = new mc.Client("10.234.177.74");
MemcacheClient = new mc.Client(["memcache.9vficl.0001.euw1.cache.amazonaws.com", "memcache.9vficl.0002.euw1.cache.amazonaws.com"]);

ElastiCache has its own client, which allows the app to auto-discover new nodes without having to restart. Very nice, but the client is only available for Java (grumble) and PHP (bleh). Hopefully other languages are on the way.

Now, how about we hit the 2-node cluster with a little more traffic? This ugly script will do the trick:



Let's run this in 2 or 3 local shells and wait for a little while. Coffee break :)

Alright, what do the graphs say?






In the beginning, we see some traffic on node 1 only, then some on node2 only, as I tested them independently. Then, I updated the app to support both nodes and I restarted it. As you can see, traffic is pretty well balanced between the two servers, which means that the mc client works :)

One last thing to conclude: going back to the web app console output, we can clearly see the parallel asynchronous nature of Node.js at work.

Stored key=51e3ce08915082db3df32bfd, value=14
Stored key=51e3ce08915082db3df32bff, value=16
Stored key=51e3ce08915082db3df32bfe, value=15
Request received, id=51e3ce08915082db3df32bf0
Request received, id=51e3ce08915082db3df32bf2
Request received, id=51e3ce08915082db3df32bf1
Cache hit,  key=51e3ce08915082db3df32bf0, value=1
Cache hit,  key=51e3ce08915082db3df32bf2, value=3
Cache hit,  key=51e3ce08915082db3df32bf1, value=2
Request received, id=51e3ce08915082db3df32bfb
Cache hit,  key=51e3ce08915082db3df32bfb, value=12
Request received, id=51e3ce08915082db3df32bfd
Request received, id=51e3ce08915082db3df32bfc
Cache hit,  key=51e3ce08915082db3df32bfc, value=13
Request received, id=51e3ce08915082db3df32bf4
Request received, id=51e3ce08915082db3df32bf3
Cache hit,  key=51e3ce08915082db3df32bf3, value=4
Request received, id=51e3ce08915082db3df32bf5
Cache hit,  key=51e3ce08915082db3df32bfd, value=14

The two lines I've highlighted above come from the processing of the same HTTP request. However, a lot of stuff happened in the middle because other requests. Did I create threads? No. Did I mess with 'worker pools' or similar atrocities? No. I just wrote my code and let the framework do the rest.

That's it for today. Next time, we'll look at... ah, you'll see :)

Happy hacking.

No comments:

Post a Comment