-
Notifications
You must be signed in to change notification settings - Fork 33
Add support for VersionStamps #72
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
- Handle both 80-bit and 96-bit sizes - Use internal flag to distinguish between both sizes, and incomplete/complete
- Support both 80-bits and 96-bits variants - BUGBUG: cannot recognized complete/incomplete stamps yet when parsing.
…tampedKey() / SetStampedValue() mutations
… tests for VersionStamps
|
Several issues with the way Versionstamps are implemented in other bindings:
This makes it a bit difficult to insert support of VersionStamps with the existing eco-system of Key and Value encoders (via An idea would be to represent incomplete versionstamps using a custom byte sequence, which is recognized and used to lookup the offset at the last minute before performing the SetVersionStampedKey/Value mutation (via "IndexOf(...)") Pros:
Cons:
One way to prevent the issue of the placeholder sequence conflicting with some other part of the key, would be to use a random token per transaction, and expose the incomplete stamp factory methods on the await db.WriteAsync((tr) =>
{
tr.SetVersionStampedKey(
location.Keys.Encode("Foo", tr.VersionStamp()),
Slice.FromString("Hello World")
);
tr.SetVersionStampedKey(
location.Keys.Encode("Bar", tr.VersionStamp(42)),
Slice.FromString("Hello World")
);
}, ct);The call Pros:
Cons:
Possible solution for last point: if we can ensure that a Transaction Version generated by the database CANNOT have the higher bit set to 1 (ie: cannot have version numbers larger than 2^63) then we could use this bit as a marker. All random incomplete stamps would have this bit set, and all complete stamps would have this bit unset. |
|
Another issue: the call to tr.GetVersionStampAsync() must be done before commiting the transaction, but it will complete after the transaction has committed. This creates a lot of problems with the current API (var result, var stampTask) = await db.ReadWriteAsync((tr) =>
{
// read/set some keys
tr.SetVersionStampedKey(location.Keys.Encode("Hello", VersionStamp.Incomplete()), Slice.FromString("World!");
// if we want to know the stamp, we have to start the task here
var task = tr.GetVersionStampAsync();
// but it won't complete until we commit ourselves!!
//BUGBUG: calling 'task.Result' or 'await task' here would DEAD LOCK!
return (...., task); // <-- this is weird having to shiip a Task<VersionStamp> as part of the result!
}, ct);
var stamp = await stampTask; // need an additional await after the fact! :(At the moment, the only solution is to return thas The core issue is that the layers that must know the actual stamp value used, have to execute code AFTER the transaction has committed. When composed with retry loops that control the lifetime of the transaction, it means that code inside the lambda must be able to schedule more code to execute outside the scope of the lambda! We cannot do much about this, because the low level binding API is designed like this. We have three choices:
Choice 2 does not solve the issue in all case. Choice 3 splits code in two, and also may lead to a bad-practice pattern: Business Logic code or Layers that needs to do this will need to have access to the database instance, and call For example, if inside a single HTTP request to an MVC Controller, I need to do 2 or 3 operations (using different layers), and if at least one of them wants to handle the transaction lifetime itself, then they cannot share the same transaction. This will probably lead to mutiple transactions called sequentially, and will 1) introduce more latency, 2) break ACID guarantees if the second or third transaction fails. |
…using a random token) - Each transaction generate a random token (and on each retry). - tr.CreateVersionStamp() can be used to get a stamp specific to this transaction
|
Message Queue Sample: [TODO: not complete] public class FdbMessageBus
{
public ITypedKeySubspace<string, VersionStamp> Subspace { get; }
public FdbMessageBus(IKeySubspace folder)
{
this.Subspace = location.UsingEncoder<string, VersionStamp>();
}
public void PostMessage(IFdbTransaction tr, string queueId, Slice message)
{
tr.SetVersionStampedKey(
this.Location.Keys[queueId, tr.CreateVersionStamp(0)],
message
);
}
public void PostMessages(IFdbTransaction tr, string queueId, IEnumerable<Slice> messages)
{
int idx = 0;
foreach(var msg in messages)
{
tr.SetVersionStampedKey(
this.Location.Keys[queueId, tr.CreateVersionStamp(idx++)],
msg
);
}
}
//TODO: consuming messages
} |
…rsionstamp present in the key, and add the 16-bit offset suffix.
…specifiy the offset of the versionstamp
…tinguish between cases
|
The current state of the PR allow basic usage of VersionStamps:
I'm going to address the last point in a future PR, at least we can start playing with versionstamps ! |
Add initial support for VersionStamps in the newest API
Open questions: see discussion at https://forums.foundationdb.org/t/implementing-versionstamps-in-bindings/250
WriteAsync(...),ReadWriterAsync(...))ToString()andDebuggerDisplay). Currently it is"@VERSION-ORDER"/"@VERSION-ORDER#USER"and"@?"/"@?#USER"for incomplete stamps.Example usage:
Some conventions: