Skip to content

[fix](be) Fix data race on Segment::_footer_pb - #67720

Open
stinger1206 wants to merge 4 commits into
apache:masterfrom
stinger1206:fix/segment-footer-weakptr-race-master
Open

stinger1206 wants to merge 4 commits into
apache:masterfrom
stinger1206:fix/segment-footer-weakptr-race-master

Conversation

@stinger1206

@stinger1206 stinger1206 commented Sep 9, 2026 •

Copy link
Copy Markdown
Contributor

What problem does this PR solve?

Issue Number: close #67719

Problem Summary:

Segment::_footer_pb (std::weak_ptr<SegmentFooterPB>) was a second footer
access path outside StoragePageCache:

  • _get_segment_footer() reads it via lock() and rebinds it
    (_footer_pb = footer_pb_shared) from query/compaction threads whenever the
    cached footer expires, while get_metadata_size() also accesses the same
    non-atomic std::weak_ptr. Concurrent assignment and lock() on the same
    weak_ptr, if these paths overlap, is a data race and therefore undefined
    behavior.
  • Moreover, the weak_ptr fast path could bypass the cache lookup whenever the
    footer object was still alive — including while the cache entry was present —
    so the cache could not observe real footer accesses and its LRU order could
    diverge from actual usage. After eviction the object could even remain alive
    through other shared_ptr references.

In production (4.1.3) we observed intermittent BE SIGSEGVs, with innermost
frames such as SegmentFooterPB::ByteSizeLong (Segment::get_metadata_size)
and ~SegmentFooterPB under LRUCache::prune_if, plus crashes at unrelated
sites. Every crash was healed by a BE restart without repair and the implicated
segment files stayed byte-identical, which is consistent with transient
in-process memory/lifetime corruption rather than persistent on-disk
corruption. The exact production failure interleaving has not been proven.
See #67719 for a TSAN micro-repro of the unsafe std::weak_ptr access
pattern.

Fix:

  • remove _footer_pb entirely;
  • _get_segment_footer() always accesses the footer through StoragePageCache
    (single access path: lookup → parse & insert on miss), so eviction and LRU
    recency are handled consistently;
  • get_metadata_size() no longer reads the footer: footer memory is tracked by
    StoragePageCache's own memory tracking, and doing a lookup there only for
    accounting would artificially refresh LRU recency on every metadata-size
    sweep without any real query access. This also matches _open()'s
    _meta_mem_usage estimation, which already excludes the footer;
  • update the stale StoragePageCache::insert comment that recommended storing
    a weak_ptr beside the cache (with Segment::_get_segment_footer as the
    example) to instead recommend going through cache lookup for every access.

Release note

