[RUBY] Simulateur de transaction

La validation des transactions est trop fastidieuse. Cela semble simple comme modèle, alors j'ai pensé en faire un.

Ruby

ruby.trans.rb


class Command end
class Begin < Command
end
class Rollback < Command
end
class Commit < Command
end
class Read < Command
    attr_reader :var
    def initialize(var)
        @var = var
    end
end
class Write < Command
    attr_reader :var,:val
    def initialize(var,val)
        @var = var
        @val = val
    end
end
class Lock
    attr_reader :tr,:var,:type
    def initialize(tr,var,type)
        @tr=tr
        @var=var
        @type=type
    end
end

class Unlock
    attr_reader :tr,:var
    def initialize(tr,var)
        @tr= tr
        @var=var
    end
end
class Insert
    attr_reader :var,:val
    def initialize(var,val)
        @var=var
        @val=val
    end
end

class Delete
    attr_reader :var
    def initialize(var)
        @var=var
    end
end

class Event
    attr_reader :tr,:cmd
    # tr :String
    # cmd : Command
    def initialize(tr, cmd)
        @tr = tr
        @cmd = cmd
    end
end


class Database 
    attr_reader :vars
    # vars : Map<var_name,value>
    def initialize(vars)
        @vars = vars
    end
end

class Transaction
    attr_accessor :name,:vars
    def initialize(name)
        @name=name
        @vars= {}
    end
end

class State
    attr_reader :db,:events,:pointer,:trs
    def initialize(db,events,trs)
        @db = db
        @locks = []
        @events = events
        @pointer = 0
        @trs = trs
    end

    def next()
        e = @events[@pointer]
        if e then
            if e.cmd.is_a?(Begin) then
                puts "Begin #{e.tr}"
            elsif e.cmd.is_a?(Rollback) then
                puts "Rollback #{e.tr}"
            elsif e.cmd.is_a?(Commit) then
                puts "Commit #{e.tr}"
            elsif e.cmd.is_a?(Read) then
                if @locks.filter {|l| l.tr != e.tr && l.var == e.cmd.var && l.type == :Exclusive}.empty? then
                    puts "Read #{e.tr} var=#{e.cmd.var} => val=#{@db.vars[e.cmd.var]}"
                else
                    puts "Read #{e.tr} var=#{e.cmd.var} => Read failed. This variable is locked by another transaction."
                    raise("Reading failure")
                end
            elsif e.cmd.is_a?(Write) then
                if @locks.filter {|l| l.tr != e.tr && l.var == e.cmd.var}.empty? then
                    pre = @db.vars[e.cmd.var]
                    @db.vars[e.cmd.var]=e.cmd.val
                    puts "Write #{e.tr} var=#{e.cmd.var} val=#{e.cmd.val} => db[#{e.cmd.var}]: #{pre} => #{@db.vars[e.cmd.var]}"
                else
                    puts "Write #{e.tr} var=#{e.cmd.var} val=#{e.cmd.val} => This variable is locked by another transaction."
                    raise("Writing failure")
                end
            elsif e.cmd.is_a?(Lock) then
                if @locks.filter {|l| l.tr != e.cmd.tr && l.var == e.cmd.var && l.type == :Exclusive}.empty? then
                    @locks << e.cmd
                    puts "Lock #{e.tr} var=#{e.cmd.var} type=#{e.cmd.type} => Success."
                else
                    puts "Lock #{e.tr} var=#{e.cmd.var} type=#{e.cmd.type} => This variable is locked by another transaction. Getting lock was failed."
                    raise("Locking failure")
                end
            elsif e.cmd.is_a?(Unlock) then
                @locks.filter!{|l| !(l.tr == e.tr && l.var == e.cmd.var)}
                puts "Unlock #{e.tr}"
            elsif e.cmd.is_a?(Insert) then
                if @db.vars[e.cmd.var].nil? then
                    @db.vars[e.cmd.var] = e.cmd.val
                    puts "Insert #{e.tr} var=#{e.cmd.var} val=#{e.cmd.val} => Success."
                else
                    puts "Insert #{e.tr} var=#{e.cmd.var} val=#{e.cmd.val} => This variable is already exists. Insertion was failed."
                    raise("Insertion failure")
                end
            elsif e.cmd.is_a?(Delete) then
                if [email protected]{|l| l.tr != e.tr && l.var == e.cmd.var}.empty? then
                    puts "Delete #{e.tr} var=#{e.cmd.var} => This variable is locked by another transaction. Deletion was failed."
                    raise("Deletion failure")
                end
                if @db.vars[e.cmd.var].nil? then
                    puts "Delete #{e.tr} var=#{e.cmd.var} => No such variable. Deletion was failed."
                    raise("Deletion failure")
                end
                @db.vars.delete(e.cmd.var)
                @locks.filter! {|l| !(l.tr == e.tr && l.var == e.cmd.var)}
                puts "Delete #{e.tr} var=#{e.cmd.var} => Success"
            else
                puts "Other"
            end
        else
            # the last of events
        end
        @pointer += 1
    end
