Skip to content

[fix](be) remove changing segment cache blocks to index type#65905

Open
bobhan1 wants to merge 2 commits into
apache:masterfrom
bobhan1:agent/remove-segment-writer-cache-type-change
Open

[fix](be) remove changing segment cache blocks to index type#65905
bobhan1 wants to merge 2 commits into
apache:masterfrom
bobhan1:agent/remove-segment-writer-cache-type-change

Conversation

@bobhan1

@bobhan1 bobhan1 commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

What problem does this PR solve?

Issue Number: None

Related PR: None

Problem Summary:

After a cloud segment is finalized, SegmentWriter allocates a cache holder over the segment index range and changes every intersecting cache block to INDEX. This extra holder is not aligned to S3 multipart buffer boundaries. In the deterministic reproduction, it creates cache block [241, 304], which crosses the boundary between buffers starting at offsets 0 and 256.

Non-blocking close allows those multipart buffers to finish out of order. When the buffer at offset 256 finishes first, it claims [241, 304] and writes its bytes at the beginning of that block. The cached data is therefore shifted by 15 bytes, while the multipart object in remote storage remains correct. Both a direct cache read and a cached S3 read observe the corrupted bytes.

Remove the post-finalize cache-holder allocation and change_cache_type(INDEX) behavior. Segment blocks keep the cache type selected by the file writer, and this cross-buffer cache block is no longer created by SegmentWriter.

The two commits intentionally preserve the proof in history:

  1. 028891cdc38 adds a deterministic end-to-end BEUT and fails on the existing behavior.
  2. 0d6964d2f80 removes the behavior and removes the temporary BEUT and synchronization hooks.

The final PR diff contains only the production-code deletion.

Release note

None

Check List (For Author)

  • Test: Unit Test
    • ./run-be-ut.sh --run --filter=SegmentWriterFileCacheConcurrencyTest.ConcurrentLaterPartMustNotShiftCachedSegmentBytes -j100 on the first commit: expected failure, reproducing shifted cached bytes in [241, 304]
    • ./run-be-ut.sh --run --filter=CloudFileCacheWriteIndexOnlyTest.* -j100 on the final commit: 3 tests passed
  • Behavior changed: Yes. Segment cache blocks are no longer changed to INDEX after segment finalization.
  • Does this need documentation: No

bobhan1 added 2 commits July 22, 2026 18:00
### What problem does this PR solve?

Issue Number: None

Related PR: None

Problem Summary: Add a deterministic end-to-end SegmentWriter and S3 multipart BE unit test that reproduces cache corruption when multipart writes complete out of order. The test lets the S3 buffer at offset 256 fill an unaligned cache block [241, 304] before the buffer at offset 0. Both direct cache reads and cached S3 reads then return bytes shifted by 15 positions. This commit intentionally keeps the failing assertions to record the existing problem before the fix.

### Release note

None

### Check List (For Author)

- Test: Unit Test
    - ./run-be-ut.sh --run --filter=SegmentWriterFileCacheConcurrencyTest.ConcurrentLaterPartMustNotShiftCachedSegmentBytes -j100 (expected failure reproducing shifted bytes in cache block [241, 304])
- Behavior changed: No
- Does this need documentation: No
### What problem does this PR solve?

Issue Number: None

Related PR: None

Problem Summary: SegmentWriter finalized a cloud segment by allocating a cache holder over the segment index range and changing every intersecting block to INDEX. The range can start inside an S3 multipart buffer, so the newly allocated cache block can cross a multipart boundary. If a later multipart write completes first, it can claim that block and populate its beginning with bytes from the later buffer, shifting the cached segment bytes. Remove the post-finalize holder allocation and cache-type change. Also remove the temporary reproduction test and synchronization hooks; the preceding commit retains the deterministic failing reproducer in the PR history.

### Release note

None

### Check List (For Author)

- Test: Unit Test
    - ./run-be-ut.sh --run --filter=CloudFileCacheWriteIndexOnlyTest.* -j100
    - The preceding commit reproducer fails as expected with shifted bytes in cache block [241, 304]
- Behavior changed: Yes. Segment cache blocks keep the cache type selected by the file writer instead of being changed to INDEX after segment finalization.
- Does this need documentation: No
@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?

@bobhan1
bobhan1 marked this pull request as ready for review July 22, 2026 10:40
@bobhan1 bobhan1 changed the title [fix](be) Stop changing segment cache blocks to index [fix](be) remove changing segment cache blocks to index type Jul 22, 2026
@bobhan1

bobhan1 commented Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

run buildall

@bobhan1

bobhan1 commented Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

/review

bobhan1 added a commit to bobhan1/doris that referenced this pull request Jul 22, 2026
### What problem does this PR solve?

Issue Number: None

Related PR: apache#65905

