96
Scaling A/B testing on Netflix.com with _________ Alex Liu @stinkydofu

[HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

Embed Size (px)

DESCRIPTION

This is the extended (full) version of the talk given at HTML5DevConf 2014. A condensed version of this talk was previously given at NodeConfEU 2014. At Netflix we run hundreds of A/B tests every year. Maintaining multivariate experiences quickly adds strain to any UI engineering team. Join us to explore the patterns we’ve built in Node.js to tame this beast - ultimately enabling quick feature development and rapid test iteration on our service used by over 50 million people around the world. Video from NodeConfEU: https://www.youtube.com/watch?v=gtjzjiTI96c

Citation preview

Page 1: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

Scaling A/B testing on Netflix.com with

_________Alex Liu @stinkydofu

Page 2: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

data driven product development

Page 3: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js
Page 4: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js
Page 5: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js
Page 6: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js
Page 7: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

A

B

C

D

E

F

G

A

B

C

D

E

F

G

A

B

C

D

E

F

G

A

B

C

D

E

F

G

A

B

C

D

E

F

G

A

B

C

D

E

F

G

A

B

C

D

E

F

G

Test 1 Test 2 Test 3 Test 4 Test 5 Test 6 Test 7

Page 8: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

2,097,152 unique experiences across seven tests

Page 9: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

hundreds of new A/B tests per year

Page 10: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

433518929550349486086117218185493567650…72061153709996

Page 11: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

2105 566 685templates CSS JS

Page 12: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

2.5M unique packages every week

Page 13: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

<html/> <link/> <script/>

Page 14: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

problem: conditional dependencies

Page 15: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js
Page 16: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

▶ Templating ▶ Packaging ▶ Bonus Round

Page 17: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

Templating

Page 18: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js
Page 19: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

payment.dust<div id="payments"> <input id="first-name"><input id="last-name"> <span class="payment-logos"> <img src="logo1.png"><img src="logo2.png"> <img src="logo3.png"><img src="logo4.png"> </span> <input id="card-number"><input id="security-code"> <select name="month"></select><select name="year"></select> <checkbox id="agree-to-terms"/> <button>Start Your Trial</button> </div>

Page 20: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js
Page 21: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js
Page 22: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

<input id=“first-name"><input id=“last-name"> {@inTest id="10" cell=“2a"} <div class="payment-types"> <div id="CC"> <span class="payment-logos"> <img src="logo1.png"><img src="logo2.png"> <img src="logo3.png"><img src="logo4.png"> </span> <input id=“card-number"><input id=“security-code”> </div> <div id="DD"> <span class=“payment-logos"> <img src="logo1.png"><img src="logo2.png"> <img src="logo3.png"><img src="logo4.png"> </span> <input id="CPF"><input id="bank-name"> <input id="branch-number"><input id="account-number"> <input id="digit"> </div> </div> {/@inTest} {@inTest id="10" cell=“3"} <div class="payment-types"> <div id="CC"> <span class="payment-logos"> <img src="logo1.png"><img src="logo2.png"> <img src="logo3.png"><img src="logo4.png"> </span> <input id=“card-number"><input id=“security-code”> </div> <div id="DD"> <span class="payment-logos"> <img src="logo1.png"><img src="logo2.png"> <img src="logo3.png"><img src="logo4.png"> </span> <input id="CPF"><input id="bank-name"> <input id="branch-number"><input id="account-number"> <input id="digit"> </div> </div> {/@inTest} {@inTest id="10" cell=“4"} <div class="payment-types"> <div id="CC"> <span class="payment-logos"> <img src="logo1.png"><img src="logo2.png"> <img src="logo3.png"><img src="logo4.png"> </span> <input id=“card-number"><input id=“security-code”> </div> <div id="DD"> <span class="payment-logos"> <img src="logo1.png"><img src="logo2.png"> <img src="logo3.png"><img src="logo4.png"> </span> <input id="CPF"><input id="bank-name"> <input id="branch-number"><input id="account-number"> <input id="digit"> </div> </div> {/@inTest} {@inTest id="10" cell=“5"} <div class="payment-types"> <div id="CC"> <span class="payment-logos"> <img src="logo1.png"><img src="logo2.png"> <img src="logo3.png"><img src="logo4.png"> </span> <input id=“card-number"><input id=“security-code”> </div> <div id="DD"> <span class="payment-logos"> <img src="logo1.png"><img src="logo2.png"> <img src="logo3.png"><img src="logo4.png"> </span> <input id="CPF"><input id="bank-name"> <input id="branch-number"><input id="account-number"> <input id="digit"> </div> </div> {/@inTest} <checkbox id="agree-to-terms"></checkbox> <button>Start Your Trial</button> </div>

