Skip to content

[perf](csv reader) Reduce V2 CSV parsing overhead - #69875

Merged
Gabriel39 merged 2 commits into
apache:masterfrom
sollhui:improvement/v2-csv-cpu-hotpaths
Oct 30, 2026
Merged

Gabriel39 merged 2 commits into
apache:masterfrom
sollhui:improvement/v2-csv-cpu-hotpaths

Conversation

@sollhui

@sollhui sollhui commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

What problem does this PR solve?

Issue Number: None

Related PR: #69716

Problem Summary:

After restoring sequential prefetch, large V2 CSV loads still spend substantial CPU time in row parsing and string materialization. The reader compares a general separator at every byte, updates atomic profile counters per cell, starts three stage timers per row, and repeats checked string-column casts per cell. It also ignores the row cap supplied by TableReader.

This change combines four optimizations on master:

  • Compare the separator byte directly for plain single-character separators; retain enclosed, multi-character, Hive/OpenCSV and PROTO parsing paths.
  • Accumulate exact work counters locally and publish at get_block/count/schema return, including errors. Sample stage timings every 1024 physical read_line calls, starting with the first, and expose unscaled SampledReadLineTime, SampledSplitLineTime and SampledDeserializeTime with sample counts. DelimitedTextReader remains full elapsed time; split-prefix alignment has its own full timer.
  • Validate nullable string column types and reserve offsets/null maps once per mutable batch, then insert strings directly with the existing NULL, quoting and escape semantics.
  • Honor FileReader::set_batch_size, retaining the existing byte budget and runtime default when no explicit cap is supplied. The caller still controls the maximum batch size; this does not force 16352-row blocks.

Shared counters and batch sizing also apply to the Hive text reader.

Performance benchmark

On TPC-DS SF100, elapsed time decreased from 1,944 seconds to 1,375 seconds:

Benchmark Before After Time reduction Speedup
TPC-DS SF100 1,944 s 1,375 s 29.3% (569 s) 1.41×

Release note

Reduce V2 CSV parsing overhead and honor caller-provided batch sizes. Delimited-text per-stage profile timers are now explicitly named Sampled* and contain unscaled sampled durations; use the full DelimitedTextReader timer for elapsed-time comparisons.

Check List (For Author)

  • Test
    • Unit Test: added cases for dynamic/zero batch caps, byte limits, filtered blocks, cross-batch sampling and exact counters, single/multi-character separators, NULL and escape behavior, missing fields, reordered projection and copy-on-write. Extended error, rejected-row and COUNT counter coverage.
    • Execution: run-be-ut.sh was attempted with CsvV2ReaderTest., TextV2ReaderTest. and LegacyAndV2/PlainCsvSplitTest.*. It exits before C++ compilation because the local JDK 17 fails to initialize (Failed setting boot class path), including outside the sandbox. Full build/UT remain pending. Performance benchmark results are reported above.
    • Passed: repository clang-format 16 formatting/check scripts, build-support/check-build-hygiene.sh and git diff --check.
  • Behavior changed: Yes. Existing batch-size contract is honored, and detailed stage timers are explicitly sampled. CSV value semantics are unchanged.
  • Does this need documentation: No. The profile includes a timing explanation; no new configuration or SQL syntax.

For further benchmark comparisons, use identical hardware, input, compression, cache conditions and load batch/byte-budget settings for the parent and this commit. Compare total load time, Scanner CPU time, DelimitedTextReader time and RowsRead/BlockNumber. Do not directly compare Sampled* timers with the previous full stage timers.

### What problem does this PR solve?

Issue Number: None

Related PR: apache#69716

Problem Summary:

After restoring sequential prefetch, large V2 CSV loads still spend substantial CPU time in row parsing and string materialization. The reader compares a general separator at every byte, updates atomic profile counters per cell, starts three stage timers per row, and repeats checked string-column casts per cell. It also ignores the row cap supplied by TableReader.

This change combines four optimizations on master:
- Compare the separator byte directly for plain single-character separators; retain enclosed, multi-character, Hive/OpenCSV and PROTO parsing paths.
- Accumulate exact work counters locally and publish at get_block/count/schema return, including errors. Sample stage timings every 1024 physical read_line calls, starting with the first, and expose unscaled SampledReadLineTime, SampledSplitLineTime and SampledDeserializeTime with sample counts. DelimitedTextReader remains full elapsed time; split-prefix alignment has its own full timer.
- Validate nullable string column types and reserve offsets/null maps once per mutable batch, then insert strings directly with the existing NULL, quoting and escape semantics.
- Honor FileReader::set_batch_size, retaining the existing byte budget and runtime default when no explicit cap is supplied. The caller still controls the maximum batch size; this does not force 16352-row blocks.

Shared counters and batch sizing also apply to the Hive text reader. No speedup is claimed yet; the combined patch is ready for a controlled benchmark against its master parent.

