PhoeixでElixirのマルチプロセスモデルを学びながらMemcachedにアクセスできるようにしてみる

# やること
Phoenixアプリにmemcachedにアクセスするプロセスを追加する

# Phoenixで子プロセスを立ち上げている部分を確認
Elixir（そしてErlang）は複数のプロセスが協働しながら一つのアプリケーションを実行するマルチプロセスモデルを採用しています.
なので当然Elixirで作られているPhoenixFrameworkも同様にマルチプロセスモデルを元に作られています.

実際に, アプリケーション作成時に以下の様なコアモジュールが作成されます.

```ex:lib/sample_phoenix.ex
defmodule PhoenixSample do
  use Application

  # See http://elixir-lang.org/docs/stable/elixir/Application.html
  # for more information on OTP Applications
  def start(_type, _args) do
    import Supervisor.Spec, warn: false

    children = [
      # Start the endpoint when the application starts
      supervisor(PhoenixSample.Endpoint, []),
      # Start the Ecto repository
      supervisor(PhoenixSample.Repo, []),
      # Here you could define other workers and supervisors as children
      # worker(PhoenixSample.Worker, [arg1, arg2, arg3]),
    ]

    # See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
    # for other strategies and supported options
    opts = [strategy: :one_for_one, name: PhoenixSample.Supervisor]
    Supervisor.start_link(children, opts)
  end

  # Tell Phoenix to update the endpoint configuration
  # whenever the application is updated.
  def config_change(changed, _new, removed) do
    PhoenixSample.Endpoint.config_change(changed, removed)
    :ok
  end
end
```

これは何をしているかと言うと, 

```ex
    children = [
      # Start the endpoint when the application starts
      supervisor(PhoenixSample.Endpoint, []),
      # Start the Ecto repository
      supervisor(PhoenixSample.Repo, []),
      # Here you could define other workers and supervisors as children
      # worker(PhoenixSample.Worker, [arg1, arg2, arg3]),
    ]
```

の部分で, DBへのアクセスを行うプロセスとHTTP経由のアクセスを処理するプロセスをSupervisorとして立ち上げ

```ex
    Supervisor.start_link(children, opts)
```

の部分で, 現在のプロセスの子プロセスとして登録しています.
Supervisor(監視者)とは何か？
Elixirでは, 一つの親プロセスの下に複数の子プロセスをぶら下げて, 
子プロセスの生き死にに関する処理を親プロセスで一括で行うという方法が, 
プロセス管理の一つの良いパターンとしてよく使われています.
この子プロセスの死活を監視している親プロセスのことをSupervisor呼び, 実際に何らかのタスクを処理していくプロセスをWorkerと呼びます.
もっとも, Supervisorの子がすべてWorkerとは限らず, 更にその子プロセスを監視するためのSupervisorだったりもします.
上のコードでも, DBへアクセスするRepositoryプロセスはsupervisorとして生成されており, おそらくこのRepositoryプロセスの子プロセスが, 
実際のDBアクセス処理を行っていくことになっているのでしょう.

