Elixir1.0.5 は Erlang18 に未対応ではなかった

というのを初めて知った。

きっかけは、dialyxir を使おうと思ったから。

dialyxir をインストールする時の必ずエラーが発生しインストールに失敗する。 エラーの内容は以下の通り。

  Creating PLT /home/ymmtmsys/.dialyxir_core_18_1.0.5.plt ...
** (FunctionClauseError) no function clause matching in Keyword.get/3
    (elixir) lib/keyword.ex:118: Keyword.get(:no_such_file, :files, nil)
    lib/mix/tasks/dialyzer.plt.ex:132: Mix.Tasks.Dialyzer.Plt.core_plt_contains?/2
    lib/mix/tasks/dialyzer.plt.ex:123: anonymous fn/1 in Mix.Tasks.Dialyzer.Plt.missing_apps/0
    (elixir) lib/enum.ex:662: anonymous fn/3 in Enum.filter/2
    (elixir) lib/enum.ex:1261: Enum."-reduce/3-lists^foldl/2-0-"/3
    (elixir) lib/enum.ex:662: Enum.filter/2
    lib/mix/tasks/dialyzer.plt.ex:106: Mix.Tasks.Dialyzer.Plt.need_add?/0
    lib/mix/tasks/dialyzer.plt.ex:57: Mix.Tasks.Dialyzer.Plt.run/1

:no_such_file ということなのでファイルが存在していなくエラーになっているっぽいが。。。よくわからないのでググってみると こんなissueを発見。そしてコメントに

Elixir does not yet support Erlang 18.

と書かれているのを発見。

...あれ、今まで動いていたから気付いていなかったけど、まだ Erlang18 は対応してないっぽい。

Elixir の github を覗いてみると、確かにv1.2.0でErlan1 18.0に対応するようだ

とりあえず、自分の環境の Erlang のバージョンを下げよう。

追記 (2015/09/15)

インストールに失敗する原因は、Erlang のバージョンではなくメモリ不足によるものだった。 512Mのメモリを積んだVM上でインストールして失敗を繰り返していたたけれど、2Gに増やしたら成功した。 ちなみに1Gでは失敗した。

自分のPCは4GBしか積んでないので、VMに2Gも割けないのでインストールが終わったら通常の512Mに戻した。

追記 (2015/09/29)

https://github.com/elixir-lang/elixir/releases/tag/v1.0.5 を見るとErlang18.0 に対応したと書いてありました。

HTTPoisonのプーリング

HTTPクライアントにHTTPoisonを利用してみた。

同時実行数を増やすと50コネクション以上のコネクションは作成されないことがわかった。コードを見てみると、内部ではhackneyが使われていることがわかった。

hackney のドキュメントをみると、何も指定がなければデフォルトのプールが利用されるようだ(これ?)。

変更するためにはどうしたらよいかを調べた。

プールの数を変更する方法

名前をつけてプールを開始する。

poolname = :mypool
options  = [max_connections: 10]
:hackney_pool.start_pool(poolname, options)

HTTPリクエストを送る際のパラメータで、[hackney: [pool: :mypool]] を渡す。

HTTPoison.get!("http://localhost:4001", %{}, hackney: [pool: :mypool])

これでデフォルトではなく、自分の希望通りのプールサイズになる。

調べた時のライブラリのバージョン

  • httpoison, 0.7.2
  • hackney, 1.3.1

Phoenix で Poolboy を使ってコネクション(プロセス)プーリング

Poolboy を使ってコネクションプーリングができるようにする。接続先は memcached

memcached のクライアントにはmcdを使った。

サンプルのhello_phoenixを元にした。手を加えたファイルだけ以下に貼っておく (ちなみに Phoenix は v1.0.0)。

実行してみと、、、

% curl localhost:4000
{"val":"Hello Phoenix"}

成功。

