NodeJS in Production
William Bruno
Desenvolvedor NodeJS
http://wbruno.com.br/
http://github.com/wbruno
wbrunom@gmail.com
@wbrunom
use istanbul
use jshint
relatório de cobertura dos seus testes
análise estática de código, check de sintaxe
seja rápido
Seja extremamente rápido, quanto menos tempo de NodeJS sua
aplicação tiver, mais ela irá escalar.
Assíncrono
Single Thread
V8 usa no máximo 1.6GB de RAM por processo
Consome pouca RAM e pouca CPU
use cluster
if (cluster.isMaster) {
for (let i = 0; i < numCPUs; i++) {
let worker = cluster.fork();
worker.on('error', _onWorkerError);
}
} else { //… }
use Objetos Literais
let AccountController = {
login: function doLogin(request, response, next) {
//..
}
};
module.exports = AccountController;
require() é síncrono, cacheado e singleton
use gzip
app.use(compression());
não use módulos desnecessários
helmet
lodash/undescore
app.disable('etag');
app.disable('x-powered-by');
outros cabeçalhos no nginx
use programação funcional
[].filter();
[].map();
[].some();
[].every();
[].find();
[].reduce();
Monads, clousures, currying,
HOF, avoid side-effects, etc
use es6
Nativa desde o NodeJS 4.0

Esqueça Babel
'use strict';
let express = require('express');
use mozilla/nunjucks
Ótimo template engine
Mantido pela Fundação Mozilla
nomeie seus middlewares
app.get('/', function getHome(request, response, next) {
response.send('Home');
});
erros num único ponto
Trate os erros num único lugar
app.use(function handleError(err, request, response, next) {
response.status(err.status || 500);
//log
if (request.xhr) {
response.json({ err: err.message });
} else {
response.render('error', {
message: err.message
});
}
});
use debug
Não deixe console.log() perdidos no código
Tudo o que vai para o std output escreve no log
escreva log
winston [splunk, graylog]
quem, quando, o quê
use newrelic
SERVER

APM

BROWSER
use a lib bluebird
Mais rápida que a implementação nativa de Promise

Possui o método .promisifyAll()
use npm scripts
{
"name": "app",
"scripts": {
"gulp": "gulp; ./scripts.sh akamai; gulp s3",
"jshint": "jshint server/*",
"karma": "./scripts.sh karma",
"nodemon": "nodemon ./server/bin/www",
"patch": "npm version patch -m "release: version %s" && git push --tags && git push",
"start": "./scripts.sh start",
"test": "./scripts.sh test"
},
use npm scripts#!/bin/bash
BROWSER=${2:-"PhantomJS"}
case "$1" in
start)
echo 'Starting...'
export DEBUG=blz:*
bower install
npm-run-all --parallel gulp-watch karma
;;
test)
echo 'Testing backend...'
gulp eslint:nodejs
export DEBUG=blz:*
./node_modules/istanbul/lib/cli.js cover ./node_modules/mocha/bin/_mocha test/nodejs/*
;;
karma)
echo 'Testing frontend...'
gulp eslint:angular
gulp eslint:jquery
./node_modules/karma/bin/karma start --browsers $BROWSER ./test/angular/karma.conf.js
;;
akamai)
echo 'Updating akamai...'
export APPLICATION_VERSION=$(node -e "console.log(require('./package.json').version);")
mkdir -p _akamai/$APPLICATION_VERSION
cp -r dist/public/* _akamai/$APPLICATION_VERSION
chmod 400 akamai.key
scp -i akamai.key -o StrictHostKeyChecking=no -rp _akamai/$APPLICATION_VERSION user@..…upload.akamai.com:/dir/
;;
*)
echo "Usage: {start|akamai|test|karma}"
exit 1
;;
esac
use forever para prod
e nodemon para dev
use /etc/init.d/nodejs
ou /etc/init/nodejs
Unix Service ou Ubuntu upstart
https://github.com/wbruno/examples/tree/gh-pages/nodejs-in-production
https://github.com/wbruno/examples/tree/gh-pages/nodejs-in-production
#!/bin/sh
### BEGIN INIT INFO
# Provides: nodejs init script
# Required-Start: forever node module
# X-Interactive: true
# Short-Description: application initscript
# Description: Uses forever module to running the application
### END INIT INFO
NODE_ENV="{{ enviroment }}"
PORT="3002"
APP_DIR="/var/{{ application }}/dist/server"
NODE_APP="bin/www"
CONFIG_DIR="$APP_DIR/config"
LOG_DIR="/var/log/{{ application }}"
LOG_FILE="$LOG_DIR/app.log"
NODE_EXEC="forever"
###############
USAGE="Usage: $0 {start|stop|restart|status}"
start_app() {
mkdir -p "$LOG_DIR"
echo "Starting node app ..."
PORT="$PORT" NODE_ENV="$NODE_ENV" NODE_CONFIG_DIR="$CONFIG_DIR"
forever start "$APP_DIR/$NODE_APP" 1>"$LOG_FILE" 2>&1 &
}
stop_app() {
forever stop "$APP_DIR/$NODE_APP"
}
status_app() {
forever list
}
restart_app() {
forever restart "$APP_DIR/$NODE_APP"
}
case "$1" in
start)
start_app
;;
stop)
stop_app
;;
restart)
restart_app
;;
status)
status_app
;;
*)
echo $USAGE
exit 1
;;
esac
Unix service
https://github.com/wbruno/examples/tree/gh-pages/nodejs-in-production
start on filesystem and started networking
stop on shutdown
expect fork
setuid www-data
env HOME="/var/{{ application }}"
env NODE_ENV="{{ enviroment }}"
env MIN_UPTIME="5000"
env SPIN_SLEEP_TIME="2000"
chdir /var/{{ application }}/dist/server
env APP_EXEC="bin/www"
script
exec forever -a -l $HOME/forever.log --minUptime=$MIN_UPTIME --spinSleepTime=$SPIN_SLEEP_TIME start $APP_EXEC
end script
pre-stop script
exec forever stopall
end script
Ubuntu upstart
use o nginx-full
Ótimo web server
Ultra rápido
Assíncrono, não bloqueante
Super configurável
http2 (server push apenas no plus)
https://github.com/wbruno/examples/tree/gh-pages/nodejs-in-production
server {
listen 80;
server_name www.{{ domain }};
access_log /var/{{ application }}/www.{{ domain }}-access.log;
error_log /var/{{ application }}/www.{{ domain }}-error.log;
proxy_cache one;
root /var/{{ application }}/dist/public/;
error_page 400 404 414 500 502 503 504 /50x.html;
location /50x.html {
internal;
}
location /api {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_pass http://nodejs/api;
}
location ~* .(?:ico|css|html|json|js|gif|jpe?g|png|ttf|woff|woff2|svg|eot|txt)$ {
access_log off;
expires 14d;
add_header Pragma public;
add_header Cache-Control "public, mustrevalidate, proxy-revalidate";
root /var/{{ application }}/dist/public;
}
}
location / {
add_header X-Cache-Status $upstream_cache_status;
add_header Strict-Transport-Security
"max-age=1440; includeSubdomains";
expires 60s;
set $mobile ‘@desktop';
if ($http_user_agent ~* "...") {
set $mobile "@tablet";
}
if ($http_user_agent ~* "...") {
set $mobile "@smartphone";
}
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_ignore_headers Cache-Control;
proxy_pass http://nodejs;
proxy_cache_key "$mobile$scheme://$host$request_uri";
proxy_cache_bypass $cookie_nocache $arg_nocache;
proxy_cache_valid 1m;
proxy_cache_min_uses 1;
}
https://github.com/wbruno/examples/tree/gh-pages/nodejs-in-production
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 60;
send_timeout 60;
client_body_timeout 60;
client_header_timeout 60;
client_body_buffer_size 10K;
client_header_buffer_size 1k;
client_max_body_size 8m;
large_client_header_buffers 4 32k;
types_hash_max_size 2048;
server_tokens off;
server_names_hash_bucket_size 64;
default_type application/octet-stream;
log_format elb '$http_x_forwarded_for - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent"';
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
proxy_cache_path /tmp/cache keys_zone=one:10m loader_threshold=100 loader_files=100 loader_sleep=30 inactive=30m max_size=2g;
charset utf-8;
gzip on;
gzip_disable "msie6";
gzip_min_length 1;
gzip_types *;
gzip_http_version 1.1;
gzip_vary on;
gzip_comp_level 6;
gzip_proxied any;
gzip_buffers 16 8k;
include /etc/nginx/mime.types;
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
include /etc/nginx/upstreams.d/nodejs;
}
user www-data;
worker_processes auto;
pid /run/nginx.pid;
events {
worker_connections 1024;
multi_accept on;
use epoll;
}
Bibliografia
https://promisesaplus.com
https://blog.risingstack.com/node-js-best-practices/
https://www.sitepoint.com/10-tips-make-node-js-web-app-faster/
http://expressjs.com/en/advanced/best-practice-performance.html
https://www.packtpub.com/books/content/fine-tune-nginx-configufine-tune-
nginx-configurationfine-tune-nginx-configurationratio
https://www.nginx.com/blog/nginx-caching-guide/
https://github.com/wbruno/examples/tree/gh-pages/nodejs-in-production
Obrigado
Nodejs in Production

Nodejs in Production

  • 1.
  • 2.
  • 3.
    use istanbul use jshint relatóriode cobertura dos seus testes análise estática de código, check de sintaxe
  • 4.
    seja rápido Seja extremamenterápido, quanto menos tempo de NodeJS sua aplicação tiver, mais ela irá escalar. Assíncrono Single Thread V8 usa no máximo 1.6GB de RAM por processo Consome pouca RAM e pouca CPU
  • 5.
    use cluster if (cluster.isMaster){ for (let i = 0; i < numCPUs; i++) { let worker = cluster.fork(); worker.on('error', _onWorkerError); } } else { //… }
  • 6.
    use Objetos Literais letAccountController = { login: function doLogin(request, response, next) { //.. } }; module.exports = AccountController; require() é síncrono, cacheado e singleton
  • 7.
  • 8.
    não use módulosdesnecessários helmet lodash/undescore app.disable('etag'); app.disable('x-powered-by'); outros cabeçalhos no nginx
  • 9.
  • 10.
    use es6 Nativa desdeo NodeJS 4.0
 Esqueça Babel 'use strict'; let express = require('express');
  • 11.
    use mozilla/nunjucks Ótimo templateengine Mantido pela Fundação Mozilla
  • 12.
    nomeie seus middlewares app.get('/',function getHome(request, response, next) { response.send('Home'); });
  • 13.
    erros num únicoponto Trate os erros num único lugar app.use(function handleError(err, request, response, next) { response.status(err.status || 500); //log if (request.xhr) { response.json({ err: err.message }); } else { response.render('error', { message: err.message }); } });
  • 14.
    use debug Não deixeconsole.log() perdidos no código Tudo o que vai para o std output escreve no log
  • 15.
    escreva log winston [splunk,graylog] quem, quando, o quê
  • 16.
  • 17.
    use a libbluebird Mais rápida que a implementação nativa de Promise
 Possui o método .promisifyAll()
  • 18.
    use npm scripts { "name":"app", "scripts": { "gulp": "gulp; ./scripts.sh akamai; gulp s3", "jshint": "jshint server/*", "karma": "./scripts.sh karma", "nodemon": "nodemon ./server/bin/www", "patch": "npm version patch -m "release: version %s" && git push --tags && git push", "start": "./scripts.sh start", "test": "./scripts.sh test" },
  • 19.
    use npm scripts#!/bin/bash BROWSER=${2:-"PhantomJS"} case"$1" in start) echo 'Starting...' export DEBUG=blz:* bower install npm-run-all --parallel gulp-watch karma ;; test) echo 'Testing backend...' gulp eslint:nodejs export DEBUG=blz:* ./node_modules/istanbul/lib/cli.js cover ./node_modules/mocha/bin/_mocha test/nodejs/* ;; karma) echo 'Testing frontend...' gulp eslint:angular gulp eslint:jquery ./node_modules/karma/bin/karma start --browsers $BROWSER ./test/angular/karma.conf.js ;; akamai) echo 'Updating akamai...' export APPLICATION_VERSION=$(node -e "console.log(require('./package.json').version);") mkdir -p _akamai/$APPLICATION_VERSION cp -r dist/public/* _akamai/$APPLICATION_VERSION chmod 400 akamai.key scp -i akamai.key -o StrictHostKeyChecking=no -rp _akamai/$APPLICATION_VERSION user@..…upload.akamai.com:/dir/ ;; *) echo "Usage: {start|akamai|test|karma}" exit 1 ;; esac
  • 20.
    use forever paraprod e nodemon para dev
  • 21.
    use /etc/init.d/nodejs ou /etc/init/nodejs UnixService ou Ubuntu upstart https://github.com/wbruno/examples/tree/gh-pages/nodejs-in-production
  • 22.
    https://github.com/wbruno/examples/tree/gh-pages/nodejs-in-production #!/bin/sh ### BEGIN INITINFO # Provides: nodejs init script # Required-Start: forever node module # X-Interactive: true # Short-Description: application initscript # Description: Uses forever module to running the application ### END INIT INFO NODE_ENV="{{ enviroment }}" PORT="3002" APP_DIR="/var/{{ application }}/dist/server" NODE_APP="bin/www" CONFIG_DIR="$APP_DIR/config" LOG_DIR="/var/log/{{ application }}" LOG_FILE="$LOG_DIR/app.log" NODE_EXEC="forever" ############### USAGE="Usage: $0 {start|stop|restart|status}" start_app() { mkdir -p "$LOG_DIR" echo "Starting node app ..." PORT="$PORT" NODE_ENV="$NODE_ENV" NODE_CONFIG_DIR="$CONFIG_DIR" forever start "$APP_DIR/$NODE_APP" 1>"$LOG_FILE" 2>&1 & } stop_app() { forever stop "$APP_DIR/$NODE_APP" } status_app() { forever list } restart_app() { forever restart "$APP_DIR/$NODE_APP" } case "$1" in start) start_app ;; stop) stop_app ;; restart) restart_app ;; status) status_app ;; *) echo $USAGE exit 1 ;; esac Unix service
  • 23.
    https://github.com/wbruno/examples/tree/gh-pages/nodejs-in-production start on filesystemand started networking stop on shutdown expect fork setuid www-data env HOME="/var/{{ application }}" env NODE_ENV="{{ enviroment }}" env MIN_UPTIME="5000" env SPIN_SLEEP_TIME="2000" chdir /var/{{ application }}/dist/server env APP_EXEC="bin/www" script exec forever -a -l $HOME/forever.log --minUptime=$MIN_UPTIME --spinSleepTime=$SPIN_SLEEP_TIME start $APP_EXEC end script pre-stop script exec forever stopall end script Ubuntu upstart
  • 24.
    use o nginx-full Ótimoweb server Ultra rápido Assíncrono, não bloqueante Super configurável http2 (server push apenas no plus)
  • 25.
    https://github.com/wbruno/examples/tree/gh-pages/nodejs-in-production server { listen 80; server_namewww.{{ domain }}; access_log /var/{{ application }}/www.{{ domain }}-access.log; error_log /var/{{ application }}/www.{{ domain }}-error.log; proxy_cache one; root /var/{{ application }}/dist/public/; error_page 400 404 414 500 502 503 504 /50x.html; location /50x.html { internal; } location /api { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $host; proxy_pass http://nodejs/api; } location ~* .(?:ico|css|html|json|js|gif|jpe?g|png|ttf|woff|woff2|svg|eot|txt)$ { access_log off; expires 14d; add_header Pragma public; add_header Cache-Control "public, mustrevalidate, proxy-revalidate"; root /var/{{ application }}/dist/public; } } location / { add_header X-Cache-Status $upstream_cache_status; add_header Strict-Transport-Security "max-age=1440; includeSubdomains"; expires 60s; set $mobile ‘@desktop'; if ($http_user_agent ~* "...") { set $mobile "@tablet"; } if ($http_user_agent ~* "...") { set $mobile "@smartphone"; } proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $host; proxy_ignore_headers Cache-Control; proxy_pass http://nodejs; proxy_cache_key "$mobile$scheme://$host$request_uri"; proxy_cache_bypass $cookie_nocache $arg_nocache; proxy_cache_valid 1m; proxy_cache_min_uses 1; }
  • 26.
    https://github.com/wbruno/examples/tree/gh-pages/nodejs-in-production http { sendfile on; tcp_nopushon; tcp_nodelay on; keepalive_timeout 60; send_timeout 60; client_body_timeout 60; client_header_timeout 60; client_body_buffer_size 10K; client_header_buffer_size 1k; client_max_body_size 8m; large_client_header_buffers 4 32k; types_hash_max_size 2048; server_tokens off; server_names_hash_bucket_size 64; default_type application/octet-stream; log_format elb '$http_x_forwarded_for - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent"'; access_log /var/log/nginx/access.log; error_log /var/log/nginx/error.log; proxy_cache_path /tmp/cache keys_zone=one:10m loader_threshold=100 loader_files=100 loader_sleep=30 inactive=30m max_size=2g; charset utf-8; gzip on; gzip_disable "msie6"; gzip_min_length 1; gzip_types *; gzip_http_version 1.1; gzip_vary on; gzip_comp_level 6; gzip_proxied any; gzip_buffers 16 8k; include /etc/nginx/mime.types; include /etc/nginx/conf.d/*.conf; include /etc/nginx/sites-enabled/*; include /etc/nginx/upstreams.d/nodejs; } user www-data; worker_processes auto; pid /run/nginx.pid; events { worker_connections 1024; multi_accept on; use epoll; }
  • 27.
  • 28.