Mashable crushed us - here's how we bounced back
blog.appstorehq.com
blog.appstorehq.com
The first things I'd do: 1. Aggregate those inserts into a single SQL command. You never want to be issuing O(n) SQL queries per request.
2. Use InnoDB, not MyISAM, for the database engine. I assume it's MyISAM because he's talking about table locking. InnoDB has row-level locking, so you'll be able to INSERT and SELECT from a table concurrently.
I had a Facebook app with 20MM MAUs, and it worked fine with two machines. A component of the app was voting in polls, and I was processing ~10k votes per second. Each "vote" is an insert into a votes table.
Soo...not really sure how you go from "We're running too many INSERT queries and locking the tables!" to "Async queues and MongoDB!"
And on the server level, Passenger isn't optimized for running a single site. I use Unicorn for my Rails app.
0. Make the right kind of table. create table user_apps (user_id integer, app_id integer, primary key (user_id, app_id)); -- this does the validation for you, and (should) index the table on both user_id and (user_id,app_id).
1. Make the right kind of insert statement! insert into user_apps select 5, 6 union all select 5, 7 union all select 5, 8; -- etc
2. How do you do recommendations? That sounds like it'd be tough to do well, especially within a web request.
Even if you have to do it in the application layer, why would you need more than one SELECT? Just get the installed apps from the DB in a single query and do a set difference operation to only get the apps not already installed.
But I agree that putting the constraint in the DB layer is the correct solution. With MySQL you can just do INSERT IGNORE to only add apps not yet marked as being installed.
Great to see that MongoDB turned out to be a quick solution, but it seems that the writes could have just been timed more effectively.
Thanks for the insight!
1. Moving the inserts to a adync background job
2. Use raw SQL instead of activerecord for the inserts.
I'm used to postgres though, I know it can handle heavy writes at the same time as heavy reads just fine without replication.
As for moving the inserts to an async job, that was one of our thoughts, too. Really it's what we did, but we just have the intermediary step of MongoDB. We wanted that step (rather than just immediately returning to the user and having the client not know when the inserts actually occur) because we wanted to keep the user experience great. Our app immediately sends you to personalized recs. We didn't want the initial experience to be: open the app => sent to a blank page of "recommendations."
An async job that didn't wait for any sort of confirmation would have left us in this position (or we would have had to implement a poller to check if the inserts were complete, which seemed wasteful and would need a push to Android Market which seemed less feasible.)
"We'll be back shortly!
We're making some changes to our infrastructure and certain pages may be unavailable for a few minutes.
We're very sorry for the inconvenience.
Please check back shortly."
I'm honestly not sure if this is a joke, or if the post made it before the commit or something, or if their new launch had technical difficulties?
Anybody know?
Given the last day we've had, this tends to fall into the "there's nothing we can do but laugh at this point" category. :)
Hopefully Tumblr comes back shortly, brings our blog back up with it, and you can read the full post in all its glory.
Sorry for the trouble!