3年ほど連載を続けているこのコラムだが、今号からは少し装いを変え、最新の技術トピックを業界全体の流れから俯瞰(ふかん)的に見た考察を加えていきたい。最初のトピックはNode.jsだ。
Node.jsを取り巻く議論の背景
2009年に発表されて以来、ソフトウェアエンジニアの間で注目を集めているNode.js だが、それを取り巻く非同期プログラミングへの賛否両論の根底は、1990年代の初めにミニコンピュータからパーソナルコンピュータ(以下PC)へと引き継がれたRPC(Remote Procedure Call)に関する議論にある。
RPCの発想をPCの世界に持ち込んだのはMicrosoftである。Windows 95より前は、ノンプリエンプティブマルチタスク[1]しかサポートしていなかった。
Windowsに本格的なマルチタスク機能を持ち込んだのは、David Cutlerを筆頭とするDEC(Digital Equipment Corporation)から来たメンバーたちであった。彼らがミニコンピュータで採用されていた本格的なマルチタスク・マルチスレッドのしくみをPC用のOS に持ち込み、それがWindows 95、Windows NT という形で商品化されたのである。
その「本格的なマルチタスク」と同時に持ち込まれたのが、「実際には時間のかかるオペレーションであっても、すべて同期型のAPI として提供したほうがプログラムしやすい。1つのタスクが待たされている間はコンテキストスイッチによりほかのタスクを走らせればよい」という同期的な発想であった。
RPCの功罪
その最たるものがRPC(とMicrosoftがそのころ発表したDCOM)で、ほかのマシンとの通信までも同期型のAPI として提供することにより、ネットワーク遅延すら意識せずにプログラムを書くべきという発想であった。彼らから見れば「非同期プログラミング」はノンプリエンプティブマルチタスク時代のなごりでしかなかったのだ。
私はちょうどそのころWindows 95の開発をしていたが、DCOM/RPCを本格的に使う最初のアプリケーションがWindows Explorer だったため、その誕生の苦しみに直接関わることとなった。
まず第一に、RPCを使うと「どのタイミングで別プロセスに処理が移行するのか」がプログラマに見えなくなるため、簡単にthread hog[2]ができてしまうのだ。たとえば、ExcelとWord間でカット&ペーストを実行するケースでは、RPCを介してお互いのオブジェクトが持つメソッドを呼び合うたびにスレッドを生成するしくみになっていたため、やたらと遅くなってしまったのだ。当初DCOMグループは、スレッドプール[3]でしのごうと試みたがうまくいかず、結局は「表向きはRPC、しかし、実装はメッセージループを使ったプリエンプティブマルチタスク」という実装でかろうじて必要なパフォーマンスを得ることができた。
これ以外にも、RPCを使ったためにネットワーク経由では使いものにならなくなってしまったExchange のMAPI[4]だとか、RPCを使って実装したために、数百台のマシンが同時に接続すると落ちてしまい出荷できなくなったプロキシサーバなど、失敗例は数多くある。RPCは一見すばらしいアイデアなのだが、「遅延をプログラマから隠す」という性質そのものが、プログラマに「スケーラビリティのないプログラムを書かせてしまう」という危険性を持っているのだ。
SOAP vs. REST
そんな経験を通して「ネットワーク遅延をプログラマから隠すのは良くない」ということは社内でもよく知られていたので、Microsoft 内部で(RPCをHTTP上で実装するための)SOAPが提案されたときに、それがいかに間違っていて、Microsoftは全面的に非同期API を前提としたREST(Representational State Transfer)にシフトするべきだと反対する声も多かった。
しかし、そのころはまだ、DECから引き継いだ「RPC信仰」が強く残っていたうえに、「インターネットの標準はMicrosoftが作る」という幻想にまだ縛られていたこともあり、SOAPはそのままの形で世の中に出ることになったのである。
結果としては、MAPIはPOP3/IMAPに淘汰され、WebサービスのデファクトスタンダードはSOAPではなくREST になり、その幻想は見事に打ち砕かれた。
そんなバックグラウンドを知っていれば、Node.js がなぜこれほどまでに注目されているかが理解できるし、同時に「非同期プログラミング」をものすごく嫌うプログラマがいまだにいることも理解できると思う。