views:

181

answers:

2

Say I have this method:

def create_registration_unless_over_limit
  if Registration.count < 50
    Registration.create!
  else
    raise "too many registrations"
  end
end

How do I ensure that I never have >= 50 registrations considering that I have more than one rails process running? Theoretically, isn't it possible that two requests come in virtually simultaneously, both run the if statement and see < 50 registrations is true and then both of them create a new registration?

A: 

stored proc to create the row if < 50, or else return a fault code

Or!

Create a queue system that creates the accounts, ensuring one process is creating them at all times

+1  A: 

For your particular case, just ignore the problem :) I'm 99.99% sure you don't really care if you have 50 or 51 registrations :) In a more general case you need to employ some locking.

SELECT count(*) FROM registrations FOR UPDATE

the above query will block if another thread executed it and is still inside a transaction.

unfortunately 'count' method of ActiveRecord::Base doesn't support the :lock parameter (hmm, why?). So you need to use count_by_sql. Try the following in 2 separate terminal screens:

./script/runner 'Registration.transaction {puts "counting...";c = Registration.count_by_sql("select count(*) from registrations for update");puts "count = #{c}. press enter to continue";gets}'

while 1st one that runs will stop at the 'press enter' stage, the 2nd one should block at the 'counting...' stage until you release the 1st.

Vitaly Kushner
So you're suggesting that I use the above count statement in place of my Registration.count.With a few consoles open that *seems* to do what I want, that is, the second console just pauses and waits for the first one to finish, however I still don't fully understand what is going on. The docs on select ... for update didn't help all that much either.
crankharder
After more playing around with this i'm still pretty sure it's what I need. Now I'm wondering if FOR UPDATE can be used in a more general sense to enforce single threading of any method?The idea would be add "FOR UPDATE" to the end of your select and then wrap everything that is needed to be single-threaded in a transaction block.Am I taking this too far now?
crankharder
no, this is more or less how its supposed to be used. When you locked something for update, others will block when trying to read it. Note though that if you do too much locking your application performance will suffer. in most, almost all of the usual cases, you can and should just ignore the locking.
Vitaly Kushner
This is especially true lately when rails only updates the fields that changed. before that if you had a thread updating object.foo and another one updating object.bar, they might step on each one's data. Now you can safely run it in parallel without the locks.
Vitaly Kushner