end


@events = [
    Event.new("tr_a",Begin.new()),
    Event.new("tr_b",Begin.new()),
    Event.new("tr_a",Lock.new("tr_a","X",:Shared)),
    Event.new("tr_a",Read.new("X")),
    Event.new("tr_b",Read.new("X")),
    Event.new("tr_a",Lock.new("tr_a","X",:Exclusive)),
    Event.new("tr_a",Write.new("X",20)),
    Event.new("tr_a",Unlock.new("tr_a","X")),
    Event.new("tr_b",Read.new("X")),
    Event.new("tr_b",Insert.new("Y",12)),
    Event.new("tr_b",Delete.new("Y")),
    Event.new("tr_a",Commit.new()),
    Event.new("tr_b",Commit.new())
]

@vars = {"X" => 10}
@db = Database.new(@vars)

@trs = {
    "tr_a" => Transaction.new("tr_a"),
    "tr_b" => Transaction.new("tr_b"),
}

@state = State.new(@db,@events,@trs)

@events.each_index {|i|
    printf("%3d ",i)
    @state.next()
}

Résultat d'exécution

  0 Begin tr_a
  1 Begin tr_b
  2 Lock tr_a var=X type=Shared => Success.
  3 Read tr_a var=X => val=10
  4 Read tr_b var=X => val=10
  5 Lock tr_a var=X type=Exclusive => Success.
  6 Write tr_a var=X val=20 => db[X]: 10 => 20
  7 Unlock tr_a
  8 Read tr_b var=X => val=20
  9 Insert tr_b var=Y val=12 => Success.
 10 Delete tr_b var=Y => Success
 11 Commit tr_a
 12 Commit tr_b

Comme ça.

Un autre événement de transaction. Lorsque le verrouillage exclusif de X est appliqué.

    Event.new("tr_a",Begin.new()),
    Event.new("tr_b",Begin.new()),
    Event.new("tr_a",Lock.new("tr_a","X",:Exclusive)),
    Event.new("tr_a",Read.new("X")),
    Event.new("tr_b",Read.new("X")),
  0 Begin tr_a
  1 Begin tr_b
  2 Lock tr_a var=X type=Exclusive => Success.
  3 Read tr_a var=X => val=10
  4 Read tr_b var=X => Read failed. This variable is locked by another transaction.
Traceback (most recent call last):
        3: from tran.rb:182:in `<main>'
        2: from tran.rb:182:in `each_index'
        1: from tran.rb:184:in `block in <main>'
tran.rb:103:in `next': Reading failure (RuntimeError)

Le chargement de tr_b est verrouillé et cette colonne d'événements échoue.

Regarder en arrière

  1. Ensuite, modélisons la situation d'attente de verrouillage. Le modèle est que lorsqu'une transaction est terminée, l'attente de verrouillage pour l'autre est également annulée.

  2. Vous devez écrire un test décent et valider le modèle.

  3. Puisqu'il s'agit d'une simulation avec un mécanisme de verrouillage simple, pas MVCC, il est déroutant de voir la séquence d'événements avec l'intention de MVCC.

Recommended Posts

Simulateur de transaction
Simulateur de transaction (version de verrouillage en attente)
"Simulateur de Pokémon de couleur différente"