<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-22677069511884100</id><updated>2012-02-16T19:33:35.971-08:00</updated><category term='Personal'/><category term='Python'/><category term='Bitbucket'/><category term='Spam'/><category term='Git'/><category term='Subversion'/><category term='Hiking'/><category term='Appengine'/><category term='Linux'/><title type='text'>Neogregious</title><subtitle type='html'></subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://neogregious.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/22677069511884100/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://neogregious.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Greg Fawcett</name><uri>http://www.blogger.com/profile/15739907007464929214</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>7</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-22677069511884100.post-4226735714110051184</id><published>2011-10-18T20:57:00.000-07:00</published><updated>2011-10-19T20:18:11.416-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Git'/><category scheme='http://www.blogger.com/atom/ns#' term='Subversion'/><category scheme='http://www.blogger.com/atom/ns#' term='Bitbucket'/><title type='text'>Migrating a Subversion Project to Git on BitBucket</title><content type='html'>&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-TdYa4mCkUdk/Tpz1yBEuaFI/AAAAAAAARR0/pJVa9ie9tEM/s1600/BitBucket.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/-TdYa4mCkUdk/Tpz1yBEuaFI/AAAAAAAARR0/pJVa9ie9tEM/s1600/BitBucket.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;span class="Apple-style-span" style="font-family: inherit;"&gt;Subversion is still the gold standard for version control (a.k.a. source control) systems, but the new kid on the block - Git - has undeniable advantages for many development models. Even for solo or centralised hierarchical&amp;nbsp;development organisations, Git's agile branching and merging make it a joy to use.&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: inherit;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: inherit;"&gt;But if you're going to go to the effort of switching version control systems, you probably also want to consider moving your repository to the cloud. &lt;/span&gt;This increases your options for off-site developers, and reduces the risks and maintenance costs of internally managed version control servers.&lt;br /&gt;&lt;br /&gt;This article describes how to migrate a Subversion repository to Bitbucket, one of several online repositories.&lt;br /&gt;&lt;a name='more'&gt;&lt;/a&gt;&lt;span class="Apple-style-span" style="font-family: inherit;"&gt;First of all, why &lt;/span&gt;&lt;a href="http://bitbucket.org/" style="font-family: inherit;"&gt;Bitbucket&lt;/a&gt;&lt;span class="Apple-style-span" style="font-family: inherit;"&gt;? &lt;/span&gt;&lt;a href="http://github.com/" style="font-family: inherit;"&gt;GitHub&lt;/a&gt;&lt;span class="Apple-style-span" style="font-family: inherit;"&gt; and &lt;/span&gt;&lt;a href="http://code.google.com/" style="font-family: inherit;"&gt;Google Code&lt;/a&gt;&lt;span class="Apple-style-span" style="font-family: inherit;"&gt; are worthy alternatives, but both have restrictions that make them less desirable for private repositories. &lt;/span&gt;&lt;span class="Apple-style-span" style="font-family: inherit;"&gt;Google Code doesn't allow them at all, and GitHub currently charges a small but significant amount per private repository. Bitbucket charges by developer instead, with the first five being free. Another important consideration is that a&lt;/span&gt;&lt;span class="Apple-style-span" style="font-family: inherit;"&gt;ll three are backed by large commercial companies that are unlikely to blink out of&amp;nbsp;&lt;/span&gt;existence&lt;span class="Apple-style-span" style="font-family: inherit;"&gt;&amp;nbsp;overnight.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: inherit; font-size: large;"&gt;1. Preparation&lt;/span&gt;&lt;br /&gt;OK, enough of the justifications, lets get migrating! In the Subversion world, the convention is that you structure your projects as follows:&lt;br /&gt;&lt;pre style="background-color: #e8e8ff;"&gt;&lt;span class="Apple-style-span" style="font-size: x-small;"&gt;foobar&lt;br /&gt;   - Trunk&lt;br /&gt;   - Branches&lt;br /&gt;      - Europe&lt;br /&gt;      - US&lt;br /&gt;   - Tags&lt;br /&gt;      - Version 3.11&lt;br /&gt;      - Version 3.12&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;If you use different conventions (like "trunk" instead of "Trunk"), take extra care to translate these in the code below.&amp;nbsp;Our example Subversion project URL is &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;svn://myserver/myrepo/foobar&lt;/span&gt; so you will need to translate this too.&lt;br /&gt;&lt;br /&gt;Next, we need to fix a small discontinuity in user handling between Subversion and Git. Subversion users are simple usernames (like &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;gregf&lt;/span&gt;), whereas Git requires a user's name and their email address. Create a file called &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;authors.txt&lt;/span&gt; containing an entry for each of your subversion users (usually listed in &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;svn/myrepo/conf/passwd&lt;/span&gt;):&lt;br /&gt;&lt;pre style="background-color: #e8e8ff;"&gt;lisas = Lisa Simpson &amp;lt;lisa@duff.com&amp;gt;&lt;br /&gt;barts = Bart Simpson &amp;lt;bart@duff.com&amp;gt;&lt;br /&gt;&lt;/pre&gt;Finally. make sure you have &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;git&lt;/span&gt; and &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;git-svn&lt;/span&gt; installed. Google to find out how to do this on your operating system.&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;2. Convert the Subversion&amp;nbsp;Project&amp;nbsp;to a Local Git Repository&lt;/span&gt;&lt;br /&gt;At the command line, enter:&lt;br /&gt;&lt;pre style="background-color: #e8e8ff;"&gt;&lt;span class="Apple-style-span" style="font-size: x-small;"&gt;git svn clone svn://myserver/myrepo/foobar --no-metadata -A authors.txt -b Branches -T Trunk -t Tags foobar_local&lt;/span&gt;&lt;br /&gt;&lt;/pre&gt;Make yourself some tea, coffee or martini - this will take a wee while, as it works through the entire history of changes in the Subversion project. Assuming all goes well, you should end up with a directory called &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;foobar_local&lt;/span&gt; containing both your project, and more importantly a hidden directory called &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;.git&lt;/span&gt;. To check, change into the directory and view the log:&lt;br /&gt;&lt;pre style="background-color: #e8e8ff;"&gt;&lt;span class="Apple-style-span" style="font-size: x-small;"&gt;cd foobar_local&lt;br /&gt;git log&lt;br /&gt;git branch&lt;/span&gt;&lt;/pre&gt;That last command doesn't show your branches! Don't panic, this will come right later.&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;3. Convert Ignore Filemasks&lt;/span&gt;&lt;br /&gt;If you have set up filemasks for Subversion to ignore, we can easily convert these into the Git equivalent:&lt;br /&gt;&lt;pre style="background-color: #e8e8ff;"&gt;&lt;span class="Apple-style-span" style="font-size: x-small;"&gt;cd foobar_local&lt;br /&gt;git svn show-ignore -i trunk &amp;gt; .gitignore&lt;br /&gt;git add .gitignore&lt;br /&gt;git commit -m 'Convert svn:ignore properties to .gitignore.'&lt;/span&gt;&lt;/pre&gt;Note that &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;Trunk&lt;/span&gt; in Subversion has been lower-cased by Git to &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;trunk&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;4. Create a Bare Git Repository&lt;/span&gt;&lt;br /&gt;So far you have a local git repository, but you need a bare repository to clone from. As part of this process, we set Git's symbolic &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;HEAD&lt;/span&gt; to what was Subversion's (lower-cased) &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;trunk&lt;/span&gt;, tell Git about any branches, and rename &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;trunk&lt;/span&gt; to &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;master&lt;/span&gt; (the convention in Git).&lt;br /&gt;&lt;pre style="background-color: #e8e8ff;"&gt;&lt;span class="Apple-style-span" style="font-size: x-small;"&gt;cd ..&lt;br /&gt;git init --bare foobar_bare&lt;br /&gt;cd foobar_bare&lt;br /&gt;git symbolic-ref HEAD refs/heads/trunk&lt;br /&gt;cd ../foobar_local&lt;br /&gt;git remote add bare ../foobar_bare&lt;br /&gt;git config remote.bare.push 'refs/remotes/*:refs/heads/*'&lt;br /&gt;git push bare&lt;br /&gt;cd ../foobar_bare&lt;br /&gt;git branch -m trunk master&lt;/span&gt;&lt;/pre&gt;After the push, Git will list the new branches. I told you not to panic!&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;5. Convert Tags&lt;/span&gt;&lt;br /&gt;Actually, if you use tags you might still be worried that all your tags seem to be branches. Git handles tags very differently from Subversion - in Git tags are simply aliases to a particular commit, whilst Subversion treats tags as a dead-end branch. Git-svn simply renames these branches, so we still need to convert these branch-tags to real Git tags:&lt;br /&gt;&lt;pre style="background-color: #e8e8ff;"&gt;&lt;span class="Apple-style-span" style="font-size: x-small;"&gt;cd foobar_bare&lt;br /&gt;git for-each-ref --format='%(refname)' refs/heads/tags |&lt;br /&gt;cut -d / -f 4 |&lt;br /&gt;while read ref&lt;br /&gt;do&lt;br /&gt;   git tag "$ref" "refs/heads/tags/$ref";&lt;br /&gt;   git branch -D "tags/$ref";&lt;br /&gt;done&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;6. Push the Bare Repository to Bitbucket&lt;/span&gt;&lt;br /&gt;Don't worry, we're nearly there now. The last step is to push all the branches up to Bitbucket. Log in to Bitbucket, and create a new repository called &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;foobar&lt;/span&gt;. It will then show you the command required to clone this repository, which will include the HTTPS URL. The command will be something like this:&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;$ git clone &lt;span class="Apple-style-span" style="background-color: #ffe599;"&gt;https://myname@bitbucket.org/myname/foobar.git&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The URL is the yellow section above. Now on your local machine, set the orgin and push the master (old trunk) and each branch to Bitbucket:&lt;br /&gt;&lt;pre style="background-color: #e8e8ff;"&gt;&lt;span class="Apple-style-span" style="font-size: x-small;"&gt;git remote add origin https://myname@bitbucket.org/myname/foobar.git&lt;br /&gt;git push origin master&lt;br /&gt;git push origin europe&lt;br /&gt;git push origin us&lt;/span&gt;&lt;/pre&gt;Your project is now on Bitbucket.&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;7. Tidy Up&lt;/span&gt;&lt;br /&gt;The only thing left now is to tidy up the local and bare repositories left on your machine.&lt;br /&gt;&lt;pre style="background-color: #e8e8ff;"&gt;&lt;span class="Apple-style-span" style="font-size: x-small;"&gt;cd ..&lt;br /&gt;rm -rf foobar_local&lt;br /&gt;rm -rf foobar_bare&lt;/span&gt;&lt;/pre&gt;And... you're done. Time for another tea, coffee, or martini to celebrate your success!&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;div style="background-color: #e0f8f8; border: 1px dashed rgb(0, 191, 230); padding: 5px;"&gt;&lt;span class="Apple-style-span" style="background-color: #e0f8f8; font-size: x-small;"&gt;If this article has helped you, you can show your appreciation by telling any schools you are involved with about&amp;nbsp;&lt;a href="http://www.schoolconferences.com/"&gt;www.SchoolConferences.com&lt;/a&gt;. This simple online app has already revolutionised parent-teacher conference evenings for over a thousand schools. See how easy it is to book conferences with the demonstration event code&amp;nbsp;&lt;b&gt;HIGH4&lt;/b&gt;&amp;nbsp;for high schools, and&amp;nbsp;&lt;b&gt;ELEM4&lt;/b&gt;&amp;nbsp;for elementary schools. Thanks!&lt;/span&gt;&lt;/div&gt;&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/22677069511884100-4226735714110051184?l=neogregious.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://neogregious.blogspot.com/feeds/4226735714110051184/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://neogregious.blogspot.com/2011/10/migrating-subversion-project-to-git-on.html#comment-form' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/22677069511884100/posts/default/4226735714110051184'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/22677069511884100/posts/default/4226735714110051184'/><link rel='alternate' type='text/html' href='http://neogregious.blogspot.com/2011/10/migrating-subversion-project-to-git-on.html' title='Migrating a Subversion Project to Git on BitBucket'/><author><name>Greg Fawcett</name><uri>http://www.blogger.com/profile/15739907007464929214</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/-TdYa4mCkUdk/Tpz1yBEuaFI/AAAAAAAARR0/pJVa9ie9tEM/s72-c/BitBucket.png' height='72' width='72'/><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-22677069511884100.post-1433902540293837717</id><published>2011-06-22T19:33:00.000-07:00</published><updated>2011-06-23T15:53:53.166-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Appengine'/><title type='text'>High-Replication Migration Lessons</title><content type='html'>&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-a0PwWYA3eEs/Tbd-79mrObI/AAAAAAAARO0/M3XbF28Nu0Q/s1600/appengine_lowres.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/-a0PwWYA3eEs/Tbd-79mrObI/AAAAAAAARO0/M3XbF28Nu0Q/s1600/appengine_lowres.png" /&gt;&lt;/a&gt;&lt;/div&gt;It's about a month since the migration, so how's it gone, what did we learn? What's it done to latency, error rates and CPU consumption? Most of all, was it worth the pain?&lt;br /&gt;&lt;br /&gt;&lt;a name='more'&gt;&lt;/a&gt;&lt;b&gt;Lesson 1: Understand how eventual consistency will affect your application.&lt;br /&gt;&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Our biggest fear was that there would be some inconsistencies between&amp;nbsp; master-slave (MS) and high replication (HR) that would cause problems with our apps. And that fear was borne out when users started reporting that new objects were not showing up after they had created them. Some debugging found that the new objects &lt;i&gt;did&lt;/i&gt; exist, and that we had been bitten by HR's &lt;b&gt;eventual-consistency&lt;/b&gt;.&lt;br /&gt;&lt;br /&gt;The datastore is much more efficient getting objects by ID than via a query, so I tend to give an object an array of IDs referencing child objects. When I added a new child, I ran a query to rebuild this ID array, which guaranteed it's consistency. With HR, the new child didn't show up in the query results for some seconds, so because I was rebuilding the array immediately after adding the new child, it disappeared. And when another new child was added, the &lt;i&gt;previous&lt;/i&gt; one appeared - but the new one didn't. Users hate that sort of thing!&lt;br /&gt;&lt;br /&gt;It was easily fixed - get_by_id() works as soon as an object is added, but queries do not. So we dispensed with the query, and simply added the ID to the array when we added a child (and removed it on deletion).&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Lesson 2: Writes take longer and reads take about the same - but both are more consistent.&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Probably the next biggest issue is performance. Writes take longer, probably about 50% on average, and reads are just about even. However, performance is far more consistent - our milliseconds/request graphs are much flatter.&lt;br /&gt;&lt;br /&gt;With the MS datastore, we got occasional operations that took &lt;i&gt;much&lt;/i&gt; longer than normal, and these seem to have largely disappeared with the HR.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Lesson 3: An order of magnitude less datastore timeouts.&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;The biggest difference has been on our error rate. With MS, we got a daily crop of datastore timeouts, and with HR they are extremely rare. With a non-relational database datastore errors lead to inconsistent data, because one entity is updated but the next may not. Users hate inconsistencies, so reducing errors by an order of magnitude is a huge win.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Lesson 4: HR costs more in CPU time.&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;Ah, but what about the cost? Google have equalised the storage costs between MS and HR, but you also pay for the CPU time to store and access your data. We're seeing a 30% increase in CPU after migrating, so HR will cost you more. How much depends on your application, but this may be also be moot when Google roll out their new instance-based billing.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;So was it worth the time and cost involved in migrating from MS to HR? &lt;b&gt;Yes.&lt;/b&gt; The reduced error rate has made our service far more robust, and reduced support calls. Also, we no longer need to manage user's expectations around Google's planned maintenance periods, which reduces a lot of customer irritation. These advantages easily outweigh the extra performance and CPU costs.&lt;br /&gt;&lt;br /&gt;Finally, please leave a comment below about how your migration went, or if you have any questions about the process. If you'd like dedicated support with your migration, please contact me (greg at vig dot co dot nz).&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;div style="background-color: #e0f8f8; border: 1px dashed rgb(0, 191, 230); padding: 5px;"&gt;&lt;span class="Apple-style-span" style="font-size: x-small;"&gt;If this article has helped you, you can show your appreciation by telling any schools you are involved with about &lt;a href="http://www.schoolconferences.com/"&gt;www.SchoolConferences.com&lt;/a&gt;. This simple online app (built on Appengine of course!) has already revolutionised parent-teacher conference evenings for over a thousand schools. See how easy it is to book conferences with the demonstration event code &lt;b&gt;HIGH4&lt;/b&gt; for high schools, and &lt;b&gt;ELEM4&lt;/b&gt; for elementary schools. Thanks!&lt;/span&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/22677069511884100-1433902540293837717?l=neogregious.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://neogregious.blogspot.com/feeds/1433902540293837717/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://neogregious.blogspot.com/2011/06/high-replication-migration-lessons.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/22677069511884100/posts/default/1433902540293837717'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/22677069511884100/posts/default/1433902540293837717'/><link rel='alternate' type='text/html' href='http://neogregious.blogspot.com/2011/06/high-replication-migration-lessons.html' title='High-Replication Migration Lessons'/><author><name>Greg Fawcett</name><uri>http://www.blogger.com/profile/15739907007464929214</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/-a0PwWYA3eEs/Tbd-79mrObI/AAAAAAAARO0/M3XbF28Nu0Q/s72-c/appengine_lowres.png' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-22677069511884100.post-3373693628775471853</id><published>2011-04-26T19:20:00.000-07:00</published><updated>2011-12-14T14:38:25.410-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Appengine'/><title type='text'>Migrating an App to High-Replication Datastore on Appengine</title><content type='html'>&lt;div style="text-align: left;"&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-a0PwWYA3eEs/Tbd-79mrObI/AAAAAAAARO0/M3XbF28Nu0Q/s1600/appengine_lowres.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/-a0PwWYA3eEs/Tbd-79mrObI/AAAAAAAARO0/M3XbF28Nu0Q/s1600/appengine_lowres.png" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;span class="Apple-style-span" style="font-family: inherit;"&gt;Google's Appengine now gives you the choice between the original master-slave datastore, and the new high-replication datastore. In a nutshell, high-replication provides more reliable storage (in terms of data security and latency) but has slower write times and costs considerably more.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: inherit;"&gt;The owners of almost all serious applications will choose the high-replication datastore. The extra cost is a small price to pay (literally) to escape the maintenance outages, datastore timeouts and high-latency requests that plague the master-slave datastore.&lt;/span&gt;&lt;/div&gt;&lt;br /&gt;&lt;span style="font-family: inherit;"&gt;The following article explains&lt;/span&gt;&lt;span class="Apple-style-span" style="font-family: inherit;"&gt; how to switch a production application from master-slave to high-replication with the minimum impact on your users.&lt;/span&gt;&lt;br /&gt;&lt;div style="text-align: left;"&gt;&lt;a name='more'&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: inherit;"&gt;Lets assume the app is called &lt;b&gt;myapp&lt;/b&gt;, and it is currently available at the URL &lt;b&gt;www.mydomain.com&lt;/b&gt; through Google Apps for your Domain. If you use &lt;b&gt;myapp.appspot.com&lt;/b&gt; instead, you become reliant on Google to create an alias from the old app to the new one, and so have less control over the timing and length of the cut-over when your app isn't available to users. I'm also assuming you have billing enabled, because transferring data is likely to exceed the free quotas (particularly CPU time).&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: inherit;"&gt; &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Many of the steps below involve switching between different Google accounts, in Appengine and Google Apps. Ideally you would log in to each account/service in a different tab before you started, but you can only have one account open in each browser. This means you either have to log in and out several times, or you can try running different browsers (Firefox, Chrome, IE) or different virtual machines for each account you need. In particular, the Datastore Admin page in Appengine admin may have a blank content section - signing out and in again usually cures this.&lt;br /&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;The biggest challenge in the migration is transferring all the data from myapp to myapp-hr. You probably don't want to create new entities with the high-level datastore API on myapp-hr because they will have different keys, and if you use keys or ids to link entities you'll need to map between the myapp keys and the myapp-hr keys. There are several possible approaches to this:&lt;/div&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;Use the appcfg.py utility to download data from myapp, and then upload it again to myapp-hr. This preserves keys, but is very slow because it is a single thread and all data has to be transferred across the internet to your machine and then back again - a two-stage serial transfer across many networks.&lt;/li&gt;&lt;li&gt;Write your own transfer utility with the Remote API so you can select which data to move at which time. This might allow you to limit the cut-over data transfer to active data only, and you can transfer the rest at your leisure during the preparation stage. However, this entails some fairly complex development and testing - you need to preserve keys (or map between old and new) and ideally have several threads running.&lt;/li&gt;&lt;li&gt;Write your own transfer utility using the Urlfetch API on myapp calling a handler on myapp-hr. As well as limiting the cut-over data transfer to active data only, it happens across Google's local network and both read and write occur simultaneously. This will be the fastest option, but again requires substantial development and testing.&lt;/li&gt;&lt;li&gt;Use the little-known &lt;a href="http://code.google.com/appengine/docs/adminconsole/datastoreadmin.html"&gt;Datastore Admin&lt;/a&gt; tool. This has all the advantages - across Google's local network, multiple threads and simultaneous read/write - except it transfers &lt;i&gt;all&lt;/i&gt; data (you &lt;i&gt;can&lt;/i&gt; select by entity kind, but usually each kind will have active and non-active entities so this isn't useful).&lt;/li&gt;&lt;li&gt;&lt;i&gt;[UPDATE] There is now a built-in migration tool in the admin console. I haven't used this, but this would now be my first choice.&lt;/i&gt;&lt;/li&gt;&lt;/ol&gt;&lt;ul&gt;&lt;/ul&gt;&lt;div&gt;My attempts to use appcfg.py to do the transfer ended when it took over an hour to transfer a fairly small dataset. I then looked at developing my own transfer process, before a kind soul on the Appengine group pointed me to the datastore admin documentation. This transferred 140,000 entities (110MB) in 20 minutes - and chewed through 5 hours of CPU time!&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;br /&gt;If you have massive amounts of data, you may choose option 3 to minimise the cut-over transfer time (and the cost of the associated CPU time), but otherwise I recommend the datastore admin tool - it's simple, fast and safe.&lt;i&gt;&amp;nbsp;[UPDATE]The built-in migration tool in the admin console would now be my first choice.&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;OK, let's get started! Each instruction is colour-coded for &lt;span class="Apple-style-span" style="color: #073763;"&gt;Appengine admin&lt;/span&gt;, &lt;span class="Apple-style-span" style="color: #660000;"&gt;Google Apps admin&lt;/span&gt;, &lt;span class="Apple-style-span" style="color: #4c1130;"&gt;Google Apps mail,&lt;/span&gt; and &lt;span class="Apple-style-span" style="color: #274e13;"&gt;shell/terminal.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;div style="margin: 0px;"&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: inherit;"&gt;The migration process is broken into three stages:&lt;/span&gt;&lt;/div&gt;&lt;ul&gt;&lt;li&gt;Preparation, which can be carried out at any time before the cut-over.&lt;/li&gt;&lt;li&gt;Cut-over, when your app is read-only for users.&lt;/li&gt;&lt;li&gt;Tidying up, done after the cut-over.&lt;/li&gt;&lt;/ul&gt;&lt;div style="font-size: 120%; padding: 10px 0px;"&gt;&lt;i&gt;&lt;b&gt;Preparation&lt;/b&gt;&lt;/i&gt;&lt;/div&gt;&lt;span class="Apple-style-span" style="font-family: Arial, Helvetica, sans-serif;"&gt;First of all, we set up the new application, enable the datastore admin tool on the old and new apps, and test the data transfer process.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;&lt;span class="Apple-style-span" style="color: #073763;"&gt;Create a new app called myapp-hr. Edit the storage options and select High Replication.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;If your app sends emails, &lt;span class="Apple-style-span" style="color: #073763;"&gt;invite the sending address(es) to become developers for myapp-hr.&lt;/span&gt; &lt;span class="Apple-style-span" style="color: #4c1130;"&gt;Accept the invitation(s) in Google Apps for mydomain.com.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span class="Apple-style-span" style="color: #073763;"&gt;Enable billing for myapp-hr, and allocate resources as required. A large allocation for CPU time is a good idea - the transfer may require more than you expect.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span class="Apple-style-span" style="color: #274e13;"&gt;Create &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;myapp-hr&lt;/span&gt; source directory, and copy everything from &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;myapp&lt;/span&gt; source directory.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span class="Apple-style-span" style="color: #274e13;"&gt;Edit the new myapp-hr &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;app.yaml&lt;/span&gt;:&lt;/span&gt;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;&lt;span class="Apple-style-span" style="color: #274e13;"&gt;&lt;span class="Apple-style-span" style="font-family: Arial, sans-serif;"&gt;Change &lt;/span&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;application:&lt;/span&gt;&lt;span class="Apple-style-span" style="font-family: Arial, sans-serif;"&gt; from &lt;/span&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;myapp&lt;/span&gt;&lt;span class="Apple-style-span" style="font-family: Arial, sans-serif;"&gt; to &lt;/span&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;myapp-hr&lt;/span&gt;&lt;span class="Apple-style-span" style="font-family: Arial, sans-serif;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span class="Apple-style-span" style="color: #274e13;"&gt;&lt;span class="Apple-style-span" style="font-family: Arial, sans-serif;"&gt;Add builtins section:&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&amp;nbsp;&amp;nbsp;builtins:&lt;br /&gt;&amp;nbsp;&amp;nbsp;- remote_api: on&lt;br /&gt;&amp;nbsp;&amp;nbsp;- datastore_admin: on&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;&lt;span class="Apple-style-span" style="color: #274e13;"&gt;Create file &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;appengine_config.py&lt;/span&gt; containing (as a single line):&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="color: #274e13;"&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&amp;nbsp;&amp;nbsp;remoteapi_CUSTOM_ENVIRONMENT_AUTHENTICATION&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&amp;nbsp;&amp;nbsp;('HTTP_X_APPENGINE_INBOUND_APPID',['myapp'])&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span class="Apple-style-span" style="color: #274e13;"&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="color: #274e13;"&gt;Upload the new myapp-hr application:&lt;/span&gt;&lt;/li&gt;&lt;li&gt; &lt;span class="Apple-style-span" style="color: #274e13; font-family: 'Courier New', Courier, monospace;"&gt;&amp;nbsp;&amp;nbsp;appcfg.py upload myapp-hr&lt;/span&gt;&lt;/li&gt;&lt;li&gt;Enable datastore admin in myapp. &lt;span class="Apple-style-span" style="color: #274e13;"&gt;Edit the original myapp &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;app.yaml&lt;/span&gt;&lt;span class="Apple-style-span" style="font-family: inherit;"&gt; to add builtins section:&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="color: #274e13;"&gt; &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;&amp;nbsp;&amp;nbsp;builtins:&lt;br /&gt;&amp;nbsp;&amp;nbsp;- remote_api: on&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span class="Apple-style-span" style="color: #274e13;"&gt;Upload the myapp application:&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="color: #274e13; font-family: 'Courier New', Courier, monospace;"&gt;&amp;nbsp;&amp;nbsp;appcfg.py upload myapp&lt;/span&gt;&lt;/li&gt;&lt;li&gt;Test the application is available by going to http://myapp-hr.appspot.com.&lt;/li&gt;&lt;li&gt;&lt;span style="color: #073763;"&gt;Test the data transfer process to find out how much real and CPU time it takes.&lt;/span&gt;&lt;span class="Apple-style-span" style="color: #073763;"&gt;Switch to myapp (&lt;span class="Apple-style-span" style="color: #073763;"&gt;&lt;i&gt;not myapp-hr&lt;/i&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="color: #073763;"&gt;)&lt;/span&gt; application&lt;/span&gt;&lt;span class="Apple-style-span" style="color: #073763;"&gt;.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span class="Apple-style-span" style="color: #073763;"&gt;On the Datastore Admin page, select all entity kinds, and click "Copy to Other App".&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span class="Apple-style-span" style="color: #073763;"&gt;Change &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;{TARGET_APPID}&lt;/span&gt; to &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;myapp-hr&lt;/span&gt; in target URL, leave extra header as is.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span class="Apple-style-span" style="color: #073763;"&gt;Note start time, go to the Task Queues page, refresh until default task queue is empty, and note end time.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span class="Apple-style-span" style="color: #073763;"&gt;Switch to myapp-hr application, and go to the Dashboard page to see how much CPU time the data transfer required.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span class="Apple-style-span" style="color: #073763;"&gt;Still in myapp-hr (&lt;i&gt;double-check it's &lt;span class="Apple-style-span" style="color: #073763;"&gt;not myapp!&lt;/span&gt;&lt;/i&gt;&lt;span class="Apple-style-span" style="color: #073763;"&gt;)&lt;/span&gt;, go to the Datastore Admin page, &lt;/span&gt;&lt;span class="Apple-style-span" style="color: #073763;"&gt;select all entity kinds, and click "Delete Entities" to clear all the data you just transferred.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;Change your local copy of myapp so it shows a maintenance message.&lt;/li&gt;&lt;/ul&gt;&lt;div style="font-size: 120%; padding: 10px 0px;"&gt;&lt;b&gt;&lt;i&gt;Cut-Over&lt;/i&gt;&lt;/b&gt;&lt;/div&gt;&lt;div&gt;Now you know how long the data transfer takes, you can warn your users when and for how long the system will be under maintenance. I usually announce a pessimistic figure of at least twice what I think it will take, to cover any problems that might arise. If data transfer takes 10 minutes, you should be able to complete the cut-over in 15 - but I told users maintenance would take an hour. If everything goes well, users will be happy the outage was shorter than announced. Otherwise you have more time to resolve the issue or revert to the old application.&lt;br /&gt;&lt;br /&gt;Here we go!&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;span class="Apple-style-span" style="color: #274e13;"&gt;Upload the myapp application to show the maintenance message:&lt;/span&gt;&lt;br /&gt;&lt;span class="Apple-style-span" style="color: #274e13; font-family: 'Courier New', Courier, monospace;"&gt;&amp;nbsp;&amp;nbsp;appcfg.py upload myapp&lt;/span&gt;&lt;br /&gt;&lt;/li&gt;&lt;li&gt;&lt;span class="Apple-style-span" style="color: #073763;"&gt;Switch to myapp application.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span class="Apple-style-span" style="color: #073763;"&gt;Set myapp to read-only mode by clicking "Disable writes..." on the Application Settings page.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span class="Apple-style-span" style="color: #274e13;"&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="color: #073763;"&gt;On the Datastore Admin page, select all entity kinds, and click "Copy to Other App".&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span class="Apple-style-span" style="color: #073763;"&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="color: #073763;"&gt;Change &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;{TARGET_APPID}&lt;/span&gt; to &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;myapp-hr&lt;/span&gt; in target URL, leave extra header as is.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span class="Apple-style-span" style="color: #073763;"&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="color: #073763;"&gt;Go to Task Queues page and wait until default task queue is empty.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span class="Apple-style-span" style="color: #660000;"&gt;Log in as admin, click on myapp in the Google App Engine section, and then delete www.mydomain.com under Web address.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span class="Apple-style-span" style="color: #073763;"&gt;Switch to myapp-hr application, go to the Application Settings page and click "Add Domain".&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span class="Apple-style-span" style="color: #660000;"&gt;Accept agreement, click "Activate this service", add new URL "www" and click "I've completed these steps".&lt;/span&gt;&lt;/li&gt;&lt;li&gt;Go to www.mydomain.com and wait for the maintenance message to disappear, which means the domain is now pointing to myapp-hr. This should happen within a minute.&lt;/li&gt;&lt;li&gt;Test the application. On one occasion I found issues that disappeared in a few minutes (before I could properly diagnose them), so don't panic if odd things happen at first.&lt;/li&gt;&lt;/ul&gt;&lt;div style="font-size: 120%; padding: 10px 0px;"&gt;&lt;b&gt;&lt;i&gt;Tidy Up&lt;/i&gt;&lt;/b&gt;&lt;/div&gt;&lt;div&gt;All that's left to do is remove the datastore admin capability from your application, clear all the data from the old application, and remove it so it doesn't count against your application limit.&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;&lt;span style="color: #274e13;"&gt;Delete&lt;/span&gt;&lt;span class="Apple-style-span" style="color: #274e13;"&gt; file &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;appengine_config.py&lt;/span&gt; from myapp-hr.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span class="Apple-style-span" style="color: #274e13;"&gt;Remove &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;datastore_admin&lt;/span&gt; and &lt;/span&gt;&lt;span class="Apple-style-span" style="color: #274e13;"&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;remote_api&lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="color: #274e13;"&gt; from builtins section of myapp-hr &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;app.yaml&lt;/span&gt;.&lt;/span&gt;&lt;/li&gt;&lt;li&gt; &lt;span class="Apple-style-span" style="color: #073763;"&gt;Change the CPU time allocation to it's normal level on the myapp-hr Billing Settings page.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span class="Apple-style-span" style="color: #073763;"&gt;Switch to myapp applicatio&lt;span style="color: #073763; font-size: small;"&gt;n, and disable billing on the Billing Settings page.&lt;/span&gt;&lt;/span&gt;&lt;span style="color: #073763; font-size: small;"&gt; &lt;/span&gt;&lt;span style="color: #073763; font-size: small;"&gt;&lt;span style="font-family: Arial, sans-serif;"&gt;Also cancel recurring charge authorization. This will take several days to complete.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="color: #073763; font-size: small;"&gt;&lt;span style="font-family: Arial, sans-serif;"&gt;Go to the Application Settings page, and disable the application. You can then request permanent deletion, which will take 72 hours to occur. &lt;/span&gt;&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;a href="http://neogregious.blogspot.com/2011/06/high-replication-migration-lessons.html"&gt;Read our migration post-mortem.&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Finally, please leave a comment below about how your migration went, or if you have any questions about the process. If you'd like dedicated support with your migration, please contact me (greg at vig dot co dot nz).&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;div style="background-color: #e0f8f8; border: 1px dashed rgb(0, 191, 230); padding: 5px;"&gt;&lt;span class="Apple-style-span" style="font-size: x-small;"&gt;If this article has helped you, you can show your appreciation by telling any schools you are involved with about &lt;a href="http://www.schoolconferences.com/"&gt;www.SchoolConferences.com&lt;/a&gt;. This simple online app (built on Appengine of course!) has already revolutionised parent-teacher conference evenings for over a thousand schools. See how easy it is to book conferences with the demonstration event code &lt;b&gt;HIGH4&lt;/b&gt; for high schools, and &lt;b&gt;ELEM4&lt;/b&gt; for elementary schools. Thanks!&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/22677069511884100-3373693628775471853?l=neogregious.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://neogregious.blogspot.com/feeds/3373693628775471853/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://neogregious.blogspot.com/2011/04/migrating-app-to-high-replication.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/22677069511884100/posts/default/3373693628775471853'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/22677069511884100/posts/default/3373693628775471853'/><link rel='alternate' type='text/html' href='http://neogregious.blogspot.com/2011/04/migrating-app-to-high-replication.html' title='Migrating an App to High-Replication Datastore on Appengine'/><author><name>Greg Fawcett</name><uri>http://www.blogger.com/profile/15739907007464929214</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/-a0PwWYA3eEs/Tbd-79mrObI/AAAAAAAARO0/M3XbF28Nu0Q/s72-c/appengine_lowres.png' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-22677069511884100.post-6533167310424945433</id><published>2011-04-02T20:26:00.000-07:00</published><updated>2011-11-05T21:58:18.525-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Appengine'/><category scheme='http://www.blogger.com/atom/ns#' term='Linux'/><category scheme='http://www.blogger.com/atom/ns#' term='Python'/><title type='text'>Installing Multiple Versions of Python on Linux</title><content type='html'>The Google Appengine SDK requires at least version 2.5 of python, or else it spits the dummy. To compound matters, Google has announced that version 1.4.3 of the SDK is the last to support python 2.5, and all newer versions will require python 2.6. &lt;i&gt;[Update: and now you need python 2.7 for multi-threaded apps.]&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;So how do you install later versions of python without impacting the system default version - which is required for all sorts of vital system services?&lt;br /&gt;&lt;br /&gt;&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;The trick is to use the &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;prefix&lt;/span&gt; and &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;altinstall&lt;/span&gt; options when building the new version of python. Make sure you have libsqlite3-dev and libssl-dev installed or Sqlite3 and SSL support won't be compiled in.&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;span class="Apple-style-span" style="font-size: x-small;"&gt;cd ~/src&lt;br /&gt;wget http://www.python.org/ftp/python/2.7.2/Python-2.7.2.tar.bz2&lt;br /&gt;tar -jxvf Python-2.7.2.tar.bz2 &lt;br /&gt;rm Python-2.7.2.tar.bz2&lt;br /&gt;cd Python-2.7.2/&lt;br /&gt;./configure &lt;b&gt;--prefix=/opt/python2.7&lt;/b&gt;&lt;br /&gt;make&lt;br /&gt;sudo make &lt;b&gt;altinstall&lt;/b&gt;&lt;br /&gt;sudo &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;ln -s /opt/python2.7/bin/python2.7 /usr/bin/python2.7&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;You (and the system) will continue to use the old version by calling &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;python&lt;/span&gt;, and you can use the new version by calling &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;python2.7&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;So to install sqlite3 support for python2.7 for instance:&lt;br /&gt;&lt;code&gt;&lt;span class="Apple-style-span" style="font-size: x-small;"&gt;cd ~/src &lt;br /&gt;wget http://pysqlite.googlecode.com/files/pysqlite-2.6.3.tar.gz&lt;br /&gt;tar -xzf pysqlite-2.6.3.tar.gz &lt;br /&gt;rm pysqlite-2.6.3.tar.gz&lt;br /&gt;cd pysqlite-2.6.3/&lt;br /&gt;sudo &lt;b&gt;python2.7&lt;/b&gt; setup.py build_static install&lt;/span&gt;&lt;/code&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/22677069511884100-6533167310424945433?l=neogregious.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://neogregious.blogspot.com/feeds/6533167310424945433/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://neogregious.blogspot.com/2011/04/installing-multiple-versions-of-python.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/22677069511884100/posts/default/6533167310424945433'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/22677069511884100/posts/default/6533167310424945433'/><link rel='alternate' type='text/html' href='http://neogregious.blogspot.com/2011/04/installing-multiple-versions-of-python.html' title='Installing Multiple Versions of Python on Linux'/><author><name>Greg Fawcett</name><uri>http://www.blogger.com/profile/15739907007464929214</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-22677069511884100.post-5302533483207166603</id><published>2011-02-12T01:19:00.000-08:00</published><updated>2011-02-28T00:01:03.575-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Hiking'/><category scheme='http://www.blogger.com/atom/ns#' term='Personal'/><title type='text'>Cascade Saddle 2010</title><content type='html'>&lt;table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: right; margin-left: 1em; text-align: right;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="https://picasaweb.google.com/g.fawcett/CascadeSaddle2011" target="_default"&gt;&lt;img border="0" height="99" src="http://3.bp.blogspot.com/-AGfag1RIxO4/TVY5gRgzhOI/AAAAAAAAQxQ/3mUWtIgOn5k/s200/map.JPG" width="200" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Click map to go to photo gallery&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;The Cascade Saddle track has a reputation of being one of New Zealand's harder walks, compensated with awe-inspiring views over the Matukituki valley, Mount Aspiring and the Dart Glacier. Who could resist?&lt;br /&gt;&lt;br /&gt;&lt;a name='more'&gt;&lt;/a&gt;My 16-year old sons Olly and Jasper are Venture Scouts, and part of their Chief Scout Award involves planning and carrying out a journey. They organised to take three days off their holiday jobs, but as the time came closer the weather forecast narrowed down our options. Luckily the Cascade Saddle was a possibility - I had wanted to do this walk for years, and the boys eventually succumbed to my arguments.&lt;br /&gt;&lt;br /&gt;So as soon as Olly got off work on Wednesday 26th January, we loaded up the truck with our gear and set off - in pouring rain. It's about two hours drive from our home in Arrowtown to Raspberry Creek, and Nicole ensured our undying love by dropping us off - then driving all the way back again. After having my shorts licked by a cow as I did up my boots (a good omen?), we set off in showers to walk the two easy hours up to Aspiring hut. Arriving just before dark, we made some hot chocolate in this prince of mountain huts, chatted to a group of Dunedin Sea Scouts, and bunked down ready for the big day.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: large;"&gt;Day One&lt;/span&gt; &lt;br /&gt;We were woken at 6:30 by raucous keas, only to find they had taken to Jasper's orthotic insoles. Luckily they were still mostly intact, and we were glad to see the weather had cleared according to plan. After a quick breakfast we set off at about 7:30.&lt;br /&gt;&lt;br /&gt;The day was broken into four main sections: a steep climb from Aspiring Hut to the pylon; across Cascade Creek to the viewpoint;&amp;nbsp; descending alongside the Dart Glacier to the valley floor; and a flat walk along the valley to Dart Hut.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-Qh6lnNQjZuQ/TVZBWEsRiII/AAAAAAAAQyo/oQeyjvN0flo/s1600/IMG_0033.JPG" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" height="150" src="http://2.bp.blogspot.com/-Qh6lnNQjZuQ/TVZBWEsRiII/AAAAAAAAQyo/oQeyjvN0flo/s200/IMG_0033.JPG" width="200" /&gt;&lt;/a&gt;&lt;/div&gt;The first section was a killer. The track was very steep, with frequent large steps that soon had my legs screaming. The boys... not so much. It became obvious that they were far fitter than I, stopping every few minutes to let their decrepit father catch up. We broke out of the bush after about two hours, getting stunning views of the Matukituki Valley and Mount Aspiring. Unfortunately the track got even steeper, and the tales of injuries and deaths on this section seemed only too credible. Steep bluffs were everywhere, and a stumble could easily be disastrous.&lt;br /&gt;&lt;br /&gt;&lt;table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: left; margin-right: 1em; text-align: left;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-8MLoecfDAlY/TVZJiiNQZTI/AAAAAAAAQzE/cEXgBlG8JnM/s1600/IMG_0050.JPG" imageanchor="1" style="clear: left; margin-bottom: 1em; margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="150" src="http://3.bp.blogspot.com/-8MLoecfDAlY/TVZJiiNQZTI/AAAAAAAAQzE/cEXgBlG8JnM/s200/IMG_0050.JPG" width="200" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;The pylon&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;The boys scampered on like mountain goats, and I toiled after them. The GPS was useful, mainly for the altitude readout - we paused for a few minutes after every hundred metres we gained. Eventually we crested the last bluff, the track mercifully levelled off, and we reached the pylon. At just over 1800m, we'd climbed 1400m in just over four hours. We munched some scroggin, and took in the incredible views - Raspberry creek was still in view, far below us, as was Aspiring Hut. Across the valley we could just make out French Ridge Hut, and we started plotting a visit. Above the hut stood Mount Aspiring cloaked in glaciers.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-cC7BBRCh_WI/TVZNteX-YUI/AAAAAAAAQzo/HuvCZpi3AoE/s1600/IMG_0063.JPG" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" height="150" src="http://2.bp.blogspot.com/-cC7BBRCh_WI/TVZNteX-YUI/AAAAAAAAQzo/HuvCZpi3AoE/s200/IMG_0063.JPG" width="200" /&gt;&lt;/a&gt;&lt;/div&gt;We then started to walk down a far more reasonable gradient to Cascade Creek. This runs through a delightful valley with snow grass meadows - maybe it was the euphoria of reaching the top, but it seemed to me to be absolutely idyllic. We reached the viewpoint after an hour, and settled in a small sheltered spot to wolf our lunch - fruit loaf with camembert! We took an hour to enjoy views from the precipitous edge, and laze in the sun under the watchful eye of a Kea.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-gPa4vcqLXyA/TVZVBiGDfKI/AAAAAAAAQ0A/-lkqPEDK94w/s1600/IMG_0077.JPG" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="150" src="http://1.bp.blogspot.com/-gPa4vcqLXyA/TVZVBiGDfKI/AAAAAAAAQ0A/-lkqPEDK94w/s200/IMG_0077.JPG" width="200" /&gt;&lt;/a&gt;&lt;/div&gt;After recharging our batteries, we set off down to the Dart Valley. Jasper's toes started to get painful in his cheap boots, but truly magnificent views of the Dart Glacier helped distract us. "Awesome" is overused these days, but that's what the glacier was. From the unblemished snow around the peaks, it ran down until the ice started cracking across the glacier as it rode over a lip of rock far beneath. Then longitudinal crevices appear as it is forced to turn a right angle by the Dart Valley, all the while picking up more and more shattered rocks from the valley sides. The tail is so encrusted with debris that it doesn't seem to be ice at all. We reached the tail after about an hour's walk down fairly loose rock on the south side of the valley. &lt;br /&gt;&lt;br /&gt;As often happens, the last section was the hardest, despite being pretty much flat. We had put on sun-screen when we left the bushline that morning, but because we hadn't brought enough, we didn't apply more. The sun was hot, there was no breeze and the raw rock we were walking over reflected the heat straight back at us. Jasper's toes were very sore by this stage, so none of us really enjoyed the experience at this stage.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/-PhnC4MhePcM/TVZX4sn1e7I/AAAAAAAAQ0k/nH0C8jjsa0s/s1600/IMG_0088.JPG" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" height="150" src="http://2.bp.blogspot.com/-PhnC4MhePcM/TVZX4sn1e7I/AAAAAAAAQ0k/nH0C8jjsa0s/s200/IMG_0088.JPG" width="200" /&gt;&lt;/a&gt;&lt;/div&gt;Which was a shame - it was almost like walking through time. At the glacier tail, no life existed and the valley was bare raw rock. But as we travelled down, mosses and small spruts of grass started appearing; then grass became thicker, and bush started appearing; and when we reached the hut 2.5 hours later, we were in mature beech forest.&lt;br /&gt;&lt;br /&gt;We were all relieved - Jasper's boots came off in a flash, and we stumbled down to the river for a wash. The cold water was heavenly on our necks, arms and legs, and we realised how burnt we all were. All up we'd taken ten hours and ten minutes to get from Aspiring Hut to Dart Hut, including a good three-quarters of an hour idling at the viewpoint.&lt;br /&gt;&lt;br /&gt;That evening was a bit of a blur - we rustled up some macaroni cheese with sliced cabanossi sticks, wolfed a pottle of mango on top of creamed rice, and hit the sack. &lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: large;"&gt;Day Two&lt;/span&gt;&lt;br /&gt;After a solid night's sleep, we cooked porridge on the trusty coke-can cooker, and packed ready for the rain that was forecast. It was overcast as we set off, and after a hour it started drizzling. We marched on, through alternating beech forest and grassy flats, and the rain got stronger. My gaiters had kept my boots dry through all the streams the day before, but I stupidly didn't put them on when we started out. My boots got soaked just from brushing past the grass on the flats, and putting them on halfway along closed the stable door after the horse had bolted.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://lh4.googleusercontent.com/-903u8UM0f9c/TVZZ5WUks0I/AAAAAAAAQ1A/jEVh6p0fO-k/s1600/IMG_0112.JPG" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="150" src="https://lh4.googleusercontent.com/-903u8UM0f9c/TVZZ5WUks0I/AAAAAAAAQ1A/jEVh6p0fO-k/s200/IMG_0112.JPG" width="200" /&gt;&lt;/a&gt;&lt;/div&gt;We didn't feel like stopping in the rain for lunch and so pushed on to Daley's Flat, arriving about 1:30pm. Despite the rain, the warden was busy with earthworks remodelling the path around the hut. It was a mud bath, and the voracious sandflies made it even less appealing to spend any time outside.&lt;br /&gt;&lt;br /&gt;We had a leisurely lunch of soup,&amp;nbsp; ryveta, cream cheese and sun-dried tomatoes. Then an afternoon snooze as the rain poured down, not waking till late afternoon. Dinner was dehydrated something (who can tell!) and a bar of chocolate. Jasper and Olly played werewolves (a version of mafia) with the rest of the hut inhabitants, then we went off for another good night's sleep.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: large;"&gt;Day Three&lt;/span&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://lh3.googleusercontent.com/-FokALD-6kmg/TVZZ6bQvz7I/AAAAAAAAQ1Q/vWbBGT9XImY/s1600/IMG_0120.JPG" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" height="150" src="https://lh3.googleusercontent.com/-FokALD-6kmg/TVZZ6bQvz7I/AAAAAAAAQ1Q/vWbBGT9XImY/s200/IMG_0120.JPG" width="200" /&gt;&lt;/a&gt;&lt;/div&gt;We woke to find the rain had stopped, and after porridge packed for the last time and rushed out the door to avoid the swarms of sandflies in the porch. The lunch we knew Nicole was bringing in was a constant driver keeping us moving, and we covered the ground at a good pace. Although it had stopped raining, the low cloud obscured Mount Earnslaw to our left. The track was similar to the previous day, with bush sections around bluffs followed by grassy flats.&lt;br /&gt;&lt;br /&gt;And before we know it we were skirting Chinaman's Bluff, seeing parked cars through the trees, and sure enough there was Nicole waiting patiently for us. It was lovely to see her, and even lovelier to see the fine feed she had brought with her - bacon and egg pie and a potato salad!&lt;br /&gt;&lt;br /&gt;It was a good trip. Apart from Jasper's toes and sunburn all round, we had no injuries, and my knees handled the climb and descent without a murmur. We got the weather right, doing the potentially dangerous Cascade Saddle on the one fine day we got. We learned not to leave orthotic insoles anywhere keas can get at them, and apart from Olly's mug cracking, our gear performed well. The boys did a sterling job of organising and executing the walk, and I was proud to have such capable companions for this unforgettable adventure.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/22677069511884100-5302533483207166603?l=neogregious.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://neogregious.blogspot.com/feeds/5302533483207166603/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://neogregious.blogspot.com/2011/02/cascade-saddle-2010.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/22677069511884100/posts/default/5302533483207166603'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/22677069511884100/posts/default/5302533483207166603'/><link rel='alternate' type='text/html' href='http://neogregious.blogspot.com/2011/02/cascade-saddle-2010.html' title='Cascade Saddle 2010'/><author><name>Greg Fawcett</name><uri>http://www.blogger.com/profile/15739907007464929214</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/-AGfag1RIxO4/TVY5gRgzhOI/AAAAAAAAQxQ/3mUWtIgOn5k/s72-c/map.JPG' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-22677069511884100.post-6382120754871168215</id><published>2011-01-15T20:07:00.000-08:00</published><updated>2011-01-15T20:49:09.875-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Appengine'/><category scheme='http://www.blogger.com/atom/ns#' term='Spam'/><title type='text'>DKIM for Google Apps (but not Appengine)</title><content type='html'>In the ongoing war against spam, DKIM (Domain Key Identified Mail) seems to be the best candidate so far - if all legitimate email users started it, it would be the end of anonymous spam. This article explains DKIM, why you should implement it &lt;i&gt;now&lt;/i&gt;, and how to do this with Google Apps.&lt;br /&gt;&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_bkHz6v1q_nI/TTJoW4BthvI/AAAAAAAAQxA/Ey4IN8r1lqo/s1600/nospam.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" height="166" src="http://3.bp.blogspot.com/_bkHz6v1q_nI/TTJoW4BthvI/AAAAAAAAQxA/Ey4IN8r1lqo/s200/nospam.jpg" width="200" /&gt;&lt;/a&gt;&lt;/div&gt;DKIM allows receiving mail servers to reliably check an email is actually from the domain it says it's from, using a cryptographic key from your domain name service (DNS). This doesn't stop spam, but it makes senders responsible. If you get an unwanted DKIM signed message, you know exactly who sent it so you can take measures against them - blocking their domain perhaps, or suing them.&lt;br /&gt;&lt;br /&gt;More and more spam filters are checking for DKIM signatures, and if your messages are signed they usually&amp;nbsp;bypass other spam checks and are&amp;nbsp;delivered immediately. So DKIM will improve your delivery statistics &lt;i&gt;right now&lt;/i&gt;. And as more and more organisations implement DKIM, unsigned messages will become increasingly likely to be classed as spam, and so more and more organisations will implement DKIM to ensure their messages get through.&lt;br /&gt;&lt;br /&gt;The faster DKIM spreads, the sooner anonymous spam will become history. We'll still need to manage spam because legitimate organisations will continue to do it, but after a few go bust from CAN SPAM fines even this should dry up. &lt;b&gt;Bottom line - unless you are a spammer, it is very much in your interests to implement DKIM as soon as possible.&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;If you use Google Apps for your email, the good news is that DKIM is very easy to set up. Here's how...&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Log in as the administrator to your Google Apps dashboard.&amp;nbsp;&lt;/li&gt;&lt;li&gt;Click on the&amp;nbsp;&lt;b&gt;Advanced tools&lt;/b&gt;&amp;nbsp;tab and then the link to&amp;nbsp;&lt;b&gt;Set up email authentication (DKIM)&lt;/b&gt;.&lt;/li&gt;&lt;li&gt;Click on &lt;b&gt;Generate new record&lt;/b&gt;.&lt;/li&gt;&lt;li&gt;Change the prefix selector from &lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"&gt;google&lt;/span&gt; to your organisation (if you want to - this isn't important at all).&lt;/li&gt;&lt;li&gt;This should result in a&amp;nbsp;DNS Host name &lt;span class="Apple-style-span" style="background-color: yellow; font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;&lt;i&gt;something&lt;/i&gt;._domainkey&lt;/span&gt;&amp;nbsp;and a TXT record value &lt;span class="Apple-style-span" style="background-color: yellow;"&gt;&lt;span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;v=DKIM1; k=rsa; t=y; p=lotsOfWeirdCharacters&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;Do not click the &lt;b&gt;Start Authentication&lt;/b&gt; button yet!&lt;/li&gt;&lt;li&gt;Now go to your registrar's DNS control panel. I can't give exact instructions here because every control panel is different, but you want to find where you can set the A, CNAME, MX and TXT records for your domain name.&lt;/li&gt;&lt;li&gt;Add a TXT record with the host name and TXT record values from step 5. (Some control panels don't provide a&amp;nbsp;&lt;/li&gt;&lt;li&gt;Wait a day or so&amp;nbsp;to let the new DNS records percolate through the global name servers. If you're impatient and have dig, try &lt;span class="Apple-style-span" style="background-color: yellow; font-family: 'Courier New', Courier, monospace; font-size: x-small;"&gt;dig txt &lt;i&gt;something&lt;/i&gt;._domainkey.&lt;i&gt;your.domain.name&lt;/i&gt;&lt;/span&gt; until it returns the key.&lt;/li&gt;&lt;li&gt;Log back in to your&amp;nbsp;Google Apps dashboard, and go to&amp;nbsp;the &lt;b&gt;Advanced tools&lt;/b&gt; tab and the link to &lt;b&gt;Set up email authentication (DKIM)&lt;/b&gt;, and &lt;b&gt;c&lt;/b&gt;lick the &lt;b&gt;Start Authentication&lt;/b&gt; button.&lt;/li&gt;&lt;/ol&gt;&lt;div&gt;Once you've done this, messages from your Google Apps domain* should contain a DKIM signature header. To test this, log in to your Google Apps account and send an email to another Gmail account. When it arrives, click the &lt;b&gt;show details&lt;/b&gt; link - if all is well, there will be a new line saying &lt;b&gt;Signed by&lt;/b&gt; giving your domain name.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;* But not messages sent from Appengine, sadly. Please &lt;a href="http://code.google.com/p/googleappengine/issues/detail?id=3161"&gt;star this issue&lt;/a&gt; if this affects you. I'll update this post if the situation changes.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/22677069511884100-6382120754871168215?l=neogregious.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://neogregious.blogspot.com/feeds/6382120754871168215/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://neogregious.blogspot.com/2011/01/dkim-for-google-apps-and-appengine.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/22677069511884100/posts/default/6382120754871168215'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/22677069511884100/posts/default/6382120754871168215'/><link rel='alternate' type='text/html' href='http://neogregious.blogspot.com/2011/01/dkim-for-google-apps-and-appengine.html' title='DKIM for Google Apps (but not Appengine)'/><author><name>Greg Fawcett</name><uri>http://www.blogger.com/profile/15739907007464929214</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_bkHz6v1q_nI/TTJoW4BthvI/AAAAAAAAQxA/Ey4IN8r1lqo/s72-c/nospam.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-22677069511884100.post-3218088997222403999</id><published>2011-01-10T16:44:00.000-08:00</published><updated>2011-01-15T20:45:48.643-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Personal'/><title type='text'>Start Here</title><content type='html'>Crowded though the blogosphere is, I occasionally feel the need to make odd thoughts more public. By using a blog, I'm hoping &lt;i&gt;you&lt;/i&gt; will add your own insights to what I hope will become a series of virtual discussions, rather than a list of my rants.&lt;br /&gt;&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;"Neogregious"? All the plays on "egregious" I could think of were taken, so this totally meaningless word will have to do. The background is the slopes of Mount Earnslaw near the Esquilant Bivvy at 2000m, taken on an epic trip with my brother Jon in 2005.&lt;br /&gt;&lt;br /&gt;&lt;div style="text-align: right;"&gt;&lt;table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: right; margin-left: 1em; text-align: right;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/_bkHz6v1q_nI/TSvBCprfTlI/AAAAAAAAQw8/N8uVCvYkt0k/s1600/IMG_5120.JPG" imageanchor="1" style="clear: right; margin-bottom: 1em; margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="209" src="http://4.bp.blogspot.com/_bkHz6v1q_nI/TSvBCprfTlI/AAAAAAAAQw8/N8uVCvYkt0k/s320/IMG_5120.JPG" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Samoa has more palm trees than Arrowtown&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;A little background about myself. I'm lucky enough to live in one of the most beautiful places in the world (Arrowtown in the heart of the Southern Alps of New Zealand), with the most gorgeous woman in the world (Nicole, a primary school teacher). We have two sons Olly and Jasper, who seem to be turning into healthy, pleasant and intelligent young men. I play the ukulele very badly, love tramping, and help run the Arrowtown Scouts.&lt;br /&gt;&lt;br /&gt;After knocking off a Physics B.Sc. and an Electronics M.Sc. in the United Kingdom, I got my most important education when I took off round the world for two years. I worked on a bulk carrier from Greece to India, enjoyed the Buddhists in Dharamsala, developed noise calculation software in Singapore, sprayed for cockroaches in Broome, fixed Commodore 64s in Darwin, organised hundreds of cables for a SCADA system in Sydney, played catch-me-if-you-can with police launches around nuclear-powered warships in Sydney Harbour, and discovered that New Zealand was where I wanted to spend the rest of my life.&lt;br /&gt;&lt;br /&gt;I also found that software development suited me, and luckily I suited software development too. Starting with C on DOS , working through C++ on Windows, back to C on Linux, PHP on LAMP and now... Python on everything!&lt;br /&gt;&lt;br /&gt;I started my own company (Virtual Industries Group) in 2001 to exploit the shift from PC-based to online applications. One of our applications was a system that allowed schools to provide online bookings for &lt;a href="http://www.schoolinterviews.co.nz/"&gt;parent-teacher interviews&lt;/a&gt; (or &lt;a href="http://www.schoolconferences.com/"&gt;conferences in the US&lt;/a&gt;), and this &lt;i&gt;really&lt;/i&gt; took off - we're doubling our customer base each year, and currently provide our service to schools in 8 countries around the world.&lt;br /&gt;&lt;br /&gt;So welcome to Neogregious - I hope I can add more signal than noise, and that you will take the time to respond to posts with your own perspective.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/22677069511884100-3218088997222403999?l=neogregious.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://neogregious.blogspot.com/feeds/3218088997222403999/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://neogregious.blogspot.com/2011/01/start-here.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/22677069511884100/posts/default/3218088997222403999'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/22677069511884100/posts/default/3218088997222403999'/><link rel='alternate' type='text/html' href='http://neogregious.blogspot.com/2011/01/start-here.html' title='Start Here'/><author><name>Greg Fawcett</name><uri>http://www.blogger.com/profile/15739907007464929214</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_bkHz6v1q_nI/TSvBCprfTlI/AAAAAAAAQw8/N8uVCvYkt0k/s72-c/IMG_5120.JPG' height='72' width='72'/><thr:total>0</thr:total></entry></feed>
