Skip to content

[fix](iceberg) Harden write lifecycle and cleanup - #66348

Open
Gabriel39 wants to merge 12 commits into
apache:masterfrom
Gabriel39:fix/iceberg-write-safety
Open

[fix](iceberg) Harden write lifecycle and cleanup#66348
Gabriel39 wants to merge 12 commits into
apache:masterfrom
Gabriel39:fix/iceberg-write-safety

Conversation

@Gabriel39

Copy link
Copy Markdown
Contributor

What problem does this PR solve?

Issue Number: None

Related PR: None

Problem Summary:

Iceberg writes had several correctness and resource-safety gaps across the insert lifecycle:

  • failures before execution could bypass the normal rollback and cleanup path;
  • failures after a durable external commit could incorrectly report the write as failed;
  • overwrite operations did not validate conflicts against their begin-time snapshot;
  • multi-partition sorted writes accounted for only the most recent partition's revocable memory;
  • an underlying partition-writer close error could be hidden by deferred cleanup;
  • large commit metadata could exceed the Thrift report limit after remote files were written;
  • failed writer initialization and multipart uploads could leave orphan files;
  • partition dispatch allocated full-block filters for every partition.

This change unifies lifecycle cleanup, makes post-commit refresh/listener work best-effort, adds overwrite OCC validation, publishes all active partition writers for memory accounting, propagates close failures, bounds commit metadata before reporting, adds explicit writer abort and orphan cleanup support, and dispatches rows with compact permutations.

Release note

Harden Iceberg write correctness, memory accounting, and orphan-file cleanup.

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
  • Behavior changed:

    • No.
    • Yes. External commits are not reported as failed because of post-commit cache/listener errors; concurrent overwrites now fail conflict validation; incomplete files and multipart uploads are cleaned up.
  • 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

Problem Summary:
Iceberg writes could bypass cleanup, miss overwrite conflicts, undercount
partition sorter memory, hide close errors, exceed report limits, or leave
incomplete remote files. Partition dispatch also allocated full-block filters
for every partition.

Solution:
Unify failure handling, preserve the durable commit point, validate overwrite
conflicts, account for all active sort writers, propagate close failures, bound
commit metadata, abort incomplete uploads, add orphan cleanup, and use compact
row permutations.
@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?

@Gabriel39

Copy link
Copy Markdown
Contributor Author

run buildall

@Gabriel39

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.

Requesting changes: the static review found nine actionable issues (six P1, three P2), including three paths that can delete or reference missing Iceberg data, a concurrent use-after-free, an aggregate Thrift limit bypass, pathological spill reservation, and two incomplete object-storage cleanup paths.

Critical checkpoint conclusions:

  1. Goal and proof: The PR partially hardens write/cleanup/OCC behavior, and the overwrite and external-insert lifecycle changes are credible, but the nine inline findings prevent the stated lifecycle and cleanup goals from being met. Added tests prove only narrow single-state, AWS-abort, permutation, and executor cases.
  2. Scope and clarity: The edits are generally focused, but the new destructive action reimplements Iceberg safety policy incompletely, and the provider-neutral abort surface is implemented with AWS-specific state.
  3. Concurrency: The async writer publishes active-writer snapshots while a separately scheduled revocation task reads them. Per-writer _sorter_mutex locking is consistent and no deadlock was found, but it cannot protect the snapshot container because the range-for drops its temporary owner before iteration (MAIN-BE-003).
  4. Lifecycle: Sort-writer close/cancel/deferred cleanup and external commit/listener ordering were traced end to end and otherwise preserve error ownership. S3/Azure abort lifecycle is incomplete: a rejected abort is never retried, and Azure reports successful abandonment without provider cleanup (MAIN-IO-001/002).
  5. Configuration: No new configuration was added. Existing thrift_max_message_size and S3 limiter settings are read dynamically, but their consumers do not enforce the correct aggregate/cleanup contracts.
  6. Compatibility: No storage-format or wire-field change was introduced. Default virtual methods preserve source-level parallel implementations, but that default is exactly why Azure silently lacks the new cleanup behavior.
  7. Parallel paths: Static/dynamic/branch/empty overwrites, data/delete writers, sync/async/destructor close, S3/Azure providers, and all insert executors were checked. The accepted findings identify the paths where parity is missing.
  8. Conditional checks: Existing overwrite conflict conditions are sound. The orphan action is missing the required gc.enabled fence, safe retention interval, canonical URI identity handling, and version-hint reachability.
  9. Test coverage: Coverage is not comprehensive. Missing cases include multi-task aggregate reports, concurrent snapshot publication/traversal, many-partition reserve accounting, hard-limit abort rejection, Azure staged-block abort, and execution-level orphan deletion tests for GC, retention, URI aliases, and the version hint.
  10. Test results: The new assertions are logically consistent within their narrow scopes; no result files changed. Builds and tests were intentionally not run because the authoritative review prompt forbids them, so this conclusion is static-only.
  11. Observability: Existing timers/logs cover writer/spill/commit paths, and abort failures are warned. No additional standalone observability defect was found, but logging does not substitute for retained cleanup ownership or retry.
  12. Transaction and persistence: No Doris EditLog state is added. Iceberg overwrite OCC anchors for main/branch/empty/static/dynamic cases are sound, and the durable external commit marker precedes refresh/listener suppression. The orphan action can nevertheless race a concurrent external commit (MAIN-FE-004).
  13. Data writes and atomicity: The aggregate-report failure can strand uncommitted objects; unsafe orphan deletion can corrupt shared, aliased, or concurrent writes; and multipart cleanup can leak provider state. These are blocking write-lifecycle defects.
  14. FE/BE propagation: No new FE-BE variable or Thrift field is introduced. Existing commit metadata is propagated on every producer path, but its budget is enforced per task rather than on the final aggregated RPC (MAIN-BE-001).
  15. Performance: Compact permutation dispatch is semantically sound, and bounded snapshot-copy cost is not independently material. Summing one full-batch reserve estimate per active partition can request roughly 128 blocks and repeatedly spill tiny partitions (MAIN-BE-002).
  16. Other issues: Error precedence, close idempotence, spill cleanup, overwrite validation, permission checks, location containment, and post-commit listener behavior produced no additional substantiated defects.

User focus: review_focus.txt contained no additional focus guidance, so the whole PR was reviewed without a narrower focus.

Review status: complete and converged in round 3. All final full-scope and risk-focused reviewers returned NO_NEW_VALUABLE_FINDINGS for this exact comment set. Static review only; no builds or tests were run.

Comment thread be/src/exec/pipeline/pipeline_fragment_context.cpp
Comment thread be/src/exec/operator/spill_iceberg_table_sink_operator.cpp Outdated
Comment thread be/src/exec/operator/spill_iceberg_table_sink_operator.cpp Outdated
Comment thread be/src/io/fs/rate_limited_obj_storage_client.cpp Outdated
Comment thread be/src/io/fs/s3_file_writer.cpp
@hello-stephen

Copy link
Copy Markdown
Contributor

FE UT Coverage Report

Increment line coverage 18.75% (3/16) 🎉
Increment coverage report
Complete coverage report

@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	17724	4095	4036	4036
q2	2006	335	200	200
q3	10328	1369	813	813
q4	4688	483	343	343
q5	7525	844	555	555
q6	187	170	136	136
q7	771	784	599	599
q8	9346	1605	1623	1605
q9	5334	4112	4083	4083
q10	6769	1631	1355	1355
q11	501	357	331	331
q12	709	575	454	454
q13	18114	3320	2756	2756
q14	260	262	247	247
q15	q16	748	732	654	654
q17	928	919	987	919
q18	6906	5853	5666	5666
q19	1360	1268	1053	1053
q20	808	657	605	605
q21	6000	2578	2390	2390
q22	447	364	307	307
Total cold run time: 101459 ms
Total hot run time: 29107 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	4319	4280	4276	4276
q2	297	314	215	215
q3	4535	4912	4383	4383
q4	2217	2258	1414	1414
q5	4276	4138	4121	4121
q6	231	176	130	130
q7	1706	1595	1391	1391
q8	2231	1955	1892	1892
q9	6827	6773	6754	6754
q10	4263	4253	3830	3830
q11	524	388	404	388
q12	715	711	510	510
q13	2979	3259	2765	2765
q14	292	304	261	261
q15	q16	670	693	607	607
q17	1268	1234	1237	1234
q18	7298	7009	6779	6779
q19	1072	1049	1074	1049
q20	2214	2232	1943	1943
q21	5323	4580	4471	4471
q22	524	469	423	423
Total cold run time: 53781 ms
Total hot run time: 48836 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-DS: Total hot run time: 169793 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 6ebd31859839dabd9ae88df95c3c1834ed99d722, data reload: false

query5	3980	613	479	479
query6	476	231	217	217
query7	4824	562	336	336
query8	325	179	163	163
query9	8513	4033	4021	4021
query10	454	357	299	299
query11	5709	2212	1980	1980
query12	150	100	99	99
query13	1266	608	412	412
query14	5318	4673	4396	4396
query14_1	3834	3792	3774	3774
query15	201	193	182	182
query16	1031	490	457	457
query17	868	679	542	542
query18	2574	470	345	345
query19	205	194	156	156
query20	104	107	106	106
query21	175	161	134	134
query22	13035	12884	12868	12868
query23	17358	16425	16089	16089
query23_1	16147	16100	16089	16089
query24	7499	1666	1250	1250
query24_1	1249	1255	1238	1238
query25	565	457	387	387
query26	1228	360	215	215
query27	2592	634	373	373
query28	4442	2056	2026	2026
query29	1125	631	496	496
query30	348	267	226	226
query31	1134	1082	950	950
query32	120	64	60	60
query33	565	326	248	248
query34	1178	1116	599	599
query35	735	744	637	637
query36	798	780	686	686
query37	158	112	96	96
query38	1867	1659	1609	1609
query39	824	830	825	825
query39_1	770	783	787	783
query40	249	163	153	153
query41	69	70	71	70
query42	96	95	94	94
query43	314	323	273	273
query44	1405	760	768	760
query45	201	175	171	171
query46	1045	1189	718	718
query47	1551	1539	1399	1399
query48	394	419	302	302
query49	591	418	305	305
query50	1033	450	344	344
query51	10626	10576	10510	10510
query52	86	89	77	77
query53	272	281	202	202
query54	288	249	244	244
query55	78	73	70	70
query56	322	322	317	317
query57	1010	1003	923	923
query58	316	259	254	254
query59	1506	1604	1352	1352
query60	341	269	255	255
query61	146	152	150	150
query62	396	318	264	264
query63	242	196	198	196
query64	2828	1033	855	855
query65	3868	3841	3860	3841
query66	1802	471	355	355
query67	28286	28254	28153	28153
query68	3061	1582	1060	1060
query69	405	311	273	273
query70	898	787	811	787
query71	379	330	308	308
query72	3025	2617	2290	2290
query73	853	856	415	415
query74	4591	4485	4297	4297
query75	2345	2356	1997	1997
query76	2335	1112	746	746
query77	329	361	276	276
query78	11206	11081	10586	10586
query79	1392	1191	741	741
query80	1270	541	454	454
query81	559	322	283	283
query82	563	148	116	116
query83	425	320	292	292
query84	322	157	129	129
query85	1011	607	529	529
query86	430	227	224	224
query87	1806	1795	1693	1693
query88	3690	2833	2795	2795
query89	399	315	289	289
query90	1781	200	187	187
query91	198	190	159	159
query92	66	61	59	59
query93	1720	1542	943	943
query94	711	346	310	310
query95	769	491	487	487
query96	1039	777	347	347
query97	2447	2443	2335	2335
query98	208	197	189	189
query99	719	718	611	611
Total cold run time: 249432 ms
Total hot run time: 169793 ms

