Hulu Ad Swap puts complete control in the hands of the user by enabling them to instantly swap out of an ad they are watching for one that is more relevant. While it is a simple concept, it represents the culmination of the key insight we gained with the introduction of the original Hulu Ad Selector -- empowering users with choices over the ads they consume can lead to a virtuous cycle where advertisers benefit from a more engaged audience, content providers can see a greater return on their content, and users get more control and greater choice in their experience.

Appearances can be deceiving, and as simple as an idea like Hulu Ad Swap may sound, it brought with it a host of implementation challenges. We'd like to share some of the technical problems we solved with the development of Hulu Ad Swap.

Hulu Ad Swap differs from the recently launched dynamic Hulu Ad Selector in two significant ways:

  1. A default ad is shown automatically to the user with Hulu Ad Swap; whereas dynamic ad selector requires the users to choose a particular ad or wait for a timer to expire before showing the actual video ad, and...
  2. Hulu Ad Swap is enabled by default for all ads whereas advertisers have to opt-in explicitly for dynamic ad selectors.
Given the similarities between Hulu Ad Swap and Hulu Ad Selector, I thought I would explain a bit about the implementation behind dynamic ad selector before delving into Hulu Ad Swap. Unlike the original ad selector where choices are statically defined within the selector slate, the ad selection server (or simply the adserver) would need to assemble all the user-selectable options in a dynamic ad selector slate. If you considered the main output of the adserver to be selecting an ad that the user will see, the efficiency of constructing a dynamic ad selector slate is effectively 33% of the norm as only one ad is watched by a user for every three ads selected. Moreover, ads chosen for a dynamic ad selector must obey Hulu’s anti-ad-fatigue (no two ads from the same brand can run side-by-side) and industry separation (no two ads of the same industry but from different advertisers can run side-by-side). This puts limits on how far you can reduce the overhead of selecting the extra ads for a dynamic ad selector. Based on these considerations, we intentionally limit dynamic ad selector to serve, at most, once per stream.

Compared with dynamic ad selector, Hulu Ad Swap is a far more ambitious undertaking since alternative ads have to be chosen for every single ad we serve. If we had gone through with the plan of choosing three alternatives for every ad we serve, the efficiency of the adserver would drop to 25% of what it was before, and anyone who has read the manual on The Challenge of Scaling an Adserver would be extremely nervous about implementing a new feature with this sort of performance implication.

For illustrative purposes, let's consider how someone would implement Hulu Ad Swap in the most straight-forward manner:

Upon receiving an ad request for an ad break from the video player, the adserver would:

  1. Select the default ad, plus three alternative ads.
  2. Given that each ad break could have more than one ad, Step 1 is repeated until the adserver has all the default + alternative ads the player will need for the ad break, at which point the ads are returned to the player.
Of course, the pseudo code above omits a lot of the difficulties involved in the ad selection process. For example, all of the default and alternative ads chosen have to obey their own targeting rules which could include but are not limited to content, day-part, demographic, or geographic restrictions as well as anti-ad-fatigue and industry separation rules. Most advertisers also require their campaigns to be delivered evenly over time, and that places further constraints on the number of ads available for selection at any given time.

The Hulu adserver also allows advertisers to specify the maximum number of ads that their ads are allowed to run alongside within a single ad break (or pod). This gives advertisers a lot of flexibility in executing their marketing strategy by making trade-offs between focusing and having broad exposure of their messages. However, having different ads-per-pod limits among different campaigns can introduce significant difficulties in carrying out Step 2 of the Ad Swap selection algorithm, as you don't necessarily know how many ads will be needed in an ad break until the user has chosen among the default and alternative ads. We could use a more conservative approach by selecting ads with ads-per-pod limits equal to or greater than the minimum ads-per-pod limit of all the ads that we have selected for the ad break so far, but that places yet further restriction on the pool of available ads.

Apart from the considerable processing overhead, the large number of rules we need to apply for alternative ads selection had also raised concerns over whether the adserver can actually find that many alternative ads in real-world conditions. So, to increase the chance of the adserver finding the number of alternative ads that Hulu Ad Swap requires, we considered relaxing the anti-ad-fatigue and industry separation rules to apply only to ads that are chosen by the user. This, however, leads to a drastic change in the ad selection protocol between the player and the adserver that resembles the following:

On each ad request from the player, the adserver:

  1. Selects default ad plus alternative ads.
  2. The player waits for the user to make the final selection before making another ad request with the ad chosen by the user. (This way, the ad server has the information it needs to apply anti-ad-fatigue and industry separation rules only to the ad that the user actually watched. And to ensure a good user experience, the adserver needs to avoid serving the same ad that the user has just swapped out.)
This switch from our current one-request-per-ad-break protocol, to a one-request-per-ad (+ alternatives) protocol might not immediately appear to be feasible because:

  1. It introduces a lot more processing overhead in the form of extra network calls between the player and the adserver, on top of the overhead of selecting the alternative ads, and...
  2. The communication protocol between the player and the adserver would need to be more complicated in order to maintain additional state information between ad selections that is currently hidden within a one-request-per-ad-break protocol. (For example, the one-request-per-ad protocol would need to keep track of the ads-per-pod limit of the ads that have been served so far, whereas the adserver keeps track of this internally in the current one-request-per-ad-break protocol.)
Nevertheless, this was the best idea we went with for a long time, until we stumbled upon the following observation: If switching to a one-request-per-ad protocol is the source of undue complexities, can’t we fit alternative ad selection into a one-request-per-ad-break protocol instead? As it turned out, yes we can, and here is how it works:

  1. Select all the default ads within an ad break as the adserver does today.
  2. Select alternative ads for the entire ad break with anti-ad-fatigue and industry separation rules applied across the default and alternative ads within the ad break.
Since we know exactly how many default ads we are going to serve in the ad break, it becomes easy to select alternative ads with a compatible ads-per-pod limit. As an added bonus, given that the alternative ads are available for all ads within the ad break instead of being selected separately for each ad, it becomes possible to select fewer alternative ads while maintaining the same number of choices. More precisely, the adserver only needs to select n + 2 alternative ads to maintain a minimum of at least 3 choices in an n-ads-per-pod (napp) scenario, whereas 3n alternatives would be needed if the adserver selects them separately for each ad. So for a 2app scenario, only 2+2=4 alternative ads would be needed if alternative ads are available for the entire ad break versus the 3x2=6 alternative ads required if they are selected separately for each ad.

As promising as the latest approach looked, we still had a few doubts as to whether or not the adserver could handle the overhead of selecting the reduced number of alternative ads, and if the adserver could find sufficient number of alternative ads to make it worthwhile. So to find out, we actually implemented the code to select the alternative ads in the adserver. This wouldn't have been possible if we decided to go with the one-request-per-ad protocol, as we would have had to make changes in both the adserver and our video player before we could measure the full impact in production.

The results of our little experiment were definitely encouraging. The overhead of selecting the alternative ads was barely noticeable, and while there were indeed cases where the adserver could not find as many alternatives as we hoped, it could find them in a majority of the cases. At that point, we were fully convinced that this was the correct approach for alternative ad selection.

Looking back, the various designs we have explored were quite similar to one another, but it is the tiniest difference in mindset that turned a seemingly infeasible design into a viable one, and we hope you get a kick out of this the next time you swap an ad on Hulu.

Raymond Mak is the Principal Software Developer focused on keeping Hulu's ad server delivering harder, better, faster and stronger.