ここから更に詳しく, SupervisorモジュールについてやGenServerについての説明をしなければいけないと思うのですが,
参考にした[こちら](http://qiita.com/naoya@github/items/ae17a8166e52fc463012)の記事が非常に分かりやすかったので, リンクだけ貼って省略します.

# memcachedにアクセスするプロセスを追加する
以上をふまえて, memcachedにアクセスするための子プロセスをサンプルアプリケーションに追加します.
なお, memcachedへのクライアントとして[mcd](https://github.com/EchoTeam/mcd)を, ワーカープロセスのプーリングに[poolboy](https://github.com/devinus/poolboy)を使用します.

## 依存パッケージの修正
mix.exsを開き, depsにライブラリを追加します.

```ex:mix.exs
  defp deps do
    [{:phoenix, "~> 1.1.4"},
     {:mariaex, ">= 0.0.0"},
     {:phoenix_ecto, "~> 2.0"},
     {:phoenix_html, "~> 2.4"},
     {:phoenix_live_reload, "~> 1.0", only: :dev},
     {:gettext, "~> 0.9"},
     {:cowboy, "~> 1.0"},
     {:ecto_fixtures, github: "DockYard/ecto_fixtures"},
     {:poolboy, "~> 1.5.1"}, # ADD
     {:mcd, github: "EchoTeam/mcd"} #ADD
    ]
  end
```

またapplicationsにも:poolboyを追加します.

```ex:mix.exs
  def application do
    [mod: {PhoenixSample, []},
     applications: [:phoenix, :phoenix_html, :cowboy, :logger, :gettext,
                    :phoenix_ecto, :mariaex, :poolboy]]
  end
```

## memcached用Supervisorモジュールの作成
以下のようなモジュールを作成します

```ex:lib/memcached.ex
defmodule PhoenixSample.Memcached do
  use Supervisor

  def start_link do
    Supervisor.start_link(__MODULE__, [])
  end

  def init([]) do
    pool_options = [
      name: {:local, :memcached_pool},
      worker_module: :mcd,
      size: 5,
      max_overflow: 0,
    ]

    arg = ['localhost', 11211]

    children = [
      :poolboy.child_spec(:memcached_pool, pool_options, arg)
    ]

    supervise(children, strategy: :one_for_one)
  end


  def set(key, value) do
    :poolboy.transaction :memcached_pool, &(:mcd.set(&1, key, value))
  end

  def get(key) do
    :poolboy.transaction(:memcached_pool, fn worker ->
      :mcd.get(worker, key)
      |> case do
      {:ok, resp} -> resp
      _ -> "Boom!"
      end
    end)
  end

  def delete(key) do
    :poolboy.transaction :memcached_pool, &(:mcd.delete(&1, key))
  end
end
```

このモジュールは何をやっているかというと, プロセス起動時に複数のmcd用のワーカープロセスからなるグループを:memcached_poolという名前で作成し

```ex:lib/memcached.ex
  def init([]) do
    pool_options = [
      name: {:local, :memcached_pool},
      worker_module: :mcd,
      size: 5,
      max_overflow: 0,
    ]

    arg = ['localhost', 11211]

    children = [
      :poolboy.child_spec(:memcached_pool, pool_options, arg)
    ]

    supervise(children, strategy: :one_for_one)
  end
```

memcachedにアクセスするときは, :memcached_poolという名前を元にプーリングされているプロセスを取得し
それを使ってアクセス実行

```ex:lib/memcached.ex
  def set(key, value) do
    :poolboy.transaction :memcached_pool, &(:mcd.set(&1, key, value))
  end
``` 

`:mcd.set/3`の第一引数は, memcachedにアクセスするプロセスのpidです.

## サンプルアプリケーションに上記モジュールを子プロセスとして追加する

```ex:phoenix_sample.ex
defmodule PhoenixSample do
  use Application

  # See http://elixir-lang.org/docs/stable/elixir/Application.html                                                                                                                               
  # for more information on OTP Applications                                                                                                                                                     
  def start(_type, _args) do
    import Supervisor.Spec, warn: false

    children = [
      # Start the endpoint when the application starts                                                                                                                                           
      supervisor(PhoenixSample.Endpoint, []),
      # Start the Ecto repository                                                                                                                                                                
      supervisor(PhoenixSample.Repo, []),
      # Here you could define other workers and supervisors as children                                                                                                                          
      # worker(PhoenixSample.Worker, [arg1, arg2, arg3]),                                                                                                                                        

      supervisor(PhoenixSample.Memcached, []), # ADD
    ]

    # See http://elixir-lang.org/docs/stable/elixir/Supervisor.html                                                                                                                              
    # for other strategies and supported options                                                                                                                                                 
    opts = [strategy: :one_for_one, name: PhoenixSample.Supervisor]
    Supervisor.start_link(children, opts)
  end

  # Tell Phoenix to update the endpoint configuration                                                                                                                                            
  # whenever the application is updated.                                                                                                                                                         
  def config_change(changed, _new, removed) do
    PhoenixSample.Endpoint.config_change(changed, removed)
    :ok
  end
end
```

## 動作確認
iexを使って動作を確認してみます.

```ex

[vagrant@vagrant-centos7 phoenix_sample]$ iex -S mix phoenix.server
Erlang/OTP 18 [erts-7.2] [source-e6dd627] [64-bit] [smp:2:2] [async-threads:10] [hipe] [kernel-poll:false]

...

[info] Running PhoenixSample.Endpoint with Cowboy using http on port 4000
Interactive Elixir (1.3.0-dev) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> [info] Creating interface #PID<0.451.0> to memcached on 'localhost':11211

[info] Creating interface #PID<0.454.0> to memcached on 'localhost':11211

[info] Creating interface #PID<0.457.0> to memcached on 'localhost':11211

[info] Creating interface #PID<0.460.0> to memcached on 'localhost':11211

[info] Creating interface #PID<0.463.0> to memcached on 'localhost':11211

iex(2)> PhoenixSample.Memcached.set("name", "YMST")
{:ok, "YMST"}
iex(3)> PhoenixSample.Memcached.get("name")        
"YMST"
```
```
$ memcached-tool localhost dump
Dumping memcache contents
  Number of buckets: 1
  Number of items  : 1
Dumping bucket 2 - 1 total items
add ucO902IZy6w7DAHvEHd/Qw== 0 1458551639 10
?mYMST
$ 
```

見ての通り, サーバ立ち上げ時にmemcached用のプロセスが5つ作成され, それらのプロセスを使って
memcachedにアクセスできている様子がわかると思います.

# おわりに
今回使用したサンプルプロジェクトはこちらになります.
https://github.com/ak-ymst/phoenix_sample

当初目的としていた, memcachedとの通信はできるようになりました.
が, 色々Wrapされたモジュールを使った形での実装になってしまいプリミティブな部分はまだまだ理解できたとは言えないので, もっと詳しく理解できたらまた記事を書いていきたいと思います.

# 参考
Elixir の OTP (GenServer 編) : http://qiita.com/naoya@github/items/ae17a8166e52fc463012

Phoenix で Poolboy を使ってコネクション（プロセス）プーリング: http://ymmtmsys.hatenablog.com/entry/2015/09/02/214254

Phoenixにオレオレプロセスを追加する: http://qiita.com/yokaigundan/items/d4a049676d1e58d0948e