Hi, On my site, if a lot of people place orders at the same time, the available quantity left in stock for a product seems to get wrong every so often. Here''s my execute_purchase method, which is a before_filter on the Order model: def execute_purchase Product.transaction do product.lock! if product.quantity >= quantity # enough in stock if credit_card_needed? response = authorize_payment if response.success? product.update_attribute(:quantity, product.quantity - self.quantity) else raise FulfillmentError, response.message end end else raise FulfillmentError, "not enough in stock" end end rescue FulfillmentError => e errors.add_to_base e.message return false end So, I enter the transaction. Lock the product. I see if there''s enough in stock. If there''s enough in stock, then I authorize the payment -- if that''s successful, then I decrement the available quantity. Since I''m in a transaction and I''ve locked the record, this method should be fine, right? So, what happens is sometimes we sell more than we have. So, the product quantity isn''t always updated when an order happens. --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
I''m just trying to guess so forgive me if I''m saying something stupid Let''s think to two almost simultaneous request A and B this is the timing 1) request A land on your page and load the product (quantity = 3) and A wants to by 1 2) request B land on your page and load the product (quantity = 3) and B wants to buy 1 3) request A execute the transaction setting quantity = 3 -1 => 2 4) request B execute the transaction setting quantity = 3 - 1 => 2 That''s how you end up with still 2 available even if you''ve sold 2 items. update_attribute doesn''t run validations so in this case lock_version doesn''t protect you. Paolo On 14/05/07, joevandyk-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org <joevandyk-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote:> > Hi, > > On my site, if a lot of people place orders at the same time, the > available quantity left in stock for a product seems to get wrong > every so often. > > Here''s my execute_purchase method, which is a before_filter on the > Order model: > > def execute_purchase > Product.transaction do > product.lock! > if product.quantity >= quantity # enough in stock > if credit_card_needed? > response = authorize_payment > if response.success? > product.update_attribute(:quantity, product.quantity - > self.quantity) > else > raise FulfillmentError, response.message > end > end > else > raise FulfillmentError, "not enough in stock" > end > end > rescue FulfillmentError => e > errors.add_to_base e.message > return false > end > > So, I enter the transaction. Lock the product. I see if there''s > enough in stock. If there''s enough in stock, then I authorize the > payment -- if that''s successful, then I decrement the available > quantity. Since I''m in a transaction and I''ve locked the record, > this method should be fine, right? > > So, what happens is sometimes we sell more than we have. So, the > product quantity isn''t always updated when an order happens.--~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
joevandyk-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org
2007-May-14 20:52 UTC
Re: concurrency issue
Shouldn''t the product.lock! line give me a lock on that row? So, the other requests need to wait until the transaction is over with. Then the product should get reloaded with current data. Right? On May 14, 1:42 pm, "Paolo Negri" <hungryl...-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote:> I''m just trying to guess so forgive me if I''m saying something stupid > > Let''s think to two almost simultaneous request A and B > > this is the timing > > 1) request A land on your page and load the product (quantity = 3) and > A wants to by 1 > 2) request B land on your page and load the product (quantity = 3) and > B wants to buy 1 > 3) request A execute the transaction setting quantity = 3 -1 => 2 > 4) request B execute the transaction setting quantity = 3 - 1 => 2 > > That''s how you end up with still 2 available even if you''ve sold 2 items. > > update_attribute doesn''t run validations so in this case lock_version > doesn''t protect you. > > Paolo > > On 14/05/07, joevan...-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org <joevan...-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote: > > > > > Hi, > > > On my site, if a lot of people place orders at the same time, the > > available quantity left in stock for a product seems to get wrong > > every so often. > > > Here''s my execute_purchase method, which is a before_filter on the > > Order model: > > > def execute_purchase > > Product.transaction do > > product.lock! > > if product.quantity >= quantity # enough in stock > > if credit_card_needed? > > response = authorize_payment > > if response.success? > > product.update_attribute(:quantity, product.quantity - > > self.quantity) > > else > > raise FulfillmentError, response.message > > end > > end > > else > > raise FulfillmentError, "not enough in stock" > > end > > end > > rescue FulfillmentError => e > > errors.add_to_base e.message > > return false > > end > > > So, I enter the transaction. Lock the product. I see if there''s > > enough in stock. If there''s enough in stock, then I authorize the > > payment -- if that''s successful, then I decrement the available > > quantity. Since I''m in a transaction and I''ve locked the record, > > this method should be fine, right? > > > So, what happens is sometimes we sell more than we have. So, the > > product quantity isn''t always updated when an order happens.--~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
Yes, you lock the row, but the point is at that point it On 14/05/07, joevandyk-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org <joevandyk-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote:> > Shouldn''t the > product.lock! > line give me a lock on that row? So, the other requests need to wait > until the transaction is over with.The above is correct, it waits, but what at this point product is has been already loaded and it contains quantity 3> Then the product should get > reloaded with current data.The above is incorrect If you don''t reload explicitly the ActiveRecord object it doesn''t reload itself, it will keep forever a STALE image of the database. Paolo> > Right? > > On May 14, 1:42 pm, "Paolo Negri" <hungryl...-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote: > > I''m just trying to guess so forgive me if I''m saying something stupid > > > > Let''s think to two almost simultaneous request A and B > > > > this is the timing > > > > 1) request A land on your page and load the product (quantity = 3) and > > A wants to by 1 > > 2) request B land on your page and load the product (quantity = 3) and > > B wants to buy 1 > > 3) request A execute the transaction setting quantity = 3 -1 => 2 > > 4) request B execute the transaction setting quantity = 3 - 1 => 2 > > > > That''s how you end up with still 2 available even if you''ve sold 2 items. > > > > update_attribute doesn''t run validations so in this case lock_version > > doesn''t protect you. > > > > Paolo > > > > On 14/05/07, joevan...-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org <joevan...-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote: > > > > > > > > > Hi, > > > > > On my site, if a lot of people place orders at the same time, the > > > available quantity left in stock for a product seems to get wrong > > > every so often. > > > > > Here''s my execute_purchase method, which is a before_filter on the > > > Order model: > > > > > def execute_purchase > > > Product.transaction do > > > product.lock! > > > if product.quantity >= quantity # enough in stock > > > if credit_card_needed? > > > response = authorize_payment > > > if response.success? > > > product.update_attribute(:quantity, product.quantity - > > > self.quantity) > > > else > > > raise FulfillmentError, response.message > > > end > > > end > > > else > > > raise FulfillmentError, "not enough in stock" > > > end > > > end > > > rescue FulfillmentError => e > > > errors.add_to_base e.message > > > return false > > > end > > > > > So, I enter the transaction. Lock the product. I see if there''s > > > enough in stock. If there''s enough in stock, then I authorize the > > > payment -- if that''s successful, then I decrement the available > > > quantity. Since I''m in a transaction and I''ve locked the record, > > > this method should be fine, right? > > > > > So, what happens is sometimes we sell more than we have. So, the > > > product quantity isn''t always updated when an order happens. > > > > >--~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
Sorry for the mess in the previous message, cut and paste error. Basically when you instantiate an AR object it loads the data at that moment and never updates until you call the .reload method. An AR object is not aware of what happens to its image in the database. Paolo On 14/05/07, Paolo Negri <hungrylist-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote:> Yes, you lock the row, but the point is at that point it > > On 14/05/07, joevandyk-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org <joevandyk-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote: > > > > Shouldn''t the > > product.lock! > > line give me a lock on that row? So, the other requests need to wait > > until the transaction is over with. > > The above is correct, it waits, but what at this point product is has > been already loaded and it contains quantity 3 > > > Then the product should get > > reloaded with current data. > > The above is incorrect > > If you don''t reload explicitly the ActiveRecord object it doesn''t > reload itself, it will keep forever a STALE image of the database. > > Paolo > > > > > Right? > > > > On May 14, 1:42 pm, "Paolo Negri" <hungryl...-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote: > > > I''m just trying to guess so forgive me if I''m saying something stupid > > > > > > Let''s think to two almost simultaneous request A and B > > > > > > this is the timing > > > > > > 1) request A land on your page and load the product (quantity = 3) and > > > A wants to by 1 > > > 2) request B land on your page and load the product (quantity = 3) and > > > B wants to buy 1 > > > 3) request A execute the transaction setting quantity = 3 -1 => 2 > > > 4) request B execute the transaction setting quantity = 3 - 1 => 2 > > > > > > That''s how you end up with still 2 available even if you''ve sold 2 items. > > > > > > update_attribute doesn''t run validations so in this case lock_version > > > doesn''t protect you. > > > > > > Paolo > > > > > > On 14/05/07, joevan...-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org <joevan...-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote: > > > > > > > > > > > > > Hi, > > > > > > > On my site, if a lot of people place orders at the same time, the > > > > available quantity left in stock for a product seems to get wrong > > > > every so often. > > > > > > > Here''s my execute_purchase method, which is a before_filter on the > > > > Order model: > > > > > > > def execute_purchase > > > > Product.transaction do > > > > product.lock! > > > > if product.quantity >= quantity # enough in stock > > > > if credit_card_needed? > > > > response = authorize_payment > > > > if response.success? > > > > product.update_attribute(:quantity, product.quantity - > > > > self.quantity) > > > > else > > > > raise FulfillmentError, response.message > > > > end > > > > end > > > > else > > > > raise FulfillmentError, "not enough in stock" > > > > end > > > > end > > > > rescue FulfillmentError => e > > > > errors.add_to_base e.message > > > > return false > > > > end > > > > > > > So, I enter the transaction. Lock the product. I see if there''s > > > > enough in stock. If there''s enough in stock, then I authorize the > > > > payment -- if that''s successful, then I decrement the available > > > > quantity. Since I''m in a transaction and I''ve locked the record, > > > > this method should be fine, right? > > > > > > > So, what happens is sometimes we sell more than we have. So, the > > > > product quantity isn''t always updated when an order happens.--~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
joevandyk-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org
2007-May-14 21:15 UTC
Re: concurrency issue
api.rubyonrails.org/classes/ActiveRecord/Locking/Pessimistic.html#M000646 Says that the record should be reloaded. In fact, here''s the source code for lock: 71: def lock!(lock = true) 72: reload(:lock => lock) unless new_record? 73: self 74: end Am I not understanding this correctly? Joe On May 14, 2:07 pm, "Paolo Negri" <hungryl...-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote:> Sorry for the mess in the previous message, cut and paste error. > > Basically when you instantiate an AR object it loads the data at that > moment and never updates until you call the .reload method. > An AR object is not aware of what happens to its image in the database. > > Paolo > > On 14/05/07, Paolo Negri <hungryl...-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote: > > > Yes, you lock the row, but the point is at that point it > > > On 14/05/07, joevan...-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org <joevan...-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote: > > > > Shouldn''t the > > > product.lock! > > > line give me a lock on that row? So, the other requests need to wait > > > until the transaction is over with. > > > The above is correct, it waits, but what at this point product is has > > been already loaded and it contains quantity 3 > > > > Then the product should get > > > reloaded with current data. > > > The above is incorrect > > > If you don''t reload explicitly the ActiveRecord object it doesn''t > > reload itself, it will keep forever a STALE image of the database. > > > Paolo > > > > Right? > > > > On May 14, 1:42 pm, "Paolo Negri" <hungryl...-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote: > > > > I''m just trying to guess so forgive me if I''m saying something stupid > > > > > Let''s think to two almost simultaneous request A and B > > > > > this is the timing > > > > > 1) request A land on your page and load the product (quantity = 3) and > > > > A wants to by 1 > > > > 2) request B land on your page and load the product (quantity = 3) and > > > > B wants to buy 1 > > > > 3) request A execute the transaction setting quantity = 3 -1 => 2 > > > > 4) request B execute the transaction setting quantity = 3 - 1 => 2 > > > > > That''s how you end up with still 2 available even if you''ve sold 2 items. > > > > > update_attribute doesn''t run validations so in this case lock_version > > > > doesn''t protect you. > > > > > Paolo > > > > > On 14/05/07, joevan...-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org <joevan...-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote: > > > > > > Hi, > > > > > > On my site, if a lot of people place orders at the same time, the > > > > > available quantity left in stock for a product seems to get wrong > > > > > every so often. > > > > > > Here''s my execute_purchase method, which is a before_filter on the > > > > > Order model: > > > > > > def execute_purchase > > > > > Product.transaction do > > > > > product.lock! > > > > > if product.quantity >= quantity # enough in stock > > > > > if credit_card_needed? > > > > > response = authorize_payment > > > > > if response.success? > > > > > product.update_attribute(:quantity, product.quantity - > > > > > self.quantity) > > > > > else > > > > > raise FulfillmentError, response.message > > > > > end > > > > > end > > > > > else > > > > > raise FulfillmentError, "not enough in stock" > > > > > end > > > > > end > > > > > rescue FulfillmentError => e > > > > > errors.add_to_base e.message > > > > > return false > > > > > end > > > > > > So, I enter the transaction. Lock the product. I see if there''s > > > > > enough in stock. If there''s enough in stock, then I authorize the > > > > > payment -- if that''s successful, then I decrement the available > > > > > quantity. Since I''m in a transaction and I''ve locked the record, > > > > > this method should be fine, right? > > > > > > So, what happens is sometimes we sell more than we have. So, the > > > > > product quantity isn''t always updated when an order happens.--~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
On 14/05/07, joevandyk-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org <joevandyk-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote:> > > api.rubyonrails.org/classes/ActiveRecord/Locking/Pessimistic.html#M000646 > > Says that the record should be reloaded. > > In fact, here''s the source code for lock: > > 71: def lock!(lock = true) > 72: reload(:lock => lock) unless new_record? > 73: self > 74: end > > Am I not understanding this correctly? > > Joe >Sorry, you''re definitely right, it should reload the object so what I''ve said is not valid. Paolo --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
I just noticed you decrease the quantity only when credit_card_needed? Is that right? Paolo On May 14, 9:31 pm, joevan...-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org wrote:> Hi, > > On my site, if a lot of people place orders at the same time, the > available quantity left in stock for a product seems to get wrong > every so often. > > Here''s my execute_purchase method, which is a before_filter on the > Order model: > > def execute_purchase > Product.transaction do > product.lock! > if product.quantity >= quantity # enough in stock > if credit_card_needed? > response = authorize_payment > if response.success? > product.update_attribute(:quantity, product.quantity - > self.quantity) > else > raise FulfillmentError, response.message > end > end > else > raise FulfillmentError, "not enough in stock" > end > end > rescue FulfillmentError => e > errors.add_to_base e.message > return false > end > > So, I enter the transaction. Lock the product. I see if there''s > enough in stock. If there''s enough in stock, then I authorize the > payment -- if that''s successful, then I decrement the available > quantity. Since I''m in a transaction and I''ve locked the record, > this method should be fine, right? > > So, what happens is sometimes we sell more than we have. So, the > product quantity isn''t always updated when an order happens.--~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
Hi, What about doing preventive stock update: Le 14 mai 07, à 22:31, joevandyk-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org a écrit :> Here''s my execute_purchase method, which is a before_filter on the > Order model: > > def execute_purchase > Product.transaction do > if product.quantity >= quantity # enough in stock > if credit_card_needed?UPDATE products SET quantity=quantity-#{product.quantity}> response = authorize_payment > if response.success?msg Success> elseUPDATE products SET quantity=quantity+#{product.quantity}> raise FulfillmentError, response.message > endelse # what if credit card is not needed ??> end > else > raise FulfillmentError, "not enough in stock" > end > end > rescue FulfillmentError => e > errors.add_to_base e.message > return false > endImho no lock should be necessary in this case. You simply risk to answer that there''s no more stock whereas there is some 5 ms later. Better than the opposite. Jean-Christophe Michel -- symetrie.com Better Nested Set for rails: opensource.symetrie.com/trac/better_nested_set --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe@googlegroups.com For more options, visit this group at groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---