Skip to content

[opt](point query) reduce short circuit lookup cache usage - #67228

Merged
morrySnow merged 1 commit into
apache:masterfrom
HonestManXin:opt_lookup_cache_usage
Oct 13, 2026
Merged

morrySnow merged 1 commit into
apache:masterfrom
HonestManXin:opt_lookup_cache_usage

Conversation

@HonestManXin

@HonestManXin HonestManXin commented Aug 27, 2026 •

Copy link
Copy Markdown
Contributor

LookupConnectionCache preallocates 32 reusable Blocks for each cached point-query context. For wide tables, these Blocks dominate the cache memory usage, and the overhead grows significantly with the number of client connections.
In the current implementation, a Reusable instance is not shared across client connections, and requests within the same connection are executed serially. Therefore, the concurrency of each Reusable instance does not exceed one, making it unnecessary to preallocate 32 Blocks.
This change makes the Block pool size configurable and reduces the default size from 32 to 1. If concurrent access is introduced in the future, additional Blocks can still be allocated on demand, while the pool retains only the configured number of idle Blocks.
In a real production online point-query workload querying a wide table with about 80 columns through approximately 8,000 client connections, the LookupConnectionCache memory usage on a single BE was reduced from about 10 GB to 1.8 GB.

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

@HonestManXin

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.

Review status: capped/incomplete. This review reached the three-round limit, and the final FE round still found one additional accepted performance path. All current candidates have nevertheless been independently verified and deduplicated. Nine accepted issues are represented by eight inline comments (the two unsent-ID paths share one comment): three P1 correctness issues and six P2 performance issues.

Critical checkpoint conclusions

  • Goal and proof: The change can bound one byte-identical prepared-query shape to 128 steady-state UUIDs, but it does not safely achieve the memory/contention goal. Cross-connection reuse introduces wrong-result races, stale schema-derived state, nondeterministic function-state carryover, cold/warm contention, and avoidable FE work. No changed test proves the new identity or ownership contract.
  • Scope and clarity: The Java diff is compact, but it changes BE cache ownership from one prepared context to unrelated connections and FE processes. That cross-stack lifecycle expansion is not safely contained by this one-file edit.
  • Concurrency and locks: AtomicLong allocation and signed wrap are locally safe. BE cache hits, however, share an unprotected RuntimeState and original VExprContextSPtrs; the one protected member performs wide allocation/clearing/destruction under _block_mutex. No additional lock-order/deadlock defect was found.
  • Lifecycle and memory safety: Cache handles, active shared_ptr lifetimes, tracker switching, and Java static initialization are balanced, with no ownership cycle or SIOF found. Lifecycle correctness still fails because hot entries retain schema-derived mappings and already-open function state across independent statements. Transient allocation issues are called out inline.
  • Error handling and nullability: This diff adds no new status/exception or nullable-column path; no separate defect was found on those surfaces.
  • Configuration and compatibility: No configuration, persisted format, transaction, storage write, or wire field changes. Existing UUID decoding and lightweight miss resend remain wire-compatible, but rolling FE/BE operation does not fence mutable or stale cached state.
  • Parallel paths and conditions: Full/lightweight prepared execution, miss resend, replica retry, cloud snapshot reads, binary/text results, COM_QUERY, and nondeterministic prepared execution were traced. The latter two contain unsent hash work, and the hasNondeterministic() FE fence is bypassed by BE content-key reuse.
  • Data correctness: Shared timezone/expression state, omitted schema generation, and sequential seeded-random state carryover are blocking wrong-result paths. Visible-version handling and read-only tablet/version ownership otherwise remain unchanged.
  • Tests and results: The changed-file set contains no test. Existing FE tests do not inspect cacheID; BE concurrency tests do not execute one shared Reusable; current timezone/nondeterministic regressions are sequential or too short to repeat a bucket. No local build/test was run because the authoritative review prompt forbids builds. Current CI has CheckStyle, formatter, license, and secrets checks passing; the PR title checker is failing, and automated code-review is pending.
  • Observability: Existing cache/profile metrics remain available, but they do not prove same-key safety. No distinct missing-observability issue was found.
  • FE/BE state passing and performance: No new field is introduced, but the UUID's semantics now omit required schema/lifecycle dimensions. Performance issues include global stride collapse, non-single-flight cold initialization, three full ByteString copies, two unsent-ID paths, and serialized warm block-pool work.
  • User focus: No additional user-provided review focus was supplied.

Please address all inline findings and add identity, same-key concurrent hit/miss, warm schema-transition, nondeterministic-state, and wide-block contention coverage before re-review.

// high sys CPU, while still bounding the number of cache entries.
private static UUID genCacheID(ByteString serializedDescTable, ByteString serializedOutputExpr,
ByteString serializedQueryOptions) {
int bucket = (int) Math.floorMod(CACHE_ID_BUCKET_COUNTER.getAndIncrement(), CACHE_ID_BUCKET_NUM);

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] Rotate buckets per query identity, not from one JVM-global ordinal. With a fixed set of N prepared contexts created in the same order on every connection, query j gets (N * connection + j) mod 128 and reaches only 128 / gcd(N, 128) buckets. For 128 statements, every instance of each hot query uses one UUID and one BE shard, recreating the single-shard contention this code claims to avoid even though the global bucket histogram is even. Scope the sequence to the unhashed query identity (or use another proven per-query spread) and test repeated multi-statement connection initialization.

