node-v0.12の新機能について

45
Node-v0.12の新機能について IIJ 大津 繁樹 2014424東京Node学園12時限目 ちょっと早く フライイングで紹介 盛りだくさんよ。 どこまで話せるかな?

Upload: shigekiohtsu

Post on 20-Aug-2015

17.954 views

Category:

Internet


5 download

TRANSCRIPT

Page 1: Node-v0.12の新機能について

Node-v0.12の新機能について

IIJ 大津 繁樹 2014年4月24日

東京Node学園12時限目

ちょっと早く フライイングで紹介

盛りだくさんよ。 どこまで話せるかな?

Page 2: Node-v0.12の新機能について

自己紹介

• 株式会社 インターネットイニシアティブ(IIJ)

プロダクト本部アプリケーション開発部所属

• twitter: @jovi0608

• github: http://github.com/shigeki

• ブログ: 「ぼちぼち日記」 http://d.hatena.ne.jp/jovi0608/

• NodeやHTTP/2など新しいもの好き

Page 3: Node-v0.12の新機能について

本日の内容(*)

1. Node-v0.12の概要

2. ES6 /7対応 (Promise, Object.observe, WeakMap/WeakSet)

3. Streams3 (Stream1+2の復習も兼ねて)

4. 同期child_process, beforeExitイベント(process)

5. 新 vm モジュール (時間があれば)

6. tracing モジュール (時間があれば)

7. Clusterのラウンドロビンモード(時間があれば)

8. 高速化の話(時間があれば)

全体的な性能向上も大きな売り。ベンチ結果は、おそらくリリース時に公式な数値が出るので今回は出しません。

(* 2014/04/23時点での master branch が対象)

Page 4: Node-v0.12の新機能について

Nodeのこれまでの歩み

2007/10 libev公開

2008/05 libeio公開

2008/09 Google V8公開

2009/02 ryan dahl Node開発開始

2009/05 node-v0.0.1 リリース

2009/06 nodejs ML開始

2009/10 npm公開

2009/11 JSConf EU ry発表

2010/04 Herokuサポート

2010/08 node-v0.2 リリース

2010/11 Joyent管轄へ

2011/02 node-v0.4 リリース

2011/03 libuv 開発開始

2011/11 node-v0.6 リリース

2011/12 Windows Azureサポート

2012/01 管理者が isaacsに変更

2012/06 node-v0.8 リリース

2013/03 node-v0.10 リリース

2014/01 管理者が TJ Fontaineに変更

? node-v0.12 リリース

Page 5: Node-v0.12の新機能について

Node-v0.12概要

• Node-v0.12は、Node-v1.0の Release Candidateの位置付け。 まだリリースされていません!

• Node-v0.10から安定性と性能向上を中心に、派手な新機能の追加や大幅なAPI廃止・変更はありません(ただし仕様や挙動変更の部分は多々あります)。

• コアの中身は大幅に変更されてます。 • V8も大きくバージョンアップ 3.14→3.25 。それに伴い V8 APIが大幅に変更→以前のNativeモジュールは大きく書き換えが必要。

• libuv もいろいろ細かく変わっていますが、0.10ほどではないので…

Page 6: Node-v0.12の新機能について

Node の ES6/ES7対応

• Chrome M35 が何かと機能がてんこ盛り。

• そこに載っているV8 3.25 で ES6/ES7 の一部機能が先行的に default で有効に(*)

• Node も V8 3.25 にアップデート

• Promise/Object.observe/WeakMap/WeakSet が harmony オプションなしで利用が可能になった。

• 全部詳細説明すると時間が足りないので簡単に。

(* なぜかV8 3.26では要オプションに戻されてます)

Page 7: Node-v0.12の新機能について

NodeのES6系ブログエントリー 詳しくは、こっち読んで下さい。

1. Node.jsにPromiseが再びやって来た!

– http://d.hatena.ne.jp/jovi0608/20140319/1395199285

2. Object.observe()とNode.jsのイベントループの関係

– http://d.hatena.ne.jp/jovi0608/20140320/1395294325

3. Node-v0.12からデフォルトでES6の一部が使えるようになった(WeakMap解説編)

– http://d.hatena.ne.jp/jovi0608/20140418/1397789018

Page 8: Node-v0.12の新機能について

Promise

• Promise A+仕様をベースにES6で採用

• 非同期処理(成功時:OnFulfill, エラー時: OnRejected)を登録、実行

• then()やcatch()でメソッドチェーンの利用

• then() を持ったオブジェクト(thenable)からプロミスオブジェクトを生成

• Promise.all([p1, p2, …]) で全てのプロミスオブジェクトの成功を一括処理

Page 9: Node-v0.12の新機能について

Promies利用例 引数で指定したファイルの確認を Promise で処理するサンプル

var fs = require('fs'); var file = process.argv[2] || __filename; var promise = new Promise(function(onFulfilled, onRejected) { fs.stat(file, function(err, stat) { // return stat object when fulfilled err ? onRejected(err) : onFulfilled(stat); }); }); // 正常処理なら onFulfilled, エラーなら onRejected が実行される promise.then( function(stat) { console.log('Fulfilled:', file, stat); }, function(error) { console.log("Rejected:", error.message); } );

Page 10: Node-v0.12の新機能について

Promiseの利用例 こんなコールバック地獄が

fs.readdir(dir1, function(...) { // dir1中のファイルリストを 取得

fs.readFile(file1, function(...) { // その中のfile1を 読み込み

hash.update(data); // ファイルデータのハッシュ値を計算

fs.readdir(dir2, function(...) { // dir2中のファイルリストを 取得

fs.readFile(file2, function(...) { ... }); }); }); }); dir1 と dir2 のディレクトリ中のファイルハッシュの比較

Page 11: Node-v0.12の新機能について

Promiseの利用例 こんな風に変えられます

function compareDirHash(dir1, dir2) { return compareFilesPromise(dir1, dir2).then(function(files) { return Promise.all(files.map(function(file) { return compareHashPromise(dir1, dir2, file); })); }).then( function(x) { // 正常処理 }, function(err) { // all case of errors // エラー処理 } ); } compareDirHash('./dir1/', './dir2/');

dir1 と dir2 のディレクトリ中のファイルハッシュの比較

Array.mapで処理結果を配列にしてから Promise.all() を使うと便利

全部正常に完了した時の処理がまとめて書ける

エラー処理も扱いやすい

Page 12: Node-v0.12の新機能について

Object.observe Array.observe

• オブジェクトのプロパティや配列要素の変更を検知してコールバックを呼び出すことができる機能

• 通常JS実行の最後にコールバックが呼び出されるが、 同期的に扱うことも可能(Object.deliverChangeRecords )

• Notifyオブジェクトを使って独自のイベントの追加、呼び出しも可能

• Angular.JS2.0のデータバインディングで利用予定

(* ES7の機能です)

Page 13: Node-v0.12の新機能について

Object.observe 実行例

検知できるイベント(*)

Object.observe Array.observe

add add

update update

delete delete

setPrototype splice

reconfigure

preventExtension

var obj = {}; function callback(changeRecord) { console.log(changeRecord); } Object.observe(obj, callback); obj.a = 1; // プロパティ追加

$ node test.js [ { type: 'add', object: { a: 1 }, name: 'a' } ]

実行例

(* Notifierを使えば独自イベントも追加可能)

Page 14: Node-v0.12の新機能について

1:時刻更新

5:poll

始まり

終わり

4:run_prepare

setTimeout() setInterval()

I/Oコールバック

3:run_idle

Node-v0.12 イベントループ 一周(Tick)

kernel I/O epoll: Linux kqueue: MacOS/BSD event port: Solaris IOCP: Windows

2:run_timers 6:run_check

setImmeidate()

メインモジュールの読み込み・実行

process.nextTick() 再帰展開・実行

Object.observe() Callback実行

0: Load

(* コールバック中に Object.observeがあればJS実行の最後に起動されます。)

Page 15: Node-v0.12の新機能について

WeakMap/WeakSet

• オブジェクトのみをキーにしたハッシュテーブル。

• WeakMapは、キーと値を WeakSet はキーのみ登録

• 弱参照なのでキーがGCされるとエントリーが自動的に削除される。

• for とかで内部のエントリーを列挙したり、格納されているエントリー数を取得するようなことはできない。

• soft field と object cache という目的で仕様化された

• ブラウザでは、DOMのプロパティを勝手に拡張してフィールドを付け加えるのではなく、WeakMapを使う。

• (何故か?) IE11 で WeakMap が実装済

Page 16: Node-v0.12の新機能について

WeakMap利用例 (*)

WeakMapを使った Private変数の隠匿

var weakmap = new WeakMap(); module.exports = Hoge; function Hoge(size) { size = +size || 8; var rand = require('crypto').randomBytes(size); // thisをキーとしてプライベート変数をWeakMapに格納 weakmap.set(this, rand); }; Hoge.prototype.getRand = function() { // WeakMapからプライベート変数を取り出す return weakmap.get(this); };

(* 別にNode固有の使い方じゃありません)

インスタンスがGCされれば

勝手になくなるよ

Page 17: Node-v0.12の新機能について

Node.js v0.10 Stream2 のきほん

補習資料

http://www.slideshare.net/shigeki_ohtsu/stream2-kihon

せっかくの入学式だから復習を

Page 18: Node-v0.12の新機能について

Stream は大事なクラス events.EventEmitter

stream.Stream

node-v0.10

stream.Readable stream.Writable

http.outgoingMessage stream.Duplex

stream.Transform

net.Socket tls.CleartextStream その他crypto系クラス

crypto sign/verify

http.IncomingMessage

補習資料

Page 19: Node-v0.12の新機能について

Stream1 おさらい

Readable Stream

src

データの流れ

dataイベントからデータの読み込み

pause() による読み込み停止

resume() による読み込み再開

補習資料

Page 20: Node-v0.12の新機能について

Stream1 の問題 var server = http.createServer(function(req, res) { setTimeout(function() { var data = ''; req.on('data', function(chunk) { data += chunk; }); req.on('end', function() { console.log(data); }); res.writeHead(200); res.end(); }, 1000); }).listen(8080);

POSTデータをちゃんと取れる?

補習資料

Page 21: Node-v0.12の新機能について

Stream1 の問題

リスナ

Readable Stream

データ

Readable Stream が 'data' イベントを生成した時にリスナが存在しなければ、 データは失われることに注意してください

補習資料

Page 22: Node-v0.12の新機能について

Stream1 の問題 pause()→drain→resume()→pause()

閉め→開け→閉め→開け

開け→閉め→開け→閉め

不安定な流れに弱い

drain

drain

補習資料

Page 23: Node-v0.12の新機能について

Stream2とは?

内部タンク付きストリーム

ストリーム内部にデータバッファを持つことによって、 • データの取りこぼしをなくす • 不安定な流れに影響されにくくする • ただし上限値(highWaterMark: default 16kB)が決まっている • オブジェクト一つをストリームバッファとして扱える

objectMode をサポート • old mode(Stream1互換)に変更可

補習資料

Page 24: Node-v0.12の新機能について

ReadableStream のプレイヤー

ソース 実装者 消費者

データの源 ストリーム本体。

ソースからデータを読み込み内部バッファに蓄える

ストリームからデータを受け取り利用する人

push() read()

_read()

readable

補習資料

Page 25: Node-v0.12の新機能について

Stream2の問題点

• readable(pull型データ取得)と data(push型データ取得)の両方使いたい

• 一度 old-mode (Stream1互換)に行ったら戻れない

• httpモジュール内で もdata イベント使いたい

参考:http://www.joyent.com/blog/streams-in-node

socketがせっかく Stream2 なのにデータ取り出しが ondata関数呼び出しで、オーバヘッドになってる。

こうしたい

Page 26: Node-v0.12の新機能について

Streams3 Readable Streamの変更

• APIを変えない。挙動を変更。

• old mode(Stream1互換) と new mode (Stream2) の区別をなくす。

• flowing/non-flowingモードを flowing/pausedモードにして、どちらも行き来OK

• readable イベント時に data イベントも一緒に出す。

Page 27: Node-v0.12の新機能について

Stream2と3の違い

new mode

pipe()

readable イベント

old mode

dataイベント resume() pause()

non-flowing mode(初期)

flowing mode

一度行ったら戻れない

pipe(),data イベント,resume()

paused mode(初期)

flowing mode

readable イベント, pause()

相互に行き来OK

Streams2 Streams3

dataイベントも一緒に出す

Stream1と2のmix

Page 28: Node-v0.12の新機能について

Streams3 モードの行き来(その1) paused → flowing → paused (pauseの利用)

var rstream = require('fs').createReadStream(__filename);

rstream.on('readable', function() {

while((b = rstream.read(1)) !== null) {

console.log('read:', b.toString());

}

});

var bytesRead = 0;

rstream.on(‘data’, function(chunk) { // flowing mode へ

bytesRead += chunk.length;

console.log(bytesRead + ' bytes read');

});

rstream.pause(); // paused modeへ

読み込み進捗率を表示

1バイトずつ表示

readableイベントと dataイベントの両立が可能になった

Page 29: Node-v0.12の新機能について

Streams3 モードの行き来(その2) paused → flowing (resumeの利用)

var rstream = require('fs').createReadStream(__filename);

rstream.resume(); // flowing mode へ

// data listener がないのでデータが失われてるよ!

rstream.on('readable', function() {

console.log(rstream.read()); // 出力なし

});

rstream.on('end', function() {

console.log('stream end');

});

flowing mode では readできないよ

内部バッファを消費しないと end は発火しない

Page 30: Node-v0.12の新機能について

Streams3 モードの行き来(その3) paused → flowing (pipeの利用)

var rstream = require('fs').createReadStream(__filename);

rstream.on('readable', function() {

var b;

while((b = rstream.read(1)) !== null) {

console.log('read:', b.toString());

}

});

rstream.pipe(process.stdout); // flowing mode へ

その1の pipe版

Page 31: Node-v0.12の新機能について

Streams3 モードの行き来(その4) paused → flowing → pause (dataイベント削除+unpipeの利用)

var rstream = require('fs').createReadStream(__filename);

rstream.on('readable', function() {

var b;

while((b = rstream.read(1)) !== null) {

console.log('read:', b.toString());

}

});

rstream.pipe(process.stdout); // flowing mode へ

rstream.removeAllListeners(‘data’).unpipe(); // paused modeへ

dataイベントを全て除いて pipe も外したらやっと paused modeに変遷

Page 32: Node-v0.12の新機能について

その他 Stream の変更 Writable Stream のAPI拡張

• writable.cork() 書き込みを停止して溜める

• writable.uncork() 溜めるのやめる

• writable._writev(chunks, callback) 一気に書き出す

Page 33: Node-v0.12の新機能について

同期child_process

• 最近利用が普及してきたフロントエンド用タスク処理向けに、同期で子プロセス処理をしたいニーズに対応

• spawnSync, execFileSync, execSyncの三種類 • 返り値は、spawnSyncがオブジェクト、他は

stdout のBuffer

• 子プロセス処理中は、親プロセスはブロッキング状態(正確には別のイベントループを生成し、SIGCHLDを待つ)

• エラー発生やタイムアウト時はThrowされるので注意

Page 34: Node-v0.12の新機能について

execSync使用例 Node Shell

var execSync = require('child_process').execSync; var readline = require('readline'); var rl = readline.createInterface(process.stdin, process.stdout); rl.setPrompt('Node_Shell$ '); rl.prompt(); rl.on('line', function(cmd) { try { var ret = execSync(cmd.trim()); // 同期処理 console.log(ret + ''); } catch(e) { console.err(e.code); } rl.prompt(); });

Page 35: Node-v0.12の新機能について

beforeExitイベント新設

• process の exit オブジェクトは、JavaScriptの実

行はできるけど、イベントループが終了しているため、もうI/O処理はできない状態。

• プロセス終了間際に、ゴミ掃除やログの送信、他へのhookなどI/O処理をしたい。

• プロセス終了直前に、もう一度イベントループを回してJavaScript実行できる beforeExit イベントを process オブジェクトに新設。

Page 36: Node-v0.12の新機能について

beforeExit サンプル httpクライアントの一時ファイル掃除

var fs = require(‘fs’), http = require('http'); var tmpdir = './tmpdir/', hosts = ['localhost']; if (!fs.existsSync(tmpdir)) fs.mkdirSync(tmpdir); hosts.forEach(function(host) { http.get({hostname: host}).on('response', function(res) { var tmpf = tmpdir + host; res.pipe(fs.createWriteStream(tmpf)).on('finish', function() { console.log('Do something special to the tmp file.'); }); }); }); // Cleanup temporary files process.once('beforeExit', function() { fs.readdir(tmpdir, function(err, files) { files.forEach(function(file) { fs.unlink(tmpdir + file); }); }); });

プロセスが終了する前に一時ファイルを全部削除する。 リスナ登録を once にしておかないと、再帰で呼び出されるから注意です。

httpクライアントによる

コンテンツの取得と一時ファイルへの書き込み全部終わったら、イベントループが終了。

出ていく前に掃除しろよー

Page 37: Node-v0.12の新機能について

新vmモジュール

• vmモジュールとは、JavaScriptを実行するモジュール。 • Context やsandboxを指定でき、iframe 上のJavaScript実行のようなもの(でもセキュリティ的に完全分離してない)。

• 信頼できない外部のJSを実行・評価するような時に使う。(使用例:UglifyJS2とか)

• Node-v0.10までは色々問題があった。

Node-v0.10.26 vmモジュールマニュアルより

vm に渡された sandbox オブジェクトを global に単純に clone してたため次頁のような issue が残っていた。

Page 38: Node-v0.12の新機能について

旧vmモジュールのダメな例

var vm = require('vm'); var sandbox = Object.create({foo: 'hoge'}); var ret = vm.runInNewContext('foo;', sandbox); console.log(ret); // Reference Error

var vm = require('vm'); var sandbox = {foo: 'hoge'}; var ret = vm.runInNewContext('this;', sandbox); console.log(ret); // {} が返る

var vm = require('vm'); var code = "setTimeout(function(){foo = 1}, 2000);"; var sandbox = {foo: 'hoge', setTimeout: setTimeout}; var ret = vm.runInNewContext(code, sandbox); setInterval(function() { console.log(sandbox.foo); // 2秒後以降もhoge }, 500);

prototypeが継承されない

this が {} オブジェクトに

非同期の変更に追従しない

Page 39: Node-v0.12の新機能について

新vmモジュール

• node_contextfy モジュールをベースにコアに取り込み。

• 完全に V8::Context を分離。

• 先の不具合を解消。

• WatchDogを導入、timeout 指定も可能に。

var vm = require('vm'); var opts = {timeout: 100}; try { var ret = vm.runInNewContext('while(true){}', {}, opts); } catch(e) { console.log(e); }

Page 40: Node-v0.12の新機能について

tracing モジュール V8のGCイベントとHeap統計情報を取得できます。

その他 AsyncListener: AsyncWrapクラスに引っ掛けてオブジェクト生成、コールバック起動前後でリスナを起動させるもの。もともとDomainの仕組みに使いたかったが中断。素人は手を出しちゃダメ。

var v8 = require('tracing').v8; console.log('initial:', v8.getHeapStatistics()); v8.on('gc', function(before, after) { console.log('before:', before); console.log('after:', after); }); gc(); // run with --expose-gc option

type - mark-sweep-compact - scavenge

flags 0: None 2: ConstructRetainedObjectInfos 4: Force

timestamp ナノ秒

total_heap_size ヒープサイズ合計

total_heap_size_executable 実行可能なヒープサイズ合計

total_physical_size 物理サイズ合計

used_heap_size 利用中のヒープサイズ

heap_size_limit ヒープサイズ制限値

メモリリーク調査に使ってね

Page 41: Node-v0.12の新機能について

Cluster RoundRobin

• 従来のClusterは、複数のプロセスが同時に accept(2) してkernelが割り当てを決定。

• kernel側がコンテキストをなるべく変更しないよう最適化→処理するプロセスが偏る問題

• Node-v0.12では、親プロセスが、accept(2)して子供に順番に渡すRoundRobin方式をデフォルトに。(旧方式も選択可)

• cluster.schedulingPolicy にmodeを代入。

• Windowsでは問題があり、未実装。

Page 42: Node-v0.12の新機能について

Cluster Modeの比較・検証する var cluster = require('cluster'), http = require('http');

if (process.argv[2] === 'none') cluster.schedulingPolicy = cluster.SCHED_NONE;

var mode = cluster.schedulingPolicy === 1 ? 'None' : 'Round Robin';

var N = 4, results = {}, total = 0;

cluster.isMaster ? Master() : Worker();

process.on('result', function() {

for(var pid in results) {

console.log('pid:', pid, 'requests:' ,results[pid]);

}

});

function Master() { console.log('cluster.schedulingPolicy:', mode); for(var i = 0; i < N; i++) { cluster.fork().on('message', function(msg) { results[this.process.pid + ''] = msg; if (++total%1000 === 0) process.emit('result'); }); } }

function Worker() { var counter = 0; var server = http.createServer(function(req, res) { res.writeHead(200, {'content-type': 'text/plain'}); res.end('Hello World'); process.send(++counter); }).listen(8080, function() { console.log('Listening on pid:' + process.pid); });

Page 43: Node-v0.12の新機能について

結果 ab -n 1000 http://localhost:8080/

cluster.SCHED_NONE

$ node test.js none

cluster.schedulingPolicy: None

Listening on pid:1831

Listening on pid:1834

Listening on pid:1832

Listening on pid:1836

pid: 1831 requests: 211

pid: 1832 requests: 267

pid: 1834 requests: 260

pid: 1836 requests: 262

cluster.SCHED_RR $ node test.js cluster.schedulingPolicy: Round Robin Listening on pid:1819 Listening on pid:1822 Listening on pid:1820 Listening on pid:1824 pid: 1819 requests: 250 pid: 1820 requests: 250 pid: 1822 requests: 250 pid: 1824 requests: 250

(環境: Ubuntu14.04, kernel-3.13)

Page 44: Node-v0.12の新機能について

その他高速化ネタ

• http.ClientRequestの同時接続制限を廃止 • Buffer実装をslaballocator廃止、smalloc化でV8管理に変更、高速化を達成

• TLS性能の高速化 – Paypalベンチでは2倍程度(AES256-GCM-SHA384) – tlsのネイティブモジュール化、AES-NI対応などの恩恵

• VDSO(Virtual Dynamically linked Shared Objects)利用による時刻取得のシステムコールを削減(Linux)

• マルチコンテキスト化に伴うHeap利用の改善とpost GCのオーバヘッドを削減

参考: http://strongloop.com/strongblog/performance-node-js-v-0-12-whats-new/

Page 45: Node-v0.12の新機能について

Any Questions?

(special thanks いらすとや http://www.irasutoya.com/ )