tag:blogger.com,1999:blog-67990882024-03-18T09:48:01.801+00:00Instant BadgerRants, raves and random thoughts on Ruby, Rails and Rabbit, plus Java, CFMX, methodologies, and development in general. And too much alliteration.Alistair Davidsonhttp://www.blogger.com/profile/07415960315873865766noreply@blogger.comBlogger181125tag:blogger.com,1999:blog-6799088.post-1406724212277341072013-08-05T14:21:00.002+01:002013-08-05T14:21:45.599+01:00UNO IllegalArgument during import phase: Source file cannot be read. URL seems to be an unsupported one.Creating Word documents in a fully linux-based environment can be tricky. There is a trick you can do which is basically saving html with a .doc extension, which allows Word to open the resulting document, but it has some drawbacks - if you try to make any changes and save it again, you can't save it as a .doc, etc etc.<br />
<br />
So we have a multi-step chain of services set up on our Quill Platform to allow us to render our articles as genuine Word documents.<br />
<br />
In the Rails app:<br />
<br />
<ol>
<li>User clicks 'download as word doc' on an article - this results in a request like this: GET /articles/1234.doc</li>
<li>Check for an existing word doc representing the correct version of the requested article. If it's there, serve it back as a document. If not....</li>
<ol>
<li>Render as a string, and save a WordDocumentConversion object which encapsulates the resulting HTML</li>
<li>Submit a job to Resque to perform the actual conversion.</li>
<li>Redirect the user, and flash a message saying "That document might take a minute or two to generate - we'll email it to you when it's ready"</li>
</ol>
</ol>
<div>
In the Resque job:</div>
<div>
<ol>
<li>Load the WordDocumentConversion</li>
<li>POST the saved HTML as a file input to our DocumentConverter web service - a little Sinatra app which provides a RESTful endpoint around LibreOffice</li>
<li>Save the response as a file named .doc, in binary mode, and email it to the user.</li>
</ol>
<div>
In the DocumentConverter web service:</div>
</div>
<div>
<ol>
<li>accept the POST-ed HTML content</li>
<li>invoke <a href="https://github.com/dagwieers/unoconv">UNOCONV</a> - a command line python script that wraps the LibreOffice / OpenOffice headless document conversion service.</li>
<li>respond with the binary content of the returned Word doc.</li>
</ol>
<div>
<br /></div>
</div>
<div>
It all works pretty well, most of the time, and was a good exercise in building complexity through keeping each individual part very simple. However, we recently moved our live platform servers from the US-EAST EC2 region over to the EU-WEST region, and took the opportunity to rebuild them from scratch on updated Ubuntu, and while setting up our staging server we got the above error ( 'UNO IllegalArgument during import phase: Source file cannot be read. URL seems to be an unsupported one.' ) which had us scratching our heads for most of Friday.</div>
<div>
<br /></div>
<div>
To cut a long story short, this is another instance of what we refer as <a href="http://en.wikiquote.org/wiki/Laozi">"Tao errors" - the error which can be seen is not the true error.</a> When it says that the URL is unsupported, what it actually means is "I can't handle the file format you've requested" - usually because there are some LibreOffice OpenOffice packages missing. </div>
<div>
<br /></div>
<div>
If you've only installed the base & core packages, that's not enough - to be able to render Word documents, you need to actually install the "writer" package as well. A quick scan of the unoconv documentation does give you this little tidbit -</div>
<div>
<br /></div>
<div>
<div style="color: #333333; font-family: Helvetica, arial, freesans, clean, sans-serif; font-size: 15px; line-height: 25px;">
<div>
<blockquote class="tr_bq">
Various sub-packages are needed for specific import or export filters, e.g. XML-based filters require the xsltfilter subpackage, e.g. <tt style="background-color: #f8f8f8; border-bottom-left-radius: 3px; border-bottom-right-radius: 3px; border-top-left-radius: 3px; border-top-right-radius: 3px; border: 1px solid rgb(221, 221, 221); margin: 0px 2px; padding: 0px 5px;">libobasis3.5-xsltfilter</tt>.<table style="border-collapse: collapse; border-spacing: 0px; display: block; margin: 15px 0px; overflow: auto; width: 722px;"><tbody>
<tr style="background-color: white; border-top-color: rgb(204, 204, 204); border-top-style: solid; border-top-width: 1px;"><td style="border: 1px solid rgb(221, 221, 221); padding: 6px 13px;">Important</td><td style="border: 1px solid rgb(221, 221, 221); padding: 6px 13px;">Neglecting these requirements will cause unoconv to fail with <b>unhelpful and confusing error messages.</b></td></tr>
</tbody></table>
</blockquote>
- so I guess we were warned... but still, problem solved at last.</div>
</div>
<div style="color: #333333; font-family: Helvetica, arial, freesans, clean, sans-serif; font-size: 15px; line-height: 25px;">
<h2 style="border-bottom-color: rgb(238, 238, 238); border-bottom-style: solid; border-bottom-width: 1px; cursor: text; font-size: 2em; line-height: 1.7; margin: 1em 0px 15px; padding: 0px; position: relative;">
<a class="anchor" href="https://github.com/dagwieers/unoconv#how-does-unoconv-work-" name="how-does-unoconv-work-" style="bottom: 0px; color: #4183c4; cursor: pointer; display: block; left: 0px; margin-left: -30px; padding-left: 30px; position: absolute; text-decoration: none; top: 0px;"></a></h2>
</div>
</div>
<div>
<br /></div>
Alistair Davidsonhttp://www.blogger.com/profile/07415960315873865766noreply@blogger.com147tag:blogger.com,1999:blog-6799088.post-3755852621653537952013-01-04T11:24:00.002+00:002013-01-04T11:24:13.677+00:00MySQL "Row size too large" when saving many text fieldsUsing MySQL? InnoDB table type? Got a table with several TEXT or BLOB fields? Getting a "Row size too large" error when saving a row with lots of text in those TEXT fields? Confused, because you thought the whole point of TEXT fields was that they stored the text off-table? Well, read on....<br />
<br />
<i>disclaimer: if you're storing many text fields in a relational database table, you might want to look again at whether that's the right place and method for storing that data - if it looks like a document and quacks like a document, then hey, maybe a document store would be more appropriate? But that's a whole other topic...</i><br />
<br />
The detail is in the MySQL docs <a href="http://dev.mysql.com/doc/refman/5.5/en/innodb-row-format.html">14.4.5. How InnoDB Stores Variable-Length Columns</a> , but I'll give a quick summary here. It hinges on the <i>file format </i>of your InnoDB engine.<br />
<br />
As the docs say:<br />
<blockquote>
Early versions of InnoDB used an unnamed file format (now called Antelope) for database files. With that format, tables were defined with ROW_FORMAT=COMPACT (or ROW_FORMAT=REDUNDANT) and <strong>InnoDB stored up to the first 768 bytes of variable-length columns (such as BLOB and VARCHAR) in the index record</strong> within the B-tree node, with the remainder stored on the overflow pages.</blockquote>
<em>(emphasis mine)</em><br />
<em><br /></em>
So with the Antelope file format, it's perfectly possible to store a single TEXT field up to 2GB without encountering the "Row size too large" error, but it's <i>not </i>possible to store 10 x 1k TEXT fields - because InnoDB will store the first 768 bytes of each TEXT field on the record itself, and exceed the row size limit of 8192 bytes.<br />
<br />
The solution is straightforward (well, mostly :) - <a href="http://dev.mysql.com/doc/innodb/1.1/en/innodb-other-changes-file-formats.html">change your innodb_file_format variable to Barracuda</a>, and alter the table to use the DYNAMIC or COMPRESSED row_format. This will store the entire contents of the TEXT fields "off-page" -<br />
<br />
<pre>SET GLOBAL innodb_file_format=Barracuda; SET GLOBAL innodb_file_per_table=ON; ALTER TABLE (your table) ROW_FORMAT=COMPRESSED;</pre>
<br />
- and you're good to go.<br />
<br />
The one complication is if you're running on Amazon RDS, in which case you'll get an error saying you don't have SUPER privileges. If that's the case, you just need to set the innodb_file_format parameter in your RDS instance parameter group, and allow a few seconds for it to propagate to all your instances.<br />
<em><br /></em>Alistair Davidsonhttp://www.blogger.com/profile/07415960315873865766noreply@blogger.com53tag:blogger.com,1999:blog-6799088.post-20545669836169661482012-11-09T18:11:00.001+00:002012-11-14T13:15:52.012+00:00Optimizing Polymorphic Association Joins On STI ModelsLets' say you have a table of assigned_users.<br />
<br />
Users can be assigned to several different things, so you make it a polymorphic relationship:<br />
<pre>
class User < ActiveRecord::Base
has_many :assigned_users, :as=>:user
end
class AssignedUser < ActiveRecord::Base
belongs_to :user
belongs_to :model, :polymorphic=>true
end
class Client < ActiveRecord::Base
has_many :assigned_users, :as=>:model
end
class Task < ActiveRecord::Base
has_many :assigned_users, :as=>:model
end
</pre>
<div>
<br /></div>
<div>
<br /></div>
<div>
...and so on. At this point you'll have the following fields in your assigned_users table:</div>
<div>
<br /></div>
<div>
id</div>
<div>
user_id</div>
<div>
model_type</div>
<div>
model_id</div>
<div>
<br /></div>
<div>
OK, so this assigned_users table is going to need some indexes. Like any sensible techie, you think about the most common access patterns, and create some indexes to optimize those:</div>
<div>
<br /></div>
<div>
add_index :assigned_users, [:user_id]</div>
<div>
add_index :assigned_users, [:model_type, :model_id]</div>
<div>
<br /></div>
<div>
Great, we're good to go. </div>
<div>
<br /></div>
<div>
Things tick along nicely, your assigned_users table is getting past the 'tiny' stage and into the 'medium' stage of, say, 10,000 records, but we're cool with that, because the joins have indexes. Right?</div>
<div>
<br /></div>
<div>
Right! Until....</div>
<div>
<br /></div>
<div>
<i>(drum roll)</i></div>
<div>
<br /></div>
<div>
....one of the parent model tables gets refactored into several sub-classes, with Single Table Inheritance.</div>
<div>
</div>
<div>
<i>(badoom.. TSH!)</i></div>
<div>
<i><br /></i></div>
<div>
What happens to your access pattern now?</div>
<div>
<br /></div>
<div>
Well, let's say that you refactor the Task class to have several sub-classes:</div>
<div>
<br /></div>
<div>
class FooTask < Task</div>
<div>
end</div>
<div>
<br /></div>
<div>
class BarTask < Task</div>
<div>
end</div>
<div>
<br /></div>
<div>
class BazTask < Task</div>
<div>
end</div>
<div>
<br /></div>
<div>
When you now try to do a join from the base class Task onto AssignedUsers, you'll get something like this:</div>
<div>
<br /></div>
<div>
(I'm using <a href="https://github.com/ernie/squeel">Ernie Miller's Squeel</a> syntax here, it's nice and clear)</div>
<div>
<br /></div>
<pre>> Task.joins{assigned_users}.to_sql
SELECT `tasks`.* FROM `tasks` INNER JOIN `assigned_users` ON `assigned_users`.`model_id` = `tasks`.`id` AND `assigned_users`.`model_type` IN ('Task', 'FooTask', 'BarTask', 'BazTask')
</pre>
<br />
<div>
....and your load times go through the roof, because all of a sudden, that IN(...) clause means that your combined index on model_type and model_id can't be used, so your database resorts to a full table scan. On a 10,000-row table...?</div>
<div>
<br /></div>
<div>
Well, as <a href="http://uk.imdb.com/character/ch0000548/quotes">Egon Spengler would say</a> - "...it would be bad."</div>
<div>
<br /></div>
<div>
Now, we can tackle this several ways:</div>
<div>
<ol>
<li>Hack ActiveRecord to generate multiple ORs on the join rather than an IN(...), or...</li>
<li>Write a scope on the parent model that constructs the join manually, using ORs, or...</li>
<li>Look for a quick and dirty solution</li>
</ol>
</div>
<div>
The first 2 sound suspiciously like rabbit holes down which we could quite easily disappear for a while... I like 3 - let's go with 3.</div>
<div>
<br /></div>
<div>
Luckily we can achieve big gains pretty easily, by noting that we could cut down the search space dramatically if we can just get the query optimizer to use an index on the model_id - even if every other class in your model is able to have assigned_users, the maximum number of collisions on model_id you're going to get is the number of classes you have. </div>
<div>
<br /></div>
<div>
So all we need to do is add an index <i>just on model_id -</i></div>
<div>
<i><br /></i></div>
<div>
add_index :assigned_users, [:model_id]</div>
<div>
<br /></div>
<div>
- and the query optimizer can now use that index in the join. Now it only needs to search through the few records with model_id = N to find those with model_type IN ('Task', 'FooTask', 'BarTask', 'BazTask'), rather than searching through every single record in the table.</div>
<div>
<br /></div>
<div>
Not a perfect solution, admittedly, but a pragmatic one - and one that brought down page load times by 80-90% for us. You're welcome :) </div>
<div>
<br /></div>
Alistair Davidsonhttp://www.blogger.com/profile/07415960315873865766noreply@blogger.com0tag:blogger.com,1999:blog-6799088.post-8298478843242019332012-09-24T17:33:00.000+01:002012-09-25T13:24:31.208+01:00Implementing Agile - When User Stories Meet Ticketing SystemsThere's lots of "Introduction to Agile principles"-type articles around the blogosphere, but not many I've seen that actually give practical advice down to the level of the real mundanities like ticket-logging. But these things are important - if you're not careful, your ticketing system can end up like an elephants graveyard, a confusing, mournful desolate place full of orphaned tickets for which no-one can remember what they're doing there, whether they're still needed, or even why they were logged in the first place.<br />
<br />
So I thought I'd share the way we've been doing it. I'm going to concentrate here on feature requests only, as bug-reporting is a whole separate issue. The system we've been trying recently at <a href="http://www.itrigga.com/">iTrigga</a> has helped us keep on top of our estimates and project mgmt, as well as our daily tasks. So here goes:<br />
<br />
<h4>
1) We agree a <a href="http://www.agilemodeling.com/artifacts/userStory.htm#InitialFormal">formal user story</a> with the requesting person</h4>
e.g. don't just record "Ability to get a second opinion on an article", go the extra small step of wording in the form<br />
<blockquote class="tr_bq">
"As a <i>(type of user)</i> I want to <i>(feature) </i>so that <i>(benefit)</i>"</blockquote>
in this example, it would be "As an <i>editor</i>, I want to <i>get a second opinion on an article </i>so that <i>when I'm editing an article that requires specialist technical knowledge I don't have, I can get it checked by a domain expert"</i><br />
<i><br /></i>
<br />
<div>
Getting the level of detail right can take a little bit of getting used to, but is <i>well</i> worth it in the long run. The "...so that <i>(benefit)" </i>may sound trivial or obvious, but it's absolutely vital when you refer back to lists of tickets long after the initial conversation has faded from memory. </div>
<div>
<br />
We've also found it very useful to agree this with the requesting person. It can be quite trying to get the rest of the business to adopt a standard format for this - the push-back is almost inevitable ("You put it into your system however you like, I don't have time for this!") but you can use it as a double-check ("..so let me just make sure I've got this straight - is it fair to say <i>As a sales manager, you want a report of job progress so that you can estimate your booked revenue for this month?</i>") and if you keep repeating the same format back to them, gradually they'll start using it themselves :)</div>
<h4>
</h4>
<h4>
2) We log a ticket for each user story</h4>
<div>
The ticket name is the "As a <i>(type of user)</i> I want to <i>(feature)</i>", the first line of the description is the "..so that <i>(benefit).</i>" </div>
<div>
<br /></div>
<div>
After that, we can add however much more context and supporting information we need, but the title and first line always follow that format so that we can always see:</div>
<div>
<ul>
<li><i>what</i> was requested</li>
<li><i>who</i> it was for, and</li>
<li><i>why </i>they wanted it</li>
</ul>
</div>
<h4>
</h4>
<h4>
3) We log sub-tasks for each change required to fulfill the user story</h4>
<div>
<div>
Let's take a quick whizz through the example above - what do we need to add or change in order to satisfy the "ability to get a second opinion on an article" ? Well, we'll need to:</div>
</div>
<div>
<ol>
<li>add a button marked "Get a second opinion" on the editing page</li>
<li>add a way of specifying (or automatically choosing) who to get the second opinion from </li>
<li>send an email to the second opinion guy telling them that their input is needed</li>
<li>record their opinion in some way (another form? comments?)</li>
<li>an email sending their opinion back to the editor who originally requested the second opinion </li>
</ol>
</div>
<div>
OK, at that level, we can log sub-tasks and probably start estimating time requirements for these tasks. </div>
<div>
<br /></div>
<div>
<i>( Or can we? Well, there's a couple there that need more detail - 2 and 4. So there'll be some discussion around those, and maybe even another level of sub-task below those two, or maybe a separate user story. But let's assume for the purposes of this post that we can move on.)</i></div>
<div>
<i><br /></i></div>
<h4>
4) Each sub-task is logged in the form of RSpec examples</h4>
<div>
For instance, sub-task number 2 above should be logged as something like this:</div>
<blockquote class="tr_bq">
"when a second opinion is requested, it should send a SecondOpinionRequested email to the chosen domain expert"</blockquote>
<div>
- which translates nicely into a functional spec:</div>
<pre><code>
describe "when a second opinion is requested" do
before do
# set up your situation
end
it "generates a message" do
# e.g. something like this:
expect{ subject.save }.to change( Message, :count ).by(1)
end
describe "the generated message" do
let(:msg){ Message.last }
it "is a SecondOpinionRequested mail" do
# your test here
end
it "goes to the chosen recipient" do
# your test here
end
end
end</code></pre>
<div>
<br /></div>
<div>
So this gives us a top-level task for the user story, and a set of RSpec examples for the changes needed to fulfil the user story. This is really handy when you come back to a half-complete job later on and want to check how much of it is still left to do.<br />
<br />
We use <a href="http://www.redmine.org/">Redmine</a> for our ticketing, and there are a couple of extra plusses when you do it this way in Redmine:<br />
<br />
<ul>
<li>estimates for the sub-tasks propagate up to the top-level task (the user story)</li>
<li>you can't complete a parent task until the sub-tasks are complete</li>
<li>a parent task can't be higher-priority than its lowest-priority sub-task</li>
<li>if you complete each sub-task as you get the spec to pass, you can easily see from the parent task how much is left to do (see figure)</li>
</ul>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhDVKVfx1NFDFotKhnFSpvApakA3RHuOD_xXE9n-EeKbQhWhy48PLiJ4ZZBgfM0QKlku6Qwg2Q6F6eWnKrnCq5T0bnK_tSo7RPbXxHIVPGt3sg6hWLZfb-JTPI1I0Rduid0v4EX/s1600/Screen+Shot+2012-09-24+at+17.31.06.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="271" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhDVKVfx1NFDFotKhnFSpvApakA3RHuOD_xXE9n-EeKbQhWhy48PLiJ4ZZBgfM0QKlku6Qwg2Q6F6eWnKrnCq5T0bnK_tSo7RPbXxHIVPGt3sg6hWLZfb-JTPI1I0Rduid0v4EX/s400/Screen+Shot+2012-09-24+at+17.31.06.png" width="400" /></a></div>
<div>
<br /></div>
</div>
<h4>
5) Each top-level user story gets its own git branch</h4>
<div>
...and the branch name is (ticket #)-brief-feature-description.</div>
<div>
<br /></div>
<div>
It's tempting to just dive in and start coding, but a little discipline here pays off big dividends in the long run. I've lost count of the number of times I've had to break off even ostensibly five-minute jobs for any of the following:</div>
<div>
<br /></div>
<div>
<ul>
<li>You're halfway through the ticket when the CEO tells you to drop everything and work on X instead</li>
<li>You realise that you can't do this until ticket Y is done, and that guy's away </li>
<li>You have to break off and do an urgent fix </li>
<li>You have to look at an issue with someone else's unreleased code</li>
</ul>
</div>
<div>
<br /></div>
<div>
etc etc etc. Whatever - remember this:</div>
<blockquote class="tr_bq">
in Git, <i>branches are cheap</i> - there's no reason NOT to use them!</blockquote>
Putting the ticket number at the start of the branch name helps keep things in a sensible order when you list the branches, and keeping the feature description in there means you don't have to keep referring back to your ticketing system to remember which is which.<br />
<br />
It also helps when trying to trace exactly which feature got merged into what, when.<br />
<br />
<br />
That's probably enough for now - I'm sure there are many alternative systems, this is just one that works for us. All suggestions and comments welcome!Alistair Davidsonhttp://www.blogger.com/profile/07415960315873865766noreply@blogger.com1tag:blogger.com,1999:blog-6799088.post-86861358041523899152012-09-03T13:21:00.000+01:002012-09-03T13:21:57.724+01:00Getting Accurate Count on Grouped Arel Relations with Squeel<p>Let's say you have a scope on a model that joins onto potentially several related models:</p>
<pre><code class="ruby">
class Post < ActiveRecord::Base
has_many :comments
scope :commented_on_by, lambda{ |user|
.joins(:comments).where( :comments=>{:author_id => user.id} )
}
end
</code></pre>
<p><em>( OK, so this is a contrived artificial example, there are better ways of doing this - but the real example which triggered this post is way too complex to go into in detail, and we're all familiar with blogs, posts and comments, right? ) </em></p>
<p>So if you call Post.commented_on_by(user) you'll get all the posts which the given user has commented on. All well and good. BUT - what if the same user comments multiple times on the same post? You'll then get the same post repeated as many times in your resultset. </p>
<p>So the logical thing to do here is to introduce a group by clause, to make sure you only get each post once, right?</p>
<pre><code class="ruby">
class Post < ActiveRecord::Base
has_many :comments
scope :commented_on_by, lambda{ |user|
.joins(:comments).where( :comments=>{:author_id => user.id} ).group(:id)
}
end
</code></pre>
<p>OK, that's that problem solved. Except.... what if you now want to run an aggregate query over that result set? Say, you want to count the number of posts the user has commented on?</p>
<pre><code class="ruby">
> Post.commented_on_by(user).count
> {1=>2, 2=>1, 3=>2}
</code></pre>
<p>Er... whut? We get a Hash? </p>
<p>Well yes - because we've grouped the query, ActiveRecord will group the count results as well. It's saying "post id 1 has two comments by this user, post id 2 has one, and post id 3 has two". All of which is entirely correct, but a bit annoying if you just want to get the number of posts. OK, you could do Post.commented_on_by(user).count.size, but that kind of defeats the intended purpose of a count call, right?</p>
<p>The fundamental problem is the underlying structure of your query. You're essentially collapsing multiple rows into one with the group clause, and your SQL parser interprets it as "get me the count of rows for each row BEFORE it's collapsed" rather than after.</p>
<p>So, how do we get it to treat these multiple rows per-post as if they were one?
IN() is your friend!</p>
<p>The good news is, you can get round this using Ernie Miller's rather excellent gem <a href="https://github.com/ernie/squeel">Squeel</a>. We've been using Squeel a lot at <a href="http://www.itrigga.com">iTrigga</a>, as it helps us keep on top of syntax for some pretty complex queries. </p>
<p>One of the many nice things about Squeel is that it lets you <a href="https://github.com/ernie/squeel#subqueries">supply ActiveRecord::Relation objects as a value for a predicate</a> - so if you can restructure your query to use an IN() clause, you can supply your already-built Relation object as the value to it.</p>
<p>In this case, you can rewrite:</p>
<pre><code class="ruby">
> Post.commented_on_by(user).count
</code></pre>
<p>as</p>
<pre><code class="ruby">
> Post.where{ id.in(Post.commented_on_by(user).select(:id)) }.count
</code></pre>
<p>and the result should be correct</p>Alistair Davidsonhttp://www.blogger.com/profile/07415960315873865766noreply@blogger.com1tag:blogger.com,1999:blog-6799088.post-35864344581107494022012-05-24T13:29:00.000+01:002012-05-24T13:32:10.200+01:00SEO Spammer TrollingI recently got this SEO Spam mail:
<blockquote><pre>
From: Katie <Katie@onlinewebfurnisher.com>
Subject: Web Proposal
Date: 24 May 2012 13:07:08 GMT+01:00
To: iTrigga Support <support@itrigga.com>
Hi,
I hope you're doing good!
We are suffering from your site: www.itrigga.com saw your website is not ranked on any search engines.
If you are interested we want to increase the number of visitors to your website, it is important that you have a top search engine position.
Our search engine optimization experts will run a ranking report showing you exactly where your website currently stands in all the major search engines. Then we will email you our analysis report along with the recommendations of how we can increase your ranking, and improve your websites traffic dramatically!
We strictly work on performance basis and can assure you of getting quality links with a proper reporting format for your site as well.
We wish you the best of luck and looking forward to a long and healthy business relationship with you and your company.
Please do let me know if you have any questions.
Kind Regards,
Name: -Katie
Post:-Business Development Manager
Reply me:- Katie@onlinewebfurnisher.com
Note: Though this is not an automated email, we are sending these emails to all those people whom we find eligible of using our services. To unsubscribe from future mails (i.e., to ensure that we do not contact you again for this matter), please send a blank email at removeallmailsplease@gmail.com
</pre></blockquote>
Being in a slightly warped & mischievous mood, having just this morning received tickets to see Faith No More and then spent the next hour listening to them on Spotify, I thought I'd take a look at onlinewebfurnisher.com:
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj1Pr9uNJtp6oqJopEfz_gdGCAyIu92X-DeyBzHIjQEsiidNmtYrrgwgqabeHeOQzPfR6A-E5HqVneGYHE64MfOQWRj-5AmuCStI48P1S10_vgY7l6fzCJe2fdWz72frn7YRq2k/s1600/Screen+Shot+2012-05-24+at+13.19.33.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="156" width="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj1Pr9uNJtp6oqJopEfz_gdGCAyIu92X-DeyBzHIjQEsiidNmtYrrgwgqabeHeOQzPfR6A-E5HqVneGYHE64MfOQWRj-5AmuCStI48P1S10_vgY7l6fzCJe2fdWz72frn7YRq2k/s320/Screen+Shot+2012-05-24+at+13.19.33.png" /></a></div>
<br />
Oh dear. The <q>search engine optimization experts</q> evidently had a bit of a problem with their own website. No content, for a start, and everyone knows that <a href="http://www.readwriteweb.com/enterprise/2011/12/googles-matt-cutts-good-conten.php">the ultimate SEO technique is to have good content</a>, right?.
So I thought I'd send her a reply:
<blockquote><pre>
From: Al Davidson
Subject: Re: Web Proposal
Date: 24 May 2012 13:27:39 GMT+01:00
To: Katie <Katie@onlinewebfurnisher.com>
Hi,
I'm good thanks, I hope you're doing good!
We are suffering from your site: www.onlinewebfurnisher.com saw your website does not have any content on it. (see screenshot)
If you are interested we want to increase the number of visitors to your website, it is important for Search Engine Optimization that you have good (or at the very least, ANY) content!
Our content experts will run an analysis showing you exactly what content strategy will work best for your business needs. Then we will email you our propsal along with the recommendations of how we can increase your ranking, and improve your websites traffic dramatically!
We can assure you of getting quality content with a proper format for your site as well.
We wish you the best of luck and looking forward to a long and healthy business relationship with you and your company.
Please do let me know if you have any questions.
Kind Regards,
Name: -Al
Post:- CTO
Note: Though this is not an automated email, we are sending these emails to all those people whom we find eligible of using our services. To unsubscribe from future mails (i.e., to ensure that we do not contact you again for this matter), please send a blank email at removeallmailsplease@itrigga.com.
--
Al Davidson
CTO
</pre></blockquote>
Hyuk hyuk hyuk.... we shall see what response I getAlistair Davidsonhttp://www.blogger.com/profile/07415960315873865766noreply@blogger.com0tag:blogger.com,1999:blog-6799088.post-51402694795926035702012-02-06T15:04:00.000+00:002012-02-06T15:04:06.865+00:00Errno::EPIPE: Broken pipe when accessing S3 ?Quick tech tip - if you're trying to access Amazon S3 from Ruby, even with the official aws-sdk gem, and you get errors like this:
<code>Errno::EPIPE: Broken pipe</code>
- when trying to upload, the issue is probably that you need to explicitly set the endpoint appropriately for the region of the bucket you're trying to access. By default, the endpoint is US-specific (US-EAST, I believe)
Yes, I know the docs say you don't need to, but try it - if, like me, you find your problem instantly goes away, then Robert is your mothers' brother, as they say. Well, they do round here, anyway.
You might find this <a href="http://www.elastician.com/2009/12/comprehensive-list-of-aws-endpoints.html">comprehensive list of AWS endpoints</a> useful.
Oh, and it's not immediately obvious from the docs how to set the endpoint - I found it easiest to pass in a :s3_endpoint param when initialising the S3 object, like so -
<code>
my_s3_object = AWS::S3.new( :s3_endpoint => 's3-eu-west-1.amazonaws.com', :access_key_id=>'my access key', :secret_access_key=>'my secret access key')
</code>Alistair Davidsonhttp://www.blogger.com/profile/07415960315873865766noreply@blogger.com3tag:blogger.com,1999:blog-6799088.post-21808208230673140742012-01-23T18:32:00.000+00:002012-01-23T18:33:03.220+00:00How many WTFs per language?Recently I've had to work with PHP an increasing amount. I've not been enjoying it. A few recent IRC conversations also got me thinking - is there an entirely arbitrary but kind of fun (and hey aren't they all arbitrary, really?) metric of programming language FAIL / coder FAILs per-language ?<br />
<br />
So on a whim, I decided to invent one: number of entries on <a href="http://thedailywtf.com/">The Daily WTF</a> which mention a given language.<br />
<br />
Here we go - "web" languages only, numbers correct at time of writing:<br />
<br />
<br />
<ul>
<li>.NET - <a href="http://www.google.co.uk/search?q=.net+site:thedailywtf.com">13,000</a></li>
<li>Java - <a href="http://www.google.co.uk/search?q=java+site:thedailywtf.com">11,300</a></li>
<li>PHP - <a href="http://www.google.co.uk/search?q=php+site:thedailywtf.com">8,100</a></li>
<li>Javascript - <a href="http://www.google.co.uk/search?q=javascript+site:thedailywtf.com">6,020</a></li>
<li>Perl - <a href="http://www.google.co.uk/search?q=perl+site:thedailywtf.com">3,520</a></li>
<li>Python - <a href="http://www.google.co.uk/search?q=python+site:thedailywtf.com">2,270</a></li>
<li>Ruby - <a href="http://www.google.co.uk/search?q=ruby+site:thedailywtf.com">1,570</a></li>
<li>Coldfusion - <a href="http://www.google.co.uk/search?q=coldfusion+site:thedailywtf.com">459</a></li>
</ul>
<p>Or in chart form:</p>
<div id="pieChart1" style="text-align: centre;"></div>
<script type="text/javascript" src="https://www.google.com/jsapi"></script>
<script type="text/javascript">
console.log('here!');
google.load("visualization", "1", {packages:["corechart"]});
google.setOnLoadCallback(drawChart);
function drawChart() {
console.log('in drawChart()');
// Create and populate the data table.
var data = new google.visualization.DataTable();
data.addColumn('string', 'Language');
data.addColumn('number', 'WTFs');
data.addRows([
['.NET', 13000],
['Java', 11300],
['PHP', 8100],
['Javascript', 6020],
['Perl', 3520],
['Python', 2270],
['Ruby', 1570],
['Coldfusion', 459]
]);
var options = {
width: 500, height: 300,
title: 'WTFs per language',
is3D: true
};
var chart = new google.visualization.PieChart(document.getElementById('pieChart1'));
chart.draw(data, options);
}
</script>
<br />
So what does this prove? Well, nothing - to draw any conclusions at all, we'd have to normalise this for all kinds of factors such as<br />
<br />
<ul>
<li>number of coders / lines of code using each language (anyone have a decent set of figures?)</li>
<li>average experience level of coder (I'm pretty sure that more beginners use PHP than Perl, for instance)</li>
<li>you name it</li>
</ul>
<div>
<br /></div>
<div>
...but it is kind of fun. Let the flamewar <strike>commence</strike> continue!</div>Alistair Davidsonhttp://www.blogger.com/profile/07415960315873865766noreply@blogger.com1tag:blogger.com,1999:blog-6799088.post-21440980201762232812012-01-10T12:03:00.001+00:002012-01-10T14:54:09.826+00:00FeedWordpress duplicate posts with YD Domain Mapping<br />
Using FeedWordPress to pull in aggregated feeds to a Wordpress blog? Also using the Wordpress MU Domain Mapping plugin to map an arbitrary domain to your blog? Seeing posts get duplicated? Then read on ....<br />
<br />
I've isolated the duplicate posts issue to some kind of interaction with the Wordpress MU Domain Mapping plugin. We're using this so that we can map arbitrary domains to our network sites.<br />
<br />
E.g.<br />
<br />
<ul>
<li>our Wordpress Network is set up as wp-network.foo.com, on a subdomain setup</li>
<li>a network site 'bar' would then be bar.wp-network.foo.com</li>
<li>we setup the feedwordpress plugin to pull in posts from a tag:uri feed</li>
<li>we get unique posts in, with GUIDs of the form bar.wp-network.foo.com?guid=(some guid)</li>
</ul>
<br />
<br />
All well and good. BUT, then we use the domain mapping plugin to map blog.bar.com to that network site...<br />
<br />
It seems that when we hit a network site on its mapped domain (blog.bar.com), it instantly duplicates the syndicated posts with guids of the form blog.bar.com?guid=(the same guid)<br />
<br />
I set up 2 identical blogs, subscribed them both to the same feed, and applied a domain mapping to one but not the other. All was fine until I hit the blog homepage on the new mapped domain- at which point all the posts got replicated with mapped domain GUIDs as above.<br />
<br />
So I don't know if this is an issue with FeedWordPress or with the Wordpress MU Domain Mapping plugin, but they definitely don't seem to play nicely with each other.<br />
<br />
Hope this helps someone!<br />
<br />Alistair Davidsonhttp://www.blogger.com/profile/07415960315873865766noreply@blogger.com2tag:blogger.com,1999:blog-6799088.post-3357643190919383552011-11-22T14:49:00.001+00:002011-11-23T14:00:16.678+00:00Porting a Rails 2.3 app to Ruby 1.9We finally managed to get enough space in the schedule to take the plunge and port our monolithic Rails 2.3 app to Ruby 1.9, with a view to increasing scalability of our app. An upgrade to Rails 3 is also on the cards for later, but... one thing at a time.<br />
<br />
As ever, the path to true Ruby nirvana is paved with good intentions, and tends to detour into dependency hell for a good portion of the way. Here's a quick shortlist of some of the issues we found along the way, and what we did to get round them.<br />
<br />
In no particular order, here we go.....<br />
<br />
<br />
<h3>
MySQL2 version should be no later than 0.2.x </h3>
If you get the dreaded <q>Please install the mysql2 adapter: `gem install activerecord-mysql2-adapter`</q> error message, what it <em>really</em> means is <q>You can't use MySQL2 version 0.3+ with Rails version less than 3</q><br />
If you're already using MySQL2 0.2.x, and you're on Mac OS X, and you're <em>still</em> getting the error, then the <em>other</em> thing that it really means is <q>I couldn't find the dynamic library libmysqlclient.18.dylib</q>. There are two proposed fixes for this:<br />
<ol>
<li><code>sudo install_name_tool -change libmysqlclient.18.dylib /usr/local/mysql/lib/libmysqlclient.18.dylib /Users/YOUR_USER_NAME/.rvm/gems/1.8/gems/mysql2-0.2</code> - I couldn't get this working</li>
<li><code>sudo ln -s /usr/local/mysql/lib/libmysqlclient.18.dylib /usr/lib/libmysqlclient.18.dylib</code> - This worked for me on Lion</li>
</ol>
<br />
<h3>Use Bundler</h3>
<p>If you weren't using it before (we weren't), use it now. Stop fighting the inevitable, just give in, bend over and take it - and use Bundler. Seriously, it makes things easier in the long run.</p>
<h3>EventMachine does not compile (update: <em>..easily..</em>) on 1.9.2</h3>
<p>...at least not on my Lion Macbook Pro. It kept giving me the compiler error: <code>cc1plus: error: unrecognized command line option ‘-Wshorten-64-to-32’</code>. Under 1.9.3, however - no problems, compiled first time every time.</p>
<p><strong>Update:</strong> actually, I <em>did</em> eventually get EventMachine 0.12.10 to compile under 1.9.2 with an evil hack. The error "-Wshorten-64-to-32" above made me think - the compiler is not part of RVM, so the only way the compiler would recognise a command-line option in one Ruby but not in another... is if it's <strong>not the same compiler!</strong> So I tried this:</p>
<pre><code>$ rvm use 1.9.3@1.9.3rails2.3
Using /Users/aldavidson/.rvm/gems/ruby-1.9.3-p0 with gemset 1.9.3rails2.3
$ irb
ruby-1.9.3-p0 :002 > require 'mkmf'
=> true
ruby-1.9.3-p0 :004 > CONFIG['CXX']
=> "g++-4.2"
ruby-1.9.3-p0 :005 > exit
</code></pre>
Now let's see what 1.9.2 is using:
<pre><code>$ rvm use 1.9.2@1.9.2rails2.3
Using /Users/aldavidson/.rvm/gems/ruby-1.9.2-p290 with gemset 1.9.2rails2.3
$ irb
ruby-1.9.2-p290 :001 > require 'mkmf'
=> true
ruby-1.9.2-p290 :002 > CONFIG['CXX']
=> "g++"
ruby-1.9.2-p290 :006 > exit
</code></pre>
Ah-hah! So they are in fact using different C++ compilers! And although many native gems (e.g. MySQL) will respect and pass-through the CXX environment variable to the Makefile, sadly EventMachine is not one of them.
So, the easiest fix was a bit of evil hackery - move g++ out of the way and symlink it to g++-4.2:
<pre><code>$ which g++
/usr/local/bin/g++
$ which g++-4.2
/usr/bin/g++-4.2
$ sudo mv /usr/local/bin/g++ /usr/local/bin/g++.bak && sudo ln -s /usr/bin/g++-4.2 /usr/local/bin/g++</code></pre>
looking good - let's go for it:
<pre><code>gem install eventmachineBuilding native extensions. This could take a while...
Successfully installed eventmachine-0.12.10
1 gem installed
</code></pre>
<p>Yay!</p>
<h3>Rails 2.3 is NOT SUPPORTED under Ruby 1.9.3!</h3>
<p>There was a big fuss about Rails loading times under 1.9.3. The way that 'require' statements work has been changed, and it no longer checks to see if the file is there before requiring it. As a result, <em><a href="http://groups.google.com/group/rubyonrails-core/browse_thread/thread/81be70a119260e59">every controller MUST have a corresponding helper file</a>, even if it's just a stub.</em> This is majorly annoying, as we have 73 controllers across 3 namespaces, and only 17 helpers. The Rails team have "frozen" the 2.3 branch, and say that only security fixes will go in after 2.3.14 - in other words, they're not going to fix this. The good-old monkeypatch-via-plugin method doesn't work either, as the files get required AS Rails is loaded, not once it's initialised.</p>
<p>So there are two options, either:</p>
<ol><li>Create a stub helper for each controller<br />...which seems a bit fugly to me, or..</li><li>Fork Rails, fix the issue, use that fork in the meantime until we can port to Rails 3.</li></ol>
<p><strike>This seems better, and who knows, if enough people complain about it, maybe we'll get a patch release (2.3.15?). I'm not holding my breath, mind.... but if we're in this situation, I'm sure others are too, so this will hopefully help some other people who are having the same problem.<br />So, here's the <a href="https://github.com/itrigga/rails">forked Rails 2.3.14 that we will fix the helper requires problem on</a> (note: WILL fix, not "have already fixed"! :)</strike></p>
<p><strong>UPDATE:</strong> After having got round the EventMachine issue above, we no longer need to do this - so we're carrying on with 1.9.2 and standard rails 2.3.14. </p>Alistair Davidsonhttp://www.blogger.com/profile/07415960315873865766noreply@blogger.com4tag:blogger.com,1999:blog-6799088.post-61731911317799704992011-09-29T10:59:00.000+01:002011-09-29T10:59:09.155+01:00MySQL idle connections still holding locksWe had an interesting problem today. We were seeing very slow single-row updates (>30s) on our (innodb) scheduled_jobs table, and a large number of the update queries were failing with a LOCK WAIT TIMEOUT. The updates were using the primary key, so they should be pretty fast - but they weren't.<br />
<br />
So we fired up a console and ran SHOW INNODB STATUS, and saw lots of transactions with long-held locks - <br />
<br />
<code>---TRANSACTION 0 200086649, ACTIVE 3000 sec, process no 29791, OS thread id 140353331377936<br />
12 lock struct(s), heap size 3024, 6 row lock(s), undo log entries 10<br />
MySQL thread id 243, query id 314676 ip-10-94-245-79.ec2.internal 10.94.245.79 tn<br />
</code><br />
<br />
- however, when we cross-referenced the MySQL thread IDs with a SHOW PROCESSLIST, we found that lots of the threads weren't actually doing anything:<br />
<br />
<code>+------+------------+-----------+-----------------+----------------+------+--------------+------------------+<br />
| Id | User | Host | db | Command | Time | State | Info |<br />
+------+------------+-----------+-----------------+----------------+------+--------------+------------------+<br />
| 243 | tn | (..snip..)| tn_production | Sleep | 3000 | | NULL </code><br />
<br />
<br />
This was strange - but after a bit of a Googling and a few minutes thought, we realised the cause.<br />
<br />
When one of our worker processes dies - e.g. either because monit has killed it for taking up too much resource for too long, or because the EC2 instance it's running on has become unresponsive to ssh and been automatically restarted by our detect-and-rescue script - it may be in the middle of a transaction at the point it's killed.<br />
<br />
If the transaction was holding locks, and never got to the "COMMIT" stage, <em>and</em> the connection wasn't closed normally, then <strong>that connection will persist - and maintain its locks - until it gets timed-out by the MySQL server</strong>.... and the default wait_timeout variable is 28800s - <strong>8 hrs!</strong>.<br />
<br />
So, we killed the idle connections, and the update queries started going through much more quickly again. We've now brought our wait_timeout variable down to 10 mins, and we'll see how we get on.Alistair Davidsonhttp://www.blogger.com/profile/07415960315873865766noreply@blogger.com6tag:blogger.com,1999:blog-6799088.post-78125402403844982022011-05-11T10:30:00.000+01:002011-05-11T10:30:24.848+01:00New EU Cookie / Privacy Law - Should you panic?There's been a lot of fuss recently about the new "EU Cookie Law", and what effect it will have on EU- and UK-based online businesses. The EU directive has been around and <a href="http://eu.techcrunch.com/2011/03/09/stupid-eu-cookie-law-will-hand-the-advantage-to-the-us-kill-our-startups-stone-dead/">discussed with varying degrees of hyperbole</a> for a while, what's caused the recent kerfuffle has been the adoption into UK legislation pretty-much as-is.<br />
<br />
So, should you be panicking in order to meet the implementation date of 26th May 2011? <br />
<br />
Well... maybe, but I'm not. <br />
<br />
Allow me to explain.<br />
<br />
There is cause for concern - just like we saw with the RIPA act circa 2000, the legislation is clearly well-intentioned, but has just-as-clearly been implemented by people with little understanding of the problem domain. The full legislation is available from the horse's mouth here: <a href="http://www.legislation.gov.uk/uksi/2011/1208/contents/made">The Privacy and Electronic Communications (EC Directive) (Amendment) Regulations 2011</a>, but I would recommend the <a href="http://www.ico.gov.uk/~/media/documents/library/Privacy_and_electronic/Practical_application/advice_on_the_new_cookies_regulations.pdf">implementation guidelines from the Information Commissioner's Office</a> for a slightly lighter read. <br />
<br />
Credit where it's due, the guidelines give a welcome degree of balance and reasonableness to the situation, but even so they manage to contradict themselves.<br />
<br />
The important section is "What do the new rules say?", where it quotes the relevant section of the new legislation:<br />
<br />
<blockquote>6 (1) Subject to paragraph (4), a person shall not store or gain access to information stored, in the terminal equipment of a subscriber or user unless the requirements of paragraph (2) are met. <br />
(2) <strong>The requirements are that the subscriber or user</strong> of that terminal equipment-- <br />
(a) is provided with clear and comprehensive information about the purposes of the storage of, or access to, that information; and <br />
(b) <strong>has given his or her consent.</strong><br />
(3) Where an electronic communications network is used by the same person to store or access information in the terminal equipment of a subscriber or user on more than one occasion, it is sufficient for the purposes of this regulation that the requirements of paragraph (2) are met in respect of the initial use. <br />
<br />
(3A) For the purposes of paragraph (2), <strong>consent may be signified by a subscriber who amends or sets controls on the internet browser</strong> which the subscriber uses or by using another application or programme to signify consent.<br />
<br />
(4) Paragraph (1) shall not apply to the technical storage of, or access to, information-- <br />
(a) for the sole purpose of carrying out the transmission of a communication over an electronic communications network; or <br />
(b) where such storage or access is strictly necessary for the <br />
provision <br />
</blockquote><br />
<br />
Now, correct me if I'm wrong, but that seems fairly clear - if the user sets the controls on their internet browser to accept cookies, consent may be taken to be signified, right? Riiiiight?<br />
<br />
Well, apparently not. A little bit further down the document, it says:<br />
<br />
<blockquote>I have heard that browser settings can be used to indicate <br />
consent – can I rely on that?<br />
<em>(...)</em><br />
At present, most browser settings are not sophisticated enough to <br />
allow you to assume that the user has given their consent to allow <br />
your website to set a cookie<br />
</blockquote><br />
Er...wut? Most browsers are set to accept cookies by default, but can be changed to reject them, or to reject third-party cookies, or prompt for each one. How is that <em>not</em> giving consent? And how does this guidance interact with paragraph 3A from the regulations themselves?<br />
<br />
There's another implementation question as well. Let's walk through a workflow.<br />
<br />
<ol><li>User X arrives at a site</li>
<li>Site wants to set a cookie so that it can identify that User X's next click comes from User X, and not any of its other users.</li>
<li>..So Site has to ask for consent, presumably by an irritating and obtrusive pop-up window, or some other interstitial means. Remember that cookies are sent back as part of the HTTP response, so the decision regarding whether or not to set a cookie is taken on the server, before a response is sent back to the user. </li>
<li>If the user says "YES", all well and good - Site sets a cookie and remembers that choice.</li>
<li>BUT... what if the user says no? How do you remember that choice, without using a cookie? You can't, right? So you're going to have to ask every request...</li>
</ol><br />
OK, that's admittedly a simplified scenario for the purposes of making a point. But the point is valid - it's going to be extremely tricky to think of ways of implementing this directive in any meaningful way without destroying your user experience. And what about the ubiquitous third-party cookies that form the basis of services such as Google Analytics? Again, the guidelines are specific:<br />
<br />
<q>An analytic cookie might not appear to be as intrusive as others that might track a user across multiple sites but you still need consent</q><br />
<br />
And <em>THAT</em>, paradoxically, is why I'm not panicking. Laws that are very difficult to comply with are, in practice, difficult to enforce. This law, if it ever does get enforced, is most likely to be used as a political club to attack a high-profile mega-corporate that couldn't be brought down any other way - think MS and the eventually farcical browser-bundling lawsuit. Think Al Capone, and the fact that the only crime he ever got convicted of was tax evasion.<br />
<br />
There have also been some <a href="http://www.itpro.co.uk/631717/businesses-urged-to-prepare-for-eu-cookie-laws">surprisingly reasonable quotes from the Information Commissioner's Office and the Government</a> :<br />
<br />
<q>[Information Commissioner Christopher] Graham said the ICO was clear the changes should not have a detrimental impact on consumers nor cause “an unnecessary burden on UK businesses.”</q><br />
<br />
and <br />
<br />
<q>we do not expect the ICO to take enforcement action in the short term against businesses and organisations as they work out how to address their use of cookies</q> (Ed Vaizey, Culture Minister)<br />
<br />
Regardless, there may well end up being a high-profile test case at some point, which will garner huge amounts of publicity and huge amounts of lawyers saying things like "this is a very interesting case" - which, to paraphrase Terry Pratchett, is lawyer-speak for "at least six months at three grand a day, plus expenses, per lawyer, with a minimum team of ten". It will eventually establish a precedent for judicial interpretation of the act, and at that point the necessary course of action will become clear. <br />
<br />
Until then, I'm not panicking. And neither should you.Alistair Davidsonhttp://www.blogger.com/profile/07415960315873865766noreply@blogger.com0tag:blogger.com,1999:blog-6799088.post-30824221455441143132011-02-22T20:51:00.000+00:002011-02-22T20:51:44.750+00:00Rails requests not timing out in time?The web server CPU was mostly idle, the 5 thin instances didn't seem to be doing much, but the requests were still taking <em>forever</em> to return anything. So what the flimmin' flip was taking so long?<br />
<br />
Nginx was configured to timeout requests after 60s, but it didn't seem to be working. In fact, the key to figuring out what was going on, was that while tailing the logs, I saw an occasional query come through with a ridiculously long execution time - 400 seconds or more... Yowch!<br />
<br />
So, if requests were supposed to time out after 60 seconds, how come a query was allowed to stay executing for 400 or more?<br />
<br />
It all comes down to threads. We're still on CRuby 1.8.7 (MRI) in production - we'll move to 1.9 and JRuby at some point, but previous experience has made me careful to test thoroughly, test some more, then test again and again and <em>again</em> before making the move, and we're not quite ready to shift yet.<br />
<br />
Now, the default (MRI) CRuby interpreter uses <a href="http://en.wikipedia.org/wiki/Green_threads">green threads</a>, and the eventmachine gem underlying Thin server uses - like most Ruby libraries - timeout.rb for its timeout mechanism. This article <a href="http://ph7spot.com/musings/system-timer">gives the full details</a>, but it boils down to the fact that:<br />
<q>it is a well-known limitations of green threads that when a green thread performs a blocking system call to the underlying operating systems, none of the green threads in the virtual machine will run until the system call returns ... From the operating system perspective, there is only a single thread in the Ruby interpreter (the native one)</q> <br />
<br />
So essentially, the monitor thread which is responsible for timing out the request has to <em>wait</em> for the blocking IO call to return, before it can resume. This means that when you have a long-running, block IO call (say, for instance, an ActiveRecord query that takes 400 seconds to run) running under MRI, the default timeout mechanism is useless.<br />
<br />
The solution? If you can't just switch to JRuby, then here's a simple 5-minute fix that did the trick for us:<br />
<br />
in environment.rb :<br />
<br />
<code class="ruby"><br />
config.gem 'SystemTimer', :lib => 'system_timer'<br />
</code><br />
<br />
in application_controller.rb :<br />
<br />
<code class="ruby"><pre> around_filter :force_timeout
def force_timeout( timeout=nil, &block )
timeout ||= 60 # <= or whatever value is appropriate
if defined?(SystemTimer) && timeout.to_i > 0
begin
SystemTimer.timeout_after(timeout) do
yield
end
rescue Timeout::Error => e
logger.error( "Timed out request after #{timeout}s!")
raise "RequestTimeout"
end
else
yield
end
end
</pre></code>Alistair Davidsonhttp://www.blogger.com/profile/07415960315873865766noreply@blogger.com1tag:blogger.com,1999:blog-6799088.post-50628202692952785552010-07-02T14:27:00.003+01:002010-07-02T15:35:32.960+01:00"cannot redeclare exchange" error on amqp and RabbitMQ 1.8 - fixed! (at last)I've been having some trouble with an inherited MacBook Pro, on Leopard (OS X 10.5), trying to get RabbitMQ and the tmm1-amqp gem up and running. The annoying thing is that it was all working fine a few weeks ago, but I ended up having to completely remove MacPorts and do several sudo rm -rf wipeouts and pretty much rebuild the dev environment from scratch. Since then, I re-installed MacPorts and did:<br /><code>sudo port install rabbitmq-server</code><br /><br />After a bit of fiddling about, I got this error (using exactly the same code that worked a few weeks ago):<br /><br /><code>vendor/gems/tmm1-amqp-0.6.4/lib/mq.rb:225:in `process_frame': PRECONDITION_FAILED - cannot redeclare exchange 'jobs' in vhost 'trigganews.com' with different type, durable or autodelete value in AMQP::Protocol::Exchange::Declare on 1 (MQ::Error)<br /></code><br /><br /><em>....several hours of frustrating googling later.....</em><br /><br />It turns out that I'd innocently upgraded my version of RabbitMQ 1.8, whereas previously I'd had v1.7.2. This is significant because RMQ 1.8 uses AMQP 0.9, whereas v1.7.2 uses AMQP 0.8 - and <em>this</em> matters because I'm using the tmm1-amqp gem to connect to it, and <a href="http://groups.google.com/group/ruby-amqp/browse_thread/thread/999b33e0d76e0be9">tmm1-amqp v0.6.4 only supports AMQP 0.8</a>...<br /><br /><em>(HINT - check the files under the protocol directory to see what version your gem supports)</em><br /><br />So - time to downgrade RabbitMQ to 1.7.2 then. Should be straightforward, no? Well, no.<br /><br />It <em>also</em> turns out that the version of Erlang in MacPorts is now R14A - and guess what? <a href="https://trac.macports.org/ticket/25377">RabbitMQ 1.7.2 doesn't build in Erlang R14A</a> <br /><br /><br />So I had to create a local MacPorts repo (using the very helpful instructions <a href="http://journal.bitshaker.com/articles/2007/10/20/install-old-versions-of-ports-using-macports/">HERE</a>) from the <a href="http://trac.macports.org/browser/trunk/dports/lang/erlang/Portfile?rev=64916#L3">previous checkin of the Erlang R13B4 portfile</a> and point ports at that.<br /><br /> <br />Then, with a <code>sudo port uninstall rabbitmq-server; sudo port install rabbitmq-server @1.7.2</code>, followed by a blat of the mnesia database files:<br /><code>sudo rm -rf /opt/local/var/lib/rabbitmq/mnesia/rabbit</code><br /><br />- and it all works again. YAY!<br /><br />(PS - I had to port uninstall and re-install rabbitmq-server <em>twice</em> for it to work properly. So that's always worth a try too - I swear MacOS gets more and more like Windows every day.....:)Alistair Davidsonhttp://www.blogger.com/profile/07415960315873865766noreply@blogger.com3tag:blogger.com,1999:blog-6799088.post-5927844519401815112010-06-21T15:28:00.002+01:002010-06-21T15:42:46.728+01:00EAFNOSUPPORT socket connection issue on Mac OS XA frustrating problem cropped up this afternoon while trying to get a home-grown Ruby <a href="http://rabbitmq.com/">RabbitMQ</a> client to talk to the broker on a MacBook.<br /><br />The client is based on the <a href="http://rubyeventmachine.com/">EventMachine</a> and <a href="http://github.com/tmm1/amqp">AMQP</a> gems, and although for once it worked perfectly on Windows -<br /><br /><em>(.... yes, frame that last sentence for posterity folks....)</em><br /><br />- it was giving bizarre EAFNOSUPPORT socket errors on Mac.<br /><br />It turns out that, for various obscure reasons, resolving "localhost" is not entirely straightforward on the Mac. There are two quick-and-easy ways round it:<br /><br /><ol><li>If you have IPv6 mappings for localhost in your /etc/hosts file, comment them out</li><li>Replace "localhost" in your connection params with the explicit "127.0.0.1" instead</li></ol><br /><br />Sorted!Alistair Davidsonhttp://www.blogger.com/profile/07415960315873865766noreply@blogger.com0tag:blogger.com,1999:blog-6799088.post-47951074645420681892009-12-08T20:54:00.003+00:002009-12-08T22:28:46.766+00:00Memcached Cache Invalidation Made Easy<p><q>There are only two hard problems in computer science - cache invalidation, and naming things</q><br />Phil Karlton</p><br />It's an oft-quoted truism that brings a knowing smile to most hardened programmers, but it's oft-quoted precisely because it's true - and during a recent enforced rush job to implement a cache, I came across a nifty solution to the first problem by judicious use of the second.<br /><br />First, the problem - someone posted <a href="http://cragwag.com">Cragwag</a> on <a href="http://stumbleupon.com">StumbleUpon</a>, which led to an immediate spike in traffic on top of the slow increase I've been getting since I made it <a href="http://twitter.com/cragwag">Tweet the latest news</a>. All the optimisation work that I knew I needed to do at some point was more than a few hours work, and I had to get something out quickly - enter <a href="http://memcached.org/">memcached</a>.<br /><br />Memcached is a simple, distributed-memory caching server that basically stores whatever data you give it in memory, associated with a given key. Rails has a built-in client that you can use simply as follows:<br /><br /><code class="ruby">my_data = Cache.get(key) do { <br /> ... do stuff to generate data<br />}</code><br /><br />If the cache has an entry for the given key, it will return it straight from the cache. If not, the block will be called, and whatever is returned from the block will be cached with that key.<br /><br />So far so good - but what exactly should you cache, and how should you do it?<br /><br /><h4>The Complicated Way To Do It</h4><br />A common pattern is to cache ActiveRecord objects, say by wrapping the finder method in a cache call, and generating a key of the class name and primary key. But this only works for single objects, which are usually pretty quick to retrieve anyway, and is no use for the more expensive queries, such as lists of objects plus related objects and metadata, or - often particularly slow - searches.<br /><br />So you could extend that simple mechanism to cache lists of objects and search results, say by using the method name and the given parameters. But then you have an all-new headache - an object might be cached in many different collections, so how do you know which cache keys to purge? You have two options:<br /><br /><ul><li>Try and keep track of which cache keys are caching which objects? Eep - that's starting to sound nasty - you're effectively creating a meta-index of cached entries and keys, which would almost certainly be comparable in size to your actual cache... and where's that index going to live and how are you going to make sure that it's faster to search this potentially large and complex index than to just hit the damn database?</li><br /><li>Sidestep the invalidation problem by invalidating the entire cache whenever data is updated. This is much simpler, but there doesn't seem to be a "purge all" method - so you'd need to keep track of what keys are generated somewhere, then loop round them and delete them individually. You could do this with, say, an ActiveRecord class and delete the cache keys on a destroy_all - but still, that's icky.</li></ul><br /><br /><h4>The Easy Way To Do It</h4><br />After a few minutes Googling, I found <a href="http://blog.leetsoft.com/2007/5/22/the-secret-to-memcached">this post</a> on the way <a href="http://shopify.com">Shopify</a> have approached it, and suddenly it all became clear. You can solve the problem of Cache Invalidation by being cunning about Naming Things - in particular, your cache keys.<br /><br />The idea is very simple - Be Specific about exactly what you're caching. Read that post for more details, or read on for how I've done it.<br /><br />So I ripped out all of my increasingly-over-complicated caching code from the model, and went for a simple approach of caching the generated html in the controllers. At the start of each request, in a before_filter, I have one database hit - load the current CacheVersion - which just retrieves one integer from a table with only one record. Super fast - and if the data is cached, that's the only db hit for the whole request. <br /><br />The current cache version number is stored as an instance variable of the application controller, and prepended to all cache keys. The rest of the key is generated from the controller name, the action, and a string constructed out of the passed parameters. Any model methods that aren't just simple retrievals but affect data, can just bump up the current cache version, and hey presto - everything then gets refreshed on next hit, and the old version just gets expired on the least-recently-used-goes-first rule.<br /><br />This has a few very nice architectural benefits:<br /><br /><ul><li>The caching code is then in the "right" place - in the bit you want to speed up - i.e. the interface</li><li>You also eliminate the overhead of rendering any complicated views - you just grab the html (or xml, or json) straight from the cache and spit it back.</li><li>It utilises, and fits in with, one of the fundamental ideas of resource-based IA - that the URL (including the query string) should uniquely identify the resource(s) requested</li><li>The application controller gives you a nice central place to generate your keys</li><li>If you have to display different data to users, no problem - just put the user id as part of the key.</li><li>Rails conveniently puts the controller and action names into the params hash, so your cache key generation is very simple</li><li>The admin interface can then easily work off up-to-date data</li><li>You can also provide an admin "Clear the cache" button that just has to bump up the current cache version number.</li></ul><br /><br />Etc etc - I could go on, but I won't. The net result is that pages which used to take several seconds to render now take just a few milliseconds, it's much much simpler and more elegant this way, and if you're not convinced by now, just give it a try. <mrsdoyle>Go on - ah go on now, ah you will now, won't you Father?</mrsdoyle><br /><br /><h4>app/models/cache_version.rb</h4><br /><pre><code class="ruby">class CacheVersion < ActiveRecord::Base<br /> def self.current<br /> CacheVersion.find(:last) || CacheVersion.new(:version=>0)<br /> end<br /><br /> def self.increment<br /> cv = current <br /> cv.version = cv.version + 1<br /> cv.save<br /> end <br />end<br /></code></pre><br /><h4>app/controllers/application_controller.rb</h4><br /><pre><code class="ruby">require 'memcache_util'<br /><br />class ApplicationController < ActionController::Base<br /> # load the current cache_version from the db<br /> # this is used to enable easy memcache "expiration"<br /> # by simply bumping up the current version whenever data changes<br /> include Cache<br /> before_filter :get_current_cache_version<br /><br />private<br /><br /> def cache_key<br /> "#{@cache_version.version}_#{params.sort.to_s.gsub(/ /, '_')}"<br /> end<br /><br /> def get_current_cache_version<br /> @cache_version = CacheVersion.current<br /> end<br /><br /> def with_cache( &block )<br /> @content, @content_type = Cache.get(cache_key) do<br /> block.call<br /> [@content, @content_type]<br /> end<br /> render :text=>@content, :content_type=>(@content_type||"text/html") <br /> end<br />end<br /></code></pre><br /><br /><h4>in your actual controller:</h4><br /><pre><code class="ruby"> def index <br /> with_cache {<br /> # get data<br /> # NOTE: you must render to string and store it in @content<br /> respond_to do |format|<br /> format.html { <br /> @content = render_to_string :action => "index", :layout => "application" <br /> }<br /> format.xml {<br /> @content_type = "text/xml"<br /> @content = render_to_string :xml => @whatever, :layout=> false <br /> }<br /> end<br /> }<br /> end<br /></code></pre>Alistair Davidsonhttp://www.blogger.com/profile/07415960315873865766noreply@blogger.com15tag:blogger.com,1999:blog-6799088.post-44782244024263885932009-10-26T15:57:00.003+00:002009-10-26T16:04:04.807+00:00Specs failing with Daylight Saving Time change?So I've been banging my head for the past hour or so, trying to work out why some of our specs have suddenly started failing without the code having been touched, and it comes down to an inconsistency in how Rails is handling UTC offsets when Time objects are manipulated:<br /><br /><code><br />>> Time.now<br /> => Mon Oct 26 15:55:20 0000 2009<br />>> Time.now.utc<br />=> Mon Oct 26 15:55:26 UTC 2009<br /></code><br /><br />OK, that's fine. Now let's try a calculation:<br /><br /><code><br />>> (Time.now - 1.day).utc<br />[Sun Oct 25 14:55:41 UTC 2009<br /></code><br /><br />What? Where did that one hour offset come from??<br /><br />It turns out THAT is the source of all the specs which suddenly failed for no apparent reason this morning.<br /><br />The good news is, it's easy to fix:<br /><br />>> Time.now.utc - 1.day<br />=> Sun Oct 25 15:56:08 UTC 2009<br /><br />MUCH better!Alistair Davidsonhttp://www.blogger.com/profile/07415960315873865766noreply@blogger.com0tag:blogger.com,1999:blog-6799088.post-36772271900334726542009-10-26T13:41:00.003+00:002009-10-26T13:46:15.483+00:00Ooh, we're in IDC's "10 most innovative software companies"!Oooh, shiny! We just got named as one of IDC's <a href="http://www.reuters.com/article/pressRelease/idUS108772+26-Oct-2009+BW20091026">10 most innovative sub-$100m software companies to watch</a><br /><br />I guess we scored more highly on the "Web 2.0-like functionality moves into the enterprise" category more than the other two, and by itself it might not mean much, but it's still great to be thought of in those terms. Makes me feel all warm and fuzzly.Alistair Davidsonhttp://www.blogger.com/profile/07415960315873865766noreply@blogger.com0tag:blogger.com,1999:blog-6799088.post-13847080223441014812009-10-13T01:08:00.008+01:002009-10-21T07:58:04.408+01:00Al's Ultimate Lasagne RecipeOK, ok, so I've never posted a recipe on here before. BUT, pretty much everyone I've ever made this lasagne for has said <q>wow, you've GOT to give me the recipe for that!</q> - the most recent example being a guy who had spent years living in Italy, no less - and I can't sleep tonight, so here goes.<br /><br />Last year I added some refinements from <a href="http://www.npr.org/templates/story/story.php?storyId=6530258">Heston Blumenthal's 'Perfect' bolognese</a>, but mercifully this version takes about 3-4hrs, rather than 3 days. You can add whatever embellishments you like, here I'm just going to describe the basic sauce preparation.<br /><br />You will need:<br /><h4>Pans</h4><ul><br /> <li>A heavy-bottomed frying pan (our <a href="http://www.amazon.co.uk/Creuset-Satin-Black-Interior-Frying/dp/B000RK2DA8/ref=sr_1_9?ie=UTF8&s=kitchen&qid=1255393075&sr=8-9">Le Creuset</a> pan was perfect for this) that can get really hot</li><br /> <li>A large sauce pot / casserole dish</li><br /> <li>A large lasagne dish - deep enough for at least 3 layers of sauce, plus about half an inch of bechamel sauce</li><br /> <li>A medium sauce pan for the bechamel sauce</li><br /></ul><br /><h4>Ingredients</h4><p><em>NOTE: all quantities are approximate. Don't be the kind of cook who has to measure everything to the 3rd significant figure - taste often and see what you think it needs, it's much more fun!</em></p><ul><br /> <li>About 1kg of lean minced beef (preferably organic, or at least free range - we like Waitrose's, and 2 of their <a href="http://www.ocado.com/webshop/product/Organic-Lean-Ground-Beef-10-Fat-Waitrose/26383011">500g packs</a> works nicely)</li><br /> <li>About 400g of lardons (again, 2 packs of the <a href="http://www.shopwiki.co.uk/Free+Range+Smoked+Dry+Cure+Lardons+Waitrose">Waitrose Free-Range lardons</a> work nicely)</li><br /> <li>1 red onion, 1 white onion</li><br /> <li>1 stick of celery</li><br /> <li>1 or 2 carrots, depending on size - we're aiming for roughly equal quantities of diced carrot, red onion and celery, so adjust as needed</li><br /> <li>3 tins of chopped tomatoes</li><br /> <li>3 or 4 mushrooms - we like <a href="http://www.ocado.com/webshop/product/Organic-Portabellini-Mushrooms-Waitrose/18872011?parentContainer=SEARCHmushrooms">Portabellini</a><br /> <li>a bulb of garlic, the fresher the better</li><br /> <li>2 large bay leaves</li><br /> <li>2 large/4 small star anise</li><br /> <li>about 1tbsp Thai fish sauce (we like <a href="http://www.squidbrand.com/squideng/product.php#">Squid Brand</a>, which you should be able to get from any good Chinese food store)</li><br /> <li>about 1tbsp Lea & Perrins Worcester Sauce</li><br /> <li>about a third of a bottle of red wine</li><br /> <li>Maldon sea salt & freshly ground black pepper</li><br /> <li>A tablespoon of Marmite</li><br /> <li>Butter. Probably about 100g, maybe a bit more</li><br /> <li>Olive oil - it's best to NOT use extra virgin, you're going to be frying with it - but really, it doesn't make that much difference in the end</li><br /> <li>Lasagne sheets</li><br /> <li>A decent handful of basil leaves (MUST be fresh - dried is no good here)</li><br /> <li>A decent handful of fresh oregano (ditto)</li> <br /> <li>The <em>vine</em> from some vine tomatoes (that's where most of the smell comes from, not the tomato itself)</li><br /> <li>For the bechamel sauce - maybe 50g of plain flour and about 1/3 pint of milk, plus about 50g of good mature cheddar (<a href="http://www.ocado.com/webshop/product/Cathedral-City-Extra-Mature-Cheddar/27789011?parentContainer=SEARCHcathedral%20city">Cathedral City Extra Mature</a> works well).<br /></ul><br /><h4>Preparation (20 mins)</h4><ol><br /> <li>Dice the carrot, the red onion and the celery. We're aiming for equal quantities of each, in equal-sized pieces.</li><br /> <li>Chop the white onion - these pieces don't need to be the same size as the previous lot, they can be bigger and rougher.</li><br /> <li>Slice the mushrooms, so they're maybe half a centimetre thick</li><br /> <li>Lightly crush and peel all the cloves of garlic. I use a good whack with my fist on top of a large knife on top the clove. It doesn't need to be obliterated, just kind of half-crushed, so the skin comes off easily.</li><br /> <li>Take half of the semi-crushed garlic and chop it finely.</li><br /> <li>In a small bowl, crush (with your fingers is fine) a good tablespoon of sea salt and grind about an equal quantity of black pepper (you're going to be handling raw beef next - you don't want the juices from your hands to hang around on the pepper mill, do you?)</li><br /> <li>Season the beef - I tip one pack of beef onto the other, then separate the strands back into the empty pack. Each time you make a full layer, sprinkle on some salt and pepper</li><br /></ol><h4>Cooking stage 1 (20 mins)</h4><ol><br /> <li>In the big pot, pour a good layer of olive oil. Heat over a low-to-medium heat.</li><br /> <li>Add the semi-crushed (not chopped) garlic cloves. Cook until they're just going golden but still soft (burnt garlic is bitter and grim), then remove them and save for later</li><br /> <li>Increase the heat slightly, and add the diced red onion, carrot, and celery. Stir these occasionally while they soften.</li><br /> <li>Put the heavy-bottom frying pan over a medium-to-high heat, and add some olive oil</li><br /> <li>In the frying pan, add the chopped white onion and star anise. Fry until the onions are caramelising - i.e. going golden brown. Then tip the contents of the frying pan into the big pot (<strong>before</strong> the onions start to burn and go bitter.</li><br /> <li>In the frying pan, turn the heat up to full and start to sear the beef. Do this layer-by-layer - ALL the beef must be touching the pan, otherwise it'll broil instead of searing, so just do a little at a time. Keep it moving until it's browned outside but pink in the middle, then tip into the big pot. Repeat until all the beef is seared.</li><br /> <li>...remember to keep stirring the big pot every so often!</li><br /> <li>In the frying pan, add a big lump of butter - probably 50g or so. This will sizzle and spit for a while. Once it's stopped sizzling, all the water has gone out of it, so then add the chopped garlic and mushrooms and saute these until they're golden brown at the edges but still plump and juicy. Then tip them into the large pot (the butter will help give the sauce a nice sheen)</li><br /> <li>Make sure the frying pan is hot, then sear the lardons. You're looking for golden crispy edges, but plump and juicy pinkness. These will probably release a fair amount of fat into the pan - this is fine, it's all good for the sauce. When done, tip everything into the big pot.</li><br /> <li>Now de-glaze the frying pan by tipping the red wine into it and keep stirring it and scraping the tasty bits off the bottom until the wine is reduced by about half. Tip into the large pot. You can now wash the frying pan - everything from now on takes place in the large pot (except the bechamel sauce)</li><br /> <li>In the large pot, add the tomatoes and stir well.</li><br /> <li>Add the Thai fish sauce and Worcester sauce, stir, and reduce the heat to low-to-medium</li> <br /> <li>Crack the bay leaves in half, and add to the pot</li><br /> <li>Chop and add the lightly-browned garlic that you used to flavour the oil with, right back at the start</li><br /></ol><h4>Reducing the sauce (1hr-2hrs)</h4><ol><br /> <li>Leave the sauce pot simmering with the lid half-on for about an hour, stirring occasionally and adding pepper if needed. (Taste!)</li><br /> <li>Sometimes I add a tablespoon of Marmite if the sauce needs more depth, sometimes not - depends on the ingredients</li><br /> <li>It's done when it's done - i.e. when it looks and tastes like really good bolognese-style sauce, and isn't too runny or too dry.</li><br /> <li>When it *is* done, turn the heat off and leave it to cool with the lid on for about 45mins</li><br /> <li>Once it's cool, chop the basil and oregano and stir through. At this point, you can add the vine of the tomatoes and leave it to rest and infuse for about half an hour, then take the vine out again.</li><br /></ol><h4>Layer the lasagne (5 mins)</h4><ol><br /> <li>Put the oven onto 180 degrees C to heat up</li><br /> <li>Put a layer of sauce in the bottom of the lasagne dish, then add a layer of lasagne</li><br /> <li>Repeat at least once, preferably twice (depending on the size of your dish and how much sauce you have) until you've used all the sauce - but make sure that your top layer is sauce, not pasta!</li><br /></ol><h4>Bechamel Sauce (5 mins)</h4><ol><br /> <li>In the saucepan, melt about 50g of butter over a medium heat</li><br /> <li>Add the flour and stir quickly until you have a good consistency - still "smearable", not a dry lump</li><br /> <li>Begin adding milk a little at a time and stirring until absorbed. Make sure you don't add too much too quickly or it'll curdle.</li><br /> <li>When you have a nice medium-thick-but-still-easily-pourable sauce, add the grated cheese and grind some more pepper into it.</li><br /> <li>Stir until the cheese is all melted, then pour over the top of your lasagne, making sure it's all covered</li><br /> <li>Grate a bit more cheese on top</li><br /></ol><h4>Final baking (45 mins)</h4><ol><br /> <li>Bake the lasagne in the oven at 180 degrees C for about 45 mins, or until the top is golden brown and ever so slightly crispy around the edges.</li><br /> <li>Remove from the oven and leave to rest for about ten minutes before serving</li><br /></ol><br /><br />And that's it! Quality of ingredients counts for a lot, but the biggest clincher is the care taken to make sure that each ingredient is individually well prepared and cooked before adding to the pot. Enjoy!Alistair Davidsonhttp://www.blogger.com/profile/07415960315873865766noreply@blogger.com4tag:blogger.com,1999:blog-6799088.post-40524752357071266762009-10-03T10:03:00.003+01:002009-10-03T11:33:17.273+01:00Blogspot ATOM in Feed-normalizer / Simple-RSSBoth <a href="http://cragwag.com">Cragwag</a> and <a href="http://sybilline.com">Sybilline</a> are using the excellent <a href="http://feed-normalizer.rubyforge.org/">Feed-normalizer</a> for parsing RSS and ATOM feeds, but there's been a <a href="http://code.google.com/p/feed-normalizer/issues/detail?id=30">niggling problem with the ATOM generated by Blogger / Blogspot in particular</a> - the resulting links on each entry end up pointing to the comments, not the post itself. <br /><br />So I just forked simple-rss at github and fixed this.<br /><br />Turns out that simple-rss is just taking the first link tag that it comes across and using that as the link for a post, which in the case of Blogspot ATOM is the comments link.<br /><br />On inspection of the <a href="http://www.ietf.org/rfc/rfc4287.txt">ATOM RFC</a> it says (section 4.2.7.2) :<br /><br /><q>atom:link elements MAY have a "rel" attribute that indicates the link relation type. If the "rel" attribute is not present, the link element MUST be interpreted as if the link relation type is "alternate".</q><br /><br />Looking at the Blogspot ATOM, it looks like every element has a link rel="alternative" that points to the URL you would see if you navigated to the post from the blog homepage, so I've made it choose that link if it exists.<br /><br />Github should build the gem automatically - but it's taking a long time to do it, so in the meantime, you can download it from <a href="http://github.com/aldavidson/simple-rss">http://github.com/aldavidson/simple-rss</a> and build it locally:<br /><br /><br /><code>gem uninstall simple-rss<br />cd (source root)<br />rake gem<br />cd pkg<br />gem install -l simple-rss<br /></code><br /><br />That should fix the problemAlistair Davidsonhttp://www.blogger.com/profile/07415960315873865766noreply@blogger.com1tag:blogger.com,1999:blog-6799088.post-61397830323810034962009-09-08T19:44:00.004+01:002012-04-10T18:33:59.764+01:00How to create and save an AMI image from a running instanceOne snag I encountered early on in my migration of <a href="http://cragwag.com">Cragwag</a> and <a href="http://sybilline.com">Sybilline</a> to Amazon's EC2 Cloud, was that I needed to take a snapshot of my running instance and save it as a new Amazon Machine Image (AMI).<br /><br />I'd created a bare-bones Debian image from a public AMI (32-bit Lenny, 5.0, not much else) and then installed a few standard software packages on it - mysql, ruby, apache, etc etc etc. Once I'd got them configured the way I wanted, it had taken a couple of hours (I'll go into the configuration relating to EBS in a separate post) so I wanted to snapshot this instance as a new AMI image. That way, if and when I needed to create a new instance, all of this work would already have been done.<br /><br />It actually took a fair amount of time to find out (well, more than a few seconds Googling, which is just <em>eternity</em> these days, y'know?) so I'll save you the pain and just give you the solution.<br /><br />First, install Amazon's AMI tools, and API tools:<br /><br /><pre><code class="bash"><br />export EC2_TOOLS_DIR=~/.ec2 #(or choose a directory here)<br />cd $EC2_TOOLS_DIR<br />mkdir ec2-ami-tools<br />cd ec2-ami-tools<br />wget http://s3.amazonaws.com/ec2-downloads/ec2-ami-tools.zip<br />unzip ec2-ami-tools.zip<br />ln -s ec2-ami-tools-* current<br />cd ..<br />mkdir ec2-api-tools<br />cd ec2-api-tools<br />wget http://s3.amazonaws.com/ec2-downloads/ec2-api-tools.zip<br />unzip ec2-api-tools.zip<br />ln -s ec2-api-tools-* current<br /><br />echo "export EC2_AMITOOL_HOME=`dirname $EC2_TOOLS_DIR`/ec2-ami-tools/current" >> ~/.bashrc<br />echo "export EC2_APITOOL_HOME=`dirname $EC2_TOOLS_DIR`/ec2-api-tools/current" >> ~/.bashrc<br />echo "export PATH=${PATH}:`dirname $AMI_TOOLS_DIR`/ec2-ami-tools/current/bin:`dirname $AMI_TOOLS_DIR`/ec2-api-tools/current/bin" >> ~/.bashrc<br />source ~/.bashrc<br /></code></pre><br /><br />Next, you'll need to get your security credentials. You can get a reminder of - or create as needed - these on the AWS "Your Account" > "Security Credentials" page.<br /><br />I recommend you saving your X.509 certificate and your private key somewhere under /mnt/ - this directory is excluded from the bundled image. Quite important that, as otherwise your credentials would be bundled up in the image - and if you ever shared that image with anyone else, you'd be sharing your credentials too!<br /><br />You'll also need to note your AWS access details - especially your access key and secret key - plus your Amazon account ID. <br /><br />Now, we're at the main event.<br /><br />To take a snapshot of your running instance:<br /><br />First, choose a name for your AMI snapshot. We'll call it ami-instance-name :)<br /><br /><pre><code class="bash"><br /># make a directory for your image:<br />mkdir /mnt/ami-instance-name<br /><br /># create the image (this will take a while!)<br />ec2-bundle-vol -d /mnt/ami-instance-name -k /path/to/your/pk-(long string).pem -c /path/to/your/cert-(long string).pem -u YOUR_AMAZON_ACCOUNT_ID_WITHOUT_DASHES</code></pre><br /><br />Once that's done, you should have a file called image.manifest.xml in your /mnt/ami-instance-name directory, along with all the bundle parts. Sometimes it will say <q>Unable to read instance meta-data for product-codes</q> - but this doesn't seem to cause any problems, and I've successfully ignored it so far :)<br /><br />Next, upload the AMI image to S3. This command will create an S3 bucket of the given name if it doesn't exist - I've found it convenient to call my buckets the same as the instance name: <br /><br /><code class="bash"><pre>ec2-upload-bundle -b ami-instance-name -m /mnt/ami-instance-name/image.manifest.xml -a YOUR_AWS_ACCESS_KEY -s YOUR_AWS_SECRET_KEY</code></pre> <br /><br />You should then be able to register the instance. I've done that using the rather spiffy AWS Management Console web UI, but you can also do it from the command line using:<br /><br /><code class="bash"><pre>ec2-register ami-instance-name/image.manifest.xml</pre></code><br /><br />And that's it!<br /><br />Of course, you could be cunning and create a script that does it all in one. I've got my AWS/EC2 credentials stored in environment variables from my .bashrc:<br /><br /><pre><code class="bash">export EC2_PRIVATE_KEY=/mnt/keys/pk-(long string).pem<br />export EC2_CERT=/mnt/keys/cert-(long string).pem<br />export AWS_ACCOUNT_ID=(my account id)<br />export AWS_ACCESS_KEY=(my AWS access key)<br />export AWS_SECRET_KEY=(my AWS secret key)</code></pre><br /><br />which means I can make, upload and register an instance in one, by running this script:<br /><br /><pre><code class="bash">#!/bin/bash<br /><br />$AMI_NAME=$1<br /><br />ec2-bundle-vol -d /mnt/images/$1 -k $EC2_PRIVATE_KEY -c $EC2_CERT -u $AWS_ACCOUNT_ID<br />ec2-upload-bundle -b $1 -m /mnt/images/$1/image.manifest.xml -a $AWS_ACCESS_KEY -s $AWS_SECRET_KEY<br />ec2-register $1/image.manifest.xml<br /></code></pre><br /><br />...and giving it a parameter of ami-instance-name. I have that script saved as make_ami.sh, so I can just call, for instance:<br /><br /><pre><code class="bash">make_ami.sh webserver-with-sites-up-and-running</code></pre><br /><br />...and go have a cup of coffee while it does it's thing.Alistair Davidsonhttp://www.blogger.com/profile/07415960315873865766noreply@blogger.com11tag:blogger.com,1999:blog-6799088.post-7477254580572735692009-09-08T18:43:00.004+01:002009-09-08T19:42:45.359+01:00Moving a site to The CloudLast week I did a lot of reading and research into cloud hosting. The "Cloud" has been a buzzword for a while now, often bandied about by those who know no better as a simple sprinkle-on solution for all of your scale problems - much in the same way as AJAX was touted around a few years ago as a magic solution to all of your interface problems. <br /><br />The perception can sometimes seem to be <q>Hey, if we just shift X to The Cloud, we can scale it infinitely!</q>. The reality, of course, is something rather more qualified. Yes, <em>in theory</em>, the cloud has all the capacity you're likely to need, unless you're going to be bigger than, say, Amazon - <em>(are you? Are you reeeeeeeally? C'mon, be honest...)</em> - provided - and it's a big proviso - that you architect it correctly. You can't just take an existing application and dump it into the cloud and expect to never have a transaction deadlock again, for instance. That's an application usage pattern issue that needs to be dealt with in your application, and no amount of hardware, physical or virtual, will solve it.<br /><br />There are also some constraints that you'll need to work around, that may seem a little confusing at first. But once I got it, the light went on, and I became increasingly of the opinion that the cloud architecture is just sheer bloody genius.<br /><br />What kind of constraints are they? Well, let's focus on Amazon's EC2, as it's the most well-known....<br /><br /><ul><li><strong>Your cloud-hosted servers are instances of an image</strong><br/>They're not physical machines - you can think of them as copies of a template Virtual Machine, if you like. Like a VMWare image. OK, that one's fairly straightforward. Got it? Good. Next:</li><br /><li><strong>Instances are transient - they do not live for ever</strong><br />Bit more of the same, this means that you create and destroy instances as you need. The flipside is that there is no <em>guarantee</em> that the instance you created yesterday will still be there today. It should be, but it might not be. <a href="http://getsatisfaction.com/cohesiveft/topics/why_do_my_google_app_engine_sdk_ec2_instances_keep_dying">EC2 instances do die</a>, and when they do, they can't be brought back - you need to create a new one. This is by design. Honestly!</li><br /><li><strong>Anything you write to an instance's disk after creation is <em>non-persistent</em></strong><br />Now we're getting down to it. This means that if you create an instance of, say, a bare-bones Linux install, and then install some more software onto it, and set up a website, then the instance dies - everything you've written to that instance's disk is <strong>GONE</strong>. There are good strategies for dealing with this, which we'll come onto next, but this is <em>also</em> by design. Yes, it is...</li><br /><li><strong>You can attach <a href="http://aws.amazon.com/ebs">EBS</a> persistent storage volumes to an instance - but only to <em>one instance per volume</em></strong><br />This one is maybe the most obscure constraint but is quite significant. Take a common architecture of two load-balanced web servers with a separate database server. It's obvious that the database needs to be stored on a persistent EBS volume - but what if the site involves users uploading files? Where do they live? A common pattern would be to have a shared file storage area mounted onto both web servers - but if an EBS volume can only be attached to one instance, you can't do that.</li></ul><br />Think about that for a few seconds - this has some pretty serious implications for the architecture of a cloud-hosted site. BUT - and here's the <q>sheer bloody genius</q> - these are the kind of things you'd have to deal with for scaling out a site on physical servers <em>anyway</em>. Physical hardware and especially disks are not infallible and shouldn't be relied on. Servers can and do go down. Disks conk out. Scaling out horizontally needs up-front thought put into the architecture. The cloud constraints simply force you to accept that, and deal with it by designing your applications with horizontal scaling in mind from the start. And, coincidentally, provide some kick-ass tools to help you do that.<br /><br />Take, for example, the last bullet point above - that EBS volumes can only be attached to one instance. So how do you have file storage shared between N load-balanced web servers? Well, the logical thing to do is to have a separate instance with a big persistent EBS volume attached to it, and have the web servers access it by some defined API - WebDAV, say, or something more application-specific.<br /><br />But hang on.... isn't that what you should be doing <em>anyway?</em>. Isn't that a more scalable model? So that when your fileserver load becomes large, you could, say, create more instances to service your file requests, and maybe load-balance <em>those</em>, and.... <br /><br />See? It forces you to <em>do the right thing</em> - or, at least, put in the thought up front as to how you'll handle it. And if you then decide to stubbornly go ahead and do the <em>wrong</em> thing, then that's up to you... :)<br /><br />So, anyway, I wanted to get my head round it, and thought I'd start by shifting <a href="http://cragwag.com">Cragwag</a> and <a href="http://sybilline.com">Sybilline</a> onto Amazons EC2 cloud hosting service. I did this over a two day period - most of which, it has to be said, was spent setting up Linux the way I was used to, rather than the cloud config - and I'll be blogging a few small, self-contained articles with handy tips I've learned along the way. Stay tuned....Alistair Davidsonhttp://www.blogger.com/profile/07415960315873865766noreply@blogger.com1tag:blogger.com,1999:blog-6799088.post-74173030176857586402009-08-17T22:58:00.015+01:002009-08-17T23:42:24.294+01:00Styling the Rails auto_complete pluginOver the weekend I was having a fiddle around with the layout on <a href="http://cragwag.com">Cragwag</a>, as the existing design was a bit, shall we say, <q>emergent</q>. I'd being trying to avoid a LHS column, because everything always seems to end up with one, but in the end I had to give up and just go with it. The auto-tag cloud just makes more sense over there, and there's no point fighting it.<br /><br />Anyway, the next question was what else to put there? I was looking for an intermediate stop-gap search until I got round to putting a proper <a href="http://lucene.apache.org">Lucene</a>-based search in there, and so the thought struck - I'd been looking for a way to browse tags that weren't necessarily in the top 20 (e.g. <q>show me all the posts about <a href="http://davemcleod.blogspot.com">Dave McLeod's</a> 2006 <a href="http://cragwag.com/posts?term=E11">E11</a> route), so why not try an auto-suggest tag search? <br /><br />So I found DHH's <a href="http://github.com/rails/auto_complete/tree/master">auto_complete plugin</a> (it used to be part of Rails core until version 2) and got to work. This should be easy, right?<br /><br />Well...<br /><br />I'll cut out the frustrations and skip to the solutions. :)<br /><br />The documentation on this plugin is virtually non-existent, and there some extra bits you'll need - but I found <a href="http://codeintensity.blogspot.com/2008/02/auto-complete-text-fields-in-rails-2.html">this post</a> very helpful.<br /><br />One minor irritation I found was that it writes out a bunch of css attributes directly into your HTML inside a style tag - <em>including</em> a <a href="http://github.com/rails/auto_complete/blob/7b18f05af68dec0f9e45b1a01d6b7c94e306caeb/lib/auto_complete_macros_helper.rb">hard-coded absolute width of 350px.</a> (see the auto_complete_stylesheet method)<br /><br /><q>Argh!</q> said I, as my search needed to fit into a 150px width.<br /><br />So how can you get round this? Simple - you <em>can</em> override the inline CSS in your stylesheet, provided you provide a CSS selector with <em>higher specificity</em><br /><br />Now, <a href="http://www.smashingmagazine.com/2007/07/27/css-specificity-things-you-should-know/">CSS specificity can be a fairly complicated topic</a>, but I usually just remember it like this - if you've got two rules that apply to a particular thing, the <em>more specific</em> rule wins.<br /><br />In this case, the inline CSS selector from the plugin:<br /><pre><code> div.auto_complete {<br /> width: 350px;<br /> background: #fff;<br />}</code></pre><br />gets trumped by Cragwag's more specific selector:<br /><pre><code>div#content div#lh_sidebar div.auto_complete {<br /> width: 150px;<br /> background: #fff;<br />}<br /></code></pre><br /><br />The first applies to any div with class="auto_complete", but the second applies only to divs with class="auto_complete" <em>which are inside a div with id="lh_sidebar" which is inside a div with id="content"</em>. So that's a <em>more specific</em> rule, so it wins.<br /><br />Yay!Alistair Davidsonhttp://www.blogger.com/profile/07415960315873865766noreply@blogger.com5tag:blogger.com,1999:blog-6799088.post-6705023005419500252009-08-12T10:53:00.004+01:002009-08-12T12:26:36.487+01:00OutOfMemoryError in ActiveRecord-JDBC on INSERT SELECTDuring some scale testing the other day, we came across this unusual / mildly amusing error in a database-bound command that just funnels INSERT SELECT statements down the ActiveRecord JDBC driver:<br /><br /><pre><code><br />java/util/Arrays.java:2734:in `copyOf': java.lang.OutOfMemoryError: Java heap space (NativeException)<br /> from java/util/ArrayList.java:167:in `ensureCapacity'<br /> from java/util/ArrayList.java:351:in `add'<br /> from com/mysql/jdbc/StatementImpl.java:1863:in `getGeneratedKeysInternal'<br /> from com/mysql/jdbc/StatementImpl.java:1818:in `getGeneratedKeys'<br /> from org/apache/commons/dbcp/DelegatingStatement.java:318:in `getGeneratedKeys'<br /> from jdbc_adapter/JdbcAdapterInternalService.java:668:in `call'<br /> from jdbc_adapter/JdbcAdapterInternalService.java:241:in `withConnectionAndRetry'<br /> from jdbc_adapter/JdbcAdapterInternalService.java:662:in `execute_insert'<br /> ... 25 levels...<br /></code></pre><br /><br />Now, there's a couple of things here that are worth pointing out.<br /><br /><ol><li>I <em>REALLY LOVE</em> the fact that it blew heap space in a method called <q>ensureCapacity</q>. That makes me smile.</li><li>Why is it calling getGeneratedKeys() for an INSERT SELECT?</li></ol><br /><br />The getGeneratedKeys() method retrieves all the primary keys that are generated when you execute an INSERT statement. Fair enough - BUT the issue here is that we'd specifically structured the process and the SQL statements involved so as to be done with INSERT SELECTS, and hence <em>avoid</em> great chunks of data being transferred backwards and forwards between the app and the database.<br /><br />It turns out that the ActiveRecord JDBC adapter is doing this : <br />(lib/active_record/connection_adapters/JdbcAdapterInternalService.java)<br /><pre><code><br />@JRubyMethod(name = "execute_insert", required = 1)<br /> public static IRubyObject execute_insert(final IRubyObject recv, final IRubyObject sql) throws SQLException {<br /> return withConnectionAndRetry(recv, new SQLBlock() {<br /> public IRubyObject call(Connection c) throws SQLException {<br /> Statement stmt = null;<br /> try {<br /> stmt = c.createStatement();<br /> smt.executeUpdate(rubyApi.convertToRubyString(sql).getUnicodeValue(), Statement.RETURN_GENERATED_KEYS);<br /> return unmarshal_id_result(recv.getRuntime(), stmt.getGeneratedKeys());<br /> } finally {<br /> if (null != stmt) {<br /> try {<br /> stmt.close();<br /> } catch (Exception e) {<br /> }<br /> }<br /> }<br /> }<br /> });<br /> }<br /><br /></code></pre><br />...in other words, explicitly telling the driver to return all the generated keys.<br />Hmm, OK, can we get round this by NOT calling the execute_insert method, and instead calling a raw execute method that <em>doesn't</em> return all the keys?<br /><br />Well, no, unfortunately, because it also turns out that the ruby code is doing this:<br />(activerecord-jdbc-adapter-0.9/lib/active_record/connection_adapters/jdbc_adapter.rb)<br /><pre><code><br /> # we need to do it this way, to allow Rails stupid tests to always work<br /> # even if we define a new execute method. Instead of mixing in a new<br /> # execute, an _execute should be mixed in.<br /> def _execute(sql, name = nil)<br /> if JdbcConnection::select?(sql)<br /> @connection.execute_query(sql)<br /> elsif JdbcConnection::insert?(sql)<br /> @connection.execute_insert(sql)<br /> else<br /> @connection.execute_update(sql)<br /> end<br /> end<br /></code></pre><br /><br />...and the JdbcConnection::insert? method is detecting if something's an insert by doing this:<br />(JdbcAdapterInternalService.java again)<br /><pre><code><br />@JRubyMethod(name = "insert?", required = 1, meta = true)<br /> public static IRubyObject insert_p(IRubyObject recv, IRubyObject _sql) {<br /> ByteList bl = rubyApi.convertToRubyString(_sql).getByteList();<br /><br /> int p = bl.begin;<br /> int pend = p + bl.realSize;<br /><br /> p = whitespace(p, pend, bl);<br /><br /> if(pend - p >= 6) {<br /> switch(bl.bytes[p++]) {<br /> case 'i':<br /> case 'I':<br /> switch(bl.bytes[p++]) {<br /> case 'n':<br /> case 'N':<br /> switch(bl.bytes[p++]) {<br /> case 's':<br /> case 'S':<br /> switch(bl.bytes[p++]) {<br /> case 'e':<br /> case 'E':<br /> switch(bl.bytes[p++]) {<br /> case 'r':<br /> case 'R':<br /> switch(bl.bytes[p++]) {<br /> case 't':<br /> case 'T':<br /> return recv.getRuntime().getTrue();<br /> }<br /> }<br /> }<br /> }<br /> }<br /> }<br /> }<br /> return recv.getRuntime().getFalse();<br /> }<br /></code></pre><br /><br />...in other words, if the sql contains the word <q>INSERT</q>, then it's an INSERT, and should be executed with an execute_insert call.<br /><br />So, looks like we're a bit knacked here. There are two possible solutions:<br /><br /><ol><li>The <q>proper</q> solution - fix the AR JDBC adapter (and, arguably, the MySQL connector/J as well, to stop it blowing heap space), submit a patch, wait for it to be accepted and make it into the next release.</li><br /><li>Or, the <q>pragmatic</q> solution - rewrite the SQL-heavy command as a stored procedure and just call it with an EXECUTE and sod the <a href="http://www.loudthinking.com/arc/2005_09.html">DHH dogma</a>.</li></ol><br /><br />We went with option 2 :)<br /><br />Big kudos to <a href="http://www.drmaciver.com/">D. R. MacIver</a> for tracking down the source of the fail in double-quick time.Alistair Davidsonhttp://www.blogger.com/profile/07415960315873865766noreply@blogger.com3tag:blogger.com,1999:blog-6799088.post-40816473008518640372009-08-03T10:24:00.006+01:002009-08-03T12:13:28.202+01:00Introducing Cragwag.com!You know those conversations you end up having in the pub, where after a couple of beers you end up saying "you know what there should be? There should be a site that does X" (where X can be anything at all)<br /><br />I've had so many of those over the years, and never quite managed to work up the free time / motivation to actually get on and put the ideas into practice..... (and then what's tended to happen is that a couple of years later, someone else goes and does them and makes a fortune, but that's just sods law) <br /><br />Well a few months ago, I decided enough was enough, and the next time I had one of those ideas, I should just stop talking about it and actually <em>do</em> it - so in my evening and weekends here and there, I've been noodling away on a couple of ideas, mainly just for my own amusement, and to keep me in the habit of actually following through on things.<br /><br />So here's one of them - <a href="http://cragwag.com">Cragwag</a> - a climbing news aggregator.<br /><br />Those of you who know me in person know that I'm a keen amateur climber. I'm under no illusions - I'm <em>definitely</em> in the "amateur" category for good reason :) - but it struck me that although there's a "definitive" go-to site for uk-centric climbing news ( <a href="http://ukclimbing.com">UK Climbing.com</a> ) it's still editorially filtered - an editor keeps himself up to date on everything that's going on in the scene, and then publishes what he thinks is significant.<br /><br />That's all well and good, but typically what's significant is what's going on at the forefront of ability. I felt there was also a place for the (admittedly by-now-a-little-cliched) long tail of climbing-related blogs - unfiltered, un-edited, everybody's tales. Whether of heart-stopping epics in the Himalaya, or scrabbling up a Stanage slab. If you felt it enough to blog about it, then someone wants to hear about it.<br /><br />Plus it was an experiment in automatic news tagging and cross-relating of posts based on content, so it was kind of techie fun too. Which is important :) I'd like to do something with a crowd-sourced google map and iphone gps too, but that's much more experimental. Need to learn the iPhone SDK first...<br /> <br />So that's <a href="http://cragwag.com">Cragwag - all the climbing news from punters and pros alike</a>. Just for fun, for the sake of an experiment, and - to paraphprase the most famous answer to "why?" of all time - because it wasn't there :) Yay!Alistair Davidsonhttp://www.blogger.com/profile/07415960315873865766noreply@blogger.com0