Locker::Room

Traditional data modelling has it that there are two patterns - the optimistic and the pessimistic locking. The optimistic is the Rails default; one in which "we get away with it - if we get away with it" whereas the pessimistic is a design choice available to only MySQL and PostgreSQL even though some like oldmoe are working on bettering this.

No matter the importance of this work most concurrency issues arise at a far earlier point on the transaction timeline - when the data which later on will conflict on writes are read; like the sales_order and sales_order_lines that really hangs on product.quantity(transposable to any domain - flights and flight_seats, theatre and theatre_seats, more). It all comes down to a conflict of resource allocation - to whom does this seat go?

Allowing Ruby/Rails developers an easy way out of this predicament would validate Rails as a lot more than just a "toy" and send SQLite on a stratospheric course!

Locker Room refers to rooms where you put stuff/clothes while doing something in a different state like playing a game, working out, working on a shop floor, more. Once done - you return to the locker room and recover your other "stuff". Should you forget/decide not to recover your other stuff, the locks in the locker room are preset to open automagically.

Workflow:

  1. find scarce resource and reserve it
  2. allow user UI to process data
  3. possibly broadcast inquiry to user asking for intent to keep locking resource
  4. update scarce resource (if appropriate) and release

Imagine

resource = Resource.reserve(
  id: params.expect(:resource)[:id], 
  lock: [ quantity: params.expect(:resource)[:quantity_to_reserve] ], 
  duration: params.expect(:resource)[:how_long_to_reserve]
)

Using SELECT...FOR UPDATE is not the right tool 'cause hits are scarse (how many browse handbags as opposed to the number actually buying them 😎 ) and postpone the issue until the write - what we need is a polymorphic table with a background job that do house cleaning on Locker::Room.where( release_at: ..Time.current-1.second).delete_all

class ApplicationRecord < ActiveRecord::Base
  def self.reserve(id:, lock:, duration:)
    release = Time.current + duration
    return find( :id) if Locker::Room.create( 
      id: id, 
      type: self.name, 
      lock: lock, 
      reserved_by: Current.user&.id
      release_at: release )
    false
  end

  def self.release(id:, lock:)
    Locker::Room.where( id: id, lock: lock).delete
  end
end

class Locker::Room < ActiveRecord::Base
  # 
  # composite primary key = [ record_id, record_type, lock ]
  # attr :release_at, :reserved_by
end

Thoughts?