ABEJA Tech Blog

中の人の興味のある情報を発信していきます

ABEJA Platform を支える技術: Elixir Language

はじめに

こんにちは!ABEJA でプロダクト開発を行っている森永です。

本日は、 ABEJA Platform のゲートウェイにて利用されている言語、 Elixir Language (以下、Elixir と表記) を紹介できればと思います。 利便性の高い言語である一方、日本語での情報が未だ少なく、こちらの記事が言語の認知を広げる一助になれれば嬉しいなと思っています。

Elixir の概要

Elixir とは?

Elixir は、スケーラブルで保守しやすいアプリケーションを構築するために設計された動的な関数型言語で、Jose Valim 氏によって開発されています。その最大の特徴として、低遅延で分散型の耐障害性のシステムを兼ね備えた Erlang VM *1 を活用し、ウェブ開発や組み込みソフトウェアの分野でも成功を収めています *2

また、 Elixir は 2012 年に登場した比較的新しい言語ですが、他の人気言語と同等に開発周りのツール (パッケージ管理機能、テスト ライブラリ等) も充実しており、その強力な同時実行処理能力によってプログラミングの世界で特異な存在となっています。

Elixir の事例

Elixir は大規模なトラフィック下で、リアルタイム性と安定性を求められるような環境で積極的に活用が進んでいます。 いくつか事例を調べてみましたので、こちらで紹介します。

Pinterest では Elixir を使用して、数百万のユーザーにタイムリーな通知を効率的に配信する堅牢な通知システムを、 Java から Elixir に移行して、 コード量を 1/10 に、 サーバー数を半分 にして 処理速度と安定性も向上 させたとのことです *3

同様に、Discord では Elixir を利用して、 1200 万以上の同時接続ユーザー が生成する 秒間 2600 万以上の WebSocket イベント トラフィック を処理しています *4

また、弊社でも利用しているインシデント管理プラットフォームを提供する PagerDuty では、 Ruby on Rails と Apache Kafka の間に入るミドルウェアを構築する言語として採用され、現在では多数の機能で積極的に Elixir を活用しているそうです *5 *6

ABEJA Platform でも、 2017 年頃に自前の API Gateway を構築するために Elixir が採用され、現在では毎日数十 TB のデータを安定的に処理してくれています。

Elixir の魅力

先日発表された StackOverflow の 2024 年版のサーベイを見ますと、 Elixir の admired (「この技術を使ったことがあり、使い続けたいと思っている」) 割合は、 トップの Rust 82.2 % に次いで 76.8 % であり、扱ったことがあるエンジニアの間では非常に高く評価されていることが窺えます *7

ここまで大きい割合のエンジニアから評価がされる要因が何であるかを考えると、なかなか 1 つに絞りにくいと感じましたので、まとめてみました。

まずは、Elixir はシンプルで表現力豊かな構文と高いスケーラビリティを兼ね備え、生産性とコードの保守性を向上させられる点でしょうか。加えて以下にあるような、 Elixir の特徴もあると考えています:

  • 分散処理へのオーバーヘッドの少なさ: プロセスが軽量で同時に実行でき、多数のタスクを効率的に処理できます。
  • 耐障害性の高さ: OTP が提供する Supervisor (プロセス監視機能) により、障害からの回復を自動的には行い、システムの稼働を続けます。
  • 関数型言語としてのメリット: Elixir は関数型言語であり、副作用を最小限に抑えることでバグを減らし、テストと保守が簡単になります。
  • Erlangとの相互運用性: Elixir は Erlang と完全に互換性があり、豊富なライブラリとアプリケーションのエコシステムにアクセスできます。
  • 活発なコミュニティ: Elixir はサポートと成長を続けるコミュニティがあり、継続的な開発に貢献しており、堅牢なドキュメントとツールを提供しています。

Elixirの始め方

導入の流れ

ABEJA Platform での導入のきっかけとしては、AWS の API Gateway では要件を満たせなくなり、自前の API Gateway を構築する必要性が出てきたたため、この API Gateway を支えるのに耐えうるスケーラビリティと高い可用性を持った言語を選定した結果、 Elixir を採用しています。

