Skip to content

branch-4.0: [opt](csv reader) optimize nullable string deserialization in CSV/text load hot path #64476#65844

Open
liaoxin01 wants to merge 1 commit into
apache:branch-4.0from
liaoxin01:codex/pick-64476-to-branch-4.0
Open

branch-4.0: [opt](csv reader) optimize nullable string deserialization in CSV/text load hot path #64476#65844
liaoxin01 wants to merge 1 commit into
apache:branch-4.0from
liaoxin01:codex/pick-64476-to-branch-4.0

Conversation

@liaoxin01

Copy link
Copy Markdown
Contributor

Pick #64476

…t load hot path (apache#64476)

Issue Number: close #xxx

Related PR: apache#60920 (previous attempt, superseded by this stateless
implementation)

Problem Summary:

When loading CSV data, every column is read as a nullable string, so
`_deserialize_nullable_string` is the per-row per-column hot path
(ClickBench: 105 columns x 100M rows = ~10.5 billion cells). Flame graph
shows two major per-cell overheads:

1. `assert_cast<ColumnNullable&>` performs a typeid comparison per cell
in release builds.
2. `DataTypeStringSerDe::deserialize_one_cell_from_csv` adds a call
layer with another per-cell `assert_cast<ColumnString&>` inside, plus
Status plumbing. Its fill-null-on-failure branch is dead code since the
method never fails.

1. Use `assert_cast<..., TypeCheckOnRelease::DISABLE>` in
`CsvReader::_deserialize_nullable_string` and
`TextReader::_deserialize_nullable_string`, which compiles to a plain
`static_cast` in release builds. Debug builds still verify the cast.
2. Write the string column and null map directly instead of going
through the SerDe layer (semantically identical, verified against
`ColumnNullable::insert_data` / `DataTypeStringSerDe` implementations).
The virtual `_deserialize_nullable_string` dispatch is kept, so
TextReader's hive-text semantics (different escape handling and null
detection) remain intact.
3. Add `_reserve_nullable_string_columns`, called once per batch: it
performs checked `assert_cast`s (backing the unchecked per-row casts
with a real type validation per batch, throwing instead of UB on
mismatch) and reserves offsets/null_map capacity to avoid incremental
PODArray growth in the row loop.

The implementation is stateless: no cached column pointers, no per-batch
member state to initialize/clear.

A/B test on full ClickBench dataset (73GB / 100M rows / 105 columns),
identical deployment and config, only the BE binary differs:

| Metric | Before | After | Improvement |
|---|---|---|---|
| Total load time (BE LoadTime) | 636.6s | 530.9s | -16.6% (1.20x) |
| CSV parse (ReadDataTime) | 590.6s | 484.5s | -18.0% |
| Avg throughput | 115 MB/s | 138 MB/s | +20% |

All 10 splits (10M rows each) improved consistently by 14-18% with small
variance. Loaded row counts are identical between the two runs
(99,997,497 rows).
Copilot AI review requested due to automatic review settings July 21, 2026 07:31
@liaoxin01
liaoxin01 requested a review from morningman as a code owner July 21, 2026 07:31
@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?

Copilot AI 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.

Pull request overview

Optimizes the CSV and Hive-text load “nullable string” deserialization hot path by bypassing the DataTypeStringSerDe call layer and per-cell checked casts, while preserving the same escaping semantics and reserving per-batch capacity to reduce reallocation churn.

Changes:

  • Inlines DataTypeStringSerDe::deserialize_one_cell_from_csv/hive_text logic (escape + insert_data) into the hot-path nullable-string deserializers.
  • Adds a per-batch _reserve_nullable_string_columns(...) step to do one-time checked casts and reserve offsets/null-map capacity.
  • Switches per-row casts in hot paths to TypeCheckOnRelease::DISABLE after the per-batch verification.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
be/src/vec/exec/format/text/text_reader.cpp Inlines hive-text nullable string deserialization to avoid SerDe overhead and per-cell checked casts.
be/src/vec/exec/format/csv/csv_reader.h Declares _reserve_nullable_string_columns helper for per-batch verification/reservation.
be/src/vec/exec/format/csv/csv_reader.cpp Adds per-batch reservation + inlines CSV nullable string deserialization to reduce overhead in the per-row/per-column loop.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@liaoxin01

Copy link
Copy Markdown
Contributor Author

run buildall

@hello-stephen

Copy link
Copy Markdown
Contributor

BE Regression && UT Coverage Report

Increment line coverage 85.29% (29/34) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 71.53% (25592/35776)
Line Coverage 54.43% (271539/498913)
Region Coverage 51.81% (224293/432937)
Branch Coverage 53.35% (96806/181452)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants