-
Notifications
You must be signed in to change notification settings - Fork 31
STITCH-1954 Implement instance id and counter versioning scheme #68
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
passing tests, but no new tests add some question comments progress with removing committed versions incomplete server test changes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good! One possible minor and some q's.
// TODO(QUESTION FOR REVIEW): The testFrozenDocumentConfig test fails if I call this, but | ||
// other tests pass. Shouldn't the receipt of this document indicate that this is the last | ||
// known remote version of this document? | ||
// docConfig.setLastKnownRemoteVersion(lastKnownRemoteVersion); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[q] Where does testFrozenDocumentConfig
fail?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no longer relevant, worked it out with Eric and we now* have the correct behavior
final BsonValue documentId, | ||
final BsonDocument document, | ||
final BsonValue atVersion, | ||
final BsonDocument atVersion, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[q] Why are we passing this around as a BsonDocument? Seems it may be cleaner to use its concrete type here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is because the consumer of atVersion
is only operating on the pure BSON of the version, whereas DocumentVersionInfo
is an abstraction on top of that that we use for various other checks.
DocumentVersionInfo
is allowed to represent an empty version, meaning consumers of atVersion would then have to check for those kinds of states. I think this is fine for now, especially since consumers of atVersion
never convert back to a DocumentVersionInfo
, and they don't have any use for its methods/functionality
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
// old version, rather than the new version. Is this intentional? It seems like we should be | ||
// setting the pending writes at the newly generated version. I'm confused though because if I | ||
// use the new version here instead, tests fail because updates don't end up happening on a | ||
// sync pass. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to have had a conflict where the local version is accepted to get to this point. However, I think atVersion
is really a misnomer for lastKnownRemoteVersion
, as seen in the CoreDocumentSynchronizationConfig
. So at this point in the code, we are preparing the document with the next version, but the pending writes are still currently at the lastKnownRemoteVersion
, which is the previous version, since we are not writing to remote yet.
On the next pass, in syncLocalToRemote
(since we have resolved the prior conflict), our document will be written to the remote, and setPendingWritesComplete
will be called with the version within the document
, which is now the lastKnownRemoteVersion
.
I'd like @edaniels to double check my logic here, but I believe this is the reasoning. By committing this version to the config, we've essentially told the system that the conflict has been resolved and that this is a locally generated change event. Via both this and the logicalT
checks, it will prevent this change event from getting mistakenly consumed in a R2L pass.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm also thinking it could be nice to have this tested as a unit test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
discussed offline. this is correct
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
return versionValues != null; | ||
} | ||
|
||
private @Nonnull VersionValues getVersionValuesThrowIfNull() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[minor] Using ThrowIfNull
as a suffix here, and the exception thrown here, is arguably a bit of an anti-pattern. If this is an illegal state, we should throw that exception in the constructor– otherwise, it's on the user to call the #isNonEmptyVersion
check.
) { | ||
if (version != null) { | ||
this.versionDoc = version; | ||
this.versionValues = new VersionValues( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[minor][related to comment below] We should be checking that these keys exist on the versionDoc
before reading them.
If it the doc is null or they do not exist and this is a disallowed state, we should throw an IllegalStateException
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From what I can tell here, if the versionDoc
is null, that should be an OK state, and is covered by isNonEmptyVersion
. Maybe the exception should be if the versionDoc is malformed? Just food for thought– I think to an extent, there's a stylistic choice here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Definitely agree that this class is a little funky, I refactored it a little bit per your comments and eric's comments to make it harder to reach an illegal state.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally LGTM. There are tests to fix with the has committed writes.
|
||
assertTrue(coll.syncedIds.contains(doc1Id)) | ||
|
||
// syncing on this document with an unsupported spv should cause the document to desync |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[test] Check that we emitted an error for this
docConfig, | ||
remoteChangeEvent); | ||
} else { | ||
// TODO(ERIC->ADAM): This event may contain old version info. We should be filtering out |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replace ERIC->ADAM with STITCH-1972
} | ||
} | ||
|
||
private static final String SYNC_PROTOCOL_VERSION_FIELD = "spv"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nit] Move this into a Fields inner static class
* Returns whether this version is non-empty (i.e. a version from a document with no version) | ||
* @return true if the version is non-empty, false if the version is empty. | ||
*/ | ||
boolean isNonEmptyVersion() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nit] easier to call this hasVersion
return versionValues != null; | ||
} | ||
|
||
private @Nonnull VersionValues getVersionValuesThrowIfNull() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nit] I would call this getVersion
and make the class called a Version, not VersionValues
return this.versionValues; | ||
} | ||
|
||
/** |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nit] These methods should be on the version, not the class.
// TODO(QFR): I ask because when we are looking at hasCommittedVersion, we are using the | ||
// lastKnownRemoteVersion of the doc config rather than the last known remote version based | ||
// on the incoming change event we got here, which would make this not conform to spec. | ||
if (docConfig.hasCommittedVersion(localVersion)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[minor] This should just be remote version
final BsonDocument localDocument = localColl.find(docFilter).first(); | ||
|
||
final BsonValue lastKnownRemoteVersion; | ||
final BsonDocument lastKnownRemoteVersion; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if it helps, name this currentRemoteVersion
which will become lastKnownRemoteVersion
in the config
return; | ||
} | ||
|
||
// TODO(QUESTION FOR REVIEW): The testFrozenDocumentConfig test fails if I call this, but |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There reason not to set this and why you can get rid of that set method is this would always make the next statement true.
// old version, rather than the new version. Is this intentional? It seems like we should be | ||
// setting the pending writes at the newly generated version. I'm confused though because if I | ||
// use the new version here instead, tests fail because updates don't end up happening on a | ||
// sync pass. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
discussed offline. this is correct
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. Just a unit test class to add
* empty version. | ||
* @return a BsonDocument representing a synchronization version | ||
*/ | ||
BsonDocument getNextVersion() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nit] Add a unit test(s) for this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM 👍
Could use a unit test for the logic we spoke about earlier in this PR. |
This is complete to the best of my knowledge and based on offline conversations I've had with Eric, but I threw in some comments where I'm still unsure if stuff needs to be done or if it should be delegated to future tickets.
The tests from 1950 (specifically
testConfigure
) are still flaky, but otherwise everything passes.I'll keep an eye on evergreen as well.