@hello-stephen

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

query1	0.01	0.01	0.01
query2	0.09	0.05	0.06
query3	0.25	0.13	0.13
query4	1.61	0.14	0.14
query5	0.24	0.22	0.23
query6	1.16	0.84	0.81
query7	0.04	0.01	0.00
query8	0.06	0.04	0.04
query9	0.38	0.32	0.32
query10	0.58	0.59	0.58
query11	0.19	0.13	0.13
query12	0.18	0.15	0.15
query13	0.45	0.48	0.48
query14	1.01	1.00	1.00
query15	0.59	0.59	0.58
query16	0.30	0.34	0.31
query17	1.11	1.12	1.14
query18	0.21	0.19	0.20
query19	2.05	1.89	1.96
query20	0.02	0.02	0.01
query21	15.42	0.21	0.13
query22	4.89	0.05	0.06
query23	16.12	0.31	0.13
query24	2.91	0.43	0.32
query25	0.11	0.05	0.05
query26	0.71	0.20	0.14
query27	0.04	0.03	0.03
query28	3.52	0.79	0.32
query29	12.49	4.03	3.20
query30	0.27	0.16	0.15
query31	2.76	0.55	0.32
query32	3.23	0.58	0.49
query33	3.20	3.26	3.14
query34	15.57	4.13	3.29
query35	3.25	3.20	3.22
query36	0.55	0.44	0.45
query37	0.09	0.07	0.06
query38	0.05	0.04	0.04
query39	0.04	0.03	0.03
query40	0.17	0.14	0.15
query41	0.07	0.03	0.03
query42	0.04	0.03	0.03
query43	0.04	0.03	0.03
Total cold run time: 96.07 s
Total hot run time: 23.92 s

@hello-stephen

Copy link
Copy Markdown
Contributor

BE UT Coverage Report

Increment line coverage 40.29% (83/206) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 59.17% (25636/43323)
Line Coverage 43.24% (257196/594852)
Region Coverage 38.91% (203842/523919)
Branch Coverage 40.27% (93046/231076)

@hello-stephen

Copy link
Copy Markdown
Contributor

BE Regression && UT Coverage Report

Increment line coverage 68.45% (141/206) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 75.68% (31980/42255)
Line Coverage 60.31% (356346/590877)
Region Coverage 56.86% (298902/525687)
Branch Coverage 58.28% (134698/231114)

@hello-stephen

Copy link
Copy Markdown
Contributor

FE Regression Coverage Report

Increment line coverage 18.75% (3/16) 🎉
Increment coverage report
Complete coverage report

### What problem does this PR solve?

Issue Number: None

Related PR: apache#66348

Problem Summary: Parallel Iceberg sink tasks could exceed the shared report limit, partition writers could over-reserve memory or outlive a temporary snapshot, failed multipart uploads could skip mandatory cleanup, and orphan-file deletion lacked Iceberg safety fences. Share the report budget, retain writer snapshots, bound reservations, make abort cleanup provider-aware, and enforce safe canonical orphan detection.

### Release note

Improve Iceberg write failure cleanup and orphan-file deletion safety.

### Check List (For Author)

- Test: Unit Test
    - BE ASAN focused tests with Azure support enabled
    - Full fe-connector-iceberg unit test suite
- Behavior changed: Yes, unsafe orphan cutoffs and GC-disabled deletion are rejected, and failed multipart uploads are cleaned up reliably.
- Does this need documentation: No
@Gabriel39

Copy link
Copy Markdown
Contributor Author

run buildall

@Gabriel39

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.

Request changes: two distinct issues remain after the current fixes: Azure abort can delete a valid pre-existing Put Blob object, and orphan-file reachability repeatedly scans inherited manifests with quadratic cost. I did not duplicate the existing review threads.