@HonestManXin HonestManXin changed the title [opt](point-query) reduce short circuit lookup cache usage [opt](point query) reduce short circuit lookup cache usage Aug 28, 2026
@HonestManXin

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.

Review status: complete after two rounds; changes requested for two P2 issues. The new default of one block reduces initial cached-block retention, but the live setting has a read/write race and a downward update can leave idle entries above the new limit. Both are explained inline. Existing FE threads cover the separate shared-ID/block-mutex concerns.

Critical checkpoint conclusions

  • Goal and proof: Cached prepared-query entries now preallocate one block by default, meeting the narrow default-allocation goal. No changed test proves the new default, override, or live-update behavior; the production memory figure in the PR description was not independently validated here.
  • Scope and clarity: The three-file BE diff is focused. The helper enforces a minimum of one; no extra feature or protocol change was introduced.
  • Concurrency and locks: Reusable::_block_mutex protects its vector. The new mutable config is written under a lock but read without that lock by RPC workers (inline M1). Allocation and clearing under the pool mutex were already raised in an existing thread. No new lock-order or deadlock issue was found.
  • Lifecycle and memory safety: The executor holds a shared_ptr to an active cached context and owns its borrowed block. Success returns the block; early failure destroys it. There is no new ownership cycle or cross-TU static initializer. A completed live decrease leaves idle pooled blocks resident (inline M2).
  • Configuration: The setting is registered mutable and affects new cache entries and later returns. It does not promptly shrink already idle entries. Zero/negative values are clamped to one by the helper; no separate failure was substantiated.
  • Compatibility and parallel paths: No FE/BE field, storage format, or function symbol changes. The noncached UUID-zero path still allocates one block; cached full/lightweight requests use the same BE context. Rolling upgrades require no new wire field.
  • Conditions, error handling, and nullability: The uuid != 0 branch is the only changed preallocation path. No new ignored Status, exception boundary, nullable-column handling, or silent data-error path was found.
  • Tests and results: No test or expected-result file changed. The existing BE point-query unit test passes an explicit block count and does not cover this setting. No build or test was run because the review instructions require static inspection only.
  • Observability: Existing cache/profile logging remains available; no separate missing-observability finding was substantiated.
  • Transactions, persistence, and writes: This change is read-path and process-local; it does not alter version visibility, delete bitmaps, EditLog, data writes, or crash-recovery semantics.
  • Performance and other issues: The lower default should reduce retained blocks per cached entry. The live downsize gap can preserve the memory footprint operators are trying to reduce; existing inline discussion covers potential same-ID pool contention. No other nonduplicate issue remained after the final changed-file sweep.
  • User focus: No additional user-provided focus was supplied.

Comment thread be/src/service/point_query_executor.cpp Resolved
Comment thread be/src/service/point_query_executor.cpp Resolved
@HonestManXin

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: validate the new point-query block-pool setting before it controls eager allocations. The inline comment covers the accepted-value mismatch and the large-value failure path.

Static review checkpoints:

  • Goal and proof: defaulting cached contexts from 32 blocks to 1 addresses the stated memory overhead. No new test exercises the setting; the PR's production memory figures were not independently reproduced.
  • Scope and clarity: the three BE file changes are focused, but the accepted configuration range is undefined.
  • Concurrency: worker threads read an atomic snapshot; set_config serializes writes, and Reusable protects its pool with a mutex. Block creation, clearing, and trimming remain inside that mutex; the relevant contention concern is already in an inline thread. No new lock-order issue was found.
  • Lifecycle: production config loads precede worker startup. The callback publishes live updates; cache ownership and destruction remain intact. Delayed trimming of idle entries after a downward update is already raised in an inline thread. No cross-translation-unit initializer dependency was found.
  • Configuration: the new mutable value reaches new cached contexts and block returns, but out-of-range values are accepted with the consequences described inline.
  • Compatibility: no FE/BE wire or storage format changes, new transmitted variable, or new libc binding was found. Rolling-upgrade behavior is unchanged by this BE-local setting.
  • Parallel paths and conditions: prepared requests with nonzero UUIDs use the configurable cached pool; UUID-zero requests use one temporary block. The branches are consistent with the current FE request path.
  • Tests and results: no targeted unit or regression test, expected-output change, or independently verified runtime result accompanies this patch. This review ran no builds or tests, as required by the review bundle.
  • Observability: config API success/readback can disagree with the effective pool size for nonpositive values. Existing cache profile logs and memory tracking remain available; no separate logging issue was substantiated.
  • Transactions, persistence, and writes: query data visibility, MoW, EditLog, and data-write paths are unaffected. Optional BE config persistence follows the normal set_config path.
  • Performance and other risks: reducing default preallocation should lower memory use; an accepted large value can instead force excessive eager allocation on a new cached context. Other suspected points were resolved or already covered by existing inline threads.
  • User focus: no additional focus was specified.

All three changed files and the unresolved-candidate list were swept after the second review round. The normal and risk-focused scans converged with no further valuable findings.

Comment thread be/src/common/config.cpp Resolved
@HonestManXin

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.

