My trip to the BACK SIDE - EN

Post on 18-Feb-2017

227 Views

Category:

Internet

0 Downloads

Preview:

Click to see full reader

Transcript

My trip to the

BACK SIDE

@LemaNahuel

I press keys on javascript

That is FWTV?

FWTV is the first Latin WebTV channel with designed exclusively for the Internet, where viewers can interact with programs offered both live and on-demand, anytime, anywhere content.

https://www.fwtv.tv/

@Obaca2015 Me

A long time ago ... (2013-2014)

fwtv.tv

fwtv.tv/api/*

fwtv.tv/admin/*

Heroku with 4 dynos

● Monolithic project● +150 endpoints and growing● callback hell● Frozen dependencies (Node .08, Mongoose,

Redis, Express, etc)

● No Documentation or Tests● Filesystem is no more scalable● Very long Files (average from 1000 to 2500 lines)● ...

... ART* 500 ms - 1000 ms or more … ¬¬

*Average Response Time

Preparing the ground for v2

Se we start with the obvious steps

api.

.tv

admin.

Like miners we start to separate the projects

● Tests implementation● Added documentación● Extreme implementation of EsLint and jsHint● Updated dependencies and Node to 0.12● Restructured the entire filesystem

○ routes/(public || private)/*.js

○ controllers/(public || private)/*.js

● Replaced all long files with small modules● ...

… ART meeh … ¬¬

… but now scale better!

v2.0 > v2.1 - Go functional!

Standardize the use of lodash● aprox 100 implementations of _.each()● more methods implemented as _.pluck(), _.

indexOf(), _.find(), _.without(), _.omit(), etc.

_.each(req.body.genres, (genre) => {

if (_.isObject(genre)) {

newGenres.push(genre._id);

} else {

newGenres.push(genre);

}

});

var genres = req.body.genres,

genre = null,

i = 0,

l = genres && genres.length ? genres.length : 0;

for (i; i < l; i++) {

genre = genres[i];

if (typeof genre === 'object') {

newGenres.push(genre._id);

} else {

newGenres.push(genre);

}

}

For example:_.each()

… ART with no improvements …

… but the code is more readable!

v2.1 > v2.2 - only what you need

Implementation of “Extender”● Now you can specify which fields are required and

which are not.● Used especially for translations and data uses only

the API or the admin.

For example:

function getTranslations() { if (LOCALE.indexOf('es') === -1) { return ',translations(' +LOCALE + '(description))'; }

return '';}

var fields = '?fields='; fields += 'landing'; fields += ',popularShows(_id,logo,slug,title,description,slogan' + getTranslations() + ')';

return $http.get(PATHS.HOME + fields);

Query strings added to Page and Limit● Skip● Limit● LastId

For example:

return $http.get(PATHS.SHOWS, {

params: {

limit: params.limit || 12,

lastId: params.lastId,

skip: params.skip

}

});

… ART barely noticeable …

… but the weight is lowered 100 kb - 250 kb to 1b-30 kb!

v2.2 > v2.3 - the real meaning of parallel

ASYNC● _.each() >

○ async.each()● callback hell >

○ async.waterfall() ○ async.parallel()

For example:async.waterfall()

getHome(function(home) { setPopularAndLiveShows(home, function(popularShows, liveNow, newNow){ home.popularShows = popularShows; home.liveNow = liveNow; home.newNow = newNow;

setFeaturesVideos(home, function(featuredVideos){ home.featuredVideos = featuredVideos; helpers.handleResponse(res, null, home); }); });});

async.waterfall([function(cb) { getHome(function(home) { cb(null, home); });}, function(cb) { setPopularAndLiveShows(home, function(popularShows, liveNow, newNow) { home.popularShows = popularShows; home.liveNow = liveNow; home.newNow = newNow; cb(null, home); });}, function(home, cb) { setFeaturesVideos(home, function(featuredVideos){ home.featuredVideos = featuredVideos; cb(null, home); });}], function(err, home) { helpers.handleResponse(res, null, home);});

For example:async.parallel()

async.parallel([function(cb) { setPopularVideos(home, likedProgramIds, function(popularVideos) { home.popularVideos = popularVideos; cb(null); }); }, function(cb) { setGenres(home, function(genres) { if(genres) { home.genres = genres; } cb(null, home); });}], function(err) { cb(null, home);});

For example:async.each()

async.each(shows, function(show, cb) { ScheduledPromo.count({

program: show._id }).exec(function(err, scheduleds) { Program.findByIdAndUpdate(show._id, { $set: { 'hasElements.scheduled': !!scheduleds } }).exec(function(err) { cb(err); }); }); }, function(err) { helpers.handleResponse(res, null, { success: !err });});

… ART incredible striking …

… it is now 500 ms - 1000 ms to 150 ms - 300 ms!

v2.2 > v3 - Sh*t just got real

Upgrade to Node 4.* and npm dependencies

… ART still the same …

… but consumption of RAM of Heroku decreased significantly!

v3 > v3.1 - sugar Marty ... syntaxis sugar everywhere

Upgrade to Node 5.* and npm dependencies● Minimum implementation of ES6

○ EsLint + ES6

○ const y let

○ arrow functions

○ classes

… ART unimproved and falling consumption of RAM ...

… but the code is much more readable

The journey so far● All up-to-date (Node && NPM)● Functional programming by lodash● Asynchronism, parallelism and callback hell resolved with

async

● ES6 minimally implemented● ART between 100 ms - 300 ms

still something missing …

… review all the access to MongoDB

v3.1 > v3.2 - the final fight!

The scenario:● Made with Mongoose● ~400 queries● most carried out by the front (.tv) and the

apps

I’m Mr. Meeseeks!! Look at me!

Step 1:● +400 queries 1 by 1:

○ .find() >

■ .findById() || .findOne()

○ .update() >

■ .findByIdAndUpdate() || .findOneAndUpdate()

○ .remove() >

■ .findByIdAndRemove() || .findOneAndRemove()

For example:.find() > .findById()

Model.find({ _id: req.params.id }).populate('video grouping program genres').exec((err, doc) => { helpers.handleResponse(res, err, doc);});

Model.update({ _id: req.params.id }, {}).exec((err, doc) => { helpers.handleResponse(res, err, doc);});

Model.remove({ _id: req.params.id }).exec((err, doc) => { helpers.handleResponse(res, err, doc);});

Model.findById(req.params.id) .populate('video grouping program genres') .exec((err, doc) => { helpers.handleResponse(res, err, doc); });

Model.findByIdAndUpdate(req.params.id, {}, { new: true }) .exec((err, doc) => { helpers.handleResponse(res, err, doc); });

Model.findByIdAndRemove(req.params.id) .exec((err, doc) => { helpers.handleResponse(res, err, doc); });

Step 2:● Implemented .lean() method on all .

find*() before .exec()

For example:.lean()

Model.findById(req.params.id) .populate('video grouping program genres') .exec((err, doc) => { helpers.handleResponse(res, err, doc); });

Model.findByIdAndUpdate(req.params.id, {}, { new: true }) .exec((err, doc) => { helpers.handleResponse(res, err, doc); });

Model.findByIdAndRemove(req.params.id) .exec((err, doc) => { helpers.handleResponse(res, err, doc); });

Model.findById(req.params.id) .populate('video grouping program genres') .lean().exec((err, doc) => { helpers.handleResponse(res, err, doc); });

Model.findByIdAndUpdate(req.params.id, {}, { new: true }) .lean().exec((err, doc) => { helpers.handleResponse(res, err, doc); });

Model.findByIdAndRemove(req.params.id) .lean().exec((err, doc) => { helpers.handleResponse(res, err, doc); });

ok, and now ...

… the ART down to 5 ms - 35 ms!! MOTHERF*CKAAA!!!!

Sum-up

● Functional, clean and less code● Lightweight requests● Low consumption of RAM● Efficient queries● Same amount of dynos, greater capacity● Weekly NCU

Questions?

How many Mr. Meeseeks did you find?- 9- 13- 15

Thanks!

@LemaNahuel

top related