<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	>

<channel>
	<title>Yale Daily News Online</title>
	<atom:link href="http://online.yaledailynews.com/feed/" rel="self" type="application/rss+xml" />
	<link>http://online.yaledailynews.com</link>
	<description>About the online operations of the Yale Daily News.</description>
	<pubDate>Mon, 29 Sep 2008 04:36:49 +0000</pubDate>
	<generator>http://wordpress.org/?v=2.6.1</generator>
	<language>en</language>
			<item>
		<title>Caching Part 5: View Caching</title>
		<link>http://online.yaledailynews.com/2008/09/28/caching-part-5-view-caching/</link>
		<comments>http://online.yaledailynews.com/2008/09/28/caching-part-5-view-caching/#comments</comments>
		<pubDate>Mon, 29 Sep 2008 04:36:30 +0000</pubDate>
		<dc:creator>Robert Baskin (Online Director)</dc:creator>
		
		<category><![CDATA[Caching]]></category>

		<category><![CDATA[Technical]]></category>

		<guid isPermaLink="false">http://online.yaledailynews.com/?p=27</guid>
		<description><![CDATA[Well, after some delay, I can now bring you the final post in my caching series. This one is about view caching, which is where the real awesome technology happens! If you haven&#8217;t read parts one, two, three and four, go back and take a look at them first – this post will make more [...]]]></description>
			<content:encoded><![CDATA[<p>Well, after some delay, I can now bring you the final post in my caching series. This one is about view caching, which is where the real awesome technology happens! If you haven&#8217;t read parts <a href="http://online.yaledailynews.com/2008/07/24/caching-an-introduction/">one</a>, <a href="http://online.yaledailynews.com/2008/08/05/caching-part-2-making-it-easy/">two</a>, <a href="http://online.yaledailynews.com/2008/08/14/caching-expiring-caches/">three</a> and <a href="http://online.yaledailynews.com/2008/08/24/caching-part-4-switching-to-memory/">four</a>, go back and take a look at them first – this post will make more sense that way.<span id="more-27"></span></p>
<p>So to summarize, we&#8217;d developed a caching system and switched to memory-based caching. Our site was doing fairly well – rebooting the server on a semi-regular basis was a distant memory. But in early April 2008, we hosted the first ever Yale Daily News Web Conference, where we invited students from other Ivy League newspapers involved in their online operations to come and talk about putting a college newspaper online. It was a good time; maybe I&#8217;ll blog about it in the future.</p>
<p>At that conference, I hosted <a href="http://neodude.net/">Thomas Bukowski</a> from <a href="http://thedartmouth.com/">The Dartmouth</a> for the night. He showed me some of what he had been doing for their site, including how he handled view caching. His work, which made his site serve very quickly on less powerful hardware than us, inspired us to implement view caching on our site. Now let me explain exactly what view caching is and why it&#8217;s sweet.</p>
<p>We were caching the data we retrieved from MySQL, which was the most expensive operation and saved us quite a bit of processing and immensely helped our server load. However, after we retrieved the data from the cache, we still had to generate the HTML we send to the user, including all of the conditional statements, loops and other code that take some time and energy. Instead, why not save the page that is generated and serve that, instead of going through all of the presentation code every single time? The idea is that as soon as possible in your code, you check and see if you have a cache available. If you do, serve the cache and stop processing. If you don&#8217;t, process the page and save the cache when you&#8217;re done. That way you don&#8217;t have to do it next time.</p>
<p>Sounds good in theory, but it takes a little bit of work. We started by putting our code in /path/to/app/config/<a href="https://trac.cakephp.org/browser/branches/1.1.x.x/app/config/bootstrap.php">bootstrap.php</a>, which is the earliest place <a href="http://cakephp.org/">CakePHP</a> is booted up and allows you to execute code. The first problem we ran into was that we allow users to register accounts and log in. If you do that, you can see a personalized welcome message in the top right instead of &#8220;Log In – Register.&#8221; If we cached the page with &#8220;Welcome Joe,&#8221; that wouldn&#8217;t be good for everybody else. The solution was to check and see if a session exists for the visitor – if so, we don&#8217;t serve a cached page. Fortunately, most people don&#8217;t log and set a session, so for everybody else we serve a cached page.</p>
<p>We also don&#8217;t want to serve a cached page if there&#8217;s form data being POSTed or GETed. So we check and make sure those two <a href="http://us2.php.net/variables.predefined">superglobal arrays</a> are empty. If they are, we begin our caching logic. We check and see if the cache is set for this page. If so, we get the cache and print it out, and the page is served. We also print how long our code has been executing for up to that point in a comment below the HTML. If you&#8217;re not logged in, check out the bottom of the source. You&#8217;ll see something like &#8220;&lt;!&#8211; cache took 0.0009s &#8211;&gt;&#8221;. Not bad.</p>
<p>If the cache isn&#8217;t set, we start output buffering with <a href="http://us2.php.net/ob_start">ob_start()</a>. Then, as the last code that executes after every page, we check if we&#8217;re buffering with the line &#8220;if ( <a href="http://us2.php.net/manual/en/function.ob-get-level.php">ob_get_level()</a> &gt;= 1 )&#8221;. If so, we store the content into the cache. The next page that&#8217;s requested is served from our lightning-fast view cache. With view caching enabled, we were able to survive our perfect storm.</p>
<p>How could we make this even better? Well, nginx has the ability to access memcached directly. It could directly check if a view cache existed, and if so, serve it. Apache would never start and it would be unbelievably fast. That would involve switching from XCache to memcached, which is something we haven&#8217;t done thus far, but may do in the future.</p>
<p>That concludes my series on caching. Fin.</p>
]]></content:encoded>
			<wfw:commentRss>http://online.yaledailynews.com/2008/09/28/caching-part-5-view-caching/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Caching Part 4: Switching to Memory</title>
		<link>http://online.yaledailynews.com/2008/08/24/caching-part-4-switching-to-memory/</link>
		<comments>http://online.yaledailynews.com/2008/08/24/caching-part-4-switching-to-memory/#comments</comments>
		<pubDate>Sun, 24 Aug 2008 18:11:04 +0000</pubDate>
		<dc:creator>Webmaster</dc:creator>
		
		<category><![CDATA[Caching]]></category>

		<category><![CDATA[Technical]]></category>

		<guid isPermaLink="false">http://online.yaledailynews.com/?p=24</guid>
		<description><![CDATA[This post is the third in my series about how we using caching here at the Yale Daily News. If you haven&#8217;t had a chance to read parts one, two and three, you should check them out first.
So as I&#8217;ve described, we got a system going where we were caching our data. Yes, we weren&#8217;t [...]]]></description>
			<content:encoded><![CDATA[<p>This post is the third in my series about how we using caching here at the Yale Daily News. If you haven&#8217;t had a chance to read parts <a href="http://online.yaledailynews.com/2008/07/24/caching-an-introduction/">one</a>, <a href="http://online.yaledailynews.com/2008/08/05/caching-part-2-making-it-easy/">two</a> and <a href="http://online.yaledailynews.com/2008/08/14/caching-expiring-caches/">three</a>, you should check them out first.<span id="more-24"></span></p>
<p>So as I&#8217;ve described, we got a system going where we were caching our data. Yes, we <a href="http://online.yaledailynews.com/2008/08/14/caching-expiring-caches/">weren&#8217;t expiring caches very well</a>, but the system was working and helping reduce load our on our server, so we were somewhat out of the woods with regards to our performance problems. It was at that point that we realized that somewhere along the way, <a href="http://en.wikipedia.org/wiki/EAccelerator">eAccelerator</a> had been disabled on our server.</p>
<p>Let me explain the purpose of eAccelerator. eAccelerator is what is known as a PHP accelerator. <a href="http://en.wikipedia.org/wiki/EAccelerator">PHP</a> is different from languages like <a href="http://en.wikipedia.org/wiki/C_Sharp_(programming_language)">C#</a> and <a href="http://en.wikipedia.org/wiki/Java_(programming_language)">Java</a> because it is <a href="http://en.wikipedia.org/wiki/Interpreted_language">interpreted</a>, not <a href="http://en.wikipedia.org/wiki/Compiled_language">compiled</a>. When you write a C# program, you use what is known as a compiler (probably Visual Studio, though there are others). The compiler takes your source code and turns it into object code (also called <a href="http://en.wikipedia.org/wiki/Bytecode">bytecode</a>), which can be more easily and quickly interpreted by the computer.</p>
<p>PHP is an interpreted language, like <a href="http://en.wikipedia.org/wiki/Python_(programming_language)">Python</a> and <a href="http://en.wikipedia.org/wiki/Ruby_(programming_language)">Ruby</a>. Instead of having a compiler that creates an executable with bytecode (the stuff that&#8217;s easy for a machine to read), when you visit a Web page, the PHP process starts up and goes through each line of the source code, compiling it to bytecode for every request. Doing this is complex and just like retrieving data from the database, ripe for caching.</p>
<p>eAccelerator lets the PHP interpreter run once, then saves the bytecode into memory. The next time a PHP script needs to be run, eAccelerator retrieves the bytecode from memory, and the PHP interpreter doesn&#8217;t need to run and do the complex parsing it normally would have to. Much faster – <a href="http://en.wikipedia.org/wiki/PHP_accelerator">according to Wikipedia</a>, 2-10 times as fast!</p>
<p>Well, somehow our eAccelerator installation on our convoluted <a href="http://online.yaledailynews.com/2008/05/03/slice-and-dice/">cPanel setup</a> had stopped working, and we needed to get another solution working. We found <a href="http://xcache.lighttpd.net/">XCache</a>, from the makers of <a href="http://www.lighttpd.net/">Lighttpd</a>. (At the time, eAccelerator hadn&#8217;t had a release in quite some time, whereas XCache development was quite active. For the record, I also hear good things about <a href="http://pecl.php.net/apc">APC</a>.) XCache solved our PHP accelerator woes, which was good.</p>
<p>However, it also offered variable caching. Instead of serializing data and saving it to a file, like our solution was doing at the time, we could instead pass in the data we wanted to save to a special <a href="http://xcache.lighttpd.net/wiki/XcacheApi">XCache function</a>. It would handle serializing the data and saving that data into its memory cache. There were also functions for retrieving the data, checking if it was expired, etc.</p>
<p>This was great! Let me explain why. I&#8217;m a &#8220;computing assistant&#8221; at Yale, which means I help students fix their computers and support their software and hardware issues. I&#8217;m very frequently asked about upgrading their computers or buying new ones. Whenever I give advice, I always recommend buying more RAM, as much as they can get. If you understand that accessing RAM is faster than accessing the disk, you can skip the following sidebar. But, if you&#8217;d like a hopefully easy to understand explanation, here&#8217;s how I explain why RAM is good to non-technical people:</p>
<blockquote><p>Your processor is the brains of the computer. It&#8217;s very good at taking instructions and doing them very quickly. The hard part is getting instructions to it. It has some storage space available on it (CPU caches). Getting information from there is super fast, but there&#8217;s not much space, just a couple megabytes. Think of it like your desk – it&#8217;s very quick to reach over and grab the stapler, but there&#8217;s not enough room to store an elephant. The CPU generally keeps things it needs very frequently in this space, often the most basic parts of your operating system.</p>
<p>Then there&#8217;s your RAM. I recommend getting at least a couple gigabytes, which is many times more than what&#8217;s available right on your processor. You could fit almost anything in there, and it&#8217;s very fast. Think of it like your house – enough space for a couple elephants, and pretty quick to get something and bring it back to your desk.</p>
<p>The absolute last resort is your hard drive. Think of it like the United States of America. It&#8217;s massive – you can fit anything you want on here, and if you run out of room, just annex Canada (buy another hard drive for more space). However, it&#8217;s very very slow. You could have to drive across the entire country to pick up your elephant! That is why when your processor receives something, it tries to put it in your RAM. If you have lots of RAM, it can keep most of what it needs in there. If it runs out of room, then it has to go to your hard drive. Not good.</p></blockquote>
<p>That was a long way to say that hitting the hard disk is all bad. With our file-based caching, we had to hit the hard drive for every caching operation! Instead, we could use XCache&#8217;s variable caching, which exists in memory. Now our caching operations read from and write to memory, which is much faster.</p>
<blockquote><p>By far the biggest player in the memory caching space is <a href="http://www.danga.com/memcached/">memcached</a>. It&#8217;s the big dog. It was developed by Danga for use at Livejournal, and now it&#8217;s used by everybody from Facebook to Youtube. We considered it, but I read that it&#8217;s actually slower than XCache because Apache has to connect to another process, whereas XCache runs in-process. That being said, memcached offers some advantages for multi-server solutions (and some nifty interoperation with <a href="http://online.yaledailynews.com/2008/06/07/engine-what/">nginx</a>), so we may switch in the near future.</p></blockquote>
<p>How does XCache avoid taking up all of available memory? We set an amount of memory that it can use. If it fills that up, it deletes the least recently used cache object from the cache to make more room. It never uses more than the maximum we allow. This is called the <a href="http://en.wikipedia.org/wiki/Cache_algorithms">LRU cache algorithm</a>.</p>
<p>So there you have it: how and why we switched to memory caching. One more left, and it&#8217;s a big one: view caching.</p>
]]></content:encoded>
			<wfw:commentRss>http://online.yaledailynews.com/2008/08/24/caching-part-4-switching-to-memory/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Caching Part 3: Expiring Caches</title>
		<link>http://online.yaledailynews.com/2008/08/14/caching-expiring-caches/</link>
		<comments>http://online.yaledailynews.com/2008/08/14/caching-expiring-caches/#comments</comments>
		<pubDate>Thu, 14 Aug 2008 23:42:16 +0000</pubDate>
		<dc:creator>Robert Baskin (Online Director)</dc:creator>
		
		<category><![CDATA[Caching]]></category>

		<category><![CDATA[Technical]]></category>

		<guid isPermaLink="false">http://online.yaledailynews.com/?p=22</guid>
		<description><![CDATA[This post is the third in my series about how we using caching here at the Yale Daily News. If you haven&#8217;t had a chance to read parts one and two, you should check them out first.
So as promised in parts one and two, this post will be about expiring caches. You want to know [...]]]></description>
			<content:encoded><![CDATA[<p>This post is the third in my series about how we using caching here at the Yale Daily News. If you haven&#8217;t had a chance to read parts <a href="http://online.yaledailynews.com/2008/07/24/caching-an-introduction/">one</a> and <a href="http://online.yaledailynews.com/2008/08/05/caching-part-2-making-it-easy/">two</a>, you should check them out first.<span id="more-22"></span></p>
<p>So as promised in parts one and two, this post will be about expiring caches. You want to know the secret of how we expire caches here at the YDN? The super-surreptitious algorithm? The answer is &#8230; we don&#8217;t!</p>
<p>Well, ok, it&#8217;s not exactly that we don&#8217;t expire caches. They do expire eventually. When we set the cache, we also set a time we want to expire. That could be 10 minutes from now, two hours from now or a day from now. Until that time, the caching helper will retrieve the data from the cache. When that time rolls around, it decides that the data is no longer valid and will regenerate the cache object.</p>
<p>What I mean by &#8220;We don&#8217;t expire caches&#8221; is that when editors make changes on the site, the relevant caches are not expired automatically. For example, let&#8217;s say an editor adds an article to the current issue. Ideally, it would show up immediately on the front page of the site. But since the data for the front page is cached, that article won&#8217;t show up immediately. It will have to wait until that cache expiration time rolls around; then it will show up.</p>
<p>Now, we do have a link that clears the entire cache. That invalidates all of the cache objects, so the entire site will be regenerated. We use that fairly often when we want updates to be reflected on the site immediately. But it&#8217;s not the greatest solution. Regenerating those caches is expensive; thus, clearing all of them is expensive. That&#8217;s why when we&#8217;re <a href="http://online.yaledailynews.com/2008/04/25/the-day-the-music-died/">under load</a>, I send out a &#8220;friendly&#8221; reminder saying &#8220;DON&#8217;T CLEAR THE **** CACHE!&#8221;</p>
<p>So why don&#8217;t we clear caches automatically? I know this is somewhat of a copout &#8230; but because it&#8217;s really really hard. This is mostly because of the way Cake returns the data from the model and how we&#8217;ve architected our caching system. The model returns an array of all of the relevant data. For example, our cache object for the front page looks something like this:<br />
<code><br />
Array {<br />
<span> </span>Issue { id:1,<br />
<span> </span><span> </span>Article {<br />
<span> </span><span> </span><span> </span>{id:1},<br />
<span> </span><span> </span><span> </span>{id:12345}<br />
<span> </span><span> </span>}<br />
<span> </span>}<br />
}<br />
</code><br />
We save that array as our cache object. Now, let&#8217;s say an editor makes a change to article 12345. We would have to expire this cache. We&#8217;d have to expire the article page&#8217;s cache. Those are easy. But let&#8217;s say they change the headline. Is it in the most popular box? We&#8217;d have to expire that cache. Is it attached to a slideshow and cached in that slideshow&#8217;s cache object? As you can see, since our data is stored in so many cache objects, trying to expire them all doesn&#8217;t scale as a manual process.</p>
<p>One solution I&#8217;ve seen is to store in a separate field some metadata about the data in the cache object. So for the example above, store &#8220;issue_1, article_2, article_12345&#8243;. Then you can search through those to find which cache objects are holding things you need to expire. But that&#8217;s fairly slow and difficult to implement.</p>
<p>So we&#8217;ve basically decided that we&#8217;re ok having some data lag. Ideally, we&#8217;d have caches expire immediately. Maybe in the next complete rewrite? <img src='http://online.yaledailynews.com/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' /> </p>
<p>Up next: switching to memory caching and view caching</p>
]]></content:encoded>
			<wfw:commentRss>http://online.yaledailynews.com/2008/08/14/caching-expiring-caches/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Inside the Dragon</title>
		<link>http://online.yaledailynews.com/2008/08/08/inside-the-dragon/</link>
		<comments>http://online.yaledailynews.com/2008/08/08/inside-the-dragon/#comments</comments>
		<pubDate>Fri, 08 Aug 2008 22:08:52 +0000</pubDate>
		<dc:creator>Andrew Mangino (Editor-in-Chief)</dc:creator>
		
		<category><![CDATA[Content]]></category>

		<guid isPermaLink="false">http://online.yaledailynews.com/?p=21</guid>
		<description><![CDATA[8/8/08 is upon us: Today, delegations from over 200 countries gather in Beijing to usher in the XXIX Olympiad. As the world turns expectantly toward China, nine Yale Daily News reporters and photographers are on site to witness and chronicle the spectacular Opening Ceremony — and all that will follow — in an in-depth team-blogging [...]]]></description>
			<content:encoded><![CDATA[<p>8/8/08 is upon us: Today, delegations from over 200 countries gather in Beijing to usher in the XXIX Olympiad. As the world turns expectantly toward China, nine Yale Daily News reporters and photographers are on site to witness and chronicle the spectacular Opening Ceremony — and all that will follow — in an in-depth team-blogging venture unprecedented for a college publication gone abroad.<span id="more-21"></span></p>
<p>In fact, these reporters have already posted to our new blog, &#8220;Inside the Dragon,&#8221; which you can visit at <a href="http://yaledailynews.com/blogs/insidethedragon">http://yaledailynews.com/blogs/insidethedragon</a>. Entries will focus not as much on the sporting events as on Chinese culture, security, economics, food, preparation, youth, politics and (of course) Yale&#8217;s close friendship with the emerging superpower. But, don&#8217;t worry, with one current junior and five Yale alumni competing in rowing, sailing and fencing, our reporters cannot possibly overlook the competition itself.</p>
<p>Reporting projects such as &#8220;Inside the Dragon&#8221; are inspired and driven by reader contributions. So please, don&#8217;t hesitate to contact me anytime at <a href="mailto:editor@yaledailynews.com">editor@yaledailynews.com</a> with ideas of your own. See you in the fall, when more exciting news from the Oldest College Daily will come your way.</p>
<p>Sincerely,<br />
Andrew Mangino<br />
Editor in Chief</p>
]]></content:encoded>
			<wfw:commentRss>http://online.yaledailynews.com/2008/08/08/inside-the-dragon/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Caching Part 2: Making It Easy</title>
		<link>http://online.yaledailynews.com/2008/08/05/caching-part-2-making-it-easy/</link>
		<comments>http://online.yaledailynews.com/2008/08/05/caching-part-2-making-it-easy/#comments</comments>
		<pubDate>Tue, 05 Aug 2008 17:57:48 +0000</pubDate>
		<dc:creator>Robert Baskin (Online Director)</dc:creator>
		
		<category><![CDATA[Caching]]></category>

		<category><![CDATA[Technical]]></category>

		<guid isPermaLink="false">http://online.yaledailynews.com/?p=20</guid>
		<description><![CDATA[It&#8217;s time for the second post in my series about caching and how we use it here at the Yale Daily News. If you haven&#8217;t read the first part, you should: It introduces the series and some important concepts.
In the first post, I posted some sample code of something we do quite frequently: check for [...]]]></description>
			<content:encoded><![CDATA[<p>It&#8217;s time for the second post in my series about caching and how we use it here at the Yale Daily News. If you haven&#8217;t read <a href="http://online.yaledailynews.com/2008/07/24/caching-an-introduction/">the first part</a>, you should: It introduces the series and some important concepts.<span id="more-20"></span></p>
<p>In the first post, I posted some sample code of something we do quite frequently: check for the existence of a cache object, if it&#8217;s set then get it, or if it&#8217;s not set, set it and then get it. We do this all the time, basically every single time we use our caching system. To avoid having to type out all of that code every single time, I wanted to create a function to wrap that logic.</p>
<p>I&#8217;m not very imaginative, so I called the function doCache. It takes three parameters: a name, <a href="http://en.wikipedia.org/wiki/Anonymous_function">an anonymous function</a>, an object and an expiration time. The signature looks like this:</p>
<p><code>function doCache($name, $func, $object='', $cachetime)</code></p>
<p>Let&#8217;s go through each one of them. The name is simple: It&#8217;s just the name that you want to set for the cache object. It could be issue_index, article_1, author_1000, mostpopular, etc.</p>
<p>Now for $func and $object. At the time I was creating the caching system, I was also becoming more advanced with JavaScript (which is really a fantastic language that gets a bad rap because of the problems browsers have with the <a href="http://en.wikipedia.org/wiki/Document_Object_Model">DOM</a>, but that&#8217;s another discussion). In JavaScript, you can pass anonymous<br />
functions as parameters into other functions and they can be used. I tried to adapt this for PHP.</p>
<blockquote><p>Something I&#8217;ve wrestled with in writing this blog is defining a target audience. For now, I&#8217;ve decided to expect readers to have a fairly good technical background. While I will try and explain the concepts relevant to what I&#8217;m talking about as best as I can, I will sometimes link to explanations for those that are too broad to go into detail in a blog post. For example, I linked to an explanation of anonymous functions above. If you aren&#8217;t familiar with them, you should read the linked page then keep reading this post – it may make more sense.</p></blockquote>
<p>The best PHP offers is <a href="http://www.php.net/create_function">create_function</a>, which is a feeble stab at anonymous functions. The function takes two parameters: arguments and the code to execute inside the function. For example, let&#8217;s take the following code:</p>
<p><code>create_function('$model', 'return $model-&gt;getHomePage();')</code></p>
<p>That parameter is passed into doCache in the parameter $func. You also need an object to pass into the anonymous function so you can use it. That&#8217;s the third parameter of doCache. It&#8217;s frequently a <a href="http://en.wikipedia.org/wiki/Model-view-controller">model object</a> – in this case, it&#8217;s the Issue Model, which retrieves the data for the home page. (I <a href="http://online.yaledailynews.com/2008/07/24/caching-an-introduction/">explain models a bit</a> in the first caching post.)</p>
<p>So now inside doCache, if you call $func($object), $object is passed into that function as $model. The second parameter of create_function is the code you want to execute. So the getHomePage function of the $object, which as we said above is the Issue model, is called. So, if you had the code &#8220;$data = $func($object);&#8221;, $data would be set to the results of the getHomePage function.</p>
<blockquote><p>There are two reasons why create_function is pretty bad. First, instead of just passing in a function, you need to pass in parameters to a helper function (the create_function function), which is unnecessary. That results in a second frustration point: If you have any quotes in your code, you have to escape them. Otherwise, they terminate the outer quotes of the second parameter, which are holding all of the code being passed into the second parameter of create_function. Very annoying.</p></blockquote>
<p>The final parameter is $cacheTime, which is how long we want the cache object to live. My next post will discuss expiring caches, but basically, it indicates how long a $cacheHelper-&gt;get() should return the data before that object is considered invalid and must be generated again. In this example, it&#8217;s set to a default, but you could pass in a different value. I&#8217;ll get into it more next time.</p>
<p>So how does the final function look? Let&#8217;s check it out:<br />
<code><br />
function doCache($name, $func, $object='', $cachetime=$default) {<br />
<span>&nbsp;&nbsp;&nbsp;&nbsp;</span>if ( $this->issetCache($name, $cachetime) ) {<br />
<span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span>&nbsp;&nbsp;&nbsp;&nbsp;</span>return $this->getCache($name);<br />
<span>&nbsp;&nbsp;&nbsp;&nbsp;</span>}<br />
<span>&nbsp;&nbsp;&nbsp;&nbsp;</span>else {<br />
<span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span>&nbsp;&nbsp;&nbsp;&nbsp;</span>$data = $func($object);<br />
<span>&nbsp;&nbsp;&nbsp;&nbsp;</span><span>&nbsp;&nbsp;&nbsp;&nbsp;</span>return $this->setCache($name, $data, $cachetime);<br />
<span>&nbsp;&nbsp;&nbsp;&nbsp;</span>}<br />
}<br />
</code><br />
And a sample call:<br />
<code><br />
$data = $this-&gt;Cache-&gt;doCache('issue_index', create_function('$model', 'return $model-&gt;getHomePage();'), $issueModel);<br />
</code><br />
That one line of code makes our caching system work. All we had to do was go through our code, find all of the slow function calls (since we try and practice <a href="http://weblog.jamisbuck.org/2006/10/18/skinny-controller-fat-model">fat modeling</a>, we often had just one function call to get all of the data) and wrap them in doCache. That helped ease the process of implementing the caching system across the site.</p>
<p>Up next: expiring caches, switching from file system to memory caching and view caching (the jackpot).</p>
]]></content:encoded>
			<wfw:commentRss>http://online.yaledailynews.com/2008/08/05/caching-part-2-making-it-easy/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Caching: An Introduction</title>
		<link>http://online.yaledailynews.com/2008/07/24/caching-an-introduction/</link>
		<comments>http://online.yaledailynews.com/2008/07/24/caching-an-introduction/#comments</comments>
		<pubDate>Thu, 24 Jul 2008 20:49:52 +0000</pubDate>
		<dc:creator>Robert Baskin (Online Director)</dc:creator>
		
		<category><![CDATA[Caching]]></category>

		<category><![CDATA[Technical]]></category>

		<guid isPermaLink="false">http://online.yaledailynews.com/?p=19</guid>
		<description><![CDATA[Well, it&#8217;s finally time to talk about how we use caching here at the Yale Daily News. I initially promised a post, but after starting this first one and realizing how long it was getting, it will now be a series of at least two and maybe more posts! Allllright!
When I started making the Yale [...]]]></description>
			<content:encoded><![CDATA[<p>Well, it&#8217;s finally time to talk about how we use caching here at the Yale Daily News. I initially promised a post, but after starting this first one and realizing how long it was getting, it will now be a series of at least two and maybe more posts! Allllright!<span id="more-19"></span></p>
<p>When I started making the Yale Daily News site, I had very little experience working with caching. Before, I really only had experience with small-scalle Web sites. The YDN operates on a <a href="http://online.yaledailynews.com/2008/04/25/the-day-the-music-died/">slightly different scale</a>. In order to handle that kind of traffic, we&#8217;ve had to work very hard to implement many <a href="http://online.yaledailynews.com/2008/03/31/making-the-site-faster/">performance</a> <a href="http://online.yaledailynews.com/2008/06/07/engine-what/">enhancements</a>. This post describes how caching has saved our site numerous times.</p>
<p>Our performance problems began in February 2007, just a few short weeks after the current version of the site launched. I naively thought that bandwidth and disk space were the only metrics that mattered. That&#8217;s not the case! CPU usage and memory are very important to handling lots of visitors to your site! That may sound extremely obvious, but if you&#8217;ve only worked on shared hosts before, you might not think about that. We were on <a href="http://dreamhost.com">Dreamhost</a>, which I had used successfully for many smaller sites. But the YDN site is large and complex, and generating each page is resource-intensive. We were Drudged in early February, and Dreamhost notified us that we had outgrown our account and needed to move.</p>
<p>I then decided to order a dedicated server from <a href="http://softlayer.com">Softlayer</a>. We moved to that server, which was much more powerful, and ran acceptably there. Our <a href="http://www.teamquest.com/resources/gunther/display/5/">load</a> was still fairly high, hovering between 1.5 and 2, but we weren&#8217;t crashing. However, as we added more features (and thus more complexity) to our pages, performance became a more serious problem. The traffic our <a href="http://yaledailynews.com/blogs/chinablog">China blog</a> attracted in May 2007 exposed these problems even further, and I had to reboot our server more than once after excessive traffic made it crash. We needed caching badly.</p>
<p>Summer finally arrived, and though I was busy with an internship, I had some time to turn my attention to caching. I actually attended a <a href="http://superhappydevhouse.org/">SuperHappyDevHouse</a> event in late June 2007 (I&#8217;ve never felt so geeky in my life), and while sitting there watching people hack in Lisp and Ocaml, I came up with the initial caching implementation for the Yale Daily News.</p>
<p>At first, I implemented data caching. At the time, we were running every SQL query on every page. While they were often very fast, there were frequently quite a few of them. (Such is the downfall of using an <a href="http://en.wikipedia.org/wiki/Object-relational_mapping">ORM</a> like <a href="http://cakephp.org">CakePHP</a> provides for us – it makes development much easier but at the cost of often doing more than you need in order to accommodate all the use cases a framework needs to support.) Additionally, there are some queries we run (for example, getting the <a href="http://yaledailynews.com/mostpopular">Most Popular</a> stories or <a href="http://yaledailynews.com/authors/view/24#frequentCoAuthors">Frequent Coauthors</a>), that are expensive and time-consuming. My goal was to cache the results of those queries.</p>
<p>What does that mean? Let&#8217;s take the home page as an example, as it&#8217;s the most-visited page, one of the most complex, and the one where I started. There is one main data call to the model that returns an array of all the data we need to generate the page. (A model is part of the MVC pattern and is in charge of modeling data, often a table in a database. If you&#8217;re not familiar with that, just understand that we called a function that queried the database for all of the data we need (issue, articles, authors, photos, etc.) and returned an array of all of it.) The goal is to run all of that code and SQL queries once, save the result, and then use the cache for subsequent views instead of running the complex and time-intensive code necessary to generate the results. How does that work? Let&#8217;s check out some example code:</p>
<p>if ($cacheHelper-&gt;isset(&#8217;indexData&#8217;)) {</p>
<p>$data = $cacheHelper-&gt;get(&#8217;indexData&#8217;);</p>
<p>}</p>
<p>else {</p>
<p>$data = $model-&gt;getData();</p>
<p>$cacheHelper-&gt;set(&#8217;indexData&#8217;, $data);</p>
<p>}</p>
<p>Let&#8217;s break that down line by line. $cacheHelper is a class I created to wrap all of the necessary caching logic. It has a function called isset(). That function checks the cache to see if that cache object exists. At first, we were using file system caching. That meant that we stored each cache object as a file in a directory. So for that example, we stored the data in a file called &#8220;indexData&#8221;. So isset() would check to see if there was a file called &#8220;indexData&#8221; in the cache directory. If there was, the get() function in the cacheHelper would read that file, and the code would store the results into data. If the file didn&#8217;t exist, the model would run its expensive getData() function, and then the cacheHelper&#8217;s set() function would create the file indexData and store the data in that file. The next time the code would run, that file would now exist, and the expensive $model-&gt;getData() line would never be reached.</p>
<blockquote><p>What exactly was stored in the cache file? We store the results of PHP&#8217;s <a href="http://us.php.net/serialize">serialize()</a> function into the cache object. When we read the cache object, we call <a href="http://us.php.net/manual/en/function.unserialize.php">unserialize()</a> on the data. 99 percent of the time, we store an array, which is how our database data is returned to use by the model.</p></blockquote>
<p>We put this caching logic into several important places on the site – the index page, the article page, the author page, etc., and saw massive improvements! Now, instead of running queries and sometimes taking seconds (years in Web server time) to generate the page, it would take well less than a second! Load dropped dramatically! Major improvements!</p>
<p>This is the first in the caching series. It got long so I decided to split it up. Look for future posts about the following:</p>
<p>•         Wrapping that code up above into one function and how that exposes PHP&#8217;s lameness<br />
•         How we handle expiring caches<br />
•         Changing from file system caching to memory caching<br />
•         View caching – the motherload</p>
]]></content:encoded>
			<wfw:commentRss>http://online.yaledailynews.com/2008/07/24/caching-an-introduction/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Tabbed Boxes and JavaScript!</title>
		<link>http://online.yaledailynews.com/2008/07/11/tabbed-boxes-and-javascript/</link>
		<comments>http://online.yaledailynews.com/2008/07/11/tabbed-boxes-and-javascript/#comments</comments>
		<pubDate>Fri, 11 Jul 2008 05:08:12 +0000</pubDate>
		<dc:creator>Michael Boyce (Online Staff)</dc:creator>
		
		<category><![CDATA[Technical]]></category>

		<guid isPermaLink="false">http://online.yaledailynews.com/?p=18</guid>
		<description><![CDATA[Certain visual elements are repeated frequently on a Web site. On our site, tabs are frequently used as a clean and simple way of displaying related but slightly different information. You can see our implementations on the calendar, crime map and most popular side bar. To make tabs easier to put on our site, especially [...]]]></description>
			<content:encoded><![CDATA[<p>Certain visual elements are repeated frequently on a Web site. On our site, tabs are frequently used as a clean and simple way of displaying related but slightly different information. You can see our implementations on the <a href="http://yaledailynews.com/calendar">calendar</a>, <a href="http://yaledailynews.com/crimes">crime map</a> and <a href="http://yaledailynews.com/#mostpopularbox">most popular side bar</a>. To make tabs easier to put on our site, especially with their smooth JavaScript implementation that doesn’t require refreshing, we wanted to have a tab template system.<span id="more-18"></span></p>
<p>Our model for a tab is simple. There is a wrapper with the class “tab” for the entire box, and then for each actual tab it is in the class “subtab.” Each subtab has a header with the class “subtabtitle” to display the title of the tab, and one more div with the class “subtabbody,” for the content shown when a tab is clicked. It looks like this:</p>
<p>&lt;div class = &#8220;tab&#8221;&gt;</p>
<p style="padding-left: 30px;">&lt;div class = &#8220;subtab&#8221;&gt;</p>
<p style="padding-left: 60px;">&lt;h3 class= &#8221; subtabtitle&#8221;&gt; Title 1 &lt;/h3&gt;</p>
<p style="padding-left: 60px;">&lt;div class =&#8221; subtabbody&#8221;&gt; text in the first tab&lt;/div&gt;</p>
<p style="padding-left: 30px;">&lt;/div&gt;</p>
<p style="padding-left: 30px;">&lt;div class = &#8220;subtab&#8221;&gt;</p>
<p style="padding-left: 60px;">&lt;h3 class= &#8221; subtabtitle&#8221;&gt; Title 1 &lt;/h3&gt;</p>
<p style="padding-left: 60px;">&lt;div class =&#8221; subtabbody&#8221;&gt; text in the second tab&lt;/div&gt;</p>
<p style="padding-left: 30px;">&lt;/div&gt;</p>
<p>&lt;/div&gt;</p>
<p>The real problem is how one can use this basic design layout and convert it into our standard tab look and feel. The trick is to use JavaScript to traverse and modify the DOM (document object model) or, simply put, to rearrange the HTML. One of the best JavaScript tools to do this is <a href="http://prototypejs.org">Prototype</a>. Prototype is easily installed and adds a host of more powerful functions to the standard JavaScript library. The most useful ones were related to tranversing the DOM. Two that I found particularly useful were the <a href="http://www.prototypejs.org/api/element/up">up</a> and <a href="http://www.prototypejs.org/api/element/down">down</a> functions. For example, if I am current at the subtab class element I can easily move to the h3 by typing:</p>
<p>var header = subTab.down(“h3.subtabtitle”)</p>
<p>I can make header move back up just as easily with:</p>
<p>header = header.up(“div.subtab”);</p>
<p>With this header variable one can use other great JavaScript and Prototype functions. One I used frequently was <a href="http://www.prototypejs.org/api/element/setStyle">setStyle</a>. For example, say I wanted to change the subtab class to be a font size of 12px, I could just write:</p>
<p>header.setStyle({<br />
fontSize: ‘12px’<br />
});</p>
<p>The one difficulty with using Prototype is that the Prototype functions return an object that uses both standard and Prototype JavaScript functions. However, standard JavaScript functions don’t return these objects. Therefore it is important to remember when you are working with a Prototype object and when you are using a plain JavaScript one. To solve this problem, be sure to wrap any variables with $(foo) to convert it to a Prototype element. Combining some prototype functions (the <a href="http://prototypejs.org/api">API</a> is fairly helpful) with crucial DOM functions such as removeChild() and appendChild(). Also setStyle() function was very handy when I wanted to generate precise CSS on the fly (for example if there are so many tabs that they fill a line of the div, a new row of tabs are automatically created).</p>
<p>A final issue was that the JavaScript originally overlaid all the tab content but using CSS hid the content that wasn’t active. This was a problem when forms were in the content, because sometimes multiple empty or default forms would be submitted that the user could see. To solve the problem we set up a dummy property in each element that stored the HTML but wasn’t displayed on the page but could still be called on for retrieving content. This meant that the forms were actually not just hidden by the CSS; they were only added to the DOM when the user clicked on the appropriate tag and the old content was cleared.</p>
]]></content:encoded>
			<wfw:commentRss>http://online.yaledailynews.com/2008/07/11/tabbed-boxes-and-javascript/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Engine What?</title>
		<link>http://online.yaledailynews.com/2008/06/07/engine-what/</link>
		<comments>http://online.yaledailynews.com/2008/06/07/engine-what/#comments</comments>
		<pubDate>Sat, 07 Jun 2008 20:00:16 +0000</pubDate>
		<dc:creator>Robert Baskin (Online Director)</dc:creator>
		
		<category><![CDATA[Technical]]></category>

		<guid isPermaLink="false">http://online.yaledailynews.com/?p=17</guid>
		<description><![CDATA[So finally we come to the Server header, which I have alluded to previously. When you make a request to our server, we send back many &#8220;Headers&#8221; for each request before we send back the actual content you requested. They include things like Size and Expires headers. The best way to see all of the [...]]]></description>
			<content:encoded><![CDATA[<p>So finally we come to the Server header, which I have <a href="http://online.yaledailynews.com/2008/05/03/slice-and-dice/">alluded to</a> <a href="http://online.yaledailynews.com/2008/05/15/its-the-little-things-in-life/">previously</a>. <span id="more-17"></span>When you make a request to our server, we send back many &#8220;Headers&#8221; for each request before we send back the actual content you requested. They include things like Size and <a href="http://online.yaledailynews.com/2008/03/31/making-the-site-faster/">Expires headers</a>. The best way to see all of the headers, and much more information about how your browser loads our page, is with the <a href="http://www.getfirebug.com/">Firebug extensio</a>n for <a href="http://www.getfirefox.com">Firefox</a>. The Net tab will show you each request as it&#8217;s made to our server - how long it took, the headers it returned and the content we sent back. Examples of requests are for the main HTML page, each CSS and JS file, images and Flash objects.</p>
<p>Anyway, if you take a quick look, you&#8217;ll notice that our Server header is &#8220;nginx/0.5.26&#8243;. (Note: Don&#8217;t look on this blog; it&#8217;s not hosted on our production server. Look on <a href="http://yaledailynews.com">YaleDailyNews.com</a>.) Anyway, you might notice that&#8217;s not one of the two servers that run the vast majority of Web sites out there on the Internet - <a href="http://httpd.apache.org/">Apache</a> and <a href="http://www.iis.net/default.aspx?tabid=1">Internet Information Services</a> (IIS). We used to send back Apache headers, but what is nginx?</p>
<p>To quote the <a href="http://wiki.codemongers.com/Main">English version of its documentation</a> (it&#8217;s a <a href="http://nginx.net/">Russian product</a>), &#8220;Nginx (pronounced &#8216;engine x&#8217;) is a free, open-source, high-performance HTTP server and reverse proxy, as well as an IMAP/POP3 proxy server.&#8221; Essentially, nginx is a FAST Web server. It&#8217;s great at handling requests for static files - CSS, JS, images, etc. Its downside is that it&#8217;s not very good at serving requests for dynamic pages: anything that involves parsing server-side code, which is PHP in our case.</p>
<p>So we decided to use nginx in what&#8217;s called a <a href="http://blog.kovyrin.net/2006/05/18/nginx-as-reverse-proxy/">reverse-proxy</a>. When you make a request to our Web server, nginx handles that request. It checks to see if the request is for a static file. If it is, it serves the file. That&#8217;s much faster than Apache! Under the old system, Apache was serving both dynamic requests, which it&#8217;s good at, and static requests, which in a LAMP configuration it is not very good at. In order to serve simple CSS files, it had to use its heavy PHP-interpreter-laden process to serve that request, which is quite memory-intensive and relatively slow. Nginx is much faster.</p>
<p>But what about dynamic requests? After checking to see if the request is static and serving it if so, nginx passes on all other requests to Apache, which is better at handling PHP requests. Apache is running on our server on another port, so nginx passes the request to localhost on that port. Apache sees that request, generates the page using PHP and passes the content back to nginx, which sends it back to you.</p>
<p>I wish we had better numbers, but we didn&#8217;t really do as much benchmarking as we should have. One of our main bottlenecks is memory (especially now that we&#8217;re doing view caching, which will be the subject of my next post), and nginx uses much less memory than Apache, so we&#8217;re able to serve more requests without overloading our server. I&#8217;d estimate a 20 percent increase in requests per second.</p>
<p>The other alternative we considered was moving our static assets to <a href="http://www.amazon.com/S3-AWS-home-page-Money/b?ie=UTF8&amp;node=16427261">Amazon S3</a>. However, in order to do so, we would have to do some reworking in our code to point to the right images location, we&#8217;d have to develop a deployment process to synchronize to S3 and we&#8217;d have to pay for Amazon&#8217;s bandwidth costs. We may revisit the idea in the future though, as well as considering moving to <a href="http://aws.amazon.com/ec2">Amazon EC2</a> for hosting as well. However, for the time being, nginx was an easy and helpful solution.</p>
<p>Sorry about the delay. Posts should come more regularly now. I&#8217;m aiming for one every week, but don&#8217;t hold me to it!</p>
]]></content:encoded>
			<wfw:commentRss>http://online.yaledailynews.com/2008/06/07/engine-what/feed/</wfw:commentRss>
		</item>
		<item>
		<title>It&#8217;s the Little Things in Life</title>
		<link>http://online.yaledailynews.com/2008/05/15/its-the-little-things-in-life/</link>
		<comments>http://online.yaledailynews.com/2008/05/15/its-the-little-things-in-life/#comments</comments>
		<pubDate>Thu, 15 May 2008 18:00:39 +0000</pubDate>
		<dc:creator>Robert Baskin (Online Director)</dc:creator>
		
		<category><![CDATA[Technical]]></category>

		<guid isPermaLink="false">http://online.yaledailynews.com/?p=16</guid>
		<description><![CDATA[I know I said my next post would have to do with our new Server header that we&#8217;re sending back, but I just wanted to get this in while it&#8217;s fresh in my mind. And plus, MySQL optimization is always worth talking about!
There are several places around our site where we use MySQL&#8217;s DATEDIFF function, [...]]]></description>
			<content:encoded><![CDATA[<p>I know <a href="http://online.yaledailynews.com/2008/05/03/slice-and-dice/">I said</a> my next post would have to do with our new Server header that we&#8217;re sending back, but I just wanted to get this in while it&#8217;s fresh in my mind. And plus, MySQL optimization is always worth talking about!<span id="more-16"></span></p>
<p>There are several places around our site where we use MySQL&#8217;s <a href="http://dev.mysql.com/doc/refman/5.0/en/date-and-time-functions.html#function_datediff">DATEDIFF function</a>, which calculates the number of days between two given times. One example is the <a href="http://www.yaledailynews.com/#mostpopularbox">&#8220;Most Popular&#8221; box</a>, which shows the most popular viewed, e-mailed and commented stories on our site. That works by retrieving stories posted within the last week sorted by the relevant count (hitcount, emailcount, or commentcount). If there aren&#8217;t 10 stories within the past seven days, it will go to 14 days to try and fill the box, then 21 and so on.</p>
<p>The query we were running had the following WHERE clause: DATEDIFF(NOW(), articles.dateposted) &lt; 7. That got back only articles posted in the last week. That worked like we wanted it to, but it was a bit slow. Instead of taking just a couple milliseconds, it would take about half a second. Remember that it has to do at least three queries to get all three types, and if it doesn&#8217;t find enough articles within the past week, it has to run the queries again with 14 days, then 21 etc. That really only happens over breaks though, when we&#8217;re not publishing daily. Regardless, even three queries taking half a second is a lot.</p>
<p>The problem was mitigated by the fact that we cache the results of our database calls. So we actually only generate that &#8220;Most Popular&#8221; box every couple hours, rather than for every request. So did I actually need to optimize it? Well why not! I enjoy do this kind of stuff, and it looked like a quick fix. I was reading some <a href="http://www.jpipes.com/index.php?/archives/231-Join-fu-The-Art-of-SQL-Tuning.html">slides called Join-Fu</a> by <a href="http://jpipes.com/">Jay Pipes</a>, who works for MySQL. Slide 17 was particularly relevant. He was talking about date calculations and optimizing them. I thought of our &#8220;Most Popular&#8221; box and set about optimizing it.</p>
<p>The problem with our query was that it could not take advantage of two important parts of what makes MySQL fast: indexes and query caching. First, apparently queries with functions operating on a column means it can&#8217;t use an index. (Indexes are kind of a large topic to get into in this post, so I&#8217;ll just direct you to <a href="http://dev.mysql.com/doc/refman/5.0/en/mysql-indexes.html">some reading on them</a>.) Not using indexes means the query runs much more slowly. To solve this problem, I followed his advice and rewrote the WHERE condition to be &#8220;articles.dateposted &gt; NOW() - INTERVAL 7 DAY,&#8221; using MySQL&#8217;s INTERVAL syntax. The time taken to execute the query dropped to around 100 milliseconds - 1/5 of what it was!</p>
<p>But because the query still had a function in it, MySQL has to evaluate the result when the query is executed and therefore couldn&#8217;t cache the results of the query. Again, query caching is a bit outside the scope of this post (<a href="http://dev.mysql.com/doc/refman/5.0/en/query-cache.html">more reading on it</a>), but at the basic level, MySQL caches the results of queries it executes, so the next time they&#8217;re run it serves the cached result instead of running the query again. I wanted to make our query use the query cache instead of having to be run every time. To do that, I just replaced the NOW() in the WHERE condition with today&#8217;s date as generated by PHP&#8217;s date function. So now the WHERE condition looks like &#8220;articles.dateposted &gt; &#8221; . date(&#8217;Y-m-d&#8217;) . &#8221; - INTERVAL 7 DAY&#8221;. That query takes about 100 milliseconds to run the first time, but one millisecond or less to run each subsequent time. Remember: MySQL&#8217;s query cache persists beyond requests, so it&#8217;s cached for as long as MySQL is running (or until MySQL expires it), not just until the end of PHP&#8217;s life at the end of the request.</p>
<p>Was the optimization worth it? Probably not, but it&#8217;s always nice to improve the speed of frequently run queries by orders of magnitude.</p>
]]></content:encoded>
			<wfw:commentRss>http://online.yaledailynews.com/2008/05/15/its-the-little-things-in-life/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Slice and Dice</title>
		<link>http://online.yaledailynews.com/2008/05/03/slice-and-dice/</link>
		<comments>http://online.yaledailynews.com/2008/05/03/slice-and-dice/#comments</comments>
		<pubDate>Sat, 03 May 2008 18:36:10 +0000</pubDate>
		<dc:creator>Robert Baskin (Online Director)</dc:creator>
		
		<category><![CDATA[Technical]]></category>

		<guid isPermaLink="false">http://online.yaledailynews.com/?p=15</guid>
		<description><![CDATA[So in my last post, I promised I would write up some of the work we planned to do to improve performance when we had a chance to actually implement the proposed changes. Well, we&#8217;ve done quite a bit of work in the past week. I&#8217;ll talk about some of the changes and leave some [...]]]></description>
			<content:encoded><![CDATA[<p>So in my last post, I promised I would write up some of the work we planned to do to improve performance when we had a chance to actually implement the proposed changes. Well, we&#8217;ve done quite a bit of work in the past week. I&#8217;ll talk about some of the changes and leave some for future posts.<span id="more-15"></span></p>
<p>First things first: We changed hosts. For the past year or so, we&#8217;ve been hosted on a dedicated box at <a href="http://softlayer.com">Softlayer</a>. I have nothing but good things to say about Softlayer - fair prices (a little higher than <a href="http://layeredtech.com">Layeredtech</a>, but competitive), good features, responsive support, etc. etc. But the server we purchased was the first dedicated server any of us had ever managed, and crud had started to accumulate and make things very difficult.</p>
<p>To begin with, we had Softlayer set up our box with <a href="http://www.cpanel.net/">CPanel</a>, which is sort f the industry standard when it comes to server management-control panels. The problem was that as we started to become more competent, we began changing things manually, and CPanel couldn&#8217;t stay in sync and thus was rendered effectively useless. I also had a sneaking suspicion that some of its &#8220;helpful&#8221; services that run to keep your server maintained were actually slowing us down. I definitely thought that it was time to move to a new<br />
server, one where we could start fresh.</p>
<p>After reading around (the <a href="http://webhostingtalk.com">Web Hosting Talk forums</a> are invaluable), I decided <a href="http://slicehost.com">Slicehost</a> would be a good place for us. (We also considered <a href="http://linode.com">Linode</a>.) As I alluded to in my previous post, getting a VPS offered several benefits over a dedicated server. First, Slicehost takes daily and weekly snapshot backups of our server, meaning we could restore to a working server easily if something every happened. Second, we could upgrade the size of our &#8220;slice&#8221; if we ever experienced a surge in traffic. I suggested it would take &#8220;a matter of minutes,&#8221; but as <a href="http://online.yaledailynews.com/2008/04/25/the-day-the-music-died/#comments">neodude pointed out</a>, it actually takes about 10-15 minutes. Regardless, being able to upgrade our server that easily is a very helpful feature.</p>
<p>Additionally, <a href="http://articles.slicehost.com">Slicehost&#8217;s tutorials</a> for setting up a LAMP server are excellent. We decided to install <a href="https://wiki.ubuntu.com/GutsyGibbon">Ubuntu Gutsy</a> on our slice, and using the magic of <a href="http://en.wikipedia.org/wiki/Advanced_Packaging_Tool">apt-get</a>, we had everything we needed set up in just a couple hours (much of that learning how to do some fairly basic things). Most importantly, the server was fresh and entirely configured by us: We could make changes easily and keep everything organized how we wanted to. It is great.</p>
<p>If you have some Linux competency, you should consider setting up your own server. With package managers these days, you don&#8217;t need to worry about compiling, just configuring, and even the defaults are generally suitable, so it&#8217;s very easy.</p>
<p>There are other fantastic features of Slicehost. Although not actually run by the company, the members of the <a href="irc://irc.freenode.net/slicehost">Slicehost IRC channel </a>have been extremely helpful. Also, Slicehost provides a SSH console in their Web manager that you can use to log in to the server if SSH stops working for some reason. (Like, for example, forgetting to leave your SSH port open in the iptables.)</p>
<p>We&#8217;ve made some other tweaks to make our Web site run faster. (Hint: look at the &#8220;Server&#8221; header we&#8217;re sending back.) The goal is to survive a Drudging or something similar. I will have more information in future posts about some more of what we&#8217;re doing.</p>
]]></content:encoded>
			<wfw:commentRss>http://online.yaledailynews.com/2008/05/03/slice-and-dice/feed/</wfw:commentRss>
		</item>
	</channel>
</rss>

<!-- Dynamic Page Served (once) in 1.538 seconds -->
<!-- Cached page served by WP-Cache -->
