Skip to content

[fix](cloud) Fence MetaService before giving up the TSO of a commit that failed with KV_TXN_COMMIT_ERR - #69925

Open
morningman wants to merge 1 commit into
apache:masterfrom
morningman:cloud-commit-err-tso-fence
Open

morningman wants to merge 1 commit into
apache:masterfrom
morningman:cloud-commit-err-tso-fence

Conversation

@morningman

Copy link
Copy Markdown
Contributor

What problem does this PR solve?

Issue Number: None

Related PR: #67820, #69831

Problem Summary:

In short: in cloud mode, when MetaService answers a commit with KV_TXN_COMMIT_ERR, the FE gives up the commit TSO at once, as for a commit that failed for good. MetaService gives that answer when the FDB commit of the transaction timed out, and such a commit may still be applied afterwards. The committed TSO watermark can then pass a commit that is yet to land, and a bounded @incr read admitted at that watermark misses its rows. This PR advances the MetaService TSO fence to the TSO before giving it up on this answer, as the FE already does when the commit RPC itself fails.

Background

  • In cloud mode the Master FE gives each commit a TSO and registers it before sending commit_txn to MetaService ([fix](binlog) Track committed TSO and fence uncertain commits for bounded incremental reads #67820). The committed TSO watermark stays below the oldest registered TSO, and a bounded @incr read whose window ends at or below the watermark is admitted without waiting (TSOService.waitForReadableWindow), on the guarantee that no commit with a TSO at or below it can still land.
  • So a TSO is given up only once the outcome of its commit is settled. An answer of success, already visible or already aborted finishes the transaction. A failed RPC (a timeout, a lost connection) leaves the outcome open, so the FE first advances the MetaService TSO fence to the TSO (TSOService.fenceAndAbandonCommitTso): commit_txn reads the fence in the FDB transaction it commits and rejects a TSO at or below it, so once the fence is advanced, the commit has landed or can no longer land. Every other answer gives the TSO up at once.
  • MetaService gives each FDB transaction a timeout (fdb_txn_timeout_ms, 10 seconds by default) and turns the error of an FDB commit into its answer through cast_as<ErrCategory::COMMIT>: a conflict is KV_TXN_CONFLICT, commit_unknown_result is KV_TXN_MAYBE_COMMITTED, and a timeout (FDB errors 1004 timed_out and 1031 transaction_timed_out) or an error it does not classify is KV_TXN_COMMIT_ERR.

The problem, and what it cost

  • A timeout cancels the FDB transaction, and the FoundationDB C API documents that for a transaction cancelled after its commit was sent, "the commit may have occurred or may occur in the future". So KV_TXN_COMMIT_ERR does not settle the outcome of the commit.
  • CloudGlobalTransactionMgr.commitTxn gives the TSO up on that answer without a fence. Once nothing older is registered, the watermark passes the TSO while the commit can still land.
  • A bounded @incr read admitted at that watermark misses the rows of a commit that lands later, and a read of the next window does not see them either: their TSO lies in the window already read. Anything else that takes the watermark as proof that no commit at or below it is coming is exposed the same way; the keep-alive of IVM streams in [fix](ivm) Keep the offsets of idle IVM stream partitions alive #69831 fences MetaService itself before it reads, for this reason.

How this PR fixes it

  • KV_TXN_COMMIT_ERR takes the path of a failed RPC: fenceAndAbandonCommitTso advances the fence to the TSO and gives the TSO up only after that.
  • A fence that fails deactivates the TSO service, as after a failed RPC: no TSO is given out and no read is admitted on the watermark until a calibration has advanced the fence above every TSO given out before. The commit fails with the fence error then, and the answer of MetaService is logged first, so it is not lost.
  • The commit fails as before in every case: only what happens to its TSO changes.

Results

Answer of MetaService to commit_txn Before this PR After this PR
KV_TXN_COMMIT_ERR (the FDB commit timed out, or failed with an error MetaService does not classify) TSO given up at once Fence advanced to the TSO, then the TSO given up
The RPC failed Fence advanced to the TSO, then the TSO given up Unchanged
KV_TXN_MAYBE_COMMITTED, KV_TXN_TOO_OLD and the other errors TSO given up at once Unchanged
OK, TXN_ALREADY_VISIBLE, TXN_ALREADY_ABORTED Transaction finished Unchanged

The cost stays on that error path: one more MetaService RPC, and the commits in flight with a TSO at or below the fence are rejected once with TXN_COMMIT_TSO_EXPIRED and retried with a new TSO, as after a failed RPC.

Classes, and how they call each other

  • CloudGlobalTransactionMgr.commitTxn: the new branch for KV_TXN_COMMIT_ERR.
  • CloudCommittedTsoTest: the new testKvCommitErrorIsFencedButDefiniteFailureIsReleased.
CloudGlobalTransactionMgr.commitTxn
  |-- MetaServiceProxy.commitTxn  -> answer, or an exception
  |-- RPC failed, or KV_TXN_COMMIT_ERR (new) -> TSOService.fenceAndAbandonCommitTso
  |       |-- GlobalTransactionMgr.advanceTsoFence(tso)  then  TSOTransactionTracker.abandonCommitTso
  |       `-- the fence failed: TSOService.deactivate, and the commit fails with the fence error
  |-- KV_TXN_MAYBE_COMMITTED, other errors          -> TSOService.abandonCommitTso
  `-- OK, TXN_ALREADY_VISIBLE, TXN_ALREADY_ABORTED  -> TSOService.markTxnFinished

Not changed: MetaService, which still answers a timed out commit with KV_TXN_COMMIT_ERR, and the handling of KV_TXN_MAYBE_COMMITTED.

Release note

Fix a bounded incremental read (@incr) in cloud mode that could miss a transaction whose commit timed out in MetaService.

Check List (For Author)

  • Test

    • Regression test
    • Unit Test
    • Manual test (add detailed scripts or steps below)
    • No need to test or manual test. Explain why:
      • This is a refactor/code format and no logic has been changed.
      • Previous test can cover this change.
      • No code files have been changed.
      • Other reason

    CloudCommittedTsoTest#testKvCommitErrorIsFencedButDefiniteFailureIsReleased: KV_TXN_COMMIT_ERR advances the fence before the TSO is given up and still fails the commit; a fence that fails fails the commit with the fence error and gives nothing up; KV_TXN_TOO_OLD still gives the TSO up without a fence. It fails on the code before this PR. The cloud transaction and TSO test classes (org/apache/doris/cloud/transaction/*Test and org/apache/doris/tso/*Test, 7 classes, 133 tests) pass.

  • Behavior changed:

    • No.
    • Yes.

    A commit that MetaService answers with KV_TXN_COMMIT_ERR costs one more MetaService RPC, and the commits in flight at or below its TSO are retried once with a new TSO. If that fence fails, the TSO service deactivates until it is calibrated again, as after a failed commit RPC.

  • Does this need documentation?

    • No.
    • Yes.

Check List (For Reviewer who merge this PR)

  • Confirm the release note
  • Confirm test cases
  • Confirm document
  • Add branch pick label

…hat failed with KV_TXN_COMMIT_ERR

### What problem does this PR solve?

Issue Number: None

Related PR: apache#67820, apache#69831

Problem Summary: In cloud mode the Master FE registers the commit TSO
of each transaction it sends to MetaService and keeps a committed TSO
watermark below the oldest registered one. A bounded `@incr` read
admits a window that ends at or below the watermark without waiting,
on the guarantee that no commit with a TSO at or below it can still
land. apache#67820 keeps that guarantee by giving up a TSO only once the
outcome of its commit is settled: a success, an already visible or an
already aborted answer finishes it; a failed RPC fences MetaService at
the TSO first (`fenceAndAbandonCommitTso`), as commit_txn reads the
fence in the FDB transaction it commits, so the commit has landed
before the fence or is rejected by it; any other answer gives the TSO
up at once, as a commit that failed for good.

`KV_TXN_COMMIT_ERR` is not always such an answer. MetaService gives
each FDB transaction a timeout (`fdb_txn_timeout_ms`, 10 seconds by
default) and answers `KV_TXN_COMMIT_ERR` when the commit of the
transaction timed out (FDB errors 1004 and 1031) or failed with an
error it does not classify. A timeout cancels the FDB transaction, and
a commit cancelled after it was sent may have happened or may still
happen. FE gave such a TSO up without a fence, so the watermark could
pass a commit that lands later, and a bounded `@incr` read admitted at
that watermark would miss its rows.

`KV_TXN_COMMIT_ERR` now takes the path of a failed RPC: MetaService is
fenced at the TSO before it is given up. A fence that fails
deactivates the TSO service, as on a failed RPC, so no read is
admitted on the watermark until a calibration fences MetaService above
every TSO given out before. The commit fails as before, with the fence
error when the fence fails; the answer of MetaService is logged first
then.

| Answer of MetaService to commit_txn | Before | After |
| --- | --- | --- |
| `KV_TXN_COMMIT_ERR` | TSO given up | MetaService fenced at the TSO, then the TSO given up |
| Failed RPC | MetaService fenced at the TSO, then the TSO given up | Unchanged |
| `KV_TXN_MAYBE_COMMITTED` and any other error | TSO given up | Unchanged |
| `OK`, `TXN_ALREADY_VISIBLE`, `TXN_ALREADY_ABORTED` | Transaction finished | Unchanged |

### Release note

Fix a bounded incremental read in cloud mode that could miss a
transaction whose commit in MetaService timed out.

### Check List (For Author)

- Test: Unit Test
    - `CloudCommittedTsoTest#testKvCommitErrorIsFencedButDefiniteFailureIsReleased`:
      `KV_TXN_COMMIT_ERR` fences MetaService before the TSO is given up
      and still fails the commit; a fence that fails fails the commit
      with the fence error and gives nothing up; `KV_TXN_TOO_OLD` still
      gives the TSO up without a fence. It fails without the fix. The
      other three tests of the class pass.
- Behavior changed: Yes. A commit that MetaService answers with
  `KV_TXN_COMMIT_ERR` costs one more MetaService RPC, the fence, and
  the commits in flight at or below the fenced TSO are rejected once
  and retried with a new TSO, as after a failed RPC. If the fence
  fails, the TSO service deactivates until it is calibrated again.
- Does this need documentation: No

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@morningman
morningman requested a review from gavinchou as a code owner November 1, 2026 13:46
@hello-stephen

Copy link
Copy Markdown
Contributor

Thank you for your contribution to Apache Doris.
Don't know what should be done next? See How to process your PR.

Please clearly describe your PR:

  1. What problem was fixed (it's best to include specific error reporting information). How it was fixed.
  2. Which behaviors were modified. What was the previous behavior, what is it now, why was it modified, and what possible impacts might there be.
  3. What features were added. Why was this function added?
  4. Which code was refactored and why was this part of the code refactored?
  5. Which functions were optimized and what is the difference before and after the optimization?

@morningman

Copy link
Copy Markdown
Contributor Author

run buildall

@morningman

Copy link
Copy Markdown
Contributor Author

/review

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change fixes the data-correctness hazard for genuinely ambiguous MetaService commit outcomes: CloudGlobalTransactionMgr now fences MetaService before releasing the commit TSO, and fence failures propagate through the existing FE cleanup path. The new unit test covers the uncertain response, fence failure, and definite KV_TXN_TOO_OLD release.

One P2 availability issue remains inline. MetaService also collapses exhausted KV_TXN_STORE_COMMIT_RETRYABLE responses originating from TXN_RETRYABLE_NOT_COMMITTED into KV_TXN_COMMIT_ERR. The FE branch therefore fences a known-not-committed attempt, unnecessarily invalidating lower in-flight TSOs and risking retry fan-out or TSO-service deactivation when the extra fence RPC fails.

Review checkpoints: the cloud transaction lifecycle and TSO tracker ordering were traced through commitTxn, MetaService retry/status normalization, advanceTsoFence, and TSOService; fencing precedes exact tracker removal and failure deactivates the service safely. No lock-order, callback, persistence, FE-BE protocol, or non-cloud parallel-path regression was found in this two-file patch. Error propagation is checked at the fence boundary. The added test setup and status-code normalization were independently rechecked. No user-provided focus points or existing inline blocking findings were present. Review was static only; no builds or tests were executed.

Review converged after 2 round(s).

} else if (code == MetaServiceCode.OK || code == MetaServiceCode.TXN_ALREADY_VISIBLE
|| code == MetaServiceCode.TXN_ALREADY_ABORTED) {
Env.getCurrentEnv().getTSOService().markTxnFinished(commitTxnRequest.getDbId(), transactionId);
} else if (code == MetaServiceCode.KV_TXN_COMMIT_ERR) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Avoid fencing known-not-committed retry exhaustion

MetaService::call_impl also rewrites an exhausted KV_TXN_STORE_COMMIT_RETRYABLE—which cast_as<COMMIT> produces for TxnErrorCode::TXN_RETRYABLE_NOT_COMMITTED—to KV_TXN_COMMIT_ERR. That source status explicitly means the FDB transaction did not commit, so this TSO can be abandoned directly. Because this branch cannot distinguish it from a timeout or unknown commit, it advances the global fence anyway, rejecting every in-flight TSO at or below this one with TXN_COMMIT_TSO_EXPIRED and potentially deactivating the whole TSO service if the extra fence RPC fails. Preserve the underlying status or fence only genuinely ambiguous commit results.

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-H: Total hot run time: 28399 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpch-tools
Tpch sf100 test result on commit 1d60f94821767cb57dbf85f3e47a9146901f10b3, data reload: false

------ Round 1 ----------------------------------
============================================
q1	17587	5173	5100	5100
q2	2164	291	245	245
q3	10284	1362	812	812
q4	4678	457	332	332
q5	7437	802	522	522
q6	180	181	157	157
q7	743	791	600	600
q8	9251	1328	1006	1006
q9	5729	4169	4139	4139
q10	6818	1288	1043	1043
q11	429	249	232	232
q12	651	411	282	282
q13	18032	3574	3022	3022
q14	281	276	254	254
q15	q16	714	730	677	677
q17	1198	745	590	590
q18	6990	6147	6114	6114
q19	1103	946	588	588
q20	387	332	231	231
q21	4622	2198	2478	2198
q22	352	295	255	255
Total cold run time: 99630 ms
Total hot run time: 28399 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	5619	5621	5633	5621
q2	702	608	583	583
q3	5204	5420	4920	4920
q4	2162	2129	1759	1759
q5	5235	5157	5609	5157
q6	251	205	162	162
q7	2353	2081	1876	1876
q8	3115	2781	2768	2768
q9	8115	7981	8034	7981
q10	3731	3943	3239	3239
q11	644	484	468	468
q12	745	777	565	565
q13	3514	3859	3286	3286
q14	299	306	277	277
q15	q16	707	741	675	675
q17	8245	7324	7098	7098
q18	13074	12219	12827	12219
q19	908	777	785	777
q20	2166	2151	1967	1967
q21	5946	5040	5117	5040
q22	517	462	446	446
Total cold run time: 73252 ms
Total hot run time: 66884 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-DS: Total hot run time: 151058 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpcds-tools
TPC-DS sf100 test result on commit 1d60f94821767cb57dbf85f3e47a9146901f10b3, data reload: false

query5	4329	583	437	437
query6	421	225	194	194
query7	4807	448	238	238
query8	331	171	161	161
query9	8767	3133	3159	3133
query10	460	320	257	257
query11	6039	3846	3575	3575
query12	143	90	86	86
query13	1247	480	319	319
query14	6134	3907	3627	3627
query14_1	3413	3385	3394	3385
query15	210	201	182	182
query16	938	454	413	413
query17	854	653	532	532
query18	2420	424	307	307
query19	205	173	135	135
query20	96	95	85	85
query21	214	130	115	115
query22	12982	13007	12778	12778
query23	13399	12892	12420	12420
query23_1	12660	12675	12547	12547
query24	7371	819	470	470
query24_1	487	486	483	483
query25	537	414	368	368
query26	1134	258	145	145
query27	2746	437	276	276
query28	4618	2019	2060	2019
query29	1551	618	465	465
query30	307	240	188	188
query31	1014	941	843	843
query32	144	114	108	108
query33	543	312	245	245
query34	898	875	506	506
query35	741	757	645	645
query36	820	803	719	719
query37	118	102	83	83
query38	1795	1744	1646	1646
query39	793	785	740	740
query39_1	728	737	756	737
query40	221	120	99	99
query41	74	68	68	68
query42	86	83	89	83
query43	351	368	309	309
query44	1330	702	704	702
query45	180	181	166	166
query46	822	967	575	575
query47	2908	2758	2789	2758
query48	341	317	218	218
query49	570	397	290	290
query50	672	280	205	205
query51	10665	10552	10502	10502
query52	83	81	72	72
query53	192	207	152	152
query54	218	207	182	182
query55	72	67	61	61
query56	222	206	221	206
query57	1633	1623	1562	1562
query58	281	263	271	263
query59	2217	2234	2060	2060
query60	302	246	223	223
query61	146	147	144	144
query62	389	340	289	289
query63	186	154	157	154
query64	2459	954	771	771
query65	3328	3272	3263	3263
query66	1743	409	301	301
query67	20241	20156	19938	19938
query68	2993	981	614	614
query69	396	285	246	246
query70	840	763	774	763
query71	301	226	229	226
query72	2775	2560	2256	2256
query73	523	520	292	292
query74	4603	4524	4353	4353
query75	2327	2294	1897	1897
query76	2313	1012	598	598
query77	347	359	267	267
query78	9877	9681	9172	9172
query79	1000	857	542	542
query80	812	425	338	338
query81	583	321	271	271
query82	580	133	106	106
query83	315	196	173	173
query84	314	122	96	96
query85	869	431	371	371
query86	358	242	231	231
query87	2013	1955	1796	1796
query88	3741	2746	2727	2727
query89	333	279	240	240
query90	1775	170	164	164
query91	157	153	122	122
query92	98	82	91	82
query93	986	996	559	559
query94	514	309	251	251
query95	585	324	371	324
query96	690	493	234	234
query97	2443	2412	2324	2324
query98	163	161	153	153
query99	780	749	662	662
Total cold run time: 234476 ms
Total hot run time: 151058 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
ClickBench: Total hot run time: 24.37 s
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/clickbench-tools
ClickBench test result on commit 1d60f94821767cb57dbf85f3e47a9146901f10b3, data reload: false

query1	0.01	0.01	0.01
query2	0.10	0.06	0.05
query3	0.27	0.09	0.09
query4	1.61	0.10	0.08
query5	0.22	0.25	0.25
query6	1.17	0.68	0.68
query7	0.04	0.01	0.01
query8	0.05	0.04	0.03
query9	0.40	0.35	0.33
query10	0.58	0.58	0.59
query11	0.20	0.15	0.14
query12	0.18	0.14	0.15
query13	0.50	0.50	0.49
query14	1.02	1.01	1.01
query15	0.63	0.61	0.62
query16	0.36	0.35	0.36
query17	1.11	1.14	1.17
query18	0.21	0.20	0.19
query19	2.10	2.00	1.96
query20	0.02	0.01	0.02
query21	15.49	0.20	0.14
query22	4.88	0.05	0.05
query23	16.25	0.32	0.14
query24	2.77	0.66	0.40
query25	0.11	0.06	0.05
query26	1.34	0.21	0.15
query27	0.04	0.04	0.04
query28	3.74	0.89	0.37
query29	12.48	4.20	3.25
query30	0.28	0.14	0.10
query31	2.78	0.56	0.33
query32	3.23	0.60	0.50
query33	3.28	3.14	3.20
query34	15.29	4.00	3.43
query35	3.35	3.33	3.38
query36	0.55	0.49	0.43
query37	0.09	0.06	0.06
query38	0.05	0.04	0.04
query39	0.04	0.03	0.03
query40	0.17	0.15	0.15
query41	0.09	0.03	0.03
query42	0.04	0.03	0.03
query43	0.05	0.03	0.04
Total cold run time: 97.17 s
Total hot run time: 24.37 s

@hello-stephen

Copy link
Copy Markdown
Contributor

FE UT Coverage Report

Increment line coverage 100.00% (8/8) 🎉
Increment coverage report
Complete coverage report

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants