【Node.js】そのNode.jsの認識ちょっと違うんじゃないの?って思って調べて考えてみた【JavaScript】


エッセンス:Node.jsの本当の姿 はこっちではというポイントを、整理してみました。

一緒に考えてみませんか?

よく見る Node.jsの説明にはこう特徴が書いてあるけど

  • サーバサイドJS
  • シングルスレッド
  • ノンブロッキングI/O
  • 大量に 省メモリでさばける
PC
PC
PC
PC
Node.js サーバ

この時点で 大小様々な??が頭に浮かんだ。

せっかく色々なページで説明してくれているけれども、
核心を突くのではなく周辺の説明ばかりで、
肝心なポイントが説明されていないので
イマイチ理解が深まらなかった。 

まず先に、どうでもいいかもまたはあまり本質と関係ないと思われる
良く主張されているポイントを整理すると。

主張その1:「長所:通常は待ち時間が発生するため、
ページの表示が遅くなるが~~」

ノンブロッキングだから早いと説明しようとしているが

ノンブロッキングしたってページを返す処理時間は変わらない 

(スレッドだろうと、マルチプロセスだろうとこれは同じこと
  サーバの話と、クライアントの話がごっちゃになってる?
  読んだ人が Node.jsの動作なのか混乱して伝達されたのかな?)

⇛ブロックするかしないかで向上するポイントは、
単位時間あたりの性能の話。
むしろ非同期のほうが1リクエストあたりは逆に処理自体遅くる。
あとで説明するけれど接続にたいしてのノンブロッキング方式は
サーバ/クライアント間で 今は実現不可能(今は調整中)。

主張その2:「シングルスレッドだから早い」

⇛node.jsでなく、JavaScriptの話をしているのか?

 シングルだと同時にさばけないと思うし
同時に裁かないという前提はありえないだろうし。

何がどう早くなるという意味だろうか?
理論がわからん。

主張その3:「シングルだからメモリ使用量が抑えられる。」

⇛(まちがってないけど実装によるし、)それだと逐次処理していることになる。
さっきと同じように
それだと一番の目的である
”大量に同時にさばく” ことができていないでしょ。 

これらをまとめると

結局シングルでどう大量にさばいているのかが不明だった。

10サイトほど見てもみんな同じポイントしか説明されておらず、なかなかわからない。

再度、やはり気になったので後日仕組みを考えてみた。

node.jsの設計を想像する

まず、今の時点でわかる正しいポイントや事実は

おそらくどこかに"シングル"の話と"ノンブロッキング"の話はあるのだろう。
Node.jsが大量に1万件さばき、メモリ使用量も抑えているのは実験データから事実。

モデルケースとして、Google Map のような Ajaxのケースを考える

多数のリクエスト を 地域のエリアの区画ごとに画像を要求するケースで
ノンブロッキング方式を考える

まず考えた設計方針:
サーバからクライアントへ
「コールバックするのであれば処理が終わったら連絡くれていいよね。
受け付けたらすぐクライアントへ返答して欲しい。待つ必要無いから 。
だからノンブロッキング 。」この設計の筋はよいと思う。

どこに何を応答するのか、は配列のような形で覚えておける。
JMSのようなメッセージングキューのイメージ 。
しかも複雑な処理は必要ない 。
大量、多様だけれどもデータを返すだけ。

ただ、そもそも根本となる問題がある

具体的に考えてみると、さっき考えた設計の筋では問題がある。
それはそもそもWebサーバは、サーバからクライアントへメッセージ送信できない
リクエストに対するレスポンスとしてなら返せるが。
(たとえIPアドレスを知っていても一般の家庭などには高確率で返せない)
クライアント/サーバ間でのブラウザによるノンブロッキングに無理がある。

やはり「リクエストはノンブロッキングせずサーバ側で保持しっぱなし」で別の案を考える

次に考えた方針:
リクエストを受けたら、返さずに待たせ、内部に処理させる。
終わったら、待たせてバインディングしていたコネクション経由で返す。
「ノンブロッキング」が指していたものは
ブラウザ等のクライアントとの通信側ではなく、
サーバ内部の分散処理側では?

ここに、ちょっとそれを匂わせる記述があった。
http://www.slideshare.net/forest1040/nodejs-7658878
24ページ

今言った方式の方向性はあっているようだけども
でもこの方式って劇的な新しさを感じない

その方式はApacheでもやっている普通のマルチスレッドライクと変わらないような
シングルスレッドなら今までと変わらないような・・・

Nodeで単純な処理しかできないから、メモリを使わない前提で作られている。
重い処理だと Apacheと一緒のはず 。
Apacheライクな処理ならば、これはマルチスレッド方式。

(うーん最初に、やや戻ってきた感があるが、、、 そうか!)

こんな感じ!っぽい

肝心なのは、ガンガン引き受けまくって、
省メモリで済むデータのやり取り
(ほぼどのリクエストも同じシンプルな処理のケース)
一プロセス上で、配列やハッシュを使ってリクエスト管理処理しているのではないか?

例えば、リクエストを引き受けたあとの処理のサイクルを早めるか
リクエストが有れば引き受けて、なくなったら処理して随時返す。
先に全リクエストを処理する前に引き受け続けると破綻するから、
必ず一つ以上処理している状態を Node.jsで管理しておく
(リソースを限界まで使うため、OSで扱うプロセスをまたがない)
そういう工夫がNode.jsにはあるのだと思う。

つまり Apache Webサーバの省エネ版
OSのプロセス起動やスイッチングコストや
OSのプロセス間通信のコストを0に近づける。
(複雑な処理の場合、それが限界性能になる)

TCPコネクションが来るたびに、
プロセスを立ちあげない(立ち上げっぱなしの)
なんらかの工夫があるはず 。

根拠や処理内容を探してみると
どうやらlibev で Cで作られている 。
それと libeioで 非同期並行処理している らしい。

これらを考えるに
これまでのマルチThreadよりもっと省エネな仕組みという考え方は正しそう。
つまりプロセスよりもスレッドよりも、マシン語に近いレベルで、
ソケットと処理通信の連携を管理しているはずだ。

ただ、それだと柔軟なコネクション管理やリクエスト処理はできない。
それに長時間処理するとタイムアウトエラーの発生頻度が上がりやすいのはデメリットとして残る。

ただし、1個しか処理できないから、同時接続数(メモリ使用量)も食わない 。
また、サーバプログラムをJSの文法や仕組みにすることで、拡張や切り替え(デプロイ)がし易い メリットも有る。

もう一点、大量にさばくのか瞬間的にさばいているのか、ここらへんがまだ曖昧。。。

一旦今の想定を整理してみる。

処理 はどうなっているか

先ほどのサイトのスライドのソースらしきもの見ながら、考えや流れ整理してみた。
-------------------------------

1. tcp受け付ける
    新規接続は タスク処理登録
    タスク処理中

2.受付た分ループ
  (a)1回目 データ受取 listenerから受信
         サーバ内のworkerクライアントに処理させる ←ここがノンブロッキング
                or
     (b) 2回目 データ処理 処理しているworkerクライアントから受信
              ↑おそらく、ここが jsを作って処理させバッファに書き込んでいるクライアント処理と推測する
         bufferに処理分(リクエスト処理結果)を貯めこむ
         終わったらソケットクローズ

また1~を無限ループ

-------------------------------

つまり、TCPのソケット通信をcで作られたバイナリ(プロセス内の独自コード)内の各ノードの単位で各リクエストの処理している。

シングルスレッドではない!!
大量にさばくため、ノードに分けて分散処理していた!

node.js のソースぐらい読んでおきたい! - by edvakf in hatena - http://goo.gl/iTgHF5

Webを探してみると、
Node.js のユーザページも見つけたし、(今更。。)
ソースを解読している人がいた。

まず、設計観点について経緯からまとめると

Node.js 日本ユーザグループの人の仕組みの説明にちゃんとあった。
(シングルスレッドではないということが)
http://d.hatena.ne.jp/badatmath/20101020/1287587240

Node.js開発経緯の課題は、C10Kという性能課題

そもそも、Nodeが求められた経緯として
スレッドで処理するか
イベントループで処理するか  の2項対立の課題があった。

これは手段論だが、本質の目的論で書き直すと

