Rails + MySQL scaling on a budget
devblog.muziboo.com
devblog.muziboo.com
When you run into resource constraints in Rails, odds are you are running out of RAM first. It's a memory munching beast in most common deployment scenarios.
It is much more efficient to offload those blocking requests to a worker process and poll for results, since a single mongrel can blow through lots of poll requests and useful work in five seconds. Bonus points: your web app will feel snappier, because progressive rendering tricks users into thinking 5 seconds is not actually 5 seconds.
My favorite anecdote: asking tptacek to help me me squeeze a few megs out of Redis to avoid having to bump my VPS up a tier. That would have been, heck if I know, $BOATLOADS / hr in engineer costs to avoid $30 per month on my Slicehost bill. He restored me to sanity fairly quickly.
The real question is which is more valuable to your business.
Consider these two examples:
Let's say I'm a bootstrapper, and have a startup that earns $3k monthly. My customers are happy, and my business is growing, but I'm having some serious scaling problems.
Scenario One: I've outgrown the small Linode that I started on. If doubling the cost of my Linode allows me to service 2X the number of customers, I do it, because $3k in revenue is way higher than the cost.
Scenario Two: I've maxed out the biggest Linode, and the next step up is a cluster of dedicated nodes at Rackspace for $4k/month, which will allow me to scale to 2X my current volume. At this point, I fix the code, because my earnings will drop even if I get twice the number of customers.
I've therefore found that adding RAM and doing nothing with it so it can be used as disk cache (or, for Postgres, allocating some of it as shared_buffers) is the best non-non-blocking solution.
(I'd originally allocated almost all RAM to running up to 16 or so Passenger instances. Once more than 3 or 4 of these were serving requests, everything ground to a halt waiting on the DB. Now I'm down to about 4 instances, with the rest of the RAM for cache, and they cope fine with the same load).
- https://github.com/flyerhzm/bullet/ : The Bullet plugin/gem is designed to help you increase your application’s performance by reducing the number of queries it makes. It will watch your queries while you develop your application and notify you when you should add eager loading (N+1 queries), when you’re using eager loading that isn’t necessary and when you should use counter cache.
- https://github.com/sdsykes/slim_scrooge/ : SlimScrooge implements inline query optimisation, automatically restricting the columns fetched based on what was used during previous passes through the same part of your code.
- http://guides.rubyonrails.org/caching_with_rails.html : Caching With Rails from the awesome rails guides
- http://guides.rubyonrails.org/performance_testing.html : Performance Testing Rails Applications from the rails guides
I suppose I could dig into the bullet code a bit, but I've been too busy.
Has this come up for you before?
You should open a ticket, eventually write a spec to reproduce.
There are also mistakes in there like saying you cannot add indexes to your tables using rails (can do this using migrations).
Am I missing something in this sentence? You can add indexes in migrations.
As mickeyben says above, choosing indexes is a compromise - you sacrifice performance in one area for performance in another. Only you can make the right decision there, because only you can begin to know how the app will be used in production.
photos_i_am_in = user.friends.map {|f| f.photos.select {|p| p.include? user}}.flatten
(Code somewhat minimalist - don't think too hard about it.)
We moved from passenger to nginx+unicorn and the performance increase was awesome + it uses less ram (which again means more ram for more app servers!). Our box was a old quad core with 4gb ram so it got a bit tight with everything on one machine. The DB blocking problem could easily be solved with a cheap VPS as a slave DB, then you can do backups off that. :-) Depending on your kind of website, caching with varnish is great, we also made great use of memcached for fragments and other little things. And since our main cpu consuming background task just did one thing in the end it was rewritten in C which also helped a lot. Offloading all files to Amazon S3 is also a very good way of reducing load if you have a lot of them.
Its probably worth taking another look? For my ruby applications at the moment i use nginx+unicorn, which works really great.
http://blog.scoutapp.com/articles/2011/02/10/understanding-d...
Always tune for your workload. Be sure to index your damn tables. And yes, measure.
This is sort of the systems side of always making sure HTTP compression is working, minifying your JavaScript, crushing your PNGs, and keeping an eye on the aggregate size of web pages. Even if it loads fast for YOU, someone might be on a slow DSL line (or, gasp, DIALUP) or a mobile connection.
Model.all.inject { sum or average code } will destroy you once you get into production. Working at an otherwise excellent Rails shop I had to launch a mini-jihad to drive this stuff out.
(NB my code is wrong because I haven't written Ruby for a while).