Critical checkpoint conclusions:

  • Goal and proof: The PR broadly hardens Iceberg write/cleanup lifecycle, OCC, reporting limits, and orphan cleanup, but the Azure data-preservation goal is not yet met. The added tests cover nominal cleanup and OCC paths, not the two cases below.
  • Scope and clarity: The changes are cohesive for the stated lifecycle work. The orphan action's hand-built reachability scan is the one area that should reuse Iceberg's unique-manifest approach.
  • Concurrency: Async writer publication is protected by retained shared snapshots and per-sorter locking; the shared report budget has a consistent lock order. Azure's committed-block test is not a valid ownership fence for an existing object.
  • Lifecycle: FE before-execution cleanup and post-commit error boundaries are sound, as are the reviewed nested writer close paths. Azure abort still destroys state it does not own.
  • Configuration: No new configuration behavior requires dynamic-reload handling.
  • Compatibility: No FE-BE or storage-format incompatibility was found. The changed Azure block-ID format has a bounded retry impact and did not survive as a separate finding.
  • Parallel paths: S3, Azure, single-shot, multipart, committer, overwrite, branch, static/dynamic overwrite, and delete/merge paths were traced; the surviving provider-specific issue is inline.
  • Conditional checks: GC, retention, URI containment, identity ambiguity, and overwrite validation checks are fail-closed. CommittedBlocks.empty() is the invalid special case.
  • Test coverage: Unit coverage is substantial but misses preservation of an existing Put Blob target and an append-heavy multi-snapshot reachability scan. No end-to-end result covers either.
  • Test results: No tests or builds were run because the authoritative review task is static-only; no generated result files were changed.
  • Observability: Existing status/log paths are adequate for the reviewed failures; no additional metric was required for a correctness conclusion.
  • Transactions and persistence: Iceberg overwrite OCC, including empty-table and branch anchors, is sound. No EditLog or master-failover persistence change is involved.
  • Data writes: Commit/report cleanup ownership is sound outside the Azure abort branch; that branch can lose previously committed data and blocks approval.
  • FE-BE variables: No new cross-version transmitted variable or missing propagation path was found.
  • Performance: Per-snapshot planFiles() causes the P2 quadratic manifest-read issue below; no other distinct hot-path issue survived duplicate suppression.
  • Other issues: The sorter aggregate-reservation concern remains covered by the existing thread, and cleanup admission/terminal-state concerns remain covered by the existing Azure/rate-limit threads, so they are not repeated here.

User focus: no additional user-provided focus was present; the full PR was reviewed.

Comment thread be/src/io/fs/azure_obj_storage_client.cpp Outdated
@hello-stephen

Copy link
Copy Markdown
Contributor

FE UT Coverage Report

Increment line coverage 18.75% (3/16) 🎉
Increment coverage report
Complete coverage report

@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	17734	4014	4200	4014
q2	2025	322	212	212
q3	10260	1426	849	849
q4	4715	473	338	338
q5	7659	840	558	558
q6	196	174	137	137
q7	733	813	590	590
q8	10415	1459	1590	1459
q9	5582	4080	4036	4036
q10	6807	1625	1362	1362
q11	527	346	342	342
q12	753	565	458	458
q13	18112	3337	2794	2794
q14	264	259	245	245
q15	q16	736	740	663	663
q17	1027	977	1049	977
q18	6986	5815	5492	5492
q19	1191	1315	1054	1054
q20	771	684	599	599
q21	5794	2661	2529	2529
q22	442	357	296	296
Total cold run time: 102729 ms
Total hot run time: 29004 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	4429	4359	4262	4262
q2	281	319	211	211
q3	4560	4972	4362	4362
q4	2164	2256	1413	1413
q5	4304	4144	4124	4124
q6	229	176	129	129
q7	1695	1888	1731	1731
q8	2500	2078	2109	2078
q9	7351	7231	7252	7231
q10	4323	4277	3878	3878
q11	568	416	395	395
q12	739	749	509	509
q13	3253	3703	2962	2962
q14	317	307	276	276
q15	q16	726	738	647	647
q17	1372	1331	1262	1262
q18	7878	7307	7282	7282
q19	1099	1082	1087	1082
q20	2193	2208	1921	1921
q21	5323	4643	4500	4500
q22	534	454	406	406
Total cold run time: 55838 ms
Total hot run time: 50661 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-DS: Total hot run time: 169560 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 1241244cc139a902b16c5f3ce1fea1335d4fa123, data reload: false

query5	4327	615	455	455
query6	475	228	202	202
query7	4911	575	348	348
query8	333	196	168	168
query9	8769	4044	4075	4044
query10	457	337	296	296
query11	5849	2196	2002	2002
query12	154	97	95	95
query13	1288	553	438	438
query14	6095	4702	4367	4367
query14_1	3857	3809	3803	3803
query15	215	204	179	179
query16	1011	475	466	466
query17	1133	738	541	541
query18	2438	460	331	331
query19	204	178	137	137
query20	108	100	98	98
query21	231	151	134	134
query22	13039	13083	12822	12822
query23	17152	16281	15998	15998
query23_1	16123	16080	16068	16068
query24	7462	1684	1224	1224
query24_1	1262	1261	1279	1261
query25	520	424	341	341
query26	1326	372	214	214
query27	2595	603	374	374
query28	4468	2023	2030	2023
query29	1022	586	464	464
query30	350	261	221	221
query31	1102	1069	935	935
query32	99	62	58	58
query33	512	307	241	241
query34	1166	1178	660	660
query35	751	748	641	641
query36	782	782	706	706
query37	153	105	95	95
query38	1833	1639	1612	1612
query39	812	808	797	797
query39_1	789	781	780	780
query40	247	163	142	142
query41	65	62	64	62
query42	101	94	93	93
query43	318	321	284	284
query44	1411	775	754	754
query45	184	169	169	169
query46	1024	1162	738	738
query47	1522	1508	1411	1411
query48	397	407	300	300
query49	588	403	294	294
query50	1010	418	329	329
query51	10784	10644	10642	10642
query52	87	94	76	76
query53	264	279	210	210
query54	301	257	236	236
query55	80	73	69	69
query56	319	316	302	302
query57	1004	997	921	921
query58	286	253	277	253
query59	1534	1600	1462	1462
query60	338	282	261	261
query61	175	167	169	167
query62	406	325	266	266
query63	232	203	205	203
query64	2932	1141	966	966
query65	3944	3830	3838	3830
query66	1828	497	376	376
query67	28341	28181	28062	28062
query68	3469	1491	926	926
query69	414	301	289	289
query70	911	821	799	799
query71	370	337	315	315
query72	3037	2617	2311	2311
query73	794	795	435	435
query74	4631	4474	4295	4295
query75	2392	2344	1999	1999
query76	2323	1150	724	724
query77	339	381	279	279
query78	11169	11189	10572	10572
query79	1364	1154	760	760
query80	1269	550	456	456
query81	517	327	280	280
query82	622	150	116	116
query83	365	320	298	298
query84	272	160	128	128
query85	970	599	524	524
query86	405	226	209	209
query87	1786	1801	1724	1724
query88	3737	2818	2788	2788
query89	395	317	289	289
query90	1893	201	200	200
query91	204	188	161	161
query92	60	61	56	56
query93	1663	1525	1002	1002
query94	719	368	314	314
query95	792	509	468	468
query96	1114	810	360	360
query97	2455	2457	2313	2313
query98	214	195	190	190
query99	724	723	612	612
Total cold run time: 256498 ms
Total hot run time: 169560 ms