Complete static review of PR #67228 at head 5e27448. No new valuable findings remain after the main risk scan, two complete-review scans, a separate risk scan, candidate deduplication, and a final sweep of all four changed files.

  • Goal and scope: The change initializes one result Block per point-query context instead of 32, validates a configurable 1–32 retention limit, and uses an atomic worker-side snapshot. The edit is focused. The new unit test checks config acceptance, rejection, and publication; it does not exercise pool behavior or independently prove the reported production memory reduction.
  • Concurrency and lifecycle: Config writers are serialized; workers read the atomic limit; the existing pool mutex protects its vector. Block construction, clearing, and excess destruction still run under that mutex, as already raised in discussion r3872318812. A request holds its Reusable across cache eviction. Successful requests return a Block; failed requests destroy their borrowed Block during executor cleanup. No new cross-translation-unit initialization dependency was found.
  • Dynamic configuration: Startup and runtime validation reject values outside 1–32 before publication. A downward update still leaves an untouched idle pool above the new limit until reuse or eviction; discussion r4123678892 already covers that case, so no duplicate inline comment is submitted. The former plain-config-read race in r4123678879 is addressed by the atomic getter.
  • Correctness and compatibility: Cached and uncached misses now both initialize one Block. Status propagation and conditional request paths are otherwise unchanged. There is no new FE/BE wire value, storage format, transaction, persistence, data-write, or version-visibility behavior. Existing logs and cache metrics cover this limited change; no distinct observability defect was found.
  • Existing blocking comments: The earlier FE P1 comments r3872318760, r3872318786, and r3872318799 depended on deterministic shared IDs; this head generates a random UUID for each context. The config P1 comment r4129073418 is addressed by the new validator and one-block initialization. No existing P0/P1 comment remains applicable to this head.
  • Tests and focus: Static build-hygiene checks passed. Builds and runtime tests were prohibited by the review contract, so runtime behavior and the production memory figure were not independently measured. No additional user review focus was supplied.

Review complete. No new inline findings.

@HonestManXin

Copy link
Copy Markdown
Contributor Author

run buildall

@HonestManXin

Copy link
Copy Markdown
Contributor Author

run buildall

@HonestManXin

Copy link
Copy Markdown
Contributor Author

run p0

@HonestManXin

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.

Completed static review of PR #67228 at head c1e6681. No new valuable inline findings. One normal review round covered both implementation paths and tests, and the separate risk scan converged with NO_NEW_VALUABLE_FINDINGS; the final changed-file and candidate sweep found no unresolved point. There was no additional user-provided focus.

Goal and evidence: Cached point-query contexts now start with one reusable Block instead of 32; the mutable retention limit is documented and validated in [1,32]. The added unit test checks both accepted endpoints and rejects 0, -1, and 33. This supports the intended lower baseline cache memory use; the PR's production memory figures were not independently measured here.

Scope and lifecycle: The four-file change is focused. Both cached and uncached misses initialize one Block; successful output returns it to the pool, and an error exit releases the borrowed Block with the executor. Cache eviction owns the remaining Reusable lifetime. No cross-translation-unit initializer dependency was found: the atomic snapshot is initialized to 1 in the same translation unit.

Concurrency and configuration: Point-query RPCs run in a worker pool. The existing Reusable mutex protects Block borrow/return, while the new getter reads an atomic snapshot. Startup parsing validates before publication; live set_config serializes writers, validates and persists before its callback publishes the new value, and rolls back on failure. No new lock-order issue was found. The current FE assigns a random UUID per short-circuit context, so older comments premised on deterministic IDs shared across connections do not apply to this head. An idle pool that grew under concurrent use may retain excess Blocks after lowering the setting until reuse or eviction; existing P2 thread r4123678892 already raises that concern, so it is not duplicated.

Compatibility, correctness, and parallel paths: No FE-BE field, storage format, transaction, data-write, version-visibility, MoW, or nullable-column behavior changes. The cached and uncached point-query paths both use the same one-Block initialization. The prior P1 config-range concern r4129073418 is fixed by this head. Prior FE P1 threads r3872318760, r3872318786, and r3872318799 refer to the removed deterministic shared-ID scheme; none is confirmed still applicable.

Performance, coverage, and observability: Removing eager 32-Block allocation avoids that cold-cache work; the existing cache metrics and point-query profile remain available. The new test covers config boundaries, but it does not exercise Block return or a live-update scenario. No builds or tests were run, as required by the review environment; conclusions are from static source inspection only. No additional observability change is needed for this focused update.

Review status: complete at the pinned head, with zero new inline findings and zero still-applicable existing P0/P1 findings.

@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	17710	3939	3917	3917
q2	2193	360	289	289
q3	10131	1428	808	808
q4	4686	477	349	349
q5	7494	839	553	553
q6	182	183	142	142
q7	757	810	605	605
q8	9300	1462	1470	1462
q9	5449	4187	4179	4179
q10	6824	1318	1027	1027
q11	435	284	238	238
q12	630	418	301	301
q13	18072	2666	2033	2033
q14	262	268	232	232
q15	q16	749	718	670	670
q17	1769	1135	1100	1100
q18	6611	5620	5551	5551
q19	1341	1203	1011	1011
q20	484	392	262	262
q21	5799	3557	3068	3068
q22	462	359	316	316
Total cold run time: 101340 ms
Total hot run time: 28113 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	4814	4825	4535	4535
q2	745	578	524	524
q3	5012	5557	4582	4582
q4	2260	2357	1459	1459
q5	4513	4407	4520	4407
q6	224	173	123	123
q7	1805	1661	1532	1532
q8	2293	2058	1997	1997
q9	7249	7276	7270	7270
q10	3662	3558	3083	3083
q11	506	370	344	344
q12	697	713	519	519
q13	2309	2618	2004	2004
q14	267	274	245	245
q15	q16	653	678	625	625
q17	7273	6712	6655	6655
q18	11838	11066	11646	11066
q19	1099	998	1028	998
q20	2196	2172	1890	1890
q21	4930	4056	4302	4056
q22	522	443	403	403
Total cold run time: 64867 ms
Total hot run time: 58317 ms