ただし、弊社のように実際に Elixir をサービスの構築段階で活用する場合は少なく、前述の Pinterest や PagerDuty での事例のように、スケーラビリティや保守性に問題が出てきた既存のシステムをリプレースする機会に、初めて Elixir を使ってみること検討してみる、かといった流れ思います。

そのような方に向けての情報が何か無いかを調べてみましたところ、移行の実例における話も交えて、 Elixir を本格的に導入するための手順を手取り足取り記載しているこちらの本がとても役に立ちそうであると思いましたので、是非手に取っていただければと存じます。 (とはいえ残念ながら物理本の入手は日本国内だと困難だと思いますが...)

pragprog.com

具体的な本の内容としまして、チームに Elixir の知名度と利便性を理解してもらい普及させて、社内教育体制を構築するまでの戦略から、コードの一貫性を保つためのベスト プラクテイス、さらには本番環境へのリリース戦略やパフォーマンス計測の心得等詳しく記載されています。(難点なのは和訳が存在していないことですね)

開発のツール周り

ある程度慣れた言語から他の言語を使い始める際に、混乱するのが開発ツールだと思いましたので、以下の表に人気言語と Elixir で開発に利用されるツールの比較表を作成してみました。

Elixir ではデフォルトでパッケージ マネージャーの Hexテスト ライブラリの ExUnitフォーマッターの Mix Format がついてくるのがとても便利です。 実際、ゲートウェイの開発に必要な外部ツールは少なく、サーバにデプロイするときもビルド結果と Erlang VM を Docker イメージとしてパッケージングして、各インスタンスに展開するだけだったりします。

ツール Elixir Python Javascript
バージョン管理ツール asdf pyenv/conda/asdf nvm *8
パッケージ マネージャー Hex Pip NPM/Yarn
テスト ライブラリ ExUnit PyTest Jest/Mocha
リント ツール Credo Pylint/Flake8 ESLint
フォーマッター Mix Format Black Prettier
デバッガ IEx/dbg PDB IDE提供のデバッガ
ドキュメント生成ツール ExDoc Sphinx JSDoc

基本構文とデータ型

Elixir の構文は慣れれば意外にもシンプルかつ直感的であり、特に Ruby に慣れた開発者は比較的とっつきやすい言語であると巷では言われています。

(筆者は Ruby を利用した経験はないのですが、過去に Ruby 経験のある弊社のエンジニアは皆、 「Elixir は Ruby が理解できていれば容易い言語だ」 と言及していました。)

例えば、基本的な型である変数、文字列、リスト/タプル、マップは他の言語と記法はほぼ変わりません。

ただし、 Elixir 独自の型としてatomkeyword listrangeがあります。

  • atom は名前自体が値となる定数型で、接頭辞:で定義されます。例 :ok
  • keyword list は key-value を連結リストで複数保持することができ、関数にオプションをお渡す時に使われます。例 [{:optionA, true}, {:optionB, false}]
  • range は整数の並びを min..max//step の形式で扱える便利な型です。Python の range 関数と似ていると理解して問題ないかと思います。例 1..10//2 (1, 3, 5, 7, 9 の数列)

関数とモジュール

先ほど慣れればシンプルかつ直感的であるとはお伝えしてしまったのですが、関数型言語特有の癖はやはりあり、オブジェクト指向の言語しか経験がなかった私もやや苦戦しました。 しかし、これは Elixir に限らずどの関数型言語でも起きうりますし、その中でも Elixir は教材が少ないものの 1 つ 1 つの完成度が高く、関数型言語初学者向けに丁寧に記載しているので理解しやすいように感じました。(この記事で紹介している教材も、なるべくわかりやすいと感じたものを厳選しています)

せっかく関数型言語についても触れさせていただいたので、少しだけコードも交えて Elixir での関数の書き方を紹介できればと思います。

