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

 <title>Dave Dash</title>
 <link href="http://davedash.com/tag/addons.mozilla.org/atom.xml" rel="self"/>
 <link href="http://davedash.com/tag/addons.mozilla.org"/>
 <updated>2012-01-17T21:54:19-08:00</updated>
 <id>http://davedash.com/</id>
 <author>
   <name>Dave Dash</name>
   <email>dd+atom1@davedash.com</email>
 </author>

 
 <entry>
   <title>Your objects, and all their friends</title>
   <link href="http://davedash.com/2010/06/25/your-objects%2C-and-all-their-friends/"/>
   <updated>2010-06-25T00:00:00-07:00</updated>
   <id>http://davedash.com/2010/06/25/your-objects,-and-all-their-friends</id>
   <content type="html">&lt;p&gt;This is complicated.&lt;/p&gt;

&lt;p&gt;In my ever-evolving quest to &lt;a href=&quot;/2010/03/05/django-fixture-magic-testing-issues-with-real-data/&quot;&gt;get data out of the AMO database&lt;/a&gt; for tests, I
found myself not just extracting a single object, but a list of complicated
requirements in order to fully replicate behavior in production in a testing
environment.&lt;/p&gt;

&lt;p&gt;For &lt;a href=&quot;https://addons.mozilla.org/en-US/firefox/&quot;&gt;AMO&lt;/a&gt; we can use &lt;a href=&quot;http://pypi.python.org/pypi/django-fixture-magic&quot;&gt;fixture magic&lt;/a&gt; to dump a single add-on and all of
it's &lt;em&gt;database&lt;/em&gt; dependencies so that it will insert safely into a
test-database.  But we need more than just valid data.  We need some supporting
data.  For an add-on to be browsable and searchable it needs to have a valid
version and the version needs to have a valid file.&lt;/p&gt;

&lt;p&gt;In our app we can check for these things by using this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;my_addon.current_version.files.all()[0]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Of course we need to check that &lt;code&gt;my_addon.current_version&lt;/code&gt; exists and that
&lt;code&gt;files.all()&lt;/code&gt; has at least one object.  This ends up being a lot of work if you
just know the &lt;code&gt;id&lt;/code&gt; of the add-on object.&lt;/p&gt;

&lt;p&gt;So what I want is something simple, like:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;./manage.py custom_dump addon 3615
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And it should get me everything I need to test add-on 3615, including a Version
object and any files associated with the version.&lt;/p&gt;

&lt;p&gt;Turns out this &lt;em&gt;just works&lt;/em&gt;.  It works if you define the following settings:&lt;/p&gt;

&lt;div class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;python&quot;&gt;&lt;span class=&quot;c&quot;&gt;## Fixture Magic&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;CUSTOM_DUMPS&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&amp;#39;addon&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;  &lt;span class=&quot;c&quot;&gt;# ./manage.py custom_dump addon id&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&amp;#39;primary&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;#39;addons.addon&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;  &lt;span class=&quot;c&quot;&gt;# This is our reference model.&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&amp;#39;dependents&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;  &lt;span class=&quot;c&quot;&gt;# These are items we wish to dump.&lt;/span&gt;
            &lt;span class=&quot;c&quot;&gt;# Magic turns this into current_version.files.all()[0].&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;&amp;#39;current_version.files.all.0&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&amp;#39;order&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;#39;app1.model1&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;#39;app2.model2&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,),&lt;/span&gt;  &lt;span class=&quot;c&quot;&gt;# stuff gets sorted&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&amp;#39;excludes&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;&amp;#39;app1.model1&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;#39;fields&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;#39;to&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;#39;hide&amp;#39;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,),&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;


&lt;p&gt;Using this we're able to find out that &lt;code&gt;addon&lt;/code&gt; means an &lt;code&gt;addons.addon&lt;/code&gt; object
and that you want the &lt;code&gt;addon&lt;/code&gt; object with an id of &lt;code&gt;3615&lt;/code&gt;.  From there we'll
try looking for dependent objects.  Using some black magic we can turn:
&lt;code&gt;current_version.files.all.0&lt;/code&gt; into
&lt;code&gt;addon.objects.get(pk=3615).current_version.files.all()[0]&lt;/code&gt;.  This gives us a
file.&lt;/p&gt;

