<?xml version="1.0" encoding="utf-8" ?>

<rss version="0.91" >
<channel>
<title>&lt;?paul</title>
<link>http://blog.preinheimer.com/</link>
<description>Paul Reinheimer</description>
<language>en</language>
<image>
        <url>http://blog.preinheimer.com/templates/bulletproof/img/s9y_banner_small.png</url>
        <title>RSS: &lt;?paul - Paul Reinheimer</title>
        <link>http://blog.preinheimer.com/</link>
        <width>100</width>
        <height>21</height>
    </image>

<item>
    <title>Where’s it Up? </title>
    <link>http://blog.preinheimer.com/index.php?/archives/359-Wheres-it-Up.html</link>

    <description>
        &lt;p&gt;&lt;a href=&quot;http://wonderproxy.com/&quot;&gt;WonderProxy&lt;/a&gt; is proud to present &lt;a href=&quot;http://wheresitup.com/&quot;&gt;Where’s it Up?&lt;/a&gt;, a new tool to help system administrators determine whether or not their site is up from around the world. The tool accepts a URL, and allows you to select global locations. It then attempts to connect to the given server and issue a HEAD request from the global locations you selected, and reports the results.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Key Features&lt;/b&gt;
&lt;ul&gt;
&lt;li&gt;Servers around the world&lt;/li&gt;
&lt;li&gt;Local DNS resolution on each server 
&lt;li&gt;Reports IP and timing information
&lt;li&gt;Follows a reasonable number of redirects&lt;/li&gt;
&lt;/ul&gt;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Key Technology&lt;/b&gt;&lt;ul&gt;
&lt;li&gt;Curl&lt;/li&gt;
&lt;li&gt;PHP with &lt;a href=&quot;http://pecl.php.net/package/pecl_http&quot;&gt;pecl_http&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Memcached&lt;/li&gt;
&lt;li&gt;Gearman&lt;/li&gt;
&lt;li&gt;Supervisord&lt;/li&gt;
&lt;/ul&gt;&lt;/p&gt;
&lt;p&gt;
&lt;h3&gt;How it Works&lt;/h3&gt;&lt;br /&gt;
Building a reasonably robust application was trivial, thanks to being able to leverage the great technology built by others. When a request is received, an intermediate script does some basic checking, then passes off a number of jobs to Gearman (one per location requested), the job ID is handed back to the user and results will be displayed via ajax. In the background Supervisord keeps five workers running all the time, they basically spin waiting for new work. When a job arrives they first check to see if there’s a recently cached result in memcached. Failing that an SSH tunnel is used to connect to the requested server, the request is issued with curl, and the results parsed. This result information is pushed into memcached. &lt;/p&gt;

&lt;p&gt;On the client side, requests are made back to the server including the job id, and already obtained results. New results are handed off as they become available.&lt;/p&gt;

&lt;p&gt;The system also uses memcached to throttle incoming requests from a given IP based on the example given by &lt;a href=&quot;http://simonwillison.net/2009/Jan/7/ratelimitcache/&quot;&gt;Simon Willison&lt;/a&gt;. In this case I’m actually using Swatch Beat time to manage the count; the edge case over midnight is quite easy.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Gearman&lt;/b&gt; is helpful in this case for three reasons: it provides great separation between the client requesting the work, and the workers actually completing it. It also trivially allows for multiple workers to be used in the resolution of requests. Finally, it provides a very effective work queue; when the site is under heavy load it will just take a bit longer to resolve requests. A small side benefit is the ability to pull down all the workers, update the worker script, then restart them, all without affecting the jobs in queue (except for the momentary delay). &lt;/p&gt;

&lt;p&gt;&lt;b&gt;Memcached&lt;/b&gt; is helpful as it’s a quick place to store the values that handles expiry for me automagically. In this particular case, as there’s a single server involved, something like APC would provider a thinner solution, however I have a lot of experience building apps with Memcached as a storage engine, and not much using APC.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Supervisord&lt;/b&gt; is helpful as it keeps the workers running, starts them up after they crash (hasn’t happened yet) and on machine boot. &lt;a href=&quot;http://phpadvent.org/2009/daemonize-your-php-by-sean-coates&quot;&gt;The dæmonize your PHP&lt;/a&gt; post by &lt;a href=&quot;http://seancoates.com/is&quot;&gt;Sean Coates&lt;/a&gt; was incredibly helpful in getting this going. &lt;/p&gt;

&lt;p&gt;&lt;b&gt;SSH&lt;/b&gt; is helpful as it allows us to display accurate timing information from that machine to the requested location. We could use our own proxies directly, but then we&#039;re dealing with the time to connect from Washington -&gt; Sydney -&gt; Your website. That extra bit of math in the middle is only moderately tricky, but more importantly: inconsistent. The tunnel lets us execute the commands directly on the remote server.&lt;/p&gt;
&lt;br /&gt;
&lt;p&gt;
&lt;h3&gt;Why we built it&lt;/h3&gt;&lt;br /&gt;
We&#039;ve used tools in the past that allow us to quickly check if a site is up or down. They&#039;re nifty, but these days many sites are using &lt;a href=&quot;http://en.wikipedia.org/wiki/Anycast&quot;&gt;anycast&lt;/a&gt; DNS to publish different IPs globally, with multiple servers and multiple data centers. Simple tools simply aren&#039;t able to adequately express whether or not a site is up, or down. By leveraging the network of servers we already had, building a more advanced and complete tool was easy.&lt;/p&gt; 
    </description>
</item>
<item>
    <title>XHGui Improvements</title>
    <link>http://blog.preinheimer.com/index.php?/archives/358-XHGui-Improvements.html</link>

    <description>
        I merged my Highcharts branch into master today, including a bunch of improvements to the GUI for XHProf including:
&lt;ul&gt;
&lt;li&gt;Using Highcharts for graphing URLs over time, which allows for multiple axis per graph&lt;/li&gt;
&lt;li&gt;Adding a pie chart to the individual run page to show where time was spent on page load&lt;/li&gt;
&lt;li&gt;Ability to merge various calls for display within the pie chart (e.g. mysql_* into mysql)&lt;/li&gt;
&lt;li&gt;Switched to the javascript tablesorter for the single run results&lt;/li&gt;
&lt;li&gt;Filter to allow you to view results only from a specific server or domain&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;http://highcharts.com/&quot;&gt;Highcharts&lt;/a&gt; is a commercial library in Javascript for graphing, it&#039;s really easy to use and offers more features than the Google libraries used previously. I&#039;ve purchased a license for the application so that anyone using this gui, regardless of the type of application they&#039;re profiling, can do so. The Highcharts integration was done by my colleague &lt;a href=&quot;http://ca.linkedin.com/pub/graham-slater/19/37/177&quot;&gt;Graham Slater&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I really didn&#039;t consider the branch &quot;done&quot;, the diff page still needs the pie charts, and to be switched over to the new table format. However, the license file received some serious updates to accurately represent the state of the code base and I considered making that file accurate to be a high priority.&lt;/p&gt;