関数は Elixir のモジュール内で定義され、機能によってグループ化できます。このカプセル化により、コードの整理とアプリケーションの異なる部分での機能の再利用が容易になります。

  • モジュールの定義:
defmodule Greetings do
  def greet(name) do
    "Hello, #{name}!"
  end
end
  • 関数の呼び出し: 関数を呼び出すには、モジュール名と関数名を参照するだけです。
Greetings.greet("Alice")
  • プライベート関数: モジュール内でのみアクセスできる関数が必要な場合、defpを使用して定義できます。

  • パターンマッチング: Elixirの関数はパターンマッチングをサポートしており、引数から変数を簡単に構造化および割り当てることができます。

def fetch_value(:ok, value) do
  "Fetched: #{value}"
end
def fetch_settings(:error, reason) do
  "Error: #{reason}"
end

他にも、パイプメタプログラミング等の便利機能があるのですが、実際に調べてみてその利便性を体感してもらえると嬉しいなと勝手ながら思っています。

高度なElixirの概念

最後に、この言語を特徴づける分散並列処理への特性と、耐障害性の高さを実現させているコンポーネントを紹介させてください。

並行性と並列処理: Erlang VM

Elixir は高い並行性を必要とするアプリケーションを構築する際に優れており、スレッド処理に伴う典型的なオーバーヘッドなしに並列プロセスを効率的に実行できます *9

これは Erlang VM 上で実現された Elixir の軽量プロセスモデルを通じて実現されており、プロセスは Erlang VM によって管理され、互いに独立して実行され、メモリを共有せず、メッセージ パッシングのみで通信します。

このモデルは、状態管理とレース コンディションに関連する複雑さを大幅に軽減し、多数のタスクを同時に実行することをシンプルかつ効率的にします。

Elixir の並行モデルを活用することで、多様な条件下で高い性能を発揮し、負荷の変化にシームレスに適応し、基礎プロセスの複雑さにもかかわらず、メンテナンスが容易なアプリケーションを構築できます。

耐障害性の高さ: OTP (Open Telecom Platform)

Open Telecom Platform (OTP) は、Erlang に不可欠なミドルウェア、ライブラリ、およびツールのコレクションです。Elixir は Erlang VM 上に構築されているため、OTP の機能をフルに活用して信頼性の高いフォールト トレラントなシステムを構築するための強力なツールとなります。

OTP は、Supervisor や GenServer などの主要コンポーネントを含む、並行および分散アプリケーションの開発フレームワークを提供します。

Supervisor は前述の通り、他のワーカープロセスを監視し、クラッシュした場合に再起動するプロセスであり、Elixir の回復力を支える技術の 1 つです。

GenServer は、バックグラウンド タスクを処理し、効果的に状態情報を維持するサーバーの実装で、k8s より粒度の細かいマクロ サービスといった捉え方が良いのかもしれません。

これらの OTP コンポーネントを理解し活用することで、堅牢で回復性が高いアプリケーションを容易に構築できます。

まとめると、Elixir はウェブ開発において多くの利点を提供し、特に性能、効率性、保守性を求める場合には、魅力的なプログラミング言語です。OTP やその並行モデルなどの高度な Elixir の概念を習得することで、堅牢で安定した応答性の高いアプリケーションを提供することができます。

Further Learning

もし Elixir に少しでも興味を持ったのであれば、まずは基本を習得してみるために次の資料で勉強してみることをお勧めします。

英語に抵抗がなければ公式ドキュメントを読んでいただき [LEARNING] に記載のある書籍等を手に取っていただくのもよいかと思います。 日本語で学びたいという方は、こちらのサイトが非常に丁寧に作られていますので、ぜひご利用してみてください。

We Are Hiring!

ABEJAは、テクノロジーの社会実装に取り組んでいます。 技術はもちろん、技術をどのようにして社会やビジネスに組み込んでいくかを考えるのが好きな方は、下記採用ページからエントリーください! (新卒の方のエントリーもお待ちしております)

careers.abejainc.com