&lt;p&gt;If we mimic our &lt;code&gt;dump_object&lt;/code&gt; command we can get the &lt;code&gt;file&lt;/code&gt; into the database
and everything that the file needs to be valid.  This in turn gives us enough
data (usually) to begin testing a single &lt;code&gt;addon&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So have fun with this, if you're database is remotely complicated, this can
save you some time replicating it during testing.&lt;/p&gt;

&lt;p&gt;Also note, that you can re-order models and exclude certain fields.  This can
make your fixtures very easy to load.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Alphabetical sorting in Sphinx</title>
   <link href="http://davedash.com/2010/04/21/alphabetical-sorting-in-sphinx/"/>
   <updated>2010-04-21T00:00:00-07:00</updated>
   <id>http://davedash.com/2010/04/21/alphabetical-sorting-in-sphinx</id>
   <content type="html">&lt;p&gt;Sphinx 0.9.9 is great at searching full text, but treating actual strings as attributes takes some work.&lt;/p&gt;

&lt;p&gt;Initially I employed the strategy of indexing my full text fields &lt;em&gt;and&lt;/em&gt; storing them as attributes.  E.g.:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;sql_query = SELECT name, name AS name_ord FROM documents
sql_attr_str2ordinal = name_ord
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This stores each attribute in lexical order.  Meaning if your name's are Apple, Aardvark, Button, Choco-room they would be given the ordinal 2, 1, 3, 4 respectively.&lt;/p&gt;

&lt;p&gt;However, this is case-insensitive.  So trying this approach:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;sql_query = SELECT name, UPPER(name) AS name_ord FROM documents
sql_attr_str2ordinal = name_ord
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Will allow for case-insensitive alphabetical sorting in Sphinx.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Test Driven Confidence</title>
   <link href="http://davedash.com/2010/04/20/test-driven-confidence/"/>
   <updated>2010-04-20T00:00:00-07:00</updated>
   <id>http://davedash.com/2010/04/20/test-driven-confidence</id>
   <content type="html">&lt;p&gt;If you're already testing your web applications, you can skip this post.&lt;/p&gt;

&lt;p&gt;One of the bugs I am working for &lt;a href=&quot;https://addons.mozilla.org/&quot;&gt;AMO&lt;/a&gt; on involves porting a small, but moderately complicated checkbox from our PHP site and rewriting it for Django.&lt;/p&gt;

&lt;p&gt;I decided to look at the existing implementation and found it to not work correctly at all.  This was frustrating, especially since I verified that my own code worked, and that QA verified that it worked as well.&lt;/p&gt;

&lt;p&gt;This is frustrating on many levels.  Chances are some minor assumption I made changed, and thus broke this functionality.  Discovering regressions is never fun, and fixing them is can be long and tedious if you can't automatically verify that everything is working correctly.&lt;/p&gt;

&lt;p&gt;Lucky for me, coming up with tests is easy, you just do what you would do to verify the code satisfies the requirements and then code it.  Sometimes the tests can take longer than writing the actual code, but ultimately you can ship with confidence.  You can be confident that your feature won't break in the future without immediate notice, and you can be confident that your new code won't break anything else.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>django-fixture-magic: Testing issues with real data.</title>
   <link href="http://davedash.com/2010/03/05/django-fixture-magic-testing-issues-with-real-data/"/>
   <updated>2010-03-05T00:00:00-08:00</updated>
   <id>http://davedash.com/2010/03/05/django-fixture-magic-testing-issues-with-real-data</id>
   <content type="html">&lt;p&gt;I just released &lt;a href=&quot;http://github.com/davedash/django-fixture-magic&quot;&gt;Fixture Magic&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;When dealing with legacy data, you'll run into all kinds of edge cases.  Perhaps, an object might not display correctly unless it has the right parameters, or if it has null parameters it might not display at all.  So when testing &lt;a href=&quot;http://djangoproject.com/&quot;&gt;Django&lt;/a&gt;, it's nice to actually use non-dummy data.&lt;/p&gt;