Page 23: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

payment.dust

if ifif if if

Control Cell 2 Cell 3 Cell 4 Cell 5

Page 24: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

<div class="payment-types"> <div id="CC"> {> payment_type_cc /} </div> <div id="DD"> {> payment_type_dd /} </div> </div>

Page 25: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

payment.dust

Control Cell 2 Cell 3 Cell 4 Cell 5

payment_type_cc.dust payment_type_dd.dust

if ifif if if

Page 26: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

payment.dust

Control.dust

payment_type_cc.dust payment_type_dd.dust

Cell3.dustCell2.dust Cell4.dust Cell5.dust

if ifif if if

Page 27: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

<div id="payments"> <input id="first-name"> <input id="last-name">

{> payment_method /}

<input type="checkbox" id="terms"> <button>Start Your Trial</button> </div>

Page 28: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

payment.json

payment.dust

?

Control.dust Cell3.dustCell2.dust Cell4.dust Cell5.dust

payment_type_cc.dust payment_type_dd.dust

Page 29: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

payment.json{ "rules": [], "templateName": "control" }, { "rules": ["PaymentTest(2)"], "templateName": "payment_cell2" }, { "rules": ["PaymentTest(3)"], "templateName": "payment_cell3" }, { "rules": ["PaymentTest(4)"], "templateName": "payment_cell4" }, { "rules": ["PaymentTest(5)"], "templateName": "payment_cell5" }

Page 30: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

require('nf-rule-infrastructure')

Page 31: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

var Rule = require('nf-rule-infrastructure'), PaymentTest;

PaymentTest = new Rule('PaymentTest', function(context, params, cb) { var test = context.abtests.get(10); cb(test && test.cell(params.id)); });

module.exports = PaymentTest;

anatomy of a rule

Page 32: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

require('nf-template-resolver')

Page 33: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

payment.dust dustjs partial

Page 34: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

resolver payment.json (mappings)

rule

Page 35: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

rules

control.dust

cell2.dust

cell3.dust

Page 36: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

payment.dust dustjs resolver

Page 37: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

<input id=“first-name"><input id=“last-name"> {@inTest id="10" cell=“2a"} <div class="payment-types"> <div id="CC"> <span class="payment-logos"> <img src="logo1.png"><img src="logo2.png"> <img src="logo3.png"><img src="logo4.png"> </span> <input id=“card-number"><input id=“security-code”> </div> <div id="DD"> <span class=“payment-logos"> <img src="logo1.png"><img src="logo2.png"> <img src="logo3.png"><img src="logo4.png"> </span> <input id="CPF"><input id="bank-name"> <input id="branch-number"><input id="account-number"> <input id="digit"> </div> </div> {/@inTest} {@inTest id="10" cell=“3"} <div class="payment-types"> <div id="CC"> <span class="payment-logos"> <img src="logo1.png"><img src="logo2.png"> <img src="logo3.png"><img src="logo4.png"> </span> <input id=“card-number"><input id=“security-code”> </div> <div id="DD"> <span class="payment-logos"> <img src="logo1.png"><img src="logo2.png"> <img src="logo3.png"><img src="logo4.png"> </span> <input id="CPF"><input id="bank-name"> <input id="branch-number"><input id="account-number"> <input id="digit"> </div> </div> {/@inTest} {@inTest id="10" cell=“4"} <div class="payment-types"> <div id="CC"> <span class="payment-logos"> <img src="logo1.png"><img src="logo2.png"> <img src="logo3.png"><img src="logo4.png"> </span> <input id=“card-number"><input id=“security-code”> </div> <div id="DD"> <span class="payment-logos"> <img src="logo1.png"><img src="logo2.png"> <img src="logo3.png"><img src="logo4.png"> </span> <input id="CPF"><input id="bank-name"> <input id="branch-number"><input id="account-number"> <input id="digit"> </div> </div> {/@inTest} {@inTest id="10" cell=“5"} <div class="payment-types"> <div id="CC"> <span class="payment-logos"> <img src="logo1.png"><img src="logo2.png"> <img src="logo3.png"><img src="logo4.png"> </span> <input id=“card-number"><input id=“security-code”> </div> <div id="DD"> <span class="payment-logos"> <img src="logo1.png"><img src="logo2.png"> <img src="logo3.png"><img src="logo4.png"> </span> <input id="CPF"><input id="bank-name"> <input id="branch-number"><input id="account-number"> <input id="digit"> </div> </div> {/@inTest} <checkbox id="agree-to-terms"></checkbox> <button>Start Your Trial</button> </div>