@hello-stephen

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

query5	4357	624	469	469
query6	431	215	201	201
query7	4820	546	288	288
query8	319	179	183	179
query9	8856	4000	3991	3991
query10	460	330	259	259
query11	5809	3606	3257	3257
query12	149	94	88	88
query13	1297	588	430	430
query14	6617	4864	4597	4597
query14_1	4332	4270	4247	4247
query15	208	208	186	186
query16	979	457	417	417
query17	930	696	570	570
query18	2428	471	332	332
query19	205	183	145	145
query20	89	88	87	87
query21	233	148	122	122
query22	13004	13046	12873	12873
query23	14322	13424	12770	12770
query23_1	12855	12617	12633	12617
query24	7170	1113	635	635
query24_1	733	702	721	702
query25	576	443	373	373
query26	1258	323	176	176
query27	2675	549	334	334
query28	4605	1979	1985	1979
query29	1565	718	538	538
query30	317	232	191	191
query31	915	794	650	650
query32	165	95	95	95
query33	544	325	261	261
query34	1234	1150	632	632
query35	785	803	678	678
query36	818	810	748	748
query37	162	117	100	100
query38	1895	1836	1743	1743
query39	689	712	654	654
query39_1	671	651	673	651
query40	224	125	105	105
query41	75	75	69	69
query42	100	94	93	93
query43	383	404	350	350
query44	1445	703	723	703
query45	186	180	169	169
query46	1081	1217	702	702
query47	1498	1507	1413	1413
query48	395	386	293	293
query49	585	402	306	306
query50	952	344	262	262
query51	10576	10542	10485	10485
query52	90	89	75	75
query53	245	257	182	182
query54	269	207	224	207
query55	76	72	71	71
query56	225	207	228	207
query57	1520	1438	1364	1364
query58	289	255	252	252
query59	2285	2295	2024	2024
query60	269	242	224	224
query61	145	139	147	139
query62	419	336	276	276
query63	218	174	174	174
query64	2771	944	814	814
query65	3605	3516	3490	3490
query66	1794	424	314	314
query67	20192	20112	19820	19820
query68	3279	1483	846	846
query69	395	309	263	263
query70	952	860	832	832
query71	297	237	214	214
query72	2699	2558	2484	2484
query73	770	772	438	438
query74	4661	4503	4361	4361
query75	2351	2295	1959	1959
query76	2297	1168	744	744
query77	383	418	309	309
query78	9164	9058	8501	8501
query79	1352	1252	731	731
query80	571	460	364	364
query81	566	329	294	294
query82	1014	163	126	126
query83	314	241	194	194
query84	318	149	111	111
query85	849	462	383	383
query86	341	243	230	230
query87	2078	2048	1896	1896
query88	3720	2757	2739	2739
query89	376	293	255	255
query90	1907	188	209	188
query91	178	160	126	126
query92	103	87	92	87
query93	1493	1505	852	852
query94	526	346	267	267
query95	661	387	343	343
query96	1091	797	355	355
query97	2518	2458	2332	2332
query98	161	153	148	148
query99	744	727	606	606
Total cold run time: 239240 ms
Total hot run time: 154896 ms

@hello-stephen

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

query1	0.01	0.00	0.01
query2	0.09	0.05	0.05
query3	0.25	0.13	0.13
query4	1.61	0.14	0.14
query5	0.24	0.21	0.22
query6	1.16	0.94	0.91
query7	0.04	0.01	0.01
query8	0.05	0.04	0.04
query9	0.39	0.33	0.34
query10	0.53	0.54	0.55
query11	0.20	0.14	0.15
query12	0.18	0.14	0.15
query13	0.46	0.47	0.45
query14	0.94	0.93	0.93
query15	0.60	0.60	0.58
query16	0.30	0.34	0.31
query17	1.05	1.08	1.09
query18	0.22	0.20	0.20
query19	2.00	1.91	1.83
query20	0.02	0.02	0.01
query21	15.49	0.21	0.13
query22	4.91	0.06	0.05
query23	16.11	0.32	0.12
query24	2.93	0.42	0.32
query25	0.11	0.05	0.04
query26	0.74	0.21	0.14
query27	0.05	0.03	0.03
query28	3.50	0.80	0.33
query29	12.48	4.04	3.18
query30	0.31	0.15	0.14
query31	2.77	0.56	0.30
query32	3.22	0.60	0.48
query33	3.21	3.22	3.23
query34	15.63	3.91	3.28
query35	3.21	3.20	3.18
query36	0.55	0.43	0.43
query37	0.11	0.06	0.06
query38	0.06	0.03	0.03
query39	0.04	0.03	0.03
query40	0.17	0.15	0.15
query41	0.09	0.02	0.02
query42	0.04	0.03	0.03
query43	0.04	0.03	0.03
Total cold run time: 96.11 s
Total hot run time: 23.75 s

@hello-stephen

Copy link
Copy Markdown
Contributor

BE Regression && UT Coverage Report

Increment line coverage 90.91% (10/11) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 76.59% (34869/45526)
Line Coverage 61.68% (393224/637520)
Region Coverage 57.94% (330305/570105)
Branch Coverage 58.96% (151875/257569)

@HonestManXin

Copy link
Copy Markdown
Contributor Author

run nonConcurrent

@hello-stephen

Copy link
Copy Markdown
Contributor

BE Regression && UT Coverage Report

