-
Notifications
You must be signed in to change notification settings - Fork 38
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
Update transaction documents #804
Conversation
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, thank you!
I just left a minor suggestion.
Co-authored-by: Vincent Guilpain <vincent.guilpain@scalar-labs.com>
@@ -69,8 +69,8 @@ public interface DistributedTransaction extends TransactionCrudOperable { | |||
/** | |||
* Commits a transaction. | |||
* | |||
* @throws CommitConflictException if conflicts happened. You can retry the transaction in this | |||
* case | |||
* @throws CommitConflictException if conflicts happened. You can retry the same transaction from |
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.
the same transaction
sounds the previously failed and rollbacked transaction to me. "the same operations in another transaction" or something would be less confusing?
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 googled with "retry the transaction"
keyword and it seems very common. So, probably okay. Never mind.
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 wondering if it could be better to remove same
. What do you think?
* @throws CommitConflictException if conflicts happened. You can retry the same transaction from | |
* @throws CommitConflictException if conflicts happened. You can retry the transaction from |
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.
Yeah, retry the transaction
doesn't sound the previously failed transaction itself compared to retry the same transaction
to me.
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.
Thank you! I'll fix it that way.
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.
@komamitsu Fixed in b512a20. Please take a look again when you have time!
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.
💯
679674b
to
b512a20
Compare
docs/api-guide.md
Outdated
// Aborting the transaction fails. You can log it here | ||
tx.rollback(); | ||
} catch (RollbackException ex) { | ||
// Rolling back the transaction fails. You can log it 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.
[trivial]
Rolling back the transaction fails
failed
sounds a bit better?
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.
Fixed in 1041dda.
int retryCount = 0; | ||
|
||
while (true) { | ||
if (retryCount > 0) { |
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.
[trivial] Incrementing the counter here might be more robust considering future modifications of the exception handling blocks below (e.g. developers notice CrudException should be retried and they change return
to break
without incrementing the counter)
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.
Fixed in 1041dda.
|
||
The APIs for CRUD operations (`get()`/`scan()`/`put()`/`delete()`/`mutate()`) could throw `CrudException` and `CrudConflictException`. | ||
If you catch `CrudException`, it indicates some failure (e.g., database failure and network error) happens during a transaction, so you should cancel the transaction or retry the transaction after the failure/error is fixed. | ||
If you catch `CrudConflictException`, it indicates a conflict occurs during the transaction so that you can retry the transaction from the beginning, preferably with well-adjusted exponential backoff based on your application and environment. |
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.
👍
while (true) { | ||
if (retryCount > 0) { | ||
// Retry the transaction three times maximum in this sample code | ||
if (retryCount == 3) { |
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.
[trivial] retryCount >= 3
would be a little bit more robust
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.
Fixed in 1041dda.
If you catch `UnknownTransactionStatusException`, you are not sure if the transaction succeeds or not. | ||
In such a case, you need to check if the transaction is committed successfully or not and retry it if it fails. | ||
How to identify a transaction status is delegated to users. | ||
You may want to create a transaction status table and update it transactionally with other application data so that you can get the status of a transaction from the status table. |
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 don't really get this. Could you tell me a simple example?
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 explained in the 3rd way in the following article (Japanese):
http://nippondanji.blogspot.com/2009/02/blog-post_23.html
You can have a status (or log) table and insert a record into this table in addition to the usual operations in a transaction. And when you get UnknownTransactionStatusException
and want to check if the transaction is really committed, you can check if the record of the status table exists or not. If the record exists, the transaction has been successfully committed.
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.
Great! Thank you for sharing it.
The blog post seems really helpful for our customers as I think it's hard for them to come up with the idea without the details. How about adding a link to similar articles here? I wish if the blog post were written in English...
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.
How about adding a link to similar articles here? I with if the blog post were written in English...
@feeblefakie What do you think? Are there any good articles written in English about that?
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.
BTW (a bit off-topic), do you think it would be great if ScalarDB maintains this kind of transaction history in coordinator.history
table or something instead of asking application developers to manage that? ScalarDB error handling can check the table when trying to throw UnknownTransactionStatusException.
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.
How about adding a link to similar articles here?
Sorry, I didn't find good ones...
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.
coordinator.history
Ah, checking coordinator.state
might be enough. But the point in this case is that maybe something wrong with the underlying database is happening and it's likely ScalarDB can't access the table... 🤔
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.
[off-topic]
This is explained in the 3rd way in the following article (Japanese):
http://nippondanji.blogspot.com/2009/02/blog-post_23.html
Just remembered I did the same way before.
https://github.com/treasure-data/digdag/blob/42ea7cb71270957ea6424c2df88dd7c65d248789/digdag-standards/src/main/java/io/digdag/standards/operator/redshift/RedshiftConnection.java#L297-L303 The workflow operator for Redshift does CRUD in the user's database and implicitly inserts a status log record to a secret table in the same transaction
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.
Ah, checking coordinator.state might be enough. But the point in this case is that maybe something wrong with the underlying database is happening and it's likely ScalarDB can't access the table... 🤔
Yes, we have coordinator.state
which has a state record for each transaction, and we can check the record to see if the transaction is committed or not. And we actually expose it through DistributedTransactionManager.getState().
However, I would like to deprecate (and remove) this API in the future because of the following reasons:
- The
coordinator.state
table is an implementation-specific thing (the Consensus Commit transaction has it, but the JDBC transaction doesn't have it). - We don't want to keep the state records in the
coordinator.state
table permanently. After successfully committing or rolling back a transaction, we can essentially remove the state record of the transaction (except for thegetState()
reason). So in the future, I want to remove a state record after the transaction is complete. - We plan to implement the one-phase commit optimization. In this optimization, we don't need to write the state record to the
coordinator.state
table.
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 see. Regarding 1, I don't think we need to expose it to users as far as we can call it internally considering the mode. As for 2 and 3, it would matter. Also, as I mentioned above, automatically internal checking state
(or history
) table doesn't work when underlying database isn't working fine. So, it's difficult to suggest the best way to handle this kind of situation. I think this PR overall looks good. Probably we can get feedbacks from customers about this section in the future and let's discuss it deeply again.
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! 👍
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.
Found one typo, but other than that, LGTM! Thank you!
@@ -69,8 +69,8 @@ public interface DistributedTransaction extends TransactionCrudOperable { | |||
/** | |||
* Commits a transaction. | |||
* | |||
* @throws CommitConflictException if conflicts happened. You can retry the transaction in this | |||
* case | |||
* @throws CommitConflictException if a transaction conflict occurs, You can retry the transaction |
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.
* @throws CommitConflictException if a transaction conflict occurs, You can retry the transaction | |
* @throws CommitConflictException if a transaction conflict occurs. You can retry the transaction |
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.
Ah it's not typo actually. The format @throws XXXException if ...
is frequently used in this project.
This PR updates (and improves) the transaction documents. Please take a look!