Page 38: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

<div id="payments"> <input id="first-name"> <input id="last-name">

{> payment_method /}

<input type="checkbox" id="terms"> <button>Start Your Trial</button> </div>

Page 39: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

Wins▶ combine rules ▶ improve template legibility ▶ increase template reuse

Page 40: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

▶ Templating ▶ Packaging ▶ Bonus Round

Page 41: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

Packaging

Page 42: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

everything is a module

Page 43: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js
Page 44: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js
Page 45: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

oldSearch

app.js

newSearch

dep1 dep2 dep3 dep4 dep5

sub-dep sub-depsub-dep sub-dep sub-dep sub-dep

Page 46: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

oldSearch

app.js

newSearch

dep1 dep2 dep3 dep4 dep5

sub-dep sub-depsub-dep sub-dep sub-dep sub-dep

Page 47: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

app.js

import jquery from 'jquery'; import oldSearch from 'oldSearch'; import newSearch from 'newSearch';

export ...

Page 48: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

oldSearch

app.js

newSearch

dep1 dep2 dep3 dep4 dep5

sub-dep sub-depsub-dep sub-dep sub-dep sub-dep

Page 49: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js
Page 50: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

685 files…?

2.5M packages…?

Page 51: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

oldSearch

app.js

newSearch

dep1 dep2 dep3 dep4 dep5

sub-dep sub-depsub-dep sub-dep sub-dep sub-dep

Page 52: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

problem: conditional dependencies

Page 53: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js
Page 54: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

requestbuild

Page 55: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

require('nf-include-when')

Page 56: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

/* * @includewhen rule.notInNewSearch */

oldSearch.js

Page 57: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

/* * @includewhen rule.inNewSearch */

newSearch.js

Page 58: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

var Rule = require('nf-rule-infrastructure'), inNewSearch;

inNewSearch = new Rule('inNewSearch', function(context, cb) { var test = context.abtests.get(1534); cb(test && test.cell(1)); });

module.exports = inNewSearch;

anatomy of a rule

Page 59: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

require('nf-asset-registry')

Page 60: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

import jquery from 'jquery'; import oldSearch from 'oldSearch'; import newSearch from 'newSearch';

export ...

app.js

Page 61: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

newSearch.js

jquery

oldSearch.js

app.js

registry

Page 62: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

"app.js": { "deps": [ "jquery", "oldSearch.js", "newSearch.js", ], "depsFull": [ "jquery", "oldSearchDep2.js", "oldSearchDep1.js", "oldSearch.js", "newSearchDep2.js", "newSearchDep1.js", "newSearch.js" ] }

Page 63: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

"newSearch.js": { "rule": "inNewSearch", "deps": [ "jquery", "newSearchDep2.js", "newSearchDep1.js", ], "depsFull": [ "jquery", "newSearchSubDep3.js", "newSearchSubDep2.js" "newSearchSubDep1.js" "newSearchDep2.js", "newSearchDep1.js" ] }

nf-include-when

Page 64: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

require('nf-packager')

Page 65: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

var packager = require('nf-packager'), includeWhen = require('nf-include-when'), registries = require('nf-asset-registry');

function getScriptUrl() return packager.getPackageDefinition('app.js', registries, includeWhen); }

Page 66: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

"app.js": { "deps": [ "jquery", "oldSearch.js", "newSearch.js", ], "depsFull": [ "jquery", "oldSearchDep2.js", "oldSearchDep1.js", "oldSearch.js", "newSearchDep2.js", "newSearchDep1.js", "newSearch.js" ], "fileSize": "4.41 kB", "fileSizeFull": "120.52 kB" }

Step 1: Get the full dependency tree for the requested package from the registry.

Page 67: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

