soranoba
soranoba Author of soranoba.net
programming

Erlangからドメインを切り離すモチベーション

2020年も残すところ僅か. 今年は仕事で数年ぶりにErlangでシステムの一部を書く機会があった. ーーとはいっても技術選定からコーディングまでサーバー側は全部自分でやっていたので, 何故Erlangを選んだかと, その振り返りをしようと思う.

システムの概要

ざっくりとシステムの概要を書くと, 典型的なPublish - Subscribeモデルだ.
Publisherに対して1万ぐらいのSubscriberは想定している. Publisherが流すメッセージは多くて1kB/sぐらい.
これに認証とか, 後で特定の状態を検索する仕組み (Xというメッセージを送ったPublisherの一覧取得とか) などが乗っている.

Publishに対して多くのSubscribeが接続するモデル


Erlangで書くモチベーション

Erlang以外の (正確にはErlangVMで動いていない) 言語で書く場合, 以下のいずれかの形でシステムを実装するように思う.

接続するnodeを決める場合 全てのメッセージをmiddlewareを経由させる場合
ノード選定がある場合 middlewareを経由する場合


メッセージデータ量が少ないとはいえ, 1つのPublish - Subscribeを1台でやらなければいけないという制約は避けたいと考えると, これらを複合した形になるのだろうか. それがErlangだと以下のようになる.

Erlangの場合


middlewareもノード選定も不要. 1番のモチベーションはこれだった.
(通信量が多い場合はノード選定の仕組みは欲しいが, 通信量やSubscriberに対してPublisherが極端に少なく, ノード数もそれほど増えないことを考えると全ノードに分散しても問題ない)

他の言語でもできなくはないだろうが, Erlangは標準でノードダウン検知などの仕組みがあるので, 1台から複数台対応にする際に追加のライブラリなしに実装ができる. (OTP23から入った, pgerpcが使える)

しかし, 全てをErlangで書きたくはなかった結果, Publish - Subscribe部分のみをErlangで書き, その他の機能をGolangで実装し, Erlang側からGolangのシステムに対してAPIを呼ぶ形で実装をした.
Golang側でWebSocket URLを払い出し, ErlangからGolangに権限の確認を行うーーといった形である.

つまり, 1つのシステムを2に割るという控えめに言って面倒な構成である. この構成にした理由はErlangのメリットとデメリットを加味した結果だった.

Erlangを使うメリットとデメリット

Erlangは決して何にでも使うような言語ではない, とても癖の強い言語である.

  • ErlangVM同士がフルメッシュで通信をし, Erlangクラスタを構築する
  • 無停止でコードを更新する, ホットコードローディングの仕組みがある
  • マルチスレッド処理が, アクターモデルで比較的容易
  • などなど

Erlangにはこのような他の言語では見ないような特徴があるものの, これらはメリットである一方で, 同じだけのデメリットを抱えていると言える.

  • Erlangクラスタ -> ノード名の付与とノードを探す為にデプロイ時の制約などが生じる
  • ホットコード -> バージョンに応じた設定ファイルなどの置き場所に制限がある
  • アクターモデル -> スレッドモデルと比べてメモリ効率は悪い
  • などなど

この中で一番面倒なのはクラスタ化に伴う弊害だと思う. それぞれのErlangノードに一意な名称を設定し, 他のErlangノードの名称を知る必要がある. その為, デプロイ時に同一ノード上にもう一つのErlangノードを予め立ち上げるというのが困難になる.
Dockerを使った場合は既存のアプローチが使えるが, これも現在進行系で改善中の問題だったりする. 2020年12月にもホットな記事が投下されたと言えば, その温度感が伝わるだろう.

そこでこのデメリットを避ける為に, 更新頻度を下げるべく必要最小限のドメインロジック以外は別システムに隔離するという手段を採ることにした.(middlewareに状態を保存せずにErlangノード内で持っている関係上, 気軽にノードを落としたくないというのもある)

これが記事タイトルである「Erlangからドメインを切り離すモチベーション」である.

ドメイン切り離しによる想定していたメリット・デメリット

ようやく本題である.
Publish - Subscribe部分をErlangで書き, それ以外を別システムに切り離したことによる想定していたメリット・デメリットは以下の通りだった.

  • メリット
    • Erlangからmiddlewareを使わなければ, ライブラリがない苦行 (自分で何個もライブラリを作る) を回避できる.
      • マイナー言語ゆえにライブラリが比較的少ない
    • 耐障害性が高くなる.
    • 複数ノードのテストが簡単に書ける.
    • ノード選択 (Leader Election / リーダー選出) をしなくて済む.
      • 個人的にLeader Electionは苦手意識がある
    • Erlang部分の更新頻度が下げられつつ, ドメイン部分は気軽に更新できる.
    • 初期実装が短時間で書ける.
  • デメリット
    • システム間連携分面倒くさい.