Problem Summary: SegmentWriter::finalize allocated a cache holder over the segment index range and changed every intersecting block to INDEX. The range can start inside a file-cache block and conflict with canonical async-read probe ranges or cross S3 multipart write boundaries. The previous workaround aligned all FileCacheAllocatorBuilder allocations and added a SegmentWriter alignment test, but that changes the behavior of every writer-side allocation to compensate for a holder whose only purpose is cache-type reclassification.

Follow the narrower solution from apache#65905: remove the post-finalize holder allocation and change_cache_type call. Segment cache blocks now retain the type selected by the file writer. Remove the allocator-alignment workaround, its accessor and comments, and the temporary synchronization hook and end-to-end alignment test. Keep the strict probe contract and the independent file-tail and downloader-ownership fixes.

### Release note

None

### Check List (For Author)

- Test: Unit Test
    - ./run-be-ut.sh --run --filter=CloudFileCacheWriteIndexOnlyTest.* -j100 (3 tests passed)
    - build-support/check-format.sh
- Behavior changed: Yes. Segment cache blocks are no longer reclassified to INDEX after SegmentWriter finalization.
- Does this need documentation: No
@github-actions github-actions Bot added the approved Indicates a PR has been approved by one committer. label Jul 22, 2026
@github-actions

Copy link
Copy Markdown
Contributor

PR approved by at least one committer and no changes requested.

@github-actions

Copy link
Copy Markdown
Contributor

PR approved by anyone and no changes requested.

@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	17630	4117	4089	4089
q2	2007	322	200	200
q3	10278	1447	819	819
q4	4682	488	343	343
q5	7530	865	556	556
q6	181	178	143	143
q7	742	817	607	607
q8	9314	1578	1590	1578
q9	5577	4382	4368	4368
q10	6719	1718	1463	1463
q11	500	348	338	338
q12	733	573	444	444
q13	18092	3397	2797	2797
q14	271	267	245	245
q15	q16	789	781	705	705
q17	970	1027	1096	1027
q18	7080	5775	5735	5735
q19	1283	1317	955	955
q20	796	672	593	593
q21	5914	2657	2350	2350
q22	432	364	300	300
Total cold run time: 101520 ms
Total hot run time: 29655 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	4548	4334	4313	4313
q2	271	321	213	213
q3	4544	4997	4405	4405
q4	2069	2137	1355	1355
q5	4407	4251	4249	4249
q6	233	182	135	135
q7	1722	1866	1889	1866
q8	2550	2166	2177	2166
q9	7922	8231	7715	7715
q10	4709	4667	4198	4198
q11	579	417	378	378
q12	763	778	568	568
q13	3306	3574	2972	2972
q14	318	308	273	273
q15	q16	707	757	637	637
q17	1350	1344	1287	1287
q18	8223	7535	7251	7251
q19	1171	1140	1079	1079
q20	2194	2192	1925	1925
q21	5179	4498	4413	4413
q22	518	437	410	410
Total cold run time: 57283 ms
Total hot run time: 51808 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-DS: Total hot run time: 176600 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 0d6964d2f806c0c1b8462edb3b57fa8d82d2b666, data reload: false

query5	4314	632	477	477
query6	459	230	216	216
query7	4838	597	341	341
query8	348	189	167	167
query9	8747	4041	4064	4041
query10	511	357	304	304
query11	5846	2326	2152	2152
query12	150	102	100	100
query13	1241	597	407	407
query14	6188	5140	4838	4838
query14_1	4226	4190	4177	4177
query15	215	205	179	179
query16	1018	464	432	432
query17	923	685	556	556
query18	2459	469	333	333
query19	202	181	141	141
query20	108	105	105	105
query21	232	159	128	128
query22	13511	13513	13234	13234
query23	17318	16509	16029	16029
query23_1	16189	16132	16202	16132
query24	7554	1730	1308	1308
query24_1	1312	1290	1270	1270
query25	545	433	352	352
query26	1341	346	206	206
query27	2610	624	382	382
query28	4505	1963	1970	1963
query29	1051	590	470	470
query30	335	261	224	224
query31	1118	1079	945	945
query32	113	61	59	59
query33	523	304	243	243
query34	1198	1141	657	657
query35	778	771	664	664
query36	1212	1188	1036	1036
query37	144	104	91	91
query38	1875	1703	1656	1656
query39	871	873	841	841
query39_1	818	832	827	827
query40	245	158	142	142
query41	65	63	62	62
query42	90	91	92	91
query43	318	326	278	278
query44	1468	777	766	766
query45	195	197	176	176
query46	1116	1198	726	726
query47	2099	2077	1969	1969
query48	415	407	309	309
query49	588	424	312	312
query50	1044	455	326	326
query51	10664	10678	10600	10600
query52	87	89	77	77
query53	266	288	206	206
query54	308	246	236	236
query55	78	74	66	66
query56	316	307	294	294
query57	1346	1287	1208	1208
query58	296	267	265	265
query59	1584	1667	1465	1465
query60	320	277	270	270
query61	185	169	175	169
query62	549	501	439	439
query63	242	201	206	201
query64	2966	1170	956	956
query65	4713	4527	4674	4527
query66	1862	523	409	409
query67	29254	29233	28558	28558
query68	3099	1519	964	964
query69	463	304	269	269
query70	1070	970	963	963
query71	383	323	321	321
query72	3008	2707	2366	2366
query73	825	836	439	439
query74	5085	4919	4674	4674
query75	2515	2498	2155	2155
query76	2355	1176	766	766
query77	362	376	275	275
query78	11860	11915	11291	11291
query79	1356	1151	766	766
query80	841	547	463	463
query81	494	336	290	290
query82	578	155	118	118
query83	397	322	301	301
query84	323	157	132	132
query85	1000	619	527	527
query86	392	281	293	281
query87	1820	1824	1750	1750
query88	3751	2790	2720	2720
query89	448	377	326	326
query90	1851	200	195	195
query91	198	200	168	168
query92	59	60	56	56
query93	1569	1688	975	975
query94	614	364	310	310
query95	788	511	478	478
query96	1053	813	353	353
query97	2619	2605	2512	2512
query98	216	204	196	196
query99	1089	1105	951	951
Total cold run time: 262422 ms
Total hot run time: 176600 ms

