Skip to content

[fix](be) Correct the external row fetch InitReaderAvgTime and GetBlockAvgTime - #69583

Open
morningman wants to merge 1 commit into
apache:masterfrom
morningman:b2m-t37b-external-fetch-avg-profile
Open

morningman wants to merge 1 commit into
apache:masterfrom
morningman:b2m-t37b-external-fetch-avg-profile

Conversation

@morningman

Copy link
Copy Markdown
Contributor

What problem does this PR solve?

Issue Number: None

Related PR: #67005

Problem Summary:

In short. This ports the profile fix that branch-4.1/4.2 made in #67005 to master: the second phase of TopN lazy materialization on external tables reported a wrong InitReaderAvgTime and put the init time into GetBlockAvgTime. It also stops one request block's averages from leaking into the next one of the same RPC, which the 4.x fix still does.

Original author: @zhangstar333

Background. When a TopN query lazily materializes an external table, the second phase sends the row IDs to the backends that own the rows. On each backend, RowIdStorageReader::read_batch_external_row reads one request block (one table) by grouping its row IDs per scan range and reading each range with its own reader. It then reports two values per request block: InitReaderAvgTime and GetBlockAvgTime, the mean reader-init and block-read time over those ranges. MaterializationOperator lists them per backend in the query profile, one value per RPC response block.

The problem, and what it cost. Both values are wrong on master:

  • InitReaderAvgTime assigns each range's init time instead of adding it, so it is the last range's init time divided by the number of ranges.
  • GetBlockAvgTime is filled with the init-time average, so the block-read time never reaches the profile.
  • The averages are accumulated in variables that read_by_rowids() zeroes once per RPC and passes to every request block. A query that lazily materializes two external tables sends two request blocks in one RPC, and the second block's sums start from the first block's averages.

So the profile of a slow lazily materialized external read cannot tell whether opening the readers or reading the rows took the time. Only the profile and one log line are affected; the fetched rows are not. #67005 (a Lance profile PR in 4.x) fixed the first two points; branch-4.1 and branch-4.2 still have the third.

How this PR fixes it. The summary moves into RowIdStorageReader::add_external_fetch_avg_time_profile(). It sums both times over the block's ranges starting from zero, overwrites the caller's variables with the averages, and reports each average under its own key. The Query stats log line at the end of read_by_rowids() prints those variables, so it now shows the averages of the RPC's last external request block, the same block whose scan range count it already prints.

Results. InitReaderAvgTime / GetBlockAvgTime for a request block with three ranges that took 10/100, 20/200 and 60/600 ms (init/read), first as the only external block of the RPC, then as the second one after a block that left 50/500 ms in the shared variables:

Only request block Second request block
master 20ms / 20ms 20ms / 20ms
branch-4.1/4.2 after #67005 30ms / 300ms 46ms / 466ms
This PR 30ms / 300ms 30ms / 300ms

Release note

None

Check List (For Author)

  • Test
    • Regression test
    • Unit Test
      • RowIdStorageReaderTest.ExternalFetchProfileAveragesInitAndGetBlockTimes: the three ranges above, with the caller's variables preset to 50/500 ms as a previous request block would leave them, give 30ms and 300ms. It fails with master's assignment (20ms), with GetBlockAvgTime reporting the init average (30ms), and with the 4.x accumulation (46ms / 466ms). The other RowIdStorageReaderTest and SubmitExternalScanTasksTest cases still pass.
    • Manual test (add detailed scripts or steps below)
    • No need to test or manual test. Explain why:
  • Behavior changed:
    • No.
    • Yes.
  • Does this need documentation?
    • No.
    • Yes.

Check List (For Reviewer who merge this PR)

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

…ckAvgTime

### What problem does this PR solve?

Issue Number: None

Related PR: apache#67005

Problem Summary: The second phase of TopN lazy materialization on an
external table (RowIdStorageReader::read_batch_external_row) reads each
scan range of a request block with its own reader and reports two
profile values per request block: InitReaderAvgTime and
GetBlockAvgTime, the mean reader-init and block-read time over those
ranges. MaterializationOperator lists them per backend in the query
profile. Both are wrong on master:

- InitReaderAvgTime assigns each range's init time instead of adding it,
  so it reports the last range's time divided by the number of ranges:
  ranges that took 10, 20 and 60 ms give 20 ms instead of 30 ms.
- GetBlockAvgTime is filled with the init-time average, so the
  block-read time never reaches the profile.
- The averages are accumulated in the caller's variables, which
  read_by_rowids() zeroes once per RPC and passes to every request
  block. When an RPC fetches two lazily materialized external tables,
  the second block's sums start from the first block's averages.

branch-4.1/4.2 fixed the first two in apache#67005 (a Lance profile PR) by
summing the init times and reporting the get-block average, but kept the
shared accumulators, so a second request block reports inflated
averages there: 46ms and 466ms instead of 30ms and 300ms in the unit test
below once the first block left 50 and 500 ms.

The summary now lives in
RowIdStorageReader::add_external_fetch_avg_time_profile(). It sums both
times over the block's ranges from zero, overwrites the caller's
variables with the averages and reports each average under its own
key. The "Query stats" log line at the end of read_by_rowids() therefore
prints the averages of the RPC's last external request block, the same
block whose scan range count it already prints.

### Release note

None

### Check List (For Author)

- Test: Unit Test
    - BE UT RowIdStorageReaderTest.ExternalFetchProfileAveragesInitAndGetBlockTimes:
      three ranges (10/100, 20/200, 60/600 ms) with the caller's
      variables preset to 50/500 ms by a previous request block give
      30ms and 300ms.
- Behavior changed: No
- Does this need documentation: No

Co-authored-by: zhangstar333 <zhangsida@selectdb.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@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?

@morningman

Copy link
Copy Markdown
Contributor Author

run buildall

@morningman

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.

No actionable new findings in the three-file PR at ec36f8f. The external row-fetch profile now reports independent reader-init and block-read means for each request block, without carrying an earlier block's values into the next. No existing inline findings require carry-forward, and no additional user focus was supplied.

Critical checkpoint conclusions:

  1. Goal and proof: fresh sums, overwritten outputs and distinct profile values address all three reported defects. The added three-range unit case checks 30ms/300ms from preset 50ms/500ms outputs; assessed statically.
  2. Scope and clarity: a small private helper, a forward declaration and a focused unit test; comments explain the averaging and overwrite semantics with examples.
  3. Concurrency and locks: scanner workers write distinct pre-sized statistics entries; the completion mutex/CV publishes their writes before aggregation. Error statuses bypass profiling. No new lock or blocking operation is introduced.
  4. Lifecycle and static initialization: the helper borrows live per-call objects after worker completion and retains nothing; no new owner, cycle, callback or global initialization.
  5. Configuration: no configuration changes.
  6. Compatibility: existing profile keys, millisecond representation and wire/storage formats remain compatible. No new libc API, library or toolchain requirement is introduced; no artifact ABI audit was performed.
  7. Thrift/protobuf definitions: no changes.
  8. Parallel paths: both FileScanner and FileScannerV2 populate the common millisecond statistics consumed by this helper; internal-table reading is unaffected.
  9. Preconditions and comments: the nonempty assertion follows from the request's nonempty row-ID gate and unconditional range grouping; missing mappings return an error before profiling.
  10. Test coverage: the deterministic unit case covers multiple ranges, distinct init/read values, prior-block output contamination and both profile strings. Scanner/RPC integration and scheduler cancellation stress were inspected in source, not executed.
  11. Test expectations and build integration: numeric/string expectations are correct; existing BE-UT flags permit private access and its CMake glob includes the test. No expected-result files changed; the test includes no source .cpp requiring unity exclusion.
  12. Observability: both repaired values reach each response block's protobuf profile and materialization consumer under the intended keys. The RPC log consistently shows its last external block's averages and range count.
  13. Transactions and persistence: no commit, visibility-version, delete-bitmap, edit-log or persistence behavior changes.
  14. Data writes and atomicity: no writes or result-scatter changes; the patch changes post-read profiling.
  15. FE-BE variables: no new transmitted variables or sending paths.
  16. Performance and memory: one linear pass over existing range statistics, constant auxiliary space and no added per-range allocation or I/O; no actionable regression found.
  17. Other checks: header hygiene, exception/status propagation, nullable/COW scope and profile serialization were reviewed without an additional issue.

Review scope and convergence: all changed lines and relevant upstream/downstream paths received main-agent and complete-reviewer static coverage, with a separate risk review for range cardinality and worker publication. The unchanged scheduler shutdown behavior was considered and yielded no defect introduced by this patch; this is not a full scheduler teardown audit. No builds, tests, runtime stress runs or artifact checks were executed under the review-only contract. No unresolved PR candidates or static coverage gaps remain. Converged after 2 rounds, including final confirmation of this frozen payload.

Review converged after 2 round(s).

@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	17618	5167	5086	5086
q2	2168	305	251	251
q3	10241	1354	813	813
q4	4675	458	336	336
q5	7444	808	531	531
q6	182	186	160	160
q7	753	805	611	611
q8	9252	1300	1028	1028
q9	5614	4222	4225	4222
q10	6850	1297	1027	1027
q11	423	248	237	237
q12	642	397	278	278
q13	18107	3555	2996	2996
q14	271	269	246	246
q15	q16	728	713	650	650
q17	1192	739	580	580
q18	7042	6210	6131	6131
q19	1099	957	605	605
q20	392	329	222	222
q21	5056	2125	2283	2125
q22	342	291	249	249
Total cold run time: 100091 ms
Total hot run time: 28384 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	5600	5603	5614	5603
q2	665	552	549	549
q3	5222	5332	4926	4926
q4	2008	2139	1549	1549
q5	5314	5160	5609	5160
q6	253	208	158	158
q7	2363	2025	1811	1811
q8	2990	2684	2735	2684
q9	8070	7965	7975	7965
q10	3752	3701	3259	3259
q11	758	440	423	423
q12	672	706	527	527
q13	3472	3894	3223	3223
q14	310	306	290	290
q15	q16	742	721	634	634
q17	8361	7485	6862	6862
q18	13022	12159	12907	12159
q19	914	836	819	819
q20	2133	2158	1920	1920
q21	5699	4628	4732	4628
q22	498	441	394	394
Total cold run time: 72818 ms
Total hot run time: 65543 ms