&lt;h3&gt;Screenshots&lt;/h3&gt;
&lt;p&gt;&lt;a class=&quot;serendipity_image_link&quot;  href=&#039;http://blog.preinheimer.com/uploads/highchart2.png&#039;&gt;&lt;!-- s9ymdb:20 --&gt;&lt;img class=&quot;serendipity_image_left&quot; width=&quot;110&quot; height=&quot;62&quot;  src=&quot;http://blog.preinheimer.com/uploads/highchart2.serendipityThumb.png&quot;  alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;a class=&quot;serendipity_image_link&quot;  href=&#039;http://blog.preinheimer.com/uploads/2010-06-14_1115.png&#039;&gt;&lt;!-- s9ymdb:19 --&gt;&lt;img class=&quot;serendipity_image_left&quot; width=&quot;110&quot; height=&quot;62&quot;  src=&quot;http://blog.preinheimer.com/uploads/2010-06-14_1115.serendipityThumb.png&quot;  alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;

&lt;p&gt;As always, the code is available at &lt;a href=&quot;http://github.com/preinheimer/xhprof&quot;&gt;GitHub&lt;/a&gt;&lt;/p&gt; 
    </description>
</item>
<item>
    <title>The accountability problem - Concluded</title>
    <link>http://blog.preinheimer.com/index.php?/archives/357-The-accountability-problem-Concluded.html</link>

    <description>
        &lt;p&gt;Last week I blogged about &lt;a href=&quot;http://blog.preinheimer.com/index.php?/archives/356-The-accountability-problem.html&quot;&gt;the accountability problem&lt;/a&gt;: how to handle tracking who made what change, and providing the ability to revert it.&lt;br /&gt;

Thanks to some great comments, and offline discussions, I&amp;rsquo;m seeing a few options, and have changed my mind on my desired approach. There&#039;s also some great technical reading on the subject: &lt;a href=&quot;http://en.wikipedia.org/wiki/Slowly_changing_dimension&quot;&gt;Slowly changing dimension&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;Option 1: Store history in the table&lt;/h3&gt;
&lt;p&gt;This was the approach I considered initially. Storing each revision of a record in the table, with an active bit set to indicate which version is active. More details in the &lt;a href=&quot;http://blog.preinheimer.com/index.php?/archives/356-The-accountability-problem.html&quot;&gt;original post&lt;/a&gt;. This approach is simple: it doesn&amp;rsquo;t require more tables, queries are still pretty simple, and with a combination of timestamps it&amp;rsquo;s easy to track down associated records that were changed. &lt;/p&gt;

&lt;p&gt;The downsides are pretty clear, and somewhat damaging. The number of rows in the most commonly hit tables will grow much faster than the table overall, which is sub-optimal. It can also complicate issues like reporting and the like. In the case of highly normalized designs this design will likely break down, as you&amp;rsquo;ll need to match revisions across multiple tables on every query.&lt;/p&gt; 

&lt;span style=&quot;font-family: verdana&quot;&gt;classes&lt;/span&gt;
&lt;table border=&quot;1&quot;&gt;
&lt;tr&gt;&lt;td&gt;id&lt;/td&gt;&lt;td&gt;version&lt;/td&gt;&lt;td&gt;active&lt;/td&gt;&lt;td&gt;session&lt;/td&gt;&lt;td&gt;title&lt;/td&gt;&lt;td&gt;start_date&lt;/td&gt;&lt;td&gt;end_date&lt;/td&gt;&lt;td&gt;timestamp&lt;/td&gt;&lt;td&gt;login_id&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;0&lt;/td&gt;&lt;td&gt;Spring&lt;/td&gt;&lt;td&gt;Dancing 101&lt;/td&gt;&lt;td&gt;May 1, 2009&lt;/td&gt;&lt;td&gt;May 28, 2009&lt;/td&gt;&lt;td&gt;1274467708&lt;/td&gt;&lt;td&gt;	23&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;Spring&lt;/td&gt;&lt;td&gt;Dancing 101&lt;/td&gt;&lt;td&gt;May 1, 2010&lt;/td&gt;&lt;td&gt;May 28, 2010&lt;/td&gt;&lt;td&gt;1274467709&lt;/td&gt;&lt;td&gt;	23&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;Summer&lt;/td&gt;&lt;td&gt;Dancing 102&lt;/td&gt;&lt;td&gt;June 1, 2010&lt;/td&gt;&lt;td&gt;July 15, 2010	&lt;/td&gt;&lt;td&gt;1274467710&lt;/td&gt;&lt;td&gt;27&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;Fall&lt;/td&gt;&lt;td&gt;Dancing 101&lt;/td&gt;&lt;td&gt;September 1, 2010&lt;/td&gt;&lt;td&gt;September 28, 2010&lt;/td&gt;&lt;td&gt;1274467808&lt;/td&gt;&lt;td&gt;35&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;
&lt;br /&gt;
&lt;p&gt;I would have used this approach in a smaller project. The system I&amp;rsquo;m replacing has been running for ten years, I have to plan for this lasting that long with only minimal maintenance. I&amp;rsquo;d rather not watch those row counts become 3-5x what they should be. &lt;/p&gt;

&lt;h3&gt;Option 2: Store histories in an external table&lt;/h3&gt;
&lt;p&gt;Rather than storing the histories in the regular table (say classes) creating a duplicate table (classes_history) and storing the revisions there. This helps trim the size of your most heavily used tables, and keeps things relatively simple. &lt;/p&gt;

