Apache Spark native functions

There are many ways to extend Apache Spark and one of the easiest is with functions that manipulate one of more columns in a DataFrame. When considering different Spark function types, it is important to not ignore the full set of options available to developers.

Beyond the two types of functions–simple Spark user-defined functions (UDFs) and functions that operate on Column–described in the previous link, there two more types of UDFs: user-defined aggregate functions (UDAFs) and user-defined table-generating functions (UDTFs). sum() is an example of an aggregate function and explode() is an example of a table-generating function. The former processes many rows to create a single value. The latter uses value(s) from a single row to “generate” many rows. Spark supports UDAFs directly and UDTFs indirectly, by converting them to Generator expressions.

Beyond all types of UDFs, Spark’s most exciting functions are Spark’s native functions, which is how the logic of most of Spark’s Column and SparkSQL functions is implemented. Internally, Spark native functions are nodes in the Expression trees that determine column values. Very loosely-speaking, an Expression is the internal Spark representation for a Column, just like a LogicalPlan is the internal representation of a data transformation (Dataset/DataFrame).

Native functions, while a bit more involved to create, have three fundamental advantages: better user experienceflexibility and performance.

Better user experience & flexibility comes from native functions’ lifecycle having two distinct phases:

  1. Analysis, which happens on the driver, while the transformation DAG is created (before an action is run).
  2. Execution, which happens on executors/workers, while an action is running.

The analysis phase allows Spark native functions to dynamically validate the type of their inputs to produce better error messages and, if necessary, change the type of their result. For example, the return type of sort_array() depends on the input type. If you pass in an array of strings, you’ll get an array of strings. If you pass in an array of ints, you’ll get an array of ints.

A user-defined function, which internally maps to a strongly-typed Scala/JVM function, cannot do this. We can parameterize an implementation by the type of its input, e.g.,

def mySortArray[A: Ordered](arr: Array[A]): Array[A]

but we cannot create type-parameterized UDFs in Spark, requiring hacks such as

spark.udf.register("my_sort_array_int", mySortArray[Int] _)
spark.udf.register("my_sort_array_long", mySortArray[Long] _)

Think of native functions like macros in a traditional programming language. The power of macros also comes from having a lifecycle with two execution phases: compile-time and runtime.

Performance comes from the fact that Spark native functions operate on the internal Spark representation of rows, which, in many cases, avoids serialization/deserialization to “normal” Scala/Java/Python/R datatypes. For example, internally Spark strings are UTF8String. Further, you can choose to implement the runtime behavior of a native function by code-generating Java and participating in whole-stage code generation (reinforcing the macro analogy) or as a simple method.

Working with Spark’s internal (a.k.a., unsafe) datatypes does require careful coding but Spark’s codebase includes many dozens of examples of native functions: essentially, the entire SparkSQL function library. I encourage you to experiment with native Spark function development. As an example, take a look at array_contains().

For user experience, flexibility and performance reasons, at Swoop we have created a number of native Spark functions. We plan on open-sourcing many of them, as well as other tools we have created for improving Spark productivity and performance, via the spark-alchemy library.

Posted in Big data | Tagged , , , , , | 3 Comments

Unicorn pressures and startup failures

The startup anti-patterns section of my blog summarizes the repeatable ways startups waste time & money and, often, fail. Learning from startup failure is valuable because there are many more examples of failures that successes. (Anti-)Patterns become more noticeable and easier to verify.

For the same reason, it’s useful to read the failure post-mortems founders write. It takes meaningful commitment to discover the posts and to distill the key insights from the sometimes lengthy prose (an exercise in therapy at least as much as reporting of the facts). Luckily, there is a shortcut: the CB Insights summary of startup failures. It’s part table of contents and part Cliff Notes. It can help you pick the ones that are worth reading in full.

Some of the insights from post-mortems come from understanding the emotional biases of founders, CXOs and investors. In the uncertain startup execution environment these biases have the ability to affect behavior much more than in situations where reality is inescapable and readily quantifiable.

Speaking of emotional biases, Bill Gurley’s post on the Unicorn pressure cooker now that the magic has worn off is a must.

Posted in angel investing, Anti-pattern, startups, VC, Venture Capital | Tagged , , , , | Leave a comment

Advertising marketplace design

In the past decade several Nobel prizes in Economics have been awarded in the broader area of market (mechanism/auction/game) design. This is not surprising as the combination of Internet connectivity and ample computing resources are causing automated markets to pop up all over. One of biggest and fastest-growing in recent years as been the programmatic advertising market. For example, variations of the Vickrey–Clarke–Groves auction power the Facebook and Google ad exchanges.

When lots of players are lining up to feed at the advertising money troth, it sometimes becomes difficult to separate reality from marketing hype. The programmatic hype is that it brings efficiency to advertising (and does your laundry to boot). The reality is very different. While there are many benefits to programmatic advertising, it also causes and exacerbates many problems in the advertising ecosystem that hurt publishers, advertisers and consumers in the long run. The root cause is that the leading open programmatic protocol–OpenRTBfails to align marketplace interests. This is what happens when adtech optimizes for volume as opposed to quality.

Posted in Advertising, Digital Media | Tagged , , , , , | Leave a comment

Angel investing strategies

My friend Jerry Neumann wrote a great post on angel investing strategies, dissecting truth and myth about different betting strategies and sharing his own approach.

The question of luck came up and a commenter linked to my work on data-driven patterns of successful angel investing with the subtext that being data driven implies index investing. That’s certainly not what I believe or recommend.

The goal of my Monte Carlo analysis was to shine a light on the main flaw I’ve seen in casual angel investing, which is the angel death spiral:

  1. Make a few relatively random investments
  2. Lose money
  3. Become disillusioned
  4. Give up angel investing
  5. Tell all your friends angel investing is terrible

Well, you can’t expect a quick win out of a highly skewed distribution (startup exits are a very skewed distribution). That’s just math and math is rather unemotional about these things.

You can get out of the angel death spiral in one of two ways. You can take the exit distribution for what it is. In that case, you need many more shots on goal (dozens of investments) to ensure a much better outcome. Alternatively, you can try to pick your investment opportunities from a different, better distribution. That’s what I like to do and this is what Jerry is advocating.

The main influencer of return for angel investors is the quality of deal flow that you can win. Why? Because this changes the shape of your personal exit distribution and, in most cases not involving unicorn hunting, improves your outcomes at any portfolio size.

As an investor, you sell cash + you and buy equity. To see better deals and win them you need to increase the value of “you.” After all, anyone’s cash is just as good as everyone else’s. The easiest way to do this is via deep, real, current expertise and relationships that are critical to the success of the companies you want to invest in, backed by a reputation that you are a helpful and easy to work with angel. One way to maximize the chance of this being true is to follow some of Jerry’s advice:

  • Invest in markets that you know
  • Make multiple investments in such markets
  • Help your companies

There is a bootstrap problem, however, when new markets are concerned. How do you get to know them? Well, one way to do it is to make a number of investments in a new space. In this case, your investments have dual value: in addition to the financial return expectations (which should be reduced) you have the benefit of learning. Yes, it can be an expensive way to learn but it may be well worth it when you consider the forward benefits that affect the quality of your deal flow and your ability to win deals.

As an aside, I’ve always advised angels to not invest just for financial return. Do angel investing to increase your overall utility (in the multi-faceted economic theory sense) and do it so that it generates a return you are happy with.

In summary:

  1. Don’t attempt to pick unicorns as an angel.
  2. Where you can get high-quality deal flow you can win, do a smaller number of deals.
  3. Where needed, and if you can afford it, use higher-volume investing as a way to signal interest in a market and to learn about it so that you can get higher-quality deal flow.
Posted in angel investing, VC, Venture Capital | Tagged , , , | 4 Comments

JSON and JSONlines from the command line

At Swoop we have many terabytes of JSON-like data in MongoDB, Redis, ElasticSearch, HDFS/Hadoop and even Amazon Redshift. While the internal representations are typically not JSON but BSON, MsgPack or native encodings, when it comes time to move large amounts of data for easy ad hoc processing I often end up using JSON and its bulk cousin, JSONlines. This post is about what you can quickly do with this type of data from the command line.

The best JSON(lines) command line tools

There has been a marked increase in the number of powerful & robust tools for validating and manipulating JSON and JSONlines from the command line. My favorites are:

  • jq: a blazingly fast, C-based stream processor for JSON documents with an easy yet powerful language. Think of it as sed and awk for JSON but without the 1970s syntax. Simple tasks are trivial. Powerful tasks are possible. The syntax is intuitive. Check out the tutorial and manual. Because of its stream orientation and speed, jq is the most natural fit when processing large amounts of JSONlines data. If you want to push the boundaries of what is sane to do on the command line there are conditionals, variables and UDFs.
  • underscore-cli: this is the Swiss Army knife for manipulating JSON on the command line. Based on Node.js, it supports JavaScript and CoffeeScript expressions with built-in functional programming primitives from the underscore.js library, relatively easy JSON traversal via json:select and more. This also is the best tool for debugging JSON data because of the multitude of output formats. A special plus in my book is that underscore-cli supports MsgPack, which we use in real-time flows and inside memory-constrained caches.
  • jsonpath: Ruby-based implementation of JSONPath with a corresponding command line tool. Speedy it is not but it’s great when you want JSONPath compatibility or can reuse existing expressions. There are some neat features such as pattern-based tree replace operations.
  • json (a.k.a., jsontool): another tool based on Node.js. Not as rich as underscore-cli but has a couple of occasionally useful features having to do with merging and grouping of documents. This tool also has a simple validation-only mode, which is convenient.

Keep in mind that you can modify/extend JSON data with these tools, not just transform it. jsontool can edit documents in place from the command line, something that can be useful for, for example, quickly updating properties in JSON config files.

JSON and 64-bit (BIGINT) numbers

JSON has undefined (as in implementation-specific ) semantics when it comes to dealing with 64-bit integers. The problem stems from the fact that JavaScript does not have this data type. There are Python, Ruby and Java JSON libraries that have no problem with 8-byte integers but I’d be suspicious of any Node.js implementation. If you have this type of data, test the edge cases with your tool of choice.

JSONlines validation & cleanup

There are times when JSONlines data does not come clean. It may include error messages or a mix of STDOUT and STDERR output (something Heroku is notorious for). At those times, it’s good to know how to quickly validate and clean up a large JSONlines file.

