From 33e2a8362731b042ba434d0ec80c9ae97434def7 Mon Sep 17 00:00:00 2001 From: Yilin Chen Date: Mon, 21 Oct 2019 16:36:56 +0800 Subject: [PATCH 01/20] pessimistic init Signed-off-by: Yilin Chen --- .../PessimisticTransaction.tla | 153 ++++++++++++++++++ .../PessimisticTransaction___Test1.launch | 30 ++++ 2 files changed, 183 insertions(+) create mode 100644 PessimisticTransaction/PessimisticTransaction.tla create mode 100644 PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test1.launch diff --git a/PessimisticTransaction/PessimisticTransaction.tla b/PessimisticTransaction/PessimisticTransaction.tla new file mode 100644 index 0000000..6c1beff --- /dev/null +++ b/PessimisticTransaction/PessimisticTransaction.tla @@ -0,0 +1,153 @@ +----------------------- MODULE PessimisticTransaction ----------------------- + +EXTENDS Integers + +\* The set of transaction keys. +CONSTANTS KEY +ASSUME KEY # {} \* Keys cannot be empty. + +\* The set of clients to execute a transaction. +CONSTANTS CLIENT + +\* next_ts is the timestamp for transaction. It is increased monotonically, +\* so every transaction must have a unique start and commit ts. +VARIABLES next_ts + +\* client_state[c] is the state of client. +VARIABLES client_state + +\* client_ts[c] is a record of [start_ts, for_update_ts, commit_ts]. +VARIABLES client_ts + +\* client_key[c] is a record of [primary: key, secondary: {key}, +\* pessimistic: {key}, pending: {key}]. Hereby, "pessimistic" denotes +\* the keys whose pessimistic locks haven't been acquired, "pending" +\* denotes the keys that are pending for prewrite. +VARIABLES client_key + +\* key_data[k] is the set of multi-version data of the key. +\* Since we don't care about the concrete value of data, a record of +\* [ts: start_ts] is sufficient to represent one data version. +VARIABLES key_data + +\* key_lock[k] is the set of lock. A lock is of a record of [ts: start_ts, +\* for_update_ts, primary: key]. If primary equals to k, it is a primary +\* lock, otherwise secondary lock. If for_update_ts > 0, it belongs to a +\* pessimistic transaction. +VARIABLES key_lock + +\* key_write[k] is a sequence of committed version of the key. +\* A committed version of the key is a record of [ts, type, start_ts]. +\* type can be "write" or "rollback" depending on record type. start_ts +\* field only exists if type is "write". For "write" record, ts denotes +\* commit_ts; for "rollback" record, $ts$ denotes start_ts. +VARIABLES key_write + +\* key_last_read_ts is for verifying snapshot isolation invariant. It should +\* not appear in a real-world implementation. +\* +\* key_last_read_ts[k] denotes the last read timestamp for key k, this +\* should be monotonic. +VARIABLES key_last_read_ts + +\* The set of all messages sent by clients. To simulate message resending, +\* client messages are inserted to the set and never deleted. The server can +\* pick any message in the set to execute. +VARIABLES msg + +client_vars == <> +key_vars == <> +vars == <> + +----------------------------------------------------------------------------- + +Pos == {x \in Nat : x > 0} + +----------------------------------------------------------------------------- + +Init == + /\ next_ts = 1 + /\ client_state = [c \in CLIENT |-> "init"] + /\ client_ts = [c \in CLIENT |-> [start_ts |-> 0, + for_update_ts |-> 0, + commit_ts |-> 0] + ] + /\ client_key = [c \in CLIENT |-> {}] + /\ key_lock = [k \in KEY |-> {}] + /\ key_data = [k \in KEY |-> {}] + /\ key_write = [k \in KEY |-> {}] + /\ key_last_read_ts = [k \in KEY |-> 0] + /\ msg = {} + +Next == + UNCHANGED <> + +----------------------------------------------------------------------------- + +NextTsTypeInv == next_ts \in Pos + +ClientStateTypeInv == + client_state \in [ + CLIENT -> {"init", "working", "prewriting", + "committing", "committed", "aborted"} + ] + +ClientTsTypeInv == + client_ts \in + [CLIENT -> [start_ts : Nat, for_update_ts: Nat, commit_ts : Nat]] + +ClientKeyTypeInv == + client_key \in [ + CLIENT -> {{}} \union [ + primary: KEY, + secondary: SUBSET KEY, + pessimistic: SUBSET KEY, + pending : SUBSET KEY + ] + ] + +KeyDataTypeInv == + key_data \in [KEY -> SUBSET [ts: Pos]] + +KeyLockTypeInv == + key_lock \in [KEY -> SUBSET [ts: Pos, for_update_ts: Nat, primary: KEY]] + +KeyWriteTypeInv == + key_write \in [KEY -> + SUBSET [ts: Pos, type: {"write", "rollback"}, start_ts: Pos] + ] + +KeyWriteStartTsInv == + \A k \in DOMAIN key_write: + LET + rec == key_write[k] + IN + \/ rec = {} + \/ IF rec.type = "write" + THEN rec.ts > rec.start_ts + ELSE rec.type = "rollback" /\ rec.ts = rec.start_ts + +KeyLastReadTsTypeInv == + key_last_read_ts \in [KEY -> Nat] + +MsgTypeInv == + msg \subseteq ( + [c: CLIENT, type: {"lock", "prewrite"}, key: KEY, + start_ts: Pos, for_update_ts: Pos] \union + [c: CLIENT, type: {"commit"}, key: KEY, commit_ts: Pos] \union + [c: CLIENT, type: {"rollback"}, key: KEY] + ) + +TypeInvariant == + /\ NextTsTypeInv + /\ ClientStateTypeInv + /\ ClientTsTypeInv + /\ ClientKeyTypeInv + /\ KeyDataTypeInv + /\ KeyLockTypeInv + /\ KeyWriteTypeInv + /\ KeyWriteStartTsInv + /\ KeyLastReadTsTypeInv + /\ MsgTypeInv + +============================================================================= diff --git a/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test1.launch b/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test1.launch new file mode 100644 index 0000000..ff25b25 --- /dev/null +++ b/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test1.launch @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 5e02db1bfb3656819197a1a9ca41911d084cc9c6 Mon Sep 17 00:00:00 2001 From: Yilin Chen Date: Mon, 21 Oct 2019 17:36:20 +0800 Subject: [PATCH 02/20] add start action Signed-off-by: Yilin Chen --- .../PessimisticTransaction.tla | 42 +++++++++++++------ .../PessimisticTransaction___Test1.launch | 2 +- 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/PessimisticTransaction/PessimisticTransaction.tla b/PessimisticTransaction/PessimisticTransaction.tla index 6c1beff..8cf7394 100644 --- a/PessimisticTransaction/PessimisticTransaction.tla +++ b/PessimisticTransaction/PessimisticTransaction.tla @@ -65,22 +65,39 @@ Pos == {x \in Nat : x > 0} ----------------------------------------------------------------------------- +Start(c) == + /\ client_state[c] = "init" + /\ next_ts' = next_ts + 1 + /\ \E ks \in SUBSET KEY: + \E primary \in ks: + client_key' = + [client_key EXCEPT + ![c] = [primary |-> primary, + secondary |-> ks \ {primary}, + pessimistic |-> ks, + pending |-> ks] + ] + /\ client_state' = [client_state EXCEPT ![c] = "working"] + /\ client_ts' = [client_ts EXCEPT ![c].start_ts = next_ts'] + /\ UNCHANGED <> + Init == /\ next_ts = 1 /\ client_state = [c \in CLIENT |-> "init"] /\ client_ts = [c \in CLIENT |-> [start_ts |-> 0, for_update_ts |-> 0, - commit_ts |-> 0] - ] + commit_ts |-> 0]] /\ client_key = [c \in CLIENT |-> {}] /\ key_lock = [k \in KEY |-> {}] /\ key_data = [k \in KEY |-> {}] /\ key_write = [k \in KEY |-> {}] /\ key_last_read_ts = [k \in KEY |-> 0] /\ msg = {} - -Next == - UNCHANGED <> + +ClientOp(c) == + \/ Start(c) + +Next == \E c \in CLIENT : ClientOp(c) ----------------------------------------------------------------------------- @@ -97,14 +114,13 @@ ClientTsTypeInv == [CLIENT -> [start_ts : Nat, for_update_ts: Nat, commit_ts : Nat]] ClientKeyTypeInv == - client_key \in [ - CLIENT -> {{}} \union [ - primary: KEY, - secondary: SUBSET KEY, - pessimistic: SUBSET KEY, - pending : SUBSET KEY - ] - ] + \A c \in CLIENT: + \/ client_state[c] = "init" + \/ client_key[c] \in [primary: KEY, + secondary: SUBSET KEY, + pessimistic: SUBSET KEY, + pending : SUBSET KEY + ] KeyDataTypeInv == key_data \in [KEY -> SUBSET [ts: Pos]] diff --git a/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test1.launch b/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test1.launch index ff25b25..6447acc 100644 --- a/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test1.launch +++ b/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test1.launch @@ -5,7 +5,7 @@ - + From 977207b0fc1594748542fe39b229ce0ba9f8f29d Mon Sep 17 00:00:00 2001 From: Yilin Chen Date: Mon, 21 Oct 2019 17:36:43 +0800 Subject: [PATCH 03/20] clean trailing spaces Signed-off-by: Yilin Chen --- .../PessimisticTransaction.tla | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/PessimisticTransaction/PessimisticTransaction.tla b/PessimisticTransaction/PessimisticTransaction.tla index 8cf7394..a91520e 100644 --- a/PessimisticTransaction/PessimisticTransaction.tla +++ b/PessimisticTransaction/PessimisticTransaction.tla @@ -71,7 +71,7 @@ Start(c) == /\ \E ks \in SUBSET KEY: \E primary \in ks: client_key' = - [client_key EXCEPT + [client_key EXCEPT ![c] = [primary |-> primary, secondary |-> ks \ {primary}, pessimistic |-> ks, @@ -96,7 +96,7 @@ Init == ClientOp(c) == \/ Start(c) - + Next == \E c \in CLIENT : ClientOp(c) ----------------------------------------------------------------------------- @@ -108,11 +108,11 @@ ClientStateTypeInv == CLIENT -> {"init", "working", "prewriting", "committing", "committed", "aborted"} ] - + ClientTsTypeInv == client_ts \in [CLIENT -> [start_ts : Nat, for_update_ts: Nat, commit_ts : Nat]] - + ClientKeyTypeInv == \A c \in CLIENT: \/ client_state[c] = "init" @@ -121,7 +121,7 @@ ClientKeyTypeInv == pessimistic: SUBSET KEY, pending : SUBSET KEY ] - + KeyDataTypeInv == key_data \in [KEY -> SUBSET [ts: Pos]] @@ -129,10 +129,10 @@ KeyLockTypeInv == key_lock \in [KEY -> SUBSET [ts: Pos, for_update_ts: Nat, primary: KEY]] KeyWriteTypeInv == - key_write \in [KEY -> + key_write \in [KEY -> SUBSET [ts: Pos, type: {"write", "rollback"}, start_ts: Pos] ] - + KeyWriteStartTsInv == \A k \in DOMAIN key_write: LET @@ -142,18 +142,18 @@ KeyWriteStartTsInv == \/ IF rec.type = "write" THEN rec.ts > rec.start_ts ELSE rec.type = "rollback" /\ rec.ts = rec.start_ts - + KeyLastReadTsTypeInv == key_last_read_ts \in [KEY -> Nat] - + MsgTypeInv == msg \subseteq ( [c: CLIENT, type: {"lock", "prewrite"}, key: KEY, start_ts: Pos, for_update_ts: Pos] \union [c: CLIENT, type: {"commit"}, key: KEY, commit_ts: Pos] \union [c: CLIENT, type: {"rollback"}, key: KEY] - ) - + ) + TypeInvariant == /\ NextTsTypeInv /\ ClientStateTypeInv @@ -165,5 +165,5 @@ TypeInvariant == /\ KeyWriteStartTsInv /\ KeyLastReadTsTypeInv /\ MsgTypeInv - + ============================================================================= From 9dcdf941e4ed6efe485dec0ab4f0b697895b4368 Mon Sep 17 00:00:00 2001 From: Yilin Chen Date: Tue, 22 Oct 2019 15:57:36 +0800 Subject: [PATCH 04/20] lock pessimistic keys Signed-off-by: Yilin Chen --- .../PessimisticTransaction.tla | 240 ++++++++++++++++-- .../PessimisticTransaction___Test1.launch | 14 +- 2 files changed, 221 insertions(+), 33 deletions(-) diff --git a/PessimisticTransaction/PessimisticTransaction.tla b/PessimisticTransaction/PessimisticTransaction.tla index a91520e..d785af6 100644 --- a/PessimisticTransaction/PessimisticTransaction.tla +++ b/PessimisticTransaction/PessimisticTransaction.tla @@ -1,6 +1,6 @@ ----------------------- MODULE PessimisticTransaction ----------------------- -EXTENDS Integers +EXTENDS Integers, TLC \* The set of transaction keys. CONSTANTS KEY @@ -25,6 +25,12 @@ VARIABLES client_ts \* denotes the keys that are pending for prewrite. VARIABLES client_key +\* lock_resolver[c] is a record of [lock: {[key, primary, start_ts]}, +\* status: {[primary, start_ts, commit_ts]}]. When client c encounters +\* a lock, the lock will be added to the lock field. When there are locks +\* in the record, client c may resolve the locks. +VARIABLES lock_resolver + \* key_data[k] is the set of multi-version data of the key. \* Since we don't care about the concrete value of data, a record of \* [ts: start_ts] is sufficient to represent one data version. @@ -46,8 +52,8 @@ VARIABLES key_write \* key_last_read_ts is for verifying snapshot isolation invariant. It should \* not appear in a real-world implementation. \* -\* key_last_read_ts[k] denotes the last read timestamp for key k, this -\* should be monotonic. +\* key_last_read_ts[k] denotes the last read timestamp for key k. +\* The commit_ts of a successful commit should greater then the last read_ts. VARIABLES key_last_read_ts \* The set of all messages sent by clients. To simulate message resending, @@ -55,7 +61,7 @@ VARIABLES key_last_read_ts \* pick any message in the set to execute. VARIABLES msg -client_vars == <> +client_vars == <> key_vars == <> vars == <> @@ -63,13 +69,52 @@ vars == <> Pos == {x \in Nat : x > 0} +Range(m) == {m[i] : i \in DOMAIN m} + +----------------------------------------------------------------------------- + +\* Checks whether there is a lock of key $k$, whose $ts$ is less or equal than +\* $ts$. +hasLockLE(k, ts) == + \E l \in key_lock[k] : l.ts <= ts + +\* Checks whether there is a lock of key $k$ with $ts$. +hasLockEQ(k, ts) == + \E l \in key_lock[k] : l.ts = ts + +\* find a stale lock for key k. +findStaleLock(k, ts) == + {l \in key_lock[k] : l.for_update_ts = 0 /\ l.ts < ts} + +\* Returns the writes with start_ts equals to $ts$. +findWriteWithStartTS(k, ts) == + {w \in Range(key_write[k]) : (w.type = "write" /\ w.start_ts = ts)} + +\* Returns the writes with commit_ts equals to $ts$. +findWriteWithCommitTS(k, ts) == + {w \in Range(key_write[k]) : (w.type = "write" /\ w.ts = ts)} + +\* Returns TRUE if there is a rollback for key $k$ at timestamp $ts$. +hasRollback(k, ts) == + {r \in Range(key_write[k]) : (r.type = "rollback" /\ r.ts = ts)} # {} + +sendTxnStatus(c, k, ts, commit_ts) == + lock_resolver' = [lock_resolver EXCEPT + ![c].status = @ \union {[primary |-> k, + start_ts |-> ts, + commit_ts |-> commit_ts]}] + +writeRollback(k, ts) == + key_write' = [key_write EXCEPT + ![k] = [ts |-> ts, type |-> "rollback", start_ts |-> ts]] + ----------------------------------------------------------------------------- Start(c) == /\ client_state[c] = "init" /\ next_ts' = next_ts + 1 /\ \E ks \in SUBSET KEY: - \E primary \in ks: + \E primary \in ks : client_key' = [client_key EXCEPT ![c] = [primary |-> primary, @@ -78,8 +123,146 @@ Start(c) == pending |-> ks] ] /\ client_state' = [client_state EXCEPT ![c] = "working"] - /\ client_ts' = [client_ts EXCEPT ![c].start_ts = next_ts'] - /\ UNCHANGED <> + /\ client_ts' = [client_ts EXCEPT ![c].start_ts = next_ts', ![c].for_update_ts = next_ts'] + /\ UNCHANGED <> + +Read(c) == + /\ client_state[c] = "working" + /\ \E k \in KEY : + /\ msg' = msg \union + {[c |-> c, type |-> "read", key |-> k, start_ts |-> client_ts[c].start_ts]} + /\ UNCHANGED <> + +CheckTxnStatus(c) == + /\ client_state[c] \in {"working", "prewriting"} + /\ LET + lock == lock_resolver[c].lock + status == lock_resolver[c].status + IN + LET + unknown_lock == + {l \in lock : + ~ \E s \in lock_resolver : + /\ l.primary = s.primary + /\ l.start_ts = s.start_ts} + IN + /\ msg' = msg \union { + [c |-> c, type |-> "cleanup", + key |-> l.primary, start_ts |-> l.start_ts] : l \in unknown_lock + } + /\ UNCHANGED <> + +ResolveLock(c) == + /\ client_state[c] \in {"working", "prewriting"} + /\ LET + lock == lock_resolver[c].lock + status == lock_resolver[c].status + IN + LET + txn_with_lock == + {s \in status : + \E l \in lock : + /\ l.primary = s.primary + /\ l.start_ts = s.start_ts} + IN + /\ msg' = msg \union { + [c |-> c, type |-> "resolve", key |-> txn.primary, + start_ts |-> txn.start_ts, commit_ts |-> txn.commit_ts] : txn \in txn_with_lock + } + /\ UNCHANGED <> + +LockKey(c) == + /\ client_state[c] = "working" + /\ client_key[c].pessimistic # {} + /\ \E k \in client_key[c].pessimistic : + /\ msg' = msg \union + {[c |-> c, type |-> "lock", key |-> k, primary |-> client_key[c].primary, + start_ts |-> client_ts[c].start_ts, for_update_ts |-> client_ts[c].for_update_ts]} + /\ UNCHANGED <> + +ClientOp(c) == + \/ Start(c) + \/ Read(c) + \/ CheckTxnStatus(c) + \/ ResolveLock(c) + \/ LockKey(c) + +DoRead(cmd) == + LET + c == cmd.c + k == cmd.key + ts == cmd.start_ts + IN + LET + stale_lock == findStaleLock(k, ts) + IN + IF stale_lock = {} + THEN + /\ key_last_read_ts' = [key_last_read_ts EXCEPT ![k] = ts] + /\ UNCHANGED <> + ELSE + /\ lock_resolver' = [lock_resolver EXCEPT ![c].lock = @ \union stale_lock] + /\ UNCHANGED <> + +DoCheckTxnStatus(cmd) == + LET + c == cmd.c + k == cmd.key + ts == cmd.start_ts + IN + LET + lock == {l \in key_lock[k] : l.ts = ts} + write == {w \in key_write[k] : w.start_ts = ts} + IN + IF lock = {} + THEN + IF \E w \in write : w.type = "write" + THEN + LET + rec == CHOOSE w \in write : w.type = "write" + IN + /\ sendTxnStatus(c, k, ts, rec.commit_ts) + /\ UNCHANGED <> + ELSE + IF \E w \in write : w.type = "rollback" + THEN + /\ sendTxnStatus(c, k, ts, 0) + /\ UNCHANGED <> + ELSE + /\ writeRollback(k, ts) + /\ sendTxnStatus(c, k, ts, 0) + /\ UNCHANGED <> + ELSE + /\ key_lock' = [key_lock EXCEPT ![k] = {}] + /\ key_data' = [key_data EXCEPT ![k] = @ \ [ts |-> ts]] + /\ writeRollback(k, ts) + /\ sendTxnStatus(c, k, ts, 0) + /\ UNCHANGED <> + +DoLockKey(cmd) == + LET + c == cmd.c + k == cmd.key + primary == cmd.primary + ts == cmd.start_ts + for_update_ts == cmd.for_update_ts + IN + IF key_lock[k] = {} + THEN + /\ key_lock' = [key_lock EXCEPT ![k] = {[ts |-> ts, for_update_ts |-> for_update_ts, primary |-> primary]}] + /\ client_key' = [client_key EXCEPT ![c].pessimistic = @ \ {k}] + /\ UNCHANGED <> + ELSE + /\ lock_resolver' = [lock_resolver EXCEPT ![c].lock = @ \union key_lock[k]] + /\ UNCHANGED <> + +ServerOp(cmd) == + \/ /\ cmd.type = "read" + /\ DoRead(cmd) + \/ /\ cmd.type = "cleanup" + /\ DoCheckTxnStatus(cmd) + \/ /\ cmd.type = "lock" + /\ DoLockKey(cmd) Init == /\ next_ts = 1 @@ -88,16 +271,16 @@ Init == for_update_ts |-> 0, commit_ts |-> 0]] /\ client_key = [c \in CLIENT |-> {}] + /\ lock_resolver = [c \in CLIENT |-> [lock |-> {}, status |-> {}]] /\ key_lock = [k \in KEY |-> {}] /\ key_data = [k \in KEY |-> {}] /\ key_write = [k \in KEY |-> {}] /\ key_last_read_ts = [k \in KEY |-> 0] /\ msg = {} -ClientOp(c) == - \/ Start(c) - -Next == \E c \in CLIENT : ClientOp(c) +Next == + \/ \E cmd \in msg : ServerOp(cmd) + \/ \E c \in CLIENT : ClientOp(c) ----------------------------------------------------------------------------- @@ -111,26 +294,30 @@ ClientStateTypeInv == ClientTsTypeInv == client_ts \in - [CLIENT -> [start_ts : Nat, for_update_ts: Nat, commit_ts : Nat]] + [CLIENT -> [start_ts : Nat, for_update_ts : Nat, commit_ts : Nat]] ClientKeyTypeInv == - \A c \in CLIENT: + \A c \in CLIENT : \/ client_state[c] = "init" - \/ client_key[c] \in [primary: KEY, - secondary: SUBSET KEY, - pessimistic: SUBSET KEY, - pending : SUBSET KEY - ] + \/ client_key[c] \in [primary : KEY, + secondary : SUBSET KEY, + pessimistic : SUBSET KEY, + pending : SUBSET KEY] + +LockResolverTypeInv == + \A c \in CLIENT : + /\ lock_resolver[c].lock \subseteq [key : KEY, primary : KEY, start_ts: Pos] + /\ lock_resolver[c].status \subseteq [primary : KEY, start_ts : Pos, commit_ts : Nat] KeyDataTypeInv == - key_data \in [KEY -> SUBSET [ts: Pos]] + key_data \in [KEY -> SUBSET [ts : Pos]] KeyLockTypeInv == - key_lock \in [KEY -> SUBSET [ts: Pos, for_update_ts: Nat, primary: KEY]] + key_lock \in [KEY -> SUBSET [ts : Pos, for_update_ts : Nat, primary : KEY]] KeyWriteTypeInv == key_write \in [KEY -> - SUBSET [ts: Pos, type: {"write", "rollback"}, start_ts: Pos] + SUBSET [ts : Pos, type : {"write", "rollback"}, start_ts : Pos] ] KeyWriteStartTsInv == @@ -148,10 +335,12 @@ KeyLastReadTsTypeInv == MsgTypeInv == msg \subseteq ( - [c: CLIENT, type: {"lock", "prewrite"}, key: KEY, - start_ts: Pos, for_update_ts: Pos] \union - [c: CLIENT, type: {"commit"}, key: KEY, commit_ts: Pos] \union - [c: CLIENT, type: {"rollback"}, key: KEY] + [c : CLIENT, type : {"lock", "prewrite"}, key : KEY, primary: KEY, + start_ts : Pos, for_update_ts : Pos] \union + [c : CLIENT, type: {"commit"}, key : KEY, start_ts : Pos, commit_ts : Pos] \union + [c : CLIENT, type: {"resolve"}, key : KEY, start_ts : Pos, commit_ts : Nat] \union + [c : CLIENT, type: {"read", "cleanup"}, key : KEY, start_ts : Pos] \union + [type : {"rollback"}, key : SUBSET KEY, start_ts: Pos] ) TypeInvariant == @@ -159,6 +348,7 @@ TypeInvariant == /\ ClientStateTypeInv /\ ClientTsTypeInv /\ ClientKeyTypeInv + /\ LockResolverTypeInv /\ KeyDataTypeInv /\ KeyLockTypeInv /\ KeyWriteTypeInv diff --git a/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test1.launch b/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test1.launch index 6447acc..9931c89 100644 --- a/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test1.launch +++ b/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test1.launch @@ -5,23 +5,21 @@ - + - + - - - - + + - - + + From 826b8899a7d4b47c5bda4fda4e51c3225527d240 Mon Sep 17 00:00:00 2001 From: Yilin Chen Date: Tue, 22 Oct 2019 20:39:29 +0800 Subject: [PATCH 05/20] Add full process Signed-off-by: Yilin Chen --- .../PessimisticTransaction.tla | 135 +++++++++++++----- .../PessimisticTransaction___Test1.launch | 25 +++- 2 files changed, 120 insertions(+), 40 deletions(-) diff --git a/PessimisticTransaction/PessimisticTransaction.tla b/PessimisticTransaction/PessimisticTransaction.tla index d785af6..d4902c8 100644 --- a/PessimisticTransaction/PessimisticTransaction.tla +++ b/PessimisticTransaction/PessimisticTransaction.tla @@ -73,31 +73,10 @@ Range(m) == {m[i] : i \in DOMAIN m} ----------------------------------------------------------------------------- -\* Checks whether there is a lock of key $k$, whose $ts$ is less or equal than -\* $ts$. -hasLockLE(k, ts) == - \E l \in key_lock[k] : l.ts <= ts - -\* Checks whether there is a lock of key $k$ with $ts$. -hasLockEQ(k, ts) == - \E l \in key_lock[k] : l.ts = ts - \* find a stale lock for key k. findStaleLock(k, ts) == {l \in key_lock[k] : l.for_update_ts = 0 /\ l.ts < ts} -\* Returns the writes with start_ts equals to $ts$. -findWriteWithStartTS(k, ts) == - {w \in Range(key_write[k]) : (w.type = "write" /\ w.start_ts = ts)} - -\* Returns the writes with commit_ts equals to $ts$. -findWriteWithCommitTS(k, ts) == - {w \in Range(key_write[k]) : (w.type = "write" /\ w.ts = ts)} - -\* Returns TRUE if there is a rollback for key $k$ at timestamp $ts$. -hasRollback(k, ts) == - {r \in Range(key_write[k]) : (r.type = "rollback" /\ r.ts = ts)} # {} - sendTxnStatus(c, k, ts, commit_ts) == lock_resolver' = [lock_resolver EXCEPT ![c].status = @ \union {[primary |-> k, @@ -106,7 +85,10 @@ sendTxnStatus(c, k, ts, commit_ts) == writeRollback(k, ts) == key_write' = [key_write EXCEPT - ![k] = [ts |-> ts, type |-> "rollback", start_ts |-> ts]] + ![k] = @ \union {[ts |-> ts, type |-> "rollback", start_ts |-> ts]}] + +abortTxn(c) == + client_state' = [client_state EXCEPT ![c] = "abort"] ----------------------------------------------------------------------------- @@ -142,7 +124,7 @@ CheckTxnStatus(c) == LET unknown_lock == {l \in lock : - ~ \E s \in lock_resolver : + ~ \E s \in status : /\ l.primary = s.primary /\ l.start_ts = s.start_ts} IN @@ -173,12 +155,43 @@ ResolveLock(c) == LockKey(c) == /\ client_state[c] = "working" - /\ client_key[c].pessimistic # {} - /\ \E k \in client_key[c].pessimistic : - /\ msg' = msg \union - {[c |-> c, type |-> "lock", key |-> k, primary |-> client_key[c].primary, - start_ts |-> client_ts[c].start_ts, for_update_ts |-> client_ts[c].for_update_ts]} - /\ UNCHANGED <> + /\ IF client_key[c].pessimistic = {} + THEN + /\ client_state' = [client_state EXCEPT ![c] = "prewriting"] + /\ UNCHANGED <> + ELSE + \E k \in client_key[c].pessimistic : + /\ msg' = msg \union + {[c |-> c, type |-> "lock", key |-> k, primary |-> client_key[c].primary, + start_ts |-> client_ts[c].start_ts, for_update_ts |-> client_ts[c].for_update_ts]} + /\ UNCHANGED <> + +Prewrite(c) == + /\ client_state[c] = "prewriting" + /\ IF client_key[c].pending = {} + THEN + /\ client_state' = [client_state EXCEPT ![c] = "committing"] + /\ UNCHANGED <> + ELSE + \E k \in client_key[c].pending : + /\ msg' = msg \union + {[c |-> c, type |-> "prewrite", key |-> k, primary |-> client_key[c].primary, + start_ts |-> client_ts[c].start_ts, for_update_ts |-> client_ts[c].for_update_ts]} + /\ UNCHANGED <> + +Commit(c) == + /\ client_state[c] = "committing" + /\ IF client_ts[c].commit_ts = 0 + THEN + /\ next_ts' = next_ts + 1 + /\ client_ts' = [client_ts EXCEPT ![c].commit_ts = next_ts'] + ELSE + UNCHANGED <> + /\ msg' = msg \union + {[c |-> c, type |-> "commit", key |-> client_key[c].primary, + start_ts |-> client_ts[c].start_ts, commit_ts |-> client_ts'[c].commit_ts]} + /\ UNCHANGED <> + ClientOp(c) == \/ Start(c) @@ -186,6 +199,8 @@ ClientOp(c) == \/ CheckTxnStatus(c) \/ ResolveLock(c) \/ LockKey(c) + \/ Prewrite(c) + \/ Commit(c) DoRead(cmd) == LET @@ -221,7 +236,7 @@ DoCheckTxnStatus(cmd) == LET rec == CHOOSE w \in write : w.type = "write" IN - /\ sendTxnStatus(c, k, ts, rec.commit_ts) + /\ sendTxnStatus(c, k, ts, rec.ts) /\ UNCHANGED <> ELSE IF \E w \in write : w.type = "rollback" @@ -234,7 +249,7 @@ DoCheckTxnStatus(cmd) == /\ UNCHANGED <> ELSE /\ key_lock' = [key_lock EXCEPT ![k] = {}] - /\ key_data' = [key_data EXCEPT ![k] = @ \ [ts |-> ts]] + /\ key_data' = [key_data EXCEPT ![k] = @ \ {[ts |-> ts]}] /\ writeRollback(k, ts) /\ sendTxnStatus(c, k, ts, 0) /\ UNCHANGED <> @@ -249,12 +264,56 @@ DoLockKey(cmd) == IN IF key_lock[k] = {} THEN - /\ key_lock' = [key_lock EXCEPT ![k] = {[ts |-> ts, for_update_ts |-> for_update_ts, primary |-> primary]}] - /\ client_key' = [client_key EXCEPT ![c].pessimistic = @ \ {k}] - /\ UNCHANGED <> + /\ key_lock' = [key_lock EXCEPT ![k] = {[ts |-> ts, for_update_ts |-> for_update_ts, primary |-> primary, pessimistic |-> TRUE]}] + /\ client_key' = [client_key EXCEPT ![c].pessimistic = @ \ {k}] \* may not change + /\ UNCHANGED <> ELSE - /\ lock_resolver' = [lock_resolver EXCEPT ![c].lock = @ \union key_lock[k]] + /\ lock_resolver' = [lock_resolver EXCEPT ![c].lock = @ \union {[key |-> k, primary |-> l.primary, start_ts |-> l.ts] : l \in key_lock[k]}] /\ UNCHANGED <> + +DoPrewrite(cmd) == + LET + c == cmd.c + k == cmd.key + primary == cmd.primary + ts == cmd.start_ts + for_update_ts == cmd.for_update_ts + IN + IF + \/ key_lock[k] = {} + \/ \E l \in key_lock[k] : l.ts # ts + THEN + /\ abortTxn(c) + /\ UNCHANGED <> + ELSE + /\ key_lock' = [key_lock EXCEPT ![k] = {[ts |-> ts, for_update_ts |-> for_update_ts, primary |-> primary, pessimistic |-> FALSE]}] + /\ key_data' = [key_data EXCEPT ![k] = @ \union {[ts |-> ts]}] + /\ client_key' = [client_key EXCEPT ![c].pending = @ \ {k}] \* may not change + /\ UNCHANGED <> + +DoCommit(cmd) == + LET + c == cmd.c + k == cmd.key + start_ts == cmd.start_ts + commit_ts == cmd.commit_ts + IN + IF + \/ key_lock[k] = {} + \/ \E l \in key_lock[k] : l.ts # start_ts + THEN + IF \E w \in key_write[k] : w.start_ts = start_ts /\ w.type = "write" + THEN + /\ client_state' = [client_state EXCEPT ![c] = "committed"] + /\ UNCHANGED <> + ELSE + /\ client_state' = [client_state EXCEPT ![c] = "aborted"] + /\ UNCHANGED <> + ELSE + /\ client_state' = [client_state EXCEPT ![c] = "committed"] + /\ key_lock' = [key_lock EXCEPT ![k] = {}] + /\ key_write' = [key_write EXCEPT ![k] = @ \union {[ts |-> commit_ts, type |-> "write", start_ts |-> start_ts]}] + /\ UNCHANGED <> ServerOp(cmd) == \/ /\ cmd.type = "read" @@ -263,6 +322,10 @@ ServerOp(cmd) == /\ DoCheckTxnStatus(cmd) \/ /\ cmd.type = "lock" /\ DoLockKey(cmd) + \/ /\ cmd.type = "prewrite" + /\ DoPrewrite(cmd) + \/ /\ cmd.type = "commit" + /\ DoCommit(cmd) Init == /\ next_ts = 1 @@ -313,7 +376,7 @@ KeyDataTypeInv == key_data \in [KEY -> SUBSET [ts : Pos]] KeyLockTypeInv == - key_lock \in [KEY -> SUBSET [ts : Pos, for_update_ts : Nat, primary : KEY]] + key_lock \in [KEY -> SUBSET [ts : Pos, for_update_ts : Nat, primary : KEY, pessimistic : BOOLEAN]] KeyWriteTypeInv == key_write \in [KEY -> diff --git a/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test1.launch b/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test1.launch index 9931c89..d3d3c06 100644 --- a/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test1.launch +++ b/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test1.launch @@ -1,12 +1,22 @@ + + + + + - - + + + + + + + @@ -16,13 +26,20 @@ + - + - + + + + + + + From f932541426bedd48842dbb0c7994344e65bc6760 Mon Sep 17 00:00:00 2001 From: Yilin Chen Date: Wed, 23 Oct 2019 13:28:19 +0800 Subject: [PATCH 06/20] remove lock_resolver Signed-off-by: Yilin Chen --- .../PessimisticTransaction.tla | 225 +++++++----------- .../PessimisticTransaction___Test1.launch | 10 +- 2 files changed, 94 insertions(+), 141 deletions(-) diff --git a/PessimisticTransaction/PessimisticTransaction.tla b/PessimisticTransaction/PessimisticTransaction.tla index d4902c8..0b38122 100644 --- a/PessimisticTransaction/PessimisticTransaction.tla +++ b/PessimisticTransaction/PessimisticTransaction.tla @@ -25,12 +25,6 @@ VARIABLES client_ts \* denotes the keys that are pending for prewrite. VARIABLES client_key -\* lock_resolver[c] is a record of [lock: {[key, primary, start_ts]}, -\* status: {[primary, start_ts, commit_ts]}]. When client c encounters -\* a lock, the lock will be added to the lock field. When there are locks -\* in the record, client c may resolve the locks. -VARIABLES lock_resolver - \* key_data[k] is the set of multi-version data of the key. \* Since we don't care about the concrete value of data, a record of \* [ts: start_ts] is sufficient to represent one data version. @@ -61,7 +55,7 @@ VARIABLES key_last_read_ts \* pick any message in the set to execute. VARIABLES msg -client_vars == <> +client_vars == <> key_vars == <> vars == <> @@ -75,90 +69,53 @@ Range(m) == {m[i] : i \in DOMAIN m} \* find a stale lock for key k. findStaleLock(k, ts) == - {l \in key_lock[k] : l.for_update_ts = 0 /\ l.ts < ts} - -sendTxnStatus(c, k, ts, commit_ts) == - lock_resolver' = [lock_resolver EXCEPT - ![c].status = @ \union {[primary |-> k, - start_ts |-> ts, - commit_ts |-> commit_ts]}] + {l \in key_lock[k] : l.pessimistic = FALSE /\ l.ts < ts} -writeRollback(k, ts) == - key_write' = [key_write EXCEPT - ![k] = @ \union {[ts |-> ts, type |-> "rollback", start_ts |-> ts]}] +rollback(k, ts) == + /\ IF \E l \in key_lock[k] : l.ts = ts + THEN key_lock' = [key_lock EXCEPT ![k] = {}] + ELSE UNCHANGED key_lock + /\ key_data' = [key_data EXCEPT ![k] = @ \ {[ts |-> ts]}] + /\ key_write' = [key_write EXCEPT + ![k] = @ \union {[ts |-> ts, type |-> "rollback", start_ts |-> ts]}] + +commit(k, start_ts, commit_ts) == + /\ key_lock' = [key_lock EXCEPT ![k] = {}] + /\ key_write' = [key_write EXCEPT ![k] = @ \union {[ts |-> commit_ts, type |-> "write", start_ts |-> start_ts]}] + /\ UNCHANGED key_data abortTxn(c) == - client_state' = [client_state EXCEPT ![c] = "abort"] + client_state' = [client_state EXCEPT ![c] = "aborted"] ----------------------------------------------------------------------------- Start(c) == /\ client_state[c] = "init" /\ next_ts' = next_ts + 1 - /\ \E ks \in SUBSET KEY: - \E primary \in ks : - client_key' = - [client_key EXCEPT - ![c] = [primary |-> primary, - secondary |-> ks \ {primary}, - pessimistic |-> ks, - pending |-> ks] - ] + /\ \E primary \in KEY : + client_key' = + [client_key EXCEPT + ![c] = [primary |-> primary, + secondary |-> KEY \ {primary}, + pessimistic |-> KEY, + pending |-> KEY] + ] /\ client_state' = [client_state EXCEPT ![c] = "working"] - /\ client_ts' = [client_ts EXCEPT ![c].start_ts = next_ts', ![c].for_update_ts = next_ts'] - /\ UNCHANGED <> + /\ client_ts' = [client_ts EXCEPT ![c].start_ts = next_ts, ![c].for_update_ts = next_ts] + /\ UNCHANGED <> -Read(c) == - /\ client_state[c] = "working" +Read(ts) == /\ \E k \in KEY : /\ msg' = msg \union - {[c |-> c, type |-> "read", key |-> k, start_ts |-> client_ts[c].start_ts]} + {[type |-> "read", key |-> k, ts |-> ts]} /\ UNCHANGED <> -CheckTxnStatus(c) == - /\ client_state[c] \in {"working", "prewriting"} - /\ LET - lock == lock_resolver[c].lock - status == lock_resolver[c].status - IN - LET - unknown_lock == - {l \in lock : - ~ \E s \in status : - /\ l.primary = s.primary - /\ l.start_ts = s.start_ts} - IN - /\ msg' = msg \union { - [c |-> c, type |-> "cleanup", - key |-> l.primary, start_ts |-> l.start_ts] : l \in unknown_lock - } - /\ UNCHANGED <> - -ResolveLock(c) == - /\ client_state[c] \in {"working", "prewriting"} - /\ LET - lock == lock_resolver[c].lock - status == lock_resolver[c].status - IN - LET - txn_with_lock == - {s \in status : - \E l \in lock : - /\ l.primary = s.primary - /\ l.start_ts = s.start_ts} - IN - /\ msg' = msg \union { - [c |-> c, type |-> "resolve", key |-> txn.primary, - start_ts |-> txn.start_ts, commit_ts |-> txn.commit_ts] : txn \in txn_with_lock - } - /\ UNCHANGED <> - LockKey(c) == /\ client_state[c] = "working" /\ IF client_key[c].pessimistic = {} THEN /\ client_state' = [client_state EXCEPT ![c] = "prewriting"] - /\ UNCHANGED <> + /\ UNCHANGED <> ELSE \E k \in client_key[c].pessimistic : /\ msg' = msg \union @@ -171,7 +128,7 @@ Prewrite(c) == /\ IF client_key[c].pending = {} THEN /\ client_state' = [client_state EXCEPT ![c] = "committing"] - /\ UNCHANGED <> + /\ UNCHANGED <> ELSE \E k \in client_key[c].pending : /\ msg' = msg \union @@ -184,29 +141,25 @@ Commit(c) == /\ IF client_ts[c].commit_ts = 0 THEN /\ next_ts' = next_ts + 1 - /\ client_ts' = [client_ts EXCEPT ![c].commit_ts = next_ts'] + /\ client_ts' = [client_ts EXCEPT ![c].commit_ts = next_ts] ELSE UNCHANGED <> /\ msg' = msg \union {[c |-> c, type |-> "commit", key |-> client_key[c].primary, start_ts |-> client_ts[c].start_ts, commit_ts |-> client_ts'[c].commit_ts]} - /\ UNCHANGED <> + /\ UNCHANGED <> ClientOp(c) == \/ Start(c) - \/ Read(c) - \/ CheckTxnStatus(c) - \/ ResolveLock(c) \/ LockKey(c) \/ Prewrite(c) \/ Commit(c) DoRead(cmd) == LET - c == cmd.c k == cmd.key - ts == cmd.start_ts + ts == cmd.ts IN LET stale_lock == findStaleLock(k, ts) @@ -216,44 +169,47 @@ DoRead(cmd) == /\ key_last_read_ts' = [key_last_read_ts EXCEPT ![k] = ts] /\ UNCHANGED <> ELSE - /\ lock_resolver' = [lock_resolver EXCEPT ![c].lock = @ \union stale_lock] - /\ UNCHANGED <> + /\ msg' = msg \union + {[type |-> "cleanup", primary |-> l.primary, start_ts |-> l.ts] : l \in stale_lock} + /\ UNCHANGED <> -DoCheckTxnStatus(cmd) == +DoCleanup(cmd) == LET - c == cmd.c - k == cmd.key + k == cmd.primary ts == cmd.start_ts IN LET lock == {l \in key_lock[k] : l.ts = ts} - write == {w \in key_write[k] : w.start_ts = ts} + committed == {w \in key_write[k] : w.start_ts = ts /\ w.type = "write"} IN - IF lock = {} + IF committed # {} THEN - IF \E w \in write : w.type = "write" - THEN - LET - rec == CHOOSE w \in write : w.type = "write" - IN - /\ sendTxnStatus(c, k, ts, rec.ts) - /\ UNCHANGED <> - ELSE - IF \E w \in write : w.type = "rollback" - THEN - /\ sendTxnStatus(c, k, ts, 0) - /\ UNCHANGED <> - ELSE - /\ writeRollback(k, ts) - /\ sendTxnStatus(c, k, ts, 0) - /\ UNCHANGED <> + /\ msg' = msg \union + {[type |-> "resolve", primary |-> k, start_ts |-> ts, commit_ts |-> t.ts] : t \in committed} + /\ UNCHANGED <> ELSE - /\ key_lock' = [key_lock EXCEPT ![k] = {}] - /\ key_data' = [key_data EXCEPT ![k] = @ \ {[ts |-> ts]}] - /\ writeRollback(k, ts) - /\ sendTxnStatus(c, k, ts, 0) - /\ UNCHANGED <> - + /\ msg' = msg \union + {[type |-> "resolve", primary |-> k, start_ts |-> ts, commit_ts |-> 0]} + /\ rollback(k, ts) + /\ UNCHANGED <> + +DoResolve(cmd) == + /\ IF cmd.commit_ts = 0 + THEN + \E k \in KEY : + \E l \in key_lock[k] : + /\ l.primary = cmd.primary + /\ l.ts = cmd.start_ts + /\ rollback(k, cmd.start_ts) + ELSE + \E k \in KEY : + \E l \in key_lock[k] : + /\ l.primary = cmd.primary + /\ l.ts = cmd.start_ts + /\ commit(k, cmd.start_ts, cmd.commit_ts) + /\ UNCHANGED <> + + DoLockKey(cmd) == LET c == cmd.c @@ -266,10 +222,11 @@ DoLockKey(cmd) == THEN /\ key_lock' = [key_lock EXCEPT ![k] = {[ts |-> ts, for_update_ts |-> for_update_ts, primary |-> primary, pessimistic |-> TRUE]}] /\ client_key' = [client_key EXCEPT ![c].pessimistic = @ \ {k}] \* may not change - /\ UNCHANGED <> + /\ UNCHANGED <> ELSE - /\ lock_resolver' = [lock_resolver EXCEPT ![c].lock = @ \union {[key |-> k, primary |-> l.primary, start_ts |-> l.ts] : l \in key_lock[k]}] - /\ UNCHANGED <> + /\ msg' = msg \union + {[type |-> "cleanup", primary |-> l.primary, start_ts |-> l.ts] : l \in key_lock[k]} \* may not change + /\ UNCHANGED <> DoPrewrite(cmd) == LET @@ -284,12 +241,12 @@ DoPrewrite(cmd) == \/ \E l \in key_lock[k] : l.ts # ts THEN /\ abortTxn(c) - /\ UNCHANGED <> + /\ UNCHANGED <> ELSE /\ key_lock' = [key_lock EXCEPT ![k] = {[ts |-> ts, for_update_ts |-> for_update_ts, primary |-> primary, pessimistic |-> FALSE]}] /\ key_data' = [key_data EXCEPT ![k] = @ \union {[ts |-> ts]}] /\ client_key' = [client_key EXCEPT ![c].pending = @ \ {k}] \* may not change - /\ UNCHANGED <> + /\ UNCHANGED <> DoCommit(cmd) == LET @@ -305,21 +262,22 @@ DoCommit(cmd) == IF \E w \in key_write[k] : w.start_ts = start_ts /\ w.type = "write" THEN /\ client_state' = [client_state EXCEPT ![c] = "committed"] - /\ UNCHANGED <> + /\ UNCHANGED <> ELSE /\ client_state' = [client_state EXCEPT ![c] = "aborted"] - /\ UNCHANGED <> + /\ UNCHANGED <> ELSE /\ client_state' = [client_state EXCEPT ![c] = "committed"] - /\ key_lock' = [key_lock EXCEPT ![k] = {}] - /\ key_write' = [key_write EXCEPT ![k] = @ \union {[ts |-> commit_ts, type |-> "write", start_ts |-> start_ts]}] - /\ UNCHANGED <> + /\ commit(k, start_ts, commit_ts) + /\ UNCHANGED <> ServerOp(cmd) == \/ /\ cmd.type = "read" /\ DoRead(cmd) \/ /\ cmd.type = "cleanup" - /\ DoCheckTxnStatus(cmd) + /\ DoCleanup(cmd) + \/ /\ cmd.type = "resolve" + /\ DoResolve(cmd) \/ /\ cmd.type = "lock" /\ DoLockKey(cmd) \/ /\ cmd.type = "prewrite" @@ -334,7 +292,6 @@ Init == for_update_ts |-> 0, commit_ts |-> 0]] /\ client_key = [c \in CLIENT |-> {}] - /\ lock_resolver = [c \in CLIENT |-> [lock |-> {}, status |-> {}]] /\ key_lock = [k \in KEY |-> {}] /\ key_data = [k \in KEY |-> {}] /\ key_write = [k \in KEY |-> {}] @@ -344,6 +301,7 @@ Init == Next == \/ \E cmd \in msg : ServerOp(cmd) \/ \E c \in CLIENT : ClientOp(c) + \/ \E ts \in 0..next_ts : Read(ts) ----------------------------------------------------------------------------- @@ -367,11 +325,6 @@ ClientKeyTypeInv == pessimistic : SUBSET KEY, pending : SUBSET KEY] -LockResolverTypeInv == - \A c \in CLIENT : - /\ lock_resolver[c].lock \subseteq [key : KEY, primary : KEY, start_ts: Pos] - /\ lock_resolver[c].status \subseteq [primary : KEY, start_ts : Pos, commit_ts : Nat] - KeyDataTypeInv == key_data \in [KEY -> SUBSET [ts : Pos]] @@ -384,14 +337,12 @@ KeyWriteTypeInv == ] KeyWriteStartTsInv == - \A k \in DOMAIN key_write: - LET - rec == key_write[k] - IN - \/ rec = {} - \/ IF rec.type = "write" - THEN rec.ts > rec.start_ts - ELSE rec.type = "rollback" /\ rec.ts = rec.start_ts + \A k \in KEY: + \A rec \in key_write[k] : + \/ /\ rec.type = "write" + /\ rec.ts > rec.start_ts + \/ /\ rec.type = "rollback" + /\ rec.ts = rec.start_ts KeyLastReadTsTypeInv == key_last_read_ts \in [KEY -> Nat] @@ -400,9 +351,10 @@ MsgTypeInv == msg \subseteq ( [c : CLIENT, type : {"lock", "prewrite"}, key : KEY, primary: KEY, start_ts : Pos, for_update_ts : Pos] \union - [c : CLIENT, type: {"commit"}, key : KEY, start_ts : Pos, commit_ts : Pos] \union - [c : CLIENT, type: {"resolve"}, key : KEY, start_ts : Pos, commit_ts : Nat] \union - [c : CLIENT, type: {"read", "cleanup"}, key : KEY, start_ts : Pos] \union + [c : CLIENT, type : {"commit"}, key : KEY, start_ts : Pos, commit_ts : Pos] \union + [type : {"read"}, key : KEY, ts : Nat] \union + [type : {"cleanup"}, primary : KEY, start_ts : Pos] \union + [type : {"resolve"}, primary : KEY, start_ts : Pos, commit_ts : Nat] \union [type : {"rollback"}, key : SUBSET KEY, start_ts: Pos] ) @@ -411,7 +363,6 @@ TypeInvariant == /\ ClientStateTypeInv /\ ClientTsTypeInv /\ ClientKeyTypeInv - /\ LockResolverTypeInv /\ KeyDataTypeInv /\ KeyLockTypeInv /\ KeyWriteTypeInv diff --git a/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test1.launch b/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test1.launch index d3d3c06..896a7fd 100644 --- a/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test1.launch +++ b/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test1.launch @@ -1,7 +1,7 @@ - + @@ -12,7 +12,7 @@ - + @@ -21,10 +21,12 @@ - + - + + + From 2b6cbec94db0ba62ad6e563184f41fdf164f6ad6 Mon Sep 17 00:00:00 2001 From: Yilin Chen Date: Wed, 23 Oct 2019 14:12:02 +0800 Subject: [PATCH 07/20] add cases for response loss Signed-off-by: Yilin Chen --- .../PessimisticTransaction.tla | 57 +++++++++++-------- .../PessimisticTransaction___Test1.launch | 6 +- 2 files changed, 35 insertions(+), 28 deletions(-) diff --git a/PessimisticTransaction/PessimisticTransaction.tla b/PessimisticTransaction/PessimisticTransaction.tla index 0b38122..4929bca 100644 --- a/PessimisticTransaction/PessimisticTransaction.tla +++ b/PessimisticTransaction/PessimisticTransaction.tla @@ -80,7 +80,9 @@ rollback(k, ts) == ![k] = @ \union {[ts |-> ts, type |-> "rollback", start_ts |-> ts]}] commit(k, start_ts, commit_ts) == - /\ key_lock' = [key_lock EXCEPT ![k] = {}] + /\ IF \E l \in key_lock[k] : l.ts = start_ts + THEN key_lock' = [key_lock EXCEPT ![k] = {}] + ELSE UNCHANGED key_lock /\ key_write' = [key_write EXCEPT ![k] = @ \union {[ts |-> commit_ts, type |-> "write", start_ts |-> start_ts]}] /\ UNCHANGED key_data @@ -103,12 +105,6 @@ Start(c) == /\ client_state' = [client_state EXCEPT ![c] = "working"] /\ client_ts' = [client_ts EXCEPT ![c].start_ts = next_ts, ![c].for_update_ts = next_ts] /\ UNCHANGED <> - -Read(ts) == - /\ \E k \in KEY : - /\ msg' = msg \union - {[type |-> "read", key |-> k, ts |-> ts]} - /\ UNCHANGED <> LockKey(c) == /\ client_state[c] = "working" @@ -166,7 +162,9 @@ DoRead(cmd) == IN IF stale_lock = {} THEN - /\ key_last_read_ts' = [key_last_read_ts EXCEPT ![k] = ts] + /\ IF key_last_read_ts[k] < ts + THEN key_last_read_ts' = [key_last_read_ts EXCEPT ![k] = ts] + ELSE UNCHANGED key_last_read_ts /\ UNCHANGED <> ELSE /\ msg' = msg \union @@ -188,10 +186,11 @@ DoCleanup(cmd) == {[type |-> "resolve", primary |-> k, start_ts |-> ts, commit_ts |-> t.ts] : t \in committed} /\ UNCHANGED <> ELSE - /\ msg' = msg \union - {[type |-> "resolve", primary |-> k, start_ts |-> ts, commit_ts |-> 0]} /\ rollback(k, ts) /\ UNCHANGED <> + /\ \/ msg' = msg \union + {[type |-> "resolve", primary |-> k, start_ts |-> ts, commit_ts |-> 0]} + \/ UNCHANGED msg DoResolve(cmd) == /\ IF cmd.commit_ts = 0 @@ -221,11 +220,12 @@ DoLockKey(cmd) == IF key_lock[k] = {} THEN /\ key_lock' = [key_lock EXCEPT ![k] = {[ts |-> ts, for_update_ts |-> for_update_ts, primary |-> primary, pessimistic |-> TRUE]}] - /\ client_key' = [client_key EXCEPT ![c].pessimistic = @ \ {k}] \* may not change /\ UNCHANGED <> + /\ \/ client_key' = [client_key EXCEPT ![c].pessimistic = @ \ {k}] + \/ UNCHANGED client_key ELSE /\ msg' = msg \union - {[type |-> "cleanup", primary |-> l.primary, start_ts |-> l.ts] : l \in key_lock[k]} \* may not change + {[type |-> "cleanup", primary |-> l.primary, start_ts |-> l.ts] : l \in key_lock[k]} /\ UNCHANGED <> DoPrewrite(cmd) == @@ -245,7 +245,8 @@ DoPrewrite(cmd) == ELSE /\ key_lock' = [key_lock EXCEPT ![k] = {[ts |-> ts, for_update_ts |-> for_update_ts, primary |-> primary, pessimistic |-> FALSE]}] /\ key_data' = [key_data EXCEPT ![k] = @ \union {[ts |-> ts]}] - /\ client_key' = [client_key EXCEPT ![c].pending = @ \ {k}] \* may not change + /\ \/ client_key' = [client_key EXCEPT ![c].pending = @ \ {k}] + \/ UNCHANGED client_key /\ UNCHANGED <> DoCommit(cmd) == @@ -255,21 +256,17 @@ DoCommit(cmd) == start_ts == cmd.start_ts commit_ts == cmd.commit_ts IN - IF - \/ key_lock[k] = {} - \/ \E l \in key_lock[k] : l.ts # start_ts + IF \/ \E l \in key_lock[k] : l.ts = start_ts + \/ \E w \in key_write[k] : w.start_ts = start_ts /\ w.type = "write" THEN - IF \E w \in key_write[k] : w.start_ts = start_ts /\ w.type = "write" - THEN - /\ client_state' = [client_state EXCEPT ![c] = "committed"] - /\ UNCHANGED <> - ELSE - /\ client_state' = [client_state EXCEPT ![c] = "aborted"] - /\ UNCHANGED <> - ELSE - /\ client_state' = [client_state EXCEPT ![c] = "committed"] /\ commit(k, start_ts, commit_ts) + /\ \/ client_state' = [client_state EXCEPT ![c] = "committed"] + \/ UNCHANGED client_state /\ UNCHANGED <> + ELSE + /\ \/ client_state' = [client_state EXCEPT ![c] = "aborted"] + \/ UNCHANGED client_state + /\ UNCHANGED <> ServerOp(cmd) == \/ /\ cmd.type = "read" @@ -284,6 +281,12 @@ ServerOp(cmd) == /\ DoPrewrite(cmd) \/ /\ cmd.type = "commit" /\ DoCommit(cmd) + +Read(ts) == + /\ \E k \in KEY : + /\ msg' = msg \union + {[type |-> "read", key |-> k, ts |-> ts]} + /\ UNCHANGED <> Init == /\ next_ts = 1 @@ -369,5 +372,9 @@ TypeInvariant == /\ KeyWriteStartTsInv /\ KeyLastReadTsTypeInv /\ MsgTypeInv + +----------------------------------------------------------------------------- + + ============================================================================= diff --git a/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test1.launch b/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test1.launch index 896a7fd..f5eaee7 100644 --- a/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test1.launch +++ b/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test1.launch @@ -12,7 +12,7 @@ - + @@ -31,8 +31,8 @@ - - + + From 8651a527709ce41feb97ab71519a9007e5b405a4 Mon Sep 17 00:00:00 2001 From: Yilin Chen Date: Wed, 23 Oct 2019 17:30:20 +0800 Subject: [PATCH 08/20] fix DoLockKey Signed-off-by: Yilin Chen --- .../PessimisticTransaction.tla | 398 +++++++++++------- .../PessimisticTransaction___Test1.launch | 34 +- 2 files changed, 265 insertions(+), 167 deletions(-) diff --git a/PessimisticTransaction/PessimisticTransaction.tla b/PessimisticTransaction/PessimisticTransaction.tla index 4929bca..a8f2f0f 100644 --- a/PessimisticTransaction/PessimisticTransaction.tla +++ b/PessimisticTransaction/PessimisticTransaction.tla @@ -1,6 +1,6 @@ ----------------------- MODULE PessimisticTransaction ----------------------- -EXTENDS Integers, TLC +EXTENDS Integers, FiniteSets, TLC \* The set of transaction keys. CONSTANTS KEY @@ -81,9 +81,12 @@ rollback(k, ts) == commit(k, start_ts, commit_ts) == /\ IF \E l \in key_lock[k] : l.ts = start_ts - THEN key_lock' = [key_lock EXCEPT ![k] = {}] - ELSE UNCHANGED key_lock - /\ key_write' = [key_write EXCEPT ![k] = @ \union {[ts |-> commit_ts, type |-> "write", start_ts |-> start_ts]}] + THEN + /\ key_lock' = [key_lock EXCEPT ![k] = {}] + /\ key_write' = [key_write EXCEPT ![k] = @ \union {[ts |-> commit_ts, type |-> "write", start_ts |-> start_ts]}] + /\ Assert(key_last_read_ts[k] < commit_ts, TRUE) + ELSE + UNCHANGED <> /\ UNCHANGED key_data abortTxn(c) == @@ -103,7 +106,7 @@ Start(c) == pending |-> KEY] ] /\ client_state' = [client_state EXCEPT ![c] = "working"] - /\ client_ts' = [client_ts EXCEPT ![c].start_ts = next_ts, ![c].for_update_ts = next_ts] + /\ client_ts' = [client_ts EXCEPT ![c].start_ts = next_ts', ![c].for_update_ts = next_ts'] /\ UNCHANGED <> LockKey(c) == @@ -137,7 +140,7 @@ Commit(c) == /\ IF client_ts[c].commit_ts = 0 THEN /\ next_ts' = next_ts + 1 - /\ client_ts' = [client_ts EXCEPT ![c].commit_ts = next_ts] + /\ client_ts' = [client_ts EXCEPT ![c].commit_ts = next_ts'] ELSE UNCHANGED <> /\ msg' = msg \union @@ -152,141 +155,179 @@ ClientOp(c) == \/ Prewrite(c) \/ Commit(c) -DoRead(cmd) == - LET - k == cmd.key - ts == cmd.ts - IN - LET - stale_lock == findStaleLock(k, ts) - IN - IF stale_lock = {} - THEN - /\ IF key_last_read_ts[k] < ts - THEN key_last_read_ts' = [key_last_read_ts EXCEPT ![k] = ts] - ELSE UNCHANGED key_last_read_ts - /\ UNCHANGED <> - ELSE - /\ msg' = msg \union - {[type |-> "cleanup", primary |-> l.primary, start_ts |-> l.ts] : l \in stale_lock} - /\ UNCHANGED <> +DoRead == + \E cmd \in msg : + /\ cmd.type = "read" + /\ LET + k == cmd.key + ts == cmd.ts + IN + LET + stale_lock == findStaleLock(k, ts) + IN + IF stale_lock = {} + THEN + /\ IF key_last_read_ts[k] < ts + THEN key_last_read_ts' = [key_last_read_ts EXCEPT ![k] = ts] + ELSE UNCHANGED key_last_read_ts + /\ UNCHANGED <> + ELSE + /\ msg' = msg \union + {[type |-> "cleanup", primary |-> l.primary, start_ts |-> l.ts] : l \in stale_lock} + /\ UNCHANGED <> -DoCleanup(cmd) == - LET - k == cmd.primary - ts == cmd.start_ts - IN - LET - lock == {l \in key_lock[k] : l.ts = ts} - committed == {w \in key_write[k] : w.start_ts = ts /\ w.type = "write"} - IN - IF committed # {} - THEN - /\ msg' = msg \union - {[type |-> "resolve", primary |-> k, start_ts |-> ts, commit_ts |-> t.ts] : t \in committed} - /\ UNCHANGED <> - ELSE - /\ rollback(k, ts) - /\ UNCHANGED <> - /\ \/ msg' = msg \union - {[type |-> "resolve", primary |-> k, start_ts |-> ts, commit_ts |-> 0]} - \/ UNCHANGED msg +DoCleanup == + \E cmd \in msg : + /\ cmd.type = "cleanup" + /\ LET + k == cmd.primary + ts == cmd.start_ts + IN + LET + lock == {l \in key_lock[k] : l.ts = ts} + committed == {w \in key_write[k] : w.start_ts = ts /\ w.type = "write"} + IN + IF committed # {} + THEN + /\ msg' = msg \union + {[type |-> "resolve", primary |-> k, start_ts |-> ts, commit_ts |-> t.ts] : t \in committed} + /\ UNCHANGED <> + ELSE + /\ rollback(k, ts) + /\ UNCHANGED <> + /\ \/ msg' = msg \union + {[type |-> "resolve", primary |-> k, start_ts |-> ts, commit_ts |-> 0]} + \/ UNCHANGED msg -DoResolve(cmd) == - /\ IF cmd.commit_ts = 0 - THEN - \E k \in KEY : - \E l \in key_lock[k] : - /\ l.primary = cmd.primary - /\ l.ts = cmd.start_ts - /\ rollback(k, cmd.start_ts) - ELSE - \E k \in KEY : - \E l \in key_lock[k] : - /\ l.primary = cmd.primary - /\ l.ts = cmd.start_ts - /\ commit(k, cmd.start_ts, cmd.commit_ts) - /\ UNCHANGED <> +DoResolve == + \E cmd \in msg : + /\ cmd.type = "resolve" + /\ IF cmd.commit_ts = 0 + THEN + \E k \in KEY : + \E l \in key_lock[k] : + /\ l.primary = cmd.primary + /\ l.ts = cmd.start_ts + /\ rollback(k, cmd.start_ts) + ELSE + \E k \in KEY : + \E l \in key_lock[k] : + /\ l.primary = cmd.primary + /\ l.ts = cmd.start_ts + /\ commit(k, cmd.start_ts, cmd.commit_ts) + /\ UNCHANGED <> -DoLockKey(cmd) == - LET - c == cmd.c - k == cmd.key - primary == cmd.primary - ts == cmd.start_ts - for_update_ts == cmd.for_update_ts - IN - IF key_lock[k] = {} - THEN - /\ key_lock' = [key_lock EXCEPT ![k] = {[ts |-> ts, for_update_ts |-> for_update_ts, primary |-> primary, pessimistic |-> TRUE]}] - /\ UNCHANGED <> - /\ \/ client_key' = [client_key EXCEPT ![c].pessimistic = @ \ {k}] - \/ UNCHANGED client_key - ELSE - /\ msg' = msg \union - {[type |-> "cleanup", primary |-> l.primary, start_ts |-> l.ts] : l \in key_lock[k]} - /\ UNCHANGED <> +DoLockKey == + \E cmd \in msg : + /\ cmd.type = "lock" + /\ LET + c == cmd.c + k == cmd.key + primary == cmd.primary + ts == cmd.start_ts + for_update_ts == cmd.for_update_ts + writeLock == + /\ key_lock' = [key_lock EXCEPT ![k] = {[ts |-> ts, for_update_ts |-> for_update_ts, primary |-> primary, pessimistic |-> TRUE]}] + /\ UNCHANGED <> + /\ \/ client_key' = [client_key EXCEPT ![c].pessimistic = @ \ {k}] + \/ UNCHANGED client_key + updateForUpdateTs(new_ts) == + IF new_ts > client_ts[c].for_update_ts + THEN + /\ client_ts' = [client_ts EXCEPT ![c].for_update_ts = for_update_ts] + /\ UNCHANGED <> + ELSE UNCHANGED vars + IN + IF key_lock[k] = {} + THEN + IF key_write[k] = {} + THEN writeLock + ELSE + LET + latest == CHOOSE w \in key_write[k] : \A w2 \in key_write[k] : w.ts >= w2.ts + IN + IF latest.ts > for_update_ts + THEN updateForUpdateTs(latest.ts) + ELSE + IF \E w \in key_write[k] : w.start_ts = ts /\ w.type = "rollback" + THEN abortTxn(c) /\ UNCHANGED <> + ELSE writeLock + ELSE + LET + l == CHOOSE l \in key_lock[k] : TRUE + IN + IF l.ts # ts + THEN + /\ msg' = msg \union + {[type |-> "cleanup", primary |-> l.primary, start_ts |-> l.ts]} + /\ UNCHANGED <> + ELSE + IF l.pessimistic = FALSE + THEN UNCHANGED vars + ELSE + IF l.for_update_ts < for_update_ts + THEN writeLock + ELSE UNCHANGED vars -DoPrewrite(cmd) == - LET - c == cmd.c - k == cmd.key - primary == cmd.primary - ts == cmd.start_ts - for_update_ts == cmd.for_update_ts - IN - IF - \/ key_lock[k] = {} - \/ \E l \in key_lock[k] : l.ts # ts - THEN - /\ abortTxn(c) - /\ UNCHANGED <> - ELSE - /\ key_lock' = [key_lock EXCEPT ![k] = {[ts |-> ts, for_update_ts |-> for_update_ts, primary |-> primary, pessimistic |-> FALSE]}] - /\ key_data' = [key_data EXCEPT ![k] = @ \union {[ts |-> ts]}] - /\ \/ client_key' = [client_key EXCEPT ![c].pending = @ \ {k}] - \/ UNCHANGED client_key - /\ UNCHANGED <> +DoPrewrite == + \E cmd \in msg : + /\ cmd.type = "prewrite" + /\ LET + c == cmd.c + k == cmd.key + primary == cmd.primary + ts == cmd.start_ts + for_update_ts == cmd.for_update_ts + IN + IF + \/ key_lock[k] = {} + \/ \E l \in key_lock[k] : l.ts # ts + THEN + /\ abortTxn(c) + /\ UNCHANGED <> + ELSE + /\ key_lock' = [key_lock EXCEPT ![k] = {[ts |-> ts, for_update_ts |-> for_update_ts, primary |-> primary, pessimistic |-> FALSE]}] + /\ key_data' = [key_data EXCEPT ![k] = @ \union {[ts |-> ts]}] + /\ \/ client_key' = [client_key EXCEPT ![c].pending = @ \ {k}] + \/ UNCHANGED client_key + /\ UNCHANGED <> -DoCommit(cmd) == - LET - c == cmd.c - k == cmd.key - start_ts == cmd.start_ts - commit_ts == cmd.commit_ts - IN - IF \/ \E l \in key_lock[k] : l.ts = start_ts - \/ \E w \in key_write[k] : w.start_ts = start_ts /\ w.type = "write" - THEN - /\ commit(k, start_ts, commit_ts) - /\ \/ client_state' = [client_state EXCEPT ![c] = "committed"] - \/ UNCHANGED client_state - /\ UNCHANGED <> - ELSE - /\ \/ client_state' = [client_state EXCEPT ![c] = "aborted"] - \/ UNCHANGED client_state - /\ UNCHANGED <> +DoCommit == + \E cmd \in msg : + /\ cmd.type = "commit" + /\ LET + c == cmd.c + k == cmd.key + start_ts == cmd.start_ts + commit_ts == cmd.commit_ts + IN + IF \/ \E l \in key_lock[k] : l.ts = start_ts + \/ \E w \in key_write[k] : w.start_ts = start_ts /\ w.type = "write" + THEN + /\ commit(k, start_ts, commit_ts) + /\ \/ client_state' = [client_state EXCEPT ![c] = "committed"] + \/ UNCHANGED client_state + /\ UNCHANGED <> + ELSE + /\ \/ client_state' = [client_state EXCEPT ![c] = "aborted"] + \/ UNCHANGED client_state + /\ UNCHANGED <> -ServerOp(cmd) == - \/ /\ cmd.type = "read" - /\ DoRead(cmd) - \/ /\ cmd.type = "cleanup" - /\ DoCleanup(cmd) - \/ /\ cmd.type = "resolve" - /\ DoResolve(cmd) - \/ /\ cmd.type = "lock" - /\ DoLockKey(cmd) - \/ /\ cmd.type = "prewrite" - /\ DoPrewrite(cmd) - \/ /\ cmd.type = "commit" - /\ DoCommit(cmd) +ServerOp == + \/ DoRead + \/ DoCleanup + \/ DoResolve + \/ DoLockKey + \/ DoPrewrite + \/ DoCommit -Read(ts) == - /\ \E k \in KEY : - /\ msg' = msg \union - {[type |-> "read", key |-> k, ts |-> ts]} - /\ UNCHANGED <> +Read == + \E ts \in 0..next_ts : + \E k \in KEY : + /\ msg' = msg \union + {[type |-> "read", key |-> k, ts |-> ts]} + /\ UNCHANGED <> Init == /\ next_ts = 1 @@ -302,9 +343,11 @@ Init == /\ msg = {} Next == - \/ \E cmd \in msg : ServerOp(cmd) + \/ ServerOp \/ \E c \in CLIENT : ClientOp(c) - \/ \E ts \in 0..next_ts : Read(ts) + \/ Read + +PessimisticSpec == Init /\ [][Next]_vars ----------------------------------------------------------------------------- @@ -339,14 +382,6 @@ KeyWriteTypeInv == SUBSET [ts : Pos, type : {"write", "rollback"}, start_ts : Pos] ] -KeyWriteStartTsInv == - \A k \in KEY: - \A rec \in key_write[k] : - \/ /\ rec.type = "write" - /\ rec.ts > rec.start_ts - \/ /\ rec.type = "rollback" - /\ rec.ts = rec.start_ts - KeyLastReadTsTypeInv == key_last_read_ts \in [KEY -> Nat] @@ -369,12 +404,87 @@ TypeInvariant == /\ KeyDataTypeInv /\ KeyLockTypeInv /\ KeyWriteTypeInv - /\ KeyWriteStartTsInv /\ KeyLastReadTsTypeInv /\ MsgTypeInv ----------------------------------------------------------------------------- +\* For each write, the commit_ts should be strictly greater than +\* the start_ts. For each rollback, the commit_ts should equals to +\* the start_ts. +WriteConsistency == + \A k \in KEY: + \A rec \in key_write[k] : + \/ /\ rec.type = "write" + /\ rec.ts > rec.start_ts + /\ \E d \in key_data[k] : rec.start_ts = d.ts + \/ /\ rec.type = "rollback" + /\ rec.ts = rec.start_ts +LockConsistency == + \* There should be at most one lock for each key. + \A k \in KEY : + /\ Cardinality(key_lock[k]) <= 1 + /\ \A l \in key_lock[k] : + ~ \E w \in key_write[k] : w.start_ts = l.ts + +CommittedClientConsistency == + \A c \in CLIENT : + LET + start_ts == client_ts[c].start_ts + commit_ts == client_ts[c].commit_ts + primary == client_key[c].primary + IN + client_state[c] = "committed" => + \E w \in key_write[primary] : + /\ w.start_ts = start_ts + /\ w.type = "write" + /\ w.commit_ts = commit_ts + +\* If a client is aborted, there should be no committed primary key. +AbortedClientConsistency == + \A c \in CLIENT : + client_state[c] = "aborted" => + ~ \E w \in key_write[client_key[c].primary] : + /\ w.start_ts = client_ts[c].start_ts + /\ w.type = "write" + +CommittedTxnConsistency == + \A c \in CLIENT : + client_state[c] # "init" => + LET + primary == client_key[c].primary + secondary == client_key[c].secondary + start_ts == client_ts[c].start_ts + IN + \A wp \in key_write[primary] : + wp.start_ts = start_ts /\ wp.type = "write" => + \A s \in secondary : + \/ \E l \in key_lock[s] : l.ts = start_ts + \/ \E ws \in key_write[s] : ws.start_ts = start_ts /\ ws.type = "write" + +\* For each transaction, we cannot have both committed and rolled back keys. +RollbackConsistency == + \A start_ts \in {client_ts[c].start_ts : c \in CLIENT} : + (\E k \in KEY : + \E w \in key_write[k] : w.start_ts = start_ts /\ w.type = "rollback") => + \A k2 \in KEY : + ~ \E w2 \in key_write[k2] : w2.start_ts = start_ts /\ w2.type = "write" + +\* For each key, each write or rollback record in write column should have a +\* unique start_ts. +UniqueWrite == + \A k \in KEY : + Cardinality({w.start_ts : w \in key_write[k]}) = Cardinality(key_write[k]) + +----------------------------------------------------------------------------- + +THEOREM Safety == + PessimisticSpec => [](/\ TypeInvariant + /\ WriteConsistency + /\ LockConsistency + /\ CommittedTxnConsistency + /\ RollbackConsistency + /\ UniqueWrite) ============================================================================= diff --git a/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test1.launch b/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test1.launch index f5eaee7..1dd5641 100644 --- a/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test1.launch +++ b/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test1.launch @@ -1,47 +1,35 @@ - - - - - - - - - - - - + + - - + + + + + + + - - + - - + - - - - - From be9ddc9e7a419ac0eb7be77d5f81c374fd00ce1c Mon Sep 17 00:00:00 2001 From: Yilin Chen Date: Wed, 23 Oct 2019 20:52:20 +0800 Subject: [PATCH 09/20] add comments to the spec Signed-off-by: Yilin Chen --- .../PessimisticTransaction.tla | 123 ++++++++++++++---- .../PessimisticTransaction___Test1.launch | 26 +++- 2 files changed, 117 insertions(+), 32 deletions(-) diff --git a/PessimisticTransaction/PessimisticTransaction.tla b/PessimisticTransaction/PessimisticTransaction.tla index a8f2f0f..8284296 100644 --- a/PessimisticTransaction/PessimisticTransaction.tla +++ b/PessimisticTransaction/PessimisticTransaction.tla @@ -51,7 +51,7 @@ VARIABLES key_write VARIABLES key_last_read_ts \* The set of all messages sent by clients. To simulate message resending, -\* client messages are inserted to the set and never deleted. The server can +\* client messages are inserted into the set and never deleted. The server can \* pick any message in the set to execute. VARIABLES msg @@ -67,45 +67,60 @@ Range(m) == {m[i] : i \in DOMAIN m} ----------------------------------------------------------------------------- -\* find a stale lock for key k. +\* Find a stale lock which blocks the read for key k. findStaleLock(k, ts) == {l \in key_lock[k] : l.pessimistic = FALSE /\ l.ts < ts} +\* Rollback key k in the transaction starting at ts rollback(k, ts) == + \* If the existing lock has the same ts, unlock it. /\ IF \E l \in key_lock[k] : l.ts = ts THEN key_lock' = [key_lock EXCEPT ![k] = {}] ELSE UNCHANGED key_lock /\ key_data' = [key_data EXCEPT ![k] = @ \ {[ts |-> ts]}] + \* Write a rollback in the write column. + \* TODO: collapse rollback /\ key_write' = [key_write EXCEPT ![k] = @ \union {[ts |-> ts, type |-> "rollback", start_ts |-> ts]}] - + +\* Commit key k commit(k, start_ts, commit_ts) == /\ IF \E l \in key_lock[k] : l.ts = start_ts - THEN + THEN + \* Write the write column and unlock the lock iff the lock exists /\ key_lock' = [key_lock EXCEPT ![k] = {}] /\ key_write' = [key_write EXCEPT ![k] = @ \union {[ts |-> commit_ts, type |-> "write", start_ts |-> start_ts]}] - /\ Assert(key_last_read_ts[k] < commit_ts, TRUE) + \* Assert we don't violate snapshot isolation + /\ Assert(key_last_read_ts[k] < commit_ts, <>) ELSE UNCHANGED <> /\ UNCHANGED key_data +\* Change the state of client c to aborted abortTxn(c) == - client_state' = [client_state EXCEPT ![c] = "aborted"] + /\ ~ client_state[c] \in {"committed", "undetermined"} + /\ client_state' = [client_state EXCEPT ![c] = "aborted"] + +\* Change the state of client c to undetermined due to lost commit RPC. +undetermine(c) == + /\ client_state[c] = "committing" + /\ client_state' = [client_state EXCEPT ![c] = "undetermined"] ----------------------------------------------------------------------------- Start(c) == /\ client_state[c] = "init" /\ next_ts' = next_ts + 1 + \* Select any key as the primary key /\ \E primary \in KEY : client_key' = [client_key EXCEPT ![c] = [primary |-> primary, secondary |-> KEY \ {primary}, - pessimistic |-> KEY, - pending |-> KEY] - ] + pessimistic |-> KEY, \* Assume we need to acquire pessimistic locks for all keys + pending |-> KEY]] /\ client_state' = [client_state EXCEPT ![c] = "working"] + \* The for_update_ts is initialized to be the same as start_ts. /\ client_ts' = [client_ts EXCEPT ![c].start_ts = next_ts', ![c].for_update_ts = next_ts'] /\ UNCHANGED <> @@ -113,9 +128,11 @@ LockKey(c) == /\ client_state[c] = "working" /\ IF client_key[c].pessimistic = {} THEN + \* The client can prewrite after all pessimistic locks have been acquired. /\ client_state' = [client_state EXCEPT ![c] = "prewriting"] /\ UNCHANGED <> ELSE + \* Select any unlocked key and acquire its pessimistic lock \E k \in client_key[c].pessimistic : /\ msg' = msg \union {[c |-> c, type |-> "lock", key |-> k, primary |-> client_key[c].primary, @@ -126,9 +143,11 @@ Prewrite(c) == /\ client_state[c] = "prewriting" /\ IF client_key[c].pending = {} THEN + \* The client can commit the primary key after prewriting all keys. /\ client_state' = [client_state EXCEPT ![c] = "committing"] /\ UNCHANGED <> ELSE + \* Select any pending key to prewrite \E k \in client_key[c].pending : /\ msg' = msg \union {[c |-> c, type |-> "prewrite", key |-> k, primary |-> client_key[c].primary, @@ -137,12 +156,14 @@ Prewrite(c) == Commit(c) == /\ client_state[c] = "committing" + \* Get a new ts as commit_ts /\ IF client_ts[c].commit_ts = 0 THEN /\ next_ts' = next_ts + 1 /\ client_ts' = [client_ts EXCEPT ![c].commit_ts = next_ts'] ELSE UNCHANGED <> + \* Commit the primary key /\ msg' = msg \union {[c |-> c, type |-> "commit", key |-> client_key[c].primary, start_ts |-> client_ts[c].start_ts, commit_ts |-> client_ts'[c].commit_ts]} @@ -154,6 +175,7 @@ ClientOp(c) == \/ LockKey(c) \/ Prewrite(c) \/ Commit(c) + \* Committing secondary keys is ommitted DoRead == \E cmd \in msg : @@ -166,15 +188,20 @@ DoRead == stale_lock == findStaleLock(k, ts) IN IF stale_lock = {} + \* Successfully read THEN + \* The client receives the read response, update the last read ts /\ IF key_last_read_ts[k] < ts THEN key_last_read_ts' = [key_last_read_ts EXCEPT ![k] = ts] ELSE UNCHANGED key_last_read_ts + \* To simulate the client fails to receive the read response /\ UNCHANGED <> ELSE + \* When there is a blocking lock, the client may resolve the lock by cleanup. /\ msg' = msg \union {[type |-> "cleanup", primary |-> l.primary, start_ts |-> l.ts] : l \in stale_lock} /\ UNCHANGED <> + \* Response loss leads to no state change DoCleanup == \E cmd \in msg : @@ -188,13 +215,17 @@ DoCleanup == committed == {w \in key_write[k] : w.start_ts = ts /\ w.type = "write"} IN IF committed # {} + \* The transaction is already committed, so resolve locks using its commit_ts THEN /\ msg' = msg \union {[type |-> "resolve", primary |-> k, start_ts |-> ts, commit_ts |-> t.ts] : t \in committed} /\ UNCHANGED <> ELSE + \* The transaction is not committed, so rollback the primary key. /\ rollback(k, ts) /\ UNCHANGED <> + \* The client may resolve locks using 0 as commit_ts. When the cleanup response is lost, + \* msg remains unchanged. /\ \/ msg' = msg \union {[type |-> "resolve", primary |-> k, start_ts |-> ts, commit_ts |-> 0]} \/ UNCHANGED msg @@ -204,12 +235,14 @@ DoResolve == /\ cmd.type = "resolve" /\ IF cmd.commit_ts = 0 THEN + \* rollback locks when commit_ts = 0 \E k \in KEY : \E l \in key_lock[k] : /\ l.primary = cmd.primary /\ l.ts = cmd.start_ts /\ rollback(k, cmd.start_ts) ELSE + \* commit locks when commit_ts > 0 \E k \in KEY : \E l \in key_lock[k] : /\ l.primary = cmd.primary @@ -217,7 +250,7 @@ DoResolve == /\ commit(k, cmd.start_ts, cmd.commit_ts) /\ UNCHANGED <> - +\* TODO: This action is too complex and concrete. Try to simplify it. DoLockKey == \E cmd \in msg : /\ cmd.type = "lock" @@ -227,11 +260,15 @@ DoLockKey == primary == cmd.primary ts == cmd.start_ts for_update_ts == cmd.for_update_ts + \* Write or overwrite the pessimistic lock writeLock == /\ key_lock' = [key_lock EXCEPT ![k] = {[ts |-> ts, for_update_ts |-> for_update_ts, primary |-> primary, pessimistic |-> TRUE]}] /\ UNCHANGED <> + \* Inform the client that the key is locked /\ \/ client_key' = [client_key EXCEPT ![c].pessimistic = @ \ {k}] + \* client_key remains the same when the response is lost \/ UNCHANGED client_key + \* Update the for_update_ts in the client with new_ts updateForUpdateTs(new_ts) == IF new_ts > client_ts[c].for_update_ts THEN @@ -242,33 +279,40 @@ DoLockKey == IF key_lock[k] = {} THEN IF key_write[k] = {} + \* If no lock or write exists, we can always lock the key THEN writeLock ELSE - LET + LET + \* Find the write record with biggest commit_ts latest == CHOOSE w \in key_write[k] : \A w2 \in key_write[k] : w.ts >= w2.ts IN IF latest.ts > for_update_ts + \* Update the client's for_update_ts when there is a newer commit. + \* Response loss causes no state change. THEN updateForUpdateTs(latest.ts) ELSE IF \E w \in key_write[k] : w.start_ts = ts /\ w.type = "rollback" + \* If any key to be locked is rollbacked, abort the transaction. + \* TODO: Maybe it needn't be included in the spec. THEN abortTxn(c) /\ UNCHANGED <> + \* Otherwise we can lock the key. ELSE writeLock ELSE LET l == CHOOSE l \in key_lock[k] : TRUE IN IF l.ts # ts + \* If there is a lock from another transaction, the client may cleanup the lock. + \* Response loss causes no state change. THEN /\ msg' = msg \union {[type |-> "cleanup", primary |-> l.primary, start_ts |-> l.ts]} /\ UNCHANGED <> ELSE - IF l.pessimistic = FALSE - THEN UNCHANGED vars - ELSE - IF l.for_update_ts < for_update_ts - THEN writeLock - ELSE UNCHANGED vars + \* Only overwrite the lock when it's a pessimistic lock with smaller for_update_ts + /\ l.pessimistic + /\ l.for_update_ts < for_update_ts + /\ writeLock DoPrewrite == \E cmd \in msg : @@ -284,12 +328,16 @@ DoPrewrite == \/ key_lock[k] = {} \/ \E l \in key_lock[k] : l.ts # ts THEN + \* Abort the transaction when its lock doesn't exist /\ abortTxn(c) /\ UNCHANGED <> ELSE + \* Otherwise rewrite the existing lock to an optimistic lock /\ key_lock' = [key_lock EXCEPT ![k] = {[ts |-> ts, for_update_ts |-> for_update_ts, primary |-> primary, pessimistic |-> FALSE]}] /\ key_data' = [key_data EXCEPT ![k] = @ \union {[ts |-> ts]}] + \* Inform the client that the key is successfully prewritten /\ \/ client_key' = [client_key EXCEPT ![c].pending = @ \ {k}] + \* Simulate response loss \/ UNCHANGED client_key /\ UNCHANGED <> @@ -304,14 +352,20 @@ DoCommit == IN IF \/ \E l \in key_lock[k] : l.ts = start_ts \/ \E w \in key_write[k] : w.start_ts = start_ts /\ w.type = "write" + \* Commit the key iff the prewritten key exists or it's a repeated commit THEN /\ commit(k, start_ts, commit_ts) + \* Change client state to committed. /\ \/ client_state' = [client_state EXCEPT ![c] = "committed"] - \/ UNCHANGED client_state + \* Commit response is lost + \/ undetermine(c) /\ UNCHANGED <> ELSE - /\ \/ client_state' = [client_state EXCEPT ![c] = "aborted"] - \/ UNCHANGED client_state + \* The lock doesn't exist and the key is not committed, so commit fails. + /\ \/ /\ Assert(client_state[c] # "committed", client_state[c]) + /\ abortTxn(c) + \* Commit response is lost + \/ undetermine(c) /\ UNCHANGED <> ServerOp == @@ -321,13 +375,21 @@ ServerOp == \/ DoLockKey \/ DoPrewrite \/ DoCommit - + Read == - \E ts \in 0..next_ts : - \E k \in KEY : - /\ msg' = msg \union - {[type |-> "read", key |-> k, ts |-> ts]} - /\ UNCHANGED <> + \* Read by a running transaction + \/ \E c \in CLIENT : + /\ client_state[c] = "working" + /\ \E k \in KEY : + msg' = msg \union + {[type |-> "read", key |-> k, ts |-> client_ts[c].start_ts]} + /\ UNCHANGED <> + \* When all transactions are finished, we read all keys with latest TS + \/ /\ \A c \in CLIENT : client_state[c] \in {"committed", "aborted", "undetermined"} + /\ \E k \in KEY : + msg' = msg \union + {[type |-> "read", key |-> k, ts |-> next_ts]} + /\ UNCHANGED <> Init == /\ next_ts = 1 @@ -356,7 +418,7 @@ NextTsTypeInv == next_ts \in Pos ClientStateTypeInv == client_state \in [ CLIENT -> {"init", "working", "prewriting", - "committing", "committed", "aborted"} + "committing", "committed", "aborted", "undetermined"} ] ClientTsTypeInv == @@ -422,9 +484,10 @@ WriteConsistency == /\ rec.ts = rec.start_ts LockConsistency == - \* There should be at most one lock for each key. \A k \in KEY : + \* There should be at most one lock for each key. /\ Cardinality(key_lock[k]) <= 1 + \* When the lock exists, there cannot be a corresponding commit record /\ \A l \in key_lock[k] : ~ \E w \in key_write[k] : w.start_ts = l.ts @@ -435,6 +498,7 @@ CommittedClientConsistency == commit_ts == client_ts[c].commit_ts primary == client_key[c].primary IN + \* When the client considers it's committed, its primary key must be committed client_state[c] = "committed" => \E w \in key_write[primary] : /\ w.start_ts = start_ts @@ -458,6 +522,8 @@ CommittedTxnConsistency == start_ts == client_ts[c].start_ts IN \A wp \in key_write[primary] : + \* If the primary key is committed, the secondary keys of the same transaction + \* must be either committed or locked wp.start_ts = start_ts /\ wp.type = "write" => \A s \in secondary : \/ \E l \in key_lock[s] : l.ts = start_ts @@ -484,6 +550,7 @@ THEOREM Safety == /\ WriteConsistency /\ LockConsistency /\ CommittedTxnConsistency + /\ AbortedClientConsistency /\ RollbackConsistency /\ UniqueWrite) diff --git a/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test1.launch b/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test1.launch index 1dd5641..e89222c 100644 --- a/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test1.launch +++ b/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test1.launch @@ -1,12 +1,22 @@ + + + + + - - + + + + + + + @@ -21,15 +31,23 @@ + + - + - + + + + + + + From c9af93cee7dce8fc823eb7868532700e4f27bde8 Mon Sep 17 00:00:00 2001 From: Yilin Chen Date: Fri, 25 Oct 2019 16:45:24 +0800 Subject: [PATCH 10/20] implement collapse rollback Signed-off-by: Yilin Chen --- .../PessimisticTransaction.tla | 34 +++++++++---------- .../PessimisticTransaction___Test1.launch | 8 ++--- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/PessimisticTransaction/PessimisticTransaction.tla b/PessimisticTransaction/PessimisticTransaction.tla index 8284296..7eaeeca 100644 --- a/PessimisticTransaction/PessimisticTransaction.tla +++ b/PessimisticTransaction/PessimisticTransaction.tla @@ -71,6 +71,15 @@ Range(m) == {m[i] : i \in DOMAIN m} findStaleLock(k, ts) == {l \in key_lock[k] : l.pessimistic = FALSE /\ l.ts < ts} +\* Get the write records of k before or at timestamp ts +historyWrites(k, ts) == + {w \in key_write[k] : w.ts <= ts} + +\* Get the set of the latest write record of k before or at timestamp ts +latestHistoryWrite(k, ts) == + {w \in historyWrites(k, ts) : + \A w2 \in historyWrites(k, ts) : w.ts >= w2.ts} + \* Rollback key k in the transaction starting at ts rollback(k, ts) == \* If the existing lock has the same ts, unlock it. @@ -79,9 +88,10 @@ rollback(k, ts) == ELSE UNCHANGED key_lock /\ key_data' = [key_data EXCEPT ![k] = @ \ {[ts |-> ts]}] \* Write a rollback in the write column. - \* TODO: collapse rollback /\ key_write' = [key_write EXCEPT - ![k] = @ \union {[ts |-> ts, type |-> "rollback", start_ts |-> ts]}] + ![k] = (@ \ {w \in latestHistoryWrite(k, ts) : w.type = "rollback"}) \* collapse rollback + \union {[ts |-> ts, type |-> "rollback", start_ts |-> ts]}] + \* Commit key k commit(k, start_ts, commit_ts) == @@ -377,19 +387,10 @@ ServerOp == \/ DoCommit Read == - \* Read by a running transaction - \/ \E c \in CLIENT : - /\ client_state[c] = "working" - /\ \E k \in KEY : - msg' = msg \union - {[type |-> "read", key |-> k, ts |-> client_ts[c].start_ts]} - /\ UNCHANGED <> - \* When all transactions are finished, we read all keys with latest TS - \/ /\ \A c \in CLIENT : client_state[c] \in {"committed", "aborted", "undetermined"} - /\ \E k \in KEY : - msg' = msg \union - {[type |-> "read", key |-> k, ts |-> next_ts]} - /\ UNCHANGED <> + \E ts \in 0..next_ts : + \E k \in KEY : + /\ msg' = msg \union {[type |-> "read", key |-> k, ts |-> next_ts]} + /\ UNCHANGED <> Init == /\ next_ts = 1 @@ -454,8 +455,7 @@ MsgTypeInv == [c : CLIENT, type : {"commit"}, key : KEY, start_ts : Pos, commit_ts : Pos] \union [type : {"read"}, key : KEY, ts : Nat] \union [type : {"cleanup"}, primary : KEY, start_ts : Pos] \union - [type : {"resolve"}, primary : KEY, start_ts : Pos, commit_ts : Nat] \union - [type : {"rollback"}, key : SUBSET KEY, start_ts: Pos] + [type : {"resolve"}, primary : KEY, start_ts : Pos, commit_ts : Nat] ) TypeInvariant == diff --git a/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test1.launch b/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test1.launch index e89222c..73f11b4 100644 --- a/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test1.launch +++ b/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test1.launch @@ -1,7 +1,7 @@ - + @@ -12,9 +12,9 @@ - + - + @@ -38,7 +38,7 @@ - + From 3c1878e9868409074e1fb75505ddbd7c29584783 Mon Sep 17 00:00:00 2001 From: Yilin Chen Date: Mon, 28 Oct 2019 10:58:23 +0800 Subject: [PATCH 11/20] distinguish from optimistic and pessimistic Signed-off-by: Yilin Chen --- .../PessimisticTransaction.tla | 53 ++++++++++++------- .../PessimisticTransaction___Test1.launch | 8 ++- 2 files changed, 41 insertions(+), 20 deletions(-) diff --git a/PessimisticTransaction/PessimisticTransaction.tla b/PessimisticTransaction/PessimisticTransaction.tla index 7eaeeca..0b80415 100644 --- a/PessimisticTransaction/PessimisticTransaction.tla +++ b/PessimisticTransaction/PessimisticTransaction.tla @@ -9,6 +9,26 @@ ASSUME KEY # {} \* Keys cannot be empty. \* The set of clients to execute a transaction. CONSTANTS CLIENT +\* CLIENT_KEY is a set of [c -> SUBSET KEY], +\* representing involved keys of each client +CONSTANTS CLIENT_KEY + +ASSUME \A c \in CLIENT: CLIENT_KEY[c] \subseteq CLIENT_KEY[c] + +CONSTANTS CLIENT_PRIMARY + +ASSUME \A c \in CLIENT: CLIENT_PRIMARY[c] \in CLIENT_KEY[c] + +\* The set of pessimistic clients +CONSTANTS PESSIMISTIC_CLIENT + +ASSUME PESSIMISTIC_CLIENT \subseteq CLIENT + +\* The set of optimistic clients +CONSTANTS OPTIMISTIC_CLIENT + +ASSUME OPTIMISTIC_CLIENT \subseteq CLIENT + \* next_ts is the timestamp for transaction. It is increased monotonically, \* so every transaction must have a unique start and commit ts. VARIABLES next_ts @@ -63,8 +83,6 @@ vars == <> Pos == {x \in Nat : x > 0} -Range(m) == {m[i] : i \in DOMAIN m} - ----------------------------------------------------------------------------- \* Find a stale lock which blocks the read for key k. @@ -118,17 +136,15 @@ undetermine(c) == ----------------------------------------------------------------------------- -Start(c) == +StartPessimistic(c) == /\ client_state[c] = "init" /\ next_ts' = next_ts + 1 - \* Select any key as the primary key - /\ \E primary \in KEY : - client_key' = + /\ client_key' = [client_key EXCEPT - ![c] = [primary |-> primary, - secondary |-> KEY \ {primary}, - pessimistic |-> KEY, \* Assume we need to acquire pessimistic locks for all keys - pending |-> KEY]] + ![c] = [primary |-> CLIENT_PRIMARY[c], + secondary |-> CLIENT_KEY[c] \ {CLIENT_PRIMARY[c]}, + pessimistic |-> CLIENT_KEY[c], \* Assume we need to acquire pessimistic locks for all keys + pending |-> CLIENT_KEY[c]]] /\ client_state' = [client_state EXCEPT ![c] = "working"] \* The for_update_ts is initialized to be the same as start_ts. /\ client_ts' = [client_ts EXCEPT ![c].start_ts = next_ts', ![c].for_update_ts = next_ts'] @@ -149,7 +165,7 @@ LockKey(c) == start_ts |-> client_ts[c].start_ts, for_update_ts |-> client_ts[c].for_update_ts]} /\ UNCHANGED <> -Prewrite(c) == +PessimisticPrewrite(c) == /\ client_state[c] = "prewriting" /\ IF client_key[c].pending = {} THEN @@ -180,10 +196,10 @@ Commit(c) == /\ UNCHANGED <> -ClientOp(c) == - \/ Start(c) +PessimisticClientOp(c) == + \/ StartPessimistic(c) \/ LockKey(c) - \/ Prewrite(c) + \/ PessimisticPrewrite(c) \/ Commit(c) \* Committing secondary keys is ommitted @@ -260,7 +276,7 @@ DoResolve == /\ commit(k, cmd.start_ts, cmd.commit_ts) /\ UNCHANGED <> -\* TODO: This action is too complex and concrete. Try to simplify it. +\* This action is too complex and concrete. Maybe simplify it? DoLockKey == \E cmd \in msg : /\ cmd.type = "lock" @@ -324,9 +340,10 @@ DoLockKey == /\ l.for_update_ts < for_update_ts /\ writeLock -DoPrewrite == +DoPessimisticPrewrite == \E cmd \in msg : /\ cmd.type = "prewrite" + /\ cmd.for_update_ts > 0 /\ LET c == cmd.c k == cmd.key @@ -383,7 +400,7 @@ ServerOp == \/ DoCleanup \/ DoResolve \/ DoLockKey - \/ DoPrewrite + \/ DoPessimisticPrewrite \/ DoCommit Read == @@ -407,7 +424,7 @@ Init == Next == \/ ServerOp - \/ \E c \in CLIENT : ClientOp(c) + \/ \E c \in PESSIMISTIC_CLIENT : PessimisticClientOp(c) \/ Read PessimisticSpec == Init /\ [][Next]_vars diff --git a/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test1.launch b/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test1.launch index 73f11b4..89d9e1a 100644 --- a/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test1.launch +++ b/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test1.launch @@ -1,7 +1,7 @@ - + @@ -12,7 +12,7 @@ - + @@ -39,6 +39,10 @@ + + + + From 4a79d0785c6d8a17c5c2f21b5f4ea4929ce5a468 Mon Sep 17 00:00:00 2001 From: Yilin Chen Date: Mon, 28 Oct 2019 11:59:49 +0800 Subject: [PATCH 12/20] implement optimistic transaction Signed-off-by: Yilin Chen --- .../PessimisticTransaction.tla | 62 +++++++++++++++++-- .../PessimisticTransaction___Test2.launch | 57 +++++++++++++++++ 2 files changed, 114 insertions(+), 5 deletions(-) create mode 100644 PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test2.launch diff --git a/PessimisticTransaction/PessimisticTransaction.tla b/PessimisticTransaction/PessimisticTransaction.tla index 0b80415..c1c345c 100644 --- a/PessimisticTransaction/PessimisticTransaction.tla +++ b/PessimisticTransaction/PessimisticTransaction.tla @@ -109,7 +109,6 @@ rollback(k, ts) == /\ key_write' = [key_write EXCEPT ![k] = (@ \ {w \in latestHistoryWrite(k, ts) : w.type = "rollback"}) \* collapse rollback \union {[ts |-> ts, type |-> "rollback", start_ts |-> ts]}] - \* Commit key k commit(k, start_ts, commit_ts) == @@ -136,6 +135,20 @@ undetermine(c) == ----------------------------------------------------------------------------- +StartOptimistic(c) == + /\ client_state[c] = "init" + /\ next_ts' = next_ts + 1 + /\ client_key' = + [client_key EXCEPT + ![c] = [primary |-> CLIENT_PRIMARY[c], + secondary |-> CLIENT_KEY[c] \ {CLIENT_PRIMARY[c]}, + pessimistic |-> {}, \* An optimistic transaction has no pessimistic lock to acquire + pending |-> CLIENT_KEY[c]]] + /\ client_state' = [client_state EXCEPT ![c] = "prewriting"] + \* The for_update_ts is initialized to be 0 in optimistic transactions. + /\ client_ts' = [client_ts EXCEPT ![c].start_ts = next_ts', ![c].for_update_ts = 0] + /\ UNCHANGED <> + StartPessimistic(c) == /\ client_state[c] = "init" /\ next_ts' = next_ts + 1 @@ -165,7 +178,7 @@ LockKey(c) == start_ts |-> client_ts[c].start_ts, for_update_ts |-> client_ts[c].for_update_ts]} /\ UNCHANGED <> -PessimisticPrewrite(c) == +Prewrite(c) == /\ client_state[c] = "prewriting" /\ IF client_key[c].pending = {} THEN @@ -195,11 +208,16 @@ Commit(c) == start_ts |-> client_ts[c].start_ts, commit_ts |-> client_ts'[c].commit_ts]} /\ UNCHANGED <> +OptimisticClientOp(c) == + \/ StartOptimistic(c) + \/ Prewrite(c) + \/ Commit(c) + \* Committing secondary keys is ommitted PessimisticClientOp(c) == \/ StartPessimistic(c) \/ LockKey(c) - \/ PessimisticPrewrite(c) + \/ Prewrite(c) \/ Commit(c) \* Committing secondary keys is ommitted @@ -339,7 +357,39 @@ DoLockKey == /\ l.pessimistic /\ l.for_update_ts < for_update_ts /\ writeLock - + +DoOptimisticPrewrite == + \E cmd \in msg : + /\ cmd.type = "prewrite" + /\ cmd.for_update_ts = 0 + /\ LET + c == cmd.c + k == cmd.key + primary == cmd.primary + ts == cmd.start_ts + lock == { l \in key_lock[k] : l.ts # ts } + IN + IF \E w \in key_write[k] : w.ts >= ts + THEN + /\ abortTxn(c) + /\ UNCHANGED <> + ELSE IF lock # {} + THEN + \* When there is another transaction's lock, the client may resolve the lock by cleanup. + /\ msg' = msg \union + {[type |-> "cleanup", primary |-> l.primary, start_ts |-> l.ts] : l \in lock} + /\ UNCHANGED <> + \* Response loss leads to no state change + ELSE + \* Otherwise prewrite + /\ key_lock' = [key_lock EXCEPT ![k] = {[ts |-> ts, for_update_ts |-> 0, primary |-> primary, pessimistic |-> FALSE]}] + /\ key_data' = [key_data EXCEPT ![k] = @ \union {[ts |-> ts]}] + \* Inform the client that the key is successfully prewritten + /\ \/ client_key' = [client_key EXCEPT ![c].pending = @ \ {k}] + \* Simulate response loss + \/ UNCHANGED client_key + /\ UNCHANGED <> + DoPessimisticPrewrite == \E cmd \in msg : /\ cmd.type = "prewrite" @@ -400,6 +450,7 @@ ServerOp == \/ DoCleanup \/ DoResolve \/ DoLockKey + \/ DoOptimisticPrewrite \/ DoPessimisticPrewrite \/ DoCommit @@ -424,6 +475,7 @@ Init == Next == \/ ServerOp + \/ \E c \in OPTIMISTIC_CLIENT : OptimisticClientOp(c) \/ \E c \in PESSIMISTIC_CLIENT : PessimisticClientOp(c) \/ Read @@ -468,7 +520,7 @@ KeyLastReadTsTypeInv == MsgTypeInv == msg \subseteq ( [c : CLIENT, type : {"lock", "prewrite"}, key : KEY, primary: KEY, - start_ts : Pos, for_update_ts : Pos] \union + start_ts : Pos, for_update_ts : Nat] \union [c : CLIENT, type : {"commit"}, key : KEY, start_ts : Pos, commit_ts : Pos] \union [type : {"read"}, key : KEY, ts : Nat] \union [type : {"cleanup"}, primary : KEY, start_ts : Pos] \union diff --git a/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test2.launch b/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test2.launch new file mode 100644 index 0000000..bf2cda3 --- /dev/null +++ b/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test2.launch @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From a7364dcd8303c3f38e125f5b05c99d9c1e3bb2cd Mon Sep 17 00:00:00 2001 From: Yilin Chen Date: Wed, 30 Oct 2019 11:39:32 +0800 Subject: [PATCH 13/20] fix wrong toolbox preference Signed-off-by: Yilin Chen --- .../PessimisticTransaction.tla | 20 +++---- .../PessimisticTransaction___Test1.launch | 44 +++----------- .../PessimisticTransaction___Test2.launch | 57 ------------------- 3 files changed, 17 insertions(+), 104 deletions(-) delete mode 100644 PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test2.launch diff --git a/PessimisticTransaction/PessimisticTransaction.tla b/PessimisticTransaction/PessimisticTransaction.tla index c1c345c..cbe3742 100644 --- a/PessimisticTransaction/PessimisticTransaction.tla +++ b/PessimisticTransaction/PessimisticTransaction.tla @@ -6,8 +6,14 @@ EXTENDS Integers, FiniteSets, TLC CONSTANTS KEY ASSUME KEY # {} \* Keys cannot be empty. +\* The set of pessimistic clients +CONSTANTS PESSIMISTIC_CLIENT + +\* The set of optimistic clients +CONSTANTS OPTIMISTIC_CLIENT + \* The set of clients to execute a transaction. -CONSTANTS CLIENT +CLIENT == PESSIMISTIC_CLIENT \union OPTIMISTIC_CLIENT \* CLIENT_KEY is a set of [c -> SUBSET KEY], \* representing involved keys of each client @@ -19,16 +25,6 @@ CONSTANTS CLIENT_PRIMARY ASSUME \A c \in CLIENT: CLIENT_PRIMARY[c] \in CLIENT_KEY[c] -\* The set of pessimistic clients -CONSTANTS PESSIMISTIC_CLIENT - -ASSUME PESSIMISTIC_CLIENT \subseteq CLIENT - -\* The set of optimistic clients -CONSTANTS OPTIMISTIC_CLIENT - -ASSUME OPTIMISTIC_CLIENT \subseteq CLIENT - \* next_ts is the timestamp for transaction. It is increased monotonically, \* so every transaction must have a unique start and commit ts. VARIABLES next_ts @@ -478,7 +474,7 @@ Next == \/ \E c \in OPTIMISTIC_CLIENT : OptimisticClientOp(c) \/ \E c \in PESSIMISTIC_CLIENT : PessimisticClientOp(c) \/ Read - + PessimisticSpec == Init /\ [][Next]_vars ----------------------------------------------------------------------------- diff --git a/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test1.launch b/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test1.launch index 89d9e1a..83bb7b6 100644 --- a/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test1.launch +++ b/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test1.launch @@ -1,57 +1,31 @@ - - - - - - - - - - - - + + - - + + - - - - - - - - - + - - - + + - - + - - + - - - - - diff --git a/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test2.launch b/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test2.launch deleted file mode 100644 index bf2cda3..0000000 --- a/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test2.launch +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From 00f95e6457f1728d3481bbe5bc19061f45345dc3 Mon Sep 17 00:00:00 2001 From: Yilin Chen Date: Wed, 30 Oct 2019 13:40:11 +0800 Subject: [PATCH 14/20] protect primary key of pessimistic txn Signed-off-by: Yilin Chen --- .../PessimisticTransaction.tla | 31 ++++++++++--------- .../PessimisticTransaction___Test1.launch | 4 +-- .../PessimisticTransaction___Test2.launch | 31 +++++++++++++++++++ 3 files changed, 50 insertions(+), 16 deletions(-) create mode 100644 PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test2.launch diff --git a/PessimisticTransaction/PessimisticTransaction.tla b/PessimisticTransaction/PessimisticTransaction.tla index cbe3742..282d6ef 100644 --- a/PessimisticTransaction/PessimisticTransaction.tla +++ b/PessimisticTransaction/PessimisticTransaction.tla @@ -19,7 +19,7 @@ CLIENT == PESSIMISTIC_CLIENT \union OPTIMISTIC_CLIENT \* representing involved keys of each client CONSTANTS CLIENT_KEY -ASSUME \A c \in CLIENT: CLIENT_KEY[c] \subseteq CLIENT_KEY[c] +ASSUME \A c \in CLIENT: CLIENT_KEY[c] \subseteq KEY CONSTANTS CLIENT_PRIMARY @@ -53,7 +53,7 @@ VARIABLES key_data VARIABLES key_lock \* key_write[k] is a sequence of committed version of the key. -\* A committed version of the key is a record of [ts, type, start_ts]. +\* A committed version of the key is a record of [ts, type, start_ts, protected]. \* type can be "write" or "rollback" depending on record type. start_ts \* field only exists if type is "write". For "write" record, ts denotes \* commit_ts; for "rollback" record, $ts$ denotes start_ts. @@ -96,15 +96,19 @@ latestHistoryWrite(k, ts) == \* Rollback key k in the transaction starting at ts rollback(k, ts) == - \* If the existing lock has the same ts, unlock it. - /\ IF \E l \in key_lock[k] : l.ts = ts - THEN key_lock' = [key_lock EXCEPT ![k] = {}] - ELSE UNCHANGED key_lock - /\ key_data' = [key_data EXCEPT ![k] = @ \ {[ts |-> ts]}] - \* Write a rollback in the write column. - /\ key_write' = [key_write EXCEPT - ![k] = (@ \ {w \in latestHistoryWrite(k, ts) : w.type = "rollback"}) \* collapse rollback - \union {[ts |-> ts, type |-> "rollback", start_ts |-> ts]}] + LET + \* rollback the primary key of a pessimistic transaction needs be protected from being collapsed + protected == \E l \in key_lock[k] : l.for_update_ts > 0 /\ k = l.primary + IN + \* If the existing lock has the same ts, unlock it. + /\ IF \E l \in key_lock[k] : l.ts = ts + THEN key_lock' = [key_lock EXCEPT ![k] = {}] + ELSE UNCHANGED key_lock + /\ key_data' = [key_data EXCEPT ![k] = @ \ {[ts |-> ts]}] + \* Write a rollback in the write column. + /\ key_write' = [key_write EXCEPT + ![k] = (@ \ {w \in latestHistoryWrite(k, ts) : w.type = "rollback" /\ ~w.protected }) \* collapse rollback + \union {[ts |-> ts, type |-> "rollback", start_ts |-> ts, protected |-> protected]}] \* Commit key k commit(k, start_ts, commit_ts) == @@ -506,9 +510,8 @@ KeyLockTypeInv == key_lock \in [KEY -> SUBSET [ts : Pos, for_update_ts : Nat, primary : KEY, pessimistic : BOOLEAN]] KeyWriteTypeInv == - key_write \in [KEY -> - SUBSET [ts : Pos, type : {"write", "rollback"}, start_ts : Pos] - ] + key_write \in [KEY -> SUBSET [ts : Pos, type : {"write"}, start_ts : Pos]] \union + [KEY -> SUBSET [ts : Pos, type : {"rollback"}, start_ts : Pos, protected : BOOLEAN]] KeyLastReadTsTypeInv == key_last_read_ts \in [KEY -> Nat] diff --git a/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test1.launch b/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test1.launch index 83bb7b6..2348680 100644 --- a/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test1.launch +++ b/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test1.launch @@ -5,7 +5,7 @@ - + @@ -19,7 +19,7 @@ - + diff --git a/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test2.launch b/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test2.launch new file mode 100644 index 0000000..1c0ef72 --- /dev/null +++ b/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test2.launch @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 862ad765b83c7b7caea688194350b07281865822 Mon Sep 17 00:00:00 2001 From: andylokandy Date: Mon, 30 Mar 2020 16:54:24 +0800 Subject: [PATCH 15/20] fix pessimistic spec Signed-off-by: andylokandy --- .gitignore | 2 +- .../PessimisticTransaction.tla | 88 +++++++++---------- .../PessimisticTransaction___Test1.launch | 52 +++++++++-- PessimisticTransaction/Test1.cfg | 33 +++++++ PessimisticTransaction/Test1.tla | 19 ++++ 5 files changed, 140 insertions(+), 54 deletions(-) create mode 100644 PessimisticTransaction/Test1.cfg create mode 100644 PessimisticTransaction/Test1.tla diff --git a/.gitignore b/.gitignore index 3fc9e3a..6c3cb85 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ states/ - +*.out **/*.toolbox/*/ **/*.toolbox/*___*_SnapShot_*.launch **/*.toolbox/.project diff --git a/PessimisticTransaction/PessimisticTransaction.tla b/PessimisticTransaction/PessimisticTransaction.tla index 282d6ef..d98af48 100644 --- a/PessimisticTransaction/PessimisticTransaction.tla +++ b/PessimisticTransaction/PessimisticTransaction.tla @@ -4,7 +4,7 @@ EXTENDS Integers, FiniteSets, TLC \* The set of transaction keys. CONSTANTS KEY -ASSUME KEY # {} \* Keys cannot be empty. +ASSUME KEY /= {} \* Keys cannot be empty. \* The set of pessimistic clients CONSTANTS PESSIMISTIC_CLIENT @@ -77,7 +77,7 @@ vars == <> ----------------------------------------------------------------------------- -Pos == {x \in Nat : x > 0} +Pos == Nat \ {0} ----------------------------------------------------------------------------- @@ -99,6 +99,7 @@ rollback(k, ts) == LET \* rollback the primary key of a pessimistic transaction needs be protected from being collapsed protected == \E l \in key_lock[k] : l.for_update_ts > 0 /\ k = l.primary + latest_write == latestHistoryWrite(k, ts) IN \* If the existing lock has the same ts, unlock it. /\ IF \E l \in key_lock[k] : l.ts = ts @@ -107,8 +108,11 @@ rollback(k, ts) == /\ key_data' = [key_data EXCEPT ![k] = @ \ {[ts |-> ts]}] \* Write a rollback in the write column. /\ key_write' = [key_write EXCEPT - ![k] = (@ \ {w \in latestHistoryWrite(k, ts) : w.type = "rollback" /\ ~w.protected }) \* collapse rollback - \union {[ts |-> ts, type |-> "rollback", start_ts |-> ts, protected |-> protected]}] + ![k] = IF ~ \E w \in latest_write: w.ts = ts + THEN (@ \ {w \in latest_write: w.type = "rollback" /\ ~w.protected }) \* collapse rollback + \union {[ts |-> ts, type |-> "rollback", start_ts |-> ts, protected |-> protected]} + ELSE @ + ] \* Commit key k commit(k, start_ts, commit_ts) == @@ -118,6 +122,7 @@ commit(k, start_ts, commit_ts) == /\ key_lock' = [key_lock EXCEPT ![k] = {}] /\ key_write' = [key_write EXCEPT ![k] = @ \union {[ts |-> commit_ts, type |-> "write", start_ts |-> start_ts]}] \* Assert we don't violate snapshot isolation + \* TODO /\ Assert(key_last_read_ts[k] < commit_ts, <>) ELSE UNCHANGED <> @@ -125,14 +130,9 @@ commit(k, start_ts, commit_ts) == \* Change the state of client c to aborted abortTxn(c) == - /\ ~ client_state[c] \in {"committed", "undetermined"} + /\ client_state[c] /= "committed" /\ client_state' = [client_state EXCEPT ![c] = "aborted"] -\* Change the state of client c to undetermined due to lost commit RPC. -undetermine(c) == - /\ client_state[c] = "committing" - /\ client_state' = [client_state EXCEPT ![c] = "undetermined"] - ----------------------------------------------------------------------------- StartOptimistic(c) == @@ -258,7 +258,7 @@ DoCleanup == lock == {l \in key_lock[k] : l.ts = ts} committed == {w \in key_write[k] : w.start_ts = ts /\ w.type = "write"} IN - IF committed # {} + IF committed /= {} \* The transaction is already committed, so resolve locks using its commit_ts THEN /\ msg' = msg \union @@ -345,7 +345,7 @@ DoLockKey == LET l == CHOOSE l \in key_lock[k] : TRUE IN - IF l.ts # ts + IF l.ts /= ts \* If there is a lock from another transaction, the client may cleanup the lock. \* Response loss causes no state change. THEN @@ -367,13 +367,13 @@ DoOptimisticPrewrite == k == cmd.key primary == cmd.primary ts == cmd.start_ts - lock == { l \in key_lock[k] : l.ts # ts } + lock == { l \in key_lock[k] : l.ts /= ts } IN IF \E w \in key_write[k] : w.ts >= ts THEN /\ abortTxn(c) /\ UNCHANGED <> - ELSE IF lock # {} + ELSE IF lock /= {} THEN \* When there is another transaction's lock, the client may resolve the lock by cleanup. /\ msg' = msg \union @@ -403,7 +403,7 @@ DoPessimisticPrewrite == IN IF \/ key_lock[k] = {} - \/ \E l \in key_lock[k] : l.ts # ts + \/ \E l \in key_lock[k] : l.ts /= ts THEN \* Abort the transaction when its lock doesn't exist /\ abortTxn(c) @@ -433,16 +433,12 @@ DoCommit == THEN /\ commit(k, start_ts, commit_ts) \* Change client state to committed. - /\ \/ client_state' = [client_state EXCEPT ![c] = "committed"] - \* Commit response is lost - \/ undetermine(c) + /\ client_state' = [client_state EXCEPT ![c] = "committed"] /\ UNCHANGED <> ELSE \* The lock doesn't exist and the key is not committed, so commit fails. - /\ \/ /\ Assert(client_state[c] # "committed", client_state[c]) - /\ abortTxn(c) - \* Commit response is lost - \/ undetermine(c) + /\ Assert(client_state[c] /= "committed", client_state[c]) + /\ abortTxn(c) /\ UNCHANGED <> ServerOp == @@ -483,19 +479,19 @@ PessimisticSpec == Init /\ [][Next]_vars ----------------------------------------------------------------------------- -NextTsTypeInv == next_ts \in Pos +NextTsTypeOK == next_ts \in Pos -ClientStateTypeInv == +ClientStateTypeOK == client_state \in [ CLIENT -> {"init", "working", "prewriting", - "committing", "committed", "aborted", "undetermined"} + "committing", "committed", "aborted"} ] -ClientTsTypeInv == +ClientTsTypeOK == client_ts \in [CLIENT -> [start_ts : Nat, for_update_ts : Nat, commit_ts : Nat]] -ClientKeyTypeInv == +ClientKeyTypeOK == \A c \in CLIENT : \/ client_state[c] = "init" \/ client_key[c] \in [primary : KEY, @@ -503,20 +499,20 @@ ClientKeyTypeInv == pessimistic : SUBSET KEY, pending : SUBSET KEY] -KeyDataTypeInv == +KeyDataTypeOK == key_data \in [KEY -> SUBSET [ts : Pos]] -KeyLockTypeInv == +KeyLockTypeOK == key_lock \in [KEY -> SUBSET [ts : Pos, for_update_ts : Nat, primary : KEY, pessimistic : BOOLEAN]] -KeyWriteTypeInv == - key_write \in [KEY -> SUBSET [ts : Pos, type : {"write"}, start_ts : Pos]] \union - [KEY -> SUBSET [ts : Pos, type : {"rollback"}, start_ts : Pos, protected : BOOLEAN]] +KeyWriteTypeOK == + key_write \in [KEY -> SUBSET ([ts : Pos, type : {"write"}, start_ts : Pos] \union + [ts : Pos, type : {"rollback"}, start_ts : Pos, protected : BOOLEAN])] -KeyLastReadTsTypeInv == +KeyLastReadTsTypeOK == key_last_read_ts \in [KEY -> Nat] -MsgTypeInv == +MsgTypeOK == msg \subseteq ( [c : CLIENT, type : {"lock", "prewrite"}, key : KEY, primary: KEY, start_ts : Pos, for_update_ts : Nat] \union @@ -526,16 +522,16 @@ MsgTypeInv == [type : {"resolve"}, primary : KEY, start_ts : Pos, commit_ts : Nat] ) -TypeInvariant == - /\ NextTsTypeInv - /\ ClientStateTypeInv - /\ ClientTsTypeInv - /\ ClientKeyTypeInv - /\ KeyDataTypeInv - /\ KeyLockTypeInv - /\ KeyWriteTypeInv - /\ KeyLastReadTsTypeInv - /\ MsgTypeInv +TypeOK == + /\ NextTsTypeOK + /\ ClientStateTypeOK + /\ ClientTsTypeOK + /\ ClientKeyTypeOK + /\ KeyDataTypeOK + /\ KeyLockTypeOK + /\ KeyWriteTypeOK + /\ KeyLastReadTsTypeOK + /\ MsgTypeOK ----------------------------------------------------------------------------- @@ -583,7 +579,7 @@ AbortedClientConsistency == CommittedTxnConsistency == \A c \in CLIENT : - client_state[c] # "init" => + client_state[c] /= "init" => LET primary == client_key[c].primary secondary == client_key[c].secondary @@ -614,7 +610,7 @@ UniqueWrite == ----------------------------------------------------------------------------- THEOREM Safety == - PessimisticSpec => [](/\ TypeInvariant + PessimisticSpec => [](/\ TypeOK /\ WriteConsistency /\ LockConsistency /\ CommittedTxnConsistency diff --git a/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test1.launch b/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test1.launch index 2348680..8a37459 100644 --- a/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test1.launch +++ b/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test1.launch @@ -1,31 +1,69 @@ + + + + + - + - - + + + + + + + - - + + + + + + + + + + + + + + + + + + + - + - + + + + + + + + + + + + diff --git a/PessimisticTransaction/Test1.cfg b/PessimisticTransaction/Test1.cfg new file mode 100644 index 0000000..1f1ac6f --- /dev/null +++ b/PessimisticTransaction/Test1.cfg @@ -0,0 +1,33 @@ +\* See Test1.tla. + +CONSTANT + KEY <- Key + OPTIMISTIC_CLIENT <- OptimistiicClient + PESSIMISTIC_CLIENT <- PessimisticClient + CLIENT_KEY <- ClientKey + CLIENT_PRIMARY <- ClientPrimary + +INIT + Init + +NEXT + Next + +INVARIANT + NextTsTypeOK + ClientStateTypeOK + ClientTsTypeOK + ClientKeyTypeOK + KeyDataTypeOK + KeyLockTypeOK + KeyWriteTypeOK + KeyLastReadTsTypeOK + MsgTypeOK + +INVARIANT + WriteConsistency + LockConsistency + CommittedTxnConsistency + AbortedClientConsistency + RollbackConsistency + UniqueWrite diff --git a/PessimisticTransaction/Test1.tla b/PessimisticTransaction/Test1.tla new file mode 100644 index 0000000..01e79f1 --- /dev/null +++ b/PessimisticTransaction/Test1.tla @@ -0,0 +1,19 @@ +--------------------------------- MODULE Test1 --------------------------------- + +EXTENDS PessimisticTransaction, TLC + +\* Model value is not used to avoid unnecessary state space +\* checked by TLC. Symmetry should not be used if we are supposed +\* to check liveness. +k1 == 1 +k2 == 2 +c1 == 1 +c2 == 2 + +Key == {k1, k2} +OptimistiicClient == {} +PessimisticClient == {c1, c2} +ClientKey == c1 :> {k1, k2} @@ c2 :> {k2} +ClientPrimary == c1 :> k1 @@ c2 :> k2 + +================================================================================ From c108f8d2019959619ade36fabb35719a01672358 Mon Sep 17 00:00:00 2001 From: andylokandy Date: Tue, 31 Mar 2020 00:59:51 +0800 Subject: [PATCH 16/20] add pessimistic test2 Signed-off-by: andylokandy --- PessimisticTransaction/Test2.cfg | 33 ++++++++++++++++++++++++++++++++ PessimisticTransaction/Test2.tla | 20 +++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 PessimisticTransaction/Test2.cfg create mode 100644 PessimisticTransaction/Test2.tla diff --git a/PessimisticTransaction/Test2.cfg b/PessimisticTransaction/Test2.cfg new file mode 100644 index 0000000..780fc44 --- /dev/null +++ b/PessimisticTransaction/Test2.cfg @@ -0,0 +1,33 @@ +\* See Test2.tla. + +CONSTANT + KEY <- Key + OPTIMISTIC_CLIENT <- OptimistiicClient + PESSIMISTIC_CLIENT <- PessimisticClient + CLIENT_KEY <- ClientKey + CLIENT_PRIMARY <- ClientPrimary + +INIT + Init + +NEXT + Next + +INVARIANT + NextTsTypeOK + ClientStateTypeOK + ClientTsTypeOK + ClientKeyTypeOK + KeyDataTypeOK + KeyLockTypeOK + KeyWriteTypeOK + KeyLastReadTsTypeOK + MsgTypeOK + +INVARIANT + WriteConsistency + LockConsistency + CommittedTxnConsistency + AbortedClientConsistency + RollbackConsistency + UniqueWrite diff --git a/PessimisticTransaction/Test2.tla b/PessimisticTransaction/Test2.tla new file mode 100644 index 0000000..f781c24 --- /dev/null +++ b/PessimisticTransaction/Test2.tla @@ -0,0 +1,20 @@ +--------------------------------- MODULE Test2 --------------------------------- + +EXTENDS PessimisticTransaction, TLC + +\* Model value is not used to avoid unnecessary state space +\* checked by TLC. Symmetry should not be used if we are supposed +\* to check liveness. +k1 == 1 +k2 == 2 +c1 == 1 +c2 == 2 +c3 == 3 + +Key == {k1, k2} +OptimistiicClient == {c2, c3} +PessimisticClient == {c1} +ClientKey == c1 :> {k1, k2} @@ c2 :> {k1} @@ c3 :> {k2} +ClientPrimary == c1 :> k1 @@ c2 :> k1 @@ c3 :> k2 + +================================================================================ From 84639c04220bf94e6f5a35194ac6577be12d7609 Mon Sep 17 00:00:00 2001 From: andylokandy Date: Wed, 8 Apr 2020 02:36:57 +0800 Subject: [PATCH 17/20] refine distributed transaction Signed-off-by: andylokandy --- .../DistributedTransaction.pdf | Bin 0 -> 217824 bytes .../DistributedTransaction.tla | 596 +++++++++++++++++ .../DistributedTransaction___Test1.launch | 41 ++ .../DistributedTransaction___Test2.launch | 42 ++ .../DistributedTransactionProofs.tla | 47 ++ DistributedTransaction/README.md | 10 + DistributedTransaction/Test1.cfg | 29 + DistributedTransaction/Test1.tla | 12 + DistributedTransaction/Test2.cfg | 30 + DistributedTransaction/Test2.tla | 12 + .../PessimisticTransaction.tla | 621 ------------------ .../PessimisticTransaction___Test1.launch | 69 -- .../PessimisticTransaction___Test2.launch | 31 - PessimisticTransaction/Test1.cfg | 33 - PessimisticTransaction/Test1.tla | 19 - PessimisticTransaction/Test2.cfg | 33 - PessimisticTransaction/Test2.tla | 20 - 17 files changed, 819 insertions(+), 826 deletions(-) create mode 100644 DistributedTransaction/DistributedTransaction.pdf create mode 100644 DistributedTransaction/DistributedTransaction.tla create mode 100644 DistributedTransaction/DistributedTransaction.toolbox/DistributedTransaction___Test1.launch create mode 100644 DistributedTransaction/DistributedTransaction.toolbox/DistributedTransaction___Test2.launch create mode 100644 DistributedTransaction/DistributedTransactionProofs.tla create mode 100644 DistributedTransaction/README.md create mode 100644 DistributedTransaction/Test1.cfg create mode 100644 DistributedTransaction/Test1.tla create mode 100644 DistributedTransaction/Test2.cfg create mode 100644 DistributedTransaction/Test2.tla delete mode 100644 PessimisticTransaction/PessimisticTransaction.tla delete mode 100644 PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test1.launch delete mode 100644 PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test2.launch delete mode 100644 PessimisticTransaction/Test1.cfg delete mode 100644 PessimisticTransaction/Test1.tla delete mode 100644 PessimisticTransaction/Test2.cfg delete mode 100644 PessimisticTransaction/Test2.tla diff --git a/DistributedTransaction/DistributedTransaction.pdf b/DistributedTransaction/DistributedTransaction.pdf new file mode 100644 index 0000000000000000000000000000000000000000..8250c1bbd785e366eab5830f4a7aaec91b03e18c GIT binary patch literal 217824 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%0Apz{A@t%xNayRVzt$U?xgpFEgN*aqlwupRo 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.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