&lt;p&gt;While trimming the row count, it does double the table count, and still fails to handle complicated objects, or normalized table structures where a single datum requires multiple tables. &lt;/p&gt;
&lt;span style=&quot;font-family: verdana&quot;&gt;classes&lt;/span&gt;
&lt;table border=&quot;1&quot;&gt;
&lt;tr&gt;&lt;td&gt;id&lt;/td&gt;&lt;td&gt;session&lt;/td&gt;&lt;td&gt;title&lt;/td&gt;&lt;td&gt;start_date&lt;/td&gt;&lt;td&gt;end_date&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;Spring&lt;/td&gt;&lt;td&gt;Dancing 101&lt;/td&gt;&lt;td&gt;May 1, 2010&lt;/td&gt;&lt;td&gt;May 28, 2010&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;Summer&lt;/td&gt;&lt;td&gt;Dancing 102&lt;/td&gt;&lt;td&gt;June 1, 2010&lt;/td&gt;&lt;td&gt;July 15, 2010&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;Fall&lt;/td&gt;&lt;td&gt;Dancing 101&lt;/td&gt;&lt;td&gt;September 1, 2010&lt;/td&gt;&lt;td&gt;September 28, 2010&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;
&lt;span style=&quot;font-family: verdana&quot;&gt;classes_history&lt;/span&gt;
&lt;table border=&quot;1&quot;&gt;
&lt;tr&gt;&lt;td&gt;id&lt;/td&gt;&lt;td&gt;version&lt;/td&gt;&lt;td&gt;active&lt;/td&gt;&lt;td&gt;session&lt;/td&gt;&lt;td&gt;title&lt;/td&gt;&lt;td&gt;start_date&lt;/td&gt;&lt;td&gt;end_date&lt;/td&gt;&lt;td&gt;timestamp&lt;/td&gt;&lt;td&gt;login_id&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;0&lt;/td&gt;&lt;td&gt;Spring&lt;/td&gt;&lt;td&gt;Dancing 101&lt;/td&gt;&lt;td&gt;May 1, 2009&lt;/td&gt;&lt;td&gt;May 28, 2009&lt;/td&gt;&lt;td&gt;1274467708&lt;/td&gt;&lt;td&gt;	23&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;
&lt;br /&gt;
&lt;p&gt;I&amp;rsquo;ll be using this method for my upcoming project. &lt;/p&gt;



&lt;h3&gt;Option 3: Store serialized information &lt;/h3&gt;
&lt;p&gt;Rather than storing rows when information is modified, the entire encapsulating object is serialized and stored. This can be done either with one table per object type, or in one large history table.&lt;/p&gt;

&lt;p&gt;The advantage here is that each revision stands alone and complete. Composite records are stored complete, and in a single location. This makes obtaining an accurate history relatively easy. On the downside, since information is serialized it&amp;rsquo;s hard to locate specific pieces of information via sql. This approach seems perfect for complex objects where a single composite record spans many tables, but a little overkill for this specific case.&lt;/p&gt;

&lt;span style=&quot;font-family: verdana&quot;&gt;classes&lt;/span&gt;
&lt;table border=&quot;1&quot;&gt;
&lt;tr&gt;&lt;td&gt;id&lt;/td&gt;&lt;td&gt;version&lt;/td&gt;&lt;td&gt;active&lt;/td&gt;&lt;td&gt;session&lt;/td&gt;&lt;td&gt;title&lt;/td&gt;&lt;td&gt;start_date&lt;/td&gt;&lt;td&gt;end_date&lt;/td&gt;&lt;td&gt;timestamp&lt;/td&gt;&lt;td&gt;login_id&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;Spring&lt;/td&gt;&lt;td&gt;Dancing 101&lt;/td&gt;&lt;td&gt;May 1, 2010&lt;/td&gt;&lt;td&gt;May 28, 2010&lt;/td&gt;&lt;td&gt;1274467709&lt;/td&gt;&lt;td&gt;	23&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;Summer&lt;/td&gt;&lt;td&gt;Dancing 102&lt;/td&gt;&lt;td&gt;June 1, 2010&lt;/td&gt;&lt;td&gt;July 15, 2010	&lt;/td&gt;&lt;td&gt;1274467710&lt;/td&gt;&lt;td&gt;27&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;Fall&lt;/td&gt;&lt;td&gt;Dancing 101&lt;/td&gt;&lt;td&gt;September 1, 2010&lt;/td&gt;&lt;td&gt;September 28, 2010&lt;/td&gt;&lt;td&gt;1274467808&lt;/td&gt;&lt;td&gt;35&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;
&lt;span style=&quot;font-family: verdana&quot;&gt;history&lt;/span&gt;
&lt;table border=&quot;1&quot;&gt;
&lt;tr&gt;&lt;td&gt;id&lt;/td&gt;&lt;td&gt;version&lt;/td&gt;&lt;td&gt;table&lt;/td&gt;&lt;td&gt;data&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;classes&lt;/td&gt;&lt;td&gt;a:8:{s:2:&quot;id&quot;;i:1;s:7:&quot;version&quot;;i:1;s:7:&quot;session&quot;;s:6:&quot;spring&quot;;s:5:&quot;title&quot;;s:11:&quot;Dancing 101&quot;;s:10:&quot;start_date&quot;;s:11:&quot;May 1, 2009&quot;;s:8:&quot;end_date&quot;;s:12:&quot;May 28, 2009&quot;;s:9:&quot;timestamp&quot;;s:10:&quot;1274467708&quot;;s:8:&quot;login_id&quot;;i:23;}&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;
&lt;br /&gt;
&lt;p&gt;I&#039;ll consider this for larger projects with complex objects.&lt;/p&gt;

&lt;br /&gt;
&lt;h3&gt;Other points to ponder:&lt;/h3&gt;
&lt;ul&gt;
 &lt;li&gt;How long will you store histories?&lt;/li&gt;
 &lt;ul&gt;
  &lt;li&gt;Forever (&amp;infin;)&lt;/li&gt;
  &lt;li&gt;Some fixed period of time (30 days)&lt;/li&gt;
  &lt;li&gt;Application specific (e.g. storing change to a class for three months after it ends)&lt;/li&gt;
 &lt;/ul&gt;
 &lt;li&gt;Storage engine for history&lt;/li&gt;
 &lt;ul&gt;
  &lt;li&gt;MyISAM&lt;/li&gt;
  &lt;li&gt;Archive&lt;/li&gt;
 &lt;/ul&gt;
  &lt;li&gt;Backup strategies &lt;/li&gt;
 &lt;ul&gt;
 &lt;li&gt;History tables are all appends&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;
&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;
Thanks to everyone who commented on the last post. 
    </description>
</item>
<item>
    <title>The accountability problem</title>
    <link>http://blog.preinheimer.com/index.php?/archives/356-The-accountability-problem.html</link>

    <description>
        &lt;p&gt;I&#039;m working on a volunteer side project for a local dance studio, the website will handle the standard sort of things: creating classes, registering students, matching leads to follows, etc. &lt;/p&gt;