@hello-stephen

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

query5	4312	594	443	443
query6	423	223	192	192
query7	4818	442	225	225
query8	312	169	152	152
query9	9553	3114	3112	3112
query10	467	305	265	265
query11	5881	3845	3581	3581
query12	147	91	85	85
query13	1262	451	326	326
query14	6142	3945	3603	3603
query14_1	3424	3402	3367	3367
query15	211	199	182	182
query16	947	429	412	412
query17	857	668	512	512
query18	2407	419	293	293
query19	196	161	127	127
query20	82	81	89	81
query21	207	131	103	103
query22	12879	12959	12798	12798
query23	13580	12973	12565	12565
query23_1	12683	12536	12571	12536
query24	7361	805	448	448
query24_1	465	463	459	459
query25	509	391	331	331
query26	1257	255	135	135
query27	2784	435	259	259
query28	4657	1912	1920	1912
query29	1526	581	430	430
query30	297	216	185	185
query31	997	925	821	821
query32	138	91	88	88
query33	533	292	229	229
query34	910	847	491	491
query35	725	768	630	630
query36	816	803	718	718
query37	128	98	74	74
query38	1789	1743	1647	1647
query39	793	800	732	732
query39_1	728	719	742	719
query40	217	112	95	95
query41	72	69	70	69
query42	87	84	85	84
query43	342	356	313	313
query44	1311	678	687	678
query45	185	179	166	166
query46	835	964	564	564
query47	2779	2875	2700	2700
query48	293	325	216	216
query49	588	408	303	303
query50	674	275	206	206
query51	10210	10554	10321	10321
query52	82	78	70	70
query53	186	210	152	152
query54	252	225	193	193
query55	73	73	63	63
query56	234	234	219	219
query57	1666	1631	1593	1593
query58	288	273	262	262
query59	2083	2100	1886	1886
query60	297	255	240	240
query61	173	181	206	181
query62	383	323	306	306
query63	191	159	150	150
query64	2749	917	785	785
query65	3315	3271	3273	3271
query66	1760	416	301	301
query67	20117	20285	20116	20116
query68	2932	998	599	599
query69	390	285	243	243
query70	847	786	806	786
query71	293	229	212	212
query72	2701	2622	2270	2270
query73	522	522	293	293
query74	4624	4530	4327	4327
query75	2335	2291	1918	1918
query76	2309	1006	579	579
query77	340	357	280	280
query78	9625	9677	9169	9169
query79	874	847	515	515
query80	520	403	341	341
query81	530	326	272	272
query82	200	127	102	102
query83	294	194	181	181
query84	301	122	105	105
query85	826	465	368	368
query86	318	233	224	224
query87	1982	1952	1838	1838
query88	3600	2648	2625	2625
query89	312	282	236	236
query90	1828	164	162	162
query91	164	149	130	130
query92	98	91	87	87
query93	972	990	569	569
query94	452	319	277	277
query95	580	398	292	292
query96	657	488	228	228
query97	2458	2424	2343	2343
query98	156	151	147	147
query99	778	757	640	640
Total cold run time: 234109 ms
Total hot run time: 150610 ms

@hello-stephen

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

query1	0.01	0.01	0.00
query2	0.09	0.05	0.05
query3	0.25	0.09	0.08
query4	1.61	0.11	0.10
query5	0.23	0.24	0.24
query6	1.18	0.69	0.66
query7	0.04	0.01	0.01
query8	0.05	0.04	0.04
query9	0.39	0.35	0.36
query10	0.60	0.61	0.57
query11	0.20	0.15	0.15
query12	0.18	0.15	0.14
query13	0.48	0.50	0.49
query14	1.03	1.01	1.02
query15	0.63	0.63	0.61
query16	0.35	0.34	0.35
query17	1.15	1.08	1.15
query18	0.20	0.19	0.19
query19	2.08	1.94	2.02
query20	0.02	0.01	0.01
query21	15.48	0.19	0.14
query22	4.73	0.05	0.05
query23	16.27	0.32	0.14
query24	2.18	1.56	0.35
query25	0.13	0.08	0.05
query26	1.47	0.22	0.14
query27	0.05	0.04	0.06
query28	3.71	0.83	0.36
query29	12.48	4.17	3.31
query30	0.28	0.13	0.11
query31	2.78	0.54	0.32
query32	3.22	0.61	0.50
query33	3.13	3.23	3.29
query34	15.46	4.00	3.41
query35	3.36	3.32	3.32
query36	0.55	0.47	0.43
query37	0.10	0.06	0.06
query38	0.05	0.04	0.04
query39	0.04	0.03	0.03
query40	0.17	0.14	0.14
query41	0.09	0.03	0.03
query42	0.04	0.02	0.02
query43	0.04	0.04	0.04
Total cold run time: 96.58 s
Total hot run time: 24.32 s

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.

2 participants