84
Continuation-Local-Storage and the Magic of AsyncListener Islam Sharabash @ibashes @DataHero

Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Embed Size (px)

Citation preview

Page 1: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Continuation-Local-Storage and the Magic of AsyncListener

Islam Sharabash @ibashes

@DataHero

Page 2: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Agenda• Problems caused by the event loop of Node.js

• Solving problems with AsyncListener API

• How AsyncListener works

Page 3: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Mo Node Mo ProblemsThings that are hard with the event loop:

• Having a request ID propagate throughout code

• Short stack traces from errors in async functions

• Profiling async functions is painful

Page 4: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Mo Node Mo ProblemsThings that are hard with the event loop:

• Having a request ID propagate throughout code

• Short stack traces from errors in async functions

• Profiling async functions is painful

Page 5: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Propagating Request ID (naive)

Page 6: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Propagating Request ID (naive)

var ctx = {rid: uuid.v4()}; users.save(ctx, attrs, function(error, userId) { users.load(ctx, userId, function(error, user) { log.info(‘user created’, {rid: ctx.rid, userId: user.id}); mailer.sendWelcome(ctx, user, function(error) { log.info(‘welcome email sent’, {rid: ctx.rid, userId: user.id}); }); });});

Page 7: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Propagating Request ID (naive)

var ctx = {rid: uuid.v4()}; users.save(ctx, attrs, function(error, userId) { users.load(ctx, userId, function(error, user) { log.info(‘user created’, {rid: ctx.rid, userId: user.id}); mailer.sendWelcome(ctx, user, function(error) { log.info(‘welcome email sent’, {rid: ctx.rid, userId: user.id}); }); });});

Page 8: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Propagating Request ID (naive)

var ctx = {rid: uuid.v4()}; users.save(ctx, attrs, function(error, userId) { users.load(ctx, userId, function(error, user) { log.info(‘user created’, {rid: ctx.rid, userId: user.id}); mailer.sendWelcome(ctx, user, function(error) { log.info(‘welcome email sent’, {rid: ctx.rid, userId: user.id}); }); });});

Page 9: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Mo Node Mo ProblemsThings that are hard with the event loop:

• Having a request ID propagate throughout code

• Short stack traces from errors in async functions

• Profiling async functions is painful

Page 10: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Short Stack Traces

users.save(attrs, function(error, userId) { if (error) { return callback(error); } users.load(userId, function(error, user) { if (error) { return callback(error); } mailers.sendWelcome(user, function(error) { if (error) { return callback(error); } }); });});

Page 11: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Short Stack Traces

users.save(attrs, function(error, userId) { if (error) { return callback(error); } users.load(userId, function(error, user) { if (error) { return callback(error); } mailers.sendWelcome(user, function(error) { if (error) { return callback(error); } }); });});

Page 12: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Short Stack Traces

Error: ER_PARSE_ERROR You have an error in your SQL syntax...at getError (lib/models/database.js:479:24)at Query._callback (lib/models/database.js:110:34) at Query.Sequence.end (node_modules/mysql/lib/protocol/sequences/Sequence.js:75:24)at Query.ErrorPacket (node_modules/mysql/lib/protocol/sequences/Query.js:93:8)at Protocol._parsePacket (node_modules/mysql/lib/protocol/Protocol.js:192:24)at Parser.write (node_modules/mysql/lib/protocol/Parser.js:62:12)at Socket.ondata (stream.js:51:26) at Socket.emit (events.js:117:20) at Socket. (_stream_readable.js:748:14)at Socket.emit (events.js:92:17) at emitReadable_ (_stream_readable.js:410:10) at emitReadable (_stream_readable.js:406:5) at readableAddChunk (_stream_readable.js:168:9)at Socket.Readable.push (_stream_readable.js:130:10) at TCP.onread (net.js:528:21)

Page 13: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Short Stack Traces

