Aplikacje DIRT
w NodeJS
Plan
- Definicja DIRT
- Relatywność czasu w aplikacjach
- Hard real-time / Soft real-time
- Node.js z lotu ptaka
- Asynchroniczność jest wszędzie
- Asynchroniczność bez kaskad callbacków
- JavaScript wysokiej wydajności
- Pytania/dyskusja
DIRT
vs
standardowe podejście
CRUD
- Create
- Read
- Update
- Delete
ACID
- Atomicity
- Consistency
- Isolation
- Durability
Standardowe podejście
Teoria CAP (Brewera)
Każdy rozproszony system może zagwarantować tylko dwa z trzech następujących punktów:
- Spójność (Consistency)
- Dostępność (Availability)
- Odporność na podział (Partition tolerance)
BASE
(Basically Available, Soft state, Eventual consistency)
- Zasadnicza dostępność
- Stan systemu może się zmieniać w czasie nawet bez ingerencji z zewnątrz
- Opóźniona spójność
DIRT
Data Intensive Real Time
- Czas jest krytyczny
- Stare dane są bezwartościowe
- Nowe dane muszą być przetwarzane tak szybko jak to tylko możliwe
Przykłady aplikacji typu DIRT
- Serwer IM
- Streaming danych finansowych
- Telefonia
- Agregatory danych (monitoring)
- Awionika
Hard real-time
Firm real-time
Soft real-time
Funkcja zysku

Funkcja zysku

Funkcja zysku

Funkcja zysku



Hard real-time
Firm real-time
Soft real-time
Relatywność czasu w aplikacjach
Jeżeli dostęp do cache L1 zajmuje 1 sekundę to:
Bład przewidywania instrukcji warunkowych: 0:00:10
Odczyt z cache L2 : 0:00:14
Mutex lock/unlock : 0:00:50
Odczyt z pamięci RAM : 0:03:20
Kompresja ZIP 1kb danych : 1:40:00
Wysłanie 1kb przez sieć 1 Gbps : 5:33:20
Odczytanie losowe 4kb danych z SSD: 3 dni, 11:20:00
Jeżeli dostęp do cache L1 zajmuje 1 sekundę to:
Odczytanie sekwencyjne 1MB danych z dysku: 5 dni, 18:53:20
PING w obrębie jednej sieci : 11 dni, 13:46:40
Odczytanie sekwencyjnie 1MB danych z SSD : 23 dni, 3:33:20
Disk seek : 231 dni, 11:33:20
Odczytanie sekwencyjnie 1MB danych z dysku : 462 dni, 23:06:40
Wysłanie pakietu Kalifornia->Holadia->Kalifornia :
9 lat, 187 dni, 5:20:00
Proporcje z którymi trzeba się oswoić

Proporcje
L2 | RAM | Dysk | Sieć | |
L1 | 5 | 83 | 13.666.666 | 80.000.000 |
L2 | 18 | 2.928.571 | 17.142.857 | |
RAM | 164.000 | 960.000 | ||
Dysk | 6 |
Charakterystyka typowej aplikacji webowej
- Małe obciążenie CPU
- Dużo operacji I/O
- Dużo czekania (np na bazę danych)
Histogram

Node.js z lotu ptaka
Dojrzałość