@hello-stephen

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

query1	0.01	0.00	0.01
query2	0.10	0.05	0.04
query3	0.26	0.14	0.13
query4	1.61	0.14	0.14
query5	0.24	0.23	0.22
query6	1.16	0.83	0.83
query7	0.03	0.01	0.01
query8	0.06	0.04	0.04
query9	0.39	0.32	0.33
query10	0.59	0.54	0.56
query11	0.19	0.14	0.14
query12	0.18	0.14	0.14
query13	0.47	0.46	0.46
query14	1.00	1.00	0.99
query15	0.60	0.59	0.58
query16	0.31	0.32	0.33
query17	1.07	1.08	1.08
query18	0.22	0.20	0.20
query19	2.03	2.01	1.95
query20	0.03	0.01	0.02
query21	15.46	0.24	0.13
query22	4.77	0.06	0.05
query23	16.12	0.31	0.12
query24	2.95	0.42	0.33
query25	0.11	0.06	0.04
query26	0.72	0.20	0.16
query27	0.05	0.03	0.04
query28	3.55	0.77	0.35
query29	12.47	4.07	3.19
query30	0.27	0.16	0.16
query31	2.78	0.54	0.31
query32	3.23	0.58	0.49
query33	3.22	3.16	3.26
query34	15.58	3.94	3.28
query35	3.24	3.23	3.24
query36	0.56	0.45	0.43
query37	0.10	0.06	0.06
query38	0.05	0.04	0.04
query39	0.04	0.03	0.03
query40	0.17	0.17	0.16
query41	0.08	0.03	0.03
query42	0.04	0.03	0.03
query43	0.04	0.03	0.04
Total cold run time: 96.15 s
Total hot run time: 23.98 s

@hello-stephen

Copy link
Copy Markdown
Contributor

BE UT Coverage Report

Increment line coverage 36.68% (95/259) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 59.18% (25642/43332)
Line Coverage 43.24% (257265/594944)
Region Coverage 38.93% (203959/523957)
Branch Coverage 40.27% (93050/231088)

@hello-stephen

Copy link
Copy Markdown
Contributor

BE Regression && UT Coverage Report

Increment line coverage 59.46% (154/259) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 75.80% (32034/42263)
Line Coverage 60.46% (357286/590968)
Region Coverage 57.19% (300637/525724)
Branch Coverage 58.55% (135316/231126)

@Gabriel39

Copy link
Copy Markdown
Contributor Author

run buildall

@Gabriel39

Copy link
Copy Markdown
Contributor Author

/review

@hello-stephen

Copy link
Copy Markdown
Contributor

Cloud UT Coverage Report

Increment line coverage 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 77.57% (1912/2465)
Line Coverage 64.53% (34218/53024)
Region Coverage 64.41% (17269/26809)
Branch Coverage 53.97% (9255/17148)

@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.

Request changes: one rolling-upgrade compatibility bug remains in Azure multipart uploads. A failed pre-upgrade upload can leave 4-byte uncommitted block IDs, while the new FE/BE path stages 47-byte IDs; Azure rejects the first new Put Block until those legacy blocks are garbage-collected. The inline comment contains the concrete scenario, fix direction, and missing regression.

Review scope: I reviewed the full 65-file authoritative patch and its FE/BE, Iceberg, filesystem, pipeline, reporting, cleanup, and test call paths. review_focus.txt supplied no additional focus, so the whole PR was reviewed. Existing live threads were treated as the duplicate baseline; no already-reported issue is repeated here.