TmufE!<0jrzAvIVJx{$JE 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 literal 0 HcmV?d00001 diff --git a/DistributedTransaction/DistributedTransaction.tla b/DistributedTransaction/DistributedTransaction.tla new file mode 100644 index 0000000..647b293 --- /dev/null +++ b/DistributedTransaction/DistributedTransaction.tla @@ -0,0 +1,596 @@ +----------------------- MODULE DistributedTransaction ----------------------- +EXTENDS Integers + +\* The set of all keys. +CONSTANTS KEY + +\* The sets of optiimistic clients and pessimistic clients. +CONSTANTS OPTIMISTIC_CLIENT, PESSIMISTIC_CLIENT +CLIENT == PESSIMISTIC_CLIENT \union OPTIMISTIC_CLIENT + +\* CLIENT_KEY is a set of [Client -> SUBSET KEY] +\* representing the involved keys of each client. +CONSTANTS CLIENT_KEY +ASSUME \A c \in CLIENT: CLIENT_KEY[c] \subseteq KEY + +\* CLIENT_PRIMARY is the primary key of each client. +CONSTANTS CLIENT_PRIMARY +ASSUME \A c \in CLIENT: CLIENT_PRIMARY[c] \in CLIENT_KEY[c] + +\* Timestamp of transactions. +Ts == Nat \ {0} +NoneTs == 0 + +\* The algorithm is easier to understand in terms of the set of msgs of +\* all messages that have ever been sent. A more accurate model would use +\* one or more variables to represent the messages actually in transit, +\* and it would include actions representing message loss and duplication +\* as well as message receipt. +\* +\* In the current spec, there is no need to model message loss because we +\* are mainly concerned with the algorithm's safety property. The safety +\* part of the spec says only what messages may be received and does not +\* assert that any message actually is received. Thus, there is no +\* difference between a lost message and one that is never received. +VARIABLES req_msgs +VARIABLES resp_msgs + +\* key_data[k] is the set of multi-version data of the key. Since we +\* don't care about the concrete value of data, a strat_ts is sufficient +\* to represent one data version. +VARIABLES key_data +\* key_lock[k] is the set of lock (zero or one element). A lock is of a +\* record of [ts: start_ts, primary: key, type: lock_type]. If primary +\* equals to k, it is a primary lock, otherwise secondary lock. lock_type +\* is one of {"prewrite_optimistic", "prewrite_pessimistic", "lock_key"}. +\* lock_key denotes the pessimistic lock performed by ServerLockKey +\* action, the prewrite_pessimistic denotes percolator optimistic lock +\* who is transformed from a lock_key lock by action +\* ServerPrewritePessimistic, and prewrite_optimistic denotes the +\* classic optimistic lock. +\* +\* In TiKV, key_lock has an additional for_update_ts field and the +\* LockType is of four variants: +\* {"PUT", "DELETE", "LOCK", "PESSIMISTIC"}. +\* +\* In the spec, we abstract them by: +\* (1) LockType \in {"PUT", "DELETE", "LOCK"} /\ for_update_ts = 0 <=> +\* type = "prewrite_optimistic" +\* (2) LockType \in {"PUT", "DELETE"} /\ for_update_ts > 0 <=> +\* type = "prewrite_pessimistic" +\* (3) LockType = "PESSIMISTIC" <=> type = "lock_key" +VARIABLES key_lock +\* key_write[k] is a sequence of commit or rollback record of the key. +\* It's a record of [ts, start_ts, type, [protected]]. type can be eihter +\* "write" or "rollback". ts represents the commit_ts of "write" record. +\* Otherwise, ts equals to start_ts on "rollback" record. "rollback" +\* record has an additional protected field. protected signifies the +\* rollback record would not be collapsed. +VARIABLES key_write + +\* client_state[c] indicates the current transaction stage of client c. +VARIABLES client_state +\* client_ts[c] is a record of [start_ts, commit_ts, for_update_ts]. +\* Fields are all initialized to NoneTs. +VARIABLES client_ts +\* client_key[c] is a record of [locking: {key}, prewriting: {key}]. +\* Hereby, "locking" denotes the keys whose pessimistic locks +\* haven't been acquired, "prewriting" denotes the keys that are pending +\* for prewrite. +VARIABLES client_key + +\* next_ts is a globally monotonically increasing integer, respresenting +\* the virtual clock of transactions. In practice, the variable is +\* maintained by PD, the time oracle of a cluster. +VARIABLES next_ts + +msg_vars == <<req_msgs, resp_msgs>> +client_vars == <<client_state, client_ts, client_key>> +key_vars == <<key_data, key_lock, key_write>> +vars == <<msg_vars, client_vars, key_vars, next_ts>> + +SendReqs(msgs) == req_msgs' = req_msgs \union msgs +SendResp(msg) == resp_msgs' = resp_msgs \union {msg} +----------------------------------------------------------------------------- +\* Type Definitions + +ReqMessages == + [start_ts : Ts, primary : KEY, type : {"lock_key"}, key : KEY, + for_update_ts : Ts] + \union [start_ts : Ts, primary : KEY, type : {"prewrite_optimistic"}, key : KEY] + \union [start_ts : Ts, primary : KEY, type : {"prewrite_pessimistic"}, key : KEY] + \union [start_ts : Ts, primary : KEY, type : {"commit"}, commit_ts : Ts] + \union [start_ts : Ts, primary : KEY, type : {"cleanup"}] + \union [start_ts : Ts, primary : KEY, type : {"resolve_rollbacked"}] + \union [start_ts : Ts, primary : KEY, type : {"resolve_committed"}, commit_ts : Ts] + +RespMessages == + [start_ts : Ts, type : {"prewrited", "locked_key"}, key : KEY] + \union [start_ts : Ts, type : {"lock_failed"}, key : KEY, latest_commit_ts : Ts] + \union [start_ts : Ts, type : {"committed", + "commit_aborted", + "prewrite_aborted", + "lock_key_aborted"}] + +TypeOK == /\ req_msgs \in SUBSET ReqMessages + /\ resp_msgs \in SUBSET RespMessages + /\ key_data \in [KEY -> SUBSET Ts] + /\ key_lock \in [KEY -> SUBSET [ts : Ts, + primary : KEY, + type : {"prewrite_optimistic", + "prewrite_pessimistic", + "lock_key"}]] + /\ \A k \in KEY : + \* At most one lock in key_lock[k] + \A l, l2 \in key_lock[k] : + l = l2 + /\ key_write \in [KEY -> SUBSET ( + [ts : Ts, start_ts : Ts, type : {"write"}] + \union [ts : Ts, start_ts : Ts, type : {"rollback"}, protected : BOOLEAN])] + /\ client_state \in [CLIENT -> {"init", "locking", "prewriting", "committing"}] + /\ client_ts \in [CLIENT -> [start_ts : Ts \union {NoneTs}, + commit_ts : Ts \union {NoneTs}, + for_update_ts : Ts \union {NoneTs}]] + /\ client_key \in [CLIENT -> [locking: SUBSET KEY, prewriting : SUBSET KEY]] + /\ next_ts \in Ts +----------------------------------------------------------------------------- +\* Client Actions + +ClientLockKey(c) == + /\ client_state[c] = "init" + /\ client_state' = [client_state EXCEPT ![c] = "locking"] + /\ client_ts' = [client_ts EXCEPT ![c].start_ts = next_ts, ![c].for_update_ts = next_ts] + /\ next_ts' = next_ts + 1 + \* Assume we need to acquire pessimistic locks for all keys + /\ client_key' = [client_key EXCEPT ![c].locking = CLIENT_KEY[c]] + /\ SendReqs({[type |-> "lock_key", + start_ts |-> client_ts'[c].start_ts, + primary |-> CLIENT_PRIMARY[c], + key |-> k, + for_update_ts |-> client_ts'[c].for_update_ts] : k \in CLIENT_KEY[c]}) + /\ UNCHANGED <<resp_msgs, key_vars>> + +ClientLockedKey(c) == + /\ client_state[c] = "locking" + /\ \E resp \in resp_msgs : + /\ resp.type = "locked_key" + /\ resp.start_ts = client_ts[c].start_ts + /\ resp.key \in client_key[c].locking + /\ client_key' = [client_key EXCEPT ![c].locking = @ \ {resp.key}] + /\ UNCHANGED <<msg_vars, key_vars, client_ts, client_state, next_ts>> + +ClientRetryLockKey(c) == + /\ client_state[c] = "locking" + /\ \E resp \in resp_msgs : + /\ resp.type = "lock_failed" + /\ resp.start_ts = client_ts[c].start_ts + /\ resp.latest_commit_ts > client_ts[c].for_update_ts + /\ client_ts' = [client_ts EXCEPT ![c].for_update_ts = resp.latest_commit_ts] + /\ SendReqs({[type |-> "lock_key", + start_ts |-> client_ts'[c].start_ts, + primary |-> CLIENT_PRIMARY[c], + key |-> resp.key, + for_update_ts |-> client_ts'[c].for_update_ts]}) + /\ UNCHANGED <<resp_msgs, key_vars, client_state, client_key, next_ts>> + +ClientPrewritePessimistic(c) == + /\ client_state[c] = "locking" + /\ client_key[c].locking = {} + /\ client_state' = [client_state EXCEPT ![c] = "prewriting"] + /\ client_key' = [client_key EXCEPT ![c].prewriting = CLIENT_KEY[c]] + /\ SendReqs({[type |-> "prewrite_pessimistic", + start_ts |-> client_ts[c].start_ts, + primary |-> CLIENT_PRIMARY[c], + key |-> k] : k \in CLIENT_KEY[c]}) + /\ UNCHANGED <<resp_msgs, key_vars, client_ts, next_ts>> + +ClientPrewriteOptimisistic(c) == + /\ client_state[c] = "init" + /\ client_state' = [client_state EXCEPT ![c] = "prewriting"] + /\ client_ts' = [client_ts EXCEPT ![c].start_ts = next_ts] + /\ next_ts' = next_ts + 1 + /\ client_key' = [client_key EXCEPT ![c].prewriting = CLIENT_KEY[c]] + /\ SendReqs({[type |-> "prewrite_optimistic", + start_ts |-> client_ts'[c].start_ts, + primary |-> CLIENT_PRIMARY[c], + key |-> k] : k \in CLIENT_KEY[c]}) + /\ UNCHANGED <<resp_msgs, key_vars>> + +ClientPrewrited(c) == + /\ client_state[c] = "prewriting" + /\ client_key[c].locking = {} + /\ \E resp \in resp_msgs : + /\ resp.type = "prewrited" + /\ resp.start_ts = client_ts[c].start_ts + /\ resp.key \in client_key[c].prewriting + /\ client_key' = [client_key EXCEPT ![c].prewriting = @ \ {resp.key}] + /\ UNCHANGED <<msg_vars, key_vars, client_ts, client_state, next_ts>> + +ClientCommit(c) == + /\ client_state[c] = "prewriting" + /\ client_key[c].prewriting = {} + /\ client_state' = [client_state EXCEPT ![c] = "committing"] + /\ client_ts' = [client_ts EXCEPT ![c].commit_ts = next_ts] + /\ next_ts' = next_ts + 1 + /\ SendReqs({[type |-> "commit", + start_ts |-> client_ts'[c].start_ts, + primary |-> CLIENT_PRIMARY[c], + commit_ts |-> client_ts'[c].commit_ts]}) + /\ UNCHANGED <<resp_msgs, key_vars, client_key>> +----------------------------------------------------------------------------- +\* Server Actions + +\* Write the write column and unlock the lock iff the lock exists. +commit(pk, start_ts, commit_ts) == + \E l \in key_lock[pk] : + /\ l.ts = start_ts + /\ key_lock' = [key_lock EXCEPT ![pk] = {}] + /\ key_write' = [key_write EXCEPT ![pk] = @ \union {[ts |-> commit_ts, + type |-> "write", + start_ts |-> start_ts]}] + +\* Rollback the transaction that starts at start_ts on key k. +rollback(k, start_ts) == + LET + \* Rollback record on the primary key of a pessimistic transaction + \* needs to be protected from being collapsed. If we can't decide + \* whether it suffices that because the lock is missing or mismatched, + \* it should also be protected. + protected == \/ \E l \in key_lock[k] : + /\ l.ts = start_ts + /\ l.primary = k + /\ l.type \in {"lock_key", "prewrite_pessimistic"} + \/ \E l \in key_lock[k] : l.ts /= start_ts + \/ key_lock[k] = {} + IN + \* If a lock exists and has the same ts, unlock it. + /\ IF \E l \in key_lock[k] : l.ts = start_ts + THEN key_lock' = [key_lock EXCEPT ![k] = {}] + ELSE UNCHANGED key_lock + /\ key_data' = [key_data EXCEPT ![k] = @ \ {start_ts}] + /\ IF + /\ ~ \E w \in key_write[k]: w.ts = start_ts + THEN + key_write' = [key_write EXCEPT + ![k] = + \* collapse rollback + (@ \ {w \in @ : w.type = "rollback" /\ ~w.protected /\ w.ts < start_ts}) + \* write rollback record + \union {[ts |-> start_ts, + start_ts |-> start_ts, + type |-> "rollback", + protected |-> protected]}] + ELSE + UNCHANGED <<key_write>> + +ServerLockKey == + \E req \in req_msgs : + /\ req.type = "lock_key" + /\ LET + k == req.key + start_ts == req.start_ts + IN + \* Pessimistic lock is allowed only if no stale lock exists. If + \* there is one, wait until ServerCleanupStaleLock to clean it up. + /\ key_lock[k] = {} + /\ LET + latest_write == {w \in key_write[k] : \A w2 \in key_write[k] : w.ts >= w2.ts} + + all_commits == {w \in key_write[k] : w.type = "write"} + latest_commit == {w \in all_commits : \A w2 \in all_commits : w.ts >= w2.ts} + IN + IF \E w \in key_write[k] : w.start_ts = start_ts /\ w.type = "rollback" + THEN + \* If corresponding rollback record is found, which + \* indicates that the transcation is rollbacked, abort the + \* transaction. + /\ SendResp([start_ts |-> start_ts, type |-> "lock_key_aborted"]) + /\ UNCHANGED <<req_msgs, client_vars, key_vars, next_ts>> + ELSE + \* Acquire pessimistic lock only if for_update_ts of req + \* is greater or equal to the latest "write" record. + \* Because if the latest record is "write", it means that + \* a new version is committed after for_update_ts, which + \* violates Read Committed guarantee. + \/ /\ ~ \E w \in latest_commit : w.ts > req.for_update_ts + /\ key_lock' = [key_lock EXCEPT ![k] = {[ts |-> start_ts, + primary |-> req.primary, + type |-> "lock_key"]}] + /\ SendResp([start_ts |-> start_ts, type |-> "locked_key", key |-> k]) + /\ UNCHANGED <<req_msgs, client_vars, key_data, key_write, next_ts>> + \* Otherwise, reject the request and let client to retry + \* with new for_update_ts. + \/ \E w \in latest_commit : + /\ w.ts > req.for_update_ts + /\ SendResp([start_ts |-> start_ts, + type |-> "lock_failed", + key |-> k, + latest_commit_ts |-> w.ts]) + /\ UNCHANGED <<req_msgs, client_vars, key_vars, next_ts>> + +ServerPrewritePessimistic == + \E req \in req_msgs : + /\ req.type = "prewrite_pessimistic" + /\ LET + k == req.key + start_ts == req.start_ts + IN + \* Pessimistic prewrite is allowed only if pressimistic lock is + \* acquired, otherwise abort the transaction. + /\ IF \E l \in key_lock[k] : l.ts = start_ts + THEN + /\ key_lock' = [key_lock EXCEPT ![k] = {[ts |-> start_ts, + primary |-> req.primary, + type |-> "prewrite_pessimistic"]}] + /\ key_data' = [key_data EXCEPT ![k] = @ \union {start_ts}] + /\ SendResp([start_ts |-> start_ts, type |-> "prewrited", key |-> k]) + /\ UNCHANGED <<req_msgs, client_vars, key_write, next_ts>> + ELSE + /\ SendResp([start_ts |-> start_ts, type |-> "prewrite_aborted"]) + /\ UNCHANGED <<req_msgs, client_vars, key_vars, next_ts>> + +ServerPrewriteOptimistic == + \E req \in req_msgs : + /\ req.type = "prewrite_optimistic" + /\ LET + k == req.key + start_ts == req.start_ts + IN + /\ IF \E w \in key_write[k] : w.ts >= start_ts + THEN + /\ SendResp([start_ts |-> start_ts, type |-> "prewrite_aborted"]) + /\ UNCHANGED <<req_msgs, client_vars, key_vars, next_ts>> + ELSE + \* Optimistic prewrite is allowed only if no stale lock exists. If + \* there is one, wait until ServerCleanupStaleLock to clean it up. + /\ \/ key_lock[k] = {} + \/ \E l \in key_lock[k] : l.ts = start_ts + /\ key_lock' = [key_lock EXCEPT ![k] = {[ts |-> start_ts, + primary |-> req.primary, + type |-> "prewrite_optimistic"]}] + /\ key_data' = [key_data EXCEPT ![k] = @ \union {start_ts}] + /\ SendResp([start_ts |-> start_ts, type |-> "prewrited", key |-> k]) + /\ UNCHANGED <<req_msgs, client_vars, key_write, next_ts>> + +ServerCommit == + \E req \in req_msgs : + /\ req.type = "commit" + /\ LET + pk == req.primary + start_ts == req.start_ts + IN + IF \E w \in key_write[pk] : w.start_ts = start_ts /\ w.type = "write" + THEN + \* Key has already been committed. Do nothing. + /\ SendResp([start_ts |-> start_ts, type |-> "committed"]) + /\ UNCHANGED <<req_msgs, client_vars, key_vars, next_ts>> + ELSE + IF \E l \in key_lock[pk] : l.ts = start_ts + THEN + \* Commit the key only if the prewrite lock exists. + /\ commit(pk, start_ts, req.commit_ts) + /\ SendResp([start_ts |-> start_ts, type |-> "committed"]) + /\ UNCHANGED <<req_msgs, client_vars, key_data, next_ts>> + ELSE + \* Otherwise, abort the transaction. + /\ SendResp([start_ts |-> start_ts, type |-> "commit_aborted"]) + /\ UNCHANGED <<req_msgs, client_vars, key_vars, next_ts>> + +\* In the spec, the primary key with a lock may clean up itself +\* spontaneously. There is no need to model a client to request clean up +\* because there is no difference between a optimistic client trying to +\* read a key that has lock timeouted and the key trying to unlock itself. +ServerCleanupStaleLock == + \E k \in KEY : + \E l \in key_lock[k] : + /\ SendReqs({[type |-> "cleanup", + start_ts |-> l.ts, + primary |-> l.primary]}) + /\ UNCHANGED <<resp_msgs, client_vars, key_vars, next_ts>> + +\* Clean up stale locks by checking the status of the primary key. Commmit +\* the secondary keys if primary key is committed; otherwise rollback the +\* transaction by rolling-back the primary key, and then also rollback the +\* secondarys. +ServerCleanup == + \E req \in req_msgs : + /\ req.type = "cleanup" + /\ LET + pk == req.primary + start_ts == req.start_ts + committed == {w \in key_write[pk] : w.start_ts = start_ts /\ w.type = "write"} + IN + IF committed /= {} + THEN + /\ SendReqs({[type |-> "resolve_committed", + start_ts |-> start_ts, + primary |-> pk, + commit_ts |-> w.ts] : w \in committed}) + /\ UNCHANGED <<resp_msgs, client_vars, key_vars, next_ts>> + ELSE + /\ rollback(pk, start_ts) + /\ SendReqs({[type |-> "resolve_rollbacked", + start_ts |-> start_ts, + primary |-> pk]}) + /\ UNCHANGED <<resp_msgs, client_vars, next_ts>> + +ServerResolveCommitted == + \E req \in req_msgs : + /\ req.type = "resolve_committed" + /\ LET + start_ts == req.start_ts + IN + \E k \in KEY: + \E l \in key_lock[k] : + /\ l.primary = req.primary + /\ l.ts = start_ts + /\ commit(k, start_ts, req.commit_ts) + /\ UNCHANGED <<msg_vars, client_vars, key_data, next_ts>> + +ServerResolveRollbacked == + \E req \in req_msgs : + /\ req.type = "resolve_rollbacked" + /\ LET + start_ts == req.start_ts + IN + \E k \in KEY: + \E l \in key_lock[k] : + /\ l.primary = req.primary + /\ l.ts = start_ts + /\ rollback(k, start_ts) + /\ UNCHANGED <<msg_vars, client_vars, next_ts>> +----------------------------------------------------------------------------- +\* Specification + +Init == + /\ next_ts = 1 + /\ req_msgs = {} + /\ resp_msgs = {} + /\ client_state = [c \in CLIENT |-> "init"] + /\ client_key = [c \in CLIENT |-> [locking |-> {}, prewriting |-> {}]] + /\ client_ts = [c \in CLIENT |-> [start_ts |-> NoneTs, + commit_ts |-> NoneTs, + for_update_ts |-> NoneTs]] + /\ key_lock = [k \in KEY |-> {}] + /\ key_data = [k \in KEY |-> {}] + /\ key_write = [k \in KEY |-> {}] + +Next == + \/ \E c \in OPTIMISTIC_CLIENT : + \/ ClientPrewriteOptimisistic(c) + \/ ClientPrewrited(c) + \/ ClientCommit(c) + \/ \E c \in PESSIMISTIC_CLIENT : + \/ ClientLockKey(c) + \/ ClientLockedKey(c) + \/ ClientRetryLockKey(c) + \/ ClientPrewritePessimistic(c) + \/ ClientPrewrited(c) + \/ ClientCommit(c) + \/ ServerLockKey + \/ ServerPrewritePessimistic + \/ ServerPrewriteOptimistic + \/ ServerCommit + \/ ServerCleanupStaleLock + \/ ServerCleanup + \/ ServerResolveCommitted + \/ ServerResolveRollbacked + +Spec == Init /\ [][Next]_vars +----------------------------------------------------------------------------- +\* Consistency Invariants + +\* Check whether there is a "write" record in key_write[k] corresponding +\* to start_ts. +keyCommitted(k, start_ts) == + \E w \in key_write[k] : + /\ w.start_ts = start_ts + /\ w.type = "write" + +\* A transaction can't be both committed and aborted. +UniqueCommitOrAbort == + \A resp, resp2 \in resp_msgs : + (resp.type = "committed") /\ (resp2.type = "commit_aborted") => + resp.start_ts /= resp2.start_ts + +\* If a transaction is committed, the primary key must be committed and +\* the secondary keys of the same transaction must be either committed +\* or locked. +CommitConsistency == + \A resp \in resp_msgs : + (resp.type = "committed") => + \E c \in CLIENT : + /\ client_ts[c].start_ts = resp.start_ts + \* Primary key must be committed + /\ keyCommitted(CLIENT_PRIMARY[c], resp.start_ts) + \* Secondary key must be either committed or locked by the + \* start_ts of the transaction. + /\ \A k \in CLIENT_KEY[c] : + (~ \E l \in key_lock[k] : l.ts = resp.start_ts) = + keyCommitted(k, resp.start_ts) + +\* If a transaction is aborted, all key of that transaction must be not +\* committed. +AbortConsistency == + \A resp \in resp_msgs : + (resp.type = "commit_aborted") => + \A c \in CLIENT : + (client_ts[c].start_ts = resp.start_ts) => + ~ keyCommitted(CLIENT_PRIMARY[c], resp.start_ts) + +\* For each write, the commit_ts should be strictly greater than the +\* start_ts and have data written into key_data[k]. For each rollback, +\* the commit_ts should equals to the start_ts. +WriteConsistency == + \A k \in KEY : + \A w \in key_write[k] : + \/ /\ w.type = "write" + /\ w.ts > w.start_ts + /\ w.start_ts \in key_data[k] + \/ /\ w.type = "rollback" + /\ w.ts = w.start_ts + +\* When the lock exists, there can't be a corresponding commit record, +\* vice versa. +UniqueLockOrWrite == + \A k \in KEY : + \A l \in key_lock[k] : + \A w \in key_write[k] : + w.start_ts /= l.ts + +\* For each key, ecah record in write column should have a unique start_ts. +UniqueWrite == + \A k \in KEY : + \A w, w2 \in key_write[k] : + (w.start_ts = w2.start_ts) => (w = w2) +----------------------------------------------------------------------------- +\* Snapshot Isolation + +\* Asserts that next_ts is monotonically increasing. +NextTsMonotonicity == + \A ts \in Ts : + (ts <= next_ts) => [](ts <= next_ts) + +\* Asserts that no msg would be deleted once sent. +MsgMonotonicity == + /\ \A req \in ReqMessages : + req \in req_msgs => [](req \in req_msgs) + /\ \A resp \in RespMessages : + resp \in resp_msgs => [](resp \in resp_msgs) + +\* Asserts that all messages sent should have ts less than next_ts. +MsgTsConsistency == + /\ \A req \in req_msgs : + /\ req.start_ts <= next_ts + /\ req.type \in {"commit", "resolve_committed"} => + req.commit_ts <= next_ts + /\ \A resp \in resp_msgs : resp.start_ts <= next_ts + +\* SnapshotIsolation is implied from the four assumptions (but is not +\* nessesary), because SnapshotIsolation means that: +\* (1) Once a transcation is committed, all keys of the transaction should +\* be always readable or have lock on secondary keys(eventually readable). +\* PROOF BY CommitConsistency, MsgConsistency +\* (2) For a given transaction, all transaction that commits after that +\* transaction should have greater commit_ts than the next_ts at the +\* time that the given transaction commits. So as to be able to +\* distinguish the transactions that commits before and after +\* from all transactions that preserved by (1). +\* PROOF BY NextTsConsistency, MsgTsConsistency +\* (3) All aborted transactions would be always not readable. +\* PROOF BY AbortConsistency, MsgConsistency +SnapshotIsolation == /\ CommitConsistency + /\ AbortConsistency + /\ NextTsMonotonicity + /\ MsgMonotonicity +----------------------------------------------------------------------------- +THEOREM Safety == + Spec => [](/\ TypeOK + /\ UniqueCommitOrAbort + /\ CommitConsistency + /\ AbortConsistency + /\ WriteConsistency + /\ UniqueLockOrWrite + /\ UniqueWrite + /\ SnapshotIsolation) +============================================================================= diff --git a/DistributedTransaction/DistributedTransaction.toolbox/DistributedTransaction___Test1.launch b/DistributedTransaction/DistributedTransaction.toolbox/DistributedTransaction___Test1.launch new file mode 100644 index 0000000..acb55a8 --- /dev/null +++ b/DistributedTransaction/DistributedTransaction.toolbox/DistributedTransaction___Test1.launch @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<launchConfiguration type="org.lamport.tla.toolbox.tool.tlc.modelCheck"> + <stringAttribute key="configurationName" value="Test1"/> + <intAttribute key="distributedFPSetCount" value="0"/> + <stringAttribute key="distributedNetworkInterface" value="10.0.75.1"/> + <intAttribute key="distributedNodesCount" value="1"/> + <stringAttribute key="distributedTLC" value="off"/> + <intAttribute key="fpIndex" value="8"/> + <intAttribute key="maxHeapSize" value="25"/> + <stringAttribute key="modelBehaviorInit" value=""/> + <stringAttribute key="modelBehaviorNext" value=""/> + <stringAttribute key="modelBehaviorSpec" value="Spec"/> + <intAttribute key="modelBehaviorSpecType" value="1"/> + <stringAttribute key="modelBehaviorVars" value="key_data, key_lock, next_ts, req_msgs, client_state, client_key, client_ts, resp_msgs, key_write"/> + <stringAttribute key="modelComments" value=""/> + <booleanAttribute key="modelCorrectnessCheckDeadlock" value="false"/> + <listAttribute key="modelCorrectnessInvariants"> + <listEntry value="1TypeOK"/> + <listEntry value="1UniqueCommitOrAbort"/> + <listEntry value="1CommitConsistency"/> + <listEntry value="1AbortConsistency"/> + <listEntry value="1WriteConsistency"/> + <listEntry value="1UniqueLockOrWrite"/> + <listEntry value="1UniqueWrite"/> + <listEntry value="1MsgTsConsistency"/> + </listAttribute> + <listAttribute key="modelCorrectnessProperties"/> + <intAttribute key="modelEditorOpenTabs" value="8"/> + <listAttribute key="modelParameterConstants"> + <listEntry value="KEY;;{k1, k2};1;0"/> + <listEntry value="CLIENT_KEY;;c1 :&gt; {k1, k2} @@ c2 :&gt; {k1} @@ c3 :&gt; {k1, k2};0;0"/> + <listEntry value="PESSIMISTIC_CLIENT;;{c1, c2};1;0"/> + <listEntry value="OPTIMISTIC_CLIENT;;{c3};1;0"/> + <listEntry value="CLIENT_PRIMARY;;c1 :&gt; k1 @@ c2 :&gt; k1 @@ c3 :&gt; k2;0;0"/> + </listAttribute> + <intAttribute key="modelVersion" value="20191005"/> + <intAttribute key="numberOfWorkers" value="6"/> + <stringAttribute key="result.mail.address" value=""/> + <stringAttribute key="specName" value="DistributedTransaction"/> + <stringAttribute key="tlcResourcesProfile" value="local custom"/> +</launchConfiguration> diff --git a/DistributedTransaction/DistributedTransaction.toolbox/DistributedTransaction___Test2.launch b/DistributedTransaction/DistributedTransaction.toolbox/DistributedTransaction___Test2.launch new file mode 100644 index 0000000..3e2e636 --- /dev/null +++ b/DistributedTransaction/DistributedTransaction.toolbox/DistributedTransaction___Test2.launch @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<launchConfiguration type="org.lamport.tla.toolbox.tool.tlc.modelCheck"> + <stringAttribute key="configurationName" value="Test2"/> + <intAttribute key="distributedFPSetCount" value="0"/> + <stringAttribute key="distributedNetworkInterface" value="10.0.75.1"/> + <intAttribute key="distributedNodesCount" value="1"/> + <stringAttribute key="distributedTLC" value="off"/> + <intAttribute key="fpIndex" value="64"/> + <intAttribute key="maxHeapSize" value="25"/> + <stringAttribute key="modelBehaviorInit" value=""/> + <stringAttribute key="modelBehaviorNext" value=""/> + <stringAttribute key="modelBehaviorSpec" value="Spec"/> + <intAttribute key="modelBehaviorSpecType" value="1"/> + <stringAttribute key="modelBehaviorVars" value="key_data, key_lock, next_ts, req_msgs, client_state, client_key, client_ts, resp_msgs, key_write"/> + <stringAttribute key="modelComments" value=""/> + <booleanAttribute key="modelCorrectnessCheckDeadlock" value="false"/> + <listAttribute key="modelCorrectnessInvariants"> + <listEntry value="1TypeOK"/> + <listEntry value="1UniqueCommitOrAbort"/> + <listEntry value="1CommitConsistency"/> + <listEntry value="1AbortConsistency"/> + <listEntry value="1WriteConsistency"/> + <listEntry value="1UniqueLockOrWrite"/> + <listEntry value="1UniqueWrite"/> + <listEntry value="1MsgTsConsistency"/> + </listAttribute> + <listAttribute key="modelCorrectnessProperties"/> + <intAttribute key="modelEditorOpenTabs" value="8"/> + <stringAttribute key="modelExpressionEval" value=""/> + <listAttribute key="modelParameterConstants"> + <listEntry value="KEY;;{k1, k2, k3};1;0"/> + <listEntry value="CLIENT_KEY;;c1 :&gt; {k1, k2, k3} @@ c2 :&gt; {k1, k2} @@ c3 :&gt; {k1, k3};0;0"/> + <listEntry value="PESSIMISTIC_CLIENT;;{c1, c2};1;0"/> + <listEntry value="OPTIMISTIC_CLIENT;;{c3};1;0"/> + <listEntry value="CLIENT_PRIMARY;;c1 :&gt; k1 @@ c2 :&gt; k1 @@ c3 :&gt; k3;0;0"/> + </listAttribute> + <intAttribute key="modelVersion" value="20191005"/> + <intAttribute key="numberOfWorkers" value="6"/> + <stringAttribute key="result.mail.address" value=""/> + <stringAttribute key="specName" value="DistributedTransaction"/> + <stringAttribute key="tlcResourcesProfile" value="local custom"/> +</launchConfiguration> diff --git a/DistributedTransaction/DistributedTransactionProofs.tla b/DistributedTransaction/DistributedTransactionProofs.tla new file mode 100644 index 0000000..0a1b1af --- /dev/null +++ b/DistributedTransaction/DistributedTransactionProofs.tla @@ -0,0 +1,47 @@ +--------------------- MODULE DistributedTransactionProofs ------------------- +EXTENDS DistributedTransaction, TLAPS + +THEOREM SpecTypeOK == Spec => TypeOK + PROOF OMITTED + +LEMMA NextInv == + Next => + /\ next_ts' = next_ts + 1 \/ UNCHANGED next_ts + /\ \E reqs : SendReqs(reqs) \/ UNCHANGED req_msgs + /\ \E resp : SendResp(resp) \/ UNCHANGED resp_msgs + BY DEF Next, vars, msg_vars, + ClientPrewriteOptimisistic, ClientPrewrited, ClientCommit, ClientLockKey, + ClientLockedKey, ClientRetryLockKey, ClientPrewritePessimistic, + ClientPrewrited, ClientCommit, ServerLockKey, ServerPrewritePessimistic, + ServerPrewriteOptimistic, ServerCommit, ServerCleanupStaleLock, + ServerCleanup, ServerResolveCommitted, ServerResolveRollbacked + +LEMMA SpecNextTsMonotonicity == Spec => NextTsMonotonicity + <1> SUFFICES ASSUME NEW ts \in Ts, TypeOK + PROVE (ts <= next_ts) /\ [][Next]_vars => [](ts <= next_ts) + BY SpecTypeOK DEF NextTsMonotonicity, Spec + <1>2. (ts <= next_ts) /\ [Next]_vars => (ts <= next_ts)' + BY NextInv DEF TypeOK, Ts, vars + <1>3. QED + BY <1>2, PTL + +LEMMA SpecMsgMonotonicity == Spec => MsgMonotonicity + <1>1. ASSUME NEW req \in ReqMessages + PROVE req \in req_msgs /\ [][Next]_vars => [](req \in req_msgs) + <2>1. req \in req_msgs /\ (\E reqs : SendReqs(reqs)) => (req \in req_msgs)' + BY DEF SendReqs + <2>2. req \in req_msgs /\ [Next]_vars => (req \in req_msgs)' + BY <2>1, NextInv DEF vars, msg_vars + <2>3. QED + BY <2>2, PTL + <1>2. ASSUME NEW resp \in RespMessages + PROVE resp \in resp_msgs /\ [][Next]_vars => [](resp \in resp_msgs) + <3>1. resp \in resp_msgs /\ (\E resp2 : SendResp(resp2)) => (resp \in resp_msgs)' + BY DEF SendResp + <3>2. resp \in resp_msgs /\ [Next]_vars => (resp \in resp_msgs)' + BY <3>1, NextInv DEF vars, msg_vars + <3>3. QED + BY <3>2, PTL + <1>3. QED + BY <1>1, <1>2, SpecTypeOK DEF MsgMonotonicity, Spec +============================================================================= diff --git a/DistributedTransaction/README.md b/DistributedTransaction/README.md new file mode 100644 index 0000000..2a1c680 --- /dev/null +++ b/DistributedTransaction/README.md @@ -0,0 +1,10 @@ +# TLA+ for Distributed Transaction + +The module contains a abstract specification of the transaction system implemented in TiKV. The implementation can be found in [TiKV Transaction Module](https://github.com/tikv/tikv/blob/master/src/storage/mvcc/txn.rs). + +The module contains two TLA+ files: `DistributedTransaction.tla` and `DistributedTransactionProofs.tla`. + +In most cases you will only have an interest in `DistributedTransaction.tla`, where the whole specification and safety invariants are defined. Besides that, in `DistributedTransactionProofs.tla`, there are some proofs, which are supposed to be build up gradually, to the safety invariants. + +To run formal proofs in `DistributedTransactionProofs.tla`, you'd better install [TLAPS](https://tla.msr-inria.inria.fr/tlaps/content/Download/Binaries.html) (TLA+ Proof System) first. It's not distributed altogether with the TLA toolbox. + diff --git a/DistributedTransaction/Test1.cfg b/DistributedTransaction/Test1.cfg new file mode 100644 index 0000000..93f0ac2 --- /dev/null +++ b/DistributedTransaction/Test1.cfg @@ -0,0 +1,29 @@ +CONSTANT + k1 = k1 + k2 = k2 + c1 = c1 + c2 = c2 + c3 = c3 + +CONSTANT + KEY <- Key + OPTIMISTIC_CLIENT <- OptimistiicClient + PESSIMISTIC_CLIENT <- PessimisticClient + CLIENT_KEY <- ClientKey + CLIENT_PRIMARY <- ClientPrimary + +INIT + Init + +NEXT + Next + +INVARIANT + TypeOK + UniqueCommitOrAbort + CommitConsistency + AbortConsistency + WriteConsistency + UniqueLockOrWrite + UniqueWrite + MsgTsConsistency diff --git a/DistributedTransaction/Test1.tla b/DistributedTransaction/Test1.tla new file mode 100644 index 0000000..d267bbd --- /dev/null +++ b/DistributedTransaction/Test1.tla @@ -0,0 +1,12 @@ +--------------------------------- MODULE Test1 --------------------------------- +EXTENDS DistributedTransaction, TLC + +CONSTANT k1, k2 +CONSTANT c1, c2, c3 + +Key == {k1, k2} +OptimistiicClient == {c3} +PessimisticClient == {c1, c2} +ClientKey == c1 :> {k1, k2} @@ c2 :> {k1} @@ c3 :> {k1, k2} +ClientPrimary == c1 :> k1 @@ c2 :> k1 @@ c3 :> k2 +================================================================================ diff --git a/DistributedTransaction/Test2.cfg b/DistributedTransaction/Test2.cfg new file mode 100644 index 0000000..1473b5d --- /dev/null +++ b/DistributedTransaction/Test2.cfg @@ -0,0 +1,30 @@ +CONSTANT + k1 = k1 + k2 = k2 + k3 = k3 + c1 = c1 + c2 = c2 + c3 = c3 + +CONSTANT + KEY <- Key + OPTIMISTIC_CLIENT <- OptimistiicClient + PESSIMISTIC_CLIENT <- PessimisticClient + CLIENT_KEY <- ClientKey + CLIENT_PRIMARY <- ClientPrimary + +INIT + Init + +NEXT + Next + +INVARIANT + TypeOK + UniqueCommitOrAbort + CommitConsistency + AbortConsistency + WriteConsistency + UniqueLockOrWrite + UniqueWrite + MsgTsConsistency diff --git a/DistributedTransaction/Test2.tla b/DistributedTransaction/Test2.tla new file mode 100644 index 0000000..f36a89e --- /dev/null +++ b/DistributedTransaction/Test2.tla @@ -0,0 +1,12 @@ +--------------------------------- MODULE Test2 --------------------------------- +EXTENDS DistributedTransaction, TLC + +CONSTANT k1, k2, k3 +CONSTANT c1, c2, c3 + +Key == {k1, k2, k3} +OptimistiicClient == {c3} +PessimisticClient == {c1, c2} +ClientKey == c1 :> {k1, k2, k3} @@ c2 :> {k1, k2} @@ c3 :> {k1, k3} +ClientPrimary == c1 :> k1 @@ c2 :> k1 @@ c3 :> k3 +================================================================================ diff --git a/PessimisticTransaction/PessimisticTransaction.tla b/PessimisticTransaction/PessimisticTransaction.tla deleted file mode 100644 index d98af48..0000000 --- a/PessimisticTransaction/PessimisticTransaction.tla +++ /dev/null @@ -1,621 +0,0 @@ ------------------------ MODULE PessimisticTransaction ----------------------- - -EXTENDS Integers, FiniteSets, TLC - -\* The set of transaction keys. -CONSTANTS KEY -ASSUME KEY /= {} \* Keys cannot be empty. - -\* The set of pessimistic clients -CONSTANTS PESSIMISTIC_CLIENT - -\* The set of optimistic clients -CONSTANTS OPTIMISTIC_CLIENT - -\* The set of clients to execute a transaction. -CLIENT == PESSIMISTIC_CLIENT \union OPTIMISTIC_CLIENT - -\* CLIENT_KEY is a set of [c -> SUBSET KEY], -\* representing involved keys of each client -CONSTANTS CLIENT_KEY - -ASSUME \A c \in CLIENT: CLIENT_KEY[c] \subseteq KEY - -CONSTANTS CLIENT_PRIMARY - -ASSUME \A c \in CLIENT: CLIENT_PRIMARY[c] \in CLIENT_KEY[c] - -\* next_ts is the timestamp for transaction. It is increased monotonically, -\* so every transaction must have a unique start and commit ts. -VARIABLES next_ts - -\* client_state[c] is the state of client. -VARIABLES client_state - -\* client_ts[c] is a record of [start_ts, for_update_ts, commit_ts]. -VARIABLES client_ts - -\* client_key[c] is a record of [primary: key, secondary: {key}, -\* pessimistic: {key}, pending: {key}]. Hereby, "pessimistic" denotes -\* the keys whose pessimistic locks haven't been acquired, "pending" -\* denotes the keys that are pending for prewrite. -VARIABLES client_key - -\* key_data[k] is the set of multi-version data of the key. -\* Since we don't care about the concrete value of data, a record of -\* [ts: start_ts] is sufficient to represent one data version. -VARIABLES key_data - -\* key_lock[k] is the set of lock. A lock is of a record of [ts: start_ts, -\* for_update_ts, primary: key]. If primary equals to k, it is a primary -\* lock, otherwise secondary lock. If for_update_ts > 0, it belongs to a -\* pessimistic transaction. -VARIABLES key_lock - -\* key_write[k] is a sequence of committed version of the key. -\* A committed version of the key is a record of [ts, type, start_ts, protected]. -\* type can be "write" or "rollback" depending on record type. start_ts -\* field only exists if type is "write". For "write" record, ts denotes -\* commit_ts; for "rollback" record, $ts$ denotes start_ts. -VARIABLES key_write - -\* key_last_read_ts is for verifying snapshot isolation invariant. It should -\* not appear in a real-world implementation. -\* -\* key_last_read_ts[k] denotes the last read timestamp for key k. -\* The commit_ts of a successful commit should greater then the last read_ts. -VARIABLES key_last_read_ts - -\* The set of all messages sent by clients. To simulate message resending, -\* client messages are inserted into the set and never deleted. The server can -\* pick any message in the set to execute. -VARIABLES msg - -client_vars == <<client_state, client_ts, client_key>> -key_vars == <<key_data, key_lock, key_write, key_last_read_ts>> -vars == <<next_ts, client_vars, key_vars, msg>> - ------------------------------------------------------------------------------ - -Pos == Nat \ {0} - ------------------------------------------------------------------------------ - -\* Find a stale lock which blocks the read for key k. -findStaleLock(k, ts) == - {l \in key_lock[k] : l.pessimistic = FALSE /\ l.ts < ts} - -\* Get the write records of k before or at timestamp ts -historyWrites(k, ts) == - {w \in key_write[k] : w.ts <= ts} - -\* Get the set of the latest write record of k before or at timestamp ts -latestHistoryWrite(k, ts) == - {w \in historyWrites(k, ts) : - \A w2 \in historyWrites(k, ts) : w.ts >= w2.ts} - -\* Rollback key k in the transaction starting at ts -rollback(k, ts) == - LET - \* rollback the primary key of a pessimistic transaction needs be protected from being collapsed - protected == \E l \in key_lock[k] : l.for_update_ts > 0 /\ k = l.primary - latest_write == latestHistoryWrite(k, ts) - IN - \* If the existing lock has the same ts, unlock it. - /\ IF \E l \in key_lock[k] : l.ts = ts - THEN key_lock' = [key_lock EXCEPT ![k] = {}] - ELSE UNCHANGED key_lock - /\ key_data' = [key_data EXCEPT ![k] = @ \ {[ts |-> ts]}] - \* Write a rollback in the write column. - /\ key_write' = [key_write EXCEPT - ![k] = IF ~ \E w \in latest_write: w.ts = ts - THEN (@ \ {w \in latest_write: w.type = "rollback" /\ ~w.protected }) \* collapse rollback - \union {[ts |-> ts, type |-> "rollback", start_ts |-> ts, protected |-> protected]} - ELSE @ - ] - -\* Commit key k -commit(k, start_ts, commit_ts) == - /\ IF \E l \in key_lock[k] : l.ts = start_ts - THEN - \* Write the write column and unlock the lock iff the lock exists - /\ key_lock' = [key_lock EXCEPT ![k] = {}] - /\ key_write' = [key_write EXCEPT ![k] = @ \union {[ts |-> commit_ts, type |-> "write", start_ts |-> start_ts]}] - \* Assert we don't violate snapshot isolation - \* TODO - /\ Assert(key_last_read_ts[k] < commit_ts, <<key_last_read_ts[k], commit_ts>>) - ELSE - UNCHANGED <<key_lock, key_write>> - /\ UNCHANGED key_data - -\* Change the state of client c to aborted -abortTxn(c) == - /\ client_state[c] /= "committed" - /\ client_state' = [client_state EXCEPT ![c] = "aborted"] - ------------------------------------------------------------------------------ - -StartOptimistic(c) == - /\ client_state[c] = "init" - /\ next_ts' = next_ts + 1 - /\ client_key' = - [client_key EXCEPT - ![c] = [primary |-> CLIENT_PRIMARY[c], - secondary |-> CLIENT_KEY[c] \ {CLIENT_PRIMARY[c]}, - pessimistic |-> {}, \* An optimistic transaction has no pessimistic lock to acquire - pending |-> CLIENT_KEY[c]]] - /\ client_state' = [client_state EXCEPT ![c] = "prewriting"] - \* The for_update_ts is initialized to be 0 in optimistic transactions. - /\ client_ts' = [client_ts EXCEPT ![c].start_ts = next_ts', ![c].for_update_ts = 0] - /\ UNCHANGED <<key_vars, msg>> - -StartPessimistic(c) == - /\ client_state[c] = "init" - /\ next_ts' = next_ts + 1 - /\ client_key' = - [client_key EXCEPT - ![c] = [primary |-> CLIENT_PRIMARY[c], - secondary |-> CLIENT_KEY[c] \ {CLIENT_PRIMARY[c]}, - pessimistic |-> CLIENT_KEY[c], \* Assume we need to acquire pessimistic locks for all keys - pending |-> CLIENT_KEY[c]]] - /\ client_state' = [client_state EXCEPT ![c] = "working"] - \* The for_update_ts is initialized to be the same as start_ts. - /\ client_ts' = [client_ts EXCEPT ![c].start_ts = next_ts', ![c].for_update_ts = next_ts'] - /\ UNCHANGED <<key_vars, msg>> - -LockKey(c) == - /\ client_state[c] = "working" - /\ IF client_key[c].pessimistic = {} - THEN - \* The client can prewrite after all pessimistic locks have been acquired. - /\ client_state' = [client_state EXCEPT ![c] = "prewriting"] - /\ UNCHANGED <<client_ts, client_key, next_ts, key_vars, msg>> - ELSE - \* Select any unlocked key and acquire its pessimistic lock - \E k \in client_key[c].pessimistic : - /\ msg' = msg \union - {[c |-> c, type |-> "lock", key |-> k, primary |-> client_key[c].primary, - start_ts |-> client_ts[c].start_ts, for_update_ts |-> client_ts[c].for_update_ts]} - /\ UNCHANGED <<next_ts, client_vars, key_vars>> - -Prewrite(c) == - /\ client_state[c] = "prewriting" - /\ IF client_key[c].pending = {} - THEN - \* The client can commit the primary key after prewriting all keys. - /\ client_state' = [client_state EXCEPT ![c] = "committing"] - /\ UNCHANGED <<client_ts, client_key, next_ts, key_vars, msg>> - ELSE - \* Select any pending key to prewrite - \E k \in client_key[c].pending : - /\ msg' = msg \union - {[c |-> c, type |-> "prewrite", key |-> k, primary |-> client_key[c].primary, - start_ts |-> client_ts[c].start_ts, for_update_ts |-> client_ts[c].for_update_ts]} - /\ UNCHANGED <<next_ts, client_vars, key_vars>> - -Commit(c) == - /\ client_state[c] = "committing" - \* Get a new ts as commit_ts - /\ IF client_ts[c].commit_ts = 0 - THEN - /\ next_ts' = next_ts + 1 - /\ client_ts' = [client_ts EXCEPT ![c].commit_ts = next_ts'] - ELSE - UNCHANGED <<next_ts, client_ts>> - \* Commit the primary key - /\ msg' = msg \union - {[c |-> c, type |-> "commit", key |-> client_key[c].primary, - start_ts |-> client_ts[c].start_ts, commit_ts |-> client_ts'[c].commit_ts]} - /\ UNCHANGED <<client_state, client_key, key_vars>> - -OptimisticClientOp(c) == - \/ StartOptimistic(c) - \/ Prewrite(c) - \/ Commit(c) - \* Committing secondary keys is ommitted - -PessimisticClientOp(c) == - \/ StartPessimistic(c) - \/ LockKey(c) - \/ Prewrite(c) - \/ Commit(c) - \* Committing secondary keys is ommitted - -DoRead == - \E cmd \in msg : - /\ cmd.type = "read" - /\ LET - k == cmd.key - ts == cmd.ts - IN - LET - stale_lock == findStaleLock(k, ts) - IN - IF stale_lock = {} - \* Successfully read - THEN - \* The client receives the read response, update the last read ts - /\ IF key_last_read_ts[k] < ts - THEN key_last_read_ts' = [key_last_read_ts EXCEPT ![k] = ts] - ELSE UNCHANGED key_last_read_ts - \* To simulate the client fails to receive the read response - /\ UNCHANGED <<client_vars, key_data, key_lock, key_write, next_ts, msg>> - ELSE - \* When there is a blocking lock, the client may resolve the lock by cleanup. - /\ msg' = msg \union - {[type |-> "cleanup", primary |-> l.primary, start_ts |-> l.ts] : l \in stale_lock} - /\ UNCHANGED <<client_state, client_ts, client_key, key_vars, next_ts>> - \* Response loss leads to no state change - -DoCleanup == - \E cmd \in msg : - /\ cmd.type = "cleanup" - /\ LET - k == cmd.primary - ts == cmd.start_ts - IN - LET - lock == {l \in key_lock[k] : l.ts = ts} - committed == {w \in key_write[k] : w.start_ts = ts /\ w.type = "write"} - IN - IF committed /= {} - \* The transaction is already committed, so resolve locks using its commit_ts - THEN - /\ msg' = msg \union - {[type |-> "resolve", primary |-> k, start_ts |-> ts, commit_ts |-> t.ts] : t \in committed} - /\ UNCHANGED <<next_ts, client_vars, key_vars>> - ELSE - \* The transaction is not committed, so rollback the primary key. - /\ rollback(k, ts) - /\ UNCHANGED <<key_last_read_ts, next_ts, client_vars>> - \* The client may resolve locks using 0 as commit_ts. When the cleanup response is lost, - \* msg remains unchanged. - /\ \/ msg' = msg \union - {[type |-> "resolve", primary |-> k, start_ts |-> ts, commit_ts |-> 0]} - \/ UNCHANGED msg - -DoResolve == - \E cmd \in msg : - /\ cmd.type = "resolve" - /\ IF cmd.commit_ts = 0 - THEN - \* rollback locks when commit_ts = 0 - \E k \in KEY : - \E l \in key_lock[k] : - /\ l.primary = cmd.primary - /\ l.ts = cmd.start_ts - /\ rollback(k, cmd.start_ts) - ELSE - \* commit locks when commit_ts > 0 - \E k \in KEY : - \E l \in key_lock[k] : - /\ l.primary = cmd.primary - /\ l.ts = cmd.start_ts - /\ commit(k, cmd.start_ts, cmd.commit_ts) - /\ UNCHANGED <<next_ts, client_vars, key_last_read_ts, msg>> - -\* This action is too complex and concrete. Maybe simplify it? -DoLockKey == - \E cmd \in msg : - /\ cmd.type = "lock" - /\ LET - c == cmd.c - k == cmd.key - primary == cmd.primary - ts == cmd.start_ts - for_update_ts == cmd.for_update_ts - \* Write or overwrite the pessimistic lock - writeLock == - /\ key_lock' = [key_lock EXCEPT ![k] = {[ts |-> ts, for_update_ts |-> for_update_ts, primary |-> primary, pessimistic |-> TRUE]}] - /\ UNCHANGED <<client_state, client_ts, key_data, key_write, key_last_read_ts, next_ts, msg>> - \* Inform the client that the key is locked - /\ \/ client_key' = [client_key EXCEPT ![c].pessimistic = @ \ {k}] - \* client_key remains the same when the response is lost - \/ UNCHANGED client_key - \* Update the for_update_ts in the client with new_ts - updateForUpdateTs(new_ts) == - IF new_ts > client_ts[c].for_update_ts - THEN - /\ client_ts' = [client_ts EXCEPT ![c].for_update_ts = for_update_ts] - /\ UNCHANGED <<client_state, client_key, next_ts, key_vars, msg>> - ELSE UNCHANGED vars - IN - IF key_lock[k] = {} - THEN - IF key_write[k] = {} - \* If no lock or write exists, we can always lock the key - THEN writeLock - ELSE - LET - \* Find the write record with biggest commit_ts - latest == CHOOSE w \in key_write[k] : \A w2 \in key_write[k] : w.ts >= w2.ts - IN - IF latest.ts > for_update_ts - \* Update the client's for_update_ts when there is a newer commit. - \* Response loss causes no state change. - THEN updateForUpdateTs(latest.ts) - ELSE - IF \E w \in key_write[k] : w.start_ts = ts /\ w.type = "rollback" - \* If any key to be locked is rollbacked, abort the transaction. - \* TODO: Maybe it needn't be included in the spec. - THEN abortTxn(c) /\ UNCHANGED <<next_ts, client_ts, client_key, key_vars, msg>> - \* Otherwise we can lock the key. - ELSE writeLock - ELSE - LET - l == CHOOSE l \in key_lock[k] : TRUE - IN - IF l.ts /= ts - \* If there is a lock from another transaction, the client may cleanup the lock. - \* Response loss causes no state change. - THEN - /\ msg' = msg \union - {[type |-> "cleanup", primary |-> l.primary, start_ts |-> l.ts]} - /\ UNCHANGED <<client_state, client_ts, client_key, key_vars, next_ts>> - ELSE - \* Only overwrite the lock when it's a pessimistic lock with smaller for_update_ts - /\ l.pessimistic - /\ l.for_update_ts < for_update_ts - /\ writeLock - -DoOptimisticPrewrite == - \E cmd \in msg : - /\ cmd.type = "prewrite" - /\ cmd.for_update_ts = 0 - /\ LET - c == cmd.c - k == cmd.key - primary == cmd.primary - ts == cmd.start_ts - lock == { l \in key_lock[k] : l.ts /= ts } - IN - IF \E w \in key_write[k] : w.ts >= ts - THEN - /\ abortTxn(c) - /\ UNCHANGED <<client_ts, client_key, next_ts, key_vars, msg>> - ELSE IF lock /= {} - THEN - \* When there is another transaction's lock, the client may resolve the lock by cleanup. - /\ msg' = msg \union - {[type |-> "cleanup", primary |-> l.primary, start_ts |-> l.ts] : l \in lock} - /\ UNCHANGED <<client_state, client_ts, client_key, key_vars, next_ts>> - \* Response loss leads to no state change - ELSE - \* Otherwise prewrite - /\ key_lock' = [key_lock EXCEPT ![k] = {[ts |-> ts, for_update_ts |-> 0, primary |-> primary, pessimistic |-> FALSE]}] - /\ key_data' = [key_data EXCEPT ![k] = @ \union {[ts |-> ts]}] - \* Inform the client that the key is successfully prewritten - /\ \/ client_key' = [client_key EXCEPT ![c].pending = @ \ {k}] - \* Simulate response loss - \/ UNCHANGED client_key - /\ UNCHANGED <<client_state, client_ts, key_write, key_last_read_ts, next_ts, msg>> - -DoPessimisticPrewrite == - \E cmd \in msg : - /\ cmd.type = "prewrite" - /\ cmd.for_update_ts > 0 - /\ LET - c == cmd.c - k == cmd.key - primary == cmd.primary - ts == cmd.start_ts - for_update_ts == cmd.for_update_ts - IN - IF - \/ key_lock[k] = {} - \/ \E l \in key_lock[k] : l.ts /= ts - THEN - \* Abort the transaction when its lock doesn't exist - /\ abortTxn(c) - /\ UNCHANGED <<client_ts, client_key, next_ts, key_vars, msg>> - ELSE - \* Otherwise rewrite the existing lock to an optimistic lock - /\ key_lock' = [key_lock EXCEPT ![k] = {[ts |-> ts, for_update_ts |-> for_update_ts, primary |-> primary, pessimistic |-> FALSE]}] - /\ key_data' = [key_data EXCEPT ![k] = @ \union {[ts |-> ts]}] - \* Inform the client that the key is successfully prewritten - /\ \/ client_key' = [client_key EXCEPT ![c].pending = @ \ {k}] - \* Simulate response loss - \/ UNCHANGED client_key - /\ UNCHANGED <<client_state, client_ts, key_write, key_last_read_ts, next_ts, msg>> - -DoCommit == - \E cmd \in msg : - /\ cmd.type = "commit" - /\ LET - c == cmd.c - k == cmd.key - start_ts == cmd.start_ts - commit_ts == cmd.commit_ts - IN - IF \/ \E l \in key_lock[k] : l.ts = start_ts - \/ \E w \in key_write[k] : w.start_ts = start_ts /\ w.type = "write" - \* Commit the key iff the prewritten key exists or it's a repeated commit - THEN - /\ commit(k, start_ts, commit_ts) - \* Change client state to committed. - /\ client_state' = [client_state EXCEPT ![c] = "committed"] - /\ UNCHANGED <<client_ts, client_key, next_ts, key_last_read_ts, msg>> - ELSE - \* The lock doesn't exist and the key is not committed, so commit fails. - /\ Assert(client_state[c] /= "committed", client_state[c]) - /\ abortTxn(c) - /\ UNCHANGED <<client_ts, client_key, next_ts, key_vars, msg>> - -ServerOp == - \/ DoRead - \/ DoCleanup - \/ DoResolve - \/ DoLockKey - \/ DoOptimisticPrewrite - \/ DoPessimisticPrewrite - \/ DoCommit - -Read == - \E ts \in 0..next_ts : - \E k \in KEY : - /\ msg' = msg \union {[type |-> "read", key |-> k, ts |-> next_ts]} - /\ UNCHANGED <<next_ts, client_vars, key_vars>> - -Init == - /\ next_ts = 1 - /\ client_state = [c \in CLIENT |-> "init"] - /\ client_ts = [c \in CLIENT |-> [start_ts |-> 0, - for_update_ts |-> 0, - commit_ts |-> 0]] - /\ client_key = [c \in CLIENT |-> {}] - /\ key_lock = [k \in KEY |-> {}] - /\ key_data = [k \in KEY |-> {}] - /\ key_write = [k \in KEY |-> {}] - /\ key_last_read_ts = [k \in KEY |-> 0] - /\ msg = {} - -Next == - \/ ServerOp - \/ \E c \in OPTIMISTIC_CLIENT : OptimisticClientOp(c) - \/ \E c \in PESSIMISTIC_CLIENT : PessimisticClientOp(c) - \/ Read - -PessimisticSpec == Init /\ [][Next]_vars - ------------------------------------------------------------------------------ - -NextTsTypeOK == next_ts \in Pos - -ClientStateTypeOK == - client_state \in [ - CLIENT -> {"init", "working", "prewriting", - "committing", "committed", "aborted"} - ] - -ClientTsTypeOK == - client_ts \in - [CLIENT -> [start_ts : Nat, for_update_ts : Nat, commit_ts : Nat]] - -ClientKeyTypeOK == - \A c \in CLIENT : - \/ client_state[c] = "init" - \/ client_key[c] \in [primary : KEY, - secondary : SUBSET KEY, - pessimistic : SUBSET KEY, - pending : SUBSET KEY] - -KeyDataTypeOK == - key_data \in [KEY -> SUBSET [ts : Pos]] - -KeyLockTypeOK == - key_lock \in [KEY -> SUBSET [ts : Pos, for_update_ts : Nat, primary : KEY, pessimistic : BOOLEAN]] - -KeyWriteTypeOK == - key_write \in [KEY -> SUBSET ([ts : Pos, type : {"write"}, start_ts : Pos] \union - [ts : Pos, type : {"rollback"}, start_ts : Pos, protected : BOOLEAN])] - -KeyLastReadTsTypeOK == - key_last_read_ts \in [KEY -> Nat] - -MsgTypeOK == - msg \subseteq ( - [c : CLIENT, type : {"lock", "prewrite"}, key : KEY, primary: KEY, - start_ts : Pos, for_update_ts : Nat] \union - [c : CLIENT, type : {"commit"}, key : KEY, start_ts : Pos, commit_ts : Pos] \union - [type : {"read"}, key : KEY, ts : Nat] \union - [type : {"cleanup"}, primary : KEY, start_ts : Pos] \union - [type : {"resolve"}, primary : KEY, start_ts : Pos, commit_ts : Nat] - ) - -TypeOK == - /\ NextTsTypeOK - /\ ClientStateTypeOK - /\ ClientTsTypeOK - /\ ClientKeyTypeOK - /\ KeyDataTypeOK - /\ KeyLockTypeOK - /\ KeyWriteTypeOK - /\ KeyLastReadTsTypeOK - /\ MsgTypeOK - ------------------------------------------------------------------------------ - -\* For each write, the commit_ts should be strictly greater than -\* the start_ts. For each rollback, the commit_ts should equals to -\* the start_ts. -WriteConsistency == - \A k \in KEY: - \A rec \in key_write[k] : - \/ /\ rec.type = "write" - /\ rec.ts > rec.start_ts - /\ \E d \in key_data[k] : rec.start_ts = d.ts - \/ /\ rec.type = "rollback" - /\ rec.ts = rec.start_ts - -LockConsistency == - \A k \in KEY : - \* There should be at most one lock for each key. - /\ Cardinality(key_lock[k]) <= 1 - \* When the lock exists, there cannot be a corresponding commit record - /\ \A l \in key_lock[k] : - ~ \E w \in key_write[k] : w.start_ts = l.ts - -CommittedClientConsistency == - \A c \in CLIENT : - LET - start_ts == client_ts[c].start_ts - commit_ts == client_ts[c].commit_ts - primary == client_key[c].primary - IN - \* When the client considers it's committed, its primary key must be committed - client_state[c] = "committed" => - \E w \in key_write[primary] : - /\ w.start_ts = start_ts - /\ w.type = "write" - /\ w.commit_ts = commit_ts - -\* If a client is aborted, there should be no committed primary key. -AbortedClientConsistency == - \A c \in CLIENT : - client_state[c] = "aborted" => - ~ \E w \in key_write[client_key[c].primary] : - /\ w.start_ts = client_ts[c].start_ts - /\ w.type = "write" - -CommittedTxnConsistency == - \A c \in CLIENT : - client_state[c] /= "init" => - LET - primary == client_key[c].primary - secondary == client_key[c].secondary - start_ts == client_ts[c].start_ts - IN - \A wp \in key_write[primary] : - \* If the primary key is committed, the secondary keys of the same transaction - \* must be either committed or locked - wp.start_ts = start_ts /\ wp.type = "write" => - \A s \in secondary : - \/ \E l \in key_lock[s] : l.ts = start_ts - \/ \E ws \in key_write[s] : ws.start_ts = start_ts /\ ws.type = "write" - -\* For each transaction, we cannot have both committed and rolled back keys. -RollbackConsistency == - \A start_ts \in {client_ts[c].start_ts : c \in CLIENT} : - (\E k \in KEY : - \E w \in key_write[k] : w.start_ts = start_ts /\ w.type = "rollback") => - \A k2 \in KEY : - ~ \E w2 \in key_write[k2] : w2.start_ts = start_ts /\ w2.type = "write" - -\* For each key, each write or rollback record in write column should have a -\* unique start_ts. -UniqueWrite == - \A k \in KEY : - Cardinality({w.start_ts : w \in key_write[k]}) = Cardinality(key_write[k]) - ------------------------------------------------------------------------------ - -THEOREM Safety == - PessimisticSpec => [](/\ TypeOK - /\ WriteConsistency - /\ LockConsistency - /\ CommittedTxnConsistency - /\ AbortedClientConsistency - /\ RollbackConsistency - /\ UniqueWrite) - -============================================================================= diff --git a/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test1.launch b/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test1.launch deleted file mode 100644 index 8a37459..0000000 --- a/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test1.launch +++ /dev/null @@ -1,69 +0,0 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<launchConfiguration type="org.lamport.tla.toolbox.tool.tlc.modelCheck"> - <stringAttribute key="TLCCmdLineParameters" value=""/> - <intAttribute key="collectCoverage" value="1"/> - <stringAttribute key="configurationName" value="Test1"/> - <booleanAttribute key="deferLiveness" value="false"/> - <intAttribute key="dfidDepth" value="100"/> - <booleanAttribute key="dfidMode" value="false"/> - <intAttribute key="distributedFPSetCount" value="0"/> - <stringAttribute key="distributedNetworkInterface" value="10.0.75.1"/> - <intAttribute key="distributedNodesCount" value="1"/> - <stringAttribute key="distributedTLC" value="off"/> - <stringAttribute key="distributedTLCVMArgs" value=""/> - <intAttribute key="fpBits" value="1"/> - <intAttribute key="fpIndex" value="3"/> - <booleanAttribute key="fpIndexRandom" value="true"/> - <intAttribute key="maxHeapSize" value="63"/> - <intAttribute key="maxSetSize" value="1000000"/> - <booleanAttribute key="mcMode" value="true"/> - <stringAttribute key="modelBehaviorInit" value="Init"/> - <stringAttribute key="modelBehaviorNext" value="Next"/> - <stringAttribute key="modelBehaviorSpec" value=""/> - <intAttribute key="modelBehaviorSpecType" value="2"/> - <stringAttribute key="modelBehaviorVars" value="msg, key_data, key_lock, next_ts, client_state, key_last_read_ts, client_key, client_ts, key_write"/> - <stringAttribute key="modelComments" value=""/> - <booleanAttribute key="modelCorrectnessCheckDeadlock" value="false"/> - <listAttribute key="modelCorrectnessInvariants"> - <listEntry value="0TypeOK"/> - <listEntry value="1WriteConsistency"/> - <listEntry value="1LockConsistency"/> - <listEntry value="1CommittedTxnConsistency"/> - <listEntry value="1AbortedClientConsistency"/> - <listEntry value="1RollbackConsistency"/> - <listEntry value="1NextTsTypeOK"/> - <listEntry value="1&#13;&#10;ClientStateTypeOK"/> - <listEntry value="1&#13;&#10;ClientTsTypeOK"/> - <listEntry value="1&#13;&#10;ClientKeyTypeOK"/> - <listEntry value="1&#13;&#10;KeyDataTypeOK"/> - <listEntry value="1&#13;&#10;KeyLockTypeOK"/> - <listEntry value="1&#13;&#10;KeyWriteTypeOK"/> - <listEntry value="1UniqueWrite"/> - </listAttribute> - <listAttribute key="modelCorrectnessProperties"/> - <intAttribute key="modelEditorOpenTabs" value="14"/> - <stringAttribute key="modelExpressionEval" value=""/> - <stringAttribute key="modelParameterActionConstraint" value=""/> - <listAttribute key="modelParameterConstants"> - <listEntry value="KEY;;{k1, k2};1;1"/> - <listEntry value="CLIENT_KEY;;c1 :&gt; {k1, k2} @@ c2 :&gt; {k2};0;0"/> - <listEntry value="PESSIMISTIC_CLIENT;;{c1, c2};1;1"/> - <listEntry value="OPTIMISTIC_CLIENT;;{};0;0"/> - <listEntry value="CLIENT_PRIMARY;;c1 :&gt; k1 @@ c2 :&gt; k2;0;0"/> - </listAttribute> - <stringAttribute key="modelParameterContraint" value=""/> - <listAttribute key="modelParameterDefinitions"/> - <stringAttribute key="modelParameterModelValues" value="{}"/> - <stringAttribute key="modelParameterNewDefinitions" value=""/> - <intAttribute key="modelVersion" value="20191005"/> - <intAttribute key="numberOfWorkers" value="10"/> - <booleanAttribute key="recover" value="false"/> - <stringAttribute key="result.mail.address" value=""/> - <intAttribute key="simuAril" value="-1"/> - <intAttribute key="simuDepth" value="100"/> - <intAttribute key="simuSeed" value="-1"/> - <stringAttribute key="specName" value="PessimisticTransaction"/> - <stringAttribute key="tlcResourcesProfile" value="local custom"/> - <stringAttribute key="view" value=""/> - <booleanAttribute key="visualizeStateGraph" value="false"/> -</launchConfiguration> diff --git a/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test2.launch b/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test2.launch deleted file mode 100644 index 1c0ef72..0000000 --- a/PessimisticTransaction/PessimisticTransaction.toolbox/PessimisticTransaction___Test2.launch +++ /dev/null @@ -1,31 +0,0 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<launchConfiguration type="org.lamport.tla.toolbox.tool.tlc.modelCheck"> - <stringAttribute key="configurationName" value="Test2"/> - <intAttribute key="distributedFPSetCount" value="0"/> - <stringAttribute key="distributedNetworkInterface" value="192.168.220.40"/> - <intAttribute key="distributedNodesCount" value="1"/> - <stringAttribute key="distributedTLC" value="off"/> - <intAttribute key="fpIndex" value="27"/> - <intAttribute key="maxHeapSize" value="25"/> - <stringAttribute key="modelBehaviorInit" value="Init"/> - <stringAttribute key="modelBehaviorNext" value="Next"/> - <stringAttribute key="modelBehaviorSpec" value=""/> - <intAttribute key="modelBehaviorSpecType" value="2"/> - <stringAttribute key="modelBehaviorVars" value="msg, key_data, key_lock, next_ts, client_state, key_last_read_ts, client_key, client_ts, key_write"/> - <stringAttribute key="modelComments" value=""/> - <booleanAttribute key="modelCorrectnessCheckDeadlock" value="true"/> - <listAttribute key="modelCorrectnessInvariants"/> - <listAttribute key="modelCorrectnessProperties"/> - <stringAttribute key="modelExpressionEval" value=""/> - <listAttribute key="modelParameterConstants"> - <listEntry value="KEY;;{k1, k2};1;1"/> - <listEntry value="CLIENT_KEY;;c1 :&gt; {k1, k2} @@ c2 :&gt; {k1} @@ c3 :&gt; {k2};0;0"/> - <listEntry value="PESSIMISTIC_CLIENT;;{c1};1;1"/> - <listEntry value="OPTIMISTIC_CLIENT;;{c2, c3};1;1"/> - <listEntry value="CLIENT_PRIMARY;;c1 :&gt; k1 @@ c2 :&gt; k1 @@ c3 :&gt; k2;0;0"/> - </listAttribute> - <intAttribute key="numberOfWorkers" value="6"/> - <stringAttribute key="result.mail.address" value=""/> - <stringAttribute key="specName" value="PessimisticTransaction"/> - <stringAttribute key="tlcResourcesProfile" value="local custom"/> -</launchConfiguration> diff --git a/PessimisticTransaction/Test1.cfg b/PessimisticTransaction/Test1.cfg deleted file mode 100644 index 1f1ac6f..0000000 --- a/PessimisticTransaction/Test1.cfg +++ /dev/null @@ -1,33 +0,0 @@ -\* See Test1.tla. - -CONSTANT - KEY <- Key - OPTIMISTIC_CLIENT <- OptimistiicClient - PESSIMISTIC_CLIENT <- PessimisticClient - CLIENT_KEY <- ClientKey - CLIENT_PRIMARY <- ClientPrimary - -INIT - Init - -NEXT - Next - -INVARIANT - NextTsTypeOK - ClientStateTypeOK - ClientTsTypeOK - ClientKeyTypeOK - KeyDataTypeOK - KeyLockTypeOK - KeyWriteTypeOK - KeyLastReadTsTypeOK - MsgTypeOK - -INVARIANT - WriteConsistency - LockConsistency - CommittedTxnConsistency - AbortedClientConsistency - RollbackConsistency - UniqueWrite diff --git a/PessimisticTransaction/Test1.tla b/PessimisticTransaction/Test1.tla deleted file mode 100644 index 01e79f1..0000000 --- a/PessimisticTransaction/Test1.tla +++ /dev/null @@ -1,19 +0,0 @@ ---------------------------------- MODULE Test1 --------------------------------- - -EXTENDS PessimisticTransaction, TLC - -\* Model value is not used to avoid unnecessary state space -\* checked by TLC. Symmetry should not be used if we are supposed -\* to check liveness. -k1 == 1 -k2 == 2 -c1 == 1 -c2 == 2 - -Key == {k1, k2} -OptimistiicClient == {} -PessimisticClient == {c1, c2} -ClientKey == c1 :> {k1, k2} @@ c2 :> {k2} -ClientPrimary == c1 :> k1 @@ c2 :> k2 - -================================================================================ diff --git a/PessimisticTransaction/Test2.cfg b/PessimisticTransaction/Test2.cfg deleted file mode 100644 index 780fc44..0000000 --- a/PessimisticTransaction/Test2.cfg +++ /dev/null @@ -1,33 +0,0 @@ -\* See Test2.tla. - -CONSTANT - KEY <- Key - OPTIMISTIC_CLIENT <- OptimistiicClient - PESSIMISTIC_CLIENT <- PessimisticClient - CLIENT_KEY <- ClientKey - CLIENT_PRIMARY <- ClientPrimary - -INIT - Init - -NEXT - Next - -INVARIANT - NextTsTypeOK - ClientStateTypeOK - ClientTsTypeOK - ClientKeyTypeOK - KeyDataTypeOK - KeyLockTypeOK - KeyWriteTypeOK - KeyLastReadTsTypeOK - MsgTypeOK - -INVARIANT - WriteConsistency - LockConsistency - CommittedTxnConsistency - AbortedClientConsistency - RollbackConsistency - UniqueWrite diff --git a/PessimisticTransaction/Test2.tla b/PessimisticTransaction/Test2.tla deleted file mode 100644 index f781c24..0000000 --- a/PessimisticTransaction/Test2.tla +++ /dev/null @@ -1,20 +0,0 @@ ---------------------------------- MODULE Test2 --------------------------------- - -EXTENDS PessimisticTransaction, TLC - -\* Model value is not used to avoid unnecessary state space -\* checked by TLC. Symmetry should not be used if we are supposed -\* to check liveness. -k1 == 1 -k2 == 2 -c1 == 1 -c2 == 2 -c3 == 3 - -Key == {k1, k2} -OptimistiicClient == {c2, c3} -PessimisticClient == {c1} -ClientKey == c1 :> {k1, k2} @@ c2 :> {k1} @@ c3 :> {k2} -ClientPrimary == c1 :> k1 @@ c2 :> k1 @@ c3 :> k2 - -================================================================================ From c8aeb14b835b62820437c312c45da9ffe1d557c0 Mon Sep 17 00:00:00 2001 From: Andy Lok <andylokandy@hotmail.com> Date: Wed, 8 Apr 2020 03:03:43 +0800 Subject: [PATCH 18/20] update readme --- DistributedTransaction/README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/DistributedTransaction/README.md b/DistributedTransaction/README.md index 2a1c680..1d4a4b4 100644 --- a/DistributedTransaction/README.md +++ b/DistributedTransaction/README.md @@ -4,7 +4,6 @@ The module contains a abstract specification of the transaction system implement The module contains two TLA+ files: `DistributedTransaction.tla` and `DistributedTransactionProofs.tla`. -In most cases you will only have an interest in `DistributedTransaction.tla`, where the whole specification and safety invariants are defined. Besides that, in `DistributedTransactionProofs.tla`, there are some proofs, which are supposed to be build up gradually, to the safety invariants. - -To run formal proofs in `DistributedTransactionProofs.tla`, you'd better install [TLAPS](https://tla.msr-inria.inria.fr/tlaps/content/Download/Binaries.html) (TLA+ Proof System) first. It's not distributed altogether with the TLA toolbox. +In most cases you will likely have an interest in only `DistributedTransaction.tla`, where the specification and safety invariants are defined. Besides that, in `DistributedTransactionProofs.tla`, there are some formal proofs, which are supposed to be build up gradually (so it's not completed yet), to the safety invariants. +To run the formal proofs in `DistributedTransactionProofs.tla`, you'd like to install the [TLAPS](https://tla.msr-inria.inria.fr/tlaps/content/Download/Binaries.html) (TLA+ Proof System) first. It's not distributed altogether with the TLA toolbox. From 92ff853acff341b1c220d57a10cbef909bcc5917 Mon Sep 17 00:00:00 2001 From: andylokandy <andylokandy@hotmail.com> Date: Wed, 8 Apr 2020 03:15:41 +0800 Subject: [PATCH 19/20] update spec Signed-off-by: andylokandy <andylokandy@hotmail.com> --- .../DistributedTransaction.pdf | Bin 217824 -> 217831 bytes .../DistributedTransaction.tla | 5 +++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/DistributedTransaction/DistributedTransaction.pdf b/DistributedTransaction/DistributedTransaction.pdf index 8250c1bbd785e366eab5830f4a7aaec91b03e18c..ef6fe031f8920ff2a6a6b8091db7afe205f51409 100644 GIT binary patch delta 3983 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 Kf`XwX1^F+oP6e0% delta 3955 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 Y(!<l&<CLQSCq1432`DPkOmqeQ1vee^P5=M^ diff --git a/DistributedTransaction/DistributedTransaction.tla b/DistributedTransaction/DistributedTransaction.tla index 647b293..919596c 100644 --- a/DistributedTransaction/DistributedTransaction.tla +++ b/DistributedTransaction/DistributedTransaction.tla @@ -566,14 +566,14 @@ MsgTsConsistency == req.commit_ts <= next_ts /\ \A resp \in resp_msgs : resp.start_ts <= next_ts -\* SnapshotIsolation is implied from the four assumptions (but is not +\* SnapshotIsolation is implied from the following assumptions (but is not \* nessesary), because SnapshotIsolation means that: \* (1) Once a transcation is committed, all keys of the transaction should \* be always readable or have lock on secondary keys(eventually readable). \* PROOF BY CommitConsistency, MsgConsistency \* (2) For a given transaction, all transaction that commits after that \* transaction should have greater commit_ts than the next_ts at the -\* time that the given transaction commits. So as to be able to +\* time that the given transaction commits, so as to be able to \* distinguish the transactions that commits before and after \* from all transactions that preserved by (1). \* PROOF BY NextTsConsistency, MsgTsConsistency @@ -583,6 +583,7 @@ SnapshotIsolation == /\ CommitConsistency /\ AbortConsistency /\ NextTsMonotonicity /\ MsgMonotonicity + /\ MsgTsConsistency ----------------------------------------------------------------------------- THEOREM Safety == Spec => [](/\ TypeOK From 8128e56c84eacb725d43b64f6b0604fc20c81a53 Mon Sep 17 00:00:00 2001 From: andylokandy <andylokandy@hotmail.com> Date: Fri, 10 Jul 2020 18:53:20 +0800 Subject: [PATCH 20/20] fix typo Signed-off-by: andylokandy <andylokandy@hotmail.com> --- .gitignore | 1 + .../DistributedTransaction.tla | 1194 ++++++++--------- 2 files changed, 598 insertions(+), 597 deletions(-) diff --git a/.gitignore b/.gitignore index 6c3cb85..6e72a9c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ states/ *.out +*.tlaps **/*.toolbox/*/ **/*.toolbox/*___*_SnapShot_*.launch **/*.toolbox/.project diff --git a/DistributedTransaction/DistributedTransaction.tla b/DistributedTransaction/DistributedTransaction.tla index 919596c..d008e4c 100644 --- a/DistributedTransaction/DistributedTransaction.tla +++ b/DistributedTransaction/DistributedTransaction.tla @@ -1,597 +1,597 @@ ------------------------ MODULE DistributedTransaction ----------------------- -EXTENDS Integers - -\* The set of all keys. -CONSTANTS KEY - -\* The sets of optiimistic clients and pessimistic clients. -CONSTANTS OPTIMISTIC_CLIENT, PESSIMISTIC_CLIENT -CLIENT == PESSIMISTIC_CLIENT \union OPTIMISTIC_CLIENT - -\* CLIENT_KEY is a set of [Client -> SUBSET KEY] -\* representing the involved keys of each client. -CONSTANTS CLIENT_KEY -ASSUME \A c \in CLIENT: CLIENT_KEY[c] \subseteq KEY - -\* CLIENT_PRIMARY is the primary key of each client. -CONSTANTS CLIENT_PRIMARY -ASSUME \A c \in CLIENT: CLIENT_PRIMARY[c] \in CLIENT_KEY[c] - -\* Timestamp of transactions. -Ts == Nat \ {0} -NoneTs == 0 - -\* The algorithm is easier to understand in terms of the set of msgs of -\* all messages that have ever been sent. A more accurate model would use -\* one or more variables to represent the messages actually in transit, -\* and it would include actions representing message loss and duplication -\* as well as message receipt. -\* -\* In the current spec, there is no need to model message loss because we -\* are mainly concerned with the algorithm's safety property. The safety -\* part of the spec says only what messages may be received and does not -\* assert that any message actually is received. Thus, there is no -\* difference between a lost message and one that is never received. -VARIABLES req_msgs -VARIABLES resp_msgs - -\* key_data[k] is the set of multi-version data of the key. Since we -\* don't care about the concrete value of data, a strat_ts is sufficient -\* to represent one data version. -VARIABLES key_data -\* key_lock[k] is the set of lock (zero or one element). A lock is of a -\* record of [ts: start_ts, primary: key, type: lock_type]. If primary -\* equals to k, it is a primary lock, otherwise secondary lock. lock_type -\* is one of {"prewrite_optimistic", "prewrite_pessimistic", "lock_key"}. -\* lock_key denotes the pessimistic lock performed by ServerLockKey -\* action, the prewrite_pessimistic denotes percolator optimistic lock -\* who is transformed from a lock_key lock by action -\* ServerPrewritePessimistic, and prewrite_optimistic denotes the -\* classic optimistic lock. -\* -\* In TiKV, key_lock has an additional for_update_ts field and the -\* LockType is of four variants: -\* {"PUT", "DELETE", "LOCK", "PESSIMISTIC"}. -\* -\* In the spec, we abstract them by: -\* (1) LockType \in {"PUT", "DELETE", "LOCK"} /\ for_update_ts = 0 <=> -\* type = "prewrite_optimistic" -\* (2) LockType \in {"PUT", "DELETE"} /\ for_update_ts > 0 <=> -\* type = "prewrite_pessimistic" -\* (3) LockType = "PESSIMISTIC" <=> type = "lock_key" -VARIABLES key_lock -\* key_write[k] is a sequence of commit or rollback record of the key. -\* It's a record of [ts, start_ts, type, [protected]]. type can be eihter -\* "write" or "rollback". ts represents the commit_ts of "write" record. -\* Otherwise, ts equals to start_ts on "rollback" record. "rollback" -\* record has an additional protected field. protected signifies the -\* rollback record would not be collapsed. -VARIABLES key_write - -\* client_state[c] indicates the current transaction stage of client c. -VARIABLES client_state -\* client_ts[c] is a record of [start_ts, commit_ts, for_update_ts]. -\* Fields are all initialized to NoneTs. -VARIABLES client_ts -\* client_key[c] is a record of [locking: {key}, prewriting: {key}]. -\* Hereby, "locking" denotes the keys whose pessimistic locks -\* haven't been acquired, "prewriting" denotes the keys that are pending -\* for prewrite. -VARIABLES client_key - -\* next_ts is a globally monotonically increasing integer, respresenting -\* the virtual clock of transactions. In practice, the variable is -\* maintained by PD, the time oracle of a cluster. -VARIABLES next_ts - -msg_vars == <<req_msgs, resp_msgs>> -client_vars == <<client_state, client_ts, client_key>> -key_vars == <<key_data, key_lock, key_write>> -vars == <<msg_vars, client_vars, key_vars, next_ts>> - -SendReqs(msgs) == req_msgs' = req_msgs \union msgs -SendResp(msg) == resp_msgs' = resp_msgs \union {msg} ------------------------------------------------------------------------------ -\* Type Definitions - -ReqMessages == - [start_ts : Ts, primary : KEY, type : {"lock_key"}, key : KEY, - for_update_ts : Ts] - \union [start_ts : Ts, primary : KEY, type : {"prewrite_optimistic"}, key : KEY] - \union [start_ts : Ts, primary : KEY, type : {"prewrite_pessimistic"}, key : KEY] - \union [start_ts : Ts, primary : KEY, type : {"commit"}, commit_ts : Ts] - \union [start_ts : Ts, primary : KEY, type : {"cleanup"}] - \union [start_ts : Ts, primary : KEY, type : {"resolve_rollbacked"}] - \union [start_ts : Ts, primary : KEY, type : {"resolve_committed"}, commit_ts : Ts] - -RespMessages == - [start_ts : Ts, type : {"prewrited", "locked_key"}, key : KEY] - \union [start_ts : Ts, type : {"lock_failed"}, key : KEY, latest_commit_ts : Ts] - \union [start_ts : Ts, type : {"committed", - "commit_aborted", - "prewrite_aborted", - "lock_key_aborted"}] - -TypeOK == /\ req_msgs \in SUBSET ReqMessages - /\ resp_msgs \in SUBSET RespMessages - /\ key_data \in [KEY -> SUBSET Ts] - /\ key_lock \in [KEY -> SUBSET [ts : Ts, - primary : KEY, - type : {"prewrite_optimistic", - "prewrite_pessimistic", - "lock_key"}]] - /\ \A k \in KEY : - \* At most one lock in key_lock[k] - \A l, l2 \in key_lock[k] : - l = l2 - /\ key_write \in [KEY -> SUBSET ( - [ts : Ts, start_ts : Ts, type : {"write"}] - \union [ts : Ts, start_ts : Ts, type : {"rollback"}, protected : BOOLEAN])] - /\ client_state \in [CLIENT -> {"init", "locking", "prewriting", "committing"}] - /\ client_ts \in [CLIENT -> [start_ts : Ts \union {NoneTs}, - commit_ts : Ts \union {NoneTs}, - for_update_ts : Ts \union {NoneTs}]] - /\ client_key \in [CLIENT -> [locking: SUBSET KEY, prewriting : SUBSET KEY]] - /\ next_ts \in Ts ------------------------------------------------------------------------------ -\* Client Actions - -ClientLockKey(c) == - /\ client_state[c] = "init" - /\ client_state' = [client_state EXCEPT ![c] = "locking"] - /\ client_ts' = [client_ts EXCEPT ![c].start_ts = next_ts, ![c].for_update_ts = next_ts] - /\ next_ts' = next_ts + 1 - \* Assume we need to acquire pessimistic locks for all keys - /\ client_key' = [client_key EXCEPT ![c].locking = CLIENT_KEY[c]] - /\ SendReqs({[type |-> "lock_key", - start_ts |-> client_ts'[c].start_ts, - primary |-> CLIENT_PRIMARY[c], - key |-> k, - for_update_ts |-> client_ts'[c].for_update_ts] : k \in CLIENT_KEY[c]}) - /\ UNCHANGED <<resp_msgs, key_vars>> - -ClientLockedKey(c) == - /\ client_state[c] = "locking" - /\ \E resp \in resp_msgs : - /\ resp.type = "locked_key" - /\ resp.start_ts = client_ts[c].start_ts - /\ resp.key \in client_key[c].locking - /\ client_key' = [client_key EXCEPT ![c].locking = @ \ {resp.key}] - /\ UNCHANGED <<msg_vars, key_vars, client_ts, client_state, next_ts>> - -ClientRetryLockKey(c) == - /\ client_state[c] = "locking" - /\ \E resp \in resp_msgs : - /\ resp.type = "lock_failed" - /\ resp.start_ts = client_ts[c].start_ts - /\ resp.latest_commit_ts > client_ts[c].for_update_ts - /\ client_ts' = [client_ts EXCEPT ![c].for_update_ts = resp.latest_commit_ts] - /\ SendReqs({[type |-> "lock_key", - start_ts |-> client_ts'[c].start_ts, - primary |-> CLIENT_PRIMARY[c], - key |-> resp.key, - for_update_ts |-> client_ts'[c].for_update_ts]}) - /\ UNCHANGED <<resp_msgs, key_vars, client_state, client_key, next_ts>> - -ClientPrewritePessimistic(c) == - /\ client_state[c] = "locking" - /\ client_key[c].locking = {} - /\ client_state' = [client_state EXCEPT ![c] = "prewriting"] - /\ client_key' = [client_key EXCEPT ![c].prewriting = CLIENT_KEY[c]] - /\ SendReqs({[type |-> "prewrite_pessimistic", - start_ts |-> client_ts[c].start_ts, - primary |-> CLIENT_PRIMARY[c], - key |-> k] : k \in CLIENT_KEY[c]}) - /\ UNCHANGED <<resp_msgs, key_vars, client_ts, next_ts>> - -ClientPrewriteOptimisistic(c) == - /\ client_state[c] = "init" - /\ client_state' = [client_state EXCEPT ![c] = "prewriting"] - /\ client_ts' = [client_ts EXCEPT ![c].start_ts = next_ts] - /\ next_ts' = next_ts + 1 - /\ client_key' = [client_key EXCEPT ![c].prewriting = CLIENT_KEY[c]] - /\ SendReqs({[type |-> "prewrite_optimistic", - start_ts |-> client_ts'[c].start_ts, - primary |-> CLIENT_PRIMARY[c], - key |-> k] : k \in CLIENT_KEY[c]}) - /\ UNCHANGED <<resp_msgs, key_vars>> - -ClientPrewrited(c) == - /\ client_state[c] = "prewriting" - /\ client_key[c].locking = {} - /\ \E resp \in resp_msgs : - /\ resp.type = "prewrited" - /\ resp.start_ts = client_ts[c].start_ts - /\ resp.key \in client_key[c].prewriting - /\ client_key' = [client_key EXCEPT ![c].prewriting = @ \ {resp.key}] - /\ UNCHANGED <<msg_vars, key_vars, client_ts, client_state, next_ts>> - -ClientCommit(c) == - /\ client_state[c] = "prewriting" - /\ client_key[c].prewriting = {} - /\ client_state' = [client_state EXCEPT ![c] = "committing"] - /\ client_ts' = [client_ts EXCEPT ![c].commit_ts = next_ts] - /\ next_ts' = next_ts + 1 - /\ SendReqs({[type |-> "commit", - start_ts |-> client_ts'[c].start_ts, - primary |-> CLIENT_PRIMARY[c], - commit_ts |-> client_ts'[c].commit_ts]}) - /\ UNCHANGED <<resp_msgs, key_vars, client_key>> ------------------------------------------------------------------------------ -\* Server Actions - -\* Write the write column and unlock the lock iff the lock exists. -commit(pk, start_ts, commit_ts) == - \E l \in key_lock[pk] : - /\ l.ts = start_ts - /\ key_lock' = [key_lock EXCEPT ![pk] = {}] - /\ key_write' = [key_write EXCEPT ![pk] = @ \union {[ts |-> commit_ts, - type |-> "write", - start_ts |-> start_ts]}] - -\* Rollback the transaction that starts at start_ts on key k. -rollback(k, start_ts) == - LET - \* Rollback record on the primary key of a pessimistic transaction - \* needs to be protected from being collapsed. If we can't decide - \* whether it suffices that because the lock is missing or mismatched, - \* it should also be protected. - protected == \/ \E l \in key_lock[k] : - /\ l.ts = start_ts - /\ l.primary = k - /\ l.type \in {"lock_key", "prewrite_pessimistic"} - \/ \E l \in key_lock[k] : l.ts /= start_ts - \/ key_lock[k] = {} - IN - \* If a lock exists and has the same ts, unlock it. - /\ IF \E l \in key_lock[k] : l.ts = start_ts - THEN key_lock' = [key_lock EXCEPT ![k] = {}] - ELSE UNCHANGED key_lock - /\ key_data' = [key_data EXCEPT ![k] = @ \ {start_ts}] - /\ IF - /\ ~ \E w \in key_write[k]: w.ts = start_ts - THEN - key_write' = [key_write EXCEPT - ![k] = - \* collapse rollback - (@ \ {w \in @ : w.type = "rollback" /\ ~w.protected /\ w.ts < start_ts}) - \* write rollback record - \union {[ts |-> start_ts, - start_ts |-> start_ts, - type |-> "rollback", - protected |-> protected]}] - ELSE - UNCHANGED <<key_write>> - -ServerLockKey == - \E req \in req_msgs : - /\ req.type = "lock_key" - /\ LET - k == req.key - start_ts == req.start_ts - IN - \* Pessimistic lock is allowed only if no stale lock exists. If - \* there is one, wait until ServerCleanupStaleLock to clean it up. - /\ key_lock[k] = {} - /\ LET - latest_write == {w \in key_write[k] : \A w2 \in key_write[k] : w.ts >= w2.ts} - - all_commits == {w \in key_write[k] : w.type = "write"} - latest_commit == {w \in all_commits : \A w2 \in all_commits : w.ts >= w2.ts} - IN - IF \E w \in key_write[k] : w.start_ts = start_ts /\ w.type = "rollback" - THEN - \* If corresponding rollback record is found, which - \* indicates that the transcation is rollbacked, abort the - \* transaction. - /\ SendResp([start_ts |-> start_ts, type |-> "lock_key_aborted"]) - /\ UNCHANGED <<req_msgs, client_vars, key_vars, next_ts>> - ELSE - \* Acquire pessimistic lock only if for_update_ts of req - \* is greater or equal to the latest "write" record. - \* Because if the latest record is "write", it means that - \* a new version is committed after for_update_ts, which - \* violates Read Committed guarantee. - \/ /\ ~ \E w \in latest_commit : w.ts > req.for_update_ts - /\ key_lock' = [key_lock EXCEPT ![k] = {[ts |-> start_ts, - primary |-> req.primary, - type |-> "lock_key"]}] - /\ SendResp([start_ts |-> start_ts, type |-> "locked_key", key |-> k]) - /\ UNCHANGED <<req_msgs, client_vars, key_data, key_write, next_ts>> - \* Otherwise, reject the request and let client to retry - \* with new for_update_ts. - \/ \E w \in latest_commit : - /\ w.ts > req.for_update_ts - /\ SendResp([start_ts |-> start_ts, - type |-> "lock_failed", - key |-> k, - latest_commit_ts |-> w.ts]) - /\ UNCHANGED <<req_msgs, client_vars, key_vars, next_ts>> - -ServerPrewritePessimistic == - \E req \in req_msgs : - /\ req.type = "prewrite_pessimistic" - /\ LET - k == req.key - start_ts == req.start_ts - IN - \* Pessimistic prewrite is allowed only if pressimistic lock is - \* acquired, otherwise abort the transaction. - /\ IF \E l \in key_lock[k] : l.ts = start_ts - THEN - /\ key_lock' = [key_lock EXCEPT ![k] = {[ts |-> start_ts, - primary |-> req.primary, - type |-> "prewrite_pessimistic"]}] - /\ key_data' = [key_data EXCEPT ![k] = @ \union {start_ts}] - /\ SendResp([start_ts |-> start_ts, type |-> "prewrited", key |-> k]) - /\ UNCHANGED <<req_msgs, client_vars, key_write, next_ts>> - ELSE - /\ SendResp([start_ts |-> start_ts, type |-> "prewrite_aborted"]) - /\ UNCHANGED <<req_msgs, client_vars, key_vars, next_ts>> - -ServerPrewriteOptimistic == - \E req \in req_msgs : - /\ req.type = "prewrite_optimistic" - /\ LET - k == req.key - start_ts == req.start_ts - IN - /\ IF \E w \in key_write[k] : w.ts >= start_ts - THEN - /\ SendResp([start_ts |-> start_ts, type |-> "prewrite_aborted"]) - /\ UNCHANGED <<req_msgs, client_vars, key_vars, next_ts>> - ELSE - \* Optimistic prewrite is allowed only if no stale lock exists. If - \* there is one, wait until ServerCleanupStaleLock to clean it up. - /\ \/ key_lock[k] = {} - \/ \E l \in key_lock[k] : l.ts = start_ts - /\ key_lock' = [key_lock EXCEPT ![k] = {[ts |-> start_ts, - primary |-> req.primary, - type |-> "prewrite_optimistic"]}] - /\ key_data' = [key_data EXCEPT ![k] = @ \union {start_ts}] - /\ SendResp([start_ts |-> start_ts, type |-> "prewrited", key |-> k]) - /\ UNCHANGED <<req_msgs, client_vars, key_write, next_ts>> - -ServerCommit == - \E req \in req_msgs : - /\ req.type = "commit" - /\ LET - pk == req.primary - start_ts == req.start_ts - IN - IF \E w \in key_write[pk] : w.start_ts = start_ts /\ w.type = "write" - THEN - \* Key has already been committed. Do nothing. - /\ SendResp([start_ts |-> start_ts, type |-> "committed"]) - /\ UNCHANGED <<req_msgs, client_vars, key_vars, next_ts>> - ELSE - IF \E l \in key_lock[pk] : l.ts = start_ts - THEN - \* Commit the key only if the prewrite lock exists. - /\ commit(pk, start_ts, req.commit_ts) - /\ SendResp([start_ts |-> start_ts, type |-> "committed"]) - /\ UNCHANGED <<req_msgs, client_vars, key_data, next_ts>> - ELSE - \* Otherwise, abort the transaction. - /\ SendResp([start_ts |-> start_ts, type |-> "commit_aborted"]) - /\ UNCHANGED <<req_msgs, client_vars, key_vars, next_ts>> - -\* In the spec, the primary key with a lock may clean up itself -\* spontaneously. There is no need to model a client to request clean up -\* because there is no difference between a optimistic client trying to -\* read a key that has lock timeouted and the key trying to unlock itself. -ServerCleanupStaleLock == - \E k \in KEY : - \E l \in key_lock[k] : - /\ SendReqs({[type |-> "cleanup", - start_ts |-> l.ts, - primary |-> l.primary]}) - /\ UNCHANGED <<resp_msgs, client_vars, key_vars, next_ts>> - -\* Clean up stale locks by checking the status of the primary key. Commmit -\* the secondary keys if primary key is committed; otherwise rollback the -\* transaction by rolling-back the primary key, and then also rollback the -\* secondarys. -ServerCleanup == - \E req \in req_msgs : - /\ req.type = "cleanup" - /\ LET - pk == req.primary - start_ts == req.start_ts - committed == {w \in key_write[pk] : w.start_ts = start_ts /\ w.type = "write"} - IN - IF committed /= {} - THEN - /\ SendReqs({[type |-> "resolve_committed", - start_ts |-> start_ts, - primary |-> pk, - commit_ts |-> w.ts] : w \in committed}) - /\ UNCHANGED <<resp_msgs, client_vars, key_vars, next_ts>> - ELSE - /\ rollback(pk, start_ts) - /\ SendReqs({[type |-> "resolve_rollbacked", - start_ts |-> start_ts, - primary |-> pk]}) - /\ UNCHANGED <<resp_msgs, client_vars, next_ts>> - -ServerResolveCommitted == - \E req \in req_msgs : - /\ req.type = "resolve_committed" - /\ LET - start_ts == req.start_ts - IN - \E k \in KEY: - \E l \in key_lock[k] : - /\ l.primary = req.primary - /\ l.ts = start_ts - /\ commit(k, start_ts, req.commit_ts) - /\ UNCHANGED <<msg_vars, client_vars, key_data, next_ts>> - -ServerResolveRollbacked == - \E req \in req_msgs : - /\ req.type = "resolve_rollbacked" - /\ LET - start_ts == req.start_ts - IN - \E k \in KEY: - \E l \in key_lock[k] : - /\ l.primary = req.primary - /\ l.ts = start_ts - /\ rollback(k, start_ts) - /\ UNCHANGED <<msg_vars, client_vars, next_ts>> ------------------------------------------------------------------------------ -\* Specification - -Init == - /\ next_ts = 1 - /\ req_msgs = {} - /\ resp_msgs = {} - /\ client_state = [c \in CLIENT |-> "init"] - /\ client_key = [c \in CLIENT |-> [locking |-> {}, prewriting |-> {}]] - /\ client_ts = [c \in CLIENT |-> [start_ts |-> NoneTs, - commit_ts |-> NoneTs, - for_update_ts |-> NoneTs]] - /\ key_lock = [k \in KEY |-> {}] - /\ key_data = [k \in KEY |-> {}] - /\ key_write = [k \in KEY |-> {}] - -Next == - \/ \E c \in OPTIMISTIC_CLIENT : - \/ ClientPrewriteOptimisistic(c) - \/ ClientPrewrited(c) - \/ ClientCommit(c) - \/ \E c \in PESSIMISTIC_CLIENT : - \/ ClientLockKey(c) - \/ ClientLockedKey(c) - \/ ClientRetryLockKey(c) - \/ ClientPrewritePessimistic(c) - \/ ClientPrewrited(c) - \/ ClientCommit(c) - \/ ServerLockKey - \/ ServerPrewritePessimistic - \/ ServerPrewriteOptimistic - \/ ServerCommit - \/ ServerCleanupStaleLock - \/ ServerCleanup - \/ ServerResolveCommitted - \/ ServerResolveRollbacked - -Spec == Init /\ [][Next]_vars ------------------------------------------------------------------------------ -\* Consistency Invariants - -\* Check whether there is a "write" record in key_write[k] corresponding -\* to start_ts. -keyCommitted(k, start_ts) == - \E w \in key_write[k] : - /\ w.start_ts = start_ts - /\ w.type = "write" - -\* A transaction can't be both committed and aborted. -UniqueCommitOrAbort == - \A resp, resp2 \in resp_msgs : - (resp.type = "committed") /\ (resp2.type = "commit_aborted") => - resp.start_ts /= resp2.start_ts - -\* If a transaction is committed, the primary key must be committed and -\* the secondary keys of the same transaction must be either committed -\* or locked. -CommitConsistency == - \A resp \in resp_msgs : - (resp.type = "committed") => - \E c \in CLIENT : - /\ client_ts[c].start_ts = resp.start_ts - \* Primary key must be committed - /\ keyCommitted(CLIENT_PRIMARY[c], resp.start_ts) - \* Secondary key must be either committed or locked by the - \* start_ts of the transaction. - /\ \A k \in CLIENT_KEY[c] : - (~ \E l \in key_lock[k] : l.ts = resp.start_ts) = - keyCommitted(k, resp.start_ts) - -\* If a transaction is aborted, all key of that transaction must be not -\* committed. -AbortConsistency == - \A resp \in resp_msgs : - (resp.type = "commit_aborted") => - \A c \in CLIENT : - (client_ts[c].start_ts = resp.start_ts) => - ~ keyCommitted(CLIENT_PRIMARY[c], resp.start_ts) - -\* For each write, the commit_ts should be strictly greater than the -\* start_ts and have data written into key_data[k]. For each rollback, -\* the commit_ts should equals to the start_ts. -WriteConsistency == - \A k \in KEY : - \A w \in key_write[k] : - \/ /\ w.type = "write" - /\ w.ts > w.start_ts - /\ w.start_ts \in key_data[k] - \/ /\ w.type = "rollback" - /\ w.ts = w.start_ts - -\* When the lock exists, there can't be a corresponding commit record, -\* vice versa. -UniqueLockOrWrite == - \A k \in KEY : - \A l \in key_lock[k] : - \A w \in key_write[k] : - w.start_ts /= l.ts - -\* For each key, ecah record in write column should have a unique start_ts. -UniqueWrite == - \A k \in KEY : - \A w, w2 \in key_write[k] : - (w.start_ts = w2.start_ts) => (w = w2) ------------------------------------------------------------------------------ -\* Snapshot Isolation - -\* Asserts that next_ts is monotonically increasing. -NextTsMonotonicity == - \A ts \in Ts : - (ts <= next_ts) => [](ts <= next_ts) - -\* Asserts that no msg would be deleted once sent. -MsgMonotonicity == - /\ \A req \in ReqMessages : - req \in req_msgs => [](req \in req_msgs) - /\ \A resp \in RespMessages : - resp \in resp_msgs => [](resp \in resp_msgs) - -\* Asserts that all messages sent should have ts less than next_ts. -MsgTsConsistency == - /\ \A req \in req_msgs : - /\ req.start_ts <= next_ts - /\ req.type \in {"commit", "resolve_committed"} => - req.commit_ts <= next_ts - /\ \A resp \in resp_msgs : resp.start_ts <= next_ts - -\* SnapshotIsolation is implied from the following assumptions (but is not -\* nessesary), because SnapshotIsolation means that: -\* (1) Once a transcation is committed, all keys of the transaction should -\* be always readable or have lock on secondary keys(eventually readable). -\* PROOF BY CommitConsistency, MsgConsistency -\* (2) For a given transaction, all transaction that commits after that -\* transaction should have greater commit_ts than the next_ts at the -\* time that the given transaction commits, so as to be able to -\* distinguish the transactions that commits before and after -\* from all transactions that preserved by (1). -\* PROOF BY NextTsConsistency, MsgTsConsistency -\* (3) All aborted transactions would be always not readable. -\* PROOF BY AbortConsistency, MsgConsistency -SnapshotIsolation == /\ CommitConsistency - /\ AbortConsistency - /\ NextTsMonotonicity - /\ MsgMonotonicity - /\ MsgTsConsistency ------------------------------------------------------------------------------ -THEOREM Safety == - Spec => [](/\ TypeOK - /\ UniqueCommitOrAbort - /\ CommitConsistency - /\ AbortConsistency - /\ WriteConsistency - /\ UniqueLockOrWrite - /\ UniqueWrite - /\ SnapshotIsolation) -============================================================================= +----------------------- MODULE DistributedTransaction ----------------------- +EXTENDS Integers + +\* The set of all keys. +CONSTANTS KEY + +\* The sets of optiimistic clients and pessimistic clients. +CONSTANTS OPTIMISTIC_CLIENT, PESSIMISTIC_CLIENT +CLIENT == PESSIMISTIC_CLIENT \union OPTIMISTIC_CLIENT + +\* CLIENT_KEY is a set of [Client -> SUBSET KEY] +\* representing the involved keys of each client. +CONSTANTS CLIENT_KEY +ASSUME \A c \in CLIENT: CLIENT_KEY[c] \subseteq KEY + +\* CLIENT_PRIMARY is the primary key of each client. +CONSTANTS CLIENT_PRIMARY +ASSUME \A c \in CLIENT: CLIENT_PRIMARY[c] \in CLIENT_KEY[c] + +\* Timestamp of transactions. +Ts == Nat \ {0} +NoneTs == 0 + +\* The algorithm is easier to understand in terms of the set of msgs of +\* all messages that have ever been sent. A more accurate model would use +\* one or more variables to represent the messages actually in transit, +\* and it would include actions representing message loss and duplication +\* as well as message receipt. +\* +\* In the current spec, there is no need to model message loss because we +\* are mainly concerned with the algorithm's safety property. The safety +\* part of the spec says only what messages may be received and does not +\* assert that any message actually is received. Thus, there is no +\* difference between a lost message and one that is never received. +VARIABLES req_msgs +VARIABLES resp_msgs + +\* key_data[k] is the set of multi-version data of the key. Since we +\* don't care about the concrete value of data, a start_ts is sufficient +\* to represent one data version. +VARIABLES key_data +\* key_lock[k] is the set of lock (zero or one element). A lock is of a +\* record of [ts: start_ts, primary: key, type: lock_type]. If primary +\* equals to k, it is a primary lock, otherwise secondary lock. lock_type +\* is one of {"prewrite_optimistic", "prewrite_pessimistic", "lock_key"}. +\* lock_key denotes the pessimistic lock performed by ServerLockKey +\* action, the prewrite_pessimistic denotes percolator optimistic lock +\* who is transformed from a lock_key lock by action +\* ServerPrewritePessimistic, and prewrite_optimistic denotes the +\* classic optimistic lock. +\* +\* In TiKV, key_lock has an additional for_update_ts field and the +\* LockType is of four variants: +\* {"PUT", "DELETE", "LOCK", "PESSIMISTIC"}. +\* +\* In the spec, we abstract them by: +\* (1) LockType \in {"PUT", "DELETE", "LOCK"} /\ for_update_ts = 0 <=> +\* type = "prewrite_optimistic" +\* (2) LockType \in {"PUT", "DELETE"} /\ for_update_ts > 0 <=> +\* type = "prewrite_pessimistic" +\* (3) LockType = "PESSIMISTIC" <=> type = "lock_key" +VARIABLES key_lock +\* key_write[k] is a sequence of commit or rollback record of the key. +\* It's a record of [ts, start_ts, type, [protected]]. type can be eihter +\* "write" or "rollback". ts represents the commit_ts of "write" record. +\* Otherwise, ts equals to start_ts on "rollback" record. "rollback" +\* record has an additional protected field. protected signifies the +\* rollback record would not be collapsed. +VARIABLES key_write + +\* client_state[c] indicates the current transaction stage of client c. +VARIABLES client_state +\* client_ts[c] is a record of [start_ts, commit_ts, for_update_ts]. +\* Fields are all initialized to NoneTs. +VARIABLES client_ts +\* client_key[c] is a record of [locking: {key}, prewriting: {key}]. +\* Hereby, "locking" denotes the keys whose pessimistic locks +\* haven't been acquired, "prewriting" denotes the keys that are pending +\* for prewrite. +VARIABLES client_key + +\* next_ts is a globally monotonically increasing integer, respresenting +\* the virtual clock of transactions. In practice, the variable is +\* maintained by PD, the time oracle of a cluster. +VARIABLES next_ts + +msg_vars == <<req_msgs, resp_msgs>> +client_vars == <<client_state, client_ts, client_key>> +key_vars == <<key_data, key_lock, key_write>> +vars == <<msg_vars, client_vars, key_vars, next_ts>> + +SendReqs(msgs) == req_msgs' = req_msgs \union msgs +SendResp(msg) == resp_msgs' = resp_msgs \union {msg} +----------------------------------------------------------------------------- +\* Type Definitions + +ReqMessages == + [start_ts : Ts, primary : KEY, type : {"lock_key"}, key : KEY, + for_update_ts : Ts] + \union [start_ts : Ts, primary : KEY, type : {"prewrite_optimistic"}, key : KEY] + \union [start_ts : Ts, primary : KEY, type : {"prewrite_pessimistic"}, key : KEY] + \union [start_ts : Ts, primary : KEY, type : {"commit"}, commit_ts : Ts] + \union [start_ts : Ts, primary : KEY, type : {"cleanup"}] + \union [start_ts : Ts, primary : KEY, type : {"resolve_rollbacked"}] + \union [start_ts : Ts, primary : KEY, type : {"resolve_committed"}, commit_ts : Ts] + +RespMessages == + [start_ts : Ts, type : {"prewrited", "locked_key"}, key : KEY] + \union [start_ts : Ts, type : {"lock_failed"}, key : KEY, latest_commit_ts : Ts] + \union [start_ts : Ts, type : {"committed", + "commit_aborted", + "prewrite_aborted", + "lock_key_aborted"}] + +TypeOK == /\ req_msgs \in SUBSET ReqMessages + /\ resp_msgs \in SUBSET RespMessages + /\ key_data \in [KEY -> SUBSET Ts] + /\ key_lock \in [KEY -> SUBSET [ts : Ts, + primary : KEY, + type : {"prewrite_optimistic", + "prewrite_pessimistic", + "lock_key"}]] + /\ \A k \in KEY : + \* At most one lock in key_lock[k] + \A l, l2 \in key_lock[k] : + l = l2 + /\ key_write \in [KEY -> SUBSET ( + [ts : Ts, start_ts : Ts, type : {"write"}] + \union [ts : Ts, start_ts : Ts, type : {"rollback"}, protected : BOOLEAN])] + /\ client_state \in [CLIENT -> {"init", "locking", "prewriting", "committing"}] + /\ client_ts \in [CLIENT -> [start_ts : Ts \union {NoneTs}, + commit_ts : Ts \union {NoneTs}, + for_update_ts : Ts \union {NoneTs}]] + /\ client_key \in [CLIENT -> [locking: SUBSET KEY, prewriting : SUBSET KEY]] + /\ next_ts \in Ts +----------------------------------------------------------------------------- +\* Client Actions + +ClientLockKey(c) == + /\ client_state[c] = "init" + /\ client_state' = [client_state EXCEPT ![c] = "locking"] + /\ client_ts' = [client_ts EXCEPT ![c].start_ts = next_ts, ![c].for_update_ts = next_ts] + /\ next_ts' = next_ts + 1 + \* Assume we need to acquire pessimistic locks for all keys + /\ client_key' = [client_key EXCEPT ![c].locking = CLIENT_KEY[c]] + /\ SendReqs({[type |-> "lock_key", + start_ts |-> client_ts'[c].start_ts, + primary |-> CLIENT_PRIMARY[c], + key |-> k, + for_update_ts |-> client_ts'[c].for_update_ts] : k \in CLIENT_KEY[c]}) + /\ UNCHANGED <<resp_msgs, key_vars>> + +ClientLockedKey(c) == + /\ client_state[c] = "locking" + /\ \E resp \in resp_msgs : + /\ resp.type = "locked_key" + /\ resp.start_ts = client_ts[c].start_ts + /\ resp.key \in client_key[c].locking + /\ client_key' = [client_key EXCEPT ![c].locking = @ \ {resp.key}] + /\ UNCHANGED <<msg_vars, key_vars, client_ts, client_state, next_ts>> + +ClientRetryLockKey(c) == + /\ client_state[c] = "locking" + /\ \E resp \in resp_msgs : + /\ resp.type = "lock_failed" + /\ resp.start_ts = client_ts[c].start_ts + /\ resp.latest_commit_ts > client_ts[c].for_update_ts + /\ client_ts' = [client_ts EXCEPT ![c].for_update_ts = resp.latest_commit_ts] + /\ SendReqs({[type |-> "lock_key", + start_ts |-> client_ts'[c].start_ts, + primary |-> CLIENT_PRIMARY[c], + key |-> resp.key, + for_update_ts |-> client_ts'[c].for_update_ts]}) + /\ UNCHANGED <<resp_msgs, key_vars, client_state, client_key, next_ts>> + +ClientPrewritePessimistic(c) == + /\ client_state[c] = "locking" + /\ client_key[c].locking = {} + /\ client_state' = [client_state EXCEPT ![c] = "prewriting"] + /\ client_key' = [client_key EXCEPT ![c].prewriting = CLIENT_KEY[c]] + /\ SendReqs({[type |-> "prewrite_pessimistic", + start_ts |-> client_ts[c].start_ts, + primary |-> CLIENT_PRIMARY[c], + key |-> k] : k \in CLIENT_KEY[c]}) + /\ UNCHANGED <<resp_msgs, key_vars, client_ts, next_ts>> + +ClientPrewriteOptimisistic(c) == + /\ client_state[c] = "init" + /\ client_state' = [client_state EXCEPT ![c] = "prewriting"] + /\ client_ts' = [client_ts EXCEPT ![c].start_ts = next_ts] + /\ next_ts' = next_ts + 1 + /\ client_key' = [client_key EXCEPT ![c].prewriting = CLIENT_KEY[c]] + /\ SendReqs({[type |-> "prewrite_optimistic", + start_ts |-> client_ts'[c].start_ts, + primary |-> CLIENT_PRIMARY[c], + key |-> k] : k \in CLIENT_KEY[c]}) + /\ UNCHANGED <<resp_msgs, key_vars>> + +ClientPrewrited(c) == + /\ client_state[c] = "prewriting" + /\ client_key[c].locking = {} + /\ \E resp \in resp_msgs : + /\ resp.type = "prewrited" + /\ resp.start_ts = client_ts[c].start_ts + /\ resp.key \in client_key[c].prewriting + /\ client_key' = [client_key EXCEPT ![c].prewriting = @ \ {resp.key}] + /\ UNCHANGED <<msg_vars, key_vars, client_ts, client_state, next_ts>> + +ClientCommit(c) == + /\ client_state[c] = "prewriting" + /\ client_key[c].prewriting = {} + /\ client_state' = [client_state EXCEPT ![c] = "committing"] + /\ client_ts' = [client_ts EXCEPT ![c].commit_ts = next_ts] + /\ next_ts' = next_ts + 1 + /\ SendReqs({[type |-> "commit", + start_ts |-> client_ts'[c].start_ts, + primary |-> CLIENT_PRIMARY[c], + commit_ts |-> client_ts'[c].commit_ts]}) + /\ UNCHANGED <<resp_msgs, key_vars, client_key>> +----------------------------------------------------------------------------- +\* Server Actions + +\* Write the write column and unlock the lock iff the lock exists. +commit(pk, start_ts, commit_ts) == + \E l \in key_lock[pk] : + /\ l.ts = start_ts + /\ key_lock' = [key_lock EXCEPT ![pk] = {}] + /\ key_write' = [key_write EXCEPT ![pk] = @ \union {[ts |-> commit_ts, + type |-> "write", + start_ts |-> start_ts]}] + +\* Rollback the transaction that starts at start_ts on key k. +rollback(k, start_ts) == + LET + \* Rollback record on the primary key of a pessimistic transaction + \* needs to be protected from being collapsed. If we can't decide + \* whether it suffices that because the lock is missing or mismatched, + \* it should also be protected. + protected == \/ \E l \in key_lock[k] : + /\ l.ts = start_ts + /\ l.primary = k + /\ l.type \in {"lock_key", "prewrite_pessimistic"} + \/ \E l \in key_lock[k] : l.ts /= start_ts + \/ key_lock[k] = {} + IN + \* If a lock exists and has the same ts, unlock it. + /\ IF \E l \in key_lock[k] : l.ts = start_ts + THEN key_lock' = [key_lock EXCEPT ![k] = {}] + ELSE UNCHANGED key_lock + /\ key_data' = [key_data EXCEPT ![k] = @ \ {start_ts}] + /\ IF + /\ ~ \E w \in key_write[k]: w.ts = start_ts + THEN + key_write' = [key_write EXCEPT + ![k] = + \* collapse rollback + (@ \ {w \in @ : w.type = "rollback" /\ ~w.protected /\ w.ts < start_ts}) + \* write rollback record + \union {[ts |-> start_ts, + start_ts |-> start_ts, + type |-> "rollback", + protected |-> protected]}] + ELSE + UNCHANGED <<key_write>> + +ServerLockKey == + \E req \in req_msgs : + /\ req.type = "lock_key" + /\ LET + k == req.key + start_ts == req.start_ts + IN + \* Pessimistic lock is allowed only if no stale lock exists. If + \* there is one, wait until ServerCleanupStaleLock to clean it up. + /\ key_lock[k] = {} + /\ LET + latest_write == {w \in key_write[k] : \A w2 \in key_write[k] : w.ts >= w2.ts} + + all_commits == {w \in key_write[k] : w.type = "write"} + latest_commit == {w \in all_commits : \A w2 \in all_commits : w.ts >= w2.ts} + IN + IF \E w \in key_write[k] : w.start_ts = start_ts /\ w.type = "rollback" + THEN + \* If corresponding rollback record is found, which + \* indicates that the transcation is rollbacked, abort the + \* transaction. + /\ SendResp([start_ts |-> start_ts, type |-> "lock_key_aborted"]) + /\ UNCHANGED <<req_msgs, client_vars, key_vars, next_ts>> + ELSE + \* Acquire pessimistic lock only if for_update_ts of req + \* is greater or equal to the latest "write" record. + \* Because if the latest record is "write", it means that + \* a new version is committed after for_update_ts, which + \* violates Read Committed guarantee. + \/ /\ ~ \E w \in latest_commit : w.ts > req.for_update_ts + /\ key_lock' = [key_lock EXCEPT ![k] = {[ts |-> start_ts, + primary |-> req.primary, + type |-> "lock_key"]}] + /\ SendResp([start_ts |-> start_ts, type |-> "locked_key", key |-> k]) + /\ UNCHANGED <<req_msgs, client_vars, key_data, key_write, next_ts>> + \* Otherwise, reject the request and let client to retry + \* with new for_update_ts. + \/ \E w \in latest_commit : + /\ w.ts > req.for_update_ts + /\ SendResp([start_ts |-> start_ts, + type |-> "lock_failed", + key |-> k, + latest_commit_ts |-> w.ts]) + /\ UNCHANGED <<req_msgs, client_vars, key_vars, next_ts>> + +ServerPrewritePessimistic == + \E req \in req_msgs : + /\ req.type = "prewrite_pessimistic" + /\ LET + k == req.key + start_ts == req.start_ts + IN + \* Pessimistic prewrite is allowed only if pressimistic lock is + \* acquired, otherwise abort the transaction. + /\ IF \E l \in key_lock[k] : l.ts = start_ts + THEN + /\ key_lock' = [key_lock EXCEPT ![k] = {[ts |-> start_ts, + primary |-> req.primary, + type |-> "prewrite_pessimistic"]}] + /\ key_data' = [key_data EXCEPT ![k] = @ \union {start_ts}] + /\ SendResp([start_ts |-> start_ts, type |-> "prewrited", key |-> k]) + /\ UNCHANGED <<req_msgs, client_vars, key_write, next_ts>> + ELSE + /\ SendResp([start_ts |-> start_ts, type |-> "prewrite_aborted"]) + /\ UNCHANGED <<req_msgs, client_vars, key_vars, next_ts>> + +ServerPrewriteOptimistic == + \E req \in req_msgs : + /\ req.type = "prewrite_optimistic" + /\ LET + k == req.key + start_ts == req.start_ts + IN + /\ IF \E w \in key_write[k] : w.ts >= start_ts + THEN + /\ SendResp([start_ts |-> start_ts, type |-> "prewrite_aborted"]) + /\ UNCHANGED <<req_msgs, client_vars, key_vars, next_ts>> + ELSE + \* Optimistic prewrite is allowed only if no stale lock exists. If + \* there is one, wait until ServerCleanupStaleLock to clean it up. + /\ \/ key_lock[k] = {} + \/ \E l \in key_lock[k] : l.ts = start_ts + /\ key_lock' = [key_lock EXCEPT ![k] = {[ts |-> start_ts, + primary |-> req.primary, + type |-> "prewrite_optimistic"]}] + /\ key_data' = [key_data EXCEPT ![k] = @ \union {start_ts}] + /\ SendResp([start_ts |-> start_ts, type |-> "prewrited", key |-> k]) + /\ UNCHANGED <<req_msgs, client_vars, key_write, next_ts>> + +ServerCommit == + \E req \in req_msgs : + /\ req.type = "commit" + /\ LET + pk == req.primary + start_ts == req.start_ts + IN + IF \E w \in key_write[pk] : w.start_ts = start_ts /\ w.type = "write" + THEN + \* Key has already been committed. Do nothing. + /\ SendResp([start_ts |-> start_ts, type |-> "committed"]) + /\ UNCHANGED <<req_msgs, client_vars, key_vars, next_ts>> + ELSE + IF \E l \in key_lock[pk] : l.ts = start_ts + THEN + \* Commit the key only if the prewrite lock exists. + /\ commit(pk, start_ts, req.commit_ts) + /\ SendResp([start_ts |-> start_ts, type |-> "committed"]) + /\ UNCHANGED <<req_msgs, client_vars, key_data, next_ts>> + ELSE + \* Otherwise, abort the transaction. + /\ SendResp([start_ts |-> start_ts, type |-> "commit_aborted"]) + /\ UNCHANGED <<req_msgs, client_vars, key_vars, next_ts>> + +\* In the spec, the primary key with a lock may clean up itself +\* spontaneously. There is no need to model a client to request clean up +\* because there is no difference between a optimistic client trying to +\* read a key that has lock timeouted and the key trying to unlock itself. +ServerCleanupStaleLock == + \E k \in KEY : + \E l \in key_lock[k] : + /\ SendReqs({[type |-> "cleanup", + start_ts |-> l.ts, + primary |-> l.primary]}) + /\ UNCHANGED <<resp_msgs, client_vars, key_vars, next_ts>> + +\* Clean up stale locks by checking the status of the primary key. Commmit +\* the secondary keys if primary key is committed; otherwise rollback the +\* transaction by rolling-back the primary key, and then also rollback the +\* secondarys. +ServerCleanup == + \E req \in req_msgs : + /\ req.type = "cleanup" + /\ LET + pk == req.primary + start_ts == req.start_ts + committed == {w \in key_write[pk] : w.start_ts = start_ts /\ w.type = "write"} + IN + IF committed /= {} + THEN + /\ SendReqs({[type |-> "resolve_committed", + start_ts |-> start_ts, + primary |-> pk, + commit_ts |-> w.ts] : w \in committed}) + /\ UNCHANGED <<resp_msgs, client_vars, key_vars, next_ts>> + ELSE + /\ rollback(pk, start_ts) + /\ SendReqs({[type |-> "resolve_rollbacked", + start_ts |-> start_ts, + primary |-> pk]}) + /\ UNCHANGED <<resp_msgs, client_vars, next_ts>> + +ServerResolveCommitted == + \E req \in req_msgs : + /\ req.type = "resolve_committed" + /\ LET + start_ts == req.start_ts + IN + \E k \in KEY: + \E l \in key_lock[k] : + /\ l.primary = req.primary + /\ l.ts = start_ts + /\ commit(k, start_ts, req.commit_ts) + /\ UNCHANGED <<msg_vars, client_vars, key_data, next_ts>> + +ServerResolveRollbacked == + \E req \in req_msgs : + /\ req.type = "resolve_rollbacked" + /\ LET + start_ts == req.start_ts + IN + \E k \in KEY: + \E l \in key_lock[k] : + /\ l.primary = req.primary + /\ l.ts = start_ts + /\ rollback(k, start_ts) + /\ UNCHANGED <<msg_vars, client_vars, next_ts>> +----------------------------------------------------------------------------- +\* Specification + +Init == + /\ next_ts = 1 + /\ req_msgs = {} + /\ resp_msgs = {} + /\ client_state = [c \in CLIENT |-> "init"] + /\ client_key = [c \in CLIENT |-> [locking |-> {}, prewriting |-> {}]] + /\ client_ts = [c \in CLIENT |-> [start_ts |-> NoneTs, + commit_ts |-> NoneTs, + for_update_ts |-> NoneTs]] + /\ key_lock = [k \in KEY |-> {}] + /\ key_data = [k \in KEY |-> {}] + /\ key_write = [k \in KEY |-> {}] + +Next == + \/ \E c \in OPTIMISTIC_CLIENT : + \/ ClientPrewriteOptimisistic(c) + \/ ClientPrewrited(c) + \/ ClientCommit(c) + \/ \E c \in PESSIMISTIC_CLIENT : + \/ ClientLockKey(c) + \/ ClientLockedKey(c) + \/ ClientRetryLockKey(c) + \/ ClientPrewritePessimistic(c) + \/ ClientPrewrited(c) + \/ ClientCommit(c) + \/ ServerLockKey + \/ ServerPrewritePessimistic + \/ ServerPrewriteOptimistic + \/ ServerCommit + \/ ServerCleanupStaleLock + \/ ServerCleanup + \/ ServerResolveCommitted + \/ ServerResolveRollbacked + +Spec == Init /\ [][Next]_vars +----------------------------------------------------------------------------- +\* Consistency Invariants + +\* Check whether there is a "write" record in key_write[k] corresponding +\* to start_ts. +keyCommitted(k, start_ts) == + \E w \in key_write[k] : + /\ w.start_ts = start_ts + /\ w.type = "write" + +\* A transaction can't be both committed and aborted. +UniqueCommitOrAbort == + \A resp, resp2 \in resp_msgs : + (resp.type = "committed") /\ (resp2.type = "commit_aborted") => + resp.start_ts /= resp2.start_ts + +\* If a transaction is committed, the primary key must be committed and +\* the secondary keys of the same transaction must be either committed +\* or locked. +CommitConsistency == + \A resp \in resp_msgs : + (resp.type = "committed") => + \E c \in CLIENT : + /\ client_ts[c].start_ts = resp.start_ts + \* Primary key must be committed + /\ keyCommitted(CLIENT_PRIMARY[c], resp.start_ts) + \* Secondary key must be either committed or locked by the + \* start_ts of the transaction. + /\ \A k \in CLIENT_KEY[c] : + (~ \E l \in key_lock[k] : l.ts = resp.start_ts) = + keyCommitted(k, resp.start_ts) + +\* If a transaction is aborted, all key of that transaction must be not +\* committed. +AbortConsistency == + \A resp \in resp_msgs : + (resp.type = "commit_aborted") => + \A c \in CLIENT : + (client_ts[c].start_ts = resp.start_ts) => + ~ keyCommitted(CLIENT_PRIMARY[c], resp.start_ts) + +\* For each write, the commit_ts should be strictly greater than the +\* start_ts and have data written into key_data[k]. For each rollback, +\* the commit_ts should equals to the start_ts. +WriteConsistency == + \A k \in KEY : + \A w \in key_write[k] : + \/ /\ w.type = "write" + /\ w.ts > w.start_ts + /\ w.start_ts \in key_data[k] + \/ /\ w.type = "rollback" + /\ w.ts = w.start_ts + +\* When the lock exists, there can't be a corresponding commit record, +\* vice versa. +UniqueLockOrWrite == + \A k \in KEY : + \A l \in key_lock[k] : + \A w \in key_write[k] : + w.start_ts /= l.ts + +\* For each key, ecah record in write column should have a unique start_ts. +UniqueWrite == + \A k \in KEY : + \A w, w2 \in key_write[k] : + (w.start_ts = w2.start_ts) => (w = w2) +----------------------------------------------------------------------------- +\* Snapshot Isolation + +\* Asserts that next_ts is monotonically increasing. +NextTsMonotonicity == + \A ts \in Ts : + (ts <= next_ts) => [](ts <= next_ts) + +\* Asserts that no msg would be deleted once sent. +MsgMonotonicity == + /\ \A req \in ReqMessages : + req \in req_msgs => [](req \in req_msgs) + /\ \A resp \in RespMessages : + resp \in resp_msgs => [](resp \in resp_msgs) + +\* Asserts that all messages sent should have ts less than next_ts. +MsgTsConsistency == + /\ \A req \in req_msgs : + /\ req.start_ts <= next_ts + /\ req.type \in {"commit", "resolve_committed"} => + req.commit_ts <= next_ts + /\ \A resp \in resp_msgs : resp.start_ts <= next_ts + +\* SnapshotIsolation is implied from the following assumptions (but is not +\* nessesary), because SnapshotIsolation means that: +\* (1) Once a transcation is committed, all keys of the transaction should +\* be always readable or have lock on secondary keys(eventually readable). +\* PROOF BY CommitConsistency, MsgMonotonicity +\* (2) For a given transaction, all transaction that commits after that +\* transaction should have greater commit_ts than the next_ts at the +\* time that the given transaction commits, so as to be able to +\* distinguish the transactions that commits before and after +\* from all transactions that preserved by (1). +\* PROOF BY NextTsConsistency, MsgTsConsistency +\* (3) All aborted transactions would be always not readable. +\* PROOF BY AbortConsistency, MsgMonotonicity +SnapshotIsolation == /\ CommitConsistency + /\ AbortConsistency + /\ NextTsMonotonicity + /\ MsgMonotonicity + /\ MsgTsConsistency +----------------------------------------------------------------------------- +THEOREM Safety == + Spec => [](/\ TypeOK + /\ UniqueCommitOrAbort + /\ CommitConsistency + /\ AbortConsistency + /\ WriteConsistency + /\ UniqueLockOrWrite + /\ UniqueWrite + /\ SnapshotIsolation) +=============================================================================