&lt;p&gt;Luckily Django has a way of pulling real data out of your database using &lt;code&gt;django.core.serializers&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;from addons.models import Addon
a = Addon.objects.get(id=3615)
from django.core.serializers import serialize
jsonize = lambda a: serialize(&quot;json&quot;, a, indent=4)
jsonize([a])
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This solution runs well in a Django shell and can be lots of fun for the whole family... until things get complicated.&lt;/p&gt;

&lt;!--more--&gt;


&lt;h3&gt;Serializing alone isn't enough.&lt;/h3&gt;

&lt;p&gt;Serializing a fixture with foreign keys means you'll have an un-loadable fixture unless you serialize the dependent fixtures.  Even for one or two foreign keys, this can be a pain.  For &lt;a href=&quot;http://addons.mozilla.org/&quot;&gt;addons.mozilla.org&lt;/a&gt;, we have a spidery-web of dependencies: &lt;code&gt;File&lt;/code&gt;s need a &lt;code&gt;Version&lt;/code&gt; which needs an &lt;code&gt;Addon&lt;/code&gt; which need &lt;code&gt;Translation&lt;/code&gt;s.&lt;/p&gt;

&lt;p&gt;Thus begat the &lt;code&gt;dump_object&lt;/code&gt; management command.  Give it an app, model name and a &lt;code&gt;pk&lt;/code&gt; and it will give you not only a serialized JSON of that object, but all the objects that it requires.&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;./manage.py dump_object files.file 64874 64876 &amp;gt; my_new_fixture.json
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This looks for the &lt;code&gt;File&lt;/code&gt; model in the &lt;code&gt;files&lt;/code&gt; app and pulls out of the database &lt;code&gt;File&lt;/code&gt;s instances with &lt;code&gt;pk&lt;/code&gt;s of &lt;code&gt;64874&lt;/code&gt; and &lt;code&gt;64876&lt;/code&gt;.  It then recursively searches for any required objects.&lt;/p&gt;

&lt;h3&gt;Too much serial&lt;/h3&gt;

&lt;p&gt;If you create a lot of fixtures, you'll eventually have overlapping serialized objects.  In &lt;code&gt;addons.mozilla.org&lt;/code&gt; we have &lt;code&gt;Addon&lt;/code&gt;s, &lt;code&gt;Version&lt;/code&gt;s (which depend on &lt;code&gt;Addon&lt;/code&gt;s) and &lt;code&gt;AddonCategory&lt;/code&gt;s (which depend on &lt;code&gt;Addon&lt;/code&gt;s and &lt;code&gt;Category&lt;/code&gt;s).  If we wanted to get serialize a specific &lt;code&gt;Addon&lt;/code&gt;, it's dependent &lt;code&gt;Version&lt;/code&gt;s and &lt;code&gt;AddonCategory&lt;/code&gt;s it makes sense to start with &lt;code&gt;dump_object&lt;/code&gt;ing the related &lt;code&gt;Version&lt;/code&gt; and then &lt;code&gt;dump_objecting&lt;/code&gt; the &lt;code&gt;AddonCategory&lt;/code&gt;.  Both &lt;code&gt;dump_object&lt;/code&gt; commands will fetch the &lt;code&gt;Addon&lt;/code&gt; in question, resulting in duplicated data.&lt;/p&gt;

