Elixir for Rubyists

なぜElixirか?

2005年にはDHHさんによってRailsが作られ、ウェブコミュニティーの中でブームになりました。それによりRails (とRuby) はウェブPHPやJavaなどのウェブプログラミング言語と比べられるような存在になりました。RailsはCoC(Convention over Configuration)という理念にそっているので開発時間が減って、生産性がとても高いです。一方、Railsにもデメリットがあります。Railsのコードベースが重いし、本番環境のメモリがすごいかかります。この問題を解決するため、監視プロセスがよく使われています。もしプロセスが多くのメモリを使うとそのプロセスを殺して再起動します。それは危険じゃないでしょうか?あと、Rubyは不変性を守らないから、並行処理の設定が難しいです。

Elixirとは

Elixir(エリクサー)はErlangのVM上で動くRuby風味の関数型言語です。ElixirはRailsのコアコミッターのJose Valimさんによって作られました。ですから、ElixirはRubyとRubyOnRailsに影響を受けています

実、Elixirは下の言語のメリットを受けつづけています

Erlangの信頼性とRubyの美しい文法とClosureの強い設定

Erlangで作られているので、まずErlangについて述べる必要があります

Erlangは1986年に生まれて、通信機械で使っていました。通信機械はリアルタイムと可用性の要求が高いので、Erlangの特徴は並行性と可用性が高いのとホットスワップと分散処理です。

Erlangは珍しい言語なんですが、SonyEricssonやAmazonやFacebookなども使っています。

よく言われる例はWhatsAppというサービスです。Erlangの上動いているWhatsAppは24コアのCPUで96GBもメモリのインスタンスで200万同時コネクションを処理が出来ます。WhatsApp使えば、サービスダウンやメンテナンスなどは全然見えてないです。素晴らしくないでしょうか?

けれども、Erlangは敷居が高いです、Erlangの敷居の高さは、文法やマナーが普通の言語と少し変わったところがあることと、ドキュメントがあまり整備されていない点(ライブラリとか)があり、実際の実装するコードに潜り込んでいく頻度が高い面にあると思います(ただ、後者に関しては他の言語にも言えそうです)。

ウェブアプリケーションを開発する上ではErlangは向いていません。 Erlangの強みを活かしたウェブアプリケーションを開発したいといいうことであれば、ElixirはPhenixの強力なフレームワークがあるので、Erlangを使うよりはElixirが良い、となるかと思います。

ElixirはBEAM(Bogdan/Björn’s Erlang Abstract Machine)と呼ばれるErlangVMで動作するから、Erlangの並行性と信頼性の特徴を受け継いでいる。 そして拡張機能としてマクロを使ったメタプログラミング等も扱うことが出来る。

上記の通り、ElixirはRubyのきれいな文法やエコシステムなどを受けつづています。Rubyと比べてみます。

機能 Elixr Ruby
ビルドツール mix rake
ライブラリ管理 mix bundler
標準レポジトリ hex.pm rubygems.org
インタラクティブシェル iex irb
テスト ExUnit minitest

文法は?

Ruby vs Elixir

にていますね?

Elixirの基本

この記事ではElixirの文法については深くは触れず、Elixirのメリットや特徴などだけを紹介したいと思います。

開発環境の準備

Railsの開発環境のよくやる手順

$ mkdir koala && cd koala
$ bundle init
$ vim Gemfile
source "https://rubygems.org"
gem "rails"

あとは

$ bundle install

Elixirも大体一緒です

$ mix new koala
$ vim mix.exs
defmodule Koala.Mixfile do
  use Mix.Project

  def project do
    [app: :koala,
     version: "0.1.0",
     elixir: "~> 1.4",
     build_embedded: Mix.env == :prod,
     start_permanent: Mix.env == :prod,
     deps: deps()]
  end

  # Configuration for the OTP application
  #
  # Type "mix help compile.app" for more information
  def application do
    # Specify extra applications you'll use from Erlang/Elixir
    [extra_applications: [:logger]]
  end

  # Dependencies can be Hex packages:
  #
  #   {:my_dep, "~> 0.3.0"}
  #
  # Or git/path repositories:
  #
  #   {:my_dep, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
  #
  # Type "mix help deps" for more examples and options
  defp deps do
    []
  end
end

あとは

$ mix deps.get

基本的な型

Rubyと比べてみます

Ruby Elixr
整数 123456789 123456789
123456789 1.2 1.2
文字 “bello” “bello”
アタム、シンボル :symbol :symbol
アレイ、タプル [1,2,3,4] {1,2,3,4}
連結リスト [1,2,3,4]
ハッシュ-、マップ {key: “value”} %{key: “value”}

 パターンマッチング

Elixirは代入演算子というものがない、代わりにパターンマッチングを使います。”=”という演算子です。左辺を右辺にマッチングしてみます。マッチしてない場合はMatchErrorが出てきます

iex> a = 1
1
iex> 1 = a
1
iex> 2 = a
** (MatchError) no match of right hand side value: 1

タブルやリストなども出来ます

iex> [a, b] = [1, 2]
[1, 2]
iex> [a, 2] = [3, 2]
[3, 2]
iex> a
3
iex> [a, 2] = [4, 3]
** (MatchError) no match of right hand side value: [4, 3]

ハッシュ−は

