LoginSignup
24
23

More than 5 years have passed since last update.

Phoenix で Cache をする

Posted at

Phoenix に Cache 機構をいれてみます。
今回は ConCache を使って OpenWeatherMap API のレスポンスをキャッシュしてみます。

Phoenix アプリケーションでの外部 API リクエストについてはコチラを参照してください。

Elixir におけるキャッシュについて

キャッシュ機構の実装というと、シングルトンクラスを作って処理することをイメージするかもしれませんが、これは オブジェクト指向 な考え方であり、Elixir のような プロセス指向 な言語には当てはめられません。
プロセス指向では、その名の通り データをストアしておくプロセス を作成して処理します。プロセスの作成と聞くとなにやら大変そうな気がしますが、Erlang/OTP のおかげでシンプルに実装できるようになっています。
では早速、ConCache を利用してプロセスを用いたキャッシュを体験してみましょう。

依存関係に ConCache を追加する

cache_sample というアプリケーションを作り、mix.exs に以下のように追記しましょう。

$ mix phoenix.new cache_sample
mix.exs
defmodule CacheSample.Mixfile do
  ...

  def application do
    [mod: {CacheSample, []},
     applications: [:phoenix, :phoenix_html, :cowboy, :logger,
                    :phoenix_ecto, :postgrex,
                    # ConCache を追記
                    :con_cache]]
  end

  ...

  defp deps do
     ...
     {:cowboy, "~> 1.0"},
     # HTTPoison を追記(API リクエスト用)
     {:httpoison, "~> 0.7.2"},
     # ConCache を追記
     {:con_cache, "~> 0.8.1"}]  end
end

最後にライブラリのダウンロードを行います。

$ mix deps.get

プロセスを作成する

Erlang/OTP では、子プロセス(worker)は親プロセス(supervisor)に監視させる必要があります。
Phoenix ではこの設定が lib/cache_sample.ex で管理されていて、以下のように追記することで ConCache プロセスが作成できます。

cache_sample.ex
defmodule CacheSample do
  ...
  def start(_type, _args) do
    import Supervisor.Spec, warn: false

    children = [
      supervisor(CacheSample.Endpoint, []),
      worker(CacheSample.Repo, []),
      # ConCache を追記
      worker(ConCache, [[], [name: :weather_cache]])
    ]
    ...
    opts = [strategy: :one_for_one, name: CacheSample.Supervisor]
    Supervisor.start_link(children, opts)
  end
  ...
end

children に ConCache プロセスを追加します。この場合は :weather_cache という名前のキャッシュが作成されます。
Supervisor.start_link(children, opts) という部分で親プロセスの監視下で各プロセスが立ち上げられています。

API リクエスト部分を実装する

コチラと同じ実装ですので、記載は割愛させていただきます。
以下のリクエストで天気データが返ってくれば OK です。

$ curl -s "http://localhost:4000/api/weather/35.6585373/139.7151887" | jq .
{
  "humidity": 74,
  "pressure": 1006,
  "temp": 26.99,
  "temp_max": 31.11,
  "temp_min": 24.44
}

キャッシュ部分を実装する

本題のキャッシュ機構を実装しましょう。
今回 Key とする値は「緯度・経度」です。
ConCache には get_or_store/3 という関数が用意されていて、これはその名の通り「指定した Key のキャッシュがあればそれを返却、なければ生成して追加 & 返却」というものです。そうそうこれが欲しかった、とはまさにこのことですね。

page_controller.ex のレスポンス生成部分を以下のように書き換えます。

page_controller.ex
defmodule CacheSample.PageController do
  ...
  def weather(conn, %{"lat" => lat, "lon" => lon}) do
    key = "#{lat},#{lon}"
    value = ConCache.get_or_store(:weather_cache, key, fn() ->
      HTTPoison.start
      result = HTTPoison.get! "http://api.openweathermap.org/data/2.5/weather?units=metric&lat=#{lat}&lon=#{lon}"

      case result do
        %{status_code: 200, body: body} -> Map.get(Poison.decode!(body), "main")
        %{status_code: code} -> %{error: code}
      end
    end)
    json conn, value
  end
end

仕組みは簡単で、最初に Key となる文字列を作成して、それを get_or_store/3 で問い合わせます。
キャッシュされていなかったら、今まで通り OpenWeatherMap API に問い合わせを行い、結果の JSON をストアします。
最後に、get_or_store/3 から返却された JSON を返却して完了です。

とても簡単ですね。

効果を測定する

curl コマンドは以下のようにオプションをつける事でレスポンス速度だけ出力されることができます。

$ curl -kL "http://www.example.com/" -o /dev/null -w "%{time_total}\n" 2> /dev/null
0.354

これを使って同じ緯度経度を用いたリクエストのレスポンス速度が向上されているかを確認してみましょう。

$ curl -kL "http://localhost:4000/api/weather/35.6995477/139.5769432" -o /dev/null -w "%{time_total}\n" 2> /dev/null
0.566
$ curl -kL "http://localhost:4000/api/weather/35.6995477/139.5769432" -o /dev/null -w "%{time_total}\n" 2> /dev/null
0.024
$ curl -kL "http://localhost:4000/api/weather/35.6995477/139.5769432" -o /dev/null -w "%{time_total}\n" 2> /dev/null
0.024

0.566 秒 -> 0.024 秒 となり、しっかりとキャッシュが効いていることが確認できました。

感想

  • Erlang/OTP のおかげて、プロセスについてそんなに意識しなくても実装できるようになっている
  • ConCache には関数や設定項目が豊富に用意されていて、かなり実用的
    • TTL(生存期間)もチューニングできるのは素晴らしい
  • ただ、全キャッシュクリアのやり方だけイマイチ分からず...
24
23
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
24
23