&lt;p&gt;We would like to have some level of accountability when various records (user, class, etc) are modified. That way if problems arise later, we can see who did what, and when. If needs be, even roll the change back.&lt;/p&gt;

&lt;p&gt;This must be a common problem, how have you solved it?&lt;/p&gt;

&lt;p&gt;My (proposed) solution is something like this:

Take an ordinary table like class:&lt;table border=&quot;1&quot;&gt;
&lt;tr&gt;&lt;td&gt;id&lt;/td&gt;&lt;td&gt;session&lt;/td&gt;&lt;td&gt;title&lt;/td&gt;&lt;td&gt;start_date&lt;/td&gt;&lt;td&gt;end_date&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;Spring&lt;/td&gt;&lt;td&gt;Dancing 101&lt;/td&gt;&lt;td&gt;May 1, 2010&lt;/td&gt;&lt;td&gt;May 28, 2010&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;Summer&lt;/td&gt;&lt;td&gt;Dancing 102&lt;/td&gt;&lt;td&gt;June 1, 2010&lt;/td&gt;&lt;td&gt;July 15, 2010&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;Fall&lt;/td&gt;&lt;td&gt;Dancing 101&lt;/td&gt;&lt;td&gt;September 1, 2010&lt;/td&gt;&lt;td&gt;September 28, 2010&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;&lt;/p&gt;
&lt;p&gt;And add a few columns to for change management &lt;table border=&quot;1&quot;&gt;
&lt;tr&gt;&lt;td&gt;id&lt;/td&gt;&lt;td&gt;version&lt;/td&gt;&lt;td&gt;active&lt;/td&gt;&lt;td&gt;session&lt;/td&gt;&lt;td&gt;title&lt;/td&gt;&lt;td&gt;start_date&lt;/td&gt;&lt;td&gt;end_date&lt;/td&gt;&lt;td&gt;timestamp&lt;/td&gt;&lt;td&gt;login_id&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;0&lt;/td&gt;&lt;td&gt;Spring&lt;/td&gt;&lt;td&gt;Dancing 101&lt;/td&gt;&lt;td&gt;May 1, 2009&lt;/td&gt;&lt;td&gt;May 28, 2009&lt;/td&gt;&lt;td&gt;1274467708&lt;/td&gt;&lt;td&gt;	23&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;Spring&lt;/td&gt;&lt;td&gt;Dancing 101&lt;/td&gt;&lt;td&gt;May 1, 2010&lt;/td&gt;&lt;td&gt;May 28, 2010&lt;/td&gt;&lt;td&gt;1274467709&lt;/td&gt;&lt;td&gt;	23&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;Summer&lt;/td&gt;&lt;td&gt;Dancing 102&lt;/td&gt;&lt;td&gt;June 1, 2010&lt;/td&gt;&lt;td&gt;July 15, 2010	&lt;/td&gt;&lt;td&gt;1274467710&lt;/td&gt;&lt;td&gt;27&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;Fall&lt;/td&gt;&lt;td&gt;Dancing 101&lt;/td&gt;&lt;td&gt;September 1, 2010&lt;/td&gt;&lt;td&gt;September 28, 2010&lt;/td&gt;&lt;td&gt;1274467808&lt;/td&gt;&lt;td&gt;35&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;&lt;/p&gt;

&lt;p&gt;The list of classes is obtained by selecting where the active bit is set. Administrators will be able to look at the list of changes and see who made them, and when. I’m tracking login id rather than simply user id so information like IP, and timestamp can be associated with the login, rather than simply who. In the case of reverting a change, the desired version will be duplicated into a new row, and have the active bit set. In reality active will always be the highest revision number (and latest date) but the duplication makes queries easier. &lt;/p&gt;

&lt;p&gt;I think there’s a lot of power in this approach, but it is a bit complicated. Thoughts?&lt;/p&gt; 
    </description>
</item>
<item>
    <title>A GUI for XHProf</title>
    <link>http://blog.preinheimer.com/index.php?/archives/355-A-GUI-for-XHProf.html</link>

    <description>
        &lt;p&gt;Facebook was kind enough to open source the &lt;a href=&quot;http://github.com/facebook/xhprof&quot;&gt;XHProf&lt;/a&gt; extension last year, but it flew under my radar until I saw a presentation including it earlier this year when they showed off &lt;a href=&quot;http://github.com/facebook/hiphop-php&quot;&gt;HipHop&lt;/a&gt;. XHProf provides profiling information about your application, while being lightweight enough to run on a production server (against a percentage of requests). Once we got it installed we ran into a few limitations with the existing GUI. With the help of Graham Slater (one of our front end designers) I hacked on the Facebook code to come up with our own flavor. Let me stress the word “hack.&quot; I was given some really tough deadlines for this project so functionality was literally hacked into the existing codebase.&lt;/p&gt;

&lt;h3&gt;Key Features:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Nice interface&lt;/li&gt;
&lt;li&gt;MySQL backend for data storage&lt;/li&gt;
&lt;li&gt;Stores key values such as Peak Memory Usage, Wall Time, and CPU Ticks in the database directly for easy lookup.&lt;/li&gt;
&lt;li&gt;Stores Get, Post, and Cookie data for easy comparison (so you can determine why a run hit that execution path)&lt;/li&gt;
&lt;li&gt;Display includes easy lookup for worst runs, most recent, etc.&lt;/li&gt;
&lt;li&gt;Support for profiling a percentage of requests, or on demand&lt;/li&gt;
&lt;li&gt;Support for not including a link to profile requests on certain files (like xml documents, images, etc).&lt;/li&gt;
&lt;li&gt;Uses Google Data Visualization API to graph runs over time&lt;/li&gt;
&lt;li&gt;Concept of “Similar” urls, to handle ?story=23 and ?story=24 having different URLs, but similar to identical code execution paths&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;Screenshots:&lt;/h3&gt;
&lt;a class=&#039;serendipity_image_link&#039; href=&#039;http://blog.preinheimer.com/uploads/2010-04-26_1443.png&#039;&gt;&lt;!-- s9ymdb:11 --&gt;&lt;img class=&quot;serendipity_image_left&quot; width=&quot;110&quot; height=&quot;65&quot; style=&quot;float: left; border: 0px; padding-left: 5px; padding-right: 5px;&quot; src=&quot;http://blog.preinheimer.com/uploads/2010-04-26_1443.serendipityThumb.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;a class=&#039;serendipity_image_link&#039; href=&#039;http://blog.preinheimer.com/uploads/2010-04-26_1444.png&#039;&gt;&lt;!-- s9ymdb:12 --&gt;&lt;img class=&quot;serendipity_image_left&quot; width=&quot;110&quot; height=&quot;65&quot; style=&quot;float: left; border: 0px; padding-left: 5px; padding-right: 5px;&quot; src=&quot;http://blog.preinheimer.com/uploads/2010-04-26_1444.serendipityThumb.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;a class=&#039;serendipity_image_link&#039; href=&#039;http://blog.preinheimer.com/uploads/2010-04-26_1445.png&#039;&gt;&lt;!-- s9ymdb:13 --&gt;&lt;img class=&quot;serendipity_image_left&quot; width=&quot;110&quot; height=&quot;65&quot; style=&quot;float: left; border: 0px; padding-left: 5px; padding-right: 5px;&quot; src=&quot;http://blog.preinheimer.com/uploads/2010-04-26_1445.serendipityThumb.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;a class=&#039;serendipity_image_link&#039; href=&#039;http://blog.preinheimer.com/uploads/2010-04-26_1447.png&#039;&gt;&lt;!-- s9ymdb:14 --&gt;&lt;img class=&quot;serendipity_image_left&quot; width=&quot;110&quot; height=&quot;65&quot; style=&quot;float: left; border: 0px; padding-left: 5px; padding-right: 5px;&quot; src=&quot;http://blog.preinheimer.com/uploads/2010-04-26_1447.serendipityThumb.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;
&lt;br style=&quot;clear: both;&quot; /&gt;

