- Erlangが信頼性の高い分散システムを作れた核心は、軽量プロセスそのものよりも、繰り返し現れる並行性・障害処理パターンを一般化したbehavioursにある
- behavioursはインターフェースのように実装ポイントを提供しつつ、
gen_serverのように並行処理を共通コンポーネント内に隠し、アプリケーションコードを逐次ロジックに近い形にする
- Joe Armstrongの論文は、
gen_server, gen_event, gen_fsm, supervisor, application, releaseといった小さな構成要素だけでAXD301のような大規模システムを構築した事例を扱う
supervisorは失敗したプロセスをone_for_oneやone_for_allのような戦略で再起動し、「Let it crash!」哲学は復旧責任をsupervisorツリーへ移す
- 状態機械形式のbehavioursはシミュレーションテストと形式検証を容易にし、開発者は並行性やシミュレータの複雑さよりも問題の意味論に集中できる
Erlangが解こうとした問題
- Erlangは信頼性の高い分散システムを作るための道具として始まった
- 当初は信頼性の高い分散システムを作るためのPrologライブラリで、その後Prolog方言を経て独立した言語になった
- Ericssonの電話交換機のプログラミングに使われ、80〜90年代に数億人規模のトラフィックと厳格なSLAを扱っていた
- Ericssonは1998年にErlangの使用を禁止したが、開発チームは、禁止するならオープンソースとして公開しようと主張し、Ericssonはそれを実行した
- Joe ArmstrongはErlangの設計と実装の中心人物の1人で、2002年にSICSで博士論文を書き始め、2003年に Making reliable distributed systems in the presence of software errors を完成させた
- この論文は数学や理論よりも、Erlangを支えるアイデアと信頼性の高い分散システムを作った経験に焦点を当てている
- Erlangの大きなアイデアは軽量プロセスとメッセージパッシングではなく、Erlangでbehavioursと呼ばれる汎用コンポーネントにある
behaviours: インターフェースとインフラの結合
- Erlangのbehavioursは、JavaやGoのインターフェースのように複数の実装を持てる型シグネチャの束である
- プログラマがそのインターフェース実装を提供すれば、そのインターフェースを対象に書かれた汎用関数を使える
- 核心的な違いは、behavioursが単なるインターフェースを超えてインフラコードまで一緒に提供する点にある
- アプリケーションプログラマは問題の意味論、つまりビジネスロジックを書く
- 並行性のようなインフラコードはbehaviourが自動的に提供する
- behavioursには経験豊富な専門家が書いたベストプラクティスが詰まっている
- システム全体が小さなbehaviour集合を再利用すれば、behaviour実装が改善されたとき、システムもコード変更なしで改善できる
- behaviourの利用は構造を強制し、テストや形式検証をより容易にする
gen_server: 逐次コードの背後に隠れた並行性
gen_serverの例はキー・バリューストアで、storeはキーと値の組を保存し、lookupはキーの値を探す
- 中核コールバックである
handle_callは、store要求ではDict状態を更新し、lookup要求では状態からキーを検索する
- この実装を
gen_serverに渡すと、同時に入ってくるstoreとlookup要求を処理できるサーバーが作られる
- 重要なのは、
handle_call自体は完全に逐次的だという点にある
- 並行性は汎用
gen_serverコンポーネントの内部に隠される
- アプリケーションコードは、状態と入力を受け取り、新しい状態と出力を作る形に近づく
- Joe Armstrongの論文は、このアプローチを
gen_server, gen_event, gen_fsm, supervisor, application, releaseにまで拡張している
AXD301事例とbehavioursの規模
- Joe ArmstrongはEricssonのAXD301電話交換機を事例として使っている
- AXD301プロジェクトには次の構成要素が含まれていた
gen_serverインスタンス 122個
gen_eventインスタンス 36個
gen_fsmインスタンス 10個
- supervisor 20個
- application 6個
- 全体が1つのreleaseとしてパッケージ化されていた
- AXD301はErlangコードが100万行以上あるシステムである
- この規模のシステムを小さなbehaviour集合で構成した点が、Erlangの構造の重要な根拠になっている
他のbehavioursの役割
gen_eventは汎用イベントマネージャである
- イベントハンドラを登録し、イベントマネージャが関連メッセージを受け取るとハンドラを実行する
- Joe Armstrongはエラーロギングのような用途を例に挙げている
- 例のloggerは直近5件のエラーメッセージを記録して報告できる
gen_fsmは後にgen_statemへ改名された状態機械behaviourである
- プロトコル実装により適している
- プロトコルはしばしば状態機械として仕様化される
- どんな
gen_serverもgen_statemで、またその逆でも実装できると見なしている
applicationはsupervisorツリーとアプリケーション配布に必要な残りの要素で構成される
releaseは1つ以上のapplicationをパッケージ化する
- アップグレード処理コードも含む
- アップグレードが失敗した場合は、以前の安定状態へロールバックできなければならない
supervisorと「Let it crash!」
supervisorは他のプロセスが正常に動作しているかを確認するプロセスである
- 監視対象プロセスが失敗すると、supervisorは事前定義された戦略に従って再起動できる
- 例の
{one_for_one, 5, 1000}戦略は次を意味する
packet_assembler, kv, simple_loggerのうち1つが失敗したら、失敗したプロセスだけを再起動する
- 1000秒の間に5回を超えて再起動が必要なら、supervisor自体が失敗する
permanent, 500, workerは、そのプロセスが常に生きているべきworkerであり、supervisorが再起動しようとするときに正常終了のため500msを与えることを意味する
one_for_all戦略では、1つのプロセスが失敗するとすべての子プロセスを再起動する
- supervisorは他のsupervisorを監視でき、必ずしも同じコンピュータ上で動作する必要もない
- この構造はDockerコンテナのレベルではなく、スレッド/軽量プロセスのレベルで動作するため、「単なるKubernetes」とは言いにくい
- 「Let it crash!」は、失敗したプロセスが再起動されることを前提とする
- プログラムは正常系だけを表現する
- 正常系で問題が起きたら自分で復旧しようとせず、crashする
- supervisorツリー上位の別プログラムが処理を引き受ける
- AXD301の長期安定性の証拠は体系的には収集されていないが、Joe Armstrongの論文には、ある主要顧客が11ノードのシステムを99.9999999%の信頼性で運用したというPowerPointの数値があったと記されている
- その数値がどう得られたのかは文書化されていない
- 他のダウンタイム報告の数値にも、算出方法が不明だという但し書きが付いている
他の言語でbehavioursを実装するなら
- 軽量プロセスとメッセージパッシングだけで、Erlangが信頼性の高いシステムに向いているわけではない
- より正直な説明は、behavioursが提供する構造と、その構造が信頼性の高いソフトウェアにつながる仕組みにある
gen_serverのインターフェースシグネチャは次の形で捉えられる
Input -> State -> (State, Output)
- 入力と現在の状態を受け取り、新しい状態と出力を作るという意味である
- この逐次シグネチャを同時要求処理へ変換する方法は、次のように考えられる
- HTTPサーバーが要求を
Inputに変換してキューへ入れる
- イベントループがキューから入力を取り出して逐次実装に渡す
- 出力をクライアント応答として書き戻す
- 複数の
gen_serverをサポートするには、各サーバーに名前を付け、要求に名前と入力を一緒に含めればよい
gen_eventは、キューの特定イベント型にコールバック登録を許す方式で実装できる
supervisorは、gen_server関数呼び出しを例外ハンドラで包み、例外が起きたらsupervisorに通知する形で単純化して考えられる
- supervisorが同じコンピュータ上で動いていなければ、さらに複雑になる
applicationとreleaseは、設定・配布・アップグレードが難しい問題であるため重要だが、深くは扱われていない
behavioursとテスト可能性
- Erlangのbehavioursが持つ構造はシミュレーションテストと形式検証へつながる
- 最近の関心はFoundationDB式の分散システムのシミュレーションテストにある
- シミュレーションテストは、システムをシミュレーションされた世界で動かし、シミュレーションがネットワークメッセージ送信のタイミングを完全に制御する方式である
- FoundationDBはシミュレーションテストのために、actorを備えたC++方言、または独自のプログラミング言語を作った
- このアプローチは、次の状態機械型だけでもかなり先まで行けたと見なしている
Input -> State -> (State, [Output])
[Output]は出力シーケンスである
- シミュレータは到着時刻順の優先度キューでメッセージを管理する
- メッセージを1つ取り出す
- 時計をそのメッセージの到着時刻まで進める
- 受信側の状態機械にメッセージを渡す
- 出力メッセージに新しい到着時刻を生成して再びキューへ入れる
- すべてが決定的で、到着時刻がseedから生成されるなら、複数のinterleavingを探索し、再現可能な失敗を得られる
- この方式は、メッセージがメモリ内で処理され、timeoutを待たずに時計を到着時刻まで進めるため、Jepsenより高速である
- このような状態機械形式は「network normal form」と呼ばれ、ネットワークで受信・送信するあらゆるプログラムをこの形にリファクタリングできるのではないか、という推測につながる
gen_serverとgen_statemが事実上同じ型構造を持つことは、この構造が恣意的ではないことを示すシグナルと見なせる
形式検証と状態機械構造
- Joe Armstrongは講演で、分散leader electionを正しく実装するのは難しいと語ったことがある
- シミュレータがあれば、この問題は大幅に単純化できる
- 航空機製造における風洞のように、本番前に不安定なネットワークや電源喪失のような極端条件をテストできる
- このシミュレータはbehavioursに対して汎用的、あるいはパラメータ化されたものにできる
- 開発者はシミュレータを別途書く必要がない
- 複雑性は
gen_serverの並行コードのようにbehaviourの背後へ隠される
- FoundationDBのテストについて、Kyle “aphyr” Kingsburyは、FoundationDBのテストは自分のJepsenテストよりもはるかに厳格に見える、という趣旨のツイートを残している
- 形式検証も、プログラムが状態機械として書かれていれば容易になる
- LamportのTLA+モデル検査の作業は、仕様が状態機械であることを前提にしている
- Kleppmannは状態機械構造を使った構造的帰納法で状態爆発問題を扱う方法を示している
- Erlangのbehavioursから得られる構造は、Joe Armstrongが難しいと言っていた問題をより簡単に解くために再利用できる
関連作業と参考資料
- Erlangからアイデアを得た関連作業として、次が挙げられている
- 参考資料として、Joe Armstrongの博士論文、OTP design principles、
gen_server, gen_event, gen_statem, supervisor, application, releaseの文書が示されている
- actor modelとErlangの関係については、Erlang開発者たちはactor modelを知らないままErlangを作り、Carl Hewittの論文がErlangプロセスとactor modelの違いを文書化している、と整理している
- Akkaには「actors」とsupervisor treesがあるが、他のErlang behavioursに相当するものは見当たらず、Akkaの「behavior」概念もErlang behavioursとは異なる
1件のコメント
Hacker Newsのコメント
ErlangとBEAMのすごいところは、機能の奥深さにある。元記事ではBehaviour/Interfaceが中心的な印象だったが、私にとっては、複雑なシステムを作るときに他の言語よりはるかに少ない開発リソースで実現できる点のほうが大きい
OTP自体にも多くのものが入っている。ElixirをiOSデバイスで動くようにコンパイルする作業をしたことがあるが、Erlangのreleaseプロセスだけでなく、eiライブラリを使ってCでNodeをコンパイルし、Erlang、Elixir、Gleamなど他のErlangノードと通常の分散ネットワークのように通信できた
さらに、Erlangのrpcライブラリを通じて、CからElixirアプリケーションの関数呼び出しまで可能だった。エンコード/デコードのオーバーヘッドはあり、FFIのほうが速いだろうが、レイテンシ予算内には十分収まっていて、以前は聞いたこともなかった機能を数日で立ち上げられた
より大きなポイントは、現代の技術スタックが苦闘している多くの問題を、Erlangはすでに何十年も前に、スケールと実装コストの面で解決していたということ。HNにはErlang/Elixirへのクリックベイト的な愛着がある面はあるが、実際の採用にはつながっておらず、Erlangスタックなら標準で無料で得られるものを実装しようとして資金を燃やしている会社がある
携わったプロジェクトはバックエンドのデータパイプラインで、処理するデータ量も多くなかったのに、核心的なバグを正確に切り分けるのが信じられないほど難しかった
その過程でNode.jsのさまざまな特性を知り、Elixir/Erlang/OTPと比べてみた結果、Node.jsは設計上、信頼性が低いという結論に至った
RubyもかなりやったしPythonも触ったが、現世代の言語プラットフォームの多くは、信頼性のある分散システム構築で苦労している。BEAM VMとOTPプラットフォームは、すでにその部分を解決している
たとえば、ほとんどのBEAMシステムでワーカーの役割を担うGenServerは、結局のところ単純な引数で複数の関数を呼び出す構造だ
したがって、その関数を直接呼び出して引数を手動で渡し、出力だけを検証すればテストできる。非同期コードを扱う複雑なテストシステムをセットアップしたり、テストで作業完了を待つために止まったりする必要はない
ジュニアがよく見落とす点だが、気づくとかなり解放感がある
何人か、主にマネージャーたちが、私たちの経験をもとに本を書くと言っていたのを見たことがある。私たちの仕事が成功した理由について、彼らがそれぞれ違う部分を核心だと見ている点がいつももどかしかった。私が不可欠だと感じたものを、彼らは「あれば良い」程度に縮小しがちだった
ここでも誰かが、軽量プロセスとメッセージパッシングは秘密のソースではないと言いながら、Erlangを通信する逐次プロセス(CSP)として見る観点が、その特性と切り離せないことを見落としている。それでいて、繰り返しCSPを秘密のソースの一部として挙げている
たとえば、アプリケーションプログラマは逐次コードを書き、並行性はbehaviourの中に隠されるとか、ビジネスロジックが逐次的なので新しいチームメンバーが始めやすいとか、supervisorと「let it crash」の哲学が信頼性のあるシステムを作る、といった話だ
Behaviourは興味深く、80年代にはよく見られ、2000年代にも一部で取り組まれていた問題を解決するが、Erlangでは目的であると同時に手段でもある。そうした他の特性を実装した方法ではあるが、ErlangがなおErlangらしくあるために、必ずそうでなければならなかったのかは分からない
CSPはoccamや他の言語を経てGoのチャネルに影響を与えた側だ。バッファなしチャネルで同期する方式が最もはっきりした違いで、アクターモデルのメールボックスに対するパターンマッチングのような違いもある
CSPとアクターモデルの議論は、見た目は似ているが実際の含意は大きく異なり、かなり興味深い
アクターモデルのほうがしっくりくると思うが、人によって大きく違うかもしれない
EricssonがなぜErlangの使用をやめたのか、Joeがなぜ解雇されたのかをもっと知りたくて来た
簡単に言うと、Ericssonは新規プロジェクトをJavaへ移行し、そのためErlangは周辺化されたようだ。その後、Joeと同僚たちは1998年にBluetailを作り、Nortelに買収された
Nortelはトロント証券取引所の価値のおよそ3分の1を占めていた通信大手だった。2000年には株価が1株125ドルまで上がったが、2002年には1ドル未満まで下落した。ドットコム崩壊の一部であり、通信支出の急減と重なって、Nortelは特に大きな打撃を受けた
Joeの解雇は、「沈みゆく船で彼の部署が先に水に浸かった」状況と見るのが安全そうだ。Nortelは6万人、つまり全従業員の3分の2以上を解雇した。その解雇はJoeが役割を果たせなかったというシグナルではなく、事業部の非効率を示すものでもない、切迫した大規模措置だった
“fired”は価値判断が強く、過失があって懲戒的に解雇されたというニュアンスを与える。実際にそうだったとしても、元記事の筆者が知るはずはなく、知る必要もない
軽量プロセスとメッセージパッシングがきっかけで Erlang を見直し始めたのであって、これまで behaviour は副次的なものだった
プロジェクトは、視覚的な フローベースプログラミング(FBP) を Erlang に持ち込むもの。FBP は Erlang のために作られたように見えるのに、既存の実装が見当たらないのは意外だった
FBP 用に主に使っていたツールは Node-RED なので、基本的なアイデアは Node-RED のフロントエンドを Erlang のバックエンドにつなぎ、すべてのノードをプロセスにすること。Node-RED のフロントエンドはノード間のメッセージ受け渡しをモデル化するのに向いているため、Erlang のプロセスとメッセージへ非常に単純な 1:1 マッピングができる
基本機能の一部を実装し、機能を少しずつ積み上げるためにフローをユニットテストとして作り始めた。Node.js バックエンドである Node-RED と 100% 互換になればうれしい。詳細は GitHub リポジトリにある → https://github.com/gorenje/erlang-red
全体として Erlang はこれに驚くほどよく合っていて、なぜ誰も似たようなことをしていないのか不思議に思う。もしかして既にあったのだろうか?
[1] = https://jpaulm.github.io/fbp/index.html
私にとって Erlang/Elixir の力は、アクターモデルの実装、Prolog 由来のマッチング、不変性、あるいは behaviour そのものというより、より少ないものでより多くを実現できることを示そうとした Joe の意志にある
よく設計され検証された計算システムであり、他の言語はもちろん「Web」の世界でもめったに見られない一貫性を備えている。完璧ではないが、かなり印象的だ
残念ながらソフトウェアの世界では、シンプルさが可能にするものへの評価と採用がかなり不足していると思う。複雑性は人々を専門家にし、管理者に大きなチームと多くの会議を正当化させ、専門家を専門家であり続けさせる
Erlang は、企業がより少ない人数と限られた性能でソフトウェアソリューションを実装しようとしていた時期に開発された。その後の数十年でこの分野に資金が大量に流れ込み、「少ないことが、すべての人にとって良い形でより多くを意味する」という価値は、以前ほど魅力的に見えなくなった
Erlang/BEAM で最も興味深い概念は、部分的な復旧 が最初から組み込まれている点だ
予期しない状態に遭遇したとき、プロセス全体を落としたり、処理を続けて破損を受け入れたりする代わりに、可能な限り細かいレベルで既知の正常状態へ戻す
このアイデアはかなり前に「マイクロリブート(microreboots)」という名前で研究され、「crash-only software」とも関係があったが、Erlang/BEAM だけがこれを運用システムの第一級の概念にした
Erlang はこうした問題を扱うための良いモデルを提供しているが、考えることを完全にやめてよいという意味ではない。単に再起動させておけば全部大丈夫だろう、という姿勢で入ると痛い目を見ることがある
Erlang、OTP、BEAM は behaviour 以上のものを提供する。VM は supervisor、隔離されたプロセス、複数の物理/仮想マシンを1つのリソースプールのように扱う分散モードを持つ 仮想カーネル に近い
OTP はデータベースである Mnesia、キャッシュ用のアトミックカウンターや ETS テーブルなど、有用なモードもいくつも提供する。ランタイムはバイトコードのホットリロードにも対応しており、システムを停止せずにパッチを適用できる。文法はスクリーンリーダーに優しいわけではないが、読める
複数マシンのリソースを1つのプールのように扱う能力という点で、BEAM に似たプラットフォームとして思い浮かぶのは Apache Mesos[1] くらいだ
1年ほど前、私の個人コンサルティング会社はバックエンド言語として Erlang を採用した。その後、TCP ベースのスタックを QUIC に置き換えたり、Rust のパッチを統合したりする形で、BEAM の内部を探り始めた
カーネルパニックや電源喪失でなければ失敗しない、軽量で高スループットなシステムには本当に優れた選択肢だ。現在、映画/ゲーム制作のトラッカーやパイプラインマネージャーのような、非常に忙しく同時実行性の高いソフトウェアを作っており、民間病院管理サービスの R&D も準備中だ
[1]: https://mesos.apache.org/
Elixir 側でメンテナンスされていないものについて、内部バインディングや実装を自分たちで維持しなければならない可能性が高い
また、構文糖が多く、ユーザーが何かを DSL インターフェースとして抽象化しようとする独特の執着を見せる点もある
「なぜ言語やライブラリの設計者は軽量プロセスとメッセージパッシングのアイデアだけを真似して、Erlang behaviour の背後にある構造を盗まないのか?」という問いには理由がある。
Erlang behaviour の関数シグネチャは、Erlang の他の機能、とくに独特な不変性の使い方と強く結びついている。サーバーに別途 init 呼び出しが必要なのもそのためで、状態管理も同じように動作させるには非常にはっきりした形が必要になる。
しかし、他の言語で同じ目標を達成しようとすると、Erlang がやっていることをそのままコピーすべきではない場合がほとんどだ。「gen_server を別の言語に移植した」と言いながら Erlang とまったく同じインターフェースを見せるなら、Erlang が何をしているのかを深く理解していない移植だと見る。
私が Go に supervisor tree のアイデアを移したとき[1]も、Go らしいやり方にした。現代の Go における「監督可能なもの」の正しいインターフェースは、Erlang と同じシグネチャではなく、次のものだけだ。
type Service interface {
Serve(context.Context)
}
Go ではこれがすべてであり、この程度だけを使うべきだ。他の言語では違うかもしれない。Go にはチャネルがあるので「handle_event/2」は不要で、それを使うべきだ。チャネルの方が良いとか悪いとかではなく、その言語がそう動作するからだ。
他の言語では別のものを使えるし、別のインフラでは「handle_event/2 を呼ぶ」代わりに Kafka やクラウドのイベントバスへ送ってもよい。重要なのはイベント駆動システムを作ることであって、Erlang の正確な実装をコピーすることではない。
Erlang コミュニティには、Erlang が採っている正確なやり方に何か途方もなく特別なものがあり、違うやり方をすれば即座に間違いで信頼できない、と過度に確信している問題がある。2005年ならそうだったかもしれないが、2025年は違う。
かつて Erlang は、ほとんど唯一筋の通った答えだった。しかし2025年の問題は、答えの洪水の中をかき分けて進むことだ。信頼性のあるソフトウェアのために Erlang から学ぶことは強く勧めるが、Erlang が達成した正確なやり方を別の言語へ盲目的に移植することには強く反対する。ほぼすべての他言語の文脈では、それは間違った答えだ。他の不変言語でさえ構造が十分に異なるため、そのままコピーすることはできない。
[1]: https://jerf.org/iri/post/2930/
Elixir を仕事で使っている立場からすると、Erlang について最も興味深い問いがまさにこれだ。
k8s、Kafka、AWS の分散システム製品が大きく成功していることを見ると、信頼性のある並行システム設計を助けるツールへの需要は明らかに非常に大きい。では、なぜ Erlang/Elixir はその取り分をつかめなかったのだろうか。
友人たちとよく議論するが、答えは分からない。
Erlang はネットワークメッセージ処理を抽象化していたのだろうと思う。さらに、長時間実行されるサーバーのようなものについて、提案されているパターンの変種を3〜4個は見たことがある。
移植するときには慣用的な構成要素を使うべきだという点には完全に同意する。しかし言語には、移植の過程で価値ある本質を失わせる隠れたメカニズムがあるかもしれない。ある種のプログラミング言語反相対主義と言える。
「チャネル? 単に X で包めばいい」というのは、聞こえよりも相互運用性にずっと有害である可能性もある。たとえば Go の http.Handler を見ると、単純だが標準ライブラリに存在するという現実的な含意は大きい。事前調整なしでも、おおむね互いに互換性のあるミドルウェアのエコシステムが生まれる。
io.Reader とその仲間たちも同様だ。こうした極めて単純なインターフェースは、実装よりも価値があると見なせる。
推測だが、Erlang が信頼性のある分散システムのための多くのインターフェースをうまく定めていたのだとしたら、それこそが全体を可能にした要素だったのかもしれない。
同意しない。インターフェースはどんな言語にも付けられる些細な概念だ。公式なインターフェース構文がない言語でも、プログラム空間で真似できる。
BEAM が成功した理由は、単一ノードで100万プロセスを動かせ、複雑な分散状態機械を簡単に表現でき、システムの一部を無停止で再起動できるからだ。それ以外にも多くの理由がある。
behaviour/interface が最も核心的なピースだとは本当に思わない。
分離されたプロセスヒープは OS プロセスを使えばよく、supervision tree は Kubernetes を使えばよく、メッセージパッシングは Java で2つのスレッドと共有キューを使えば作れるし、ホットコードローディングは Java でも可能で、低レイテンシ処理は LMAX Disruptor をチューニングすれば Erlang に勝てるかもしれない、という具合だ。
しかし、このすべてを1つのプラットフォームやライブラリに集めることが核心だ。OS プロセスは重く、サーバー上で200万個動かすのは簡単ではない。グリーンスレッドや promise を使えば、分離されたヒープを失う。
Kubernetes もある程度は使えるが、ネストした supervision tree をうまく扱うわけではない。可能ではあるだろうが、コード以外に Pod、コントローラー、ボリュームなど、あらゆるものが発生する。
さまざまな言語のアクターライブラリでメッセージパッシングはできるが、receive でパターンマッチしたり、別ノード上の別スレッドへ送ったりすることを透過的に統合できるわけではない。
ホットコードローディングも可能だが、ランタイムのデータ構造と状態をどう扱うかが問題になる。Erlang はそれを中心に作られており、gen_server は状態が不変かつ明示的なので、コードだけでなく状態そのものをアップグレードするコールバックを持っている。
BEAM がこの問題を取り除くとは言わないが、直線の傾きを下げているように見える。デプロイ、自動復旧、負荷分散を含む自己一貫したイディオムと機能が、モジュール間の摩擦を減らす。
12人のエンジニアが30個のエンドポイントを簡単に管理でき、表面積もなおべき法則に従えるようなシステムを作ってくれる。
この記事の内容には同意しない。Behaviour はシステムの基盤アーキテクチャがあるからこそ可能になっている。
Behaviour はインターフェースではなく、Java のような言語における抽象オブジェクトにより近い。協調のためのインターフェースの背後に隠された、基本的で自己完結した機能を実装するものだが、各プロセスが他のプロセスから完全に分離され、すべてのプロセスをメモリやリソースのリークなしに安全に閉じられ、2 つのプロセス間で危険なポインタを共有できないことを保証する基盤インフラがなければ、多くのことはできない。
Joe が論文で示したのは、与えられたレゴブロックの集合で、信頼性の高いシステム、さらにはある程度信頼性の高い分散システムをどう作れるかということだった。
それを適切に実装するには Erlang VM が必要で、他の VM では完全には実装できない。下層の配管がなければ監督ツリーは漏れてしまう。Java では、リソースを保持しているスレッドを殺して、すべてが常にうまくいくことを期待するわけにはいかず、異なるプロセスを監視する方法も不足している。