### Release note

Reduce V2 CSV parsing overhead and honor caller-provided batch sizes. Delimited-text per-stage profile timers are now explicitly named Sampled* and contain unscaled sampled durations; use the full DelimitedTextReader timer for elapsed-time comparisons.

### Check List (For Author)

- Test
    - [x] Unit Test: added cases for dynamic/zero batch caps, byte limits, filtered blocks, cross-batch sampling and exact counters, single/multi-character separators, NULL and escape behavior, missing fields, reordered projection and copy-on-write. Extended error, rejected-row and COUNT counter coverage.
    - Execution: run-be-ut.sh was attempted with CsvV2ReaderTest.*, TextV2ReaderTest.* and LegacyAndV2/PlainCsvSplitTest.*. It exits before C++ compilation because the local JDK 17 fails to initialize (Failed setting boot class path), including outside the sandbox. Full build/UT and performance benchmark remain pending.
    - Passed: repository clang-format 16 formatting/check scripts, build-support/check-build-hygiene.sh and git diff --check.
- Behavior changed: Yes. Existing batch-size contract is honored, and detailed stage timers are explicitly sampled. CSV value semantics are unchanged.
- Does this need documentation: No. The profile includes a timing explanation; no new configuration or SQL syntax.

Benchmark: use identical hardware, input, compression, cache conditions and load batch/byte-budget settings for the parent and this commit. Compare total load time, Scanner CPU time, DelimitedTextReader time and RowsRead/BlockNumber. Do not directly compare Sampled* timers with the previous full stage timers.
@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?

@sollhui

sollhui commented Oct 30, 2026

Copy link
Copy Markdown
Contributor Author

run buildall

@sollhui

sollhui commented Oct 30, 2026

Copy link
Copy Markdown
Contributor Author

/review

@sollhui sollhui changed the title [improvement](be) Reduce V2 CSV parsing overhead [perf](csv reader) Reduce V2 CSV parsing overhead Oct 30, 2026

@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 defect found in the supplied diff for head 08c5b57. All six changed files and their upstream/downstream reader contracts were reviewed. No existing inline findings or additional user focus points were present.

Critical checkpoint conclusions:

  • Goal and proof: the implementation removes repeated separator/SerDe/type-check/profile work and honors caller batch caps. Focused BE tests cover the intended behavior; their code and expectations were inspected, not executed.
  • Scope and clarity: changes stay within CSV/shared delimited readers and their tests, using existing FileReader hooks and column APIs.
  • Concurrency and locks: pending counters, sample state and batch caps remain reader-local in serialized scanner calls. Profile publication uses existing atomic counters; prefetch workers do not access the new fields. No new locks or lock-order changes.
  • Lifecycle and memory: column preparation follows recursive Block copy-on-write detachment. Both concrete column types are checked before unchecked cell casts; no view is retained across reads. Reserves use existing tracked column storage. No new static initialization dependency or ownership cycle.
  • Configuration: no new settings; existing row limits, adaptive byte budgets and text attributes remain in force.
  • Compatibility: no on-disk, external text syntax, protocol, dependency or toolchain change. Profile names and timing semantics deliberately change and are documented. No new libc API use was found; ELF compatibility was not measured.
  • Thrift/protobuf: no definitions changed, so field-ID/rolling-upgrade comparisons are inapplicable.
  • Parallel paths: reviewed plain single/multiple-byte separators, enclosed CSV, OpenCSV, PROTO, compressed CSV, Hive Text, generic non-string decoding, COUNT and schema discovery. Dispatch and layer ownership remain intact.
  • Conditions: the separator specialization preserves exact delimiter advancement. NULL/empty/quoted-marker decisions retain their prior order; direct unescaping/insertion and NULL appends match the replaced operations.
  • Test coverage: changing/zero caps, byte stops, filtered empty batches, cross-batch sampling, rejected rows, UTF-8 errors, COUNT, missing/reordered fields and shared-column ownership are covered by inspected tests. Relevant split, quote, compression and empty-projection tests were also inspected.
  • Test results: added GTest expectations agree with the traced paths; no result fixture changed. This review supplies no executed-test result.
  • Observability: exact work counters flush at block/count/schema API exits, including errors. Sampled durations are explicitly unscaled, sample counts are exposed, and full elapsed/filter/split-prefix timing remains available.
  • Persistence and failover: no EditLog, transaction, version-visibility or delete-bitmap changes.
  • Writes and atomicity: changes append in-memory scan columns only. Existing malformed-row rollback and status propagation remain; persistent write/crash atomicity is unaffected.
  • FE/BE propagation: no new transmitted variables or sender changes.
  • Performance: repeated hot-path work is removed and readers persist when caps change. Eager reservation is bounded by caller predictions/runtime limits; no concrete regression was established. Speedup and peak-memory effects require measurement.
  • Other concerns: file-local mapping, residual/delete filtering and table-level casts/defaults retain their existing layer boundaries; no additional verified issue remains.

Validation limits: static review only; no builds, tests, ASAN, benchmarks, formatting/static-analysis tools or ELF audits were run. New tests do not directly add PROTO or nested-subcolumn COW fixtures; their equivalent operations and ownership were checked statically. No quantitative performance claim is made. These dynamic validation limits remain even after static review convergence.

Review converged after 2 rounds. Every assigned reviewer in the final round reported NO_NEW_VALUABLE_FINDINGS, required static coverage is complete, and all candidate/risk checks have evidence-backed dispositions. No unresolved static question remains.

Review converged after 2 round(s).

### What problem does this PR solve?

Issue Number: None

Related PR: apache#69875

Problem Summary: BE UT build 1086461 aborts in StringFastPathPreservesValuesForSingleAndMultiByteSeparators during reader->open(). The new test passes a null RuntimeProfile, while NewPlainTextLineReader registers counters unconditionally. Use the fixture-owned profile, whose lifetime covers every reader, so the separator/NULL/escape/COW assertions can run.

### Release note

None

### Check List (For Author)

- Test: inspected the ASAN stack and constructor contract; repository clang-format 16, format check, build hygiene and git diff --check pass. Attempted the focused run-be-ut.sh test, but local JDK startup still fails before C++ build/test execution. CI rerun remains required.
- Behavior changed: No production behavior change; correct test setup only.
- Does this need documentation: No.
@sollhui

sollhui commented Oct 30, 2026

Copy link
Copy Markdown
Contributor Author

run buildall

@sollhui

sollhui commented Oct 30, 2026

Copy link
Copy Markdown
Contributor Author

/review

@github-actions

Copy link
Copy Markdown
Contributor

Codex automated review failed and did not complete.

Error: Selected model is at capacity. Please try a different model.
Workflow run: https://github.com/apache/doris/actions/runs/41017483176

Please inspect the workflow logs and rerun the review after the underlying issue is resolved.

@hello-stephen

Copy link
Copy Markdown
Contributor

BE UT Coverage Report

Increment line coverage 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 67.42% (32836/48702)
Line Coverage 52.38% (348746/665823)
Region Coverage 47.73% (283289/593508)
Branch Coverage 49.43% (133867/270796)

@hello-stephen

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

------ Round 1 ----------------------------------
============================================
q1	17615	5167	5071	5071
q2	2095	342	247	247
q3	10254	1406	818	818
q4	4680	468	332	332
q5	7445	795	546	546
q6	186	184	152	152
q7	746	789	607	607
q8	9260	1368	1087	1087
q9	5511	4208	4198	4198
q10	6808	1287	1037	1037
q11	440	249	229	229
q12	633	403	287	287
q13	18089	3639	3023	3023
q14	275	264	242	242
q15	q16	724	719	641	641
q17	1220	758	595	595
q18	6967	6164	6148	6148
q19	1250	960	627	627
q20	383	338	225	225
q21	6085	2487	2481	2481
q22	373	325	298	298
Total cold run time: 101039 ms
Total hot run time: 28891 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	5962	5938	6164	5938
q2	728	600	589	589
q3	5653	5797	5370	5370
q4	2029	2143	1566	1566
q5	5758	5823	5611	5611
q6	257	207	149	149
q7	2279	2003	1767	1767
q8	3041	2618	2677	2618
q9	7880	7456	7613	7456
q10	3654	3605	3163	3163
q11	572	413	385	385
q12	669	660	500	500
q13	3329	3617	3070	3070
q14	282	292	271	271
q15	q16	653	677	603	603
q17	7652	7044	7105	7044
q18	12902	12176	12858	12176
q19	934	794	805	794
q20	2125	2145	1913	1913
q21	5609	4630	4715	4630
q22	486	446	390	390
Total cold run time: 72454 ms
Total hot run time: 66003 ms

@hello-stephen

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

query5	4311	582	435	435
query6	430	207	193	193
query7	4820	456	231	231
query8	338	160	150	150
query9	8784	3157	3153	3153
query10	467	308	271	271
query11	6005	3868	3580	3580
query12	139	90	85	85
query13	1246	451	336	336
query14	6111	3911	3652	3652
query14_1	3416	3386	3403	3386
query15	201	193	180	180
query16	931	438	440	438
query17	832	636	519	519
query18	2424	410	293	293
query19	194	158	133	133
query20	83	81	90	81
query21	206	130	105	105
query22	12988	12995	12744	12744
query23	13560	12805	12348	12348
query23_1	12597	12578	12688	12578
query24	7349	813	470	470
query24_1	472	481	488	481
query25	516	394	345	345
query26	1263	251	136	136
query27	2782	436	265	265
query28	4648	2002	2015	2002
query29	1548	590	438	438
query30	300	226	187	187
query31	1007	947	822	822
query32	158	93	94	93
query33	539	311	237	237
query34	896	859	512	512
query35	732	760	665	665
query36	849	826	732	732
query37	128	104	82	82
query38	1786	1752	1654	1654
query39	801	793	751	751
query39_1	726	724	732	724
query40	224	139	102	102
query41	73	69	70	69
query42	86	81	85	81
query43	343	364	312	312
query44	1329	707	718	707
query45	183	187	168	168
query46	832	964	577	577
query47	2855	2951	2806	2806
query48	311	318	227	227
query49	590	404	311	311
query50	697	275	220	220
query51	10622	10385	10561	10385
query52	92	84	73	73
query53	202	212	155	155
query54	261	206	193	193
query55	83	68	64	64
query56	262	227	229	227
query57	1660	1606	1560	1560
query58	301	274	265	265
query59	2180	2161	2044	2044
query60	312	257	240	240
query61	178	170	168	168
query62	390	337	286	286
query63	192	165	165	165
query64	2896	1032	737	737
query65	3326	3301	3246	3246
query66	1780	420	307	307
query67	20195	20209	19945	19945
query68	2978	988	614	614
query69	367	303	240	240
query70	858	779	772	772
query71	299	227	216	216
query72	2708	2816	2335	2335
query73	511	520	289	289
query74	4597	4525	4312	4312
query75	2303	2271	1912	1912
query76	2312	1008	593	593
query77	348	373	274	274
query78	9771	9797	9217	9217
query79	995	915	541	541
query80	1281	433	350	350
query81	584	312	279	279
query82	661	137	110	110
query83	293	197	176	176
query84	315	132	105	105
query85	1021	450	371	371
query86	368	239	232	232
query87	1990	1922	1783	1783
query88	3808	2810	2790	2790
query89	333	289	239	239
query90	1752	177	182	177
query91	168	154	134	134
query92	106	93	89	89
query93	1000	984	586	586
query94	572	306	278	278
query95	574	352	367	352
query96	690	493	237	237
query97	2436	2426	2317	2317
query98	167	157	154	154
query99	772	771	641	641
Total cold run time: 235758 ms
Total hot run time: 151171 ms

@hello-stephen

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

query1	0.01	0.00	0.00
query2	0.09	0.05	0.05
query3	0.26	0.09	0.09
query4	1.61	0.11	0.12
query5	0.23	0.25	0.24
query6	1.16	0.68	0.69
query7	0.04	0.01	0.01
query8	0.05	0.04	0.03
query9	0.40	0.34	0.36
query10	0.58	0.56	0.59
query11	0.21	0.14	0.13
query12	0.18	0.15	0.14
query13	0.49	0.49	0.49
query14	1.04	1.01	1.02
query15	0.62	0.62	0.61
query16	0.35	0.36	0.35
query17	1.10	1.10	1.16
query18	0.21	0.21	0.20
query19	2.05	2.00	1.96
query20	0.02	0.01	0.02
query21	15.46	0.20	0.13
query22	4.82	0.04	0.05
query23	16.28	0.32	0.13
query24	2.52	0.50	0.42
query25	0.08	0.05	0.07
query26	1.38	0.20	0.14
query27	0.06	0.05	0.03
query28	4.16	0.81	0.37
query29	12.50	4.02	3.19
query30	0.28	0.13	0.12
query31	2.78	0.57	0.32
query32	3.22	0.60	0.50
query33	3.15	3.25	3.19
query34	15.38	4.02	3.39
query35	3.37	3.36	3.35
query36	0.54	0.45	0.43
query37	0.09	0.07	0.06
query38	0.05	0.04	0.04
query39	0.03	0.02	0.02
query40	0.17	0.15	0.14
query41	0.08	0.03	0.02
query42	0.04	0.03	0.02
query43	0.04	0.04	0.03
Total cold run time: 97.18 s
Total hot run time: 24.24 s

@hello-stephen

Copy link
Copy Markdown
Contributor

BE Regression && UT Coverage Report

Increment line coverage 100% (0/0) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 77.50% (36575/47196)
Line Coverage 62.82% (413833/658778)
Region Coverage 59.11% (349552/591347)
Branch Coverage 60.09% (162318/270136)

@Gabriel39
Gabriel39 merged commit 374d87d into apache:master Oct 30, 2026
42 of 43 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

PR approved by anyone and no changes requested.

@github-actions github-actions Bot added the approved Indicates a PR has been approved by one committer. label Oct 30, 2026
@github-actions

Copy link
Copy Markdown
Contributor

PR approved by at least one committer and no changes requested.

yiguolei pushed a commit to sollhui/doris that referenced this pull request Nov 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by one committer. reviewed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants