Building and Scaling a Startup on Rails: Things We Learned the Hard Way (by Posterous S08)
axonflux.com
axonflux.com
I'd suggest absolutely everybody with a web page read up as much as they can about optimizing web page loading. It doesn't matter if you're Posterous or you run your 2,000 daily visitors off a 256 MB VPS which is never under load, optimizing the page can result in 90% decreases in the user-perceptible time it takes to load the page. It doesn't matter if you slave over a hot memcache to save 200 ms off the database queries if your page then takes 7 seconds to render.
Shaving off two seconds, one second, half a second just prints money. Every time I do it I'm flabbergasted by how much it matters.
The HTTP cache control mentioned in the article is one excellent place to start. For the rest, Yahoo pretty much owns this field of research -- any of the presentations from the YSlow folks are worth your time.
http://www.slideshare.net/natekoechley/high-performance-web-...
I'm kind of a lightbox junkie and I've had some good results recently by the simple expedient of having the browser preload the image (e.g. <img src="foo.jpg" style="display: none;" />) prior to the actual user interaction which calls it to display. There are a billion similar site-specific tricks you can do in Javascript these days.
http://www.slideshare.net/stoyan/high-performance-web-pages-...
The presentation I cited in the parent is more motivational and less stuffed with "Here is a checklist of actionable steps that if you implement will make you more money for each one you do."
The reason why websites can't scale is ALWAYS the DB. To fix the DB it is always master/slave, memcached, then sharding. For EVERY language.
Good article, sums up a lot of the tools I used to scale my stuff.
I'd like to add a little more.
1. Turn on slow-query logging in your DB and tail -f the slow query log. Find slow queries and kill them in your code through indices or using several fast queries to make up for 1 long-one.
2. Cache most reads. Both from application caching and memcached.
3. Turn off associations, they don't play well with caching.
4. If you're using memcached, don't use the plugins.
5. If you're a serious startup, use Engine Yard, they're life savers.
This isn't necessarily a killer, but it means there's sometimes more work than you might like in scaling. There are patches to tune the garbage collector, and JRuby etc. may help, but ultimately you need to be much more aware of memory allocation than you might think.
And by scaling here, we're talking about 1 M to 10 M, not 10,000 to 100,000.
- If you're a serious startup that doesn't monetize off of display ads, use Engine Yard. Otherwise, you can't afford them.
:)
Sphinx is cool, but there's no reason to be afraid of solr/lucene. It takes _very_ little java knowledge to get it up and running and it's very very, really, crazy fast. Like, hundreds of thousands of searches a day on millions of documents and it's totally stable. knocks on wood
Also, Passenger is indeed better than all this mongrel + god + tweaks they're talking about it. AboutUs.org (my employeer) is the largest site on Passenger (that we can find) and we've had 2 actual crashes in the last 6 months, and they were fixed with rebooting Apache.
Passenger I have heard great things -- been meaning to try since it does fix the mongrel queue problem.
My Lucene setup began to throw deadlocks and memory exceptions pretty early on. Searching on "deadlock lucene" on Google yields 25000 results. I have later rewritten the system to Xapian where it has run without any problems.
For live updates I would recommend using Xapian. For fairly static indexes I would recommend Sphinx (as it's _really_ fast for both indexing and searching, but it does not support live index updates yet).
We're doing live updating, probably close to 1 update per second.
Use HAProxy. Take the time to configure it correctly. It's absolutely the most stable and useful loadbalancer I've used. It also solves this issue by talking to your backends.
EDIT: ipvs/keepalived only operate at layer 3, which is one reason why they are so fast. if you need layer 7 stuff, this won't do it.
We've had zero issues with pen, it's been rock solid, but we're still looking at moving to Passenger at some point: seems like it will be more flexible and efficient than our current pack of mongrels.
I don't use Rails, or even Ruby at all if I can avoid it, so I'm sure I'm missing something obvious here, but... why in the world would anyone want to use a web server which can only handle one request at once?
That's why you run like 4 or 8 or 12 or N many mongrels to handle additional load.
Something like Yaws, built in Erlang. With fine-grain threading that works well, you don't get the one-to-one OS process to task mapping.
But that certainly comes with its own set of tradeoffs. There are some workloads where that can be a massive win, but committing to any of the currently still-obscure languages/runtimes that can pull this off with panache means you're committing to a less-well-developed library environment.
Facebook chat runs on Erlang for a reason... and the rest of Facebook runs on PHP, also for a reason.
Of course, even without that Ruby itself will only use a single CPU, since the interpreter itself has a Big Giant Lock, but it can still use threads to multiplex requests and avoid wasting time waiting on every IO.
JRuby allows for proper concurrent multithreaded request handling; I'm surprised it's not a more popular deployment option.
I develop 90% of my stuff on the Microsoft stack, and it just plain handles anything you can throw at it out of the box. We see 2,000,000 pageviews a day steady state, and very seldom lift the CPU off of zero, with nothing more than bread and butter index and stored procedure tuning.
With that as a baseline, I just don't see why all these little startups are having such problems just keeping their website up under a little traffic. I can't believe that these scripted ORMs are really that inefficient that you'd need to spend this amount of effort bolting 3rd party stuff onto them just to keep them alive.
There has to be something else going on. What, exactly am I missing here?
I think it's a fantastic point that you should focus on optimizing your database before you start adding caching. If you can tune your DB with the right indexes and give it enough RAM to fit the whole dataset, you've got a great cache right there!
(BTW, I have been using PostgreSQL on my latest project. I'm impressed so far. It has a much query optimizer and better indexes than MySQL.)
I also like using Solr/acts_as_solr. I haven't used Sphinx but from what I've read about setting it up it sounds incredibly fiddly. Solr, by contrast, is quite simple.
I had to work around some problems in acts_as_solr, such as it's tendency to automatically update solr as soon as a DB record changed (regardless if no fields that solr is indexing have changed). Thinking Sphinx simply updates it's index in a batch process that is called by cron (and is super fast!). I highly recommend it.
Here's some of our learnings:
1. Use query_trace to trace your DB calls and query_analyzer to automatically run EXPLAIN on each call:
http://github.com/ntalbott/query_trace/tree/master http://github.com/jeberly/query-analyzer/tree/master
2. Use our patches to mongrel proc_title to troubleshoot slow queries: http://asemanfar.com/Request-Queue-via-Mongrel-Proctitle
3. Don't use ActiveRecord.
4. Don't use any link or url helpers in Rails.
5. For that matter, don't use Rails. Rewrite your most hit components in something faster.
I love Ruby, but the simple truth of it is that we'd be saving a couple of engineer's worth of money if we weren't on Rails.
You absolutely can iterate faster with fewer people and less wasted time.
Servers are cheap. People (salaries) are expensive.
(Well.. until servers become expensive due to straight up load/scale, that is.)
Background/deferred job processing has been immensely painful for us, and I have no idea what the accepted best job queue is. We're still stuck on backgroundrb, which is a nightmare. Nanite looks like overkill, and has too many deps.
I'm eager to see if anyone else can recommend alternatives that they've had good experiences with.
Tobi's deferred jobs also looked totally solid to me. That one's proven because it drives Shopify. We use his liquid plugin, which is also stellar.
That underlines a big issue with Rails dev even today -- its really hard to know what's good / what works, and what is just some weekend project for someone.
RabbitMQ is another option to consider in this area.
>> myobject.very_slow_method
with
>> myobject.async :very_slow_method
and it's executed in background
One obvious way around this is to ensure the DB joins are done correctly. But the article mentions batching/grouping up the requests. How does that work?
Say you have 30 blog posts, and your views reference associations to the blog post's owners. Well, views are dumb and if you call post.user on each one within a loop, you end up calling User.find 30 times.
But if you do Post.find(..., :include => [:user]), Rails will know to eager load all users -- and User.find never gets called 10 times.