Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions be/benchmark/parquet/parquet_benchmark_scenarios.h
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,19 @@ inline std::vector<ReaderScenario> reader_scenarios() {
scenario.operation = ReaderOperation::PREDICATE_SCAN;
add(scenario);
}
for (const auto encoding : {Encoding::BYTE_STREAM_SPLIT, Encoding::DELTA_BINARY_PACKED}) {

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.

[P2] Update and assert the reader registration total

This loop contributes 14 unique registrations, not 16, because each encoding's 10%-selectivity projected case already comes from the preceding loop. The total therefore grows from 137 to 151, while be/benchmark/parquet/AGENTS.md still tells smoke reviewers to expect 137 and to reject a result with a different count. Please assert reader_scenarios().size() == 151 and update the current listing/matrix counts (leaving the historical validation record tied to its old commit unchanged).

for (const int selectivity : {1, 10, 50, 90}) {
for (const auto projection :
{Projection::PREDICATE_ONLY, Projection::PREDICATE_PROJECTED}) {
auto scenario = baseline;
scenario.operation = ReaderOperation::PREDICATE_SCAN;
scenario.encoding = encoding;
scenario.selectivity_percent = selectivity;
scenario.projection = projection;
add(scenario);
}
}
}
for (const int width : {4, 32, 128, 512}) {
for (const int predicate_position : {0, width - 1}) {
auto scenario = baseline;
Expand Down
12 changes: 6 additions & 6 deletions be/src/format_v2/parquet/parquet_profile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -175,10 +175,10 @@ void ParquetProfile::init(RuntimeProfile* profile) {
TUnit::BYTES, parquet_profile, 1);
predicate_compaction_count = ADD_CHILD_COUNTER_WITH_LEVEL(profile, "PredicateCompactionCount",
TUnit::UNIT, parquet_profile, 1);
plain_predicate_direct_batches = ADD_CHILD_COUNTER_WITH_LEVEL(
profile, "PlainPredicateDirectBatches", TUnit::UNIT, parquet_profile, 1);
plain_predicate_direct_rows = ADD_CHILD_COUNTER_WITH_LEVEL(profile, "PlainPredicateDirectRows",
TUnit::UNIT, parquet_profile, 1);
fixed_width_predicate_direct_batches = ADD_CHILD_COUNTER_WITH_LEVEL(
profile, "FixedWidthPredicateDirectBatches", TUnit::UNIT, parquet_profile, 1);

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.

[P2] Preserve the profile keys or update their compatibility contract

This removes the only PlainPredicateDirectBatches/Rows registrations, but the mandatory Parquet scan design still describes the predicate-only PLAIN path and tells operators/profile parsers to read those exact keys. After this change those lookups silently disappear, even for the existing PLAIN workload. Please preserve the old keys as deprecated aliases (or retain them with broadened semantics) and update the current-pipeline design to cover projected PLAIN/BSS/Delta behavior and the new names.

fixed_width_predicate_direct_rows = ADD_CHILD_COUNTER_WITH_LEVEL(
profile, "FixedWidthPredicateDirectRows", TUnit::UNIT, parquet_profile, 1);
dict_filter_rewrite_time =
ADD_CHILD_TIMER_WITH_LEVEL(profile, "DictFilterRewriteTime", parquet_profile, 1);
dict_filter_expr_rewrite_time =
Expand Down Expand Up @@ -316,8 +316,8 @@ ParquetScanProfile ParquetProfile::scan_profile() const {
.predicate_compaction_time = predicate_compaction_time,
.predicate_compaction_bytes = predicate_compaction_bytes,
.predicate_compaction_count = predicate_compaction_count,
.plain_predicate_direct_batches = plain_predicate_direct_batches,
.plain_predicate_direct_rows = plain_predicate_direct_rows,
.fixed_width_predicate_direct_batches = fixed_width_predicate_direct_batches,
.fixed_width_predicate_direct_rows = fixed_width_predicate_direct_rows,
.dict_filter_rewrite_time = dict_filter_rewrite_time,
.dict_filter_expr_rewrite_time = dict_filter_expr_rewrite_time,
.dict_filter_read_dict_time = dict_filter_read_dict_time,
Expand Down
8 changes: 4 additions & 4 deletions be/src/format_v2/parquet/parquet_profile.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,8 @@ struct ParquetScanProfile {
RuntimeProfile::Counter* predicate_compaction_time = nullptr;
RuntimeProfile::Counter* predicate_compaction_bytes = nullptr;
RuntimeProfile::Counter* predicate_compaction_count = nullptr;
RuntimeProfile::Counter* plain_predicate_direct_batches = nullptr;
RuntimeProfile::Counter* plain_predicate_direct_rows = nullptr;
RuntimeProfile::Counter* fixed_width_predicate_direct_batches = nullptr;
RuntimeProfile::Counter* fixed_width_predicate_direct_rows = nullptr;
RuntimeProfile::Counter* dict_filter_rewrite_time = nullptr; // dictionary rewrite time (ns)
RuntimeProfile::Counter* dict_filter_expr_rewrite_time =
nullptr; // expression/residual rewrite time (ns)
Expand Down Expand Up @@ -203,8 +203,8 @@ struct ParquetProfile {
RuntimeProfile::Counter* predicate_compaction_time = nullptr;
RuntimeProfile::Counter* predicate_compaction_bytes = nullptr;
RuntimeProfile::Counter* predicate_compaction_count = nullptr;
RuntimeProfile::Counter* plain_predicate_direct_batches = nullptr;
RuntimeProfile::Counter* plain_predicate_direct_rows = nullptr;
RuntimeProfile::Counter* fixed_width_predicate_direct_batches = nullptr;
RuntimeProfile::Counter* fixed_width_predicate_direct_rows = nullptr;
RuntimeProfile::Counter* dict_filter_rewrite_time = nullptr;
RuntimeProfile::Counter* dict_filter_expr_rewrite_time = nullptr;
RuntimeProfile::Counter* dict_filter_read_dict_time = nullptr;
Expand Down
49 changes: 29 additions & 20 deletions be/src/format_v2/parquet/parquet_scan.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1551,11 +1551,11 @@ Status ParquetScanScheduler::read_filter_columns(int64_t batch_rows,
auto read_predicate_column =
[&](ParquetColumnReader* column_reader, size_t block_position,
format::LocalColumnId local_id, const VExprContextSPtrs* single_column_conjuncts,
bool* used_dictionary_filter, bool* used_plain_filter) -> Status {
bool* used_dictionary_filter, bool* used_fixed_width_filter) -> Status {
DORIS_CHECK(used_dictionary_filter != nullptr);
DORIS_CHECK(used_plain_filter != nullptr);
DORIS_CHECK(used_fixed_width_filter != nullptr);
*used_dictionary_filter = false;
*used_plain_filter = false;
*used_fixed_width_filter = false;
DCHECK(remove_nullable(column_reader->type())
->equals(*remove_nullable(file_block->get_by_position(block_position).type)))
<< column_reader->type()->get_name() << " "
Expand Down Expand Up @@ -1597,8 +1597,7 @@ Status ParquetScanScheduler::read_filter_columns(int64_t batch_rows,
}

if (single_column_conjuncts != nullptr &&
!residual_predicate_positions.contains(block_position) &&
request.is_predicate_only(local_id)) {
!residual_predicate_positions.contains(block_position)) {
VExprSPtrs direct_conjuncts;
direct_conjuncts.reserve(single_column_conjuncts->size());
std::ranges::transform(*single_column_conjuncts, std::back_inserter(direct_conjuncts),
Expand All @@ -1607,13 +1606,19 @@ Status ParquetScanScheduler::read_filter_columns(int64_t batch_rows,
const uint16_t selected_rows_before = *selected_rows;
IColumn::Filter compact_filter;
bool used_filter = false;
RETURN_IF_ERROR(column_reader->select_with_plain_filter(
const bool predicate_only = request.is_predicate_only(local_id);
// The raw decoder cannot rewind after evaluating encoded fixed-width values.
// Project survivors in that pass when output still needs the predicate column.
IColumn* projected_column = predicate_only ? nullptr : column.get();
RETURN_IF_ERROR(column_reader->select_with_fixed_width_filter(
*selection, *selected_rows, batch_rows, direct_conjuncts,
cast_set<int>(block_position), &compact_filter, &used_filter));
cast_set<int>(block_position), projected_column, &compact_filter,
&used_filter));
if (used_filter) {
DORIS_CHECK_EQ(compact_filter.size(), selected_rows_before);
update_counter_if_not_null(_scan_profile.plain_predicate_direct_batches, 1);
update_counter_if_not_null(_scan_profile.plain_predicate_direct_rows,
update_counter_if_not_null(_scan_profile.fixed_width_predicate_direct_batches,
1);
update_counter_if_not_null(_scan_profile.fixed_width_predicate_direct_rows,
selected_rows_before);
const uint16_t new_selected_rows = count_selected_rows(compact_filter);
const auto filtered_rows = static_cast<int64_t>(selected_rows_before) -
Expand All @@ -1625,15 +1630,19 @@ Status ParquetScanScheduler::read_filter_columns(int64_t batch_rows,
*selected_rows = apply_compact_filter_to_selection(
compact_filter, selection, selected_rows_before);
}
// This slot is absent from every residual/delete conjunct, so no later
// expression can observe its payload. Keep only the block row-shape contract.
auto placeholder = column->clone_empty();
placeholder->insert_many_defaults(*selected_rows);
file_block->replace_by_position(block_position, std::move(placeholder));
if (predicate_only) {
// This slot is absent from every residual/delete conjunct, so no later
// expression can observe its payload. Keep only the block row-shape contract.
auto placeholder = column->clone_empty();
placeholder->insert_many_defaults(*selected_rows);
file_block->replace_by_position(block_position, std::move(placeholder));
} else {
file_block->replace_by_position(block_position, std::move(column));
}
read_column_positions.push_back(cast_set<uint32_t>(block_position));
remember_column_selection(cast_set<uint32_t>(block_position));
*predicate_columns_filtered = true;
*used_plain_filter = true;
*used_fixed_width_filter = true;
return Status::OK();
}
}
Expand Down Expand Up @@ -1761,10 +1770,10 @@ Status ParquetScanScheduler::read_filter_columns(int64_t batch_rows,
auto position_it = request.local_positions.find(fid);
DORIS_CHECK(position_it != request.local_positions.end());
bool used_dictionary_filter = false;
bool used_plain_filter = false;
bool used_fixed_width_filter = false;
RETURN_IF_ERROR(read_predicate_column(column_reader.get(), position_it->second.value(),
fid, nullptr, &used_dictionary_filter,
&used_plain_filter));
&used_fixed_width_filter));
}
return Status::OK();
};
Expand Down Expand Up @@ -1803,21 +1812,21 @@ Status ParquetScanScheduler::read_filter_columns(int64_t batch_rows,
predicate_batch_sequence);
const int64_t start_ns = sample ? MonotonicNanos() : 0;
bool used_dictionary_filter = false;
bool used_plain_filter = false;
bool used_fixed_width_filter = false;
const auto conjunct_it = schedule.single_column_conjuncts.find(block_position);
const VExprContextSPtrs* column_conjuncts =
conjunct_it == schedule.single_column_conjuncts.end() ? nullptr
: &conjunct_it->second;
RETURN_IF_ERROR(read_predicate_column(reader_it->second.get(), block_position, fid,
column_conjuncts, &used_dictionary_filter,
&used_plain_filter));
&used_fixed_width_filter));
if (*selected_rows != 0 && conjunct_it != schedule.single_column_conjuncts.end()) {
if (used_dictionary_filter) {
const auto residual_it = _current_dictionary_residual_conjuncts.find(fid);
DORIS_CHECK(residual_it != _current_dictionary_residual_conjuncts.end());
RETURN_IF_ERROR(execute_scheduled_dictionary_residual_conjuncts_with_profile(
residual_it->second));
} else if (!used_plain_filter) {
} else if (!used_fixed_width_filter) {
RETURN_IF_ERROR(execute_scheduled_conjuncts_with_profile(conjunct_it->second));
}
}
Expand Down
8 changes: 4 additions & 4 deletions be/src/format_v2/parquet/reader/column_reader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,10 @@ Status ParquetColumnReader::select_with_dictionary_filter(const SelectionVector&
name());
}

Status ParquetColumnReader::select_with_plain_filter(const SelectionVector&, uint16_t, int64_t,
const VExprSPtrs&, int,
IColumn::Filter* row_filter,
bool* used_filter) {
Status ParquetColumnReader::select_with_fixed_width_filter(const SelectionVector&, uint16_t,
int64_t, const VExprSPtrs&, int,
IColumn*, IColumn::Filter* row_filter,
bool* used_filter) {
DORIS_CHECK(row_filter != nullptr);
DORIS_CHECK(used_filter != nullptr);
row_filter->clear();
Expand Down
14 changes: 8 additions & 6 deletions be/src/format_v2/parquet/reader/column_reader.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,14 @@ class ParquetColumnReader {
MutableColumnPtr& column,
IColumn::Filter* row_filter, bool* used_filter);

// Consume batch_rows and evaluate eligible fixed-width PLAIN values without constructing a
// predicate column. Implementations must leave the cursor untouched when used_filter=false.
virtual Status select_with_plain_filter(const SelectionVector& selection,
uint16_t selected_rows, int64_t batch_rows,
const VExprSPtrs& conjuncts, int column_id,
IColumn::Filter* row_filter, bool* used_filter);
// Consume batch_rows and evaluate eligible fixed-width values without first constructing a
// complete predicate column. Append survivors when projected_column is non-null. Implementations
// must leave the cursor untouched when used_filter=false.
virtual Status select_with_fixed_width_filter(const SelectionVector& selection,
uint16_t selected_rows, int64_t batch_rows,
const VExprSPtrs& conjuncts, int column_id,
IColumn* projected_column,
IColumn::Filter* row_filter, bool* used_filter);

// Native statistics are cumulative and can be recursively aggregated for complex columns.
// Flush once at the scheduler batch boundary instead of snapshotting after each operation.
Expand Down
46 changes: 32 additions & 14 deletions be/src/format_v2/parquet/reader/native/column_chunk_reader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -653,14 +653,15 @@ Status decode_selected_nullable_values(IColumn& column, const DataTypeSerDe& ser
return Status::OK();
}

class PlainPredicateConsumer final : public ParquetFixedValueConsumer {
class FixedWidthPredicateConsumer final : public ParquetFixedValueConsumer {
public:
PlainPredicateConsumer(const VExprSPtrs& conjuncts, DataTypePtr data_type, int column_id,
IColumn::Filter* matches)
FixedWidthPredicateConsumer(const VExprSPtrs& conjuncts, DataTypePtr data_type, int column_id,
IColumn::Filter* matches, IColumn* projected_column)
: _conjuncts(conjuncts),
_data_type(std::move(data_type)),
_column_id(column_id),
_matches(matches) {
_matches(matches),
_projected_column(projected_column) {
DORIS_CHECK(_matches != nullptr);
}

Expand All @@ -672,6 +673,23 @@ class PlainPredicateConsumer final : public ParquetFixedValueConsumer {
_data_type, _column_id,
_matches->data() + old_size));
}
if (_projected_column != nullptr) {

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.

[P2] Avoid per-match-run column appends

With alternating predicate results, this loop invokes virtual insert_many_raw_data() once per survivor (N/2 calls), and each call resizes the vector before copying only 4/8 bytes; nullable output also grows its null map and nested column each time. The previous projected path materialized the batch and used vectorized filtering, so ordinary unsorted values can turn this fast path into thousands of virtual calls and tiny copies per batch. Both added tests have a single contiguous survivor run. Please reserve/grow once and compact survivors into the destination in a bounded batch/span operation, and cover an alternating-match case.

size_t row = 0;
while (row < num_values) {
while (row < num_values && (*_matches)[old_size + row] == 0) {
++row;
}
const size_t run_begin = row;
while (row < num_values && (*_matches)[old_size + row] != 0) {
++row;
}
if (row != run_begin) {
_projected_column->insert_many_raw_data(

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.

[P2] Keep native timing complete on projected direct scans

Projected predicate columns now append survivors in this branch and no longer pass through materialize_values(), which is where decode_value_time and materialization_time are incremented. filter_plain_values() calls decode_selected_fixed_values() and this line copies survivors, but neither contributes to the separately published DecodeValueTime/MaterializationTime, so every eligible projected direct scan underreports both stages. Please mirror the existing timing convention: an inclusive batch/span decode timer around the direct decoder/consumer call and a nested materialization timer around projected survivor writes, avoiding per-row timers.

reinterpret_cast<const char*>(values + run_begin * value_width),
row - run_begin);
}
}
}
return Status::OK();
}

Expand All @@ -680,6 +698,7 @@ class PlainPredicateConsumer final : public ParquetFixedValueConsumer {
DataTypePtr _data_type;
int _column_id;
IColumn::Filter* _matches;
IColumn* _projected_column;
};

} // namespace
Expand Down Expand Up @@ -1428,11 +1447,10 @@ Status ColumnChunkReader<IN_COLLECTION, OFFSET_INDEX>::materialize_values(
}

template <bool IN_COLLECTION, bool OFFSET_INDEX>
bool ColumnChunkReader<IN_COLLECTION, OFFSET_INDEX>::can_filter_plain_values(
bool ColumnChunkReader<IN_COLLECTION, OFFSET_INDEX>::can_filter_fixed_width_values(
const VExprSPtrs& conjuncts, int column_id) const {
if (conjuncts.empty() || _current_encoding != tparquet::Encoding::PLAIN ||
(_metadata.type != tparquet::Type::INT32 && _metadata.type != tparquet::Type::INT64 &&
_metadata.type != tparquet::Type::FLOAT && _metadata.type != tparquet::Type::DOUBLE)) {
if (conjuncts.empty() ||
!supports_raw_fixed_filter_encoding(_current_encoding, _metadata.type)) {
return false;
}
const auto primitive_type = remove_nullable(_field_schema->data_type)->get_primitive_type();
Expand All @@ -1453,17 +1471,17 @@ bool ColumnChunkReader<IN_COLLECTION, OFFSET_INDEX>::can_filter_plain_values(
}

template <bool IN_COLLECTION, bool OFFSET_INDEX>
Status ColumnChunkReader<IN_COLLECTION, OFFSET_INDEX>::filter_plain_values(
Status ColumnChunkReader<IN_COLLECTION, OFFSET_INDEX>::filter_fixed_width_values(
const VExprSPtrs& conjuncts, int column_id, ColumnSelectVector& select_vector,
NullMap* selected_nulls, IColumn::Filter* physical_matches, IColumn::Filter* row_filter,
bool* used_filter) {
NullMap* selected_nulls, IColumn::Filter* physical_matches, IColumn* projected_column,
IColumn::Filter* row_filter, bool* used_filter) {
DORIS_CHECK(selected_nulls != nullptr);
DORIS_CHECK(physical_matches != nullptr);
DORIS_CHECK(row_filter != nullptr);
DORIS_CHECK(used_filter != nullptr);
*used_filter = false;
row_filter->clear();
if (!can_filter_plain_values(conjuncts, column_id)) {
if (!can_filter_fixed_width_values(conjuncts, column_id)) {
return Status::OK();
}
if (UNLIKELY(_remaining_num_values < select_vector.num_values())) {
Expand Down Expand Up @@ -1520,8 +1538,8 @@ Status ColumnChunkReader<IN_COLLECTION, OFFSET_INDEX>::filter_plain_values(
if (selection.selected_values == 0) {
RETURN_IF_ERROR(_page_decoder->skip_values(selection.total_values));
} else {
PlainPredicateConsumer consumer(conjuncts, _field_schema->data_type, column_id,
physical_matches);
FixedWidthPredicateConsumer consumer(conjuncts, _field_schema->data_type, column_id,
physical_matches, projected_column);
RETURN_IF_ERROR(_page_decoder->decode_selected_fixed_values(selection, consumer));
DORIS_CHECK_EQ(physical_matches->size(), selection.selected_values);
}
Expand Down
Loading
Loading