&lt;h3&gt;Download:&lt;/h3&gt;
&lt;p&gt;Please download the source code from &lt;a href=&quot;http://github.com/preinheimer/xhprof&quot;&gt;GitHub&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;Warnings:&lt;/h3&gt;
&lt;p&gt;Seriously, we hacked this up! Please let me know if you encounter any bugs, there&#039;s probably a bunch. &lt;/p&gt; 
    </description>
</item>
<item>
    <title>Memory usage in PHP</title>
    <link>http://blog.preinheimer.com/index.php?/archives/354-Memory-usage-in-PHP.html</link>

    <description>
        &lt;p&gt;A colleague called me over today for some help with a memory usage issue in his PHP script. The script was performing the basic (but critical) task of importing data, pulling it in from MySQL in large chunks, then exporting it elsewhere. He was receiving the wonderful &quot;&lt;span style=&quot;font-family: Verdana, Helvetica; background-color: #EFEFDF;&quot;&gt;Fatal error: Allowed memory size of XXXX bytes exhausted (tried to allocate YY bytes&lt;/span&gt;)&quot; error message.&lt;/p&gt;

&lt;p&gt;The code was following a basic flow:&lt;br /&gt;
10 Get stuff from the database (large array of arrays)&lt;br /&gt;
20 Iterate over it, doing stuff&lt;/br&gt;
30 Goto 10&lt;/p&gt;

&lt;p&gt;I fixed the memory usage exceeded problem with an &lt;span style=&quot;font-family: Verdana, Helvetica; background-color: #EFEFDF;&quot;&gt;unset()&lt;/span&gt;, at the end of a loop.&lt;/p&gt;

&lt;p&gt;Take a look at this sample program:
&lt;span style=&quot;font-family: Verdana, Helvetica; background-color: #EFEFDF; display: block; white-space: pre;&quot;&gt;loopOverStuff();

function loopOverStuff()
{
    $var = null;
    for($i = 0; $i &lt; 10; $i++)
    {
         $var = getData();
         //Do stuff
    }
}


function getData()
{
    $a = str_repeat(&quot;This string is exactly 40 characters long&quot;, 20000);
    return $a;
}
&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;The important thing to realize is that PHP will end up needing around twice as much memory as &lt;span style=&quot;font-family: Verdana, Helvetica; background-color: #EFEFDF;&quot;&gt;getData()&lt;/span&gt; takes. The problem is the line &lt;span style=&quot;font-family: Verdana, Helvetica; background-color: #EFEFDF;&quot;&gt;$var = getData()&lt;/span&gt;. The first time it is called &lt;span style=&quot;font-family: Verdana, Helvetica; background-color: #EFEFDF;&quot;&gt;$var&lt;/span&gt; is incredibly small, it&#039;s clobbered and the return value of &lt;span style=&quot;font-family: Verdana, Helvetica; background-color: #EFEFDF;&quot;&gt;getData()&lt;/span&gt;is assigned to it. The second time through the loop &lt;span style=&quot;font-family: Verdana, Helvetica; background-color: #EFEFDF;&quot;&gt;$var&lt;/span&gt; still holds the value from the previous iteration, so while &lt;span style=&quot;font-family: Verdana, Helvetica; background-color: #EFEFDF;&quot;&gt;getData()&lt;/span&gt; is executing you&#039;re maintaining the original data (in &lt;span style=&quot;font-family: Verdana, Helvetica; background-color: #EFEFDF;&quot;&gt;$var&lt;/span&gt;), and a whole new set (being built in &lt;span style=&quot;font-family: Verdana, Helvetica; background-color: #EFEFDF;&quot;&gt;getData()&lt;/span&gt;).&lt;/p&gt;

&lt;p&gt;Fixing this is incredibly easy:
&lt;span style=&quot;font-family: Verdana, Helvetica; background-color: #EFEFDF; display: block; white-space: pre;&quot;&gt;function loopOverStuff()
{
    $var = null;
    for($i = 0; $i &lt; 10; $i++)
     {
         $var = stealMemories();
        //Do Stuff
        unset($var);
     }
}
&lt;/span&gt;&lt;/p&gt;
This way we avoid the duplication in memory of those values on that line. To see this happen in more detail take a look at this sample script with ouptut: &lt;a href=&quot;http://example.preinheimer.com/memory-usage-example.php&quot;&gt;memory-usage-example.php&lt;/a&gt;.&lt;/p&gt;

&lt;/p&gt;This isn&#039;t critical, except when it is. Once &lt;span style=&quot;font-family: Verdana, Helvetica; background-color: #EFEFDF;&quot;&gt;loopOverStuff()&lt;/span&gt; completes, and the function ends. The memory is released back to the rest of PHP automatically. You&#039;ll only run into problems where &lt;span style=&quot;font-family: Verdana, Helvetica; background-color: #EFEFDF;&quot;&gt;Other Stuff + (2 * memory needed in loop) &gt; Memory Limit&lt;/span&gt;. There are better architectures available to avoid the issue entirely (like not storing everything in the array, just to iterate over it later) but they&#039;re an issue for a different post. &lt;/p&gt;

