-
Notifications
You must be signed in to change notification settings - Fork 605
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
spanner: how should transactional promises look? #2152
Comments
A quick "A or B" is: // A)
datastore
.runTransaction(function(err, transaction) {
if (err) {
// Could not start transaction
return Promise.reject(err)
}
// ...transaction stuff...
return transaction.commit()
})
.then(success)
.then(doSomethingElse)
.catch(handleErrors)
// B)
datastore.runTransaction(function(err, tranaction) {
if (err) {
// Could not start transaction
}
// ...transaction stuff...
transaction.commit()
.then(success)
.then(doSomethingElse)
.catch(handleErrors)
}) Gist: https://gist.github.com/stephenplusplus/3cb49529ee537c640a4670edf900e294 |
I am opposed to the current solution being discussed. A question for @vkedia:
A comment:
|
@lukesneeringer There are two levels of retries here. In case of a retryable error like UNAVAILABLE, gapic would take care of retries and it would retry just the commit call. The retry we are discussing is when commit fails with ABORTED which usually means that there was some other conflicting transaction but there might be other reasons for that. In that case the correct thing to do is to create a new transaction again and do all the operations on that new transactions again. If you just replay the commit, it would again fail with ABORTED. |
Thanks, that answers my question. So, the correct solution becomes to save the operations that were made on the transaction and replay them. |
Yes, and we also have to replay arbitrary user code that may have been executed before, after, and in-between the calls to our API, e.g. reading a file from disk, making a remote request, querying Datastore, etc. That's why I'm drawn towards design I also think it's less prone to user error. They won't have to remember to return a promise from the I dislike |
Something that @vkedia has asked about a few times is why we make the user manually call commit and I think that by abstracting that away, we might be able to solve this. In an earlier version of Datastore we never used to make the user call commit directly, instead we had them pass in multiple callbacks, one of which we supplied a Pretending we took a similar approach here, here's how our callback version would look database.runTransaction((transaction, done) => {
transaction.run('SELECT * FROM Singers', (err, rows) => {
if (err) {
done(err);
return;
}
transaction.insert('Singers', { ... });
done();
});
}, (err) => {
// the transaction is finished
}); The introduction of the second callback would mean we can now guess whether or not the user wants a Promise in the same fashion as our other APIs. database.runTransaction((transaction) => {
return transaction.run('SELECT * FROM Singers').then([rows, resp] => {
transaction.insert('Singers', {});
});
}).then(() => {
// the transaction is finished
}); I think the downside here is that we still have to pass in a callback that returns a promise, but I don't think there's any avoiding that due to the retry requirement for aborted transactions. Thoughts? |
Doesn't Taking away the Just to re-state, users did not like when we had a |
Sorry, what I meant was that there is no avoiding providing a callback (not the returning the promise part)
That's a fair point, but it seems like maybe coming up with a better name for
I think you're not really handling commit per se, but when the transaction is finished in general - like if an error were to occur at any time during or if everything went fine. To me this is very reminiscent of async.
That is definitely something to consider but I think given the current options for our problem, if we end up supporting a promise mode for this, there will likely be some aspect of it the users don't like and/or find confusing. |
Do we? What if we only replayed the SQL, regardless of how that SQL came to be? That is less powerful than replaying arbitrary user code, and there are reasons to consider against doing it, but it is a far easier solution. That said, we can replay arbitrary code too, as long as the code is passed as a function. That actually more or less becomes the "atomic transaction" code I originally posted. (Editor's note: I do not have throughput to follow this thread right now, but it is critically important. Keep discussing it, but please do not make any final decisions without a meeting. Thanks.) |
So, the question here is which of these two is going to be the bigger gripe for our users:
Personally, I think that someone will be unhappy regardless of which one we choose. I'm leaning slightly towards @vkedia's proposal, but I'm not sold on anything yet. |
What if we passed in a function, and then the entire function could just be re-run? |
@lukesneeringer So how is that different from the current callback based API? Are you saying that the passed in function would return a Promise? |
Maybe it is not different. Sorry, I derped. |
@callmehiphop please correct me if I'm wrong, but as far as how these methods technically behave, I believe we have that part working-- with the exception of a bug that didn't include the transaction ID on the outgoing API requests. With that fixed, we handle the retry logic correctly. This conversation is to help us find the most obvious API for the user who wants to use promises for transactions. Flip back to the A or B post: #2152 (comment) -- A is what we previously had, until we removed it, and B is a refactor. |
@stephenplusplus that is correct! |
Unless we hear other opinions, I think it's up to us, @callmehiphop! From a code-only standpoint (not factoring in anything other than the UX of the API), which one would you like to move forward with, A or B? |
@stephenplusplus going to go with B on this one - essentially we just won't support Promises on this method and if the user wants to use them they'd have to wrap it themselves. |
Yeah, I agree with that plan. |
I discussed this with @vkedia and @jgeewax a few weeks ago. The problem with removing promises from certain methods (IIUC, What do you think @stephenplusplus, @callmehiphop, and @swcloud? |
@bjwatson I think not retrying for promises makes sense to me. As it is |
@callmehiphop SGTM. As long as we provide some kind of promise mechanism for that method, then users are unlikely to seek out their own. |
Originally posted by @callmehiphop
@vkedia @stephenplusplus @lukesneeringer @jgeewax so our typical callback vs. promise convention is pretty simple - omit the callback and you shall receive a promise. I think this is the first API that really complicates this approach.
Consider the following example
My main issue is that in order to properly retry our transaction here, we would need to capture all functions passed to the promise before
transaction.commit()
and retry them in the correct order before allowing any chaining that occurs after to continue. Technically I don't think this is even feasible, but even if it were I think the overall behavior itself is totally unpredictable for our users. Based on this opinion, I think that our traditional approach will not fly here.That being said, if we were to continue to try and let both callbacks and promises live together within a single method, we need a different way of letting the user tell us that they want promises.
My current suggestion is to let the user return a promise within the callback (like so)
However after some discussion in my PR, this also may be confusing for the users. So I would like to revisit this specific API since we don't have a precedent for this specific issue.
An idea that we've been kicking around would basically be to not directly support promises in and out of this method, but only in.. This means the code would resemble the following
At which point if a user doesn't feel that this is the way they want to use Promises, it would be fairly easy to wrap that into something like
The text was updated successfully, but these errors were encountered: