やること
Phoenixアプリにmemcachedにアクセスするプロセスを追加する
Phoenixで子プロセスを立ち上げている部分を確認
Elixir(そしてErlang)は複数のプロセスが協働しながら一つのアプリケーションを実行するマルチプロセスモデルを採用しています.
なので当然Elixirで作られているPhoenixFrameworkも同様にマルチプロセスモデルを元に作られています.
実際に, アプリケーション作成時に以下の様なコアモジュールが作成されます.
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
これは何をしているかと言うと,
    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として立ち上げ
    Supervisor.start_link(children, opts)
の部分で, 現在のプロセスの子プロセスとして登録しています.
Supervisor(監視者)とは何か?
Elixirでは, 一つの親プロセスの下に複数の子プロセスをぶら下げて,
子プロセスの生き死にに関する処理を親プロセスで一括で行うという方法が,
プロセス管理の一つの良いパターンとしてよく使われています.
この子プロセスの死活を監視している親プロセスのことをSupervisor呼び, 実際に何らかのタスクを処理していくプロセスをWorkerと呼びます.
もっとも, Supervisorの子がすべてWorkerとは限らず, 更にその子プロセスを監視するためのSupervisorだったりもします.
上のコードでも, DBへアクセスするRepositoryプロセスはsupervisorとして生成されており, おそらくこのRepositoryプロセスの子プロセスが,
実際のDBアクセス処理を行っていくことになっているのでしょう.
ここから更に詳しく, SupervisorモジュールについてやGenServerについての説明をしなければいけないと思うのですが,
参考にしたこちらの記事が非常に分かりやすかったので, リンクだけ貼って省略します.
memcachedにアクセスするプロセスを追加する
以上をふまえて, memcachedにアクセスするための子プロセスをサンプルアプリケーションに追加します.
なお, memcachedへのクライアントとしてmcdを, ワーカープロセスのプーリングにpoolboyを使用します.
依存パッケージの修正
mix.exsを開き, depsにライブラリを追加します.
  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を追加します.
  def application do
    [mod: {PhoenixSample, []},
     applications: [:phoenix, :phoenix_html, :cowboy, :logger, :gettext,
                    :phoenix_ecto, :mariaex, :poolboy]]
  end
memcached用Supervisorモジュールの作成
以下のようなモジュールを作成します
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という名前で作成し
  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という名前を元にプーリングされているプロセスを取得し
それを使ってアクセス実行
  def set(key, value) do
    :poolboy.transaction :memcached_pool, &(:mcd.set(&1, key, value))
  end
:mcd.set/3の第一引数は, memcachedにアクセスするプロセスのpidです.
サンプルアプリケーションに上記モジュールを子プロセスとして追加する
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を使って動作を確認してみます.
[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