Fix unsafe Segment footer caching that could cause BE crashes and bypass
StoragePageCache LRU accounting.

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

    Unit test: SegmentFooterCacheTest.ConcurrentGetSegmentFooterWithEviction
    (adapted from [fix](be) Guard Segment::_footer_pb against concurrent re-assignment #69811, thanks to @airborne12 for the original reproducer):
    8 threads concurrently call _get_segment_footer() on a segment while half
    of them keep erasing the footer's cache page, verifying that every caller
    gets a valid, identical footer regardless of concurrent evictions. A similar
    original workload in [fix](be) Guard Segment::_footer_pb against concurrent re-assignment #69811 reproduced a heap-use-after-free under ASAN
    within 20 runs on the old weak_ptr implementation (a thread read the footer
    through SegmentFooterPB::ByteSizeLong after another thread freed it in
    std::weak_ptr assignment); with the cache-only design of this PR the
    specific unsafe weak_ptr reassignment path is no longer present.

    Manual test: the standalone TSAN micro-repro of the unsafe std::weak_ptr
    access pattern (attached to [Bug] BE SIGSEGV caused by data race on Segment::_footer_pb #67719) demonstrates the data race. The current
    cache-only implementation is deployed on a production canary BE and the crash
    family has not recurred under the previously-crashing workload.

  • Behavior changed:

    • No.
    • Yes.
  • 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

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

@csun5285

csun5285 commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

run buildall

@hello-stephen

Copy link
Copy Markdown
Contributor

BE UT Coverage Report

Increment line coverage 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 63.42% (29907/47157)
Line Coverage 48.47% (314122/648085)
Region Coverage 43.96% (253261/576129)
Branch Coverage 45.59% (118141/259165)

@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	17558	2999	3012	2999
q2	2102	263	227	227
q3	10237	893	514	514
q4	4667	251	196	196
q5	7689	585	383	383
q6	140	115	95	95
q7	531	488	384	384
q8	9231	898	900	898
q9	3518	2400	2374	2374
q10	6535	849	685	685
q11	399	199	183	183
q12	623	261	197	197
q13	18122	1542	1153	1153
q14	164	151	137	137
q15	q16	451	399	370	370
q17	1330	951	802	802
q18	3111	2250	2240	2240
q19	1283	932	773	773
q20	370	303	206	206
q21	5611	1694	1855	1694
q22	333	273	235	235
Total cold run time: 94005 ms
Total hot run time: 16745 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	3354	3289	3299	3289
q2	513	396	380	380
q3	2243	2364	2218	2218
q4	1198	1155	887	887
q5	2175	2124	2105	2105
q6	172	126	88	88
q7	1037	947	878	878
q8	1590	1371	1395	1371
q9	3103	3085	3086	3085
q10	1864	1838	1607	1607
q11	353	274	255	255
q12	453	441	336	336
q13	1490	1554	1148	1148
q14	175	169	162	162
q15	q16	390	400	359	359
q17	3578	3258	3254	3254
q18	4848	4466	4792	4466
q19	899	899	872	872
q20	1006	981	806	806
q21	3817	3164	3165	3164
q22	410	339	333	333
Total cold run time: 34668 ms
Total hot run time: 31063 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-DS: Total hot run time: 82135 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 341c9a86f5979db474c0c8ba1cb0cf255ed13c72, data reload: false

query5	4266	436	356	356
query6	382	135	126	126
query7	4912	428	237	237
query8	306	124	127	124
query9	8711	2897	2897	2897
query10	407	223	188	188
query11	5378	1027	914	914
query12	116	70	67	67
query13	1194	429	324	324
query14	6101	2210	2081	2081
query14_1	1981	1956	1942	1942
query15	174	132	110	110
query16	900	371	337	337
query17	785	453	362	362
query18	2335	329	240	240
query19	165	141	114	114
query20	71	69	72	69
query21	201	102	91	91
query22	5388	5398	5394	5394
query23	7227	6288	6078	6078
query23_1	6168	6210	6182	6182
query24	7266	1114	769	769
query24_1	777	796	781	781
query25	431	307	259	259
query26	1224	251	128	128
query27	2775	429	257	257
query28	4676	1506	1492	1492
query29	935	446	352	352
query30	263	156	134	134
query31	828	401	334	334
query32	139	81	78	78
query33	491	212	181	181
query34	984	823	470	470
query35	407	413	345	345
query36	598	555	502	502
query37	120	79	66	66
query38	991	833	816	816
query39	486	496	460	460
query39_1	445	462	477	462
query40	201	87	71	71
query41	55	53	52	52
query42	72	70	74	70
query43	235	233	208	208
query44	988	547	532	532
query45	105	110	103	103
query46	780	858	532	532
query47	764	756	700	700
query48	320	313	234	234
query49	545	249	189	189
query50	721	257	195	195
query51	8117	7988	8062	7988
query52	66	92	57	57
query53	190	202	155	155
query54	210	155	149	149
query55	72	57	55	55
query56	203	180	251	180
query57	673	656	659	656
query58	196	161	167	161
query59	1204	1224	1107	1107
query60	246	197	207	197
query61	139	105	128	105
query62	363	223	180	180
query63	165	136	139	136
query64	2773	661	623	623
query65	1606	1571	1573	1571
query66	1829	270	215	215
query67	9726	9644	9599	9599
query68	2881	1219	772	772
query69	332	223	187	187
query70	643	649	605	605
query71	250	172	171	171
query72	2242	1621	1461	1461
query73	633	588	335	335
query74	1842	1207	1136	1136
query75	1160	1092	967	967
query76	2284	711	495	495
query77	260	257	225	225
query78	3900	3599	3245	3245
query79	2035	890	596	596
query80	1225	342	295	295
query81	495	158	133	133
query82	629	132	102	102
query83	273	212	200	200
query84	300	110	95	95
query85	802	331	284	284
query86	385	174	176	174
query87	1019	958	911	911
query88	2783	2093	2109	2093
query89	273	195	173	173
query90	1973	125	131	125
query91	126	116	99	99
query92	79	65	71	65
query93	1439	1119	717	717
query94	633	262	228	228
query95	519	339	216	216
query96	768	577	265	265
query97	1061	1033	1029	1029
query98	137	134	132	132
query99	421	349	310	310
Total cold run time: 176771 ms
Total hot run time: 82135 ms

@hello-stephen

Copy link
Copy Markdown
Contributor

BE Regression && UT Coverage Report

Increment line coverage 100% (0/0) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 76.45% (34924/45680)
Line Coverage 61.51% (394126/640710)
Region Coverage 57.52% (330136/573926)
Branch Coverage 58.47% (151104/258424)

@hello-stephen

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

query1	0.01	0.00	0.01
query2	0.08	0.04	0.04
query3	0.25	0.11	0.11
query4	1.59	0.10	0.10
query5	0.17	0.16	0.16
query6	1.26	0.69	0.74
query7	0.03	0.00	0.01
query8	0.04	0.03	0.02
query9	0.28	0.21	0.22
query10	0.35	0.36	0.34
query11	0.16	0.12	0.12
query12	0.14	0.12	0.12
query13	0.31	0.31	0.32
query14	0.45	0.46	0.46
query15	0.36	0.34	0.35
query16	0.22	0.21	0.22
query17	0.66	0.67	0.74
query18	0.16	0.17	0.17
query19	1.19	1.20	1.17
query20	0.02	0.01	0.01
query21	15.43	0.15	0.12
query22	5.06	0.04	0.05
query23	16.13	0.26	0.12
query24	3.00	0.32	0.26
query25	0.12	0.04	0.02
query26	0.81	0.17	0.13
query27	0.03	0.03	0.04
query28	3.62	0.55	0.28
query29	12.47	3.19	2.58
query30	0.25	0.11	0.11
query31	2.75	0.37	0.17
query32	3.53	0.32	0.23
query33	1.39	1.43	1.47
query34	15.40	2.15	1.78
query35	1.76	1.74	1.73
query36	0.44	0.30	0.28
query37	0.06	0.04	0.04
query38	0.05	0.04	0.04
query39	0.03	0.02	0.02
query40	0.10	0.08	0.08
query41	0.08	0.03	0.03
query42	0.04	0.02	0.02
query43	0.03	0.03	0.03
Total cold run time: 90.31 s
Total hot run time: 14.82 s

Comment thread be/src/storage/segment/segment.cpp Resolved

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

pls resolve the comment

@morningman

Copy link
Copy Markdown
Contributor

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

COMPLETE static review of PR #67720 at head 341c9a86f5979db474c0c8ba1cb0cf255ed13c72. One required round converged: both full-review agents and the separate risk-focused agent returned NO_NEW_VALUABLE_FINDINGS. I found no new, distinct inline issue.

  • Goal and proof: The three production reads/writes of Segment::_footer_pb in the changed code now use _footer_pb_lock, and a local shared_ptr keeps the footer alive after unlocking. That mechanically removes unsynchronized access to this weak-pointer object. No test was added for the race, and the existing footer-cache tests are sequential. I did not run builds or tests, as the review instructions prohibit them; the PR's manual testing claims are not independent validation here.

  • Scope and code shape: The two-file change is focused. Segment already contains a mutex and is noncopyable. The added mutex is included in sizeof(Segment) metadata accounting.

  • Concurrency and lock order: The new critical sections contain only weak-pointer promotion or assignment; footer parsing, cache operations, and ByteSizeLong() remain outside them. Reader callbacks take ColumnReaderCache::_cache_mutex before the new mutex, and I found no reverse acquisition. The footer is parsed and normalized before cache publication; downstream consumers use const references or copies. No distinct deadlock, use-after-free, or mutable-protobuf race was substantiated.

  • Lifecycle and parallel paths: Initial footer loading, column-reader creation, traversal, metadata sizing, and cache lookup/insert were traced. Concurrent cache misses can still parse and replace entries, but their handles retain valid footers; that behavior predates this patch. The live weak-pointer fast return can bypass an evicted cache entry and LRU recency. This is already raised in inline thread 4144557940 on this head, so I did not duplicate it. That unranked recency concern is not an independently confirmed P0/P1 blocking comment.

  • Crash-cause limitation: The PR body and added header comment say Daemon memory refresh calls Segment::get_metadata_size() concurrently with footer rebinding. Current MemoryProfile::refresh_memory_overview_profile() instead reads an aggregate bvar via get_all_segments_estimate_size(). Production Segment::update_metadata_size() reaches get_metadata_size() during _open, before the object is published; its other call is under BE_TEST. Reader footer callbacks are serialized by their cache mutex, and the two production traversal callers open local Segment objects. I could not establish the stated same-Segment production interleaving from current call sites, so this static review does not verify that the mutex resolves the reported production crash. The rationale deserves correction and a reachable reproducer if that crash claim is to be relied on.

  • Other critical checkpoints: No configuration, dynamic-update, FE/BE variable, protocol, storage-format, persistence, transaction, data-write, MoW, or version-visibility change is present. No new observability path is needed for the lock itself. The added lock is on footer acquisition and metadata sizing rather than per-row reads; I found no substantive new performance issue. No additional user focus was supplied.

Final sweep covered both changed files, every _footer_pb access, all risk items, and the live existing thread. There are no unresolved candidate findings or new inline comments.

### What problem does this PR solve?

Issue Number: close apache#67719

Problem Summary: `Segment::_footer_pb` is a non-atomic
`std::weak_ptr<SegmentFooterPB>`. `_get_segment_footer()` reads it via
`lock()` and rebinds it (`_footer_pb = footer_pb_shared`) from
query/compaction threads whenever the cached footer expires, while
`get_metadata_size()` calls `_footer_pb.lock()` from the memory
maintenance thread without synchronization. Concurrent assignment and
`lock()` on the same non-atomic `std::weak_ptr` is a data race and
therefore undefined behavior; the observed production crashes (SIGSEGV in
`SegmentFooterPB::ByteSizeLong`, in `~SegmentFooterPB` under
`LRUCache::prune_if`, and at unrelated sites, healed by restart with
segment files byte-identical) are consistent with process-memory
corruption caused by this UB.

Per review feedback, remove `_footer_pb` entirely instead of guarding it.
`_get_segment_footer()` now goes through `StoragePageCache` on every
access, so eviction and recency are handled consistently. This also fixes
a second problem with the old weak_ptr fast path: callers could keep
using a footer without a cache lookup, so the cache did not observe those
accesses and its LRU order no longer reflected real usage.
`get_metadata_size()` no longer looks at the footer: it is owned and
tracked by `StoragePageCache` (which `_open()`'s `_meta_mem_usage`
estimation already excludes), and a lookup there would bump LRU recency
on every memory-maintenance sweep without any real query access.

### Release note

None

### Check List (For Author)

- Test: Manual test (canary BE under production load)
- Behavior changed: No
- Does this need documentation: No
@stinger1206
stinger1206 force-pushed the fix/segment-footer-weakptr-race-master branch from 341c9a8 to 31a388f Compare September 30, 2026 14:49
@stinger1206
stinger1206 requested a review from gavinchou October 5, 2026 08:47
@gavinchou

Copy link
Copy Markdown
Contributor

run buildall

@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	17604	5140	5042	5042
q2	2163	309	259	259
q3	10327	1411	839	839
q4	4675	462	334	334
q5	7438	799	539	539
q6	182	185	155	155
q7	763	808	612	612
q8	9259	1359	1117	1117
q9	5501	4190	4197	4190
q10	6830	1284	1038	1038
q11	431	249	232	232
q12	642	400	275	275
q13	18093	3648	3048	3048
q14	288	271	245	245
q15	q16	734	710	669	669
q17	1230	759	594	594
q18	7050	6163	6139	6139
q19	1263	956	604	604
q20	394	351	227	227
q21	6212	2466	2388	2388
q22	380	332	273	273
Total cold run time: 101459 ms
Total hot run time: 28819 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	5992	5893	6111	5893
q2	730	619	578	578
q3	5726	5780	5383	5383
q4	1994	2134	1579	1579
q5	5878	5712	5537	5537
q6	238	186	140	140
q7	2255	2020	1784	1784
q8	2938	2655	2677	2655
q9	7816	7474	7490	7474
q10	3644	3588	3142	3142
q11	577	399	373	373
q12	669	665	509	509
q13	3324	3595	3040	3040
q14	295	295	259	259
q15	q16	657	678	605	605
q17	7744	7072	6842	6842
q18	12925	12179	12892	12179
q19	915	778	810	778
q20	2131	2150	1897	1897
q21	5602	4614	4637	4614
q22	491	453	413	413
Total cold run time: 72541 ms
Total hot run time: 65674 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-DS: Total hot run time: 151125 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 31a388f1e930293079dee8d508c161ac038e8c9f, data reload: false

query5	4324	591	453	453
query6	428	206	190	190
query7	4807	441	238	238
query8	318	177	157	157
query9	8774	3235	3229	3229
query10	478	314	273	273
query11	6066	3860	3577	3577
query12	158	97	86	86
query13	1255	449	347	347
query14	6138	3943	3654	3654
query14_1	3442	3461	3421	3421
query15	210	196	183	183
query16	955	444	403	403
query17	862	650	528	528
query18	2436	439	310	310
query19	197	169	149	149
query20	87	87	82	82
query21	214	130	112	112
query22	12929	13031	12794	12794
query23	13403	12783	12486	12486
query23_1	12637	12489	12409	12409
query24	7406	829	481	481
query24_1	493	520	486	486
query25	511	398	333	333
query26	1263	253	138	138
query27	2766	436	266	266
query28	4642	2036	2037	2036
query29	1580	574	435	435
query30	301	223	184	184
query31	1011	930	797	797
query32	155	93	90	90
query33	517	287	251	251
query34	920	850	509	509
query35	737	740	644	644
query36	821	799	749	749
query37	124	105	89	89
query38	1811	1735	1672	1672
query39	794	794	743	743
query39_1	731	723	735	723
query40	222	129	92	92
query41	66	67	63	63
query42	86	83	87	83
query43	346	366	309	309
query44	1372	691	719	691
query45	182	175	164	164
query46	839	969	576	576
query47	2836	2938	2749	2749
query48	293	308	223	223
query49	586	385	292	292
query50	685	287	211	211
query51	10464	10427	10528	10427
query52	79	89	69	69
query53	188	209	149	149
query54	250	230	204	204
query55	70	63	66	63
query56	239	224	216	216
query57	1667	1625	1544	1544
query58	278	248	268	248
query59	2132	2153	1989	1989
query60	274	241	222	222
query61	160	136	144	136
query62	397	357	297	297
query63	196	162	164	162
query64	2739	925	754	754
query65	3312	3288	3246	3246
query66	1785	428	310	310
query67	19959	20212	20023	20023
query68	2916	1034	631	631
query69	388	297	239	239
query70	838	773	758	758
query71	302	245	261	245
query72	2758	2619	2318	2318
query73	532	536	299	299
query74	4667	4500	4318	4318
query75	2344	2307	1928	1928
query76	2328	1011	607	607
query77	353	368	264	264
query78	9864	9623	9111	9111
query79	913	881	549	549
query80	496	415	353	353
query81	536	320	274	274
query82	209	137	108	108
query83	234	192	182	182
query84	303	124	101	101
query85	784	428	367	367
query86	257	240	231	231
query87	2006	1937	1812	1812
query88	3811	2795	2744	2744
query89	318	284	235	235
query90	2116	176	175	175
query91	168	154	126	126
query92	103	92	86	86
query93	1000	1035	583	583
query94	452	317	281	281
query95	578	395	293	293
query96	706	505	240	240
query97	2461	2415	2295	2295
query98	169	158	154	154
query99	761	761	643	643
Total cold run time: 233816 ms
Total hot run time: 151125 ms

@hello-stephen

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

query1	0.01	0.01	0.00
query2	0.09	0.04	0.05
query3	0.26	0.09	0.09
query4	1.61	0.10	0.10
query5	0.24	0.25	0.25
query6	1.17	0.68	0.68
query7	0.04	0.00	0.00
query8	0.05	0.03	0.03
query9	0.39	0.36	0.34
query10	0.59	0.58	0.58
query11	0.20	0.15	0.13
query12	0.18	0.15	0.15
query13	0.50	0.49	0.51
query14	1.04	1.02	1.00
query15	0.63	0.61	0.63
query16	0.35	0.34	0.35
query17	1.10	1.18	1.10
query18	0.21	0.18	0.19
query19	2.12	1.97	2.02
query20	0.02	0.02	0.02
query21	15.50	0.20	0.14
query22	4.77	0.06	0.05
query23	16.28	0.32	0.14
query24	3.08	0.95	0.58
query25	0.11	0.07	0.04
query26	1.58	0.21	0.15
query27	0.06	0.05	0.05
query28	3.53	0.72	0.36
query29	12.54	4.15	3.25
query30	0.27	0.13	0.10
query31	2.78	0.57	0.33
query32	3.23	0.61	0.50
query33	3.14	3.18	3.21
query34	15.37	4.00	3.42
query35	3.33	3.32	3.36
query36	0.56	0.44	0.44
query37	0.09	0.07	0.06
query38	0.05	0.03	0.03
query39	0.03	0.03	0.03
query40	0.17	0.15	0.14
query41	0.09	0.03	0.03
query42	0.04	0.03	0.03
query43	0.04	0.03	0.03
Total cold run time: 97.44 s
Total hot run time: 24.5 s

@airborne12

Copy link
Copy Markdown
Member

Two notes from #69811, which fixed the same race and which I'm closing in favor of this PR:

  1. be/test/storage/segment/segment_footer_cache_test.cpp still calls segment_ptr->_footer_pb.reset(); in GetSegmentFooterPropagatesIoContext (added by [feature](file-cache) Disable cache writes after remote scan threshold #65058; it is also at this PR's head, line 175). With _footer_pb removed, the BE UT build fails to compile there. The line can simply be dropped: the test erases the footer's cache entry right before it, and without the weak_ptr that alone forces the reload.
  2. [fix](be) Guard Segment::_footer_pb against concurrent re-assignment #69811 added SegmentFooterCacheTest.ConcurrentGetSegmentFooterWithEviction: 8 threads call _get_segment_footer() and get_metadata_size() while half of them keep erasing the footer's cache page. On the old code, ASAN reported a heap-use-after-free within 20 runs: a thread read the footer through SegmentFooterPB::ByteSizeLong after another thread had freed it in std::weak_ptr assignment, the same frame as the production crashes described above. The test does not touch _footer_pb, so it also works with the cache-only design here; feel free to take it: https://github.com/apache/doris/pull/69811/files

… test

### What problem does this PR solve?

Issue Number: close apache#67719

Related PR: apache#67720

Problem Summary: PR apache#67720 removed Segment::_footer_pb, but
be/test/storage/segment/segment_footer_cache_test.cpp still called
segment_ptr->_footer_pb.reset() in GetSegmentFooterPropagatesIoContext,
so the BE UT build failed to compile. Per review feedback in apache#67720
(apache#67720 (comment)),
drop the stale line: the cache erase() right before it already forces
the reload through the cache-only path, so the SyncPoint on
Segment::_parse_footer::io_ctx still fires.

Also port SegmentFooterCacheTest.ConcurrentGetSegmentFooterWithEviction
from apache#69811 (thanks to @airborne12 for the original reproducer): 8
threads call _get_segment_footer() on a segment while half of them keep
erasing the footer's cache page; every caller must get a valid,
identical footer regardless of concurrent evictions. Adapted to this
PR's cache-only design: the get_metadata_size() check is dropped because
that method no longer reads the footer.

### Release note

None

### Check List (For Author)

- Test: Unit Test (this change itself is a test; BE UT run deferred to CI)
- Behavior changed: No
- Does this need documentation: No
@stinger1206

Copy link
Copy Markdown
Contributor Author

Thank you @airborne12! Both suggestions are taken in d07b797:

  1. Dropped the stale segment_ptr->_footer_pb.reset(); in GetSegmentFooterPropagatesIoContext — the cache erase() right before it alone forces the reload now.
  2. Ported SegmentFooterCacheTest.ConcurrentGetSegmentFooterWithEviction from [fix](be) Guard Segment::_footer_pb against concurrent re-assignment #69811 (with the get_metadata_size() check dropped, since this PR's get_metadata_size() no longer reads the footer at all). Also closing the loop on your note: the PR description now lists the unit test and credits you for the original reproducer — thanks for testing that workload under ASAN and for closing [fix](be) Guard Segment::_footer_pb against concurrent re-assignment #69811 in favor of this PR.

@airborne12

Copy link
Copy Markdown
Member

run buildall

@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	17606	5149	5044	5044
q2	2125	313	244	244
q3	10265	1381	811	811
q4	4679	455	331	331
q5	7433	784	527	527
q6	183	192	157	157
q7	758	808	608	608
q8	9260	1241	1115	1115
q9	5504	4160	4144	4144
q10	6833	1295	1030	1030
q11	432	247	222	222
q12	643	400	280	280
q13	18072	3540	3011	3011
q14	272	267	245	245
q15	q16	726	734	651	651
q17	1207	741	570	570
q18	6999	6177	6093	6093
q19	1096	951	606	606
q20	370	329	224	224
q21	4797	2114	2253	2114
q22	339	293	261	261
Total cold run time: 99599 ms
Total hot run time: 28288 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	5576	5812	5582	5582
q2	705	576	536	536
q3	5181	5495	4935	4935
q4	1974	2131	1557	1557
q5	5257	5152	5484	5152
q6	265	206	157	157
q7	2346	2070	1872	1872
q8	3035	2701	2639	2639
q9	7947	7968	7892	7892
q10	3786	3734	3511	3511
q11	630	460	426	426
q12	704	714	550	550
q13	3730	3966	3262	3262
q14	316	323	302	302
q15	q16	728	756	659	659
q17	8334	7000	6823	6823
q18	12997	12159	12872	12159
q19	911	777	799	777
q20	2137	2141	1917	1917
q21	5541	4546	5192	4546
q22	510	453	424	424
Total cold run time: 72610 ms
Total hot run time: 65678 ms

@hello-stephen

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

query5	4310	573	442	442
query6	433	220	192	192
query7	4799	441	223	223
query8	317	166	150	150
query9	8810	3210	3215	3210
query10	483	303	253	253
query11	6038	3891	3574	3574
query12	141	93	89	89
query13	1250	462	348	348
query14	6131	3923	3708	3708
query14_1	3432	3473	3424	3424
query15	207	197	184	184
query16	932	447	406	406
query17	879	688	563	563
query18	2414	423	321	321
query19	194	168	142	142
query20	93	91	82	82
query21	202	125	111	111
query22	13140	12901	12841	12841
query23	13432	12766	12536	12536
query23_1	12530	12587	12488	12488
query24	7394	857	480	480
query24_1	485	487	480	480
query25	536	429	366	366
query26	1310	262	148	148
query27	2922	448	287	287
query28	4969	2095	2042	2042
query29	1568	603	463	463
query30	321	221	190	190
query31	1006	941	812	812
query32	141	98	92	92
query33	520	303	251	251
query34	924	857	517	517
query35	741	745	646	646
query36	800	809	732	732
query37	127	102	82	82
query38	1796	1768	1688	1688
query39	794	790	753	753
query39_1	726	736	741	736
query40	224	119	98	98
query41	85	81	68	68
query42	88	87	88	87
query43	345	366	310	310
query44	1333	713	708	708
query45	181	174	167	167
query46	843	969	583	583
query47	3050	2670	2682	2670
query48	310	305	220	220
query49	578	393	292	292
query50	682	270	203	203
query51	10488	10116	10189	10116
query52	81	81	70	70
query53	185	211	154	154
query54	233	207	173	173
query55	75	75	63	63
query56	254	210	221	210
query57	1670	1619	1525	1525
query58	278	287	261	261
query59	2066	2120	1978	1978
query60	280	239	223	223
query61	139	145	146	145
query62	388	336	281	281
query63	192	156	161	156
query64	2734	980	772	772
query65	3319	3269	3275	3269
query66	1778	418	301	301
query67	20020	19887	20018	19887
query68	2965	1043	625	625
query69	392	282	247	247
query70	844	782	761	761
query71	301	235	205	205
query72	2622	2557	2274	2274
query73	533	533	296	296
query74	4621	4494	4329	4329
query75	2334	2292	1899	1899
query76	2311	1032	597	597
query77	337	374	286	286
query78	9733	9781	9081	9081
query79	1027	922	543	543
query80	533	423	344	344
query81	538	315	270	270
query82	576	134	109	109
query83	326	195	180	180
query84	312	122	110	110
query85	830	438	370	370
query86	320	239	233	233
query87	2004	1942	1799	1799
query88	3809	2788	2732	2732
query89	339	279	248	248
query90	1912	168	166	166
query91	162	154	127	127
query92	102	100	105	100
query93	1039	1008	580	580
query94	439	303	269	269
query95	585	404	299	299
query96	680	506	242	242
query97	2467	2427	2314	2314
query98	166	154	146	146
query99	765	759	644	644
Total cold run time: 234928 ms
Total hot run time: 150829 ms

@hello-stephen

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

query1	0.01	0.01	0.00
query2	0.09	0.05	0.05
query3	0.26	0.10	0.09
query4	1.61	0.08	0.09
query5	0.24	0.25	0.24
query6	1.17	0.68	0.68
query7	0.05	0.01	0.01
query8	0.06	0.04	0.04
query9	0.40	0.35	0.34
query10	0.58	0.58	0.58
query11	0.20	0.14	0.14
query12	0.19	0.14	0.14
query13	0.48	0.48	0.48
query14	1.02	1.01	1.01
query15	0.64	0.61	0.60
query16	0.35	0.36	0.36
query17	1.19	1.09	1.14
query18	0.20	0.19	0.19
query19	2.05	2.04	1.92
query20	0.02	0.02	0.02
query21	15.49	0.19	0.13
query22	4.98	0.06	0.05
query23	16.28	0.33	0.13
query24	3.09	0.46	0.37
query25	0.13	0.06	0.03
query26	0.77	0.21	0.16
query27	0.05	0.05	0.05
query28	3.39	0.78	0.36
query29	12.48	4.16	3.23
query30	0.30	0.13	0.11
query31	2.77	0.54	0.34
query32	3.24	0.59	0.51
query33	3.13	3.33	3.31
query34	15.30	3.99	3.37
query35	3.32	3.32	3.34
query36	0.56	0.43	0.42
query37	0.10	0.07	0.07
query38	0.06	0.03	0.04
query39	0.04	0.03	0.02
query40	0.18	0.15	0.15
query41	0.09	0.04	0.02
query42	0.04	0.03	0.03
query43	0.05	0.03	0.04
Total cold run time: 96.65 s
Total hot run time: 24.3 s

@hello-stephen

Copy link
Copy Markdown
Contributor

BE UT Coverage Report

Increment line coverage 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 67.23% (32554/48425)
Line Coverage 52.11% (344897/661910)
Region Coverage 47.56% (280472/589756)
Branch Coverage 49.24% (132456/268989)

@hello-stephen

Copy link
Copy Markdown
Contributor

BE Regression && UT Coverage Report

Increment line coverage 100% (0/0) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 77.55% (36393/46927)
Line Coverage 62.81% (411348/654877)
Region Coverage 59.32% (348598/587617)
Branch Coverage 60.16% (161428/268331)

@airborne12

Copy link
Copy Markdown
Member

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

Completed the exact-head static review of PR #67720 at head d07b797f0d03cccc93163aa7d7d82eda4b2be411 in one converged round. The change removes Segment's weak footer pointer, routes footer reads through StoragePageCache, removes the footer from Segment metadata estimates, and adds concurrent eviction coverage.

Goal and proof: the current implementation copies the footer's shared_ptr while a PageCacheHandle is live, so LRU erase, eviction, or replacement cannot free the value before callers receive it. I traced initial open, column-reader creation, traversal, variant/column-reader callbacks, and cache invalidation. The added tests cover forced misses, IO-context propagation through direct and public open paths, cache-key fields, page-type lookup, and concurrent erase/miss/reinsert behavior.

Concurrency and lifecycle: LRU handles retain entries through erase/replacement; concurrent misses may parse equivalent immutable footers but do not cross-contaminate segments. The cache key includes path, file size, and footer offset, and the rowset-writer invalidation recomputes the same key. The cache wrapper owns and tracks footer bytes on normal and zero-capacity DummyLRU paths, while Segment and SegmentLoader estimates intentionally exclude them. ExecEnv teardown stops StorageEngine before destroying the page cache and destroys the page cache before CacheManager. No lock-order, status-propagation, protocol, persistence, transaction, MoW, version-visibility, configuration, or observability regression was found.

Existing review context: inline thread 4144557940 and reply 4182174702 raised the prior weak-pointer/eviction concern. I independently confirmed that concern is addressed by this head and did not duplicate it. No additional user focus was supplied.

Validation was static only as required: I inspected the diff, related call paths, tests, ledger, and live PR state; I did not run builds or tests. Final sweep covered all four changed files and every recorded risk candidate. No new inline finding is warranted.

Review converged after 1 round(s).

yiguolei
yiguolei previously approved these changes Nov 3, 2026
csun5285
csun5285 previously approved these changes Nov 3, 2026
VLOG_DEBUG << fmt::format("Segment footer of {}:{}:{} is missing, try to load it",
_file_reader->path().native(), _file_reader->size(),
_file_reader->size() - 12);
std::shared_ptr<SegmentFooterPB> footer_pb_shared;
RETURN_IF_ERROR(_parse_footer(footer_pb_shared, stats, source_io_ctx));

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.

_footer_pb if this member is deleted, then I think you should modify _parse_footer method to const method. Since there are many threads may call parse footer concurrently and parse footer should not modify Segment's member.
And you should add comment to parse footer method.

@yiguolei yiguolei added usercase Important user case type label dev/4.1.x dev/4.2.x labels Nov 3, 2026
@gavinchou

Copy link
Copy Markdown
Contributor

Please make _parse_footer() const as suggested and add a test that repeated footer accesses refresh the LRU order.

I'd also like to see the cache-pressure case checked before merging. VariantExternalMetaReader can keep a footer alive after eviction, so removing the weak reference may reload the same footer and leave multiple copies alive. Please compare parse counts, query latency and memory with #69811 using a small index-page cache and several VARIANT columns/paths, and check that memory stays accounted for until the last reader releases it.

### What problem does this PR solve?

Issue Number: close apache#67719

Related PR: apache#67720

Problem Summary: Per review feedback on apache#67720, now that Segment::_footer_pb
is removed, _parse_footer() never modifies Segment members — many threads
call it concurrently via _get_segment_footer(), each parsing into a
caller-owned output object. Mark it const, together with the
_write_error_file() helper it calls, and add a comment on the definition
stating the contract: concurrent cache misses may parse independent,
content-identical footer copies; sharing is handled by StoragePageCache in
_get_segment_footer().

### Release note

None

### Check List (For Author)

- Test: No need to test (const-correctness and comment only; no behavior change)
- Behavior changed: No
- Does this need documentation: No
### What problem does this PR solve?

Issue Number: close apache#67719

Related PR: apache#67720

Problem Summary: Per review feedback on apache#67720, add two tests to
SegmentFooterCacheTest:

- RepeatedFooterAccessesRefreshLruOrder proves repeated footer accesses
  refresh the LRU order: against a single-shard index cache measured to
  hold exactly two footer pages, A and B are loaded, A is re-accessed
  (asserted a cache hit via a parse-count sync point), and a third
  footer-sized insert evicts B — the least recently used page — while the
  re-accessed A survives. Entry weights (charge + handle overhead) are
  measured and the eviction arithmetic asserted up front, so the scenario
  is self-verifying.
- HeldFooterStaysValidAcrossCacheEvictionAndReload pins the
  VariantExternalMetaReader pattern under eviction: a held footer copy
  stays valid across eviction and reload, the reloaded footer is valid and
  content-identical (compared via SerializeAsString), and the index cache
  tracker is charged exactly while the entry is cached (footer bytes plus
  entry overhead), uncharged at eviction, re-charged identically at
  reload, and unaffected by holders that outlive the entry.

The LRU test constructs a temporary StoragePageCache. Under BE_TEST,
CacheManager::register_cache() replaces same-type entries and
unregister_cache() is a no-op, so any constructed StoragePageCache hijacks
the CacheManager registrations and leaves them dangling after destruction;
the test restores the global cache registrations on exit (RAII, also on
ASSERT failure), including for the pre-existing local cache in
TestSemgnetFooterPBPage, which had the same dangling-registration issue.
The held-footer test uses the existing global cache. SyncPoint
enable/disable is RAII-guarded so ASSERT failures cannot leave
processing enabled for subsequent tests.

### Release note

None

### Check List (For Author)

- Test: Unit Test (SegmentFooterCacheTest 8/8 passed in a local ASAN UT
  build — both new tests plus all six pre-existing tests; full BE UT
  deferred to CI)
- Behavior changed: No
- Does this need documentation: No
@stinger1206
stinger1206 dismissed stale reviews from csun5285 and yiguolei via d36fef9 November 15, 2026 08:04
@stinger1206

Copy link
Copy Markdown
Contributor Author

Hi, I've addressed the review comments and pushed two additional commits.

  • Marked _parse_footer() and its helper _write_error_file() const, with a comment explaining the concurrent parsing behavior.
  • Added a regression test verifying that repeated footer accesses refresh the LRU order.
  • Added a test covering footer lifetime and cache accounting across eviction and reload.

All 8 SegmentFooterCacheTest tests passed locally under ASAN, including the two new tests. The full BE UT suite is left to CI.

Regarding the cache-pressure concern, the tests confirm that a footer remains valid when held by a reader after cache eviction, while the cache's memory charge is released. The current implementation may re-parse the footer in this situation.

I haven't been able to run a representative performance comparison against #69811. Would the regression coverage be sufficient to proceed, or could the maintainers help with the benchmark?

The changes are ready for another review. Thanks!

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

Labels

dev/4.1.x dev/4.2.x usercase Important user case type label

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] BE SIGSEGV caused by data race on Segment::_footer_pb

7 participants