Node.js活用のノウハウとモバイル対応のポイント
スピーカー2名体制で行われたセッションのテーマは「Node.js」「Mobile」「Global」。まず久富木隆一氏が登壇し、参加者に「Node.jsに触れたことがある人」と問いかけると、全体の約8割が挙手。関心の高いエンジニアが多数参加していることが実感できた。
JavaScript標準はECMAScriptだが、Webブラウザ上では方言が存在するため、互換性を維持するのが困難になっている。それに対し「Node.js」はGoogle ChromeのV8エンジン採用のサーバーサイドJavaScriptプラットフォームで、標準準拠度が非常に高く、方言を気にすることなくピュアなスクリプトを書いていける。さらにプログラミング言語のパフォーマンス比較を見てみると、V8上のJavaScriptはJITコンパイルされた上で実行され、Perl、Python、PHP、Rubyよりも約10倍速い。
Node.jsは、レポジトリから必要なモジュールを持ってくることで、高速かつ軽量なネットワークアプリケーションを簡単に構築することができる仕組みになっている。また、epollなどOSが提供しているスケジューリング機能を利用した、イベント駆動の非同期I/Oモデルにより、分散デバイス群に配置された、大量のデータを処理するリアルタイムアプリケーションからの多数の同時接続に対応できる。ただ、複雑なアルゴリズムをサーバー上で実行するような、CPUパワーを要求するタスクにはあまり向いていない(図1)。
グリーではNode.jsにおけるコード品質を保つための工夫をしている。例えば--strictという引数を与えて実行すると、コードの厳密なチェックが行われるようになり、潜在的なエラーを探すのに非常に役立つ。またソーシャルゲームの文脈で、クライアントとサーバーでコードを共用することが重要になってきている。そこでnode-browserifyというモジュールを使えば、サーバー側のモジュールをクライアントで使えるようにしてくれる。
Node.jsの欠点とされているのが、JavaScriptのコードを記述していくとき、callbackのネストが非常に深くなってしまうということだ。非同期フロー管理のためのライブラリであるnode-seqを用いれば、コードを簡潔にできる。
またJavaScriptで大規模なアプリケーションを書く場合、デバッグが困難になる。そこではlong stack traceという、非常に詳細なデバッグ情報を出してくれるモジュールも採用している。さらに、PHPでWebアプリケーションを作る際と同様にエラー情報をファイルとして見られるよう、カスタマイズしている。
またNode.js向けのコードは非同期処理を多く含み、その場合、処理を始めた場所とは別のフレームでコードが実行される。そのため単にtry、catchをソースに書いていても、エラー時の例外は別のフレームに飛んでいてキャッチできない。これに対し、非同期コードでも例外を簡単に処理できるように、独自開発のモジュールを追加している。
グリーはGitHubエンタープライズによる社内レポジトリを持っており、Node.jsに対するセキュリティパッチやバックポートなどを速やかに適用している。社内で作ったモジュールなどもGithubエンタープライズ上で、ローカルで管理している。
本セッションにおける第2のテーマは「モバイル対応」だ。Webブラウザ上のJavaScriptは分裂しているが、モバイルデバイスの状況について久富木氏は「エンジンが限られ、プレイヤーが限られているので状況が良好になっている」と見ている。大半のモバイルブラウザはHTML5の高度な機能をサポートしており、それを使えばコードベースがスリムになり、問題も生じにくい。
リアルタイムにユーザー、サーバー間の通信をするニーズに対し、HTML5で利用できるのはWebSocketだ。これをNode.js上で便利に使うことができるSocket.ioというモジュールがある。ただWebSocketのサポートを欠くプラットフォームも存在する。そこはSocket.ioがケアをしており、AJAXで代替可能だ。
しかし、Socket.ioをWebSocketではなくAJAXを使って実行した場合は、元々の目的であるリアルタイムのパフォーマンスが犠牲になってしまう。従って、WebSocketが利用できない場合は、WebSocketのインターフェースを保ちつつ、中身は独自にネイティブ実装している。
またモバイルネットワークの多くは低速、狭帯域で、特に海外では回線の品質が悪いところが多い。高レイテンシクライアントへのコンテンツダウンロード対策を考えなければならない。そこで使えるのが、HTML5のWeb Storageだ。これも、サポートしていないプラットフォームの場合、インターフェースを揃えて自前でネイティブ実装する。
またHTML5の機能がサポートされていても、容量に制限があるなど、アプリケーションの要件に合致しない場合もあり、その場合も独自実装を行う。久富木氏は「HTML5の弱点を補いながら使うのが一般的な解」と話す。
Node.jsをグローバル規模で活用するためのテクニック
3番目のテーマは「グローバル」。スピーカーはRobert J. Gould 氏に交代した。
Node.jsにはいろいろな特徴があるが、例えばグローバルで100台のNode.jsサーバーを動かす場合には、さまざまな考慮すべきポイントがある。Node.jsは、確かにI/Oと大量データ処理アプリケーションには強いが、シングルスレッドでCPUを使うタスクには弱い。そのためCPUリソースの節約が望ましく、できるだけ結果をキャッシュする必要がある。
HTMLテンプレートのレンダリングやコンテンツのローカライズは、サーバーのCPU資源を浪費する。そこで1つの戦略として、クライアントにそれらの処理を委譲している。クライアントがコンテンツをローカルに持つようになるため、WebSocketでJSONをやりとりするAPIがあれば足りる。
また単一Node.jsサーバーの作成は簡単だが、グローバル展開のためにスケールさせるにはどうすればいいのか。非同期I/Oによりスケールアップは可能で、インスタンス1つで数千ユーザーをホストできるが、グローバル展開ではパワーが足りない。そこでNode.jsのClusterモジュールを使うと、マルチコアCPUのコアをすべて使えるようになる。
さらに、複数サーバー上にスケールアウトするために、グリーではNode.jsのキャッシュ&プロセス間通信にRedisを採用している。Redisは単なるKVSではなく、MemCached並のパフォーマンスを保ちつつ、多様な機能を備える。基本KVS機能に加え、アトミックな計算や文字列編集といった操作が可能である。MySQLなどのSQLデータベースに向いていない配列データ、キューや集合の処理を簡単に高パフォーマンスを保ちつつ行え、さらにリアルタイム通知やランキングシステムも組み込みで用意されている。これら機能は全てソーシャルアプリケーションを作る際に定番な機能であるため、相性は非常に良い。一番重要なのはリアルタイムプッシュの仕組みであるPubSubで、非常に高速だ。ただ現状のRedisはファイルからのバックアップが不安定なので、最終的なマスターはまだMySQLに残している。
サーバー間バスでの通信を、Node.jsイベント通信(eventemitter)並に簡略化し、WebSocketとRedisのPubシステムを融合したグリーのシステム上でクライアントが相互通信する仕組みを構築している(図2)。
データベースには、独自のドメイン固有言語(DSL)によりアクセスする。DSLにより手書きSQLを排除し、文字列入力のミスなどの問題を未然に防ぐ。結果は透過的にキャッシュされ、一部はメモリ上にキャッシュして高パフォーマンスを実現する。レガシーシステムのRESTAPIも、今後上記のデータDSLに対応予定だ。
グリーではnode-seqを大きく拡張し、便利な機能を数多く追加している。APIを整理することで、非同期コードをシンプルに書けるようにしている。さらに今後、MySQL、MongoDB、Redisのバックエンドを同一APIで利用可能にしていく予定だ。
最後にGould氏は「今年はNode.jsの年になる。本格的に手がけてみたいのであれば、ぜひグリーで一緒にやりましょう」と呼びかけ、セッションを閉じた。
GREE Engineers' Blog
グリー株式会社
東京都港区六本木6-10-1 六本木ヒルズ森タワー