Part 1.3 checkpoint conclusions:

  • Goal and proof: The patch substantially hardens Iceberg write OCC, reporting, memory reservation, publication, and cleanup, but it does not yet accomplish rolling-safe Azure retry because of the blocker above. The new Azure tests cover new-format IDs, not a retained legacy block followed by a new upload.
  • Scope and clarity: The patch is broad because it closes interacting FE/BE lifecycle gaps across 65 files. The individual changes generally follow existing abstractions; I found no smaller standalone defect beyond the inline compatibility issue and existing live threads.
  • Concurrency: I traced pipeline-task report aggregation, async-writer queue handoff, sorter snapshots/reservations, and same-key Azure writers. Shared report-budget and reservation ownership are consistent on current head; no new deadlock, lock-order, or token-release bug remains beyond existing comments.
  • Lifecycle: Reservation tokens, async close/abort, commit-data ownership, connector/TCCL scope, post-commit hooks, and orphan-file cleanup were followed through success, failure, cancellation, and crash-adjacent paths. The accepted Azure legacy-block namespace transition is the only new lifecycle failure.
  • Configuration: No new user-facing configuration knob is introduced. The existing coordinator Thrift limit is propagated to BE for the report budget; no separate dynamic-configuration defect was established.
  • Compatibility: Optional Thrift fields, FE/BE completion fallbacks, exact block-ID transport, and Iceberg snapshot pins were reviewed. The 4-byte-to-47-byte Azure block-ID transition is not compatible with legacy uncommitted state and blocks approval.
  • Parallel paths: Both FE and BE Azure multipart implementations, the shared object-storage writer/abort layer, S3 behavior, and deferred FE completion were checked. No missing parallel-path update remains outside the accepted finding and existing threads.
  • Special conditions: Explicit-empty snapshots, exact-versus-legacy block-ID selection, ownership fences, retention cutoffs, rate limits, and terminal-report gating were checked against upstream guarantees. No additional unsafe condition was found.
  • Test coverage: The patch adds targeted BE and FE unit coverage for its principal changes, but the Azure suite lacks the negative rolling-upgrade case that seeds a legacy uncommitted block and retries the same key with the new format. End-to-end behavior for that case is therefore unproved.
  • Test results: This review was static-only as required; I did not run builds or tests. I inspected the added test code and expected assertions, but do not independently claim execution results.
  • Observability: Error propagation, cleanup logs, report rejection, and provider exception context were reviewed. Existing diagnostics are adequate for the changed paths; no distinct logging or metrics blocker was found.
  • Transaction and persistence: Iceberg statement pins, branch ancestry/OCC checks, commit/rollback boundaries, cache refresh, and edit-log-adjacent post-commit behavior were traced. Durable publication precedes best-effort hooks and no new failover/persistence hole was established.
  • Data writes: Exact snapshot fencing and atomic Iceberg/Azure publication paths otherwise preserve committed/uncommitted visibility. The accepted issue causes valid post-upgrade Azure retries to fail before publication; it does not rely on speculative corruption.
  • FE/BE variables: The new report limit, snapshot/write context, upload ID, and exact staged block ID are carried through the relevant send/receive paths. Rolling handling is otherwise fail-closed, but the Azure ID-length transition remains unsupported.
  • Performance: Shared report budgeting, bounded orphan enumeration, streamed deletion candidates, and memory reservations were reviewed. The remaining full-batch-per-sorter over-reservation is already covered by live comment 3695317514; no new nonduplicate performance issue remains.
  • Other issues: Security review was not requested. After duplicate suppression and two converged review rounds, no other valuable current-head finding remains.

BlockBlobClient blockBlobClient = getClient().getBlobContainerClient(uri.container())
.getBlobClient(uri.key()).getBlockBlobClient();
String blockId = toBlockId(partNum);
String blockId = multipartBlockId(uploadId, partNum);

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.

[P1] Preserve compatibility with legacy uncommitted block IDs. A failed or crashed pre-upgrade upload can leave 4-byte decoded block IDs on this blob (the old toBlockId format), while this retry now stages a 47-byte decoded UUID:part ID. Azure rejects Put Block with HTTP 400 when its ID length differs from existing uncommitted blocks, so a valid same-key retry remains unusable until service GC. This happens before the exact-ID completion safeguards and is not covered by the new-ID-only tests. Please keep a compatible decoded length during the rolling window or isolate the new upload from the legacy block namespace, and seed a legacy residual block in the regression test. Azure contract: https://learn.microsoft.com/en-us/rest/api/storageservices/put-block

@Gabriel39

Copy link
Copy Markdown
Contributor Author

run buildall

@Gabriel39

Copy link
Copy Markdown
Contributor Author

/review

@Gabriel39

Copy link
Copy Markdown
Contributor Author

run buildall

@Gabriel39

Copy link
Copy Markdown
Contributor Author

/review

@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Codex automated review failed and did not complete.

Error: Codex completed, but no new pull request review was submitted for the current head SHA.
Workflow run: https://github.com/apache/doris/actions/runs/30783843765

Please inspect the workflow logs and rerun the review after the underlying issue is resolved.

@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Codex automated review failed and did not complete.

Error: Review step was failure (possibly timeout or cancelled)
Workflow run: https://github.com/apache/doris/actions/runs/30783947011

Please inspect the workflow logs and rerun the review after the underlying issue is resolved.

@hello-stephen

Copy link
Copy Markdown
Contributor

BE UT Coverage Report

Increment line coverage 45.92% (169/368) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 59.30% (25724/43383)
Line Coverage 43.34% (258130/595570)
Region Coverage 38.99% (204506/524479)
Branch Coverage 40.36% (93373/231348)

@924060929 924060929 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.

Requesting changes for one remaining Azure multipart correctness blocker. Matching the legacy four-byte block-ID length fixes the rolling-upgrade 400 response, but hashing each upload UUID into 32 bits no longer guarantees same-target writer isolation and can publish another writer’s bytes.

for (byte value : uploadId.getBytes(StandardCharsets.UTF_8)) {
uploadNamespace = (uploadNamespace ^ (value & 0xFF)) * 0x01000193;
}
int namespacedPart = uploadNamespace + partNum;

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.

[P1] Do not compress writer identity into a colliding 32-bit block namespace

The four-byte length fixes compatibility with legacy residual blocks, but FNV-1a-32(uploadId) + partNum cannot isolate concurrent writers. For example, these two valid UUIDs both hash to 541563024 under this exact implementation:

  • c8e3f981-50d5-46d3-8c3a-2d2723f686ee
  • dd3cf642-9f55-4b6b-957d-0616633116ea