[ "jquery", /* no rule */ "oldSearchDep2.js", /* no rule */ "oldSearchDep1.js", /* no rule */ "oldSearch.js", /* rules.notInNewSearch */ "newSearchDep2.js", /* no rule */ "newSearchDep1.js”, /* no rule */ "newSearch.js" /* rules.inNewSearch */ ]

Step 2: Determine which files have rules.

Page 68: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

[ "jquery", /* no rule */ "oldSearchDep2.js", /* no rule */ "oldSearchDep1.js", /* no rule */ "oldSearch.js", /* rules.notInNewSearch */ "newSearchDep2.js", /* no rule */ "newSearchDep1.js”, /* no rule */ "newSearch.js" /* rules.inNewSearch */ ]

Step 3: Run the rules. Filter out all deps that resolved false.

Page 69: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

[ "jquery", /* no rule */ "oldSearchDep2.js", /* no rule */ "oldSearchDep1.js", /* no rule */ "oldSearch.js", /* rules.notInNewSearch */ "newSearchDep2.js", /* no rule */ "newSearchDep1.js”, /* no rule */ "newSearch.js" /* rules.inNewSearch */ ]

Step 4: Filter out all extraneous sub deps.

Page 70: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

Step 5: Concatenate the files.

[ "jquery", /* no rule */ "newSearchDep2.js", /* no rule */ "newSearchDep1.js”, /* no rule */ "newSearch.js" /* rules.inNewSearch */ ]

Page 71: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

buildjavascript

registry

Page 72: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

request registry

rulespackager

Page 73: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

Wins▶ leverage build time tools ▶ leverage the server ▶ divide and conquer with modules

Page 74: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

▶ Templating ▶ Packaging ▶ Bonus Round

Page 75: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

Bonus Round

Page 76: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

be creative with the registry

Page 77: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

"account/bb/models/ratingHistoryModel.js": { "rule": null, "deps": [...], "depsFull": [...], "depsCount": { "underscore": 2, "backbone": 1, "jquery": 2, "common/requirejs-plugins.js": 4, "requirejs-text": 4, "utils/contextData.js": 1, "common/nfNamespace.js": 1 }, "hash": "dd23b163", "fileSize": "1.21 kB", "fileSizeFull": "173.04 kB" }

dependency counting

dependency pruning

file sizes

Page 78: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

@import (reference) "/common/_nf_defs.less"; @import (reference) "/member/memberCore.less"; @import (reference) "/components/menu.less"; @import (reference) "/components/breadcrumbs.less";

@import modules

Page 79: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

"account/containerResponsive.css": { "rule": null, "deps": [...], "depsFull": [...], "depsCount": [...], "hash": "65a431f3", "fileSize": "709 B", "fileSizeFull": "709 B", "css": { "selectors": 8, "declarationBlocks": 6, "declarations": 17, "mediaQueries": 3 } }

css analysis

Page 80: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

the

best part

Page 81: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

"cache": { "account/pin.js": "define('account/pin.js', ['member/memberC…", "account/bb/models/changePlanModel.js": "define('account/b…", "account/bb/models/ratingHistoryModel.js": "define('account…", "account/bb/models/viewingActivityModel.js": "define('account…", "account/bb/views/changePlanView.js": "define('account/bb/vi…", "account/bb/views/changePlanView.js": "define('account/bb/vi…", "account/bb/views/emailSubView.js": "define('account/bb/views…", "account/bb/views/viewingActivityView.js": "define('account…", "common/UITracking.js": "define('common/UITracking.js, ['me…", "common/UITrackingOverlay.js": "define('common/UITrackingOve…", … … …

Page 82: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

css

mappings

javascript

templates templates

mappings

javascript

css

Page 83: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

templates

mappings

javascript

css

UI Bundle

Page 84: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js
Page 85: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

deploy UI bundles

anytime

Page 86: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

never touch the file system

Page 87: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

< 5ms package response times

Page 88: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

Wins▶ static analysis FTW ▶ independent UI deployments ▶ requests never touch the fs ▶ fast package response times

Page 89: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

Our Learnings

Page 90: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

learn by doing

Page 91: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

fail fastmove faster

Page 92: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

“I have not failed.I’ve just found 10,000 waysthat won’t work.”

Thomas Edison

Page 93: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

simplify

Page 94: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js
Page 95: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

Alex Liu @stinkydofu

thank you

Page 96: [HTML5DevConf2014] Scaling AB Testing on Netflix.com with Node.js

Alex Liu @stinkydofu

questions?