On Blahfe

LYSE本を読む

blog/backenderlangelixir

Elixirの存在を知ったのが2014年7月14日。それからおよそ3年経つというのに一向に理解した気になれないでいます。特にSLAナイン・ナインはなぜその数値なのか腑に落ちないでいました。今回『Learn You Some Erlang for Great Good!(通称LYSE本)』を読むことで積年の謎を解明してみようと臨みました。

PROBLEM

  • Elixirをさわりはじめてしばらく経つけどふかく理解した気になれない
  • Phoenixやほかのフレームワークに頼られないケースが出てきたとき自由な発想ができるようになっておきたい
  • 巷でいわれているSLAナイン・ナイン(99.9999999%)などの実際がどうなのか腹落ちしてない

TLDR

  • 下記Erlang機能を中心に高いSLAを提供する素地をなしている
    • 分散システム Erlang Port Mapper Daemon(EPMD)
      • 他の言語・フレームワークが「分散コンピューティングの落とし穴」「CAP定理」にどのように対応している比較すると、Erlangの特徴がより見えてくる。
      • EMPDの特徴
        • 耐障害性(スケーリングはやや弱い)
        • マルチプロセス
        • ネットワークの障害監視
    • ライフサイクル、再起動戦略を備えたライブラリ(分散OTP)
    • CPシステムでNoSQLデータベース「Mnesia」

SOLUTION

というわけで、「LYSE本」を読むことにしました。各セクションにはElixirに関係ありそうな箇所を抜粋しています。長い長いメモになるので、TLDRだけで済ませて問題ありません。結論、輪郭は見えてきましたがまだその探求の入り口に来たに過ぎないのだということは理解しました。

1-3 Erlang概要

1.2 Erlangって何?

  • 関数型言語
    • 純粋主義(参照透過性、破壊的データを避けるなど)に従いつつ、実世界で問題が発生した場合はそれを取り払う
  • アクターモデル
  • 開発環境
    • クロスプラットフォーム
      • BEAM
    • 開発ツール
      • コンパイラ
      • デバッガ
      • プロファイラ
      • テストフレームワーク
  • ライブラリ
    • OTPフレームワーク
    • Webサーバー
    • パーサジェネレータ
    • Mnesiaデータベース

1.3 Don't drink too much Kool-Aid

  • 軽量プロセスによるスケール
    • タスクを細かく分けすぎる=むやみに並行処理させると処理速度に影響がでる
  • CPUコア数によるスケール
    • すべてを同時に稼働させることができない
  • 技術領域
    • 適切でない技術領域
      • 画像処理
      • 信号処理
      • OSのデバイスドライバ
    • 適切な技術領域
      • 巨大なサーバソフトウェア - QMS, MapReduce
      • 多言語との接続
      • 高レベルプロトコルの実装
      • Ex:
        • IANOというUNICTチームが作成したロボット
        • Wings 3D

3.2. 変化できない変数

  • パターンマッチング(= 演算子)
    • 比較の役割も果たしている
      • 値が違っていたらエラーを出す
      • 値が同じだったら当該の値を返す
erlang
> 47 = 45 + 2.
> 47 = 45 + 3.
** exception error: no match of right hand side value 48
  • アンダースコア変数(_)
    • 使用はできるが値の格納はできない
erlang
> _ = 14+3.
17
> _.
* 1: variable '_' is unbound

3.3. アトム

  • アトムと予約語
    • いくつかのアトムは予約語
      • after and andalso band begin bnot bor bsl bsr bxor case catch cond div end fun if let not of or orelse query receive rem try when xor false true

3.4. ブール代数と比較演算子

  • false trueはアトムなので数値の代替にはならない
  • アトムなどのほかの型も比較対象になる
    • number < atom < reference < fun < port < pid < tuple < list < bit string
erlang
> 0 == false.
false
> 1 < false.
true

3.8. ビット構文!

  • Erlangはおもいデータを数値処理するにはむいてない
  • 一方、数値処理が必要ないアプリケーションの中では速い
    • 次のような処理にむいている
      • イベントに反応する
        • イベントをミリ秒単位でしょりできリアルタイムアプリケーションに適している
      • メッセージングパッシング
        • アトムをつかうと軽く処理できる
  • 軽量なビット文字列
    • Pros
      • リストで表現する文字列は1文字につき1ノード
      • ビット文字列はC言語の配列のようなもの - <<"this is a bit string!">>
    • Cons
      • パターンマッチなどの捜査の際に単純さが失われる

4-5 パターンマッチング

4-5章からIDEがないとreplなどに時間がとられるので整備しておこう。Emacsならerlang.elがある。インデント、フィルコメント、コメントアウト、OTPなどのscafold、Eshell、コンパイル等ひととおりそろっている。

5.5. 関数呼び出しによるパターンマッチガードはcase文よりも優れているのか?

まず、パフォーマンス上かわらない。

つぎに、引数が複数あるときは関数をつかう。

erlang
beach(Temperature) ->
    case Temperature of
        {celsius, N} when N > 20 andalso N =< 45 ->
            'favorable';
        {kelvin, N} when N >= 293 andalso N =< 318 ->
            'scientifically favorable';
        {fahrenheit, N} when N >= 68 andalso N =< 113 ->
            'favorable in the US';
        _ ->
            'avoid beach'
    end.

上記のようだと可読性がさがる、冗長的。以下のように関数でまとめる。

erlang
beachf({celsius, N}) when N >= 20 andalso N =< 45 ->
    'favorable';
beachf({kelvin, N}) when N >=293 andalso N =< 318 ->
    'scientifically favorable';
beachf({fahrenheit, N}) when N >= 68 andalso N =< 113 ->
    'favorable in the US';
beachf(_) ->
    'avoid beach'.

ただし、引数が評価関数の対象の場合はcase文が向いている。

erlang
prepend(X, []) ->
    [X];
prepend(X, Set) ->
    case lists:member(X, Set) of
        true  -> Set;
        false -> [X | Set]
    end.

6-11 文法

  • 今回は標準文法について
    • Erlangの特徴はざっと次の通り
      • 型変換の関数が素朴
        • erlang:<type>_to_<type> という形式をとっているため、型が追加されるたびに変換用関数を BIF (built-in function) に追加しなければいけない
      • 再帰、無名関数、エラーは普通、Ruby, JSっぽい
      • レコードによってインターフェイスを定義できる
      • データ構造にキーバリューストア、セット、配列、有効グラフ、キューがある
        • ただ配列は、他の手続き型言語の配列とは逆に、一定時間での挿入や検索ができない
          • Erlangでなされるプログラミングスタイルでは配列や行列と結びつける必要がなく、実際にはめったに使われないため

7. 再帰

  • lists モジュール
    • sort/1
    • join/2
    • last/1
    • flatten/1
    • all/1
    • reverse/1
    • map/2
    • filter/2
  • gb_tree モジュール
    • lookup/2
    • map/2

8.2. 無名関数

erlang
> (fun() -> a end)().
a
> lists:filter(fun(X) -> X rem 2 == 0 end, lists:seq(1, 10)).
[2,4,6,8,10]

9. エラー

コンパイル時エラー

type error description
Module Module name 'madule' does not match file name 'module' -module 属性内に書いたモジュール名がファイル名と一致していない
Function Warning: function somefunction/0 is unused 関数を公開していない、あるいはその関数が使われている場所が間違った関数名やアリティになっている
Function function somefunction/1 undefined 関数が存在していない: -export 属性内あるいは関数を宣言するときに間違った関数名やアリティを書いてる
Function head mismatch 関数定義を他の関数での先頭の節の間に差し込んでいる
Syntax syntax error before: 'SomeCharacterOrWord' Ex: 括弧の閉じ忘れやタプルやおかしな式接尾辞、予約語・おかしな文字コードにエンコードされたUnicode文字の使用
Syntax syntax error before: 行末がおかしい
Variable Warning: this expression will fail with a 'badarith' exception Ex: llama + 5
Variable Warning: a term is constructed, but never used 関数の中に、リス作成、タプル宣言、どんな変数にも束縛されていない無名関数・自身を返す無名関数の宣言がある
Variable Warning: variable 'Var' is unused 使わない変数を宣言している
Variable Warning: this clause cannot match because a previous clause at line 4 always matches モジュール内で定義された関数が catch-all 節のあとに特定の節を持っている
Variable variable 'A' unsafe in 'case' case ... of の中で宣言されている変数を、その外側で使っている

ランタイムエラー (exception error)

type error description
function clause no function clause matching somefunction 関数内のすべてのガード節で失敗、あるいはすべてのパターンマッチで失敗
case clause no case clause matching 'value' 条件の書き忘れ、誤った種類のデータ送信、 catch-all 節が必要な場合
if clause no true branch found when evaluating an if expression 条件の書き忘れ、 catch-all 節が必要な場合
badmatch no match of right hand side value 無理なパターンマッチ、変数束縛の繰り返し
badarg bad argument 誤った引数の呼び出し
undef undefined function somefunction 未定義関数の呼び出し
badarith bad argument in an arithmetic expression 誤った算術演算
badfun bad function 変数を関数として呼び出した場合
badarity interpreted function with arity 1 called with two arguments 誤ったアリティ

例外処理

erlang
1> erlang:error(badarith).
** exception error: bad argument in an arithmetic expression
2> erlang:error(custom_error).
** exception error: custom_error

11.2 レコード

erlang
-record(robot, {name, type=industrial, hobbies, details=[]}).
erlang
5> Crusher = #robot{name="Crusher", hobbies=["Crushing people","petting cats"]}.
#robot{name = "Crusher",type = industrial,
       hobbies = ["Crushing people","petting cats"],
       details = []}
6> Crusher#robot.hobbies. %% 参照
["Crushing people","petting cats"]
7> NestedBot = #robot{details=#robot{name="erNest"}}.
#robot{name = undefined,type = industrial,
       hobbies = undefined,
       details = #robot{name = "erNest",type = industrial,
       hobbies = undefined,details = []}}
8> NestedBot#robot.details#robot.name. %% ネスト参照
"erNest"

11.3. キーバリューストア

キーバリューストアは4つのモジュールで提供されている。

module methods description
proplist get_value/2, get_all_values/2, lookup/2, lookup_all/2 少量データ向け, 緩いデータ構造
orddict store/3, find/2, fetch/2, erase/2 少量データ向け(75要素くらい)
dict store/3, find/2, fetch/2, erase/2, map/2, fold/2 大量データ向け、ordict と同じインターフェイス
gb_trees enter/2, lookup/2, delete_any/2, insert/3, get/2, update/3, delete/2 大量データ向け、データ構造の入出力がわかるスマートモードとわからないネイティブモードがあるため状況に応じて安全性とパフォーマンスのバランスを取ることができる

11.5.セット

セット(集合)は4つのモジュールで提供されている。

module methods description
ordsets new/0, is_element/2, add_element/2, del_element/2, union/1, intersection/1 少量セット向け、遅いが可読性が高い
sets - 大量セット向け、ordsets と同じインターフェイス
gb_sets - 大量セット向け、スマートモード・ネイティブモードがあるため状況に応じ安全性とパフォーマンスのバランスを取ることができる
sofs - 一意な要素の集合だけではなく、数学的な概念として集合が必要な場合

11.6. 有効グラフ

有効グラフは2つのモジュールで提供されている。

module description
digraph 生成、変更、エッジ・辺の操作、経路・周の検索
digraph_utils グラフの誘導、木のテスト、隣接ノードの検索

11.7. キュー

queue モジュール

api methods description
Original API new/0, in/2, out/1 キューの作成・削除
Extended API get/1, peek/1, drop/1 キューの中身の確認
Okasaki API - 関数型データ構造にもとづく

12-13 並列性について

12.2.1. 並列性の概念

  • Erlangの開発の背景
    • 大抵の人は並行ソフトウェアを書くことにうんざりしてる
      • 並行の解決策のほとんどがロックやミューテックスと呼ばれる小さな理論を扱うことに終始する
    • 通信業界では並行性・並列性をめざす文化があった
      • PLEX
      • AXE
  • 満たすべき要求
    • スケーラビリティ
      • リニアスケールが可能
      • 実装方針
        • 小さなプロセス
        • プロセスに共有メモリの使用を禁じる
    • フォールトレランス (交換機上の何千ものユーザをサポートすること)
      • クラッシュが可能
      • クラッシュ後リスタートが可能
      • 実装方針
        • 分散
        • 非同期メッセージングパッシング
  • 実装
    • 並列・並行に最適化されたVM
      • コア1つに対してスケジューラとして1つのスケジューラを起動
      • 実行キューにあるタスクが多すぎた場合、他スレッドのスケジューラに移される
      • 過負荷なプロセスに対して送れるメッセージ量を制御

12.3. すべてが線形にスケールするわけではない

  • アムダールの法則
    • 50%並行にしたコードは2倍ほど速くなる
    • 95%並行にしたコードは20倍ほど速くなる

14 プロセス間通信

  • 今回は2つのプロセス間通信のプロセス管理について
    • 片方のプロセスがエラーになっても、もう片方のプロセスが死なないようにする
      • プロセスがエラーになったものはクラッシュさせる
        • クラッシュしたプロセスをリスタートさせる
      • また、そのクラッシュ情報をもう片方にメッセージとして送る

エラーとプロセス

以下、3つの関数をつかい実装する。

  1. erlang:exit(Pid, Why) - Pid に対して自分が異常終了で死んだとつたえる。自身は死なない。
  2. erlang:process_flag(trap_exit, Value) - Value が true ならシステムプロセスとなり、 false ならシステムプロセスでなくなる。デフォルトは false。この関数によりクラッシュしたプロセスをリスタートさせる
  3. erlang:register(Atom, Pid)erlang:make_ref() - register/2 で予測不可能な PidAtom として登録し、make_ref/0 で生成された Reference をもとにメッセージ送信をおこなう。なお、ここで生成される Reference は同一Erlang VM上、あるいはクラスタリングしたVM上のみでユニークになる。
erlang
start_critic2() ->
    spawn(?MODULE, restarter, []).