&lt;p&gt;For a very simple base case demonstration of the issue take a look at the &lt;a href=&quot;http://example.preinheimer.com/memory-usage-simple-example.php&quot;&gt;simple example&lt;/a&gt;.&lt;/p&gt; 
    </description>
</item>
<item>
    <title>Search without the Database</title>
    <link>http://blog.preinheimer.com/index.php?/archives/353-Search-without-the-Database.html</link>

    <description>
        I gave my second talk at #ConFoo today, this one was on Searching without the Database. This talk was based on a situation at work where I replaced a MySQL search solution with a Sphinx + Memcached solution for higher performance. If you&#039;re interested, here&#039;s the slides: &lt;a href=&quot;http://blog.preinheimer.com/talks/Search without the DB - ConFoo 2010.pdf&quot;&gt;Search without the DB - ConFoo 2010.pdf&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
If you attended my talk, you can rate it on the &lt;a href=&quot;http://joind.in/talk/view/1383&quot;&gt;Joind.in Page&lt;/a&gt; 
    </description>
</item>
<item>
    <title>ConFoo - PHP in the Enterprise</title>
    <link>http://blog.preinheimer.com/index.php?/archives/352-ConFoo-PHP-in-the-Enterprise.html</link>

    <description>
        I just finished my PHP in the Enterprise talk at &lt;a href=&quot;http://confoo.ca/&quot;&gt;ConFoo&lt;/a&gt;, Slides are available here: &lt;a href=&quot;http://blog.preinheimer.com/talks/PHP in the Enterprise - Confoo March 2010.pdf&quot;&gt;PHP in the Enterprise - ConFoo March 2010&lt;/a&gt; 
    </description>
</item>
<item>
    <title>A Customer found a Bug - Redux</title>
    <link>http://blog.preinheimer.com/index.php?/archives/351-A-Customer-found-a-Bug-Redux.html</link>

    <description>
        A little over a week ago I posted about a customer discovering a bug on the &lt;a href=&quot;http://wonderproxy.com/&quot; title=&quot;WonderProxt - A Proxy Service aimed at GeoIP Developers&quot;&gt;WonderProxy&lt;/a&gt; website, and &lt;a href=&quot;http://blog.preinheimer.com/index.php?/archives/350-A-Customer-found-a-Bug.html&quot;&gt;how I handled the situation&lt;/a&gt;. I wanted to speak to that once more, in part as a follow up based on feedback I received, and in part about why I didn&amp;rsquo;t panic that a customer had found a rather critical problem in my website.&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;First, the follow up.&lt;/h3&gt;The overwhelming opinion is that I did okay in handling the issue, but I could have done better. Namely, I should have emailed the customer as soon as I confirmed the issue. That way the customer would be in the loop throughout the entire process. The fact that resolving the issue only took a few minutes was a mitigating factor, but really I failed to keep the customer in the loop, and that earlier notification could have allowed them to better plan use of my service.&lt;br /&gt;
&lt;br /&gt;
In the future, I&amp;rsquo;ll behave differently. I have no intention of setting up auto-responders, but I can, and will, do better in terms of keeping customers in the loop.&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;Second, not Panicking.&lt;/h3&gt;Customer Service is a big topic, there&amp;rsquo;s a tonne of research out there on customer satisfaction, their interaction with customer service, etc. The body of the research shows that customers who have problems that are resolved to their satisfaction are on the whole happier than customers who had no problems at all. Keeping that in mind, I didn&amp;rsquo;t panic. This was an opportunity to have a very happy customer, rather than a crisis.&lt;br /&gt;
&lt;br /&gt;
The research I read was admittedly based on hotels (with an interesting anecdote about discovering a customer who over something like 207 nights of stay had not yet paid for one, as every time they complained about something and the night was comp&amp;rsquo;d). But the data resounds well with me. We&amp;rsquo;ve all got a plethora of customer service horror stories, and precious few great ones. As a result, those great customer service experiences are carried with us, and often shared with friends and colleagues.&lt;br /&gt;
&lt;br /&gt;
While I don&amp;rsquo;t intend to go as far as to introduce bugs into the system simply so I can fix them in a fantastic manner, I will embrace every opportunity I get to show a customer great service.   
    </description>
</item>
<item>
    <title>X-Host-IP</title>
    <link>http://blog.preinheimer.com/index.php?/archives/349-X-Host-IP.html</link>

    <description>
        When you discover a problem on you website, and you run multiple web servers, one of the first questions you ask is &quot;Which server am I on&quot; (often followed immediately by: &quot;Does it happen on all of them&quot;).&lt;br /&gt;
&lt;br /&gt;
This presented an unfortunate problem for &lt;a href=&quot;http://wonderproxy.com/?from=blog&quot;&gt;WonderProxy&lt;/a&gt; customers, as when they connect to a server through their proxy, they are no longer able to determine which webserver provided a resource. That information is hidden because their connections take place through the proxy.&lt;br /&gt;
&lt;br /&gt;
This is why we&#039;re happy to announce the introduction of a new HTTP header being served through all 18 of our proxies: &lt;span style=&quot;font: Verdana&quot;&gt;X-Host-IP&lt;/a&gt;. This header is injected by our proxy software when handing data back to the client. This header includes the IP address of the machine that provided the resource, allowing clients to obtain this information through a variety of methods like Live HTTP Headers, Firebug, or Dragonfly.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
This is the first of two steps we’re taking to better inform, and empower our clients when it comes to servers. We’re planning to roll out a new feature allowing customers to inject a hosts file[1] onto the proxy server of their choice to allow server selection through a web interface. This should be completed in the next few weeks.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[1] A hosts file (&lt;span style=&quot;font: Verdana&quot;&gt;/etc/hosts&lt;/style&gt; on most linux machines, and &lt;span style=&quot;font: Verdana&quot;&gt;\system32\drivers\etc\hosts.txt&lt;/style&gt; on Windows boxes) allows users to hard code hostname and IP combinations, often used for testing and development. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
 
    </description>
</item>
<item>
    <title>A Customer found a Bug</title>
    <link>http://blog.preinheimer.com/index.php?/archives/350-A-Customer-found-a-Bug.html</link>

    <description>
        So this morning I woke up to an email (well, several, but only one is relevant) from a &lt;a href=&quot;http://wonderproxy.com/?from=blog&quot; title=&quot;WonderProxy is a GeoIP proxy service&quot;&gt;WonderProxy&lt;/a&gt; customer. They had discovered a bug in the website. A rather serious bug mind you, as it stopped them from adding servers to their account, and thus prevented them from actually using the service.&lt;br /&gt;
