github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/specs/dbnode/snapshots/SnapshotsSpec.tla (about) 1 ------------------------------- MODULE SnapshotsSpec ------------------------------- 2 EXTENDS Integers, Sequences, TLC 3 4 CONSTANTS numClients 5 CONSTANTS numWrites 6 7 \* Set to reasonable values to limit the search space that TLC needs to check. Otherwise, 8 \* the search space is infinite because its always valid for the server to perform a 9 \* persistence step. 10 CONSTANTS minNumWritesForPersistence 11 CONSTANTS minNumWritesForCleanup 12 13 AllExceptLast(seq) == SubSeq(seq, 1, Len(seq)-1) 14 15 (*************************************************************************** 16 --algorithm SnapshotsSpec 17 18 \* Writes issued and acked by the client. 19 variable 20 \* Unique identifier for the next write. 21 CurrentIndex = 0; 22 \* Writes issued by a client, acked or not. 23 IssuedWrites = {}; 24 \* Writes that have been acked by M3DB. 25 AckedWrites = {}; 26 \* Sequence of files, each of which is represented by a set which stores 27 \* the writes contained by the commitlog. 28 CommitLogFiles = << {} >>; 29 \* Sequence of snapshot checkpoint files, each of which points to the index 30 \* in the CommitLogFiles sequence which it contains all the writes up to. 31 SnapshotCheckpointFiles = << >>; 32 \* Writes persisted outside of the commitlog. 33 PersistedWrites = {}; 34 35 macro write_to_commitlog_and_ack(writes) 36 begin 37 \* Store the writes in the last (more recent / active) commitlog file. 38 CommitLogFiles[Len(CommitLogFiles)] := CommitLogFiles[Len(CommitLogFiles)] \union (writes); 39 \* Mark all writes as Acked. 40 AckedWrites := AckedWrites \union writes; 41 end macro 42 43 macro handle_snapshot() 44 begin 45 \* We haven't already started a snapshot, so rotate the commitlog and mark a snapshot as in progress. 46 if snapshotInProgress = FALSE /\ CurrentIndex-lastPersistIndex >= minNumWritesForPersistence then 47 \* "Rotate" the commitlog by adding a new one. 48 CommitLogFiles := Append(CommitLogFiles, {}); 49 snapshotInProgress := TRUE; 50 lastPersistIndex := CurrentIndex; 51 \* We've already started a snapshot, so complete it. 52 elsif snapshotInProgress = TRUE then 53 \* Sanity checks. 54 assert(Len(CommitLogFiles) >= 2); 55 56 either 57 \* Snapshot success 58 \* 59 \* Since we rotate the commitlog at the beginning of every Snapshot before doing 60 \* anything else, we know that when snapshotting is complete we can add to PersistedWrites 61 \* all the writes in all the commitlog files except for the (most recent) rotated one. 62 with allCommitlogFilesExceptLast = AllExceptLast(CommitLogFiles); 63 writesToPersist = (UNION {allCommitlogFilesExceptLast[x]: x \in DOMAIN allCommitlogFilesExceptLast}); 64 do 65 PersistedWrites := PersistedWrites \union writesToPersist; 66 \* Add a new snapshot checkpoint file which points to the commitlog file up until 67 \* which it contains all the data for. 68 SnapshotCheckpointFiles := Append(SnapshotCheckpointFiles, Len(CommitLogFiles)-1); 69 snapshotInProgress := FALSE; 70 end with 71 or 72 \* Snapshot failure 73 snapshotInProgress := FALSE; 74 end either 75 end if 76 end macro 77 78 macro handle_cleanup() 79 begin 80 if Len(SnapshotCheckpointFiles) >=1 /\ 81 CurrentIndex-lastCleanupIndex >= minNumWritesForCleanup 82 then 83 with lastSnapshottedCommitlogIndex = SnapshotCheckpointFiles[Len(SnapshotCheckpointFiles)]; 84 do 85 \* Identify the most recent snapshot metadata file, and delete all commitlogs up to 86 \* and including that one because all of thoes writes should have been snapshotted already. 87 CommitLogFiles := SubSeq(CommitLogFiles, lastSnapshottedCommitlogIndex+1, Len(CommitLogFiles)); 88 SnapshotCheckpointFiles := << >>; 89 lastCleanupIndex := CurrentIndex; 90 end with 91 end if 92 end macro 93 94 \* Server process. 95 process M3DB = 0 96 variable 97 \* Variables used for persistence state (flushing / snapshotting) 98 snapshotInProgress = FALSE; 99 100 \* Variables used for preventing background operations from occurring 101 \* infinitely. 102 lastPersistIndex = 0; 103 lastCleanupIndex = 0; 104 105 begin 106 server_loop: while TRUE do 107 either 108 \* Take all the unacked writes in IssuedWrites and put them in the commitlog and ack them. 109 write_to_commitlog_and_ack(IssuedWrites \ AckedWrites); 110 or 111 handle_snapshot(); 112 or 113 handle_cleanup(); 114 end either 115 end while 116 end process 117 118 \* Client processes. 119 process n \in 1..numClients 120 begin 121 client_loop: while CurrentIndex < numWrites do 122 IssuedWrites := IssuedWrites \union {CurrentIndex}; 123 CurrentIndex := CurrentIndex+1; 124 end while 125 end process 126 127 end algorithm; 128 ***************************************************************************) 129 \* BEGIN TRANSLATION 130 VARIABLES CurrentIndex, IssuedWrites, AckedWrites, CommitLogFiles, 131 SnapshotCheckpointFiles, PersistedWrites, pc, snapshotInProgress, 132 lastPersistIndex, lastCleanupIndex 133 134 vars == << CurrentIndex, IssuedWrites, AckedWrites, CommitLogFiles, 135 SnapshotCheckpointFiles, PersistedWrites, pc, snapshotInProgress, 136 lastPersistIndex, lastCleanupIndex >> 137 138 ProcSet == {0} \cup (1..numClients) 139 140 Init == (* Global variables *) 141 /\ CurrentIndex = 0 142 /\ IssuedWrites = {} 143 /\ AckedWrites = {} 144 /\ CommitLogFiles = << {} >> 145 /\ SnapshotCheckpointFiles = << >> 146 /\ PersistedWrites = {} 147 (* Process M3DB *) 148 /\ snapshotInProgress = FALSE 149 /\ lastPersistIndex = 0 150 /\ lastCleanupIndex = 0 151 /\ pc = [self \in ProcSet |-> CASE self = 0 -> "server_loop" 152 [] self \in 1..numClients -> "client_loop"] 153 154 server_loop == /\ pc[0] = "server_loop" 155 /\ \/ /\ CommitLogFiles' = [CommitLogFiles EXCEPT ![Len(CommitLogFiles)] = CommitLogFiles[Len(CommitLogFiles)] \union ((IssuedWrites \ AckedWrites))] 156 /\ AckedWrites' = (AckedWrites \union (IssuedWrites \ AckedWrites)) 157 /\ UNCHANGED <<SnapshotCheckpointFiles, PersistedWrites, snapshotInProgress, lastPersistIndex, lastCleanupIndex>> 158 \/ /\ IF snapshotInProgress = FALSE /\ CurrentIndex-lastPersistIndex >= minNumWritesForPersistence 159 THEN /\ CommitLogFiles' = Append(CommitLogFiles, {}) 160 /\ snapshotInProgress' = TRUE 161 /\ lastPersistIndex' = CurrentIndex 162 /\ UNCHANGED << SnapshotCheckpointFiles, 163 PersistedWrites >> 164 ELSE /\ IF snapshotInProgress = TRUE 165 THEN /\ Assert((Len(CommitLogFiles) >= 2), 166 "Failure of assertion at line 54, column 9 of macro called at line 111, column 13.") 167 /\ \/ /\ LET allCommitlogFilesExceptLast == AllExceptLast(CommitLogFiles) IN 168 LET writesToPersist == (UNION {allCommitlogFilesExceptLast[x]: x \in DOMAIN allCommitlogFilesExceptLast}) IN 169 /\ PersistedWrites' = (PersistedWrites \union writesToPersist) 170 /\ SnapshotCheckpointFiles' = Append(SnapshotCheckpointFiles, Len(CommitLogFiles)-1) 171 /\ snapshotInProgress' = FALSE 172 \/ /\ snapshotInProgress' = FALSE 173 /\ UNCHANGED <<SnapshotCheckpointFiles, PersistedWrites>> 174 ELSE /\ TRUE 175 /\ UNCHANGED << SnapshotCheckpointFiles, 176 PersistedWrites, 177 snapshotInProgress >> 178 /\ UNCHANGED << CommitLogFiles, 179 lastPersistIndex >> 180 /\ UNCHANGED <<AckedWrites, lastCleanupIndex>> 181 \/ /\ IF Len(SnapshotCheckpointFiles) >=1 /\ 182 CurrentIndex-lastCleanupIndex >= minNumWritesForCleanup 183 THEN /\ LET lastSnapshottedCommitlogIndex == SnapshotCheckpointFiles[Len(SnapshotCheckpointFiles)] IN 184 /\ CommitLogFiles' = SubSeq(CommitLogFiles, lastSnapshottedCommitlogIndex+1, Len(CommitLogFiles)) 185 /\ SnapshotCheckpointFiles' = << >> 186 /\ lastCleanupIndex' = CurrentIndex 187 ELSE /\ TRUE 188 /\ UNCHANGED << CommitLogFiles, 189 SnapshotCheckpointFiles, 190 lastCleanupIndex >> 191 /\ UNCHANGED <<AckedWrites, PersistedWrites, snapshotInProgress, lastPersistIndex>> 192 /\ pc' = [pc EXCEPT ![0] = "server_loop"] 193 /\ UNCHANGED << CurrentIndex, IssuedWrites >> 194 195 M3DB == server_loop 196 197 client_loop(self) == /\ pc[self] = "client_loop" 198 /\ IF CurrentIndex < numWrites 199 THEN /\ IssuedWrites' = (IssuedWrites \union {CurrentIndex}) 200 /\ CurrentIndex' = CurrentIndex+1 201 /\ pc' = [pc EXCEPT ![self] = "client_loop"] 202 ELSE /\ pc' = [pc EXCEPT ![self] = "Done"] 203 /\ UNCHANGED << CurrentIndex, IssuedWrites >> 204 /\ UNCHANGED << AckedWrites, CommitLogFiles, 205 SnapshotCheckpointFiles, PersistedWrites, 206 snapshotInProgress, lastPersistIndex, 207 lastCleanupIndex >> 208 209 n(self) == client_loop(self) 210 211 Next == M3DB 212 \/ (\E self \in 1..numClients: n(self)) 213 214 Spec == Init /\ [][Next]_vars 215 216 \* END TRANSLATION 217 218 \* Invariants - Add these to the model checker when running. 219 AllAckedWritesAreBootstrappable == AckedWrites \subseteq ( (UNION { CommitLogFiles[x] : x \in DOMAIN CommitLogFiles }) \union PersistedWrites) 220 ============================================================================= 221 \* Modification History 222 \* Last modified Sun Nov 25 21:19:27 EST 2018 by richardartoul 223 \* Created Sat Nov 24 16:19:03 EST 2018 by richardartoul