<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/">
  
  <channel>
    <title>Taylor Hughes &amp;mdash; Blog</title>
    <link>http://www.taylor-hughes.com/</link>
    <pubDate>Tue, 15 Dec 2009 03:43:51 +0000</pubDate>
    <description>10 most recent blog posts by Taylor Hughes.</description>
    
    <item>
      <title>HTTPS: Insist on secure authentication</title>
      <link>http://www.taylor-hughes.com/2009/dec/https.html</link>
      <description>&lt;p&gt;While checking out some &lt;a href=&quot;http://designm.ag/resources/online-to-do-lists/&quot;&gt;other to-do list options&lt;/a&gt; about four months ago, I noticed something disturbing: &lt;a href=&quot;http://tadalist.com/&quot;&gt;Ta-da List&lt;/a&gt;, a popular to-do list app and &lt;a href=&quot;http://www.google.com/search?q=to-do+list&quot;&gt;top Google result for &amp;#8220;to-do list&amp;#8221;&lt;/a&gt;, doesn&amp;#8217;t use SSL for its login or sign-up forms.
&lt;/p&gt;
&lt;p&gt;This was shocking to me, since using HTTPS for authentication has always been a no-brainer. Since then, however, I&amp;#8217;ve been &lt;a href=&quot;http://getsatisfaction.com/thinglabs/topics/authentication_and_signup_require_https_for_secure_password_transmission&quot;&gt;flabbergasted&lt;/a&gt; to find two new services that also launched without HTTPS support for sign-up and authentication: &lt;a href=&quot;http://brizzly.com&quot;&gt;Brizzly&lt;/a&gt;, a twitter client, and &lt;a href=&quot;http://teuxdeux.com/&quot;&gt;TeuxDeux&lt;/a&gt;, another to-do list app. Brizzly is awesome &amp;#8212; really, I love it &amp;#8212; but I haven&amp;#8217;t used it since I discovered this issue a month or so ago. (Update: Brizzly has added HTTPS for authentication; I am free to use it again.)
&lt;/p&gt;
&lt;p&gt;I contacted support on all three sites &amp;#8212; which I consider to be high-profile, professional efforts &amp;#8212; and all three essentially said they&amp;#8217;d get around to adding SSL for authentication at some point in the future.
&lt;/p&gt;
&lt;p&gt;Frankly, I don&amp;#8217;t understand how they can consider plaintext password transmission acceptable for their users, now or ever.
&lt;/p&gt;
&lt;p&gt;Perhaps the problem is misunderstood. Maybe web developers aren&amp;#8217;t concerned with unsecured wireless networks or the off chance that somebody is doing &lt;a href=&quot;http://en.wikipedia.org/wiki/Deep_packet_inspection&quot;&gt;deep packet inspection&lt;/a&gt; in between their users&amp;#8217; web browsers and their datacenters.
&lt;/p&gt;
&lt;p&gt;But I think they should be concerned &amp;#8212; especially about unsecured wireless networks &amp;#8212; and not risk exposure of their users&amp;#8217; sensitive data.
&lt;/p&gt;

&lt;h4&gt;Vulnerable information: Users&amp;#8217; passwords are valuable&lt;/h4&gt;
&lt;p&gt;My first assumption going into this is that &lt;strong&gt;passwords are valuable information and users trust us as web developers to keep them safe.&lt;/strong&gt;
&lt;/p&gt;
&lt;p&gt;Passwords are valuable because &lt;strong&gt;people reuse passwords all the time&lt;/strong&gt;. I work at a network security company, and even I reuse the same set of passwords over and over again for a variety of services I use &amp;#8212; it&amp;#8217;s just unavoidable when you have to sign up for a new web site virtually every day. 
&lt;/p&gt;
&lt;p&gt;Thus, the password I use at some random site is probably the same password I use for any number of other sites &amp;#8212; and most people probably use the same password for their bank accounts, too. Maybe even the same username.
&lt;/p&gt;
&lt;p&gt;This isn&amp;#8217;t how things have to be &amp;#8212; you can store your passwords in the Apple Keychain or use a program like &lt;a href=&quot;http://agilewebsolutions.com/products/1Password&quot;&gt;1Password&lt;/a&gt; to remember unique, complicated passwords for you for each site you use. But, for the typical user, this is not the case.
&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note: I am not as worried about sessions being hijacked because session hijacking has a limited scope. For example, with a hijacked twitter session, someone could post malicious tweets, but he can&amp;#8217;t log into my bank&amp;#8217;s web site. Passwords are &lt;strong&gt;private data&lt;/strong&gt; that people trust is kept secret.&lt;/em&gt;
&lt;/p&gt;

&lt;h4&gt;Anatomy of an insecure login&lt;/h4&gt;
&lt;p&gt;Here&amp;#8217;s how a typical insecure login happens over HTTP:
&lt;/p&gt;
&lt;ol&gt;
 &lt;li&gt;
     You type your username and password in a form and hit &amp;#8220;Login&amp;#8221;
 &lt;/li&gt;

 &lt;li&gt;
     The browser &amp;#8220;form-encodes&amp;#8221; the data you typed into the form so it looks sort of like this: &lt;code&gt;username=taylor&amp;amp;password=mypassword&lt;/code&gt;
 &lt;/li&gt;

 &lt;li&gt;
     A web request is created including the form-encoded data
 &lt;/li&gt;

 &lt;li&gt;
     The request data leaves your computer and travels over your local wired or &lt;span style=&quot;color:red&quot;&gt;wireless network&lt;/span&gt;
 &lt;/li&gt;

 &lt;li&gt;
     The request data leaves your home or office, traveling through &lt;span style=&quot;color:red&quot;&gt;your ISP&amp;#8217;s network&lt;/span&gt;
 &lt;/li&gt;

 &lt;li&gt;
     The data leaves your ISP and travels to its destination through a variety of core routers and switches, which are generally regarded as secure
 &lt;/li&gt;

 &lt;li&gt;
     Your data arrives at the &lt;span style=&quot;color:red&quot;&gt;datacenter where the servers live&lt;/span&gt; and is routed to the proper destination
 &lt;/li&gt;

 &lt;li&gt;
     The server gets your request, checks your credentials and issues you a session cookie, which is something you&amp;#8217;ll identify yourself with in subsequent requests
 &lt;/li&gt;

 &lt;li&gt;
     The response from the server &amp;#8212; including your new session cookie &amp;#8212; flows back through the same channels and into your browser.
 &lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The parts &lt;span style=&quot;color:red&quot;&gt;highlighted in red&lt;/span&gt; are places where your password, transmitted in plaintext, could be intercepted. Let&amp;#8217;s look at those spots a little more in detail.
&lt;/p&gt;

&lt;h4&gt;Wireless networks: Unsecured wireless as a bullhorn&lt;/h4&gt;
&lt;p&gt;At home, you probably use WPA or (gasp) WEP security to keep your neighbors from accessing your wireless router. But those technologies also encrypt your network&amp;#8217;s traffic, so the hacker sitting outside with a laptop and a &lt;a href=&quot;http://en.wikipedia.org/wiki/Cantenna&quot;&gt;Pringles can&lt;/a&gt; can&amp;#8217;t easily get a look at the juicy gossip you&amp;#8217;re reading on Perez Hilton.
&lt;/p&gt;
&lt;p&gt;Most public access points &amp;#8212; coffee shops, airports, etc. &amp;#8212; use unsecured wireless networks. These unsecured wireless networks rely on HTTPS and other secure transmission media to protect your sensitive data while it flies through the air. Thus, on an unsecured wireless network, your insecure HTTP requests and AIM conversations and torrent downloads are blasting out over the airwaves for anybody to read.
&lt;/p&gt;
&lt;p&gt;That&amp;#8217;s basically like announcing your passwords over a bullhorn for the entire coffee shop to hear.
&lt;/p&gt;
&lt;p&gt;You can try this experiment on your home computer: Download &lt;a href=&quot;http://www.wireshark.org/&quot;&gt;Wireshark&lt;/a&gt;, start collecting packets from your own network interface, and login to Ta-da List. You&amp;#8217;ll see an HTTP packet with a form-encoded version of your login information contained in a HTTP POST request. &lt;a href=&quot;http://dump.taylor-hughes.com/wireshark-tadalist.png&quot;&gt;It looks sort of like this&lt;/a&gt;, except the password isn&amp;#8217;t blurred out.
&lt;/p&gt;
&lt;p&gt;It&amp;#8217;s right there, in plaintext.
&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This is easily the most dangerous point for unsecured HTTP transmissions&lt;/strong&gt;, and the point that should most worry both users who are logging in using an insecure form and the web developers who created them. You should assume that every packet you transmit on an unsecured wireless network is being read by someone else.
&lt;/p&gt;

&lt;h4&gt;Your corporate LAN or ISP, and the datacenter your data ends up in&lt;/h4&gt;
&lt;p&gt;Your network traffic, somewhere down the line, is probably being siphoned off and monitored by some assortment of devices. The core Internet is not so much a worry, but your local ISP, your corporate LAN or your university&amp;#8217;s vast network are probably being tapped at various points and inspected.
&lt;/p&gt;
&lt;p&gt;Heavy-duty switches nowadays come equipped with ports specifically designed for this: &lt;a href=&quot;http://www.cisco.com/en/US/products/hw/switches/ps708/products_tech_note09186a008015c612.shtml&quot;&gt;SPAN ports&lt;/a&gt; copy the data off for various analyses, or you can buy a &lt;a href=&quot;http://en.wikipedia.org/wiki/Network_tap&quot;&gt;network tap&lt;/a&gt; to do effectively the same thing. This is how &lt;a href=&quot;http://en.wikipedia.org/wiki/Intrusion_detection_system&quot;&gt;intrusion detection systems&lt;/a&gt; work: You plug an IDS into a port that essentially copies off all the traffic on your network, then the IDS performs some packet inspection to figure out if anything bad is going on. You can do this at immense speed, applying &amp;#8220;filters&amp;#8221; or &amp;#8220;signatures&amp;#8221; to gigabits of traffic every second without breaking a sweat. IDS technology is &lt;a href=&quot;http://www.google.com/finance?q=NASDAQ:FIRE&quot;&gt;extremely popular&lt;/a&gt;, making up a huge portion of the cybersecurity performed at nearly every major organization across the globe.
&lt;/p&gt;
&lt;p&gt;The point is not that security groups are out to get you, it&amp;#8217;s that &lt;strong&gt;deep packet inspection at line speeds is not difficult&lt;/strong&gt;.
&lt;/p&gt;
&lt;p&gt;With the system I work on every day, for example, I could write a filter to extract Brizzly usernames and passwords in just a few minutes. You could even do it with a simple packet capture device filtered by destination IP: You just need access to the network anywhere in between the source and destination.
&lt;/p&gt;
&lt;p&gt;The sheer volume of the traffic on most big networks is the only thing working to your advantage in this case &amp;#8212; it&amp;#8217;s nearly impossible to look through everything in detail, so &lt;strong&gt;it&amp;#8217;s unusual that your password would be stolen over a wired connection&lt;/strong&gt;. But I would rather not take my chances.
&lt;/p&gt;

&lt;h4&gt;Technology to the rescue: HTTPS&lt;/h4&gt;
&lt;p&gt;Luckily, it doesn&amp;#8217;t matter who is listening if you encrypt HTTP requests containing passwords using HTTPS. SSL uses a &lt;a href=&quot;http://en.wikipedia.org/wiki/Public_key_infrastructure&quot;&gt;public-key handshake&lt;/a&gt; to guarantee two things:
&lt;/p&gt;
&lt;ol&gt;
 &lt;li&gt;
     My password is being transmitted to the correct recipient (Ta-da List, etc.), and
 &lt;/li&gt;

 &lt;li&gt;
     My password information is encrypted and unreadable by anyone else who doesn&amp;#8217;t own a supercomputer.
 &lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Further, it&amp;#8217;s easy to configure: You buy an SSL certificate and install it, &lt;a href=&quot;http://www.modssl.org/&quot;&gt;flip on SSL&lt;/a&gt;, write a rewrite directive in your HTTP server, and you&amp;#8217;re set &amp;#8212; nobody can read your users&amp;#8217; passwords except you. No app code changes necessary.
&lt;/p&gt;
&lt;p&gt;HTTPS is not going to solve every security problem &amp;#8212; how&amp;#8217;s your &lt;a href=&quot;http://www.codinghorror.com/blog/archives/000953.html&quot;&gt;password storage&lt;/a&gt;, by the way? &amp;#8212; but it&amp;#8217;s an easy step in the right direction. Web developers should use HTTPS for sign-up and authentication, no questions asked.
&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Disclaimer, or qualifier, depending on how you look at it: I work at &lt;a href=&quot;http://nemeannetworks.com/&quot;&gt;a network security company&lt;/a&gt; that builds a device to analyze networks and figure out who the bad guys are.&lt;/em&gt;
&lt;/p&gt;</description>
      <pubDate>Tue, 15 Dec 2009 03:43:51 +0000</pubDate>
      <guid>http://www.taylor-hughes.com/2009/dec/https.html</guid>
      <author>taylor@taylor-hughes.com (Taylor Hughes)</author>
    </item>
    
    <item>
      <title>The four dimensions of developer happiness</title>
      <link>http://www.taylor-hughes.com/2009/jun/the_four_dimensions_of_developer_happiness.html</link>
      <description>&lt;p&gt;I&amp;#8217;ve spent a lot of time thinking about what I like about programming for a living, what I like about my job in particular and what could make it all even better &amp;#8212; and I&amp;#8217;ve boiled it down to four dimensions that need to be satisfied by a workplace to make it truly rock. I&amp;#8217;ll call them &amp;#8220;Taylor&amp;#8217;s Dimensions of Developer Happiness.&amp;#8221;
&lt;/p&gt;
&lt;p&gt;If you&amp;#8217;re firing on all four cylinders, congratulations &amp;#8212; you&amp;#8217;ve found a great place to work.
&lt;/p&gt;

&lt;h4&gt;The code: writing good software&lt;/h4&gt;
&lt;p&gt;As software engineers, the product of our toil is essentially a pile of text files &amp;#8212; but the quality and composition of them is vital. Even more important is the &lt;a href=&quot;http://code.google.com/events/io/sessions/MythGeniusProgrammer.html&quot;&gt;collaborative, thoughtful process&lt;/a&gt; that goes into creating a lasting set of reusable, maintainable components.
&lt;/p&gt;
&lt;p&gt;Gauging this category might mean answering questions from &lt;a href=&quot;http://www.joelonsoftware.com/articles/fog0000000043.html&quot;&gt;the Joel Test&lt;/a&gt;, but there&amp;#8217;s even more. How about some of these:
&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;
     Do you believe in testing? Does someone on your team &lt;a href=&quot;http://www.randsinrepose.com/archives/2002/06/17/the_big_qa_frea.html&quot;&gt;freak out&lt;/a&gt; if a code path is untestable (or untested)?
 &lt;/li&gt;

 &lt;li&gt;
     Do you do code reviews? Have you been talked into refactoring a nasty bit of code recently?
 &lt;/li&gt;

 &lt;li&gt;
     Do you have deep discussions about the merit of various source control systems?
 &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of these things individually are great &amp;#8212; but the more that resonates with you, the more likely it is that there&amp;#8217;s experience behind the process you&amp;#8217;re following to build good code. 
&lt;/p&gt;

&lt;h4&gt;The coworkers: working with people you like&lt;/h4&gt;
&lt;p&gt;I am a strong believer that the people you spend eight hours a day with should be people you&amp;#8217;d gladly spend the rest of the evening with at a bar or restaurant. If you can&amp;#8217;t get along with them over drinks, you sure as hell won&amp;#8217;t be able to build a quality product with them.
&lt;/p&gt;
&lt;p&gt;That isn&amp;#8217;t to say you should be best friends with everyone you work with &amp;#8212; just that you enjoy spending the majority of your daily life with them and that you trust in their ability to make decisions that impact you on a daily basis.
&lt;/p&gt;
&lt;p&gt;This is mostly something that goes without saying, but it&amp;#8217;s easy to glaze over or forget the importance of the team&amp;#8217;s social makeup when you&amp;#8217;re interviewing or distracted by a job offer.
&lt;/p&gt;

&lt;h4&gt;The mission: building a great product&lt;/h4&gt;
&lt;p&gt;I saw a talk recently by one of the co-founders of &lt;a href=&quot;http://alice.com/&quot;&gt;Alice.com&lt;/a&gt; talking about what has led to the success of his previous start-ups. He said something to this effect: Start-up employees are on a mission to change the world; they need to know the mission and be revved up about the product to make it all come together. In fact, he said the start-up employee is one of &lt;a href=&quot;http://flywheelblog.com/2008/11/the-3-advantages-of-a-start-up/&quot;&gt;the only advantages start-ups have&lt;/a&gt;.
&lt;/p&gt;
&lt;p&gt;Whether or not you work for a start-up, however, the mission is extremely important: You need to be commuting to work every morning for a reason.
&lt;/p&gt;
&lt;p&gt;You should be able to delve into an exciting summary of the work you&amp;#8217;re doing and talk my ear off for about a half hour without stopping. It should keep you up at night. The end result of your work is extremely important: You need to be proud and excited about what you&amp;#8217;re doing.
&lt;/p&gt;

&lt;h4&gt;The challenge: learning as you go&lt;/h4&gt;
&lt;p&gt;The last dimension to developer happiness: The environment you work in should push you with new challenges on a constant basis. This means learning new things and venturing into unknown territory every day. 
&lt;/p&gt;
&lt;p&gt;Lots of different things can be challenging, and in a start-up environment especially, challenges will crop up out of nowhere all the time: technological nightmares,  interpersonal conflicts and everythingelseinal &lt;a href=&quot;http://www.randsinrepose.com/archives/2006/04/20/10.html&quot;&gt;challenges&lt;/a&gt; &lt;a href=&quot;http://paulgraham.com/die.html&quot;&gt;abound&lt;/a&gt;.
&lt;/p&gt;
&lt;p&gt;But I think it&amp;#8217;s important to put yourself in an environment that breeds those challenges, and to avoid situations you know you can handle.
&lt;/p&gt;

&lt;h4&gt;The&amp;#8230; end&lt;/h4&gt;
&lt;p&gt;And that&amp;#8217;s it. This is mostly the product of my working in a small start-up and evaluating the options out there in the rest of the world &amp;#8212; so, take it with a grain of salt. I am fairly certain, however, that if you can cover those four bases, the job is a great fit.
&lt;/p&gt;</description>
      <pubDate>Tue, 23 Jun 2009 01:38:41 +0000</pubDate>
      <guid>http://www.taylor-hughes.com/2009/jun/the_four_dimensions_of_developer_happiness.html</guid>
      <author>taylor@taylor-hughes.com (Taylor Hughes)</author>
    </item>
    
    <item>
      <title>Going Django-less on App Engine</title>
      <link>http://www.taylor-hughes.com/2009/jun/going_djangoless_on_app_engine.html</link>
      <description>&lt;p&gt;I just finished migrating &lt;a href=&quot;http://www.getdnzo.com/&quot;&gt;DNZO&lt;/a&gt; to run on the &lt;a href=&quot;http://code.google.com/appengine/docs/python/tools/webapp/&quot;&gt;webapp framework&lt;/a&gt;, after running on Django 1.0 via &lt;a href=&quot;http://code.google.com/p/app-engine-patch/&quot;&gt;app-engine-patch&lt;/a&gt; for the past several months. I learned a valuable lesson in the process of switching: The webapp framework is really nice and, perhaps more importantly, way faster than using a zipimported version of Django.
&lt;/p&gt;
&lt;p&gt;It makes sense that using a zipped up copy of Django would be slow: You&amp;#8217;re making the webserver inflate a copy of a framework you&amp;#8217;re using to serve requests before it can serve them. The most important thing to note here, however, is that App Engine aggressively kills off application instances, especially during the day (when overall system load is presumably higher) &amp;#8212; so you don&amp;#8217;t get the opportunity to reuse those inflated copies of Django very often.
&lt;/p&gt;
&lt;p&gt;As a result, I ended up being hit with the initial load penalty frequently: Almost every request during the day took upwards of &lt;strong&gt;two full seconds&lt;/strong&gt;. And that totally sucks.
&lt;/p&gt;
&lt;p&gt;So, I started out the transition by building a new section of DNZO that uses the webapp framework. Obviously you lose a lot of goodies provided by Django, but the webapp framework provides a basic class structure, some URL mapping and resolution, and it&amp;#8217;s generally really nice and easy to extend. It provides nothing I don&amp;#8217;t want, and the stuff I do want that it doesn&amp;#8217;t have (like straightforward cookie support &amp;#8212; really?) is pretty easy to whip up. Before long, I had a basic structure that worked well and I was ready to migrate the rest of DNZO to the same framework.
&lt;/p&gt;
&lt;p&gt;A few subversion commits later and we have the new version of DNZO: Now with page loads that are about &lt;strong&gt;5 to 10 times faster&lt;/strong&gt; than when using the zipimport&amp;#8217;d version of Django. Fresh page loads usually take between 300 and 500 milliseconds, down from two to three full seconds: the difference between feeling slow and feeling pretty snappy.
&lt;/p&gt;</description>
      <pubDate>Tue, 16 Jun 2009 03:09:25 +0000</pubDate>
      <guid>http://www.taylor-hughes.com/2009/jun/going_djangoless_on_app_engine.html</guid>
      <author>taylor@taylor-hughes.com (Taylor Hughes)</author>
    </item>
    
    <item>
      <title>Movin&amp;#8217; on up, to the client side</title>
      <link>http://www.taylor-hughes.com/2009/mar/movin_on_up_to_the_client_side.html</link>
      <description>&lt;p&gt;I launched a new feature for &lt;a href=&quot;http://www.getdnzo.com/&quot;&gt;DNZO&lt;/a&gt; last week: fast, JavaScript-based task sorting. Instead of forcing a new page load, tasks are sorted near-instantly on the client side. In order to enable bookmarking and the back button, I used the &lt;a href=&quot;http://www.prototypextensions.com/history&quot;&gt;PrototypeXtensions.js&lt;/a&gt; library&amp;#8217;s history manager &amp;#8212; after a bit of hackery to get it working as I expected.
&lt;/p&gt;
&lt;p&gt;This move to JavaScript for sorting mirrors a common theme I&amp;#8217;ve experienced in developing DNZO: I&amp;#8217;ve slowly moved a lot of things over to the client side to increase responsiveness, minimize server-side requests and keep the requests that do need to happen light.
&lt;/p&gt;