&lt;br /&gt;
Here’s how I approached the problem:&lt;br /&gt;
&lt;ol&gt;&lt;br /&gt;
&lt;li&gt;Reproduce the issue&lt;br /&gt;
I logged into the website as the customer and attempted to reproduce the issue, no problems. I tried again on the development version of the website and reproduced it there as well.&lt;/li&gt;&lt;br /&gt;
&lt;li&gt;Estimate bug fix&lt;br /&gt;
I took a look at the code and estimated that I could resolve the bug in under ten minutes. The code base for the WonderProxy website is rather small, and bugs there can’t affect customer usage (unless they break the DB) so I wasn’t too concerned about accidentally taking something down. The short time frame for release also let me fix the bug before contacting the customer.&lt;/li&gt;&lt;br /&gt;
&lt;li&gt;Fix the bug&lt;br /&gt;
Found the bug, fixed it. Tested it out in development, checked other accounts to look for unrelated breaks. Finally I rolled it out from development to production and checked again.&lt;/li&gt;&lt;br /&gt;
&lt;li&gt;Contact Customer&lt;br /&gt;
Replying to the customer I indicated that problem was indeed on our end (the customer wasn’t using the site wrong or anything), and that it had been fixed. Additionally I had updated the billing period to reflect the fact that the account hadn’t been usable up until the fix was applied. As a token of appreciation I upgraded the account slightly.&lt;/li&gt;&lt;br /&gt;
&lt;/ol&gt;&lt;br /&gt;
&lt;br /&gt;
Overall, I found the process rather pain free. When I look at it from a customer’s prospective, I also feel like it was a reasonable problem resolution. &lt;br /&gt;
&lt;br /&gt;
I’d like to delve deeper into how this problem made it onto my production website, but that’s a separate post. For now I’m really just looking at the customer service side, there was a total of 67 minutes between the initial contact (on the part of the customer) and my reply with the fix in place.&lt;br /&gt;
&lt;br /&gt;
Thoughts? How did I do? 
    </description>
</item>
<item>
    <title>WonderProxy - Server Status</title>
    <link>http://blog.preinheimer.com/index.php?/archives/348-WonderProxy-Server-Status.html</link>

    <description>
        A few weeks ago I launched the &lt;a href=&quot;http://wonderproxy.com/?from=blog&quot;&gt;WonderProxy&lt;/a&gt; website, one of the first decisions I made when working on it was the network status bar shown on the left side. That&#039;s a nearly live showing of the state of all the servers in the network (as of when the page loads). &lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;What I Did&lt;/h3&gt;The box with Nagios running on it runs real tests against all the proxies in the network on a regular basis. I do mean real tests: it uses curl to make a request against a script on a different web server, which checks the headers to ensure that the correct headers are being used. This checks: the proxy is running, the proxy is listening on the correct port, the proxy is able to authenticate users, the proxy is able to retrieve remote documents and return them. Every minute the Nagios box collates the data for all the servers and creates a .json document. That document is then pushed to the web serving machine, ready for consumption by obliging clients. &lt;br /&gt;
&lt;br /&gt;
&lt;i&gt;Note, I&#039;ve said &quot;what I did&quot;, in reality the JavaScript ninjary was done by &lt;a href=&quot;http://neato.co.nz&quot;&gt;Christine&lt;/a&gt;, and the curl ninjary was done by my obliging sys-admin Will&lt;/a&gt;&lt;/i&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;Why I Did It&lt;/h3&gt;Presently, I deal with upwards of twenty hosts between professional and personal projects, that&#039;s a lot. I&#039;ve probably dealt with another ten that I’ve terminated relationships over my professional career. All in all, I’m sick of the hoops that they make me jump through to find out what&#039;s going wrong. &lt;br /&gt;
Here&#039;s a typical outage for me:&lt;br /&gt;
0 minutes - Nagios notifies me that something is wrong&lt;br /&gt;
+2 - I&#039;ve logged into nagios, and attempted to log into the affected system for more information, confirming the outage&lt;br /&gt;
+9 - I&#039;m on the website for the host of the affected system, finding absolutely no helpful information.&lt;br /&gt;
+10 - I&#039;ve started trying to figure out what my login credentials are for the affected system&#039;s hosting provider.&lt;br /&gt;
+11 - I&#039;ve logged in, found no more information&lt;br /&gt;
+12 - File a ticket with the host&lt;br /&gt;
... &lt;br /&gt;
+35 - Nagios informs me that the service is back up&lt;br /&gt;
+90 - Host replies with some generic &quot;something went wrong on our end, we fixed it, we may credit your account with $0.12 later&quot; message.&lt;br /&gt;
&lt;br /&gt;
I argue this is patently absurd. Some companies put on a front about being better about this sort of thing, but then an outage occurs and the one girl/guy with access to the twitter account is on vacation, or asleep, or at the movies so nothing happens.&lt;br /&gt;
&lt;br /&gt;
When my box goes down its either my fault, in which case I need to start working my rear end off to bring it back up, or the host is experiencing a real outage, in which case I should just sit on my hands and wait. In that typical listing I&#039;ve indicated above I don’t know which situation I’m in until 90 minutes on, though I can start poking at logs 35 minutes in. &lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;What I Hope This Means&lt;/h3&gt;&lt;br /&gt;
If you&#039;re a WonderProxy customer running tests and something breaks, you can log onto the WonderProxy website and know immediately if the proxy is broken, or if you should be looking harder at your own infrastructure. &lt;br /&gt;
&lt;br /&gt;
Thoughts? Does your host do better? 
    </description>
</item>
<item>
    <title>Holiday Swap</title>
    <link>http://blog.preinheimer.com/index.php?/archives/347-Holiday-Swap.html</link>

    <description>
        This year I took place in the &lt;a href=&quot;http://holidayswap.wordpress.com/&quot;&gt;Book Blogger Holiday Swap&lt;/a&gt; which was pretty awesome. My giftee is way more on the ball than I am (also: a cheater, opening things christmas eve!) and &lt;a href=&quot;http://saveophelia.wordpress.com/2009/12/24/santa-baby/#comments&quot;&gt;blogged already&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
