Repository navigation
[opt](point query) reduce short circuit lookup cache usage - #67228
Conversation
|
Thank you for your contribution to Apache Doris. Please clearly describe your PR:
|
|
/review |
There was a problem hiding this comment.
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:
AtomicLongallocation and signed wrap are locally safe. BE cache hits, however, share an unprotectedRuntimeStateand originalVExprContextSPtrs; 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_ptrlifetimes, 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 thehasNondeterministic()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 sharedReusable; 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 automatedcode-reviewis 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
ByteStringcopies, 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); |
There was a problem hiding this comment.
[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.
7b78efb to
2a2af73
Compare
|
/review |
There was a problem hiding this comment.
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_mutexprotects 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_ptrto 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 != 0branch is the only changed preallocation path. No new ignoredStatus, 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.
2a2af73 to
a4f623b
Compare
|
/review |
There was a problem hiding this comment.
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.
a4f623b to
5e27448
Compare
|
/review |
There was a problem hiding this comment.
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.
|
run buildall |
5e27448 to
c1e6681
Compare
|
run buildall |
|
run p0 |
|
/review |
There was a problem hiding this comment.
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.
TPC-H: Total hot run time: 28113 ms |
TPC-DS: Total hot run time: 154896 ms |
ClickBench: Total hot run time: 23.75 s |
BE Regression && UT Coverage ReportIncrement line coverage Increment coverage report
|
|
run nonConcurrent |
BE Regression && UT Coverage ReportIncrement line coverage Increment coverage report
|
|
run nonConcurrent |
### 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
…69020) #67228 Co-authored-by: TRAE CLI <[email protected]>
### 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]>
### 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]>
### 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]>
### 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]>
### 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]>
### 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]>
### 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]>
### 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]>
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.