Error: ER_PARSE_ERROR You have an error in your SQL syntax...at getError (lib/models/database.js:479:24)at Query._callback (lib/models/database.js:110:34) at Query.Sequence.end (node_modules/mysql/lib/protocol/sequences/Sequence.js:75:24)at Query.ErrorPacket (node_modules/mysql/lib/protocol/sequences/Query.js:93:8)at Protocol._parsePacket (node_modules/mysql/lib/protocol/Protocol.js:192:24)at Parser.write (node_modules/mysql/lib/protocol/Parser.js:62:12)at Socket.ondata (stream.js:51:26) at Socket.emit (events.js:117:20) at Socket. (_stream_readable.js:748:14)at Socket.emit (events.js:92:17) at emitReadable_ (_stream_readable.js:410:10) at emitReadable (_stream_readable.js:406:5) at readableAddChunk (_stream_readable.js:168:9)at Socket.Readable.push (_stream_readable.js:130:10) at TCP.onread (net.js:528:21)

Page 14: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Mo Node Mo ProblemsThings that are hard with the event loop:

• Having a request ID propagate throughout code

• Short stack traces from errors in async functions

• Profiling async functions is painful

Page 15: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Profiling Async Functions

var asyncFunc = function(callback) { setTimeout(callback, 1000); };

Page 16: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Profiling Async Functions

var orig = asyncFunc;asyncFunc = function() { var start = new Date(); var args = Array.prototype.slice.call(arguments, 0); var cb = args[args.length - 1]; args[args.length - 1] = function() { console.log((new Date()) - start); return cb.apply(this, arguments); } return orig.apply(this, args);}

Page 17: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Profiling Async Functions

var orig = asyncFunc;asyncFunc = function() { var start = new Date(); var args = Array.prototype.slice.call(arguments, 0); var cb = args[args.length - 1]; args[args.length - 1] = function() { console.log((new Date()) - start); return cb.apply(this, arguments); } return orig.apply(this, args);}

Page 18: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Where Do These Problems Come From?

Page 19: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Where Do These Problems Come From?

Page 20: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Where Do These Problems Come From?

• Asynchronicity and the Event Loop

Page 21: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Where Do These Problems Come From?

• Asynchronicity and the Event Loop

process .nextTick(myFunc);

Event Loop myFunc Executed

Page 22: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Where Do These Problems Come From?

• Asynchronicity and the Event Loop

process .nextTick(myFunc);

Event Loop myFunc Executed

State and stack lost

Page 23: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Where Do These Problems Come From?

• Asynchronicity and the Event Loop

process .nextTick(myFunc);

Event Loop myFunc Executed

State and stack lost

Page 24: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

How do we solve it?• AsyncListener API

process.createAsyncListenerprocess.addAsyncListenerprocess.removeAsyncListener

• Available in Node >0.11.9; Polyfill for Node <= 0.11.8

Page 25: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

AsyncListener

process .nextTick(myFunc);

Page 26: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

AsyncListener

process .nextTick(myFunc);

Event Loop

Page 27: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

AsyncListener

process .nextTick(myFunc);

Event Loop myFunc Executed

Some time later…

Page 28: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

AsyncListener

process .nextTick(myFunc);

Event Loop myFunc Executed myFunc returns

myFunc errors

Some time later…

Page 29: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

AsyncListener

process .nextTick(myFunc);

Event Loop myFunc Executed myFunc returns

myFunc errors

Some time later…

Create Callback

Page 30: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

AsyncListener

process .nextTick(myFunc);

Event Loop myFunc Executed myFunc returns

myFunc errors

Some time later…

Create Callback Before Callback

Page 31: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

AsyncListener

process .nextTick(myFunc);

Event Loop myFunc Executed myFunc returns

myFunc errors

Some time later…

Create Callback Before Callback After / Error Callback

Page 32: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Propagate Request IDsHow do we solve it?

Page 33: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Propagate Request IDsHow do we solve it?

• Don’t use AsyncListener Directly

Page 34: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Propagate Request IDsHow do we solve it?

• Don’t use AsyncListener Directly

• Use continuation-local-storage

Page 35: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Propagate Request IDsHow do we solve it?

• Don’t use AsyncListener Directly

• Use continuation-local-storage

• But what is it?

Page 36: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

continuation-local-storage

Page 37: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

continuation-local-storageasync function 1