Increment line coverage 90.91% (10/11) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 76.59% (34869/45526)
Line Coverage 61.68% (393213/637520)
Region Coverage 57.93% (330275/570105)
Branch Coverage 58.96% (151870/257569)

@HonestManXin

Copy link
Copy Markdown
Contributor Author

run nonConcurrent

@eldenmoon eldenmoon left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM

@morrySnow
morrySnow merged commit e718258 into apache:master Oct 13, 2026
37 checks passed
suxiaogang223 added a commit that referenced this pull request Oct 13, 2026
### What problem does this PR solve?

Issue Number: None

Related PR: #67228

Problem Summary:

PR #67228 added `LookupConnectionCacheBlockPoolSize` with `TEST` to the
existing `ConfigValidatorTest` suite, while the suite already uses
`TEST_F`. GoogleTest requires all tests in a suite to use the same
fixture declaration and fails during test registration:

```text
All tests in the same test suite must use the same test fixture
```

Change the new case to `TEST_F` so it uses the existing fixture
consistently.

### Release note

None

### Check List (For Author)

- Test: No need to test (one-line GoogleTest fixture declaration
correction)
- Behavior changed: No
- Does this need documentation: No
morningman added a commit to morningman/doris that referenced this pull request Oct 18, 2026
### What problem does this PR solve?

Issue Number: None

Related PR: apache#67180, apache#67228

Problem Summary:

jni_scanner_heap_budget_ratio and jni_scanner_heap_max_wait_ms can be changed on a running BE.
config::set_config() assigns the plain global under mutable_string_config_lock, while JniScanHeapGate
read both without it: the ratio in jvm_heap_budget(), which the scan workers and the gate's own thread
call whenever they decide an admission, and the wait limit in _poll(), on the gate's thread. That is a
data race by the letter of the standard, which a TSAN build reports. On 64-bit targets the read only
ever sees the old or the new value, and the gate takes either: the account moves only by each
admission's own bytes and the wait limit is compared with each waiter's own start, so nothing could
underflow or be left behind.

The earlier reply to the review kept the plain reads, saying atomic storage would make these the only
atomic mutable configs in BE. That was wrong: inverted_index_candidate_pushdown_ratio, an mDouble read
on the scan path, has been read from an atomic copy since apache#67180, whose author confirmed the same race,
and apache#67228 does the same for lookup_connection_cache_block_pool_size. The gate's two settings now follow
them: an atomic copy each, published by init() once be.conf is read and by an update callback on every
set_config(), read through config::get_jni_scanner_heap_budget_ratio() and
config::get_jni_scanner_heap_max_wait_ms(); relaxed, since neither publishes anything else. The tests
that set the two configs by assignment now go through set_config(), which is what publishes the copies;
the ratio that gives the process gate 10 MB is written with fmt's shortest round-trip text, so it parses
back to the same double.

### Release note

None

### Check List (For Author)

- Test: Unit Test
    - No new case: the existing cases already read the published copies. JniScanHeapGateTest's two
      wait-limit cases set the limit to 200 and 300 ms, and JniTableReaderTest's process gate sets the
      ratio, both now through set_config(); had it not published, the waiters would wait out 60 s and
      the process gate would keep half of an unlimited heap, and those cases would fail. The changed
      sources, JniScanHeapGateTest, ScannerContextTest and JniTableReaderTest compile (-fsyntax-only)
      with the BE UT flags (ASAN), as does the unity batch that holds the gate; clang-format 16 and the
      build hygiene gates pass. Not run locally: config.h changed, so the UT target in this worktree
      would rebuild nearly all of BE; left to CI
- Behavior changed: No
- Does this need documentation: No

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
morningman added a commit to morningman/doris that referenced this pull request Oct 23, 2026
### What problem does this PR solve?

Issue Number: None

Related PR: apache#67180, apache#67228

Problem Summary:

jni_scanner_heap_budget_ratio and jni_scanner_heap_max_wait_ms can be changed on a running BE.
config::set_config() assigns the plain global under mutable_string_config_lock, while JniScanHeapGate
read both without it: the ratio in jvm_heap_budget(), which the scan workers and the gate's own thread
call whenever they decide an admission, and the wait limit in _poll(), on the gate's thread. That is a
data race by the letter of the standard, which a TSAN build reports. On 64-bit targets the read only
ever sees the old or the new value, and the gate takes either: the account moves only by each
admission's own bytes and the wait limit is compared with each waiter's own start, so nothing could
underflow or be left behind.

The earlier reply to the review kept the plain reads, saying atomic storage would make these the only
atomic mutable configs in BE. That was wrong: inverted_index_candidate_pushdown_ratio, an mDouble read
on the scan path, has been read from an atomic copy since apache#67180, whose author confirmed the same race,
and apache#67228 does the same for lookup_connection_cache_block_pool_size. The gate's two settings now follow
them: an atomic copy each, published by init() once be.conf is read and by an update callback on every
set_config(), read through config::get_jni_scanner_heap_budget_ratio() and
config::get_jni_scanner_heap_max_wait_ms(); relaxed, since neither publishes anything else. The tests
that set the two configs by assignment now go through set_config(), which is what publishes the copies;
the ratio that gives the process gate 10 MB is written with fmt's shortest round-trip text, so it parses
back to the same double.

### Release note

None

### Check List (For Author)

