(Implementing New Requirements) Significant improvement
Refining the design
How do we prevent the cleaner from deleting items just as they are being written? Well, the common sense solution is to check the creation time. We should only start cleaning a key after a safe window of time has passed since its creation. Let’s call it 2 hours.
Storage Cleaner Run
Assumptions
We are making one main assumption: that all of our clocks are accurate within a reasonable margin of error (5 minutes is generous), and our code errs on the side of not deleting based on those margins.
While you can’t always make assumptions about clock time in distributed systems, in this case our time frames are so large (hours) that it’s probably not a bad assumption. Note: this doesn’t mean a program will necessarily check its clock. It could stall and then resume what it’s doing an hour later.
Modeling the design
Now we have to introduce a concept of time in the model. You might think we’ve been using time throughout this whole tutorial, but actually, we were just using ordering. We will need to add the concept of creation time to the blob store. Keep in mind that the state diagram has not changed.
Note: This isn’t adding functionality. We’re just modeling details that have become relevant.
Verifying Storage Cleaner
We’re going to start off with the slightly larger model (two servers and two cleaners), since the last test didn’t show behavioral differences. Might as well perform the more rigorous test.
Invariant ConsistentReads is violated.
- 1. Initial predicate
- blobStoreState
- ui3
- image: UNSET
- status: UNSET
- ui2
- image: UNSET
- status: UNSET
- ui1
- image: UNSET
- status: UNSET
- ui3
- cleanerStates
- c2
- state: waiting
- blobKeys:
- unusedBlobKeys:
- c1
- state: waiting
- blobKeys:
- unusedBlobKeys:
- c2
- databaseState
- u1
- metadata: UNSET
- imageId: UNSET
- u1
- operations :
- serverStates
- s2
- metadata: UNSET
- imageId: UNSET
- image: UNSET
- state: waiting
- userId: UNSET
- s1
- metadata: UNSET
- imageId: UNSET
- image: UNSET
- state: waiting
- userId: UNSET
- s2
- time : 0
- blobStoreState
- 2. ServerStartWrite Server s1 starts write at time 0
- blobStoreState
- ui3
- image: UNSET
- status: UNSET
- ui2
- image: UNSET
- status: UNSET
- ui1
- image: UNSET
- status: UNSET
- ui3
- cleanerStates
- c2
- state: waiting
- blobKeys:
- unusedBlobKeys:
- c1
- state: waiting
- blobKeys:
- unusedBlobKeys:
- c2
- databaseState
- u1
- metadata: UNSET
- imageId: UNSET
- u1
- operations
-
- metadata : m1
- image : i1
- userId : u1
- type : WRITE
-
- serverStates
- s2
- metadata: UNSET
- imageId: UNSET
- image: UNSET
- state: waiting
- userId: UNSET
- s1
- metadata: m1
- imageId: UNSET
- image: i1
- state: started_write
- userId: u1
- s2
- time : 0
- blobStoreState
- 3. ServerWriteBlob and successfully writes blob with id 1
- blobStoreState
- ui3
- image: UNSET
- status: UNSET
- ui2
- image: UNSET
- status: UNSET
- ui1
- image: i1
- created: 0
- ui3
- cleanerStates
- c2
- state: waiting
- blobKeys:
- unusedBlobKeys:
- c1
- state: waiting
- blobKeys:
- unusedBlobKeys:
- c2
- databaseState
- u1
- metadata: UNSET
- imageId: UNSET
- u1
- operations
-
- metadata : m1
- image : i1
- userId : u1
- type : WRITE
-
- serverStates
- s2
- metadata: UNSET
- imageId: UNSET
- image: UNSET
- state: waiting
- userId: UNSET
- s1
- metadata: m1
- imageId: ui1
- image: i1
- state: wrote_blob
- userId: u1
- s2
- time : 0
- blobStoreState
- 4. TimePasses Server s1 hangs at this point for 2 hours
- blobStoreState
- ui3
- image: UNSET
- status: UNSET
- ui2
- image: UNSET
- status: UNSET
- ui1
- image: i1
- created: 0
- ui3
- cleanerStates
- c2
- state: waiting
- blobKeys:
- unusedBlobKeys:
- c1
- state: waiting
- blobKeys:
- unusedBlobKeys:
- c2
- databaseState
- u1
- metadata: UNSET
- imageId: UNSET
- u1
- operations
-
- metadata : m1
- image : i1
- userId : u1
- type : WRITE
-
- serverStates
- s2
- metadata: UNSET
- imageId: UNSET
- image: UNSET
- state: waiting
- userId: UNSET
- s1
- metadata: m1
- imageId: ui1
- image: i1
- state: wrote_blob
- userId: u1
- s2
- time : 1
- blobStoreState
- 5. TimePasses
- blobStoreState
- ui3
- image: UNSET
- status: UNSET
- ui2
- image: UNSET
- status: UNSET
- ui1
- image: i1
- created: 0
- ui3
- cleanerStates
- c2
- state: waiting
- blobKeys:
- unusedBlobKeys:
- c1
- state: waiting
- blobKeys:
- unusedBlobKeys:
- c2
- databaseState
- u1
- metadata: UNSET
- imageId: UNSET
- u1
- operations
-
- metadata : m1
- image : i1
- userId : u1
- type : WRITE
-
- serverStates
- s2
- metadata: UNSET
- imageId: UNSET
- image: UNSET
- state: waiting
- userId: UNSET
- s1
- metadata: m1
- imageId: ui1
- image: i1
- state: wrote_blob
- userId: u1
- s2
- time : 2
- blobStoreState
- 6. ServerStartRead Server s2 starts to read a record for this user
- blobStoreState
- ui3
- image: UNSET
- status: UNSET
- ui2
- image: UNSET
- status: UNSET
- ui1
- image: i1
- created: 0
- ui3
- cleanerStates
- c2
- state: waiting
- blobKeys:
- unusedBlobKeys:
- c1
- state: waiting
- blobKeys:
- unusedBlobKeys:
- c2
- databaseState
- u1
- metadata: UNSET
- imageId: UNSET
- u1
- operations
-
- metadata : m1
- image : i1
- userId : u1
- type : WRITE
-
- serverStates
- s2
- metadata: UNSET
- imageId: UNSET
- image: UNSET
- state: started_read
- userId: u1
- s1
- metadata: m1
- imageId: ui1
- image: i1
- state: wrote_blob
- userId: u1
- s2
- time : 2
- blobStoreState
- 7. CleanerStartGetBlobKeys The cleaner starts and gets the blob id 1 because 2 hours has passed
- blobStoreState
- ui3
- image: UNSET
- status: UNSET
- ui2
- image: UNSET
- status: UNSET
- ui1
- image: i1
- created: 0
- ui3
- cleanerStates
- c2
- state: waiting
- blobKeys:
- unusedBlobKeys:
- c1
- state: got_blob_keys
- blobKeys: ui1
- unusedBlobKeys:
- c2
- databaseState
- u1
- metadata: UNSET
- imageId: UNSET
- u1
- operations
-
- metadata : m1
- image : i1
- userId : u1
- type : WRITE
-
- serverStates
- s2
- metadata: UNSET
- imageId: UNSET
- image: UNSET
- state: started_read
- userId: u1
- s1
- metadata: m1
- imageId: ui1
- image: i1
- state: wrote_blob
- userId: u1
- s2
- time : 2
- blobStoreState
- 8. CleanerGetUnusedKeys Blob id 1 is not in database, because write did not complete
- blobStoreState
- ui3
- image: UNSET
- status: UNSET
- ui2
- image: UNSET
- status: UNSET
- ui1
- image: i1
- created: 0
- ui3
- cleanerStates
- c2
- state: waiting
- blobKeys:
- unusedBlobKeys:
- c1
- state: got_unused_keys
- blobKeys: ui1
- unusedBlobKeys: ui1
- c2
- databaseState
- u1
- metadata: UNSET
- imageId: UNSET
- u1
- operations
-
- metadata : m1
- image : i1
- userId : u1
- type : WRITE
-
- serverStates
- s2
- metadata: UNSET
- imageId: UNSET
- image: UNSET
- state: started_read
- userId: u1
- s1
- metadata: m1
- imageId: ui1
- image: i1
- state: wrote_blob
- userId: u1
- s2
- time : 2
- blobStoreState
- 9. ServerWriteMetadataAndReturn Server s1 finishes writing
- blobStoreState
- ui3
- image: UNSET
- status: UNSET
- ui2
- image: UNSET
- status: UNSET
- ui1
- image: i1
- created: 0
- ui3
- cleanerStates
- c2
- state: waiting
- blobKeys:
- unusedBlobKeys:
- c1
- state: got_unused_keys
- blobKeys: ui1
- unusedBlobKeys: ui1
- c2
- databaseState
- u1
- metadata: m1
- imageId: ui1
- u1
- operations
-
- metadata : m1
- image : i1
- userId : u1
- type : WRITE
-
- serverStates
- s2
- metadata: UNSET
- imageId: UNSET
- image: UNSET
- state: started_read
- userId: u1
- s1
- metadata: UNSET
- imageId: UNSET
- image: UNSET
- state: waiting
- userId: UNSET
- s2
- time : 2
- blobStoreState
- 10. ServerReadMetadata Server s2 reads the record under the assumption nothing is wrong
- blobStoreState
- ui3
- image: UNSET
- status: UNSET
- ui2
- image: UNSET
- status: UNSET
- ui1
- image: i1
- created: 0
- ui3
- cleanerStates
- c2
- state: waiting
- blobKeys:
- unusedBlobKeys:
- c1
- state: got_unused_keys
- blobKeys: ui1
- unusedBlobKeys: ui1
- c2
- databaseState
- u1
- metadata: m1
- imageId: ui1
- u1
- operations
-
- metadata : m1
- image : i1
- userId : u1
- type : WRITE
-
- serverStates
- s2
- metadata: m1
- imageId: ui1
- image: UNSET
- state: read_metadata
- userId: u1
- s1
- metadata: UNSET
- imageId: UNSET
- image: UNSET
- state: waiting
- userId: UNSET
- s2
- time : 2
- blobStoreState
- 11. CleanerDeletingKeys The cleaner deletes blob with id 1
- blobStoreState
- ui3
- image: UNSET
- status: UNSET
- ui2
- image: UNSET
- status: UNSET
- ui1
- image: UNSET
- status: UNSET
- ui3
- cleanerStates
- c2
- state: waiting
- blobKeys:
- unusedBlobKeys:
- c1
- state: got_unused_keys
- blobKeys: ui1
- unusedBlobKeys:
- c2
- databaseState
- u1
- metadata: m1
- imageId: ui1
- u1
- operations
-
- metadata : m1
- image : i1
- userId : u1
- type : WRITE
-
- serverStates
- s2
- metadata: m1
- imageId: ui1
- image: UNSET
- state: read_metadata
- userId: u1
- s1
- metadata: UNSET
- imageId: UNSET
- image: UNSET
- state: waiting
- userId: UNSET
- s2
- time : 2
- blobStoreState
- 12. ServerReadBlobAndReturn Server s2 reads an invalid blob
- blobStoreState
- ui3
- image: UNSET
- status: UNSET
- ui2
- image: UNSET
- status: UNSET
- ui1
- image: UNSET
- status: UNSET
- ui3
- cleanerStates
- c2
- state: waiting
- blobKeys:
- unusedBlobKeys:
- c1
- state: got_unused_keys
- blobKeys: ui1
- unusedBlobKeys:
- c2
- databaseState
- u1
- metadata: m1
- imageId: ui1
- u1
- operations
-
- metadata : m1
- image : i1
- userId : u1
- type : WRITE
-
- metadata : m1
- image : UNSET
- userId : u1
- type : READ
-
- serverStates
- s2
- metadata: UNSET
- imageId: UNSET
- image: UNSET
- state: waiting
- userId: UNSET
- s1
- metadata: UNSET
- imageId: UNSET
- image: UNSET
- state: waiting
- userId: UNSET
- s2
- time : 2
- blobStoreState
The error we saw previously is gone, implying we fixed the design flaw we hoped to fix. This new error is much more complex. It requires the Server to stall at just the wrong time and be out of commission for 2 hours. This is pretty unlikely; in fact, it’s unlikely enough that some companies might be okay with it. But the fix is obvious: kill the servers after 1 hour of stalling or less. Chances are we were going to do it anyway in implementation, but let’s model it to get the extra assurance.
A quick fix
All the changes in the model are in the server behavior. Despite the large number of changes, all we’re really saying is that if less than an hour has passed since the server request started, it can proceed to the next state. Otherwise, it proceeds to the restart state.
This can be reflected in an updated state diagram for Server writes:
This is reflected in the code below.
Looks good, right? Let’s test it.
Close but no cigar. Invariant ConsistentReads is violated.
- 1. Initial predicate Let's see what's going on here
- blobStoreState
- ui3
- image: UNSET
- status: UNSET
- ui2
- image: UNSET
- status: UNSET
- ui1
- image: UNSET
- status: UNSET
- ui3
- cleanerStates
- c2
- state: waiting
- blobKeys:
- unusedBlobKeys:
- c1
- state: waiting
- blobKeys:
- unusedBlobKeys:
- c2
- databaseState
- u1
- metadata: UNSET
- imageId: UNSET
- u1
- operations :
- serverStates
- s2
- metadata: UNSET
- imageId: UNSET
- image: UNSET
- state: waiting
- userId: UNSET
- start: 0
- s1
- metadata: UNSET
- imageId: UNSET
- image: UNSET
- state: waiting
- userId: UNSET
- start: 0
- s2
- time : 0
- blobStoreState
- 2. TimePasses
- blobStoreState
- ui3
- image: UNSET
- status: UNSET
- ui2
- image: UNSET
- status: UNSET
- ui1
- image: UNSET
- status: UNSET
- ui3
- cleanerStates
- c2
- state: waiting
- blobKeys:
- unusedBlobKeys:
- c1
- state: waiting
- blobKeys:
- unusedBlobKeys:
- c2
- databaseState
- u1
- metadata: UNSET
- imageId: UNSET
- u1
- operations :
- serverStates
- s2
- metadata: UNSET
- imageId: UNSET
- image: UNSET
- state: waiting
- userId: UNSET
- start: 0
- s1
- metadata: UNSET
- imageId: UNSET
- image: UNSET
- state: waiting
- userId: UNSET
- start: 0
- s2
- time : 1
- blobStoreState
- 3. ServerStartWrite
- blobStoreState
- ui3
- image: UNSET
- status: UNSET
- ui2
- image: UNSET
- status: UNSET
- ui1
- image: UNSET
- status: UNSET
- ui3
- cleanerStates
- c2
- state: waiting
- blobKeys:
- unusedBlobKeys:
- c1
- state: waiting
- blobKeys:
- unusedBlobKeys:
- c2
- databaseState
- u1
- metadata: UNSET
- imageId: UNSET
- u1
- operations
-
- metadata : m1
- image : i1
- userId : u1
- type : WRITE
-
- serverStates
- s2
- metadata: UNSET
- imageId: UNSET
- image: UNSET
- state: waiting
- userId: UNSET
- start: 0
- s1
- metadata: m1
- imageId: UNSET
- image: i1
- state: started_write
- userId: u1
- start: 1
- s2
- time : 1
- blobStoreState
- 4. ServerWriteBlob
- blobStoreState
- ui3
- image: UNSET
- status: UNSET
- ui2
- image: UNSET
- status: UNSET
- ui1
- image: i1
- created: 1
- ui3
- cleanerStates
- c2
- state: waiting
- blobKeys:
- unusedBlobKeys:
- c1
- state: waiting
- blobKeys:
- unusedBlobKeys:
- c2
- databaseState
- u1
- metadata: UNSET
- imageId: UNSET
- u1
- operations
-
- metadata : m1
- image : i1
- userId : u1
- type : WRITE
-
- serverStates
- s2
- metadata: UNSET
- imageId: UNSET
- image: UNSET
- state: waiting
- userId: UNSET
- start: 0
- s1
- metadata: m1
- imageId: ui1
- image: i1
- state: wrote_blob
- userId: u1
- start: 1
- s2
- time : 1
- blobStoreState
- 5. ServerWriteMetadataAndReturn A server successfully writes
- blobStoreState
- ui3
- image: UNSET
- status: UNSET
- ui2
- image: UNSET
- status: UNSET
- ui1
- image: i1
- created: 1
- ui3
- cleanerStates
- c2
- state: waiting
- blobKeys:
- unusedBlobKeys:
- c1
- state: waiting
- blobKeys:
- unusedBlobKeys:
- c2
- databaseState
- u1
- metadata: m1
- imageId: ui1
- u1
- operations
-
- metadata : m1
- image : i1
- userId : u1
- type : WRITE
-
- serverStates
- s2
- metadata: UNSET
- imageId: UNSET
- image: UNSET
- state: waiting
- userId: UNSET
- start: 0
- s1
- metadata: UNSET
- imageId: UNSET
- image: UNSET
- state: waiting
- userId: UNSET
- start: 1
- s2
- time : 1
- blobStoreState
- 6. TimePasses
- blobStoreState
- ui3
- image: UNSET
- status: UNSET
- ui2
- image: UNSET
- status: UNSET
- ui1
- image: i1
- created: 1
- ui3
- cleanerStates
- c2
- state: waiting
- blobKeys:
- unusedBlobKeys:
- c1
- state: waiting
- blobKeys:
- unusedBlobKeys:
- c2
- databaseState
- u1
- metadata: m1
- imageId: ui1
- u1
- operations
-
- metadata : m1
- image : i1
- userId : u1
- type : WRITE
-
- serverStates
- s2
- metadata: UNSET
- imageId: UNSET
- image: UNSET
- state: waiting
- userId: UNSET
- start: 0
- s1
- metadata: UNSET
- imageId: UNSET
- image: UNSET
- state: waiting
- userId: UNSET
- start: 1
- s2
- time : 2
- blobStoreState
- 7. TimePasses 2 hours pass
- blobStoreState
- ui3
- image: UNSET
- status: UNSET
- ui2
- image: UNSET
- status: UNSET
- ui1
- image: i1
- created: 1
- ui3
- cleanerStates
- c2
- state: waiting
- blobKeys:
- unusedBlobKeys:
- c1
- state: waiting
- blobKeys:
- unusedBlobKeys:
- c2
- databaseState
- u1
- metadata: m1
- imageId: ui1
- u1
- operations
-
- metadata : m1
- image : i1
- userId : u1
- type : WRITE
-
- serverStates
- s2
- metadata: UNSET
- imageId: UNSET
- image: UNSET
- state: waiting
- userId: UNSET
- start: 0
- s1
- metadata: UNSET
- imageId: UNSET
- image: UNSET
- state: waiting
- userId: UNSET
- start: 1
- s2
- time : 3
- blobStoreState
- 8. ServerStartWrite A server starts to update that same record
- blobStoreState
- ui3
- image: UNSET
- status: UNSET
- ui2
- image: UNSET
- status: UNSET
- ui1
- image: i1
- created: 1
- ui3
- cleanerStates
- c2
- state: waiting
- blobKeys:
- unusedBlobKeys:
- c1
- state: waiting
- blobKeys:
- unusedBlobKeys:
- c2
- databaseState
- u1
- metadata: m1
- imageId: ui1
- u1
- operations
-
- metadata : m1
- image : i1
- userId : u1
- type : WRITE
-
- metadata : m1
- image : i1
- userId : u1
- type : WRITE
-
- serverStates
- s2
- metadata: UNSET
- imageId: UNSET
- image: UNSET
- state: waiting
- userId: UNSET
- start: 0
- s1
- metadata: m1
- imageId: UNSET
- image: i1
- state: started_write
- userId: u1
- start: 3
- s2
- time : 3
- blobStoreState
- 9. ServerWriteBlob
- blobStoreState
- ui3
- image: UNSET
- status: UNSET
- ui2
- image: i1
- created: 3
- ui1
- image: i1
- created: 1
- ui3
- cleanerStates
- c2
- state: waiting
- blobKeys:
- unusedBlobKeys:
- c1
- state: waiting
- blobKeys:
- unusedBlobKeys:
- c2
- databaseState
- u1
- metadata: m1
- imageId: ui1
- u1
- operations
-
- metadata : m1
- image : i1
- userId : u1
- type : WRITE
-
- metadata : m1
- image : i1
- userId : u1
- type : WRITE
-
- serverStates
- s2
- metadata: UNSET
- imageId: UNSET
- image: UNSET
- state: waiting
- userId: UNSET
- start: 0
- s1
- metadata: m1
- imageId: ui2
- image: i1
- state: wrote_blob
- userId: u1
- start: 3
- s2
- time : 3
- blobStoreState
- 10. ServerStartRead A read is starting at the same time. No worries, we've handled this
- blobStoreState
- ui3
- image: UNSET
- status: UNSET
- ui2
- image: i1
- created: 3
- ui1
- image: i1
- created: 1
- ui3
- cleanerStates
- c2
- state: waiting
- blobKeys:
- unusedBlobKeys:
- c1
- state: waiting
- blobKeys:
- unusedBlobKeys:
- c2
- databaseState
- u1
- metadata: m1
- imageId: ui1
- u1
- operations
-
- metadata : m1
- image : i1
- userId : u1
- type : WRITE
-
- metadata : m1
- image : i1
- userId : u1
- type : WRITE
-
- serverStates
- s2
- metadata: UNSET
- imageId: UNSET
- image: UNSET
- state: started_read
- userId: u1
- start: 3
- s1
- metadata: m1
- imageId: ui2
- image: i1
- state: wrote_blob
- userId: u1
- start: 3
- s2
- time : 3
- blobStoreState
- 11. ServerReadMetadata Reads data that is about to change: image id is ui1
- blobStoreState
- ui3
- image: UNSET
- status: UNSET
- ui2
- image: i1
- created: 3
- ui1
- image: i1
- created: 1
- ui3
- cleanerStates
- c2
- state: waiting
- blobKeys:
- unusedBlobKeys:
- c1
- state: waiting
- blobKeys:
- unusedBlobKeys:
- c2
- databaseState
- u1
- metadata: m1
- imageId: ui1
- u1
- operations
-
- metadata : m1
- image : i1
- userId : u1
- type : WRITE
-
- metadata : m1
- image : i1
- userId : u1
- type : WRITE
-
- serverStates
- s2
- metadata: m1
- imageId: ui1
- image: UNSET
- state: read_metadata
- userId: u1
- start: 3
- s1
- metadata: m1
- imageId: ui2
- image: i1
- state: wrote_blob
- userId: u1
- start: 3
- s2
- time : 3
- blobStoreState
- 12. ServerWriteMetadataAndReturn Write finishes, removing ui1 from the database
- blobStoreState
- ui3
- image: UNSET
- status: UNSET
- ui2
- image: i1
- created: 3
- ui1
- image: i1
- created: 1
- ui3
- cleanerStates
- c2
- state: waiting
- blobKeys:
- unusedBlobKeys:
- c1
- state: waiting
- blobKeys:
- unusedBlobKeys:
- c2
- databaseState
- u1
- metadata: m1
- imageId: ui2
- u1
- operations
-
- metadata : m1
- image : i1
- userId : u1
- type : WRITE
-
- metadata : m1
- image : i1
- userId : u1
- type : WRITE
-
- serverStates
- s2
- metadata: m1
- imageId: ui1
- image: UNSET
- state: read_metadata
- userId: u1
- start: 3
- s1
- metadata: UNSET
- imageId: UNSET
- image: UNSET
- state: waiting
- userId: UNSET
- start: 3
- s2
- time : 3
- blobStoreState
- 13. CleanerStartGetBlobKeys The cleaner pounces and deletes ui1
- blobStoreState
- ui3
- image: UNSET
- status: UNSET
- ui2
- image: i1
- created: 3
- ui1
- image: i1
- created: 1
- ui3
- cleanerStates
- c2
- state: got_blob_keys
- blobKeys: ui1
- unusedBlobKeys:
- c1
- state: waiting
- blobKeys:
- unusedBlobKeys:
- c2
- databaseState
- u1
- metadata: m1
- imageId: ui2
- u1
- operations
-
- metadata : m1
- image : i1
- userId : u1
- type : WRITE
-
- metadata : m1
- image : i1
- userId : u1
- type : WRITE
-
- serverStates
- s2
- metadata: m1
- imageId: ui1
- image: UNSET
- state: read_metadata
- userId: u1
- start: 3
- s1
- metadata: UNSET
- imageId: UNSET
- image: UNSET
- state: waiting
- userId: UNSET
- start: 3
- s2
- time : 3
- blobStoreState
- 14. CleanerGetUnusedKeys
- blobStoreState
- ui3
- image: UNSET
- status: UNSET
- ui2
- image: i1
- created: 3
- ui1
- image: i1
- created: 1
- ui3
- cleanerStates
- c2
- state: got_unused_keys
- blobKeys: ui1
- unusedBlobKeys: ui1
- c1
- state: waiting
- blobKeys:
- unusedBlobKeys:
- c2
- databaseState
- u1
- metadata: m1
- imageId: ui2
- u1
- operations
-
- metadata : m1
- image : i1
- userId : u1
- type : WRITE
-
- metadata : m1
- image : i1
- userId : u1
- type : WRITE
-
- serverStates
- s2
- metadata: m1
- imageId: ui1
- image: UNSET
- state: read_metadata
- userId: u1
- start: 3
- s1
- metadata: UNSET
- imageId: UNSET
- image: UNSET
- state: waiting
- userId: UNSET
- start: 3
- s2
- time : 3
- blobStoreState
- 15. CleanerDeletingKeys
- blobStoreState
- ui3
- image: UNSET
- status: UNSET
- ui2
- image: i1
- created: 3
- ui1
- image: UNSET
- status: UNSET
- ui3
- cleanerStates
- c2
- state: got_unused_keys
- blobKeys: ui1
- unusedBlobKeys:
- c1
- state: waiting
- blobKeys:
- unusedBlobKeys:
- c2
- databaseState
- u1
- metadata: m1
- imageId: ui2
- u1
- operations
-
- metadata : m1
- image : i1
- userId : u1
- type : WRITE
-
- metadata : m1
- image : i1
- userId : u1
- type : WRITE
-
- serverStates
- s2
- metadata: m1
- imageId: ui1
- image: UNSET
- state: read_metadata
- userId: u1
- start: 3
- s1
- metadata: UNSET
- imageId: UNSET
- image: UNSET
- state: waiting
- userId: UNSET
- start: 3
- s2
- time : 3
- blobStoreState
- 16. ServerReadBlobAndReturn The reader tries to read ui1 and fails
- blobStoreState
- ui3
- image: UNSET
- status: UNSET
- ui2
- image: i1
- created: 3
- ui1
- image: UNSET
- status: UNSET
- ui3
- cleanerStates
- c2
- state: got_unused_keys
- blobKeys: ui1
- unusedBlobKeys:
- c1
- state: waiting
- blobKeys:
- unusedBlobKeys:
- c2
- databaseState
- u1
- metadata: m1
- imageId: ui2
- u1
- operations
-
- metadata : m1
- image : i1
- userId : u1
- type : WRITE
-
- metadata : m1
- image : i1
- userId : u1
- type : WRITE
-
- metadata : m1
- image : UNSET
- userId : u1
- type : READ
-
- serverStates
- s2
- metadata: UNSET
- imageId: UNSET
- image: UNSET
- state: waiting
- userId: UNSET
- start: 3
- s1
- metadata: UNSET
- imageId: UNSET
- image: UNSET
- state: waiting
- userId: UNSET
- start: 3
- s2
- time : 3
- blobStoreState
We’ve gotten closer, and our error is even more obscure. Now it requires an interaction of Storage Cleaner, a Read Server, and a Write Server. But there’s a relatively simple fix.
Note: This was the first error that I did not expect. But it is exciting that TLA+ wouldn’t let me get away with it!