No post for almost 2 weeks : we’ve been incredibly busy. We had a lot of instability in the past 10 days, in conjonction (but not related) to significant API changes. We’d like to apologize for the few times when you had problems accessing our service. We’ve done our best to limit these downtime periods and most of the time they only affected subscription/unsubscriptions : notifications were still going on, as usual.
In that period, the number of feeds in our system increased by 50%, most of them through the PubSubHubbub API. This significant growth in the HTTP traffic made light on the weaknesses of our HTTP architecture. The HTTP API had a direct access to the MySQL database. This was quite bad design from us, as both the XMPP and HTTP were accessing this database and basically doing the same thing : subscribing and unsubscribing users to feeds.
As I am a true DRY lover, I just hated the fact that we had to create and maintain 2 components doing exactly the same thing and I’ve been looking for ways to change that.
Here is how our architecture looked like until this morning:
- The XMPP App would deal with all subscriptions and unsubscriptions, as well as notifications from users using the XMPP API. Nothing changed there.
- The Web app would do the same with both the website (you can add/remove feeds through our website) and the PubSubHubbub API. It was a plain vanilla Rails application.
- It was simple to understand, but not so clean and probably not very easy to maintain, since we have so much duplicate information. I was also “scared” by the scaling issues we might have had to face on both sides, but I knew that our XMPP app can scale pretty easily as XMPP components are load balanced by default by EJabberd, which means that we can scale horizontally in a mater of minutes (Chef is great!).
Here is how it looks like now:
At first sight it looks more complex, and you’re probably right, but it’s also more coherent and much much much dryer. What changed? Basically, both the web app and the PubSubHubbub API became XMPP clients, as they now both access the XMPP App to add/remove/list subscriptions. In both cases, this is BOSH magic.
Bosh is and HTTP extension that allows bi-directional streams – very much like comet -. XMPP has a BOSH extension, that has been implemented in eJabberd. Once you’ve got this stream, it’s pretty easy to transmit XMPP traffic over HTTP. A key feature is that these connection are “stored” on the HTTP side of the XMPP server. It means that as long as your client remembers the session id of this stream, you can re-connect to the XMPP server and take the connection where it was left.
The web app : Strophe
Strophe is a very well done Javascript library for XMPP. My friend Jack wrote it and it’s awesome. Among its key advantages : a simple syntax, very close to Jquery, and an “attach” feature that allows the application server (Rails in our case), to start the BOSH stream and then hand it over to Javascript so that user credentials are never sent back to the client. That is exactly what we used for the web application. You can now list, unsubscribe, and subscribe to feeds through a simple interface that uses our XMPP API. How cool is that?
PubSubHubbub : Proxying
PubSubHubbub is an HTTP API. That was a little trickier, because obviously, running javascript on the server side doesn’t make much sense. However, like any HTTP API, we can of course “proxy” it from Rails. That is what we did.
Here is what happens when you subscribe or unsubscribe with the PubSubHubbub API :
- on the server, it starts an XMPP Bosh session with your credentials
- it sends the corresponding XMPP query,
- it translate the XMPP response
- it returns it to you as an HTTP response.
Of course, there is a performance cost to that, but it’s not that big, as our HTTP server is "geographically"" very close to the XMPP app. Also, at the first PubSubHubbub query that you make, we store the connection into a Memcached server, which allows faster processing on the following requests. In terms of results, we can server any PubSubHubbub query in something like 200ms to 500ms. It is slow, but don’t forget that we also check acceptance of the subscription/unsusbcription (by sending an HTTP GET request) on your side, and that accounts for the biggest part of that time, actually.
We wrote it earlier, but we’re true believers in the fact that web apps are about to see a massive shift in their architecture, and we think that XMPP based apps, where the browser is just an XMPP client are going to be a significant part of that trend. We hope Superfeedr will be an example that parents will show their kids, when they teach them about web apps ;) (Other cool examples include Collecta or Present.ly …)
Comments