[fix][txn] fix txn coordinator recover handle committing and aborting txn race condition. - #19201
Conversation
|
Maybe there is a better way to fix it? from |
|
if the TC does not exist in the broker, the op doesn't need to retry. #18924 may has fix this problem |
I have include this PR in my test enviroment, and exceptions above have been throwed. That PR do not gurantee that TC have been put into |
|
the future thenAccept change the executing thread, so the problem has never been truth fixed.
so its better to change code like this can slove this problem |
good idea. i have changed the patch. |
|
@thetumbled It looks like the change is not about the issue that you described in the PR details. When reading the motivation of the PR, I thought it was related to the transaction buffer snapshot, but after checking the changes, it looks like the fix is for the transaction coordinator. |
When I was troubleshooting why transaction recovery took a long time, I found that the root cause is some transactions cannot be terminated though exceeding timeout, which leads to subsequent phenomena. |
| completableFuture.complete(null); | ||
| tcLoadSemaphore.release(); | ||
| })).exceptionally(e -> { | ||
| completableFuture.complete(null); |
There was a problem hiding this comment.
@congbobo184 @thetumbled Sorry, I didn't get the key point of the problem. The completableFuture is completed by the same thread of executing stores.put(tcId, store); Why will we have race condition here? The client-side should send the end transaction command after completing the TC connect stage.
There was a problem hiding this comment.
There are two thread pools org.apache.pulsar.transaction.coordinator.impl.MLTransactionMetadataStore#internalPinnedExecutor and org.apache.pulsar.broker.TransactionMetadataStoreService#internalPinnedExecutor. They are different.
There was a problem hiding this comment.
Yes, but before we complete the completableFuture here, we have added the item to the map. Why the subsequent request is not able to get from the map? The subsequent request only happened after the completableFuture was done, right? And the map is a ConcurrentHashMap, so what is the race condition here? Could you please provide more details about the race condition? How does it happen?
There was a problem hiding this comment.
The completableFuture return by openTransactionMetadataStore(tcId) is completed by following code in thread MLTransactionMetadataStore#internalPinnedExecutor.
**completableFuture.complete(MLTransactionMetadataStore.this);**
recoverTracker.handleCommittingAndAbortingTransaction();
Once the completableFuture is completed, stores.put will be executed in another thread TransactionMetadataStoreService#internalPinnedExecutor.
openTransactionMetadataStore(tcId).thenAccept((store) -> internalPinnedExecutor.execute(() -> {
stores.put(tcId, store);
So, we may execute recoverTracker.handleCommittingAndAbortingTransaction() before execute stores.put(tcId, store).
There was a problem hiding this comment.
Essentially this is a circular dependency problem.
TransactionMetadataStoreService.handleTcClientConnect -> MLTransactionMetadataStore.init -> TransactionRecoverTracker.handleCommittingAndAbortingTransaction -> TransactionMetadataStoreService.handleTcClientConnect
It looks like we need to ensure some state is changed in TransactionMetadataStoreService during init MLTransactionMetadataStore.
IMO, we should refactor this part finally to move the recoverTracker.handleCommittingAndAbortingTransaction(); to the TransactionMetadataStoreService to decouple the mutual state dependence.
@congbobo184 @liangyepianzhou WDYT?
It's hard to understand why the map put operation should be executed out of the internalPinnedExecutor while reading the code. This may present challenges for future maintenance.
There was a problem hiding this comment.
openTransactionMetadataStore(tcId) can return MutablePair<store, recoverTracker> or after openTransactionMetadataStore(tcId) then init return the recoverTracker. I prefer to use the second way, in this way the logical more clear, after store init the tracker need to do something to handle the legacy the committing and abort transactions.
There was a problem hiding this comment.
the first solution is more concise, and i have implement it.
As for the second approach, do you means that move init method out of openTransactionMetadataStore method?
There was a problem hiding this comment.
yes, in the second way can completely decoupled. @codelipenghui WDYT?
There was a problem hiding this comment.
Sorry, I haven't got the point of openTransactionMetadataStore(tcId) then init return the recoverTracker, @congbobo184 Can you share the link?
There was a problem hiding this comment.
change to
public CompletableFuture<TransactionRecoverTracker> init()
openTransactionMetadataStore(tcId) only return the store,
then the store can add a interface public CompletableFuture<TransactionRecoverTracker> init(),
TransactionMetadataStoreService can invoke the store.init() return TransactionRecoverTracker
init completely, recoverTracker can do
in the
TransactionMetadataStoreService
…ice to decouple the mutual state dependence.
codelipenghui
left a comment
There was a problem hiding this comment.
Looks good to me now.
I just left some minor comments.
449d962 to
e9f2f30
Compare
|
@congbobo184 @liangyepianzhou Please help review again. |
Codecov Report
@@ Coverage Diff @@
## master #19201 +/- ##
=============================================
+ Coverage 48.96% 60.71% +11.74%
- Complexity 7300 25533 +18233
=============================================
Files 424 1895 +1471
Lines 45473 137527 +92054
Branches 4672 15099 +10427
=============================================
+ Hits 22268 83503 +61235
- Misses 20698 46300 +25602
- Partials 2507 7724 +5217
Flags with carried forward coverage won't be shown. Click here to find out more.
|
… txn race condition. (#19201) Fixes #19200 transaction lasted for long time and will not be aborted, which cause TB's MaxReadPosition do not move and will not take snapshot. With an old snapshot, TB will read a lot of entry while doing recovery. In worst cases, there are 30 minutes of unavailable time with Topics. avoid concurrent execution.
… txn race condition. (#19201) Fixes #19200 transaction lasted for long time and will not be aborted, which cause TB's MaxReadPosition do not move and will not take snapshot. With an old snapshot, TB will read a lot of entry while doing recovery. In worst cases, there are 30 minutes of unavailable time with Topics. avoid concurrent execution. (cherry picked from commit 96f4161)
… txn race condition. (apache#19201) Fixes apache#19200 transaction lasted for long time and will not be aborted, which cause TB's MaxReadPosition do not move and will not take snapshot. With an old snapshot, TB will read a lot of entry while doing recovery. In worst cases, there are 30 minutes of unavailable time with Topics. avoid concurrent execution. (cherry picked from commit 96f4161) (cherry picked from commit 5dd13ec)
|
@thetumbled Can you help cherry-pick this PR to branch-2.9? |
ok. |
…ng and aborting txn race condition. apache#19201 (apache#19699) Cherry-pick: apache#19201
Fixes #19200
Motivation
transaction lasted for long time and will not be aborted, which cause TB's MaxReadPosition do not move and will not take snapshot. With an old snapshot, TB will read a lot of entry while doing recovery.
In worst cases, there are 30 minutes of unavailable time with Topics.
Modifications
makeCoordinatorNotFoundExceptionretryable.avoid concurrent execution.
Verifying this change
(Please pick either of the following options)
This change is a trivial rework / code cleanup without any test coverage.
Does this pull request potentially affect one of the following parts:
If the box was checked, please highlight the changes
Documentation
docdoc-requireddoc-not-neededdoc-completeMatching PR in forked repository
PR in forked repository: thetumbled#12