创建子记录时缓存查询?

时间:2014-01-06 10:47:37

标签: activerecord ruby-on-rails-4

我有一个处理订单项目的应用程序。订单项以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

*价格不是来自订单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"]]

2 个答案:

答案 0 :(得分:1)

如果您想加快创建操作,可以选择以下几种方法:

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

根据您的应用程序需求,这些可能是对您的代码库和基础架构产生影响的可行选项。 这完全取决于你已经设置的内容,所以它可能是另一种方式。

通过删除在创建代码中创建1 + n问题的回调(set_price),您必须创建一些查找方法,一次获取所有价格并将其应用于订单。

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

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

答案 1 :(得分:0)

最后,它只是进行了一些工作流程更改,以加快订单创建速度。

不是在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  

OrdersController:

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