To clean up the input, we can use a simple sed incantation that removes all lines that do not begin with [ and {, the start of a JSON array or object. It is hard to think of a bulk export command or script that outputs primitive JSON types. To validate the remaining lines, we can filter through jq and output the type of the root object.

cat data.jsonlines | sed '/^[^[{]/d' > clean_data.jsonlines
cat clean_data.jsonlines | jq 'type' > /dev/null

This will generate output on STDERR with the line & column of any bad JSON.

Pretty printing JSON

Everyone has their favorite way to pretty print JSON. Mine uses the default jq output because it comes in color and because it makes it easy to drill down into the data structure. Let’s use the GitHub API as an example here.

# List of Swoop repos on GitHub
alias swoop_repos="curl $API"

# Pretty print the list of Swoop repos on GitHub in color
swoop_repos | jq '.'

JSON arrays to JSONlines

GitHub gives us an array of repo objects but let’s say we want JSONlines instead in order to prepare the API output for input into MongoDB via mongoimport. The –compact option of jq is perfect for JSONlines output.

# Swoop repos as JSONlines
swoop_repos | jq -c '.[]'

The .[] filter breaks up an array of inputs into individual inputs.

Filtering and selection

Say we want to pull out the full names of Swoop’s own repos as a JSON array. “Own” in this case means not forked.

swoop_repos | jq '[.[] | select(.fork == false) | .full_name]'

Let’s parse this one piece at a time:

  • The wrapping [...] merges any output into an array.
  • You’ve seen .[] already. It breaks up the single array input into many separate inputs, one per repo.
  • The select only outputs those repos that are not forked.
  • The .full_name filter plucks the value of that field from the repo data.

Here is the equivalent using underscore-cli and a json:select expression:

swoop_repos | underscore select \ 
    'object:has(.fork:expr(x=false)) > .full_name'

In both cases we are not saving that much code but not having to create files just keeps things simpler. For comparison, here is the code to output the names of Swoop’s own GitHub repos in Ruby.

require 'open-uri'
require 'json'
API = 'https://api.github.com/users/swoop-inc/repos'
open(API) do |io|
puts JSON.parse(io.read).
reject { |repo| repo['fork'] }.
map { |repo| repo['full_name'] }.

view raw


hosted with ❤ by GitHub

Posted in Code | Tagged , , , , , , , , | 5 Comments

My most favorite math proof ever

Math is beautiful and, sometimes, math becomes even more beautiful with the help of a bit of computer science. My favorite proof of all time combines the two in just such a way.

Goal: prove that the cardinality of the set of positive rational numbers is the same as that of the set of natural numbers.

This is an old problem dating back to Cantor with many proofs:

  • The traditional proof uses a diagonal argument: geometric insight that lays out the numerator and the denominator of a rational number along the x and y axes of a plane. The proof is intuitive but cumbersome to formalize.
  • There is a short but dense proof that uses a Cartesian product mapping and another theorem. Personally, I don’t find simplicity and beauty in referring to complex things.
  • There is a generative proof using a breadth-first traversal of a Calkin-Wilf tree (a.k.a, H tree because of its shape). Now we are getting some help from computer science but not in a way that aids simplicity.

We can do much better.


Given a rational number p/q, write it as the hexadecimal number pAq. QED


  • 0/1 → 0A1 (161 in decimal)
  • ¾ → 3A4 (932 in decimal)
  • 12/5 → 12A5 (4773 in decimal)

Code (because we can):

def to_natural(p, q)

It is trivial to extend the generation to all rationals, not just the positive ones, as long as we require p/q to be in canonical form:

def to_natural(p, q)
 "#{p < 0 ? 'A' : ''}#{p.abs}A#{q}"

To me, this CS-y proof feels much simpler and more accessible than any of the standard math-y proofs. It is generative, reducible to a line of code and does not require knowledge of any advanced concepts beyond number systems which are not base 10, a straight, intuitive extension of base 10 positional arithmetic.

Note: we don’t need to use hexadecimal. The first time I heard this proof it was done in base 11 but I feel that using an unusual base system does not make the proof better.

Posted in Uncategorized | 5 Comments

Monitoring Redis with MONITOR and WireShark

At Swoop we use Redis extensively for caching, message processing and analytics. The Redis documentation can be pithy at times and recently I found myself wanting to look in more depth at the Redis wire protocol. Getting everything set up the right way took some time and, hopefully, this blog post can save you that hassle.


The Redis logs do not include the commands that the database is executing but you can see them via the MONITOR command. As a habit, during development I run redis-cli MONITOR in a terminal window to see what’s going on.

Getting set up with WireShark

While normally we’d use a debugging proxy such as Charles to look at traffic in a Web application, here we need a real network protocol analyzer because Redis uses a TCP-based binary protocol. My go-to tool is WireShark because it is free, powerful and highly customizable (including Lua scriptable). The price for all this is dealing with an X11 interface from the last century and the expectation that you passed your Certified Network Engineer exams with flying colors.

To get going:

  1. WireShark needs X11. Since even Mac OS X stopped shipping X11 by default with Mountain Lion, you’ll most likely want to grab a copy, e.g., XQuartz for OS X or Xming for Windows.
  2. Download and install WireShark.
  3. Start WireShark. If you see nothing, it may be because the app shows as a window associated with the X11 server process. Look for that and you’ll find the main application window.

Redis protocol monitoring

WireShark’s plugin architecture allows it to understand dozens of network protocols. Luckily for us, jzwinck has written a Redis protocol plugin. It doesn’t come with WireShark by default so you’ll need to install it. Run the following:

mkdir ~/.wireshark/plugins && cd ~/.wireshark/plugins && curl -O https://raw.github.com/jzwinck/redis-wireshark/master/redis-wireshark.lua

view raw


hosted with ❤ by GitHub

If WireShark is running, restart it to pick up the Redis plugin.

Now let’s monitor the traffic to a default Redis installation (port 6379) on your machine. In WireShark, you’ll have to select the loopback interface.

wireshark-startTo reduce the noise, filter capture to TCP packets on port 6379. If you need more sophisticated filtering, consult the docs.


Once you start capture, it’s time to send some Redis commands. I’ll use the Ruby console for that.

1.9.3p392 :001 > r = Redis.new
=> #<Redis client v3.0.4 for redis://>
1.9.3p392 :002 > r.set("key:5", "\xad\xad")
=> "OK"
1.9.3p392 :003 > r.get("key:5")
=> "\xAD\xAD"

view raw


hosted with ❤ by GitHub

This will generate the following output from the MONITOR command:

1999[~]$ redis-cli MONITOR
1369526925.306016 [0] "set" "key:5" "\xad\xad"
1369526927.497785 [0] "get" "key:5"

In WireShark you’ll be able to see the binary data moving between the client and Redis with the benefit of the command and its parameters clearly visible.


Check out the time between request and response. Redis is fast!

Posted in Software Development | Tagged , , , , , | 1 Comment

Google and the ecosystem test

I am roaming the halls of Google I/O 2013 and wondering whether Google’s platform passes the ecosystem test.

… no platform has become hugely successful without a corresponding ecosystem of vendors building significant businesses on top of the platform. Typically, the combined revenues of the ecosystem are a multiple of the revenues of the platform.

So much activity but what’s the combined revenue of the businesses building on top of Android, Chrome & Apps?

Posted in Google | Tagged , , , | 3 Comments

Anatomy of an online ad

I’ve been asked to explain how online ads are delivered many times and every time I’m surprised by the complexity of covering even the most basic elements of how ads appear on Web pages. Since Wikipedia’s article on ad serving is not much help, I’ll try to explain one common way ads are delivered using a concrete example.

Side note: this is not how Swoop works. At Swoop we use a much simpler and more efficient model because we’ve built an end-to-end system. This eliminates the need for lots of different systems to touch (+ cookie + track) users. It also allows us to create deeper and more relevant matches by placing Swoop content not in arbitrary fixed slots but in dynamic slots right next to the part of a page it relates to. If you are looking for an analogy, think about Google Adwords on SERPs. It’s an end-to-end system where Google has complete control over ad placement and no ads are shown if there are no relevant ads to show.

Tools of the trade

If you want to know how adtech works, there is no better tool than Ghostery. Ghostery was created by my friend David Cancel and, later, when he was starting Performable (now part of Hubspot), my previous startup, Evidon, became the custodian of the Ghostery community. Ghostery will show you, page-by-page, all the different adtech players operating on a site. For example, on Boston.com’s sports page, there are 35 (!) separate adtech scripts running today.

ghostery-on-boston-sportsGhostery will show you what is happening but not how it happened. If you are technical and want to understand the details of how ad delivery works, there no better tool than a debugging proxy such as Charles or Fiddler. Just be prepared for the need to use code deobfuscators. If you don’t have time for wading through obfuscated code and you really want to know what’s going with your sites(s) or campaign(s), it is worth taking a look at Evidon Encompass. It’s an advanced analytics suite built on top of the Ghostery data.

The example

The example we’ll use is the arthritis page on Yahoo!’s health network. We will focus on the leaderboard ad at the top, which is a Celebrex (arthritis drug) ad from Pfizer.


What Yahoo! sent the browser

The initial response my browser got from Yahoo!’s server included the following chunk of HTML about the leaderboard ad unit, which I’ve formatted and added comments to. (Not sure what’s up with the empty lines that WP is adding to the bottom of the gists–they’re not on GitHub).

<!– Leaderboard slot on Yahoo!'s health network –>
<div id="yahoohealth-n-leaderboard-ad" style="border:0;margin-top:0;">
<div style="margin-bottom:9px;text-align:center">
<!– Ad unit delivery script if scripting is available –>
<script language="JavaScript" type="text/javascript" src="http://us.adserver.yahoo.com/a?f=96843138&amp;p=health&amp;l=N&amp;c=r&amp;rs=cnp:healthline&amp;at=hlids%3Dx%26hlk1%3D8234384%26hlk2%3D8317112%26hlkwa%3D2790905%20refurl%3D%22%22">
<!– Yahoo 1×1 tracking pixel delivery script –>
<script language="JavaScript" type="text/javascript" src="http://us.adserver.yahoo.com/a?f=96843138&amp;p=health&amp;l=FSRVY&amp;c=sr">
<!– Ad unit delivery as HTML if scripting is not available –>
<iframe frameborder="0" marginwidth="0" marginheight="0" scrolling="no" width="728" height="106" src="http://us.adserver.yahoo.com/a?f=96843138&amp;p=health&amp;l=N&amp;c=h&amp;rs=cnp:healthline&amp;at=hlids%3Dx%26hlk1%3D8234384%26hlk2%3D8317112%26hlkwa%3D2790905%20refurl%3D%22%22">

This content was mostly likely emitted by the Yahoo publishing system without direct coordination with the Yahoo ad server but instead using conventions about categories, page types, etc. and hence parameters like rs=cnp:healthline that you see on the URLs.

Display advertising units use standard IAB formats. In this case, we are dealing with a 728×90 leaderboard unit. The DIV with id yahoohealth-n-leaderboard-ad sets up the location where the ad unit will be displayed. The DIV under it serves the dubious function of controlling some styling related to the ad content.

Beyond this there are two things going on here. The first is the delivery of the ad script and the second is the delivery of a tracking pixel via a tracking pixel script.

Tracking pixels

Tracking pixels are 1×1 invisible images served from highly-parameterized URLs. They are not used for their content but for the request they generate to a server. The request parameters are used for record-keeping and the response could be used to cookie the user, though this did not happen in this case.

The tracking pixel is delivered via the script inside the <center> tag. It’s contents are shown below.

The script uses the JavaScript document.write function to write some HTML into the page. In this case the HTML is for an invisible image (display: none, height: 0, width: 0) whose URL is that of the tracking pixel, whose unencoded value is the long URL:


As you can see, lots of data getting sent, most likely to record the impression opportunity parameters.

Yahoo! ad delivery script

There are two ways to deliver an ad unit. The preferred way is via a script. If scripting is disabled in the browser, however, Yahoo doesn’t want to lose the ad impression opportunity and so there is the <noscript> option to show the ad in an iframe, probably as an image.

The code for the Yahoo! ad delivery script, which comes from the Yahoo! ad server, is shown below with reformatting and comments from me.

// This sets up AdChoice notice
document.write("<style type=\"text/css\">\n");
// AdChoice image: http://s.yimg.com/hl/a/optimized/adchoice_1.png
document.write(".can_ad_slug {font-family:arial;font-size:11px;color:#999;text-decoration:none;background-image:url(\'http://s.yimg.com/hl/a/optimized/adchoice_1.png\');background-repeat:no-repeat;background-position:right;padding:0px 14px 0px 1px !important;margin:1px 0px 1px 0;cursor:hand;height:12px;display:block;line-height:12px;}\n");
document.write(".ad_slug_table a {text-decoration:none;}\n");
document.write(".ad_slug_table div.CAN_marker { display:none }\n");
document.write("<div class=\"CAN_ad\">\n");
document.write("<table class=\"ad_slug_table\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\">\n");
// Link to click on to learn more
// It will record which ad unit the clicks is for and
// then redirect to http://info.yahoo.com/privacy/us/yahoo/relevantads.html
document.write("<tr><td align=\"right\"><a href=\"http://clicks.beap.bc.yahoo.com/yc/YnY9MS4wLjAmYnM9KDE1aGtrdjRldShnaWQkVHdWQTBqYzJMakZRR0o2VVRZdnRVd1VTTVRjekxsRnRfMGJfXzcwayxzdCQxMzY2MTYzMjcwNTgzNjQ0LHNpJDQ0NTMwNTEsc3AkOTY4NDMxMzgsY3IkMzM1MTkyOTA1MSx2JDIuMCxhaWQkMENPUU9rd05QZlEtLGN0JDI1LHlieCR2ZFJfX19Ed2xLd2liQ0k4SEdHUVVBLGJpJDE3Mzk2MTcwNTEsdyQwKSk/1/*http://info.yahoo.com/relevantads/\" class=\"CAN_link\" target=\"_blank\"><span class=\"can_ad_slug\">AdChoices</span></a></td></tr>\n");
document.write("<tr><td><!– APT Vendor: Doubleclick –>\n");
// Random number for cache busting
var ord = window.ord || Math.floor(Math.random() * 1e16);
// Google ad delivery script
document.write('<script type="text/javascript" src="http://ad.doubleclick.net/N4788/adj/hn.us.hmnyh.dir.x.x.x/Celebrex_Arthritis;sz=728×90;ord=&#39; + ord + '?"><\/script>');
// If no scripting is available
// Yahoo click tracking (will redirect to Google)
document.write("<!– %SCBT% –><a HREF=\"http://clicks.beap.bc.yahoo.com/yc/YnY9MS4wLjAmYnM9KDE1dTdhbzlmcShnaWQkVHdWQTBqYzJMakZRR0o2VVRZdnRVd1VTTVRjekxsRnRfMGJfXzcwayxzdCQxMzY2MTYzMjcwNTgzNjQ0LHNpJDQ0NTMwNTEsc3AkOTY4NDMxMzgsY3IkMzM1MTkyOTA1MSx2JDIuMCxhaWQkMENPUU9rd05QZlEtLGN0JDI1LHlieCR2ZFJfX19Ed2xLd2liQ0k4SEdHUVVBLGJpJDE3Mzk2MTcwNTEsciQwLHJkJDFhZTdwMjFpaykp/0/*http://global.ard.yahoo.com/SIG=15g7h30s5/M=999999.999999.999999.999999/D=yahoo/S=96843138:N/Y=YAHOO/EXP=1366170470/L=TwVA0jc2LjFQGJ6UTYvtUwUSMTczLlFt_0b__70k/B=0COQOkwNPfQ-/J=1366163270625997/K=VND6yHbc_zFI4AJoKlGCbw/A=7308269325681159395/R=0/X=6/SIG=13gee38lu/*http://ad.doubleclick.net/N4788/jump/hn.us.hmnyh.dir.x.x.x/Celebrex_Arthritis;sz=728×90;ord=1366163270.625997?\"><!– %ECBT% –>\n");
// Google ad as a simple image
document.write("<img src=\"http://ad.doubleclick.net/N4788/ad/hn.us.hmnyh.dir.x.x.x/Celebrex_Arthritis;sz=728×90;ord=1366163270.625997?\" width=\"728\" height=\"90\" />\n");
document.write("</noscript><!–QYZ 1739617051,3351929051,;;N;96843138;1;–></td></tr>\n");
// Yahoo impression tracking pixel
document.write("</div><img style=\"display:none\" width=0 height=0 alt=\"\" src=\"http://csc.beap.bc.yahoo.com/yi?bv=1.0.0&bs=(134i64f2m(gid$TwVA0jc2LjFQGJ6UTYvtUwUSMTczLlFt_0b__70k,st$1366163270583644,si$4453051,sp$96843138,pv$0,v$2.0))&t=D_3&al=(as$12rveflk1,aid$0COQOkwNPfQ-,bi$1739617051,cr$3351929051,ct$25,at$0,eob$-1)\">");

There are several things going on here:

  • AdChoice notice
  • Cache busting
  • Google ad delivery script activation
  • Yahoo impression tracking
  • No script handling

Let’s consider them one at a time.

AdChoice notice

AdChoice came about in 2010 as the online advertising industry’s response to FTC pressure to reign in some poor privacy practices and provide consumers with more transparency and choice when it comes to interest-based advertising, a.k.a., behavioral targeting (BT in adtech parlance).

The AdChoice icon is a triangle with an i in it. Its color can vary. Yahoo!’s is gray (). Next time you see an ad with it, click on the AdChoice notice. You should see information about who targeted the ad at you and get some options to opt-out of interest-based advertising. We started Evidon back in 2009 to bring more transparently to adtech and we helped create AdChoice. Evidon is now the leading independent player in this space.

In the case of the Celebrex ad from our example, the AdChoice icon is tied to a very long URL:


If you click on the AdChoice icon, Yahoo! will record information about which ad you are selecting to learn more about and then redirect you to the page at the end of the URL, which is the Yahoo learn more about this ad page. The long URL is just for bookkeeping.

BTW, the reason why you don’t see AdChoice notice with Swoop is because Swoop does not do any behavioral targeting at this time. Still, because we want to make it clear that Swoop is serving content, you’ll see our logo on our units.

Cache busting

After the AdChoice notice setup comes a line of script that creates a random number. This is used for cache busting.

A cache-buster is a unique piece of code that prevents a browser from reusing an ad it has already seen and cached, or saved, to a temporary memory file.

Adding a random number to a URL does that nicely.

Google ad delivery script activation

The following script tag loads Google’s ad delivery script from ad.doubleclick.net. We will look at this later on.

Yahoo impression tracking

Remember how Yahoo already fired one tracking pixel to record the impression opportunity. Well, here, at the end of the script they are going to fire another tracking pixel but this time the purpose will be to record the impression of the Google ad. As before, you can see lots of data being passed.


Noscript processing & click tracking

As before, in the case that the browser does not have JavaScript enabled, Yahoo doesn’t want to miss the opportunity to deliver an ad, which is why they have the option to display the Google ad as an image.

In that case, Yahoo is also positioned to capture the click and then redirect to Google. This is achieved by wrapping the image (<img>) in a link (<a>). Getting click feedback data would be valuable for Yahoo as it allow is to optimize better. If the unit is sold on a cost-per-click (CPC) basis, then getting click data is a requirement for good record-keeping.

Google/DoubleClick ad delivery script

It’s time for us to take a look at what Google’s ad delivery script does. Alas, the guys at Google don’t want to waste bandwidth so they’ve packed everything into a single unreadable document.write call. You can scroll to the right for a very long time…

document.write('\x3c!– Template Id \x3d 13,901 Template Name \x3d Banner Creative (Flash) – In Page Multiples – [DFA] –\x3e\n\x3c!– Copyright 2006 DoubleClick Inc., All rights reserved. –\x3e\x3cscript src\x3d\x22http://s0.2mdn.net/879366/flashwrite_1_2.js\x22\x3e\x3c/script\x3e\n\x3cSCRIPT LANGUAGE\x3d\x22JavaScript\x22\x3e\n\x3c!–\nfunction DCFlash(id,pVM){\nvar swf \x3d \x22http://s0.2mdn.net/877848/CLB012__Hiking_728x90.swf\x22;\nvar gif \x3d \x22http://s0.2mdn.net/877848/Celebrex_728x90_Unbranded_Backup.3.11.13_1.gif\x22;\nvar minV \x3d 8;\nvar FWH \x3d \x27 width\x3d\x22728\x22 height\x3d\x2290\x22 \x27;\nvar url \x3d escape(\x22http://adclick.g.doubleclick.net/aclk?sa\x3dL\x26ai\x3dBkkTr1ABuUYnfKOz56AHh_4HQCbOj0YsDAAAAEAEgADgAUIC6g9b______wFYs4bGy0xgyYb2iISk7A-CARdjYS1wdWItNjc2MDQyMjg4NTEzMDEyMrIBGHd3dy5kY2xrLWRlZmF1bHQtcmVmLmNvbboBCWdmcF9pbWFnZcgBCdoBIGh0dHA6Ly93d3cuZGNsay1kZWZhdWx0LXJlZi5jb20vmAKIpAHAAgLgAgDqAi00Nzg4L2huLnVzLmhtbnloLmRpci54LngueC9DZWxlYnJleF9BcnRocml0aXP4AoHSHpAD4AOYA-ADqAMB4AQBoAYe\x26num\x3d0\x26sig\x3dAOD64_2y2kW7wNSq8iMKhxr0rN1gC3lDFA\x26client\x3dca-pub-6760422885130122\x26adurl\x3dhttp%3A%2F%2Fad.doubleclick.net/click%3Bh%3Dv8/3dc7/3/0/%2a/v%3B266113741%3B3-0%3B0%3B92295057%3B3454-728/90%3B53480227/53398896/1%3B%3B%7Esscs%3D%3fhttp://www.celebrex.com/default.aspx?o\x3d92295057|266113741|53480227\x22);\nvar wmode \x3d \x22opaque\x22;\nvar bg \x3d \x22same as SWF\x22;\nvar dcallowscriptaccess \x3d \x22never\x22;\n\nvar openWindow \x3d \x22false\x22;\nvar winW \x3d 600;\nvar winH \x3d 400;\nvar winL \x3d 0;\nvar winT \x3d 0;\n\nvar moviePath\x3dswf.substring(0,swf.lastIndexOf(\x22/\x22));\nvar sm\x3dnew Array();\nsm[1] \x3d \x22\x22;\nsm[2] \x3d \x22\x22;\nsm[3] \x3d \x22\x22;\nsm[4] \x3d \x22\x22;\nsm[5] \x3d \x22\x22;\n\nvar ct\x3dnew Array();\nct[0]\x3d\x22\x22;if(ct[0].substr(0,4)!\x3d\x22http\x22){ct[0]\x3d\x22\x22;} \nct[1] \x3d \x22http://www.celebrex.com/default.aspx?cmp\x3dCLXHAFL\x26o\x3d92295057|266113741|53480227\x22;\nct[2] \x3d \x22http://pfizer.com/files/products/uspi_celebrex.pdf\x22;\nct[3] \x3d \x22http://media.pfizer.com/files/products/mg_celebrex.pdf\x22;\nct[4] \x3d \x22http://www.celebrex.com/isi.aspx?cmp\x3dCLXHAFL\x26o\x3d92295057|266113741|53480227\x22;\nct[5] \x3d \x22http://www.celebrex.com/default.aspx?cmp\x3dCLXHAFL\x26o\x3d92295057|266113741|53480227\x22;\nct[6] \x3d \x22\x22;\nct[7] \x3d \x22\x22;\nct[8] \x3d \x22\x22;\nct[9] \x3d \x22\x22;\nct[10] \x3d \x22\x22;\n\nvar fv\x3d\x27\x22clickTag\x3d\x27+url+\x27\x26clickTAG\x3d\x27+url+\x27\x26clicktag\x3d\x27+url+\x27\x26moviePath\x3d\x27+moviePath+\x27/\x27+\x27\x26moviepath\x3d\x27+moviePath+\x27/\x27;\nfor(i\x3d1;i\x3csm.length;i++){if(sm[i]!\x3d\x22\x22){fv+\x3d\x22\x26submovie\x22+i+\x22\x3d\x22+escape(sm[i]);}}\nfor(i\x3d1;i\x3cct.length;i++){if(ct[i]!\x3d\x22\x22){if(ct[i].indexOf(\x22http\x22)\x3d\x3d0){x\x3descape(\x22http://adclick.g.doubleclick.net/aclk?sa\x3dL\x26ai\x3dBkkTr1ABuUYnfKOz56AHh_4HQCbOj0YsDAAAAEAEgADgAUIC6g9b______wFYs4bGy0xgyYb2iISk7A-CARdjYS1wdWItNjc2MDQyMjg4NTEzMDEyMrIBGHd3dy5kY2xrLWRlZmF1bHQtcmVmLmNvbboBCWdmcF9pbWFnZcgBCdoBIGh0dHA6Ly93d3cuZGNsay1kZWZhdWx0LXJlZi5jb20vmAKIpAHAAgLgAgDqAi00Nzg4L2huLnVzLmhtbnloLmRpci54LngueC9DZWxlYnJleF9BcnRocml0aXP4AoHSHpAD4AOYA-ADqAMB4AQBoAYe\x26num\x3d0\x26sig\x3dAOD64_2y2kW7wNSq8iMKhxr0rN1gC3lDFA\x26client\x3dca-pub-6760422885130122\x26adurl\x3dhttp%3A%2F%2Fad.doubleclick.net/click%3Bh%3Dv8/3dc7/3/0/%2a/v%3B266113741%3B3-0%3B0%3B92295057%3B3454-728/90%3B53480227/53398896/1%3B%3B%7Esscs%3D%3f\x22+ct[i]);}else{x\x3descape(ct[i]);}fv+\x3d\x22\x26clickTag\x22+i+\x22\x3d\x22+x+\x22\x26clickTAG\x22+i+\x22\x3d\x22+x+\x22\x26clicktag\x22+i+\x22\x3d\x22+x;}}\nfv+\x3d\x27\x22\x27;\nvar bgo\x3d(bg\x3d\x3d\x22same as SWF\x22)?\x22\x22:\x27\x3cparam name\x3d\x22bgcolor\x22 value\x3d\x22#\x27+bg+\x27\x22\x3e\x27;\nvar bge\x3d(bg\x3d\x3d\x22same as SWF\x22)?\x22\x22:\x27 bgcolor\x3d\x22#\x27+bg+\x27\x22\x27;\nfunction FSWin(){if((openWindow\x3d\x3d\x22false\x22)\x26\x26(id\x3d\x3d\x22DCF0\x22))alert(\x27openWindow is wrong.\x27);if((openWindow\x3d\x3d\x22center\x22)\x26\x26window.screen){winL\x3dMath.floor((screen.availWidth-winW)/2);winT\x3dMath.floor((screen.availHeight-winH)/2);}window.open(unescape(url),id,\x22width\x3d\x22+winW+\x22,height\x3d\x22+winH+\x22,top\x3d\x22+winT+\x22,left\x3d\x22+winL+\x22,status\x3dno,toolbar\x3dno,menubar\x3dno,location\x3dno\x22);}this.FSWin \x3d FSWin;\nua\x3dnavigator.userAgent;\nif(minV\x3c\x3dpVM\x26\x26(openWindow\x3d\x3d\x22false\x22||(ua.indexOf(\x22Mac\x22)\x3c0\x26\x26ua.indexOf(\x22Opera\x22)\x3c0))){\n\tvar adcode\x3d\x27\x3cobject classid\x3d\x22clsid:d27cdb6e-ae6d-11cf-96b8-444553540000\x22 id\x3d\x22\x27+id+\x27\x22\x27+FWH+\x27\x3e\x27+\n\t\t\x27\x3cparam name\x3d\x22movie\x22 value\x3d\x22\x27+swf+\x27\x22\x3e\x3cparam name\x3d\x22flashvars\x22 value\x3d\x27+fv+\x27\x3e\x3cparam name\x3d\x22quality\x22 value\x3d\x22high\x22\x3e\x3cparam name\x3d\x22wmode\x22 value\x3d\x22\x27+wmode+\x27\x22\x3e\x3cparam name\x3d\x22base\x22 value\x3d\x22\x27+swf.substring(0,swf.lastIndexOf(\x22/\x22))+\x27\x22\x3e\x3cPARAM NAME\x3d\x22AllowScriptAccess\x22 VALUE\x3d\x22\x27+dcallowscriptaccess+\x27\x22\x3e\x27+bgo+\n\t\t\x27\x3cembed src\x3d\x22\x27+swf+\x27\x22 flashvars\x3d\x27+fv+bge+FWH+\x27 type\x3d\x22application/x-shockwave-flash\x22 quality\x3d\x22high\x22 swliveconnect\x3d\x22true\x22 wmode\x3d\x22\x27+wmode+\x27\x22 name\x3d\x22\x27+id+\x27\x22 base\x3d\x22\x27+swf.substring(0,swf.lastIndexOf(\x22/\x22))+\x27\x22 AllowScriptAccess\x3d\x22\x27+dcallowscriptaccess+\x27\x22\x3e\x3c/embed\x3e\x3c/object\x3e\x27;\n if((\x27x\x27!\x3d\x22j\x22)\x26\x26(typeof dclkFlashWrite!\x3d\x22undefined\x22)){dclkFlashWrite(adcode);}else{document.write(adcode);}\n}else{\n\tdocument.write(\x27\x3ca target\x3d\x22_blank\x22 href\x3d\x22\x27+unescape(url)+\x27\x22\x3e\x3cimg src\x3d\x22\x27+gif+\x27\x22\x27+FWH+\x27border\x3d\x220\x22 alt\x3d\x22\x22 galleryimg\x3d\x22no\x22\x3e\x3c/a\x3e\x27);\n}}\nvar pVM\x3d0;var DCid\x3d(isNaN(\x22266113741\x22))?\x22DCF2\x22:\x22DCF266113741\x22;\nif(navigator.plugins \x26\x26 navigator.mimeTypes.length){\n var x\x3dnavigator.plugins[\x22Shockwave Flash\x22];if(x \x26\x26 x.description){var pVF\x3dx.description;var y\x3dpVF.indexOf(\x22Flash \x22)+6;pVM\x3dpVF.substring(y,pVF.indexOf(\x22.\x22,y));}}\nelse if (window.ActiveXObject \x26\x26 window.execScript){\n window.execScript(\x27on error resume next\\npVM\x3d2\\ndo\\npVM\x3dpVM+1\\nset swControl \x3d CreateObject(\x22ShockwaveFlash.ShockwaveFlash.\x22\x26pVM)\\nloop while Err \x3d 0\\nOn Error Resume Next\\npVM\x3dpVM-1\\nSub \x27+DCid+\x27_FSCommand(ByVal command, ByVal args)\\nCall \x27+DCid+\x27_DoFSCommand(command, args)\\nEnd Sub\\n\x27,\x22VBScript\x22);}\neval(\x22function \x22+DCid+\x22_DoFSCommand(c,a){if(c\x3d\x3d\x27openWindow\x27)o\x22+DCid+\x22.FSWin();}o\x22+DCid+\x22\x3dnew DCFlash(\x27\x22+DCid+\x22\x27,pVM);\x22);\n//–\x3e\n\x3c/SCRIPT\x3e\n\x3cnoscript\x3e\x3ca target\x3d\x22_blank\x22 href\x3d\x22http://adclick.g.doubleclick.net/aclk?sa\x3dL\x26ai\x3dBkkTr1ABuUYnfKOz56AHh_4HQCbOj0YsDAAAAEAEgADgAUIC6g9b______wFYs4bGy0xgyYb2iISk7A-CARdjYS1wdWItNjc2MDQyMjg4NTEzMDEyMrIBGHd3dy5kY2xrLWRlZmF1bHQtcmVmLmNvbboBCWdmcF9pbWFnZcgBCdoBIGh0dHA6Ly93d3cuZGNsay1kZWZhdWx0LXJlZi5jb20vmAKIpAHAAgLgAgDqAi00Nzg4L2huLnVzLmhtbnloLmRpci54LngueC9DZWxlYnJleF9BcnRocml0aXP4AoHSHpAD4AOYA-ADqAMB4AQBoAYe\x26num\x3d0\x26sig\x3dAOD64_2y2kW7wNSq8iMKhxr0rN1gC3lDFA\x26client\x3dca-pub-6760422885130122\x26adurl\x3dhttp%3A%2F%2Fad.doubleclick.net/click%3Bh%3Dv8/3dc7/3/0/%2a/v%3B266113741%3B3-0%3B0%3B92295057%3B3454-728/90%3B53480227/53398896/1%3B%3B%7Esscs%3D%3fhttp://www.celebrex.com/default.aspx?o\x3d92295057|266113741|53480227\x22\x3e\x3cimg src\x3d\x22http://s0.2mdn.net/877848/3-CBX012_GIF1_728x90_L3S.gif\x22 width\x3d\x22728\x22 height\x3d\x2290\x22 border\x3d\x220\x22 alt\x3d\x22\x22 galleryimg\x3d\x22no\x22\x3e\x3c/a\x3e\x3c/noscript\x3e\n\x3cscript type\x3d\x22text/javascript\x22 src\x3d\x22http://secure-us.imrworldwide.com/cgi-bin/m?ci\x3dENT21188\x26am\x3d1\x26mr\x3d1\x26ty\x3djs\x26ep\x3d1\x26at\x3dview\x26rt\x3dbanner\x26st\x3dimage\x26ca\x3dcmp12600\x26cr\x3d53480227\x26pc\x3d92295057\x26r\x3d1028618\x22\x3e\x3c/script\x3e\n\x3cnoscript\x3e\x3cimg src\x3d\x22http://secure-us.imrworldwide.com/cgi-bin/m?ci\x3dENT21188\x26am\x3d1\x26ep\x3d1\x26at\x3dview\x26rt\x3dbanner\x26st\x3dimage\x26ca\x3dcmp12600\x26cr\x3d53480227\x26pc\x3d92295057\x26r\x3d1028618\x22/\x3e\n\x3cimg src\x3d\x22http://pixel.adsafeprotected.com/?anid\x3d5092\x26campid\x3dcmp12600\x26placementid\x3d1_92295057\x26creativeid\x3d53480227\x22/\x3e\x3c/noscript\x3e\n\x3cscript Src\x3d\x22http://cdn.doubleverify.com/dvtp_src.js?ctx\x3d525748\x26cmp\x3d7160975\x26sid\x3d477325\x26plc\x3d92295057\x26num\x3d\x26adid\x3d\x26advid\x3d877848\x26adsrv\x3d1\x26region\x3d30\x26btreg\x3d266113741\x26btadsrv\x3ddoubleclick\x26crt\x3d\x26crtname\x3d\x26chnl\x3d\x26unit\x3d\x26pid\x3d\x26uid\x3d\x26dvtagver\x3d6.1.src\x22 type\x3d\x22text/javascript\x22\x3e\x3c/script\x3e');

Here is what Google is actually trying to write into the HTML page (with my comments added):

<!– Template Id = 13,901 Template Name = Banner Creative (Flash) – In Page Multiples – [DFA] –>
<!– Copyright 2006 DoubleClick Inc., All rights reserved. –>
<!– Utilities for using Flash –>
<script src="http://s0.2mdn.net/879366/flashwrite_1_2.js">
<!– Script to active Flash ad unit –>
// DoubleClick Flash ad display utility
function DCFlash(id, pVM) {
// Note: 2mdn.net is a DoubleClick domain
// Flash creative for ad
var swf = "http://s0.2mdn.net/877848/CLB012__Hiking_728x90.swf&quot;;
// Unbranded (no Celebrex) Pfizer image
var gif = "http://s0.2mdn.net/877848/Celebrex_728x90_Unbranded_Backup.3.11.13_1.gif&quot;;
var minV = 8;
var FWH = ' width="728" height="90" ';
// Target URL for non-Flash mode (with Google click tracking redirection)
// Actual target URL is http://www.celebrex.com/default.aspx?o=92295057|266113741|53480227
var url = escape("http://adclick.g.doubleclick.net/aclk?sa=L&ai=BkkTr1ABuUYnfKOz56AHh_4HQCbOj…cs%3D%3fhttp://www.celebrex.com/default.aspx?o=92295057|266113741|53480227");
var wmode = "opaque";
var bg = "same as SWF";
var dcallowscriptaccess = "never";
var openWindow = "false";
var winW = 600;
var winH = 400;
var winL = 0;
var winT = 0;
var moviePath = swf.substring(0, swf.lastIndexOf("/"));
var sm = new Array();
sm[1] = "";
sm[2] = "";
sm[3] = "";
sm[4] = "";
sm[5] = "";
var ct = new Array();
ct[0] = "";
if (ct[0].substr(0, 4) != "http") {
ct[0] = "";
// Different content targets for users to take action against
ct[1] = "http://www.celebrex.com/default.aspx?cmp=CLXHAFL&o=92295057|266113741|53480227";
ct[2] = "http://pfizer.com/files/products/uspi_celebrex.pdf&quot;;
ct[3] = "http://media.pfizer.com/files/products/mg_celebrex.pdf&quot;;
ct[4] = "http://www.celebrex.com/isi.aspx?cmp=CLXHAFL&o=92295057|266113741|53480227";
ct[5] = "http://www.celebrex.com/default.aspx?cmp=CLXHAFL&o=92295057|266113741|53480227";
ct[6] = "";
ct[7] = "";
ct[8] = "";
ct[9] = "";
ct[10] = "";
var fv = '"clickTag=' + url + '&clickTAG=' + url + '&clicktag=' + url + '&moviePath=' + moviePath + '/' + '&moviepath=' + moviePath + '/';
for (i = 1; i < sm.length; i++) {
if (sm[i] != "") {
fv += "&submovie" + i + "=" + escape(sm[i]);
for (i = 1; i < ct.length; i++) {
if (ct[i] != "") {
if (ct[i].indexOf("http") == 0) {
// Click tracking redirect
x = escape("http://adclick.g.doubleclick.net/aclk?sa=L&ai=BkkTr1ABuUYnfKOz56AHh_4HQCbOj…B3-0%3B0%3B92295057%3B3454-728/90%3B53480227/53398896/1%3B%3B%7Esscs%3D%3f&quot; + ct[i]);
} else {
x = escape(ct[i]);
fv += "&clickTag" + i + "=" + x + "&clickTAG" + i + "=" + x + "&clicktag" + i + "=" + x;
fv += '"';
var bgo = (bg == "same as SWF") ? "" : '<param name="bgcolor" value="#' + bg + '">';
var bge = (bg == "same as SWF") ? "" : ' bgcolor="#' + bg + '"';
function FSWin() {
if ((openWindow == "false") && (id == "DCF0")) alert('openWindow is wrong.');
if ((openWindow == "center") && window.screen) {
winL = Math.floor((screen.availWidth winW) / 2);
winT = Math.floor((screen.availHeight winH) / 2);
window.open(unescape(url), id, "width=" + winW + ",height=" + winH + ",top=" + winT + ",left=" + winL + ",status=no,toolbar=no,menubar=no,location=no");
this.FSWin = FSWin;
ua = navigator.userAgent;
if (minV <= pVM && (openWindow == "false" || (ua.indexOf("Mac") < 0 && ua.indexOf("Opera") < 0))) {
// Preparing to instantiate Flash player
var adcode = '<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" id="' + id + '"' + FWH + '>' +
'<param name="movie" value="' + swf + '"><param name="flashvars" value=' + fv + '><param name="quality" value="high"><param name="wmode" value="' + wmode + '"><param name="base" value="' + swf.substring(0, swf.lastIndexOf("/")) + '"><PARAM NAME="AllowScriptAccess" VALUE="' + dcallowscriptaccess + '">' + bgo +
'<embed src="' + swf + '" flashvars=' + fv + bge + FWH + ' type="application/x-shockwave-flash" quality="high" swliveconnect="true" wmode="' + wmode + '" name="' + id + '" base="' + swf.substring(0, swf.lastIndexOf("/")) + '" AllowScriptAccess="' + dcallowscriptaccess + '"><\/embed><\/object>';
if (('x' != "j") && (typeof dclkFlashWrite != "undefined")) {
// If DoubleClick code is available, activate Flash through it
} else {
// Otherwise, just write out the Flash object
} else {
// Can't use Flash, use an image instead
document.write('<a target="_blank" href="' + unescape(url) + '"><img src="' + gif + '"' + FWH + 'border="0" alt="" galleryimg="no"><\/a>');
var pVM = 0;
var DCid = (isNaN("266113741")) ? "DCF2" : "DCF266113741";
// Kick it all off using a bunch of browser-specific processing
if (navigator.plugins && navigator.mimeTypes.length) {
var x = navigator.plugins["Shockwave Flash"];
if (x && x.description) {
var pVF = x.description;
var y = pVF.indexOf("Flash ") + 6;
pVM = pVF.substring(y, pVF.indexOf(".", y));
} else if (window.ActiveXObject && window.execScript) {
window.execScript('on error resume next\npVM=2\ndo\npVM=pVM+1\nset swControl = CreateObject("ShockwaveFlash.ShockwaveFlash."&pVM)\nloop while Err = 0\nOn Error Resume Next\npVM=pVM-1\nSub ' + DCid + '_FSCommand(ByVal command, ByVal args)\nCall ' + DCid + '_DoFSCommand(command, args)\nEnd Sub\n', "VBScript");
eval("function " + DCid + "_DoFSCommand(c,a){if(c=='openWindow')o" + DCid + ".FSWin();}o" + DCid + "=new DCFlash('" + DCid + "',pVM);");
<!– Can't use Flash ad; use an image instead –>
<a target="_blank" href="http://adclick.g.doubleclick.net/aclk?sa=L&ai=BkkTr1ABuUYnfKOz56AHh_4HQCbOj…cs%3D%3fhttp://www.celebrex.com/default.aspx?o=92295057|266113741|53480227">
<img src="http://s0.2mdn.net/877848/3-CBX012_GIF1_728x90_L3S.gif" width="728" height="90" border="0" alt="" galleryimg="no">
<!– Ad delivery verification & analytics –>
<!– This is Nielsen't NetRatings product –>
<!– The script will load Facebook brand life tracking behind the scenes –>
<!– It will also load AdSafe –>
<script type="text/javascript" src="http://secure-us.imrworldwide.com/cgi-bin/m?ci=ENT21188&am=1&mr=1&ty=js&ep=1&at=view&rt=banner&st=image&ca=cmp12600&cr=53480227&pc=92295057&r=1028618">
<!– In the noscript case, run NetRatings & AdSafe but no Facebook –>
<img src="http://secure-us.imrworldwide.com/cgi-bin/m?ci=ENT21188&am=1&ep=1&at=view&rt=banner&st=image&ca=cmp12600&cr=53480227&pc=92295057&r=1028618"/>
<img src="http://pixel.adsafeprotected.com/?anid=5092&campid=cmp12600&placementid=1_92295057&creativeid=53480227"/>
<!– DoubleVerify viewability verification–>
<script Src="http://cdn.doubleverify.com/dvtp_src.js?ctx=525748&cmp=7160975&sid=477325&p…1&btadsrv=doubleclick&crt=&crtname=&chnl=&unit=&pid=&uid=&dvtagver=6.1.src" type="text/javascript">

Don’t worry about the volume of code. There are basically two things going on here: delivering a Flash ad and lots of third party ad verification.

Delivering a Flash ad

Flash ads are richer and more interactive but there are browsers where Flash ads don’t do so well. The first part of the Google/DoubleClick ad delivery script is about carefully determining whether a Flash ad unit can be used and falling back to images otherwise. As before, all clicks are tracked via redirects.

Third party  verification

We saw Yahoo! attempting to fire three types of tracking pixels: (a) for impression opportunities, (b) for actual impressions and, in the case of no scripting, (c) for clicks. This is to help optimize the performance of Yahoo!’s ad network. This is first party verification. Google/DoubleClick does the same with its own systems.

Third party verification happens when the advertiser asks the delivery network, in this case Google/DoubleClick, to include additional verification tags (scripts) to prove that the campaign is delivered based on its configured parameters.

In the case of this Celebrex campaign, Pfizer is using four separate verification vendors. At the top level we have only Nielsen NetRatings and DoubleVerify, however NetRatings’s script loads AdSafe as well as Facebook in the pattern we are familiar with: a script that writes out <script> tags to load more scripts.

Putting it all together

Let’s try to piece together the requests that allow this one single ad unit for Celebrex to appear:

  • Yahoo ad unit delivery script
    • Google ad delivery script
      • Flash movie
        • ??? (not easy to track Flash traffic)
      • Nielsen NetRatings tracking script
        • AdSafe pixel
        • Facebook iframe
          • Facebook tracking
            • ??? (did not analyze)
      • DoubleVerify script
        • Tracking script (like a pixel)
    • Yahoo impression tracker
      • Tracking pixel
  • Yahoo impression opportunity tracker
    • Tracking pixel

All in all, 13 separate HTTP requests to 6 separate companies, not counting redirects and cacheable scripts. With this much complexity and independent parties doing their own accounting, it’s no surprise the display advertising value chain is in a bit of a mess right now.

Posted in Advertising, Swoop | Tagged , , , , , , , , | 1 Comment

Startup anti-pattern: platform risk

One of the fastest ways for a startup to grow has always been to ride on the shoulders of a successful platform: from Microsoft/OSS in software to AWS in cloud computing to iOS/Android in mobile to Facebook/Twitter/Pinterest in social to IAB/Google in advertising and the many SaaS players. Betting on a platform focuses product development both because of technology/API choices and because of the automatic reduction in the customer/user pool. Also, platforms that satisfy the ecosystem test help the startups that bet on them make money. That is, until they don’t.

I’ve been involved with three startups that have been significantly helped by platforms initially and then hurt by them. Two cases involved Microsoft. One case involved Twitter. The first time it happened, our eyes were closed and it hurt. It prompted me to learn more about how platform companies operate and how they use and abuse partners—companies small and large—to help them compete with other platforms. The basic reality is that platform companies will do whatever it takes to win and they typically don’t care much about the collateral damage they cause.

Just like hacking fast & loose, which accumulates technical debt, accelerating the growth of a startup by leveraging a platform may come with substantial platform risk.

Note: links to undocumented anti-patterns will take you to the main list.

Startup Anti-Pattern: Platform Risk

What it is

Platform risk is the debt associated with adopting a platform. Platform risk becomes an anti-pattern when three conditions are met:

  1. The platform dependency becomes critical to company operations.
  2. The company is unaware of the extent of the risk it has assumed.
  3. There is increased likelihood of adverse platform change.

Platform risk tends to appear with other situational awareness anti-patterns such as ignorance and unrealistic expectations.

Why it matters

Here are the top 10 sub-patterns of platform risk hurting startups that I’ve seen:

  • Lock-in. Startups that adopt a closed platform can be locked into their choice typically for the duration of the company’s life. This is not a problem until the need arises to support another platform. At that point the time & cost associated with the work could be substantial, especially if the core architecture was not designed with this in mind. In many cases, it is cheaper to start from scratch.
  • Forced upgrades. When software came in boxes, if you didn’t like the new version or if it was incompatible with your own software, you and your customers did not need to upgrade. You could take the time to make things work and upgrade on your own schedule. In the platform-as-a-service world, you do not have this option. Instead, forced upgrades are the norm. You have to deal with them on the platform vendor’s schedule, which may be quite inconvenient and costly. You do not have the option to ignore the update. Vendors vary widely in how they manage their partner ecosystems with respect to forced upgrades. Google has been pretty good when it comes to its APIs and has acted like a not-so-benevolent dictator when it comes to non-API-related behaviors of services such as search and advertising. Facebook and Google have both been accused of manipulating the behavior of their systems to force businesses to spend more money in their advertising platforms. In the case of Facebook, the issue has been pay-to-play for likes. Google has come repeatedly under fire for manipulating the search user experience to (a) shape traffic away from large publishers it competes with and (b) reduce advertiser choices and drive more ad dollars to AdWords. If your business depends on SEO or SEM, these changes can be very significant. The former CEO of a large advertising agency once summarized this as “Google giveth and Google taketh away.”
  • Forced platform switch. A forced platform switch usually comes as a side effect of platform vendors playing turf wars. For example, Apple severely hurt Adobe’s Flash platform as a way to limit write once, run anywhere options in mobile, thus also slowing Android’s adoption a bit. Thousands of small game & other types of content developers in the Web & Flash ecosystem were affected and had to either abandon iOS development or find new costly talent.
  • The partner dance. The partner dance is most commonly seen in enterprise software. It was popularized by Microsoft. As one former MS exec described it to me: “first you design your partners in and then you design you partners out.” During the design-in phase, a platform vendor partners with and, in some cases, spends meaningful resources helping an innovative startup company with solutions that compete with the solutions of another platform vendor. As the platform company’s own product roadmap matures, it designs its partners out starts directly competing with them.
  • Swinging. Swinging is a variation of the partner dance where rather than competing directly with a startup, the platform vendor partners with one of the startup’s competitors. Some years ago I was on the board of a European company that was Microsoft’s preferred partner in a fast-growing market. After winning against much bigger players such as EMC and IBM, the startup convinced MS that there was a big business to be built in this market. At that point MS promptly terminated the startup’s preferred status and partnered with a much bigger competitor. We were expecting the move: Microsoft now wanted to move hundreds of millions of dollars of its platform products in this space and the startup, despite closing significant business, could not operate at this scale. The Facebook/Zynga saga is an example from the online world.
  • Hundred flowers. The name of this sub-pattern comes from the famous Chairman Mao quote “Let a hundred flowers blossom.” Mao fostered “innovation” in Chinese socialist culture—open dissent—and then promptly executed many of the innovators. It seems that Twitter, Facebook and other social platforms have studied the Chairman quite well, judging by how efficiently they have moved from relying on the adopters of their APIs for growth and traffic to restricting their access and hurting their businesses. The prototypical example is Twitter driving much of its traffic from third party clients and then moving against them.
  • Failure to deliver. Startups pick platforms not just because of their current capabilities and distribution but also because of their expected future capabilities and distribution power. If the platform does not deliver, the startup’s ability to execute can be significantly hampered. One of the most common use cases of this sub-pattern relates to open-source platforms where the frequent lack of a single driving force behind a product or service could lead to substantial delays. At various points, teams I’ve been involved with have had to dedicate significant resources to accelerate development of OSS, e.g., Apache Axis, which turned out to be the most popular Web services engine, and Merb, whose adoption turned out to be a bad platform decision for my startup. It’s rewarding work but it also usually is plumbing work that generated little business value.
  • Divergence. Divergence is a form of failure to deliver rooted in a change of strategic direction of the platform. Divergence can be very costly over time and difficult to diagnose correctly because it happens very slowly. The analogy that comes to mind is of a frog in a pot of water on the stove. I knew a startup with a neat idea on how to provide significant value on top on the Salesforce platform APIs. They just needed one improvement that was “on the roadmap.” The improvement remained on the Salesforce roadmap for more than two years as the startup ran out of money. The hidden reason was that Salesforce had grown less interested in the use case. Another Salesforce-related example is the recent hoopla about the unannounced changes in Heroku’s routing mechanism, which cost RapGenius a lot of money. In this case, the reason was Heroku moving from being a great place to host Ruby apps to being a great place to host any apps and in the process becoming a less great place to host Ruby apps.
  • Poison pill. A platform choice made years ago could turn out to be a poison pill when it comes to selling your company to another larger platform vendor. As an example, consider the case of Google buying a company whose products are built on Microsoft’s .NET platform or Microsoft buying a SaaS collaboration solution that runs on Google Apps. Alas, most startups do not think about the exit implications as they make platform decisions early on.
  • Exit pressure. Platform companies may sometimes exert substantial pressure on partners when they want to acquire them. When Photobucket did not want to sell to MySpace they somehow experienced “integration problems” with MySpace, which affected their traffic. The sale soon completed. This goes to show that talking softly while controlling the source of traffic tends to deliver results. This week we learned that Twitter’s acquisition of social measurement service Bluefin Labs involved some threats, which must have been perceived as credible since 90% of Bluefin’s data came from Twitter.


Good diagnosis of the platform risk anti-pattern is exceptionally difficult because it requires predicting the future path of a platform as well as those of the platforms it competes with. The basic strategy for diagnosing this anti-pattern involves three parts:

  1. Investment in ongoing deep learning about the platform and its key competitors. This should cover the gamut from history to technology to business model to the personalities involved.
  2. Developing relationships with industry experts with a deep perspective of the platform, whose businesses, like telltales on a sailboat, in some way provide leading indicators of platform change. You don’t want just smart people. You want people with proprietary access and data. For enterprise software try preferred channel partners. For open-source software try high-end OSS consultants. For advertising, find the right type of agency.
  3. Network into the group(s) responsible for the platform, both involving people currently on the job as well as senior people who’ve recently left. This latter group has been the most helpful in my experience.

Ignorance is the most common anti-pattern that makes the diagnosis of platform risk difficult.


A common misdiagnosis stems from failure to consider the effects of competitive platforms on the platform a startup has adopted. Sometimes it is these competitors’ actions that trigger the negative consequences, as was the case of Apple’s decisions hurting the Adobe Flash developer ecosystem.

Refactored solutions

Once diagnosed, the key question regarding the platform risk anti-pattern is whether anything at all should be done about it. Most companies choose to live with the risk, though very few fully use the diagnosis strategies to get an accurate handle of the net present value of the risk.

The refactoring of platform risk is typically very, very expensive as well as very distracting. For example, some would argue that Zynga’s fight on two fronts (a) trying to refactor its platform risk related to Facebook and (b) ship new games is what hurt the company’s ability to execute.

In the case of platform risk, prevention is far better than any cure. In the words of Fred Wilson (an investor in Twitter): “Don’t be a Google Bitch, don’t be a Facebook Bitch, and don’t be a Twitter Bitch. Be your own Bitch.” Being your own bitch doesn’t mean not leveraging platforms. It means getting in the habit of doing the following three things in a lightweight, continuous process:

  1. Explicitly evaluate platform adoption decisions, once you have sufficient information.  Having sufficient information usually involves more than reading a few blogs. For example, at Swoop we recently had to make a search platform choice. We decided to go with Elastic Search but not before I had talked to the company, not before Benchmark invested significantly in ES, and not before I’d talked to friends who ran some of the largest ES deployments to get the lowdown on what it was operate ES at scale. 
  2. Invest the time to learn about the platform and develop the relationships that would help you have special access to information about the platform. Here is my simple rule of thumb with respect to any platform critical to your business: someone on your team should be able to contact one of the platform’s leaders and get a response relatively quickly. This is especially important if you are dealing with new or not super-popular open-source projects. The best way to achieve this is to think about how you and your business can help the platform.
  3. Every now and then spend a few minutes to honestly evaluate your company’s level of platform risk and think about how you’d mitigate it and when you’d have to put mitigation in action.

Remember, the goal is not to eliminate platform risk. You cannot do this while at the same time taking advantage of a platform. The goal is to efficiently reduce the likelihood of Black Swan-like events related to the platform hurting your business. If you understand the mechanics of how platforms operate and how platform risk accrues, you will be able to predict and prepare for events that take others by surprise. These are sometimes the best times to scale fast and leapfrog competitors.

When it could help

Betting on a platform can be hugely helpful to a startup, despite some level of platform risk. There is never a benefit from platform risk increasing to the anti-pattern level.


The startup anti-pattern repository is work in progress helped immensely by the stories and experiences you share on this blog and in person. Subscribe to get updates and share this anti-pattern with others so that we can avoid more preventable startup failures.

Posted in Anti-pattern, startups | Tagged , , , , , , | 6 Comments