&lt;p&gt;To combat this we can use &lt;code&gt;merge_fixtures&lt;/code&gt; to dedupe our fixtures:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;./manage.py dump_object versions.version 64874 &amp;gt; 1.json
./manage.py dump_object categories.addoncategory &amp;gt; 2.json
./manage.py merge_json 1.json 2.json &amp;gt; happy_fixture.json
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This should make creating test data slightly less painful.  So &lt;a href=&quot;http://github.com/davedash/django-fixture-magic&quot;&gt;give it a try&lt;/a&gt;.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Django: Model Inheritance or Related Tables wrt AMO</title>
   <link href="http://davedash.com/2009/12/15/django-model-inheritance-or-related-tables-wrt-amo/"/>
   <updated>2009-12-15T00:00:00-08:00</updated>
   <id>http://davedash.com/2009/12/15/django-model-inheritance-or-related-tables-wrt-amo</id>
   <content type="html">&lt;p&gt;When I attended DjangoCon this year, I lamented that our flagship web property was difficult to test, and not fun to develop.  I figured DjangoCon was a way to placate me, and Django might mean something for some of the smaller projects at Mozilla.  However, Wil Clouser, our lead web developer, &lt;a href=&quot;http://micropipes.com/blog/2009/11/17/amo-development-changes-in-2010/&quot;&gt;announced development changes&lt;/a&gt; for &lt;a href=&quot;http://addons.mozilla.org&quot;&gt;addons.mozilla.org&lt;/a&gt; (AMO) that says we'll be moving to Django.&lt;/p&gt;

&lt;p&gt;Wil was open to Django and knew that's what we in the dev team wanted.  Jeff spawned our foray into a new AMO with &lt;a href=&quot;http://github.com/jbalogh/zamboni&quot;&gt;Zamboni&lt;/a&gt;.  I've been working on some grunt-work tasks inside and outside of Django.&lt;/p&gt;

&lt;p&gt;One of those tasks is building a transparent layer in Django to keep users logged in from our PHP-based site.  That kind of problem almost immediately forces you to ask one of the most fundamental questions you ask when using any framework:&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;How much do I change my app, in order to accommodate the framework?&lt;/p&gt;&lt;/blockquote&gt;

&lt;!--more--&gt;


&lt;p&gt;More specifically:&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;Should I use the &lt;code&gt;django.contrib.auth&lt;/code&gt; User module, and to what extent?&lt;/p&gt;&lt;/blockquote&gt;

&lt;p&gt;The more we looked into what features of Django we might want to use, &lt;code&gt;django.contrib.auth&lt;/code&gt; was heavily tied into other things we wanted, so it made sense for us to use it.  The next question is whether we try the &lt;a href=&quot;http://scottbarnham.com/blog/2008/08/21/extending-the-django-user-model-with-inheritance/&quot;&gt;inheritance approach&lt;/a&gt; or do we treat our legacy users table as a sort of User Profile and utilize the User module using the &lt;a href=&quot;http://www.b-list.org/weblog/2007/feb/20/about-model-subclassing/&quot;&gt;related table approach&lt;/a&gt;?&lt;/p&gt;

&lt;p&gt;Using model-inheritance seems real nice, because we can pretend that our legacy user is the same thing as a &lt;code&gt;djaango.contrib.auth&lt;/code&gt; User - but this isn't true:&lt;/p&gt;

&lt;p&gt;Looking at our &lt;code&gt;users&lt;/code&gt; table more closely:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;mysql&amp;gt; explain users;
+-------------------------+---------------------+------+-----+---------------------+----------------+
| Field                   | Type                | Null | Key | Default             | Extra          |
+-------------------------+---------------------+------+-----+---------------------+----------------+
| id                      | int(11) unsigned    | NO   | PRI | NULL                | auto_increment |
| email                   | varchar(255)        | YES  | UNI | NULL                |                |
| password                | varchar(255)        | NO   |     |                     |                |
| firstname               | varchar(255)        | NO   |     |                     |                |
| lastname                | varchar(255)        | NO   |     |                     |                |
| nickname                | varchar(255)        | YES  | MUL | NULL                |                |
| bio                     | int(11) unsigned    | YES  | MUL | NULL                |                |
| emailhidden             | tinyint(1) unsigned | NO   |     | 0                   |                |
| sandboxshown            | tinyint(1) unsigned | NO   |     | 0                   |                |
| homepage                | varchar(255)        | YES  |     | NULL                |                |
| display_collections     | tinyint(1) unsigned | NO   |     | 0                   |                |
| display_collections_fav | tinyint(1) unsigned | NO   |     | 0                   |                |
| confirmationcode        | varchar(255)        | NO   |     |                     |                |
| resetcode               | varchar(255)        | NO   |     |                     |                |
| resetcode_expires       | datetime            | NO   |     | 0000-00-00 00:00:00 |                |
| notifycompat            | tinyint(1) unsigned | NO   | MUL | 1                   |                |
| notifyevents            | tinyint(1) unsigned | NO   | MUL | 1                   |                |
| deleted                 | tinyint(1)          | YES  |     | 0                   |                |
| created                 | datetime            | NO   | MUL | 0000-00-00 00:00:00 |                |
| modified                | datetime            | NO   |     | 0000-00-00 00:00:00 |                |
| notes                   | text                | YES  |     | NULL                |                |
| location                | varchar(255)        | NO   |     |                     |                |
| occupation              | varchar(255)        | NO   |     |                     |                |
| picture_type            | varchar(25)         | NO   |     |                     |                |
| averagerating           | varchar(255)        | YES  |     | NULL                |                |
+-------------------------+---------------------+------+-----+---------------------+----------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You can very easily argue that this is a profile table, which happens to have credential information thrown in.&lt;/p&gt;