&lt;h4&gt;Original plan: JavaScript as frosting&lt;/h4&gt;
&lt;p&gt;When I started building DNZO, I initially designed the JavaScript side of things as sort of a frosting on top of the server side logic. 
&lt;/p&gt;
&lt;p&gt;For example, clicking &amp;#8220;edit&amp;#8221; on a task fired an Ajax.Request to the edit link&amp;#8217;s href (e.g. /t/1234), whose server-side counterpart supplied the markup for editing that task. It was really nice, because I had less state to manage and didn&amp;#8217;t have to know anything about the structure of the tasks table. Similarly, script.aculo.us&amp;#8217;s Ajax.Autocompleter initially loaded autocomplete suggestions from the server side by requesting something like /projects/?q=DN&amp;#8230;, and the script just displayed the suggestions once it got them. The App Engine datastore did most of the work for me, and since the suggestions were updated whenever tasks were saved, I didn&amp;#8217;t have to worry about suggestions becoming out of date.
&lt;/p&gt;
&lt;p&gt;The frosting technique provided three primary advantages:
&lt;/p&gt;
&lt;ol&gt;
 &lt;li&gt;
     &lt;strong&gt;Less client-side logic.&lt;/strong&gt; When you write your app this way, the client side is pretty dumb. My edit link just replaced something with an Ajax call and the autocompleter just asked somebody else for suggestions. Totally straightforward and less prone to cross-browser quirks.
 &lt;/li&gt;

 &lt;li&gt;
     &lt;strong&gt;Cleaner initial markup.&lt;/strong&gt; In the case of the edit button, I didn&amp;#8217;t have to include each task&amp;#8217;s inline-editing markup (e.g. input fields, submit button, etc.), so the initial page size was smaller. There were also fewer hidden elements sitting around and nothing output as JSON for client-side consumption &amp;#8212; there was no need for it.
 &lt;/li&gt;

 &lt;li&gt;
     &lt;strong&gt;Very DRY.&lt;/strong&gt; Using Ajax.Autocompleter for autocomplete meant the JavaScript side of things knew nothing of how to generate suggestions or how to keep a list of things to suggest. Moving this all to the client side with &lt;a href=&quot;http://www.taylor-hughes.com/2009/feb/introducing_instantautocompleter.html&quot;&gt;InstantAutocompleter&lt;/a&gt; meant I had to duplicate some of that functionality: I had to both update the list of things to suggest on the client side and permanently store that list of suggestions on the server side.
 &lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Unfortunately, the typical workflow for a user (read: me) ended up looking like this:
&lt;/p&gt;
&lt;ol&gt;
 &lt;li&gt;
     Try to do something (e.g. click &amp;#8220;edit&amp;#8221;, type in part of a project name, etc.).
 &lt;/li&gt;

 &lt;li&gt;
     Wait for an &lt;a href=&quot;http://ajaxload.info/&quot;&gt;ajaxy spinner&lt;/a&gt; to stop spinning. Tap foot during a brief pause until the display finally changes.
 &lt;/li&gt;

 &lt;li&gt;
     Complete the action.
 &lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Hitting the &amp;#8220;edit&amp;#8221; link and then waiting about a second for the request to complete was frustrating. Waiting for a list of suggestions to come back from an Ajax call reduced its utility to a point where I just didn&amp;#8217;t use the autocomplete at all. And, similarly, waiting for an entire page load just to reverse your sort order just sucked.
&lt;/p&gt;

&lt;h4&gt;Revised plan: JavaScript provides core functionality&lt;/h4&gt;
&lt;p&gt;So, I have a new strategy: Make things happen instantly in your web app if you can, even if you have to repeat yourself in JavaScript and even if you have to work a little harder on the client side. Things need to happen immediately, or the app will feel slow.
&lt;/p&gt;
&lt;p&gt;I think the main issue is that I originally thought of DNZO as a web &lt;em&gt;site&lt;/em&gt; rather than a web &lt;em&gt;application&lt;/em&gt;. I didn&amp;#8217;t realize that I valued the app&amp;#8217;s responsiveness over its code zen factor &amp;#8212; especially when the web app purports to help people get things, er, &lt;a href=&quot;http://www.getdnzo.com/&quot;&gt;done-zo&lt;/a&gt;.
&lt;/p&gt;</description>
      <pubDate>Mon, 30 Mar 2009 01:12:08 +0000</pubDate>
      <guid>http://www.taylor-hughes.com/2009/mar/movin_on_up_to_the_client_side.html</guid>
      <author>taylor@taylor-hughes.com (Taylor Hughes)</author>
    </item>
    
    <item>
      <title>Load Google Analytics asynchronously</title>
      <link>http://www.taylor-hughes.com/2009/mar/load_google_analytics_asynchronously.html</link>
      <description>&lt;p&gt;So at some point last week I realized that Google Analytics was slowing down &lt;a href=&quot;http://www.getdnzo.com&quot;&gt;DNZO&lt;/a&gt;&amp;#8216;s page loads. I had just been using the default Google Analytics loader code, which spits out its &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; blocks inline. That behavior causes the browser to load the tracking stuff before continuing with anything else, and that&amp;#8217;s not cool.
&lt;/p&gt;
&lt;p&gt;After some searching I found &lt;a href=&quot;http://ajaxian.com/archives/google-analytics-after-onload-and-documentwrite-for-xhtml&quot;&gt;a blog post at Ajaxian&lt;/a&gt; that talks about the issue. I didn&amp;#8217;t immediately find a Prototype-based solution for Analytics in particular, however.
&lt;/p&gt;
&lt;p&gt;So I wrote a bit of Prototypey JS to write the elements out once the page is loaded, and I&amp;#8217;ve noticed a &lt;strong&gt;dramatic improvement&lt;/strong&gt; in page load time.
&lt;/p&gt;
&lt;p&gt;Here&amp;#8217;s the code; it&amp;#8217;s pretty straightforward:
&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;script type=&quot;text/javascript&quot;&amp;gt;
function _checkGAT() {
  if (typeof _gat == &#39;undefined&#39;) { 
    setTimeout(_checkGAT, 200); 
  } else {
    try {
      var pageTracker = _gat._getTracker(&#39;UA-123456-7&#39;);
      pageTracker._trackPageview();
    } catch(err) {}
  }
}
Event.observe(window, &#39;load&#39;, function(e) {
  $$(&#39;head&#39;).first().appendChild(new Element(&#39;script&#39;, { 
      src: (&quot;https:&quot; == document.location.protocol) ? 
            &quot;https://ssl.&quot; : &quot;http://www.&quot; +
            &quot;google-analytics.com/ga.js&quot;
     }));
  _checkGAT();
});
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Hopefully someone finds this useful. ;)
&lt;/p&gt;</description>
      <pubDate>Mon, 30 Mar 2009 01:06:48 +0000</pubDate>
      <guid>http://www.taylor-hughes.com/2009/mar/load_google_analytics_asynchronously.html</guid>
      <author>taylor@taylor-hughes.com (Taylor Hughes)</author>
    </item>
    
    <item>
      <title>Getting things DNZO</title>
      <link>http://www.taylor-hughes.com/2009/mar/getting_things_dnzo.html</link>
      <description>&lt;p&gt;Some time last summer, I decided I needed a to-do list. I had too much to keep track of, and things were starting to get messy. But &lt;a href=&quot;http://www.tadalist.com/&quot;&gt;Ta-da List&lt;/a&gt; is too simple, &lt;a href=&quot;http://www.rememberthemilk.com/&quot;&gt;Remember the Milk&lt;/a&gt; is too complicated, and desktop to-do list apps like &lt;a href=&quot;http://www.hogbaysoftware.com/products/taskpaper&quot;&gt;TaskPaper&lt;/a&gt; and &lt;a href=&quot;http://www.omnigroup.com/applications/omnifocus/&quot;&gt;OmniFocus&lt;/a&gt; don&amp;#8217;t sync to multiple computers, which is a deal breaker for me.
