Mixpanel Engineering

Real-time scaling

My first week at Mixpanel, or how I didn’t take down the Internet

with 2 comments

If you're interested in what we work on, please apply - we're hiring: http://mixpanel.com/jobs/

During my first week at Mixpanel I was asked to design, implement and deploy a highly requested feature in our core javascript library.  I had just started as the new intern and I hit the ground running.  Our customers wanted a simple method to track link clicks without having to hassle with browser incompatibilities or fiddle with event models.  The new functionality would also lay the groundwork for future enhancements such as form integration.  I got to work right away.

The Design Process

When designing a new feature like this, I aim to create a simple interface.  I started the process by analyzing how a user currently implemented link tracking. The recommended pattern was the following:

1 2 3 4 5 6 7 8 9 10 11

This pattern has a couple of problems:

  1. It requires that the user knows to call preventDefault() on the event object
  2. It requires a selection library to be installed, in addition to our library
  3. It increases the time required to integrate with our library, and confuses some users

Next, I wrote up what I wanted the new interface to be:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31

I was careful to consider different ways a user might want to hook up link tracking, while also ensuring that we didn’t bloat the javascript with too many features.  Because of our goal to have a slim library, I evaluated the cost of each additional feature to determine how to get the most functionality from the least amount of code.

One major design choice was to not include a full featured dom querying library.  This choice meant that the interface would only be able to select by a single class/id without the help of jQuery/other selector engine.  To help me make this decision, I considered embedding a number of selection engines including SizzleJS, Qwery and the Essential Selector Library.  I then worked out the code required to implement a super simple selection engine (only selects by single class or single id):

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
function to_array(obj) {
var l, i = 0, ret = [];
if(obj === null || obj === undefined) { return []; }
try {
return Array.prototype.slice.call(obj, 0);
} catch (err) {
if (typeof(obj.length) === "number") {
for (l = obj.length; i < l; i++) {
ret[i] = obj[i];
}
} else {
while (obj[i] !== undefined) {
ret[i] = obj[i];
i++;
}
}
return ret;
}
}
 
function get_elements_by_class_name(className) {
if (typeof(document.getElementsByClassName) === "undefined") {
var hasClassName = new RegExp("(?:^|\\s)" + className + "(?:$|\\s)");
var allElements = document.getElementsByTagName("*");
var results = [];
 
var element, i;
for (i = 0; (element = allElements[i]) != null; i++) {
var elementClass = element.className;
if (elementClass && elementClass.indexOf(className) != -1 && hasClassName.test(elementClass)) {
results.push(element);
}
}
 
return results;
} else {
var elements = document.getElementsByClassName(className);
return to_array(elements);
}
}
 
function get_element_by_id(q) {
if (typeof(q) !== 'string') { return q; }
if (document.getElementById === undefined) {
if (document.all !== undefined) { return document.all[q]; }
if (document.layers !== undefined) { return document.layers[q]; }
return null;
} else {
return document.getElementById(q);
}
}

Based on the minimal amount of code required to implement our own simple selection library, I decided to go that route and allow users to pass in jQuery objects if they wanted to select something more complex.

 

Implementation

When writing code for a javascript library that is distributed across thousands of websites, cross-browser support is paramount.  Because of this I had to make a second major decision: how I would handle the click event.  After looking at event models in various browsers I decided to go with the traditional model.  The primary reason behind this conclusion is that it is supported across every browser that I tested against.  A very useful resource to use when dealing with the various javascript event models is the Introduction to Events chapter on Quirksmode.org.

To implement the actual mpmetrics.track_links() method I wrote an argument based router.  The idea was to catch errors as early as possible, and fail gracefully.  The router parses the arguments variable, and proxies the arguments to one of two functions based on the first argument.  Javascript makes this pattern very elegant with language features such as apply().

1 2 3 4 5 6 7 8 9 10 11

Another way our library fails gracefully is by ensuring that we wait only 300 ms for the Mixpanel servers to respond.  In this way our user’s sites stay responsive even if our servers are having problems.  I chose 300 ms based on a browser benchmark I wrote to evaluate the response time for the mpmetrics.track() method.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
// callback is a function that transitions to the new url
// the setup below ensures that we call the callback as soon as we hear
// back from mixpanel, or if 300 ms has passed, whichever happens first.
 
var t = window.setTimeout(callback, 300);
var cb = function() {
// we need to clear the timeout if we hear back from mixpanel
// otherwise we could trigger two page loads
window.clearTimeout(t);
callback();
};
 
// we send the link href along with the event
if(typeof(properties.url) === 'undefined' && element.href) {
properties.url = element.href;
}
 
metrics.track(event_name, properties, cb);
// prevent_default() ensures that the browser doesn't fire the default
// click event (or it would go to the page without waiting for mixpanel)
return prevent_default(e);

 

101 Big Ones

Part of writing a new feature for a core library is testing.  This part is always tedious, but a necessary evil in the world of software development.  To ensure that the new features I added weren’t going to destroy the Internet I doubled the number of tests in our suite to a healthy 101.  Although this may seem drastic for a single feature, it was important to test the many new tricks I added to our library.  QUnit, our javascript testing framework of choice, provides a no-hassle solution to writing lots of tests and quickly debugging any errors that come up.  Using modules you can further refine your test suite with full support for setting up and tearing down the DOM environment.

 

101 tests passed

 

Go Time.

After a final code review with our lead front-end engineer, Tim Trefren, it was time to deploy.  Seconds after our deploy scripts finished, thousands of browsers around the world downloaded and ran my code, no Internet apocalypse in sight.  I never imagined that I would be pushing something of this magnitude out in my first week, let alone as an intern.  Mixpanel has welcomed me as a core member of the team from day one, putting just as much trust on me as everyone else.  I know that every day I come into the office I will be making a difference rather than just fixing bugs and writing tests.  For a rewarding internship contact me and help us change the world of analytics.

“Enjoy the little things, for one day you may look back and realize they were the big things.”
~Robert Brault

If you're interested in what we work on, please apply - we're hiring: http://mixpanel.com/jobs/

Written by

May 23rd, 2011 at 10:54 am

Posted in Frontend

Tagged with , ,

2 Responses to 'My first week at Mixpanel, or how I didn’t take down the Internet'

Subscribe to comments with RSS or TrackBack to 'My first week at Mixpanel, or how I didn’t take down the Internet'.

  1. Great post!

    Is there a way to guarantee the tracking of the link with the asynchronous version of MixPanel? (i.e. mpq instead of mpmetrics)

    Adam

    21 Jul 11 at 3:17 pm

  2. Is the click handler using event delegation? Seems like it would have been easier and more efficient to match the clicked element via conditional rather than trying to find the elements and apply click handlers to each.

    Kevin

    30 Apr 12 at 12:55 am

Leave a Reply

Safe place to purchase clomid and buy weight loss pills