github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/specs/dbnode/flush/FlushVersion.tla (about) 1 ---------------------------- MODULE FlushVersion ---------------------------- 2 EXTENDS Integers, TLC 3 4 \* Constants used to limit the potential search space when 5 \* running the TLC model checker. 6 CONSTANT maxNumWrites 7 CONSTANT maxNumFlushes 8 CONSTANT maxNumTicks 9 10 (*************************************************************************** 11 \* This specification models the version-based flushing/ticking of series 12 \* buffer buckets that is required for the warm/cold write feature. The 13 \* specification models the system by running three different processes: 14 \* 15 \* 1. One process responsible for beginning and ending the flush process. 16 \* This process will begin a flush by looping through all the BucketsInMemory 17 \* and setting their LastSuccessfulFlushVersion to whatever the 18 \* CurrentLastSuccessfulFlushVersion is. It also models both flush 19 \* successes and flush failures. 20 \* Flush Success: Copy all the BucketsInMemory whose 21 |* LastSuccessfulFlushVersion is smaller than 22 \* or equal to the CurrentLastSuccessfulFlushVersion (I.E those 23 \* we could have conceivably known about at flush start time) into 24 \* the set of PersistedBuckets, and then increment the 25 \* CurrentLastSuccessfulFlushVersion. 26 \* 27 \* Flush Failure: Nothing happens and the CurrentLastSuccessfulFlushVersion stays the same. 28 \* 29 \* 30 \* 2. One process responsible for creating new buckets by issuing "writes". It does 31 \* this by storing them in the BucketsInMemory set, as well as in the 32 \* WrittenBuckets set (so we can keep track of them independently of the eviction 33 \* from memory process). 34 \* 35 \* 36 \* 3. One process responsible for the "Ticking" process that will periodically evict 37 \* every bucket from the BucketsInMemory set whose LastSuccessfulFlushVersion is >= 38 \* CurrentLastSuccessfulFlushVersion and also > 0 (because version zero is the bucket 39 \* for unflushed writes). 40 \* 41 \* 42 \* The specification verifies the system by allowing all of these processes to run 43 \* concurrently (as they do in M3DB) and asserting that the invariant that we can 44 \* always read every bucket that has been written from the combination of the 45 \* BucketsInMemory and PersistedBuckets holds true at every step (meaning we never 46 \* lose data due to the Tick evicting data that hasn't been flushed yet.) 47 --algorithm FlushVersion 48 49 variable 50 \* BucketsInMemory, WrittenBuckets, and Persisted buckets are all sets that 51 \* store records in the form: 52 \* 1. ID (int) 53 \* 2. LastSuccessfulFlushVersion (int) 54 \* 55 \* BucketsInMemory stores all the buckets that are currently in the M3DB 56 \* process's memory, WrittenBuckets stores all the buckets that the client 57 \* ever issued (to serve as a source of truth for checking invariants), and 58 \* PersistedBuckets stores all the buckets that have been flushed successfully. 59 BucketsInMemory = {[ID |-> 0, FlushVersion |-> 0]}; 60 WrittenBuckets = {[ID |-> 0, FlushVersion |-> 0]}; 61 PersistedBuckets = {}; 62 \* The last version that was successfully flushed to disk. 63 LastSuccessfulFlushVersion = 0; 64 65 \* Simulate background flushes which either succeed, copy buckets to the 66 \* PersistedBuckets set and then increment the flush version, or fail and 67 \* leave everything as is. 68 process Flush = 0 69 variable 70 CurrentIndex = 0; 71 \* Whether a flush is currently ongoing. 72 FlushInProgress = FALSE; 73 begin 74 flush_loop: while CurrentIndex < maxNumFlushes do 75 either 76 await FlushInProgress = TRUE; 77 78 either 79 \* Flush success 80 \* 81 \* Move all BucketsInMemory that have been flushed (according to 82 \* their flush version) to the PersistedBuckets set but keep them, 83 \* around in memory for now. 84 PersistedBuckets := PersistedBuckets \union { 85 x \in BucketsInMemory: 86 x.FlushVersion <= LastSuccessfulFlushVersion + 1 /\ 87 x.FlushVersion > 0}; 88 LastSuccessfulFlushVersion := LastSuccessfulFlushVersion + 1; 89 or 90 \* Flush failure - No-op 91 LastSuccessfulFlushVersion := LastSuccessfulFlushVersion; 92 end either; 93 94 FlushInProgress := FALSE; 95 or 96 await FlushInProgress = FALSE; 97 98 \* Update the flush version of all BucketsInMemory in memory to the 99 \* current value + 1 since that is what the (possibly partially complete) 100 \* flush could have done to them. 101 BucketsInMemory := { 102 [ID |-> x.ID, FlushVersion |-> LastSuccessfulFlushVersion + 1]: 103 x \in BucketsInMemory}; 104 FlushInProgress := TRUE; 105 end either; 106 107 CurrentIndex := CurrentIndex+1; 108 end while 109 end process 110 111 \* Simulate a process that is writing by putting new buckets into BucketsInMemory. 112 process Write = 1 113 variable 114 CurrentIndex = 0; 115 NextBucketID = 1; 116 begin 117 write_loop: while CurrentIndex < maxNumWrites do 118 \* Write a new bucket. Note that the FlushVersion is always zero for a new bucket. 119 BucketsInMemory := BucketsInMemory \union {[ID |-> NextBucketID, FlushVersion |-> 0]}; 120 WrittenBuckets := WrittenBuckets \union {[ID |-> NextBucketID, FlushVersion |-> 0]}; 121 NextBucketID := NextBucketID + 1; 122 CurrentIndex := CurrentIndex+1; 123 end while 124 end process 125 126 process Tick = 2 127 variable CurrentIndex = 0; 128 129 begin 130 tick_loop: while CurrentIndex < maxNumTicks do 131 \* Evict from memory any block whose flush version is smaller than or equal to the 132 \* current flush version. 133 BucketsInMemory := BucketsInMemory \ { 134 x \in BucketsInMemory: 135 x.FlushVersion <= LastSuccessfulFlushVersion /\ 136 x.FlushVersion > 0}; 137 end while 138 end process 139 140 end algorithm 141 142 143 ***************************************************************************) 144 \* BEGIN TRANSLATION 145 \* Process variable CurrentIndex of process Flush at line 66 col 9 changed to CurrentIndex_ 146 \* Process variable CurrentIndex of process Write at line 114 col 9 changed to CurrentIndex_W 147 VARIABLES BucketsInMemory, WrittenBuckets, PersistedBuckets, 148 LastSuccessfulFlushVersion, pc, CurrentIndex_, FlushInProgress, 149 CurrentIndex_W, NextBucketID, CurrentIndex 150 151 vars == << BucketsInMemory, WrittenBuckets, PersistedBuckets, 152 LastSuccessfulFlushVersion, pc, CurrentIndex_, FlushInProgress, 153 CurrentIndex_W, NextBucketID, CurrentIndex >> 154 155 ProcSet == {0} \cup {1} \cup {2} 156 157 Init == (* Global variables *) 158 /\ BucketsInMemory = {[ID |-> 0, FlushVersion |-> 0]} 159 /\ WrittenBuckets = {[ID |-> 0, FlushVersion |-> 0]} 160 /\ PersistedBuckets = {} 161 /\ LastSuccessfulFlushVersion = 0 162 (* Process Flush *) 163 /\ CurrentIndex_ = 0 164 /\ FlushInProgress = FALSE 165 (* Process Write *) 166 /\ CurrentIndex_W = 0 167 /\ NextBucketID = 1 168 (* Process Tick *) 169 /\ CurrentIndex = 0 170 /\ pc = [self \in ProcSet |-> CASE self = 0 -> "flush_loop" 171 [] self = 1 -> "write_loop" 172 [] self = 2 -> "tick_loop"] 173 174 flush_loop == /\ pc[0] = "flush_loop" 175 /\ IF CurrentIndex_ < maxNumFlushes 176 THEN /\ \/ /\ FlushInProgress = TRUE 177 /\ \/ /\ PersistedBuckets' = ( PersistedBuckets \union { 178 x \in BucketsInMemory: 179 x.FlushVersion <= LastSuccessfulFlushVersion + 1 /\ 180 x.FlushVersion > 0}) 181 /\ LastSuccessfulFlushVersion' = LastSuccessfulFlushVersion + 1 182 \/ /\ LastSuccessfulFlushVersion' = LastSuccessfulFlushVersion 183 /\ UNCHANGED PersistedBuckets 184 /\ FlushInProgress' = FALSE 185 /\ UNCHANGED BucketsInMemory 186 \/ /\ FlushInProgress = FALSE 187 /\ BucketsInMemory' = { 188 [ID |-> x.ID, FlushVersion |-> LastSuccessfulFlushVersion + 1]: 189 x \in BucketsInMemory} 190 /\ FlushInProgress' = TRUE 191 /\ UNCHANGED <<PersistedBuckets, LastSuccessfulFlushVersion>> 192 /\ CurrentIndex_' = CurrentIndex_+1 193 /\ pc' = [pc EXCEPT ![0] = "flush_loop"] 194 ELSE /\ pc' = [pc EXCEPT ![0] = "Done"] 195 /\ UNCHANGED << BucketsInMemory, PersistedBuckets, 196 LastSuccessfulFlushVersion, 197 CurrentIndex_, FlushInProgress >> 198 /\ UNCHANGED << WrittenBuckets, CurrentIndex_W, NextBucketID, 199 CurrentIndex >> 200 201 Flush == flush_loop 202 203 write_loop == /\ pc[1] = "write_loop" 204 /\ IF CurrentIndex_W < maxNumWrites 205 THEN /\ BucketsInMemory' = (BucketsInMemory \union {[ID |-> NextBucketID, FlushVersion |-> 0]}) 206 /\ WrittenBuckets' = (WrittenBuckets \union {[ID |-> NextBucketID, FlushVersion |-> 0]}) 207 /\ NextBucketID' = NextBucketID + 1 208 /\ CurrentIndex_W' = CurrentIndex_W+1 209 /\ pc' = [pc EXCEPT ![1] = "write_loop"] 210 ELSE /\ pc' = [pc EXCEPT ![1] = "Done"] 211 /\ UNCHANGED << BucketsInMemory, WrittenBuckets, 212 CurrentIndex_W, NextBucketID >> 213 /\ UNCHANGED << PersistedBuckets, LastSuccessfulFlushVersion, 214 CurrentIndex_, FlushInProgress, CurrentIndex >> 215 216 Write == write_loop 217 218 tick_loop == /\ pc[2] = "tick_loop" 219 /\ IF CurrentIndex < maxNumTicks 220 THEN /\ BucketsInMemory' = BucketsInMemory \ { 221 x \in BucketsInMemory: 222 x.FlushVersion <= LastSuccessfulFlushVersion /\ 223 x.FlushVersion > 0} 224 /\ pc' = [pc EXCEPT ![2] = "tick_loop"] 225 ELSE /\ pc' = [pc EXCEPT ![2] = "Done"] 226 /\ UNCHANGED BucketsInMemory 227 /\ UNCHANGED << WrittenBuckets, PersistedBuckets, 228 LastSuccessfulFlushVersion, CurrentIndex_, 229 FlushInProgress, CurrentIndex_W, NextBucketID, 230 CurrentIndex >> 231 232 Tick == tick_loop 233 234 Next == Flush \/ Write \/ Tick 235 \/ (* Disjunct to prevent deadlock on termination *) 236 ((\A self \in ProcSet: pc[self] = "Done") /\ UNCHANGED vars) 237 238 Spec == Init /\ [][Next]_vars 239 240 Termination == <>(\A self \in ProcSet: pc[self] = "Done") 241 242 \* END TRANSLATION 243 244 245 \* Invariants, include these when running the model checker. 246 WrittenIDs == {x.ID: x \in WrittenBuckets} 247 ReadableIDs == {x.ID: x \in (BucketsInMemory \union PersistedBuckets)} 248 DoesNotLoseData == WrittenIDs \subseteq ReadableIDs 249 250 ============================================================================= 251 \* Modification History 252 \* Last modified Fri Nov 30 19:08:55 EST 2018 by richardartoul 253 \* Created Fri Nov 30 15:08:04 EST 2018 by richardartoul