Co jest pod maską?
- Silnik V8 od Google
- Pojedynczy proces
- Libuv
- Cały kod z założenia jest pisany tak, aby nie blokował I/O
- Możliwość wspóldzielenia kodu pomiędzy serwerem a klientem
Hello World 0
console.log("Hello World");
Hello World I
var fs = require("fs");
fs.readFile("example_log.txt", function (err, logData) {
if (err) {
throw err;
}
var text = logData.toString();
console.log(text);
});
Hello World II
var http = require("http");
var server = http.createServer(function (request, response) {
response.writeHead(200, {"Content-Type": "text/plain"});
response.end("Hello World\n");
});
server.listen(8000);
console.log("Server running at http://127.0.0.1:8000/");
Hello World III
var http = require("http");
var mysql = require("mysql");
var connection = mysql.createConnection({
user: "root",
password: "",
database: "db_name"
});
http.createServer(function (request, response) {
request.on("end", function () {
connection.query("SELECT * FROM your_table;", function (error, rows, fields) {
response.writeHead(200, { "Content-Type": "application/json" });
response.end(JSON.stringify(rows));
});
});
}).listen(8080);
Wydajność I
var i, a, b, c, max;
max = 1000000000;
var time = process.hrtime();
for (i = 0; i < max; i++) {
a = 1234 + 5678 + i;
b = 1234 * 5678 + i;
c = 1234 / 2 + i;
}
var diff = process.hrtime(time);
console.log((diff[0] * 1e9 + diff[1]) / 1e6);
<?php
$a = $b = $c = $i = null;
$max = 1000000000;
$start = microtime(true);
for ($i = 0; $i < $max; $i++) {
$a = 1234 + 5678 + $i;
$b = 1234 * 5678 + $i;
$c = 1234 / 2 + $i;
}
$end = microtime(true);
var_dump(($end - $start) * 1000);
Czy takie testy są sensowne?
PHP 5.6.6 (VC11 x64)
Node v0.12.0 (x64)
Wydajność I
Czy takie porównania mają sens?
Liczba iteracji
100
10,000
1,000,000
10,000,000
1,000,000,000
Node.js
0.008 ms
0.06 ms
1.5 ms
10 ms
943 ms
PHP
0.03 ms
1.44 ms
141 ms
1,429 ms
142,414 ms
Wydajność II
function fibonacci(n) {
if (n < 2)
return 1;
else
return fibonacci(n-2) + fibonacci(n-1);
}
var time = process.hrtime();
console.log(fibonacci(40));
var diff = process.hrtime(time);
console.log((diff[0] * 1e9 + diff[1]) / 1e6);
<?php
function fibonacci($n) {
if ($n < 2) {
return 1;
} else {
return fibonacci($n - 2) + fibonacci($n - 1);
}
}
$start = microtime(true);
echo fibonacci(40);
$end = microtime(true);
var_dump(($end - $start) * 1000);
Czy takie testy są sensowne?
PHP 5.6.6 (VC11 x64)
Node v0.12.0 (x64)
Wydajność II
Czy takie porównania mają sens?
Wartość n
10
30
40
45
Node.js
14 ms
26 ms
1,527 ms
16,782 ms
PHP
0.27 ms
473 ms
57,902 ms
640,945 ms
PHP / Node.js
x 0.019
x 18,2
x 37,9
x 38,2
Asynchroniczność jest wszędzie
Klasyczny kod
<?php
$result = query($sql)
doSomething($result);
echo "Next command";
"use strict";
query(sql, function(result) {
doSomething(result);
});
console.log("Next command");
Siła tkwi w tym, że w obrębie JS to jest naturalne
<?php
$result = query($sql)
doSomething($result);
// waste of time
echo "Next command";
"use strict";
query(sql, function(result) {
doSomething(result);
});
console.log("Next command");
Porównajmy z:

