Node.js徹底攻略 ─ ヤフーのノウハウに学ぶ、パフォーマンス劣化やコールバック地獄との戦い方

Node.jsをうまく活用できている企業は、どのような方法でベストプラクティスを習得してきたのでしょうか。ヤフー株式会社でNode.jsの社内普及に務めてきた言語サポートチームに、同社の実施を紹介してもらいました。

Node.js徹底攻略 ─ ヤフーのノウハウに学ぶ、パフォーマンス劣化やコールバック地獄との戦い方

Node.jsは「イベントループモデルで、ノンブロッキングI/Oを使用している」「問題発生時にHTTP/TCPやPOSIX APIなど低レイヤーの知識を求められる」といった特徴を持つ言語です。開発者が習得すべき技術領域が広いため、Node.jsらしい書き方の学習難易度は高いと言えます。

それでは、Node.jsをうまく活用できている企業は、どのような方法でNode.jsのベストプラクティスを習得してきたのでしょうか。ヤフー株式会社でNode.jsの社内普及に務めてきた言語サポートチームの方々に「パフォーマンス向上のため」「コールバック地獄をなくすため」に、同社で実施してきた手法を紹介してもらいました。

1

伊藤 康太(いとう・こうた) 2koh110 3koh110(写真左)
ヤフー株式会社 システム統括本部 情報システム本部 リーダー
チャットシステムなど内製基盤の開発・運用・マネジメントを担当。Node.js言語サポートチームにも所属し、サーバサイドTypeScriptの活用や、SSR、BFFのチューニングを支援している。
大津 繁樹(おおつ・しげき) 4jovi0608 5shigeki(写真中央)
ヤフー株式会社 システム統括本部 サイトオペレーション本部
CDNチームでTLSを中心としたネットワークセキュリティ技術を担当する。Node.jsの言語サポートチームにも所属。Node.js Collaborator。第8代黒帯(ネットワーク・セキュリティ)。
ブログ「ぼちぼち日記
栗山 太希(くりやま・だいき) 6Ajido(写真右)
ヤフー株式会社 システム統括本部 セキュリティ&コアテクノロジー本部
リアルタイムコミュニケーション基盤の設計と開発を担当。言語サポートチームでは非同期処理の基礎と最先端の扱い方を学ぶ演習を開催し、効率的かつ効果的なサービス開発を促進している。第8代黒帯(Node.js)。

Node.jsを使いこなすには、プリミティブな知識を習得しよう

──Node.jsはどんな特徴を持った言語でしょうか?

大津 Node.jsを使用した開発では、コンピューターの低レイヤーな部分を扱わなければいけないケースがよくあります。コーディングをする際に「APIの内部でどんな処理が行われているか?」についてのプリミティブな知識が求められます。言語の持つ抽象度が低いと言いますか。フレームワークが整備されており、抽象度の高い開発ができる他の言語とは、その点が根本的に異なっています。

栗山 下の図は、ヤフー社内のハンズオンで受講者によく見せている「JavaScriptや関連ライブラリがどのような要素技術から成り立っているか」を表す資料です。

7

この図からも分かるように、Node.jsはJavaScriptに加えて、HTTP/HTTPSやTCP/IP、OSやPOSIXなどの基礎技術が組み合わさってできています。

大津 さらに、現時点で用いられているライブラリやフレームワークにもデファクトスタンダードと呼べるものが少ないです。

フレームワークの体系に合わせて開発するのではなく、自らの用途に合わせてライブラリ・フレームワークをアラカルト的に選んでいく開発スタイルになります。そのため、開発者自身が低レイヤーの基礎知識を持ち合わせていなければ、問題が起きたときの原因特定ができなくなってしまいます。

伊藤 さらには、シングルプロセス・シングルスレッドで、ノンブロッキングI/Oであるという特徴もあります。コーディングする際には、この特徴を最大限に生かすような実装をしていく必要があります。

※ なお、伊藤さん曰く「Node.js内部では、実際にはマルチスレッドを用いた処理が行われている。そのため正確には、表面的にはシングルプロセス・シングルスレッドの動作をしているという表現が適切」とのこと。

8

伊藤康太さん

〈パフォーマンス向上(1)〉
同期処理のAPIをなるべく使用しない

──具体的な実装テクニックとして「Node.jsの性能をフルに引き出すための方法」を教えてください。

栗山 初学者向けの内容として、私が必ず伝えていることは次の3つです。

  1. Clusterモジュールを使ってマルチプロセス化すること
  2. Sync(同期)という接尾辞がついたAPIを使わないこと
  3. JSON.parseの実行回数を減らすこと

これらに従うことが常に正しいとは限りません。ですが、パフォーマンスを損なう原因となっているケースが多いため、特に重要視すべき部分として挙げています。

──前の2つはイメージがつきやすいのですが、なぜJSON.parseは減らさないといけないのでしょうか?

大津 JavaScriptが提供しているJSON.parseのAPIが、同期処理しかサポートしていないためです。そのため、JSON.parseの処理を行っている間は、他のリクエストを受けつけなくなってしまいます。

栗山 過去事例として、Yahoo!ニュースの開発メンバーから「パフォーマンスが出ない理由を探ってほしい」と相談されたことがありました。

原因を探ってみると、あるキャッシュモジュールの内部でJSON.stringifyとJSON.parseが何度も呼ばれていたことが分かりました。他のモジュールを使うようにしてもらったり、どうしても回避できない部分ではモジュールを自作することで、パフォーマンス改善をしていきました。

伊藤 パフォーマンス劣化を引き起こすAPIは他にもあります。

例えば、日付を扱うIntl.DateTimeFormatというAPIがあるのですが、これがホットコードで何度も呼び出され、パフォーマンス劣化を引き起こしているケースがありました。代わりに正規表現による置換に書き換えることで、約10倍の高速化ができました。

他にもヤフーの事例で、DIを実現するためのライブラリを利用していましたが、デフォルト設定のまま利用し、シングルトンとすべき実装の箇所がシングルトンになっておらず、リクエストのたびにインスタンスが生成される実装になっていました。なおかつ、そのconstructor内で同期処理が呼ばれてしまっていたため、この2つが重なり、大きなパフォーマンス劣化を引き起こしていました。

これらの事例から分かるように、Node.jsのパフォーマンス改善は、「性能をより良くする」というよりも「悪いコードによって落ちてしまった性能を元に戻す」というイメージです。具体的には次の2点が重要になります。

  • どのようなAPIがパフォーマンス劣化を引き起こすのかを知ること
  • 内部でどのようなAPIが呼ばれているかを把握すること

大津 ですから、検索して自分の用途に合ったライブラリが見つかったとしても、何も考えずに導入してはいけません。ソースをきちんと読みましょう。もしかしたら、そのライブラリはパフォーマンスに問題があるかもしれません。中身を把握して、どんな処理が行われているかを理解した上で使うべきなんです。

──どうすれば「このAPIは性能劣化を引き起こす」と分かるようになるでしょうか?

伊藤 丁寧にプロファイリングをして、どの箇所で性能劣化が発生しているのかボトルネックを探ることが重要になります。私たちは、プロファイルログをFlame Graphで表示してくれるflamebearerを用いて、プログラムの動作を計測することが多いです。

(23ページ)

〈パフォーマンス向上(2)〉
Node.jsのデザインパターンやCore APIの仕様を理解する

エンジニアHubに会員登録すると
続きをお読みいただけます(無料)。
登録のメリット
  • すべての過去記事を読める
  • 過去のウェビナー動画を
    視聴できる
  • 企業やエージェントから
    スカウトが届く