&lt;p&gt;I can see overtime, I'll just struggle to keep our legacy User to act like a Django User, whereas a UserProfile is fairly standard.&lt;/p&gt;

&lt;p&gt;Had I been writing this app from scratch, I would have chosen the UserProfile route.  This is extra data which takes up a lot of space, and changes far more often than user credentials.  Changing 4M+ rows sucks, by making users our UserProfile table, any changes to that table, don't tie up the table used for sign-ins.&lt;/p&gt;

&lt;p&gt;I'm curious what other people who port their apps to Django have done.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>AMO Search: Powered by Sphinx</title>
   <link href="http://davedash.com/2009/09/30/amo-search-powered-by-sphinx/"/>
   <updated>2009-09-30T00:00:00-07:00</updated>
   <id>http://davedash.com/2009/09/30/amo-search-powered-by-sphinx</id>
   <content type="html">&lt;p&gt;Last night, I gave a talk at the &lt;a href=&quot;https://wiki.mozilla.org/AddonMeetups:2009:Chicago&quot;&gt;Addons Meetup&lt;/a&gt; at Threadless HQ in Chicago on the new search engine powering &lt;a href=&quot;http://addons.mozilla.org/&quot;&gt;addons.mozilla.org&lt;/a&gt;.  I'll recap the technical portion of the talk and give a bit more details.&lt;/p&gt;

&lt;p&gt;First, I'd like to thank Harper and Threadless.  It was a great location in the greatest city in the universe.  Before and after the meetup, Harper was just an all-around great guy to hang with and the threadless headquarters was a nice hangout place for meeting people interested in addons.&lt;/p&gt;

&lt;p&gt;Shortly after my talk, our Engineering Ops team deployed the new AMO 5.1 complete with a new Sphinx powered search engine.&lt;/p&gt;

&lt;p&gt;So let's talk about search.  Note: parts of this are a rehash of my talk, so feel free to skip around.&lt;/p&gt;

&lt;!--more--&gt;


&lt;h3&gt;A bit about addons&lt;/h3&gt;

&lt;p&gt;Addons is a huge growing space.  Arguably it's Mozilla's best kept secret.  Sure readers of this blog probably know what Addons are, but ask people who aren't as web-savvy.  Most people don't know what a browser is - and it's hard to explain it to people without getting technical.&lt;/p&gt;

&lt;p&gt;We can just skip that step.  Because Addons are small things that people can easily &quot;get&quot;.&lt;/p&gt;

&lt;p&gt;&quot;It's an easy way to customize the internet when your surfing.&quot;&lt;/p&gt;

&lt;p&gt;While perhaps not technically correct, its one way of explaining it to people.  Maybe a better way is just showing people what they can do with addons.&lt;/p&gt;

&lt;p&gt;On my flight out to Chicago, I talked to a person on the plane who didn't know what a browser was, but after showing her &lt;a href=&quot;http://addons.mozilla.org/&quot;&gt;AMO&lt;/a&gt; she was really intrigued.&lt;/p&gt;