I was lucky enough to receive a great book, some chocolate, and a book mark!  &lt;br /&gt;
&lt;br /&gt;
&lt;a href=&quot;http://www.flickr.com/photos/preinheimer/4216303667/&quot; title=&quot;Holiday Swap Gift Received.jpg by preinheimer, on Flickr&quot;&gt;&lt;img src=&quot;http://farm5.static.flickr.com/4063/4216303667_11241bedb5.jpg&quot; width=&quot;500&quot; height=&quot;335&quot; alt=&quot;Holiday Swap Gift Received.jpg&quot; /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
As you can see, I&#039;ve already ripped into the chocolate! I think I&#039;ll start into the book after I finish re-reading one of my William Gibson books. I think it will be great for my sporadic reading during my daily commutes!&lt;br /&gt;
&lt;br /&gt;
Thanks &lt;a href=&quot;http://booksandmovies.colvilleblogger.com&quot;&gt;Carrie K&lt;/a&gt; 
    </description>
</item>
<item>
    <title>Functional URLs - More than Developer Vanity</title>
    <link>http://blog.preinheimer.com/index.php?/archives/346-Functional-URLs-More-than-Developer-Vanity.html</link>

    <description>
        I read &lt;a href=&quot;http://phpadvent.org/2009/do-urls-matter-by-david-sklar&quot;&gt;David Sklar&#039;s post&lt;/a&gt; to the PHP Advent calendar this morning, and as the title rather clearly points out, I disagree with his conclusion. &lt;br /&gt;
&lt;br /&gt;
Before I go any further I&#039;d like to clarify something in the title, namely the word &quot;functional&quot;. I&#039;m using this meaning from &lt;a href=&quot;http://dictionary.reference.com/browse/functional&quot;&gt;dictionary.reference.com&lt;/a&gt;: &lt;br /&gt;
&lt;blockquote&gt;having or serving a utilitarian purpose; capable of serving the purpose for which it was designed: functional architecture; a chair that is functional as well as decorative.&lt;/blockquote&gt;&lt;br /&gt;
If you define a URL&#039;s purpose as simply being an instruction to a browser or other application, then sure a URL such as this one qualifies:&lt;br /&gt;
&lt;blockquote&gt;http://www.realtor.ca/map.aspx?&amp;vs=VEResidential&amp;beds=0-0&amp;baths=0-0&amp;minp=0&amp;maxp=0&amp;area=Montreal%2c+Quebec&amp;trt=2#acr:false;ac:false;baths:0-0;beds:0-0;fp:false;gar:false;pmin:175000;pmax:200000;rmin:0;rmax:0;openh:false;&lt;br /&gt;
pool:false;stories:0-0;buildingstyle:;buildingtypeid:;viewtypeid:;waterfront:false;forsale:true;forrent:false;orderBy:A;sortBy:1;&lt;br /&gt;
LisStartDate:;mapZ:12;page:1;mapC:45.49792488715173, -73.56531143188478;curView:0;&lt;/blockquote&gt;&lt;br /&gt;
That said, the only purpose of a URL isn&#039;t to give an instruction to a browser or other application. It&#039;s an indicator to the user, telling them what the page is about, and one they use quite a bit[1]. It&#039;s an address that does still need to be typed on occasion, or at the very least copy/pasted into a variety of mediums. It should be a destructible resource that users can use to navigate the site and obtain what they&#039;re looking for.&lt;br /&gt;
&lt;br /&gt;
Compare: &lt;br /&gt;
&lt;blockquote&gt;store.example.com/plv2/product.php?refid=4AF82BC932&lt;/blockquote&gt;&lt;br /&gt;
and&lt;br /&gt;
&lt;blockquote&gt;store.example.com/toys/dolls/barbie &lt;/blockquote&gt;&lt;br /&gt;
&lt;br /&gt;
In the first case you&#039;re stuck, there&#039;s no options to you for navigating the site beyond the on screen options. In the latter case you can move from looking at Barbie, to looking at all dolls, to looking at all toys, to looking at the entire product line of the store rather easily. I will concede that not all users will think to navigate in that manner, but some will, and they have the option.&lt;br /&gt;
&lt;br /&gt;
In conclusion: URLs are more than some text an end user clicks on, and having good ones has more worth than letting the developer look cool in front of their peers: It&#039;s an actively used resource. Having clear, functional URLs is a worthwhile goal for any web property. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[1] &lt;a href=&quot;http://www.useit.com/alertbox/990321.html&quot;&gt;Jakob Nielsen&#039;s Alertbox, March 21, 1999:&lt;/a&gt; 2007 update at the bottom. &lt;br /&gt;
&lt;br /&gt;
&lt;i&gt;p.s. I may receive a beating shortly for the rather ugly URL this blog post possess while standing on a soap box shouting for better URLs. Software I build myself does include more functional addressing, compare &lt;a href=&quot;http://wonderproxy.com/servers/Miami&quot;&gt;wonderproxy.com/servers/Miami/&lt;/a&gt; and  &lt;a href=&quot;http://wonderproxy.com/servers/&quot;&gt;wonderproxy.com/servers/&lt;/a&gt; (I&#039;m working on the case sensitivity thing). &lt;/i&gt; 
    </description>
</item>
<item>
    <title>noreply@ Needs to Die</title>
    <link>http://blog.preinheimer.com/index.php?/archives/344-noreply-Needs-to-Die.html</link>

    <description>
        It&#039;s 2009, there&#039;s &lt;em&gt;absolutely no reason&lt;/em&gt; to make it hard for your customers to talk to you. The technology exists now, and has for a while, to import whatever mail you receive into whatever ticketing system you&#039;re using. Sure, internally use whatever system you&#039;re happy with, there&#039;s lots of inefficiencies in trying to handle any volume of support through traditional mail clients. Your clients on the other hand, aren&#039;t wizards at whatever system you&#039;re using, but they do know how to use their email client.&lt;br /&gt;
&lt;br /&gt;
When you send a client an email, monitor the address and import replies.&lt;br /&gt;
&lt;br /&gt;
Yes, there&#039;s challenges, watching vacation auto-responders (&quot;Hi, I&#039;m on vacation! I&#039;ll come back on the 20th&quot;) and ticketing systems (“Thank’s for emailing Widget-Co, Your email will be answered soonish!”) get into a reply fight isn&#039;t pretty. Handling character sets, HTML formatted mail, etc. all present challenges. But these are manageable problems.&lt;br /&gt;
&lt;br /&gt;
The only reason not to import email into your ticketing system is if you don&#039;t actually want to talk to your customers.  
    </description>
</item>

</channel>
</rss>