要するに、大量に捌(さば)く
(Apacheなどの現在の方法:スイッチングコストが高くトータル処理が少し落ちる )

メモリを抑え瞬間瞬間にさばくか
(昔の当初のシングルの方法:ブロックされると全部に遅延が伝播する )

ブロックする方法は、待ち行列理論などで、
このデメリットが大きいし、遅延が拡大することがわかってる。

という2つの対立項がある。
どっちも一長一短で、もっと欲張りにやりたい。

イベントループ方式のLeanさでスイッチングコストを抑えたまま
メモリ使用量を抑え、 ブロックしないようにできないか

まず、現状の問題を具体的にみてみると

ブロックとは、アーキテクチャでみると CPUと通信やディスクI/Oなどと処理するもの。 これが重い。
(ネットワークやDISKなどもその対象
 何百万倍も遅いからね
 20~200msec 対 数/Gsec レベルぐらい違いがある)

ネットワークの歴史的にはノンブロッキングするために、
マルチプロセスやマルチスレッドがそもそも考えられている

ノンブロッキングI/Oを実現するのは結構大変。しかも遅かった。

ここまでが、Nodeの前の課題の状況。

話を元に戻し、スレッドよりも省エネの方法でソケット通信を効率化しようとしていた経緯を読んで推測してみる。

その悩みを抱えていたところ、ライブラリで今までのようにやったのでは処理プログラムでメモリも必要だし、切り替えコストもかかった。かといって自前でソケット通信を低レベルなマシン語レベルで書くのはしんどい。。。OSやスレッドライブラリがやっているような複雑で柔軟性のある処理は難しい。

そこになんと
幸運なことに、子プロセスに処理任せてポーリングできるようなepoll という高速ライブラリが合った!
しかも面倒なOSの差分を吸収してくれるlibeventもある !!(memcachedで使用)
さらに、その安定版の libev があり
セットでlibeio という ノンブロッキングI/Oの仕組みを作りやすくするライブラリもあった!!!

実際の処理部分の結合を毎回 Cで作るのも、柔軟性がない。
なんとその課題に対しても、
仮にJSバインディングができれば、CのようにOSに依存せず色々な処理内容をくっつけられる。
Web処理ごとやOSごとのNode.js全体を毎回まるっと別タイプで
作りこみなおす必要もなくなる。
ソケット通信と jsの処理を結び付けられたりすれば、拡張性も高い。
その技術を chrome開発していた googleは持っていた !それがV8!

これで、機能的レイヤーで
TCP受付から、分散処理、JavaScript の連携はできるようになった。

おそらく Googleはv8のカスタマイズ版 を利用して、
JavaScriptをサーバ用途に処理し、ローカルリソースを扱えるようにしているはず。
(JavaScriptはクライアントサイドの技術だしそういう機能しかない)

このv8が tcp のnodeに分けて処理しているサーバと、
JavaScriptの独自処理との間を仲立ちしている。
普通のソケット通信のマルチプロセス/スレッド方式とNode.jsの方式は本質的には変わらない。

ちなみに、libeio上でスレッドプールを使用している。

構成を図示してみると

Node.jsとは
(下図の左側の)サーバ入り口でソケットを受け付けて、
worker経由で JavaScriptのバイナリ(コンパイル済みコード)が、それぞれ処理している。

サーバ側のところでイベントループをし、
終わったかどうか(コールバック?か、バッファリスニングで)確認しながら処理している

PC:clientinternetサーバworker JavaScript
PC:clientworker JavaScript
PC:clientworker JavaScript

補足:Node.jsでやると柔軟性があるといっても、Webサーバ処理自体はJavaScript部分について
自前でかなりゴテゴテと処理を(headerやhttpのURL切り分けから)書かないといけない。

まとめ

Node.js は javascriptを指しているわけではなく
tcpのソケット通信部分と Node分散処理と、
ノンブロッキングI/Oや JavaScript解析用のV8カスタマイズ版で
できている。
それらはc++などで実装されている。

Node.jsは 引き受けた処理を node単位に分散処理させて、
それぞれのバッファキューで処理を管理している。

V8エンジンという JavaScriptをコンパイルしたバイナリで、
workerとなっている nodeに結果をわたしている。