これらが実際はどうであったかを振り返りたい.

実際にあったメリット・デメリット

メリット: Erlang側は無停止無更新

8月末頃に稼働してから現在に至るまで, Erlang側は無停止無更新だった. 今のところ更新する予定もない.
想定以上にドメイン部分を切り離せたので, 更新時に余計な気を使う必要がなく, それでいてシステム全体としては容易に必要な機能を追加できている. これは想定以上の恩恵を得ることができたように思う.

何より人的なインフラリソースが乏しい状況では, デプロイ作業の手間を最小限にする必要性を改めて感じたので, Erlangをクラスタ運用する場合にドメインロジックを切り離すのは良い選択肢だと思う.

メリット: 実行中のVM上でデバッグができるのは強い

Erlangにはパフォーマンスにそれほど影響なく, 実行中のプロセスの状態を見たりメッセージを取得したりすることができる機能がある.これが今回は思いの外役に立った.

今まで単に自己に完結したデバッグにのみ使用していたが, 自己に完結しない範囲のデバッグにも使えたことで思わぬ収穫があった.
クライアントの開発者から「なんかうまくいかないのだが何が悪いのか分からない」という質問が来ても, 実行して貰っている間にサーバー側の状態を確認して問題を解決するといったことができた. これによって, 今まで一つ一つビルド済みのアプリを貰って確認していたものが, Slackのやりとりだけで済ますことができたことを考えると非常にコストを下げられたように思う.
またコネクションが切れた場合の動作確認なんかは, Erlangプロセスを殺すことで行っていたので, こういう場面でも非常に便利だった.

メリット: 複数ノードのテストが書きやすい

確認したところ, ノードダウンや接続・再接続時の挙動のテストを書いているファイルは313行だった.
Golangで他のプロセスがいるテストを書いた時はdocker-composeを使ってビルド済みバイナリを使ってテストを書いたが, それよりも遥かに色々なケースがテストできていてこの行数で済んでいることを思うと, 間違いなくメリットだと思う.
Mac上で複数ノードのテストがうまく動作しなくてDocker上で走らせていたりはするのだが. (コンテナは1つ)

デメリット: Leader Electionがないのは面倒なことも多い

Leader Electionなしに実装はしたものの, それが逆に面倒になるケースがあった.
具体的にはグローバルロックが取れない為に, Publisherが複数になる場合が発生するという問題があった.
これは最終的に2人目のPublisherを検知したら片方のPublisherを落とすことで対応したが, その後もこれに起因する問題が出てきたのでグローバルロックの偉大さを噛みしめることになった.

リーダーをユニークに保つのも難しい問題なので, どっちもどっちな気はするが.

デメリット: 説明することは多い

更新頻度を下げる為に, ホットコードの選択肢を捨てないようにしていた. (無停止無更新なので一度もやっていないが, すぐにできるようにはなっている)
その為, あれこれ制約のもと実装していたが, その制約をインフラの人に都度説明するのが面倒ではあった.

それ以前にErlangクラスタの環境構築がまず面倒臭い. 「あーこれはホスト名が引けてないですね」「epmdというのがあって……」「ホスト名でノード解決できるようにする必要があって……」みたいなやりとりが発生した. それぞれがそれぞれの事情を知らないという状況で問題を解決する必要があるものだから, 説明することは多いと改めて感じた.

デメリット: 結局ライブラリは作る

Erlangのラリブラリは作らなくて済んだ一方で, Golangの実装で必要なものを作っていたら4つぐらいライブラリを作っていた.
なので, ライブラリ作らなくて済むというのは幻想だった.

まとめ

結論として良かったのか悪かったのかというと, 悪くはなかったという表現になるのではないだろうか.
振り返るとErlangのメリットを享受しつつ, デメリットを最小限にするという意味では, 想定した通りーーもしかすると想定以上に意味はあったように思う.

一方で, Erlangのメリットが不要であれば, こんな面倒な構成にする必要もなかったと改めて思った.
特に感じるメリットは複数ノードのテストだが, ここらへんがGolangでスンナリ書けるように私がなれば話は変わってくるのだろう.

もし似たようなことをやる機会があったら, 今回の経験を生かしてより良い形にしていきたいものだ.

(Updated: )

comments powered by Disqus