- Test: Unit Test
    - No new case: the existing cases already read the published copies. JniScanHeapGateTest's two
      wait-limit cases set the limit to 200 and 300 ms, and JniTableReaderTest's process gate sets the
      ratio, both now through set_config(); had it not published, the waiters would wait out 60 s and
      the process gate would keep half of an unlimited heap, and those cases would fail. The changed
      sources, JniScanHeapGateTest, ScannerContextTest and JniTableReaderTest compile (-fsyntax-only)
      with the BE UT flags (ASAN), as does the unity batch that holds the gate; clang-format 16 and the
      build hygiene gates pass. Not run locally: config.h changed, so the UT target in this worktree
      would rebuild nearly all of BE; left to CI
- Behavior changed: No
- Does this need documentation: No

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
morningman added a commit to morningman/doris that referenced this pull request Oct 27, 2026
### What problem does this PR solve?

Issue Number: None

Related PR: apache#67180, apache#67228

Problem Summary:

jni_scanner_heap_budget_ratio and jni_scanner_heap_max_wait_ms can be changed on a running BE.
config::set_config() assigns the plain global under mutable_string_config_lock, while JniScanHeapGate
read both without it: the ratio in jvm_heap_budget(), which the scan workers and the gate's own thread
call whenever they decide an admission, and the wait limit in _poll(), on the gate's thread. That is a
data race by the letter of the standard, which a TSAN build reports. On 64-bit targets the read only
ever sees the old or the new value, and the gate takes either: the account moves only by each
admission's own bytes and the wait limit is compared with each waiter's own start, so nothing could
underflow or be left behind.

The earlier reply to the review kept the plain reads, saying atomic storage would make these the only
atomic mutable configs in BE. That was wrong: inverted_index_candidate_pushdown_ratio, an mDouble read
on the scan path, has been read from an atomic copy since apache#67180, whose author confirmed the same race,
and apache#67228 does the same for lookup_connection_cache_block_pool_size. The gate's two settings now follow
them: an atomic copy each, published by init() once be.conf is read and by an update callback on every
set_config(), read through config::get_jni_scanner_heap_budget_ratio() and
config::get_jni_scanner_heap_max_wait_ms(); relaxed, since neither publishes anything else. The tests
that set the two configs by assignment now go through set_config(), which is what publishes the copies;
the ratio that gives the process gate 10 MB is written with fmt's shortest round-trip text, so it parses
back to the same double.

### Release note

None

### Check List (For Author)

- Test: Unit Test
    - No new case: the existing cases already read the published copies. JniScanHeapGateTest's two
      wait-limit cases set the limit to 200 and 300 ms, and JniTableReaderTest's process gate sets the
      ratio, both now through set_config(); had it not published, the waiters would wait out 60 s and
      the process gate would keep half of an unlimited heap, and those cases would fail. The changed
      sources, JniScanHeapGateTest, ScannerContextTest and JniTableReaderTest compile (-fsyntax-only)
      with the BE UT flags (ASAN), as does the unity batch that holds the gate; clang-format 16 and the
      build hygiene gates pass. Not run locally: config.h changed, so the UT target in this worktree
      would rebuild nearly all of BE; left to CI
- Behavior changed: No
- Does this need documentation: No

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
morningman added a commit to morningman/doris that referenced this pull request Oct 29, 2026
### What problem does this PR solve?

Issue Number: None

Related PR: apache#67180, apache#67228

Problem Summary:

jni_scanner_heap_budget_ratio and jni_scanner_heap_max_wait_ms can be changed on a running BE.
config::set_config() assigns the plain global under mutable_string_config_lock, while JniScanHeapGate
read both without it: the ratio in jvm_heap_budget(), which the scan workers and the gate's own thread
call whenever they decide an admission, and the wait limit in _poll(), on the gate's thread. That is a
data race by the letter of the standard, which a TSAN build reports. On 64-bit targets the read only
ever sees the old or the new value, and the gate takes either: the account moves only by each
admission's own bytes and the wait limit is compared with each waiter's own start, so nothing could
underflow or be left behind.

The earlier reply to the review kept the plain reads, saying atomic storage would make these the only
atomic mutable configs in BE. That was wrong: inverted_index_candidate_pushdown_ratio, an mDouble read
on the scan path, has been read from an atomic copy since apache#67180, whose author confirmed the same race,
and apache#67228 does the same for lookup_connection_cache_block_pool_size. The gate's two settings now follow
them: an atomic copy each, published by init() once be.conf is read and by an update callback on every
set_config(), read through config::get_jni_scanner_heap_budget_ratio() and
config::get_jni_scanner_heap_max_wait_ms(); relaxed, since neither publishes anything else. The tests
that set the two configs by assignment now go through set_config(), which is what publishes the copies;
the ratio that gives the process gate 10 MB is written with fmt's shortest round-trip text, so it parses
back to the same double.

### Release note

None

### Check List (For Author)

- Test: Unit Test
    - No new case: the existing cases already read the published copies. JniScanHeapGateTest's two
      wait-limit cases set the limit to 200 and 300 ms, and JniTableReaderTest's process gate sets the
      ratio, both now through set_config(); had it not published, the waiters would wait out 60 s and
      the process gate would keep half of an unlimited heap, and those cases would fail. The changed
      sources, JniScanHeapGateTest, ScannerContextTest and JniTableReaderTest compile (-fsyntax-only)
      with the BE UT flags (ASAN), as does the unity batch that holds the gate; clang-format 16 and the
      build hygiene gates pass. Not run locally: config.h changed, so the UT target in this worktree
      would rebuild nearly all of BE; left to CI
- Behavior changed: No
- Does this need documentation: No

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
morningman added a commit to morningman/doris that referenced this pull request Nov 4, 2026
### What problem does this PR solve?