&lt;p&gt;If everyday non-technical people can realize the potential of addons, it's only a matter of time before they start knocking down the doors to AMO.&lt;/p&gt;

&lt;p&gt;So we better be prepared to handle them, and get them what they want.&lt;/p&gt;

&lt;h3&gt;The technical details of addons.mozilla.org&lt;/h3&gt;

&lt;p&gt;Everytime you open Firefox, it pings &lt;a href=&quot;http://addons.mozilla.org/&quot;&gt;AMO&lt;/a&gt; to see if there's any updates to any of the addons that happen to be installed.  Over a third of the people using Firefox have at least one addon, and Firefox is roughly 22% of the browser market.  That means roughly 7% of people opening their browsers are pinging our servers for updates.&lt;/p&gt;

&lt;p&gt;Needless to say it's a lot of traffic, and to support it we need a fair amount of hardware.  AMO is clearly the largest site in the Mozilla universe in both respects.&lt;/p&gt;

&lt;p&gt;Some stats:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1 mySQL master&lt;/li&gt;
&lt;li&gt;4 mySQL slaves&lt;/li&gt;
&lt;li&gt;2 memached servers&lt;/li&gt;
&lt;li&gt;2 Sphinx indexer/search daemons&lt;/li&gt;
&lt;li&gt;24 Web Frontend&lt;/li&gt;
&lt;li&gt;Multiple Zeus ZXTM clusters all&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;Most of this is standard, we'll talk about Sphinx later, but Zeus is amazing.  I didn't know what Zeus was until earlier this year when I interviewed with Mozilla's VP of Engineering Operations.  All our requests get cached so much of our hits actually hit our Zeus cluster and not our web servers.&lt;/p&gt;

&lt;p&gt;To see just how amazing they are read our &lt;a href=&quot;http://blog.mozilla.com/mrz/&quot;&gt;mrz's ops blog&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;Why search matters&lt;/h3&gt;

&lt;p&gt;If you have any kind of custom content and unique meta data a custom search solution is a must.  Browsing through a site isn't going to cut it.  Browsing is dead.  Search is how you find things on a web site.  On &lt;a href=&quot;http://addons.mozilla.org/&quot;&gt;AMO&lt;/a&gt; you may see an addon that's featured somewhere, or you might want to see what's out there, but the right search query will find you the right addon in two clicks.&lt;/p&gt;

&lt;h3&gt;Improve Search&lt;/h3&gt;

&lt;p&gt;So my first job on AMO was to &lt;a href=&quot;https://bugzilla.mozilla.org/show_bug.cgi?id=498999&quot;&gt;improve addons search&lt;/a&gt;.  It was a vague request and born out of frustration with what we had.  It wasn't a problem that certain things were indexed, or unicode didn't work, or results weren't sorted.  We may have had all those problems, but as a product search needed to be replaced.&lt;/p&gt;

&lt;p&gt;To me it meant that we needed some framework that would allow developers to quickly debug and fix any future search calamities at a moments notice.&lt;/p&gt;

&lt;p&gt;So here were the goals I made for myself:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do something that sucks less than what we’ve got&lt;/li&gt;
&lt;li&gt;Do something that makes it easier to suck less in the future&lt;/li&gt;
&lt;li&gt;Do something that’s easy to use for our operations team, web developers and most importantly, end-users&lt;/li&gt;
&lt;li&gt;Reduce strain on our databases, developers and operations teams&lt;/li&gt;
&lt;/ul&gt;


&lt;h3&gt;Complex Data&lt;/h3&gt;

&lt;p&gt;Our data set is small (we have 5,000 addons), but there's a lot of secondary meta data about the addons that we track:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Addons work in 1 or more locales (e.g. en-US, fr, de, etc)&lt;/li&gt;
&lt;li&gt;Addons are optionally platform specific (Linux, OS X, etc)&lt;/li&gt;
&lt;li&gt;Addons work with one or more products (Firefox, Thunderbird, Seamonkey, Sunbird or Fennec)&lt;/li&gt;
&lt;li&gt;Addons come in multiple flavors (extensions, themes, dictionaries and more)&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;We want to index all this data.  Unfortunately to get at much of this data it involves either numerous queries, or numerous joins which put a strain on mysql.  How much strain?&lt;/p&gt;

&lt;p&gt;At peak we get about 10 search queries per second.  If we do something smarter this won't have to cause a lot of strain.&lt;/p&gt;

&lt;h3&gt;Using Sphinx&lt;/h3&gt;

&lt;p&gt;Sphinx is an open source search indexer and daemon.  It's used by Craigslist, the Pirate Bay and &lt;a href=&quot;http://support.mozilla.com&quot;&gt;Mozilla Support&lt;/a&gt;.  It was very easy to use and despite a complicated set of data and business logic, Sphinx was up to the task.&lt;/p&gt;

&lt;h3&gt;The challenges&lt;/h3&gt;

&lt;p&gt;We needed to search for addons in several languages.  So indexing just addons wouldn't work, we need to make sure we have every translation of every addon indexed.  For those counting, we have 5,000 addons, but 18,000 translations of addons.&lt;/p&gt;

&lt;p&gt;All the joining and filtering that needed to be done for our old search still needs to be done, but we can do this all in one shot by using a mysql view.  This view is a flat list of each translated addon as well as all meta data associated with it.  This then gets fed into the sphinx indexer.&lt;/p&gt;

&lt;p&gt;Along the way we ran into some issues which used to be dealt with outside of mysql, such as comparing versions.  It was gross and quite a hack, so we turned the variety of &lt;a href=&quot;http://spindrop.us/2009/08/07/v-is-for-version-hell/&quot;&gt;acceptable version strings into integers&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We also learned that stemming wasn't a good idea as we assumed it would be.  Stemming was great for searching through lots of text, but a great deal of addon searches were really just searches for product names, so we opted for substring searches.  We'll see how that fares.  There is probably room for improvement.&lt;/p&gt;

&lt;p&gt;Much of this, however involved knowing our data, and knowing how it will be used by our users.  Once we got that down, we could hammer it all out using Sphinx.&lt;/p&gt;

&lt;h3&gt;Wins&lt;/h3&gt;

&lt;p&gt;So Sphinx gains us a bit architecturally.  We have a complicated query, but it only gets run once every 5 minutes versus the 180,000 times it was run &quot;on demand.&quot;&lt;/p&gt;

&lt;p&gt;Indexing happens rather quickly, just over a minute.&lt;/p&gt;

&lt;p&gt;The API was a breeze to work with, and was easy to drop into our own codebase.&lt;/p&gt;

&lt;p&gt;Because of our relatively small data set, and quick indexing, we're able to scale this simply by cloning and load balancing.  Meaning, we just need to scale for traffic, but addon growth (which is slower than traffic growth) we can safely not worry about for a while.&lt;/p&gt;

&lt;p&gt;Our ops team can monitor the sphinx clusters and just deploy additional nodes as needed.&lt;/p&gt;

&lt;h3&gt;Building a platform&lt;/h3&gt;

&lt;p&gt;What we've done is built a foundation for search.  Not all the problems are gone, but a lot of the problems that our QA team finds are able to be resolved quickly.  We have a nice pile of unit tests as well that help us keep our results in check when we start tweaking dials.&lt;/p&gt;

&lt;p&gt;We even have the groundwork for some nifty advanced search syntax, that hopefully we can inject into future releases of AMO.&lt;/p&gt;

&lt;p&gt;Enjoy.  And if you find anything, &lt;a href=&quot;http://bit.ly/search-bugs&quot;&gt;let me know&lt;/a&gt;.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>V is for Version Hell</title>
   <link href="http://davedash.com/2009/08/07/v-is-for-version-hell/"/>
   <updated>2009-08-07T00:00:00-07:00</updated>
   <id>http://davedash.com/2009/08/07/v-is-for-version-hell</id>
   <content type="html">&lt;p&gt;Versioning is quite difficult to deal with.  Versions are nearly-numbers, but
you can't quite sort them using standard numerical algorithms.&lt;/p&gt;