For part 1 both therefore generate kZhHIA==. If writer A stages that ID, writer B stages the same ID, and A commits its block list, Azure commits B’s last-staged bytes while A reports success. Partial overlap is also possible because each upload occupies the additive range [hash + 1, hash + partCount]. This can publish a mixed/corrupt data file whose Iceberg metadata belongs to another writer.

The current test only proves upload-a and upload-b differ; it cannot establish uniqueness in a 32-bit space. Azure documents that the last upload of a repeated uncommitted block ID is what a subsequent block-list commit selects: https://learn.microsoft.com/en-us/rest/api/storageservices/put-block

Please use target-level coordination such as a lease, or another publication design that preserves both legacy-length compatibility and deterministic writer isolation. Changing the 32-bit hash function cannot fix the namespace-size limitation.

@Gabriel39

Copy link
Copy Markdown
Contributor Author

run buildall

@Gabriel39

Copy link
Copy Markdown
Contributor Author

/review

@hello-stephen

Copy link
Copy Markdown
Contributor

BE Regression && UT Coverage Report

Increment line coverage 73.84% (271/367) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 75.83% (32082/42308)
Line Coverage 60.48% (357807/591572)
Region Coverage 56.98% (299866/526236)
Branch Coverage 58.35% (135004/231382)

@hello-stephen

Copy link
Copy Markdown
Contributor

FE Regression Coverage Report

Increment line coverage 5.33% (4/75) 🎉
Increment coverage report
Complete coverage report

@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.

Static review of the full 66-file diff found two new blocking correctness issues, annotated inline.

Checkpoint conclusions:

  • Goal and tests: the intended hardening is not yet proved end to end. Queued sorter reservations are based on stale state, and old-BE/new-FE Azure compatibility can publish Hive metadata before object publication. Added tests do not exercise either actual ordering or payload.
  • Scope and architecture: the changes are generally focused and reuse existing writer and transaction abstractions; the remaining failures occur at async-queue and rolling-protocol boundaries.
  • Concurrency and lifecycle: active-writer snapshots, per-sorter locking, transfer of the dequeued reservation, and the new-version Azure lease flow are coherent. Existing threads already cover terminal token cleanup, retained sorted memory, cross-task cleanup, the FE output-stream lease bypass, and provider abort retry, so those were not duplicated.
  • Compatibility and configuration: the optional Thrift field is wire-compatible, while the framed receiver-limit gap remains tracked in an existing thread. New-BE/old-FE Azure completion fails closed; old-BE/new-FE does not.
  • Data integrity and persistence: Iceberg OCC and explicit-empty improvements are coherent, while the selector-canonicalization and shared-root orphan-cleanup concerns remain tracked in existing threads. The two new P1 issues still block safely completing this patch.
  • Tests, observability, and performance: this was a static-only review, so no build or tests were run. Existing tests miss multi-queued sorter-state advancement and the real base-BE payload with no pending upload.

No additional user review focus was provided; the whole PR was reviewed.