Issue Number: None

Related PR: apache#67180, apache#67228

Problem Summary:

jni_scanner_heap_budget_ratio and jni_scanner_heap_max_wait_ms can be changed on a running BE.
config::set_config() assigns the plain global under mutable_string_config_lock, while JniScanHeapGate
read both without it: the ratio in jvm_heap_budget(), which the scan workers and the gate's own thread
call whenever they decide an admission, and the wait limit in _poll(), on the gate's thread. That is a
data race by the letter of the standard, which a TSAN build reports. On 64-bit targets the read only
ever sees the old or the new value, and the gate takes either: the account moves only by each
admission's own bytes and the wait limit is compared with each waiter's own start, so nothing could
underflow or be left behind.

The earlier reply to the review kept the plain reads, saying atomic storage would make these the only
atomic mutable configs in BE. That was wrong: inverted_index_candidate_pushdown_ratio, an mDouble read
on the scan path, has been read from an atomic copy since apache#67180, whose author confirmed the same race,
and apache#67228 does the same for lookup_connection_cache_block_pool_size. The gate's two settings now follow
them: an atomic copy each, published by init() once be.conf is read and by an update callback on every
set_config(), read through config::get_jni_scanner_heap_budget_ratio() and
config::get_jni_scanner_heap_max_wait_ms(); relaxed, since neither publishes anything else. The tests
that set the two configs by assignment now go through set_config(), which is what publishes the copies;
the ratio that gives the process gate 10 MB is written with fmt's shortest round-trip text, so it parses
back to the same double.

### Release note

None

### Check List (For Author)

- Test: Unit Test
    - No new case: the existing cases already read the published copies. JniScanHeapGateTest's two
      wait-limit cases set the limit to 200 and 300 ms, and JniTableReaderTest's process gate sets the
      ratio, both now through set_config(); had it not published, the waiters would wait out 60 s and
      the process gate would keep half of an unlimited heap, and those cases would fail. The changed
      sources, JniScanHeapGateTest, ScannerContextTest and JniTableReaderTest compile (-fsyntax-only)
      with the BE UT flags (ASAN), as does the unity batch that holds the gate; clang-format 16 and the
      build hygiene gates pass. Not run locally: config.h changed, so the UT target in this worktree
      would rebuild nearly all of BE; left to CI
- Behavior changed: No
- Does this need documentation: No

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
morningman added a commit to morningman/doris that referenced this pull request Nov 4, 2026
### What problem does this PR solve?

Issue Number: None

Related PR: apache#67180, apache#67228

Problem Summary:

jni_scanner_heap_budget_ratio and jni_scanner_heap_max_wait_ms can be changed on a running BE, and
neither had a validator. config::strtox takes whatever strtod or strtoll parses, so set_config() and
be.conf accepted nan, inf, -1 or 2 for the ratio and any int64 for the wait, and the gate computed
with them as given:

- jvm_heap_budget() multiplies the JVM's maximum heap by the ratio and converts the product to int64.
  A NaN ratio fails the comparison that keeps an unlimited heap unlimited and reaches
  static_cast<int64_t>(NaN), which is undefined behavior. An infinite ratio makes the budget
  unlimited and admits every reader, one above 1 lets the declarations add up to more than the heap,
  and a negative one is no share at all.
- _poll() takes the wait limit in nanoseconds as ms * 1000 * 1000. A negative wait admits every
  waiting reader at the gate's next poll, at most 100 ms later, and one above INT64_MAX / 10^6 ms
  overflows the multiplication, undefined behavior again.

