Skip to content

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

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

[fix](iceberg) Harden write lifecycle and cleanup#66348
Gabriel39 wants to merge 2 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.

} else if (!req.runtime_states.empty()) {
for (auto* rs : req.runtime_states) {
if (auto rs_icd = rs->iceberg_commit_datas(); !rs_icd.empty()) {
rs->append_iceberg_commit_datas(&params.iceberg_commit_datas);

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] Enforce one budget for the aggregated report

The new guard is per task RuntimeState, but this loop concatenates every task's accepted vector into one TReportExecStatusParams. With a 100 MiB limit, two parallel sink tasks can each accept about 60 MiB and then send a roughly 120 MiB RPC, which FE rejects. By this point successful writers have cleared their local cleanup lists, so the failed report also strands the uncommitted objects. Please enforce a shared fragment/report budget before cleanup ownership is released and add a multi-task aggregate-limit test.

if (!sort_writer) {
return 0;
size_t reserve_size = 0;
for (const auto& writer : *_writer->active_writers()) {

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 reserve a full input batch per partition

Each FullSorter::get_reserve_mem_size assumes that sorter may receive state->batch_size() rows, but this loop sums that full-batch estimate across every active partition even though there is only one incoming block and its rows are divided among them. At the default 128-partition limit this can request memory for roughly 128 blocks; when that false reservation fails, revoke_memory spills only one small partition and the same block repeatedly pauses. Please bound the aggregate to the actual one-block input (or reserve after dispatch using each selected row count) and test the many-partition case.

if (!sort_writer) {
return 0;
size_t revocable_size = 0;
for (const auto& writer : *_writer->active_writers()) {

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] Retain the snapshot owner while iterating

active_writers() returns a shared_ptr by value, but this C++20 range-for dereferences that temporary directly. The temporary owner is destroyed before iterator initialization; if the async writer then publishes a replacement snapshot, the old vector can be freed while the revocation thread is traversing it. The same lifetime bug appears in the loops at lines 59 and 87. Please first store the snapshot in a named shared_ptr and iterate through that retained owner, and cover publication versus traversal concurrency.

}

@Override
protected List<String> executeAction(Table table, ConnectorSession session) {

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] Honor Iceberg's GC-disabled safety fence

This destructive path never checks gc.enabled. Iceberg uses gc.enabled=false specifically when files may be shared with another table, and its 1.10.1 orphan-file action refuses to run in that state because deletion can corrupt the other table. With dry_run=false, this implementation proceeds directly to deleteFile. Please reject execution when GC is disabled (using the Iceberg property/default) and add an execution test proving no deletion occurs.

// from being treated as children of "table".
String listingPrefix = scanLocation.endsWith("/") ? scanLocation : scanLocation + "/";
for (FileInfo file : ((SupportsPrefixOperations) table.io()).listPrefix(listingPrefix)) {
if (file.createdAtMillis() < olderThan && !reachable.contains(file.location())) {

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] Compare canonical file identities before deleting

The membership test compares raw locations. If retained metadata references s3a://bucket/key while prefix listing returns s3://bucket/key for the same object (or an equivalent authority differs), this branch treats the live file as orphaned and deletes it. Iceberg's 1.10.1 action canonicalizes both sides, treats s3/s3a/s3n as equivalent by default, and fails closed on unresolved prefix mismatches. Please adopt equivalent identity handling and add alias/mismatch tests before enabling deletion.

Set<String> reachable = collectReachableFiles(table);
long orphanCount = 0;
long deletedCount = 0;
long olderThan = namedArguments.getLong(OLDER_THAN);

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 unsafe recent orphan cutoffs

older_than is accepted as any nonnegative value, including now or a future timestamp. A concurrent writer's newly uploaded file is not yet reachable from table snapshots, so this action can list and delete it and the writer can then commit metadata pointing to the missing object. Iceberg's 1.10.1 SQL procedure rejects intervals shorter than 24 hours for exactly this race, reserving arbitrary cutoffs for its expert Action API. Please enforce the SQL safety interval (or an explicit expert-only override) and test a recent uncommitted file.


ObjectStorageResponse RateLimitedObjStorageClient::abort_multipart_upload(
const ObjectStoragePathOptions& opts) {
S3RateLimitGuard guard(S3RateLimitType::PUT, 0);

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] Ensure cleanup bypasses or reserves the PUT limit

This abort uses the same hard PUT request-count bucket as create/upload/complete. When that bucket is what caused the write failure, the immediately following abort is rejected before _inner is called; S3FileWriter::close only warns, transitions to its terminal close state, and the destructor no longer retries, leaving the multipart upload behind. Please give mandatory abort cleanup guaranteed provider admission (or retain a retryable cleanup state) and cover the exhausted-limit path rather than only an unwrapped successful abort.

Status S3FileWriter::_abort_impl() {
_wait_until_finish(
fmt::format("wait s3 file {} before abort", _obj_storage_path_opts.path.native()));
_pending_buf.reset();

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] Implement abort for Azure staged blocks

Azure uses this writer too, but create_multipart_upload intentionally returns no upload ID and upload_part stages blocks. This guard therefore skips provider cleanup, returns OK, and lets abort() mark the writer CLOSED even after Azure blocks were staged; generic upload/destructor failures have no later path deletion, so those blocks remain until provider GC. Please make abandonment provider-aware (for example, delete/discard the uncommitted blob through an Azure override), preserve cleanup failures, and add an Azure failure test.

}

private Set<String> collectReachableFiles(Table table) throws IOException {
Set<String> reachable = new HashSet<>(ReachableFileUtil.metadataFileLocations(table, true));

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] Keep the live version hint reachable

metadataFileLocations(table, true) does not include Hadoop Iceberg's metadata/version-hint.text; Iceberg exposes that path separately via ReachableFileUtil.versionHintLocation(table) and its own orphan action explicitly adds it. HadoopFileIO supports prefix listing, so a table-root run with a cutoff newer than an old hint will classify and delete this live metadata file. Please add the hint to the reachable identities and cover it in a non-dry-run test.

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

GetBlockListOptions get_options;
get_options.ListType = Models::BlockListType::All;
auto block_list = client.GetBlockList(get_options);
if (block_list.Value.CommittedBlocks.empty()) {

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 a pre-existing Put Blob object during abort

CommittedBlocks.empty() does not prove that the target key has no committed object. Azure defines this list as blocks committed through Put Block List, while this client itself writes small objects with UploadFrom / Put Blob. Because S3FileSystem::create_file_impl allows an existing key, a multipart replacement can stage blocks over such an object and, on failure, this branch deletes the valid old blob; IfMatch, when present, merely authorizes deleting the ETag that already existed. Please preserve committed content when abandoning this writer (leaving its unique staged blocks to expire if Azure cannot selectively unstage them) and add a test that seeds the key through put_object, stages a multipart block, aborts, and verifies the original bytes remain. See Azure's Get Block List contract.

deletes.forEach(delete -> reachable.add(delete.location()));
}
}
try (CloseableIterable<FileScanTask> tasks = table.newScan()

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] Read each retained manifest only once

This calls planFiles() for every retained snapshot. In an append-heavy history, later snapshots inherit earlier data manifests, so the same remote manifests and entries are reopened across snapshots and total work grows quadratically before the HashSet removes duplicate locations. This synchronous FE action can therefore become impractical on a table with a long retained history. Iceberg 1.10.1's own action deduplicates manifest paths before reading their entries. Please collect unique data manifests and read each once, and add a recording-FileIO test whose manifest-open count is bounded by unique manifests rather than snapshots.

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

@hello-stephen

Copy link
Copy Markdown
Contributor

FE Regression Coverage Report

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

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants