【问题标题】:Caching an advanced search query in rails在 Rails 中缓存高级搜索查询
【发布时间】:2015-03-02 12:39:36
【问题描述】:


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

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 个月的过程中一直运行良好,但现在产品表中的行已接近 300,000,它变得非常缓慢,并且很多时候它抛出此错误:错误 R14 内存配额超出(该应用程序托管在赫鲁库)。

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

Ps:我一般使用 memcached 来缓存我的应用程序。而且我在其他地方使用过Rails Cache fetch,但是由于它有很多参数,我对如何缓存如此动态的东西感到困惑。

【问题讨论】:

    标签: ruby-on-rails performance postgresql activerecord heroku


    【解决方案1】:

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

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

    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 或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)
    

    【讨论】:

    • 非常感谢 Humza 的全面回答。我会尝试你的建议并回复你:)
    • 感谢 Humza,我从您构建代码的方式中学到了一些东西,最重要的是一种缓存烦人查询的方法。我按照您的建议将条件移至他们自己的方法,并且我目前正在研究弹性搜索。非常感谢您的帮助。
    猜你喜欢
    • 1970-01-01
    • 2014-06-08
    • 2012-02-08
    • 2018-10-21
    • 2015-06-09
    • 2016-08-09
    • 1970-01-01
    • 2018-11-05
    • 2016-03-27
    相关资源
    最近更新 更多