HTTPoisonのプーリング

HTTPクライアントにHTTPoisonを利用してみた。

同時実行数を増やすと50コネクション以上のコネクションは作成されないことがわかった。コードを見てみると、内部ではhackneyが使われていることがわかった。

hackney のドキュメントをみると、何も指定がなければデフォルトのプールが利用されるようだ(これ?)。

変更するためにはどうしたらよいかを調べた。

プールの数を変更する方法

名前をつけてプールを開始する。

poolname = :mypool
options  = [max_connections: 10]
:hackney_pool.start_pool(poolname, options)

HTTPリクエストを送る際のパラメータで、[hackney: [pool: :mypool]] を渡す。

HTTPoison.get!("http://localhost:4001", %{}, hackney: [pool: :mypool])

これでデフォルトではなく、自分の希望通りのプールサイズになる。

調べた時のライブラリのバージョン

  • httpoison, 0.7.2
  • hackney, 1.3.1

Phoenix で Poolboy を使ってコネクション(プロセス)プーリング

Poolboy を使ってコネクションプーリングができるようにする。接続先は memcached

memcached のクライアントにはmcdを使った。

サンプルのhello_phoenixを元にした。手を加えたファイルだけ以下に貼っておく (ちなみに Phoenix は v1.0.0)。

実行してみと、、、

% curl localhost:4000
{"val":"Hello Phoenix"}

成功。

メモ

  1. mcd はプロセスを生成した時点ではmemcached と接続されていない。ので、start_link してすぐに使おうとすると、結果が取れない。
  2. max_overflow の値を0以上にすると、プールしている数が size に達していてもさらにそこから max_overflow 分だけプロセスを生成する。
  3. max_overflow 分のプロセスは必要になった時点で生成されるので、01 で書いたとおり、コネクションが確立されていないプロセスが渡される時がある(というか、全てそうだった)。

感想

  • 既存ライブラリのおかげで簡単にプーリングできた。
  • Poolboy, mcd 両方とも、Erlang のライブラリ。Elixir を学ぶなら、Erlang を勉強しないとだめっぽい(動作を理解するのと、ライブラリの情報)。

Phoenixのデプロイ

Erlang (Elixir) の機能の中で気になっているのが、ホットデプロイだ。といっても、そういう機能があるからといって気軽にデプロイできるものでもないらしいけど。

デプロイに関するスライド。

デプロイ方法

Exrm

Exrm (Elixir Release Manager) を使うのがよさげ。 Phoenix 公式でも紹介されている。 設定方法などはそちらを参照。

パッケージ作成

MIX_ENV=prod mix release で本番環境用のリリースパッケージが rel/hello_phoenix/releases の下にバージョンごとに作成される。

起動
$ ./rel/hello_phoenix/bin/hello_phoenix start

停止するときは stop で。

更新

mix.ex に書かれているバージョンを上げ、MIX_ENV=prod mix release でパッケージを作成する。 (ここでは バージョンをv0.0.1 からv0.0.2に上るとする。)

v0.0.1のアプリが稼働中の状態でv0.0.2に更新するには

 rel/hello_phoenix/bin/hello_phoenix upgrade 0.0.2

で、OK。

自分の環境で試した限りでは、更新時に接続できないということはなかった。

mix phoenix.new のオプション

アプリケーション作成時のオプションに何があるか調べてみた(といっても、大して調べてないけれど)。

答えはここに

  • --app
    • アプリケーション名?よくわかってない。
  • --module
    • モジュール名を指定する。phoenix.new したときのディレクトリ名とモジュール名を異なる名前にできる。
  • --database
  • --no-brunch
    • brunchを使わないときに指定する。APIサーバとして使うならこれを指定した方がいいかも。
  • --no-ecto
    • Ectoを使用しない場合に使用。

Phoenix のアップデート

v0.17.0 がリリースされていたので更新してみる。フレームワーク(というか、Mixでインストールしたライブラリ)をアップデートするのは今回が初めて。

% mix archive.install https://github.com/phoenixframework/phoenix/releases/download/v0.17.0/phoenix_new-0.17.0.ez
Found existing archive(s): phoenix_new-0.16.1.ez.
Are you sure you want to replace them? [Yn] y
* creating .mix/archives/phoenix_new-0.17.0.ez

特別悩むことはなくアップデート完了。インストールと同じコマンドで大丈夫だった。

変更点は http://www.phoenixframework.org/blog/upgrading-from-v016-to-v017 に書かれている。

大きな変更はないけれど、個人的に良かったと思う点は、パラメータに %{"format" => "html"} が追加されなくなったところ。jsonしか返さないAPIサーバを作る場合とか邪魔だったので。

求めていたのはinspect/2だった

to_string/1 ではなくinspect/2 だった。

iex(1)> to_string %{a: 1, b: "hoyhoy"}
** (Protocol.UndefinedError) protocol String.Chars not implemented for %{a: 1, b: "hoyhoy"}
    (elixir) lib/string/chars.ex:3: String.Chars.impl_for!/1
    (elixir) lib/string/chars.ex:17: String.Chars.to_string/1
iex(1)> inspect %{a: 1, b: "hoyhoy"}
"%{a: 1, b: \"hoyhoy\"}"

Map には to_string が定義されていないので、どうすればログに出力できるのかを悩んでいたのだけれど、これで解決。

IEx.Pryでデバッグ

ここ

デバッガみたいなのはある?

と書いた。調べてみたところIEx.pryが使えることがわかった。

使い方

大雑把にいうとこんな感じ

  1. IEx モジュールを require する。
  2. ブレイクポイントコードに仕込む。
  3. iex でサーバを起動する
  4. リクエストを出す。
  5. ブレイクポイントに到達すると、pry を実行するかを尋ねられるので Y を入力する。
  6. respwan で処理を続行する。

コードはこんな感じ(ベースはここ)。

defmodule HelloPhoenix.PageController do
  use HelloPhoenix.Web, :controller

  require IEx

  def index(conn, _params) do
    IEx.pry                    # ブレイクポイント
    render conn, "index.html"
  end
end

できること

  • 変数の内容を表示させる

などなど(詳しく調べていない)

実行サンプル

localhost:4000/?foo=bar にリクエストを出したところ。

% iex -S mix phoenix.server
Erlang/OTP 18 [erts-7.0] [source] [64-bit] [smp:2:2] [async-threads:10] [hipe] [kernel-poll:false]

Interactive Elixir (1.0.5) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> 17 Aug 21:02:01 - info: compiled 5 files into 2 files, copied 3 in 2280ms
Request to pry #PID<0.267.0> at web/controllers/page_controller.ex:7. Allow? [Yn] y

Interactive Elixir (1.0.5) - press Ctrl+C to exit (type h() ENTER for help)
pry(1)> _params
%{"foo" => "bar", "format" => "html"}
pry(2)> conn
%Plug.Conn{adapter: {Plug.Adapters.Cowboy.Conn, :...}, assigns: %{},
 before_send: [#Function<1.90859823/1 in Plug.CSRFProtection.call/2>,
  #Function<4.77861751/1 in Phoenix.Controller.fetch_flash/2>,
  #Function<0.121794808/1 in Plug.Session.before_send/2>,
  #Function<1.60260050/1 in Plug.Logger.call/2>,
  #Function<0.94118563/1 in Phoenix.LiveReloader.before_send_inject_reloader/1>],
 body_params: %{}, cookies: %{}, halted: false, host: "localhost",
 method: "GET", owner: #PID<0.267.0>,
 params: %{"foo" => "bar", "format" => "html"}, path_info: [],
 peer: {{127, 0, 0, 1}, 36626}, port: 4000,
 private: %{HelloPhoenix.Router => {[], %{}}, :phoenix_action => :index,
   :phoenix_controller => HelloPhoenix.PageController,
   :phoenix_endpoint => HelloPhoenix.Endpoint, :phoenix_flash => %{},
   :phoenix_layout => {HelloPhoenix.LayoutView, :app},
   :phoenix_pipelines => [:browser],
   :phoenix_route => #Function<1.114667012/1 in HelloPhoenix.Router.match/4>,
   :phoenix_router => HelloPhoenix.Router,
   :phoenix_view => HelloPhoenix.PageView, :plug_session => %{},
   :plug_session_fetch => :done}, query_params: %{"foo" => "bar"},
 query_string: "foo=bar", remote_ip: {127, 0, 0, 1}, req_cookies: %{},
 req_headers: [{"user-agent", "curl/7.35.0"}, {"host", "localhost:4000"},
  {"accept", "*/*"}], request_path: "/", resp_body: nil, resp_cookies: %{},
 resp_headers: [{"cache-control", "max-age=0, private, must-revalidate"},
  {"x-request-id", "jcei0fi1smlj9c9npf323nfukor7p6eo"},
  {"x-frame-options", "SAMEORIGIN"}, {"x-xss-protection", "1; mode=block"},
  {"x-content-type-options", "nosniff"}], scheme: :http, script_name: [],
 secret_key_base: nil, state: :unset, status: nil}
pry(3)> respawn

Interactive Elixir (1.0.5) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)>