2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

あまねAdvent Calendar 2022

Day 15

Railsでメモ化を使ってパフォーマンスを向上させる

Last updated at Posted at 2022-12-16

TL;DR

Railsでメモ化というテクニックを使えば一度のリクエストに対して同じ処理が複数走る箇所のパフォーマンスを改善できます。

@hoge ||= #重い処理 

このようにインスタンス変数の自己代入を使ってメモ化すれば、2回目以降の呼び出しはインスタンス変数に格納された値を使ってその後の処理を実行できます。

解決したい課題

次のようなProductモデルを考えてみます。

product.rb
# == Schema Information
#  id   :bigint           not null, primary key
#  name :string(255)      not null
#
class Product < ApplicationRecord
    has_many :shops, through: :malls
    has_many :malls

    def has_shops?
      # 内容は省略
      # 要件を満たしたshopかどうかを判定して真偽値を返す
    end
end

こちらのProductに対してnameの検索を行うようなSearchableモジュールのsearchメソッドを考えてみます。

product_filterable.rb
module ProductFilterable
    def filter(query)
         target_products = Product.all.inlcludes(malls: :shops).select(&:has_shop?)
         target_products.select { |product| product.name == "query" }
    end
end

対象となる商品(products)からnameが文字列queryと一致するものを抽出するようなメソッドです。
ただし、検索対象として予め要件で決められたshopをアソシエーションで持っているproductから抽出したいです。
(本番環境でProduct.allを使うとメモリを圧迫する可能性がありますが、今回はサンプルとしています)

2回目の呼び出し以降も同じ結果を返すならば無駄な処理が走る

このコードの問題として、検索の度に引数として渡されたすべてのProductに対してhas_shop?という判定がされていまいます。
もしもProductレコードが頻繁に追加されることがなく、抽出元となる対象がかわらないのであれば、 filterの実行の度に全件に対しての判定処理は無駄になってしまいます。

メモ化を使って初回の実行結果をキャッシュする

そんなときに使えるのがメモ化のテクニックです。

メモ化とは

メモ化をWikipediaで調べると下記のような説明があります。

メモ化(英: memoization)とは、プログラムの高速化のための最適化技法の一種であり、サブルーチン呼び出しの結果を後で再利用するために保持し、そのサブルーチン(関数)の呼び出し毎の再計算を防ぐ手法である。メモ化は構文解析などでも使われる(必ずしも高速化のためだけとは限らない)。キャッシュはより広範な用語であり、メモ化はキャッシュの限定的な形態を指す用語である。

今回のケースで言うと、メモ化とは同じ処理を走らせる場合に、1回目の処理で返したレスポンスをキャッシュとして残しておくことで2度目以降の処理が走らないようにしてパフォーマンスを向上させるテクニックだと言えます。

メモ化を使ったパフォーマンス改善

実際に上記のコードは下記のように書き換えることができます。

def fitler(query)
     @target_products ||= Product.all.inlcludes(malls: :shops).select(&:has_shop?)
     @target_products.select { |product| product.name == "query" }
end

メモ化の効果

こうすることで、最初の実行時のみ

Product.all.inlcludes(:shop).select(&:has_shop?)

こちらの処理が呼ばれてインスタンス変数@target_productsに格納されます。二度目以降はインスタンス変数がそのまま使われてselectの処理が実行されます。

ちなみに、||=という書き方はRubyの自己代入という仕組みで「左辺が偽か未定義ならば右辺を代入する」という意味です。

参考

2
0
2

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
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?