bobhan1 added a commit to bobhan1/doris that referenced this pull request Jul 22, 2026
### What problem does this PR solve?

Issue Number: None

Related PR: apache#65905

Problem Summary: SegmentWriter::finalize allocated a cache holder over the segment index range and changed every intersecting block to INDEX. The range can start inside a file-cache block and conflict with canonical async-read probe ranges or cross S3 multipart write boundaries. The previous workaround aligned all FileCacheAllocatorBuilder allocations and added a SegmentWriter alignment test, but that changes the behavior of every writer-side allocation to compensate for a holder whose only purpose is cache-type reclassification.

Follow the narrower solution from apache#65905: remove the post-finalize holder allocation and change_cache_type call. Segment cache blocks now retain the type selected by the file writer. Remove the allocator-alignment workaround, its accessor and comments, and the temporary synchronization hook and end-to-end alignment test. Keep the strict probe contract and the independent file-tail and downloader-ownership fixes.

### Release note

None

### Check List (For Author)

- Test: Unit Test
    - ./run-be-ut.sh --run --filter=CloudFileCacheWriteIndexOnlyTest.* -j100 (3 tests passed)
    - build-support/check-format.sh
- Behavior changed: Yes. Segment cache blocks are no longer reclassified to INDEX after SegmentWriter finalization.
- Does this need documentation: No
@hello-stephen

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

query1	0.01	0.01	0.00
query2	0.12	0.04	0.04
query3	0.25	0.13	0.13
query4	1.60	0.14	0.14
query5	0.23	0.23	0.22
query6	1.27	1.06	1.08
query7	0.04	0.01	0.01
query8	0.06	0.04	0.04
query9	0.38	0.30	0.33
query10	0.54	0.57	0.54
query11	0.19	0.13	0.14
query12	0.18	0.15	0.14
query13	0.46	0.47	0.48
query14	1.01	0.99	1.02
query15	0.62	0.59	0.58
query16	0.32	0.31	0.32
query17	1.09	1.15	1.08
query18	0.22	0.22	0.21
query19	1.99	1.94	1.89
query20	0.01	0.01	0.01
query21	15.49	0.22	0.14
query22	4.78	0.05	0.05
query23	16.17	0.31	0.14
query24	2.98	0.45	0.31
query25	0.11	0.07	0.03
query26	0.73	0.20	0.15
query27	0.04	0.04	0.04
query28	3.51	0.92	0.52
query29	12.49	4.16	3.29
query30	0.27	0.16	0.15
query31	2.76	0.59	0.31
query32	3.22	0.60	0.49
query33	3.12	3.11	3.17
query34	15.56	4.20	3.54
query35	3.53	3.55	3.53
query36	0.54	0.44	0.44
query37	0.09	0.07	0.06
query38	0.06	0.04	0.04
query39	0.04	0.03	0.03
query40	0.20	0.17	0.16
query41	0.08	0.03	0.03
query42	0.04	0.03	0.03
query43	0.05	0.04	0.04
Total cold run time: 96.45 s
Total hot run time: 24.92 s

@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. The deletion removes the demonstrated finalize-time overlapping holder, but runtime part-size mutation leaves a residual unaligned-holder corruption path. It also removes the existing INDEX-queue policy for ordinary non-TTL SegmentWriter cache writes, and the final tree does not retain a regression test for the repaired ordering.

