『すごいErlang ゆかいに学ぼう』を読んだ ~Erlangの地獄の最下層まで進もう~

すごいErlang ゆかいに学ぼう』を読みました。すごいE本、Elixir学んでいると必然的に?いきつく先ですね。C++学ぶためにCを学ぶ、みたいな。

Programming Elixir、Elixir in Actionを事前に読んでいたので、内容の大半は頭の整理のためにさらっと読んだくらいでした。その中で、最後付近のCommon Testとかは目新しいものでした。王道としては、Programming Elixir + すごいE本、でしょうか。

※すごいE本、もとはこのURLのもの?: http://www.ymotongpoo.com/works/lyse-ja/

学習曲線

Elixir/Erlangの学習度合いがどのくらいかな、と思って残してみました。ここまでくるのにざっと平均して 3ヶ月 x 1h/day くらいかなーという個人的な感想です。この間に写経やら、ちょっとしたライブラリ作ったり。このくらいになれば、PhoenixやEcto、Elixirのコードを読みながら動作を理解できるようになるかな。という感じです。

Railsの経験が高かったり、Ruby、はたまた関数型の経験が高い人だとPhoenixやElixirをもっと効率的に学べそう。私は純粋なプログラマーの方々よりプログラミング能力はないのでこのくらいな感じです。


以下、この本を読んでちょくちょく気になったものをメモがてら残していきます。

atomに関して

  • atom
    • 結びつけたデータを表現したり制限したりするために使う
    • Tuple内で使う
    • atomはアトム表で参照される
      • 1 atom は 4byte(32bit OS), 8byte(64bit OS) 消費する
      • ガーベジコレクトの対象外
      • システムが落ちるか、1,048,577個のatomが宣言されるまで蓄積される(optionで変更は可能)
    • 動的に生成されるべきではない
    • 開発者のツール

末尾関数(tail recursion)

  • これは、tail recursionであることをこのまえのblogでもかいた => blog
  • 末尾呼び出しの場合、最適化処理が実施されるため、メモリの消費が増えない。
    これは TCO: Tail Call Optimizationとお晴れ、LOC: Last Call Optimizationの特殊な一例とのこと。LOCは、関数の末尾の式が他関数を呼び出すときに実施される、スタックフレームの保存を避けるという最適化。

エラーハンドリング系

  • 12章の、ビヘイビアなどのエラー関係は、Elixirやるにせよ重要そうですね。GenServer になる gen_server をもとにした解説などあるので、必要になるときには確認できるように覚えておこう。

ホットコードローディング

  • コードサーバという、ホットコードローディングをするためのESTを管理している仮想マシンの特殊なプロセスが存在するみたいです。これにより、動的にメモリ上で異なるバージョンの同一モジュールを保持しておき、あるタイミングで使用するモジュールを置き換える、といったことができるようですね。ここは、Erlangの動的型付け言語の恩恵がここで受けられている一因のようです。
    • このための処理系の詳細はこの書籍などにお任せ

アムダールの法則

システムを並列化したときに、どれくらい高速化できるか、それはどのくらいの部分を並列化した場合かを示す法則。

ここら辺で高速化の話が出ますが、結局は一連の処理のなかでクリティカルパスのなかにボトルネックが存在するばあい、そこを改善しないと全体の性能は向上しませんよね。

Supervisorのstrategyの差

多くの子プロセスを持つ場合、 simple_one_for_one のほうが断然早くなる

  • one_for_oneは、起動したすべての子プロセスのリストを起動した順で保持している
  • simple_one_for_oneは、1つの辞書としてすべての子プロセスの定義を保持してるだけ
    • Supervisor.start_link/2 の返り値が動的に追加された子のリストになる
    • simple_one_for_one のスーパバイザは、子プロセスの仕様をすべての子プロセスで共有する

simple_one_for_one 以外のstrategyにおいても、動的に子を追加できます。ただsimple_one_for_one に比べて管理もしにくいので操作頻度や要求頻度がそれほどない場合だけ使い、他は simple_one_for_one を使うほうがよさそうです。

Erlangの地獄の最下層

Erlangを学ぼうとしたばあい、このようにどんどん深層へと潜っていくようです。

http://www.ymotongpoo.com/works/lyse-ja/ja/25_leveling_up_in_the_process_quest.html#erlang

17 ~ 19章: Supervisorの話なんかが書かれているので、ここは押さえておくべき箇所かな、と思います。

Erlang Term Storageのデフォルト生成数

  • ETSは、デフォルトでは1400 tableしか生成できない。
  • 共有する領域なので、actor mdoelなどで保証しようとしていたプロセスの独立性とかを容易に大きく下げてしまう。手軽な性能向上を期待できるが、使い方には注意が必要。

分散アプリ構築のための、クラスタ生成とApplicationの設定

:global や、 :rpc など使ってからのクラスタ構成まではElixir in Actionにすでにあったので、こちらのメモに任せて少し流す感じで…
このクラスタ構成は、例えば論理構成でも区切られたネットワークでは対象できない。

OTPの分散アプリケーション構成の話は少し新しかったので、ちょっと備忘録。dist_ac のプロセスの機構。最後の案。複雑で管理しにくくなるので、最後の手段。

Elixirでは用意されていないらしく?、 :dist_ac で見てみると以下の感じで出てきました。

iex> :dist_ac.
code_change/3                       get_known_nodes/0
handle_call/3                       handle_cast/2
handle_info/2                       info/0
init/1                              load_application/2
module_info/0                       module_info/1
permit_application/2                permit_only_loaded_application/2
send_timeout/3                      start_link/0
takeover_application/2              terminate/2

以下、試しに module_info やってみたもの。

iex(10)> :dist_ac.module_info
[module: :dist_ac,
 exports: [start_link: 0, get_known_nodes: 0, init: 1, info: 0, handle_cast: 2,
  handle_call: 3, handle_info: 2, terminate: 2, code_change: 3, send_timeout: 3,
  module_info: 0, module_info: 1, permit_application: 2,
  permit_only_loaded_application: 2, takeover_application: 2,
  load_application: 2],
 attributes: [vsn: [123738990260421499065642176037586554143],
  behaviour: [:gen_server]],
 compile: [options: [{:outdir,
    '/private/tmp/erlang20150810-30915-7ryu7o/otp-OTP-18.0.2/lib/kernel/src/../ebin'},
   {:i,
    '/private/tmp/erlang20150810-30915-7ryu7o/otp-OTP-18.0.2/lib/kernel/src/../include'},
   :warnings_as_errors, :debug_info], version: '6.0',
  time: {2015, 8, 10, 17, 59, 53},
  source: '/private/tmp/erlang20150810-30915-7ryu7o/otp-OTP-18.0.2/lib/kernel/src/dist_ac.erl'],
 native: false,
 md5: <<93, 23, 67, 61, 227, 52, 148, 191, 172, 220, 196, 15, 130, 225, 77, 31>>]

Elixirとしては、以下のように Application には記載されていたので、

The type passed into start/2 is usually :normal unless in a distributed setup where applications takeover and failovers are configured. This particular aspect of applications can be read with more detail in the OTP documentation:

読んでみる。これは、 Application を使うときの、

defmodule MyApp do
  use Application

  def start(_type, _args) do
    MyApp.Supervisor.start_link()
  end
end

に対する説明で、このApplicationモジュールのstartに対する記述。

ここで、モジュールならとErlangの application の、 Module:start(StartType, StartArgs) -&gt; {ok, Pid} | {ok, Pid, State} | {error, Reason} を参考にすると、この _type には以下になる。

start_type() = normal
             | {takeover, Node :: node()}
             | {failover, Node :: node()}
  • lib/kernel-4.0/doc/html/application.html
Module:start(StartType, StartArgs) -> {ok, Pid} | {ok, Pid, State} | {error, Reason}

...

Common test

ElixirにはExUnitが提供されています。一方、ErlangにあるEUnitがこれに該当します。
Erlangではそのほかにcmmmon testといって、より大きな、nodeを対象としたテストを持っているようです。Erlangのモジュールなので、実際に使うことができるのかな。

Mnesia

トランザクションを搭載する、CAP定理のCPを主な焦点にしたデータストアとのことです。ETSがAPになりますね。

iexにおいて、 :etsでは補完が出るのですが :mnesia とかで補完されなかったので、Elixirでは提供されていないのかな。

dializer

楽観的な解析ツールです。Elixirだと、.beamのファイルを解析してみても、Unknown functions: と出るので完全には使えなさそうな感じです。


ざっと、大事そうなところ、Elixirに関係しそうなところのErlangや分散システムの話を読んでみました。

簡単なものなら読んだり書いたりできるようになったのであとはざっとコード読んだり、書いたりする段階かな。Erlangに対する興味が少しましたので、過去に触っていたTung(負荷テスト用ツール)とか少し調べてみようかな。

良い負荷ツールとか作れると役立ちそう。


2015/09/05 追記: Mnesiaについて

試しに、一度 :mnesia を宣言してみると使えました。なるほど!

iex> :mnesia. # 1度も宣言していない

iex> :mnesia # 1度、宣言する
iex> :mnesia.
abort/1                        activate_checkpoint/1
activity/2                     activity/3
activity/4                     add_table_copy/3
add_table_index/2              all_keys/1
all_keys/4                     async_dirty/1
async_dirty/2                  backup/1

...

uninstall_fallback/0           uninstall_fallback/1
unsubscribe/1                  wait_for_tables/2
wread/1                        write/1
write/3                        write/5
write_lock_table/1             write_table_property/2

iex> :mnesia.info
===> System info in version {mnesia_not_loaded,nonode@nohost,
                                {1441,440008,529525}}, debug level = none <===
opt_disc. Directory "/my_folder/Mnesia.nonode@nohost" is NOT used.
use fallback at restart = false
running db nodes   = []
stopped db nodes   = [nonode@nohost]
:ok
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s