<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	xmlns:georss="http://www.georss.org/georss" xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#" xmlns:media="http://search.yahoo.com/mrss/"
	>

<channel>
	<title>AWS Ninja</title>
	<atom:link href="http://awsninja.wordpress.com/feed/" rel="self" type="application/rss+xml" />
	<link>http://awsninja.wordpress.com</link>
	<description>Web development and hosting with Amazon Web Services</description>
	<lastBuildDate>Thu, 07 Oct 2010 21:55:44 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.com/</generator>
<cloud domain='awsninja.wordpress.com' port='80' path='/?rsscloud=notify' registerProcedure='' protocol='http-post' />
<image>
		<url>http://s2.wp.com/i/buttonw-com.png</url>
		<title>AWS Ninja</title>
		<link>http://awsninja.wordpress.com</link>
	</image>
	<atom:link rel="search" type="application/opensearchdescription+xml" href="http://awsninja.wordpress.com/osd.xml" title="AWS Ninja" />
	<atom:link rel='hub' href='http://awsninja.wordpress.com/?pushpress=hub'/>
		<item>
		<title>A PHP Framework for Hosting your JavaScript Applications on CloudFront</title>
		<link>http://awsninja.wordpress.com/2010/09/28/a-php-framework-for-hosting-your-javascript-applications-on-cloudfront/</link>
		<comments>http://awsninja.wordpress.com/2010/09/28/a-php-framework-for-hosting-your-javascript-applications-on-cloudfront/#comments</comments>
		<pubDate>Tue, 28 Sep 2010 04:16:12 +0000</pubDate>
		<dc:creator>Jay Muntz</dc:creator>
				<category><![CDATA[Libraries]]></category>
		<category><![CDATA[aws]]></category>
		<category><![CDATA[cdn]]></category>
		<category><![CDATA[cloudfront]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[php]]></category>

		<guid isPermaLink="false">http://awsninja.com/?p=280</guid>
		<description><![CDATA[Today I’m going to try to answer the question: JavaScript &#8211; How do you organize this mess?!? The StackOverflow user who posted this question was primarily concerned with organizing JavaScript classes and namespaces &#8211; but many of the replies were about managing and serving script files. Both topics are extremely important, and in this post [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=awsninja.wordpress.com&amp;blog=14043929&amp;post=280&amp;subd=awsninja&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>Today I’m going to try to answer the question: <a href="http://stackoverflow.com/questions/247209/javascript-how-do-you-organize-this-mess">JavaScript &#8211; How do you organize this mess?!?</a> The StackOverflow user who posted this question was primarily concerned with organizing JavaScript classes and namespaces &#8211; but many of the replies were about managing and serving script files.  Both topics are extremely important, and in this post I’ll be focusing on the latter.</p>
<p>I’ve <a href="http://awsninja.com/2010/06/11/a-php-framework-for-hosting-images-on-amazon-cloudfront/">previously written</a> about managing your site’s images on CloudFront, and this article will basically tackle the same topic for JavaScript.  Someday, I will post a third post in this series that deals with CSS.</p>
<h3>The Headaches</h3>
<div class="wp-caption alignright" style="width: 189px"><a href="http://www.flickr.com/photos/migrainechick/526103563/"><img title="My headache has JavaScript written all over it." src="http://assets.docmonk.com/ninja/chronicpain.jpg" alt="My headache has JavaScript written all over it." width="179" height="240" /></a><p class="wp-caption-text">My headache has JavaScript written all over it.</p></div>
<p>The headaches that your site’s JavaScript cause can be grouped into two categories, <strong>Maintainability Considerations</strong> and <strong>Performance Considerations</strong>.  Here are the main points:</p>
<p><strong>JavaScript Maintainability Considerations:</strong></p>
<ul>
<li><strong>It’s a lot of code doing a lot of complex stuff.</strong> In addition to the functionality you write yourself, you’re including more third-party libraries and plug-ins than ever before.</li>
<li><strong>You need to manage dependencies.</strong> When you include a component on the page, you need to make sure you include the other required components.</li>
<li><strong>The order that you include the code in your page matters.</strong> A dependency needs to appear before the component that depends on it.</li>
<li><strong>The development environment is completely different from production. </strong>
<ul>
<li>In <strong>development</strong>, your primary concern is finding where the error is, so you want your application to be broken down logically into files and you want the code to be readable for a human being like yourself.</li>
<li>On <strong>production</strong>, you want peak performance which means combining the JavaScript into one file and minimizing the code.</li>
</ul>
</li>
</ul>
<p><strong>JavaScript Performance Considerations:</strong></p>
<ul>
<li><strong>Minimize</strong> your code.</li>
<li><strong>Compress</strong> your files.</li>
<li><strong>Combine</strong> your files.</li>
<li>Get the browser to <strong>cache</strong> your files (but also make sure the browser isn’t using an outdated version).</li>
</ul>
<p><em>These performance considerations (and many, many others) come mainly from the work of <a href="http://stevesouders.com/">Steve Souders</a>. If you’re not familiar with his work in front-end optimizations, go read up.  We&#8217;ll be here when you get back.</em></p>
<p>The CloudFrontJavaScript package is my strategy for implementing the best practices in all of these areas.  The idea is to keep track of all of your JavaScript files in a couple of database tables, then use a class called the CloudFrontJavaScriptService to manage the resources on CloudFront and generate the SCRIPT tags with the proper source URLs to be inserted into your HTML page.</p>
<p>Before I describe the technical details and how to install and use the package, I am going to highlight a couple of key features of CloudFrontJavaScriptService:</p>
<h3>Maintenance Mode</h3>
<p>To facilitate website development, the CloudFrontJavaScriptService can be run in Maintenance Mode.  In Maintenance Mode, several things are different:</p>
<div class="wp-caption alignright" style="width: 210px"><a href="http://www.flickr.com/photos/ultimateslug/109566859/"><img title="Next time, try flushing your cache." src="http://assets.docmonk.com/ninja/caching.jpg" alt="Next time, try flushing your cache." width="200" height="317" /></a><p class="wp-caption-text">Next time, try flushing your cache.</p></div>
<ul>
<li>JavaScripts are sent to the browser as the original source files.   They are not minimized, compressed or combined.  This is so that your browser development tools (e.g. Firebug) can work effectively.</li>
<li>A random string (called “cacheBust”) is appended to the script paths on each page refresh.  This is done just to make sure that your web browser does not cache any of your JavaScript, avoiding that frustrating development scenario that goes like this:
<ul>
<li>Adjust your code</li>
<li>Refresh</li>
<li>See no change</li>
<li>Make Another Change</li>
<li>Refresh</li>
<li>Still no change</li>
<li>Make Still Another Change</li>
<li>Refresh</li>
<li>“What’s wrong with me!?!”</li>
<li>Make a change that is absolutely guaranteed to completely break the program.</li>
<li>Refresh</li>
<li>&amp;%^#(%$)!!!</li>
</ul>
</li>
<li>The script files are served from your web server, not CloudFront.</li>
</ul>
<p>By providing Maintenance Mode, the CloudFrontJavaScriptService accommodates the needs of the development environment without adding to the work required to maintain the code and without making any compromises in performance on the production environment.</p>
<h3>Compression</h3>
<p>One of the major downsides of Amazon CloudFront compared to some other CDNs is that CloudFront will not automatically compress (i.e. gzip) your content for web browsers that support it.  Amazon is almost certainly developing this feature and could announce its availability at any time.   In the meantime, the CloudFrontJavaScriptService handles this for you.  The service does this by storing both the compressed and uncompressed versions on CloudFront.  When a page request comes in, it examines the headers for the HTTP_ACCEPT_ENCODING header to determine if gzip is supported.  It then returns the URL to either the compressed or uncompressed  version of the file.</p>
<h3>Versioning</h3>
<p>Both CloudFront edge locations and your users’ web browsers are going to cache your content.  This is what you want them to do in order to get the best possible performance from your website.  However, this creates an issue when your content changes because you can’t fully control when your users will see the updated content.</p>
<p>The CloudFrontJavaScript service handles this by maintaining a version number.  The version number is part of the URL of the JavaScript file.  The number is reset whenever a new version of your site is deployed.    This causes the URL of the JavaScript files to change.  Because a new URL indicates a completely new file, both CloudFront and the user’s browser are guaranteed to request the new version.  Consider the URL of “script 25” in CloudFrontJavaScriptService:</p>
<p><pre class="brush: plain;">
http://d5zgyj1bk6lvu.cloudfront.net/javascript/1285535650_25.gz.js
</pre></p>
<p>After deploying a new version, the URL of this resource might look like this</p>
<p><pre class="brush: plain;">
http://d5zgyj1bk6lvu.cloudfront.net/javascript/1285362461_25.gz.js
</pre></p>
<p>The large number is the version number and is also a Unix timestamp.  That value matches the time that the version was deployed, ensuring that caching does not prevent users from getting the most recent version of your JavaScript.</p>
<h3>The Database</h3>
<p>To help you get a sense of the underlying architecture of the service, here are the two database tables that keep track of the details:</p>
<p><img src="http://assets.docmonk.com/ninja/cloudfrontjavascript_tables.png" alt="CloudFrontImageService Database Tables" /></p>
<p>The <strong>tbl_javaScriptScript</strong> table contains the following fields:</p>
<ul>
<li><strong>id</strong> &#8211; The auto-incrementing primary key.</li>
<li><strong>lookupId</strong> &#8211; This is a unique number that you will assign to the script file and will be the number you use to indicate which script you’re requesting from the CloudFrontScriptService.  I prefer to increment the numbers by five and to make the lookupId be the same as the sortOrder.</li>
<li><strong>fileName</strong> &#8211; This is the file name of the script.  Each script will appear in your filesystem in two versions (one normal and one minimized).  In this field, the filename is displayed in the form to be used for the minimized version of the file.  The minimized form will be like this “yahoo_yahoo.js.”  In your development environment (where you will actually work the files) this file would appear in “yahoo” subdirectory and would appear like this “yahoo/yahoo.js”  The CloudFrontScriptService has a method in it for translating one form to the other.</li>
<li><strong>dependencies</strong> &#8211; This field contains a comma-delimited string of lookupIds which represent the dependencies of the script.  A typical value in this field would be “25,35,40” where the scripts with lookup ids of “25”, “35”, and “40” are dependencies of the current script.</li>
<li><strong>sortOrder</strong> &#8211; This field specifies the sort order the scripts.  When the CloudFrontScriptService determines which scripts (including dependencies) it needs to include in your webpage, it will order them in the order specified here.  Typically, I like to start out with the lookupIds and sortOrder as the same value.  For example, the file with a lookupId of 10 will start out with a sortOrder of 10.   If I find out later that things need to be reordered, I can just adjust the sortOrder without the much bigger hassle of rearranging the lookupIds.</li>
</ul>
<p>The <strong>tbl_javaScriptCDN</strong> table contains the following fields:</p>
<ul>
<li><strong>id</strong> &#8211; An auto-incrementing id.  Not the primary key and not currently used.  (The primary key is a composite of scriptIds and gzip.)</li>
<li><strong>scriptIds</strong> &#8211; This is a comma-separated list of the scriptIds that were requested.  If you request the script with lookupId 25, then the value in this field will be “25”.  If you request scripts 25 and 35 then the value will be “25,35”.  The dependencies of scripts 25 and 35 won’t be included here even though they would be sent to the browser.</li>
<li><strong>version</strong> &#8211; This indicates the version number of the scripts that are currently being served from Cloudfront.  If the version number in this row is out of date, the CloudfrontJavaScript service knows to rebuild the script package, upload it to CloudFront and update the version number in this table.</li>
<li><strong>gzip</strong> &#8211; This indicates whether the combined JavaScript file is gzipped.</li>
</ul>
<h3>Installation and Usage</h3>
<p>Download the <a href="http://github.com/awsninja/awsninja_cloudfrontjavascript">package</a> from GitHub and put it on your webserver.  You will also need the <a href="http://github.com/awsninja/awsninja_core">awsninja_core</a> package.</p>
<p><strong>YUI Components</strong><br />
To demonstrate how the CloudFrontJavaScriptService works with a large JavaScript project, the javascript directory in the package contains the JavaScript files from  the YUI Library.   If you want to test out the ability of the service to serve files to a web browser in Maintenance Mode, you will need to move the files to a location within your web root and change the CDN_JAVASCRIPT_SOURCE_PATH in config.php to match the new path.</p>
<p><strong>Steps to Get Set Up</strong></p>
<ol>
<li>Update the settings on config.samp.php and rename the file to config.php.</li>
<li>Run the sql in the ninja_cloudfrontimages.sql against your database to create the needed tables.</li>
<li>In the examples directory, run the yuiStoke.php script from the command line  (e.g.  type “php yuiStoke.php”) to populate the tbl_javaScriptScript table with the YUI script file information.  The yuiStoke.php script uses a <a href="http://old.nabble.com/Re:-yui-dependency-tree-for-use-in-php.-p22585940.html">dependency tree</a> created by jassem.shahrani to set up the YUI files.</li>
<li>Run the deploymentTest.php script from the command line.  This will minimize and copy the YUI JavaScript files to the js-minified directory.  It will also delete the version file, which will cause the CloudFrontJavaScriptService to create a new version number at the first opportunity.</li>
<li>Run the serviceTest.php script to see a demonstration of how the CloudFrontJavaScriptService generates HTML Script Tags.</li>
</ol>
<p>At step five, you know that you’ve got it set up correctly if you see output similar to this:<br />
<img src="http://assets.docmonk.com/ninja/coudfrontjavascript_output.png" alt="serviceTest.php output" /></p>
<p>The code in the example requests script 25, which is the YUI datasource.js script.  This script is dependent on event.js (script 10) which itself is dependent on yahoo.js (script 5).  In all of the examples, we use the getScriptHTML() method with the first argument 25.  See the serviceTest.php source for more details.</p>
<p><strong>The following uses are demonstrated:</strong></p>
<ul>
<li>Under <strong>“Here’s the uncombined script”</strong> you see that the datasource.js script and it’s dependencies are individually included.  The scripts are ordered according to the sort_order field in the database and a random “cacheBust” string is included at the end of the URLs.  This is ideal for the development environment, because your code will be properly broken out into files that are not minimized.  Your browser development tool (e.g. Firebug)  will have no problem directing you to your errors by file and line number.</li>
<li>Under <strong>“Here&#8217;s the combined uncompressed script:”</strong> you see that there  is a single file and it is being served from a CloudFront domain name.  The three JavaScript files are combined into one and minimized.  In this example, they are not compressed because this is an example of how the scripts would be served to browsers that do not support compression.</li>
<li>Under <strong>“Here&#8217;s the combined compressed script:”</strong> you see something very similar to the previous one.  The difference is that before the “.js” extension there is an extra “.gz” in the path.  This is the path to the combined, minimized and compressed file.</li>
<li>Under <strong>“Here&#8217;s how to let the web browser determine whether to compress or not:”</strong> the script echos out the most common way to use the service to insert script into HTML.  Basically, you just give it the script number you want and let the service figure out the best way to serve the script.</li>
<li>Finally, under <strong>“In the current context, that code produces this:”</strong> the script gives an example of what the script in the previous example outputs in the current context.  The context of this output is the command line, so it sends the combined and minimized (but not compressed) script. The reason it does not send the compressed script is that in the command line context, there is no browser HTTP_ACCEPT_ENCODING header.</li>
</ul>
<h3>How I Organize and Add New Scripts to my JavaScript Project</h3>
<p>There is one more thing that I would like to share about how I like to manage my scripts.  My general strategy is to separate my third party scripts (third-party libraries and plug-ins) from the scripts that I write specifically for the project that I’m working on.  I do this by including a large gap of lookupIds between the third-party components and my own components.  Here’s an example from one of my projects where I’m using jQuery and a bunch of jQuery plug-ins:<br />
<img src="http://assets.docmonk.com/ninja/projectfiles2.png" alt="Project JavaScript Files" /><br />
Note that the third party code (jquery and jquery plug-ins) start at lookupId 1000 and my code (the “marketplace” files) start at lookupId 1500.  The gap between 1000 and 1500 provides plenty of room for me to add more third party components as needed.  The third-party components will always appear above my components, which is good because the third-party scripts are the dependencies.</p>
<p>When I need to add a new JavaScript file (either third-party or my own component), I add the file to the filesystem, then add the row directly to the database table.</p>
<p><strong>Conclusion:</strong><br />
I hope that you find this method of managing your JavaScript useful, and I hope that I was successful in clearly explaining how it works.  As always, I’d very much like to know what you think.  If you read this far, then for gosh sakes leave a comment and let me know!  It makes me happy.</p>
<br />  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/awsninja.wordpress.com/280/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/awsninja.wordpress.com/280/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/awsninja.wordpress.com/280/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/awsninja.wordpress.com/280/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/awsninja.wordpress.com/280/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/awsninja.wordpress.com/280/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/awsninja.wordpress.com/280/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/awsninja.wordpress.com/280/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/awsninja.wordpress.com/280/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/awsninja.wordpress.com/280/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/awsninja.wordpress.com/280/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/awsninja.wordpress.com/280/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/awsninja.wordpress.com/280/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/awsninja.wordpress.com/280/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=awsninja.wordpress.com&amp;blog=14043929&amp;post=280&amp;subd=awsninja&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://awsninja.wordpress.com/2010/09/28/a-php-framework-for-hosting-your-javascript-applications-on-cloudfront/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
	
		<media:content url="http://0.gravatar.com/avatar/0811f04dc6f8ec38e7ae3d6627327694?s=96&#38;d=identicon&#38;r=PG" medium="image">
			<media:title type="html">awsninja</media:title>
		</media:content>

		<media:content url="http://assets.docmonk.com/ninja/chronicpain.jpg" medium="image">
			<media:title type="html">My headache has JavaScript written all over it.</media:title>
		</media:content>

		<media:content url="http://assets.docmonk.com/ninja/caching.jpg" medium="image">
			<media:title type="html">Next time, try flushing your cache.</media:title>
		</media:content>

		<media:content url="http://assets.docmonk.com/ninja/cloudfrontjavascript_tables.png" medium="image">
			<media:title type="html">CloudFrontImageService Database Tables</media:title>
		</media:content>

		<media:content url="http://assets.docmonk.com/ninja/coudfrontjavascript_output.png" medium="image">
			<media:title type="html">serviceTest.php output</media:title>
		</media:content>

		<media:content url="http://assets.docmonk.com/ninja/projectfiles2.png" medium="image">
			<media:title type="html">Project JavaScript Files</media:title>
		</media:content>
	</item>
		<item>
		<title>A Simple Framework for Managing your Images, Instances, Volumes and Snapshots on EC2</title>
		<link>http://awsninja.wordpress.com/2010/09/07/a-simple-framework-for-managing-your-images-instances-volumes-and-snapshots-on-ec2/</link>
		<comments>http://awsninja.wordpress.com/2010/09/07/a-simple-framework-for-managing-your-images-instances-volumes-and-snapshots-on-ec2/#comments</comments>
		<pubDate>Wed, 08 Sep 2010 00:47:03 +0000</pubDate>
		<dc:creator>Jay Muntz</dc:creator>
				<category><![CDATA[Libraries]]></category>

		<guid isPermaLink="false">http://awsninja.com/?p=246</guid>
		<description><![CDATA[When I first moved beyond simply playing around with EC2 and made the decision that I was actually going to build and deploy a web application on it, I quickly discovered that it’s next to impossible to keep everything straight. The reason for this is the heart of the difference between using actual computer hardware [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=awsninja.wordpress.com&amp;blog=14043929&amp;post=246&amp;subd=awsninja&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>When I first moved beyond simply playing around with EC2 and made the decision that I was actually going to build and deploy a web application on it, I quickly discovered that it’s next to impossible to keep everything straight.</p>
<p>The reason for this is the heart of the difference between using actual computer hardware and using virtualization. Virtualization is more flexible because it has more parts.  That&#8217;s also the reason why it is more complicated.</p>
<p>With traditional hosting, you (or your hosting provider) build a server to a specification.  This involves capital investment because you have to purchase the hardware and locate it somewhere.  Once you’ve bought the hardware, you are stuck with it.  Changes to your configuration require new capital investment and may result in unwanted legacy components in your possession.  This costs time and money.</p>
<p>EC2 and other cloud-hosting services exist mainly for the purpose of eliminating these burdens.  But as is often the case, elimination of one problem creates a new one.  Since the problems associated with virtualization are newer, developers are not as adept at dealing with them yet.  We’re still learning.</p>
<p>The biggest problem is essentially this: The power to rearrange your hardware in real-time means that you now have much more to keep track of.  With traditional hosting, you were constrained by your inability to quickly add disk storage, RAM, CPU, or whatever.  You also may have been constrained in terms of your ability to quickly add new software.</p>
<p>Those constraints are now gone:</p>
<ul>
<li><strong>Need more hard disk space?</strong> Add it in five minutes.</li>
<li><strong>Need a backup?</strong> Take a snapshot.</li>
<li><strong>Running low on RAM?</strong> Spin up a high-memory instance and you’re back in business.</li>
</ul>
<p>But virtualization has created new questions, which you may not have answers for:</p>
<ul>
<li><strong>How will you keep track of that new volume you created?</strong></li>
<li><strong>How can you find that snapshot you created six weeks ago?</strong></li>
<li><strong>What’s the Instance-Id of that high-memory instance you started yesterday?</strong></li>
</ul>
<p><strong>Here is a partial-list of EC2 objects you will need to track:</strong></p>
<ul>
<li>Your <strong>Amazon Machine Images</strong>.  Each different type of server you use in your application will have an AMI.  Additionally, you may have multiple versions of those AMI’s that you need to keep track of. More about this below.</li>
<li>Your <strong>Instances</strong>.  Traditionally you will have a Development, Staging and Production environment, each consisting of one or more instances.  For instance you may have web and database servers in each environment.  That’s at least six instances right there.</li>
<li>Your <strong>Elastic Block Store Volumes</strong>.  EBS volumes provide wonderful flexibility for managing your data stores.  You may have volumes for your database, application root or for storing files such as images or PDF files.  Wonderful, if you can keep track of them.</li>
<li>Your <strong>EBS Snapshots</strong>.  The most common use of EBS Snapshots is to make backups of your EBS volumes.  You might have hundreds of these.  You may also need a plan for purging them when the time is right.</li>
</ul>
<h3>How I Organized my EC2 Resources</h3>
<p>For my applications, I’ve settled on using SimpleDB for the purpose of keeping track of all of my EC2 resources.  This has several advantages:</p>
<ul>
<li><strong>It’s free.</strong> The free tier of SimpleDB is more than adequate for this purpose.</li>
<li><strong>It’s always on.</strong> This is very important.  See below.</li>
<li><strong>It’s Amazon.</strong> If you’re familiar with other AWS services (ahem &#8211; why else would you be reading this?), you have a leg-up in learning SimpleDb.</li>
</ul>
<p>The “Always On” characteristic of SimpleDb is extremely useful for our purposes here.  It would make sense to keep track of your EC2 resources in a relational database like MySQL, except for the fact that you probably want to run your MySQL database on EC2.  There’s a chicken and egg problem in here.  Suppose you want to start up your MySQL instance?  If the Instance ID  and other information is sitting in your MySQL database, which isn’t running yet. . . . you see what I mean.</p>
<p>By keeping your EC2 resource information on SimpleDB, you can use it even when none of the resources of your application are running by running a SimpleDB client like <a href="http://awsninja.com/2010/07/08/simpledbadmin-a-phpmyadmin-like-interface-for-amazon-simpledb/">SimpleDBAdmin</a> or <a href="http://www.sdbexplorer.com/">SimpleDB Explorer</a>.</p>
<p>Another advantage of using SimpleDB to store information about your EC2 resources is that it makes it possible for your EC2 instances to identify themselves at boot time by running a boot script that queries SimpleDb.  This is an elegant and seamless way to start a new instance.  You can include a boot script which checks for its own EC2 Instance ID, looks itself  up in SimpleDb and then automatically configures all of the EBS Volumes and other resources that belong to it. The <em>EC2ResourceManagement</em> package includes an example of this.</p>
<h3>The Code</h3>
<p>The <em>EC2ResourceManagement</em> library consists of a few different services and two types of classes.  The <em>EC2Service</em> and <em>EC2Classes</em> interact with the Amazon EC2 API.  The <em>EC2Classes</em> represent objects like instances, volumes, AMIs and snapshots that exist in your EC2 account.  The <em>EC2ManagementService</em> and the <em>SimpleDBService</em> interact with SimpleDB.  The classes used by the <em>EC2ManagementService</em> (found in the <em>classes</em> directory of the library) represent records that are stored in SimpleDB and which are designed to represent EC2 objects and to keep track of how they are intended to be used.</p>
<h3>Getting Started</h3>
<p>To get started, download and unpack the <a href="http://github.com/awsninja/awsninja_ec2resourcemanagement">awsninja_ec2resourcemanagement</a> and <a href="http://github.com/awsninja/awsninja_core">awsninja_core</a> packages.  You’ll also need an AWS account with EC2 and SimpleDB enabled.</p>
<p>Add your credentials to the <em>config.samp.php</em> file and rename it to <em>config.php</em>.  Then try running a few of the scripts found in the examples directory (these are designed to be run from the command line).</p>
<p><strong>The following example scripts are included:</strong></p>
<ul>
<li><strong>buildEC2ResourceInventory.php</strong> &#8211; This is a good script to start with.  This script will examine your EC2 account and find the Images, Instances and Volumes that belong to you and will create records in SimpleDB to help you keep track of them.  To do this, it will ask you a few questions about what all of the different objects are for.  You’ll have better luck if you know the answers.</li>
<li><strong>attachVolume.php</strong> &#8211; This is a simple script that shows how to attach a volume to an instance.  You give it the Volume object, and the script will determine which instance to attach to and what device to use by looking up the details in SimpleDB.</li>
<li><strong>backups.php</strong> &#8211; This script will make snapshots of your Volumes and create Snapshot records in SimpleDB.  Perfect for backing up your data &#8211; and keeping track of what your snapshots are for.</li>
<li><strong>cloneVolume.php </strong>- This script will clone a volume and assign it to whatever instance you want.  For instance, if you wanted to clone the MySQL database volume on your development server  so that you could use an exact copy on your staging server, this script will do that.  Clones are accomplished by creating a snapshot, then creating a volume from that snapshot.  A record for your new Volume is created in SimpleDB and the new Volume becomes one of your managed EC2 resources.</li>
<li><strong>launchInstance.php </strong>- This will launch an instance from one of your images.  You only need to specify an Image and a role.  The role is something like “development,” “staging,” or “production.”</li>
<li><strong>listInstances.php</strong> &#8211; This simply lists your managed instances.  Simple enough.</li>
</ul>
<p>If you’ve got everything configured, you can run the buildEC2ResourceInventory.php to get your environment to create SimpleDB records for your Images, Instances and Volumes.  The script is intended to be run from the command line and will prompt you to answer some questions about your Images, Instances and Volumes.</p>
<p>When you run the buildEC2ResourceInventory.php, here are the questions you will get:</p>
<div class="wp-caption alignnone" style="width: 483px"><img src="http://docmonkcdn.s3.amazonaws.com/ninja/ec2resources_imageName.png" alt="What is the name of the image?" width="473" height="29" /><p class="wp-caption-text">The answer given here is &quot;WebServer.&quot;  The idea is to describe the purpose of the AMI that is being asked about.  Your &quot; WebServer&quot; could be launched into instances in multiple roles (i.e. Development, Staging, Production) - but presumably all of these instances would use the WebServer image.</p></div>
<p>Next, it asks about your Instances:</p>
<div class="wp-caption alignnone" style="width: 589px"><img src="http://docmonkcdn.s3.amazonaws.com/ninja/ec2resources_instanceRole.png" alt="What is the instance role?" width="579" height="76" /><p class="wp-caption-text">This question (asked regarding two instances in the image above) inquires about he role of individual Instances you are running. I have answered that one is the Development and one is Staging.</p></div>
<p>Then, it asks about your EBS Volumes:</p>
<div class="wp-caption alignnone" style="width: 634px"><img src="http://docmonkcdn.s3.amazonaws.com/ninja/ec2resources_volumeroleandpath.png" alt="What is the Volume Role and Path" width="624" height="196" /><p class="wp-caption-text">Next, the script inquires about our volumes. It asks the role of the Volume (&quot;application&quot; and &quot;database&quot; are the answers because these volumes hold the application root and the database files), then it asks what path on the Instance that these volumes should be mounted to. Note that the script does not ask what instance the volume is attached to or what the device address is. It gets that information from the EC2 api and will automatically configure those values.</p></div>
<p>Once the script is finished, you can look in your SimpleDB domains to see how your EC2 resources are mapped to records in SimpleDB</p>
<h3>Final Note</h3>
<p>The scripts in this package are not intended to be a turn-key solution for getting your stuff organized.   You will need to adjust and build on them for your own projects.  The main idea here is to provide you with a foundation on which you can start keeping track of your objects and automating operations on them to save time, reduce errors and increase the overall quality of the service you provide.</p>
<br />  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/awsninja.wordpress.com/246/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/awsninja.wordpress.com/246/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/awsninja.wordpress.com/246/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/awsninja.wordpress.com/246/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/awsninja.wordpress.com/246/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/awsninja.wordpress.com/246/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/awsninja.wordpress.com/246/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/awsninja.wordpress.com/246/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/awsninja.wordpress.com/246/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/awsninja.wordpress.com/246/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/awsninja.wordpress.com/246/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/awsninja.wordpress.com/246/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/awsninja.wordpress.com/246/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/awsninja.wordpress.com/246/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=awsninja.wordpress.com&amp;blog=14043929&amp;post=246&amp;subd=awsninja&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://awsninja.wordpress.com/2010/09/07/a-simple-framework-for-managing-your-images-instances-volumes-and-snapshots-on-ec2/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
	
		<media:content url="http://0.gravatar.com/avatar/0811f04dc6f8ec38e7ae3d6627327694?s=96&#38;d=identicon&#38;r=PG" medium="image">
			<media:title type="html">awsninja</media:title>
		</media:content>

		<media:content url="http://docmonkcdn.s3.amazonaws.com/ninja/ec2resources_imageName.png" medium="image">
			<media:title type="html">What is the name of the image?</media:title>
		</media:content>

		<media:content url="http://docmonkcdn.s3.amazonaws.com/ninja/ec2resources_instanceRole.png" medium="image">
			<media:title type="html">What is the instance role?</media:title>
		</media:content>

		<media:content url="http://docmonkcdn.s3.amazonaws.com/ninja/ec2resources_volumeroleandpath.png" medium="image">
			<media:title type="html">What is the Volume Role and Path</media:title>
		</media:content>
	</item>
		<item>
		<title>CloudWatch as an Ad-Hoc Debugging Tool</title>
		<link>http://awsninja.wordpress.com/2010/07/20/cloudwatch-as-an-ad-hoc-debugging-tool/</link>
		<comments>http://awsninja.wordpress.com/2010/07/20/cloudwatch-as-an-ad-hoc-debugging-tool/#comments</comments>
		<pubDate>Wed, 21 Jul 2010 03:12:50 +0000</pubDate>
		<dc:creator>Jay Muntz</dc:creator>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[aws]]></category>
		<category><![CDATA[cloudwatch]]></category>
		<category><![CDATA[mysql]]></category>
		<category><![CDATA[web development]]></category>

		<guid isPermaLink="false">http://awsninja.com/?p=199</guid>
		<description><![CDATA[This post is not going to be long-winded dissertation on how CloudWatch can help you with debugging your applications.  It is just a quick description of a recent experience I had. I recently had an issue on DocMonk when the script that sends out the emails to the PDF recipients was crashing.  After checking it [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=awsninja.wordpress.com&amp;blog=14043929&amp;post=199&amp;subd=awsninja&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>This post is not going to be long-winded dissertation on how CloudWatch can help you with debugging your applications.  It is just a quick description of a recent experience I had.</p>
<p>I recently had an issue on <a href="http://www.docmonk.com" target="_blank">DocMonk</a> when the script that sends out the emails to the PDF recipients was crashing.  After checking it out, I discovered that the crashes coincided with the startup of another script which handles backend aggregating for the reporting pages.  I was initially skeptical that this other script could really be causing such a problem.  I decided to try to see what else was going on on the server.  I started CloudWatch Monitoring on the server and went away for a few hours.</p>
<p>When I came back, this is what the CPU Utilization Chart looked like:<br />
<span id="more-199"></span><br />
<img class="aligncenter" title="Some Spiky CPU" src="http://assets.docmonk.com/ninja/cpuspikes.png" alt="Some Spiky CPU" width="785" height="463" /></p>
<p>Those spikes were happening every 15 minutes, which happens to be the frequency of the reportAggregator.php script &#8211; the reporting script I originally suspected.</p>
<p>Armed with new certainty that I was on the right track, I set about diagnosing the problem.  One SQL query was running very slowly, but only under certain conditions.  It looked like this:</p>
<p><pre class="brush: sql;">
SELECT * FROM tbl_pdfRecipient WHERE pdfOrigialId=:pdfOriginalId ORDER BY ID ASC LIMIT 0,100
</pre></p>
<p>Assuming you&#8217;ve got intelligent indexes on the table, this is a very fast query, but change the limit clause to &#8220;LIMIT 100000, 100&#8243; an it will be much, much slower.  If you&#8217;re going to be processing a lot pages of data, this is a very inefficient way to do it.</p>
<p>The solution was to query the minimum and maximum id values of the rows I&#8217;m querying for, then step through the pages by comparing the id values to ranges within the max and min range of the entire set.  The following queries demonstrate:</p>
<p><pre class="brush: sql;">
SELECT min(id), max(id) FROM tbl_pdfRecipient WHERE pdfOriginalId=:pdfOriginalId
/* Assume :min=1 and :max=1534.  We will grab these pages in groups of 100*/
/* Here's the efficient way to get all of the records */
SELECT * FROM tbl_pdfRecipient WHERE pdfOriginalId=:pdfOriginalId and id BETWEEN 1 AND 100;
SELECT * FROM tbl_pdfRecipient WHERE pdfOriginalId=:pdfOriginalId and id BETWEEN 101 AND 200;
SELECT * FROM tbl_pdfRecipient WHERE pdfOriginalId=:pdfOriginalId and id BETWEEN 201 AND 300;
SELECT * FROM tbl_pdfRecipient WHERE pdfOriginalId=:pdfOriginalId and id BETWEEN 301 AND 400;
SELECT * FROM tbl_pdfRecipient WHERE pdfOriginalId=:pdfOriginalId and id BETWEEN 401 AND 500;
SELECT * FROM tbl_pdfRecipient WHERE pdfOriginalId=:pdfOriginalId and id BETWEEN 501 AND 600;
SELECT * FROM tbl_pdfRecipient WHERE pdfOriginalId=:pdfOriginalId and id BETWEEN 601 AND 700;
/* and so fourth. . .*/
</pre></p>
<p>With this method, the 10,000th page would be retrieved just as quickly as the first.  Here&#8217;s the PHP code from the reportAggregator application.  Note that the SQL queries are not shown because they are abstracted away to other parts of the application.  This code is intended to show how to handle the looping and the <em>startId</em> and <em>endId</em> for the queries.</p>
<p><pre class="brush: php;">
$pageSize = 100;  //whatever your page size will be

//Initialize the start and end id values
$minId = PDFTO_domain_PDFRecipient::findMinByPDFOId($pdfo-&gt;getId());
$maxId = PDFTO_domain_PDFRecipient::findMaxByPDFOId($pdfo-&gt;getId());

//Initialize the first $startId and $endId
$startId = $minId;
$endId = $startId+$pageSize-1;

//Get the first page of results
$pdfRecpts = PDFTO_domain_PDFRecipient::findByPDFOIdAndIdRange($pdfo-&gt;getId(), $startId, $endId);
while($startId &lt;= $maxId)  //See how we will know when to stop it?
{
	foreach($pdfRecpts as $pdfRecpt) //Loop though the members of the page
	{
		/* DO SOMETHING WITH THE $pdfRecpt */
	}
	//Done with page, now set $startId and $endId for the next page
	$startId = $endId+1;
	$endId = $startId+$pageSize-1;

	//Run the next query - (this is at the bottom of the while loop because the first instance happens before we entered it)
	$pdfRecpts = PDFTO_domain_PDFRecipient::findByPDFOIdAndIdRange($pdfo-&gt;getId(), $startId, $endId);
}
//All done!
</pre></p>
<p>A few hours later after deploying this change, here what my CPU Utilization Chart looked like:<br />
<img class="aligncenter" title="All Fixed" src="http://assets.docmonk.com/ninja/cloudwatch_allfixed.png" alt="All Fixed" width="794" height="488" /></p>
<p>Much better, I think.</p>
<br />  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/awsninja.wordpress.com/199/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/awsninja.wordpress.com/199/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/awsninja.wordpress.com/199/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/awsninja.wordpress.com/199/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/awsninja.wordpress.com/199/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/awsninja.wordpress.com/199/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/awsninja.wordpress.com/199/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/awsninja.wordpress.com/199/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/awsninja.wordpress.com/199/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/awsninja.wordpress.com/199/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/awsninja.wordpress.com/199/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/awsninja.wordpress.com/199/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/awsninja.wordpress.com/199/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/awsninja.wordpress.com/199/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=awsninja.wordpress.com&amp;blog=14043929&amp;post=199&amp;subd=awsninja&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://awsninja.wordpress.com/2010/07/20/cloudwatch-as-an-ad-hoc-debugging-tool/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
	
		<media:content url="http://0.gravatar.com/avatar/0811f04dc6f8ec38e7ae3d6627327694?s=96&#38;d=identicon&#38;r=PG" medium="image">
			<media:title type="html">awsninja</media:title>
		</media:content>

		<media:content url="http://assets.docmonk.com/ninja/cpuspikes.png" medium="image">
			<media:title type="html">Some Spiky CPU</media:title>
		</media:content>

		<media:content url="http://assets.docmonk.com/ninja/cloudwatch_allfixed.png" medium="image">
			<media:title type="html">All Fixed</media:title>
		</media:content>
	</item>
		<item>
		<title>SimpleDBAdmin &#8211; A &#8220;PHPMyAdmin-like&#8221; Interface for Amazon SimpleDB</title>
		<link>http://awsninja.wordpress.com/2010/07/08/simpledbadmin-a-phpmyadmin-like-interface-for-amazon-simpledb/</link>
		<comments>http://awsninja.wordpress.com/2010/07/08/simpledbadmin-a-phpmyadmin-like-interface-for-amazon-simpledb/#comments</comments>
		<pubDate>Thu, 08 Jul 2010 13:15:47 +0000</pubDate>
		<dc:creator>Jay Muntz</dc:creator>
				<category><![CDATA[Developer Tools]]></category>
		<category><![CDATA[aws]]></category>
		<category><![CDATA[jquery]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[phpmyadmin]]></category>
		<category><![CDATA[simpledb]]></category>
		<category><![CDATA[web development]]></category>

		<guid isPermaLink="false">http://awsninja.com/?p=142</guid>
		<description><![CDATA[SimpleDBAdmin is an administration tool for Amazon SimpleDB.   If you don’t know what SimpleDB is here’s the first paragraph from Amazon’s SimpleDB site: Amazon SimpleDB is a highly available, scalable, and flexible non-relational data store that offloads the work of database administration. Developers simply store and query data items via web services requests, and Amazon SimpleDB [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=awsninja.wordpress.com&amp;blog=14043929&amp;post=142&amp;subd=awsninja&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<div>SimpleDBAdmin is an administration tool for Amazon SimpleDB.   If you don’t know what SimpleDB is here’s the first paragraph from <a href="http://aws.amazon.com/simpledb/">Amazon’s SimpleDB site</a>:</div>
<div>
<div>
<blockquote><p>Amazon SimpleDB is a highly available, scalable, and flexible non-relational data store that offloads the work of database administration. Developers simply store and query data items via web services requests, and Amazon SimpleDB does the rest.</p></blockquote>
<h3>Why SimpleDBAdmin is Needed</h3>
<div>I started building SimpleDBAdmin because I wanted a package to administer my SimpleDB data that met the following criteria:</div>
<ul>
<li><strong>Web-based </strong>- so it could be used by anybody anywhere</li>
<li><strong>Free</strong> &#8211; for obvious reaons</li>
<li><strong>Intuitive to use</strong></li>
</ul>
<p><span id="more-142"></span></p>
<div>There are a couple of web-based SimpleDB managers out there, but I found them to be incredibly difficult to use for actually manipulating data. They seem to be designed for the purpose of teaching basic SimpleDB concepts to programmers.  That’s a laudable goal but it’s not what I was looking for.  <a href="http://www.awszone.com">AWSZone</a> and Amazon’s own <a href="http://developer.amazonwebservices.com/connect/entry.jspa?externalID=1137&amp;categoryID=148">JavaScript Scratchpad</a> expose all of the intricacies of the SimpleDB API and require you to perform actions like “Invoke Request” or use “PutAttributes.”  Rather than deal with all that, I wanted a package that provides a higher level of abstraction and focuses mainly on letting the user view and manipulate data. SimpleDBAdmin is designed to meet that need.</div>
</div>
<h3>How it was Built</h3>
<div>
<div>Because I wanted to make it possible to use the application on any platform, the vast majority of the application is written in JavaScript and based on JQuery.  In addition to JQuery and JQuery UI, the following packages are also used:</div>
<div>
<ul>
<li>The AWS Query Signer from <a href="http://sowacs.appspot.com/AWS/Downloads/">SonaCWS</a></li>
<li>Chris Domigan’s <a href="http://www.trendskitchens.co.nz/jquery/contextmenu/">Context Menu</a> plug-in for JQuery</li>
<li>Carhartl’s <a href="http://plugins.jquery.com/project/cookie">Cookie plug-in</a> for JQuery</li>
<li>The <a href="http://www.ibm.com/developerworks/xml/library/x-xml2jsonphp/">xmltojson php library</a> published on IBM developerWorks.</li>
</ul>
</div>
</div>
<div>
<div>Also included is a PHP component called <strong>relay.php</strong> that handles relaying the API commands to SimpleDB.  This is needed because the JavaScript <a href="http://docstore.mik.ua/orelly/webprog/jscript/ch21_03.htm">Same Origin</a> policy prevents a JavaScript application from sending requests to a separate domain name.  The relay.php component receives the completed request from the SimpleDBAdmin JavaScript package, passes it on to SimpleDB, receives the XML response from SimpleDB, converts it to JSON and passes it back to the JavaScript package.</div>
<div><strong>Note:</strong> Your AWS Secret Key is not sent to the relay.php script.  Instead, the signed request is created in JavaScript and sent to relay.php.  This prevents your Secret Key from being exposed in the internet traffic between the browser and server.</div>
<h3>SimpleDBAdmin Features</h3>
<div>
<div>
<ul>
<li>Supports saving multiple sets of AWS account credentials, useful if you manage SimpleDB data on multiple accounts.</li>
<li>Secret keys are encrypted and saved as cookies protected by a simple password.  You can access your SimpleDB account from the same computer without having to enter the AWS Access and Secret keys every time.</li>
<li>An elegant interface for storing, modifying and removing multiple values in the same attribute name.  Read about <a href="http://docs.amazonwebservices.com/AmazonSimpleDB/2009-04-15/DeveloperGuide/">SimpleDB’s data model</a> if you’re unfamiliar with SimpleDB’s ability to hold multiple values with the same attribute name.</li>
<li>Results are presented in a table format and are pageable.</li>
<li>Easily add and remove SimpleDB Domains.</li>
</ul>
</div>
</div>
<h3>Limitations and To-dos</h3>
<div>
<div>
<ul>
<li><strong>No Sorting</strong> &#8211; Currently, Domain contents are returned in SimpleDB&#8217;s default order.  I believe that this is the same as the chronological order that the items were added, but not sure if this is aways true.</li>
<li><strong>No Searching</strong> &#8211; The ability to search the items has not been added yet.</li>
<li><strong>Large Data</strong> &#8211; Domains with a large number of attribute names or large attribute values will not display optimally in the web browser.</li>
<li><strong>No Counts</strong> &#8211; The interface does not indicate the number of items in each Domain.</li>
</ul>
</div>
</div>
<h3>Installation</h3>
<div>To install SimpleDBAdmin, download the package from <a href="http://github.com/awsninja/awsninja_simpledbadmin">GitHub</a> and place the contents somewhere on your server’s web root.  Then navigate to the index.html with your web browser.  When you get it set up, it looks like this:</div>
<div>
<div class="wp-caption aligncenter" style="width: 520px"><img title="Login Dialog" src="http://docmonkcdn.s3.amazonaws.com/ninja/simpledb_admin_installed.png" alt="Login Dialog" width="510" height="325" /><p class="wp-caption-text">The Login Dialog</p></div>
</div>
<div>If it doesn’t work, you may need to adjust some of the paths within the index.html, relay.php or SimpleDbAdmin.js.</div>
</div>
<h3>Usage</h3>
<div>Once you’ve got the package installed and have the dialog shown above displayed in your browser, you then must enter your SimpleDB credentials.  To do this, fill in the form shown above with the following values:</div>
<div>
<div>
<ul>
<li><strong>Name</strong> &#8211; This is just the name that you want to give your account.  If you manage multiple SimpleDB accounts, this will help you keep track of them.</li>
<li><strong>Access Key</strong> &#8211; This is your AWS Access Key Id, which can be found from the<a href="https://aws-portal.amazon.com/gp/aws/developer/account/index.html?ie=UTF8&amp;action=access-key"> AWS Credentials page</a> when you are logged into the website.</li>
<li><strong>Secret Key</strong> &#8211; This is the AWS Secret Key associated with the Access Key and also found on the AWS Credentials Page.</li>
<li><strong>Password</strong> &#8211; This is a password chosen by you.  When you return to SimpleDBAdmin from the same web browser, the accounts you created will be available to you if you can enter the password.  The password is used to encrypt your Secret Key, so that it is not stored in plaintext in your browser’s cookies.</li>
</ul>
</div>
</div>
<div>Once you’ve entered your credentials, you should see your SimpleDB Domains (if any) listed on the left side of the screen.  You can click any of those Domains to browse through the data.  You can right click for the option to Delete a Domain (you will be asked to confirm this action).  You can also add a new Domain by clicking the “Add Domain” button.</div>
<div>
<div class="wp-caption aligncenter" style="width: 526px"><img title="Browse Data" src="http://docmonkcdn.s3.amazonaws.com/ninja/simpledb_admin_browsing.png" alt="Browse Data" width="516" height="315" /><p class="wp-caption-text">Browse Data</p></div>
<p>You can edit an item row by clicking on it.  Doing so brings up the Edit Item dialog box:</p>
<div class="wp-caption aligncenter" style="width: 770px"><img title="Editing Data" src="http://docmonkcdn.s3.amazonaws.com/ninja/simpledb_admin_editing.png" alt="Editing Data" width="760" height="384" /><p class="wp-caption-text">Editing Data</p></div>
<p>Clicking the &#8220;Add Attribute Name&#8221; link will open a dialog that allows you to enter another name-value pair to the record.  When you are done making edits, click &#8220;Put&#8221; to save your changes.</p>
<h3>Online Sample</h3>
<p>You can give the application a try here:</p>
<p><a href="http://samples.awsninja.com/awsninja_simpledbadmin/">http://samples.awsninja.com/awsninja_simpledbadmin/</a></p>
<p>Please don&#8217;t use the sample application to manage live web applications or to view or manipulate any sensitive data.  The application is not configured to examine or log your data -but you should install your own copy of SimpleDBAdmin to be completely secure.</p>
<p>If you&#8217;d like to install a copy on your own server, <a href="http://github.com/awsninja/awsninja_simpledbadmin/downloads">download</a> it from GitHub.</p>
<p>I welcome questions, comments or suggestions.  Thanks for reading.</p>
</div>
</div>
<br />  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/awsninja.wordpress.com/142/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/awsninja.wordpress.com/142/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/awsninja.wordpress.com/142/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/awsninja.wordpress.com/142/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/awsninja.wordpress.com/142/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/awsninja.wordpress.com/142/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/awsninja.wordpress.com/142/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/awsninja.wordpress.com/142/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/awsninja.wordpress.com/142/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/awsninja.wordpress.com/142/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/awsninja.wordpress.com/142/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/awsninja.wordpress.com/142/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/awsninja.wordpress.com/142/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/awsninja.wordpress.com/142/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=awsninja.wordpress.com&amp;blog=14043929&amp;post=142&amp;subd=awsninja&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://awsninja.wordpress.com/2010/07/08/simpledbadmin-a-phpmyadmin-like-interface-for-amazon-simpledb/feed/</wfw:commentRss>
		<slash:comments>8</slash:comments>
	
		<media:content url="http://0.gravatar.com/avatar/0811f04dc6f8ec38e7ae3d6627327694?s=96&#38;d=identicon&#38;r=PG" medium="image">
			<media:title type="html">awsninja</media:title>
		</media:content>

		<media:content url="http://docmonkcdn.s3.amazonaws.com/ninja/simpledb_admin_installed.png" medium="image">
			<media:title type="html">Login Dialog</media:title>
		</media:content>

		<media:content url="http://docmonkcdn.s3.amazonaws.com/ninja/simpledb_admin_browsing.png" medium="image">
			<media:title type="html">Browse Data</media:title>
		</media:content>

		<media:content url="http://docmonkcdn.s3.amazonaws.com/ninja/simpledb_admin_editing.png" medium="image">
			<media:title type="html">Editing Data</media:title>
		</media:content>
	</item>
		<item>
		<title>A PHP Framework for Hosting Images on Amazon CloudFront</title>
		<link>http://awsninja.wordpress.com/2010/06/11/a-php-framework-for-hosting-images-on-amazon-cloudfront/</link>
		<comments>http://awsninja.wordpress.com/2010/06/11/a-php-framework-for-hosting-images-on-amazon-cloudfront/#comments</comments>
		<pubDate>Fri, 11 Jun 2010 20:32:13 +0000</pubDate>
		<dc:creator>Jay Muntz</dc:creator>
				<category><![CDATA[Libraries]]></category>
		<category><![CDATA[aws]]></category>
		<category><![CDATA[cdn]]></category>
		<category><![CDATA[cloudfront]]></category>
		<category><![CDATA[expires header]]></category>
		<category><![CDATA[image hosting]]></category>
		<category><![CDATA[image naming]]></category>
		<category><![CDATA[imagemagick]]></category>
		<category><![CDATA[just in time]]></category>
		<category><![CDATA[magickwand]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[reduced redundancy storage]]></category>
		<category><![CDATA[rrs]]></category>
		<category><![CDATA[s3]]></category>
		<category><![CDATA[versoning]]></category>
		<category><![CDATA[web development]]></category>

		<guid isPermaLink="false">http://awsninja.wordpress.com/?p=4</guid>
		<description><![CDATA[A Parable Not interested in a story? Go Right to the Framework. You&#8217;re the lead developer at a hot new startup called Woofbook. Elevator pitch: &#8220;It&#8217;s like Facebook meets Flickr, but for dogs.&#8221; The founder of Woofbook has started many companies, and run them to many different levels of success and failure, by the shear [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=awsninja.wordpress.com&amp;blog=14043929&amp;post=4&amp;subd=awsninja&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<h2>A Parable</h2>
<p>Not interested in a story?  <a href="http://awsninja.com/2010/06/11/a-php-framework-for-hosting-images-on-amazon-cloudfront/#framework">Go Right to the Framework</a>.</p>
<p>You&#8217;re the lead developer at a hot new startup called Woofbook.  Elevator pitch: &#8220;It&#8217;s like Facebook meets Flickr, but for <a href="http://37signals.com/svn/posts/2188-theres-no-room-for-the-idea-guy">dogs</a>.&#8221;  The founder of Woofbook has started many companies, and run them to many different levels of success and failure, by the shear force of his outgoing personality.  In other words, he is a typical web entrepreneur.  One day, while getting his goatee manscaped, a barber tells your boss that his nephew told him that Cloud Computing is the next big thing.  He rushes back to the office and announces &#8220;We need to harness the cloud!&#8221;  After searching for &#8220;Cloud&#8221; on TechCrunch, your boss tells you &#8220;Cloudfront is the thing we need.&#8221;<span id="more-4"></span></p>
<p>You take a moment to marvel that a pretty good idea was actually derived from such ridiculous reasoning.  You know that using CDN is actually a <a href="http://developer.yahoo.com/performance/rules.html#cdn">very good practice</a> and you&#8217;re pleased that you will actually spend the next few days working on something that will actually provide long-term value to the project.</p>
<p>It just so happens that you were building the photo-sharing section of Woofbook when your boss interrupted you with his Cloudfront idea. After 15 minutes of cruising the Amazon&#8217;s Cloudfront website and a few blog posts, you conclude that implementing Cloudfront is going to be easier than you thought.  You just need to create a Cloudfront distribution, upload your images to it, then point the HTML image tags to the Cloudfront Service.  Perfect.</p>
<p>A week later, the photo-sharing feature goes live and it&#8217;s a big hit.  Before long users have uploaded over 100,000 photos and it loads pretty fast and you think you&#8217;re pretty darn sophisticated for making it look so easy.</p>
<div class="wp-caption alignright" style="width: 210px"><a href="http://www.flickr.com/photos/bobmarley753/253261970/"><img title="&quot;dog dreams&quot; by bobmarley753" src="http://assets.docmonk.com/ninja/dog1.jpg" alt="&quot;dog dreams&quot; by bobmarley753" width="200" height="133" /></a><p class="wp-caption-text">&quot;dog dreams&quot; by bobmarley753</p></div>
<p>Several weeks after that, your boss comes back from a meeting with a potential investor (those meetings always tend to result in bothersome new initiatives) and exclaims, &#8220;The photo sharing is great!  We need to leverage it.&#8221;</p>
<p>It turns out that dogs have active social lives. They go out to dog gatherings every day at specially designated meeting places (known as dog parks) to socialize and conduct their. . .  ahem &#8211; <em>business</em>.</p>
<p>&#8220;It would be great if dogs could tag their friends in the photos they uploaded.&#8221; your boss says.  &#8220;Also, wouldn&#8217;t it be great if one dog could use a photo uploaded by another dog as his profile pic?&#8221;</p>
<p>&#8220;No problem!&#8221; you say confidently.  You smartly designed the database architecture to make it easy to use the photos on the site for virtually any purpose.  &#8220;I&#8217;ll have it done by COB tomorrow.&#8221;  You link up the Photos table to the Users table by adding a &#8220;profilePhotoId&#8221; foreign-key field to the Users table.  Then you build out the functionality to allow users to choose any photo as their profile pic.</p>
<p>But you soon realize there <em>is</em> a problem.  Profile pics are displayed at 200&#215;150 pixels and the photo album photos typically display at 500&#215;375.  You&#8217;re afraid you don&#8217;t have time to properly resize the photos without missing your self-imposed deadline.  To speed things up you try resizing the actual photo images by using the WIDTH and HEIGHT attributes of the IMG tag, but that causes the images to appear warped because the aspect-ratios are different.</p>
<p>So now you need to run a script to resize the photos.  Or maybe you&#8217;ll just resize the subset of the album photos that users have chosen as profile pics.  You realize there&#8217;s a trade-off between these two options.  It&#8217;s more logically simple to store all of the photos in two sizes, but it&#8217;s more efficient and elegant to only store the profile-sized images for images that will actually be used as profile pics.  Unfortunately, you promised it would be done &#8220;by COB tomorrow.&#8221;  To meet your deadline you settle on resizing all of the photos and uploading them all on Cloudfront.  Even though it is less efficient to do it this way, it also involves less programming which means you&#8217;ll meet your deadline.  You run a process to resize the photos overnight and finish up the next day.  Everybody&#8217;s happy.</p>
<p>Several months later, things are continuing to go well at Woofbook Inc.  Users are loving the ability to use their friends&#8217; photos as their profile pics.  Woofbook is starting to get positive coverage on TechCrunch and even the regular business press is beginning to take notice.  Imitators start to appear on the scene.  One of them, BarkSquare, decides to capitalize on some bad press Woofbook received about some ill-advised privacy-policy changes to promote their new &#8220;Transfer to BarkSquare&#8221; feature.  The feature allows users to copy their entire Woofbook profile, including all of their friends and photos, to BarkSquare.</p>
<div class="wp-caption alignleft" style="width: 210px"><a href="http://www.flickr.com/photos/derekgavey/4190468784/"><img class=" " title="&quot;Caught Surfin Flickr&quot; by derekGavey" src="http://assets.docmonk.com/ninja/dog2.jpg" alt="&quot;Caught Surfin Flickr&quot; by derekGavey" width="200" height="133" /></a><p class="wp-caption-text">&quot;Caught Surfin Flickr&quot; by derekGavey</p></div>
<p>Predictably, this causes a significant panic in the WoofBook offices. &#8220;How will we stop this?&#8221; your boss cries with the agony not unlike the pleadings of a bullied child.  After some determined and serious discussion, he decides that all of the photos should be watermarked.  &#8220;At least we&#8217;ll be able to keep track of where the images go and get some free advertising,&#8221; he says.</p>
<p>You have your doubts.  First of all, it was the VP of Marketing who implemented the privacy policy change.  You didn&#8217;t create this problem, but it belongs to you now.  Also, you think that making it harder for users to switch from Woofbook to others might further erode users&#8217; trust and exacerbate the problem.  Unfortunatly, you&#8217;re just a lowly programmer and your opinion isn&#8217;t really valued as much as your boss would like you to believe.</p>
<p>You roll your eyes and get to work figuring out how to add watermarks to the images. Once you&#8217;ve created and tested the script that will apply the watermarks, you kick it off and leave it running as you head home for the evening. Before going to bed, you check the script&#8217;s progress, see that it&#8217;s finished, and send a quick email to your boss to let him know.  You&#8217;re quite pleased that the timestamp on your email says 11:32pm.  You expect that your dedication will not go unnoticed.</p>
<p>When you arrive at work the next morning, rather than thanking you for your after-hours efforts, your boss wants to know why he&#8217;s not seeing any watermarks on the photos on the site.  That&#8217;s when you remember.  Because of the chaos and urgency in the office yesterday, you forgot something important.  The images on Cloudfront are cached on the edge locations.    Updating the original does not automatically update the cached versions residing around the world.  Worse, there is no way to manually flush the cache.  You can only wait for the Cloudfront system to refresh the cache on it&#8217;s own.  Since you followed the best-practice of setting a <a href="http://developer.yahoo.com/performance/rules.html#expires">far future Expires header</a> on the images, it can potentially be a very long time before the cached objects get refreshed.</p>
<p>Now it&#8217;s even more complicated.  In order to show the new images with the watermarks, you have to upload all of them with different file names, then point to the new versions.  It&#8217;s going to be a long day.<br />
<a name="framework"></a></p>
<h2>The Framework</h2>
<p>The CloudfrontImageService is designed to eliminate the pain of managing images on Cloudfront distributions.  The framework has built-in functionality for managing:</p>
<ul>
<li>The need to display images with different dimensions (i.e. thumbnails).</li>
<li>The need to make changes to images (i.e. add watermarks).</li>
<li>The optimal way to store images to S3 to maximize caching at the browser level and minimize your costs.</li>
<li>Just-in-time uploading &#8211; so that only the specific images that are needed in your web application ever get uploaded to Cloudfront, saving you money and eliminating the need for a background process to do that work.</li>
</ul>
<p>There are three database tables that are used to manage images in the CloudFrontImageService.  They are tbl_image, tbl_imageDimensions, and tbl_imageDimensionsMap.</p>
<p><strong><span style="font-weight:normal;"><img class="aligncenter" src="http://assets.docmonk.com/ninja/cloudfrontImagesDiagram.png" alt="" width="532" height="288" /></span></strong></p>
<p><strong>tbl_images</strong> &#8211; This table is used to track the original image file that is stored on your server&#8217;s filesystem or on an EBS volume (not on S3).</p>
<ul>
<li><strong>filePath</strong>: A  relative path to your image on the filesystem.</li>
<li><strong>version</strong>: A number indicating the version number of the image.  If an image changes (for instance, if a user uploads a new profile picture), you would write the new image to the filePath then increment the version number by one.</li>
</ul>
<p><strong>tbl_dimensions</strong> &#8211; This table holds the definitions of different dimension sizes like: thumbnail, x-large, original and whatever else you need.</p>
<ul>
<li><strong>keyName</strong>: This is a string that make it easy to reference the image size you want in your code.  For example &#8220;original&#8221;, &#8220;thumbnail&#8221;, &#8220;extra-large&#8221;.</li>
<li><strong>description</strong>: An informational field to help the site developer keep track of the purpose of the dimension.  A place to store comments that are not actually used on your site.</li>
<li><strong>width, height</strong>:  This is the maximum width or height for the dimension.  When an image is rendered using a dimension, it will have a width and height no larger than these values.  The image is resized to preserve the aspect ratio but still fit in these dimensions</li>
</ul>
<p><strong>tbl_imageDimensionsMap</strong> &#8211; This table holds the records of actual image objects that are currently residing in in your Cloudfront distribution.</p>
<ul>
<li><strong>imageId, imageDimensionsId</strong>: The primary key for this table and the foreign keys to tbl_image and tbl_imageDimensions.</li>
<li><strong>width, height</strong>:  This is the actual width and height of the image resized to fit the dimensions.  When the image is resized the aspect ratio is preserved, meaning that either the width or height is likely to be different than the value in tbl_imageDimensions.  For this reason, we record the actual width and height here.</li>
<li><strong>version</strong>:  This is the version number of the image that is currently stored on Cloudfront.  When the version in tbl_image gets incremented, we don&#8217;t immediately upload a new version of the image to Cloudfront.  When an image URL is requested (you request the URL in order to insert it into your HTML), the current version number is compared to the version number in tbl_images.  If the version in tbl_images has been incremented but the version in tbl_imageDimensionsMap has not, the new version of the image in the requested dimension will be created and uploaded, and the URL to that new version is returned to the caller.</li>
</ul>
<h3>Adding Images</h3>
<h4>Adding Images to the Framework</h4>
<p>Before you can serve up images in your webpages, you need to add them to the framework.  This is done through the createImageObjectFromFileSystemPath() method.  Example:</p>
<p><pre class="brush: php;">
&lt;?php
  $cfImgSvc = new CloudfrontImageService();

  $imgObj = $cfImgSvc-&gt;createImageObjectFromFileSystemPath('/var/www/imagedrop/flower.jpg', 'flowers/flower.jpg');

?&gt;
</pre></p>
<p>On line two we instantiate the CloudFrontImageService.  On line four we use the createImageObjectFromFileSystemPath() method to take an image saved on the filesystem at location /var/www/imagedrop/flower.jpg and create an Image object which will be stored in the flowers subdirectory of the IMAGES_ROOT that is set in the config file.  The createImageObjectFromFileSystemPath() handles the following steps:</p>
<ul>
<li>Determines the type of the image (JPEG, GIF, PNG).</li>
<li>Determines that the relative path (i.e. flowers/flower.jpg) is not already used.  If it is a duplicate, it will append a digit to the file name until there is a unique version (e.g. flowers/flower1.jpg, flowers/flower2.jpg).</li>
<li>Makes sure that the sub-directories exist. If the flowers directory does not exist in the IMAGES_ROOT, then it will create it.</li>
<li>Copies the flower.jpg file to its new location in the IMAGES_ROOT.</li>
<li>Creates the tbl_image row in the database.</li>
<li>Returns the newly created Image object.</li>
</ul>
<h4>Inserting Cloudfront URLs into your HTML</h4>
<p>Once you have added an image to the framework, you can use the CloudfrontImageService class to insert the image URL into your HTML.  CloudfrontImageService has three methods that you can use to do this:</p>
<p><strong>getUrlFromFilePath($filePath, $dimensionKeyName)</strong> &#8211; Takes the $filePath (which is one found in tbl_images) and a valid dimensionKey (which is a keyName found in tbl_imageDimensions).  It returns a URL to the image on Cloudfront.  If the image doesn&#8217;t exist on Cloudfront, it automatically uploads it to your Cloudfront distribution it before returning the URL.</p>
<p><pre class="brush: php;">
&lt;html&gt;
  &lt;body&gt;
    &lt;img src=&quot;&lt;?php
      echo $cfImgSvc-&gt;getUrlFromFilePath('flowers/flower.jpg', 'thumbnail');
    ?&gt;&quot;/&gt;
  &lt;/body&gt;
&lt;/html&gt;
</pre></p>
<p>This will insert the URL of the thumbnail-sized image into the HTML output.  The thumbnail dimensionkey (thumbnail) must be defined in tbl_imageDimensions.  The resulting URL will look something like this:<br />
<em>http://cdn.docmonk.com/flowers/flower_1_thumbnail_136x111.jpg</em><br />
The version, dimensionkey, width, and height are all part of the URL for the purposes of ensuring that it is a unique URL and for your convenience.</p>
<p><strong>getUrlFromImageId($imageId , $dimensionKeyName)</strong> &#8211; Same as getUrlFromFilePath(), except that you provide the Image id.</p>
<p><pre class="brush: php;">
&lt;html&gt;
  &lt;body&gt;
    &lt;img src=&quot;&lt;?php
      echo $cfImgSvc-&gt;getUrlFromImageId(55, 'extra-large');
    ?&gt;&quot;/&gt;
  &lt;/body&gt;
&lt;/html&gt;
</pre></p>
<p><strong>getUrl(Image $imgObj, $dimensionsKeyName)</strong> &#8211; Same as the two above, except you can use it if you already have the Image object.</p>
<p><pre class="brush: php;">
&lt;html&gt;
  &lt;body&gt;
    &lt;img src=&quot;&lt;?php
      $imgObj = Image::findById(55);
      echo $cfImgSvc-&gt;getUrl($imgObj, 'original');
    ?&gt;&quot;/&gt;
  &lt;/body&gt;
&lt;/html&gt;
</pre></p>
<h4>Other Considerations</h4>
<ul>
<li>All of the images uploaded to S3 have the Cache-Control header automatically set to &#8220;public, max-age=315360000&#8243;.  The &#8220;public&#8221; setting is there so that browsers will cache the images even if they are served via <a href="http://aws.typepad.com/aws/2010/06/amazon-cloudfront-support-for-https-access.html">HTTPS</a>.  The max-age is set to 10 years.  The max-age has two affects.  Having a long max-age setting increases the amount of time that the edge locations will cache your content, reducing the number of times that users will have to wait for the cache to be refreshed and potentially saving you money.  It also tells the users&#8217;s web browser to cache the image for up to 10 years, making your site load faster.  The 10-year expiration won&#8217;t have any adverse effects if an image changes, because the URL of the image will also change (the version number will increase) so Cloudfront and the user&#8217;s browser will consider it to be a brand new object.</li>
<li>The framework relies on <a href="http://www.magickwand.org/">MagickWand</a> to resize images.  This is not typically installed by default.</li>
<li>Amazon recently added a S3 feature called <a href="http://aws.typepad.com/aws/2010/05/new-amazon-s3-reduced-redundancy-storage-rrs.html">Reduced Redundancy Storage</a> which offers lower prices in exchange for lower durability of the file storage.  Because the CloudfrontImageService stores your original image files on the file system (hopefully an EBS volume if you&#8217;re hosting on EC2), it is entirely feasible to use this cheaper alternative to store your CloudfrontImageService images.  If you were to lose your S3 image storage, you could recover by just incrementing the version number of all of the records in the tbl_images table.  The CloudfrontImageService will then handle the re-uploading of the image objects to S3 as needed.</li>
</ul>
<h3>Download</h3>
<p>Get the AWS Ninja <a href="http://github.com/awsninja/awsninja_cloudfrontimageservice/downloads">CloudFrontImageService</a> from <a href="http://github.com/awsninja/awsninja_cloudfrontimageservice/downloads">GitHub</a>.  You will also need the <a href="http://github.com/awsninja/awsninja_core/downloads">AWS Ninja Core</a>.  Installation instructions are the in the package README.txt files.</p>
<br />  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/awsninja.wordpress.com/4/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/awsninja.wordpress.com/4/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/awsninja.wordpress.com/4/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/awsninja.wordpress.com/4/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/awsninja.wordpress.com/4/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/awsninja.wordpress.com/4/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/awsninja.wordpress.com/4/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/awsninja.wordpress.com/4/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/awsninja.wordpress.com/4/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/awsninja.wordpress.com/4/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/awsninja.wordpress.com/4/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/awsninja.wordpress.com/4/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/awsninja.wordpress.com/4/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/awsninja.wordpress.com/4/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=awsninja.wordpress.com&amp;blog=14043929&amp;post=4&amp;subd=awsninja&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://awsninja.wordpress.com/2010/06/11/a-php-framework-for-hosting-images-on-amazon-cloudfront/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
	
		<media:content url="http://0.gravatar.com/avatar/0811f04dc6f8ec38e7ae3d6627327694?s=96&#38;d=identicon&#38;r=PG" medium="image">
			<media:title type="html">awsninja</media:title>
		</media:content>

		<media:content url="http://assets.docmonk.com/ninja/dog1.jpg" medium="image">
			<media:title type="html">&#34;dog dreams&#34; by bobmarley753</media:title>
		</media:content>

		<media:content url="http://assets.docmonk.com/ninja/dog2.jpg" medium="image">
			<media:title type="html">&#34;Caught Surfin Flickr&#34; by derekGavey</media:title>
		</media:content>

		<media:content url="http://assets.docmonk.com/ninja/cloudfrontImagesDiagram.png" medium="image" />
	</item>
	</channel>
</rss>
