Mi viaje al BACK SIDE

Post on 18-Feb-2017

450 views 1 download

Transcript of Mi viaje al BACK SIDE

Mi viaje al

BACK SIDE

@LemaNahuel

Pico teclas en javascript

Que es FWTV?

FWTV es el primer canal de WebTV de Hispanoamérica, con contenido pensado exclusivamente para Internet, donde los espectadores pueden interactuar con los programas que se ofrecen tanto en vivo como bajo demanda, en cualquier momento y desde cualquier lugar.

https://www.fwtv.tv/

@Obaca2015 Yo

Hace mucho tiempo atrás ... (2013-2014)

fwtv.tv

fwtv.tv/api/*

fwtv.tv/admin/*

Heroku con 4 dynos

● Proyecto Monolítico● Realizado a las chapas en unas pocas semanas● +150 endpoints y en continuo crecimiento● callback hell● Dependencias freezadas (Node .08, Mongoose,

Redis, Express, etc)

● Sin Documentación o Tests● Filesystem ya no escalable● Archivos kilométricos (promedio 1000 - 2500

líneas)● ...

... ART* 500 ms - 1000 ms o más … ¬¬

*Average Response Time

Preparando el terreno para la v2

Empezamos con los pasos obvios

y como mineros separamos los proyectos

api.

.tv

admin.

● Implementación de Tests● Agregada documentación● Extremo linteo con EsLint y jsHint● Actualizadas dependencias y Node 0.12● Se reestructuró todo el filesystem

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

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

● Desarmar archivos kilométricos haciendo pequeños módulos

● ...

… ART meeh … ¬¬

… pero ahora escala mejor!

v2.0 > v2.1 - Go functional!

Estandarizado el uso de lodash● aprox 100 implementaciones de _.each()● más métodos implementados como _.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);

}

}

Por ejemplo:_.each()

… ART sin mejoría …

… pero el código es más legible!

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

Implementación de “Extender”● Ahora se puede especificar cuales campos se

necesitan y cuáles no.● Utilizado especialmente para traducciones y datos

que solo usa la api o el admin.

Por ejemplo:

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);

Agregados query strings para Paginar y Limitar● Skip● Limit● LastId

Por ejemplo:

return $http.get(PATHS.SHOWS, {

params: {

limit: params.limit || 12,

lastId: params.lastId,

skip: params.skip

}

});

… ART apenas notable …

… pero se bajó el peso de 100 kb - 250 kb a 1b-30 kb!

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

ASYNC● _.each() >

○ async.each()● callback hell >

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

Por ejemplo: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);});

Por ejemplo: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);});

Por ejemplo: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 zarpadamente notable…

… bajo de 500 ms - 1000 ms a 150 ms - 300 ms!

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

Actualización a Node 4.* y dependencias npm

… ART igual …

… pero se redujo el consumo de RAM en Heroku significativamente!

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

Actualización a Node 5.* y dependencias npm● Refactor mínimo a ES6

○ EsLint + ES6

○ const y let

○ arrow functions

○ classes

… ART sin mejoras y continúa bajando el consumo de RAM ...

… pero el código es más limpio

El viaje hasta ahora● Todas las dependencias actualizadas (Node && NPM)● Programación funcional mediante lodash● Peticiones muy livianas● Asincronismo, paralelismo y callback hell resuelto con async● ES6 mínimamente implementado● ART entre 100 ms - 300 ms

aun así algo faltaba …

… revisar los accesos a MongoDB

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

● Mediante con Mongoose● ~400 queries a la base en toda la API.● la mayoría realizadas por el front y las apps

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

Paso 1:● +400 queries 1 x 1:

○ .find() >

■ .findById() || .findOne()

○ .update() >

■ .findByIdAndUpdate() || .findOneAndUpdate()

○ .remove() >

■ .findByIdAndRemove() || .findOneAndRemove()

Por ejemplo:.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, {}) .exec((err, doc) => { helpers.handleResponse(res, err, doc); });

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

Paso 2:● Implementar el método .lean() en todos

los .find*() antes del .exec()

Ejemplo:.lean()

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

Model.findByIdAndUpdate(req.params.id, {}) .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, {}) .lean().exec((err, doc) => { helpers.handleResponse(res, err, doc); });

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

ahora si viejo … a ver

… el ART bajó a 5 ms - 35 ms!! MOTHERF*CKAAA!!!!

Conclusiones

● Código más limpio, ordenado y funcional● Respuestas muy livianas● Bajo consumo de RAM● Queries eficientes● Misma cantidad de dynos, mayor capacidad● ncu semanales

Preguntas?

Cuantos Mr. Meeseeks encontraste?- 9- 13- 15

Gracias!

@LemaNahuel