Skip to content
This repository was archived by the owner on Oct 18, 2023. It is now read-only.

Commit 4157b59

Browse files
committed
Add TLA+ specification for unified WAL replication/bottomless
1 parent 6132c30 commit 4157b59

File tree

1 file changed

+193
-0
lines changed

1 file changed

+193
-0
lines changed

tlaplus/wal_replication.tla

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
---------------------------- MODULE wal_replication ---------------------------
2+
\* A formal specification of write-ahead log (WAL) replication algorithm.
3+
\*
4+
\* The algorithm assumes the presence of a write-ahead log (WAL), like the one
5+
\* used in SQLite, where transactions append modified pages to a WAL. Each
6+
\* modified page within the WAL is referred to as a frame and is assigned a
7+
\* monotonically increasing frame index.
8+
\*
9+
\* A write is not guaranateed durability until it is backed up. Therefore,
10+
\* when recovering, primary and replicas revert back to the latest durable
11+
\* index.
12+
13+
EXTENDS Naturals, FiniteSetsExt, Sequences, SequencesExt, Bags, Functions, TLC
14+
15+
VARIABLE
16+
txID,
17+
commitIndex,
18+
commitServer,
19+
messages,
20+
logs,
21+
durableLog
22+
23+
----------------------------------------------------------------------------------
24+
\* Cluster topology.
25+
26+
CONSTANT
27+
Servers
28+
29+
CONSTANTS
30+
Node1,
31+
Node2,
32+
Node3
33+
34+
IsPrimary(d) ==
35+
d = Node1
36+
37+
Primary == Node1
38+
39+
----------------------------------------------------------------------------------
40+
\* Message passing. We assume an ordered networking with no duplicates.
41+
42+
InitMessageVar ==
43+
messages = [ s \in Servers |-> <<>>]
44+
45+
WithMessage(m, msgs) ==
46+
IF \E i \in 1..Len(msgs[m.dest]) : msgs[m.dest][i] = m THEN
47+
msgs
48+
ELSE
49+
[ msgs EXCEPT ![m.dest] = Append(@, m) ]
50+
51+
WithoutMessage(m, msgs) ==
52+
IF \E i \in 1..Len(msgs[m.dest]) : msgs[m.dest][i] = m THEN
53+
[ msgs EXCEPT ![m.dest] = RemoveAt(@, SelectInSeq(@, LAMBDA e: e = m)) ]
54+
ELSE
55+
msgs
56+
57+
Messages ==
58+
UNION { Range(messages[s]) : s \in Servers }
59+
60+
MessagesTo(dest, source) ==
61+
IF \E i \in 1..Len(messages[dest]) : messages[dest][i].source = source THEN
62+
{messages[dest][SelectInSeq(messages[dest], LAMBDA e: e.source = source)]}
63+
ELSE
64+
{}
65+
66+
Send(m) ==
67+
/\ messages' = WithMessage(m, messages)
68+
69+
Discard(m) ==
70+
messages' = WithoutMessage(m, messages)
71+
72+
----------------------------------------------------------------------------------
73+
\* Protocol
74+
75+
\* Message types:
76+
CONSTANTS
77+
GetFramesMsg,
78+
ExecuteMsg
79+
80+
RecoverLog(s) ==
81+
/\ logs' = [logs EXCEPT ![s] = durableLog]
82+
83+
Recover ==
84+
/\ \A s \in Servers: RecoverLog(s)
85+
/\ commitIndex = IF Len(durableLog) > 0 THEN Max(ToSet(durableLog)) ELSE 0
86+
/\ UNCHANGED(<<txID, commitIndex, commitServer, messages, durableLog>>)
87+
88+
SyncLog(s) ==
89+
/\ logs' = [logs EXCEPT ![s] = logs[Primary]]
90+
91+
SyncDurable(l) ==
92+
/\ durableLog' = durableLog \o l
93+
94+
AppendToLog(s, i) ==
95+
/\ logs' = [logs EXCEPT ![s] = Append(logs[s], i)]
96+
97+
HandleExecuteMsg(m) ==
98+
/\ IF IsPrimary(m.dest) THEN
99+
\* Append the write to the local WAL.
100+
/\ AppendToLog(m.dest, commitIndex + 1)
101+
ELSE
102+
\* Append the write to the WAL on the primary...
103+
/\ AppendToLog(Primary, commitIndex + 1)
104+
\* ...but also sync local WAL for read your writes.
105+
/\ SyncLog(m.dest)
106+
/\ SyncDurable(logs'[Primary])
107+
/\ commitIndex' = commitIndex + 1
108+
/\ commitServer' = m.dest
109+
/\ Discard(m)
110+
/\ UNCHANGED(<<txID>>)
111+
112+
RcvExecuteMsg(i, j) ==
113+
\E m \in MessagesTo(i, j) :
114+
/\ m.type = ExecuteMsg
115+
/\ HandleExecuteMsg(m)
116+
117+
HandleGetFramesMsg(m) ==
118+
/\ IsPrimary(m.dest)
119+
/\ SyncLog(m.source)
120+
/\ Discard(m)
121+
/\ UNCHANGED(<<txID, commitServer, commitIndex, durableLog>>)
122+
123+
RcvGetFramesMsg(i, j) ==
124+
\E m \in MessagesTo(i, j) :
125+
/\ m.type = GetFramesMsg
126+
/\ HandleGetFramesMsg(m)
127+
128+
Receive(i, j) ==
129+
\/ RcvGetFramesMsg(i, j)
130+
\/ RcvExecuteMsg(i, j)
131+
132+
SendGetFrames(s) ==
133+
LET
134+
msg == [
135+
type |-> GetFramesMsg,
136+
dest |-> Primary,
137+
source |-> s
138+
]
139+
IN
140+
/\ Send(msg)
141+
/\ UNCHANGED(<<txID, commitIndex, commitServer, logs, durableLog>>)
142+
143+
SendExecute(i, j) ==
144+
LET
145+
msg == [
146+
type |-> ExecuteMsg,
147+
txId |-> txID,
148+
dest |-> i,
149+
source |-> j
150+
]
151+
IN
152+
/\ txID' = txID + 1
153+
/\ Send(msg)
154+
/\ UNCHANGED(<<commitIndex, commitServer, logs, durableLog>>)
155+
156+
Next ==
157+
\/ Recover
158+
\/ \E i, j \in Servers: SendExecute(i, j)
159+
\/ \E s \in Servers: SendGetFrames(s)
160+
\/ \E i, j \in Servers: Receive(i, j)
161+
162+
Init ==
163+
/\ txID = 0
164+
/\ commitIndex = 0
165+
/\ commitServer = Primary
166+
/\ InitMessageVar
167+
/\ logs = [s \in Servers |-> <<>>]
168+
/\ durableLog = <<>>
169+
170+
----------------------------------------------------------------------------------
171+
\* Invariants
172+
173+
ReadYourWritesInv ==
174+
commitIndex = 0 \/ Contains(logs[commitServer], commitIndex)
175+
176+
LogsAreContinuousInv ==
177+
\A s \in Servers: Len(logs[s]) = 0 \/ \A i \in 1..Max(ToSet(logs[s])) : Contains(logs[s], i)
178+
179+
NoServerIsAheadOfPrimaryInv ==
180+
\A s \in Servers: Len(logs[s]) <= Len(logs[Primary])
181+
182+
NoDurableFramesLostInv ==
183+
\A i \in 1..commitIndex : i \in ToSet(durableLog)
184+
185+
----------------------------------------------------------------------------------
186+
\* Temporal properties
187+
188+
WriteLivenessProp == <>(commitIndex > 0)
189+
190+
ReplicationProp ==
191+
[] (commitIndex > 0 => \A s \in Servers : <> (Len(logs[s]) > 0))
192+
193+
====

0 commit comments

Comments
 (0)