Critical checkpoint conclusions:

  • Goal and data correctness: the finalize-time holder is removed, but the runtime-mutable part-size issue shows that a misaligned dynamic update lets the remaining holder recreate the byte shift. Remote object bytes remain correct; the affected surface is the local cache copy.
  • Concurrency and lifecycle: the relevant actors are the finalize/non-block-close thread, S3 upload-pool workers, published cache readers, and local cache block ownership. Remote close is not a local-cache-copy barrier when async cache flush is enabled. Part/block alignment must be a lifetime/per-writer invariant, and any replacement classification needs a separate cache-copy barrier or atomic promotion.
  • Scope, parallel paths, and compatibility: no protocol, format, transaction, MoW, version, or persistence change is involved. TTL and index-only loader modes are unaffected; vertical/packed paths did not use this conversion. The ordinary non-TTL SegmentWriter path has the cache-residency regression described inline.
  • Tests: retained index-only tests do not reach the removed cache-builder branch or compare cache bytes with the remote object. A final deterministic regression is required.
  • Performance and observability: removing the oversized allocation is beneficial, but losing the separately-sized INDEX queue and its longer hot-data interval can force avoidable remote metadata reads. Existing cache/preload metrics are otherwise sufficient.
  • Error handling, memory safety, and configuration: deletion removes an ignored change_cache_type() Status and adds no allocation or lock hazard. The existing mutable part-size configuration lacks update validation/per-writer snapshotting and leaves a correctness hole.
  • User focus: no extra focus points were provided; the full one-file change and its upstream/downstream cache paths were reviewed.

Validation was static as required by the review runner; no local builds or source modifications were performed.

LOG(INFO) << "segment flush consumes a lot time_ns " << timer.elapsed_time()
<< ", segmemt_size " << *segment_file_size;
}
// When the cache type is not ttl(expiration time == 0), the data should be split into normal cache queue

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 retained test suite does not exercise this fix. CloudFileCacheWriteIndexOnlyTest forces index-only mode, which gives segment writers no cache builder and mocks the loader before the actual cache read, so the deleted branch was unreachable even before this patch. The deterministic out-of-order test from 028891cdc38 is removed by the head commit, meaning this data-corruption path can be reintroduced while CI stays green. Please retain an adapted passing test that blocks adjacent mock multipart uploads, lets finalize() return, releases the later remote upload and waits until its cache block is DOWNLOADED, then releases the earlier upload, waits for both local copies and close, and compares cached bytes—including the cached-reader path—with the assembled remote object.

}
// When the cache type is not ttl(expiration time == 0), the data should be split into normal cache queue
// and index cache queue
if (auto* cache_builder = _file_writer->cache_builder(); cache_builder != nullptr &&

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.

This deletion removes the finalize-time unaligned holder, but it does not make the remaining S3 part holders unconditionally aligned. s3_write_buffer_size is runtime-mutable, while divisibility by immutable file_cache_each_block_size is checked only during ExecEnv::init_file_cache_factory(). For example, updating 5 MiB to 5.5 MiB before a writer makes part 2 start at 5.5 MiB. Because async cache flush signals remote completion before its local copy, a published reader can first install the aligned 5-6 MiB block; part 2 then intersects it and copies buffer position zero at 5 MiB, shifting cached bytes by 0.5 MiB—the same corruption class this PR targets. Please make the part size immutable, or validate aligned updates and snapshot one validated size per S3FileWriter; include this contract in the retained regression.

auto size = *index_size + *segment_file_size;
auto holder = cache_builder->allocate_cache_holder(index_start, size, _tablet->tablet_id());
for (auto& segment : holder->file_blocks) {
static_cast<void>(segment->change_cache_type(io::FileCacheType::INDEX));

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 corruption comes from allocating an overlapping holder while multipart cache copies are active, but deleting the whole block also removes the only INDEX classification for ordinary non-TTL SegmentWriter cache writes. FileCacheAllocatorBuilder creates those blocks as NORMAL, later is_index_data reads only touch the block in its existing queue, and SegmentIndexFileCacheLoader runs only under enable_file_cache_write_index_file_only (default false). Thus explicit/adaptive full-cache writes leave segment metadata in the NORMAL queue, with a 1-day hot-data interval, instead of the separately-sized INDEX queue with a 7-day interval; normal data churn can now evict metadata and force remote reads. Please preserve the policy without recreating an unaligned live holder—for example, wait on a separate local cache-copy completion barrier before promoting only recorded ranges, or atomically promote existing blocks on index access. SegmentFileCollection::close() alone is not such a barrier when enable_flush_file_cache_async is true.

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

Labels

approved Indicates a PR has been approved by one committer. reviewed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants