Skip to content

[improvement](be) Prioritize Parquet V2 row groups for TopN scans - #69646

Merged
yiguolei merged 8 commits into
apache:masterfrom
Gabriel39:dev/parquet-topn-row-group-order
Oct 28, 2026
Merged

yiguolei merged 8 commits into
apache:masterfrom
Gabriel39:dev/parquet-topn-row-group-order

Conversation

@Gabriel39

@Gabriel39 Gabriel39 commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

What problem does this PR solve?

Problem Summary:

Parquet V2 currently visits row groups in physical file order. For a small ORDER BY ... LIMIT, the best values can be near the end of a file. Earlier groups are then decoded before the TopN sink publishes a useful bound. Even after a bound arrives, the deferred row-group path does not recheck the footer unless a new scan request is published; TopN updates its bound in place. VTopNPred also lacks the ZoneMap interface needed to reject whole row groups.

For example, consider four groups containing consecutive integer keys [300, 399], [200, 299], [100, 199], [0, 99] and ORDER BY k ASC LIMIT 3. Reading the last group first produces a bound of 2; the other three groups can be rejected using their footer minimum before dictionary/index probes, prefetch, or data decoding. The descending case uses the footer maximum.

Optimization approach

  • Add conservative ZoneMap evaluation to VTopNPred. Snapshot the current runtime bound at each evaluation, retain equality for secondary sort keys, honor NULL ordering, and fall back when types, ranges, or floating-point NaN information are unsuitable.
  • Prioritize selected row groups by the leading TopN slot's sanitized footer minimum/maximum. Resolve the file-local slot through the request's column mapping. Keep execution indices separate from the physical plans so row IDs, delete coordinates, and Condition Cache boundaries remain valid.
  • Recheck the latest TopN bound at every row-group boundary before expensive metadata probes and I/O. Files without statistics retain the existing path. Reordering respects row-ID fetch order and the request's existing metadata-pruning boundary; missing or unusable statistics never exclude a group by themselves.

Result and evaluation contract

The optimization must preserve the final TopN result, including secondary-key ties and NULL placement. It may avoid evaluating expressions or materializing columns for rows that a valid TopN bound proves cannot contribute to that result. Preserving potential exceptions from such excluded rows is not a requirement: a DATE overflow in a discarded row group, for example, does not require reading that group. Existing TopN/runtime-filter pushdown eligibility and file-to-table localization remain unchanged.

No executed error is caught, ignored, or converted to success. A conversion that is actually needed still follows the existing strict/non-strict rules and propagates its failure. This distinction also applies to unprojected equality-delete keys and set-operation inputs reduced through existing runtime filters. It does not permit skipping candidate rows, bypassing deletes for retained rows, re-evaluating a volatile leading key as a different value, or trusting incompatible statistics.

Earlier follow-ups added FE expression whitelists and table-mapping restrictions to preserve errors in rows outside the result. Those restrictions are removed; they unnecessarily reduced existing optimization eligibility. The production change is confined to the TopN ZoneMap capability and Parquet scheduling. Existing metadata-pruning boundaries and ordered row-ID fetches remain respected. Upgrade compatibility for legacy FE requests using Parquet V1 is outside the scope of this PR; the follow-up dedicated to that case has been reverted.

Eligibility and limits

  • FE must already generate a TopN runtime filter for the plan and leading-key type. Its existing selectivity check remains max(estimated_scan_rows, 1) * topn_filter_ratio > LIMIT + OFFSET (default ratio: 0.5); existing window, CTE, nested-TopN and join-side rules remain unchanged.
  • This row-group scheduling optimization applies to Parquet V2. After localization, the leading-key filter must reference a direct, non-repeated primitive file column with a compatible type. Arbitrary computed keys such as abs(k) are not mapped to footer bounds. Projected or secondary-key expressions do not need a new non-throwing proof.
  • Reordering requires at least two selected row groups, at least one usable leading-key range, no ordered row-ID fetch, and all localized conjuncts inside the existing metadata-pruning-safe boundary. Ordering is within the current file/split, without coordinating a global order across files or scanners. Current-bound footer rechecks can still apply without reordering when their own eligibility holds.
  • ASC/DESC, NULL ordering, OFFSET and multiple sort keys retain their existing semantics. Leading-key equality is retained for secondary keys. Missing/untrusted statistics, incompatible types, or an unavailable bound retain candidates; they cannot by themselves reject a group.
  • Benefits depend on reaching promising groups before the others are read and on having separable ranges. Overlapping ranges, many leading-key ties, one group per split, or late feedback can substantially reduce the gain. No new production setting is introduced.

Measured optimization effect

The included opt-in ParquetScanTest.TopNRowGroupOrderMicrobench exercises the actual Parquet V2 reader plus a bounded TopK heap and per-block updates of the real runtime predicate. Its workload corresponds to:

SELECT k FROM parquet_source ORDER BY k ASC LIMIT 10;
SELECT k FROM parquet_source ORDER BY k DESC LIMIT 10;

Fixture: one required INT32 column, 128 row groups x 4,096 rows (524,288 rows), disjoint key ranges, and the best group physically last for each direction. Files use uncompressed PLAIN DataPageV2 with dictionary and PageIndex disabled, isolating footer pruning. Every iteration verifies all ten final values.

Per reader query Before After
Row groups decoded 128 1
RawRowsRead 524,288 4,096 (-99.22%)
RowGroupsFilteredByMinMax 0 127
Actual local read bytes 2,149,888 65,602 (-96.95%)
ASC median latency 10.698 ms 0.669 ms (~16.0x)
DESC median latency 8.590 ms 0.660 ms (~13.0x)

Method: Clang 20 RELEASE builds, warm local filesystem cache, the same pinned CPU, nine paired samples alternating baseline/candidate order, and 32 reader queries per sample after warmup. These paired timings were collected before the eligibility cleanup; the direct one-column workload exercises the same scheduling path. Fresh reader correctness tests still verify best-first reads and late-bound group rejection. The baseline restores vtopn_pred.{h,cpp} and parquet_scan.{h,cpp} from the pre-optimization base 8d2bd1e14b6d95d923d43e00857da326639cc466 while retaining the identical benchmark harness and request plumbing; the benchmark calls ParquetReader directly. All paired checksums match. Work counters are identical across all samples. ASC candidate samples range from 0.647 to 1.097 ms and DESC from 0.642 to 0.745 ms; the shared host has timing outliers. These are reader microbenchmark results; complete SQL gains depend on file splits, parallelism, feedback latency, and storage.

Validation and reproduction

  • Fresh verification of the revised implementation: 497 BE tests passed (one opt-in microbenchmark skipped in the ordinary run), and all 31 FE tests passed across the four suites below. The three aggregation query shapes also pass the final translated TOPN OPT: EXPLAIN assertion. The output-evolution optimization test and FE filter-generation test first failed on the preceding implementation, then passed after removal of the unnecessary fences.
  • Reader integration covers ASC/DESC, top-level/nested DATE-to-TIMESTAMP_NS output evolution, strict cast on/off, strict identity DATE materialization, and an unprojected Iceberg equality-delete key. Excluded groups are skipped. The native and hidden-delete cases include ordinary-scan and reversed-TopN controls that still raise the original conversion error when the invalid group is required; existing output-evolution tests also retain their overflow checks. The tests verify all surviving keys/converted values and row counts with real per-block TopN feedback.
  • FE Checkstyle (zero violations), clang-format 16, BE build hygiene, and changed-line clang-tidy for every affected C++ file passed.
  • Existing reader tests cover exact TopK results, physical row IDs, localized slot mapping, missing statistics, metadata-pruning boundaries, ordered row-ID fetches, NULL placement, and equality boundaries.
  • FE coverage checks that potentially throwing secondary expressions do not disable existing TopN runtime-filter generation. The existing nereids_tpch_p0/tpch/topn-filter.groovy also exposed an actual regression: the blanket visitor stop removed TOPN OPT for GROUP BY k ORDER BY k LIMIT 1. Restore the original visitor traversal and cover grouped keys, aggregation with HAVING and a join, and grouping sets with push_topn_to_agg=false. These cases run full FE planning and check TOPN OPT: in the translated EXPLAIN fragments. The same unnecessary expression whitelist also disabled TOPN OPT for a nested STRUCT order key with an IS NOT NULL predicate on a sibling field, as exposed by nereids_rules_p0/column_pruning/topn_filter_nested_column_pruning.groovy. Additional translated-plan checks cover TIMESTAMPTZ, INT and DATE fields in ASC/DESC, including the complete and predicate-only nested access paths. Both existing regression suites and their assertions are unchanged. FE production logic matches the pre-PR base; only an explanatory traversal comment remains in the FE production diff.
  • The earlier SQL suite asserting that all excluded-row errors must be preserved is removed. Complete SQL performance is not claimed; performance evidence comes from the reader microbenchmark below.

Correctness suites:

./run-be-ut.sh -j8 --run \
  --filter='*Parquet*:*ColumnMapper*:*TableReader*:*Iceberg*:*EvolutionCast*:*RuntimePredicate*'
./run-fe-ut.sh --run \
  org.apache.doris.nereids.postprocess.TopNRuntimeFilterTest,org.apache.doris.nereids.rules.rewrite.DateArithmeticFilterPushDownTest,org.apache.doris.nereids.postprocess.PushDownFilterThroughProjectTest,org.apache.doris.nereids.util.ExpressionUtilsTest

Opt-in benchmark (use a RELEASE UT build):

DORIS_TOPN_ROW_GROUP_BENCHMARK=1 BUILD_TYPE_UT=RELEASE \
  ./run-be-ut.sh -j8 --run --filter=ParquetScanTest.TopNRowGroupOrderMicrobench

For paired comparison, build and save baseline/candidate UT executables with the same harness, then use the existing runner:

DORIS_TOPN_ROW_GROUP_BENCHMARK=1 \
DORIS_BE_UT_EXECUTABLE=build-support/run-query-engine-paired-benchmarks.py \
QUERY_ENGINE_BENCH_BASELINE=./baseline/doris_be_test \
QUERY_ENGINE_BENCH_CANDIDATE=./candidate/doris_be_test \
QUERY_ENGINE_BENCH_CPU=0 \
QUERY_ENGINE_BENCH_OUTPUT=./paired-results \
BUILD_TYPE_UT=RELEASE ./run-be-ut.sh -j8 --run \
  --filter=ParquetScanTest.TopNRowGroupOrderMicrobench

Select an available idle CPU for QUERY_ENGINE_BENCH_CPU. The runner emits paired timings/checksums; PARQUET_TOPN_WORK lines report per-query raw rows, groups filtered by min/max, and actual local read bytes.

Release note

Improve Parquet V2 TopN scans by reading promising row groups first and applying the latest runtime bound before opening subsequent groups.

Check List (For Author)

  • Test
    • Unit Test
    • Manual test (reader microbenchmark and commands above)
  • Behavior changed:
    • Yes. Eligible Parquet V2 TopN scans may read row groups in a different order and skip more groups. SQL ordering remains defined by the TopN operator. Expressions and conversions in excluded groups may not execute, so an exception from an excluded row may no longer be observed.
  • Does this need documentation?
    • No. Internal scan optimization with no new user-facing option.

Check List (For Reviewer who merge this PR)

  • Confirm the release note
  • Confirm test cases
  • Confirm document
  • Add branch pick label

### What problem does this PR solve?

Problem Summary: Physical-order Parquet V2 scans can decode many row
groups before finding a useful small-TopN bound. Add TopN ZoneMap
evaluation, schedule promising footer ranges first, and recheck the
current in-place bound before opening each row group. Preserve physical
identities and evaluation-sensitive predicate/project fences.