&lt;/p&gt;
&lt;p&gt;So, a Google App Engine hackathon followed by approximately six months of a few hours here and there and voilà: &lt;a href=&quot;http://www.getdnzo.com/&quot;&gt;DNZO&lt;/a&gt; (&amp;#8220;done-zo&amp;#8221;) is born. Sign up with your Google Account today!
&lt;/p&gt;

&lt;h4&gt;Features&lt;/h4&gt;
&lt;ol&gt;
 &lt;li&gt;
     &lt;strong&gt;Quickly add new tasks&lt;/strong&gt; with a responsive interface focused on making it easy to get started. Once you&amp;#8217;ve added some tasks, quickly edit, complete, delete or re-order them at will.
 &lt;/li&gt;

 &lt;li&gt;
     &lt;strong&gt;Sort or filter your tasks&lt;/strong&gt; based on projects, contexts and due dates. Click a project, context or due date to see all the matching tasks &amp;#8212; and sort on any columns you like. Don&amp;#8217;t use one column or another? Hide it.
 &lt;/li&gt;

 &lt;li&gt;
     &lt;strong&gt;Add multiple lists&lt;/strong&gt; to better organize disparate tasks. Keep &amp;#8220;Groceries&amp;#8221; here and &amp;#8220;Work Stuff&amp;#8221; there, or put them together in the same list under different projects &amp;#8212; we&amp;#8217;re flexible.
 &lt;/li&gt;

 &lt;li&gt;
     &lt;strong&gt;Track what you&amp;#8217;ve done&lt;/strong&gt; with the archived tasks interface. See every task you&amp;#8217;ve completed today, this week, last week or last month, and give yourself a pat on the back when you see everything you&amp;#8217;ve accomplished.
 &lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;a href=&quot;http://www.getdnzo.com/&quot;&gt;Give DNZO a try&lt;/a&gt; &amp;#8212; I look forward to any feedback, either by comment or by e-mail. Happy to-doing!
&lt;/p&gt;</description>
      <pubDate>Fri, 6 Mar 2009 05:08:59 +0000</pubDate>
      <guid>http://www.taylor-hughes.com/2009/mar/getting_things_dnzo.html</guid>
      <author>taylor@taylor-hughes.com (Taylor Hughes)</author>
    </item>
    
    <item>
      <title>Introducing InstantAutocompleter</title>
      <link>http://www.taylor-hughes.com/2009/feb/introducing_instantautocompleter.html</link>
      <description>&lt;p&gt;I spent some free time over a weekend recently writing my own &lt;a href=&quot;http://prototypejs.org/&quot;&gt;Prototype&lt;/a&gt;-based JavaScript autocompleter. Holding out hope that someone else could find it useful, I&amp;#8217;ve created a &lt;a href=&quot;http://code.google.com/p/instant-autocompleter/&quot;&gt;Google Code project&lt;/a&gt; for it &amp;#8212; check it out!
&lt;/p&gt;
&lt;p&gt;InstantAutocompleter handles matching within a single keystroke event and doesn&amp;#8217;t wait around to appear, so your choices come up immediately. Further, the first choice may or may not be selected by default, and you can also adjust matching patterns and token separators as regular expressions for  autocompletion of items in a list.
&lt;/p&gt;
&lt;p&gt;The obvious choice for this sort of thing in the Prototype-and-&lt;a href=&quot;http://script.aculo.us/&quot;&gt;script.aculo.us&lt;/a&gt; realm is &lt;a href=&quot;http://wiki.github.com/madrobby/scriptaculous/autocompleter-local&quot;&gt;Autocompleter.Local&lt;/a&gt;, a control that ships with script.aculo.us. But I found it clunky and occasionally intrusive: it takes a bit to show up, and it can get in the way if it falls behind and provides a match after you&amp;#8217;re already done. InstantAutocompleter is meant as a replacement for Autocompleter.Local and should be even easier to use by both end-users and programmers alike.
&lt;/p&gt;
&lt;p&gt;(Some of the event-handling code takes vague cues from Autocompleter.Base, so thanks to the developers who put that package together.)
&lt;/p&gt;
&lt;p&gt;Give InstantAutocompleter a shot over at the &lt;a href=&quot;http://instantautocompleter.appspot.com/&quot;&gt;InstantAutocompleter demo page&lt;/a&gt;, or visit the &lt;a href=&quot;http://code.google.com/p/instant-autocompleter/&quot;&gt;project page&lt;/a&gt; to find out more.
&lt;/p&gt;</description>
      <pubDate>Thu, 12 Feb 2009 04:31:21 +0000</pubDate>
      <guid>http://www.taylor-hughes.com/2009/feb/introducing_instantautocompleter.html</guid>
      <author>taylor@taylor-hughes.com (Taylor Hughes)</author>
    </item>
    
    <item>
      <title>To App Engine or not to App Engine</title>
      <link>http://www.taylor-hughes.com/2009/jan/to_app_engine_or_not_to_app_engine.html</link>
      <description>&lt;p&gt;The web development landscape has gotten a lot more interesting over the past several years. Web frameworks have been popping up like dandelions, new and interesting languages have become popular and even entire hosted web platforms have emerged.
