LoginSignup
3
7

More than 3 years have passed since last update.

redis-objectsでrails APIにキャッシュ機構を追加する

Last updated at Posted at 2019-10-13

これはなに

  • rails APIにredisによるキャッシュ機構を導入する際、接続するredisの設定や各キャッシュの設定(キー・期限など)を隠蔽してくれるredis-objectsが役に立ちます。今回、RailsAPIにRedisキャッシュ機構を実装したので、メモを残しておきます。
  • 検証時の各種ライブラリのバージョンは以下です
    • redis-objects 1.4.3
    • rails 5.2.3
    • ruby 2.6.1p33

前提

redisとは

インメモリKVS。データの格納・取得が鬼早い。詳しくはググってください。
なお、今回の記事では記載していませんが、redis-objectの導入前に、redis-cliを用いて色々コマンドを触ってみると、より理解が深まる気がします。

キャッシュとは

APIに飛んできたリクエストに対して毎回DBにデータを取りに行くのは非効率なので、同じパラメータを持つリクエストへのレスポンスはAPIサーバー側で保持しておいて、2回目以降のリクエストに対してはそれを返しちゃえばいいじゃん、というやつ。DBへのアクセス数が減る分、DBサーバーの負荷軽減、レスポンス速度の向上、などの効果が見込まれる。
気をつけないといけない点としては、キャッシュに有効期限を設定しないと、コンテンツ側の変更が反映されなかったり、キャッシュサーバーの容量が圧迫されたりするので、いい感じ(今回は1時間)の有効期限を設定してあげるとよい。

今回導入するキャッシュ機構

Cache-Aside Pattern と呼ばれるキャッシュパターンを採用しました。

  • APIに対してリクエストがきた際、特定のルールに基づいてパラメータからキー(文字列)を生成
  • そのキーに対応するキャッシュがあるかredisサーバーに問い合わせる
    • あった場合
      • redisサーバーから受け取った値を(必要に応じて整形し)レスポンスとして返す
    • なかった場合
      • DBに値を問い合わせ、先ほど生成したキーとDBから受け取った値をredisサーバーに格納
      • DBから受け取った値をレスポンスとして返す

という感じです。

導入手順

要件

  • /items?item_category_id=${カテゴリID}というエンドポイントへのリクエストに対するレスポンスを、カテゴリIDごとにJSON型でキャッシュする
  • キャッシュの期限は1時間とする

redis-objectsの導入

gemfile
gem "redis-objects", "~> 1.4.3"
terminal
bundle install

redis-objects用の設定追加

  • 今回のプロジェクトにはglobalが導入されていたので、Global.redis.host という感じに環境ごとのホストとポートの設定を読み込めるよう、設定ファイルを config/globals/ に追加します
  • 今回の記事では本番環境などの場合の環境変数は省略しますが、必要な場合は適当に追加してください。
config/globals/redis.yml
default: &default
  host: <%= ENV.fetch("REDIS_HOST", "redis") %>
  port: <%= ENV.fetch("REDIS_PORT", "6379") %> # 6379はredisのデフォルトポート番号

development:
  <<: *default

test:
  <<: *default
  • Railsアプリ起動時にRedisObjectとRedisサーバーの紐付けを行うために、config/initializersにredisの接続設定を記載します
config/initializers/redis.rb
host = Global.redis.host
port = Global.redis.port
Redis::Objects.redis = Redis.new(host: host, port: port)

キャッシュ用のクラス作成

  • 今回はレスポンス用のJSONをまるごとキャッシュするので、model/配下にredis-objectsをincludesした*Response.rbというクラスを作成します
app/models/items_response.rb
class ItemsResponse
  include Redis::Objects

  value :resource, expireat: -> { Time.current + 1.hour } # 有効期限1時間の設定

  def initialize(item_category_id)
    @item_category_id = item_category_id
  end

  def id # redis-objectがキーを生成するのに使用する。設定するキャッシュ単位ごとに一意である必要がある
    @item_category_id
  end
end

コントローラー側でキャッシュ機能作成

app/controllers/items_controller.rb
class ItemsController < ApplicationController
  def show
    if cache_exists
      result.resource = JSON.parse(@cached_value)
    else
      ... # DBからitemsを取得する処理
      store_cache(result.resource)
    end
    render json: result.resource
  end

  private

  def cache_exists
    @cached_value = ItemsResponse.new(params[:item_id]).resource.value
  end

  def store_cache(resource)
    ItemsResponse.new(params[:item_id]).resource = resource.to_json
  end
end

その他

キーの変更

  • redisに格納する際のキーはデフォルトでは"#{モデル名}:#{self.id}:#{フィールド名}"ですが、これを変えるときは、valueの宣言時にkeyオプションを渡すことでキーの変更が可能です。
ラムダを渡してキー変更
class ItemsResponse
  include Redis::Objects

  value :resource, key: ->(instance) { "it:is:new:key:#{instance.id}:good" }
  ...
end
文字列を渡してキー変更
class ItemsResponse
  include Redis::Objectsfff

  value :resource, key: 'it:is:new:key:#{instance.id}:good' # 文字列はシングルクオートで囲って渡す点に注意
  ...
end

redis DBの変更

アプリケーション内で複数のredisキャッシュ用クラスを持つ場合、クラスごとにredis dbの切り替えを行うことが可能です。

items_response.rb
class ItemsRespnose
  include Redis::Objects
  ...
end
users_response.rb
class UsersRespnose
  include Redis::Objects
  ...
end
redis.rb
host = Global.redis.host
port = Global.redis.port
ItemsResponse.redis = Redis.new(host: host, port: port, db: 0) # デフォルトは0
UsersResponse.redis = Redis.new(host: host, port: port, db: 1)
3
7
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
3
7