LoginSignup
0
0

More than 3 years have passed since last update.

トランザクションシミュレータ

Last updated at Posted at 2020-10-15

トランザクションの検証が面倒すぎる。モデルとしては単純そうだから、作ってしまおう、と思った。

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 !@locks.filter{|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()
}

実行結果

  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

こんな感じ。

別のトランザクションイベント。Xの排他ロックがかかっていた場合。

    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)

tr_bの読み込みがロックされて、このイベント列は失敗する。

振り返り

  1. 次はロック待ちの状況をモデリングしていこう。片方のトランザクションが終われば、もう片方のロック待ちも解消される、というパターン。

  2. まともにテスト書いて、モデルを検証する必要がある。

  3. MVCCではなくて、単純なロック機構でのシミュレーションなので、MVCCのつもりでイベント列を見ていると混乱する。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0