A reader microbenchmark with 128 disjoint row groups and the best group
last reduces raw rows from 524,288 to 4,096 and local read bytes from
2,149,888 to 65,602. ASC/DESC median reader-plus-TopK latency improves
from 10.698/8.590 ms to 0.669/0.660 ms on warm local files.

### Release note

Improve eligible Parquet V2 ORDER BY LIMIT scans by prioritizing row
groups and applying the latest TopN bound before additional I/O.

### Check List (For Author)

- Test: 370 targeted BE tests; 8 TopNRuntimeFilterTest FE tests;
  paired reader microbenchmark; FE Checkstyle; clang-format 16;
  BE build hygiene and clang-tidy.
- Behavior changed: Yes. Eligible row groups may execute in TopN order.
- Does this need documentation: No. Internal scan optimization.
@hello-stephen

Copy link
Copy Markdown
Contributor

Thank you for your contribution to Apache Doris.
Don't know what should be done next? See How to process your PR.

Please clearly describe your PR:

  1. What problem was fixed (it's best to include specific error reporting information). How it was fixed.
  2. Which behaviors were modified. What was the previous behavior, what is it now, why was it modified, and what possible impacts might there be.
  3. What features were added. Why was this function added?
  4. Which code was refactored and why was this part of the code refactored?
  5. Which functions were optimized and what is the difference before and after the optimization?

@Gabriel39

Copy link
Copy Markdown
Contributor Author

run buildall

@Gabriel39

Copy link
Copy Markdown
Contributor Author

/review

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Static review of PR #69646 at b0466c1. Two P2 findings remain: the new project fence misses deterministic throwing date arithmetic, and the reordering safety check cannot see evaluation-sensitive predicates retained on joins. Both can make the new promising-group order suppress an error encountered before any TopN bound in the original physical order. The inline comments describe separate reachable operator boundaries and fixes.

No existing inline findings were present, so there are no existing blocking comment IDs to carry. User focus: no additional focus was supplied; all 11 changed files and their relevant upstream/downstream paths were reviewed.

Critical checkpoint conclusions:

  1. Goal and proof: earlier TopN feedback, row-group prioritization and late footer pruning are implemented. Focused BE/FE tests support the scalar cases, but the two error-preservation defects prevent full semantic equivalence.
  2. Scope and clarity: the changes are focused and reuse existing mapping, statistics conversion and profiling contracts.
  3. Concurrency and locks: scanner tasks read a copied Field under RuntimePredicate's shared mutex while sort tasks update under its exclusive mutex. Published bounds tighten monotonically; direction and NULL ordering are immutable. No new thread, nested lock order, heavy locked I/O or deadlock path was found.
  4. Lifecycle and static initialization: execution order is reset with the scan plan; group readers, pending outputs and merged ranges are released at existing boundaries. QueryContext owns the runtime predicate. No new static initializer or ownership cycle was found.
  5. Configuration: no production configuration or session variable was added.
  6. Compatibility: no wire or persisted format changes. External statistics still use the existing sanitized logical-type conversion and conservative fallbacks; no new ABI/toolchain dependency was identified by inspection.
  7. Thrift/protobuf: neither directory changed; cross-branch field-ID comparison is inapplicable.
  8. Parallel paths: checked the new ZoneMap capability's native storage, legacy Parquet and V2 footer/page consumers; ORC SARG does not consume this interface. Row-ID fetches retain physical order. Join-expression safety is incomplete as reported inline.
  9. Conditional checks: NULL/no-bound, equality, incompatible types, unusable ranges and unknown NaN information retain candidates conservatively. The upstream expression-fence coverage is incomplete in the two reported cases.
  10. Test coverage: new tests cover ASC/DESC, late bounds, exact integer TopK values, localized slots, physical GLOBAL_ROWID, missing statistics, row-ID fetch and predicate fences, plus synthetic ZoneMap NULL/tie cases. End-to-end error cases for throwing projections and join predicates are missing.
  11. Expected test results: integer outputs, physical row-ID formulas and NULL/equality expectations are consistent with the fixtures. No golden result files changed; no generated-output provenance claim is needed.
  12. Observability: late exclusions feed the existing deferred-pruning/min-max/read-row counters before expensive probes. No new per-row logging was introduced.
  13. Transactions and persistence: no EditLog, commit, visible-version, delete-bitmap publication or failover protocol changed.
  14. Writes and crash atomicity: this is read-side scheduling/pruning; no write or transaction protocol changed. Existing error/cancellation cleanup remains in the reader ownership chain.
  15. FE/BE propagation: no new transmitted variable; the internal request flag is assigned on both initial open and refresh. Direction and NULL placement reuse the existing TopN descriptor.
  16. Performance: one O(G) priority buffer and O(G log G) stable sort per file, then footer checks before dictionary/index probes and prefetch. No additional per-row allocation was identified. Benchmark code and work counters were inspected; reported speedups were not rerun or independently established.
  17. Other core invariants: physical plan IDs and file-global row offsets stay unchanged under the execution permutation. Position-delete searches restart per block; equality deletes are order-independent. Production Condition Cache is disabled when TopN source IDs exist. Late runtime-filter activation retains the physical layout and replans unvisited groups. No additional verified memory, nullable-column, unchecked-status or result-pruning defect was found.

Static-only limits: no builds, tests, sanitizers or benchmarks were run. New physical-file fixtures mainly cover Arrow-written INT32; broader writer/type/NULL/delete/cache/late-filter differential combinations are not newly demonstrated by tests. These are validation limits, with no unresolved candidate or assigned static-review coverage gap remaining.

The review converged after 2 rounds: every latest-round reviewer returned NO_NEW_VALUABLE_FINDINGS on the unchanged final payload, all assigned coverage and risk questions were closed, and the final sweep retained exactly the two verified inline findings.

Review converged after 2 round(s).

@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	17578	5186	5060	5060
q2	2150	313	251	251
q3	10253	1385	817	817
q4	4680	459	334	334
q5	7436	797	541	541
q6	187	192	153	153
q7	760	802	600	600
q8	9257	1293	1062	1062
q9	5473	4233	4235	4233
q10	6849	1302	1051	1051
q11	437	238	224	224
q12	638	405	280	280
q13	18110	3641	3010	3010
q14	268	273	246	246
q15	q16	736	715	678	678
q17	1211	759	580	580
q18	7087	6187	6132	6132
q19	1115	964	609	609
q20	391	335	231	231
q21	5362	2139	2298	2139
q22	353	299	254	254
Total cold run time: 100331 ms
Total hot run time: 28485 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	5603	5581	5619	5581
q2	698	581	546	546
q3	5267	5439	4922	4922
q4	2008	2153	1563	1563
q5	5328	5204	5758	5204
q6	254	199	154	154
q7	2332	2161	1956	1956
q8	3186	2773	2673	2673
q9	8257	8309	7988	7988
q10	3747	3672	3262	3262
q11	616	428	401	401
q12	743	696	538	538
q13	3633	3824	3240	3240
q14	322	322	300	300
q15	q16	727	727	639	639
q17	8306	7521	6930	6930
q18	12976	12197	12859	12197
q19	919	785	779	779
q20	2141	2115	1910	1910
q21	5593	4604	4735	4604
q22	520	460	403	403
Total cold run time: 73176 ms
Total hot run time: 65790 ms

@hello-stephen

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

query5	4320	589	446	446
query6	420	218	187	187
query7	4809	438	220	220
query8	318	169	144	144
query9	8766	3095	3094	3094
query10	458	310	254	254
query11	6030	3847	3556	3556
query12	141	94	85	85
query13	1239	417	306	306
query14	6103	3901	3642	3642
query14_1	3431	3389	3450	3389
query15	209	198	180	180
query16	931	450	421	421
query17	821	638	498	498
query18	2400	406	296	296
query19	206	167	126	126
query20	82	82	79	79
query21	208	121	102	102
query22	13017	12980	12912	12912
query23	13511	13014	12443	12443
query23_1	12528	12567	12603	12567
query24	7376	814	469	469
query24_1	470	467	469	467
query25	504	395	340	340
query26	1271	250	139	139
query27	2769	437	269	269
query28	4644	1924	1928	1924
query29	1581	600	460	460
query30	299	221	183	183
query31	1003	926	817	817
query32	136	92	95	92
query33	531	304	241	241
query34	923	839	504	504
query35	741	756	645	645
query36	818	825	730	730
query37	123	99	80	80
query38	1762	1711	1669	1669
query39	796	793	745	745
query39_1	728	730	751	730
query40	224	114	99	99
query41	71	68	65	65
query42	88	83	84	83
query43	338	360	319	319
query44	1298	688	691	688
query45	185	180	160	160
query46	839	956	571	571
query47	2938	2827	2757	2757
query48	302	306	222	222
query49	578	402	294	294
query50	668	273	221	221
query51	10718	10477	10578	10477
query52	76	80	72	72
query53	188	210	156	156
query54	243	210	189	189
query55	79	67	69	67
query56	242	241	210	210
query57	1694	1593	1564	1564
query58	293	273	264	264
query59	2178	2216	1977	1977
query60	307	251	230	230
query61	170	172	171	171
query62	395	332	287	287
query63	192	171	160	160
query64	2857	1037	777	777
query65	3338	3263	3252	3252
query66	1770	408	289	289
query67	20238	20118	20083	20083
query68	3059	990	605	605
query69	368	291	246	246
query70	838	777	778	777
query71	293	229	212	212
query72	2764	2667	2298	2298
query73	508	518	296	296
query74	4609	4529	4346	4346
query75	2332	2286	1916	1916
query76	2314	1008	589	589
query77	327	369	267	267
query78	9901	9777	9177	9177
query79	949	871	514	514
query80	1416	427	330	330
query81	585	315	275	275
query82	571	131	106	106
query83	279	200	182	182
query84	306	126	95	95
query85	1128	428	368	368
query86	393	237	213	213
query87	2016	1917	1797	1797
query88	3630	2656	2653	2653
query89	340	282	242	242
query90	1871	159	164	159
query91	163	163	127	127
query92	96	87	93	87
query93	985	994	573	573
query94	638	333	247	247
query95	564	379	294	294
query96	657	488	231	231
query97	2505	2453	2336	2336
query98	163	153	150	150
query99	761	776	636	636
Total cold run time: 236130 ms
Total hot run time: 150917 ms

@hello-stephen

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

query1	0.01	0.01	0.00
query2	0.08	0.05	0.05
query3	0.26	0.09	0.07
query4	1.60	0.10	0.10
query5	0.23	0.24	0.24
query6	1.16	0.68	0.68
query7	0.04	0.02	0.01
query8	0.06	0.04	0.03
query9	0.40	0.34	0.35
query10	0.57	0.58	0.58
query11	0.19	0.14	0.14
query12	0.18	0.15	0.14
query13	0.49	0.49	0.50
query14	1.06	1.02	1.01
query15	0.63	0.61	0.61
query16	0.35	0.35	0.35
query17	1.14	1.08	1.11
query18	0.20	0.20	0.19
query19	2.14	2.06	1.92
query20	0.01	0.01	0.01
query21	15.49	0.19	0.14
query22	4.90	0.05	0.05
query23	16.27	0.34	0.12
query24	2.73	0.72	0.40
query25	0.11	0.05	0.04
query26	1.62	0.22	0.14
query27	0.05	0.04	0.05
query28	3.90	0.72	0.37
query29	12.48	4.12	3.23
query30	0.28	0.12	0.10
query31	2.77	0.55	0.32
query32	3.22	0.60	0.51
query33	3.24	3.18	3.15
query34	15.33	4.00	3.43
query35	3.36	3.37	3.37
query36	0.54	0.44	0.44
query37	0.10	0.06	0.07
query38	0.05	0.04	0.04
query39	0.04	0.03	0.03
query40	0.17	0.15	0.15
query41	0.09	0.03	0.02
query42	0.04	0.03	0.03
query43	0.05	0.03	0.03
Total cold run time: 97.63 s
Total hot run time: 24.25 s

@hello-stephen

Copy link
Copy Markdown
Contributor

FE UT Coverage Report

Increment line coverage 100.00% (2/2) 🎉
Increment coverage report
Complete coverage report

@hello-stephen

Copy link
Copy Markdown
Contributor

BE UT Coverage Report

Increment line coverage 100.00% (4/4) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 66.91% (32207/48135)
Line Coverage 51.72% (340646/658578)
Region Coverage 47.14% (276174/585806)
Branch Coverage 48.83% (130229/266697)

@hello-stephen

Copy link
Copy Markdown
Contributor

BE Regression && UT Coverage Report

Increment line coverage 100.00% (4/4) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 77.45% (36121/46638)
Line Coverage 62.67% (408340/651542)
Region Coverage 59.14% (345196/583677)
Branch Coverage 60.10% (159890/266045)

@hello-stephen

Copy link
Copy Markdown
Contributor

FE Regression Coverage Report

Increment line coverage 100.00% (2/2) 🎉
Increment coverage report
Complete coverage report

### What problem does this PR solve?

Problem Summary: TopN row-group reordering and runtime pruning can hide
an overflow in a date projection or an assertion retained on a join.
Fence filter pushdown across these expressions before creating a scan
target, including both hash and nested-loop joins and all join conjuncts.
Reuse the existing throwing-date classification while retaining safe
literal-zero arithmetic and ordinary join optimizations.

### Release note

Preserve date arithmetic errors and join assertion evaluation when
pushing down TopN runtime filters.

### Check List (For Author)

- Test: 33 FE tests across four suites passed. The new project/hash-join/
  nested-loop-join cases failed on the original code. FE Checkstyle passed.
  Added a SQL error/explain regression; Groovy compilation passed, full
  cluster execution pending CI.
- Behavior changed: Yes. Withhold unsafe TopN filters at evaluation-sensitive
  projections and joins.
- Does this need documentation: No. Internal optimizer correctness fix.
@Gabriel39

Copy link
Copy Markdown
Contributor Author

run buildall

@Gabriel39

Copy link
Copy Markdown
Contributor Author

/review

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewed exact head 495b081d65f1137a9d3567383ad3aecd86b49d28 against the supplied aggregate diff. Two new P2 findings remain: unchecked residual-filter/generator traversal and throwing epoch conversions can still let Parquet row-group prioritization suppress an error. Both are detailed inline.

Existing review context: independently confirmed the reported weeks_add projection and ON-assertion hash/nested-loop join instances are fixed. Recursive date classification includes nonzero add/subtract and rounding; the join expression list includes hash, other, and mark conjuncts. Neither existing P2 report is reposted. No existing P0/P1 finding remains to carry forward. No additional user focus was supplied.

Critical checkpoint conclusions:

  1. Goal and proof: best-first Parquet row groups and current-bound footer checks are implemented, with focused reader tests, but error preservation is incomplete as described inline.
  2. Scope and reuse: changes are focused; footer sanitization, statistics conversion, column mapping, and the existing date classifier are reused. The classifier extraction preserves its previous behavior.
  3. Concurrency: sort-sink updates take the runtime predicate's exclusive lock; each new ZoneMap evaluation copies one bound under its shared lock. Direction/null flags are immutable. Reader scheduling and request activation remain sequential; no new nested-lock ordering issue found.
  4. Lifecycle and memory: execution indices remain separate from physical plans; request snapshots pin expression state until a row-group boundary. Reset clears ordering state and readers flush profiles before destruction. No new ownership cycle or cross-translation-unit static initialization found; additional ordering storage is O(row groups).
  5. Configuration: no new configuration or dynamic-reload requirement. FE test session overrides are restored.
  6. Compatibility: no storage format or wire-protocol change; inspected shared ZoneMap consumers beyond Parquet V2 and found no separate type/NULL compatibility regression. Release ELF/ABI validation was not run.
  7. Thrift/protobuf: no definitions changed; field-ID upgrade checks are inapplicable.
  8. Parallel paths: hash/nested-loop ON fences and native/v1 ZoneMap consumers were checked. Residual filters, generators, and epoch conversions are the remaining verified gaps; CTE/window/nested-TopN boundaries stay intact. ORC ordering is unchanged.
  9. Conditions and errors: new comments describe concrete safety cases; equality ties, missing statistics, NULL ordering, NaN/unsafe ranges, and row-ID fetch fallbacks are conservative. The inline findings identify missing error-preservation boundaries.
  10. Tests: added BE tests cover both directions, feedback, localized slots, physical row IDs, missing statistics, fences, NULLs and ties; FE tests cover reported project/ON failures and safe controls. The new SQL suite uses OLAP tables and does not exercise an actual reversed-Parquet-group integration path. The newly identified cases are absent from these tests.
  11. Expected results: added expected vectors and planner/error assertions are consistent with their tested shapes. No generated regression result file changed.
  12. Observability: deferred pruning continues updating existing min/max group/row/byte counters, and EXPLAIN exposes TopN target creation. No separate logging regression found.
  13. Transactions and persistence: no transaction, EditLog, failover, visible-version, or delete-bitmap-version logic changed.
  14. Writes and atomicity: production changes affect reads/planning; no write atomicity or crash-recovery change.
  15. FE-BE values: the new row-reordering flag is an internal FileScanRequest field, set on initial and refreshed requests; no new transport field or send path.
  16. Performance: one footer priority pass and stable O(G log G) sort are added per eligible reader; late footer exclusion precedes dictionary/index probes and prefetch. Benchmark claims were inspected, not reproduced.
  17. Additional checks: physical row/delete coordinates and Condition Cache remain tied to file positions; production TopN scans disable Condition Cache. No additional actionable BE defect found.

Validation limits: static source and test inspection only. No builds, tests, benchmarks, cluster queries, source modifications, or submodule initialization were performed. Combined external-writer/deletes/page-index/live-refresh scenarios remain execution-validation gaps, rather than unresolved static candidates. All 13 changed files and assigned risks were covered; the review converged in two rounds.

Review converged after 2 round(s).

@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	17573	5125	5014	5014
q2	2158	310	248	248
q3	10251	1372	804	804
q4	4684	461	339	339
q5	7435	793	530	530
q6	186	187	156	156
q7	748	789	610	610
q8	9255	1366	1142	1142
q9	5482	4202	4150	4150
q10	6831	1282	1026	1026
q11	437	253	231	231
q12	639	399	277	277
q13	18098	3558	2963	2963
q14	274	273	251	251
q15	q16	728	744	655	655
q17	1179	747	590	590
q18	6940	6174	6133	6133
q19	1092	960	590	590
q20	365	333	228	228
q21	5056	2158	2261	2158
q22	347	300	259	259
Total cold run time: 99758 ms
Total hot run time: 28354 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	5591	5609	5610	5609
q2	704	564	508	508
q3	5237	5382	4938	4938
q4	1997	2146	1534	1534
q5	5279	5219	5409	5219
q6	259	198	155	155
q7	2335	2086	1881	1881
q8	3073	2696	2662	2662
q9	8115	7972	7937	7937
q10	3707	3651	3487	3487
q11	619	434	419	419
q12	722	690	543	543
q13	3520	3920	3272	3272
q14	321	320	304	304
q15	q16	702	724	642	642
q17	8350	7220	6917	6917
q18	12972	12235	12917	12235
q19	901	767	761	761
q20	2131	2166	1916	1916
q21	5641	4591	4695	4591
q22	502	475	402	402
Total cold run time: 72678 ms
Total hot run time: 65932 ms

@hello-stephen

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

query5	4320	583	436	436
query6	425	210	198	198
query7	4800	443	227	227
query8	332	164	150	150
query9	8777	3055	3099	3055
query10	465	315	264	264
query11	5944	3856	3565	3565
query12	157	90	88	88
query13	1254	420	325	325
query14	6143	3903	3621	3621
query14_1	3407	3433	3417	3417
query15	224	197	189	189
query16	956	420	377	377
query17	879	629	512	512
query18	2426	412	295	295
query19	191	161	129	129
query20	84	82	81	81
query21	207	133	107	107
query22	13009	12906	12782	12782
query23	13301	12819	12549	12549
query23_1	12556	12573	12544	12544
query24	7372	806	464	464
query24_1	471	465	462	462
query25	502	391	333	333
query26	1246	250	137	137
query27	2766	438	268	268
query28	4636	1949	1932	1932
query29	1555	582	443	443
query30	297	212	185	185
query31	1028	951	831	831
query32	140	92	88	88
query33	522	300	234	234
query34	888	869	504	504
query35	739	764	660	660
query36	836	823	719	719
query37	124	103	74	74
query38	1786	1778	1674	1674
query39	791	822	763	763
query39_1	757	734	754	734
query40	224	120	107	107
query41	74	68	68	68
query42	96	84	87	84
query43	348	371	311	311
query44	1298	678	701	678
query45	177	179	168	168
query46	825	961	564	564
query47	3000	2984	2699	2699
query48	310	316	229	229
query49	575	405	300	300
query50	676	270	211	211
query51	10649	10559	10520	10520
query52	80	83	68	68
query53	182	207	149	149
query54	234	189	179	179
query55	75	67	63	63
query56	241	221	203	203
query57	1664	1625	1589	1589
query58	282	264	250	250
query59	2217	2143	2051	2051
query60	282	246	225	225
query61	143	146	156	146
query62	399	335	287	287
query63	183	154	160	154
query64	2717	911	744	744
query65	3336	3255	3254	3254
query66	1772	413	294	294
query67	20381	20086	20103	20086
query68	2968	996	619	619
query69	387	301	238	238
query70	825	772	776	772
query71	296	242	204	204
query72	2680	2554	2093	2093
query73	509	519	292	292
query74	4567	4506	4330	4330
query75	2309	2307	1912	1912
query76	2310	1021	586	586
query77	348	371	267	267
query78	9525	9761	9105	9105
query79	889	860	516	516
query80	473	417	327	327
query81	512	318	268	268
query82	204	130	104	104
query83	195	201	175	175
query84	295	125	103	103
query85	768	424	374	374
query86	256	235	232	232
query87	1970	1969	1792	1792
query88	3570	2667	2636	2636
query89	320	279	238	238
query90	2127	165	165	165
query91	154	153	128	128
query92	98	90	79	79
query93	979	977	573	573
query94	452	289	265	265
query95	579	333	365	333
query96	671	489	235	235
query97	2439	2445	2337	2337
query98	164	149	160	149
query99	755	769	656	656
Total cold run time: 233176 ms
Total hot run time: 150552 ms

### What problem does this PR solve?

Problem Summary: Default descent and a blacklist of throwing functions miss
residual filters, generators, and epoch conversions. Row-group reordering
can then suppress errors by pruning their original input. Require explicit
operator traversal and a closed set of total expressions, preserving safe
slot queries while fencing unknown operations and cardinality assertions.

### Release note

Preserve errors when TopN runtime filters reduce input to sensitive operations.

### Check List (For Author)

- Test: 38 related FE unit tests passed; five new test methods failed first
  on the previous revision. FE Checkstyle and Groovy compilation passed.
  Full SQL regression execution is pending CI.
- Behavior changed: Yes; unclassified operations block TopN filter pushdown.
- Does this need documentation: No.
@Gabriel39

Copy link
Copy Markdown
Contributor Author

run buildall

@Gabriel39

Copy link
Copy Markdown
Contributor Author

/review

@hello-stephen

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

query1	0.00	0.00	0.00
query2	0.09	0.05	0.05
query3	0.26	0.08	0.08
query4	1.61	0.09	0.11
query5	0.22	0.24	0.24
query6	1.17	0.69	0.69
query7	0.04	0.01	0.01
query8	0.06	0.04	0.04
query9	0.40	0.34	0.35
query10	0.57	0.56	0.58
query11	0.20	0.14	0.15
query12	0.19	0.14	0.14
query13	0.49	0.49	0.49
query14	1.04	1.02	1.01
query15	0.62	0.60	0.61
query16	0.35	0.35	0.34
query17	1.10	1.14	1.09
query18	0.20	0.19	0.20
query19	2.06	1.95	1.94
query20	0.03	0.01	0.02
query21	15.50	0.19	0.14
query22	4.93	0.05	0.06
query23	16.29	0.33	0.12
query24	3.11	0.59	0.37
query25	0.09	0.07	0.06
query26	1.05	0.21	0.16
query27	0.05	0.05	0.05
query28	3.06	0.77	0.36
query29	12.48	4.10	3.26
query30	0.28	0.14	0.10
query31	2.77	0.56	0.32
query32	3.23	0.60	0.51
query33	3.12	3.21	3.19
query34	15.58	4.01	3.39
query35	3.38	3.30	3.31
query36	0.55	0.44	0.45
query37	0.09	0.07	0.06
query38	0.05	0.04	0.04
query39	0.04	0.03	0.03
query40	0.17	0.16	0.14
query41	0.10	0.03	0.02
query42	0.04	0.03	0.03
query43	0.04	0.04	0.03
Total cold run time: 96.7 s
Total hot run time: 24.22 s

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

One new P2 finding: the table-layer reorder proof omits implicit file-to-table materialization, allowing a strict schema-evolution overflow to be suppressed. The four existing P2 threads (date arithmetic, ON predicates, retained WHERE/generators, and epoch conversions) are fixed at this head; no duplicate comments are posted and no existing P0/P1 remains to carry forward. No additional user focus was supplied.

Critical checkpoint conclusions:

  • Goal/data correctness/error semantics: best-first Parquet reads and late-bound pruning are implemented, but implicit projection errors remain exposed by the inline finding.
  • Scope/reuse: changes are focused on TopN ordering and its safety gates; the FE closed expression/operator set deliberately reduces eligibility. Existing date classification and statistics sanitization are reused.
  • Concurrency/locks: sink feedback and scanner reads use locked monotonic bound snapshots; no new threads, nested locks, or heavy work under a new lock.
  • Lifecycle/memory/static initialization: order state resets per plan, pending requests activate after reader teardown, physical coordinates remain attached to plans, and scratch is file/reader scoped. No new ownership cycle or cross-TU initializer identified.
  • Configuration: no new configuration or dynamic-update contract.
  • Compatibility: no stored-format or wire change, new external artifact dependency, or new ABI requirement identified by source inspection; binary compatibility was not executed.
  • Thrift/protobuf IDs: unchanged; branch-ID comparison is not applicable.
  • Parallel paths: traced footer/page and legacy/OLAP ZoneMap consumers, ORC boundaries, request refresh, row-ID fetches, absolute row positions/deletes, and TopN condition-cache exclusion. No additional verified defect.
  • Conditional branches: eligibility/fallback comments explain NULL, ties, unsafe predicates, row IDs, missing statistics, and late feedback.
  • Coverage: BE tests cover ASC/DESC feedback, exact values, localized slots, physical row IDs, fences, NULL placement and ties; FE tests cover the four previous findings and safe controls. Implicit evolution is missing from the new reorder tests.
  • Expected results: no output snapshots changed; the SQL suite uses error/EXPLAIN checks. Assertions and expected values were inspected statically.
  • Observability: deferred pruning uses existing counters/profile publication; no new per-row logging.
  • Transactions/persistence/failover: unaffected by this read-path change.
  • Writes/atomicity/crash recovery: no data-write path changed.
  • FE/BE variables: allow_row_reordering is a BE-local request field, set on initial construction and refresh; no new transmitted variable.
  • Performance: one per-file priority sort plus boundary footer checks can avoid decoding/I/O. The microbenchmark is opt-in; author timing claims were not independently reproduced.
  • Other concerns: no further substantiated finding after complete changed-path and downstream review.

Validation limits: static source review only; no builds, tests, lint, benchmarks, cluster runs, or ELF audits were executed. The SQL regression uses OLAP tables and does not exercise external Parquet permutation; the new implicit-evolution case needs a multi-row-group regression. There are no unresolved review candidates or uncovered changed files. Review converged in 3 rounds.

Review converged after 3 round(s).

Comment thread be/src/format_v2/table_reader.h Resolved
### What problem does this PR solve?

Problem Summary: FE and scan predicates cannot see file-to-table evolution.
Reordering can visit valid low-key groups first and skip an earlier group
whose unrelated DATE-to-TIMESTAMP_NS output would overflow. Require every
projected mapping to be direct, including nested materialization, after
request customization and reconciliation. Apply one shared proof to initial
and refreshed requests while retaining a hook's false flag.

### Release note

Preserve implicit conversion errors during Parquet V2 TopN scans.

### Check List (For Author)

- Test: 483 related BE tests passed; the two new regression methods failed
  first on the reviewed head. Real Iceberg/Parquet coverage includes both
  sort directions, top-level/nested outputs, strict/non-strict conversions,
  identity positive controls and request refresh. clang-format 16,
  build-hygiene and changed-line clang-tidy checks passed.
- Behavior changed: Yes; nontrivial output mappings retain physical order.
- Does this need documentation: No.
@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	17610	5111	5037	5037
q2	2116	314	256	256
q3	10250	1380	825	825
q4	4685	449	337	337
q5	7439	784	538	538
q6	178	186	155	155
q7	751	789	602	602
q8	9261	1246	1028	1028
q9	5389	4198	4155	4155
q10	6837	1299	1019	1019
q11	420	252	232	232
q12	640	394	273	273
q13	18089	3631	3038	3038
q14	274	270	251	251
q15	q16	720	703	655	655
q17	1197	747	578	578
q18	7058	6195	6112	6112
q19	1101	950	613	613
q20	380	336	226	226
q21	5008	2133	2261	2133
q22	346	295	250	250
Total cold run time: 99749 ms
Total hot run time: 28313 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	5616	5624	5621	5621
q2	658	573	534	534
q3	5182	5402	4995	4995
q4	2171	2157	1534	1534
q5	5309	5195	5560	5195
q6	266	202	152	152
q7	2388	2075	1923	1923
q8	3042	2699	2630	2630
q9	8102	7968	8048	7968
q10	3965	3676	3257	3257
q11	601	447	409	409
q12	698	686	523	523
q13	3708	3797	3286	3286
q14	309	305	277	277
q15	q16	708	723	687	687
q17	7959	7002	6900	6900
q18	13002	12170	12941	12170
q19	901	786	788	786
q20	2147	2140	1917	1917
q21	5592	4587	4760	4587
q22	511	444	405	405
Total cold run time: 72835 ms
Total hot run time: 65756 ms

### What problem does this PR solve?

Related PR: apache#69646

Problem Summary: Retained V1 Parquet requests can expose STRING min/max
Fields under a VARBINARY context. Newly enabling generic TopN ZoneMap
evaluation reaches the shared typed-range assertion after a late bound
arrives, failing valid queries during a BE-first rolling upgrade.
Validate the bound and both statistic carriers before that helper, using
the existing unsupported fallback for incompatible metadata. Correctly
typed VARBINARY ranges retain pruning and equality semantics.

### Release note

Prevent legacy VARBINARY TopN scans from failing on incompatible
statistic carriers during rolling upgrades.

### Check List (For Author)

- Test: 498 BE tests passed; one opt-in benchmark skipped. The regression
  first failed on the previous head and uses the actual V1 statistics
  decoder with real late-bound RuntimePredicate feedback, ASC/DESC,
  mixed carriers, typed positive controls, binary bytes and equality.
  clang-format 16, build hygiene and changed-line clang-tidy verified.
- Behavior changed: Yes. Incompatible statistics retain candidate rows.
- Does this need documentation: No new configuration or public API.
@Gabriel39

Copy link
Copy Markdown
Contributor Author

run buildall

@Gabriel39

Copy link
Copy Markdown
Contributor Author

/review

@hello-stephen

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

query5	4303	574	468	468
query6	424	201	189	189
query7	4805	443	228	228
query8	316	172	167	167
query9	8787	3106	3100	3100
query10	484	305	250	250
query11	6042	3855	3574	3574
query12	148	89	87	87
query13	1232	456	314	314
query14	6070	3933	3597	3597
query14_1	3409	3418	3452	3418
query15	203	195	181	181
query16	936	420	406	406
query17	838	622	511	511
query18	2410	411	304	304
query19	187	156	141	141
query20	82	85	81	81
query21	208	129	105	105
query22	12944	13031	12866	12866
query23	13433	12832	12511	12511
query23_1	12532	12632	12529	12529
query24	7398	828	471	471
query24_1	483	480	468	468
query25	540	426	369	369
query26	1280	258	140	140
query27	2774	434	275	275
query28	4654	1937	1942	1937
query29	1573	592	463	463
query30	292	221	187	187
query31	1004	931	821	821
query32	141	90	96	90
query33	526	311	244	244
query34	998	857	500	500
query35	735	755	648	648
query36	814	815	824	815
query37	125	97	81	81
query38	1788	1740	1677	1677
query39	785	802	769	769
query39_1	752	753	758	753
query40	222	116	102	102
query41	73	70	71	70
query42	89	85	88	85
query43	355	380	313	313
query44	1325	716	713	713
query45	181	181	174	174
query46	834	1000	580	580
query47	2968	2858	2546	2546
query48	317	329	231	231
query49	582	404	305	305
query50	702	291	217	217
query51	10760	10610	10292	10292
query52	87	83	78	78
query53	188	213	157	157
query54	245	225	201	201
query55	80	72	68	68
query56	279	235	233	233
query57	1644	1615	1569	1569
query58	292	278	263	263
query59	2160	2184	1998	1998
query60	305	258	238	238
query61	174	167	166	166
query62	387	339	304	304
query63	200	166	163	163
query64	2853	946	781	781
query65	3375	3275	3256	3256
query66	1775	414	286	286
query67	20290	20249	20266	20249
query68	2970	985	609	609
query69	375	289	269	269
query70	844	787	772	772
query71	289	233	220	220
query72	2712	2622	2285	2285
query73	518	519	299	299
query74	4614	4522	4341	4341
query75	2358	2299	1908	1908
query76	2309	998	574	574
query77	353	363	260	260
query78	9798	9693	9138	9138
query79	994	859	542	542
query80	1265	421	349	349
query81	586	328	284	284
query82	595	129	103	103
query83	319	193	186	186
query84	309	115	104	104
query85	864	458	373	373
query86	395	236	233	233
query87	1996	1942	1815	1815
query88	3623	2677	2662	2662
query89	327	279	242	242
query90	1927	175	169	169
query91	165	150	128	128
query92	100	90	88	88
query93	1025	1014	591	591
query94	642	311	255	255
query95	568	321	374	321
query96	649	488	238	238
query97	2467	2465	2311	2311
query98	156	148	156	148
query99	763	776	642	642
Total cold run time: 235908 ms
Total hot run time: 151213 ms

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

Reviewed #69646 at ba5680f using the supplied aggregate diff and all nine changed paths. No new actionable defect was verified. No independently confirmed existing P0/P1 inline findings remain to carry forward.

Existing review context: the legacy VARBINARY statistics issue (4385250260) is fixed by checking the bound and both min/max Field carriers before the asserting typed-range helper; the new regression exercises the retained V1 decoder and late publication. The other existing threads concern evaluation of expressions or conversions in excluded rows. The current implementation and explicit PR contract permit omitting that work when a valid TopN bound excludes the rows; executed failures still propagate. These known concerns are not reposted. There was no additional user-provided review focus.

Critical checkpoint conclusions:

  • Goal and evidence: the implementation can visit promising Parquet V2 row groups first and recheck the current TopN frontier before dictionary/index probes and I/O. Added reader tests model real per-block runtime-predicate feedback and check exact TopK values and pruning counters; their execution was not verified in this review.
  • Scope and clarity: production changes are limited to the shared TopN ZoneMap capability and Parquet scheduler; FE production behavior is unchanged apart from a comment. Tests and the optional microbenchmark support that scope.
  • Concurrency: the sort sink publishes bounds through RuntimePredicate::update under its existing shared mutex; each new ZoneMap evaluation copies one bound under the matching read lock and compares outside it. Direction and NULL ordering are immutable. Scanner-owned scheduling state introduces no new shared mutation or lock ordering.
  • Lifecycle: the execution permutation stores indices rather than predicate/schema pointers. Scheduler reset clears ordering and recheck state; request activation retains current-row-group ownership until its readers are released. No cross-translation-unit static initializer or new ownership cycle is introduced.
  • Configuration: no new production configuration is added. The benchmark environment switch is test-only.
  • Compatibility: no persisted format, wire field, or external function symbol contract is changed. Shared capability consumers retain conservative fallback for incompatible types/carriers and unsuitable ranges; retained V1 VARBINARY statistics are explicitly covered.
  • Thrift/protobuf: no files under gensrc/thrift or gensrc/proto change.
  • Parallel paths: inspected shared expression consumers and V1/V2 boundaries. Row-group prioritization belongs to the V2 Parquet scheduler; existing mapper localization, ORC paths, and FE filter eligibility are preserved.
  • Conditional boundaries: ordering uses the existing metadata-pruning prefix, direct compatible scalar slot, available statistics, and ordered row-ID fetch restriction. Equal leading keys remain candidates for secondary sort keys; NULL ordering and unavailable/unsafe statistics remain conservative.
  • Test coverage: inspected ASC/DESC, late-bound publication, secondary-key equality, NULL placement, localized slot identity, absent statistics, pruning fences, row-ID fetch order, native DATE conversion, projected evolution, hidden Iceberg equality-delete conversions, and legacy binary carriers. FE tests cover secondary expressions and translated grouped-key aggregation plans. These are source-inspected tests, not executed validation.
  • Expected results: added expected TopK values, physical row IDs, row counts, converted dates, error controls, and pruning counters agree with the fixture construction. No regression .out file is changed.
  • Observability: late footer pruning updates existing deferred-pruning profiles before continuing; existing raw-row, min/max-pruning and I/O counters expose avoided work. No new per-row logging is introduced.
  • Transactions and persistence: no transaction, EditLog, snapshot-selection, visible-version, or delete-bitmap publication logic is modified.
  • Writes and crash atomicity: production changes are read-side scheduling/pruning only. No new durable write or crash-atomicity protocol is introduced.
  • FE/BE variable passing: no new transmitted variables; existing TopN descriptors and mapper-owned file-local identities are reused.
  • Performance and memory: priorities are computed once from sanitized footer statistics, sorted in O(G log G), and retain O(G) execution indices for G candidate row groups. Footer rechecks precede expensive probes and decode. The included benchmark isolates a favorable one-column reader workload; no full-SQL, remote-storage or broad workload speedup was independently established.
  • Other integration checks: physical plans remain in file order while only their execution indices change. Per-group readers use first_file_row for row positions; position-delete evaluation restarts lookup for each batch and equality deletes use key lookup. TopN scans disable production Condition Cache, and row-ID fetches retain physical order. Refresh preserves fixed local positions and reevaluates remaining groups against the active request.

Validation limits: static source review only, as required by the review contract. No build, unit/regression test, benchmark, running cluster, sanitizer, or rolling-upgrade execution was performed. Author-reported test counts and timings were not independently reproduced. Full SQL feedback scheduling and external-writer/remote-storage performance remain empirical validation limits; no unresolved static candidate remains in the reviewed scope.

Review converged after 1 round(s).

Revert ba5680f.

Keep legacy FE/BE mixed-version Parquet V1 compatibility outside the
scope of the Parquet V2 row-group scheduling optimization. Restore the
preceding evaluator and remove its dedicated compatibility regression.
Verify complete translated plans for STRUCT order keys with sibling
IS NOT NULL predicates, including TIMESTAMPTZ, INT and DATE in both
sort directions. Check the TopN filter and both nested access paths
without changing the existing SQL regression assertions.
@Gabriel39

Copy link
Copy Markdown
Contributor Author

run buildall

@Gabriel39

Copy link
Copy Markdown
Contributor Author

/review

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Static review of PR #69646 at 96e6d38: no new actionable findings.

The existing legacy V1 VARBINARY statistics issue still applies to the older-FE request path. It is already reported as P2, and the author explicitly reverted its compatibility fix at the maintainer's request. It is retained as a known limitation, without a duplicate inline. All supplied original inline findings are P2; no existing P0/P1 finding qualifies for existing_blocking_comment_ids. The other existing threads concern exceptions in excluded rows; the documented contract permits avoiding their evaluation, and no distinct wrong-result case was established.

Critical checkpoint conclusions:

  1. Goal and correctness: The implementation prioritizes promising Parquet V2 groups and rechecks the current bound before expensive probes. Inspected tests check exact ASC/DESC TopK values, physical row identities, row counts and avoided groups using actual RuntimePredicate feedback. This is source evidence, not an executed validation result.
  2. Scope and reuse: Production changes are confined to the shared TopN ZoneMap interface and Parquet scheduling; the FE production delta is explanatory. Existing statistics sanitation, conversion and metadata-pruning machinery are reused.
  3. Concurrency and locks: SortSink publishes the frontier under RuntimePredicate's unique lock; the evaluator copies an owning Field under its shared lock, then evaluates outside the lock. Direction and NULL placement are immutable. Scheduling state is scanner-local. No new nested locking, thread or lock-order edge was found.
  4. Lifecycle and static initialization: Temporary predicate/schema observers do not escape preparation; retained order state contains indices. Reset clears it with the scan plans. Request refresh waits for a row-group boundary after current readers and pending materialization finish. No new ownership cycle or static initializer is introduced.
  5. Configuration: No configuration item or dynamic-update requirement is added.
  6. Compatibility: No wire or storage format changes. The shared capability also reaches retained V1 and native-storage ZoneMap consumers; their types and fallbacks were traced. The known legacy V1 VARBINARY compatibility exception above remains unfixed.
  7. Thrift/Protobuf: No gensrc definition or field/enum ID changes.
  8. Parallel paths: Raw/dictionary TopN filtering, V2 footer/page pruning, retained V1 consumers, native storage, aggregate bypass and ORC's separate SARG path were considered. Ordered row-ID fetches retain physical order. Existing FE grouped-key, join, set, window and CTE behavior is unchanged.
  9. Conditions and semantics: Direct slot/type/local-position checks, the metadata-safe prefix, missing/unusable statistics, NULL placement and strict leading-key inequality retain candidates conservatively. Ties may still win on secondary keys. Position deletes and deletion vectors use absolute row positions and fresh per-batch lookup; immutable physical plans preserve their coordinates. Production TopN scans disable Condition Cache admission through the existing scanner digest guard.
  10. Test coverage: New GTest cases cover late bounds, ASC/DESC, NULL/equality boundaries, localized slots, absent statistics, pruning fences, ordered fetches and physical IDs. Iceberg cases cover top-level/nested evolution, strict-mode variants, native DATE materialization and hidden equality-delete conversions, with required-row error controls. FE tests cover eligibility plus translated aggregation/nested-field EXPLAIN. Full SQL feedback, matching-delete combinations, remote/multi-scanner execution and the broad external-writer matrix are not newly demonstrated by these tests.
  11. Expected results: No generated regression .out files change. Inspected unit expectations match retained ties, physical coordinates and conversion behavior; no handwritten result artifact was introduced.
  12. Observability: Existing deferred-pruning counters, projected-byte accounting, reader timers and profile flush paths remain in use. No per-row logging or timing overhead is added.
  13. Transactions and persistence: Not applicable to this read-only optimization; no EditLog, snapshot-selection, publish or failover state changes.
  14. Data writes and crash atomicity: No write path changes. Existing RAII and error/close paths retain reader ownership and cleanup.
  15. FE/BE propagation: No transmitted variable is added. Key binding follows mapper block position to local column ID to physical leaf; the localized-slot test checks a nonidentity mapping.
  16. Performance and memory: Priority construction uses O(G) temporary state and an O(G log G) stable sort once per file, without dictionary/page I/O. The O(G) index vector resets with scan state. The opt-in benchmark checks heap keys/checksums and work counters, but it is a local reader microbenchmark with modeled feedback; its timings and full SQL gains were not independently verified.
  17. Other issues and user focus: All eight changed files and affected producer/consumer paths were reviewed. There was no additional user-provided focus, no new substantiated defect, and no unresolved concrete candidate.

Validation was static only: no builds, tests, benchmarks, formatting, build-hygiene or clang-tidy checks were run; repository source and submodules were unchanged. Author-reported results are not review-executed evidence. Full SQL, rolling-upgrade and broad interoperability/performance validation remain outside this review's execution evidence.

Review converged after two rounds: the latest complete-review and separate risk reviewers returned NO_NEW_VALUABLE_FINDINGS on the unchanged payload, and all candidates and initial risk items have an evidenced disposition.

Review converged after 2 round(s).

@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	6841	5049	5024	5024
q2	796	316	291	291
q3	994	1346	795	795
q4	701	453	329	329
q5	1095	794	533	533
q6	182	186	151	151
q7	741	800	607	607
q8	953	889	943	889
q9	4476	4432	4229	4229
q10	1496	1273	1032	1032
q11	379	250	232	232
q12	363	395	279	279
q13	18052	3621	3053	3053
q14	285	263	248	248
q15	q16	751	717	649	649
q17	1200	741	584	584
q18	8977	6181	6090	6090
q19	1217	960	617	617
q20	574	336	218	218
q21	6453	2138	2313	2138
q22	364	299	252	252
Total cold run time: 56890 ms
Total hot run time: 28240 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	5625	5582	5583	5582
q2	734	616	556	556
q3	5244	5378	4918	4918
q4	2100	2134	1551	1551
q5	5271	5173	5143	5143
q6	231	185	140	140
q7	2146	1873	1667	1667
q8	2855	2558	2528	2528
q9	7512	7479	7465	7465
q10	3644	3624	3172	3172
q11	598	418	384	384
q12	663	683	518	518
q13	3317	3629	3071	3071
q14	295	294	266	266
q15	q16	660	690	641	641
q17	7831	7021	6945	6945
q18	12948	12203	12877	12203
q19	919	797	805	797
q20	2153	2172	1932	1932
q21	5620	4622	4738	4622
q22	504	460	411	411
Total cold run time: 70870 ms
Total hot run time: 64512 ms

@hello-stephen

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

query5	4321	605	415	415
query6	444	207	187	187
query7	4813	449	224	224
query8	319	163	150	150
query9	8756	3103	3102	3102
query10	466	313	256	256
query11	6062	3848	3567	3567
query12	154	90	85	85
query13	1243	435	320	320
query14	6092	3900	3619	3619
query14_1	3391	3435	3359	3359
query15	205	190	178	178
query16	938	422	432	422
query17	1079	632	518	518
query18	2414	423	296	296
query19	196	156	126	126
query20	98	84	80	80
query21	209	126	104	104
query22	13023	12984	12716	12716
query23	13512	12784	12336	12336
query23_1	12568	12526	12570	12526
query24	7369	817	451	451
query24_1	472	458	468	458
query25	545	428	364	364
query26	1273	268	139	139
query27	2762	444	271	271
query28	4620	1934	1922	1922
query29	1576	604	459	459
query30	296	220	187	187
query31	1008	928	822	822
query32	150	92	89	89
query33	534	311	245	245
query34	883	852	492	492
query35	732	762	658	658
query36	841	800	717	717
query37	125	95	84	84
query38	1787	1704	1661	1661
query39	792	785	750	750
query39_1	741	734	748	734
query40	223	120	97	97
query41	76	73	69	69
query42	87	85	83	83
query43	345	361	312	312
query44	1316	687	696	687
query45	180	191	169	169
query46	834	965	555	555
query47	2744	2734	2561	2561
query48	291	316	221	221
query49	583	397	295	295
query50	690	273	216	216
query51	10511	10407	10707	10407
query52	83	82	81	81
query53	194	207	151	151
query54	244	200	214	200
query55	78	73	73	73
query56	248	240	220	220
query57	1646	1633	1580	1580
query58	296	270	269	269
query59	2160	2224	1966	1966
query60	305	257	242	242
query61	174	171	168	168
query62	397	354	288	288
query63	185	156	159	156
query64	2919	916	735	735
query65	3301	3239	3275	3239
query66	1788	419	292	292
query67	20330	20136	20003	20003
query68	3232	1010	601	601
query69	397	267	245	245
query70	821	765	755	755
query71	288	227	202	202
query72	2745	2709	2303	2303
query73	531	516	297	297
query74	4705	4506	4337	4337
query75	2341	2282	1908	1908
query76	2402	1004	584	584
query77	330	363	270	270
query78	9740	9773	9204	9204
query79	986	839	515	515
query80	859	451	339	339
query81	587	320	271	271
query82	573	130	107	107
query83	295	192	181	181
query84	266	125	100	100
query85	928	414	368	368
query86	344	234	231	231
query87	2012	1948	1854	1854
query88	3617	2676	2663	2663
query89	339	280	241	241
query90	1794	170	169	169
query91	165	149	134	134
query92	104	90	78	78
query93	993	998	574	574
query94	524	315	272	272
query95	568	389	298	298
query96	652	481	228	228
query97	2459	2435	2319	2319
query98	154	149	144	144
query99	771	771	640	640
Total cold run time: 235130 ms
Total hot run time: 150251 ms

@hello-stephen

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

query1	0.00	0.00	0.00
query2	0.10	0.05	0.05
query3	0.27	0.09	0.08
query4	1.61	0.10	0.10
query5	0.23	0.25	0.23
query6	1.16	0.68	0.68
query7	0.04	0.01	0.01
query8	0.06	0.04	0.04
query9	0.39	0.35	0.34
query10	0.57	0.57	0.62
query11	0.19	0.14	0.14
query12	0.18	0.15	0.14
query13	0.49	0.51	0.48
query14	1.03	1.00	1.01
query15	0.63	0.61	0.61
query16	0.35	0.35	0.36
query17	1.17	1.19	1.09
query18	0.20	0.18	0.18
query19	2.06	2.07	1.90
query20	0.02	0.01	0.01
query21	15.45	0.20	0.13
query22	4.90	0.06	0.05
query23	16.29	0.32	0.14
query24	3.05	0.72	0.41
query25	0.11	0.06	0.05
query26	0.95	0.22	0.14
query27	0.05	0.05	0.05
query28	3.06	0.75	0.38
query29	12.50	4.20	3.35
query30	0.27	0.14	0.10
query31	2.78	0.56	0.32
query32	3.22	0.59	0.50
query33	3.15	3.19	3.27
query34	15.46	4.01	3.39
query35	3.36	3.35	3.35
query36	0.57	0.48	0.43
query37	0.10	0.06	0.06
query38	0.05	0.04	0.04
query39	0.03	0.03	0.03
query40	0.17	0.15	0.15
query41	0.08	0.03	0.03
query42	0.03	0.03	0.03
query43	0.04	0.03	0.03
Total cold run time: 96.42 s
Total hot run time: 24.35 s

@hello-stephen

Copy link
Copy Markdown
Contributor

BE UT Coverage Report

Increment line coverage 100.00% (2/2) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 66.92% (32197/48111)
Line Coverage 51.75% (340589/658135)
Region Coverage 47.19% (276360/585578)
Branch Coverage 48.87% (130247/266544)

@hello-stephen

Copy link
Copy Markdown
Contributor

BE Regression && UT Coverage Report

Increment line coverage 100.00% (2/2) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 77.29% (36031/46615)
Line Coverage 62.54% (407211/651100)
Region Coverage 59.04% (344480/583450)
Branch Coverage 59.93% (159361/265892)

@hello-stephen

Copy link
Copy Markdown
Contributor

FE Regression Coverage Report

Increment line coverage 0.00% (0/36) 🎉
Increment coverage report
Complete coverage report

@yiguolei
yiguolei merged commit e493c61 into apache:master Oct 28, 2026
45 checks passed
Gabriel39 added a commit to Gabriel39/incubator-doris that referenced this pull request Oct 29, 2026
Apply the outstanding changes in master merge order:
apache#69545 7d16729
apache#69551 7224a2d
apache#69612 61d1aef
apache#69615 8d2bd1e
apache#69650 3dc7341
apache#69651 c00ecb1
apache#69646 e493c61
apache#69668 7865646

apache#69614 already exists on branch-4.2 through apache#69644.
Adapt datasource APIs and tests to the release branch, retaining its
existing scanner dispatch and statement-bound metadata contracts.
yiguolei pushed a commit that referenced this pull request Oct 30, 2026
### What problem does this PR solve?

Backport the requested external-data and FileScannerV2 changes to
`branch-4.2` in their actual master merge order:

| Order | Master PR | Change | Backport |
| --- | --- | --- | --- |
| 1 | #69545 | Preserve Paimon schema options after field renames |
Included |
| 2 | #69551 | Expand Iceberg capability, transaction and snapshot tests
| Included with 4.2 API adaptations |
| 3 | #69614 | Elasticsearch HTTP scans in FileScannerV2 | Already
present through #69644 (`2104b7509cda`); not applied twice |
| 4 | #69612 | Check catalog visibility before SHOW DATABASES accesses
metadata | Included |
| 5 | #69615 | Publish Iceberg Parquet split offsets | Included |
| 6 | #69650 | Preserve ES compound residuals and expand scanner
coverage | Included |
| 7 | #69651 | Accept Elasticsearch 9.x during node discovery | Included
|
| 8 | #69646 | Prioritize Parquet V2 row groups using TopN bounds |
Included |
| 9 | #69668 | Arrow loads and schema inference in FileScannerV2 |
Included |

The eight outstanding changes are combined in one commit. This branch
retains the 4.2 FE datasource APIs instead of importing master's
connector-module refactor.

### Backport adaptations

- Port Paimon schema restoration, catalog-option provenance, fallback
handling and tests to the existing table-copy/serialization lifecycle.
Keep statement-bound fields when reader options are applied after a
schema-only rename.
- Exercise Iceberg's existing metadata and transaction entry points with
real in-memory tables and retained-generation fixtures. Preserve the
nine disabled known-bug reproductions; omit two connector-only lifecycle
tests. Expiration checks verify live data-file sets and exact
delete-file counters; 4.2's separate manifest-name/data-counter issue
remains unchanged. The delete/history SQL suite checks visible rows and
eager/late-materialization parity because 4.2 does not expose master's
`_file`/`_pos` columns. The inventory labels upstream-only evidence
explicitly.
- Port ES residual tracking to the legacy planner and add compound
OR/NOT, docvalue eligibility and Thrift-context tests there. Use Java
8-compatible HTTP APIs for opt-in ES 9 tests and the branch's supported
node-address fixtures.
- Keep the existing full-schema Iceberg metadata-table scan, which
already supplies the file-size dependency for split offsets. Adapt the
external write suite to verify offsets without requiring master-only
distribution plans or golden output.
- Use the existing Parquet statistics interface and physical
row-location representation. Keep the TopN scheduling and pruning
invariants.
- Include the shared Arrow array normalizer required by the new reader,
taken from the same master revision. Preserve 4.2 Lance/Avro dispatch;
omit the schema-inference test for the unavailable Hive OpenCSV parser.

### Validation

- FE production and test sources compile with the branch's Java 8
target. Across the targeted runs and focused reruns, 20 test classes
report 181 discovered, 171 passed, zero failures/errors, and 10 skipped:
nine intentionally disabled known-bug reproductions plus the opt-in live
ES 9 class.
- FE Checkstyle: passed with zero violations.
- Clang compiler syntax checks: all 23 affected C++ translation units
passed against the current sources and freshly generated Thrift, using
cached third-party/generated build dependencies. Focused BE runtime
validation is listed below.
- clang-format 16: all 30 affected C++ source/header files passed.
- Groovy 4.0.19 syntax and AST conversion: all seven changed suites
passed. All 105 local inventory links resolve.
- Complete staged-diff self-review, whitespace/conflict checks, and
sensitive-information checks passed.

A full BE rebuild and SQL/external-service regressions were not run
locally. Live ES 9 tests require their opt-in endpoint. CI is requested
for the combined backport.

### Release note

Fix Paimon reads after field renames, external-catalog SHOW DATABASES
authorization, and ES compound residual filtering. Support ES 9.x node
discovery, publish Iceberg Parquet split offsets, improve Parquet V2
TopN row-group scheduling, and use FileScannerV2 for Arrow loads and
text/JSON schema inference.

### Check List (For Author)

- Test
    - [x] Unit Test
- [x] Regression test additions and syntax checks; SQL execution pending
CI
- Behavior changed:
    - [x] Yes. Backport the changes listed above.
- Does this need documentation?
- [x] No. Existing interfaces and settings are retained; the test
coverage inventory is included.

### Follow-up fixes

- Set the Thrift `enable_file_scanner_v2` default to `true`, matching
the session default. Preserve explicit `false` for legacy query scans
and extend the existing load-selection test.
- Keep the selected Paimon snapshot and schema together during relation
projection. Historical sequence/aggregation options must not be combined
with the latest field names. Read-only principals can resolve historical
selectors without INSERT permission.
- Read legacy append schemas with missing or empty bucket keys without
bucket pruning, preserving every historical file. The regression covers
both unaware and multiple fixed buckets.

Follow-up validation: 169 targeted Paimon FE tests passed with zero
failures/errors/skips. FE production compilation and Checkstyle passed.
Both changed C++ test translation units passed compiler syntax checks
and clang-format 16. Regenerated Java/C++ defaults are enabled; a Java
8-targeted protocol check verifies constructor/clear defaults, an
omitted wire field, and an explicit-false round trip. Complete BE CI and
live external SQL suites remain pending.

The two new reproductions first failed with the same missing-bucket-key
and renamed-sequence-field errors as CI. A separate read-only-principal
reproduction also failed before the permission correction. Complete
external regression execution remains pending CI.

### BE unit-test compatibility

- Complete the native Arrow Variant input dependency for `branch-4.2`:
accept binary storage and the Parquet Variant extension, validate
metadata and payloads before appending, and retain owned data after
Arrow buffers are released. Allow that ingestion path through the load
reader while preserving query-format restrictions and source-default
validation.
- Preserve Variant V2 markers in the Arrow test fixtures, including
nested types. Verify timestamp input against this branch's SerDe for
positive, negative, and second-boundary values. Match the existing DATE
range diagnostic in the TopN pruning test.
- Add tests for sliced and empty reads, invalid ranges and carriers,
malformed metadata/payloads, NULL children, atomic failure, buffer
ownership, and the query capability boundary.

Validation: all 13 previously failing BE tests passed. The expanded run
passed all 309 tests across nine suites, including Arrow/JSON loads,
load validation, Iceberg, table mapping, and Variant input/output. The
focused executable rebuilds the affected readers and SerDe against
cached 4.2 build dependencies; this is not a complete BE rebuild. All
seven changed C++ translation units passed compiler checks with current
headers/generated Thrift, and all 33 affected C/C++ files passed
clang-format 16. The complete staged diff, whitespace, and
sensitive-information checks passed. Full CI is requested with `run
buildall` and `/review`.
yiguolei pushed a commit that referenced this pull request Nov 6, 2026
### What problem does this PR solve?

Issue Number: None

Related PR: #65730 #66608 #66805 #66841 #66999 #67005 #67011 #67110
#67268 #67289 #67325 #67650 #67921 #68028 #68335 #68375 #68615 #68690
#68813 #68815 #68707 #68649

Problem Summary:

**In short.** This ports the Lance BE read path of branch-4.1 /
branch-4.2 to master, in its branch-4.2 form as of `cabcf45c1af`, plus
the BE side of three 4.x changes merged after that snapshot: the search
filter `prefilter` flag and profile layout (#68813), distance bounds for
`vector_search` (#68815), and the split-level index rule of #68707 that
#68815 relies on. It gathers the BE parts of the PRs listed above.
Master links `liblance_c` but has no Lance reader, so every
`FORMAT_LANCE` scan fails today. With this PR the BE reads Lance
datasets through FileScannerV2: plain scans, vector search and full-text
search, predicate and runtime-filter pushdown, the schema fetch of a
local Lance TVF, and the second phase of TopN lazy materialization. The
PR is BE only. The FE connector plugin and the regression suites come in
separate PRs, so until then nothing on master plans a Lance scan, and
the BE unit tests carry the proof.

Original authors: @zhangstar333 @Gabriel39 @wangzhaobo957-cloud @Jay-ju
@FANNG1

**Background.** Lance shipped in 4.1.4: the Lance catalog, `local()` /
`s3()` with `format=lance`, and the `vector_search` / `full_text_search`
TVFs. On master the FE side is being rebuilt as a connector plugin
(`fe-connector-lance`), and the BE needs the reader from 4.x. The thrift
ids are the branch-4.1 / 4.2 ones, including `TExternalSearchRequest.5`
(`prefilter`) and `TVectorSearchParams.6/7` (distance bounds) added
after the snapshot, so a BE built from master also reads the Lance scan
ranges and search requests that a 4.x FE sends during a rolling upgrade.
The one exception is `TVectorSearchOptions` 6, branch-4.2's
`approx_mode` (#69852, merged into branch-4.2 after the snapshot and not
ported): it is reserved in a comment, and a master BE ignores it, so
during a rolling upgrade a 4.2 FE that asks for `fast` or `accurate`
gets Lance's default (`normal`).

**The problem, and what it cost.**

| | master today | with this PR |
|---|---|---|
| A `FORMAT_LANCE` scan | FileScannerV2 has no Lance reader, so the scan
fails with "FileScannerV2 does not support file format" | read by
`LanceTableReader` in FileScannerV2 |
| `fetch_table_schema` for a local Lance TVF | not handled | returns the
Arrow schema mapped to Doris types; Null leaves keep `NULL_TYPE` |
| The row location of a Lance row in TopN lazy materialization |
`GlobalRowLoacationV2` only has a 32-bit row id, but a Lance row id is a
64-bit id within the snapshot | a version 1 layout carries the 64-bit
row id; phase two reads the rows with the dataset's `take_rows` |

**How this PR fixes it.**

- Reader: `be/src/format_v2/lance/*` and
`be/src/format_v2/table/lance_reader.*`, as on branch-4.2 except one
local renamed for master's `-Wshadow`. The unit tests construct
`RuntimeState(TQueryOptions(), globals)`, because master removed the
globals-only constructor.
- FileScannerV2: the reader dispatch and `_to_file_format` handle
`FORMAT_LANCE` / `"lance"`; `FileFormat::LANCE` is added to its two
exhaustive switches. Since #69723 every file scan uses FileScannerV2, so
nothing else routes Lance scans (on branch-4.2 a Lance query fails with
NotSupported when `enable_file_scanner_v2` is off).
- Row locations: `ROW_VERSION {FILE_LOCAL_ROW_ID = 0,
LANCE_DATASET_ROW_ID = 1}` and a union layout for
`GlobalRowLoacationV2`. Version 0 keeps its byte layout, which static
asserts pin. `PRequestBlockDesc.row_id` becomes `uint64` (a proto2
varint, so the wire format is compatible). The materialization operator
decodes row locations by version, rejects an unknown version or a wrong
size, and stamps `row_location_version`. `RowIdStorageReader` validates
the counts and the version, range-checks internal row ids against
`uint32`, and groups Lance rows by plan node, snapshot version and
dataset URI, so each dataset is read with one `take_rows`. Master's
segment batching, scan-slot dedup and fetch concurrency stay as they
are.
- Thrift (`PlanNodes.thrift`): the external search request body
(`TVectorElementType`, `TVectorMetric`, `TSearchVector`,
`TVectorSearchParams` 1-7, `TFtsCoverageMode`, `TFtsQueryType`,
`TFtsMatchOperator`, `TFullTextSearchParams`, `TSearchFilterFormat`,
`TSearchFilter`, `TVectorSearchOptions`, `TExternalSearchQuery`),
`TExternalSearchRequest` 2-5 and `TLanceFileDesc` 4-6, all with the
branch-4.1 / 4.2 ids. The enums spell out their 4.x values. No id is
reused. On master, #70075 reserved `TExternalSearchRequest` 2-5 in
comments and added 6 (`paimon_options`); this PR replaces those comments
with the fields and leaves 6 as it is. `TVectorSearchOptions` 6 is
reserved in a comment for branch-4.2's `approx_mode` (#69852).
- After the snapshot (one commit each):
- #68813: `prefilter` decides whether the TVF search filter runs before
(true) or after (unset / false) candidate selection, for vector and
full-text search. The profile groups the Lance counters under
`LanceTiming`, `LanceIO`, `LanceIndex`, `LanceVectorIndex`,
`LanceScalarIndex`, `LanceExecutionPlan`, `LanceScan`, `LanceSearchPlan`
and `LanceDataCache`, uses the 4.x counter names, and registers every
scan metric before the statistics callback is installed, so the
callback, which Lance invokes from its execution streams, never adds
counters. #68813's FE tweak that hides `TUnit.NONE` labels in
`AggCounter` belongs to the FE port.
- #68707 (part): a vector split searches exactly the index segments FE
planned for it, and a split without segments is searched flat whatever
`use_index` says; a split with segments under `use_index = false` is
rejected. 4.x FEs plan segments only when `use_index` allows them.
- #68815: `distance_lower_bound` (inclusive) and `distance_upper_bound`
(exclusive) are validated (finite FLOAT values, lower < upper at FLOAT
precision, single-vector only) and installed with
`lance_scanner_set_distance_range()`, which master's pinned lance-c
provides.
- Review fixes:
- A nested array goes to the Doris SerDes without a copy only when it
and every descendant start at offset 0 and reference exactly their child
ranges. Lance's legacy (v1) decoder can return a struct or list that is
tight at its own level over a Boolean child with a bit offset (a batch
that starts mid-byte in the bitmap), and the SerDes reject that under
Arrow input validation.
- A zero-field `STRUCT<>`, which Lance decodes, is reported as an
unsupported column at any nesting level, like the other unsupported
Lance types. It used to be typed as a STRUCT that `ColumnStruct` cannot
hold (its row count comes from its first child).
- A Lance Decimal256 field with precision <= 38 is narrowed to
Decimal128 by a safe Arrow cast before decoding. Doris types it
DECIMAL32/64/128I by precision, and those SerDes read only Decimal128
arrays; the BE used to crash on the first value, also for nested leaves.
Only values a reader can see are cast: before a nested leaf is narrowed,
every child slot that no visible row references (under a NULL list,
fixed-size-list, map or struct row, or outside a sliced parent) is
marked NULL, so a hidden value does not fail the cast and a visible
value outside the declared precision still does; map keys, which cannot
be NULL, stay checked.
- FLOAT / DOUBLE `>=` runtime filters stay residual-only. Lance compares
floats by totalOrder, so a pushed `x >= c` dropped negative-NaN rows
that Doris keeps.
- Runtime filters that mention INT64_MIN stay residual-only. The pinned
Lance SQL parser reads `-9223372036854775808` as the negation of a
magnitude that it parses as i64 first; the overflow turns the literal
into a Float64, which the planner cannot coerce to Int64 or Decimal, so
a pushed IN, `>=` or `<=` filter on an INT64 or scale-zero decimal
column failed planning and the query failed. Every other i64 is still
pushed.
- The shared Arrow DATE32 decoding
(`DataTypeDateV2SerDe::read_column_from_arrow`) rejects days outside
0000-01-01..9999-12-31 instead of returning a non-null `0000-00-00`,
skips null slots and adds the epoch in 64 bits: the DATE32 range part of
branch-4.1 / 4.2 #68649. It also changes ADBC, Arrow stream load and
paimon-rust reads.
- Child values hidden by a NULL list, fixed-size-list, map or struct row
are not decoded: the array, map and struct SerDes give the element SerDe
a validity bitmap that also clears those slots
(`mask_arrow_children_hidden_by_null_parents()`). Arrow leaves them
unspecified, and Lance's v2.1 miniblock encoding keeps them, so a hidden
10000-01-01 under a NULL fixed-size list used to fail the DATE32 range
check above. Visible values are unchanged; this applies to ADBC, Arrow
stream load and paimon-rust as well. The masks come from ExecEnv's
tracked Arrow pool. A nested Lance array that is compacted before
decoding is masked the same way, at every level, before its
`ValidateFull()`, so a hidden invalid UTF-8 string, out-of-precision
decimal, Date64 or time no longer fails the batch while a visible one
still does. Since #69697 the same masks keep hidden DATETIMEV2 /
TIMESTAMPTZ values out of its range checks. A mask costs at most one
pass per batch: a NULL row that owns no child slots, or only NULL ones,
allocates nothing, and Arrow stream load masks each retained IPC batch
once (`mask_arrow_descendants_not_visible()`) before it hands row ranges
to the SerDes, so its per-block reads allocate nothing. The one
per-block mask left is for map keys under a NULL map row that still owns
entries, since a MapArray cannot hold NULL keys; Arrow's map builder
writes NULL map rows without entries.
- Core: `to_protobuf` keeps `NULL_TYPE` for a NULL literal type, so a
Null leaf in ARRAY / MAP / STRUCT no longer comes back as BOOLEAN. The
nullable serde reads an Arrow Null array as NULLs without touching
buffers that the array does not have.
- Configs, caches and metrics, with the 4.x names and defaults:

  | Config | Default |
  |---|---|
  | `lance_index_cache_size_bytes` | 10 GiB |
  | `lance_metadata_cache_size_bytes` | 1 GiB |
  | `enable_lance_data_cache` | true |
  | `lance_data_cache_path` | `${DORIS_HOME}/lance_data_cache` |
  | `lance_data_cache_disk_capacity_bytes` | 100 GiB |
  | `lance_data_cache_read_block_size_bytes` | 1 MiB |
  | `lance_io_buffer_size_bytes` (mutable) | 2 GiB |
  | `lance_batch_readahead` (mutable) | 5 |
  | `lance_fragment_readahead` (mutable) | 5 |

The BE-wide Lance session (index cache, metadata cache and the optional
Foyer disk cache) is created by the first Lance scan, so a BE that never
reads Lance creates no cache directory. The first six configs take
effect after a restart. A negative cache size (and, while the data cache
is enabled, a negative disk capacity or read block size) fails the first
Lance scan with a message that names the setting, as does any data-cache
value lance-c rejects; the rest of the BE keeps serving. Ten
`lance_session_*` metrics report the two in-memory caches. With the
defaults, the disk tier keeps 51,200 sparse 2 MiB block files open from
the first Lance scan on (100 GiB in blocks of twice the read block
size), the same as on branch-4.1 / 4.2. Size
`lance_data_cache_disk_capacity_bytes` and
`lance_data_cache_read_block_size_bytes` against the BE's open-file
limit, or set `enable_lance_data_cache = false`.

The index and metadata caches live in lance-c memory: they are not
charged to a MemTracker or registered with CacheManager, so memory GC,
the cache capacity adjustment under memory pressure and
`/api/clear_cache/all` do not shrink them, and `enable_lance_data_cache
= false` does not turn them off. lance-c has no call to clear, evict or
resize them, so size them with the two configs. The disk tier keys
blocks by Lance's store prefix (`scheme$bucket` for S3, GCS and OSS,
without the endpoint) and the object path
(`thirdparty/patches/lance-c-foyer.patch`). Several endpoints that serve
the same bucket name and data-file path with different bytes (data files
registered under chosen names or replaced in place; Lance's writers use
random file names) share entries, also across a BE restart; such a
deployment should set `enable_lance_data_cache = false`. Both are as on
branch-4.1 / 4.2 and tracked as follow-ups.

A Lance scan's native working set, that is the I/O buffered up to
`lance_io_buffer_size_bytes` per scanner, the decode readahead and the
operators of a search plan such as the top-k of a flat vector search, is
allocated inside lance-c and is charged to neither the query nor its
workload group. It counts toward the BE process memory only, so memory
GC, which ranks queries by tracked memory, does not attribute it to the
Lance query. lance-c accepts no memory budget, so on BEs with little
memory headroom size `lance_io_buffer_size_bytes`,
`lance_batch_readahead` and `lance_fragment_readahead` for the number of
concurrent Lance scanners. As on branch-4.1 / 4.2; tracked as a
follow-up.
- Build: macOS links `-framework IOKit`. This is the first time objects
of `liblance_c.a` are pulled into `doris_be`, so the binary and the link
time grow. With this PR `util/rle_encoding.h` reaches 231 TUs and
`util/threadpool.h` 589, the same as master `315c2e00331` alone (limits
275 and 660 since #69717), and `gen_cpp/internal_service.pb.h` 562
(master alone 559, limit 660); the reach budgets are unchanged.

Not included: the Lance index metadata scanner (`LANCE_INDEX_ENTRIES`);
the FE side, including the rolling-upgrade guard that keeps Lance lazy
materialization off while an old BE could decode a version 1 row
location as version 0; the regression suites; the rest of #68707
(searching historical versions, tags and branches, rejecting fragment
ids missing from the snapshot, naming the snapshot in errors and the
profile). The `InitReaderAvgTime` / `GetBlockAvgTime` profile fix that
#67005 carried goes in a separate PR.

**Results.**

| | master | with this PR |
|---|---|---|
| A `FORMAT_LANCE` query | fails before reading anything | read by
`LanceTableReader` in FileScannerV2 |
| A Lance row in the second phase of TopN lazy materialization | cannot
be represented | a version 1 row location, read back with `take_rows` |
| A row location of an unknown version or size in phase two | decoded as
version 0 | rejected with an error |
| An Arrow DATE32 day outside 0000-01-01..9999-12-31 (ADBC, Arrow stream
load, paimon-rust, Lance) | read as a non-null `0000-00-00` | the read
fails with `InvalidArgument` |

BE unit tests (ASAN, macOS arm64, against master's pinned lance-c), on
the head rebased onto master `378404eeb12`: 1029 cases in 54 suites run,
1027 pass and 2 Parquet microbenchmarks skip by design. They cover the
Lance suites (107: `LanceTableReader*` 83, `LanceNestedNullTest` 10,
`LanceRuntimeFilterHelperTest` 10, `LanceSessionManagerTest` 4); the
row-location protocol (`MaterializationSharedStateTest`,
`RowIdStorageReaderTest`, and the new `RowIdFetchRequestValidationTest`:
count mismatch, unknown version, a Lance version on an internal mapping,
an internal row id of 2^32); `FileScannerV2Test` (28); the ORC and
Parquet reader suites (`NewOrcReaderTest`, `NewParquetReaderTest`,
`ParquetScanTest` and the other `Parquet*` suites), whose row ids now go
through `file_local`; and the Arrow / protobuf / DATE serde suites with
the new `ArrowParentNullsTest`, plus the Iceberg and Parquet reader
suites that decode Arrow dates. Reverting any of the review fixes, the
#68813 flag, the #68707 split rule or the #68815 bounds makes its new
tests fail. The sixth review round adds 13 cases (`ArrowParentNullsTest`
10, `LanceTableReaderTypeTest` 3) and extends one; with them the Arrow
SerDe, Lance, ADBC and `IcebergV2ReaderTest` suites run 317 cases: 310
pass and the 7 ADBC SQLite cases skip without a local SQLite driver. The
ninth review round adds
`LanceRuntimeFilterHelperTest.KeepsInt64MinFiltersResidualOnly` and
`LanceTableReaderFilterTest.KeepsInt64MinRuntimeFiltersOutOfLanceScanner`:
both pass with the 106 Lance cases (`Lance*`) on the branch rebased onto
`339e087b11f`, and restoring `parse_int64()` in either check makes
exactly these two fail. On the head rebased onto master `315c2e00331`,
with the per-block mask fix (`42cf4e3e5d1`), 328 cases in 24 suites
pass: the Arrow SerDe and validation suites, `ArrowParentNullsTest`
(15), `LoadArrowPipeTest` (47), the DATETIMEV2 / TIMESTAMPTZ SerDe
suites, `ArrowColumnToDorisColumnTest`, `ArrowBlockConvertorTest`,
`AdbcReaderTest`, `RemoteDorisV2ReaderTest`, `PaimonRustTableReaderTest`
and the Lance suites. Allocating at the first NULL row of each call
again fails the new `ArrowParentNullsTest` case and the four new
`LoadArrowPipeTest` block cases, dropping the per-batch mask fails the
LIST and STRUCT block cases, and returning the child unmasked fails the
new timestamp case, four `ArrowParentNullsTest` cases and
`LanceTableReaderTypeTest.IgnoresDate32PayloadHiddenByNullParents`.

### Release note

Add nine `lance_*` BE configs and `lance_session_*` BE metrics. They
take effect once the FE can plan Lance scans. Reading an Arrow DATE32
value outside 0000-01-01..9999-12-31 through ADBC, Arrow stream load or
paimon-rust now fails instead of returning 0000-00-00.

### Check List (For Author)

- Test <!-- At least one of them must be included. -->
    - [ ] Regression test
    - [x] Unit Test
- `run-be-ut.sh --run --filter=...` with the suites listed under
Results: 420 of 420 pass on macOS arm64 (ASAN) on the content before the
rebases, and the Lance and Arrow SerDe suites pass again with the
nested-offset, zero-field struct, hidden-child, hidden-Decimal256 and
compaction-mask fixes (the ADBC SQLite cases skip without a local SQLite
driver). Rebased onto master `43f9ceb48d7`: #69668 conflicted in the
scanner selection and in `fetch_table_schema`, which now hands every
file format to `format::fetch_file_schema()`, so the Lance branch runs
ahead of that call; #69646 added a TopN row-group test that reads the
version 0 row location fields, which now go through `file_local` (the
earlier rebase onto `5caaeb3e3d4` resolved #69614 and #69601 in the
scanner selection, the `FileFormat` enum, its test and the reach budget
table). The 95 TUs that this PR changes or that master changed since
`5caaeb3e3d4` were syntax-checked against the rebased tree with the UT
build flags, and CI runs the suites again. Rebased again onto master
`8d88f1cc6f7`: the only conflict was the budget table, where #65858
moved the `util/rle_encoding.h` baseline to 828, and the 163 TUs that
this PR changes or that master changed since `43f9ceb48d7` pass the same
syntax check. Rebased again onto master `339e087b11f`: the only conflict
was the budget table again, where #67292 moved the `util/rle_encoding.h`
baseline to 834; #69872 added Arrow type checks ahead of the NULL-parent
masking in the map and struct readers and merged cleanly. Of the 114
sources that this PR changes or that master changed since `8d88f1cc6f7`,
111 pass the same syntax check. The other three are master's: two new
#67292 files under `storage/segment` fail the same way on master alone,
since they compile only inside their unity batch, and this PR adds no
source to that target; the #69818 benchmark is not in the UT build and
includes only `util/simd` headers that this PR does not touch. Reverting
any of the review fixes, the #68813 flag, the #68707 split rule or the
#68815 bounds makes its new tests fail. Rebased again onto master
`378404eeb12`, which removed the legacy file scanner (#69723): the
conflicts were in the scanner-selection code and tests that #69723
deleted, so this PR's Lance lines there went with them (including its
`LanceQueriesAlwaysUseScannerV2` test), in the profile keys of
`materialization_opertor.cpp`, and in the row fetch of
`rowid_fetcher.cpp`, whose non-Lance path is now only
`FileScannerV2::read_by_rows`; the deleted V1 ORC / Parquet row id tests
took this PR's `file_local` rename with them, and the
`util/rle_encoding.h` budget commit is dropped because master is no
longer near that limit. Of the 100 sources that this PR changes or that
master changed since `339e087b11f`, 99 pass the same syntax check; the
other, `format_v2/orc/orc_reader.cpp`, fails the same way on master
alone. On the rebased head the suites listed under Results run 1029
cases: 1027 pass and 2 microbenchmarks skip. The test-utility fix that
`DataTypeDateTimeV2SerDeTest` needed on macOS (#69695) is now on master.
Rebased again onto master `315c2e00331`: the only conflict was
`TExternalSearchRequest` in `PlanNodes.thrift`, where #70075 had
reserved fields 2-5 in comments; the two commits that add the fields
replace those comments, and field 6 stays #70075's `paimon_options`.
#69717 cut transitive includes from shared headers: of the 160 sources
that this PR changes or that master changed since `378404eeb12`, 156
pass the same syntax check; the two `jemalloc_hook` tests have no
compile command on macOS, `runtime/memory/jemalloc_hook.cpp` uses glibc
aliases that macOS cannot build (#69819), and
`format_v2/table_reader.cpp` alone fails the same way on master alone
while its unity batch compiles. The full `doris_be_test` builds and
links on the rebased head; `ArrowParentNulls*` and `Lance*` (121 cases)
pass before the mask fix, and the 328 cases above with it.
- `build-support/check-build-hygiene.sh` (on this branch rebased onto
master `315c2e00331`), clang-format 16 and
`build-support/run-clang-tidy.sh` on the changed files: all pass; on the
changed lines clang-tidy (llvm 22) reports only
`readability-inconsistent-ifelse-braces` on the `DORIS_CHECK` lines of
the masking functions, the macro's expansion that master's existing
`DORIS_CHECK`s report too.
    - [ ] Manual test (add detailed scripts or steps below)
    - [ ] No need to test or manual test. Explain why:
- Behavior changed:
    - [ ] No.
- [x] Yes. `PRequestBlockDesc.row_id` is `uint64`; phase two of TopN
lazy materialization rejects row locations of an unknown version or
size; `FORMAT_LANCE` scans run on FileScannerV2; an Arrow DATE32 day
outside the Doris DATE range fails the read instead of becoming
`0000-00-00`.
- Does this need documentation?
    - [x] No. Lance is documented with the FE connector.
- [ ] Yes. <!-- Add document PR link here. eg:
apache/doris-website#1214 -->

### Check List (For Reviewer who merge this PR)

- [ ] Confirm the release note
- [ ] Confirm test cases
- [ ] Confirm document
- [ ] Add branch pick label <!-- Add branch pick label that this PR
should merge into -->

---------

Co-authored-by: zhangstar333 <[email protected]>
Co-authored-by: Gabriel <[email protected]>
Co-authored-by: zhaobo wang <[email protected]>
Co-authored-by: jay <[email protected]>
Co-authored-by: FANNG <[email protected]>
Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
Co-authored-by: zy-kkk <[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.

4 participants