Scaling Audiogalaxy to 80 million daily page views

When I first started working at Audiogalaxy, the site was running on a handful of cobbled together machines and serving about 100K page views per day. By the time we shut it down in 2002, we had scaled it to 80M daily page views and around 35M user accounts. Those were big numbers back then, and the 3 years I spent working there were a lot of fun.

Alexa may be a pack of lies sometimes, but it has to mean something that it claims we were in the top 20 or 30 websites on the web:


When we were still on the positive side of that curve, we had tons of interesting problems (not surprisingly, dealing with exponentially shrinking traffic doesn’t take quite the same level of dedication as exponentially increasing traffic). At the peak, our hardware distribution looked something like this:

  • Web Servers: ~45 dual proc PIII’s with 2GB of RAM
  • Databases: ~40 dual proc PIII’s with 1-2GB of RAM
  • Search: ~8 misc machines with fast hard drives
  • Satellite Servers: 300+ single proc PIII’s with 1GB RAM
  • Misc: About a dozen other machines with assorted specs for message passing, mail, etc.

Scaling the Databases
We scaled our databases out using a number of strategies. For our most heavily accessed data set, we had an extremely good read/write ratio, so we were able to fan out to about 20 slaves from a single master. This particular database had several hundred million rows, which challenged the limits of our hardware (periodically, we had to clean out stale data when it got too large), so one trick we used was index-segmentation. Different sets of slaves had different indexes, and our database access layer could pick a different cluster based on the necessary index. Specifically, the tables in this database generally had an ID and a string, but the index on the string was only necessary for some queries. So, on some slaves we simply didn’t have the string index. This allowed those machines to keep the entire ID index in memory, which was a huge performance boost.

We used sharding to scale our databases in other areas. For example, each user account had a column that indicated which database that user’s requests for songs were stored in. This prevented us from doing some aggregate queries easily, but it allowed us to easily scale out our capacity to hold user requests.

Sessions
As any site scales beyond a single web server, tracking user sessions is a standard problem. We used a set of MySQL servers with in-memory databases for our sessions. We partitioned across them by simply taking a few bits from the session ID and using that to index into our machine list. If I was going to build this again, I would seriously look into using MySQL Cluster for the following reasons:

  • It would be a compartmentalized, easy to monitor area to evaluate how the technology works and gain experience dealing with problems
  • It would simplify the logic behind building stats about who is currently browsing the website (the script that did that would not have to be partitioning aware)
  • We could restart databases without losing sessions and forcing users to login again. Knowing we won’t lose sessions may allow us to keep more interesting information in them.

Search
I’m going to cover search in more detail in another article, but our search engine had the following requirements:

  • Search ~200M songs in a MySQL database
  • Adjust the order of results based on real time file availability
  • Handle 2000 searches per second at peak times.

Our actual search algorithm was more complicated than our schema, so even if it had existed at the time, the MySQL full text index wouldn’t have been a turnkey solution. Search was one of the most interesting areas of work for me, and it took several iterations before I completely solved the scalability problems. Not surprisingly, it ended up being a completely custom solution.

Web Servers
Our entire web site was built on PHP, and as I mentioned above, we had about 45 web servers. I believe we used the RedHat High Availability solution as a software load balancer. To keep CPU and script execution time down to a reasonable level, we used a very new and flaky PHP accelerator to cache compiled versions of the PHP scripts. Finally, we found that gzip compression was a big win — we used over 100 megabits of bandwidth all of the time, so every little percent we could cut out saved us money.

One neat management hack we used was a webpage with 45 frames that loaded the front page of the website off each server. Refreshing the page after doing any sort of maintenance made it easy to spot broken servers.

Satellite Servers
We used about 300 machines to handle all of the incoming connections from users running the Satellite, which made it the largest and most expensive piece of our infrastructure. I’ll go through this in more detail in a later article as well, but this was a primarily single-threaded server written in C using /dev/epoll as our IO model. As a whole, the cluster handled about 1.1M simultaneous connections at peak times. Individual servers could handle 6-8K connections each, which left us with plenty of spare capacity.