restarter() ->
    process_flag(trap_exit, true),
    Pid = spawn_link(?MODULE, critic2, []),
    register(critic2, Pid),
    receive
        {'EXIT', Pid, normal} ->   % not a crash
            ok;
        {'EXIT', Pid, shutdown} -> % manual termination, not a crash
            ok;
        {'EXIT', Pid, _} ->
            restarter()
    end.

judge2(Band, Album) ->
    Ref = make_ref(),
    critic2 ! {self(), Ref, {Band, Album}},
    receive
        {Ref, Criticism} ->
            Criticism
    after 2000 ->
            timeout
    end.

critic2() ->
    receive
        {From, Ref, {"Rage Against the Turing Machine", "Unit Testify"}} ->
            From ! {Ref, "They are great!"};
        {From, Ref, {"System of a Downtime", "Memoize"}} ->
            From ! {Ref, "They're not Johnny Crash but they're good."};
        {From, Ref, {"Johnny Crash", "The Token Ring of Fire"}} ->
            From ! {Ref, "Simply incredible."};
        {From, Ref, {_Band, _Album}} ->
            From ! {Ref, "They are terrible!"}
    end,
    critic2().
erlang
27> functions:start_critic2().
<0.125.0>
28> functions:judge2("The Doors", "Light my Firewall").
"They are terrible!"
29> exit(whereis(critic2), kill).
true
30> whereis(critic2).
<0.129.0>
31> functions:judge2("The Doors", "Light my Firewall").
"They are terrible!"

15 アプリケーションを作成する

リマインダーアプリケーション

  • 仕様
    • イベントを追加
      • イベントには締切り(警告する時間)、イベント名、詳細が含まれます
    • イベントの時間が来たら警告
    • イベント名でイベントをキャンセル
    • 永続的なディスク保存先を持たない
        • これは今回見ようとしているのアーキテクチャの概念を示すのに必要ありません
        • これは実際のアプリケーションを作るなら全然ダメなことですが、代わりにもし実装したくなったらその機能はどこに挿入されるかを示して、ちょっとだけその実装の手助けになる関数も示します
      • 永続化保存先がないとした場合、実行中にコードを更新しなければいけません
    • ソフトウェアとのやりとりはコマンドライン経由で
      • あとでこれは拡張できる
        • たとえばGUI、Webページアクセス、IMソフト、メールなど
  • ユースケース
    • Client
      • イベントサーバをサブスクライブして、メッセージとして通知を受ける
        • こうすることで、すべてイベントサーバをサブスクライブしている多くのクライアントを設計するのが簡単になる
        • 各クライアントは潜在的に、前述した異なるゲートウェイとなる
          • GUI、Webページ、IMソフト、メールなど
      • サーバにイベントを詳細情報とともに追加するように依頼
      • サーバにイベントをキャンセルするように依頼
      • サーバを(落ちたかどうか知る為に)監視
      • 必要があればイベントサーバを終了
    • Event Server
      • メソッド
        • クライアントからのサブスクライブを受取
        • イベントプロセスから出された通知を各サブスクライバに転送
        • イベントを追加するためにメッセージを受取
          • 必要なX, Y, Zプロセスを起動
        • イベントキャンセルのメッセージを受取
          • イベントプロセスを殺す
        • クライアントから終了できる
        • シェルから自分自身のコードを再読込出来る
      • Process X, Y, Z
        • 発報が待たれる通知を表す
          • 天気にはイベントサーバにリンクされたタイマーに過ぎない
        • 時間が来たらイベントサーバにメッセージを送る
        • キャンセルのメッセージを受け取って死ぬ
  • 実装
    • ディレクトリ構成
      • ebin/
      • include/
      • priv/
      • src/
        • event.erl - event module
          • loop/1
          • normalize/1
          • start/2
          • start_link/2
          • cancel/1
          • time_to_go/1
          • init/3
        • evserv.erl - event server
          • loop/1
          • valid_datetime/1
          • valid_time/1
          • valid_time/3
          • send_to_clients/2
          • start/0
          • start_link/0
          • terminate/0
          • subscribe/1
          • add_event/3
          • add_event2/3
          • cancel/1
          • listen/1
          • init/0
        • sup.erl - supervisor
          • start/2
          • start_link/2
          • init/1
          • loop/1
      • Emakefile
        {'src/*', [debug_info,
                   {i, "src"},
                   {i, "include"},
                   {outdir, "ebin"}]}.
        
sh
$ erl -make && erl -pa ebin/
Erlang/OTP 19 [erts-8.1] [source] [64-bit] [smp:2:2] [async-threads:10] [kernel-poll:false]

