<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

 <title>nonplus</title>
 <link href="http://nonplus.mehttp://nonplus.me/atom.xml" rel="self"/>
 <link href="http://nonplus.mehttp://nonplus.me/"/>
 <updated>2017-07-08T16:21:00+00:00</updated>
 <id>http://nonplus.me</id>
 <author>
   <name>scotts</name>
   <email>scott@nonplus.me</email>
 </author>

 
 <entry>
   <title>Using Google's Closure Compiler in Phing</title>
   <link href="http://nonplus.me/2011/09/19/using-googles-closure-compiler-in-phing/"/>
   <updated>2011-09-19T21:46:00+00:00</updated>
   <id>http://nonplus.mehttp://nonplus.me/2011/09/19/using-googles-closure-compiler-in-phing</id>
   <content type="html">&lt;p&gt;&lt;a href=&quot;http://www.phing.info/trac/&quot;&gt;Phing&lt;/a&gt; is a build tool for automating and managing php deployments. One great feature is the ability to &lt;a href=&quot;http://www.phing.info/docs/guide/stable/chapters/appendixes/AppendixC-OptionalTasks.html#JsMinTask&quot;&gt;minify your .js files&lt;/a&gt; during the build process.  Unfortunately Phing uses jsmin to accomplish this instead of the newer, more efficient compilers like &lt;a href=&quot;http://code.google.com/closure/compiler/&quot;&gt;Google’s Closure Compiler&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To add minifying via Closure I created &lt;a href=&quot;https://github.com/mrowl/phing-tasks/blob/master/tasks/my/JsCompileTask.php&quot;&gt;this Phing task&lt;/a&gt;. The API is very similar to that of jsmin, e.g.&lt;/p&gt;

&lt;div class=&quot;CodeRay&quot;&gt;
  &lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;tag&quot;&gt;&amp;lt;taskdef&lt;/span&gt; &lt;span class=&quot;attribute-name&quot;&gt;name&lt;/span&gt;=&lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;delimiter&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;jsCompile&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;attribute-name&quot;&gt;classname&lt;/span&gt;=&lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;delimiter&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;phing.tasks.my.JsCompileTask&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;tag&quot;&gt;/&amp;gt;&lt;/span&gt;