One of the less technical challenges I did have to deal with once we started turning the system off was unracking the servers and bringing them back to the office. If you imagine 2 guys who each own a car limited to holding about 15 servers, I’ll let you do the math to figure out how many trips it took to construct this:

casecollection.jpg
I’ve got more details about how we built Audiogalaxy on the way, so please subscribe to the feed if you’re interested.

6 Responses to “Scaling Audiogalaxy to 80 million daily page views”


  1. 1 Kennon Ballou

    As one of the guys writing the PHP code that you had to scale, I have to say you did a great job :)
    It’s really amazing to think about what we were doing back then! What a crazy ride.

  2. 2 Sue Massey

    I found your site on google blog search and read a few of your other posts. Keep up the good work. Just added your RSS feed to my feed reader. Look forward to reading more from you.

    - Sue.

  3. 3 Oz

    Found your site on news.ycombinator.com

    Damn, those were good days. I remember downloading 30 Temptations songs on Good Friday. Over dialup, too.
    I wonder if my parents figured out why they got no calls that day…oh well..

  4. 4 Tomas Doran

    Nice article, thanks.

    I have to say that from my experience MySQL cluster is to be avoided as it’s horrifically flakey. Whilst it is meant to be scalable and robust, having tried running it in a large production environment (to store metadata about cache items in memcache) it has a number of massive problems:

    . Tendency to core dump, usually at the worst times. And not just part of your cluster core dumps, it *all* core dumps. (e.g. 2 people saying TRUNCATE TABLE for the same table at the same time == coredump)

    . Never frees space. Deleting rows just marks them dead, it doesn’t actually free them. So unless your data set / key values are constant then you’re stuffed. This is similar to PostgreSQL, except MySQL cluster *does not* have a VACUUM which frees the space. This obviously compounds the problem above - when your DB is about to get full, a couple of your admins get paged, and truncate a no longer used table.. Bang goes your cluster.

    . If your cluster core-dumps, and you need to re-load it from disk, this takes *AGES* (e.g. > 30mins for ~ 4G of data across 4 machines)

    . If you don’t re-load the cluster from disk, but just nuke it and start again - your web servers start issuing queries against tables which no longer exist (until you re-create them). This brings the cluster to a grinding halt. About 10 sessions saying ‘SELECT * FROM non_existant_table’ or generally any query to ‘non_existant_table’ is *more than enough* to make the cluster unuseable (>3 mins for a new connection).

    As our cache system had to deal with all of this flakyness, we ended up with MySQL being an *optional* component (needed for performance, but if not there would not block requests - however if it wasn’t there cache clears had to be pretty shotgun).. We moved to just using a load of standalone innoDB instances, so that any one could fall over, and only affect a portion of our client-base, as it wouldn’t drag all the others down with it..

    If we’d known that MySQL Cluster was so unreliable / untrustable in advance, we never have used it and/or designed our cache system in a different way.

    It’s a shame, as it’s a good looking project, and if it’s failure modes weren’t so abysmal, it’d be awesome, but I won’t in any way trust it again until I hear of someone actually using it in production at a reasonable scale (outside of mysql’s marketing literature).

  5. 5 Tom

    Wow, Tomas — thanks for the great notes about MySQL Cluster. Glad to hear that you don’t think Cluster is ready for primetime yet. It is always hard to know before you use a product how well it will perform, particularly with failures. Stress testing can only help you so much.

    Sometimes I wish there was some sort of independent wiki or resource that documented how usable projects are. Given how proven they are, something like Apache or PHP or memcached are probably safe to use. But for newer things, it can be hard to find examples of other people using them. Benchmarks are nice, but I want to hear real world stories about how they performed under real world failures — DNS problems, hard drive failures, bad memory, etc.

  1. 1   Scaling Audiogalaxy… by Performance Within Reach

Leave a Reply