Q&A: The adventures of scaling, Stage 1 March 18th
My initial article about the scaling journey we went through with eins.de generated quite some buzz in the Rails niche of the web. A lot more than I had anticipated.
A number of questions have popped up here and there which I’ll try to address in this article. While you’ll surely understand that I’m not going to spoil what’s coming up in the rest of the series (which we’re only through with by a quarter!), I’ll share some additional details.
Assuming the amount of questions stays at this volume, each article will be accompanied by a Q&A followup article a few days later.
As an aside, please understand that there is no one-size-fits-all solution for scalability problems or a walkthrough guide for your specific application needs. eins.de has certain characteristics which your application might not have.
For example, we store a lot of historic data for forums, personal messages, gallery comments, and more. On any given day, we have tens of thousands of rows being newly inserted while millions are already sitting in each table. This obviously affects query times and the need arises to temporarily store SQL results outside of the SQL service. Your application might have an entirely different concept there.
If you need help analyzing the characteristics and needs of your particular application, please drop me an email at patrick@limited-overload.de and we’ll work something out on a consulting level.
(Click the link for the Q&A.)
Wayne asks on the Rails weblog:
It is interesting that there is no backup capabilities mentioned in this diagram. How do you deal with backing up your data?
I already responded within the comments. For completeness’ sake, here it is again:
On the database side, backup is primarily handled through the replicated setup. Additionally, nightly dumps are created. All machines (obviously including the dumpfiles) are then backed up via rsync to a central backup repository not shown in the diagram.
Actually it’s even simpler. The only data that changes resides in the database (which is dumped as described) and on our NFS server (as shown in the diagram) which would include uploaded user photos, galleries, editorial images and so on. So the only boxes that get backed up via rsync are the NFS server and the dumps from one database server.
Thomas asks on the Rails weblog:
In part II of your write-up, would you make it much more technical in explain how you cluster (what software is used), how file replication is performed, how load balancing is perform (what software is used).
All software we use is actually mentioned in stage 1 of the write-up. There’s absolutely nothing missing.
We are using a single external IP address configured on the proxy box. lighttpd answers on port 80. If it’s a static request for a CSS file, a JS file or an image, it is served directly from the NFS repository which is mounted on each machine we have. If it’s a dynamic request, lighttpd forwards the request to one of the few dozen FastCGI listeners distributed among our 4 application servers.
That’s it. No file replication needed, no hardware or software load balancers, no cluster software.
As mentioned, we use NFS as shared storage (as shown in the diagram). The sitecode is held in Subversion and distributed to the relevant machines using Capistrano (the tool formerly known as SwitchTower).
Load balancing between the backend application servers is handled entirely in lighttpd (a snippet of the config is included in the write-up as well, exactly showing off this fact). It’s all a matter of a single fastcgi.server directive listing all your remote listeners with their ports.
Henry asks questions similar to the ones above in the poocs.net comments, including:
How did you setup MySQL to have a failover.
The original writeup has a link on how to setup MySQL for multi-master replication. This is all you have to do.
The concept behind it is simple. You use each of your servers as a replication slave of the other. By specifying sort of a namespace for auto-increment primary keys you make sure replication doesn’t collide when a record is inserted into the same auto-incremented table on both ends simultaneously. By spacing your masters by the amount of 10 apart you’ll end up with auto_created IDs that end in ..1 for MySQL server 1 and ..2 for MySQL server 2. That’ll all there is to it.
Additionally, we use a capistrano task to remotely switch all application servers to a specific database server in case of a hard- or software failure. Given you have sort of a template in your repository which has a placeholder {{ production_database_host }}, this task is as simple as this:
desc "Lock app servers to specific database host"
task :lock_db_host, :roles => :app do
run <<-CMD
ruby -pe '$_.gsub!("{{ production_database_host }}", "#{ENV["HOST"]}")' \
#{current_path}/config/database.yml.tmpl > #{current_path}/config/database.yml
CMD
restart
end
From the command-line you may now use cap lock_db_host HOST=db-2
What are your application servers? Are they just web servers running lighttpd?
No, they’re not running lighttpd. They run standalone FastCGI listeners using the Rails combo spinner and spawner. See chapter 3 of the capistrano manual for examples.
john asks in the poocs.net comments:
so it looks like the majority of gains were in a re-architecting of the back-end, and not so much from using Rails-specific features ?
This is mostly true indeed. In fact, leaving hands off of Rails’ components feature was a nice performance gain on itself.
As mentioned in the article, Rails’ caching features were of little use for us since the dynamic nature of the site with tailored content based on user preferences was a clear no-match.
Sean asks in the poocs.net comments:
Could you go into more detail about the refactoring you did on your sidebar code so that it is no longer component based? [..] i.e. did you just make the sidebars partials?
No. While I don’t know how much of what I did will work for Typo (as Typo’s sidebars tend to be a lot more full featured and configurable), here’s a quick rundown.
First of all, I refactored the former controller rendered via components into a module, keeping all the controller methods named after each sidebar we have. All sidebars also have an accompanying view.
Then, in ApplicationController, I loop over the array of assigned blocks for a particular page invoking the appropriate method in the sidebar module and storing the rendered return value (rendered via render_to_string) for later display by a helper method.
Dick Davies asks in the poocs.net comments:
is that really only 1 lighttpd in the diagram? it’s not (http) proxying, it’s just using remote fcgis, right?
Absolutely true.
malcontent stated in the poocs.net comments:
How much of this was the fault of mysql. Could your application better handle the load with postgres or even oracle.
All of this is pure speculation. We’ve optimized FastCGI related items just as well as database handling in general and relieving the database from doing certain things specifically.
I don’t think web applications can be treated in binary form that way, it’s not like a certain application will only work in PostgreSQL *or* MySQL, let alone having the budget for an Oracle instance.
gumi would like to know in the poocs.net comments:
What did all this cost?
There’s no way on earth to accurately answer this question. I’ve given the specs of the hardware so you can have that calculated by your local hardware guys. Development time of the whole solution was roughly 4-5 months with some initial planning.
goyaves asks in the poocs.net comments:
What app did you use for the grpahics on this page ?
The diagrams have been setup in OmniGraffle 4.1 Pro on a Mac.
Caleb cries on the Rails weblog:
Please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please please provide more information!!!
Of course. March 20 is only 2 days away.

16 comments
Jump to comment form