Gdzie szukamy zysku?
- O ile moglibyśmy zmniejszyć czas odpowiedzi na pojedyncze zapytanie zrównoleglając ten kod, który odpytuje zewnętrzne systemy?
- Wciąż obecne podejście mikrooptymalizacji pomimo używania wysokopoziomowych języków
- Kupujemy 100 cykli procesora równocześnie marnując 10 mln obok
Anatomia żądania
Obsłużenie zapytania
Odpytanie bazy danych
Przetworzenie wyniku
Przygotowanie odpowiedzi
Wysłanie
odpowiedzi
Jak to skalować?
Wielowątkowość
Wątek 1
Wątek 2
Wątek 3
Wątek 4
Musimy brać pod uwagę narzut ze zmianą kontekstu oraz zwiększony poziom skomplikowania kodu jaki to generuje
Wiele procesów równolegle
Proces 1
Proces 2
Proces 3
Proces 4
Musimy brać pod uwagę wysoki koszt pamięci
Pętla zdarzeń
Proces 1
System plików
Sieć
Baza danych
Inne
Pula wątków
Kolejka zdarzeń
Pętla zdarzeń
Asynchroniczność bez kaskad callbacków
Jak wygląda kod w js?
fs.readdir(source, function(err, files) {
if (err) {
console.log('Error finding files: ' + err)
} else {
files.forEach(function(filename, fileIndex) {
console.log(filename)
gm(source + filename).size(function(err, values) {
if (err) {
console.log('Error identifying file size: ' + err)
} else {
console.log(filename + ' : ' + values)
aspect = (values.width / values.height)
widths.forEach(function(width, widthIndex) {
height = Math.round(width / aspect)
console.log('resizing ' + filename + 'to ' + height + 'x' + height)
this.resize(width, height).write(destination + 'w' + width + '_' + filename, function(err) {
if (err) console.log('Error writing file: ' + err)
})
}.bind(this))
}
})
})
}
})
Rozwiązanie 1 - async
async.map(['file1','file2','file3'], fs.stat, function(err, results){
// results is now an array of stats for each file
});
async.filter(['file1','file2','file3'], fs.exists, function(results){
// results now equals an array of the existing files
});
async.parallel([
function(){ ... },
function(){ ... }
], callback);
async.series([
function(){ ... },
function(){ ... }
]);
Rozwiązanie 2 - Q
var Q = require("q");
var FS = require("fs");
function getFile(name) {
var deferred = Q.defer();
FS.readFile(name, "utf-8", function (error, text) {
if (error) {
deferred.reject(new Error(error));
} else {
deferred.resolve(text);
}
});
return deferred.promise;
}
var file = getFile("foo.txt");
file.then(function(data) {
console.log('File has been read:', data);
})
.fail(function(err) {
console.error('Error received:', err);
})
Rozwiązanie 2 - Q - lepiej
var FS = require("fs");
var Q = require("q");
Q.nfcall(FS.readFile, "foo.txt", "utf-8")
.then(function(data) {
console.log('File has been read:', data);
})
.fail(function(err) {
console.error('Error received:', err);
})
.done();
Rozwiązanie 2
Q - możliwości
Q.all([getFromDisk(), getFromCloud()]).spread(function (diskVal, cloudVal) {
assert(diskVal === cloudVal);
}).done();
promise.timeout(10000).then(
function (result) {
// will be called if the promise resolves normally
console.log(result);
},
function (err) {
// will be called if the promise is rejected, or the 10 second timeout occurs
console.log(err);
}
);
Rozwiązanie 3
EventEmitter
var EventEmitter = require("events").EventEmitter;
var door = new EventEmitter();
door.on("knockknock", function(guest) {
console.log(guest);
});
setInterval(function() {
door.emit("knockknock", "Boss");
}, 1000);
JavaScript wysokiej wydajności
TypedArrays
Benchmark.prototype.setup = function() {
var canvas = document.getElementById("canvas");
var canvasWidth = canvas.width;
var canvasHeight = canvas.height;
var ctx = canvas.getContext("2d");
var imageData = ctx.getImageData(0, 0, canvasWidth, canvasHeight);
var data = imageData.data;
var buf = new ArrayBuffer(imageData.data.length);
var buf8 = new Uint8ClampedArray(buf); //View for buf
var data32 = new Uint32Array(buf); //View for buf
};
Benchmark.prototype.teardown = function() {
ctx.putImageData(imageData, 0, 0);
};
Cluster mode
var cluster = require("cluster");
var http = require("http");
var numCPUs = require("os").cpus().length;
if (cluster.isMaster) {
// Fork workers.
for (var i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on("exit", function(worker, code, signal) {
console.log("worker " + worker.process.pid + " died");
});
} else {
// Workers can share any TCP connection
// In this case its a HTTP server
http.createServer(function(req, res) {
res.writeHead(200);
res.end("hello world\n");
}).listen(8000);
}
asm.js
function M(stdlib, foreign, heap) {
"use asm";
var i32 = new stdlib.Int32Array(heap);
var f64 = new stdlib.Float64Array(heap);
var imul = stdlib.Math.imul;
var truncate = foreign.truncate;
function foo() {
var a = 0; // a is signed
var b = 0.0; // b is double
a = i32[0]|0;
b = +f64[0];
return imul(a, truncate(b))|0;
}
return { foo: foo };
}
var module = M(window, {
truncate: function (x) {
return x | 0;
}
}, new ArrayBuffer(4 * 1024));
asm.js - C++
#include <stdio.h>
int fib(int x) {
if (x < 2) {
return 1;
} else {
return fib(x - 1) + fib(x - 2);
}
}
int main() {
int result = fib(45);
printf("Fib: %d\n", result);
return 1;
}
(Visual Studio 2013 z opcją /O2) - 5,288 ms
asm.js - konwersja
function __Z3fibi($x) {
$x = $x|0;
var $0 = 0, $1 = 0, $2 = 0, $3 = 0, $4 = 0, $5 = 0, $accumulator$tr$lcssa = 0,
$accumulator$tr1 = 0, $x$tr2 = 0, label = 0, sp = 0;
sp = STACKTOP;
$0 = ($x|0)<(2);
if ($0) {
$accumulator$tr$lcssa = 1;
} else {
$accumulator$tr1 = 1;$x$tr2 = $x;
while(1) {
$1 = (($x$tr2) + -1)|0;
$2 = (__Z3fibi($1)|0);
$3 = (($x$tr2) + -2)|0;
$4 = (($2) + ($accumulator$tr1))|0;
$5 = ($3|0)<(2);
if ($5) {
$accumulator$tr$lcssa = $4; break;
} else {
$accumulator$tr1 = $4;$x$tr2 = $3;
}
}
}
STACKTOP = sp;return ($accumulator$tr$lcssa|0);
}
16,050 ms
asm.js - ręczny kod
function asmCode(global, env, buffer) {
"use asm";
function fib(x) {
x = x|0;
if ((x >>> 0) < 2) return 1;
return ((fib((x-2)|0)|0) + (fib((x-1)|0)|0))|0;
}
return fib;
}
var fibModuleAsm = asmCode(window, {}, new ArrayBuffer(4 * 1024));
console.log(fibModuleAsm( 45 ));
8,695 ms
(C++ /O2) 5,288 ms
vs
(js) 17,831 ms
vs
(asm.js - automat) 16,050 ms
vs
(asm.js - ręczny kod) 8,695 ms
SIMD.js
var a = SIMD.float32x4(1.0, 2.0, 3.0, 4.0);
var b = SIMD.float32x4(5.0, 6.0, 7.0, 8.0);
var c = SIMD.float32x4.add(a,b);
function simdAverage(src, len) {
var sumx4 = SIMD.float32x4.splat(0.0);
var srcx4 = new Float32x4Array(src.buffer);
for (var i = 0, n = len/4; i < n; ++i) {
sumx4 = SIMD.float32x4.add(sumx4, srcx4.getAt(i));
}
return (sumx4.x + sumx4.y + sumx4.z + sumx4.w) / len;
}

Websockets
var server = http.createServer(function(request, response){});
var connections = [];
server.listen(8080, function(){});
var wsServer = new WebSocketServer({
httpServer: server,
});
wsServer.on("request", function(request) {
var connection = request.accept(null, request.origin);
connections.push(connection);
connection.send("Hello World!");
connection.on("message", function(message) {
parseMessage(message);
});
connection.on("close", function(connection) {
connections.forEach(function(connection) {
connection.send("Client disconnected");
});
});
});
GPU? Da się.
Baza wiedzy
Pytania/dyskusja
Dziękuję za uwagę!
DIRTy Application in NodeJs
By Artur Trzesiok
DIRTy Application in NodeJs
- 1,054