&lt;p&gt;While the following is true:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;1.1 &amp;lt; 1.2
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The following is also true:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;1.2 &amp;lt; 1.18 &amp;lt; 1.20
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The &quot;.&quot; is not a decimal point but a separator.&lt;/p&gt;

&lt;p&gt;Mozilla uses a modestly complicated &lt;a href=&quot;https://developer.mozilla.org/en/Toolkit_version_format&quot;&gt;versioning system&lt;/a&gt; that involves stars,
plusses, and sometimes &quot;x&quot;.&lt;/p&gt;

&lt;p&gt;I found a very convoluted way to translate these versions into large integers.
The versions for applications in the AMO database have four parts at most, they
are potentially alpha or beta and potentially a pre-release.  In some cases we
have multiple versions represented with &lt;code&gt;.*&lt;/code&gt;, &lt;code&gt;.x&lt;/code&gt; or &lt;code&gt;+&lt;/code&gt; at the end.&lt;/p&gt;

&lt;!--more--&gt;


&lt;p&gt;The &lt;a href=&quot;https://developer.mozilla.org/en/Toolkit_version_format&quot;&gt;Toolkit docs&lt;/a&gt; let us translate &quot;+&quot; to mean &quot;pre-release of the next
version&quot;.  E.g. 1.0+ is 1.1pre0.  Since my primary purpose of all this is for
sorting, &lt;code&gt;.*&lt;/code&gt; and &lt;code&gt;.+&lt;/code&gt; may as well just be a very large &quot;version part.&quot;  Since
all the version parts I deal with are a maximum of 2-digits, I turned &lt;code&gt;.*&lt;/code&gt; and
&lt;code&gt;.+&lt;/code&gt; into &lt;code&gt;.99&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;3.5+ =&amp;gt; '03'+'05'+'99' =&amp;gt; 030599
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We also need to deal with versions that may be alpha, beta or not.  If
everything else is equal:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;3.5a &amp;lt; 3.5a5 &amp;lt; 3.5b &amp;lt; 3.5b2 &amp;lt; 3.5 &amp;lt; 3.5+
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We assign a single integer to represent a version's &quot;non-alphaness&quot;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;a =&amp;gt; 0
b =&amp;gt; 1
non alpha/beta =&amp;gt; 2
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We assume that &lt;code&gt;3.5a = 3.5a1&lt;/code&gt;.  Therefore:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;'3.5a =&amp;gt; 3.5.0a1 =&amp;gt; '03'+'05'+'00'+'0'+'01' =&amp;gt; 030500001
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Similarly if it's a pre-release we assign a 0 or 1 to represent
&quot;non-pre-releaseness&quot;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;'3.5a pre2 =&amp;gt; 3.5.0a1pre2
=&amp;gt; '03'+'05'+'00'+'0'+'01'+'0'+'02
=&amp;gt; 030500001002
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;So what does this get us?  Integers which we can use for comparison, sorting,
etc.  It's a one time calculation for each version and we can do some nice SQL
statements in AMO like:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;mysql&amp;gt; SELECT version,version_int FROM appversions WHERE application_id = 1 ORDER BY version_int LIMIT 15;
+---------+--------------+
| version | version_int  |
+---------+--------------+
| 0.3     |  30000200100 |
| 0.6     |  60000200100 |
| 0.7     |  70000200100 |
| 0.7+    |  80000200000 |
| 0.8     |  80000200100 |
| 0.8+    |  90000200000 |
| 0.9     |  90000200100 |
| 0.9.0+  |  90100200000 |
| 0.9.1+  |  90200200000 |
| 0.9.2+  |  90300200000 |
| 0.9.3   |  90300200100 |
| 0.9.3+  |  90400200000 |
| 0.9.x   |  99900200100 |
| 0.9+    | 100000200000 |
| 0.10    | 100000200100 |
+---------+--------------+
15 rows in set (0.00 sec)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I can now index these integers using Sphinx and do some very easy searches for
addons based on version number.&lt;/p&gt;
</content>
 </entry>
 

</feed>

