【问题标题】:Cache queries when creating sub records?创建子记录时缓存查询?
【发布时间】:2014-01-20 18:41:47
【问题描述】:

我有一个处理带有订单项的订单的应用程序。订单项以 JSON 格式作为订单的一部分出现,例如:

{
  "customer_id":24,
  "line_items":[
  {
     "variant_id":"1423_101_10",
     "quantity":"5",
     "product_id":"1423"
  },
  {
     "variant_id":"2396_101_12",
     "quantity":"3",
     "product_id":"2396"
  }
  ]
}

所以这将在订单表中设置一个订单,例如:

id | customer_id
1  | 24

以及 line_items 表中的订单项,例如:

id | order_id | product_id | variant_id  | quantity | price*
1  | 1        | 1423       | 1423_101_10 | 5        | 10
2  | 1        | 2396       | 2396_101_10 | 3        | 15

*price 不是来自订单 JSON,而是通过查找检索到的

但是,当创建新记录时,它会为添加的每个 line_item 的订单执行 SELECT。在上面的示例中这不是问题,但是这个应用程序可以并且确实有数百甚至数千个特定订单的订单项,因此它看起来效率低下并且可能是 Heroku 服务器内存不足的原因。有没有办法只加载一次订单,而不是每个订单项?

另一个潜在的瓶颈是针对 Products 表进行查找以获取价格。在上面的示例中,没有可能的缓存,但如果选择了同一产品的多个变体,则每次可能已经加载的产品时查找它似乎效率低下。例如,1423_101_10、1423_101_12、1423_102_10 和 1423_102_12 都是相同价格的相同产品。尝试缓存已经查找过的产品会更好,还是会使事情进一步复杂化?

编辑:

完全忘记添加任何代码!

订购型号:

class Order < ActiveRecord::Base
has_many :line_items, :dependent => :destroy  

订单项模型:

class LineItem < ActiveRecord::Base
before_create :set_price
belongs_to :order
belongs_to :product, :primary_key => "product_id", :conditions => proc { "season = '#{order.season}'" }

def set_price
  write_attribute :price, product.prices[order.currency] if price.nil? && product && order
end  

产品型号:

class Product < ActiveRecord::Base

编辑 2:

OrdersController(简化版)

class OrdersController < ApplicationController

def create
  @order = Order.new(order_params)
  authorize! :create, @order

  if @order.save
    render_order_json
  end
end

def order_params
  permitted = params.permit(:customer_id, :line_items => line_item_params)
  permitted[:line_items_attributes] = permitted.delete("line_items") if    permitted["line_items"]
  permitted
end

def line_item_params
  [:product_id, :variant_id, :quantity]
end

编辑 3:我看到报告的 SQL 示例:

Order Load (1.0ms)  SELECT "orders".* FROM "orders" WHERE "orders"."id" = $1 ORDER BY "orders"."id" ASC LIMIT 1  [["id", 1]]
Product Load (1.0ms)  SELECT "products".* FROM "products" WHERE "products"."product_id" = $1 AND (season = 'AW14') ORDER BY "products"."id" ASC LIMIT 1  [["product_id", 1423]]
SQL (2.0ms)  INSERT INTO "line_items" ("order_id", "price", "product_id", "quantity", "variant_id") VALUES ($1, $2, $3, $4, $5) RETURNING "id"  [["order_id", 1], ["price", 10.0], ["product_id", 1423], ["quantity", 5], ["variant_id", "1423_101_10"]]
Order Load (1.0ms)  SELECT "orders".* FROM "orders" WHERE "orders"."id" = $1 ORDER BY "orders"."id" ASC LIMIT 1  [["id", 1]]
Product Load (2.0ms)  SELECT "products".* FROM "products" WHERE "products"."product_id" = $1 AND (season = 'AW14') ORDER BY "products"."id" ASC LIMIT 1  [["product_id", 2396]]
SQL (1.0ms)  INSERT INTO "line_items" ("order_id", "price", "product_id", "quantity", "variant_id") VALUES ($1, $2, $3, $4, $5) RETURNING "id"  [["order_id", 1], ["price", 15.0], ["product_id", 2396], ["quantity", 3], ["variant_id", "2396_101_10"]]

【问题讨论】:

  • 您还缺少添加生成数据库查询的控制器代码。
  • 我添加了 OrdersController 的简化版本。我希望这已经足够了。
  • 你提到的那些数据库查询来自哪里对我来说仍然不是很明显
  • 所以您是说应该为每个订单项生成这些查询是不正常的行为?我添加了一个添加订单时在控制台中看到的示例。
  • 据我所知,您的代码中没有任何内容可以执行任何查询。获取价格的代码在哪里?

标签: activerecord ruby-on-rails-4


【解决方案1】:

如果您想加快创建操作,您有多种选择:

  • 删除数据库密集型回调
  • 通过缓存加速回调
  • 延迟创建通过后台任务执行

根据您的应用程序需求,按照对您的代码库和基础架构的影响顺序,这些选项可能是可行的。 这完全取决于您已经设置了什么,因此可能正好相反。

通过在您的创建代码中删除造成 1+n 问题的回调 (set_price),您将不得不创建一些查找方法,该方法可以一次获取所有价格并将它们应用于订单。

缓存可以进入set_price 方法,这样查找只进行一次。当价格发生变化时,您将不得不处理缓存过期问题,这可能很重要。

使用 resque 或 sidekiq 之类的后台作业可以接受订单并完成所有处理,而不会导致响应超时。您必须对要处理的订单进行异步检查,以使其在前端可见。

【讨论】:

  • 我已经添加了我所做的作为答案,但是您的回答帮助了我,所以我会将您的回答标记为已接受。感谢您的帮助。
【解决方案2】:

最后,只需对工作流程进行一些更改即可加快订单创建速度。

不是在 before_create 方法上调用 set_price,而是通过 Order 模型在 OrderController 中完成。所以现在我的代码看起来像:

订购型号:

class Order < ActiveRecord::Base
has_many :line_items, :dependent => :destroy  

def set_prices
  self.line_items.each do |item|
    item.set_price
  end
end 

LineItem 模型:

class LineItem < ActiveRecord::Base
belongs_to :order
belongs_to :product, :primary_key => "product_id", :conditions => proc { "season = '#{order.season}'" }

def set_price
  self.price = Product.where(:product_id => product_id, :season =>  season).first.prices[currency]
end  

订单控制器:

class OrdersController < ApplicationController

def create
  @order = Order.new(order_params)
  authorize! :create, @order
  @order.set_prices

  if @order.save
    render_order_json
  end
end

def order_params
  permitted = params.permit(:customer_id, :line_items => line_item_params)
  permitted[:line_items_attributes] = permitted.delete("line_items") if        permitted["line_items"]
  permitted
end

def line_item_params
   [:product_id, :variant_id, :quantity]
end

这个问题也很相关,也加快了速度。 Stop child models updating when parent is updated

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-21
    • 1970-01-01
    • 2015-03-22
    • 2012-07-09
    • 1970-01-01
    相关资源
    最近更新 更多