Skip to content

Parquet v3 read concurrency - #2235

Open
UnamedRus wants to merge 5 commits into
antalya-26.6from
parquet-v3-read-concurrency
Open

Parquet v3 read concurrency#2235
UnamedRus wants to merge 5 commits into
antalya-26.6from
parquet-v3-read-concurrency

Conversation

@UnamedRus

Copy link
Copy Markdown
Collaborator

Number of changes to bring parquet v3 reader perf closer to arrow based

Changelog category (leave one):

  • Performance Improvement

Changelog entry (a user-readable short description of the changes that goes into CHANGELOG.md):

@github-actions

github-actions Bot commented Aug 18, 2026

Copy link
Copy Markdown

Workflow [PR], commit [89def51]

Budget memory and threads separately per read stage instead of one shared
fraction, and add a ColumnDataPrefetch stage that issues the compressed
data-page reads (charged to its own memory budget) while ColumnData only
decodes. The old single 0.2 fraction capped the data stage at 0.2 of both
memory and threads, so only ~2 row groups were read/decoded ahead and the S3
link sat idle on latency-bound, high-RTT reads. Now compressed reads run deep
(cheap per row group) while decoded row groups stay bounded, hiding per-GET
latency. Also reconcile the decoded-memory charge up to the actual footprint
inside decodePrimitiveColumn, before formOutputColumn moves the column, so the
honest cap actually bounds decode-ahead.

Squashed extraction of 34816a3 + 114640e + f260506 from the
parquet-v3 feature branch onto antalya-26.6.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: UnamedRus <dtitmoav@gmail.com>
@mkmkme

mkmkme commented Aug 19, 2026

Copy link
Copy Markdown
Collaborator

Could you add Changelog entry please?

mkmkme
mkmkme previously approved these changes Aug 19, 2026

@mkmkme mkmkme left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Looks good from the brief look

@UnamedRus

Copy link
Copy Markdown
Collaborator Author

Will do (and benchmark justification), but i would ask to start reviewing of it tomorrow, i want to do some small changes and remove some slop. :)

/// Budget memory and threads separately: a single 0.2 fraction capped ColumnData at 0.2 of both,
/// so only ~2 row groups were read/decoded ahead. Give ColumnData most of the memory and threads
/// (deep, decode-bound); give the small latency-bound index/bloom reads threads for parallelism.
using S = ReadStage;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Useless alias. Keeps several characters in each line, but adds cognitive complexity.

set_fractions(S::ColumnDataPrefetch, 0.45, 1);
/// Decode: bounded memory (decoded row groups are large) but most threads. Caps resident decoded
/// row groups independently of prefetch depth.
set_fractions(S::ColumnData, 0.30, 3);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Each stage get part of memory/thread budget based on some magic numbers.
I trust that numbers has a ground, but good to add it somewhere.

UnamedRus and others added 2 commits August 21, 2026 16:11
The reader scheduler splits a static memory and thread budget across the
read stages (bloom/index reads, compressed prefetch, decode) using fixed
relative weights. Those weights are the main lever for latency-bound
object-storage reads - the compressed prefetch weight sets the read-ahead
depth, the decode thread weight sets decode concurrency - but they were
hardcoded, so tuning them required a rebuild.

Add `input_format_parquet_read_stage_weights`, an experimental `Map`
setting. Keys are `<stage>.<resource>` (resource is `memory` or `threads`);
values are relative weights, normalized per resource across stages exactly
like the built-in defaults. Only the listed keys override; unspecified
stages keep their defaults, so an empty map (the default) reproduces the
previous behavior bit for bit.

Unknown stage or resource names, negative weights, and a configuration that
zeroes out the whole memory or thread budget all raise `BAD_ARGUMENTS`
rather than silently falling back.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: UnamedRus <dtitmoav@gmail.com>
…atches

`ColumnDataPrefetch` tasks do all their work (`determinePagesToPrefetch` +
`startPrefetch`) synchronously in `scheduleTask`; their `runTask` body is empty
and the reads proceed asynchronously in the Prefetcher's io pool. Their run time
is therefore ~0, but `cost_estimate_bytes` was set to the charged compressed
bytes, which are large.

`scheduleTasksIfNeeded` uses `cost_estimate_bytes` as a proxy for run time to
group tiny tasks into batches. Reporting the large compressed-byte charge made
the batcher split these zero-work tasks across up to `parsing_threads` batches -
one thread-pool dispatch each - for tasks that do nothing on the thread.

Report a cost of 0 for `ColumnDataPrefetch` tasks so they collapse into a single
batch, removing the spurious per-subgroup dispatch overhead introduced by the
prefetch/decode stage split.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@UnamedRus
UnamedRus force-pushed the parquet-v3-read-concurrency branch from ccd3c2d to df7105e Compare August 24, 2026 09:34
UnamedRus and others added 2 commits August 24, 2026 12:40
Condense the oversized comment blocks added by the read-concurrency work (the
stage-weight rationale in `ReadManager::init`, the ColumnDataPrefetch/ColumnData
scheduleTask and runTask notes, and the cost-estimate exception) to a few lines
each. No code changes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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