The atomic copies the gate reads the two values from ("Read the JNI heap gate's two BE configs from
atomic copies", earlier in this PR) make publishing them free of races; they do not make the values
usable. The two configs that commit followed, inverted_index_candidate_pushdown_ratio and
lookup_connection_cache_block_pool_size, also have a DEFINE_Validator, and now so do these: the
ratio takes a finite number from 0 to 1 (at 0 the declaring readers open one at a time, unless they
wait out the limit), the wait 0 to INT64_MAX / 10^6 ms, the most _poll() can count in nanoseconds.
set_config() runs a validator before it persists the value or calls the update callback, so a
refused value puts the old one back and never reaches the copy the gate reads; a refused value in
be.conf fails config::init(), as for every other validated config.

### Release note

None

### Check List (For Author)

- Test: Unit Test
    - JniScanHeapGateTest.SettingsTheGateCannotUseAreRefused: both ends of each range are taken (the
      ratio 0 and 1, the wait 0 and 9223372036854 ms), which shows the field names are right and
      only the validators refuse; then nan, inf, -0.1 and 1.5 for the ratio and -1 and
      9223372036855 ms for the wait are refused, and the gate still reads the last values taken.
      Without the validators set_config() takes every one of the refused values (read from
      UPDATE_FIELD), so the case would fail. The fixture now also puts the ratio back after each case.
    - config.cpp and the test compile (-fsyntax-only) with the BE UT flags (ASAN, -Werror); clang-tidy
      reports nothing on the changed lines; clang-format 16 and the build hygiene gates pass. Not run
      locally: this worktree has no BE UT build, and one would compile all of BE; left to CI
- Behavior changed: Yes. set_config() and be.conf refuse a value of the two configs outside the
  ranges above. Both configs are new in this PR, and every value the gate could use is still taken
- Does this need documentation: No. The ranges go into the doc of the two configs, which is to follow

Co-Authored-By: Claude Opus 5.5 <[email protected]>
morningman added a commit to morningman/doris that referenced this pull request Nov 6, 2026
### What problem does this PR solve?

Issue Number: None

Related PR: apache#67180, apache#67228

Problem Summary:

jni_scanner_heap_budget_ratio and jni_scanner_heap_max_wait_ms can be changed on a running BE.
config::set_config() assigns the plain global under mutable_string_config_lock, while JniScanHeapGate
read both without it: the ratio in jvm_heap_budget(), which the scan workers and the gate's own thread
call whenever they decide an admission, and the wait limit in _poll(), on the gate's thread. That is a
data race by the letter of the standard, which a TSAN build reports. On 64-bit targets the read only
ever sees the old or the new value, and the gate takes either: the account moves only by each
admission's own bytes and the wait limit is compared with each waiter's own start, so nothing could
underflow or be left behind.

The earlier reply to the review kept the plain reads, saying atomic storage would make these the only
atomic mutable configs in BE. That was wrong: inverted_index_candidate_pushdown_ratio, an mDouble read
on the scan path, has been read from an atomic copy since apache#67180, whose author confirmed the same race,
and apache#67228 does the same for lookup_connection_cache_block_pool_size. The gate's two settings now follow
them: an atomic copy each, published by init() once be.conf is read and by an update callback on every
set_config(), read through config::get_jni_scanner_heap_budget_ratio() and
config::get_jni_scanner_heap_max_wait_ms(); relaxed, since neither publishes anything else. The tests
that set the two configs by assignment now go through set_config(), which is what publishes the copies;
the ratio that gives the process gate 10 MB is written with fmt's shortest round-trip text, so it parses
back to the same double.

### Release note

None

### Check List (For Author)

- Test: Unit Test
    - No new case: the existing cases already read the published copies. JniScanHeapGateTest's two
      wait-limit cases set the limit to 200 and 300 ms, and JniTableReaderTest's process gate sets the
      ratio, both now through set_config(); had it not published, the waiters would wait out 60 s and
      the process gate would keep half of an unlimited heap, and those cases would fail. The changed
      sources, JniScanHeapGateTest, ScannerContextTest and JniTableReaderTest compile (-fsyntax-only)
      with the BE UT flags (ASAN), as does the unity batch that holds the gate; clang-format 16 and the
      build hygiene gates pass. Not run locally: config.h changed, so the UT target in this worktree
      would rebuild nearly all of BE; left to CI
- Behavior changed: No
- Does this need documentation: No

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
morningman added a commit to morningman/doris that referenced this pull request Nov 6, 2026
### What problem does this PR solve?

Issue Number: None

Related PR: apache#67180, apache#67228

Problem Summary:

jni_scanner_heap_budget_ratio and jni_scanner_heap_max_wait_ms can be changed on a running BE, and
neither had a validator. config::strtox takes whatever strtod or strtoll parses, so set_config() and
be.conf accepted nan, inf, -1 or 2 for the ratio and any int64 for the wait, and the gate computed
with them as given:

- jvm_heap_budget() multiplies the JVM's maximum heap by the ratio and converts the product to int64.
  A NaN ratio fails the comparison that keeps an unlimited heap unlimited and reaches
  static_cast<int64_t>(NaN), which is undefined behavior. An infinite ratio makes the budget
  unlimited and admits every reader, one above 1 lets the declarations add up to more than the heap,
  and a negative one is no share at all.
- _poll() takes the wait limit in nanoseconds as ms * 1000 * 1000. A negative wait admits every
  waiting reader at the gate's next poll, at most 100 ms later, and one above INT64_MAX / 10^6 ms
  overflows the multiplication, undefined behavior again.

The atomic copies the gate reads the two values from ("Read the JNI heap gate's two BE configs from
atomic copies", earlier in this PR) make publishing them free of races; they do not make the values
usable. The two configs that commit followed, inverted_index_candidate_pushdown_ratio and
lookup_connection_cache_block_pool_size, also have a DEFINE_Validator, and now so do these: the
ratio takes a finite number from 0 to 1 (at 0 the declaring readers open one at a time, unless they
wait out the limit), the wait 0 to INT64_MAX / 10^6 ms, the most _poll() can count in nanoseconds.
set_config() runs a validator before it persists the value or calls the update callback, so a
refused value puts the old one back and never reaches the copy the gate reads; a refused value in
be.conf fails config::init(), as for every other validated config.

### Release note

None

### Check List (For Author)

- Test: Unit Test
    - JniScanHeapGateTest.SettingsTheGateCannotUseAreRefused: both ends of each range are taken (the
      ratio 0 and 1, the wait 0 and 9223372036854 ms), which shows the field names are right and
      only the validators refuse; then nan, inf, -0.1 and 1.5 for the ratio and -1 and
      9223372036855 ms for the wait are refused, and the gate still reads the last values taken.
      Without the validators set_config() takes every one of the refused values (read from
      UPDATE_FIELD), so the case would fail. The fixture now also puts the ratio back after each case.
    - config.cpp and the test compile (-fsyntax-only) with the BE UT flags (ASAN, -Werror); clang-tidy
      reports nothing on the changed lines; clang-format 16 and the build hygiene gates pass. Not run
      locally: this worktree has no BE UT build, and one would compile all of BE; left to CI
- Behavior changed: Yes. set_config() and be.conf refuse a value of the two configs outside the
  ranges above. Both configs are new in this PR, and every value the gate could use is still taken
- Does this need documentation: No. The ranges go into the doc of the two configs, which is to follow

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants