<?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"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Perplexed Labs &#187; Infrastructure</title>
	<atom:link href="http://blog.perplexedlabs.com/category/development/infrastructure/feed/" rel="self" type="application/rss+xml" />
	<link>http://blog.perplexedlabs.com</link>
	<description>web development war stories from the frontlines to the backend</description>
	<lastBuildDate>Mon, 16 May 2011 14:19:38 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.1.2</generator>
		<item>
		<title>Experiences Developing My First iOS / iPhone App &#8211; OnCall for Nagios</title>
		<link>http://blog.perplexedlabs.com/2011/05/16/experiences-developing-my-first-ios-iphone-app-oncall-for-nagios/</link>
		<comments>http://blog.perplexedlabs.com/2011/05/16/experiences-developing-my-first-ios-iphone-app-oncall-for-nagios/#comments</comments>
		<pubDate>Mon, 16 May 2011 14:00:29 +0000</pubDate>
		<dc:creator>Matt</dc:creator>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[Infrastructure]]></category>
		<category><![CDATA[iOS]]></category>
		<category><![CDATA[ios]]></category>
		<category><![CDATA[ios sdk]]></category>
		<category><![CDATA[ipad]]></category>
		<category><![CDATA[ipad sdk]]></category>
		<category><![CDATA[iphone]]></category>
		<category><![CDATA[iphone sdk]]></category>
		<category><![CDATA[nagios]]></category>
		<category><![CDATA[oncall]]></category>

		<guid isPermaLink="false">http://blog.perplexedlabs.com/?p=517</guid>
		<description><![CDATA[I've long since wanted to participate in the explosion of popularity that is the iOS App Store. I toyed with demonstration-purpose applications, learning the foundational aspects of developing on this new platform and the intricacies of Objective-C, but never really had a compelling idea to follow through to completion. Due to the nature of my [...]


Related posts:<ol><li><a href='http://blog.perplexedlabs.com/2009/07/29/django-1-1-released/' rel='bookmark' title='Permanent Link: Django 1.1 Released'>Django 1.1 Released</a></li>
<li><a href='http://blog.perplexedlabs.com/2009/06/30/php-5-3-0-released-and-firefox-3-5/' rel='bookmark' title='Permanent Link: PHP 5.3.0 Released and Firefox 3.5'>PHP 5.3.0 Released and Firefox 3.5</a></li>
</ol>]]></description>
			<content:encoded><![CDATA[<p>I've long since wanted to participate in the explosion of popularity that is the iOS App Store.  I toyed with demonstration-purpose applications, learning the foundational aspects of developing on this new platform and the intricacies of Objective-C, but never really had a compelling idea to follow through to completion.</p>
<p>Due to the nature of my current employment I spend alternating periods of time as primary oncall responder when issues arise in our infrastructure.  I've long been a fan of <a href="http://www.nagios.org/">Nagios</a>, which we use at work as well.  It lacks a native iPhone interface, but provides easy-enough hooks to be able to build one.  This gave me a fantastic opportunity to hone my iOS SDK skills with a app that would certainly "scratch my own itch".  I'm a strong believer that you learn best when addressing a problem you're actually experiencing - you can taste the pain going away.  With <strong><a href="http://bit.ly/nagiosss">OnCall for Nagios</a></strong>, the iPhone becomes a fantastic platform to be able to do basic triage and diagnosis as well as respond and communicate to other participants of your infrastructure team.</p>
<p>The great thing about this as my first <em>real</em> application was that it required a deeper dive into many of the core iPhone SDK APIs as well as the building of a more complicated (from a development perspective) UI.</p>
<p>Under the hood it's interaction with Nagios is accomplished via screen-scraping.  This allows the app to work right out of the box, acting as just another client to the existing Nagios web interface without any server side changes required.  For HTML parsing I built a light abstraction around libxml and used xpath queries to retrieve the relevant data.  Certain views required multiple asynchronous HTTP requests, each with the same delegate, which required another light abstraction layer around NSURLConnection to be able to pass in identifiers for the connections so that incoming data would be appended to the correct NSMutableData property.  Seemingly simple tasks such as HTTP authentication for async NSURLConnection requests and (optionally) ignoring self-signed SSL certificates took significant digging through documentation to gain a better understanding of connection's didReceiveAuthenticationChallenge method.</p>
<p><strong><a href="http://bit.ly/nagiosss">OnCall for Nagios</a></strong> uses a TabBarController with child NavigationBarControllers - this gives you the familiar tabbed buttons at the bottom with the ability to navigate forwards and backwards at the top.  It also uses a combination of custom and NSUserDefaults to provide saved settings - allowing multiple Nagios instances to be setup and switched between within the app.  </p>
<div style="width: 680px; padding: 0px: margin:0px">
<div style="float:left">
<a href="http://blog.perplexedlabs.com/wp-content/uploads/2011/05/sshot4.png"><img src="http://blog.perplexedlabs.com/wp-content/uploads/2011/05/sshot4.png" alt="Problems View Screen Shot" title="Problems" width="320" height="480" class="size-full wp-image-519" /></a>
</div>
<div style="float:right">
<a href="http://blog.perplexedlabs.com/wp-content/uploads/2011/05/sshot5.png"><img src="http://blog.perplexedlabs.com/wp-content/uploads/2011/05/sshot5.png" alt="Service Detail View" title="ServiceDetail" width="320" height="480" class="size-full wp-image-525" /></a>
</div>
<div style="clear:both;"></div>
</div>
<p>Interface development was slow and tedious for the initial setup.  Determining the correct nesting order (TabBarController is the parent of multiple NavigationBarControllers) was huge.  Once you get the hang of IBOutlets and where to "connect the dots" duplicating previous discoveries becomes easier.  For certain interface details I often found myself hand-coding these elements instead of arguably spending more time figuring out how to do them correctly in Interface Builder.  I'd be curious to hear of other's experiences on where to draw that line.  I found that anything non-trivial required some level of code (take custom UITableViewCells) although I'm sure there are other ways to accomplish this.</p>
<p>One of the most important aspects to developing in Objective-C is a solid of understanding of it's memory management.  With a solid background in C as well as Python - this wasn't terribly difficult.  <strong><a href="http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/MemoryMgmt/MemoryMgmt.html">This document</a></strong> was extremely helpful.  If you follow the golden rule <strong>"you only release or autorelease objects you own"</strong> you'll be fine.  I found Xcode's static code analysis (clang) to be extremely helpful in situations that weren't immediately obvious, diagnosing and eliminating problems in my code.  It's quite impressive to see arrows being overlaid onto your source code illustrating the code path that's causing the issue.</p>
<p><a href="http://blog.perplexedlabs.com/wp-content/uploads/2011/05/clang3.png"><img src="http://blog.perplexedlabs.com/wp-content/uploads/2011/05/clang3.png" alt="Enable Static Code Analyzer" title="clang" width="649" height="246" class="aligncenter size-full wp-image-522" /></a></p>
<p>I also highly recommend <a href="http://www.flurry.com">Flurry</a>, a free analytics SDK.  It's extremely easy to integrate and provides a wide variety of metrics without much effort.  It was also helpful in identifying when Apple was reviewing the application.  The approval process took longer than I had hoped but wasn't as problematic as I had imagined it to be.</p>
<p>I also found it extremely beneficial to release a <em>lite</em> version.  For this type of application the choices were obvious as to limiting functionality (display fewer problems, dont allow multiple instances, only show 2 hosts, etc.).  In terms of implementation of the lite version - it exists in the same codebase.  In Xcode I defined a new target with a different output binary and a specific C define which the code pivots on.</p>
<pre class="brush: cpp; title: ;">
#ifdef LITE_VERSION
    if ([table count] &lt; 2) {
        [table addObject:data];
    } else if (!didShowUpgrade) {
        didShowUpgrade = TRUE;
        UIAlertView *alertBox = [[UIAlertView alloc] initWithTitle:@&quot;Lite Version&quot; message:@&quot;The lite version only displays 2 problems, consider upgrading!&quot; delegate:self cancelButtonTitle:@&quot;Ok&quot; otherButtonTitles:nil];
        [alertBox show];
        [alertBox release];
    }
#else
    [table addObject:data];
#endif
</pre>
<p>The lite version sees much higher downloads and facilitates the need for experimentation and testing before purchasing.</p>
<p>I've certainly learned a lot throughout this process and I'm looking forward to continuing to develop features for this app as well as getting back into game development on this powerful platform.  If you'd like more detail on anything I've glossed over feel free to ask questions in the comments or e-mail me directly.</p>
<p>Good luck!</p>


<p>Related posts:<ol><li><a href='http://blog.perplexedlabs.com/2009/07/29/django-1-1-released/' rel='bookmark' title='Permanent Link: Django 1.1 Released'>Django 1.1 Released</a></li>
<li><a href='http://blog.perplexedlabs.com/2009/06/30/php-5-3-0-released-and-firefox-3-5/' rel='bookmark' title='Permanent Link: PHP 5.3.0 Released and Firefox 3.5'>PHP 5.3.0 Released and Firefox 3.5</a></li>
</ol></p>]]></content:encoded>
			<wfw:commentRss>http://blog.perplexedlabs.com/2011/05/16/experiences-developing-my-first-ios-iphone-app-oncall-for-nagios/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Async DNS Resolution in Tornado&#8217;s AsyncHttpClient (curl multi, c-ares)</title>
		<link>http://blog.perplexedlabs.com/2010/11/01/asynchronous-dns-resolution-in-tornados-asynchttpclient-curl-multi-c-ares/</link>
		<comments>http://blog.perplexedlabs.com/2010/11/01/asynchronous-dns-resolution-in-tornados-asynchttpclient-curl-multi-c-ares/#comments</comments>
		<pubDate>Mon, 01 Nov 2010 23:50:26 +0000</pubDate>
		<dc:creator>Matt</dc:creator>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[Infrastructure]]></category>
		<category><![CDATA[async]]></category>
		<category><![CDATA[asynchronous]]></category>
		<category><![CDATA[asynchttpclient]]></category>
		<category><![CDATA[c-ares]]></category>
		<category><![CDATA[curl]]></category>
		<category><![CDATA[dns]]></category>
		<category><![CDATA[libcurl]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[tornado]]></category>

		<guid isPermaLink="false">http://blog.perplexedlabs.com/?p=513</guid>
		<description><![CDATA[I learned some rather important facts about cURL's multi interface (which makes it possible to perform asynchronous HTTP requests and what Python's Tornado framework uses under the hood in it's AsyncHttpClient helper). I was investigating some intermittent issues in an application at work - transient DNS issues were causing the application to become unresponsive. This [...]


Related posts:<ol><li><a href='http://blog.perplexedlabs.com/2010/07/01/pythons-tornado-has-swept-me-off-my-feet/' rel='bookmark' title='Permanent Link: Python&#8217;s Tornado has swept me off my feet'>Python&#8217;s Tornado has swept me off my feet</a></li>
<li><a href='http://blog.perplexedlabs.com/2010/07/24/tornado-1-0-released/' rel='bookmark' title='Permanent Link: Tornado 1.0 Released'>Tornado 1.0 Released</a></li>
<li><a href='http://blog.perplexedlabs.com/2010/02/08/deployment-using-capistrano-and-webistrano-via-rails-and-phusion-passenger/' rel='bookmark' title='Permanent Link: Deployment Using Capistrano / Webistrano via Rails / Phusion Passenger'>Deployment Using Capistrano / Webistrano via Rails / Phusion Passenger</a></li>
</ol>]]></description>
			<content:encoded><![CDATA[<p>I learned some rather important facts about cURL's multi interface (which makes it possible to perform asynchronous HTTP requests and what Python's <a href="http://www.tornadoweb.org/">Tornado</a> framework uses under the hood in it's AsyncHttpClient helper).</p>
<p>I was investigating some intermittent issues in an application at work - transient DNS issues were causing the application to become unresponsive.  This was confusing at first because it was written to perform HTTP requests asynchronously.  The important thing here is that it was specifically DNS resolution that was failing.  As I dug deeper I realized that simple health checks, ones that did not perform any HTTP requests, were also hanging... something had to be blocking.</p>
<p>Taking a look at the Tornado source, unsurprisingly, it was leveraging the <a href="http://curl.haxx.se/libcurl/c/libcurl-multi.html">multi</a> functionality of libcurl (via pycurl).  What was surprising to me was that the DNS resolution portion of the multi interface, by default, blocks on non-windows installations.  Only when compiled with <a href="http://c-ares.haxx.se/">c-ares</a> support does it perform these async.</p>
<p>You learn something new every day!</p>


<p>Related posts:<ol><li><a href='http://blog.perplexedlabs.com/2010/07/01/pythons-tornado-has-swept-me-off-my-feet/' rel='bookmark' title='Permanent Link: Python&#8217;s Tornado has swept me off my feet'>Python&#8217;s Tornado has swept me off my feet</a></li>
<li><a href='http://blog.perplexedlabs.com/2010/07/24/tornado-1-0-released/' rel='bookmark' title='Permanent Link: Tornado 1.0 Released'>Tornado 1.0 Released</a></li>
<li><a href='http://blog.perplexedlabs.com/2010/02/08/deployment-using-capistrano-and-webistrano-via-rails-and-phusion-passenger/' rel='bookmark' title='Permanent Link: Deployment Using Capistrano / Webistrano via Rails / Phusion Passenger'>Deployment Using Capistrano / Webistrano via Rails / Phusion Passenger</a></li>
</ol></p>]]></content:encoded>
			<wfw:commentRss>http://blog.perplexedlabs.com/2010/11/01/asynchronous-dns-resolution-in-tornados-asynchttpclient-curl-multi-c-ares/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Improved deploy:cleanup for capistrano</title>
		<link>http://blog.perplexedlabs.com/2010/09/08/improved-deploycleanup-for-capistrano/</link>
		<comments>http://blog.perplexedlabs.com/2010/09/08/improved-deploycleanup-for-capistrano/#comments</comments>
		<pubDate>Wed, 08 Sep 2010 22:08:50 +0000</pubDate>
		<dc:creator>Matt</dc:creator>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[Infrastructure]]></category>
		<category><![CDATA[Ruby]]></category>
		<category><![CDATA[Ruby on Rails]]></category>
		<category><![CDATA[capistrano]]></category>
		<category><![CDATA[rails]]></category>
		<category><![CDATA[ruby]]></category>

		<guid isPermaLink="false">http://blog.perplexedlabs.com/?p=478</guid>
		<description><![CDATA[We ran into a problem today where capistrano wasn't correctly cleaning up old releases on a 15-minute multi-host deploy. It seems like the default deploy:cleanup task wasn't written with multiple hosts in mind. Essentially what it does is list the contents of your releases_path for the first host in the list of hosts and assumes [...]


Related posts:<ol><li><a href='http://blog.perplexedlabs.com/2010/02/08/deployment-using-capistrano-and-webistrano-via-rails-and-phusion-passenger/' rel='bookmark' title='Permanent Link: Deployment Using Capistrano / Webistrano via Rails / Phusion Passenger'>Deployment Using Capistrano / Webistrano via Rails / Phusion Passenger</a></li>
<li><a href='http://blog.perplexedlabs.com/2009/06/01/be-language-agnostic-solve-the-problem/' rel='bookmark' title='Permanent Link: Be Language Agnostic &#8211; Solve the Problem!'>Be Language Agnostic &#8211; Solve the Problem!</a></li>
<li><a href='http://blog.perplexedlabs.com/2009/11/30/8-books-to-get-a-developer-for-the-holidays/' rel='bookmark' title='Permanent Link: 8 Books To Get A Developer For The Holidays'>8 Books To Get A Developer For The Holidays</a></li>
</ol>]]></description>
			<content:encoded><![CDATA[<p>We ran into a problem today where capistrano wasn't correctly cleaning up old releases on a 15-minute multi-host deploy.  It seems like the default deploy:cleanup task wasn't written with multiple hosts in mind.</p>
<p>Essentially what it does is list the contents of your releases_path for the first host in the list of hosts and assumes that all those individual release directories will be present on all other hosts in the current deploy.  This is a poor assumption.  What if one host isn't deployed to as frequently (perhaps a QA environment?).  These edge cases cause trouble with the default code.</p>
<p>What it should do is, for each host you're deploying to, check the releases_path for that host with keep_releases and delete only those old directories on just that host.  I re-wrote deploy:cleanup to do just that using some of the features of run() to execute commands only on specific hosts...</p>
<pre class="brush: ruby; title: ;">
  task :cleanup, :except =&gt; { :no_release =&gt; true } do
    count = fetch(:keep_releases, 5).to_i
    run &quot;hostname&quot; do |c, s, hostname|
      local_releases = capture(&quot;ls -xt #{releases_path}&quot;, :hosts =&gt; [hostname]).split.reverse
      if count &gt;= local_releases.length
        logger.important &quot;no old releases to clean up on #{hostname}&quot;
      else
        logger.info &quot;keeping #{count} of #{local_releases.length} deployed releases on #{hostname}&quot;

        (local_releases - local_releases.last(count)).each { |release|
          run &quot;#{sudo} rm -rf #{File.join(releases_path, release)}&quot;, :hosts =&gt; [hostname]
        }
      end
    end
  end
</pre>


<p>Related posts:<ol><li><a href='http://blog.perplexedlabs.com/2010/02/08/deployment-using-capistrano-and-webistrano-via-rails-and-phusion-passenger/' rel='bookmark' title='Permanent Link: Deployment Using Capistrano / Webistrano via Rails / Phusion Passenger'>Deployment Using Capistrano / Webistrano via Rails / Phusion Passenger</a></li>
<li><a href='http://blog.perplexedlabs.com/2009/06/01/be-language-agnostic-solve-the-problem/' rel='bookmark' title='Permanent Link: Be Language Agnostic &#8211; Solve the Problem!'>Be Language Agnostic &#8211; Solve the Problem!</a></li>
<li><a href='http://blog.perplexedlabs.com/2009/11/30/8-books-to-get-a-developer-for-the-holidays/' rel='bookmark' title='Permanent Link: 8 Books To Get A Developer For The Holidays'>8 Books To Get A Developer For The Holidays</a></li>
</ol></p>]]></content:encoded>
			<wfw:commentRss>http://blog.perplexedlabs.com/2010/09/08/improved-deploycleanup-for-capistrano/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Python&#8217;s Tornado has swept me off my feet</title>
		<link>http://blog.perplexedlabs.com/2010/07/01/pythons-tornado-has-swept-me-off-my-feet/</link>
		<comments>http://blog.perplexedlabs.com/2010/07/01/pythons-tornado-has-swept-me-off-my-feet/#comments</comments>
		<pubDate>Thu, 01 Jul 2010 13:00:37 +0000</pubDate>
		<dc:creator>Matt</dc:creator>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[Infrastructure]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[API]]></category>
		<category><![CDATA[asynchronous]]></category>
		<category><![CDATA[non-blocking]]></category>
		<category><![CDATA[REST]]></category>
		<category><![CDATA[tornado]]></category>
		<category><![CDATA[web.py]]></category>

		<guid isPermaLink="false">http://blog.perplexedlabs.com/?p=457</guid>
		<description><![CDATA[I've been working with Python's Tornado for about 2 months now and I love it. Tornado is a non-blocking web server written in Python. It's structure is similar to web.py so users of that popular Python web framework will feel right at home. This is a structure that lends itself really well to developing RESTful [...]


Related posts:<ol><li><a href='http://blog.perplexedlabs.com/2010/03/04/python-data-sharing-in-the-multiprocessing-module/' rel='bookmark' title='Permanent Link: Python data sharing in the multiprocessing module'>Python data sharing in the multiprocessing module</a></li>
<li><a href='http://blog.perplexedlabs.com/2010/07/24/tornado-1-0-released/' rel='bookmark' title='Permanent Link: Tornado 1.0 Released'>Tornado 1.0 Released</a></li>
<li><a href='http://blog.perplexedlabs.com/2010/11/01/asynchronous-dns-resolution-in-tornados-asynchttpclient-curl-multi-c-ares/' rel='bookmark' title='Permanent Link: Async DNS Resolution in Tornado&#8217;s AsyncHttpClient (curl multi, c-ares)'>Async DNS Resolution in Tornado&#8217;s AsyncHttpClient (curl multi, c-ares)</a></li>
</ol>]]></description>
			<content:encoded><![CDATA[<p>I've been working with Python's <a href="http://www.tornadoweb.org/">Tornado</a> for about 2 months now and I love it.</p>
<p>Tornado is a non-blocking web server written in Python.  It's structure is similar to web.py so users of that popular Python web framework will feel right at home.  This is a structure that lends itself really well to developing RESTful APIs as the methods you write to handle incoming requests are named after the HTTP methods used:</p>
<pre class="brush: python; title: ;">
class PlaceHandler(tornado.web.RequestHandler):
    def get(self, id):
        # respond to a GET
        self.write('GETting something')

    def post(self):
        # respond to a POST
        self.write('POSTing something')
</pre>
<p>You match URI paths to "handlers" (the <em>controller</em> for those MVC folk) via a list of regex, handler tuples that instantiate an "application".</p>
<pre class="brush: python; title: ;">
application = tornado.web.Application([
    (r&quot;/place&quot;, PlaceHandler),
    (r&quot;/place/([0-9]+)&quot;, PlaceHandler)
])

if __name__ == &quot;__main__&quot;:
    http_server = tornado.httpserver.HTTPServer(application)
    http_server.listen(8888)
    tornado.ioloop.IOLoop.instance().start()
</pre>
<p>As usual any values that are captured from the regex are passed, in order, to the method that receives the request in the handler.</p>
<p>Because of it's non-blocking nature Tornado bundles an asynchronous HTTP client for use internally.  Additional modules include a command line and config file convenience library, escaping, 3rd party authentication (Facebook, Twitter, etc.), a wrapper around MySQLdb, and templating.  All in all this makes it a formidable web framework in its own right, especially if you're looking for something that's light and <a href="http://www.tornadoweb.org/documentation#performance">FAST</a>.</p>
<p>In production, I'm running 4 Tornado instances per server behind <a href="http://nginx.org/">nginx</a>.</p>
<p>One issue not addressed out of the box was daemonizing the Tornado instance.  I added PID file management and the ability to daemonize as follows (pid.py module follows):</p>
<pre class="brush: python; title: ;">
# capture stdout/err in logfile
log_file = 'tornado.%s.log' % options.port
log = open(os.path.join(settings.log_path, log_file), 'a+')

# check pidfile
pidfile_path = settings.PIDFILE_PATH % options.port
pid.check(pidfile_path)

# daemonize
daemon_context = daemon.DaemonContext(stdout=log, stderr=log, working_directory='.')
with daemon_context:
    # write the pidfile
    pid.write(pidfile_path)

    # initialize the application
    http_server = tornado.httpserver.HTTPServer(application.app)
    http_server.listen(options.port, '127.0.0.1')

    try:
        # enter the Tornado IO loop
        tornado.ioloop.IOLoop.instance().start()
    finally:
        # ensure we remove the pidfile
        pid.remove(pidfile_path)
</pre>
<p>And now the pid.py module:</p>
<pre class="brush: python; title: ;">
# pid.py - module to help manage PID files
import os
import logging
import fcntl
import errno

def check(path):
    # try to read the pid from the pidfile
    try:
        logging.info(&quot;Checking pidfile '%s'&quot;, path)
        pid = int(open(path).read().strip())
    except IOError, (code, text):
        pid = None
        # re-raise if the error wasn't &quot;No such file or directory&quot;
        if code != errno.ENOENT:
            raise

    # try to kill the process
    try:
        if pid is not None:
            logging.info(&quot;Killing PID %s&quot;, pid)
            os.kill(pid, 9)
    except OSError, (code, text):
        # re-raise if the error wasn't &quot;No such process&quot;
        if code != errno.ESRCH:
            raise

def write(path):
    try:
        pid = os.getpid()
        pidfile = open(path, 'wb')
        # get a non-blocking exclusive lock
        fcntl.flock(pidfile.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
        # clear out the file
        pidfile.seek(0)
        pidfile.truncate(0)
        # write the pid
        pidfile.write(str(pid))
        logging.info(&quot;Writing PID %s to '%s'&quot;, pid, path)
    except:
        raise
    finally:
        try:
            pidfile.close()
        except:
            pass

def remove(path):
    try:
        # make sure we delete our pidfile
        logging.info(&quot;Removing pidfile '%s'&quot;, path)
        os.unlink(path)
    except:
        pass
</pre>
<p>I'm going to follow up this post another on how I added a simple concept of "models" and an easy way to perform MySQL transactions.  Let me know if you have any specific questions!</p>


<p>Related posts:<ol><li><a href='http://blog.perplexedlabs.com/2010/03/04/python-data-sharing-in-the-multiprocessing-module/' rel='bookmark' title='Permanent Link: Python data sharing in the multiprocessing module'>Python data sharing in the multiprocessing module</a></li>
<li><a href='http://blog.perplexedlabs.com/2010/07/24/tornado-1-0-released/' rel='bookmark' title='Permanent Link: Tornado 1.0 Released'>Tornado 1.0 Released</a></li>
<li><a href='http://blog.perplexedlabs.com/2010/11/01/asynchronous-dns-resolution-in-tornados-asynchttpclient-curl-multi-c-ares/' rel='bookmark' title='Permanent Link: Async DNS Resolution in Tornado&#8217;s AsyncHttpClient (curl multi, c-ares)'>Async DNS Resolution in Tornado&#8217;s AsyncHttpClient (curl multi, c-ares)</a></li>
</ol></p>]]></content:encoded>
			<wfw:commentRss>http://blog.perplexedlabs.com/2010/07/01/pythons-tornado-has-swept-me-off-my-feet/feed/</wfw:commentRss>
		<slash:comments>17</slash:comments>
		</item>
		<item>
		<title>Deployment Using Capistrano / Webistrano via Rails / Phusion Passenger</title>
		<link>http://blog.perplexedlabs.com/2010/02/08/deployment-using-capistrano-and-webistrano-via-rails-and-phusion-passenger/</link>
		<comments>http://blog.perplexedlabs.com/2010/02/08/deployment-using-capistrano-and-webistrano-via-rails-and-phusion-passenger/#comments</comments>
		<pubDate>Mon, 08 Feb 2010 13:00:32 +0000</pubDate>
		<dc:creator>Matt</dc:creator>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[Django]]></category>
		<category><![CDATA[Infrastructure]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[Ruby]]></category>
		<category><![CDATA[Ruby on Rails]]></category>
		<category><![CDATA[deployment]]></category>
		<category><![CDATA[mod_wsgi]]></category>
		<category><![CDATA[mongrel]]></category>
		<category><![CDATA[passenger]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[phusion passenger]]></category>
		<category><![CDATA[rails]]></category>
		<category><![CDATA[ruby]]></category>
		<category><![CDATA[subversion]]></category>

		<guid isPermaLink="false">http://blog.perplexedlabs.com/?p=408</guid>
		<description><![CDATA[I finally got around to setting up a more sophisticated deployment system for some of my apps. These apps include some built on a custom PHP framework and others that are Python / Django apps. I figured I'd share my experience... Why is a high-level deployment infrastructure important? Deployment is something that should be simple, [...]


Related posts:<ol><li><a href='http://blog.perplexedlabs.com/2009/01/13/installing-ruby-enterprise-edition-with-phusion-passenger/' rel='bookmark' title='Permanent Link: Installing Ruby Enterprise Edition with Phusion Passenger'>Installing Ruby Enterprise Edition with Phusion Passenger</a></li>
<li><a href='http://blog.perplexedlabs.com/2010/09/08/improved-deploycleanup-for-capistrano/' rel='bookmark' title='Permanent Link: Improved deploy:cleanup for capistrano'>Improved deploy:cleanup for capistrano</a></li>
<li><a href='http://blog.perplexedlabs.com/2009/11/30/8-books-to-get-a-developer-for-the-holidays/' rel='bookmark' title='Permanent Link: 8 Books To Get A Developer For The Holidays'>8 Books To Get A Developer For The Holidays</a></li>
</ol>]]></description>
			<content:encoded><![CDATA[<p>I finally got around to setting up a more sophisticated deployment system for some of my apps.  These apps include some built on a custom PHP framework and others that are Python / Django apps.  I figured I'd share my experience...</p>
<p>Why is a high-level deployment infrastructure important?  Deployment is something that should be simple, accessible, and repeatable.  It should be as close to a "single click" as possible.  Previously, for me, it was a bash script that exported some SVN branches.  While this worked fine, as projects progress, you want some accountability, history, and the ability to roll back mission critical applications when something goes wrong with a deploy.</p>
<p><a href="http://www.capify.org/">Capistrano</a> is an open source, command line, deployment tool that provides all of these features.  It's written in Ruby.  You leverage a variety of built in "recipes" (Capistrano's term for a deployment script) that execute certain procedures to deploy an app.  Out-of-the-box it's ideally built to deploy a Rails app.  However, after some minor tweaks it can deploy most anything and do it well.  It can restart servers, update symlinks, change permissions - pretty much anything.  It assumes you access your POSIX compliant server via SSH via the same password (or have ssh keys setup).</p>
<p><a href="http://labs.peritor.com/webistrano">Webistrano</a> is an open source web front-end for Capistrano.  It's a convenience layer that abstracts the command line away and provides an interface to perform the same tasks.  This interface shows history as well as providing a convenient GUI for creating new deployment projects, stages, and recipes.  Highly recommended.</p>
<p>Let's get down to business.  This post makes a few assumptions about things you've already installed and used previously.</p>
<ul>
<li><a href="http://www.ruby-lang.org/en/">Ruby 1.8.5</a>+
<li><a href="http://rubyforge.org/projects/rubygems/">RubyGems</a>
<li><a href="http://www.mysql.com/">MySQL 5.0</a>+
<li><a href="http://www.apache.org/">Apache 2</a>+
<li><a href="http://subversion.tigris.org/">Subversion</a> and a repository containing the "production" branch of your app.
</ul>
<h3>Installing Capistrano</h3>
<p>Well, this is an easy one (you probably want to do this as root):</p>
<blockquote><p>
gem install capistrano
</p></blockquote>
<h3>Installing Webistrano</h3>
<p>Also fairly easy, with a little splash of configuration.</p>
<blockquote><p>
# wget http://labs.peritor.com/webistrano/attachment/wiki/Download/webistrano-1.4.zip<br />
# unzip webistrano-1.4.zip<br />
# mv webistrano-1.4 /path/to/where/you/want/webistrano
</p></blockquote>
<p>Setup the database tables and create a new webistrano user (obviously be conscious of your security preferences for access to your database in the host and password portions):</p>
<blockquote><p>
# mysql<br />
mysql> CREATE DATABASE `webistrano`;<br />
mysql> CREATE USER 'webistrano'@'localhost' IDENTIFIED BY 'password';<br />
mysql> GRANT ALL PRIVILEGES ON `webistrano`.* TO 'webistrano'@'localhost' WITH GRANT OPTION;
</p></blockquote>
<p>Now, in the directory where you placed webistrano you're going to want to copy <i>config/database.yml.sample</i> to <i>config/database.yml</i>.  Edit this file, in the production area, to match your database settings.  By default the file expects a socket to connect, you can chase this by specifying <i>host:</i> and <i>port:</i>.  (Keep in mind Webistrano is simply a Rails app).</p>
<p>You should now be able to have Rails migrate the new database you created.  In the webistrano directory:</p>
<blockquote><p>
# RAILS_ENV=production rake db:migrate
</p></blockquote>
<p>Finally, copy <i>config/webistrano_config.rb.sample</i> to <i>config/webistrano_config.rb</i> and edit according to your preferred mail settings.</p>
<p>We can now test to see if webistrano is working properly by serving it via mongrel:</p>
<blockquote><p>
# ruby script/server -d -e production -p 3000
</p></blockquote>
<p>This starts a single mongrel daemon, using the production environment, listening on port 3000.  You should now be able to hit http://127.0.0.1:3000/ and get the Webistrano login prompt.  If this is working, kill that mongrel instance.</p>
<p>For longer term serving I decided to go with Phusion Passenger (essentially mod_rails for Apache).  It's a nearly zero configuration solution for serving a rails app and will feel at home to anyone with experience serving PHP apps via Apache and mod_php.</p>
<h3>Installing Phusion Passenger</h3>
<p>Again, as root:</p>
<blockquote><p>
# gem install passenger<br />
# passenger-install-apache2-module
</p></blockquote>
<p>The second command will invoke an installer which compiled Passenger and provides instructions on integrating it into your Apache config.  Essentially, edit your httpd.conf as follows (<strong>these were specific to my install, make sure to use the ones provide by the installer for you</strong>):</p>
<blockquote><p>
LoadModule passenger_module /usr/lib/ruby/gems/1.8/gems/passenger-2.2.9/ext/apache2/mod_passenger.so<br />
PassengerRoot /usr/lib/ruby/gems/1.8/gems/passenger-2.2.9<br />
PassengerRuby /usr/bin/ruby
</p></blockquote>
<p>Now you can simply add VirtualHost entries to your httpd.conf for any of your Rails apps.  Let's add one for Webistrano:</p>
<blockquote><p>
&lt;VirtualHost *:80&gt;<br />
ServerName webistrano.mydomain.com<br />
DocumentRoot /path/to/webistrano/public<br />
&lt;/VirtualHost&gt;
</p></blockquote>
<p>Yes, Passenger makes it that simple.  Add configuration directives as needed for your environment.</p>
<p>Now Webistrano should be serving from the VirtualHost you specified, seamlessly, via Passenger.</p>
<h3>Deploying A Non-Rails App</h3>
<p>Now the fun stuff.</p>
<p>Capistrano breaks things down into projects, stages, and recipes.  Each app you want managed by capistrano should be it's own project.  Each project should have a stage for at least production and optionally staging and development.</p>
<p>Hosts are added globally and form the targets of a deploy for any given project.  Hosts can include web, app, and database servers.</p>
<p>Deployments in Capistrano are done to a child directory under "releases" named via the date and time of the deployment.  By default 5 releases are kept and available to rollback to.  Upon successful deployment a symlink (default is called "current" and can be modified via the <i>current_path</i> configuration variable) is updated to that release directory.  It is this symlink that should be targeted by your webserver (your DocumentRoot in Apache).</p>
<p>Capistrano also creates a "shared" directory that is symlinked to in each release useful for storing logs and other data that should be maintained through each deployment.</p>
<p>For non-rails apps you'll use the "Pure File" project type when creating your new project.  Upon project creation you can add configuration variables specific to your project.  I recommend using <i>:export</i> instead of <i>:checkout</i> for <i>deploy_via</i> for production subversion deployments as this doesn't expose .svn directories.  Use an SSH user that has enough permissions to create directories where your deploy will occur or, specify <i>use_sudo</i> to true and create a new configuration variable <i>admin_runner</i> and set it to the same user as <i>runner</i>.</p>
<p>Add a stage to your new project for "production".  In the "Manage Hosts" page add a new host for each of your application servers.  Then add each host as a target of your "production" stage of your project.</p>
<p>At this point you should be able to execute the "Setup" task for your "production" stage.  This is a one time task that simply creates the directories.</p>
<p>Assuming this went successfully, try doing a "Deploy" and see if that finishes without error.  You might have to play around with permissions and other minor issues - post a comment if you have any specific questions.</p>
<p>For my PHP framework there are a couple specific tasks I wanted to run in addition to the default Capistrano tasks.  You do this by creating custom recipes in the "Manage Recipes" page in Webistrano.  Recipes are simply procedures written in ruby.  Here's what my recipe looks like:</p>
<pre class="brush: ruby; title: ;">
namespace :deploy do
	task :setup, :except =&gt; { :no_release =&gt; true } do
		dirs = [deploy_to, releases_path, shared_path]
		dirs += shared_children.map { |d| File.join(shared_path, d) }
		run &quot;#{try_sudo} mkdir -p #{dirs.join(' ')} &amp;&amp; #{try_sudo} chmod g+w #{dirs.join(' ')}&quot;
		run &quot;chmod 777 #{shared_path}/log&quot;
	end

	task :finalize_update, :except =&gt; { :no_release =&gt; true } do
		run &quot;mkdir -p #{latest_release}/app/tmp&quot;
		run &quot;chmod -R 777 #{latest_release}/app/tmp&quot;
		run &quot;rm -rf #{latest_release}/app/logs&quot;
		run &quot;ln -s #{shared_path}/log #{latest_release}/app/logs&quot;
		run &quot;cp #{latest_release}/public_html/.htaccess-production #{latest_release}/public_html/.htaccess&quot;
		run &quot;cp #{latest_release}/app/config/config-production.php #{latest_release}/app/config/config.php&quot;
		run &quot;cp #{latest_release}/app/config/db-default.php #{latest_release}/app/config/db.php&quot;
		run &quot;cp #{latest_release}/app/config/memcache-default.php #{latest_release}/app/config/memcache.php&quot;
	end
end
</pre>
<p>If you're not familiar with Ruby - what this code is essentially doing is overwriting two tasks in the :deploy namespace with my custom code.</p>
<p>The first, :setup, simply duplicates the base :setup functionality discussed above (creating the releases and shared directories) and chmods the shared log directory to be writable.</p>
<p>The second, :finalize_update, performs a variety of configuration tasks for a PHP app built with my framework.  Also, you'll notice that I'm removing my app's logs directory and symlinking to the shared log directory.  This way all releases will log to the same directory, consistently.  </p>
<p>In my case all of these procedures are command line instructions.  Alternatively, you can do a variety of things leveraging the full breadth of the Ruby language and any gem you'd like to introduce.   Things such as accessing your CDN API to clear image, JS, or CSS caching, etc.</p>
<h3>Deploying Django Apps</h3>
<p>First off it's worth noting that I serve my Django apps via mod_wsgi.  To make the deployment process easier here's what my app.wsgi script looks like:</p>
<pre class="brush: python; title: ;">
import os
import sys

appdir = os.path.normpath(os.path.join(os.path.realpath(os.path.dirname(__file__)), '..'))
sys.path.insert(0, appdir)
os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'
os.environ['PYTHON_EGG_CACHE'] = os.path.join(appdir, '.python-eggs')
import django.core.handlers.wsgi
application = django.core.handlers.wsgi.WSGIHandler()
</pre>
<p>This code allows us to avoid having to hardcode paths in the wsgi script (and thus avoid having to change them when we deploy).  It assumes the following directory structure:</p>
<blockquote><p>
.python-eggs (egg cache)<br />
apps (apps path is added to python system path in settings.py)<br />
public (where your .wsgi script resides)<br />
site_media<br />
templates<br />
settings.py<br />
settings-production.py (used for deploy)<br />
urls.py<br />
...
</p></blockquote>
<p>If you follow this convention, the following Capistrano recipe works great:</p>
<pre class="brush: ruby; title: ;">
namespace :deploy do
	task :setup, :except =&gt; { :no_release =&gt; true } do
		dirs = [deploy_to, releases_path, shared_path]
		dirs += shared_children.map { |d| File.join(shared_path, d) }
		run &quot;#{try_sudo} mkdir -p #{dirs.join(' ')} &amp;&amp; #{try_sudo} chmod g+w #{dirs.join(' ')}&quot;
		run &quot;chmod 777 #{shared_path}/log&quot;
	end

	task :finalize_update, :except =&gt; { :no_release =&gt; true } do
		run &quot;rm -rf #{latest_release}/logs&quot;
		run &quot;ln -s #{shared_path}/log #{latest_release}/logs&quot;
		run &quot;cp #{latest_release}/settings-production.py #{latest_release}/settings.py&quot;
		run &quot;mkdir -p #{latest_release}/.python-eggs&quot;
		run &quot;chmod 777 #{latest_release}/.python-eggs&quot;
	end
end
</pre>
<h3>Fin</h3>
<p>This should give you a nice intro to leveraging Capistrano via Webistrano.  Feel free to comment with questions, suggestions, or anything else!</p>


<p>Related posts:<ol><li><a href='http://blog.perplexedlabs.com/2009/01/13/installing-ruby-enterprise-edition-with-phusion-passenger/' rel='bookmark' title='Permanent Link: Installing Ruby Enterprise Edition with Phusion Passenger'>Installing Ruby Enterprise Edition with Phusion Passenger</a></li>
<li><a href='http://blog.perplexedlabs.com/2010/09/08/improved-deploycleanup-for-capistrano/' rel='bookmark' title='Permanent Link: Improved deploy:cleanup for capistrano'>Improved deploy:cleanup for capistrano</a></li>
<li><a href='http://blog.perplexedlabs.com/2009/11/30/8-books-to-get-a-developer-for-the-holidays/' rel='bookmark' title='Permanent Link: 8 Books To Get A Developer For The Holidays'>8 Books To Get A Developer For The Holidays</a></li>
</ol></p>]]></content:encoded>
			<wfw:commentRss>http://blog.perplexedlabs.com/2010/02/08/deployment-using-capistrano-and-webistrano-via-rails-and-phusion-passenger/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>8 Books To Get A Developer For The Holidays</title>
		<link>http://blog.perplexedlabs.com/2009/11/30/8-books-to-get-a-developer-for-the-holidays/</link>
		<comments>http://blog.perplexedlabs.com/2009/11/30/8-books-to-get-a-developer-for-the-holidays/#comments</comments>
		<pubDate>Mon, 30 Nov 2009 13:30:06 +0000</pubDate>
		<dc:creator>Matt</dc:creator>
				<category><![CDATA[Book Reviews]]></category>
		<category><![CDATA[Clojure]]></category>
		<category><![CDATA[CSS]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[Django]]></category>
		<category><![CDATA[Infrastructure]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[Ruby]]></category>
		<category><![CDATA[Ruby on Rails]]></category>
		<category><![CDATA[book review]]></category>
		<category><![CDATA[clojure]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[programmer]]></category>
		<category><![CDATA[rails]]></category>
		<category><![CDATA[ruby]]></category>

		<guid isPermaLink="false">http://blog.perplexedlabs.com/?p=385</guid>
		<description><![CDATA[Send this to your significant other/parent/relative/friend so, instead of that sweater, you get one of these nuggets of awesome this Christmas. The Pragmatic Programmer: From Journeyman to Master Write better, cleaner, more maintainable code. Learn how to manage your projects and focus on shipping your product. With insight that covers the gamut of software development [...]


Related posts:<ol><li><a href='http://blog.perplexedlabs.com/2009/06/01/be-language-agnostic-solve-the-problem/' rel='bookmark' title='Permanent Link: Be Language Agnostic &#8211; Solve the Problem!'>Be Language Agnostic &#8211; Solve the Problem!</a></li>
<li><a href='http://blog.perplexedlabs.com/2010/02/08/deployment-using-capistrano-and-webistrano-via-rails-and-phusion-passenger/' rel='bookmark' title='Permanent Link: Deployment Using Capistrano / Webistrano via Rails / Phusion Passenger'>Deployment Using Capistrano / Webistrano via Rails / Phusion Passenger</a></li>
<li><a href='http://blog.perplexedlabs.com/2009/07/28/django-1-0-template-development-sample-chapter-serving-multiple-templates/' rel='bookmark' title='Permanent Link: Django 1.0 Template Development: Sample Chapter &#8220;Serving Multiple Templates&#8221;'>Django 1.0 Template Development: Sample Chapter &#8220;Serving Multiple Templates&#8221;</a></li>
</ol>]]></description>
			<content:encoded><![CDATA[<p>Send this to your significant other/parent/relative/friend so, instead of that sweater, you get one of these nuggets of awesome this Christmas.</p>
<h3><a href="http://www.amazon.com/gp/product/020161622X?ie=UTF8&#038;tag=perplabs-20&#038;linkCode=as2&#038;camp=1789&#038;creative=390957&#038;creativeASIN=020161622X">The Pragmatic Programmer: From Journeyman to Master</a><img src="http://www.assoc-amazon.com/e/ir?t=perplabs-20&#038;l=as2&#038;o=1&#038;a=020161622X" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" /></h3>
<p>Write better, cleaner, more maintainable code.  Learn how to manage your projects and focus on shipping your product.  With insight that covers the gamut of software development from low level to management<br />
this one is a must have for anyone involved in this industry.</p>
<h3><a href="http://www.amazon.com/gp/product/1934356344?ie=UTF8&#038;tag=perplabs-20&#038;linkCode=as2&#038;camp=1789&#038;creative=390957&#038;creativeASIN=1934356344">The Passionate Programmer: Creating a Remarkable Career in Software Development</a><img src="http://www.assoc-amazon.com/e/ir?t=perplabs-20&#038;l=as2&#038;o=1&#038;a=1934356344" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" /></h3>
<p>Highly recommended!  <a href="http://blog.perplexedlabs.com/2009/07/29/book-review-the-passionate-programmer/">Read my full review</a>.</p>
<h3><a href="http://www.amazon.com/gp/product/0735619670?ie=UTF8&#038;tag=perplabs-20&#038;linkCode=as2&#038;camp=1789&#038;creative=390957&#038;creativeASIN=0735619670">Code Complete: A Practical Handbook of Software Construction</a><img src="http://www.assoc-amazon.com/e/ir?t=perplabs-20&#038;l=as2&#038;o=1&#038;a=0735619670" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" /></h3>
<p>Another classic "software construction" book.  Sharpen your saw with timeless information that can be applied to any project in any language.  Less bugs, more productivity, more programmer happiness.</p>
<h3><a href="http://www.amazon.com/gp/product/1430219483?ie=UTF8&#038;tag=perplabs-20&#038;linkCode=as2&#038;camp=1789&#038;creative=390957&#038;creativeASIN=1430219483">Coders at Work</a><img src="http://www.assoc-amazon.com/e/ir?t=perplabs-20&#038;l=as2&#038;o=1&#038;a=1430219483" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" /></h3>
<p>This one is different.  Written as a set of interview transcripts with 15 legendary industry giants, this book is a fantastic insight into how some of the great minds think.  It's inspiring to hear it from the source, must have!</p>
<h3><a href="http://www.amazon.com/gp/product/1934356336?ie=UTF8&#038;tag=perplabs-20&#038;linkCode=as2&#038;camp=1789&#038;creative=390957&#038;creativeASIN=1934356336">Programming Clojure</a><img src="http://www.assoc-amazon.com/e/ir?t=perplabs-20&#038;l=as2&#038;o=1&#038;a=1934356336" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" /></h3>
<p>A developer should learn at least one new language a year.  This year that language should be Clojure.  Clojure is a dynamic, general purpose, language targeting the Java virtual machine and designed for multi-threaded use.  It's growing popularity, ability to leverage the Java standard library, and its multi-threaded nature make this a must have.</p>
<h3><a href="http://www.amazon.com/gp/product/0201835959?ie=UTF8&#038;tag=perplabs-20&#038;linkCode=as2&#038;camp=1789&#038;creative=390957&#038;creativeASIN=0201835959">The Mythical Man-Month: Essays on Software Engineering</a><img src="http://www.assoc-amazon.com/e/ir?t=perplabs-20&#038;l=as2&#038;o=1&#038;a=0201835959" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" /></h3>
<p>Another classic.  Primarily discusses project management from the perspective of Fred Brooks and his experiences at IBM.  Brooks' Law states that "adding manpower to a late software project makes it later".</p>
<h3><a href="http://www.amazon.com/gp/product/0321344758?ie=UTF8&#038;tag=perplabs-20&#038;linkCode=as2&#038;camp=1789&#038;creative=390957&#038;creativeASIN=0321344758">Don't Make Me Think: A Common Sense Approach to Web Usability</a><img src="http://www.assoc-amazon.com/e/ir?t=perplabs-20&#038;l=as2&#038;o=1&#038;a=0321344758" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" /></h3>
<p>Web developers should always keep in mind the user of the product their creating.  Usability becomes increasingly important as applications move to the web.  The design and usability of your app can make or break its success.  This classic is a must read.</p>
<h3><a href="http://www.amazon.com/gp/product/0201633612?ie=UTF8&#038;tag=perplabs-20&#038;linkCode=as2&#038;camp=1789&#038;creative=390957&#038;creativeASIN=0201633612">Design Patterns: Elements of Reusable Object-Oriented Software</a><img src="http://www.assoc-amazon.com/e/ir?t=perplabs-20&#038;l=as2&#038;o=1&#038;a=0201633612" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" /></h3>
<p>This classic known most commonly as the "gang of four" book is the definitive reference on design patterns.  Covering all of the most common cases and time and time again serving as an invaluable source of information.</p>


<p>Related posts:<ol><li><a href='http://blog.perplexedlabs.com/2009/06/01/be-language-agnostic-solve-the-problem/' rel='bookmark' title='Permanent Link: Be Language Agnostic &#8211; Solve the Problem!'>Be Language Agnostic &#8211; Solve the Problem!</a></li>
<li><a href='http://blog.perplexedlabs.com/2010/02/08/deployment-using-capistrano-and-webistrano-via-rails-and-phusion-passenger/' rel='bookmark' title='Permanent Link: Deployment Using Capistrano / Webistrano via Rails / Phusion Passenger'>Deployment Using Capistrano / Webistrano via Rails / Phusion Passenger</a></li>
<li><a href='http://blog.perplexedlabs.com/2009/07/28/django-1-0-template-development-sample-chapter-serving-multiple-templates/' rel='bookmark' title='Permanent Link: Django 1.0 Template Development: Sample Chapter &#8220;Serving Multiple Templates&#8221;'>Django 1.0 Template Development: Sample Chapter &#8220;Serving Multiple Templates&#8221;</a></li>
</ol></p>]]></content:encoded>
			<wfw:commentRss>http://blog.perplexedlabs.com/2009/11/30/8-books-to-get-a-developer-for-the-holidays/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Setup Python 2.6.4, mod_wsgi 2.6, and Django 1.1.1 on CentOS 5.3 (cPanel)</title>
		<link>http://blog.perplexedlabs.com/2009/11/15/setup-python-2-6-4-mod_wsgi-2-6-and-django-1-1-1-on-centos-5-3-cpanel/</link>
		<comments>http://blog.perplexedlabs.com/2009/11/15/setup-python-2-6-4-mod_wsgi-2-6-and-django-1-1-1-on-centos-5-3-cpanel/#comments</comments>
		<pubDate>Sun, 15 Nov 2009 20:37:38 +0000</pubDate>
		<dc:creator>Matt</dc:creator>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[Django]]></category>
		<category><![CDATA[Infrastructure]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[centos]]></category>
		<category><![CDATA[linux]]></category>
		<category><![CDATA[mod_wsgi]]></category>
		<category><![CDATA[mysql]]></category>
		<category><![CDATA[mysql-python]]></category>
		<category><![CDATA[mysqldb]]></category>
		<category><![CDATA[setuptools]]></category>

		<guid isPermaLink="false">http://blog.perplexedlabs.com/?p=378</guid>
		<description><![CDATA[This is an update to my previous how-to Setup Python 2.5, mod_wsgi, and Django 1.0 on CentOS 5 (cPanel). The biggest reason why I chose to go with Python 2.5 at the time was because the MySQL Python (MySQLdb) package didn't support Python 2.6. The 1.2.3c1 release does so that roadblock is lifted. The instructions [...]


Related posts:<ol><li><a href='http://blog.perplexedlabs.com/2008/11/10/setup-python-25-mod_wsgi-and-django-10-on-centos-5-cpanel/' rel='bookmark' title='Permanent Link: Setup Python 2.5, mod_wsgi, and Django 1.0 on CentOS 5 (cPanel)'>Setup Python 2.5, mod_wsgi, and Django 1.0 on CentOS 5 (cPanel)</a></li>
<li><a href='http://blog.perplexedlabs.com/2008/02/04/building-a-rails-capable-slice-from-scratch/' rel='bookmark' title='Permanent Link: Ruby On Rails and SliceHost Part 1: Initial Setup'>Ruby On Rails and SliceHost Part 1: Initial Setup</a></li>
<li><a href='http://blog.perplexedlabs.com/2009/02/08/getting-started-with-django-and-python-first-impressions/' rel='bookmark' title='Permanent Link: Getting Started with Django and Python &#8211; First Impressions'>Getting Started with Django and Python &#8211; First Impressions</a></li>
</ol>]]></description>
			<content:encoded><![CDATA[<p>This is an update to my previous how-to <a href="http://blog.perplexedlabs.com/2008/11/10/setup-python-25-mod_wsgi-and-django-10-on-centos-5-cpanel/">Setup Python 2.5, mod_wsgi, and Django 1.0 on CentOS 5 (cPanel)</a>.</p>
<p>The biggest reason why I chose to go with Python 2.5 at the time was because the MySQL Python (MySQLdb) package didn't support Python 2.6.  The 1.2.3c1 release does so that roadblock is lifted.</p>
<p>The instructions are identical - nothing has really changed in that regard.  Just change the references from Python 2.5 to 2.6.  Here are the links to the versions I'm using successfully:</p>
<blockquote><p>
Python 2.6.4: <a href="http://www.python.org/ftp/python/2.6.4/Python-2.6.4.tgz">http://www.python.org/ftp/python/2.6.4/Python-2.6.4.tgz</a></p>
<p>setuptools 0.6c11: <a href="http://pypi.python.org/packages/2.6/s/setuptools/setuptools-0.6c11-py2.6.egg#md5=bfa92100bd772d5a213eedd356d64086">http://pypi.python.org/packages/2.6/s/setuptools/setuptools-0.6c11-py2.6.egg#md5=bfa92100bd772d5a213eedd356d64086</a></p>
<p>MySQLdb 1.2.3c1: <a href="http://sourceforge.net/projects/mysql-python/files/mysql-python-test/1.2.3c1/MySQL-python-1.2.3c1.tar.gz/download">http://sourceforge.net/projects/mysql-python/files/mysql-python-test/1.2.3c1/MySQL-python-1.2.3c1.tar.gz/download</a></p>
<p>mod_wsgi 2.6: <a href="http://modwsgi.googlecode.com/files/mod_wsgi-2.6.tar.gz">http://modwsgi.googlecode.com/files/mod_wsgi-2.6.tar.gz</a></p>
<p>Django 1.1.1: <a href="http://www.djangoproject.com/download/1.1.1/tarball/">http://www.djangoproject.com/download/1.1.1/tarball/</a>
</p></blockquote>


<p>Related posts:<ol><li><a href='http://blog.perplexedlabs.com/2008/11/10/setup-python-25-mod_wsgi-and-django-10-on-centos-5-cpanel/' rel='bookmark' title='Permanent Link: Setup Python 2.5, mod_wsgi, and Django 1.0 on CentOS 5 (cPanel)'>Setup Python 2.5, mod_wsgi, and Django 1.0 on CentOS 5 (cPanel)</a></li>
<li><a href='http://blog.perplexedlabs.com/2008/02/04/building-a-rails-capable-slice-from-scratch/' rel='bookmark' title='Permanent Link: Ruby On Rails and SliceHost Part 1: Initial Setup'>Ruby On Rails and SliceHost Part 1: Initial Setup</a></li>
<li><a href='http://blog.perplexedlabs.com/2009/02/08/getting-started-with-django-and-python-first-impressions/' rel='bookmark' title='Permanent Link: Getting Started with Django and Python &#8211; First Impressions'>Getting Started with Django and Python &#8211; First Impressions</a></li>
</ol></p>]]></content:encoded>
			<wfw:commentRss>http://blog.perplexedlabs.com/2009/11/15/setup-python-2-6-4-mod_wsgi-2-6-and-django-1-1-1-on-centos-5-3-cpanel/feed/</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
		<item>
		<title>PHP Custom MySQL Session Handler</title>
		<link>http://blog.perplexedlabs.com/2009/10/05/php-custom-session-handler/</link>
		<comments>http://blog.perplexedlabs.com/2009/10/05/php-custom-session-handler/#comments</comments>
		<pubDate>Mon, 05 Oct 2009 14:00:30 +0000</pubDate>
		<dc:creator>Matt</dc:creator>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[Infrastructure]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[memcache]]></category>
		<category><![CDATA[mysql]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[session]]></category>
		<category><![CDATA[session handler]]></category>

		<guid isPermaLink="false">http://www.perplexedlabs.com/?p=302</guid>
		<description><![CDATA[The Problem I'm sure many have used PHP's default session handling capabilities. By default, PHP uses the filesystem to store session data naming files with their session id # and putting them in /tmp. This is done for the sake of simplicity. On a single-server, low load website, this particular setup works fine. It's when [...]


Related posts:<ol><li><a href='http://blog.perplexedlabs.com/2008/02/06/mutex-with-php-and-mysql/' rel='bookmark' title='Permanent Link: Mutex with PHP and mySQL'>Mutex with PHP and mySQL</a></li>
<li><a href='http://blog.perplexedlabs.com/2008/12/17/php-parallel-web-scraper/' rel='bookmark' title='Permanent Link: PHP Parallel Web Scraper'>PHP Parallel Web Scraper</a></li>
<li><a href='http://blog.perplexedlabs.com/2008/02/04/php-simple-profiling-class/' rel='bookmark' title='Permanent Link: PHP Simple Profiling Class'>PHP Simple Profiling Class</a></li>
</ol>]]></description>
			<content:encoded><![CDATA[<h3>The Problem</h3>
<p>I'm sure many have used PHP's default session handling capabilities.  By default, PHP uses the filesystem to store session data naming files with their session id # and putting them in /tmp.</p>
<p>This is done for the sake of simplicity.  On a single-server, low load website, this particular setup works fine.  It's when you start having multiple simultaneous requests from a single client (identified by a session) that the problems begin to show their ugly heads.  Utilizing AJAX multiple simultaneous requests might be the norm, even for a low load website.</p>
<p>Essentially, in order to prevent <a href="http://en.wikipedia.org/wiki/Race_condition">race conditions</a> PHP internally uses a lock to maintain exclusive access to the file containing the session data for the client connection.  This means that as soon as a single request acquires exclusive access to that session file, no other request can access the file until the original request completes.  What happens when that second request asks to start the session?  It waits.</p>
<h3>The Solution</h3>
<p>Fortunately there's a solution to all this.  Implementing your own custom session handler and moving your session storage backend to another technology (such as a MySQL database or memcache) affords you the ability to handle simultaneous requests in a thread-safe manner.  Remember, it's a good thing that PHP prevents race conditions by locking the session file.  What we're looking to do is increase the granularity of the lock to the level of individual session data key =&gt; value pairs.</p>
<p>For this post we're going to stick to storing sessions in the database with our own custom session save handler.  Perhaps in another post I'll talk about doing the same in memcache.  It's the theory we're concerned about, not necessarily the exact storage mechanism implementation.</p>
<p>Implementing your own custom session handler is simply a matter of calling session_set_save_handler() with the appropriate callback methods for handling the following scenarios:</p>
<ul>
<li><strong>open</strong><br />
Open function, this works like a constructor in classes and is executed when the session is being opened. The open function expects two parameters, where the first is the save path and the second is the session name.</li>
<li><strong>close</strong><br />
Close function, this works like a destructor in classes and is executed when the session operation is done.</li>
<li><strong>read</strong><br />
Read function must return string value always to make save handler work as expected. Return empty string if there is no data to read. Return values from other handlers are converted to boolean expression. TRUE for success, FALSE for failure.</li>
<li><strong>write</strong><br />
The "write" handler is not executed until after the output stream is closed.</li>
<li><strong>destroy</strong><br />
The destroy handler, this is executed when a session is destroyed with session_destroy() and takes the session id as its only parameter.</li>
<li><strong>gc</strong><br />
The garbage collector, this is executed when the session garbage collector is executed and takes the max session lifetime as its only parameter.</li>
</ul>
<p>There's a well known trick for situations like this that allow you to pass a class method (static or instance) for callbacks.  Let's take a look at a simple example.  This correctly implements the required methods but obviously doesn't do much:</p>
<pre class="brush: php; title: ;">
class MySession
{
	public function __construct()
	{
		session_set_save_handler(
						array('MySession', 'sess_open'),
						array('MySession', 'sess_close'),
						array('MySession', 'sess_read'),
						array('MySession', 'sess_write'),
						array('MySession', 'sess_destroy'),
						array('MySession', 'sess_gc')
						);

		ini_set('session.auto_start',					0);
		ini_set('session.gc_probability',				1);
		ini_set('session.gc_divisor',					100);
		ini_set('session.gc_maxlifetime',				604800);
		ini_set('session.referer_check',				'');
		ini_set('session.entropy_file',					'/dev/urandom');
		ini_set('session.entropy_length',				16);
		ini_set('session.use_cookies',					1);
		ini_set('session.use_only_cookies',				1);
		ini_set('session.use_trans_sid',				0);
		ini_set('session.hash_function',				1);
		ini_seT('session.hash_bits_per_character',		5);

		session_cache_limiter('nocache');
		session_set_cookie_params(0, '/', '.mydomainname.com');
		session_name('mySessionName');
		session_start();
	}

	public static function sess_open($save_path, $session_name)
	{
		return true;
	}

	public static function sess_close()
	{
		return true;
	}

	public static function sess_read($id)
	{
		return '';
	}

	public static function sess_write($id, $sess_data)
	{
		return true;
	}

	public static function sess_destroy($id)
	{
		return true;
	}

	public static function sess_gc($maxlifetime)
	{
		return true;
	}
}
</pre>
<h3>SPL and Storing Session Data In MySQL</h3>
<p>Adding support for MySQL to this class is fairly trivial.  Let's start off by creating a table to store our session data:</p>
<pre class="brush: sql; title: ;">
CREATE TABLE `sessions` (
  `sesskey` char(32) NOT NULL,
  `timestamp` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP,
  `varkey` varchar(128) NOT NULL,
  `varval` longtext NOT NULL,
  PRIMARY KEY  (`sesskey`,`varkey`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
</pre>
<p><a href="http://us3.php.net/spl">PHP's SPL</a> provides the ability to create objects that behave as though they were arrays.  You can make objects that iterate, respond to accessing them via array notation ($object['key']), and lots of other interesting things.</p>
<p>We're going to enhance the MySession class with a couple SPL interfaces that will allow the object, when instantiated, to behave like an array.   We can then override the $_SESSION superglobal with an instance of our new MySession class.  It will provide an identical interface to access session data while internally storing session data to the database.  Also, internally it will use methods for row-level locking via MySQL's advisory locks (GET_LOCK() and RELEASE_LOCK()).</p>
<p>The next step is to implement the required methods for the interfaces we're using.  These methods allow the object to behave as though it's an array.</p>
<pre class="brush: php; title: ;">
class MySession implements Countable, ArrayAccess, Iterator
{
	private $index;
	private $curElement;
	private $locks = array();
	private $sessionName = 'sessionId';
	private $serialize = 'serialize';
	private $unserialize = 'unserialize';
	private $session_id = null;

	public function __construct()
	{
		session_set_save_handler(
						array('MySession', 'sess_open'),
						array('MySession', 'sess_close'),
						array('MySession', 'sess_read'),
						array('MySession', 'sess_write'),
						array('MySession', 'sess_destroy'),
						array('MySession', 'sess_gc')
						);

		ini_set('session.auto_start',					0);
		ini_set('session.gc_probability',				1);
		ini_set('session.gc_divisor',					100);
		ini_set('session.gc_maxlifetime',				604800);
		ini_set('session.referer_check',				'');
		ini_set('session.entropy_file',					'/dev/urandom');
		ini_set('session.entropy_length',				16);
		ini_set('session.use_cookies',					1);
		ini_set('session.use_only_cookies',				1);
		ini_set('session.use_trans_sid',				0);
		ini_set('session.hash_function',				1);
		ini_seT('session.hash_bits_per_character',		5);

		session_cache_limiter('nocache');
		session_set_cookie_params(0, '/', '.mydomainname.com');
		session_name('mySessionName');
		session_start();
	}

	public function destroy()
	{
		$sessionName = session_name();
		$cookieInfo = session_get_cookie_params();
		$cookieExpires = time() - 3600;
		if((empty($cookieInfo['domain'])) &amp;&amp; (empty($cookieInfo['secure']))) {
			setcookie($sessionName, '', $cookieExpires, $cookieInfo['path']);
		} elseif(empty($cookieInfo['secure'])) {
			setcookie($sessionName, '', $cookieExpires, $cookieInfo['path'], $cookieInfo['domain']);
		} else {
			setcookie($sessionName, '', $cookieExpires, $cookieInfo['path'], $cookieInfo['domain'], $cookieInfo['secure']);
		}
		unset($_COOKIE[$sessionName]);

		$dbo = DBO::getInstance();
		$q = &quot;DELETE FROM `sessions` WHERE `sesskey` = '&quot;.$this-&gt;session_id.&quot;'&quot;;
		$dbo-&gt;query($q);

		session_destroy();
	}

	private function lockName($k)
	{
		return 'sesslock'.$this-&gt;session_id.$k;
	}

	public function locked($k)
	{
		$k = $this-&gt;lockName($k);

		return isset($this-&gt;locks[$k]);
	}

	public function acquire($k, $timeout = 0)
	{
		$k = $this-&gt;lockName($k);

		if(!isset($this-&gt;locks[$k])) {
			$dbo = DBO::getInstance();
			$q = &quot;SELECT GET_LOCK('&quot;.$k.&quot;', &quot;.$timeout.&quot;)&quot;;
			$rs = $dbo-&gt;query($q);
			$this-&gt;locks[$k] = $dbo-&gt;result($rs, 0);
			$dbo-&gt;fr($rs);

			return $this-&gt;locks[$k];
		}

		return false;
	}

	public function release($k)
	{
		$k = $this-&gt;lockName($k);

		unset($this-&gt;locks[$k]);

		$dbo = DBO::getInstance();
		$q = &quot;SELECT RELEASE_LOCK('&quot;.$k.&quot;')&quot;;
		$rs = $dbo-&gt;query($q);
		$ret = $dbo-&gt;fetch($rs);
		$dbo-&gt;fr($rs);

		return true;
	}

	public function count()
	{
		$dbo = DBO::getInstance();
		$q = &quot;SELECT COUNT(*) FROM `sessions` WHERE `sesskey` = '&quot;.$this-&gt;session_id.&quot;'&quot;;
		$rs = $dbo-&gt;query($q);
		$ret = $dbo-&gt;result($rs, 0);
		$dbo-&gt;fr($rs);

		return $ret;
	}

	public function rewind()
	{
		$this-&gt;index = 0;
		$this-&gt;getCurElement();
	}

	private function getCurElement()
	{
		$dbo = DBO::getInstance();
		$q = &quot;SELECT `varkey`, `varval` FROM `sessions` WHERE `sesskey` = '&quot;.$this-&gt;session_id.&quot;' LIMIT &quot;.$this-&gt;index.&quot;,1&quot;;
		$rs = $dbo-&gt;query($q);
		$row = $dbo-&gt;fetch($rs);
		$dbo-&gt;fr($rs);
		if(is_array($row) &amp;&amp; (count($row) == 2)) {
			$this-&gt;curElement = $row;
		} else {
			$this-&gt;curElement = array(null, null);
		}
	}

	public function key()
	{
		return $this-&gt;curElement[0];
	}

	public function current()
	{
		return call_user_func($this-&gt;unserialize, $this-&gt;curElement[1]);
	}

	public function next()
	{
		$this-&gt;index++;
		$this-&gt;getCurElement();
	}

	public function valid()
	{
		return ($this-&gt;curElement[0] !== null);
	}

	public function offsetSet($k, $v)
	{
		$dbo = DBO::getInstance();
		$q = &quot;REPLACE INTO `sessions` (`sesskey`, `varkey`, `varval`) VALUES ('&quot;.$this-&gt;session_id.&quot;', '&quot;.$k.&quot;', '&quot;.$dbo-&gt;sanitize(call_user_func($this-&gt;serialize, $v)).&quot;')&quot;;
		$dbo-&gt;query($q);
	}

	public function offsetGet($k)
	{
		$dbo = DBO::getInstance();
		$q = &quot;SELECT `varval` FROM `sessions` WHERE `sesskey` = '&quot;.$this-&gt;session_id.&quot;' AND `varkey` = '&quot;.$k.&quot;'&quot;;
		$rs = $dbo-&gt;query($q);
		if($ret = $dbo-&gt;result($rs, 0)) {
			$ret = call_user_func($this-&gt;unserialize, $ret);
		}
		$dbo-&gt;fr($rs);

		return $ret;
	}

	public function offsetUnset($k)
	{
		$dbo = DBO::getInstance();
		$q = &quot;DELETE FROM `sessions` WHERE `sesskey` = '&quot;.$this-&gt;session_id.&quot;' AND `varkey` = '&quot;.$k.&quot;'&quot;;
		$dbo-&gt;query($q);
	}

	public function offsetExists($k)
	{
		$dbo = DBO::getInstance();
		$q = &quot;SELECT `varval` FROM `sessions` WHERE `sesskey` = '&quot;.$this-&gt;session_id.&quot;' AND `varkey` = '&quot;.$k.&quot;'&quot;;
		$rs = $dbo-&gt;query($q);
		$ret = $dbo-&gt;result($rs, 0);
		$dbo-&gt;fr($rs);

		return (bool)$ret;
	}

	public static function sess_open($save_path, $session_name)
	{
		return true;
	}

	public static function sess_close()
	{
		return true;
	}

	public static function sess_read($id)
	{
		return '';
	}

	public static function sess_write($id, $sess_data)
	{
		return true;
	}

	public static function sess_destroy($id)
	{
		return true;
	}

	public static function sess_gc($maxlifetime)
	{
		$dbo = DBO::getInstance();
		$q = &quot;DELETE FROM `sessions` WHERE `timestamp` &lt; '&quot;.date('Y-m-d H:i:s', time() - $maxlifetime).&quot;'&quot;;
		$dbo-&gt;query($q);

		return $dbo-&gt;query($q);
	}
}
</pre>
<p>The $dbo object is just an example of an interface to the database through the use of a singleton.  Replace the $dbo object with your preferred mysql database interface and you'll be set to go!</p>
<p>Starting your session is now as simple as:</p>
<pre class="brush: php; title: ;">
$_SESSION = new MySession;
</pre>


<p>Related posts:<ol><li><a href='http://blog.perplexedlabs.com/2008/02/06/mutex-with-php-and-mysql/' rel='bookmark' title='Permanent Link: Mutex with PHP and mySQL'>Mutex with PHP and mySQL</a></li>
<li><a href='http://blog.perplexedlabs.com/2008/12/17/php-parallel-web-scraper/' rel='bookmark' title='Permanent Link: PHP Parallel Web Scraper'>PHP Parallel Web Scraper</a></li>
<li><a href='http://blog.perplexedlabs.com/2008/02/04/php-simple-profiling-class/' rel='bookmark' title='Permanent Link: PHP Simple Profiling Class'>PHP Simple Profiling Class</a></li>
</ol></p>]]></content:encoded>
			<wfw:commentRss>http://blog.perplexedlabs.com/2009/10/05/php-custom-session-handler/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Backing Up Subversion Repositories Using svnadmin hotcopy</title>
		<link>http://blog.perplexedlabs.com/2009/09/11/backing-up-subversion-repositories-using-svnadmin-hotcopy/</link>
		<comments>http://blog.perplexedlabs.com/2009/09/11/backing-up-subversion-repositories-using-svnadmin-hotcopy/#comments</comments>
		<pubDate>Fri, 11 Sep 2009 17:18:02 +0000</pubDate>
		<dc:creator>Matt</dc:creator>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[Infrastructure]]></category>
		<category><![CDATA[backup]]></category>
		<category><![CDATA[bash]]></category>
		<category><![CDATA[hotcopy]]></category>
		<category><![CDATA[subversion]]></category>
		<category><![CDATA[svn]]></category>
		<category><![CDATA[svnadmin]]></category>

		<guid isPermaLink="false">http://blog.perplexedlabs.com/?p=363</guid>
		<description><![CDATA[Just wanted to post this quick bash script to iterate over the repositories in a directory, perform an svnadmin hotcopy, and tar/gzip the output. By using hotcopy this can be performed on a live subversion repository and will produce a pristine backup. #!/bin/bash REPOS_PATH=/var/repos mkdir -p /backups/weekly rm -rf /backups/tmp mkdir -p /backups/tmp/repos for i [...]


Related posts:<ol><li><a href='http://blog.perplexedlabs.com/2009/02/09/automated-backups-a-5-minute-script-to-safer-data/' rel='bookmark' title='Permanent Link: Automated Backups &#8211; A 5 Minute Script To Safer Data'>Automated Backups &#8211; A 5 Minute Script To Safer Data</a></li>
<li><a href='http://blog.perplexedlabs.com/2008/03/18/svn-keyword-substitution/' rel='bookmark' title='Permanent Link: SVN Keyword Substitution svn:keywords'>SVN Keyword Substitution svn:keywords</a></li>
<li><a href='http://blog.perplexedlabs.com/2008/11/10/setup-python-25-mod_wsgi-and-django-10-on-centos-5-cpanel/' rel='bookmark' title='Permanent Link: Setup Python 2.5, mod_wsgi, and Django 1.0 on CentOS 5 (cPanel)'>Setup Python 2.5, mod_wsgi, and Django 1.0 on CentOS 5 (cPanel)</a></li>
</ol>]]></description>
			<content:encoded><![CDATA[<p>Just wanted to post this quick bash script to iterate over the repositories in a directory, perform an svnadmin hotcopy, and tar/gzip the output.</p>
<p>By using hotcopy this can be performed on a live subversion repository and will produce a pristine backup.</p>
<pre class="brush: bash; title: ;">
#!/bin/bash
REPOS_PATH=/var/repos
mkdir -p /backups/weekly
rm -rf /backups/tmp
mkdir -p /backups/tmp/repos
for i in $(ls $REPOS_PATH); do
        /path/to/svnadmin hotcopy $REPOS_PATH/$i /backups/tmp/repos/$i
done
FN=svn.weekly.`date '+%Y%m%d'`.tar.gz
tar -czf /backups/weekly/$FN -C /backups/tmp .
</pre>


<p>Related posts:<ol><li><a href='http://blog.perplexedlabs.com/2009/02/09/automated-backups-a-5-minute-script-to-safer-data/' rel='bookmark' title='Permanent Link: Automated Backups &#8211; A 5 Minute Script To Safer Data'>Automated Backups &#8211; A 5 Minute Script To Safer Data</a></li>
<li><a href='http://blog.perplexedlabs.com/2008/03/18/svn-keyword-substitution/' rel='bookmark' title='Permanent Link: SVN Keyword Substitution svn:keywords'>SVN Keyword Substitution svn:keywords</a></li>
<li><a href='http://blog.perplexedlabs.com/2008/11/10/setup-python-25-mod_wsgi-and-django-10-on-centos-5-cpanel/' rel='bookmark' title='Permanent Link: Setup Python 2.5, mod_wsgi, and Django 1.0 on CentOS 5 (cPanel)'>Setup Python 2.5, mod_wsgi, and Django 1.0 on CentOS 5 (cPanel)</a></li>
</ol></p>]]></content:encoded>
			<wfw:commentRss>http://blog.perplexedlabs.com/2009/09/11/backing-up-subversion-repositories-using-svnadmin-hotcopy/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Automated Backups &#8211; A 5 Minute Script To Safer Data</title>
		<link>http://blog.perplexedlabs.com/2009/02/09/automated-backups-a-5-minute-script-to-safer-data/</link>
		<comments>http://blog.perplexedlabs.com/2009/02/09/automated-backups-a-5-minute-script-to-safer-data/#comments</comments>
		<pubDate>Mon, 09 Feb 2009 20:16:24 +0000</pubDate>
		<dc:creator>Eric</dc:creator>
				<category><![CDATA[Infrastructure]]></category>
		<category><![CDATA[backup]]></category>
		<category><![CDATA[cron]]></category>
		<category><![CDATA[logs]]></category>
		<category><![CDATA[mysql]]></category>
		<category><![CDATA[mysqldump]]></category>

		<guid isPermaLink="false">http://www.perplexedlabs.com/?p=178</guid>
		<description><![CDATA[Sometimes, taking a few minutes to do a simple thing will save you a headache down the road.  Like changing the oil in your car, or brushing your teeth every night, or automatically backing up your databases. The development server that I mess around on has recently accumulated a lot of data that I really [...]


Related posts:<ol><li><a href='http://blog.perplexedlabs.com/2008/02/05/mysqldump-usage/' rel='bookmark' title='Permanent Link: mysqldump usage'>mysqldump usage</a></li>
<li><a href='http://blog.perplexedlabs.com/2009/09/11/backing-up-subversion-repositories-using-svnadmin-hotcopy/' rel='bookmark' title='Permanent Link: Backing Up Subversion Repositories Using svnadmin hotcopy'>Backing Up Subversion Repositories Using svnadmin hotcopy</a></li>
<li><a href='http://blog.perplexedlabs.com/2008/02/04/building-a-rails-capable-slice-from-scratch/' rel='bookmark' title='Permanent Link: Ruby On Rails and SliceHost Part 1: Initial Setup'>Ruby On Rails and SliceHost Part 1: Initial Setup</a></li>
</ol>]]></description>
			<content:encoded><![CDATA[<p>Sometimes, taking a few minutes to do a simple thing will save you a headache down the road.  Like changing the oil in your car, or brushing your teeth every night, or automatically backing up your databases.</p>
<p>The development server that I mess around on has recently accumulated a lot of data that I really don't want to lose, so I whipped up a small script that will help to ensure this never happens.</p>
<blockquote><p>#!/bin/bash<br />
rm -f /path/to/backups/tmp/*<br />
mysqldump --opt --host=localhost --user=USERNAME --password=PASSWORD --all-databases &gt; /path/to/backups/tmp/dbBackup.sql<br />
tar -czvf /path/to/backups/database/mysql.`date '+%Y%m%d%H%M%S'`.tar.gz /backups/tmp<br />
service httpd restart</p></blockquote>
<p>Line by line, what's happening here is:</p>
<ul>
<li>delete the .sql file generated by the previous backup</li>
<li>dump every MySQL table to a file called dbBackup.sql.  You don't have to go this far; check out <a href="http://dev.mysql.com/doc/refman/5.1/en/mysqldump.html">MySQL's documentation on mysqldump</a> for all the options</li>
<li>tar up the dbBackup.sql file, include a timestamp in the filename</li>
<li>restart Apache</li>
</ul>
<p>I want this script to run every night at 1am, so I added it to the crontab:</p>
<blockquote><p>0 1 * * * /path/to/backup.daily.cron</p></blockquote>
<p>Obviously this is a very simple script that can grow in complexity fairly quickly.  For example, you can clear out old logs (a must for some Rails apps), and do other housekeeping functions.  A good idea would also be to have a syncing program grab that .tar and upload it to your home machine, or even an S3 bucket.</p>
<p>When your MySQL instance gets corrupted or otherwise obliterated, you'll thank yourself for taking 5 minutes to set this up.</p>


<p>Related posts:<ol><li><a href='http://blog.perplexedlabs.com/2008/02/05/mysqldump-usage/' rel='bookmark' title='Permanent Link: mysqldump usage'>mysqldump usage</a></li>
<li><a href='http://blog.perplexedlabs.com/2009/09/11/backing-up-subversion-repositories-using-svnadmin-hotcopy/' rel='bookmark' title='Permanent Link: Backing Up Subversion Repositories Using svnadmin hotcopy'>Backing Up Subversion Repositories Using svnadmin hotcopy</a></li>
<li><a href='http://blog.perplexedlabs.com/2008/02/04/building-a-rails-capable-slice-from-scratch/' rel='bookmark' title='Permanent Link: Ruby On Rails and SliceHost Part 1: Initial Setup'>Ruby On Rails and SliceHost Part 1: Initial Setup</a></li>
</ol></p>]]></content:encoded>
			<wfw:commentRss>http://blog.perplexedlabs.com/2009/02/09/automated-backups-a-5-minute-script-to-safer-data/feed/</wfw:commentRss>
		<slash:comments>8</slash:comments>
		</item>
	</channel>
</rss>

