読者です 読者をやめる 読者になる 読者になる

なぜErlang/OTPなのか

このテーマ自体はさんざん語り尽くされていることである。たとえば山口君によるWhy Erlang? というブログ記事の翻訳や、戦闘機本(Programming Erlang: Software for a Concurrent World (Pragmatic Programmers))を読めば世間でいわれていることはよく分かる。もしくは、同僚が最近書いたソフトウェアデザインの記事を読んでもらってもよいだろう。

私自身もErlangに出会ってから5,6年が経とうとしているが、当初はそのよさがよくわかっていなかったように思う。しかし、仕事で高可用性が要求される複雑な分散システムに携わるようになって*1、いろいろな問題がコンピュータというか、データベースとか分散システムの世界にあることを知った。

メモリ管理の問題

単にmainを読んで、なんらかのロジックを実行してそれなりの時間で終了するプログラムを書いているうちは、メモリリークやメモリ管理の問題についてうるさくいうことはなかったのだけども、マルチスレッドでプログラムを動かしていて、開放されたところをうっかり他のスレッドがタイミング依存でさわってしまうとかいろいろあったわけで…
かといってメモリ管理をJavaやRubyのようなシステムでやってしまうとグローバルなGCをせざるを得ず、死活監視をほぼリアルタイム*2でやりたいといったときにGCでシステムが固まるのは困る。といったときにErlangの仕組みを考えてみると、Per-process-GCというのだけども、すべてのデータはTLSになっていてスレッド間のデータ渡しはメッセージなので基本的にはコピーで行う、というやり方をみて眼から鱗が落ちたのであった。メモリ管理に関する本質的な難しさが完全に解決するわけではないのだけども、人間の脳でもある程度考えられる程度になったという意味で、これと類似のことができるのはErlangくらいなものだと思う。

軽量プロセス

プログラミングモデルでいうと、1つのTCPコネクションに大して1つのOSプロセスをforkしたり、スレッドをcreateするのはとても自然なことである。そのTCPコネクションと、リクエスト処理に関わるもろもろのリソースのライフサイクルを一致させることができ、かつ、関係のない他のものと綺麗に分離できるためだ。だがちょっと待ってほしい。いくらCoWが働くとはいえ、ひとつのOSプロセスを起動して捨てるためのオーバーヘッドはいかほどのものだろうか。スレッドのスタックをある程度確保しないといけない。コンテキストスイッチも毎回おきる。レジスタは総入れ替えでキャッシュも効かなくなる(はず…)ので、モデルとしては正しくても、コスト的には釣り合わない。
そうするとどういうシステムになるかというと、たとえばクロージャを持ち回したり、TCPコネクションにひもづいたコンテキストの構造体を作ってスレッド間で引き回してスレッドプールを作って、つまらせたくない場合はキューをつくって別のスレッドプールに処理を流して、それのレスポンスをトリッキーな方法で待って…とやっていくと大抵は破綻するのである。一方でErlangの軽量プロセスとメッセージングの仕組みは、ひとつのTCPコネクションに対してひとつの軽量プロセスを割り当てる分かりやすいモデルにしつつ、ひとつの軽量プロセスのオーバーヘッドをかなり抑えることができる(300ワードくらいと言っていたような)。これを知ってしまうと、僕のようにあまり頭の回転がはやくないタイプはC++とかJavaで真面目に考えて実装するのはかなり面倒になってしまうのである。

Resilence

これを日本語でどういったらいいのかちょっと分からないが、耐久性というか安定性がある。ほっといても不安定になることはあまりないし、軽量プロセスを多少ぶっ壊したところで再起動するからどうってことないのである。もちろんErlang VM内部のメモリを破壊したらダメなところはあるのだが、それでもノンストップでコードをアップグレードしたりダウングレードできて、それが人間で扱えるレベルで簡単になっているのは恐ろしいことだ。ヤバい。鬼ヤバい。

マルチコア

20や30くらいのコア数だと安定してスケールする。GILとかGVLといったものがない。それは前述した通り、メモリ管理の方法が異なるためだ(もちろん、個々の軽量プロセスのレベルでGC時はとまってしまうのだが)。なので、ひとつのOSに複数のプロセスを起動する必要がない。それがChefがErlangを採用した理由だと聞いているが、何も考えずにOSプロセスをひとつ起動しておけばコアをよしなに使ってくれるのだから便利なものである。コア数が100や200まで増えたときの挙動はまだ私はよくしらないが、基本的にはN:Mのスレッドモデルをユーザー空間で実現しており、しかもメモリがShared-nothingなのでなんとかなるのではないかと楽観している。

まとめ

ここまでみてきたように、Erlangの言語そのものよりも、Erlang VMという処理系のメリットを私は気に入っているようだ。だから、Elixirのようなのものでも同じメリットを享受できる。安定したシステムを作るためにはErlangは最適の処理系である。

残念なところ

とはいっても、残念なところは沢山あるのでちょっと愚痴をいっておきたい。

まず、何よりも型安全ではない。この点ではRubyやPythonと同類で、うっかりテストを忘れていると修正で簡単にデグレがおきる。それを防ぐためのちょっと賢い方法としてDialyzerやQuickCheckがあるが、できればコンパイラと密結合してほしいものである。そのためには今のような beam ファイルの構成ではなくて、全ての実行ファイルを静的に結合してしまってひとつの実行ファイルにするのがよいのであるが、そうするとモジュール毎のライブアップデートや、ネットワークなど外部から変なTermが飛んできたときの扱いやらで、ずっと走り続けるシステムに静的で型安全な世界はムズカシイのかもしれない。それでも、Tupleじゃない代数的データ型、ほしいやん…

古い仕様が残っていることが多い。なるべく使わないものや有害なものはDeprecateしたり使えなくなるようになっているのだが、やはり長期間走るシステムをターゲットにしているだけあって古い仕様を捨てることはあまりない。まあこれはしょうがないか。

最後にDisclaimerであるが、この文章は読み返しているとはいえ、酔っ払って書いたものなのでどうか適度に聞き流して忘れてほしい。

ニッカ 余市 12年 45度 700ml

ニッカ 余市 12年 45度 700ml

*1:実装やテストの作業に携われなかったのは今でも口惜しい

*2:古典的な意味で