&lt;span class=&quot;tag&quot;&gt;&amp;lt;jsCompile&lt;/span&gt; &lt;span class=&quot;attribute-name&quot;&gt;compilerPath&lt;/span&gt;=&lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;delimiter&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;docroot/bin/compiler.jar&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;attribute-name&quot;&gt;targetPath&lt;/span&gt;=&lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;delimiter&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;docroot/script/minified&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;attribute-name&quot;&gt;failOnError&lt;/span&gt;=&lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;delimiter&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;tag&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;tag&quot;&gt;&amp;lt;fileset&lt;/span&gt; &lt;span class=&quot;attribute-name&quot;&gt;dir&lt;/span&gt;=&lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;delimiter&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;docroot/script&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;tag&quot;&gt;&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;tag&quot;&gt;&amp;lt;include&lt;/span&gt; &lt;span class=&quot;attribute-name&quot;&gt;name&lt;/span&gt;=&lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;delimiter&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;**/*.js&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;tag&quot;&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;tag&quot;&gt;&amp;lt;/fileset&amp;gt;&lt;/span&gt;
&lt;span class=&quot;tag&quot;&gt;&amp;lt;/jsCompile&amp;gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Happy Phinging!&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Using bits to store tags, or perhaps not</title>
   <link href="http://nonplus.me/2011/09/15/using-bits-to-store-tags-or-not/"/>
   <updated>2011-09-15T16:35:00+00:00</updated>
   <id>http://nonplus.mehttp://nonplus.me/2011/09/15/using-bits-to-store-tags-or-not</id>
   <content type="html">&lt;p&gt;In a &lt;a href=&quot;http://nonplus.me//2011/08/16/using-postgresql-arrays-with-gin-indexes-for/&quot;&gt;previous post&lt;/a&gt; I looked at using PostgreSQL arrays w/ GiN indexes to store tags for quick subset and overlap matching. I noticed soon after implementing this on &lt;a href=&quot;http://filmlust.com/title/&quot;&gt;filmlust&lt;/a&gt; (for the film genres) that some queries were taking longer than they should have. Namely, queries with a genre filter in conjunction with a highly selective filter, such as a high minimum vote count.&lt;/p&gt;

&lt;p&gt;So, I had a look at a query plan (note: I map the genres to integers for a smaller footprint, so this is actually looking for all of “action”, “adventure”, “classic”):&lt;/p&gt;

&lt;div class=&quot;CodeRay&quot;&gt;
  &lt;div class=&quot;code&quot;&gt;&lt;pre&gt;filmlust=&amp;gt; explain analyze &lt;span class=&quot;class&quot;&gt;select&lt;/span&gt; id &lt;span class=&quot;keyword&quot;&gt;from&lt;/span&gt; title &lt;span class=&quot;keyword&quot;&gt;WHERE&lt;/span&gt; imdb_count &amp;gt;= &lt;span class=&quot;integer&quot;&gt;5000&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;AND&lt;/span&gt; genre &lt;span class=&quot;error&quot;&gt;@&lt;/span&gt;&amp;gt; &lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;{6,7,19}&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;/span&gt;;
                                                               QUERY PLAN                                                                
&lt;span class=&quot;comment&quot;&gt;-----------------------------------------------------------------------------------------------------------------------------------------&lt;/span&gt;
 Bitmap Heap Scan &lt;span class=&quot;keyword&quot;&gt;on&lt;/span&gt; title  (cost=&lt;span class=&quot;float&quot;&gt;101.26&lt;/span&gt;.&lt;span class=&quot;float&quot;&gt;.120&lt;/span&gt;&lt;span class=&quot;float&quot;&gt;.59&lt;/span&gt; rows=&lt;span class=&quot;integer&quot;&gt;5&lt;/span&gt; width=&lt;span class=&quot;integer&quot;&gt;4&lt;/span&gt;) (actual &lt;span class=&quot;predefined-type&quot;&gt;time&lt;/span&gt;=&lt;span class=&quot;float&quot;&gt;4.533&lt;/span&gt;.&lt;span class=&quot;float&quot;&gt;.4&lt;/span&gt;&lt;span class=&quot;float&quot;&gt;.593&lt;/span&gt; rows=&lt;span class=&quot;integer&quot;&gt;55&lt;/span&gt; loops=&lt;span class=&quot;integer&quot;&gt;1&lt;/span&gt;)
   Recheck Cond: ((genre &lt;span class=&quot;error&quot;&gt;@&lt;/span&gt;&amp;gt; &lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;{6,7,19}&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;/span&gt;::&lt;span class=&quot;predefined-type&quot;&gt;smallint&lt;/span&gt;[]) &lt;span class=&quot;keyword&quot;&gt;AND&lt;/span&gt; (imdb_count &amp;gt;= &lt;span class=&quot;integer&quot;&gt;5000&lt;/span&gt;))
   -&amp;gt;  BitmapAnd  (cost=&lt;span class=&quot;float&quot;&gt;101.26&lt;/span&gt;.&lt;span class=&quot;float&quot;&gt;.101&lt;/span&gt;&lt;span class=&quot;float&quot;&gt;.26&lt;/span&gt; rows=&lt;span class=&quot;integer&quot;&gt;5&lt;/span&gt; width=&lt;span class=&quot;integer&quot;&gt;0&lt;/span&gt;) (actual &lt;span class=&quot;predefined-type&quot;&gt;time&lt;/span&gt;=&lt;span class=&quot;float&quot;&gt;4.522&lt;/span&gt;.&lt;span class=&quot;float&quot;&gt;.4&lt;/span&gt;&lt;span class=&quot;float&quot;&gt;.522&lt;/span&gt; rows=&lt;span class=&quot;integer&quot;&gt;0&lt;/span&gt; loops=&lt;span class=&quot;integer&quot;&gt;1&lt;/span&gt;)
         -&amp;gt;  Bitmap &lt;span class=&quot;type&quot;&gt;Index&lt;/span&gt; Scan &lt;span class=&quot;keyword&quot;&gt;on&lt;/span&gt; ix_title_genre  (cost=&lt;span class=&quot;float&quot;&gt;0.00&lt;/span&gt;.&lt;span class=&quot;float&quot;&gt;.8&lt;/span&gt;&lt;span class=&quot;float&quot;&gt;.82&lt;/span&gt; rows=&lt;span class=&quot;integer&quot;&gt;175&lt;/span&gt; width=&lt;span class=&quot;integer&quot;&gt;0&lt;/span&gt;) (actual &lt;span class=&quot;predefined-type&quot;&gt;time&lt;/span&gt;=&lt;span class=&quot;float&quot;&gt;3.922&lt;/span&gt;.&lt;span class=&quot;float&quot;&gt;.3&lt;/span&gt;&lt;span class=&quot;float&quot;&gt;.922&lt;/span&gt; rows=&lt;span class=&quot;integer&quot;&gt;372&lt;/span&gt; loops=&lt;span class=&quot;integer&quot;&gt;1&lt;/span&gt;)
               &lt;span class=&quot;type&quot;&gt;Index&lt;/span&gt; Cond: (genre &lt;span class=&quot;error&quot;&gt;@&lt;/span&gt;&amp;gt; &lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;{6,7,19}&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;/span&gt;::&lt;span class=&quot;predefined-type&quot;&gt;smallint&lt;/span&gt;[])
         -&amp;gt;  Bitmap &lt;span class=&quot;type&quot;&gt;Index&lt;/span&gt; Scan &lt;span class=&quot;keyword&quot;&gt;on&lt;/span&gt; ix_title_imdb_count  (cost=&lt;span class=&quot;float&quot;&gt;0.00&lt;/span&gt;.&lt;span class=&quot;float&quot;&gt;.92&lt;/span&gt;&lt;span class=&quot;float&quot;&gt;.19&lt;/span&gt; rows=&lt;span class=&quot;integer&quot;&gt;5305&lt;/span&gt; width=&lt;span class=&quot;integer&quot;&gt;0&lt;/span&gt;) (actual &lt;span class=&quot;predefined-type&quot;&gt;time&lt;/span&gt;=&lt;span class=&quot;float&quot;&gt;0.563&lt;/span&gt;.&lt;span class=&quot;float&quot;&gt;.0&lt;/span&gt;&lt;span class=&quot;float&quot;&gt;.563&lt;/span&gt; rows=&lt;span class=&quot;integer&quot;&gt;5296&lt;/span&gt; loops=&lt;span class=&quot;integer&quot;&gt;1&lt;/span&gt;)
               &lt;span class=&quot;type&quot;&gt;Index&lt;/span&gt; Cond: (imdb_count &amp;gt;= &lt;span class=&quot;integer&quot;&gt;5000&lt;/span&gt;)
 Total runtime: &lt;span class=&quot;float&quot;&gt;4.620&lt;/span&gt; ms&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;And there’s the crux of the issue. In a perfect world it would perform the one scan on the imdb_count index, which is faster than the genre GiN index, and then scan the few remaining rows (5296 here) for genre matches. I could make this happen with a &lt;a href=&quot;http://www.postgresql.org/docs/9.0/static/indexes-multicolumn.html&quot;&gt;multicolumn index&lt;/a&gt;, but because of the large variety of filters I have it’s not a viable option as there would be too many permutations. Thus, Postgres is forced to do two separate index scans on queries like this (see &lt;a href=&quot;http://www.postgresql.org/docs/9.0/static/indexes-bitmap-scans.html&quot;&gt;“combining multiple indexes”&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;Drop the index? Surely you jest.&lt;/h2&gt;

&lt;p&gt;One way to get around this is to drop the genre index, so Postgres would first do the index scan on the fast, selective, imdb_count condition, and then filter those rows.&lt;/p&gt;

&lt;div class=&quot;CodeRay&quot;&gt;
  &lt;div class=&quot;code&quot;&gt;&lt;pre&gt;filmlust=&amp;gt; &lt;span class=&quot;class&quot;&gt;drop&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;index&lt;/span&gt; ix_title_genre;
&lt;span class=&quot;class&quot;&gt;DROP&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;INDEX&lt;/span&gt;
filmlust=&amp;gt; explain analyze &lt;span class=&quot;class&quot;&gt;select&lt;/span&gt; id &lt;span class=&quot;keyword&quot;&gt;from&lt;/span&gt; title &lt;span class=&quot;keyword&quot;&gt;WHERE&lt;/span&gt; imdb_count &amp;gt;= &lt;span class=&quot;integer&quot;&gt;5000&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;AND&lt;/span&gt; genre &lt;span class=&quot;error&quot;&gt;@&lt;/span&gt;&amp;gt; &lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;{6,7,19}&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;/span&gt;;
                                                            QUERY PLAN                                                             
&lt;span class=&quot;comment&quot;&gt;-----------------------------------------------------------------------------------------------------------------------------------&lt;/span&gt;
 Bitmap Heap Scan &lt;span class=&quot;keyword&quot;&gt;on&lt;/span&gt; title  (cost=&lt;span class=&quot;float&quot;&gt;92.19&lt;/span&gt;.&lt;span class=&quot;float&quot;&gt;.2169&lt;/span&gt;&lt;span class=&quot;float&quot;&gt;.77&lt;/span&gt; rows=&lt;span class=&quot;integer&quot;&gt;5&lt;/span&gt; width=&lt;span class=&quot;integer&quot;&gt;4&lt;/span&gt;) (actual &lt;span class=&quot;predefined-type&quot;&gt;time&lt;/span&gt;=&lt;span class=&quot;float&quot;&gt;0.838&lt;/span&gt;.&lt;span class=&quot;float&quot;&gt;.5&lt;/span&gt;&lt;span class=&quot;float&quot;&gt;.229&lt;/span&gt; rows=&lt;span class=&quot;integer&quot;&gt;55&lt;/span&gt; loops=&lt;span class=&quot;integer&quot;&gt;1&lt;/span&gt;)
   Recheck Cond: (imdb_count &amp;gt;= &lt;span class=&quot;integer&quot;&gt;5000&lt;/span&gt;)
   Filter: (genre &lt;span class=&quot;error&quot;&gt;@&lt;/span&gt;&amp;gt; &lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;{6,7,19}&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;/span&gt;::&lt;span class=&quot;predefined-type&quot;&gt;smallint&lt;/span&gt;[])
   -&amp;gt;  Bitmap &lt;span class=&quot;type&quot;&gt;Index&lt;/span&gt; Scan &lt;span class=&quot;keyword&quot;&gt;on&lt;/span&gt; ix_title_imdb_count  (cost=&lt;span class=&quot;float&quot;&gt;0.00&lt;/span&gt;.&lt;span class=&quot;float&quot;&gt;.92&lt;/span&gt;&lt;span class=&quot;float&quot;&gt;.19&lt;/span&gt; rows=&lt;span class=&quot;integer&quot;&gt;5305&lt;/span&gt; width=&lt;span class=&quot;integer&quot;&gt;0&lt;/span&gt;) (actual &lt;span class=&quot;predefined-type&quot;&gt;time&lt;/span&gt;=&lt;span class=&quot;float&quot;&gt;0.569&lt;/span&gt;.&lt;span class=&quot;float&quot;&gt;.0&lt;/span&gt;&lt;span class=&quot;float&quot;&gt;.569&lt;/span&gt; rows=&lt;span class=&quot;integer&quot;&gt;5296&lt;/span&gt; loops=&lt;span class=&quot;integer&quot;&gt;1&lt;/span&gt;)
         &lt;span class=&quot;type&quot;&gt;Index&lt;/span&gt; Cond: (imdb_count &amp;gt;= &lt;span class=&quot;integer&quot;&gt;5000&lt;/span&gt;)
 Total runtime: &lt;span class=&quot;float&quot;&gt;5.253&lt;/span&gt; ms&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Unfortunately that wasn’t any faster because the comparisons, as few as there may be, are too expensive. Perhaps there’s another way of storing genres to speed up the “Filter” step in the above query process.&lt;/p&gt;

&lt;h2&gt;Enter the bits&lt;/h2&gt;

&lt;p&gt;If we pack the genres into a &lt;a href=&quot;http://www.postgresql.org/docs/8.4/static/datatype-bit.html&quot;&gt;bit string&lt;/a&gt; we can compare them very quickly with &lt;a href=&quot;http://www.postgresql.org/docs/8.4/static/functions-math.html&quot;&gt;bitwise operations&lt;/a&gt; while still maintaining the small memory size I crave.  Adding a mere bit for each of the 24 genres to every row. To implement this technique we’ll first need to map all the genres to a position in the bit string. The one I use for filmlust is at the bottom of &lt;a href=&quot;https://github.com/mrowl/filmdata/blob/master/filmdata/__init__.py&quot;&gt;this file&lt;/a&gt; (coincidentally this is how I do the integer mapping I noted above). So the bit at the 4th least significant position (2&lt;sup&gt;3)&lt;/sup&gt; would be on if the film contained the “foreign” genre.&lt;/p&gt;

&lt;h2&gt;Querying for all genres&lt;/h2&gt;

&lt;p&gt;Here’s the results for returning the same rows as the two examples above:&lt;/p&gt;

&lt;div class=&quot;CodeRay&quot;&gt;
  &lt;div class=&quot;code&quot;&gt;&lt;pre&gt;filmlust=&amp;gt; explain analyze &lt;span class=&quot;class&quot;&gt;select&lt;/span&gt; id &lt;span class=&quot;keyword&quot;&gt;from&lt;/span&gt; title_bit &lt;span class=&quot;keyword&quot;&gt;WHERE&lt;/span&gt; imdb_count &amp;gt;= &lt;span class=&quot;integer&quot;&gt;5000&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;AND&lt;/span&gt; genre &amp;amp; &lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;modifier&quot;&gt;B&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;000010000000000011000000&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;/span&gt; = &lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;modifier&quot;&gt;B&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;000010000000000011000000&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;/span&gt;;
                                                              QUERY PLAN                                                               
&lt;span class=&quot;comment&quot;&gt;---------------------------------------------------------------------------------------------------------------------------------------&lt;/span&gt;
 Bitmap Heap Scan &lt;span class=&quot;keyword&quot;&gt;on&lt;/span&gt; title_bit  (cost=&lt;span class=&quot;float&quot;&gt;96.97&lt;/span&gt;.&lt;span class=&quot;float&quot;&gt;.1794&lt;/span&gt;&lt;span class=&quot;float&quot;&gt;.30&lt;/span&gt; rows=&lt;span class=&quot;integer&quot;&gt;28&lt;/span&gt; width=&lt;span class=&quot;integer&quot;&gt;4&lt;/span&gt;) (actual &lt;span class=&quot;predefined-type&quot;&gt;time&lt;/span&gt;=&lt;span class=&quot;float&quot;&gt;0.771&lt;/span&gt;.&lt;span class=&quot;float&quot;&gt;.4&lt;/span&gt;&lt;span class=&quot;float&quot;&gt;.572&lt;/span&gt; rows=&lt;span class=&quot;integer&quot;&gt;55&lt;/span&gt; loops=&lt;span class=&quot;integer&quot;&gt;1&lt;/span&gt;)
   Recheck Cond: (imdb_count &amp;gt;= &lt;span class=&quot;integer&quot;&gt;5000&lt;/span&gt;)
   Filter: ((genre &amp;amp; &lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;modifier&quot;&gt;B&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;000010000000000011000000&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;/span&gt;::&lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;delimiter&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;bit&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;) = &lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;modifier&quot;&gt;B&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;000010000000000011000000&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;/span&gt;::&lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;delimiter&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;bit&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;)
   -&amp;gt;  Bitmap &lt;span class=&quot;type&quot;&gt;Index&lt;/span&gt; Scan &lt;span class=&quot;keyword&quot;&gt;on&lt;/span&gt; ix_title_bit_imdb_count  (cost=&lt;span class=&quot;float&quot;&gt;0.00&lt;/span&gt;.&lt;span class=&quot;float&quot;&gt;.96&lt;/span&gt;&lt;span class=&quot;float&quot;&gt;.96&lt;/span&gt; rows=&lt;span class=&quot;integer&quot;&gt;5562&lt;/span&gt; width=&lt;span class=&quot;integer&quot;&gt;0&lt;/span&gt;) (actual &lt;span class=&quot;predefined-type&quot;&gt;time&lt;/span&gt;=&lt;span class=&quot;float&quot;&gt;0.554&lt;/span&gt;.&lt;span class=&quot;float&quot;&gt;.0&lt;/span&gt;&lt;span class=&quot;float&quot;&gt;.554&lt;/span&gt; rows=&lt;span class=&quot;integer&quot;&gt;5296&lt;/span&gt; loops=&lt;span class=&quot;integer&quot;&gt;1&lt;/span&gt;)
         &lt;span class=&quot;type&quot;&gt;Index&lt;/span&gt; Cond: (imdb_count &amp;gt;= &lt;span class=&quot;integer&quot;&gt;5000&lt;/span&gt;)
 Total runtime: &lt;span class=&quot;float&quot;&gt;4.602&lt;/span&gt; ms&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Huzzah! It cut the time by a whole .018ms. Well, that wasn’t the huge gain I was looking for, but I figured it warranted further investigation.&lt;/p&gt;

&lt;h2&gt;Benchmarks&lt;/h2&gt;

&lt;p&gt;The following benchmarks were run with my production code, so it includes the limits for pages and a second query to obtain the row count. The first column describes the conditions: small cull = a not very selective condition (leaves about 1/3), large cull = a selective condition (the imdb_count condition above, leaves only 1/30 of the rows). The second and third columns are the time it took to execute using the array and bit string types, respectively. The fourth column is the difference (array – bits).&lt;/p&gt;

&lt;div class=&quot;CodeRay&quot;&gt;
  &lt;div class=&quot;code&quot;&gt;&lt;pre&gt;                              Arrays      Bits        Diff
all 1 genre                   0.01839     0.06916     -0.05077
all 3 genres                  0.00949     0.06374     -0.05425
any 3 genres                  0.02175     0.06315     -0.04140
all 1 genre, small cull       0.00969     0.04161     -0.03192
all 3 genres, small cull      0.00982     0.04101     -0.03119
any 3 genres, small cull      0.01587     0.02574     -0.00987
all 1 genre, large cull       0.01072     0.00770     0.00303
all 3 genres, large cull      0.01053     0.00713     0.00339
any 3 genres, large cull      0.01759     0.00604     0.01155&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;There’s  a definite performance advantage to using the bit strings with the more selective conditions, about 1.5x for matching any genre and 3x for matching all, but they’re a bit slower when flying solo or combined with a condition that doesn’t cut the data down very much.&lt;/p&gt;

&lt;h2&gt;Matching any genre&lt;/h2&gt;

&lt;p&gt;For posterity here’s how to match an overlap of genres (i.e. the film contains any of “action”, “adventure”, “classic”):&lt;/p&gt;

&lt;div class=&quot;CodeRay&quot;&gt;
  &lt;div class=&quot;code&quot;&gt;&lt;pre&gt;filmlust=&amp;gt; explain analyze &lt;span class=&quot;class&quot;&gt;select&lt;/span&gt; id &lt;span class=&quot;keyword&quot;&gt;from&lt;/span&gt; title_bit &lt;span class=&quot;keyword&quot;&gt;WHERE&lt;/span&gt; imdb_count &amp;gt;= &lt;span class=&quot;integer&quot;&gt;5000&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;AND&lt;/span&gt; genre &amp;amp; &lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;modifier&quot;&gt;B&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;000010000000000011000000&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;/span&gt; != &lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;modifier&quot;&gt;B&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;000000000000000000000000&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;/span&gt;;
                                                              QUERY PLAN                                                               
&lt;span class=&quot;comment&quot;&gt;---------------------------------------------------------------------------------------------------------------------------------------&lt;/span&gt;
 Bitmap Heap Scan &lt;span class=&quot;keyword&quot;&gt;on&lt;/span&gt; title_bit  (cost=&lt;span class=&quot;float&quot;&gt;98.34&lt;/span&gt;.&lt;span class=&quot;float&quot;&gt;.1795&lt;/span&gt;&lt;span class=&quot;float&quot;&gt;.68&lt;/span&gt; rows=&lt;span class=&quot;integer&quot;&gt;5534&lt;/span&gt; width=&lt;span class=&quot;integer&quot;&gt;4&lt;/span&gt;) (actual &lt;span class=&quot;predefined-type&quot;&gt;time&lt;/span&gt;=&lt;span class=&quot;float&quot;&gt;0.758&lt;/span&gt;.&lt;span class=&quot;float&quot;&gt;.4&lt;/span&gt;&lt;span class=&quot;float&quot;&gt;.767&lt;/span&gt; rows=&lt;span class=&quot;integer&quot;&gt;2228&lt;/span&gt; loops=&lt;span class=&quot;integer&quot;&gt;1&lt;/span&gt;)
   Recheck Cond: (imdb_count &amp;gt;= &lt;span class=&quot;integer&quot;&gt;5000&lt;/span&gt;)
   Filter: ((genre &amp;amp; &lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;modifier&quot;&gt;B&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;000010000000000011000000&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;/span&gt;::&lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;delimiter&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;bit&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;) &amp;lt;&amp;gt; &lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;modifier&quot;&gt;B&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;000000000000000000000000&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;/span&gt;::&lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;delimiter&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;bit&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;)
   -&amp;gt;  Bitmap &lt;span class=&quot;type&quot;&gt;Index&lt;/span&gt; Scan &lt;span class=&quot;keyword&quot;&gt;on&lt;/span&gt; ix_title_bit_imdb_count  (cost=&lt;span class=&quot;float&quot;&gt;0.00&lt;/span&gt;.&lt;span class=&quot;float&quot;&gt;.96&lt;/span&gt;&lt;span class=&quot;float&quot;&gt;.96&lt;/span&gt; rows=&lt;span class=&quot;integer&quot;&gt;5562&lt;/span&gt; width=&lt;span class=&quot;integer&quot;&gt;0&lt;/span&gt;) (actual &lt;span class=&quot;predefined-type&quot;&gt;time&lt;/span&gt;=&lt;span class=&quot;float&quot;&gt;0.548&lt;/span&gt;.&lt;span class=&quot;float&quot;&gt;.0&lt;/span&gt;&lt;span class=&quot;float&quot;&gt;.548&lt;/span&gt; rows=&lt;span class=&quot;integer&quot;&gt;5296&lt;/span&gt; loops=&lt;span class=&quot;integer&quot;&gt;1&lt;/span&gt;)
         &lt;span class=&quot;type&quot;&gt;Index&lt;/span&gt; Cond: (imdb_count &amp;gt;= &lt;span class=&quot;integer&quot;&gt;5000&lt;/span&gt;)
 Total runtime: &lt;span class=&quot;float&quot;&gt;4.903&lt;/span&gt; ms&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;You can eke out a little more performance using bit strings for tags/genres depending on the other conditions in your typical query. If they’re highly selective, use bit strings, otherwise you’ll probably be better off with the array data type. I’m sticking with the arrays for now because they are somewhat easier to work with and the queries are still a bit faster in many of my use cases. I’ve started logging user interaction to get a handle on which filters are most popular so I may switch over to bit strings in the future if necessary.&lt;/p&gt;

&lt;p&gt;One caveat: a major potential shortcoming of the bit string method is the size.  With my data I only have 24 possible genres yielding 24 bits per row. There is an average of 4 genres per film which would yield an array size of ~64 bits (4 smallints) per row. So the bit strings are nearly a third of the size.  However, if you implemented this with 1000 possible genres the arrays would probably be the lighter option because you would need that massive 1000 bit column to hold the bit string for each row.&lt;/p&gt;

&lt;p&gt;Thus, as usual, there’s no “one size fits all” solution, so pick your poison carefully.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Aggressively caching CSS media with Sass and Tornado</title>
   <link href="http://nonplus.me/2011/09/02/aggressively-caching-css-media-with-sass-and/"/>
   <updated>2011-09-02T20:22:00+00:00</updated>
   <id>http://nonplus.mehttp://nonplus.me/2011/09/02/aggressively-caching-css-media-with-sass-and</id>
   <content type="html">&lt;p&gt;Tornado comes with a nice &lt;a href=&quot;http://www.tornadoweb.org/documentation/web.html#tornado.web.RequestHandler.static_url&quot;&gt;static url feature&lt;/a&gt; for including static files in your templates. It appends hashes to those urls and turns on really aggressive file caching, since the hash bearing url of the requested static file will change whenever the file itself changes, thus busting any browser caching.  Unfortunately there’s no inherent means of dealing with the urls in the css (or scss as the case may be) files (e.g. png icons).&lt;/p&gt;

&lt;p&gt;To solve this there are a few (probably more) options that I thought of: regex replacements before deployment, making the css files into a tornado template and processing them, adding a hook to sass, or adding the same static_url method to Sass’s arsenal using &lt;a href=&quot;http://sass-lang.com/docs/yardoc/Sass/Script/Functions.html#adding_custom_functions&quot;&gt;custom functions&lt;/a&gt;.  I opted for the last method and added the following to &lt;code&gt;config.rb&lt;/code&gt; in order to extend Sass and add a new &lt;code&gt;static_url&lt;/code&gt; function for .scss files.&lt;/p&gt;

&lt;p&gt;&lt;script src=&quot;https://gist.github.com/mrowl/1189785.js&quot;&gt;&lt;/script&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>The perfect French press cold brew</title>
   <link href="http://nonplus.me/2011/08/25/the-perfect-french-press-cold-brew/"/>
   <updated>2011-08-25T15:35:00+00:00</updated>
   <id>http://nonplus.mehttp://nonplus.me/2011/08/25/the-perfect-french-press-cold-brew</id>
   <content type="html">&lt;p&gt;Ah, the cold brew.  Nothing quite hits the spot on a hot summer
morning.  I (over?) indulged in it frequently over the past few months and at this point I’d say unequivocally that I prefer it to hot coffee, regardless of
the season.  Most of the people online were using regular drippers to
home brew the stuff so I figured I’d write this guide for using a
French press, a most magical apparatus for both hot and cold alike.
Though, it’s so simple and intuitive as to render a tutorial nearly
superfluous.&lt;/p&gt;

&lt;p&gt;In any event, after a bit of experimentation I’ve settled on the
process and measurements for extracting the optimal cold brew cup from
a French press.  I haven’t found the ideal beans yet, as I prefer to
try something new every time, but I generally opt for something dark
that’s heavy on the chocolate notes.  I’ve noticed the chocolate
flavor, for which I have a particular affinity, really gets amplified
with the cold brew process.  Recently I rather enjoyed the &lt;a href=&quot;http://www.stinkybklyn.com/&quot;&gt;Stinky
Bkln&lt;/a&gt; blend (ultimately from
&lt;a href=&quot;http://www.damicofoods.com/&quot;&gt;D'Amico&lt;/a&gt;) and a Guatemalan roast from
&lt;a href=&quot;http://www.gorillacoffee.com&quot;&gt;Gorilla&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Without further ado…&lt;/p&gt;

&lt;h2&gt;Ingredients&lt;/h2&gt;

&lt;ul&gt;&lt;li&gt;0.5 cups fresh &lt;strong&gt;coarsely&lt;/strong&gt; ground coffee (the usual for a French press)&lt;/li&gt;
&lt;li&gt;3.5 cups water&lt;/li&gt;
&lt;li&gt;A jug of sorts. I use a glass (no BPA!) quart bottle. Previous tenant: milk.&lt;/li&gt;
&lt;li&gt;Cold storage area (e.g. a refrigerator)&lt;/li&gt;
&lt;/ul&gt;&lt;h2&gt;Process&lt;/h2&gt;

&lt;ol&gt;&lt;li&gt;Pour the coffee into the French press.&lt;/li&gt;
&lt;li&gt;Add the water and stir vigorously with a spoon.&lt;/li&gt;
&lt;li&gt;Put the &lt;em&gt;uncovered&lt;/em&gt; press into cold storage for 12-24 hours.&lt;/li&gt;
&lt;li&gt;Put the jug in next to the press (pouring the cold brew into a
warm container sucks).&lt;/li&gt;
&lt;li&gt;After the time has elapsed, take the brewed coffee out&lt;/li&gt;
&lt;li&gt;Break the coffee cake on the top and lightly stir.&lt;/li&gt;
&lt;li&gt;Scrape off the “fines” that remain hovering
(&lt;a href=&quot;http://vimeo.com/2222293&quot;&gt;demonstration&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;Cover and press!&lt;/li&gt;
&lt;li&gt;Let it sit for a minute or so.&lt;/li&gt;
&lt;li&gt;Unload the cold brew into the cool container (or straight into a
mug) and partake.&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;The length of the brewing process is up for debate; some  swear by 24
hours and others 12.  I always do the latter mostly because I don’t
want to go sans French press for a whole day and it works out nicely
to do the preparation after dinner, let it sit overnight, and then
have it waiting in the morning.&lt;/p&gt;

&lt;p&gt;One of the great things about cold brewing is it doesn’t go bad within the hour like hot coffee.  You can brew a large quantity and store it in the fridge for up to a week without tarnishing the flavor.&lt;/p&gt;

&lt;p&gt;Note: I’m sorely lacking in the precision measurement department at
the moment, but I’ll hopefully acquire a scale soon and write an
update with more accurate numbers.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Managing virtualenv apps with Supervisor</title>
   <link href="http://nonplus.me/2011/08/18/managing-virtualenv-apps-with-supervisor/"/>
   <updated>2011-08-18T16:57:00+00:00</updated>
   <id>http://nonplus.mehttp://nonplus.me/2011/08/18/managing-virtualenv-apps-with-supervisor</id>
   <content type="html">&lt;p&gt;I’ve been using &lt;a href=&quot;http://supervisord.org&quot;&gt;supervisor&lt;/a&gt; a lot to manage my server processes and I’ve grown quite fond of it but it can make things a bit tricky when running apps contained in a virtualenv sandbox.  There are a couple workarounds: setting all the environment variables that virtualenv exports in its bin/activate script or using a bash script to launch the app from within the virtual environment. I chose the latter route because it’s pretty simple and it can be convenient to have the script around for other tasks.  The script, which I place in a my_app/script folder, follows.&lt;/p&gt;

&lt;p&gt;&lt;script src=&quot;https://gist.github.com/mrowl/1153271.js&quot;&gt;&lt;/script&gt;&lt;/p&gt;

&lt;p&gt;And in the supervisor conf file:&lt;/p&gt;

&lt;div class=&quot;CodeRay&quot;&gt;
  &lt;div class=&quot;code&quot;&gt;&lt;pre&gt;[program:my_app_0]
user=my_user
autostart=true
autorestart=true
command=/home/my_user/my_env/my_app/script/env_run.sh python main.py -p 8880

[program:my_app_1]
user=my_user
autostart=true
autorestart=true
command=/home/my_user/my_env/my_app/script/env_run.sh python main.py -p 8881&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;In the last line of the script using exec to start the program is somewhat important because it replaces the bash script’s process with the python process, thus giving supervisor control of the app itself instead of the bash script. You can check the process ids to verify. Here’s a quick test I ran on my local box.&lt;/p&gt;

&lt;div class=&quot;CodeRay&quot;&gt;
  &lt;div class=&quot;code&quot;&gt;&lt;pre&gt;# Running without exec (don't actually daemonize (the &quot;&amp;amp;&quot;) with supervisor)
scott@c ~ $ /home/scott/webdev/my_env/my_app/script/env_run.sh python main.py -p 8888 &amp;amp;
[1] 5152
scott@c ~ $ ps -ef | grep main.py
scott     5152  4642  0 12:30 pts/19   00:00:00 /bin/bash /home/scott/webdev/my_env/my_app/script/env_run.sh python main.py -p 8888
scott     5161  5152 15 12:30 pts/19   00:00:00 python main.py -p 8888
#
#
# Running with exec...
scott@c ~ $ /home/scott/webdev/my_env/my_app/script/env_run.sh python main.py -p 8888 &amp;amp;
[1] 3740
scott@c ~ $ ps -ef | grep main.py
scott     3740 27015  7 12:24 pts/9    00:00:00 python main.py -p 8888&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;No more bash middleman!&lt;/p&gt;

&lt;p&gt;Note: the above examples were done for a &lt;a href=&quot;http://www.tornadoweb.org&quot;&gt;tornado&lt;/a&gt; deployment.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Launching chromium from xmonad prompt shortcuts</title>
   <link href="http://nonplus.me/2011/08/17/launching-chromium-with-urls-from-xmonad-shor/"/>
   <updated>2011-08-17T01:50:00+00:00</updated>
   <id>http://nonplus.mehttp://nonplus.me/2011/08/17/launching-chromium-with-urls-from-xmonad-shor</id>
   <content type="html">&lt;p&gt;For some unknown reason chromium seems only to accept UTF-8 encoded urls for its cli arguments.  This conflicts with xmonad which pipes ISO-8859 text when it spawns processes via shortcuts (e.g. the &lt;code&gt;XMonad.Actions.Search&lt;/code&gt; module).  To get around this you can point the default browser (xmonad uses the &lt;code&gt;$BROWSER&lt;/code&gt; env variable in bash) to a script which uses the unix &lt;code&gt;iconv&lt;/code&gt; program to sanitize xmonad’s text for chromium input and then launches chromium with the purified url.  It also logs all the xmonad-spawned searches to a file for my future amusement.&lt;/p&gt;

&lt;p&gt;In .bashrc:&lt;/p&gt;

&lt;div class=&quot;CodeRay&quot;&gt;
  &lt;div class=&quot;code&quot;&gt;&lt;pre&gt;export BROWSER=&quot;${HOME}/.chromium_opener.sh&quot;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;And .chromium_opener.sh…&lt;/p&gt;

&lt;p&gt;&lt;script src=&quot;https://gist.github.com/mrowl/1150624.js&quot;&gt;&lt;/script&gt;&lt;/p&gt;

&lt;p&gt;Voila!&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Using PostgreSQL arrays with GiN indexes for fast tag filtering</title>
   <link href="http://nonplus.me/2011/08/16/using-postgresql-arrays-with-gin-indexes-for/"/>
   <updated>2011-08-16T19:06:00+00:00</updated>
   <id>http://nonplus.mehttp://nonplus.me/2011/08/16/using-postgresql-arrays-with-gin-indexes-for</id>
   <content type="html">&lt;h2&gt;The setup&lt;/h2&gt;

&lt;p&gt;A major feature on sites like &lt;a href=&quot;http://www.yelp.com&quot;&gt;yelp&lt;/a&gt; and
&lt;a href=&quot;http://filmlust.com/title&quot;&gt;filmlust&lt;/a&gt; is the ability to quickly drill
down search results by filtering in/out unwanted tags. In a typical
normalized relational db the tags would have a many-to-many
relationship with the main objects and they would be stored in a
separate table with an association table joining the two. This leads
to painfully slow filtering queries. A better method is to add a
“tags” column using &lt;a href=&quot;http://www.postgresql.org/docs/9.0/static/arrays.html&quot;&gt;PostgreSQL’s array
type&lt;/a&gt; to the
main table and store the tags as text in there. For example…&lt;/p&gt;

&lt;h3&gt;Create a new table&lt;/h3&gt;

&lt;div class=&quot;CodeRay&quot;&gt;
  &lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;class&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;TABLE&lt;/span&gt; posts (post_id &lt;span class=&quot;predefined-type&quot;&gt;INTEGER&lt;/span&gt;, title &lt;span class=&quot;predefined-type&quot;&gt;TEXT&lt;/span&gt;, content &lt;span class=&quot;predefined-type&quot;&gt;TEXT&lt;/span&gt;, tags &lt;span class=&quot;predefined-type&quot;&gt;TEXT&lt;/span&gt;[]);
&lt;span class=&quot;class&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;class&quot;&gt;INTO&lt;/span&gt; posts (post_id, title, content, tags) &lt;span class=&quot;keyword&quot;&gt;VALUES&lt;/span&gt; (&lt;span class=&quot;integer&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;el posto numero uno&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;witticisms here&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;{&quot;bacon&quot;, &quot;chunky&quot;}&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;/span&gt;);
&lt;span class=&quot;class&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;class&quot;&gt;INTO&lt;/span&gt; posts (post_id, title, content, tags) &lt;span class=&quot;keyword&quot;&gt;VALUES&lt;/span&gt; (&lt;span class=&quot;integer&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;el posto numero dos&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;mas witticisms here&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;{&quot;chunky&quot;, &quot;fauna&quot;}&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;/span&gt;);
&lt;span class=&quot;class&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;class&quot;&gt;INTO&lt;/span&gt; posts (post_id, title, content, tags) &lt;span class=&quot;keyword&quot;&gt;VALUES&lt;/span&gt; (&lt;span class=&quot;integer&quot;&gt;3&lt;/span&gt;, &lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;el posto numero tres&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;plus witticisms here&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;{&quot;bacon&quot;, &quot;fauna&quot;}&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;/span&gt;);&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h3&gt;Alter an existing table&lt;/h3&gt;

&lt;div class=&quot;CodeRay&quot;&gt;
  &lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;class&quot;&gt;ALTER&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;TABLE&lt;/span&gt; posts &lt;span class=&quot;class&quot;&gt;ADD&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;COLUMN&lt;/span&gt; tags &lt;span class=&quot;predefined-type&quot;&gt;TEXT&lt;/span&gt;[]; &lt;span class=&quot;comment&quot;&gt;#add the array of text values&lt;/span&gt;
&lt;span class=&quot;class&quot;&gt;UPDATE&lt;/span&gt; posts &lt;span class=&quot;class&quot;&gt;SET&lt;/span&gt; tags=&lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;{&quot;bacon&quot;, &quot;chunky&quot;}&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;WHERE&lt;/span&gt; post_id=;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;Querying&lt;/h2&gt;

&lt;p&gt;So the table is all set to go and the tags column is populated for all
your items. Another advantage of using arrays over normalization is
more intuitive queries, particularly for some of the more complex
queries. To search for all posts tagged with “bacon” with the
traditional many-to-many setup…&lt;/p&gt;

&lt;div class=&quot;CodeRay&quot;&gt;
  &lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;class&quot;&gt;SELECT&lt;/span&gt; p.* &lt;span class=&quot;keyword&quot;&gt;FROM&lt;/span&gt; posts &lt;span class=&quot;keyword&quot;&gt;AS&lt;/span&gt; p &lt;span class=&quot;keyword&quot;&gt;JOIN&lt;/span&gt; posts_tags_assoc &lt;span class=&quot;keyword&quot;&gt;AS&lt;/span&gt; pta &lt;span class=&quot;keyword&quot;&gt;ON&lt;/span&gt; p.post_id=pta.post_id &lt;span class=&quot;keyword&quot;&gt;JOIN&lt;/span&gt; tags t &lt;span class=&quot;keyword&quot;&gt;ON&lt;/span&gt; pta.tag_id=t.tag_id &lt;span class=&quot;keyword&quot;&gt;WHERE&lt;/span&gt; t.name = &lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;bacon&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;/span&gt;;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;vs. the new array columns…&lt;/p&gt;

&lt;div class=&quot;CodeRay&quot;&gt;
  &lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;class&quot;&gt;SELECT&lt;/span&gt; posts.* &lt;span class=&quot;keyword&quot;&gt;FROM&lt;/span&gt; posts &lt;span class=&quot;keyword&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;bacon&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;/span&gt; = &lt;span class=&quot;keyword&quot;&gt;ANY&lt;/span&gt; (tags);&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;This is certainly more elegant, but the array type really shines when
using the &lt;a href=&quot;http://www.postgresql.org/docs/8.4/static/functions-array.html&quot;&gt;array operators&lt;/a&gt;
to do overlaps and subsets. To find all posts which are tagged by “chunky” &lt;em&gt;or&lt;/em&gt; “bacon” (an overlap) or to find the posts tagged by both “chunky” &lt;em&gt;and&lt;/em&gt; “bacon” (a subset, or “is contained by” in postgres verbiage) in the normalized tables:&lt;/p&gt;

&lt;div class=&quot;CodeRay&quot;&gt;
  &lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;class&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;DISTINCT&lt;/span&gt;(p.*) &lt;span class=&quot;keyword&quot;&gt;FROM&lt;/span&gt; posts &lt;span class=&quot;keyword&quot;&gt;AS&lt;/span&gt; p &lt;span class=&quot;keyword&quot;&gt;JOIN&lt;/span&gt; posts_tags_assoc &lt;span class=&quot;keyword&quot;&gt;AS&lt;/span&gt; pta &lt;span class=&quot;keyword&quot;&gt;ON&lt;/span&gt; p.post_id=pta.post_id &lt;span class=&quot;keyword&quot;&gt;JOIN&lt;/span&gt; tags t &lt;span class=&quot;keyword&quot;&gt;ON&lt;/span&gt; pta.tag_id=t.tag_id &lt;span class=&quot;keyword&quot;&gt;WHERE&lt;/span&gt; t.name &lt;span class=&quot;keyword&quot;&gt;IN&lt;/span&gt; (&lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;bacon&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;chunky&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;/span&gt;); &lt;span class=&quot;comment&quot;&gt;# overlap&lt;/span&gt;
&lt;span class=&quot;class&quot;&gt;SELECT&lt;/span&gt; p.post_id, p.title, p.content &lt;span class=&quot;keyword&quot;&gt;FROM&lt;/span&gt; posts &lt;span class=&quot;keyword&quot;&gt;AS&lt;/span&gt; p &lt;span class=&quot;keyword&quot;&gt;JOIN&lt;/span&gt; posts_tags_assoc &lt;span class=&quot;keyword&quot;&gt;AS&lt;/span&gt; pta &lt;span class=&quot;keyword&quot;&gt;ON&lt;/span&gt; p.post_id=pta.post_id &lt;span class=&quot;keyword&quot;&gt;JOIN&lt;/span&gt; tags t &lt;span class=&quot;keyword&quot;&gt;ON&lt;/span&gt; pta.tag_id=t.tag_id &lt;span class=&quot;keyword&quot;&gt;WHERE&lt;/span&gt; t.name &lt;span class=&quot;keyword&quot;&gt;IN&lt;/span&gt; (&lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;bacon&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;chunky&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;/span&gt;) &lt;span class=&quot;keyword&quot;&gt;GROUP&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;BY&lt;/span&gt; p.post_id, p.name, p.content &lt;span class=&quot;keyword&quot;&gt;HAVING&lt;/span&gt; &lt;span class=&quot;predefined&quot;&gt;COUNT&lt;/span&gt;(pta.post_id) = &lt;span class=&quot;integer&quot;&gt;2&lt;/span&gt;; &lt;span class=&quot;comment&quot;&gt;# subset&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;vs. the new array columns…&lt;/p&gt;

&lt;div class=&quot;CodeRay&quot;&gt;
  &lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;class&quot;&gt;SELECT&lt;/span&gt; posts.* &lt;span class=&quot;keyword&quot;&gt;FROM&lt;/span&gt; posts &lt;span class=&quot;keyword&quot;&gt;WHERE&lt;/span&gt; ARRAY[&lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;chunky&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;bacon&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;/span&gt;] &amp;amp;&amp;amp; tags; &lt;span class=&quot;comment&quot;&gt;# overlap&lt;/span&gt;
&lt;span class=&quot;class&quot;&gt;SELECT&lt;/span&gt; posts.* &lt;span class=&quot;keyword&quot;&gt;FROM&lt;/span&gt; posts &lt;span class=&quot;keyword&quot;&gt;WHERE&lt;/span&gt; ARRAY[&lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;chunky&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;bacon&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;/span&gt;] &amp;lt;&lt;span class=&quot;error&quot;&gt;@&lt;/span&gt; tags; &lt;span class=&quot;comment&quot;&gt;# subset&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Not only is this easier on the developer but it’s going to be a bit
faster since you’re not joining three tables together.&lt;/p&gt;

&lt;h2&gt;Optimizing with GiN indexes&lt;/h2&gt;

&lt;p&gt;At this point there’s a fully functioning table which you can query
and it’s probably a little bit quicker than original many-to-many
method. However, we can make it &lt;em&gt;much&lt;/em&gt; faster by indexing the new tags
array. The standard postgres index types (bitmap, hash, etc.) don’t
work with arrays but fortunately there’s the oft overlooked &lt;a href=&quot;http://www.postgresql.org/docs/8.4/static/textsearch-indexes.html&quot;&gt;GiN
index&lt;/a&gt;
which can make these queries really fly. Before adding the index let’s
take a look at what’s going on behind the scenes when we run one of
the above queries. I’m using the filtering table from filmlust for
this since postgres won’t use the indexes without a significant amount
of data in there.&lt;/p&gt;

&lt;div class=&quot;CodeRay&quot;&gt;
  &lt;div class=&quot;code&quot;&gt;&lt;pre&gt;mydb=&amp;gt; explain analyze &lt;span class=&quot;class&quot;&gt;select&lt;/span&gt; id &lt;span class=&quot;keyword&quot;&gt;from&lt;/span&gt; films_test &lt;span class=&quot;keyword&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;/span&gt; = &lt;span class=&quot;keyword&quot;&gt;ANY&lt;/span&gt;(tag);
                                                  QUERY PLAN
&lt;span class=&quot;comment&quot;&gt;---------------------------------------------------------------------------------------------------------------&lt;/span&gt;
 Seq Scan &lt;span class=&quot;keyword&quot;&gt;on&lt;/span&gt; films_test  (cost=&lt;span class=&quot;float&quot;&gt;0.00&lt;/span&gt;.&lt;span class=&quot;float&quot;&gt;.6666&lt;/span&gt;&lt;span class=&quot;float&quot;&gt;.27&lt;/span&gt; rows=&lt;span class=&quot;integer&quot;&gt;8507&lt;/span&gt; width=&lt;span class=&quot;integer&quot;&gt;4&lt;/span&gt;) (actual &lt;span class=&quot;predefined-type&quot;&gt;time&lt;/span&gt;=&lt;span class=&quot;float&quot;&gt;0.034&lt;/span&gt;.&lt;span class=&quot;float&quot;&gt;.61&lt;/span&gt;&lt;span class=&quot;float&quot;&gt;.595&lt;/span&gt; rows=&lt;span class=&quot;integer&quot;&gt;14767&lt;/span&gt; loops=&lt;span class=&quot;integer&quot;&gt;1&lt;/span&gt;)
   Filter: (&lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;/span&gt;::&lt;span class=&quot;predefined-type&quot;&gt;text&lt;/span&gt; = &lt;span class=&quot;keyword&quot;&gt;ANY&lt;/span&gt; (tag))
 Total runtime: &lt;span class=&quot;float&quot;&gt;62.396&lt;/span&gt; ms
(&lt;span class=&quot;integer&quot;&gt;3&lt;/span&gt; rows)

mydb=&amp;gt; explain analyze &lt;span class=&quot;class&quot;&gt;select&lt;/span&gt; id &lt;span class=&quot;keyword&quot;&gt;from&lt;/span&gt; films_test &lt;span class=&quot;keyword&quot;&gt;WHERE&lt;/span&gt; ARRAY[&lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;/span&gt;,&lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;adventure&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;/span&gt;] &amp;lt;&lt;span class=&quot;error&quot;&gt;@&lt;/span&gt; tag;
                                                 QUERY PLAN
&lt;span class=&quot;comment&quot;&gt;-------------------------------------------------------------------------------------------------------------&lt;/span&gt;
 Seq Scan &lt;span class=&quot;keyword&quot;&gt;on&lt;/span&gt; films_test  (cost=&lt;span class=&quot;float&quot;&gt;0.00&lt;/span&gt;.&lt;span class=&quot;float&quot;&gt;.4926&lt;/span&gt;&lt;span class=&quot;float&quot;&gt;.15&lt;/span&gt; rows=&lt;span class=&quot;integer&quot;&gt;174&lt;/span&gt; width=&lt;span class=&quot;integer&quot;&gt;4&lt;/span&gt;) (actual &lt;span class=&quot;predefined-type&quot;&gt;time&lt;/span&gt;=&lt;span class=&quot;float&quot;&gt;0.123&lt;/span&gt;.&lt;span class=&quot;float&quot;&gt;.75&lt;/span&gt;&lt;span class=&quot;float&quot;&gt;.030&lt;/span&gt; rows=&lt;span class=&quot;integer&quot;&gt;3340&lt;/span&gt; loops=&lt;span class=&quot;integer&quot;&gt;1&lt;/span&gt;)
   Filter: (&lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;{action,adventure}&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;/span&gt;::&lt;span class=&quot;predefined-type&quot;&gt;text&lt;/span&gt;[] &amp;lt;&lt;span class=&quot;error&quot;&gt;@&lt;/span&gt; tag)
 Total runtime: &lt;span class=&quot;float&quot;&gt;75.269&lt;/span&gt; ms
(&lt;span class=&quot;integer&quot;&gt;3&lt;/span&gt; rows)&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;So it’s doing a normal sequential scan and doing row-by-row
comparisons. Now, to add the GiN index:&lt;/p&gt;

&lt;div class=&quot;CodeRay&quot;&gt;
  &lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;class&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;type&quot;&gt;INDEX&lt;/span&gt; ix_posts_tags &lt;span class=&quot;keyword&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;delimiter&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;posts&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;keyword&quot;&gt;USING&lt;/span&gt; GIN (&lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;delimiter&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;tags&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;);&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;And running the queries again we get:&lt;/p&gt;

&lt;div class=&quot;CodeRay&quot;&gt;
  &lt;div class=&quot;code&quot;&gt;&lt;pre&gt;mydb=&amp;gt; explain analyze &lt;span class=&quot;class&quot;&gt;select&lt;/span&gt; id &lt;span class=&quot;keyword&quot;&gt;from&lt;/span&gt; films_test &lt;span class=&quot;keyword&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;/span&gt; = &lt;span class=&quot;keyword&quot;&gt;ANY&lt;/span&gt;(tag);
                                                  QUERY PLAN
&lt;span class=&quot;comment&quot;&gt;---------------------------------------------------------------------------------------------------------------&lt;/span&gt;
 Seq Scan &lt;span class=&quot;keyword&quot;&gt;on&lt;/span&gt; films_test  (cost=&lt;span class=&quot;float&quot;&gt;0.00&lt;/span&gt;.&lt;span class=&quot;float&quot;&gt;.6666&lt;/span&gt;&lt;span class=&quot;float&quot;&gt;.27&lt;/span&gt; rows=&lt;span class=&quot;integer&quot;&gt;8507&lt;/span&gt; width=&lt;span class=&quot;integer&quot;&gt;4&lt;/span&gt;) (actual &lt;span class=&quot;predefined-type&quot;&gt;time&lt;/span&gt;=&lt;span class=&quot;float&quot;&gt;0.035&lt;/span&gt;.&lt;span class=&quot;float&quot;&gt;.61&lt;/span&gt;&lt;span class=&quot;float&quot;&gt;.593&lt;/span&gt; rows=&lt;span class=&quot;integer&quot;&gt;14767&lt;/span&gt; loops=&lt;span class=&quot;integer&quot;&gt;1&lt;/span&gt;)
   Filter: (&lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;/span&gt;::&lt;span class=&quot;predefined-type&quot;&gt;text&lt;/span&gt; = &lt;span class=&quot;keyword&quot;&gt;ANY&lt;/span&gt; (tag))
 Total runtime: &lt;span class=&quot;float&quot;&gt;62.343&lt;/span&gt; ms
(&lt;span class=&quot;integer&quot;&gt;3&lt;/span&gt; rows)&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;So that first query remained the same because the index isn’t used
when using the ANY function. The GiN index only kicks in with the
operators &lt;a href=&quot;http://www.postgresql.org/docs/8.4/static/indexes-types.html&quot;&gt;shown here&lt;/a&gt;.
Trying it again with the overlap operator:&lt;/p&gt;

&lt;div class=&quot;CodeRay&quot;&gt;
  &lt;div class=&quot;code&quot;&gt;&lt;pre&gt;mydb=&amp;gt; explain analyze &lt;span class=&quot;class&quot;&gt;select&lt;/span&gt; id &lt;span class=&quot;keyword&quot;&gt;from&lt;/span&gt; films_test &lt;span class=&quot;keyword&quot;&gt;WHERE&lt;/span&gt; ARRAY[&lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;/span&gt;] &amp;amp;&amp;amp; tag;
                                                            QUERY PLAN
&lt;span class=&quot;comment&quot;&gt;----------------------------------------------------------------------------------------------------------------------------------&lt;/span&gt;
 Bitmap Heap Scan &lt;span class=&quot;keyword&quot;&gt;on&lt;/span&gt; films_test  (cost=&lt;span class=&quot;float&quot;&gt;4.83&lt;/span&gt;.&lt;span class=&quot;float&quot;&gt;.557&lt;/span&gt;&lt;span class=&quot;float&quot;&gt;.34&lt;/span&gt; rows=&lt;span class=&quot;integer&quot;&gt;174&lt;/span&gt; width=&lt;span class=&quot;integer&quot;&gt;4&lt;/span&gt;) (actual &lt;span class=&quot;predefined-type&quot;&gt;time&lt;/span&gt;=&lt;span class=&quot;float&quot;&gt;2.926&lt;/span&gt;.&lt;span class=&quot;float&quot;&gt;.8&lt;/span&gt;&lt;span class=&quot;float&quot;&gt;.755&lt;/span&gt; rows=&lt;span class=&quot;integer&quot;&gt;14767&lt;/span&gt; loops=&lt;span class=&quot;integer&quot;&gt;1&lt;/span&gt;)
   Recheck Cond: (&lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;{action}&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;/span&gt;::&lt;span class=&quot;predefined-type&quot;&gt;text&lt;/span&gt;[] &amp;lt;&lt;span class=&quot;error&quot;&gt;@&lt;/span&gt; tag)
   -&amp;gt;  Bitmap &lt;span class=&quot;type&quot;&gt;Index&lt;/span&gt; Scan &lt;span class=&quot;keyword&quot;&gt;on&lt;/span&gt; ix_films_test  (cost=&lt;span class=&quot;float&quot;&gt;0.00&lt;/span&gt;.&lt;span class=&quot;float&quot;&gt;.4&lt;/span&gt;&lt;span class=&quot;float&quot;&gt;.78&lt;/span&gt; rows=&lt;span class=&quot;integer&quot;&gt;174&lt;/span&gt; width=&lt;span class=&quot;integer&quot;&gt;0&lt;/span&gt;) (actual &lt;span class=&quot;predefined-type&quot;&gt;time&lt;/span&gt;=&lt;span class=&quot;float&quot;&gt;2.533&lt;/span&gt;.&lt;span class=&quot;float&quot;&gt;.2&lt;/span&gt;&lt;span class=&quot;float&quot;&gt;.533&lt;/span&gt; rows=&lt;span class=&quot;integer&quot;&gt;14767&lt;/span&gt; loops=&lt;span class=&quot;integer&quot;&gt;1&lt;/span&gt;)
         &lt;span class=&quot;type&quot;&gt;Index&lt;/span&gt; Cond: (&lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;{action}&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;/span&gt;::&lt;span class=&quot;predefined-type&quot;&gt;text&lt;/span&gt;[] &amp;lt;&lt;span class=&quot;error&quot;&gt;@&lt;/span&gt; tag)
 Total runtime: &lt;span class=&quot;float&quot;&gt;9.461&lt;/span&gt; ms
(&lt;span class=&quot;integer&quot;&gt;5&lt;/span&gt; rows)

mydb=&amp;gt; explain analyze &lt;span class=&quot;class&quot;&gt;select&lt;/span&gt; id &lt;span class=&quot;keyword&quot;&gt;from&lt;/span&gt; films_test &lt;span class=&quot;keyword&quot;&gt;WHERE&lt;/span&gt; ARRAY[&lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;/span&gt;,&lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;adventure&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;/span&gt;] &amp;lt;&lt;span class=&quot;error&quot;&gt;@&lt;/span&gt; tag;
                                                           QUERY PLAN
&lt;span class=&quot;comment&quot;&gt;---------------------------------------------------------------------------------------------------------------------------------&lt;/span&gt;
 Bitmap Heap Scan &lt;span class=&quot;keyword&quot;&gt;on&lt;/span&gt; films_test  (cost=&lt;span class=&quot;float&quot;&gt;4.83&lt;/span&gt;.&lt;span class=&quot;float&quot;&gt;.557&lt;/span&gt;&lt;span class=&quot;float&quot;&gt;.34&lt;/span&gt; rows=&lt;span class=&quot;integer&quot;&gt;174&lt;/span&gt; width=&lt;span class=&quot;integer&quot;&gt;4&lt;/span&gt;) (actual &lt;span class=&quot;predefined-type&quot;&gt;time&lt;/span&gt;=&lt;span class=&quot;float&quot;&gt;2.761&lt;/span&gt;.&lt;span class=&quot;float&quot;&gt;.5&lt;/span&gt;&lt;span class=&quot;float&quot;&gt;.229&lt;/span&gt; rows=&lt;span class=&quot;integer&quot;&gt;3340&lt;/span&gt; loops=&lt;span class=&quot;integer&quot;&gt;1&lt;/span&gt;)
   Recheck Cond: (&lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;{action,adventure}&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;/span&gt;::&lt;span class=&quot;predefined-type&quot;&gt;text&lt;/span&gt;[] &amp;lt;&lt;span class=&quot;error&quot;&gt;@&lt;/span&gt; tag)
   -&amp;gt;  Bitmap &lt;span class=&quot;type&quot;&gt;Index&lt;/span&gt; Scan &lt;span class=&quot;keyword&quot;&gt;on&lt;/span&gt; ix_films_test  (cost=&lt;span class=&quot;float&quot;&gt;0.00&lt;/span&gt;.&lt;span class=&quot;float&quot;&gt;.4&lt;/span&gt;&lt;span class=&quot;float&quot;&gt;.78&lt;/span&gt; rows=&lt;span class=&quot;integer&quot;&gt;174&lt;/span&gt; width=&lt;span class=&quot;integer&quot;&gt;0&lt;/span&gt;) (actual &lt;span class=&quot;predefined-type&quot;&gt;time&lt;/span&gt;=&lt;span class=&quot;float&quot;&gt;2.517&lt;/span&gt;.&lt;span class=&quot;float&quot;&gt;.2&lt;/span&gt;&lt;span class=&quot;float&quot;&gt;.517&lt;/span&gt; rows=&lt;span class=&quot;integer&quot;&gt;3340&lt;/span&gt; loops=&lt;span class=&quot;integer&quot;&gt;1&lt;/span&gt;)
         &lt;span class=&quot;type&quot;&gt;Index&lt;/span&gt; Cond: (&lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;{action,adventure}&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;/span&gt;::&lt;span class=&quot;predefined-type&quot;&gt;text&lt;/span&gt;[] &amp;lt;&lt;span class=&quot;error&quot;&gt;@&lt;/span&gt; tag)
 Total runtime: &lt;span class=&quot;float&quot;&gt;5.427&lt;/span&gt; ms
(&lt;span class=&quot;integer&quot;&gt;5&lt;/span&gt; rows)&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Now we’re cooking with grease! A solid order of magnitude performance
increase when using the proper index and corresponding operators.&lt;/p&gt;

&lt;h2&gt;Notes&lt;/h2&gt;

&lt;h3&gt;Disadvantages&lt;/h3&gt;

&lt;p&gt;One downside is the management of the tags themselves. It can become
more tenuous to do some operations such as changing the name of the
tag. Whereas in a normalized schema you just change one column in one
row of the tags table you now need to search through the entire posts
table and update all the tags arrays.  There also could be some issues
maintaining consistency since you don’t really have one list of tags
anymore. It’s not to difficult with data like mine on filmlust, where
all the tags are known beforehand, but it becomes hairy once you have
users inserting/updating all sorts of things on something like
delicious. One possibility for amelioration here would be to still
carry that separate tags table but cut out the middleman and put the
tag_ids directly into the tags array in the posts table.&lt;/p&gt;

&lt;h3&gt;Size&lt;/h3&gt;

&lt;p&gt;Usually when denormalizing a schema you face the prospect of growing
the physical size of your db exponentially due to all the newly
redundant data.  For something as small as a tag this isn’t really the
case.  Before there was an association table which had a post_id and a
tag_id for every tag/post combo so we’re only adding the difference of
the average tag length (~10 chars = 10 bytes) and the two id integers
(~2 * 4 = 8 bytes) (ignoring the table overhead and the entire
separate tags table).  A paltry 2 bytes, so I don’t think this is much
of an issue here since the content itself is going to be much much
larger.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Pragmatic language experimentation</title>
   <link href="http://nonplus.me/2011/04/11/language-experimentation/"/>
   <updated>2011-04-11T02:37:00+00:00</updated>
   <id>http://nonplus.mehttp://nonplus.me/2011/04/11/language-experimentation</id>
   <content type="html">&lt;p&gt;I purchased the book &lt;a href=&quot;http://pragprog.com/titles/btlang/seven-languages-in-seven-weeks&quot;&gt;Seven Languages in Seven Weeks&lt;/a&gt; as a Christmas present for myself in order to dabble in several languages and hopefully choose one or two of my favorites to pursue further.  I’m three languages deep at the moment and so far the book has fulfilled this expectation.&lt;/p&gt;

&lt;p&gt;It’s a good resource for quickly grokking a new language as it’s meant to be used as opposed to merely translating the ubiquitous C/Java/etc. approaches to solving problems.  I especially like that the book is targeted towards experienced programmers who have already picked up multiple languages.  Not much space is devoted to the tedious particulars of each language’s syntax, since these can be found readily via google or the main documentation.  Instead the focus seems to be on the aspects of the language which are unique and the problem domains for which it is particular apt.  For example, there is a section for prolog, which is an exceptional language for quickly finding solutions to constraint based problems.  A huge chunk of this chapter walks through writing a program for solving sudoku puzzles, a problem well suited for prolog.  This exercise isn’t used for any of the other languages, since, well, none of the other languages are quite as appropriate for the task.&lt;/p&gt;

&lt;p&gt;You can definitely tell it’s a work by “The Pragmatic Programmers” as one of the goals throughout seems to be exposure to as many tools (i.e. languages) as possible so that you can pick the right one for your next project.  Thus providing the programmer with a screwdriver to drive that screw instead of the more familiar sledgehammer.&lt;/p&gt;

&lt;p&gt;I’ve put all my code up in &lt;a href=&quot;https://github.com/mrowl/seven&quot;&gt;a github repo&lt;/a&gt; (including the exercises) and I plan on sharing my thoughts on each of the languages in the days to come.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Thank you, Glaucon</title>
   <link href="http://nonplus.me/2011/02/01/thank-you-glaucon/"/>
   <updated>2011-02-01T03:22:41+00:00</updated>
   <id>http://nonplus.mehttp://nonplus.me/2011/02/01/thank-you-glaucon</id>
   <content type="html">&lt;p&gt;In reading Plato’s &lt;a href=&quot;http://www.amazon.com/gp/product/0465069347?ie=UTF8&amp;amp;tag=filmlust-20&amp;amp;linkCode=as2&amp;amp;camp=1789&amp;amp;creative=390957&amp;amp;creativeASIN=0465069347&quot; title=&quot;The Republic of Plato&quot;&gt;The Republic&lt;/a&gt; I’ve been entirely befuddled by Socrates on more than one occasion.  The beauty of the piece is that it’s a dialog and the various interlocutors are often as nonplussed as myself.  I’ve found that usually during these times of bewilderment one only has to read on for Glaucon, Adeimantus, etc. to request and subsequently receive an explanation from Socrates himself.  Sure beats trying to make sense of some Socrates&amp;rsquo; verbal soup myself.&lt;/p&gt;

&lt;blockquote class=&quot;posterous_medium_quote&quot;&gt;&lt;p&gt;“However,” I said, “of all things that are such as to be related to something, those that are of a certain kind are related to a thing of a certain kind, as it seems to me, while those that are severally themselves are related only to a thing that is itself.”&lt;/p&gt;

&lt;p&gt;“I don’t understand,” [Glaucon] said.&lt;/p&gt;

&lt;p&gt;“Don’t you understand,” I said, “that the greater is such as to be greater than something?”&lt;/p&gt;

&lt;p&gt;…&lt;/p&gt;&lt;/blockquote&gt;
</content>
 </entry>
 
 <entry>
   <title>Dynamically adding methods to a Rails model</title>
   <link href="http://nonplus.me/2011/01/24/40821094/"/>
   <updated>2011-01-24T18:24:36+00:00</updated>
   <id>http://nonplus.mehttp://nonplus.me/2011/01/24/40821094</id>
   <content type="html">&lt;p&gt;I’ve been using &lt;a href=&quot;https://github.com/ryanb/cancan&quot;&gt;CanCan&lt;/a&gt; for implementing role-based access in my rails app and it’s been great so far.  I had created a separate convenience method in the User model for testing each possible user role, e.g.&lt;/p&gt;

&lt;div class=&quot;CodeRay&quot;&gt;
  &lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;function&quot;&gt;admin?&lt;/span&gt;
  &lt;span class=&quot;predefined-constant&quot;&gt;self&lt;/span&gt;.role == &lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;admin&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;'&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;keyword&quot;&gt;end&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;This was great and allowed me to do a simple &lt;code&gt;user.admin?&lt;/code&gt; to test for role membership.  After changing my roles around a few times I wanted a way to avoid the tedium of creating/editing those methods.  I began experimenting with Ruby’s powerful class manipulation abilities and came up with this:&lt;/p&gt;

&lt;p&gt;&lt;script src=&quot;https://gist.github.com/mrowl/793616.js&quot;&gt;&lt;/script&gt;&lt;/p&gt;

&lt;p&gt;It dynamically adds all those methods based on a simple list of possible roles.  I’ve only been using Ruby for a short while and the ease with which I was able to accomplish such a feat was pretty impressive.  Score one for Ruby.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Creating a new enum type in sqlalchemy with the type decorator</title>
   <link href="http://nonplus.me/2011/01/19/creating-a-new-enum-type-in-sqlalchemy-with-t/"/>
   <updated>2011-01-19T18:17:00+00:00</updated>
   <id>http://nonplus.mehttp://nonplus.me/2011/01/19/creating-a-new-enum-type-in-sqlalchemy-with-t</id>
   <content type="html">&lt;p&gt;I wasn’t satisfied with the built-in &lt;a href=&quot;http://www.sqlalchemy.org/docs/core/types.html#sqlalchemy.types.Enum&quot;&gt;EnumType&lt;/a&gt; that SQLAlchemy offers for column definitions because it’s limited to using the native sql enum type or a large varchar. The native sql enum isn’t a bad choice if you’re using MySQL, but I’ve found the postgres (which is what I’m using) implementation to be too rigid since you can’t easily alter the list of possible values. The varchar is overkill and takes up too much storage space.&lt;/p&gt;

&lt;p&gt;I settled on storage using a small integer due to its small footprint while still allowing a large bucket of values. I was initially planning on using the integers in the code and trying to remember what each integer represented but my feeble mind wasn’t up for the challenge. So I found the &lt;a href=&quot;http://www.sqlalchemy.org/docs/core/types.html#custom-types&quot;&gt;SQLAlchemy TypeDecorator&lt;/a&gt; class which makes it dead simple to create a custom column type:&lt;/p&gt;

&lt;p&gt;&lt;script src=&quot;https://gist.github.com/mrowl/786501.js&quot;&gt;&lt;/script&gt;&lt;/p&gt;

&lt;p&gt;Using the new type is very similar to the generic EnumType:&lt;/p&gt;

&lt;div class=&quot;CodeRay&quot;&gt;
  &lt;div class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;comment&quot;&gt;#in your table declaration&lt;/span&gt;
animal_table = sa.Table(&lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;delimiter&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;, meta,
    sa.Column(&lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;delimiter&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;, sa.types.Unicode(&lt;span class=&quot;integer&quot;&gt;31&lt;/span&gt;), nullable=&lt;span class=&quot;predefined-constant&quot;&gt;False&lt;/span&gt;),
    sa.Column(&lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;delimiter&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;, EnumIntType((&lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;delimiter&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;dog&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;delimiter&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;cat&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;delimiter&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;narwhal&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;)), nullable=&lt;span class=&quot;predefined-constant&quot;&gt;False&lt;/span&gt;),
    )

&lt;span class=&quot;comment&quot;&gt;#in a query&lt;/span&gt;
session.query(Animal).filter(Animal.type == &lt;span class=&quot;string&quot;&gt;&lt;span class=&quot;delimiter&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;content&quot;&gt;cat&lt;/span&gt;&lt;span class=&quot;delimiter&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;).all()&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
</content>
 </entry>
 

</feed>