Page 38: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

continuation-local-storageBag Of Holding (CLS makes this) async function 1

Page 39: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

continuation-local-storageBag Of Holding (CLS makes this) async function 1

async function 2

Page 40: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

continuation-local-storageBag Of Holding (CLS makes this) async function 1

async function 2

async function 3

Page 41: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

continuation-local-storageBag Of Holding (CLS makes this)

Each async chain gets it’s own bag

async function 1

async function 2

async function 3

Page 42: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

continuation-local-storageBag Of Holding (CLS makes this) async function 1

async function 2

async function 3

Each async chain gets it’s own bag

async function 1

async function 2

async function 3

Page 43: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Propagate Request IDsWant

• Request ID in every log message

• Easy

Page 44: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Propagate Request IDsWant

2013-12-20T18:12:20.037Z - Executing SQL BEGIN statement 2013-12-20T18:12:20.385Z - Values and label are different datasources 2013-12-20T18:12:20.530Z - Executing SQL COMMIT statement 2013-12-20T18:12:20.531Z - Picking filter implementation: DEFAULT

Page 45: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Propagate Request IDsWant

2013-12-20T18:12:20.037Z - Executing SQL BEGIN statement - 3dd4ee56-e8e3-4270-bda7-90c5ee409782

2013-12-20T18:12:20.385Z - Values and label are different datasources - 02671cb7-d22b-4a3e-9bb8-282608999688

2013-12-20T18:12:20.530Z - Executing SQL COMMIT statement - 3dd4ee56-e8e3-4270-bda7-90c5ee409782

2013-12-20T18:12:20.531Z - Picking filter implementation: DEFAULT - 02671cb7-d22b-4a3e-9bb8-282608999688

Page 46: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Propagate Request IDsHow?

• Create continuation-local-storage namespace

• Middleware to create/assign Request ID

• Look up Request ID when logging

Page 47: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Propagate Request IDsCreate namespace

Page 48: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Propagate Request IDsCreate namespace

var createNamespace = require(‘continuation-local-storage') .createNamespace;

var namespace = createNamespace('com.datahero');

Page 49: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Propagate Request IDsCreate namespace

var createNamespace = require(‘continuation-local-storage') .createNamespace;

var namespace = createNamespace('com.datahero');

Once at top of application

Page 50: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Propagate Request IDsMiddleware

Page 51: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Propagate Request IDsMiddleware

var getNamespace = require(‘continuation-local-storage') .getNamespace;

var namespace = getNamespace('com.datahero'), uuid = require('node-uuid');

Page 52: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Propagate Request IDsMiddleware

var getNamespace = require(‘continuation-local-storage') .getNamespace;

var namespace = getNamespace('com.datahero'), uuid = require('node-uuid');

Just requiring… nothing special

Page 53: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Propagate Request IDsMiddleware

app.use(function(req, res, next) { var rid = uuid.v4(); namespace.bindEmitter(req); namespace.bindEmitter(res); namespace.run(function() { namespace.set(‘rid’, rid); next(); }); });

Page 54: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Propagate Request IDsMiddleware

app.use(function(req, res, next) { var rid = uuid.v4(); namespace.bindEmitter(req); namespace.bindEmitter(res); namespace.run(function() { namespace.set(‘rid’, rid); next(); }); });

Page 55: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Propagate Request IDsMiddleware

app.use(function(req, res, next) { var rid = uuid.v4(); namespace.bindEmitter(req); namespace.bindEmitter(res); namespace.run(function() { namespace.set(‘rid’, rid); next(); }); });

Page 56: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Propagate Request IDsMiddleware

app.use(function(req, res, next) { var rid = uuid.v4(); namespace.bindEmitter(req); namespace.bindEmitter(res); namespace.run(function() { namespace.set(‘rid’, rid); next(); }); });

Page 57: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Propagate Request IDsMiddleware

app.use(function(req, res, next) { var rid = uuid.v4(); namespace.bindEmitter(req); namespace.bindEmitter(res); namespace.run(function() { namespace.set(‘rid’, rid); next(); }); });

Page 58: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Propagate Request IDsLogger

Page 59: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Propagate Request IDsLogger

var getNamespace = require(‘continuation-local-storage') .getNamespace;

var namespace = getNamespace(‘com.datahero');

Page 60: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Propagate Request IDsLogger

Logger.debug = function(message) { console.log( (new Date()).toISOString() + ‘ - ‘ + message + ‘ - ‘ + namespace.get(‘rid') ); };

Page 61: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Propagate Request IDsLogger

Logger.debug = function(message) { console.log( (new Date()).toISOString() + ‘ - ‘ + message + ‘ - ‘ + namespace.get(‘rid') ); };

Page 62: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Propagate Request IDsBoom!

Page 63: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Short Stack Traces

Page 64: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Short Stack Traces

setImmediate(function start() { process.nextTick(function howDeep() { setImmediate(function isTheStack() { throw new Error('Oh no!'); }); }); });

Page 65: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Short Stack Traces

setImmediate(function start() { process.nextTick(function howDeep() { setImmediate(function isTheStack() { throw new Error('Oh no!'); }); }); });

Error: Oh no! at Object.isTheStack [as _onImmediate] (short-stack.js:4:13) at processImmediate [as _immediateCallback] (timers.js:336:15)

Page 66: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Short Stack Traces

setImmediate(function start() { process.nextTick(function howDeep() { setImmediate(function isTheStack() { throw new Error('Oh no!'); }); }); });

Error: Oh no! at Object.isTheStack [as _onImmediate] (short-stack.js:4:13) at processImmediate [as _immediateCallback] (timers.js:336:15)

Page 67: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Short Stack Traces

Page 68: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Short Stack Traces

process .nextTick(myFunc);

Event Loop myFunc Executed myFunc returns

myFunc errors

Save copy of stack Stitch stack together

Page 69: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Short Stack TracesUse stackup

• https://www.npmjs.org/package/stackup

Page 70: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Short Stack TracesUse stackup

• https://www.npmjs.org/package/stackup

npm install --save stackup

Page 71: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Short Stack TracesUse stackup

• https://www.npmjs.org/package/stackup

npm install --save stackup

require(‘stackup’);

Page 72: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Profiling Async Code

Page 73: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Profiling Async Code

process .nextTick(myFunc);

Event Loop myFunc Executed myFunc returns

myFunc errors

Record TimeCompute / RecordTime Difference

Page 74: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Profiling Async CodeUse async-profile

• https://www.npmjs.org/package/async-profile

Page 75: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Profiling Async CodeUse async-profile

• https://www.npmjs.org/package/async-profile

npm install --save async-profile

Page 76: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Profiling Async CodeUse async-profile

• https://www.npmjs.org/package/async-profile

var AsyncProfile = require('async-profile') function () { … new AsyncProfile(); doAsyncWork(); }

Page 77: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Summary• Problems

• Can we solve them? Yes we can!

• Use continuation-local-storage, stackup, and async-profile

• All built on AsyncListener

• Prepare for more tracing packages

Page 78: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

We’re Hiring

Email me at [email protected]

Page 79: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Islam Sharabash@ibashes

[email protected]

Page 80: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

AsyncListener APIrequire(‘async-listener’);var al = process.createAsyncListener({ create: function(data) {}, before: function(context, data) {}, after: function(context, data) {}, error: function(data, error) {}}, data)

process.addAsyncListener(al);

Page 81: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Cause Of Short Stack Tracesfunction foo { bar(); } function bar { baz(); } function baz { process.nextTick(bonk); }

Page 82: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Cause Of Short Stack Tracesfunction foo { bar(); } function bar { baz(); } function baz { process.nextTick(bonk); }

Stack:bazbarfoo

Page 83: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Cause Of Short Stack Tracesfunction foo { bar(); } function bar { baz(); } function baz { process.nextTick(bonk); }

Stack:

Page 84: Node.js: Continuation-Local-Storage and the Magic of AsyncListener

Cause Of Short Stack Tracesfunction foo { bar(); } function bar { baz(); } function baz { process.nextTick(bonk);}

Stack:— tick — bonk