This is an automated email from the ASF dual-hosted git repository.
yiguolei pushed a commit to branch branch-4.2
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/branch-4.2 by this push:
new e4f23b9ac56 branch-4.2: [improvement](be) Reuse Parquet profile
counters across external scan files (#69300) (#69804)
e4f23b9ac56 is described below
commit e4f23b9ac56b5c114bd2e7f02777c80ee90c9d68
Author: 924060929 <[email protected]>
AuthorDate: Thu Oct 29 18:31:44 2026 +0800
branch-4.2: [improvement](be) Reuse Parquet profile counters across
external scan files (#69300) (#69804)
cherry pick from #69300
---
be/src/exec/operator/file_scan_operator.cpp | 10 ++
be/src/exec/operator/file_scan_operator.h | 9 +-
be/src/exec/scan/file_scanner_v2.cpp | 4 +-
be/src/exec/scan/file_scanner_v2.h | 1 +
be/src/format_v2/file_reader.cpp | 2 +-
be/src/format_v2/parquet/parquet_profile.cpp | 75 ++++-----
be/src/format_v2/parquet/parquet_profile.h | 75 ++++++---
be/src/format_v2/parquet/parquet_reader.cpp | 10 +-
be/src/format_v2/parquet/parquet_reader.h | 3 +-
.../parquet/reader/variant_column_reader.cpp | 62 +++++---
be/src/format_v2/table/hudi_reader.cpp | 1 +
.../iceberg_position_delete_sys_table_reader.cpp | 11 ++
.../iceberg_position_delete_sys_table_reader.h | 2 +
be/src/format_v2/table/iceberg_reader.cpp | 2 +-
be/src/format_v2/table/load_reader.cpp | 1 +
be/src/format_v2/table/paimon_reader.cpp | 1 +
be/src/format_v2/table_reader.cpp | 4 +-
be/src/format_v2/table_reader.h | 6 +
be/test/format_v2/parquet/parquet_profile_test.cpp | 169 +++++++++++++++++++++
be/test/format_v2/parquet/parquet_reader_test.cpp | 50 ++++++
.../parquet/variant_column_reader_test.cpp | 8 +-
be/test/format_v2/table/hudi_reader_test.cpp | 74 +++++++++
...eberg_position_delete_sys_table_reader_test.cpp | 45 ++++++
be/test/format_v2/table/iceberg_reader_test.cpp | 37 +++++
be/test/format_v2/table/load_reader_test.cpp | 17 ++-
25 files changed, 577 insertions(+), 102 deletions(-)
diff --git a/be/src/exec/operator/file_scan_operator.cpp
b/be/src/exec/operator/file_scan_operator.cpp
index 9ba2747624b..6802dae071f 100644
--- a/be/src/exec/operator/file_scan_operator.cpp
+++ b/be/src/exec/operator/file_scan_operator.cpp
@@ -34,6 +34,7 @@
#include "exec/scan/scanner_context.h"
#include "format/format_common.h"
#include "format/table/iceberg_scan_semantics.h"
+#include "format_v2/parquet/parquet_profile.h"
#include "storage/storage_engine.h"
#include "storage/tablet/tablet_manager.h"
@@ -63,6 +64,11 @@ bool contains_variant_type(const DataTypePtr& input) {
} // namespace
+FileScanLocalState::FileScanLocalState(RuntimeState* state, OperatorXBase*
parent)
+ : ScanLocalState<FileScanLocalState>(state, parent) {}
+
+FileScanLocalState::~FileScanLocalState() = default;
+
PushDownType FileScanLocalState::_should_push_down_binary_predicate(
VectorizedFnCall* fn_call, VExprContext* expr_ctx, Field& constant_val,
const std::set<std::string> fn_name) const {
@@ -273,6 +279,10 @@ Status
FileScanLocalState::_init_scanners(std::list<ScannerSPtr>* scanners) {
"External VARIANT columns require FileScannerV2; the legacy
file scanner does "
"not support VARIANT");
}
+ if (use_file_scanner_v2) {
+ _parquet_profile_cache =
+
std::make_unique<format::parquet::ParquetProfileCache>(_scanner_profile.get());
+ }
for (int i = 0; i < _max_scanners; ++i) {
ScannerSPtr scanner;
if (use_file_scanner_v2) {
diff --git a/be/src/exec/operator/file_scan_operator.h
b/be/src/exec/operator/file_scan_operator.h
index 80c16e1177d..03c0c980d93 100644
--- a/be/src/exec/operator/file_scan_operator.h
+++ b/be/src/exec/operator/file_scan_operator.h
@@ -28,6 +28,10 @@
#include "exec/scan/split_source_connector.h"
#include "format/format_common.h"
+namespace doris::format::parquet {
+class ParquetProfileCache;
+}
+
namespace doris {
#include "common/compile_check_begin.h"
class FileScanner;
@@ -42,8 +46,8 @@ public:
using Parent = FileScanOperatorX;
using Base = ScanLocalState<FileScanLocalState>;
ENABLE_FACTORY_CREATOR(FileScanLocalState);
- FileScanLocalState(RuntimeState* state, OperatorXBase* parent)
- : ScanLocalState<FileScanLocalState>(state, parent) {}
+ FileScanLocalState(RuntimeState* state, OperatorXBase* parent);
+ ~FileScanLocalState() override;
Status init(RuntimeState* state, LocalStateInfo& info) override;
@@ -101,6 +105,7 @@ private:
VectorizedFnCall* fn_call, VExprContext* expr_ctx, Field&
constant_val,
const std::set<std::string> fn_name) const override;
std::shared_ptr<SplitSourceConnector> _split_source = nullptr;
+ std::unique_ptr<format::parquet::ParquetProfileCache>
_parquet_profile_cache;
int _max_scanners;
// A in memory cache to save some common components
// of the this scan node. eg:
diff --git a/be/src/exec/scan/file_scanner_v2.cpp
b/be/src/exec/scan/file_scanner_v2.cpp
index 45b40ee8ab4..2090a330624 100644
--- a/be/src/exec/scan/file_scanner_v2.cpp
+++ b/be/src/exec/scan/file_scanner_v2.cpp
@@ -415,7 +415,8 @@ FileScannerV2::FileScannerV2(RuntimeState* state,
FileScanLocalState* local_stat
: Scanner(state, local_state, limit, profile),
_split_source(std::move(split_source)),
_kv_cache(kv_cache),
- _constructed_scanners(local_state->_max_scanners) {
+ _constructed_scanners(local_state->_max_scanners),
+ _parquet_profile_cache(local_state->_parquet_profile_cache.get()) {
(void)colname_to_slot_id;
if (state->get_query_ctx() != nullptr &&
state->get_query_ctx()->file_scan_range_params_map.count(local_state->parent_id())
> 0) {
@@ -777,6 +778,7 @@ Status FileScannerV2::_init_table_reader(const
TFileRangeDesc& range) {
.io_ctx = _io_ctx,
.runtime_state = _state,
.scanner_profile = _local_state != nullptr ?
_local_state->scanner_profile() : _profile,
+ .parquet_profile_cache = _parquet_profile_cache,
.file_slot_descs = &_file_slot_descs,
.push_down_agg_type = _local_state != nullptr ?
_local_state->get_push_down_agg_type()
:
TPushAggOp::type::NONE,
diff --git a/be/src/exec/scan/file_scanner_v2.h
b/be/src/exec/scan/file_scanner_v2.h
index b7de342988a..660ae0f4ff0 100644
--- a/be/src/exec/scan/file_scanner_v2.h
+++ b/be/src/exec/scan/file_scanner_v2.h
@@ -261,6 +261,7 @@ private:
std::shared_ptr<io::IOContext> _io_ctx;
ShardedKVCache* _kv_cache = nullptr;
int _constructed_scanners = 1;
+ format::parquet::ParquetProfileCache* _parquet_profile_cache = nullptr;
RuntimeProfile::Counter* _scanner_total_timer = nullptr;
RuntimeProfile::Counter* _init_timer = nullptr;
diff --git a/be/src/format_v2/file_reader.cpp b/be/src/format_v2/file_reader.cpp
index 894cd49fa6b..7ce26e61308 100644
--- a/be/src/format_v2/file_reader.cpp
+++ b/be/src/format_v2/file_reader.cpp
@@ -100,7 +100,7 @@ std::string FileScanRequest::debug_string() const {
}
Status FileReader::init(RuntimeState* state) {
- _init_profile();
+ // Concrete readers initialize their counters before starting their
lifecycle timers.
SCOPED_RAW_TIMER(&_reader_statistics.file_reader_create_time);
++_reader_statistics.open_file_num;
io::FileReaderOptions reader_options =
diff --git a/be/src/format_v2/parquet/parquet_profile.cpp
b/be/src/format_v2/parquet/parquet_profile.cpp
index a9788db044b..4cfb2dad3af 100644
--- a/be/src/format_v2/parquet/parquet_profile.cpp
+++ b/be/src/format_v2/parquet/parquet_profile.cpp
@@ -22,6 +22,20 @@
namespace doris::format::parquet {
+const ParquetProfile& ParquetProfileCache::get() {
+ std::call_once(_initialized, [this] { _counters.init(_profile); });
+ return _counters;
+}
+
+ParquetProfileCache* ParquetProfileCache::get_child_cache(RuntimeProfile*
child_profile) {
+ std::lock_guard lock(_children_mutex);
+ auto& cache = _children[child_profile];
+ if (cache == nullptr) {
+ cache = std::make_unique<ParquetProfileCache>(child_profile);
+ }
+ return cache.get();
+}
+
namespace {
std::shared_ptr<RuntimeProfile::Counter>
add_persistent_counter(RuntimeProfile* profile,
@@ -109,38 +123,40 @@ void ParquetProfile::init(RuntimeProfile* profile) {
ADD_CHILD_TIMER_WITH_LEVEL(profile, "LevelOnlySkipTime",
parquet_profile, 1);
materialization_time =
ADD_CHILD_TIMER_WITH_LEVEL(profile, "MaterializationTime",
parquet_profile, 1);
- variant_reconstruction_time = add_persistent_counter(profile,
"VariantReconstructionTime",
- TUnit::TIME_NS,
parquet_profile);
- variant_reconstructed_rows = add_persistent_counter(profile,
"VariantReconstructedRows",
- TUnit::UNIT,
parquet_profile);
- variant_unshredded_direct_import_time = add_persistent_counter(
+ auto variant = std::make_shared<ParquetVariantProfile>();
+ variant->variant_reconstruction_time = add_persistent_counter(
+ profile, "VariantReconstructionTime", TUnit::TIME_NS,
parquet_profile);
+ variant->variant_reconstructed_rows = add_persistent_counter(
+ profile, "VariantReconstructedRows", TUnit::UNIT, parquet_profile);
+ variant->variant_unshredded_direct_import_time = add_persistent_counter(
profile, "VariantUnshreddedDirectImportTime", TUnit::TIME_NS,
parquet_profile);
- variant_unshredded_direct_import_rows = add_persistent_counter(
+ variant->variant_unshredded_direct_import_rows = add_persistent_counter(
profile, "VariantUnshreddedDirectImportRows", TUnit::UNIT,
parquet_profile);
- variant_unshredded_direct_import_bytes = add_persistent_counter(
+ variant->variant_unshredded_direct_import_bytes = add_persistent_counter(
profile, "VariantUnshreddedDirectImportBytes", TUnit::BYTES,
parquet_profile);
- variant_unshredded_direct_seek_time = add_persistent_counter(
+ variant->variant_unshredded_direct_seek_time = add_persistent_counter(
profile, "VariantUnshreddedDirectSeekTime", TUnit::TIME_NS,
parquet_profile);
- variant_unshredded_direct_seek_rows = add_persistent_counter(
+ variant->variant_unshredded_direct_seek_rows = add_persistent_counter(
profile, "VariantUnshreddedDirectSeekRows", TUnit::UNIT,
parquet_profile);
- variant_unshredded_direct_seek_bytes = add_persistent_counter(
+ variant->variant_unshredded_direct_seek_bytes = add_persistent_counter(
profile, "VariantUnshreddedDirectSeekBytes", TUnit::BYTES,
parquet_profile);
- variant_unshredded_prefix_reuse_rows = add_persistent_counter(
+ variant->variant_unshredded_prefix_reuse_rows = add_persistent_counter(
profile, "VariantUnshreddedPrefixReuseRows", TUnit::UNIT,
parquet_profile);
- variant_direct_subtree_rows = add_persistent_counter(profile,
"VariantDirectSubtreeRows",
- TUnit::UNIT,
parquet_profile);
- variant_direct_leaf_rows =
+ variant->variant_direct_subtree_rows = add_persistent_counter(
+ profile, "VariantDirectSubtreeRows", TUnit::UNIT, parquet_profile);
+ variant->variant_direct_leaf_rows =
add_persistent_counter(profile, "VariantDirectLeafRows",
TUnit::UNIT, parquet_profile);
- variant_direct_leaf_path_misses = add_persistent_counter(profile,
"VariantDirectLeafPathMisses",
- TUnit::UNIT,
parquet_profile);
- variant_direct_leaf_residual_fallbacks = add_persistent_counter(
+ variant->variant_direct_leaf_path_misses = add_persistent_counter(
+ profile, "VariantDirectLeafPathMisses", TUnit::UNIT,
parquet_profile);
+ variant->variant_direct_leaf_residual_fallbacks = add_persistent_counter(
profile, "VariantDirectLeafResidualFallbacks", TUnit::UNIT,
parquet_profile);
- variant_direct_leaf_residual_merged_rows = add_persistent_counter(
+ variant->variant_direct_leaf_residual_merged_rows = add_persistent_counter(
profile, "VariantDirectLeafResidualMergedRows", TUnit::UNIT,
parquet_profile);
- variant_residual_seek_rows = add_persistent_counter(profile,
"VariantResidualSeekRows",
- TUnit::UNIT,
parquet_profile);
- variant_direct_leaf_unsupported_fallbacks = add_persistent_counter(
+ variant->variant_residual_seek_rows = add_persistent_counter(profile,
"VariantResidualSeekRows",
+ TUnit::UNIT,
parquet_profile);
+ variant->variant_direct_leaf_unsupported_fallbacks =
add_persistent_counter(
profile, "VariantDirectLeafUnsupportedFallbacks", TUnit::UNIT,
parquet_profile);
+ variant_counters = std::move(variant);
hybrid_selection_batches = ADD_CHILD_COUNTER_WITH_LEVEL(profile,
"HybridSelectionBatches",
TUnit::UNIT,
parquet_profile, 1);
hybrid_selection_ranges = ADD_CHILD_COUNTER_WITH_LEVEL(profile,
"HybridSelectionRanges",
@@ -369,22 +385,7 @@ ParquetColumnReaderProfile
ParquetProfile::column_reader_profile() const {
.level_only_read_time = level_only_read_time,
.level_only_skip_time = level_only_skip_time,
.materialization_time = materialization_time,
- .variant_reconstruction_time = variant_reconstruction_time,
- .variant_reconstructed_rows = variant_reconstructed_rows,
- .variant_unshredded_direct_import_time =
variant_unshredded_direct_import_time,
- .variant_unshredded_direct_import_rows =
variant_unshredded_direct_import_rows,
- .variant_unshredded_direct_import_bytes =
variant_unshredded_direct_import_bytes,
- .variant_unshredded_direct_seek_time =
variant_unshredded_direct_seek_time,
- .variant_unshredded_direct_seek_rows =
variant_unshredded_direct_seek_rows,
- .variant_unshredded_direct_seek_bytes =
variant_unshredded_direct_seek_bytes,
- .variant_unshredded_prefix_reuse_rows =
variant_unshredded_prefix_reuse_rows,
- .variant_direct_subtree_rows = variant_direct_subtree_rows,
- .variant_direct_leaf_rows = variant_direct_leaf_rows,
- .variant_direct_leaf_path_misses = variant_direct_leaf_path_misses,
- .variant_direct_leaf_residual_fallbacks =
variant_direct_leaf_residual_fallbacks,
- .variant_direct_leaf_residual_merged_rows =
variant_direct_leaf_residual_merged_rows,
- .variant_residual_seek_rows = variant_residual_seek_rows,
- .variant_direct_leaf_unsupported_fallbacks =
variant_direct_leaf_unsupported_fallbacks,
+ .variant_counters = variant_counters,
.hybrid_selection_batches = hybrid_selection_batches,
.hybrid_selection_ranges = hybrid_selection_ranges,
.hybrid_selection_null_fallback_batches =
hybrid_selection_null_fallback_batches,
diff --git a/be/src/format_v2/parquet/parquet_profile.h
b/be/src/format_v2/parquet/parquet_profile.h
index 0a27dc9d8c5..11673ed2de7 100644
--- a/be/src/format_v2/parquet/parquet_profile.h
+++ b/be/src/format_v2/parquet/parquet_profile.h
@@ -16,6 +16,8 @@
#pragma once
#include <memory>
+#include <mutex>
+#include <unordered_map>
#include "runtime/runtime_profile.h"
@@ -32,14 +34,9 @@ struct ParquetPageSkipProfile {
// ============================================================================
// ============================================================================
-struct ParquetColumnReaderProfile {
- RuntimeProfile::Counter* reader_read_rows = nullptr; // rows read by
read()
- RuntimeProfile::Counter* reader_skip_rows = nullptr; // rows skipped by
skip()
- RuntimeProfile::Counter* reader_select_rows = nullptr; // rows selected by
select()
- // COUNT(nullable_col) shape-only path; ordinary scans keep both counters
zero.
- RuntimeProfile::Counter* level_only_read_time = nullptr;
- RuntimeProfile::Counter* level_only_skip_time = nullptr;
- RuntimeProfile::Counter* materialization_time = nullptr; // value
materialization time (ns)
+// Deferred Variant columns can outlive the scanner. This bundle retains all
their counters,
+// while ordinary file/column reader copies only increment one shared
reference count.
+struct ParquetVariantProfile {
std::shared_ptr<RuntimeProfile::Counter> variant_reconstruction_time;
std::shared_ptr<RuntimeProfile::Counter> variant_reconstructed_rows;
// Complete unshredded roots imported without rebuilding their Variant
value tree. Bytes count
@@ -59,6 +56,24 @@ struct ParquetColumnReaderProfile {
std::shared_ptr<RuntimeProfile::Counter>
variant_direct_leaf_residual_merged_rows;
std::shared_ptr<RuntimeProfile::Counter> variant_residual_seek_rows;
std::shared_ptr<RuntimeProfile::Counter>
variant_direct_leaf_unsupported_fallbacks;
+};
+
+struct ParquetColumnReaderProfile {
+ RuntimeProfile::Counter* reader_read_rows = nullptr; // rows read by
read()
+ RuntimeProfile::Counter* reader_skip_rows = nullptr; // rows skipped by
skip()
+ RuntimeProfile::Counter* reader_select_rows = nullptr; // rows selected by
select()
+ // COUNT(nullable_col) shape-only path; ordinary scans keep both counters
zero.
+ RuntimeProfile::Counter* level_only_read_time = nullptr;
+ RuntimeProfile::Counter* level_only_skip_time = nullptr;
+ RuntimeProfile::Counter* materialization_time = nullptr; // value
materialization time (ns)
+ // One ownership reference per profile copy, rather than one per Variant
counter.
+ std::shared_ptr<const ParquetVariantProfile> variant_counters;
+
+ const ParquetVariantProfile& variant_profile() const {
+ static const ParquetVariantProfile disabled;
+ return variant_counters ? *variant_counters : disabled;
+ }
+
RuntimeProfile::Counter* hybrid_selection_batches = nullptr;
RuntimeProfile::Counter* hybrid_selection_ranges = nullptr;
RuntimeProfile::Counter* hybrid_selection_null_fallback_batches = nullptr;
@@ -193,23 +208,14 @@ struct ParquetProfile {
RuntimeProfile::Counter* level_only_read_time = nullptr;
RuntimeProfile::Counter* level_only_skip_time = nullptr;
RuntimeProfile::Counter* materialization_time = nullptr;
- std::shared_ptr<RuntimeProfile::Counter> variant_reconstruction_time;
- std::shared_ptr<RuntimeProfile::Counter> variant_reconstructed_rows;
- // These counters are persistent because direct import can happen after
scanner teardown.
- std::shared_ptr<RuntimeProfile::Counter>
variant_unshredded_direct_import_time;
- std::shared_ptr<RuntimeProfile::Counter>
variant_unshredded_direct_import_rows;
- std::shared_ptr<RuntimeProfile::Counter>
variant_unshredded_direct_import_bytes;
- std::shared_ptr<RuntimeProfile::Counter>
variant_unshredded_direct_seek_time;
- std::shared_ptr<RuntimeProfile::Counter>
variant_unshredded_direct_seek_rows;
- std::shared_ptr<RuntimeProfile::Counter>
variant_unshredded_direct_seek_bytes;
- std::shared_ptr<RuntimeProfile::Counter>
variant_unshredded_prefix_reuse_rows;
- std::shared_ptr<RuntimeProfile::Counter> variant_direct_subtree_rows;
- std::shared_ptr<RuntimeProfile::Counter> variant_direct_leaf_rows;
- std::shared_ptr<RuntimeProfile::Counter> variant_direct_leaf_path_misses;
- std::shared_ptr<RuntimeProfile::Counter>
variant_direct_leaf_residual_fallbacks;
- std::shared_ptr<RuntimeProfile::Counter>
variant_direct_leaf_residual_merged_rows;
- std::shared_ptr<RuntimeProfile::Counter> variant_residual_seek_rows;
- std::shared_ptr<RuntimeProfile::Counter>
variant_direct_leaf_unsupported_fallbacks;
+ // One ownership reference per profile copy, rather than one per Variant
counter.
+ std::shared_ptr<const ParquetVariantProfile> variant_counters;
+
+ const ParquetVariantProfile& variant_profile() const {
+ static const ParquetVariantProfile disabled;
+ return variant_counters ? *variant_counters : disabled;
+ }
+
RuntimeProfile::Counter* hybrid_selection_batches = nullptr;
RuntimeProfile::Counter* hybrid_selection_ranges = nullptr;
RuntimeProfile::Counter* hybrid_selection_null_fallback_batches = nullptr;
@@ -293,4 +299,23 @@ struct ParquetProfile {
RuntimeProfile::Counter* bloom_filter_read_time = nullptr;
};
+// One immutable counter set per scanner RuntimeProfile. Readers copy its
handles instead of
+// registering every counter again for each file. Lazy initialization keeps
other formats' profiles
+// free of unused Parquet counters.
+class ParquetProfileCache {
+public:
+ explicit ParquetProfileCache(RuntimeProfile* profile) : _profile(profile)
{}
+ const ParquetProfile& get();
+ // Nested table readers share a child RuntimeProfile across scanner
instances. Their counters
+ // must stay on that child, while the root scan owns the cache until every
scanner is closed.
+ ParquetProfileCache* get_child_cache(RuntimeProfile* child_profile);
+
+private:
+ RuntimeProfile* _profile;
+ std::once_flag _initialized;
+ ParquetProfile _counters;
+ std::mutex _children_mutex;
+ std::unordered_map<RuntimeProfile*, std::unique_ptr<ParquetProfileCache>>
_children;
+};
+
} // namespace doris::format::parquet
diff --git a/be/src/format_v2/parquet/parquet_reader.cpp
b/be/src/format_v2/parquet/parquet_reader.cpp
index 0379327486c..c98e794a3c2 100644
--- a/be/src/format_v2/parquet/parquet_reader.cpp
+++ b/be/src/format_v2/parquet/parquet_reader.cpp
@@ -684,8 +684,9 @@
ParquetReader::ParquetReader(std::shared_ptr<io::FileSystemProperties>& system_p
std::shared_ptr<const FileContext> file_context,
int64_t format_split_id, int64_t
format_split_id_end,
std::optional<std::string> hive_parquet_time_zone,
- bool preserve_binary_uuid)
+ bool preserve_binary_uuid, ParquetProfileCache*
profile_cache)
: FileReader(system_properties, file_description, io_ctx, profile),
+ _profile_cache(profile_cache),
_global_rowid_context(global_rowid_context),
_enable_mapping_timestamp_tz(enable_mapping_timestamp_tz),
_enable_mapping_varbinary(enable_mapping_varbinary),
@@ -1373,7 +1374,12 @@ Status ParquetReader::close() {
}
void ParquetReader::_init_profile() {
- _parquet_profile.init(_profile);
+ if (_profile_cache != nullptr) {
+ _parquet_profile = _profile_cache->get();
+ } else {
+ // Standalone readers do not have a scan instance to own a shared
counter set.
+ _parquet_profile.init(_profile);
+ }
}
} // namespace doris::format::parquet
diff --git a/be/src/format_v2/parquet/parquet_reader.h
b/be/src/format_v2/parquet/parquet_reader.h
index d5830b91e6a..a5f2e65d2de 100644
--- a/be/src/format_v2/parquet/parquet_reader.h
+++ b/be/src/format_v2/parquet/parquet_reader.h
@@ -51,7 +51,7 @@ public:
std::shared_ptr<const FileContext> file_context = nullptr,
int64_t format_split_id = -1, int64_t format_split_id_end =
-1,
std::optional<std::string> hive_parquet_time_zone =
std::nullopt,
- bool preserve_binary_uuid = false);
+ bool preserve_binary_uuid = false, ParquetProfileCache*
profile_cache = nullptr);
~ParquetReader() override;
Status init(RuntimeState* state) override;
@@ -100,6 +100,7 @@ private:
std::unique_ptr<ParquetReaderScanState>
_state; // complete scan state (file_context +
schema + scheduler)
ParquetProfile _parquet_profile; // RuntimeProfile counter set
+ ParquetProfileCache* _profile_cache;
ParquetPageCacheStats _reported_page_cache_stats;
std::optional<format::GlobalRowIdContext> _global_rowid_context; // global
RowId context
size_t _batch_size = ParquetScanScheduler::DEFAULT_READ_BATCH_SIZE;
diff --git a/be/src/format_v2/parquet/reader/variant_column_reader.cpp
b/be/src/format_v2/parquet/reader/variant_column_reader.cpp
index 29b8d2639a8..19870b5d438 100644
--- a/be/src/format_v2/parquet/reader/variant_column_reader.cpp
+++ b/be/src/format_v2/parquet/reader/variant_column_reader.cpp
@@ -587,7 +587,8 @@ ColumnVariantV2::MutablePtr encode_variant_column(
int64_t imported_bytes = 0;
RuntimeProfile::Counter* direct_import_time =
profile == nullptr ? nullptr
- :
profile->variant_unshredded_direct_import_time.get();
+ : profile->variant_profile()
+
.variant_unshredded_direct_import_time.get();
{
// Update the shared timer on every exit, including validation
failures. Rows and
// bytes are published only after the complete chunk has been
appended.
@@ -599,13 +600,15 @@ ColumnVariantV2::MutablePtr encode_variant_column(
}
if (profile != nullptr) {
const auto imported_rows = static_cast<int64_t>(end - begin);
- if (profile->variant_unshredded_direct_import_rows != nullptr)
{
-
COUNTER_UPDATE(profile->variant_unshredded_direct_import_rows.get(),
- imported_rows);
+ if
(profile->variant_profile().variant_unshredded_direct_import_rows != nullptr) {
+ COUNTER_UPDATE(
+
profile->variant_profile().variant_unshredded_direct_import_rows.get(),
+ imported_rows);
}
- if (profile->variant_unshredded_direct_import_bytes !=
nullptr) {
-
COUNTER_UPDATE(profile->variant_unshredded_direct_import_bytes.get(),
- imported_bytes);
+ if
(profile->variant_profile().variant_unshredded_direct_import_bytes != nullptr) {
+ COUNTER_UPDATE(
+
profile->variant_profile().variant_unshredded_direct_import_bytes.get(),
+ imported_bytes);
}
}
} else {
@@ -1675,7 +1678,7 @@ public:
std::optional<VariantShreddedTypedValue> find_typed_value(
std::span<const VariantShreddedPathSegment> path) const override {
auto path_miss = [&]() -> std::optional<VariantShreddedTypedValue> {
- update_counter(_profile.variant_direct_leaf_path_misses, 1);
+
update_counter(_profile.variant_profile().variant_direct_leaf_path_misses, 1);
return std::nullopt;
};
if (path.empty()) {
@@ -1745,7 +1748,8 @@ public:
!levels.back().residual_view.present()) {
return path_miss();
}
- update_counter(_profile.variant_residual_seek_rows,
static_cast<int64_t>(rows));
+
update_counter(_profile.variant_profile().variant_residual_seek_rows,
+ static_cast<int64_t>(rows));
return VariantShreddedTypedValue {
.column = nullptr,
.type = nullptr,
@@ -1789,7 +1793,8 @@ public:
if (typed_schema->kind != ParquetColumnSchemaKind::PRIMITIVE ||
check_and_get_column<ColumnNullable>(*typed) == nullptr) {
static_cast<void>(validate_levels());
-
update_counter(_profile.variant_direct_leaf_unsupported_fallbacks, 1);
+
update_counter(_profile.variant_profile().variant_direct_leaf_unsupported_fallbacks,
+ 1);
if (!_complete) {
// Binary element_at evaluates complex prefixes before the
validated leaf.
// Serialize only retained descendants so projected-out
fields stay hidden.
@@ -1806,7 +1811,8 @@ public:
// Decoding a residual needs the root dictionary. A state that
projected the
// metadata away cannot serve those rows, so it keeps the
complete fallback.
if (!metadata.present()) {
-
update_counter(_profile.variant_direct_leaf_residual_fallbacks, 1);
+ update_counter(
+
_profile.variant_profile().variant_direct_leaf_residual_fallbacks, 1);
if (!_complete) {
// A projected state cannot rebuild its root, so
serialize the retained
// descendants instead of demanding the complete
Variant.
@@ -1817,8 +1823,9 @@ public:
}
return std::nullopt;
}
- update_counter(_profile.variant_direct_leaf_rows,
static_cast<int64_t>(rows));
-
update_counter(_profile.variant_direct_leaf_residual_merged_rows,
+
update_counter(_profile.variant_profile().variant_direct_leaf_rows,
+ static_cast<int64_t>(rows));
+
update_counter(_profile.variant_profile().variant_direct_leaf_residual_merged_rows,
static_cast<int64_t>(rows));
return VariantShreddedTypedValue {
.column = nullptr,
@@ -1827,7 +1834,8 @@ public:
levels, path,
metadata_index)};
}
- update_counter(_profile.variant_direct_leaf_rows,
static_cast<int64_t>(rows));
+ update_counter(_profile.variant_profile().variant_direct_leaf_rows,
+ static_cast<int64_t>(rows));
if (!supports_direct_typed_variant_state(*typed_schema)) {
// ColumnVariantV2 typed state carries only a Doris type, so
binary/UUID
// annotations and temporal units would be lost. Normalizing
the leaf through its
@@ -1917,21 +1925,24 @@ public:
const auto borrowed =
borrow_unshredded_path(_unshredded_prefix);
int64_t copied_bytes = 0;
{
-
SCOPED_TIMER(_profile.variant_unshredded_direct_seek_time.get());
+ SCOPED_TIMER(
+
_profile.variant_profile().variant_unshredded_direct_seek_time.get());
_normalized_prefix = normalize_unshredded_path(
*_schema, *_physical, *child_indices, borrowed,
&copied_bytes);
}
const auto rows = static_cast<int64_t>(_physical->size());
- update_counter(_profile.variant_unshredded_direct_seek_rows,
rows);
- update_counter(_profile.variant_unshredded_direct_seek_bytes,
copied_bytes);
+
update_counter(_profile.variant_profile().variant_unshredded_direct_seek_rows,
+ rows);
+
update_counter(_profile.variant_profile().variant_unshredded_direct_seek_bytes,
+ copied_bytes);
}
const auto& nullable = assert_cast<const
ColumnNullable&>(*_normalized_prefix);
return assert_cast<const
ColumnVariantV2&>(nullable.get_nested_column());
}
if (!_materialized) {
- SCOPED_TIMER(_profile.variant_reconstruction_time.get());
+
SCOPED_TIMER(_profile.variant_profile().variant_reconstruction_time.get());
_materialized = encode_variant_column(*_schema, *_physical, true,
&_profile);
- update_counter(_profile.variant_reconstructed_rows,
+
update_counter(_profile.variant_profile().variant_reconstructed_rows,
static_cast<int64_t>(_physical->size()));
}
return *_materialized;
@@ -2062,7 +2073,7 @@ private:
std::shared_ptr<const UnshreddedMetadataIndex> metadata_index;
UnshreddedPathScan scan;
{
- SCOPED_TIMER(_profile.variant_unshredded_direct_seek_time.get());
+
SCOPED_TIMER(_profile.variant_profile().variant_unshredded_direct_seek_time.get());
metadata_index = get_unshredded_metadata_index(*child_indices);
scan = scan_unshredded_path(
*_schema, *_physical, *child_indices, *metadata_index,
@@ -2073,22 +2084,23 @@ private:
}
const auto rows = static_cast<int64_t>(_physical->size());
- update_counter(_profile.variant_unshredded_direct_seek_rows, rows);
+
update_counter(_profile.variant_profile().variant_unshredded_direct_seek_rows,
rows);
if (_unshredded_path_cache) {
- update_counter(_profile.variant_unshredded_prefix_reuse_rows,
rows);
+
update_counter(_profile.variant_profile().variant_unshredded_prefix_reuse_rows,
rows);
}
MutableColumnPtr values;
std::shared_ptr<ParquetVariantShreddedState> subtree_state;
if (scan.typed_values) {
values =
ColumnVariantV2::create_typed(std::move(scan.typed_values),
std::move(scan.typed_type));
- update_counter(_profile.variant_unshredded_direct_seek_bytes,
scan.copied_bytes);
- update_counter(_profile.variant_direct_leaf_rows, rows);
+
update_counter(_profile.variant_profile().variant_unshredded_direct_seek_bytes,
+ scan.copied_bytes);
+
update_counter(_profile.variant_profile().variant_direct_leaf_rows, rows);
} else {
subtree_state = std::make_shared<ParquetVariantShreddedState>(
_schema, _physical, _complete, _profile, combined,
std::move(scan.path_cache));
values = ColumnVariantV2::create_shredded(subtree_state);
- update_counter(_profile.variant_direct_subtree_rows, rows);
+
update_counter(_profile.variant_profile().variant_direct_subtree_rows, rows);
}
ColumnPtr result = ColumnNullable::create(std::move(values),
std::move(scan.outer_nulls));
return result;
diff --git a/be/src/format_v2/table/hudi_reader.cpp
b/be/src/format_v2/table/hudi_reader.cpp
index 88fdc484695..a9ce03f7536 100644
--- a/be/src/format_v2/table/hudi_reader.cpp
+++ b/be/src/format_v2/table/hudi_reader.cpp
@@ -204,6 +204,7 @@ Status
HudiHybridReader::_init_child_reader(format::TableReader* reader,
.io_ctx = _io_ctx,
.runtime_state = _runtime_state,
.scanner_profile = _scanner_profile,
+ .parquet_profile_cache = _parquet_profile_cache,
.push_down_agg_type = _push_down_agg_type,
.push_down_count_columns = _push_down_count_columns,
.condition_cache_digest = _condition_cache_digest,
diff --git
a/be/src/format_v2/table/iceberg_position_delete_sys_table_reader.cpp
b/be/src/format_v2/table/iceberg_position_delete_sys_table_reader.cpp
index cd84a255e69..03a4a69057d 100644
--- a/be/src/format_v2/table/iceberg_position_delete_sys_table_reader.cpp
+++ b/be/src/format_v2/table/iceberg_position_delete_sys_table_reader.cpp
@@ -35,6 +35,7 @@
#include "core/types.h"
#include "format/table/iceberg_delete_file_reader_helper.h"
#include "format/table/parquet_utils.h"
+#include "format_v2/parquet/parquet_profile.h"
#include "format_v2/table/iceberg_reader.h"
#include "format_v2/table/iceberg_schema_utils.h"
#include "runtime/descriptors.h"
@@ -323,6 +324,15 @@ Status
IcebergPositionDeleteSysTableV2Reader::_init_position_delete_reader() {
// readers share the scanner profile and initialize concurrently, so
lookup and creation
// must also be atomic to preserve the unique child-name invariant.
_position_reader_profile =
_scanner_profile->get_or_create_child(kPositionReaderProfile);
+ if (_parquet_profile_cache != nullptr) {
+ _position_profile_cache =
+
_parquet_profile_cache->get_child_cache(_position_reader_profile);
+ } else {
+ // Standalone callers have no scan-owned cache. Keep one across
this reader's splits.
+ _standalone_position_profile_cache =
+
std::make_unique<parquet::ParquetProfileCache>(_position_reader_profile);
+ _position_profile_cache = _standalone_position_profile_cache.get();
+ }
}
_position_reader = std::make_unique<PositionDeleteFileTableReader>();
RETURN_IF_ERROR(_position_reader->init({
@@ -333,6 +343,7 @@ Status
IcebergPositionDeleteSysTableV2Reader::_init_position_delete_reader() {
.io_ctx = _io_ctx,
.runtime_state = _runtime_state,
.scanner_profile = _position_reader_profile,
+ .parquet_profile_cache = _position_profile_cache,
.file_slot_descs = nullptr,
.push_down_agg_type = TPushAggOp::type::NONE,
.condition_cache_digest = 0,
diff --git a/be/src/format_v2/table/iceberg_position_delete_sys_table_reader.h
b/be/src/format_v2/table/iceberg_position_delete_sys_table_reader.h
index 09756daaabe..bbd535ed31f 100644
--- a/be/src/format_v2/table/iceberg_position_delete_sys_table_reader.h
+++ b/be/src/format_v2/table/iceberg_position_delete_sys_table_reader.h
@@ -77,6 +77,8 @@ private:
DeleteFileKind _delete_file_kind = DeleteFileKind::POSITION_DELETE;
std::unique_ptr<format::TableReader> _position_reader;
RuntimeProfile* _position_reader_profile = nullptr;
+ std::unique_ptr<parquet::ParquetProfileCache>
_standalone_position_profile_cache;
+ parquet::ParquetProfileCache* _position_profile_cache = nullptr;
std::vector<ReadColumn> _read_columns;
ColumnPtr _partition_value;
roaring::Roaring64Map _dv_positions;
diff --git a/be/src/format_v2/table/iceberg_reader.cpp
b/be/src/format_v2/table/iceberg_reader.cpp
index 88eb497c3dc..03de2fa308c 100644
--- a/be/src/format_v2/table/iceberg_reader.cpp
+++ b/be/src/format_v2/table/iceberg_reader.cpp
@@ -1547,7 +1547,7 @@ Status
IcebergTableReader::_create_delete_file_reader(const TIcebergDeleteFileDe
*reader = std::make_unique<format::parquet::ParquetReader>(
system_properties, file_description, io_ctx, _scanner_profile,
std::nullopt,
enable_mapping_timestamp_tz, enable_mapping_varbinary,
nullptr, -1, -1, "",
- preserve_binary_uuid());
+ preserve_binary_uuid(), _parquet_profile_cache);
} else {
*reader = std::make_unique<format::orc::OrcReader>(
system_properties, file_description, io_ctx, _scanner_profile,
std::nullopt,
diff --git a/be/src/format_v2/table/load_reader.cpp
b/be/src/format_v2/table/load_reader.cpp
index 2f592272045..1e501a3cb03 100644
--- a/be/src/format_v2/table/load_reader.cpp
+++ b/be/src/format_v2/table/load_reader.cpp
@@ -88,6 +88,7 @@ Status LoadTableReader::init(TableReadOptions&& options) {
.io_ctx = options.io_ctx,
.runtime_state = options.runtime_state,
.scanner_profile = options.scanner_profile,
+ .parquet_profile_cache =
options.parquet_profile_cache,
.file_slot_descs = &_source_file_slots,
.push_down_agg_type = TPushAggOp::NONE,
.condition_cache_digest = 0});
diff --git a/be/src/format_v2/table/paimon_reader.cpp
b/be/src/format_v2/table/paimon_reader.cpp
index 2ec6bde53f2..de373b1e5f8 100644
--- a/be/src/format_v2/table/paimon_reader.cpp
+++ b/be/src/format_v2/table/paimon_reader.cpp
@@ -496,6 +496,7 @@ Status
PaimonHybridReader::_init_child_reader(format::TableReader* reader,
.io_ctx = _io_ctx,
.runtime_state = _runtime_state,
.scanner_profile = _scanner_profile,
+ .parquet_profile_cache = _parquet_profile_cache,
.push_down_agg_type = _push_down_agg_type,
.push_down_count_columns = _push_down_count_columns,
.condition_cache_digest = _condition_cache_digest,
diff --git a/be/src/format_v2/table_reader.cpp
b/be/src/format_v2/table_reader.cpp
index 4750aef67f8..9e946ef65c2 100644
--- a/be/src/format_v2/table_reader.cpp
+++ b/be/src/format_v2/table_reader.cpp
@@ -1125,6 +1125,7 @@
TableReader::_find_table_column_identity_path_by_field_id(int32_t field_id,
Status TableReader::init(TableReadOptions&& options) {
_scanner_profile = options.scanner_profile;
+ _parquet_profile_cache = options.parquet_profile_cache;
if (_scanner_profile != nullptr) {
const auto hierarchy =
file_scan_profile::ensure_hierarchy(_scanner_profile);
static const char* table_profile = file_scan_profile::TABLE_READER;
@@ -1693,7 +1694,8 @@ Status
TableReader::create_file_reader(std::unique_ptr<FileReader>* reader) {
_system_properties, _current_task->data_file, _io_ctx,
_scanner_profile,
_global_rowid_context, enable_mapping_timestamp_tz,
enable_mapping_varbinary,
_current_task->file_context, _current_task->format_split_id,
- _current_task->format_split_id_end, hive_parquet_time_zone,
preserve_binary_uuid());
+ _current_task->format_split_id_end, hive_parquet_time_zone,
preserve_binary_uuid(),
+ _parquet_profile_cache);
return Status::OK();
}
if (_format == FileFormat::ORC) {
diff --git a/be/src/format_v2/table_reader.h b/be/src/format_v2/table_reader.h
index b71f1be3c99..1f8a6d9b29a 100644
--- a/be/src/format_v2/table_reader.h
+++ b/be/src/format_v2/table_reader.h
@@ -76,6 +76,10 @@ class RuntimeState;
namespace doris::format {
+namespace parquet {
+class ParquetProfileCache;
+}
+
using DeleteRows = std::vector<int64_t>;
// Row-level predicates on table/global schema. They are rewritten to
file-local expressions when
@@ -149,6 +153,7 @@ struct TableReadOptions {
std::shared_ptr<io::IOContext> io_ctx;
RuntimeState* runtime_state;
RuntimeProfile* scanner_profile;
+ parquet::ParquetProfileCache* parquet_profile_cache = nullptr;
// File formats without complete self-describing metadata, such as CSV,
Text, and JSON, need
// the FE-planned physical file slots to build their file-local schema and
deserialize values.
const std::vector<SlotDescriptor*>* file_slot_descs = nullptr;
@@ -2035,6 +2040,7 @@ protected:
std::shared_ptr<io::IOContext> _io_ctx;
RuntimeState* _runtime_state;
RuntimeProfile* _scanner_profile;
+ parquet::ParquetProfileCache* _parquet_profile_cache = nullptr;
const std::vector<SlotDescriptor*>* _file_slot_descs = nullptr;
FileFormat _format;
TPushAggOp::type _push_down_agg_type = TPushAggOp::type::NONE;
diff --git a/be/test/format_v2/parquet/parquet_profile_test.cpp
b/be/test/format_v2/parquet/parquet_profile_test.cpp
new file mode 100644
index 00000000000..bdb999b5993
--- /dev/null
+++ b/be/test/format_v2/parquet/parquet_profile_test.cpp
@@ -0,0 +1,169 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#include "format_v2/parquet/parquet_profile.h"
+
+#include <gtest/gtest.h>
+
+#include <array>
+#include <atomic>
+#include <chrono>
+#include <future>
+#include <memory>
+#include <thread>
+#include <vector>
+
+namespace doris::format::parquet {
+
+TEST(ParquetProfileCacheTest, InitializesLazilyAndKeepsCounterIdentity) {
+ RuntimeProfile profile("scan");
+ ParquetProfileCache cache(&profile);
+ EXPECT_EQ(profile.get_counter("ParquetReader"), nullptr);
+ const auto& counters = cache.get();
+ ASSERT_NE(counters.total_time, nullptr);
+ EXPECT_EQ(counters.total_time, profile.get_counter("ParquetReader"));
+ counters.total_time->update(17);
+ EXPECT_EQ(&cache.get(), &counters);
+ EXPECT_EQ(cache.get().total_time->value(), 17);
+}
+
+TEST(ParquetProfileCacheTest, ConcurrentReadersShareOneCounterSet) {
+ RuntimeProfile profile("scan");
+ ParquetProfileCache cache(&profile);
+ std::array<const ParquetProfile*, 16> handles {};
+ std::atomic<bool> start = false;
+ std::vector<std::thread> readers;
+ for (size_t i = 0; i < handles.size(); ++i) {
+ readers.emplace_back([&, i] {
+ while (!start.load(std::memory_order_acquire)) {
+ std::this_thread::yield();
+ }
+ handles[i] = &cache.get();
+ for (int file = 0; file < 100; ++file) {
+ cache.get().open_file_num->update(1);
+ }
+ });
+ }
+ start.store(true, std::memory_order_release);
+ for (auto& reader : readers) {
+ reader.join();
+ }
+ for (const auto* handle : handles) {
+ EXPECT_EQ(handle, handles[0]);
+ }
+ EXPECT_EQ(cache.get().open_file_num->value(), 1600);
+}
+
+TEST(ParquetProfileCacheTest,
ChildCachesShareAcrossThreadsButKeepProfilesSeparate) {
+ RuntimeProfile root("scan");
+ RuntimeProfile* child = root.get_or_create_child("nested");
+ RuntimeProfile* other = root.get_or_create_child("other");
+ ParquetProfileCache cache(&root);
+ std::array<ParquetProfileCache*, 16> children {};
+ std::vector<std::thread> readers;
+ for (size_t i = 0; i < children.size(); ++i) {
+ readers.emplace_back([&, i] { children[i] =
cache.get_child_cache(child); });
+ }
+ for (auto& reader : readers) {
+ reader.join();
+ }
+ for (auto* handle : children) {
+ EXPECT_EQ(handle, children[0]);
+ }
+ EXPECT_NE(cache.get_child_cache(other), children[0]);
+ EXPECT_NE(cache.get().total_time, children[0]->get().total_time);
+ EXPECT_EQ(child->get_counter("ParquetReader"),
children[0]->get().total_time);
+}
+
+TEST(ParquetProfileCacheTest,
CachedReadersDoNotAcquireCounterRegistrationLock) {
+ RuntimeProfile profile("scan");
+ ParquetProfileCache cache(&profile);
+ const auto* counters = &cache.get();
+ // BE tests compile with -fno-access-control, as do the existing
RuntimeProfile tests.
+ std::unique_lock registration_lock(profile._counter_map_lock);
+ std::promise<const ParquetProfile*> result;
+ auto future = result.get_future();
+ std::thread reader([&] { result.set_value(&cache.get()); });
+ const auto status = future.wait_for(std::chrono::seconds(5));
+ registration_lock.unlock();
+ reader.join();
+ EXPECT_EQ(status, std::future_status::ready);
+ EXPECT_EQ(future.get(), counters);
+}
+
+TEST(ParquetProfileCacheTest, ScanInstancesKeepIndependentCounters) {
+ RuntimeProfile first("first");
+ RuntimeProfile second("second");
+ ParquetProfileCache first_cache(&first);
+ ParquetProfileCache second_cache(&second);
+ first_cache.get().raw_rows_read->update(3);
+ EXPECT_NE(first_cache.get().raw_rows_read,
second_cache.get().raw_rows_read);
+ EXPECT_EQ(second_cache.get().raw_rows_read->value(), 0);
+}
+
+TEST(ParquetProfileCacheTest, NullProfileKeepsCountersDisabled) {
+ ParquetProfileCache cache(nullptr);
+ EXPECT_EQ(cache.get().total_time, nullptr);
+ EXPECT_EQ(cache.get().variant_profile().variant_reconstruction_time,
nullptr);
+ EXPECT_EQ(&cache.get(), &cache.get());
+}
+
+TEST(ParquetProfileCacheTest, VariantCountersOutliveScanProfile) {
+ ParquetProfile reader_counters;
+ {
+ RuntimeProfile profile("scan");
+ ParquetProfileCache cache(&profile);
+ reader_counters = cache.get();
+ }
+ // Deferred Variant materialization retains shared counters after the
scanner is destroyed.
+ ASSERT_NE(reader_counters.variant_profile().variant_reconstruction_time,
nullptr);
+ reader_counters.variant_profile().variant_reconstruction_time->update(23);
+
EXPECT_EQ(reader_counters.variant_profile().variant_reconstruction_time->value(),
23);
+ reader_counters.variant_profile().variant_direct_leaf_rows->update(7);
+
EXPECT_EQ(reader_counters.variant_profile().variant_direct_leaf_rows->value(),
7);
+}
+
+TEST(ParquetProfileCacheTest,
ColumnProfileCopiesRetainOneOwnerAfterScanDestruction) {
+ ParquetColumnReaderProfile reader;
+ std::weak_ptr<const ParquetVariantProfile> lifetime;
+ {
+ RuntimeProfile profile("scan");
+ ParquetProfileCache cache(&profile);
+ reader = cache.get().column_reader_profile();
+ lifetime = reader.variant_counters;
+ const auto& counter =
reader.variant_profile().variant_unshredded_direct_import_rows;
+ const auto references = counter.use_count();
+ auto copy = reader;
+ EXPECT_EQ(copy.variant_counters, reader.variant_counters);
+ EXPECT_EQ(counter.use_count(), references);
+
copy.variant_profile().variant_unshredded_direct_import_rows->update(9);
+ }
+ EXPECT_FALSE(lifetime.expired());
+
EXPECT_EQ(reader.variant_profile().variant_unshredded_direct_import_rows->value(),
9);
+ reader.variant_profile().variant_unshredded_direct_import_rows->update(3);
+
EXPECT_EQ(reader.variant_profile().variant_unshredded_direct_import_rows->value(),
12);
+ reader = {};
+ EXPECT_TRUE(lifetime.expired());
+}
+
+TEST(ParquetProfileCacheTest, DefaultColumnProfileHasDisabledVariantCounters) {
+ ParquetColumnReaderProfile reader;
+ EXPECT_EQ(reader.variant_profile().variant_unshredded_direct_import_time,
nullptr);
+ EXPECT_EQ(reader.variant_profile().variant_reconstruction_time, nullptr);
+}
+
+} // namespace doris::format::parquet
diff --git a/be/test/format_v2/parquet/parquet_reader_test.cpp
b/be/test/format_v2/parquet/parquet_reader_test.cpp
index ae195250a1d..3d036c26dce 100644
--- a/be/test/format_v2/parquet/parquet_reader_test.cpp
+++ b/be/test/format_v2/parquet/parquet_reader_test.cpp
@@ -2327,6 +2327,56 @@ TEST_F(NewParquetReaderTest,
UuidPlainDictionaryNullableAndMappingMatrix) {
}
}
+TEST_F(NewParquetReaderTest, InitializesProfileOnlyOncePerFile) {
+ class CountingParquetReader final : public format::parquet::ParquetReader {
+ public:
+ using ParquetReader::ParquetReader;
+ int profile_initializations = 0;
+
+ protected:
+ void _init_profile() override {
+ ++profile_initializations;
+ ParquetReader::_init_profile();
+ }
+ };
+ auto properties = std::make_shared<io::FileSystemProperties>();
+ properties->system_type = TFileType::FILE_LOCAL;
+ auto file = std::make_unique<io::FileDescription>();
+ file->path = _file_path;
+ file->file_size =
static_cast<int64_t>(std::filesystem::file_size(_file_path));
+ RuntimeProfile profile("scan");
+ CountingParquetReader reader(properties, file, nullptr, &profile);
+ RuntimeState state {TQueryOptions(), TQueryGlobals()};
+ ASSERT_TRUE(reader.init(&state).ok());
+ EXPECT_EQ(reader.profile_initializations, 1);
+ std::vector<format::ColumnDefinition> schema;
+ ASSERT_TRUE(reader.get_schema(&schema).ok());
+ EXPECT_EQ(schema.size(), 2);
+ EXPECT_TRUE(reader.close().ok());
+}
+
+TEST_F(NewParquetReaderTest, ReusesProfileHandlesAcrossFiles) {
+ RuntimeProfile profile("scan");
+ format::parquet::ParquetProfileCache cache(&profile);
+ RuntimeState state {TQueryOptions(), TQueryGlobals()};
+ for (int file_index = 0; file_index < 3; ++file_index) {
+ auto properties = std::make_shared<io::FileSystemProperties>();
+ properties->system_type = TFileType::FILE_LOCAL;
+ auto file = std::make_unique<io::FileDescription>();
+ file->path = _file_path;
+ file->file_size =
static_cast<int64_t>(std::filesystem::file_size(_file_path));
+ format::parquet::ParquetReader reader(properties, file, nullptr,
&profile, std::nullopt,
+ false, false, nullptr, -1, -1,
std::nullopt, false,
+ &cache);
+ ASSERT_TRUE(reader.init(&state).ok());
+ std::vector<format::ColumnDefinition> schema;
+ ASSERT_TRUE(reader.get_schema(&schema).ok());
+ EXPECT_EQ(schema.size(), 2);
+ ASSERT_TRUE(reader.close().ok());
+ EXPECT_EQ(cache.get().open_file_num->value(), file_index + 1);
+ }
+}
+
TEST_F(NewParquetReaderTest, GetSchemaReturnsFileLocalColumns) {
auto reader = create_reader();
RuntimeState state {TQueryOptions(), TQueryGlobals()};
diff --git a/be/test/format_v2/parquet/variant_column_reader_test.cpp
b/be/test/format_v2/parquet/variant_column_reader_test.cpp
index 0d179f5e5a7..b8423c1f74c 100644
--- a/be/test/format_v2/parquet/variant_column_reader_test.cpp
+++ b/be/test/format_v2/parquet/variant_column_reader_test.cpp
@@ -2553,10 +2553,10 @@ TEST(VariantColumnReaderTest,
ShreddedStateOutlivesScannerProfile) {
parquet_profile.init(runtime_profile.get());
ParquetProfile reused_profile;
reused_profile.init(runtime_profile.get());
- EXPECT_EQ(parquet_profile.variant_reconstructed_rows,
- reused_profile.variant_reconstructed_rows);
- EXPECT_EQ(parquet_profile.variant_unshredded_direct_import_rows,
- reused_profile.variant_unshredded_direct_import_rows);
+ EXPECT_EQ(parquet_profile.variant_profile().variant_reconstructed_rows,
+ reused_profile.variant_profile().variant_reconstructed_rows);
+
EXPECT_EQ(parquet_profile.variant_profile().variant_unshredded_direct_import_rows,
+
reused_profile.variant_profile().variant_unshredded_direct_import_rows);
auto visible_output =
make_nullable(std::make_shared<DataTypeVariantV2>())->create_column();
ASSERT_TRUE(materialize_variant_rows(shredded_int64_schema(),
shredded_int64_physical({7}),
diff --git a/be/test/format_v2/table/hudi_reader_test.cpp
b/be/test/format_v2/table/hudi_reader_test.cpp
index af72dcf1857..c2984430560 100644
--- a/be/test/format_v2/table/hudi_reader_test.cpp
+++ b/be/test/format_v2/table/hudi_reader_test.cpp
@@ -38,6 +38,7 @@
#include "core/data_type/data_type_struct.h"
#include "core/field.h"
#include "format_v2/column_data.h"
+#include "format_v2/parquet/parquet_reader.h"
#include "gen_cpp/ExternalTableSchema_types.h"
#include "gen_cpp/PlanNodes_types.h"
#include "io/io_common.h"
@@ -156,9 +157,51 @@ public:
int refresh_count = 0;
};
+class ProfileCacheTrackingTableReader final : public TableReader {
+public:
+ Status init(TableReadOptions&& options) override {
+ observed_cache = options.parquet_profile_cache;
+ return TableReader::init(std::move(options));
+ }
+
+ Status prepare_split(const SplitReadOptions&) override { return
Status::OK(); }
+
+ parquet::ParquetProfileCache* observed_cache = nullptr;
+};
+
// Scenario: FileScannerV2 Hudi native reader uses the split schema id to
annotate the physical
// file schema before TableColumnMapper runs. This keeps schema-evolved Hudi
files on field-id
// mapping, including renamed nested children.
+TEST(HudiReaderTest, CustomParquetFactoryReusesScannerCounters) {
+ RuntimeProfile profile("hudi");
+ parquet::ParquetProfileCache cache(&profile);
+ TQueryOptions options;
+ TQueryGlobals globals;
+ RuntimeState state(options, globals);
+ TFileScanRangeParams scan_params;
+ hudi::HudiReader reader;
+ ASSERT_TRUE(reader.init({.projected_columns = {},
+ .conjuncts = {},
+ .format = FileFormat::PARQUET,
+ .scan_params = &scan_params,
+ .io_ctx = nullptr,
+ .runtime_state = &state,
+ .scanner_profile = &profile,
+ .parquet_profile_cache = &cache})
+ .ok());
+ reader._current_task = std::make_unique<ScanTask>();
+ for (int i = 0; i < 2; ++i) {
+ reader._current_task->data_file =
std::make_unique<io::FileDescription>();
+ std::unique_ptr<FileReader> file;
+ ASSERT_TRUE(reader.create_file_reader(&file).ok());
+ auto* parquet = dynamic_cast<parquet::ParquetReader*>(file.get());
+ ASSERT_NE(parquet, nullptr);
+ EXPECT_EQ(parquet->_profile_cache, &cache);
+ parquet->_init_profile();
+ EXPECT_EQ(parquet->_parquet_profile.total_time,
cache.get().total_time);
+ }
+}
+
TEST(HudiReaderTest, AnnotatesFileSchemaFromSplitHistorySchema) {
TFileScanRangeParams scan_params;
scan_params.__set_current_schema_id(200);
@@ -277,6 +320,37 @@ TEST(HudiHybridReaderTest,
AdaptiveBatchSizeReachesBothChildReaders) {
EXPECT_EQ(child_batch_sizes.second, 123);
}
+TEST(HudiHybridReaderTest, ForwardsParquetProfileCacheToNativeChild) {
+ RuntimeProfile profile("hudi_hybrid");
+ parquet::ParquetProfileCache cache(&profile);
+ TFileScanRangeParams scan_params;
+ scan_params.__set_format_type(TFileFormatType::FORMAT_PARQUET);
+ hudi::HudiHybridReader reader;
+ ProfileCacheTrackingTableReader* native = nullptr;
+ reader.TEST_set_child_reader_factories(
+ [&] {
+ auto child =
std::make_unique<ProfileCacheTrackingTableReader>();
+ native = child.get();
+ return child;
+ },
+ [] { return std::make_unique<TableReader>(); });
+ ASSERT_TRUE(reader.init({.projected_columns = {},
+ .conjuncts = {},
+ .format = FileFormat::PARQUET,
+ .scan_params = &scan_params,
+ .io_ctx = nullptr,
+ .runtime_state = nullptr,
+ .scanner_profile = &profile,
+ .parquet_profile_cache = &cache})
+ .ok());
+ SplitReadOptions split;
+ split.current_split_format = FileFormat::PARQUET;
+ split.current_range.__set_format_type(TFileFormatType::FORMAT_PARQUET);
+ ASSERT_TRUE(reader.prepare_split(split).ok());
+ ASSERT_NE(native, nullptr);
+ EXPECT_EQ(native->observed_cache, &cache);
+}
+
TEST(HudiHybridReaderTest, AggregatesConditionCacheHitsFromBothChildren) {
hudi::HudiHybridReader reader;
reader.TEST_install_batch_size_children();
diff --git
a/be/test/format_v2/table/iceberg_position_delete_sys_table_reader_test.cpp
b/be/test/format_v2/table/iceberg_position_delete_sys_table_reader_test.cpp
index 8cf538eb0cf..2fde7a6fba6 100644
--- a/be/test/format_v2/table/iceberg_position_delete_sys_table_reader_test.cpp
+++ b/be/test/format_v2/table/iceberg_position_delete_sys_table_reader_test.cpp
@@ -22,6 +22,8 @@
#include "core/data_type/data_type_number.h"
#include "core/data_type/data_type_struct.h"
#include "format/table/iceberg_scan_semantics.h"
+#include "format_v2/parquet/parquet_profile.h"
+#include "format_v2/parquet/parquet_reader.h"
#include "runtime/runtime_profile.h"
#include "runtime/runtime_state.h"
@@ -62,6 +64,49 @@ TEST(IcebergPositionDeleteSysTableV2ProfileTest,
UsesDistinctProfileForNestedPos
reader._position_reader_profile);
}
+TEST(IcebergPositionDeleteSysTableV2ProfileTest,
ReusesChildCountersAcrossSplitsAndScanners) {
+ RuntimeState state {TQueryOptions(), TQueryGlobals()};
+ RuntimeProfile profile("position_delete_system_table_profile");
+ parquet::ParquetProfileCache outer_cache(&profile);
+ TFileScanRangeParams params;
+ std::vector<SlotDescriptor*> file_slot_descs;
+ TIcebergDeleteFileDesc delete_file;
+ delete_file.__set_content(1);
+ delete_file.__set_file_format(TFileFormatType::FORMAT_PARQUET);
+ delete_file.__set_path("/not-opened-during-prepare.parquet");
+ parquet::ParquetProfileCache* shared_child = nullptr;
+ for (int scanner = 0; scanner < 2; ++scanner) {
+ IcebergPositionDeleteSysTableV2Reader reader;
+ reader._runtime_state = &state;
+ reader._scanner_profile = &profile;
+ reader._parquet_profile_cache = &outer_cache;
+ reader._scan_params = ¶ms;
+ reader._file_slot_descs = &file_slot_descs;
+ for (int split = 0; split < 2; ++split) {
+ reader._current_range = range_with_delete_file(delete_file);
+ ASSERT_TRUE(reader._init_split().ok());
+ auto* cache = reader._position_reader->_parquet_profile_cache;
+ ASSERT_NE(cache, nullptr);
+ ASSERT_NE(cache, &outer_cache);
+ if (shared_child == nullptr) {
+ shared_child = cache;
+ }
+ EXPECT_EQ(cache, shared_child);
+ std::unique_ptr<format::FileReader> file;
+
ASSERT_TRUE(reader._position_reader->create_file_reader(&file).ok());
+ auto* parquet_reader =
dynamic_cast<parquet::ParquetReader*>(file.get());
+ ASSERT_NE(parquet_reader, nullptr);
+ EXPECT_EQ(parquet_reader->_profile_cache, shared_child);
+ parquet_reader->_init_profile();
+ EXPECT_EQ(parquet_reader->_parquet_profile.total_time,
shared_child->get().total_time);
+ }
+ }
+ EXPECT_EQ(profile.get_counter("FileFooterReadCalls"), nullptr);
+ EXPECT_NE(profile.get_child("IcebergPositionDeleteFileReader")
+ ->get_counter("FileFooterReadCalls"),
+ nullptr);
+}
+
TEST(IcebergPositionDeleteSysTableV2ProfileTest,
PreparesNestedRowInitialDefaults) {
IcebergPositionDeleteSysTableV2Reader reader;
TFileScanRangeParams params;
diff --git a/be/test/format_v2/table/iceberg_reader_test.cpp
b/be/test/format_v2/table/iceberg_reader_test.cpp
index b8193879e4c..2b7a5fad5dd 100644
--- a/be/test/format_v2/table/iceberg_reader_test.cpp
+++ b/be/test/format_v2/table/iceberg_reader_test.cpp
@@ -75,6 +75,7 @@
#include "format/format_common.h"
#include "format/orc/orc_memory_stream_test.h"
#include "format/table/deletion_vector_reader.h"
+#include "format_v2/parquet/parquet_reader.h"
#include "format_v2/table_reader.h"
#include "gen_cpp/Exprs_types.h"
#include "gen_cpp/ExternalTableSchema_types.h"
@@ -5971,5 +5972,41 @@ TEST(IcebergV2ReaderTest,
IcebergEqualityDeleteFileSizeUnknownFallsBackToStat) {
std::filesystem::remove_all(test_dir);
}
+TEST(IcebergTableReaderTest, DeleteParquetFactoryReusesScannerCounters) {
+ RuntimeProfile profile("iceberg");
+ doris::format::parquet::ParquetProfileCache cache(&profile);
+ doris::format::iceberg::IcebergTableReader reader;
+ reader._scanner_profile = &profile;
+ reader._parquet_profile_cache = &cache;
+ TFileScanRangeParams params;
+ TQueryOptions options;
+ TQueryGlobals globals;
+ RuntimeState state(options, globals);
+ reader._runtime_state = &state;
+ IcebergDeleteFileIOContext io_context(&state);
+ const auto file_path =
+ (std::filesystem::temp_directory_path() /
+ ("iceberg_profile_" +
+
std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()) +
+ ".parquet"))
+ .string();
+ write_single_int_parquet_file(file_path, "id", {1}, 1);
+ for (int i = 0; i < 2; ++i) {
+ TIcebergDeleteFileDesc desc;
+ desc.__set_file_format(TFileFormatType::FORMAT_PARQUET);
+ desc.__set_path(file_path);
+ desc.__set_file_size(std::filesystem::file_size(file_path));
+ std::unique_ptr<doris::format::FileReader> file;
+ const auto status = reader._create_delete_file_reader(desc, params,
&io_context, &file);
+ ASSERT_TRUE(status.ok()) << status.to_string();
+ auto* parquet =
dynamic_cast<doris::format::parquet::ParquetReader*>(file.get());
+ ASSERT_NE(parquet, nullptr);
+ EXPECT_EQ(parquet->_profile_cache, &cache);
+ parquet->_init_profile();
+ EXPECT_EQ(parquet->_parquet_profile.total_time,
cache.get().total_time);
+ }
+ std::filesystem::remove(file_path);
+}
+
} // namespace
} // namespace doris::format
diff --git a/be/test/format_v2/table/load_reader_test.cpp
b/be/test/format_v2/table/load_reader_test.cpp
index a88931bd4d6..6b1c3c399d8 100644
--- a/be/test/format_v2/table/load_reader_test.cpp
+++ b/be/test/format_v2/table/load_reader_test.cpp
@@ -30,6 +30,7 @@
#include "core/data_type/data_type_nullable.h"
#include "core/data_type/data_type_number.h"
#include "core/data_type/data_type_string.h"
+#include "format_v2/parquet/parquet_profile.h"
#include "io/fs/kafka_consumer_pipe.h"
#include "load/stream_load/new_load_stream_mgr.h"
#include "load/stream_load/stream_load_context.h"
@@ -130,6 +131,7 @@ public:
TestLoadTableReader(std::unique_ptr<SourceReader> source_reader,
TupleDescriptor* source,
TupleDescriptor* destination)
: LoadTableReader(source, destination),
_input(std::move(source_reader)) {}
+ parquet::ParquetProfileCache* parquet_profile_cache() const { return
_parquet_profile_cache; }
Status init(TableReadOptions&& options) override {
RETURN_IF_ERROR(LoadTableReader::init(std::move(options)));
_input->columns = _projected_columns;
@@ -236,7 +238,8 @@ protected:
cast.__set_num_children(1);
params.expr_of_dest_slot[20].nodes.insert(params.expr_of_dest_slot[20].nodes.begin(),
cast);
}
- Status init(LoadTableReader& reader, FileFormat format = FileFormat::CSV) {
+ Status init(LoadTableReader& reader, FileFormat format = FileFormat::CSV,
+ parquet::ParquetProfileCache* parquet_profile_cache = nullptr)
{
TTupleDescriptor tuple;
tuple.__set_id(0);
tuple.__set_byteSize(0);
@@ -259,7 +262,8 @@ protected:
.scan_params = ¶ms,
.io_ctx = io,
.runtime_state = &state,
- .scanner_profile = &profile});
+ .scanner_profile = &profile,
+ .parquet_profile_cache = parquet_profile_cache});
}
void add_partial_update_columns(bool key) {
TTupleDescriptor tuple;
@@ -340,6 +344,15 @@ protected:
std::unique_ptr<SlotDescriptor> source_slot, destination_slot;
};
+TEST_F(LoadReaderTest, ForwardsParquetProfileCacheToBaseReader) {
+ parquet::ParquetProfileCache cache(&profile);
+ TestLoadTableReader reader(std::make_unique<SourceReader>(), source.get(),
destination.get());
+ const auto status = init(reader, FileFormat::PARQUET, &cache);
+ ASSERT_TRUE(status.ok()) << status;
+ EXPECT_EQ(reader.parquet_profile_cache(), &cache);
+ ASSERT_TRUE(reader.close().ok());
+}
+
class LoadJsonPipeTest : public LoadReaderTest {
protected:
void SetUp() override {
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]