メモ

  1. mcd はプロセスを生成した時点ではmemcached と接続されていない。ので、start_link してすぐに使おうとすると、結果が取れない。
  2. max_overflow の値を0以上にすると、プールしている数が size に達していてもさらにそこから max_overflow 分だけプロセスを生成する。
  3. max_overflow 分のプロセスは必要になった時点で生成されるので、01 で書いたとおり、コネクションが確立されていないプロセスが渡される時がある(というか、全てそうだった)。

感想

  • 既存ライブラリのおかげで簡単にプーリングできた。
  • Poolboy, mcd 両方とも、Erlang のライブラリ。Elixir を学ぶなら、Erlang を勉強しないとだめっぽい(動作を理解するのと、ライブラリの情報)。

Phoenixのデプロイ

Erlang (Elixir) の機能の中で気になっているのが、ホットデプロイだ。といっても、そういう機能があるからといって気軽にデプロイできるものでもないらしいけど。

デプロイに関するスライド。

デプロイ方法

Exrm

Exrm (Elixir Release Manager) を使うのがよさげ。 Phoenix 公式でも紹介されている。 設定方法などはそちらを参照。

パッケージ作成

MIX_ENV=prod mix release で本番環境用のリリースパッケージが rel/hello_phoenix/releases の下にバージョンごとに作成される。

起動
$ ./rel/hello_phoenix/bin/hello_phoenix start

停止するときは stop で。

更新

mix.ex に書かれているバージョンを上げ、MIX_ENV=prod mix release でパッケージを作成する。 (ここでは バージョンをv0.0.1 からv0.0.2に上るとする。)

v0.0.1のアプリが稼働中の状態でv0.0.2に更新するには

 rel/hello_phoenix/bin/hello_phoenix upgrade 0.0.2

で、OK。

自分の環境で試した限りでは、更新時に接続できないということはなかった。

mix phoenix.new のオプション

アプリケーション作成時のオプションに何があるか調べてみた(といっても、大して調べてないけれど)。

答えはここに

  • --app
    • アプリケーション名?よくわかってない。
  • --module
    • モジュール名を指定する。phoenix.new したときのディレクトリ名とモジュール名を異なる名前にできる。
  • --database
  • --no-brunch
    • brunchを使わないときに指定する。APIサーバとして使うならこれを指定した方がいいかも。
  • --no-ecto
    • Ectoを使用しない場合に使用。

Phoenix のアップデート

v0.17.0 がリリースされていたので更新してみる。フレームワーク(というか、Mixでインストールしたライブラリ)をアップデートするのは今回が初めて。

% mix archive.install https://github.com/phoenixframework/phoenix/releases/download/v0.17.0/phoenix_new-0.17.0.ez
Found existing archive(s): phoenix_new-0.16.1.ez.
Are you sure you want to replace them? [Yn] y
* creating .mix/archives/phoenix_new-0.17.0.ez

特別悩むことはなくアップデート完了。インストールと同じコマンドで大丈夫だった。

変更点は http://www.phoenixframework.org/blog/upgrading-from-v016-to-v017 に書かれている。

大きな変更はないけれど、個人的に良かったと思う点は、パラメータに %{"format" => "html"} が追加されなくなったところ。jsonしか返さないAPIサーバを作る場合とか邪魔だったので。

求めていたのはinspect/2だった

to_string/1 ではなくinspect/2 だった。

iex(1)> to_string %{a: 1, b: "hoyhoy"}
** (Protocol.UndefinedError) protocol String.Chars not implemented for %{a: 1, b: "hoyhoy"}
    (elixir) lib/string/chars.ex:3: String.Chars.impl_for!/1
    (elixir) lib/string/chars.ex:17: String.Chars.to_string/1
iex(1)> inspect %{a: 1, b: "hoyhoy"}
"%{a: 1, b: \"hoyhoy\"}"

Map には to_string が定義されていないので、どうすればログに出力できるのかを悩んでいたのだけれど、これで解決。