在 rails 中缓存高级搜索查询

Caching an advanced search query in rails


我的模型中有这种方法,可以为我的应用程序中的搜索功能提供支持:

def self.get_products(cat_ids, term, min_price, max_price, sort_column, sort_direction, current_store = nil)
        products = Product.where(category_id: cat_ids).joins('LEFT JOIN stores ON stores.id = products.store_id') 
        products = products.order(sort_column + " " + sort_direction)
        products = products.where(store_id: current_store.id) if current_store.present?
        products = products.where("lower(product_title) like ?", "%#{term}%") if term.present?
        products = products.where("price_pennies >= ?", (min_price.to_f/1.2).round * 100) if min_price.present?
        products = products.where("price_pennies <= ?", (max_price.to_f/1.2).round * 100) if max_price.present?
        products = products.where('stores.opened_to_customers = ?', true)
        products
    end

上面方法中的参数简单说明:

cat_ids: An array of all the relevant category_ids. In this case, using the awesome_nested_set gem helper
cat_ids = @category.self_and_descendants.pluck(:id)
term: The search query entered by the user

我觉得剩下的参数非常自我描述。
这在 2 个月的过程中一直运行良好,但现在产品 table 中的行接近 300,000,它变得非常慢并且多次抛出此错误:Error R14 Memory Quota Exceeded(The应用程序托管在 heroku 上)。

缓存此查询的最佳方法是什么?更重要的是,是否有更好的方法来编写此查询以提高其速度并避免内存泄漏?

ps:我一般用memcached来缓存我的app。我在其他地方使用了 Rails 缓存获取,但由于它有很多参数,我对如何缓存如此动态的东西感到困惑。

如果您想知道如何准确地缓存结果,那么一种方法是生成一个缓存键,该缓存键取决于传递的参数。

在下面的实现中,我将每个条件分成不同的变量,然后基于由条件变量组成的字符串创建哈希键。

base_conditions = {
  products: {category_id: cat_ids.sort},
  stores: {opened_to_customers: true}
}

current_store_condition = \
  if current_store.present?
    {store_id: current_store.id}
  end || ""

term_condition = \
  if term.present?
    ["LOWER(product_title) LIKE ?", "%#{term.downcase}%"]
  end || ""

price_range_min_condition = \
  if min_price.present? 
    ["price_pennies >= ?", (100 * min_price.fdiv(1.2)).round]
  end || ""

price_range_max_condition = \
  if max_price.present?
    ["price_pennies <= ?", (100 * max_price.fdiv(1.2)).round]
  end || ""

与其所有这些都在同一个方法中,不如让这些条件来自专用方法会更好。事情会变得更整洁。

cache_key_string = [
  base_conditions,
  current_store_condition,
  term_condition,
  price_range_min_condition,
  price_range_max_condition
].join('/')

cache_key = Digest::MD5.hexdigest(cache_key_string)

some_time_limit = 1.day # or some other suitable value

Rails.cache.fetch(cache_key, expires_in: some_time_limit) do  
  Product.
  joins("LEFT OUTER JOIN stores ON stores.id = products.store_id").
  where(base_conditions).
  where(current_store_condition).
  where(term_condition).
  where(price_range_min_condition).
  where(price_range_max_condition).
  order("#{sort_column} #{sort_direction}").
  all
end

此外,您在那里的 LIKE 查询会很慢。我建议使用 ThinkingSphinx or ElasticSearch.

另一种方法是使用分页并一次获得选定数量的结果。这将减轻内存压力,并且您每次都会获得更新的结果。为此,您可以将 page 参数传递给您的方法并执行类似以下操作:

limit = 20 # show 20 results at a time

Product.
joins(joins_string).
where(conditions).
order(order_string).
offset((page - 1) * limit). # value of first page is 1
limit(limit)