Eshell V8.1  (abort with ^G)
1> evserv:start().
<0.59.0>
2> evserv:subscribe(self()).
{ok,#Ref<0.0.2.84>}
3> evserv:add_event("Hey there3", "test", { {2017, 7, 9}, {1, 23, 59} }).
ok
4> evserv:listen(2000).
[{done,"Hey there3","test"}]

TIPS

  • Erlangのタイムアウト値はミリ秒でおよそ50日に制限されている

16 OTP

OTPとはなにか

  • Erlangの特徴
    • 並列・分散
    • エラー処理
  • 組込関数
    • リンク
    • モニター
    • タイムアウト
    • 終了の補足
  • OTP
    • 下記のようなバグになりやすい箇所をモジュールとして吸収
      • プロセスの順番
      • 競合条件をどのように避けるか
        • プロセスはいつでも死ぬ可能性があること
      • ホットコードローディング
      • 名前付きプロセス
      • Supervisor
    • Pros
      • バグを解決するための時間を大幅に減らせる
      • 個々のモジュールのテストが行いやすい
      • 個々のモジュールの最適化が利用者全員の恩恵となる

17 gen_server

今回はよくつかわれるビヘイビア gen_server 。この汎用化によって、メンテナンス、コードの単純化、テストの簡易化へとつながる。下記に gen_server のコールバックの概要を記す。

クライアントとサーバ

  • gen_server
    • init/1
      • Return
        • {ok, State}
        • {ok, State, TimeOut}
        • {ok, State, hibernate}
        • {stop, Reason}
        • ignore
    • handle_call/3
      • Params
        • Request, From, State
      • Return
        • {reply, Reply, NewState}
        • {reply, Reply, NewState, Timeout}
        • {reply, Reply, NewState, hibernate}
        • {noreply, NewState}
        • {noreply, NewState, Timeout}
        • {noreply, NewState, hibernate}
        • {stop, Reason, Reply, NewState}
        • {stop, Reason, NewState}
    • handle_cast/2
      • Params
        • Message, State
      • Return
        • {noreply, NewState}
        • {noreply, NewState, Timeout}
        • {noreply, NewState, hibernate}
        • {stop, Reason, NewState}
    • handle_info/2
      • Callback case
        • bang operator (!)
        • TimeOut
        • モニター通知
        • 'EXIT' シグナル
      • Params
        • Message, State
      • Return
        • {noreply, NewState}
        • {noreply, NewState, Timeout}
        • {noreply, NewState, hibernate}
        • {stop, Reason, NewState}
    • terminate/2
      • Callback case
        • handle_* 関数の返値
          • {stop, Reason, NewState}
          • {stop, Reason, Reply, NewState}
        • 親が死んで、gen_serverが終了を補足していた場合
      • Params
        • Reason, State
      • Return
        • init/1 の反対
        • VMがETS (erlang term storage) を削除
    • code_change/3
      • Params
        • PreviousVersion
          • {down, Version}
        • State
          • {ok, NewState}
        • Extra

18 gen_fsm

今回は有限ステートマシン (finite state machine)。ビヘイビア gen_fsm をとりあげる。このビヘイビアは gen_server に基本似た挙動をするが、呼び出しやメッセージ投入をあつかうのではなく同期や非同期のイベントをあつう。

18.1. 有限ステートマシン

  • 状態遷移図 (state diagram) による記述
  • シーケンス図による記述
  • gen_fsm
    • init/1
      • Return
        • {ok, StateName, Data}
        • {ok, StateName, Data, Timeout}
        • {ok, StateName, Data, hibernate}
        • {stop, Reason}
    • StateName/2
      • 非同期イベント
      • Params
        • Event, StateData
      • Return
        • {next_state, NextStateName, NewData}
        • {next_state, NextStateName, NewData, Timeout}
        • {next_state, NextStateName, hibernate}
        • {stop, Reason, Data}
    • StateName/3
      • 同期イベント
      • Params
        • Event, From, StateData
      • Return
        • {reply, Reply, NextStateName, NewStateData}
        • {reply, Reply, NextStateName, NewStateData, Timeout}
        • {reply, Reply, NextStateName, NewStateData, hibernate}
        • {next_state, NextStateName, NewStateData}
        • {next_state, NextStateName, NewStateData, Timeout}
        • {next_state, NextStateName, NewStateData, hibernate}
        • {stop, Reason, Reply, NewStateData}
        • {stop, Reason, NewStateData}
    • handle_event/3
      • 非同期イベント、グローバルイベント
      • Params
        • Event, StateData
      • Return
        • {next_state, NextStateName, NewData}
        • {next_state, NextStateName, NewData, Timeout}
        • {next_state, NextStateName, hibernate}
        • {stop, Reason, Data}
    • handle_sync_event/4
      • 同期イベント、グローバルイベント
      • Params
        • Event, From, StateData
      • Return
        • {reply, Reply, NextStateName, NewStateData}
        • {reply, Reply, NextStateName, NewStateData, Timeout}
        • {reply, Reply, NextStateName, NewStateData, hibernate}
        • {next_state, NextStateName, NewStateData}
        • {next_state, NextStateName, NewStateData, Timeout}
        • {next_state, NextStateName, NewStateData, hibernate}
        • {stop, Reason, Reply, NewStateData}
        • {stop, Reason, NewStateData}
    • code_change/4
      • Params
        • OldVersion, StateName, Data, Extra
      • Return
        • {ok, NextStateName, NewStateData}
    • terminate/3
      • init/1 と逆の挙動をする
  • イベント送信関数
    • ローカル
      • send_event/2
      • sync_send_event/2-3
    • グローバル
      • send_all_state_event/2-3
      • sync_send_event/2-3

18.2. ユーザー同士の取引システム例

  • thumbnail
  • image.png (24.2 kB)

19 gen_event

今回はイベントハンドラ。ビヘイビアは gen_event をつかう。

  • 人々(あるいは、あるプロセスやアプリケーション)にイベントが起きたことを知らせる機能の実装方法
    • 単純出力方式
      • 結果を出力するのみ
    • 単純サブスクライバ方式
      • メッセージ送信前にサブスクライバのPidを取得
      • Cons
        • コールバックのために待機プロセスが必要
    • イベントマネージャ方式
      • 関数をうけとるプロセスをたて、すべてのイベントに対して当該関数をはしらせる
      • Pros
        • サーバのサブスクライバがたくさんあっても稼働し続けられる
        • すべてのコールバックは同じインスタンスで操作される
        • 短命なタスクに対して新しいプロセスを生成する必要がない
      • Cons
        • 関数が長時間動作する必要があった場合、お互いブロックしあう
          • イベントマネージャをイベントフォワーダにすれば防ぐことができる
        • 無限ループする関数はクラッシュするまで新規イベントをブロックする

19.2. 汎用イベントハンドラ gen_event

image.png (19.9 kB)
  • gen_event の各関数
    • init/1
      • Response
        • {ok, State}
    • terminate/2
    • handle_event/2
      • 非同期
        • すべてのイベントマネージャは戻り値を返さないことで呼び出しプロセスをブロックする
      • Params
        • Event, State
      • Response
        • {ok, NewState}
          • gen_server:handle_cast/2 と似た動作
        • {ok, NewState, hibernate}
        • {remove_handler}
          • イベントマネージャからハンドラを削除する
        • {swap_handler, Args1, NewState, NewHandler, Args2}
          • 今あるイベントハンドラを削除して新しいものに置き換える
    • notify/2
      • すべての流入イベントを通知する (非同期)
    • sync_notify/2
      • すべての流入イベントを通知する (同期)
    • cast/2
      • 非同期
    • handle_call
      • gen_server:handle_call コールバックに似ているがレスポンスが異なる
      • gen_server:call/3-4 が呼び出しに使われている
      • Response
        • {ok, Reply, NewState}
        • {ok, Reply, NewState, hibernate}
        • {remove_handler, Reply}
        • {swap_handler, Reply, Args1, NewState, Handle_Call}
    • handle_info/2
      • hendle_event と同じレスポンスだが、終了シグナルや ! 演算子でイベントマネージャに贈られたメッセージのみを扱う点で異なる
    • code_change/3
      • gen_server:code_change と同じ動作をする
      • Params
        • OldVsn バージョン番号, State ハンドラの状態, Extra
      • Response
        • {ok, NewState}

20 supervisor

今回のビヘイビアはスーパバイザ supervisor。 監視戦略の概要にふれてみる。

  • init/1
    • レスポンス
      • {ok, { {RestartStrategy, MaxRestart, MaxTime}, [{ChildId, StartFunc, Restart, Shutdown, Type, Modules}] } }.
          • {ok, { {one_for_all, 5, 60}, [ {fake_id, {fake_mod, start_link, [SomeArg]}, permanent, 5000, worker, [fake_mod]}, {other_id, {event_manager_mod, start_link, []}, transient, infinity, worker, dynamic} ] } }.
        • 再起動戦略
          • one_for_one
            • 1つのワーカがクラッシュしたら当該ワーカを再起動する
            • 各々のワーカが独立している、互いが関係していない、あるいは、ワーカが再起動して状態が消えても隣のワーカに影響を与えない場合につかう
          • one_for_all
            • 1つのワーカがクラッシュしたらすべてのワーカをクラッシュさせて再起動する
            • 各々のワーカが互いに強く依存している場合につかう
          • rest_for_one
            • 1つのワーカがクラッシュした当該ワーカのら子ワーカたちをクラッシュさせて再起動する
            • 各々のワーカがチェーン状態に依存している場合につかう
          • simple_one_for_one
            • one_for_one はワーカのリストを起動した順で保持している一方 、simple_one_for_one はワーカへの定義を dict で保持している
        • 再起動制限
          • MaxTime 秒以内に MaxRestart 回起動したら、スーパバイザは再起動をやめて自身をシャットダウンする
        • ワーカ (子プロセス)
          • 構成要素
            • ChildId
            • StartFunc
              • スーパバイザの起動方法をつたえるためのタプル {M, F, A}
            • Restart
              • 再起動戦略
                • permanent
                  • 常に再起動
                • temporary
                  • 再起動しない
                • transient
                  • 正常終了の場合は再起動しない
                  • 異常終了の場合は再起動する
            • Shutdown
              • 終了期限
            • Type
              • 子プロセスの種類
                • ワーカ
                • スーパバイザ
            • Modules
              • コールバックモジュールのリスト

21 プロセスプールをつくる

今回はプロセスプール管理アプリケーションをOTPを使ってつくる。

要件

  • サーバを最大でN個の並列接続に制限
  • アプリケーションによって開かれるファイルの数を制限
  • サブシステムに優先順位を与える
  • 不定期なアクセス負荷に対してキューにタスクを貯めることで、可用性を高める

実装

機能

  • アプリケーション
    • 起動
    • 停止
  • 特定のプロセスプール
    • 起動
    • 停止
  • プール内のタスク
    • プールに余裕がある場合
      • 実行
      • タスクが実行されたら呼び出し元は解放
      • できる限りタスクを非同期に実行
    • プールに余裕がない場合
      • 起動できないと告げる
      • タスクがキューの中にある間は呼び出し元のプロセスを待たせておく
      • タスクをキューに貯める

状態

  • 静的な状態
    • 下記から容易に取得
      • 設定ファイル
      • 他のプロセス
      • アプリケーションを再起動しているスーパーバイザ
  • 動的な状態
    • 再計算できるデータから取得
    • 状態の種類
      • 初期からの状態
      • 現在までの状態
      • 変換すべき状態
  • 再計算できない動的なデータ
    • 下記から取得
      • ユーザの入力
      • 生のデータ
      • 逐次的な外部イベント
      • など
    • 保存方法
      • データベースに登録
image.png (20.1 kB)

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

  • 今回はホットコードローディング(更新)についてかんがえる。
  • ホットコードローディングはホットスワップの1種でElixir/Erlangの信頼性につながっている。
  • ホットコードローディングは systools により複数の appup から relup として構成されている。

リリースの更新

バージョンがあがるほどリリースの更新は煩雑になっていく。

  • OTPアプリケーションを書く (ver. 1.0.0)
    • それらをリリースにする
  • 1つ以上のOTPアプリケーションのバージョンを更新する (ver. 1.1.0)
    • そのアプリケーションの古いバージョンから新しいバージョンへの遷移を行うために、何を変更すべきかを説明した appup ファイルを作成する
  • 新しいアプリケーションで新しいリリースを作る (ver. 1.2.0)
    • appup ファイルをこれらのリリースから生成する
    • 新しいアプリケーションを稼働しているErlangシェルにインストールする

リリース作業として以下の通り。relup 構成ツールとして、Erlangは relx 、Elixirは distillery がある。

  • 巻き戻しできるようアップグレードとダウングレード双方を appup ファイルに記述
  • モジュールによるリリース構成を config ファイルに記述
  • systools:make_reluprelup を作成
  • release_hanlder によって更新
appup
%% {NewVersion,
%%  [{VersionUpgradingFrom, [Instructions]}]
%%  [{VersionDownGradingTo, [Instructions]}]}.
{"1.1.0",
 [{"1.0.0", [{add_module, pq_quest},
             {load_module, pq_enemy},
             {load_module, pq_events},
             {update, pq_player, {advanced, []}, [pq_quest, pq_events]}]}],
 [{"1.0.0", [{update, pq_player, {advanced, []}},
             {delete_module, pq_quest},
             {load_module, pq_enemy},
             {load_module, pq_events}]}]}.
{"1.0.1",
 [{"1.0.0", [{load_module, sockserv_serv}]}],
 [{"1.0.0", [{load_module, sockserv_serv}]}]}.

27 EUnit

  • 今回は単体テストEUnitについてかんがえる。

EUnitの特徴

  • eunit:test(モジュール名, [verbose]) で実行
  • postfixがtestの関数 _test() に対してテストをおこなう
  • モジュールの内外ともにテストコードをかける
  • モジュール外に書いた場合プライベート関数に対するテストはできなくなる
  • テスト用モジュールはpostfixがtestsとなる _tests.erl

EUnitの関数

  • 表現系
    • テスト
      • foo_test -> ?assert(is_number(ops:add(1, 2))), ?assertEqual(4, ops:add(2, 2)).
    • テストジェネレータ
      • foo_test_ -> [test_them_1(), test_them_2, ?_assertError(badarith, 1/0)].
    • フィクスチャー
      • setup
        • {setup, Setup, Instantiator}
        • {setup, Setup, Cleanup, Instantiator}
        • {setup, Where, Setup, Instantiator}
        • {setup, Where, Setup, Cleanup, Instantiator}
      • foreach
        • {foreach, Setup, [Instantiator]}
        • {foreach, Setup, Cleanup, [Instantiator]}
        • {foreach, Where, Setup, [Instantiator]}
        • {foreach, Where, Setup, Cleanup, [Instantiator]}
      • instantiator制御
        • {spawn, TestSet} - 並行処理
        • {timeout, (時間 秒), TestSet} - 時間指定処理
        • {inorder, TestSet} - 逐次処理
        • {inparallel, TestSet} - 並列処理
      • コメント
        • {Comment, Fixture}
  • アサーション系
    • ?assert(expression)
    • ?assertNot(expression)
    • ?assertEqual(A, B)
    • ?assertMatch(Pattern, Expression)
    • ?assertError(Pattern, Expression)
    • ?assertThrow(Pattern, Expression)
    • ?assertExit(Pattern, Expression)
    • ?assertException(Class, Pattern, Expression)

フィクスチャー (foreach) をつかったテストジェネレータ

erlang
-define(setup(F), {setup, fun start/0, fun stop/1, F}).

%% 関数some2について
some2_test_() ->
    [{"SetupData1を適切に評価できること",
      {foreach,
       ?setup(fun (SetupData1) ->
           [some_instantiator1(SetupData1),
            some_instantiator2(SetupData1),
            ...
            some_instantiatorN(SetupData1)]
       end}},
     {"SetupData2を並列処理で適切に評価できること",
      {foreach,
       ?setup(fun (SetupData2) ->
           {inparallel,
            [some_instantiator1(SetupData2),
             some_instantiator2(SetupData2),
             ...
             some_instantiatorN(SetupData2)]}
       end)}}].

並列・並行でテストする際の注意点

  • 名前をa、b、cのようにハードコードすると、並列で走らせている際、名前の衝突が起きる可能性がある。可能な限り名前はハードコードせずに make_ref() によって一意の値をつかうこと。

28 インメモリーデータベース ETS

ETSの特徴

  • データへの並列平行なアクセスが可能
    • ただし、安全性と並行性が低下する可能性がある
    • ETSのデフォルト使用数は1400
      • erl -env ERL_MAX_ETS_TABLES Number で設定

ETSのテーブル種類

  • set
    • 標準
  • ordered_set
    • テーブルデータのソート機能あり
    • ユースケース: 範囲指定してデータ取得する (ただし、アクセス時間が遅くなる O(log N))
  • bag
    • 同一キーのタプル(レコード)を保持可能
  • duplicate_bag
    • 同一内容のタプル(レコード)を保持可能
  • 共通機能
    • テーブル所有権
      • ETSテーブル起動関数であらたにコールしたプロセスがそのテーブルの所有者
      • 権限
        • protected level
          • 所有者 read, write
          • その他 read
        • public level
          • 所有者 read, write
          • その他 read, write
        • private level
          • 所有者 read, write
          • その他 n/a
    • テーブルの移譲
      • ETSテーブルはプロセスが死ぬと消滅する
      • 移譲の種類
        • 都度指定する移譲
        • プロセスが死んだ場合の自動移譲

ETSの関数

  • テーブル作成・削除
    • ets:new/2
  • データ挿入・参照
    • ets:insert(Table, ObjectOrObjects)
    • ets:lookup(Table, Key)
  • その他
    • ets:delete(Table, Key)
    • ets:match(Table, MatchClause)
    • ets:match_object(Table, MatchClause)
    • ets:fun2ms(MatchSpecClause)

setテーブルでnamed_tableオプションをつける場合

erlang
21> ets:new(ingredients, [set, named_table]).
ingredients
22> ets:insert(ingredients, {bacon, great}).
true
23> ets:lookup(ingredients, bacon).
[{bacon,great}]
24> ets:insert(ingredients, [{bacon, awesome}, {cabbage, alright}]).
true
25> ets:lookup(ingredients, bacon).
[{bacon,awesome}]
26> ets:lookup(ingredients, cabbage).
[{cabbage,alright}]
27> ets:delete(ingredients, cabbage).
true
28> ets:delete(ingredients, cabbage).
true
29> ets:lookup(ingredients, cabbage).
[]
32> ets:insert_new(ingredients, {tomato, hey}).
true
33> ets:insert_new(ingredients, {tomato, hey}).
false

bagテーブルでnamed_tableオプションをつけない場合

erlang
34> TabId = ets:new(ingredients, [bag]).
16401
35> ets:insert(TabId, {bacon, delicious}).
true
36> ets:insert(TabId, {bacon, fat}).
true
37> ets:insert(TabId, {bacon, fat}).
true
38> ets:lookup(TabId, bacon).
[{bacon,delicious},{bacon,fat}]

ordered_setターブルで、named_tableオプションをつける場合

erlang
42> ets:new(ingredients, [ordered_set, named_table]).
ingredients
43> ets:insert(ingredients, [{ketchup, "not much"}, {mustard, "a lot"}, {cheese, "yes", "goat"}, {patty, "moose"}, {onions, "a lot", "caramelized"}]).
true
44> Res1 = ets:first(ingredients).
cheese
45> Res2 = ets:next(ingredients, Res1).
ketchup
46> Res3 = ets:next(ingredients, Res2).
mustard
47> ets:last(ingredients).
patty
48> ets:prev(ingredients, ets:last(ingredients)).
onions

named_tableオプションつきbagテーブルでパターンマッチをする

erlang
53> ets:new(table, [named_table, bag]).
table
54> ets:insert(table, [{items, a, b, c, d}, {items, a, b, c, a}, {cat, brown, soft, loveable, selfish}, {friends, [jem, jeff, etc]}, {items, 1, 2, 3, 1}]).
true
55> ets:match(table, {items, '$1', '$2', '_', '$1'}).
[[a,b],[1,2]]
56> ets:match(table, {items, '$114', '$212', '_', '$6'}).
[[d,a,b],[a,a,b],[1,1,2]]
57> ets:match_object(table, {items, '$1', '$2', '_', '$1'}).
[{items,a,b,c,a},{items,1,2,3,1}]
58> ets:delete(table).
true

29 分散システム EPMD

  • 今回は分散システム Erlang Port Mapper Daemon (EPMD) についてかんがえる。今回の章がSLAにもっとも関係しているといえそうである。ほかの言語・フレームワークが「分散コンピューティングの落とし穴」「CAP定理」にどのように対応している比較すると、Erlangの特徴がより見えてくるだろう。

分散システムEPMDの特徴

前提

  • 分散システムによるFault toleranceについて
    • ソフトウェアの稼働状況と対ハードウェア障害リスク
      • マシン1台
        • リスク対策できない
      • マシン複数台
        • アプリケーションが正しく構築されない場合、リスク対策できない
  • 「分散コンピューティングの落とし穴」へのErlangの対応
    • ネットワークは信頼できる
      • Erlangの対応
        • 非同期通信モード(リンクやモニタ)により、メッセージを送信に正常な場合に必ず返信するように設計
        • ただし、ノード間でリンクやモニタを張った際にネットワーク障害起きた場合、リンクやモニタが一斉にトリガーされシステムに予期しない負荷をかけることになる
    • レイテンシはゼロである
      • Erlangの対応
        • タイムアウト、リンク、モニタ、非同期パターンにより遅延を想定し設計
    • 帯域幅は無限である
      • Erlangの対応
        • 大きなメッセージを送らない
    • ネットワークはセキュアである
      • Erlangの対応
        • Erlangはネットワークの安全性を確認しないため
          • 異なるデータセンター間で自動的にクラスタ化しない
          • あるいは、SSLに切り替える
          • 安全なチャンネル越しにトンネルする
          • ノード間の通信プロトコルを再実装する
    • ネットワーク構成は変化せず一定である
      • Erlangの対応
        • アプリケーションでネットワーク構成(トポロジー)を管理しない
    • 管理者は1人である
      • Erlangの対応
        • デバッグツールによる個別障害対応
        • ノード監視ツールによるシステム運用状況の共有
        • 実装プロトコルやAPIのバージョン管理
    • 転送コストはゼロである
      • Erlangの対応
        • Erlangはほかのノードに渡されるメッセージを圧縮しないため
          • 送るメッセージを小さくする
          • あるいは、独自の通信レイヤを実装する
    • ネットワークは均質である
      • Erlangの対応
        • Erlangノードと同じプロトコル形式にして通信
          • Cノード
          • BERT
          • BERT-RPC
  • 障害(ノードの応答不能)への対応
    • 下記の中から原因を特定するが確実には対応できない
      • ハードウェア障害
      • アプリケーションクラッシュ
      • ネットワーク分断
        • 輻輳
        • 遮断
    • ゾンビ化するということ
      • ネットワーク分断が起きている間アプリケーションが生きていた場合
        • 当該ノードで保持していたデータがクラスタ間で保持していたデータと整合性がとれず、欠損扱いになる(一貫性の欠如)
        • 当該ノードからレスポンスが返ってこないため生きているか死んでいるかわからない(可用性の欠如)
    • CAP(Consistency, Availability, Partition Tolerance)定理
      • ノード間において、同時に下記3つの要素を保証することはできない
        • 一貫性 Consistency
          • すべてのデータ読み込みにおいて、最新の書き込みデータもしくはエラーのどちらかを受け取れること
        • 可用性 Availability
          • システム要求に応答できること(SPOFがない)
        • 分断耐性 Partition tolerance
          • ネットワーク分断時でもシステムを継続して運用できること
      • 組合せ、採用条件、採用ケース
        • CA
          • 採用条件
            • ネットワークが絶対に落ちない場合
            • ネットワークが1つの塊として動作している場合
            • データの読み書きが多い場合
          • 採用ケース
            • RDBMS
            • NFS
        • AP
          • 採用条件
            • ネットワーク分断が起きやすい場合
            • SPOFがない場合
            • ネットワーク分断時データ変更不可だと問題がある場合
            • 結果整合性を適用可能な場合
              • 時間ベース
              • 個別にコンフリクト解消
              • 全ノードによる合意
                • 合意割合による調整
                • 問い合わせ対象による調整
          • 採用ケース
            • Amazon SimpleDB
            • Apache Cassandra
            • DNS
            • HTTPキャッシュ
        • CP
          • 採用条件
            • ネットワーク分断が起きやすい場合
            • SPOFがある場合
            • ネットワーク分断時データ変更不可でも問題ない場合
          • 採用ケース
            • Mnesia
            • Apache HBase

Erlangが提供する道具

  • ノードとEPMD
    • 特徴
      • ノードとはErlang VMのインスタンスのこと
      • 各ノードはEPMDに接続されている
        • 新しいノードは自動的にErlangクラスタに接続され、各ノードに接続される
      • 接続されているノードでも完全に独立している
        • 各ノードが固有に保持しているもの
          • プロセスレジストリ
          • ETSテーブル
          • 読み込んだモジュール
      • EPMDはErlangクラスタの一部として、各コンピューター上で稼働する
      • EPMDは名前サーバとして機能する
    • Pros
      • Fault tolerance
    • Cons
      • 1ノードにつき1エフェメラルポートが必要になるため、Scalingに制限がある
        • 対処として、ノードのグループを小さなクラスタに分割
  • シリアライズ、デシリアライズ
  • マルチプロセス
  • ネットワークの障害監視

Erlangクラスタを設定する

  • ノードの名前解決
    • 長い名前
      • aaa.bbb.cccのような完全修飾ドメイン名
      • DNSリゾルバによって解決
      • erl -name LongName
    • 短い名前
      • ピリオドがないホスト名
      • ホストファイルやDNSエントリによって解決
      • erl -sname ShortName
    • 注意点
      • 1台のコンピューター上でErlangノードを設定する際は通常短い名前をつかう
      • 長い名前と短い名前、双方でのやり取りはできない
  • 関数
    • net_kernel:connect_node/1 - ノード接続
    • ノード名参照
      • node/0 - 現在のノード名を参照
      • nodes/0 - 接続ノード参照
    • リンクとモニタ
      • link - ノードをまたいだリンク
      • erlang:monitor/2 - ノードをまたいだモニタ(ネットワーク分断時に一斉に活性化し負荷が増加する可能性あり)
      • erlang:monitor_node/2 - ノードを指定したモニタ
    • 遠隔操作
      • spawn/2
      • spawn/4
      • spawn_link/2
      • spawn_link/4
  • クラスタのUIDトークンとしてのCookie
    • 関数
      • erl -sname ShortName -setcookie CookieName
      • erlang:get_cookei/0
      • erlang:set_cookei/2
  • 隠しノード
    • クラスタを介してノード接続すると、クラスタ間で予期しないメッセージ通信がおこなわれる可能性があるため、それを防止する策としてクラスタを介さず接続可能な隠しノード機能がある
    • 関数
      • erlang:send(Dest, Message, [noconnect]) - クラスタを介さずにノードに接続
      • erl -sname ShortName -hidden - クラスタを介さずに隠しノードに接続
      • nodes(hidden) - 隠しノードを参照
  • ポート範囲指定
    • EPMDのポート番号は 4369
    • 設定方法
      • erl -name LongName -kernel inet_dist_listen_min 9100 -kernel inet_dist_listen_max 9115
      • erl -name LongName -config ports
        • ports.config: [{kernel, [{inet_dist_listen_min, 9100}, {inet_dist_listen_max, 9115}]}].
  • 分散用モジュール
    • net_kernel
      • start([Name, Type, HeartbeatInMilliseconds]) - インスタンスを一時的にノード化
      • set_net_ticktime(Milliseconds) - Ticktime(ハートビートを4倍した時間、ノードの死亡判定時間)を設定変更
    • global - プロセスレジストリの代替
      • register_name(Name, Pid) - gloabl登録し名前変更
      • unregister_name(Name, Pid) - globalから名前解除
      • re_register_name(Name, Pid) - 参照先の喪失を防ぎながらglobal登録し名前変更
      • whereis_name(Name) - PID探索
      • send(Name, Message) - メッセージ送信
      • random_exit_name/3 - ランダムにプロセスをkillする
      • random_notify_name/3 - 2つのプロセスのうちいかすプロセスをランダムに1つ選び、globalから解除されるプロセスに {global_name_conflict, Name} というメッセージを送信
      • notify_all_name/3 - 指定したPIDプロセスをglobalから解除し、 {global_name_conflict, Name, OtherPid} というメッセージを送信、コンフリクト解消を促す
    • rpc
      • call(Node, Module, Function, Args[, Timout])
      • async_call(Node, Mod, Fun, Args[, Timout])
      • yield(AsyncPid)
      • nb_yield(AsyncPid[, Timeout]) - ポーリング、ロングポーリング
      • cast(Node, Mod, Fun, Args) - 返値なしのコール
      • multicall(Nodes, Mod, Fun, Args) - 複数ノードへのコール
      • eval_everywhere(Nodes, Mod, Fun, Args) - 複数ノードへの命令(コールとほぼ同じ)

30 分散OTP

  • 前回の分散の章でnine ninesのなぞが見えてきた。今回はそれを補完する分散OTPについてかんがえる。

分散アプリケーションの特徴

構成

  • 標準アプリケーション
    • アプリケーションコントローラ(ノード)
      • アプリケーションマスタ
        • スーパーバイザ
  • 分散アプリケーション
    • アプリケーションコントローラ(ノード)
      • アプリケーションマスタ
        • スーパーバイザ

ライフサイクル

  • 標準アプリケーション
    1. 読込中
    2. 起動中
    3. 停止中
    4. 解放中
  • 分散アプリケーション
    1. 読込中
    2. 起動中
      • 稼働中の分散ノードが死んだら稼働中ステータスに移行
    3. 稼働中
      • 稼働中ステータスは1つのノードのみ
    4. 停止中
    5. 解放中

再起動戦略

  • 特徴
    • ハードウェア障害を前提にしたCAシステムでよくとられます
    • ネットワーク分断を前提にしたCPシステムの場合は採用を熟慮すること
  • 2つの戦略
    • フェイルオーバー
      • アプリケーション停止後、別の場所で再起動する戦略
        • メインとバックアップを入れ替える方法
        • 複数台サーバを立ち上げて相互に負荷を補完しあう方法
    • テイクオーバー
      • アプリケーション復活後、バックアップからメインに移行する戦略

32 Mnesia

Mnesiaの特徴

  • Pros
    • CPシステム(NoSQL)
      • トランザクション機能 (ACID)
      • ネットワーク分断につよい
        • ただし、10ノード前後が実用上の限界と考えられている
  • Cons
    • 各テーブルにつき2Gの容量制限
      • Table Frangmentation機能で回避可能
    • 厳密なシステム要求に応答することはむずかしい
    • 数テラバイトの大きなデータをあつかうのに向いていない
    • 組込型制限がない

適切なユースケース

  • 下記条件をみたした場合
    • ノード数、データ量双方を見積もることが可能
    • ETS/DETS(タプル)形式でアクセス可能

テーブルオプション

  • 保存方法
    • ram_copies - データをETS(メモリ)にのみ保存、32ビットマシンで4Gの容量制限
    • disc_only_copies - データをDETSにのみ保存、2Gの容量制限
    • disc_copies - データをETSとDETS双方に保存、DETSの2G容量制限はない、通常はこちらはつかう
  • テーブル種類
    • set
    • bag
    • ordered_set

CLI

  • erl -name LongName -mnesia dir path/to/db - dir 変数でスキーマ保存場所を指定

関数

  • mnesia
    • create_schema(ListOfNodes)
    • create_table(TableName, Option)
      • オプション
        • {attributes, List} - テーブルのカラム名
        • {disc_copies, NodeList} {disc_only_copies, NodeList} {ram_copies, NodeList} - テーブルの保存場所
        • {index, ListOfIntegers} - インデックスをはる
        • {record_name, Atom} - テーブルの別名(非推奨)
        • {type, Type} - テーブル種類 (set, ordered_set, bag)
        • {local_content, Boolean} - デフォルト falsetrue にすると、多数のノード上に共有されない固有のローカルテーブルを作成する
    • wait_for_tables - テーブル読込完了まで待機
    • activity(AccessContext, Fun[, Args]) - クエリの実行方法を指定
      • AccessContextの種類
        • transaction - 非同期トランザクション、トランザクションの完了を待つわけではないので正確ではない
        • sync_transaction - 同期トランザクション
        • async_dirty - 非同期のロックなし処理
        • sync_dirty - 同期のロックなし処理
        • ets - MnesiaをつかわずETSテーブルで処理
    • write
    • delete
    • read
    • match_object
    • select
  • application
    • set_env(mnesia, dir, "path/to/db") - スキーマ保存場所を指定

クエリリスト内包表記

  • qlc
    • q(Fun, Generator) - クエリハンドル
    • eval(QueryHandle) - 評価
    • fold(Fun, Dict, QueryHandle)

33 Dialyzer

今回は静的型チェッカーDialyzerについてかんがえる。

Dialyzerの特徴

CLI

  • PLT (Persistent Lookup Table 永続的探索表)
    • dialyzer --build_plt --apps erts kernel stdlib mnesia sasl common_test eunit - PLT作成
    • dialyzer --add_to_plt --apps reltool - PLT追加
  • 型チェック
    • dialyzer foo/src/bar.erl - ファイルを解析
    • dialyzer -r foo/src bar/src --src - ディレクトリ指定してerlファイルを解析

Erlangの型

  • シングルトン型 - それ自体が型を示すオブジェクト
    • 'some atom - アトム
    • 42 - 整数
    • [] - 空リスト
    • {} - 空タプル
    • <<>> - 空バイナリ
  • BIF型
    • any()
    • none()
    • pid()
    • port()
    • reference()
    • atom()
    • atom()
    • binary()
    • <<_:Integer>> - 特定サイズのバイナリ
    • <<_:*Integer>> - 特定のユニットサイズで長さは指定されていないバイナリ
    • <<_:Integer, _:_*OtherInteger>> - 上記2つの組み合わせ、バイナリの最小の長さを指定する形式
    • integer()
    • N..M - 整数の範囲
    • non_neg_integer()
    • pos_integer() - ゼロより大きい自然数
    • neg_integer() - 負の整数
    • float()
    • fun() - あらゆる種類の関数
    • fun((...) -> Type) - 引数のアリティが決まっていない、特定の肩を返す無名関数
    • fun(() -> Type)
    • fun((Type1, Type2, ..., TypeN) -> Type)
    • [Type()] - 特定の型を持つリスト
    • [Type(), ...] - 特定の型を持つリスト、またリストが空でないことを示す
    • tuple()
    • {Type1, Type2, ..., TypeN} - 全要素の型とサイズがわかっているタプル
  • エイリアス型
    • term()
    • boolean() - 'true' | 'false'
    • byte() - 0..255
    • char() - 0..16#10ffff
    • number() - integer() | float()
    • maybe_improper_list() - maybe_improper_list(any(), any())
    • maybe_improper_list(T) - maybe_improper_list(T, any())
    • string() - [char()]
    • iolist() - maybe_improper_list(char() | binary() | iolist(), binary() | [])
    • module() - atom()
    • timeout() - non_neg_integer()
    • node() - アトム
    • no_return() - none()

判定できない例と対策

erlang
-module(cards).
-export([kind/1, main/0]).

-type suit() :: spades | clubs | hearts | diamonds.
-type value() :: 1..10 | j | q | k.
-type card() :: {suit(), value()}.

-spec kind(card()) -> 'face' | 'number'. % 注釈をくわえることでdialyzerに警告させる
kind({_, A}) when A >= 1, A =< 10 ->
    number;
kind(_) ->
    face.

main() ->
    number = kind({spades, 7}),
    face = kind({hearts, k}),
    number = kind({rubies, 4}), % タプルの中の型が違う
    face = kind({clubs, q}).
erlang
-module(convert).
-export([main/0]).

%% 注釈をつけないとdialyzerは下記のように判定する
% -spec convert(list() | tuple()) -> list() | tuple().
-spec convert(tuple()) -> list();
             (list()) -> tuple().
main() ->
    [_, _] = convert({a, b}),
    {_, _} = convert([a, b]),
    [_, _] = convert([a, b]),
    {_, _} = convert({a, b}).

%% private
convert(Tup) when is_tuple(Tup) ->
    tuple_to_list(Tup);
convert(L=[_|_]) ->
    list_to_tuple(L).
nabinno
Emacsianでアート好き、ランニング好きな@nabinnoが書いています
GitHub / X / LinkedIn / ネクイノ
blog/market

今後の成長分野:新たなテクノロジーの展望

テクノロジーの進化は、絶え間ない変化の中で私たちの日常を塗り替えてきました。時には経済的な危機が、新たな可能性を切り拓く契機となることもあります。そこで、過去のリセッション期に生まれたテクノロジーの足

market-trendrecession
blog/organization

ATKerneyの課題解決パターンの魅力的な探求

ATKerneyの課題解決パターン は、課題の本質を見極め、効果的な戦略的構造化を通じて解決策を導き出す手法にフォーカスしています。この冒険の旅は、解決者と協力者たちが心を一つにし、課題に立ち向かう様

problem-solvingatkerney
blog/market

就職氷河期とは何だったのか

私はいわゆる就職氷河期世代です。周囲から時折漏れ聞こえる不平のような言葉がありますが、それを単なる不平として片付けるのはもったいない気がします。できれば、その中に新しい視点を見つけ、次のチャンスへ繋げ

labor-economicsrecessionemployment-ice-age