分散処理として、Node.jsはイベントループ方式をとっている(シングル方式ではない)。

つまり、1プロセスレベルでループしながら
スレッドよりも、マシン語に近いレベルの処理でスイッチングコストやメモリを抑えている
本質的には Apache のprefork式と同じ。
タスクはもちろん切り替えている。

ネットワークコネクションを受け持っている部分はひとつっぽい。

処理の重さ

prefork (子プロセス版) >  worker (子プロセスの中のマルチプロセス版) > Node.js (イベントループによる簡易キュー処理)

ソケット通信からダイレクトにJSの処理部分につながっているので、
余計なアプリケーションレイヤー間のコストもないから省エネで速い。

でも密結合だから、サーバ側の通信以外の処理をまるっとJS側で頑張って書くしか無い。
つまり複雑なとくにバリエーションに富んだ処理をするのは大変。
→Node.jsのデメリット:複雑な処理は向かない。使う側の作りこみが大変。

起動時にメモリ上へJSも含めてネイティブバイナリにV8がコンパイルしているので、
変更する度プロセス立ち上げをし、各Webサーバ独自処理のJS部分を含めた読み直しは必要になる。

JSを使うことで、学習コストも低いし拡張性も保持してある。
→つまり、JSの使いやすさと、組み込むことでスピードの両立を実現したのがNode.js

重い複雑な切り分けも含んだ処理をJSにさせると
Apache Webサーバの方式と性能は近づくかもしれないですよ。
というかNode.jsはあまりにも素そのものなので作るの大変

Node.jsは「サーバ側で動作するJS」というよりも、
「web動作をJSライクに変更できる」方が語弊がないかも。

ちなみに、Node.jsではサーバ用途向けにしなければいけないので
ローカルファイルにアクセスするなど普通のJSには無いサーバ側の処理向けAPIが
幾つか用意されている。

おさらい

冒頭の、特徴4点について、おさらいしてみようと思う。

  • サーバサイドJS
  • シングルスレッド
  • ノンブロッキングI/O
  • 大量に 省メモリでさばける

サーバサイドJS→OK。違和感はなし。処理を受け付けた後の処理はまるまる書ける/書かないといけない。つまり似たような単純処理向け。JSのライブラリが使えるが、JSはクライアントブラウザ上の処理が目的なので、サーバやディスクリソース等に関するライブラリは多分充実しきれていないと思う。

シングルスレッド→ネットワークの切り口で見たサーバクライアント間でリクエストを受け付けるところは、シングルスレッド。プリミティブに実装しているため(JSもプリコンパイルしていると思う)、省コスト。その分複雑なリクエスト処理はできない。

ノンブロッキングI/O→サーバの話なのに、これだけ話しのポイントが違うが、サーバ内部で各ワーカーに処理を渡した時に、処理完了を待たない。ブラウザ側のクライアントは待たせていると思う。

大量に省メモリでさばける→2個上で話したとおり。一貫して処理部分を効率化した話。

つまり

Node.jsはシングルではないよ!
ノンブロッキング方式で分散処理させる方法を省コストで編み出したのが Node.jsだよ。
JSで簡単に作れて処理できそうであれば、ApacheよりもNode.jsってことになるかもよー。

感想

Node.jsって アップしちゃいけないファイルをコントロールするには向かないので、
Webサーバの後ろでpublicサーバのように動かすのが良さそう。
小さなファイルを大量にリクエストされた時の用途など(同じような処理でしょ?)。 

あとは簡単にWebサーバが建てられるので、
開発用途としてもちょっとしたことには便利な気がする。

meteorとの比較しておくとよいかもしれませんね。

Delicious にシェア
Digg にシェア
reddit にシェア
LinkedIn にシェア
LINEで送る
email this
Pocket

6,576 views.



2 thoughts on “【Node.js】そのNode.jsの認識ちょっと違うんじゃないの?って思って調べて考えてみた【JavaScript】

  1. 2017/09/19 at 00:38

    https://t.co/SBfayFdYl6
    Nodeの記事だけど、ノンブロッキングとリソース節約に関して同じ疑問

@tk5_21 にコメントする コメントをキャンセル

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です