概要
Elixir + Phoenixが早い理由としてどこがキモになるのか。
関数型言語であること、並行処理が可能であることが大きなシナジーを生んでいる言語と考えてよいのか。
その場合、DBアクセス処理設計を行うときは、並行処理という特性を踏まえた慎重なものにしなければならないのか。
といった要旨です。
整理しきれずすみません。質問が大きく二つございます。
質問したいこと - Elixir+Phoenixが早い理由
Elixir+PhoenixのCRUDを作ってみよう系のコードやベンチマークのコードを見ていると、
(言葉は悪いですが)特に工夫もないコードしかなく、(Erlangで並行処理を宣言するspawn
が使われているわけではない)
これで高速なレスポンスが可能である理由がわかりませんでした。
言語特性として並行処理が可能である、VM内でのマルチプロセスが可能である
これが高速の根拠であるのなら、Elixir+Phoenixは裏でマルチプロセス処理を行なっているから早い
ということで良いのでしょうか。それとも単にErlangの関数型言語という仕様だったり、BEAM VMが優秀だからであって、
更にPhoenixは裏で自動ビルドしているので、スクリプト言語に比べて処理が高速である、ということでしょうか。
(=並行処理機能がベンチマーク結果に寄与しているわけではない)
また、もしElixir+Phoenixが裏でマルチプロセス処理を行なっているのであれば、
|>
を使わない処理は全てElixir+Phoenixによって並行処理に変換されていたりするのでしょうか。。
(以下の質問はこの前提に立って話が進みます。ここがそもそも間違っていたらすみません。)
質問したいこと - 並行処理によるDBのデッドロック
Elixir+PhoenixでのAPIサーバーを構築する記事を読んでふと感じたのですが、
Erlangの特性である並行処理って、DBアクセス時に簡単にデッドロックを引き起こしてしまいそうなのですが、
実際のところどうなのでしょうか?
(実例が思いつかなくて恐縮です。次の項目で詳しく考えたことを書いてみています。)
うまく言えませんが、関数の設計において、
従来通り設計を行えばよくて、並行処理の特性などは考慮しなくても良いのか、
並行処理の特性を考えながら設計しなければならないのか....
また、後者の場合、アクセス処理は結局DBが行うものなので
Elixirに対応できるDBの選定などが必要になってくる気がしていますが、これも実際必要でしょうか?
(たとえばsqliteは実質的にマルチプロセス向きではなく、PostgreSQLはマルチプロセスで構築されてるから、
sqliteをElixir+PhoenixのDBとして使うと速攻で破綻してしまう、とか。。)
考えたこと
仮定1
並行処理ということは、
普通にWebアプリケーションを作って、APIをPHPなんかで実装して、複数人からアクセスがきた時に発生する処理と、
Elixirで実装して、1人がアクセスしたときに発生する処理は
同じような処理が走るのではないかな(=同時DBアクセスが発生する)と想像しています。
なので、従来の実装の考え方では
「基本的に関数A内で定義してあるDBアクセス処理はこの順番で走るのだから、
この設計さえ間違えなければ、複数人アクセスがきても
誰しもが同じ順番でDBアクセスされるはずなので、問題は発生しない」
だったのが、
「Elixirで処理を関数Aにある通りに定義したが、どの処理が最初に走って終わるのかが明確ではないから
関数Aに内包される全てのDBアクセスの組み合わせを検証して、どれもデッドロックが生じないように
クエリやアクセスを考えなければならない」
となるような気がしています。。
仮定2
また、Elixir+Phoenixの早い理由がいまいちつかめていないのですが、
ソースを見ても関数をすごく細かい粒度に分割していたり、spawn
を多用しているわけでもなく、
普通に他言語と同じような書き方をするだけで早くなっているような印象を受けました。
勝手な推測ですが、関数型言語ということで、関数単位で並行処理してるのかな?という妄想をしています。
(=JSにおけるvarの巻き上げ
のような現象が、関数単位で発生する。
つまり必ずしも上から下に書いてある通りに処理されない...というイメージです。)
例えば次のような形です。
php
1//この場合、BとCはAの処理開始時に同時にスタートする 2public function A(){ 3 $this->B(); 4 $this->C(); 5} 6 7//こうすれば順番に処理される 8public function X(){ 9 if($this->Y()){ 10 $this->Z(); 11 } 12}
結論
典型的なデッドロックの例として、
テーブルA, テーブルBがあって、
BがAに対して参照を持っている時、トランザクション1がAに対してUpdate, トランザクション2がBに対してInsertすると発生しますが、
DBアクセス処理が順番に処理されず、同時並行的に処理される場合、容易にこれが生じてしまいそうな気がしています。
なので、Elixir+Phoenixが高速な理由が仮定2の通りならば、
次のような関数の定義を行うと、デッドロックが(容易に)発生してしまうような気がしていますが、どうなのでしょうか。
elixir
1def 関数A_裏でspawnが自動で行われてると仮定すると、この関数ではデッドロックが発生する?(connect) do 2 case TableA.Update(params) do 3 {:ok, _} -> 4 # ... 5 case TableB.Insert(params) do 6 {:ok, _} -> 7 # ...
また上のコードが真である前提に立つと、
次のような形で定義すればデッドロックが起きないような気がしていますが、
さながらコールバック地獄で、実装が大変そうだな...と感じていますが
実際にデッドロックを意識したとき、PhoenixのDBアクセス部分を設計するときのコードは
大抵こんな形になるものなのでしょうか。
elixir
1def 関数A_パイプラインを使って順番に処理すれば、デッドロックが発生しない?(connect) do 2 case TableA.Update(params) do 3 {:ok, _} -> 4 |> case TableB.Insert(params) do 5 {:ok, params} -> 6 # ... 7 {:error, params} -> 8 |> putMsg(:error, params)
付記
自分で調べていて混乱していたので、ここで使っている言葉の概念を次のようにまとめてみました。
(合っているといいのですが)
ことば | 意味 |
---|---|
非同期処理 | 関数と関数の間隙にキューされた処理が割り込む。ワンオペでうまいこと厨房を回してるイメージ(レンジを3分にセットして、その間に配膳して、チンされたら取り出して....みたいな) |
並行処理(マルチプロセス/マルチタスク) | 別のプロセスを起動する。その分リソースを食う。複数人で処理を行うイメージ。きちんと管理しないと同期しない。お店で例えるとドライブスルー担当、調理担当、配膳担当がいて、それぞれちゃんと現在の業務内容を共有しないと業務が滞るイメージ |
並列処理(マルチスレッド) | 1つのプロセスの中で複数のスレッドを起動する。スレッドの使うメモリは共通。並行処理を包摂している。お店で例えると何店舗もあるチェーン店のそれぞれの動きを俯瞰で見てるイメージ |
正直言ってデッドロック
や排他制御
などの用語の字面だけ知っているような初心者で、
Elixirについては実際にコードも書いたことがなく、
上の表を見てもわかるように、マルチ***
についても全く詳しくないです。
このFWが早い理由と、並行でDBアクセスが発生するときどう処理するのか、といった点に興味を持っただけなので、
完全に杞憂だったらすみません。
十分な知識がないまま、色々質問してしまってすみません。
ご存知の方、ご回答のほどよろしくお願いいたします。
回答1件
あなたの回答
tips
プレビュー
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2018/08/26 03:28