&lt;/p&gt;
&lt;p&gt;With the advent of platforms like Google App Engine (and hey, &lt;a href=&quot;http://appjet.com/&quot;&gt;AppJet&lt;/a&gt;, I&amp;#8217;m looking at you), starting your next project is not just about deciding on a language anymore. Instead, it&amp;#8217;s about weighing trade-offs between entire platforms: from the language and available frameworks to the way the app is deployed and scaled using them.
&lt;/p&gt;
&lt;p&gt;In deciding what to use for my next project, I&amp;#8217;m weighing the benefits and costs of using App Engine &amp;#8212; and it&amp;#8217;s no straightforward task.
&lt;/p&gt;

&lt;h4&gt;The hard parts&lt;/h4&gt;
&lt;ol&gt;
 &lt;li&gt;
     &lt;strong&gt;Datastore design&lt;/strong&gt; &amp;#8212; What&amp;#8217;s that line about premature optimization, again? Well, with Google App Engine, you&amp;#8217;re basically confronted with the task of designing a scalable data layout from the very early stages of developing your application. Datastore queries are restricted in ways such that you simply can&amp;#8217;t not think about the effect they&amp;#8217;ll have as you scale to millions of users &amp;#8212; which is obviously not a pressing concern for most endeavors.
 &lt;/li&gt;

 &lt;li&gt;
     &lt;strong&gt;Application monitoring&lt;/strong&gt; &amp;#8212; Performing queries that would be simple in a standard RDBMS are not straightforward in App Engine, so figuring out even how many users you have or how many records of a certain type exist in your datastore is not easy. (To do it, you&amp;#8217;ll need to actually keep track of those counts somewhere as the data is coming in or perform some very heavy calculations on demand.) Furthermore, there&amp;#8217;s no interface to the actual hardware or software running your application besides the sparse App Engine administration console &amp;#8212; so more interesting statistics about performance and deeper analysis into your traffic are hard to come by.
 &lt;/li&gt;

 &lt;li&gt;
     &lt;strong&gt;Limited framework access&lt;/strong&gt; &amp;#8212; Django ships with App Engine and can be easily included in any project (an old version of it, but you can upgrade it yourself manually if you like). But as you&amp;#8217;ll quickly discover, almost none of the amazing features of Django are enabled for App Engine. A lot of the easy administration interfaces provided by the framework are impossible to use with the aforementioned datastore restrictions, and as such, you&amp;#8217;re on your own. 
 &lt;/li&gt;

 &lt;li&gt;
     &lt;strong&gt;Quotas&lt;/strong&gt; &amp;#8212; Quotas are something I haven&amp;#8217;t actually run into with App Engine, but they&amp;#8217;re a scary upper bound when you&amp;#8217;re building what&amp;#8217;s sure to be the Next Big Thing. Some of them would easily be reached with a few hundred users: An app that sends out e-mail notifications frequently could easily hit a paltry 2000 e-mails a day, for example, with no way yet to buy more quota &amp;#8212; though we hear it&amp;#8217;s coming soon. 
 &lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;The reward&lt;/h4&gt;
&lt;ol&gt;
 &lt;li&gt;
     &lt;strong&gt;Wonderfully easy development setup&lt;/strong&gt; &amp;#8212; The App Engine development server is  wonderful. As I noted in my previous post, there are some inconsistencies with the production environment, but you really can&amp;#8217;t beat the way it all works. The OS X client is really spectacular in particular. While designing and coding an App Engine app isn&amp;#8217;t always the easiest task, the development server and mock datastore are just great.
 &lt;/li&gt;

 &lt;li&gt;
     &lt;strong&gt;Wonderfully easy deployment&lt;/strong&gt; &amp;#8212; Deploying new versions of an app to App Engine is great. With some tips from a Google IO talk called &lt;a href=&quot;http://www.youtube.com/watch?v=dP99fLhGwAU&quot;&gt;Building Quality Apps on App Engine&lt;/a&gt;, I setup a deployment script to safely deploy new versions of the code by incrementing the application&amp;#8217;s version number to match my Subversion revision number, which is further utilized in the app for smart caching of minified JavaScript and CSS &amp;#8212; and it&amp;#8217;s a breeze. I can deploy a new version, check it out to make sure it works as planned and turn it on as the default version quickly and easily. If something blows up, fix and repeat or revert. Fantastic.
 &lt;/li&gt;

 &lt;li&gt;
     &lt;strong&gt;Built-in scaling&lt;/strong&gt; &amp;#8212; It&amp;#8217;s a disadvantage because you have to think about it early on, but it&amp;#8217;s an advantage because once you do, you theoretically don&amp;#8217;t have to again. The hardware and software automatically grow your app across Google&amp;#8217;s infrastructure, and as long as you&amp;#8217;ve structured your data properly and had efficiency in mind you should be set as traffic grows. You never have to think about moving to a distributed database infrastructure after the fact or about how to go from a single server or set of servers to a server farm. Twitter on App Engine probably would have just&amp;#8230; worked.
 &lt;/li&gt;

 &lt;li&gt;
     &lt;strong&gt;No network administrator needed&lt;/strong&gt; &amp;#8212; In a similar vein, system configuration, maintenance, backup and the like are all taken care of with App Engine for free. You never have to worry about keeping your version of PHP up to date or migrating to a new version of PostgreSQL or upgrading your hard drives.
 &lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;Non-conclusion&lt;/h4&gt;
&lt;p&gt;There are a lot of costs and a lot of benefits to using App Engine. If you&amp;#8217;re planning on building a big app with a lot of users, it could be worth it to put the extra time in to build it out with Google &amp;#8212; but it&amp;#8217;s not an easy decision by any means.
&lt;/p&gt;</description>
      <pubDate>Wed, 7 Jan 2009 05:07:30 +0000</pubDate>
      <guid>http://www.taylor-hughes.com/2009/jan/to_app_engine_or_not_to_app_engine.html</guid>
      <author>taylor@taylor-hughes.com (Taylor Hughes)</author>
    </item>
    
    <item>
      <title>When not to use App Engine</title>
      <link>http://www.taylor-hughes.com/2008/sep/when_not_to_use_app_engine.html</link>
      <description>&lt;p&gt;A &lt;a href=&quot;http://www.feld.com/blog/archives/2008/09/what_happened_t.html&quot;&gt;recent post by Brad Feld&lt;/a&gt; reminded me to write this.
&lt;/p&gt;
&lt;p&gt;When &lt;a href=&quot;http://code.google.com/appengine&quot;&gt;Google App Engine&lt;/a&gt; was released in April, I was instantly intrigued by what amounts to a potentially huge new platform for building web apps. With App Engine, you can write a web app with access to a &lt;a href=&quot;http://labs.google.com/papers/bigtable.html&quot;&gt;BigTable&lt;/a&gt; datastore, hosted for free on Google hardware, with the ability to scale to millions of users. For now, your application must be written in Python, but you can leverage most of &lt;a href=&quot;http://www.djangoproject.com/&quot;&gt;Django&lt;/a&gt; to make the web application development process less hostile.
&lt;/p&gt;
&lt;p&gt;This sounds like the perfect medium to build just about anything: possibly a little more work than throwing together a Rails app, but no database or server maintenance, no hosting costs, and Google-scale scalability for free. But alas: &lt;strong&gt;Google App Engine is not ideal for quick, small-scale projects&lt;/strong&gt; &amp;#8212; for a variety of reasons.
&lt;/p&gt;
&lt;p&gt;When I set out to &lt;a href=&quot;http://www.taylor-hughes.com/2008/jul/powered_by_app_engine.html&quot;&gt;build my new blog&lt;/a&gt; using App Engine, I figured I would have a few tasks ahead of me:
&lt;/p&gt;
&lt;ol&gt;
 &lt;li&gt;
     Learn enough Python and Django to get me going.
 &lt;/li&gt;

 &lt;li&gt;
     Design new templates (&amp;#8220;views&amp;#8221; in Railspeak) that incorporate what I want in the new blog.
 &lt;/li&gt;

 &lt;li&gt;
     Build the models (provided by the App Engine API) and controllers (&amp;#8220;views&amp;#8221; in Djangospeak) to serve up the content.
 &lt;/li&gt;

 &lt;li&gt;
     Import my existing data: 140-some blog entries and about 1200 comments stored in a MySQL database on my old host.
 &lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;At first glance, #2 doesn&amp;#8217;t seem like anything new, #1 and #3 sound like a moderate challenge, and #4 should be easy given #1 and #3, besides transportation and translation of the existing data itself.
&lt;/p&gt;
&lt;p&gt;As it turned out, however, existing data import and template rendering were the two biggest issues I had in building my new blog. Unfortunately for me, &lt;strong&gt;Google App Engine is built for quick, efficient requests that can scale to millions of users&lt;/strong&gt;, not clunky requests that require lots of rendering.
&lt;/p&gt;

&lt;h4&gt;App Engine requires performance tuning&lt;/h4&gt;
&lt;p&gt;Toss together a fairly complicated blog with threaded comments and pretty text formatting, and you&amp;#8217;ve got some serious processing that typically needs to be done on every request: Certain data needs to be retrieved from the datastore, templates need to be parsed and included (recursively in the case of threaded comments), text of all kinds needs to be translated and formatted in many ways with a handful of external libraries, and finally all of it needs to be cobbled together and sent back to the user.
&lt;/p&gt;
&lt;p&gt;For the typical small-to-midsize project, nobody cares that all this needs to happen on every request because that sort of optimization takes time and care.
&lt;/p&gt;
&lt;p&gt;As it stands, however, App Engine considers every request for any page of my blog a &amp;#8220;high CPU&amp;#8221; request &amp;#8212; usually 2-3 times above the &amp;#8220;high CPU threshold.&amp;#8221; Some unknown-but-small quantity of &amp;#8220;high CPU&amp;#8221; requests is tolerated by GAE, but I get a warning log message on just about every page load.
&lt;/p&gt;
&lt;p&gt;Here&amp;#8217;s the worst part: I didn&amp;#8217;t hear anything about my requests being &amp;#8220;high CPU&amp;#8221; requests until I deployed the application and started importing the existing data &amp;#8212; after developing in the local App Engine development environment for something like a month and a half.
&lt;/p&gt;

&lt;h4&gt;App Engine apps can&amp;#8217;t have background processes&lt;/h4&gt;
&lt;p&gt;What this meant for me is that I couldn&amp;#8217;t simply hack together a crappy, one-time-use script to import my existing blog data. Instead, I had to write a request handler that could import &lt;em&gt;part of my existing data&lt;/em&gt;, and then I had to make a ton of quick requests in rapid succession to get all the data loaded. If I tried to import it all at once, I (a) ran out of memory or (b) ran out of execution time, &lt;strong&gt;both of which did not happen in the development environment&lt;/strong&gt;.
&lt;/p&gt;
&lt;p&gt;What&amp;#8217;s more, the regular updates of my Flickr photo stream and Last.fm statistics also have to happen by way of actual Web requests, instead of having the ability to set up some sort of cron for background maintenance tasks.
&lt;/p&gt;
&lt;p&gt;And that sucks.
&lt;/p&gt;

&lt;h4&gt;The App Engine API is still fresh&lt;/h4&gt;
&lt;p&gt;As I mentioned under the previous two headings, one of the most annoying parts about the process of getting my App Engine-powered blog up and running was discovering, at the last minute, new development constraints upon deployment. These surprises highlighted the fact that, while it is pretty awesome, &lt;strong&gt;the App Engine development server does not mirror the production environment&lt;/strong&gt; in some really important ways:
&lt;/p&gt;
&lt;ol&gt;
 &lt;li&gt;
     The development server does not have request timeouts or request memory limits.
 &lt;/li&gt;

 &lt;li&gt;
     The development server does not know what makes a &amp;#8220;high CPU&amp;#8221; request.
 &lt;/li&gt;

 &lt;li&gt;
     The development datastore is extraordinarily slow: so slow that I spent quite a bit of time optimizing my application to use more memory and reduce database queries &amp;#8212; which ended up being a bad move because of memory constraints in production.
 &lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;What&amp;#8217;s more, I ran into a few very frustrating bugs in the deployment process:
&lt;/p&gt;
&lt;ol&gt;
 &lt;li&gt;
     An issue with appcfg.py reported a completely opaque error message when attempting to update a malformed YAML configuration file. This issue did not affect the development server, which happily parsed and utilized the YAML locally.
 &lt;/li&gt;

 &lt;li&gt;
     An issue with administrative user account management meant that a new version of my blog needed to be uploaded before I could successfully login to the application as an administrator.
 &lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;The App Engine documentation is sparse&lt;/h4&gt;
&lt;p&gt;There, I said it.
&lt;/p&gt;
&lt;p&gt;The important stuff is covered by App Engine API documentation, but only the most basic use cases for the APIs are elaborated on. There just isn&amp;#8217;t a whole lot of meat in the docs.
&lt;/p&gt;
&lt;p&gt;I found myself constantly asking, &amp;#8220;Well, what if I was doing &lt;em&gt;X&lt;/em&gt; instead of &lt;em&gt;Y&lt;/em&gt;?&amp;#8221;, where Y was the most basic example and X was slightly more contrived: &amp;#8220;But what if I&amp;#8217;m building a tree of nested comments and I want them all to load in one datastore call?&amp;#8221; Especially for something that ended up being so picky about optimization, you&amp;#8217;d think there would be a focus on that sort of thing in the docs.
&lt;/p&gt;
&lt;p&gt;As an added blow, my experience searching for just about any problem I was having yielded few-to-no results: Looking for help using Django? Sure. Using App Engine? Sure. Using Django and App Engine? Hmmm&amp;#8230; Using Django and App Engine and only experiencing the error when you try to deploy and can&amp;#8217;t log in? Good luck.
&lt;/p&gt;

&lt;h4&gt;Finally&lt;/h4&gt;
&lt;p&gt;In closing, &lt;strong&gt;App Engine isn&amp;#8217;t for low-volume projects&lt;/strong&gt; unless you&amp;#8217;re a glutton for punishment. It&amp;#8217;s just not a platform designed or optimized for rapid development &amp;#8212; it&amp;#8217;s a platform for lightweight multiuser apps that you intend to scale to thousands or millions of users.
&lt;/p&gt;
&lt;p&gt;And for that, it sounds killer.
&lt;/p&gt;</description>
      <pubDate>Thu, 4 Sep 2008 23:47:44 +0000</pubDate>
      <guid>http://www.taylor-hughes.com/2008/sep/when_not_to_use_app_engine.html</guid>
      <author>taylor@taylor-hughes.com (Taylor Hughes)</author>
    </item>
    
    <item>
      <title>Two years with Last.fm</title>
      <link>http://www.taylor-hughes.com/2008/aug/two_years_with_lastfm.html</link>
      <description>&lt;p&gt;I joined &lt;a href=&quot;http://last.fm/&quot;&gt;Last.fm&lt;/a&gt; in July, 2006, and have been &amp;#8220;scrobbling&amp;#8221; at least some portion of what I listen to to the service ever since. What this means is that every time I play a song in iTunes, Last.fm records the play and generates statistics for my listening history over time.
&lt;/p&gt;
&lt;p&gt;For awhile now I&amp;#8217;ve been meaning to take a look at the progression of music I&amp;#8217;ve found since I joined the service. I ended up writing a quick Ruby script to download my weekly top artists from the service since I joined and calculate the first time I (theoretically) ever listened to them.
&lt;/p&gt;
&lt;p&gt;I&amp;#8217;ve distilled the raw results down to what I think is an accurate portrayal of my &lt;strong&gt;new artist discovery&lt;/strong&gt; over the past two years.
&lt;/p&gt;
&lt;p&gt;I say &amp;#8220;distilled&amp;#8221; because the original results had a lot of music I had already listened to before signing up for Last.fm &amp;#8212; Last.fm just didn&amp;#8217;t know about it beforehand. (The stats also do not include plays from my iPod or car, or, before 2007, the plays from my home computer; so these are slightly inaccurate.)
&lt;/p&gt;
&lt;p&gt;So, here&amp;#8217;s the list. It&amp;#8217;s formatted as such: Artist Name &amp;rarr; the number of songs I&amp;#8217;ve listened to by the artist, according to Last.fm, over the past two years. 
&lt;/p&gt;
&lt;p&gt;The journey begins:
&lt;/p&gt;

&lt;h4&gt;2006&lt;/h4&gt;
&lt;h5&gt;July-August&lt;/h5&gt;&lt;ul&gt;
 &lt;li&gt;
     Belle and Sebastian &amp;rarr; 277 plays 
 &lt;/li&gt;

 &lt;li&gt;
     Sufjan Stevens &amp;rarr; 1584 plays
 &lt;/li&gt;

 &lt;li&gt;
     The New Pornographers &amp;rarr; 128 plays
 &lt;/li&gt;

 &lt;li&gt;
     OK Go &amp;rarr; 302 plays
 &lt;/li&gt;

 &lt;li&gt;
     The Roots &amp;rarr; 78 plays
 &lt;/li&gt;

 &lt;li&gt;
     Editors &amp;rarr; 162 plays
 &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Belle and Sebastian, OK Go and the New Pornographers were all recommendations from fellow Heralders at the time; Sufjan Stevens and the Roots were some sort of personal experiment (I think); and the Editors were a recommendation from John Zeratsky.
&lt;/p&gt;
&lt;h5&gt;September-December&lt;/h5&gt;&lt;ul&gt;
 &lt;li&gt;
     Nada Surf &amp;rarr; 142 plays
 &lt;/li&gt;

 &lt;li&gt;
     Lady Sovereign &amp;rarr; 60 plays
 &lt;/li&gt;

 &lt;li&gt;
     of Montreal &amp;rarr; 422 plays
 &lt;/li&gt;

 &lt;li&gt;
     Ray LaMontagne &amp;rarr; 153 plays
 &lt;/li&gt;

 &lt;li&gt;
     Junior Boys &amp;rarr; 262 plays
 &lt;/li&gt;

 &lt;li&gt;
     The Sea and Cake &amp;rarr; 118 plays
 &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Nada Surf was a recommendation from Ryan Swan; Lady Sovereign, Junior Boys and the Sea and Cake were all Jeff Greene&amp;#8217;s; and of Montreal and Ray LaMontagne were inspired by Molly&amp;#8217;s visit to my apartment around November.
&lt;/p&gt;

&lt;h4&gt;2007&lt;/h4&gt;
&lt;h5&gt;March-April&lt;/h5&gt;&lt;ul&gt;
 &lt;li&gt;
     Headlights &amp;rarr; 134 plays
 &lt;/li&gt;

 &lt;li&gt;
     Mika &amp;rarr; 13 plays recorded, more in reality
 &lt;/li&gt;

 &lt;li&gt;
     Elton John &amp;rarr; 59 plays
 &lt;/li&gt;

 &lt;li&gt;
     Grizzly Bear &amp;rarr; 233 plays
 &lt;/li&gt;

 &lt;li&gt;
     Blonde Redhead &amp;rarr; 163 plays
 &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Elton John was a tip from my Finance professor; Headlights came from Jeff G.; Mika and Blonde Redhead were the result of two different newspaper reviews; and Grizzly Bear came from Nate and Elyssa.
&lt;/p&gt;
&lt;h5&gt;July-August&lt;/h5&gt;&lt;ul&gt;
 &lt;li&gt;
     Weekly &amp;rarr; 52 plays
 &lt;/li&gt;

 &lt;li&gt;
     Air &amp;rarr; 42 plays
 &lt;/li&gt;

 &lt;li&gt;
     The Be Good Tanyas &amp;rarr; 93 plays
 &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&amp;#8217;d been meaning to give Air a try for some time; Weekly was a John Z. recommendation; and the Be Good Tonyas came from Jeff G.
&lt;/p&gt;
&lt;h5&gt;September-December&lt;/h5&gt;&lt;ul&gt;
 &lt;li&gt;
     The Beatles &amp;rarr; 1155 plays
 &lt;/li&gt;

 &lt;li&gt;
     Feist &amp;rarr; 351 plays
 &lt;/li&gt;

 &lt;li&gt;
     Steely Dan &amp;rarr; 364 plays
 &lt;/li&gt;

 &lt;li&gt;
     Menomena &amp;rarr; 246 plays
 &lt;/li&gt;

 &lt;li&gt;
     Interpol &amp;rarr; 513 plays
 &lt;/li&gt;

 &lt;li&gt;
     Kings of Convenience &amp;rarr; 127 plays
 &lt;/li&gt;

 &lt;li&gt;
     The Knife &amp;rarr; 615 plays
 &lt;/li&gt;

 &lt;li&gt;
     Chromeo &amp;rarr; 140 plays
 &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&amp;#8217;d also been meaning to give the Beatles a spin, and it really worked out (as you can see by their enormous play count). Feist and Menomena came from John Z.; Steely Dan from Nate; Interpol from a binge drinking session at the Irish Pub; and Kings of Convenience, the Knife (awesome!) and Chromeo came from my sister.
&lt;/p&gt;

&lt;h4&gt;2008&lt;/h4&gt;
&lt;h5&gt;January-March&lt;/h5&gt;&lt;ul&gt;
 &lt;li&gt;
     Aphrodite &amp;rarr; 73 plays
 &lt;/li&gt;

 &lt;li&gt;
     Lily Allen &amp;rarr; 74 plays
 &lt;/li&gt;

 &lt;li&gt;
     José González &amp;rarr; 203 plays
 &lt;/li&gt;

 &lt;li&gt;
     Vampire Weekend &amp;rarr; 43 plays
 &lt;/li&gt;

 &lt;li&gt;
     The Rapture &amp;rarr; 57 plays
 &lt;/li&gt;

 &lt;li&gt;
     The Bird and the Bee &amp;rarr; 165 plays
 &lt;/li&gt;

 &lt;li&gt;
     Bon Iver &amp;rarr; 228 plays
 &lt;/li&gt;

 &lt;li&gt;
     The Helio Sequence &amp;rarr; 593 plays
 &lt;/li&gt;

 &lt;li&gt;
     Joni Mitchell &amp;rarr; 250 plays
 &lt;/li&gt;

 &lt;li&gt;
     James &amp;rarr; 143 plays
 &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Aphrodite and Lily Allen were recommendations from Molly; José González was a result of listening to his cover of &amp;#8220;Heartbeats&amp;#8221;, originally by the Knife; Vampire Weekend came from Matt Dolbey; The Rapture and the Bird and the Bee came from Joselyn; Bon Iver and the Helio Sequence came from two online reviews at &lt;a href=&quot;http://www.killerparkjob.com/&quot;&gt;Killer Park Job&lt;/a&gt;; Joni Mitchell and James were recommendations from Joel.
&lt;/p&gt;
&lt;h5&gt;April-Present&lt;/h5&gt;&lt;ul&gt;
 &lt;li&gt;
     Yael Naim &amp;rarr; 180 plays
 &lt;/li&gt;

 &lt;li&gt;
     The Good, The Bad and the Queen &amp;rarr; 11 plays
 &lt;/li&gt;

 &lt;li&gt;
     Gotye &amp;rarr; 116 plays
 &lt;/li&gt;

 &lt;li&gt;
     The Hood Internet &amp;rarr; 188 plays
 &lt;/li&gt;

 &lt;li&gt;
     Paper Route &amp;rarr; 57 plays
 &lt;/li&gt;

 &lt;li&gt;
     Neil Young &amp;rarr; 136 plays
 &lt;/li&gt;

 &lt;li&gt;
     Viva Voce &amp;rarr; 114 plays
 &lt;/li&gt;

 &lt;li&gt;
     Grand Archives &amp;rarr; 42 plays
 &lt;/li&gt;

 &lt;li&gt;
     Annuals &amp;rarr; 122 plays
 &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Yael Naim came from the television and Apple, Inc.; the Good, the Bad and the Queen and Paper Route were from Joselyn; Gotye and the Hood Internet were from John Z.; Neil Young was a result of random coincidence; and, finally, Viva Voce, Grand Archives and Annuals were all recommendations from Last.fm itself.
&lt;/p&gt;
&lt;p&gt;Whew.
&lt;/p&gt;
&lt;p&gt;So, there you have it.
&lt;/p&gt;
&lt;p&gt;Two years of finding new music to enjoy.
&lt;/p&gt;
&lt;p&gt;What&amp;#8217;s next?
&lt;/p&gt;</description>
      <pubDate>Thu, 14 Aug 2008 17:34:28 +0000</pubDate>
      <guid>http://www.taylor-hughes.com/2008/aug/two_years_with_lastfm.html</guid>
      <author>taylor@taylor-hughes.com (Taylor Hughes)</author>
    </item>
    
  </channel>
</rss>