iex> %{key: a}  = %{key: 2}
%{key: 2}
iex> a
2
iex> %{key_a: a}  = %{key_b: 2}
** (MatchError) no match of right hand side value: %{key_b: 2}

その他、タプルは要素を連続したメモリ上に保存していて、インデックスによる要素をランダムアクセスできるのでとても早いです。一方、リストは連結リストなため、要素を順にたどる必要があり、タプルよりは低速になってしまいます。ですから、リストの「先頭要素」と「続くリスト」というマッチングが用いられます。

iex(10)> [head | tail] = [1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]
iex(11)> head
1
iex(12)> tail
[2, 3, 4, 5]

head | tail アクセスの仕方は連結をアクセスする一番良い方法です

関数

関数の文法はRubyと大体同じですが、仕組が違います。パターンマッチングの上、引数にマッチしている関数が呼ばれます。更に呼ばれる要件の「関数ガード」も取り入れられています。下の階乗関数を参照してください

defmodule Factorial do
  def of(0), do: 1
  def of(n), when n > 1 do:
    n * of(n - 1)
  end
end

パイプライン |>

Linuxを使う時、パイプラインはよく作られていますね。前の結果は次のコマンドの引数となります

ls -l | grep ex | wc -l

上記のような|>演算子が紹介されました。前の関数戻り値は次の関数の一番目の引数となります

a(b(c(d)) => d |> c |> b |> a

読みやすくて、関数形の言語のデータプロセシングが感じられますね。

Elixirの並行性・信頼性

上記通り、並行性と信頼性を紹介しないとElixirのメリットを理解出来ないんです

なぜElixirが速い?

並行のプログラムを設計する時、一番難しい問題は「データ共用」という問題です。「スレッドセーフ」のプログラムと呼ばれます。

データ共用問題

複数のスレッドが同時に読み込む時は問題ありませんけど、同時に書き込むとデータが壊れやすいです。これを防ぐため、Ruby、PythonともGILという仕組を使っています。

GILは「グローバルインタープリターロック」という仕組です。マルチスレッドのプログラムを一時点に一スレッドしか動かさないようにします。なので、CPU何コアがあってもシングルスレッドを比べるとスピードがあまり変わらないんです。GILはボトルネックになってしまいます。

一方、Elixirはデータ不可変言語なのでデータ共用問題がないんです。更に、スレッドがなくErlangプロセスを使います。Erlangプロセスはシステムのプロセスではなく、ErlangVM上動くプロセス。そのプロセスの起動にかかるリソースはほぼゼロだから、Elixirのプログラムは何千万のプロセスがあるのは全然普通です。

Erlangのスケジューラ = OSスレッドになっています。デフォルトではCPUコア1に対してスケジューラ1の割り当てになっています。Erlangプロセスはそのスケジューラによってプリエンプティブに仕事を割り振っていきます(中身の詳細は割愛します)。

特徴としては、OSプロセスとは異なり、通常のコンテキストスイッチが発生しないので切替コストが非常に低いという点です。また、Erlangプロセス1つに消費するリソースも低い(309ワード = 1ワード(約64bits))ので大量にプロセスを立ち上げることができる感じですね。

Elixrの各プロセスは、他プロセスとメッセージを送受信出来ます。他のプロセスは現在のホストのものではなくても大丈夫です。

メッセージで通信

99.9999999%の信頼性・ゼロダウンタイム

RailsのAppを運用する時開発ではGodなどの監視ツールをよく使っています。しかし、ロードが高い時はプロセスを殺して再起動するので、データがなくなってしまいます。そしてバージョンアップの時にも、再起動は必要がります。一方、落ちてはいけない通信機械のための設計なので、スーパーバイザーとコードチェンジの仕組を利用します。この仕組のお陰でデータは守られなくならないようになります。

Erlangのスーパーバイザーの特徴は、Erlangプロセスの再起動戦略と機能の保証する範囲(どの状態を正常とするか)を読みやすく書ける点だと思ってます。通信機器の要件として、確実に落ちてほしくないという点から、プロセスがどういった状態を安定状態とするか、を比較的容易に設計できるような言語設計(OTPがそのような感じにいなっている)になっています。

ベンチマークを見てみます

RubyといえばRailsがよく聞かれています。Elixirにもウェブフレームワークがあります。Phoenixというフレームワークです。Railsの構造と似ていますが、ベンチマークが10倍速くて、GoのGinほどのスピードになります

Framework Throughput (req/s) Latency (ms) Consistency (σ ms)
Gin 51483.20 1.94 0.63
Phoenix 43063.45 2.82 (1) 7.46
Express Cluster 27669.46 3.73 2.12
Martini 14798.46 6.81 10.34
Sinatra 9182.86 6.55 3.03
Express 9965.56 10.07 0.95
Rails 3274.81 17.25 6.88
Plug (1) 54948.14 3.83 12.40
Play (2) 63256.20 1.62 2.96

引用元に参照して下さい

デメリット

Elixirは速くて信頼度がたかいけれども、RubyとRailsに比べるとエコシステムのRubyGemsやコミュニティーなどはまだまだです。けれども、Elixirの考えが良いし、Elixirの将来が明るそうですから、一回試して下さい

カヤックではサーバーサイドエンジニアも募集しています!