per_partition_reservations.reserve(active_writers->size());
for (const auto& writer : *active_writers) {
if (auto* sort_writer = dynamic_cast<VIcebergSortWriter*>(writer.get())) {
auto reservation = sort_writer->get_reserve_mem_size_components(state, eos);

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.

[P1] Account for sorter state already queued ahead of this block

This estimate samples the async writer's currently published sorter state, but its queue accepts three blocks. If the consumer is stalled, all three producer-side calls can see the same pre-queue rows/capacity; after earlier queued blocks append, a later token can cross the 85% growth or sort threshold without reserving that allocation. The earlier tokens have already been shrunk by then, so transferring each token across threads still lets this sink exceed the hard memory limit instead of spilling. Please account for projected sorter state from blocks already queued ahead (or allow only one outstanding block), and add a deterministic multi-queued boundary test.

.allMatch(part -> part.etag() != null && !part.etag().isEmpty());
for (UploadPartResult part : sorted) {
blockIds.add(toBlockId(part.partNumber()));
// Missing IDs identify an older BE upload, whose blocks use the legacy namespace.

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.

[P1] Reject old-BE Azure writes before publishing Hive metadata

The target-branch Azure BE returns success from multipart initiation without an upload ID. Because Hive defers completion, its blocks stay uncommitted, and VHivePartitionWriter omits the entire pending-upload record when that ID is empty, so this fallback is never invoked. The FE can then commit the reported file name while the blob is still invisible; if an old-BE update is merged first, HiveWriteUtils also drops later new-BE pending tokens. Please require one Azure completion record per reported file or capability-gate deferred Azure writes during rolling upgrade, and test an actual base-BE update with no pending list plus the mixed merge order, rather than a fabricated ID with empty ETags. Azure publishes staged blocks only through Put Block List.

@Gabriel39

Copy link
Copy Markdown
Contributor Author

run buildall

@Gabriel39

Copy link
Copy Markdown
Contributor Author

/review

@Gabriel39

Copy link
Copy Markdown
Contributor Author

run buildall

@Gabriel39

Copy link
Copy Markdown
Contributor Author

/review

@hello-stephen

Copy link
Copy Markdown
Contributor

FE UT Coverage Report

Increment line coverage 40.74% (11/27) 🎉
Increment coverage report
Complete coverage report

@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.

Automated review: changes requested.

I found six correctness/resource-safety issues: five P1 and one P2. Three are attached inline; three narrower follow-ups are posted on the existing Azure rolling-compatibility, orphan-cleanup ownership, and report-size discussions.

Critical checkpoint conclusions:

  • Goal/fitness: The PR aims to harden external-table write lifecycle, commit validation, cleanup, memory admission, report sizing, and Azure compatibility. It makes substantial progress, but MAIN-F1 through MAIN-F6 show the goal is not yet met on important failure/race paths.
  • Scope/clarity: All 71 changed files form a coherent hardening change; I found no unrelated edits. The main paths are reasonably factored and documented, but the six edge contracts below remain incomplete.
  • Concurrency/locking: I traced pipeline producer vs async writer, spill/revoke/final merge, concurrent Iceberg commits, and Azure same-key writers. Reservation ownership and lock ordering are mostly coherent and I found no new deadlock, but the EOS fan-in is under-reserved (F1) and async-open failure retains a queued token (F2).
  • Lifecycle/static initialization: Multipart upload, queued-token, writer cleanup, transaction, and action lifecycles were traced through success, cancellation, and failures. F2 and F6 leave resource ownership at the wrong boundary. No cross-TU static/global initialization problem was introduced.
  • Configuration: The new session/query-option budget path has defaults and FE-to-BE propagation. No dynamic-config-specific defect was found; the report budget itself is incomplete because it excludes the variable envelope (F6).
  • Compatibility: Old-BE/new-FE, new-BE/old-FE, optional Thrift fields, and Azure four-byte block-ID behavior were checked. The finite lease stops fencing an old FE after expiry (F4), so rolling compatibility is not safe.
  • Parallel/special paths: Sorted and unsorted table writes, delete writes, full/static/dynamic overwrite, RowDelta/branch paths, S3/Azure completion, and custom/default orphan scan scopes were checked. F3 and F5 remain uncovered special cases; no additional parallel-path defect survived verification.
  • Tests/results: The PR adds broad BE and FE unit coverage, but it misses the empty-sorter multi-run EOS merge, enqueue-before-open-failure, empty-head check-to-refresh race, expired-lease rolling completion, same-suffix shared-root cleanup, and full near-limit report-envelope cases. Tests were not run because this review environment explicitly permits static review only.
  • Observability: Existing status propagation, logs, and profile accounting cover most touched paths; I found no separate metrics-only blocker. F6 is more severe than observability because the final report may fail after cleanup ownership is released.
  • Transactions/persistence/failover: No Doris EditLog schema/replay change was introduced. Connector OCC, metadata publication, cleanup, and failure boundaries were traced; F3, F4, and F5 violate commit/ownership fencing.
  • Data-write atomicity/crash safety: Multipart publication, Iceberg commit, abort, and orphan cleanup were checked. The accepted findings can overwrite concurrent data, republish stale Azure bytes, delete another table's live files, or strand successfully written files.
  • FE-BE variables/protocol: The new query option and exact block IDs are carried through the touched send/receive paths, but rolling behavior (F4) and whole-report sizing (F6) remain unsafe. No other stable mismatch was found.
  • Performance/other: Reachability caps, hash scans, admission bookkeeping, and merge costs were reviewed. F1 permits final merge allocation beyond the admitted estimate; no additional stable performance or Nereids semantic issue emerged.

User focus: no additional focus was supplied, so I reviewed the entire PR.

Please address all six findings before approval.


return sort_writer->get_reserve_mem_size(state, eos);
// Column growth remains in every touched sorter, while sorting workspace is reused by serial dispatch.
// The final queued item may contain rows and also owns the reservation used by async finish().

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.

[P1] Reserve the final spill-merge fan-in on EOS. When a sorter has already spilled its in-memory block, its reserve components are zero, so this path retains only the minimum allowance. finish() nevertheless enters _combine_files_output(), whose merger constructs every input cursor and immediately reads one spill block per run (up to the configured merge fan-in, 64 MiB by default) before producing output. That work runs under this transferred reservation but is materially under-reserved and can cross a hard query/process limit. Please include EOS merge inputs/output workspace in the estimate and cover an empty sorter with several spill runs under a tight limit.

if (rows) {
_memory_used_counter->update(add_block->allocated_bytes());
}
_data_queue.emplace_back(QueuedBlock {

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] Release queued reservation tokens when asynchronous open() fails. The producer can enqueue here while the writer thread is still opening; force_close() then makes while (_writer_status.ok()) skip the only _data_queue.clear() path, and the token stays charged until writer destruction even though the finish dependency is ready. This can retain a large query/process/workload-group reservation throughout cancellation. Please clear or swap the queue on every terminal exit and add an enqueue-before-open-failure test that verifies the accounting returns to baseline.

if (readSnapshotId < 0) {
// An explicit empty read must conflict with any snapshot created before beginWrite.
if (targetHead != null) {
throw new DorisConnectorException("Iceberg table " + tableName

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.

[P1] Preserve the explicit-empty generation fence across the transaction refresh. applyBeginGuards() observes an empty head and converts the pinned -1 to null here, but openTransaction() immediately follows and newTransaction() refreshes the mutable table. If another writer creates the first snapshot in that interval, it becomes this transaction's base; the overwrite paths then skip validateFromSnapshot because baseSnapshotId is null and can replace that concurrent data. Please retain the -1 OCC sentinel (as the RowDelta path does) or perform the empty-head check against the refreshed transaction base, with an injected check-to-refresh race test.

@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Codex automated review failed and did not complete.

Error: Review step was failure (possibly timeout or cancelled)
Workflow run: https://github.com/apache/doris/actions/runs/30801617409

Please inspect the workflow logs and rerun the review after the underlying issue is resolved.

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.

3 participants