This is an automated email from the ASF dual-hosted git repository.
bobhan1 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/master by this push:
new f45888fe98b [improvement](topn) Add option to skip file cache writes
in topn lazy materialization (#65021)
f45888fe98b is described below
commit f45888fe98b45ae171f77021d408fa8e1842a285
Author: bobhan1 <[email protected]>
AuthorDate: Tue Jul 7 09:54:49 2026 +0800
[improvement](topn) Add option to skip file cache writes in topn lazy
materialization (#65021)
## Summary
This PR adds an opt-in session variable for TopN lazy materialization
phase-2 file-cache miss handling.
- add `enable_topn_lazy_mat_phase2_no_write_file_cache`
- keep the option disabled by default, so existing behavior is unchanged
- when the option is enabled, phase-2 lazy materialization uses
already-downloaded file-cache blocks for full local hits and reads
remote data directly on cache miss without writing the file cache
- expose aggregate and per-BE phase-2 rows, segments, and file-cache
counters in the `MaterializeNode` profile
- accumulate per-BE stats across multiple phase-2 fetch calls
- cover both row-store and column-store fetch paths
## Validation
- `./build.sh --be --fe --cloud -j100`
- before packaging, verified no `output/fe/conf/fe_custom.conf`, no
`output/be/conf/be_custom.conf`, empty `output/fe/doris-meta`, and empty
`output/be/storage`
- for docker regression image, verified FE/BE debug-point config and BE
Java support config:
- `output/fe/conf/fe.conf`: `enable_debug_points=true`
- `output/be/conf/be.conf`: `enable_debug_points=true`,
`enable_java_support=false`
- `docker build -f docker/runtime/doris-compose/Dockerfile -t
bh-cluster-2 .`
- `env -u HTTP_PROXY -u HTTPS_PROXY -u http_proxy -u https_proxy -u
ALL_PROXY -u all_proxy ./run-regression-test.sh --run -d
cloud_p0/cache/topn_lazy_file_cache -s
test_topn_lazy_mat_phase2_no_write_file_cache -g docker -runMode=cloud
-dockerSuiteParallel 1`
- `All suites success`
- `Test 1 suites, failed 0 suites, fatal 0 scripts, skipped 0 scripts`
- `./run-be-ut.sh --run
--filter=MaterializationSharedStateTest.*:BlockFileCacheTest.get_downloaded_blocks_if_fully_covered_is_read_only:BlockFileCacheTest.cached_remote_file_reader_remote_only_on_miss:BlockFileCacheTest.fd_cache_remove:BlockFileCacheTest.fd_cache_evict
-j100`
- `PASSED 9 tests`
---
be/src/exec/operator/materialization_opertor.cpp | 209 ++++++++++++++-
be/src/exec/operator/materialization_opertor.h | 27 +-
be/src/exec/rowid_fetcher.cpp | 74 +++++-
be/src/exec/rowid_fetcher.h | 19 +-
be/src/io/cache/block_file_cache.cpp | 91 +++++++
be/src/io/cache/block_file_cache.h | 8 +
be/src/io/cache/cached_remote_file_reader.cpp | 93 +++++++
be/src/io/cache/cached_remote_file_reader.h | 17 ++
be/src/io/io_common.h | 6 +
.../bloom_filter/bloom_filter_index_reader.cpp | 18 +-
.../index/bloom_filter/bloom_filter_index_reader.h | 19 +-
be/src/storage/index/indexed_column_reader.cpp | 23 +-
be/src/storage/index/indexed_column_reader.h | 18 +-
be/src/storage/index/ordinal_page_index.cpp | 15 +-
be/src/storage/index/ordinal_page_index.h | 10 +-
be/src/storage/index/zone_map/zone_map_index.cpp | 14 +-
be/src/storage/index/zone_map/zone_map_index.h | 6 +-
be/src/storage/segment/column_reader.cpp | 11 +-
be/src/storage/segment/segment.cpp | 7 +-
be/src/storage/tablet/base_tablet.cpp | 18 +-
be/src/storage/tablet/base_tablet.h | 2 +-
.../operator/materialization_shared_state_test.cpp | 86 +++---
be/test/io/cache/block_file_cache_test.cpp | 134 ++++++++++
.../java/org/apache/doris/qe/SessionVariable.java | 12 +
gensrc/proto/internal_service.proto | 14 +
gensrc/thrift/PaloInternalService.thrift | 1 +
...topn_lazy_mat_phase2_no_write_file_cache.groovy | 294 +++++++++++++++++++++
27 files changed, 1136 insertions(+), 110 deletions(-)
diff --git a/be/src/exec/operator/materialization_opertor.cpp
b/be/src/exec/operator/materialization_opertor.cpp
index 7ceeae0f261..1a475af0bcc 100644
--- a/be/src/exec/operator/materialization_opertor.cpp
+++ b/be/src/exec/operator/materialization_opertor.cpp
@@ -21,8 +21,12 @@
#include <fmt/format.h>
#include <gen_cpp/internal_service.pb.h>
+#include <set>
+#include <sstream>
#include <utility>
+#include "cloud/config.h"
+#include "common/config.h"
#include "common/status.h"
#include "core/block/block.h"
#include "core/column/column.h"
@@ -31,9 +35,107 @@
#include "exec/scan/file_scanner.h"
#include "util/brpc_client_cache.h"
#include "util/brpc_closure.h"
+#include "util/pretty_printer.h"
namespace doris {
+namespace {
+
+constexpr const char* TOPN_LAZY_MAT_PHASE2_PER_BACKEND =
+ "TopNLazyMaterializationSecondPhasePerBackend";
+constexpr const char* TOPN_LAZY_MAT_PHASE2_PER_BACKEND_ROWS_READ =
+ "TopNLazyMaterializationSecondPhasePerBackendRowsRead";
+constexpr const char* TOPN_LAZY_MAT_PHASE2_PER_BACKEND_SEGMENTS_READ =
+ "TopNLazyMaterializationSecondPhasePerBackendSegmentsRead";
+constexpr const char* TOPN_LAZY_MAT_PHASE2_PER_BACKEND_LOCAL_IO_COUNT =
+ "TopNLazyMaterializationSecondPhasePerBackendLocalIOCount";
+constexpr const char* TOPN_LAZY_MAT_PHASE2_PER_BACKEND_LOCAL_IO_BYTES =
+ "TopNLazyMaterializationSecondPhasePerBackendLocalIOBytes";
+constexpr const char* TOPN_LAZY_MAT_PHASE2_PER_BACKEND_REMOTE_IO_COUNT =
+ "TopNLazyMaterializationSecondPhasePerBackendRemoteIOCount";
+constexpr const char* TOPN_LAZY_MAT_PHASE2_PER_BACKEND_REMOTE_IO_BYTES =
+ "TopNLazyMaterializationSecondPhasePerBackendRemoteIOBytes";
+constexpr const char* TOPN_LAZY_MAT_PHASE2_PER_BACKEND_SKIP_CACHE_IO_COUNT =
+ "TopNLazyMaterializationSecondPhasePerBackendSkipCacheIOCount";
+constexpr const char* TOPN_LAZY_MAT_PHASE2_PER_BACKEND_WRITE_CACHE_BYTES =
+ "TopNLazyMaterializationSecondPhasePerBackendWriteCacheBytes";
+constexpr const char* TOPN_LAZY_MAT_PHASE2_PER_BACKEND_LOCAL_IO_TIME =
+ "TopNLazyMaterializationSecondPhasePerBackendLocalIOTime";
+constexpr const char* TOPN_LAZY_MAT_PHASE2_PER_BACKEND_REMOTE_IO_TIME =
+ "TopNLazyMaterializationSecondPhasePerBackendRemoteIOTime";
+constexpr const char* TOPN_LAZY_MAT_PHASE2_PER_BACKEND_WRITE_CACHE_IO_TIME =
+ "TopNLazyMaterializationSecondPhasePerBackendWriteCacheIOTime";
+
+void update_counter(RuntimeProfile* profile, const std::string& name,
TUnit::type unit,
+ int64_t value) {
+ COUNTER_UPDATE(ADD_COUNTER_WITH_LEVEL(profile, name, unit, 2), value);
+}
+
+void update_topn_lazy_materialization_profile(RuntimeProfile* profile,
+ const
PTopNLazyMaterializationFileCacheStats& stats) {
+ if (profile == nullptr) {
+ return;
+ }
+ update_counter(profile,
RowIdStorageReader::TopNLazyMaterializationSecondPhaseLocalIOCount,
+ TUnit::UNIT, stats.local_io_count());
+ update_counter(profile,
RowIdStorageReader::TopNLazyMaterializationSecondPhaseLocalIOBytes,
+ TUnit::BYTES, stats.local_io_bytes());
+ update_counter(profile,
RowIdStorageReader::TopNLazyMaterializationSecondPhaseRemoteIOCount,
+ TUnit::UNIT, stats.remote_io_count());
+ update_counter(profile,
RowIdStorageReader::TopNLazyMaterializationSecondPhaseRemoteIOBytes,
+ TUnit::BYTES, stats.remote_io_bytes());
+ update_counter(profile,
RowIdStorageReader::TopNLazyMaterializationSecondPhaseSkipCacheIOCount,
+ TUnit::UNIT, stats.skip_cache_io_count());
+ update_counter(profile,
RowIdStorageReader::TopNLazyMaterializationSecondPhaseWriteCacheBytes,
+ TUnit::BYTES, stats.write_cache_bytes());
+ update_counter(profile,
RowIdStorageReader::TopNLazyMaterializationSecondPhaseLocalIOTime,
+ TUnit::TIME_NS, stats.local_io_time());
+ update_counter(profile,
RowIdStorageReader::TopNLazyMaterializationSecondPhaseRemoteIOTime,
+ TUnit::TIME_NS, stats.remote_io_time());
+ update_counter(profile,
RowIdStorageReader::TopNLazyMaterializationSecondPhaseWriteCacheIOTime,
+ TUnit::TIME_NS, stats.write_cache_io_time());
+}
+
+int64_t count_request_rows(const PMultiGetRequestV2& request) {
+ int64_t rows = 0;
+ for (const auto& request_block_desc : request.request_block_descs()) {
+ rows += request_block_desc.row_id_size();
+ }
+ return rows;
+}
+
+int64_t count_request_segments(const PMultiGetRequestV2& request) {
+ std::set<uint32_t> file_ids;
+ for (const auto& request_block_desc : request.request_block_descs()) {
+ DCHECK_EQ(request_block_desc.file_id_size(),
request_block_desc.row_id_size());
+ for (const auto file_id : request_block_desc.file_id()) {
+ file_ids.insert(file_id);
+ }
+ }
+ return file_ids.size();
+}
+
+template <typename AppendValue>
+std::string format_array(size_t size, AppendValue append_value) {
+ std::stringstream values;
+ values << "[";
+ for (size_t i = 0; i < size; ++i) {
+ append_value(values, i);
+ values << ", ";
+ }
+ values << "]";
+ return values.str();
+}
+
+template <typename GetValue>
+std::string format_counter_array(size_t size, TUnit::type unit, GetValue
get_value) {
+ return format_array(size, [&](std::stringstream& values, size_t i) {
+ values << PrettyPrinter::print(static_cast<int64_t>(get_value(i)),
unit);
+ });
+}
+
+} // namespace
+
void MaterializationSharedState::get_block(Block* block) {
for (int i = 0, j = 0, rowid_to_block_loc = rowid_locs[j]; i <
origin_block.columns(); i++) {
if (i != rowid_to_block_loc) {
@@ -53,6 +155,98 @@ void MaterializationSharedState::get_block(Block* block) {
origin_block.clear();
}
+void MaterializationSharedState::_update_topn_lazy_materialization_profile(
+ RuntimeProfile* profile) {
+ DORIS_CHECK(profile != nullptr);
+ for (const auto& [backend_id, rpc_struct] : rpc_struct_map) {
+ const int64_t rows_read = count_request_rows(rpc_struct.request);
+ const int64_t segments_read =
count_request_segments(rpc_struct.request);
+ update_counter(profile,
RowIdStorageReader::TopNLazyMaterializationSecondPhaseRowsRead,
+ TUnit::UNIT, rows_read);
+ update_counter(profile,
RowIdStorageReader::TopNLazyMaterializationSecondPhaseSegmentsRead,
+ TUnit::UNIT, segments_read);
+
+ auto& stats = _topn_lazy_materialization_backend_stats[backend_id];
+ if (stats.backend.empty()) {
+ stats.backend = rpc_struct.backend_address.empty() ?
fmt::format("id={}", backend_id)
+ :
rpc_struct.backend_address;
+ }
+ stats.rows_read += rows_read;
+ stats.segments_read += segments_read;
+ if
(!rpc_struct.response.has_topn_lazy_materialization_file_cache_stats()) {
+ continue;
+ }
+
+ const auto& file_cache_stats =
+
rpc_struct.response.topn_lazy_materialization_file_cache_stats();
+ update_topn_lazy_materialization_profile(profile, file_cache_stats);
+ stats.local_io_count += file_cache_stats.local_io_count();
+ stats.local_io_bytes += file_cache_stats.local_io_bytes();
+ stats.remote_io_count += file_cache_stats.remote_io_count();
+ stats.remote_io_bytes += file_cache_stats.remote_io_bytes();
+ stats.skip_cache_io_count += file_cache_stats.skip_cache_io_count();
+ stats.write_cache_bytes += file_cache_stats.write_cache_bytes();
+ stats.local_io_time += file_cache_stats.local_io_time();
+ stats.remote_io_time += file_cache_stats.remote_io_time();
+ stats.write_cache_io_time += file_cache_stats.write_cache_io_time();
+ }
+
+ std::vector<const TopNLazyMaterializationBackendStats*> stats;
+ stats.reserve(_topn_lazy_materialization_backend_stats.size());
+ for (const auto& [_, backend_stats] :
_topn_lazy_materialization_backend_stats) {
+ stats.push_back(&backend_stats);
+ }
+
+ const size_t size = stats.size();
+ profile->add_info_string(TOPN_LAZY_MAT_PHASE2_PER_BACKEND,
+ format_array(size, [&](std::stringstream& values,
size_t i) {
+ values << stats[i]->backend;
+ }));
+ profile->add_info_string(
+ TOPN_LAZY_MAT_PHASE2_PER_BACKEND_ROWS_READ,
+ format_counter_array(size, TUnit::UNIT, [&](size_t i) { return
stats[i]->rows_read; }));
+ profile->add_info_string(TOPN_LAZY_MAT_PHASE2_PER_BACKEND_SEGMENTS_READ,
+ format_counter_array(size, TUnit::UNIT,
[&](size_t i) {
+ return stats[i]->segments_read;
+ }));
+ profile->add_info_string(TOPN_LAZY_MAT_PHASE2_PER_BACKEND_LOCAL_IO_COUNT,
+ format_counter_array(size, TUnit::UNIT,
[&](size_t i) {
+ return stats[i]->local_io_count;
+ }));
+ profile->add_info_string(TOPN_LAZY_MAT_PHASE2_PER_BACKEND_LOCAL_IO_BYTES,
+ format_counter_array(size, TUnit::BYTES,
[&](size_t i) {
+ return stats[i]->local_io_bytes;
+ }));
+ profile->add_info_string(TOPN_LAZY_MAT_PHASE2_PER_BACKEND_REMOTE_IO_COUNT,
+ format_counter_array(size, TUnit::UNIT,
[&](size_t i) {
+ return stats[i]->remote_io_count;
+ }));
+ profile->add_info_string(TOPN_LAZY_MAT_PHASE2_PER_BACKEND_REMOTE_IO_BYTES,
+ format_counter_array(size, TUnit::BYTES,
[&](size_t i) {
+ return stats[i]->remote_io_bytes;
+ }));
+
profile->add_info_string(TOPN_LAZY_MAT_PHASE2_PER_BACKEND_SKIP_CACHE_IO_COUNT,
+ format_counter_array(size, TUnit::UNIT,
[&](size_t i) {
+ return stats[i]->skip_cache_io_count;
+ }));
+
profile->add_info_string(TOPN_LAZY_MAT_PHASE2_PER_BACKEND_WRITE_CACHE_BYTES,
+ format_counter_array(size, TUnit::BYTES,
[&](size_t i) {
+ return stats[i]->write_cache_bytes;
+ }));
+ profile->add_info_string(TOPN_LAZY_MAT_PHASE2_PER_BACKEND_LOCAL_IO_TIME,
+ format_counter_array(size, TUnit::TIME_NS,
[&](size_t i) {
+ return stats[i]->local_io_time;
+ }));
+ profile->add_info_string(TOPN_LAZY_MAT_PHASE2_PER_BACKEND_REMOTE_IO_TIME,
+ format_counter_array(size, TUnit::TIME_NS,
[&](size_t i) {
+ return stats[i]->remote_io_time;
+ }));
+
profile->add_info_string(TOPN_LAZY_MAT_PHASE2_PER_BACKEND_WRITE_CACHE_IO_TIME,
+ format_counter_array(size, TUnit::TIME_NS,
[&](size_t i) {
+ return stats[i]->write_cache_io_time;
+ }));
+}
+
// Merges RPC responses from multiple BEs into `response_blocks` in the
original row order.
//
// After parallel multiget_data_v2 RPCs complete, each BE's response contains
a partial block
@@ -63,7 +257,9 @@ void MaterializationSharedState::get_block(Block* block) {
// rpc_struct_map[backend_id].response (per-BE partial blocks, unordered
across BEs)
// + block_order_results[i][j] (maps each output row → its source
backend_id)
// → response_blocks[i] (final merged result in original
TopN row order)
-Status MaterializationSharedState::merge_multi_response() {
+Status MaterializationSharedState::merge_multi_response(RuntimeProfile*
profile) {
+ _update_topn_lazy_materialization_profile(profile);
+
// Outer loop: iterate over each relation (i.e., each rowid column /
table).
// A query with lazy materialization on 2 tables would have
block_order_results.size() == 2,
// each with its own set of response_blocks and RPC request_block_descs.
@@ -265,6 +461,9 @@ Status MaterializationSharedState::init_multi_requests(
// Initialize the base struct of PMultiGetRequestV2
multi_get_request.set_be_exec_version(state->be_exec_version());
multi_get_request.set_wg_id(state->get_query_ctx()->workload_group()->id());
+ multi_get_request.set_file_cache_remote_only_on_miss(
+ config::is_cloud_mode() &&
+
state->query_options().enable_topn_lazy_mat_phase2_no_write_file_cache);
auto* query_id = multi_get_request.mutable_query_id();
query_id->set_hi(state->query_id().hi);
query_id->set_lo(state->query_id().lo);
@@ -315,7 +514,10 @@ Status MaterializationSharedState::init_multi_requests(
FetchRpcStruct {.stub = std::move(client),
.cntl =
std::make_unique<brpc::Controller>(),
.request = multi_get_request,
- .response =
PMultiGetResponseV2()});
+ .response =
PMultiGetResponseV2(),
+ .backend_address = fmt::format(
+ "id={} {}:{}",
node_info.id, node_info.host,
+
node_info.async_internal_port)});
}
return Status::OK();
@@ -427,7 +629,8 @@ Status MaterializationOperator::push(RuntimeState* state,
Block* in_block, bool
if (local_state._materialization_state.need_merge_block) {
SCOPED_TIMER(local_state._merge_response_timer);
-
RETURN_IF_ERROR(local_state._materialization_state.merge_multi_response());
+
RETURN_IF_ERROR(local_state._materialization_state.merge_multi_response(
+ local_state.operator_profile()));
local_state._max_rows_per_backend_counter->set(
(int64_t)local_state._materialization_state._max_rows_per_backend);
}
diff --git a/be/src/exec/operator/materialization_opertor.h
b/be/src/exec/operator/materialization_opertor.h
index 5cf2bf1ee9e..889d94a11ac 100644
--- a/be/src/exec/operator/materialization_opertor.h
+++ b/be/src/exec/operator/materialization_opertor.h
@@ -19,6 +19,10 @@
#include <stdint.h>
+#include <map>
+#include <string>
+#include <unordered_map>
+
#include "common/status.h"
#include "exec/operator/operator.h"
@@ -32,6 +36,7 @@ struct FetchRpcStruct {
std::unique_ptr<brpc::Controller> cntl;
PMultiGetRequestV2 request;
PMultiGetResponseV2 response;
+ std::string backend_address;
};
struct MaterializationSharedState {
@@ -41,11 +46,27 @@ public:
Status init_multi_requests(const TMaterializationNode& tnode,
RuntimeState* state);
Status create_muiltget_result(const Columns& columns, bool eos);
- Status merge_multi_response();
+ Status merge_multi_response(RuntimeProfile* profile);
void get_block(Block* block);
private:
void _update_profile_info(int64_t backend_id, RuntimeProfile*
response_profile);
+ void _update_topn_lazy_materialization_profile(RuntimeProfile* profile);
+
+ struct TopNLazyMaterializationBackendStats {
+ std::string backend;
+ int64_t rows_read = 0;
+ int64_t segments_read = 0;
+ int64_t local_io_count = 0;
+ int64_t local_io_bytes = 0;
+ int64_t remote_io_count = 0;
+ int64_t remote_io_bytes = 0;
+ int64_t skip_cache_io_count = 0;
+ int64_t write_cache_bytes = 0;
+ int64_t local_io_time = 0;
+ int64_t remote_io_time = 0;
+ int64_t write_cache_io_time = 0;
+ };
public:
bool rpc_struct_inited = false;
@@ -68,6 +89,10 @@ public:
uint32_t _max_rows_per_backend = 0;
// Store the number of rows processed by each backend
std::unordered_map<int64_t, uint32_t> _backend_rows_count; // backend_id
=> rows_count
+
+private:
+ // backend id => accumulated TopN phase-2 profile stats.
+ std::map<int64_t, TopNLazyMaterializationBackendStats>
_topn_lazy_materialization_backend_stats;
};
class MaterializationLocalState final : public
PipelineXLocalState<FakeSharedState> {
diff --git a/be/src/exec/rowid_fetcher.cpp b/be/src/exec/rowid_fetcher.cpp
index c2317a103e5..1c2a6f1729a 100644
--- a/be/src/exec/rowid_fetcher.cpp
+++ b/be/src/exec/rowid_fetcher.cpp
@@ -53,6 +53,7 @@
#include "exec/scan/file_scanner.h"
#include "format/orc/vorc_reader.h"
#include "format/parquet/vparquet_reader.h"
+#include "io/io_common.h"
#include "runtime/descriptors.h"
#include "runtime/exec_env.h" // ExecEnv
#include "runtime/fragment_mgr.h" // FragmentMgr
@@ -73,6 +74,23 @@
namespace doris {
+namespace {
+
+void set_topn_lazy_materialization_file_cache_stats(
+ const io::FileCacheStatistics& stats,
PTopNLazyMaterializationFileCacheStats* pstats) {
+ pstats->set_local_io_count(stats.num_local_io_total);
+ pstats->set_local_io_bytes(stats.bytes_read_from_local);
+ pstats->set_remote_io_count(stats.num_remote_io_total);
+ pstats->set_remote_io_bytes(stats.bytes_read_from_remote);
+ pstats->set_skip_cache_io_count(stats.num_skip_cache_io_total);
+ pstats->set_write_cache_bytes(stats.bytes_write_into_cache);
+ pstats->set_local_io_time(stats.local_io_timer);
+ pstats->set_remote_io_time(stats.remote_io_timer);
+ pstats->set_write_cache_io_time(stats.write_cache_io_timer);
+}
+
+} // namespace
+
Status RowIDFetcher::init() {
DorisNodesInfo nodes_info;
nodes_info.setNodes(_fetch_option.t_fetch_opt.nodes_info);
@@ -548,6 +566,11 @@ Status RowIdStorageReader::read_by_rowids(const
PMultiGetRequestV2& request,
int64_t external_get_block_avg_ms = 0;
size_t external_scan_range_cnt = 0;
+ const auto file_cache_miss_policy =
+ request.file_cache_remote_only_on_miss()
+ ? io::FileCacheMissPolicy::REMOTE_ONLY_ON_MISS
+ : io::FileCacheMissPolicy::READ_THROUGH_AND_WRITE_BACK;
+
// Add counters for different file mapping types
std::unordered_map<FileMappingType, int64_t> file_type_counts;
@@ -589,7 +612,7 @@ Status RowIdStorageReader::read_by_rowids(const
PMultiGetRequestV2& request,
RETURN_IF_ERROR(read_batch_doris_format_row(
request_block_desc, id_file_map, slots,
tquery_id, result_blocks[i],
stats, &acquire_tablet_ms, &acquire_rowsets_ms,
- &acquire_segments_ms, &lookup_row_data_ms));
+ &acquire_segments_ms, &lookup_row_data_ms,
file_cache_miss_policy));
} else {
RETURN_IF_ERROR(read_batch_external_row(
request.wg_id(), request_block_desc,
id_file_map, slots,
@@ -637,6 +660,9 @@ Status RowIdStorageReader::read_by_rowids(const
PMultiGetRequestV2& request,
acquire_rowsets_ms, acquire_segments_ms,
lookup_row_data_ms,
file_type_stats, external_init_reader_avg_ms,
external_get_block_avg_ms,
external_scan_range_cnt);
+ set_topn_lazy_materialization_file_cache_stats(
+ stats.file_cache_stats,
+
response->mutable_topn_lazy_materialization_file_cache_stats());
}
return Status::OK();
@@ -646,7 +672,8 @@ Status RowIdStorageReader::read_batch_doris_format_row(
const PRequestBlockDesc& request_block_desc,
std::shared_ptr<IdFileMap> id_file_map,
std::vector<SlotDescriptor>& slots, const TUniqueId& query_id, Block&
result_block,
OlapReaderStatistics& stats, int64_t* acquire_tablet_ms, int64_t*
acquire_rowsets_ms,
- int64_t* acquire_segments_ms, int64_t* lookup_row_data_ms) {
+ int64_t* acquire_segments_ms, int64_t* lookup_row_data_ms,
+ io::FileCacheMissPolicy file_cache_miss_policy) {
if (result_block.is_empty_column()) [[likely]] {
result_block = Block(slots, request_block_desc.row_id_size());
}
@@ -724,11 +751,11 @@ Status RowIdStorageReader::read_batch_doris_format_row(
}
scan_blocks[batch_idx] = Block(slots, row_ids.size());
- RETURN_IF_ERROR(read_doris_format_row(id_file_map,
scan_batch.file_mapping, row_ids, slots,
- full_read_schema,
row_store_read_struct, stats,
- acquire_tablet_ms,
acquire_rowsets_ms,
- acquire_segments_ms,
lookup_row_data_ms, seg_map,
- iterator_map,
scan_blocks[batch_idx]));
+ RETURN_IF_ERROR(read_doris_format_row(
+ id_file_map, scan_batch.file_mapping, row_ids, slots,
full_read_schema,
+ row_store_read_struct, stats, acquire_tablet_ms,
acquire_rowsets_ms,
+ acquire_segments_ms, lookup_row_data_ms, seg_map, iterator_map,
+ file_cache_miss_policy, scan_blocks[batch_idx]));
}
scatter_scan_blocks_to_result_block(row_id_block_idx, scan_blocks,
result_block);
@@ -740,6 +767,28 @@ const std::string
RowIdStorageReader::ScannersRunningTimeProfile = "ScannersRunn
const std::string RowIdStorageReader::InitReaderAvgTimeProfile =
"InitReaderAvgTime";
const std::string RowIdStorageReader::GetBlockAvgTimeProfile =
"GetBlockAvgTime";
const std::string RowIdStorageReader::FileReadLinesProfile = "FileReadLines";
+const std::string
RowIdStorageReader::TopNLazyMaterializationSecondPhaseLocalIOCount =
+ "TopNLazyMaterializationSecondPhaseLocalIOCount";
+const std::string
RowIdStorageReader::TopNLazyMaterializationSecondPhaseLocalIOBytes =
+ "TopNLazyMaterializationSecondPhaseLocalIOBytes";
+const std::string
RowIdStorageReader::TopNLazyMaterializationSecondPhaseRemoteIOCount =
+ "TopNLazyMaterializationSecondPhaseRemoteIOCount";
+const std::string
RowIdStorageReader::TopNLazyMaterializationSecondPhaseRemoteIOBytes =
+ "TopNLazyMaterializationSecondPhaseRemoteIOBytes";
+const std::string
RowIdStorageReader::TopNLazyMaterializationSecondPhaseSkipCacheIOCount =
+ "TopNLazyMaterializationSecondPhaseSkipCacheIOCount";
+const std::string
RowIdStorageReader::TopNLazyMaterializationSecondPhaseWriteCacheBytes =
+ "TopNLazyMaterializationSecondPhaseWriteCacheBytes";
+const std::string
RowIdStorageReader::TopNLazyMaterializationSecondPhaseLocalIOTime =
+ "TopNLazyMaterializationSecondPhaseLocalIOTime";
+const std::string
RowIdStorageReader::TopNLazyMaterializationSecondPhaseRemoteIOTime =
+ "TopNLazyMaterializationSecondPhaseRemoteIOTime";
+const std::string
RowIdStorageReader::TopNLazyMaterializationSecondPhaseWriteCacheIOTime =
+ "TopNLazyMaterializationSecondPhaseWriteCacheIOTime";
+const std::string
RowIdStorageReader::TopNLazyMaterializationSecondPhaseRowsRead =
+ "TopNLazyMaterializationSecondPhaseRowsRead";
+const std::string
RowIdStorageReader::TopNLazyMaterializationSecondPhaseSegmentsRead =
+ "TopNLazyMaterializationSecondPhaseSegmentsRead";
Status RowIdStorageReader::read_external_row_from_file_mapping(
size_t idx, const std::multimap<segment_v2::rowid_t, size_t>& row_ids,
@@ -1034,7 +1083,7 @@ Status RowIdStorageReader::read_doris_format_row(
int64_t* acquire_tablet_ms, int64_t* acquire_rowsets_ms, int64_t*
acquire_segments_ms,
int64_t* lookup_row_data_ms, std::unordered_map<SegKey, SegItem,
HashOfSegKey>& seg_map,
std::unordered_map<IteratorKey, IteratorItem, HashOfIteratorKey>&
iterator_map,
- Block& result_block) {
+ io::FileCacheMissPolicy file_cache_miss_policy, Block& result_block) {
auto [tablet_id, rowset_id, segment_id] =
file_mapping->get_doris_format_info();
SegKey seg_key {.tablet_id = tablet_id, .rowset_id = rowset_id,
.segment_id = segment_id};
@@ -1104,13 +1153,18 @@ Status RowIdStorageReader::read_doris_format_row(
}
auto result_columns_guard = result_block.mutate_columns_scoped();
MutableColumns& result_columns =
result_columns_guard.mutable_columns();
+ io::IOContext io_ctx;
+ io_ctx.reader_type = ReaderType::READER_QUERY;
+ io_ctx.file_cache_stats = &stats.file_cache_stats;
+ io_ctx.file_cache_miss_policy = file_cache_miss_policy;
for (auto row_id : row_ids) {
RowLocation loc(rowset_id, segment->id(),
cast_set<uint32_t>(row_id));
row_store_read_struct.row_store_buffer.clear();
RETURN_IF_ERROR(scope_timer_run(
[&]() {
return tablet->lookup_row_data({}, loc, rowset, stats,
-
row_store_read_struct.row_store_buffer);
+
row_store_read_struct.row_store_buffer,
+ false, &io_ctx);
},
lookup_row_data_ms));
@@ -1133,6 +1187,8 @@ Status RowIdStorageReader::read_doris_format_row(
iterator_map[iterator_key].segment = segment;
iterator_item.storage_read_options.stats = &stats;
iterator_item.storage_read_options.io_ctx.reader_type =
ReaderType::READER_QUERY;
+
iterator_item.storage_read_options.io_ctx.file_cache_miss_policy =
+ file_cache_miss_policy;
}
set_slot_access_paths(slots[x], full_read_schema,
iterator_item.storage_read_options);
RETURN_IF_ERROR(segment->seek_and_read_by_rowid(
diff --git a/be/src/exec/rowid_fetcher.h b/be/src/exec/rowid_fetcher.h
index 76411036669..790f9cf17e7 100644
--- a/be/src/exec/rowid_fetcher.h
+++ b/be/src/exec/rowid_fetcher.h
@@ -38,6 +38,9 @@ namespace doris {
class DorisNodesInfo;
class RuntimeState;
class TupleDescriptor;
+namespace io {
+enum class FileCacheMissPolicy : uint8_t;
+}
struct FileMapping;
struct SegKey;
@@ -97,6 +100,17 @@ public:
static const std::string InitReaderAvgTimeProfile;
static const std::string GetBlockAvgTimeProfile;
static const std::string FileReadLinesProfile;
+ static const std::string TopNLazyMaterializationSecondPhaseLocalIOCount;
+ static const std::string TopNLazyMaterializationSecondPhaseLocalIOBytes;
+ static const std::string TopNLazyMaterializationSecondPhaseRemoteIOCount;
+ static const std::string TopNLazyMaterializationSecondPhaseRemoteIOBytes;
+ static const std::string
TopNLazyMaterializationSecondPhaseSkipCacheIOCount;
+ static const std::string TopNLazyMaterializationSecondPhaseWriteCacheBytes;
+ static const std::string TopNLazyMaterializationSecondPhaseLocalIOTime;
+ static const std::string TopNLazyMaterializationSecondPhaseRemoteIOTime;
+ static const std::string
TopNLazyMaterializationSecondPhaseWriteCacheIOTime;
+ static const std::string TopNLazyMaterializationSecondPhaseRowsRead;
+ static const std::string TopNLazyMaterializationSecondPhaseSegmentsRead;
static Status read_by_rowids(const PMultiGetRequest& request,
PMultiGetResponse* response);
static Status read_by_rowids(const PMultiGetRequestV2& request,
PMultiGetResponseV2* response);
@@ -112,13 +126,14 @@ private:
int64_t* acquire_tablet_ms, int64_t* acquire_rowsets_ms, int64_t*
acquire_segments_ms,
int64_t* lookup_row_data_ms, std::unordered_map<SegKey, SegItem,
HashOfSegKey>& seg_map,
std::unordered_map<IteratorKey, IteratorItem, HashOfIteratorKey>&
iterator_map,
- Block& result_block);
+ io::FileCacheMissPolicy file_cache_miss_policy, Block&
result_block);
static Status read_batch_doris_format_row(
const PRequestBlockDesc& request_block_desc,
std::shared_ptr<IdFileMap> id_file_map,
std::vector<SlotDescriptor>& slots, const TUniqueId& query_id,
Block& result_block,
OlapReaderStatistics& stats, int64_t* acquire_tablet_ms, int64_t*
acquire_rowsets_ms,
- int64_t* acquire_segments_ms, int64_t* lookup_row_data_ms);
+ int64_t* acquire_segments_ms, int64_t* lookup_row_data_ms,
+ io::FileCacheMissPolicy file_cache_miss_policy);
static Status read_batch_external_row(
const uint64_t workload_group_id, const PRequestBlockDesc&
request_block_desc,
diff --git a/be/src/io/cache/block_file_cache.cpp
b/be/src/io/cache/block_file_cache.cpp
index 4f24eab0a58..df8bfb12b70 100644
--- a/be/src/io/cache/block_file_cache.cpp
+++ b/be/src/io/cache/block_file_cache.cpp
@@ -730,6 +730,97 @@ void
BlockFileCache::add_need_update_lru_block(FileBlockSPtr block) {
}
}
+Status BlockFileCache::get_downloaded_blocks_if_fully_covered(const
UInt128Wrapper& hash,
+ size_t offset,
size_t size,
+ const
CacheContext& context,
+ FileBlocks*
blocks,
+ bool*
fully_covered) {
+ DCHECK(blocks != nullptr);
+ DCHECK(fully_covered != nullptr);
+ blocks->clear();
+ *fully_covered = false;
+ if (size == 0) {
+ *fully_covered = true;
+ return Status::OK();
+ }
+
+ FileBlock::Range range(offset, offset + size - 1);
+ std::lock_guard cache_lock(_mutex);
+ auto it = _files.find(hash);
+ if (it == _files.end()) {
+ if (_async_open_done) {
+ return Status::OK();
+ }
+ FileCacheKey key;
+ key.hash = hash;
+ key.meta.type = context.cache_type;
+ key.meta.expiration_time = context.expiration_time;
+ key.meta.tablet_id = context.tablet_id;
+ _storage->load_blocks_directly_unlocked(this, key, cache_lock);
+
+ it = _files.find(hash);
+ if (it == _files.end()) {
+ return Status::OK();
+ }
+ }
+
+ auto& file_blocks = it->second;
+ if (file_blocks.empty()) {
+ LOG(WARNING) << "file_blocks is empty for hash=" << hash.to_string()
+ << " cache type=" << context.cache_type
+ << " cache expiration time=" << context.expiration_time
+ << " cache range=" << range.left << " " << range.right
+ << " query id=" << context.query_id;
+ DCHECK(false);
+ _files.erase(hash);
+ return Status::OK();
+ }
+
+ std::vector<FileBlockCell*> covered_cells;
+ auto block_it = file_blocks.lower_bound(range.left);
+ if (block_it == file_blocks.end() ||
block_it->second.file_block->range().left > range.left) {
+ if (block_it == file_blocks.begin()) {
+ return Status::OK();
+ }
+ --block_it;
+ }
+
+ size_t current_pos = range.left;
+ while (current_pos <= range.right) {
+ if (block_it == file_blocks.end()) {
+ return Status::OK();
+ }
+
+ auto& cell = block_it->second;
+ const auto& block_range = cell.file_block->range();
+ if (block_range.right < current_pos) {
+ ++block_it;
+ continue;
+ }
+ if (block_range.left > current_pos ||
+ cell.file_block->state() != FileBlock::State::DOWNLOADED) {
+ return Status::OK();
+ }
+
+ covered_cells.push_back(&cell);
+ if (range.right <= block_range.right) {
+ *fully_covered = true;
+ break;
+ }
+ current_pos = block_range.right + 1;
+ ++block_it;
+ }
+
+ if (!*fully_covered) {
+ return Status::OK();
+ }
+ for (const auto* cell : covered_cells) {
+ use_cell(*cell, blocks, need_to_move(cell->file_block->cache_type(),
context.cache_type),
+ cache_lock);
+ }
+ return Status::OK();
+}
+
std::string BlockFileCache::clear_file_cache_async() {
return clear_file_cache_impl(false);
}
diff --git a/be/src/io/cache/block_file_cache.h
b/be/src/io/cache/block_file_cache.h
index 1a19df5eb4a..9a01d07d136 100644
--- a/be/src/io/cache/block_file_cache.h
+++ b/be/src/io/cache/block_file_cache.h
@@ -237,6 +237,14 @@ public:
FileBlocksHolder get_or_set(const UInt128Wrapper& hash, size_t offset,
size_t size,
CacheContext& context);
+ /**
+ * Return existing downloaded blocks only if they fully cover [offset,
offset + size).
+ * This lookup is read-only: it does not reserve cache space or create
EMPTY blocks.
+ */
+ Status get_downloaded_blocks_if_fully_covered(const UInt128Wrapper& hash,
size_t offset,
+ size_t size, const
CacheContext& context,
+ FileBlocks* blocks, bool*
fully_covered);
+
/**
* record blocks read directly by CachedRemoteFileReader
*/
diff --git a/be/src/io/cache/cached_remote_file_reader.cpp
b/be/src/io/cache/cached_remote_file_reader.cpp
index 907a6e7df10..8868969b233 100644
--- a/be/src/io/cache/cached_remote_file_reader.cpp
+++ b/be/src/io/cache/cached_remote_file_reader.cpp
@@ -1080,6 +1080,93 @@ Status
CachedRemoteFileReader::_read_from_indirect_cache(size_t offset, Slice re
return Status::OK();
}
+Status CachedRemoteFileReader::_read_remote_only_on_cache_miss(
+ size_t offset, Slice result, size_t bytes_req, bool is_dryrun, size_t*
bytes_read,
+ ReadStatistics& stats, SourceReadBreakdown& source_read_breakdown,
+ const IOContext* io_ctx) {
+ auto read_remote = [&]() -> Status {
+ stats.hit_cache = false;
+ stats.from_peer_cache = false;
+ stats.skip_cache = true;
+ s3_read_counter << 1;
+ if (is_dryrun) [[unlikely]] {
+ *bytes_read = bytes_req;
+ g_read_cache_indirect_bytes << 0;
+ g_read_cache_indirect_total_bytes << bytes_req;
+ return Status::OK();
+ }
+
+ size_t remote_bytes_read = bytes_req;
+ SCOPED_RAW_TIMER(&stats.remote_read_timer);
+ RETURN_IF_ERROR(_remote_file_reader->read_at(offset,
Slice(result.data, bytes_req),
+ &remote_bytes_read,
io_ctx));
+ *bytes_read = remote_bytes_read;
+ DCHECK_EQ(*bytes_read, bytes_req);
+ source_read_breakdown.remote_bytes += remote_bytes_read;
+ g_read_cache_indirect_bytes << remote_bytes_read;
+ g_read_cache_indirect_total_bytes << remote_bytes_read;
+ return Status::OK();
+ };
+
+ g_read_cache_indirect_num << 1;
+ CacheContext cache_context(io_ctx);
+ cache_context.stats = &stats;
+ cache_context.tablet_id = _tablet_id;
+ FileBlocks file_blocks;
+ bool fully_covered = false;
+ {
+ SCOPED_RAW_TIMER(&stats.get_timer);
+ RETURN_IF_ERROR(_cache->get_downloaded_blocks_if_fully_covered(
+ _cache_hash, offset, bytes_req, cache_context, &file_blocks,
&fully_covered));
+ }
+ if (!fully_covered) {
+ return read_remote();
+ }
+
+ size_t local_read_bytes = 0;
+ size_t current_offset = offset;
+ size_t end_offset = offset + bytes_req - 1;
+ for (auto& block : file_blocks) {
+ if (current_offset > end_offset) {
+ break;
+ }
+ const auto& block_range = block->range();
+ if (block_range.right < current_offset) {
+ continue;
+ }
+
+ size_t read_left = std::max(current_offset, block_range.left);
+ size_t read_right = std::min(end_offset, block_range.right);
+ size_t read_size = read_right - read_left + 1;
+ if (is_dryrun) [[unlikely]] {
+ g_skip_local_cache_io_sum_bytes << read_size;
+ } else {
+ SCOPED_RAW_TIMER(&stats.local_read_timer);
+ Status st = block->read(Slice(result.data + (read_left - offset),
read_size),
+ read_left - block_range.left);
+ if (!st.ok()) {
+ if (st.is<ErrorCode::NOT_FOUND>()) {
+ _cache->remove_if_cached_async(_cache_hash);
+ }
+ LOG_EVERY_N(WARNING, 100)
+ << "Read data failed from file cache in
remote-only-on-miss path. "
+ << "Fallback to remote. err=" << st.msg()
+ << ", block state=" << block->state();
+ return read_remote();
+ }
+ source_read_breakdown.local_bytes += read_size;
+ local_read_bytes += read_size;
+ }
+ current_offset = read_right + 1;
+ }
+
+ *bytes_read = bytes_req;
+ stats.hit_cache = true;
+ g_read_cache_indirect_bytes << local_read_bytes;
+ g_read_cache_indirect_total_bytes << bytes_req;
+ return Status::OK();
+}
+
Status CachedRemoteFileReader::read_at_impl(size_t offset, Slice result,
size_t* bytes_read,
const IOContext* io_ctx) {
SCOPED_CONCURRENCY_COUNT(ConcurrencyStatsManager::instance().cached_remote_reader_read_at);
@@ -1142,6 +1229,12 @@ Status CachedRemoteFileReader::read_at_impl(size_t
offset, Slice result, size_t*
}
}};
+ if (io_ctx->file_cache_miss_policy ==
FileCacheMissPolicy::REMOTE_ONLY_ON_MISS) {
+ read_st = _read_remote_only_on_cache_miss(offset, result, bytes_req,
is_dryrun, bytes_read,
+ stats,
source_read_breakdown, io_ctx);
+ return read_st;
+ }
+
size_t already_read = 0;
if (_try_read_from_cached_files_directly(offset, result, bytes_req,
is_dryrun, stats,
source_read_breakdown,
already_read, bytes_read)) {
diff --git a/be/src/io/cache/cached_remote_file_reader.h
b/be/src/io/cache/cached_remote_file_reader.h
index 562d79bb9f1..5c562c82513 100644
--- a/be/src/io/cache/cached_remote_file_reader.h
+++ b/be/src/io/cache/cached_remote_file_reader.h
@@ -228,6 +228,23 @@ private:
SourceReadBreakdown&
source_read_breakdown,
const IOContext* io_ctx);
+ /// Read local cache only when downloaded blocks fully cover the request;
otherwise read remote
+ /// data directly without writing file cache.
+ /// @param[in] offset Original request offset.
+ /// @param[out] result Destination buffer for the original request.
+ /// @param[in] bytes_req Original request size.
+ /// @param[in] is_dryrun True if local cache IO should be skipped.
+ /// @param[out] bytes_read Total bytes read for the request.
+ /// @param[in,out] stats Read statistics updated for local or remote work.
+ /// @param[in,out] source_read_breakdown Source bytes used by profile
metrics.
+ /// @param[in] io_ctx IO context passed to cache lookup and remote read.
+ /// @return OK on success; otherwise an error from cache lookup, cache
read, or remote read.
+ Status _read_remote_only_on_cache_miss(size_t offset, Slice result, size_t
bytes_req,
+ bool is_dryrun, size_t* bytes_read,
+ ReadStatistics& stats,
+ SourceReadBreakdown&
source_read_breakdown,
+ const IOContext* io_ctx);
+
/// Fall back to S3: clear peer_result, allocate buffer, and read from
remote storage.
/// @param[in] empty_start Start offset of the contiguous remote-read
range.
/// @param[in] span_size Size of the remote-read range in bytes.
diff --git a/be/src/io/io_common.h b/be/src/io/io_common.h
index afb5f9e8862..e686c23a313 100644
--- a/be/src/io/io_common.h
+++ b/be/src/io/io_common.h
@@ -40,6 +40,11 @@ enum class ReaderType : uint8_t {
namespace io {
+enum class FileCacheMissPolicy : uint8_t {
+ READ_THROUGH_AND_WRITE_BACK = 0,
+ REMOTE_ONLY_ON_MISS = 1,
+};
+
struct FileReaderStats {
size_t read_calls = 0;
size_t read_bytes = 0;
@@ -188,6 +193,7 @@ struct IOContext {
int64_t predicate_filtered_rows = 0;
// if true, bypass peer read / peer-vs-S3 race and read directly from
remote storage
bool bypass_peer_read {false};
+ FileCacheMissPolicy file_cache_miss_policy =
FileCacheMissPolicy::READ_THROUGH_AND_WRITE_BACK;
};
} // namespace io
diff --git a/be/src/storage/index/bloom_filter/bloom_filter_index_reader.cpp
b/be/src/storage/index/bloom_filter/bloom_filter_index_reader.cpp
index b8c1c9b3744..2d0a9a0ab8b 100644
--- a/be/src/storage/index/bloom_filter/bloom_filter_index_reader.cpp
+++ b/be/src/storage/index/bloom_filter/bloom_filter_index_reader.cpp
@@ -32,10 +32,11 @@
namespace doris::segment_v2 {
Status BloomFilterIndexReader::load(bool use_page_cache, bool kept_in_memory,
- OlapReaderStatistics* index_load_stats) {
+ OlapReaderStatistics* index_load_stats,
+ const io::IOContext* io_ctx) {
// TODO yyq: implement a new once flag to avoid status construct.
- return _load_once.call([this, use_page_cache, kept_in_memory,
index_load_stats] {
- return _load(use_page_cache, kept_in_memory, index_load_stats);
+ return _load_once.call([this, use_page_cache, kept_in_memory,
index_load_stats, io_ctx] {
+ return _load(use_page_cache, kept_in_memory, index_load_stats, io_ctx);
});
}
@@ -45,21 +46,24 @@ int64_t BloomFilterIndexReader::get_metadata_size() const {
}
Status BloomFilterIndexReader::_load(bool use_page_cache, bool kept_in_memory,
- OlapReaderStatistics* index_load_stats) {
+ OlapReaderStatistics* index_load_stats,
+ const io::IOContext* io_ctx) {
const IndexedColumnMetaPB& bf_index_meta =
_bloom_filter_index_meta->bloom_filter();
_bloom_filter_reader = std::make_unique<IndexedColumnReader>(_file_reader,
bf_index_meta);
- RETURN_IF_ERROR(_bloom_filter_reader->load(use_page_cache, kept_in_memory,
index_load_stats));
+ RETURN_IF_ERROR(
+ _bloom_filter_reader->load(use_page_cache, kept_in_memory,
index_load_stats, io_ctx));
update_metadata_size();
return Status::OK();
}
Status
BloomFilterIndexReader::new_iterator(std::unique_ptr<BloomFilterIndexIterator>*
iterator,
- OlapReaderStatistics*
index_load_stats) {
+ OlapReaderStatistics*
index_load_stats,
+ const io::IOContext* io_ctx) {
DBUG_EXECUTE_IF("BloomFilterIndexReader::new_iterator.fail", {
return Status::InternalError("new_iterator for bloom filter index
failed");
});
- *iterator = std::make_unique<BloomFilterIndexIterator>(this,
index_load_stats);
+ *iterator = std::make_unique<BloomFilterIndexIterator>(this,
index_load_stats, io_ctx);
return Status::OK();
}
diff --git a/be/src/storage/index/bloom_filter/bloom_filter_index_reader.h
b/be/src/storage/index/bloom_filter/bloom_filter_index_reader.h
index dc0b78f16c3..43958651744 100644
--- a/be/src/storage/index/bloom_filter/bloom_filter_index_reader.h
+++ b/be/src/storage/index/bloom_filter/bloom_filter_index_reader.h
@@ -31,6 +31,9 @@
#include "util/once.h"
namespace doris {
+namespace io {
+struct IOContext;
+}
namespace segment_v2 {
@@ -46,19 +49,21 @@ public:
_bloom_filter_index_meta.reset(new
BloomFilterIndexPB(bloom_filter_index_meta));
}
- Status load(bool use_page_cache, bool kept_in_memory,
- OlapReaderStatistics* bf_index_load_stats);
+ Status load(bool use_page_cache, bool kept_in_memory,
OlapReaderStatistics* bf_index_load_stats,
+ const io::IOContext* io_ctx = nullptr);
BloomFilterAlgorithmPB algorithm() { return
_bloom_filter_index_meta->algorithm(); }
// create a new column iterator.
Status new_iterator(std::unique_ptr<BloomFilterIndexIterator>* iterator,
- OlapReaderStatistics* index_load_stats);
+ OlapReaderStatistics* index_load_stats,
+ const io::IOContext* io_ctx = nullptr);
FieldType type() const { return FieldType::OLAP_FIELD_TYPE_VARCHAR; }
private:
- Status _load(bool use_page_cache, bool kept_in_memory,
OlapReaderStatistics* index_load_stats);
+ Status _load(bool use_page_cache, bool kept_in_memory,
OlapReaderStatistics* index_load_stats,
+ const io::IOContext* io_ctx);
int64_t get_metadata_size() const override;
@@ -73,8 +78,10 @@ private:
class BloomFilterIndexIterator {
public:
- explicit BloomFilterIndexIterator(BloomFilterIndexReader* reader,
OlapReaderStatistics* stats)
- : _reader(reader),
_bloom_filter_iter(reader->_bloom_filter_reader.get(), stats) {}
+ explicit BloomFilterIndexIterator(BloomFilterIndexReader* reader,
OlapReaderStatistics* stats,
+ const io::IOContext* io_ctx)
+ : _reader(reader),
+ _bloom_filter_iter(reader->_bloom_filter_reader.get(), stats,
io_ctx) {}
// Read bloom filter at the given ordinal into `bf`.
Status read_bloom_filter(rowid_t ordinal, std::unique_ptr<BloomFilter>*
bf);
diff --git a/be/src/storage/index/indexed_column_reader.cpp
b/be/src/storage/index/indexed_column_reader.cpp
index d30c2c82dbc..29510f21452 100644
--- a/be/src/storage/index/indexed_column_reader.cpp
+++ b/be/src/storage/index/indexed_column_reader.cpp
@@ -60,7 +60,8 @@ int64_t IndexedColumnReader::get_metadata_size() const {
}
Status IndexedColumnReader::load(bool use_page_cache, bool kept_in_memory,
- OlapReaderStatistics* index_load_stats) {
+ OlapReaderStatistics* index_load_stats,
+ const io::IOContext* io_ctx) {
_use_page_cache = use_page_cache;
_kept_in_memory = kept_in_memory;
@@ -78,7 +79,7 @@ Status IndexedColumnReader::load(bool use_page_cache, bool
kept_in_memory,
} else {
RETURN_IF_ERROR(load_index_page(_meta.ordinal_index_meta().root_page(),
&_ordinal_index_page_handle,
- _ordinal_index_reader.get(),
index_load_stats));
+ _ordinal_index_reader.get(),
index_load_stats, io_ctx));
_has_index_page = true;
}
}
@@ -90,7 +91,7 @@ Status IndexedColumnReader::load(bool use_page_cache, bool
kept_in_memory,
} else {
RETURN_IF_ERROR(load_index_page(_meta.value_index_meta().root_page(),
&_value_index_page_handle,
_value_index_reader.get(),
- index_load_stats));
+ index_load_stats, io_ctx));
_has_index_page = true;
}
}
@@ -102,13 +103,14 @@ Status IndexedColumnReader::load(bool use_page_cache,
bool kept_in_memory,
Status IndexedColumnReader::load_index_page(const PagePointerPB& pp,
PageHandle* handle,
IndexPageReader* reader,
- OlapReaderStatistics*
index_load_stats) {
+ OlapReaderStatistics*
index_load_stats,
+ const io::IOContext* io_ctx) {
Slice body;
PageFooterPB footer;
BlockCompressionCodec* local_compress_codec;
RETURN_IF_ERROR(get_block_compression_codec(_meta.compression(),
&local_compress_codec));
RETURN_IF_ERROR(read_page(PagePointer(pp), handle, &body, &footer,
INDEX_PAGE,
- local_compress_codec, false, index_load_stats));
+ local_compress_codec, false, index_load_stats,
io_ctx));
RETURN_IF_ERROR(reader->parse(body, footer.index_page_footer()));
_mem_size += body.get_size();
return Status::OK();
@@ -117,11 +119,14 @@ Status IndexedColumnReader::load_index_page(const
PagePointerPB& pp, PageHandle*
Status IndexedColumnReader::read_page(const PagePointer& pp, PageHandle*
handle, Slice* body,
PageFooterPB* footer, PageTypePB type,
BlockCompressionCodec* codec, bool
pre_decode,
- OlapReaderStatistics* stats) const {
+ OlapReaderStatistics* stats,
+ const io::IOContext* io_ctx) const {
OlapReaderStatistics tmp_stats;
OlapReaderStatistics* stats_ptr = stats != nullptr ? stats : &tmp_stats;
- PageReadOptions opts(io::IOContext {.is_index_data = true,
- .file_cache_stats =
&stats_ptr->file_cache_stats});
+ io::IOContext page_io_ctx = io_ctx != nullptr ? *io_ctx : io::IOContext {};
+ page_io_ctx.is_index_data = true;
+ page_io_ctx.file_cache_stats = &stats_ptr->file_cache_stats;
+ PageReadOptions opts(page_io_ctx);
opts.use_page_cache = _use_page_cache;
opts.kept_in_memory = _kept_in_memory;
opts.pre_decode = pre_decode;
@@ -158,7 +163,7 @@ Status IndexedColumnIterator::_read_data_page(const
PagePointer& pp) {
Slice body;
PageFooterPB footer;
RETURN_IF_ERROR(_reader->read_page(pp, &handle, &body, &footer, DATA_PAGE,
_compress_codec,
- true, _stats));
+ true, _stats, _io_ctx));
// parse data page
// note that page_index is not used in IndexedColumnIterator, so we pass 0
PageDecoderOptions opts;
diff --git a/be/src/storage/index/indexed_column_reader.h
b/be/src/storage/index/indexed_column_reader.h
index 1cea4641595..b1562409582 100644
--- a/be/src/storage/index/indexed_column_reader.h
+++ b/be/src/storage/index/indexed_column_reader.h
@@ -40,6 +40,9 @@ namespace doris {
class KeyCoder;
class BlockCompressionCodec;
+namespace io {
+struct IOContext;
+}
namespace segment_v2 {
@@ -57,12 +60,14 @@ public:
~IndexedColumnReader() override;
Status load(bool use_page_cache, bool kept_in_memory,
- OlapReaderStatistics* index_load_stats = nullptr);
+ OlapReaderStatistics* index_load_stats = nullptr,
+ const io::IOContext* io_ctx = nullptr);
// read a page specified by `pp' from `file' into `handle'
Status read_page(const PagePointer& pp, PageHandle* handle, Slice* body,
PageFooterPB* footer,
PageTypePB type, BlockCompressionCodec* codec, bool
pre_decode,
- OlapReaderStatistics* stats = nullptr) const;
+ OlapReaderStatistics* stats = nullptr,
+ const io::IOContext* io_ctx = nullptr) const;
int64_t num_values() const { return _num_values; }
const EncodingInfo* encoding_info() const { return _encoding_info; }
@@ -76,7 +81,7 @@ public:
private:
Status load_index_page(const PagePointerPB& pp, PageHandle* handle,
IndexPageReader* reader,
- OlapReaderStatistics* index_load_stats);
+ OlapReaderStatistics* index_load_stats, const
io::IOContext* io_ctx);
int64_t get_metadata_size() const override;
@@ -108,11 +113,13 @@ private:
class IndexedColumnIterator {
public:
explicit IndexedColumnIterator(const IndexedColumnReader* reader,
- OlapReaderStatistics* stats = nullptr)
+ OlapReaderStatistics* stats = nullptr,
+ const io::IOContext* io_ctx = nullptr)
: _reader(reader),
_ordinal_iter(reader->_ordinal_index_reader.get()),
_value_iter(reader->_value_index_reader.get()),
- _stats(stats) {}
+ _stats(stats),
+ _io_ctx(io_ctx) {}
// Seek to the given ordinal entry. Entry 0 is the first entry.
// Return Status::Error<ENTRY_NOT_FOUND> if provided seek point is past
the end.
@@ -162,6 +169,7 @@ private:
// iterator owned compress codec, should NOT be shared by threads,
initialized before used
BlockCompressionCodec* _compress_codec = nullptr;
OlapReaderStatistics* _stats = nullptr;
+ const io::IOContext* _io_ctx = nullptr;
};
} // namespace segment_v2
diff --git a/be/src/storage/index/ordinal_page_index.cpp
b/be/src/storage/index/ordinal_page_index.cpp
index 054f30f67f2..688657f5755 100644
--- a/be/src/storage/index/ordinal_page_index.cpp
+++ b/be/src/storage/index/ordinal_page_index.cpp
@@ -71,16 +71,17 @@ Status OrdinalIndexWriter::finish(io::FileWriter*
file_writer, ColumnIndexMetaPB
}
Status OrdinalIndexReader::load(bool use_page_cache, bool kept_in_memory,
- OlapReaderStatistics* index_load_stats) {
+ OlapReaderStatistics* index_load_stats,
+ const io::IOContext* io_ctx) {
// TODO yyq: implement a new once flag to avoid status construct.
- return _load_once.call([this, use_page_cache, kept_in_memory,
index_load_stats] {
- return _load(use_page_cache, kept_in_memory, std::move(_meta_pb),
index_load_stats);
+ return _load_once.call([this, use_page_cache, kept_in_memory,
index_load_stats, io_ctx] {
+ return _load(use_page_cache, kept_in_memory, std::move(_meta_pb),
index_load_stats, io_ctx);
});
}
Status OrdinalIndexReader::_load(bool use_page_cache, bool kept_in_memory,
std::unique_ptr<OrdinalIndexPB> index_meta,
- OlapReaderStatistics* stats) {
+ OlapReaderStatistics* stats, const
io::IOContext* io_ctx) {
if (index_meta->root_page().is_root_data_page()) {
// only one data page, no index page
_num_pages = 1;
@@ -92,8 +93,10 @@ Status OrdinalIndexReader::_load(bool use_page_cache, bool
kept_in_memory,
// need to read index page
OlapReaderStatistics tmp_stats;
OlapReaderStatistics* stats_ptr = stats != nullptr ? stats : &tmp_stats;
- PageReadOptions opts(io::IOContext {.is_index_data = true,
- .file_cache_stats =
&stats_ptr->file_cache_stats});
+ io::IOContext page_io_ctx = io_ctx != nullptr ? *io_ctx : io::IOContext {};
+ page_io_ctx.is_index_data = true;
+ page_io_ctx.file_cache_stats = &stats_ptr->file_cache_stats;
+ PageReadOptions opts(page_io_ctx);
opts.use_page_cache = use_page_cache;
opts.kept_in_memory = kept_in_memory;
opts.type = INDEX_PAGE;
diff --git a/be/src/storage/index/ordinal_page_index.h
b/be/src/storage/index/ordinal_page_index.h
index f17d80c20ed..87bc4dc3177 100644
--- a/be/src/storage/index/ordinal_page_index.h
+++ b/be/src/storage/index/ordinal_page_index.h
@@ -36,7 +36,8 @@ namespace doris {
namespace io {
class FileWriter;
-}
+struct IOContext;
+} // namespace io
namespace segment_v2 {
class ColumnIndexMetaPB;
@@ -75,7 +76,8 @@ public:
virtual ~OrdinalIndexReader();
// load and parse the index page into memory
- Status load(bool use_page_cache, bool kept_in_memory,
OlapReaderStatistics* index_load_stats);
+ Status load(bool use_page_cache, bool kept_in_memory,
OlapReaderStatistics* index_load_stats,
+ const io::IOContext* io_ctx = nullptr);
// the returned iter points to the largest element which is less than
`ordinal`,
// or points to the first element if all elements are greater than
`ordinal`,
@@ -94,8 +96,8 @@ public:
private:
Status _load(bool use_page_cache, bool kept_in_memory,
- std::unique_ptr<OrdinalIndexPB> index_meta,
- OlapReaderStatistics* index_load_stats);
+ std::unique_ptr<OrdinalIndexPB> index_meta,
OlapReaderStatistics* index_load_stats,
+ const io::IOContext* io_ctx);
private:
friend class OrdinalPageIndexIterator;
diff --git a/be/src/storage/index/zone_map/zone_map_index.cpp
b/be/src/storage/index/zone_map/zone_map_index.cpp
index b12de9d03b6..23e2e5a4a0f 100644
--- a/be/src/storage/index/zone_map/zone_map_index.cpp
+++ b/be/src/storage/index/zone_map/zone_map_index.cpp
@@ -297,20 +297,22 @@ Status
TypedZoneMapIndexWriter<Type>::finish(io::FileWriter* file_writer,
}
Status ZoneMapIndexReader::load(bool use_page_cache, bool kept_in_memory,
- OlapReaderStatistics* index_load_stats) {
+ OlapReaderStatistics* index_load_stats,
+ const io::IOContext* io_ctx) {
// TODO yyq: implement a new once flag to avoid status construct.
- return _load_once.call([this, use_page_cache, kept_in_memory,
index_load_stats] {
+ return _load_once.call([this, use_page_cache, kept_in_memory,
index_load_stats, io_ctx] {
return _load(use_page_cache, kept_in_memory,
std::move(_page_zone_maps_meta),
- index_load_stats);
+ index_load_stats, io_ctx);
});
}
Status ZoneMapIndexReader::_load(bool use_page_cache, bool kept_in_memory,
std::unique_ptr<IndexedColumnMetaPB>
page_zone_maps_meta,
- OlapReaderStatistics* index_load_stats) {
+ OlapReaderStatistics* index_load_stats,
+ const io::IOContext* io_ctx) {
IndexedColumnReader reader(_file_reader, *page_zone_maps_meta);
- RETURN_IF_ERROR(reader.load(use_page_cache, kept_in_memory,
index_load_stats));
- IndexedColumnIterator iter(&reader, index_load_stats);
+ RETURN_IF_ERROR(reader.load(use_page_cache, kept_in_memory,
index_load_stats, io_ctx));
+ IndexedColumnIterator iter(&reader, index_load_stats, io_ctx);
_page_zone_maps.resize(reader.num_values());
diff --git a/be/src/storage/index/zone_map/zone_map_index.h
b/be/src/storage/index/zone_map/zone_map_index.h
index 96d9a7a26e5..80c6928f60b 100644
--- a/be/src/storage/index/zone_map/zone_map_index.h
+++ b/be/src/storage/index/zone_map/zone_map_index.h
@@ -38,6 +38,7 @@
namespace doris {
namespace io {
class FileWriter;
+struct IOContext;
} // namespace io
namespace segment_v2 {
@@ -182,7 +183,8 @@ public:
// load all page zone maps into memory
Status load(bool use_page_cache, bool kept_in_memory,
- OlapReaderStatistics* index_load_stats = nullptr);
+ OlapReaderStatistics* index_load_stats = nullptr,
+ const io::IOContext* io_ctx = nullptr);
const std::vector<ZoneMapPB>& page_zone_maps() const { return
_page_zone_maps; }
@@ -190,7 +192,7 @@ public:
private:
Status _load(bool use_page_cache, bool kept_in_memory,
std::unique_ptr<IndexedColumnMetaPB>,
- OlapReaderStatistics* index_load_stats);
+ OlapReaderStatistics* index_load_stats, const io::IOContext*
io_ctx);
int64_t get_metadata_size() const override;
diff --git a/be/src/storage/segment/column_reader.cpp
b/be/src/storage/segment/column_reader.cpp
index d0900561fa2..f15e7462e6c 100644
--- a/be/src/storage/segment/column_reader.cpp
+++ b/be/src/storage/segment/column_reader.cpp
@@ -609,7 +609,8 @@ Status ColumnReader::get_row_ranges_by_bloom_filter(const
AndBlockColumnPredicat
_load_bloom_filter_index(_use_index_page_cache,
_opts.kept_in_memory, iter_opts));
RowRanges bf_row_ranges;
std::unique_ptr<BloomFilterIndexIterator> bf_iter;
- RETURN_IF_ERROR(_bloom_filter_index->new_iterator(&bf_iter,
iter_opts.stats));
+ RETURN_IF_ERROR(
+ _bloom_filter_index->new_iterator(&bf_iter, iter_opts.stats,
&iter_opts.io_ctx));
size_t range_size = row_ranges->range_size();
// get covered page ids
std::set<uint32_t> page_ids;
@@ -641,13 +642,14 @@ Status ColumnReader::_load_ordinal_index(bool
use_page_cache, bool kept_in_memor
if (!_ordinal_index) {
return Status::InternalError("ordinal_index not inited");
}
- return _ordinal_index->load(use_page_cache, kept_in_memory,
iter_opts.stats);
+ return _ordinal_index->load(use_page_cache, kept_in_memory,
iter_opts.stats, &iter_opts.io_ctx);
}
Status ColumnReader::_load_zone_map_index(bool use_page_cache, bool
kept_in_memory,
const ColumnIteratorOptions&
iter_opts) {
if (_zone_map_index != nullptr) {
- return _zone_map_index->load(use_page_cache, kept_in_memory,
iter_opts.stats);
+ return _zone_map_index->load(use_page_cache, kept_in_memory,
iter_opts.stats,
+ &iter_opts.io_ctx);
}
return Status::OK();
}
@@ -766,7 +768,8 @@ bool ColumnReader::has_bloom_filter_index(bool ngram) const
{
Status ColumnReader::_load_bloom_filter_index(bool use_page_cache, bool
kept_in_memory,
const ColumnIteratorOptions&
iter_opts) {
if (_bloom_filter_index != nullptr) {
- return _bloom_filter_index->load(use_page_cache, kept_in_memory,
iter_opts.stats);
+ return _bloom_filter_index->load(use_page_cache, kept_in_memory,
iter_opts.stats,
+ &iter_opts.io_ctx);
}
return Status::OK();
}
diff --git a/be/src/storage/segment/segment.cpp
b/be/src/storage/segment/segment.cpp
index c7124e708ee..251452babe5 100644
--- a/be/src/storage/segment/segment.cpp
+++ b/be/src/storage/segment/segment.cpp
@@ -1145,13 +1145,14 @@ Status Segment::seek_and_read_by_rowid(const
TabletSchema& schema, SlotDescripto
DORIS_CHECK(std::adjacent_find(row_ids.begin(), row_ids.end()) ==
row_ids.end());
// ColumnIterator::seek_and_read expects monotonically increasing row_ids
without
// duplicates for correct ordinal scanning. Enforce this contract at the
entry point.
+ auto io_ctx = storage_read_options.io_ctx;
+ io_ctx.reader_type = ReaderType::READER_QUERY;
+ io_ctx.file_cache_stats = &storage_read_options.stats->file_cache_stats;
segment_v2::ColumnIteratorOptions opt {
.use_page_cache = !config::disable_storage_page_cache,
.file_reader = file_reader().get(),
.stats = storage_read_options.stats,
- .io_ctx = io::IOContext {.reader_type = ReaderType::READER_QUERY,
- .file_cache_stats =
-
&storage_read_options.stats->file_cache_stats},
+ .io_ctx = io_ctx,
};
if (!slot->column_paths().empty()) {
diff --git a/be/src/storage/tablet/base_tablet.cpp
b/be/src/storage/tablet/base_tablet.cpp
index 09f73ec43d2..3cd8747ee79 100644
--- a/be/src/storage/tablet/base_tablet.cpp
+++ b/be/src/storage/tablet/base_tablet.cpp
@@ -81,7 +81,8 @@ Status _get_segment_column_iterator(const
BetaRowsetSharedPtr& rowset, uint32_t
const TabletColumn& target_column,
SegmentCacheHandle* segment_cache_handle,
std::unique_ptr<segment_v2::ColumnIterator>* column_iterator,
- OlapReaderStatistics* stats) {
+ OlapReaderStatistics* stats,
+ const io::IOContext* input_io_ctx =
nullptr) {
RETURN_IF_ERROR(SegmentLoader::instance()->load_segments(rowset,
segment_cache_handle, true));
// find segment
auto it = std::find_if(
@@ -95,13 +96,18 @@ Status _get_segment_column_iterator(const
BetaRowsetSharedPtr& rowset, uint32_t
segment_v2::SegmentSharedPtr segment = *it;
StorageReadOptions opts;
opts.stats = stats;
+ if (input_io_ctx != nullptr) {
+ opts.io_ctx = *input_io_ctx;
+ }
RETURN_IF_ERROR(segment->new_column_iterator(target_column,
column_iterator, &opts));
+ auto io_ctx = opts.io_ctx;
+ io_ctx.reader_type = ReaderType::READER_QUERY;
+ io_ctx.file_cache_stats = &stats->file_cache_stats;
segment_v2::ColumnIteratorOptions opt {
.use_page_cache = !config::disable_storage_page_cache,
.file_reader = segment->file_reader().get(),
.stats = stats,
- .io_ctx = io::IOContext {.reader_type = ReaderType::READER_QUERY,
- .file_cache_stats =
&stats->file_cache_stats},
+ .io_ctx = io_ctx,
};
RETURN_IF_ERROR((*column_iterator)->init(opt));
return Status::OK();
@@ -428,7 +434,8 @@ std::vector<RowsetSharedPtr> BaseTablet::get_rowset_by_ids(
Status BaseTablet::lookup_row_data(const Slice& encoded_key, const
RowLocation& row_location,
RowsetSharedPtr input_rowset,
OlapReaderStatistics& stats,
- std::string& values, bool write_to_cache) {
+ std::string& values, bool write_to_cache,
+ const io::IOContext* io_ctx) {
MonotonicStopWatch watch;
size_t row_size = 1;
watch.start();
@@ -444,7 +451,8 @@ Status BaseTablet::lookup_row_data(const Slice&
encoded_key, const RowLocation&
std::unique_ptr<segment_v2::ColumnIterator> column_iterator;
const auto& column =
*DORIS_TRY(tablet_schema->column(BeConsts::ROW_STORE_COL));
RETURN_IF_ERROR(_get_segment_column_iterator(rowset,
row_location.segment_id, column,
- &segment_cache_handle,
&column_iterator, &stats));
+ &segment_cache_handle,
&column_iterator, &stats,
+ io_ctx));
// get and parse tuple row
MutableColumnPtr column_ptr = ColumnString::create();
std::vector<segment_v2::rowid_t> rowids
{static_cast<segment_v2::rowid_t>(row_location.row_id)};
diff --git a/be/src/storage/tablet/base_tablet.h
b/be/src/storage/tablet/base_tablet.h
index a835e2465ee..c26e944b01c 100644
--- a/be/src/storage/tablet/base_tablet.h
+++ b/be/src/storage/tablet/base_tablet.h
@@ -170,7 +170,7 @@ public:
// Lookup a row with TupleDescriptor and fill Block
Status lookup_row_data(const Slice& encoded_key, const RowLocation&
row_location,
RowsetSharedPtr rowset, OlapReaderStatistics&
stats, std::string& values,
- bool write_to_cache = false);
+ bool write_to_cache = false, const io::IOContext*
io_ctx = nullptr);
// Lookup the row location of `encoded_key`, the function sets
`row_location` on success.
// NOTE: the method only works in unique key model with primary key index,
you will got a
// not supported error in other data model.
diff --git a/be/test/exec/operator/materialization_shared_state_test.cpp
b/be/test/exec/operator/materialization_shared_state_test.cpp
index f03a150496e..b1950482a70 100644
--- a/be/test/exec/operator/materialization_shared_state_test.cpp
+++ b/be/test/exec/operator/materialization_shared_state_test.cpp
@@ -23,9 +23,19 @@
#include "core/field.h"
#include "exec/operator/materialization_opertor.h"
#include "exec/pipeline/dependency.h"
+#include "runtime/runtime_profile.h"
namespace doris {
+namespace {
+
+void add_request_row(PRequestBlockDesc* request_block_desc, uint32_t row_id,
uint32_t file_id) {
+ request_block_desc->add_row_id(row_id);
+ request_block_desc->add_file_id(file_id);
+}
+
+} // namespace
+
class MaterializationSharedStateTest : public testing::Test {
protected:
void SetUp() override {
@@ -107,12 +117,10 @@ TEST_F(MaterializationSharedStateTest,
TestMergeMultiResponse) {
// 2. Setup response blocks from multiple backends
// Backend 1's response
{
- _shared_state->rpc_struct_map[_backend_id1]
- .request.mutable_request_block_descs(0)
- ->add_row_id(0);
- _shared_state->rpc_struct_map[_backend_id1]
- .request.mutable_request_block_descs(0)
- ->add_row_id(1);
+ auto* request_block_desc =
+
_shared_state->rpc_struct_map[_backend_id1].request.mutable_request_block_descs(0);
+ add_request_row(request_block_desc, 0, 1);
+ add_request_row(request_block_desc, 1, 1);
Block resp_block1;
auto resp_value_col1 = _int_type->create_column();
auto* value_col_data1 =
reinterpret_cast<ColumnInt32*>(resp_value_col1.get());
@@ -137,9 +145,9 @@ TEST_F(MaterializationSharedStateTest,
TestMergeMultiResponse) {
// Backend 2's response
{
- _shared_state->rpc_struct_map[_backend_id2]
- .request.mutable_request_block_descs(0)
- ->add_row_id(2);
+ add_request_row(
+
_shared_state->rpc_struct_map[_backend_id2].request.mutable_request_block_descs(0),
+ 2, 2);
Block resp_block2;
auto resp_value_col2 = _int_type->create_column();
auto* value_col_data2 =
reinterpret_cast<ColumnInt32*>(resp_value_col2.get());
@@ -166,7 +174,8 @@ TEST_F(MaterializationSharedStateTest,
TestMergeMultiResponse) {
// 4. Test merging responses
Block result_block;
- Status st = _shared_state->merge_multi_response();
+ RuntimeProfile profile("MaterializationSharedStateTest");
+ Status st = _shared_state->merge_multi_response(&profile);
_shared_state->get_block(&result_block);
EXPECT_TRUE(st.ok());
@@ -219,9 +228,9 @@ TEST_F(MaterializationSharedStateTest,
TestMergeMultiResponseMultiBlocks) {
// 2. Setup response blocks from multiple backends for first rowid
{
- _shared_state->rpc_struct_map[_backend_id1]
- .request.mutable_request_block_descs(0)
- ->add_row_id(0);
+ add_request_row(
+
_shared_state->rpc_struct_map[_backend_id1].request.mutable_request_block_descs(0),
+ 0, 1);
Block resp_block1;
auto resp_value_col1 = _int_type->create_column();
auto* value_col_data1 =
reinterpret_cast<ColumnInt32*>(resp_value_col1.get());
@@ -244,9 +253,9 @@ TEST_F(MaterializationSharedStateTest,
TestMergeMultiResponseMultiBlocks) {
// Backend 2's response for first rowid
{
- _shared_state->rpc_struct_map[_backend_id2]
- .request.mutable_request_block_descs(0)
- ->add_row_id(0);
+ add_request_row(
+
_shared_state->rpc_struct_map[_backend_id2].request.mutable_request_block_descs(0),
+ 0, 2);
Block resp_block2;
auto resp_value_col2 = _int_type->create_column();
auto* value_col_data2 =
reinterpret_cast<ColumnInt32*>(resp_value_col2.get());
@@ -270,9 +279,9 @@ TEST_F(MaterializationSharedStateTest,
TestMergeMultiResponseMultiBlocks) {
_shared_state->rpc_struct_map[_backend_id1].request.add_request_block_descs();
_shared_state->rpc_struct_map[_backend_id2].request.add_request_block_descs();
{
- _shared_state->rpc_struct_map[_backend_id1]
- .request.mutable_request_block_descs(1)
- ->add_row_id(0);
+ add_request_row(
+
_shared_state->rpc_struct_map[_backend_id1].request.mutable_request_block_descs(1),
+ 0, 3);
Block resp_block1;
auto resp_value_col1 = _int_type->create_column();
auto* value_col_data1 =
reinterpret_cast<ColumnInt32*>(resp_value_col1.get());
@@ -292,9 +301,9 @@ TEST_F(MaterializationSharedStateTest,
TestMergeMultiResponseMultiBlocks) {
}
{
- _shared_state->rpc_struct_map[_backend_id2]
- .request.mutable_request_block_descs(1)
- ->add_row_id(0);
+ add_request_row(
+
_shared_state->rpc_struct_map[_backend_id2].request.mutable_request_block_descs(1),
+ 0, 4);
Block resp_block2;
auto resp_value_col2 = _int_type->create_column();
auto* value_col_data2 =
reinterpret_cast<ColumnInt32*>(resp_value_col2.get());
@@ -320,7 +329,8 @@ TEST_F(MaterializationSharedStateTest,
TestMergeMultiResponseMultiBlocks) {
// 4. Test merging responses
Block result_block;
- Status st = _shared_state->merge_multi_response();
+ RuntimeProfile profile("MaterializationSharedStateTest");
+ Status st = _shared_state->merge_multi_response(&profile);
EXPECT_TRUE(st.ok());
_shared_state->get_block(&result_block);
@@ -361,9 +371,9 @@ TEST_F(MaterializationSharedStateTest,
TestMergeMultiResponseBackendNotFound) {
// --- BE_1: valid response with 1 row ---
{
- _shared_state->rpc_struct_map[_backend_id1]
- .request.mutable_request_block_descs(0)
- ->add_row_id(0);
+ add_request_row(
+
_shared_state->rpc_struct_map[_backend_id1].request.mutable_request_block_descs(0),
+ 0, 1);
Block resp_block;
auto col = _int_type->create_column();
reinterpret_cast<ColumnInt32*>(col.get())->insert(
@@ -388,9 +398,9 @@ TEST_F(MaterializationSharedStateTest,
TestMergeMultiResponseBackendNotFound) {
// After deserialization this produces a Block with 0 columns,
// so is_empty_column() == true and it won't be inserted into block_maps.
{
- _shared_state->rpc_struct_map[_backend_id2]
- .request.mutable_request_block_descs(0)
- ->add_row_id(0);
+ add_request_row(
+
_shared_state->rpc_struct_map[_backend_id2].request.mutable_request_block_descs(0),
+ 0, 2);
PMultiGetResponseV2 response;
response.add_blocks(); // empty PMultiGetBlockV2, no mutable_block()
data
_shared_state->rpc_struct_map[_backend_id2].response =
std::move(response);
@@ -410,7 +420,8 @@ TEST_F(MaterializationSharedStateTest,
TestMergeMultiResponseBackendNotFound) {
_shared_state->rowid_locs = {0};
// merge_multi_response() should return InternalError
- Status st = _shared_state->merge_multi_response();
+ RuntimeProfile profile("MaterializationSharedStateTest");
+ Status st = _shared_state->merge_multi_response(&profile);
ASSERT_FALSE(st.ok());
ASSERT_TRUE(st.is<ErrorCode::INTERNAL_ERROR>());
ASSERT_TRUE(st.to_string().find("not match request row id count") !=
std::string::npos)
@@ -444,9 +455,9 @@ TEST_F(MaterializationSharedStateTest,
TestMergeMultiResponseStaleBlockMaps) {
// --- Build BE_1's response: blocks[0]=1 row (INT), blocks[1]=empty ---
{
- _shared_state->rpc_struct_map[_backend_id1]
- .request.mutable_request_block_descs(0)
- ->add_row_id(0);
+ add_request_row(
+
_shared_state->rpc_struct_map[_backend_id1].request.mutable_request_block_descs(0),
+ 0, 1);
PMultiGetResponseV2 response;
// blocks[0]: 1 row of INT for relation 0
@@ -481,9 +492,9 @@ TEST_F(MaterializationSharedStateTest,
TestMergeMultiResponseStaleBlockMaps) {
reinterpret_cast<ColumnString*>(col.get())->insert_data("Alice", 5);
rel1_block.insert({make_nullable(std::move(col)),
make_nullable(_string_type), "name"});
- _shared_state->rpc_struct_map[_backend_id2]
- .request.mutable_request_block_descs(1)
- ->add_row_id(0);
+ add_request_row(
+
_shared_state->rpc_struct_map[_backend_id2].request.mutable_request_block_descs(1),
+ 0, 2);
auto* pb1 = response.add_blocks()->mutable_block();
size_t us = 0, cs = 0;
int64_t ct = 0;
@@ -509,7 +520,8 @@ TEST_F(MaterializationSharedStateTest,
TestMergeMultiResponseStaleBlockMaps) {
_shared_state->rowid_locs = {0, 1};
// merge should succeed — each relation only references the BE that has
data
- Status st = _shared_state->merge_multi_response();
+ RuntimeProfile profile("MaterializationSharedStateTest");
+ Status st = _shared_state->merge_multi_response(&profile);
ASSERT_TRUE(st.ok()) << "merge_multi_response failed: " << st.to_string();
// Verify results
diff --git a/be/test/io/cache/block_file_cache_test.cpp
b/be/test/io/cache/block_file_cache_test.cpp
index 18c41953764..974cbe1e502 100644
--- a/be/test/io/cache/block_file_cache_test.cpp
+++ b/be/test/io/cache/block_file_cache_test.cpp
@@ -387,6 +387,140 @@ void complete_into_memory(const io::FileBlocksHolder&
holder) {
}
}
+TEST_F(BlockFileCacheTest,
get_downloaded_blocks_if_fully_covered_is_read_only) {
+ const std::string local_cache_path =
+ (caches_dir / "remote_only_on_miss_helper_cache" / "").string();
+ if (fs::exists(local_cache_path)) {
+ fs::remove_all(local_cache_path);
+ }
+ fs::create_directories(local_cache_path);
+
+ io::BlockFileCache mgr(local_cache_path,
cached_remote_reader_cache_settings());
+ ASSERT_TRUE(mgr.initialize().ok());
+
+ ReadStatistics read_stats;
+ io::CacheContext context;
+ context.stats = &read_stats;
+ context.cache_type = io::FileCacheType::NORMAL;
+ auto key = io::BlockFileCache::hash("remote_only_on_miss_helper_key");
+ const size_t block_size =
cached_remote_reader_cache_settings().max_file_block_size;
+
+ auto assert_not_fully_covered = [&](size_t offset, size_t size, size_t
expected_blocks_num) {
+ io::FileBlocks blocks;
+ bool fully_covered = true;
+ ASSERT_TRUE(mgr.get_downloaded_blocks_if_fully_covered(key, offset,
size, context, &blocks,
+ &fully_covered)
+ .ok());
+ EXPECT_FALSE(fully_covered);
+ EXPECT_TRUE(blocks.empty());
+ EXPECT_EQ(mgr.get_file_blocks_num(io::FileCacheType::NORMAL),
expected_blocks_num);
+ };
+
+ auto assert_fully_covered = [&](size_t offset, size_t size, size_t
expected_blocks_size) {
+ io::FileBlocks blocks;
+ bool fully_covered = false;
+ ASSERT_TRUE(mgr.get_downloaded_blocks_if_fully_covered(key, offset,
size, context, &blocks,
+ &fully_covered)
+ .ok());
+ EXPECT_TRUE(fully_covered);
+ EXPECT_EQ(blocks.size(), expected_blocks_size);
+ };
+
+ assert_not_fully_covered(0, 10, 0);
+
+ complete_into_memory(mgr.get_or_set(key, 0, block_size, context));
+ ASSERT_EQ(mgr.get_file_blocks_num(io::FileCacheType::NORMAL), 1);
+ assert_fully_covered(2, 5, 1);
+ assert_not_fully_covered(block_size - 4, 8, 1);
+
+ complete_into_memory(mgr.get_or_set(key, block_size, block_size, context));
+ ASSERT_EQ(mgr.get_file_blocks_num(io::FileCacheType::NORMAL), 2);
+ assert_fully_covered(block_size - 4, 8, 2);
+
+ complete_into_memory(mgr.get_or_set(key, 2 * block_size, block_size,
context));
+ ASSERT_EQ(mgr.get_file_blocks_num(io::FileCacheType::NORMAL), 3);
+ assert_fully_covered(block_size - 4, block_size + 8, 3);
+
+ const size_t partial_block_size = 123 * 1024 + 17;
+ complete_into_memory(mgr.get_or_set(key, 3 * block_size,
partial_block_size, context));
+ ASSERT_EQ(mgr.get_file_blocks_num(io::FileCacheType::NORMAL), 4);
+ assert_fully_covered(3 * block_size + 7, 31, 1);
+ assert_fully_covered(3 * block_size - 4, 16, 2);
+ assert_fully_covered(0, 3 * block_size + partial_block_size, 4);
+ assert_not_fully_covered(3 * block_size + partial_block_size - 4, 8, 4);
+ assert_not_fully_covered(0, 3 * block_size + partial_block_size + 1, 4);
+}
+
+TEST_F(BlockFileCacheTest, cached_remote_file_reader_remote_only_on_miss) {
+ const std::string local_cache_path =
+ (caches_dir / "remote_only_on_miss_reader_cache" / "").string();
+ BlockFileCache* cache = nullptr;
+ ASSERT_TRUE(create_cached_remote_reader_cache(local_cache_path,
&cache).ok());
+
+ {
+ const auto remote_file = caches_dir /
"remote_only_on_miss_reader_file";
+ {
+ std::ofstream ofs(remote_file, std::ios::binary | std::ios::trunc);
+ ASSERT_TRUE(ofs.is_open());
+ for (int i = 0; i < 2; ++i) {
+ std::string data(1_mb, static_cast<char>('a' + i));
+ ofs.write(data.data(), data.size());
+ }
+ }
+
+ FileReaderSPtr local_reader;
+ ASSERT_TRUE(global_local_filesystem()->open_file(remote_file,
&local_reader).ok());
+ io::FileReaderOptions opts;
+ opts.cache_type = io::FileCachePolicy::FILE_BLOCK_CACHE;
+ opts.is_doris_table = true;
+ opts.tablet_id = 10086;
+ auto reader = std::make_shared<CachedRemoteFileReader>(local_reader,
opts);
+
+ auto key = io::BlockFileCache::hash(remote_file.filename().string());
+ Defer fd_cache_cleanup {
+ [key] {
io::FDCache::instance()->remove_file_reader(std::make_pair(key, 0)); }};
+ cache->remove_if_cached(key);
+
+ std::string buffer(4_kb, '\0');
+ size_t bytes_read = 0;
+ io::IOContext io_ctx;
+ io::FileCacheStatistics stats;
+ io_ctx.file_cache_stats = &stats;
+ io_ctx.file_cache_miss_policy =
io::FileCacheMissPolicy::REMOTE_ONLY_ON_MISS;
+
+ ASSERT_TRUE(reader->read_at(123, Slice(buffer.data(), buffer.size()),
&bytes_read, &io_ctx)
+ .ok());
+ EXPECT_EQ(bytes_read, buffer.size());
+ EXPECT_EQ(stats.num_remote_io_total, 1);
+ EXPECT_EQ(stats.bytes_read_from_remote, buffer.size());
+ EXPECT_EQ(stats.num_skip_cache_io_total, 1);
+ EXPECT_EQ(stats.bytes_write_into_cache, 0);
+ EXPECT_TRUE(cache->get_blocks_by_key(key).empty());
+
+ io::FileCacheStatistics write_back_stats;
+ io_ctx.file_cache_stats = &write_back_stats;
+ io_ctx.file_cache_miss_policy =
io::FileCacheMissPolicy::READ_THROUGH_AND_WRITE_BACK;
+ ASSERT_TRUE(reader->read_at(123, Slice(buffer.data(), buffer.size()),
&bytes_read, &io_ctx)
+ .ok());
+ EXPECT_EQ(bytes_read, buffer.size());
+ EXPECT_GT(write_back_stats.bytes_write_into_cache, 0);
+ EXPECT_FALSE(cache->get_blocks_by_key(key).empty());
+
+ io::FileCacheStatistics full_hit_stats;
+ io_ctx.file_cache_stats = &full_hit_stats;
+ io_ctx.file_cache_miss_policy =
io::FileCacheMissPolicy::REMOTE_ONLY_ON_MISS;
+ ASSERT_TRUE(reader->read_at(123, Slice(buffer.data(), buffer.size()),
&bytes_read, &io_ctx)
+ .ok());
+ EXPECT_EQ(bytes_read, buffer.size());
+ EXPECT_EQ(full_hit_stats.num_local_io_total, 1);
+ EXPECT_EQ(full_hit_stats.bytes_read_from_local, buffer.size());
+ EXPECT_EQ(full_hit_stats.num_remote_io_total, 0);
+ EXPECT_EQ(full_hit_stats.bytes_write_into_cache, 0);
+ }
+
+ cleanup_cached_remote_reader_cache(local_cache_path);
+}
+
void test_file_cache(io::FileCacheType cache_type) {
TUniqueId query_id;
query_id.hi = 1;
diff --git a/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java
b/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java
index 749726bc9ca..3e613bc0a31 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java
@@ -514,6 +514,9 @@ public class SessionVariable implements Serializable,
Writable {
public static final String DISABLE_FILE_CACHE = "disable_file_cache";
+ public static final String ENABLE_TOPN_LAZY_MAT_PHASE2_NO_WRITE_FILE_CACHE
+ = "enable_topn_lazy_mat_phase2_no_write_file_cache";
+
public static final String FILE_CACHE_QUERY_LIMIT_PERCENT =
"file_cache_query_limit_percent";
public static final String FILE_CACHE_BASE_PATH = "file_cache_base_path";
@@ -2308,6 +2311,14 @@ public class SessionVariable implements Serializable,
Writable {
@VarAttrDef.VarAttr(name = DISABLE_FILE_CACHE, needForward = true)
public boolean disableFileCache = false;
+ @VarAttrDef.VarAttr(name =
ENABLE_TOPN_LAZY_MAT_PHASE2_NO_WRITE_FILE_CACHE, needForward = true,
+ description = {
+ "开启后,TopN 延迟物化第二阶段读取在 file cache miss 时直接读远端且不写回 file
cache。",
+ "When enabled, TopN lazy materialization phase-2 reads go
remote-only on "
+ + "file-cache miss and do not write the missed
range back to file cache."
+ })
+ public boolean enableTopnLazyMatPhase2NoWriteFileCache = false;
+
// Whether enable block file cache. Only take effect when BE config item
enable_file_cache is true.
@VarAttrDef.VarAttr(name = ENABLE_FILE_CACHE, needForward = true,
description = {
"是否启用 file cache。该变量只有在 be.conf 中 enable_file_cache=true 时才有效,"
@@ -5685,6 +5696,7 @@ public class SessionVariable implements Serializable,
Writable {
tResult.setParallelScanMinRowsPerScanner(parallelScanMinRowsPerScanner);
tResult.setOptimizeIndexScanParallelism(optimizeIndexScanParallelism);
tResult.setDisableFileCache(disableFileCache);
+
tResult.setEnableTopnLazyMatPhase2NoWriteFileCache(enableTopnLazyMatPhase2NoWriteFileCache);
tResult.setEnablePreferCachedRowset(getEnablePreferCachedRowset());
tResult.setQueryFreshnessToleranceMs(getQueryFreshnessToleranceMs());
diff --git a/gensrc/proto/internal_service.proto
b/gensrc/proto/internal_service.proto
index 2ca659b283c..ec60a1b2a3a 100644
--- a/gensrc/proto/internal_service.proto
+++ b/gensrc/proto/internal_service.proto
@@ -822,6 +822,18 @@ message PRequestBlockDesc {
repeated uint32 column_idxs = 7;
}
+message PTopNLazyMaterializationFileCacheStats {
+ optional int64 local_io_count = 1;
+ optional int64 local_io_bytes = 2;
+ optional int64 remote_io_count = 3;
+ optional int64 remote_io_bytes = 4;
+ optional int64 skip_cache_io_count = 5;
+ optional int64 write_cache_bytes = 6;
+ optional int64 local_io_time = 7;
+ optional int64 remote_io_time = 8;
+ optional int64 write_cache_io_time = 9;
+}
+
message PMultiGetRequestV2 {
repeated PRequestBlockDesc request_block_descs = 1;
@@ -830,6 +842,7 @@ message PMultiGetRequestV2 {
optional PUniqueId query_id = 3;
optional bool gc_id_map = 4;
optional uint64 wg_id = 5;
+ optional bool file_cache_remote_only_on_miss = 6;
};
message PMultiGetBlockV2 {
@@ -846,6 +859,7 @@ message PMultiGetBlockV2 {
message PMultiGetResponseV2 {
optional PStatus status = 1;
repeated PMultiGetBlockV2 blocks = 2;
+ optional PTopNLazyMaterializationFileCacheStats
topn_lazy_materialization_file_cache_stats = 3;
};
message PFetchColIdsRequest {
diff --git a/gensrc/thrift/PaloInternalService.thrift
b/gensrc/thrift/PaloInternalService.thrift
index 97b9ef86b99..49416576064 100644
--- a/gensrc/thrift/PaloInternalService.thrift
+++ b/gensrc/thrift/PaloInternalService.thrift
@@ -513,6 +513,7 @@ struct TQueryOptions {
1000: optional bool disable_file_cache = false
1001: optional i32 file_cache_query_limit_percent = -1
1002: optional bool enable_file_scanner_v2 = false
+ 1003: optional bool enable_topn_lazy_mat_phase2_no_write_file_cache = false
}
diff --git
a/regression-test/suites/cloud_p0/cache/topn_lazy_file_cache/test_topn_lazy_mat_phase2_no_write_file_cache.groovy
b/regression-test/suites/cloud_p0/cache/topn_lazy_file_cache/test_topn_lazy_mat_phase2_no_write_file_cache.groovy
new file mode 100644
index 00000000000..2e3ea83265c
--- /dev/null
+++
b/regression-test/suites/cloud_p0/cache/topn_lazy_file_cache/test_topn_lazy_mat_phase2_no_write_file_cache.groovy
@@ -0,0 +1,294 @@
+// 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.
+
+import org.apache.doris.regression.suite.ClusterOptions
+import org.apache.doris.regression.util.Http
+
+import java.util.regex.Pattern
+
+suite("test_topn_lazy_mat_phase2_no_write_file_cache", "docker") {
+ def options = new ClusterOptions()
+ options.feNum = 1
+ options.beNum = 1
+ options.msNum = 1
+ options.cloudMode = true
+ options.beConfigs += [
+ "enable_file_cache=true",
+ "disable_storage_page_cache=true",
+ "enable_java_support=false",
+ "enable_evict_file_cache_in_advance=false",
+ "file_cache_enter_disk_resource_limit_mode_percent=99",
+ "file_cache_each_block_size=4096",
+
"file_cache_path=[{\"path\":\"/opt/apache-doris/be/storage/file_cache\","
+ + "\"total_size\":104857600,\"query_limit\":104857600}]"
+ ]
+
+ docker(options) {
+ def clusters = sql "SHOW CLUSTERS"
+ assert !clusters.isEmpty()
+ def computeGroup = clusters[0][0]
+ sql "use @${computeGroup}"
+
+ def backends = sql "SHOW BACKENDS"
+ assert backends.size() == 1
+ def beHost = backends[0][1]
+ def beHttpPort = backends[0][4]
+ def clearFileCache = {
+ def result =
Http.GET("http://${beHost}:${beHttpPort}/api/file_cache?op=clear&sync=true",
true)
+ logger.info("clear file cache result: ${result}")
+ }
+
+ sql "set enable_profile = true"
+ sql "set profile_level = 2"
+ sql "set enable_sql_cache = false"
+ sql "set enable_query_cache = false"
+ sql "set enable_page_cache = false"
+ sql "set topn_lazy_materialization_threshold = 1024"
+
+ def metricValue = { String profileText, String metricName ->
+ def matcher = profileText =~
+
/(?m)^\s*-\s*${Pattern.quote(metricName)}:\s*(?:sum\s+)?([0-9]+(?:\.[0-9]+)?).*$/
+ assert matcher.find() : "missing metric ${metricName} in
profile:\n${profileText}"
+ return new BigDecimal(matcher.group(1))
+ }
+
+ def metricValues = { String profileText, String metricName ->
+ def matcher = profileText =~
+
/(?m)^\s*-\s*${Pattern.quote(metricName)}:\s*(?:sum\s+)?([0-9]+(?:\.[0-9]+)?).*$/
+ def values = []
+ while (matcher.find()) {
+ values.add(new BigDecimal(matcher.group(1)))
+ }
+ assert !values.isEmpty() : "missing metric ${metricName} in
profile:\n${profileText}"
+ return values
+ }
+
+ def numericValues = { String text ->
+ def matcher = text =~ /([0-9]+(?:\.[0-9]+)?)/
+ def values = []
+ while (matcher.find()) {
+ values.add(new BigDecimal(matcher.group(1)))
+ }
+ return values
+ }
+
+ def metricLineValues = { String profileText, String metricName ->
+ def matcher = profileText =~
/(?m)^\s*-\s*${Pattern.quote(metricName)}:\s*(.*)$/
+ def values = []
+ while (matcher.find()) {
+ values.add(matcher.group(1).trim())
+ }
+ return values
+ }
+
+ def topnSecondPhaseMetricNames = [
+ "TopNLazyMaterializationSecondPhaseLocalIOCount",
+ "TopNLazyMaterializationSecondPhaseLocalIOBytes",
+ "TopNLazyMaterializationSecondPhaseRemoteIOCount",
+ "TopNLazyMaterializationSecondPhaseRemoteIOBytes",
+ "TopNLazyMaterializationSecondPhaseSkipCacheIOCount",
+ "TopNLazyMaterializationSecondPhaseWriteCacheBytes",
+ "TopNLazyMaterializationSecondPhaseLocalIOTime",
+ "TopNLazyMaterializationSecondPhaseRemoteIOTime",
+ "TopNLazyMaterializationSecondPhaseWriteCacheIOTime",
+ "TopNLazyMaterializationSecondPhaseRowsRead",
+ "TopNLazyMaterializationSecondPhaseSegmentsRead",
+ "TopNLazyMaterializationSecondPhasePerBackend",
+ "TopNLazyMaterializationSecondPhasePerBackendRowsRead",
+ "TopNLazyMaterializationSecondPhasePerBackendSegmentsRead",
+ "TopNLazyMaterializationSecondPhasePerBackendLocalIOCount",
+ "TopNLazyMaterializationSecondPhasePerBackendLocalIOBytes",
+ "TopNLazyMaterializationSecondPhasePerBackendRemoteIOCount",
+ "TopNLazyMaterializationSecondPhasePerBackendRemoteIOBytes",
+ "TopNLazyMaterializationSecondPhasePerBackendSkipCacheIOCount",
+ "TopNLazyMaterializationSecondPhasePerBackendWriteCacheBytes",
+ "TopNLazyMaterializationSecondPhasePerBackendLocalIOTime",
+ "TopNLazyMaterializationSecondPhasePerBackendRemoteIOTime",
+ "TopNLazyMaterializationSecondPhasePerBackendWriteCacheIOTime"
+ ]
+
+ def logTopnSecondPhaseMetrics = { String name, String profileText ->
+ def metrics = topnSecondPhaseMetricNames.collectEntries {
metricName ->
+ def values = metricLineValues(profileText, metricName)
+ assert !values.isEmpty() : "missing metric ${metricName} in
profile:\n${profileText}"
+ [(metricName): values]
+ }
+ logger.info("${name} TopN lazy materialization second phase
metrics: ${metrics}")
+ logger.info("${name} CachedPagesNum values:
${metricLineValues(profileText, 'CachedPagesNum')}")
+ }
+
+ def runProfileQuery = { String name, String query, Closure checker ->
+ profile(name) {
+ run {
+ sql "/* ${name} */ ${query}"
+ sleep(1000)
+ }
+ check { profileString, exception ->
+ if (exception != null) {
+ logger.error("Profile failed, profile
result:\n${profileString}", exception)
+ throw exception
+ }
+ assert
profileString.contains("TopNLazyMaterializationSecondPhase") :
+ "missing TopN lazy materialization
profile:\n${profileString}"
+ assert profileString.contains("Is Cached: No")
+ || profileString.contains("Is Cached: No") :
+ "query should not hit SQL/query
cache:\n${profileString}"
+ logTopnSecondPhaseMetrics(name, profileString)
+ checker(profileString)
+ }
+ }
+ }
+
+ def assertRemoteOnlyMiss = { String profileText ->
+ assert metricValue(profileText,
"TopNLazyMaterializationSecondPhaseRemoteIOCount") > 0
+ assert metricValue(profileText,
"TopNLazyMaterializationSecondPhaseRemoteIOBytes") > 0
+ assert metricValue(profileText,
"TopNLazyMaterializationSecondPhaseSkipCacheIOCount") > 0
+ assert metricValue(profileText,
"TopNLazyMaterializationSecondPhaseWriteCacheBytes")
+ .compareTo(BigDecimal.ZERO) == 0
+ }
+
+ def assertPerBackendRowsMatchAggregate = { String profileText ->
+ def aggregateRows = metricValues(profileText,
+ "TopNLazyMaterializationSecondPhaseRowsRead")
+ .inject(BigDecimal.ZERO) { sum, value -> sum + value }
+ def perBackendRows = metricLineValues(profileText,
+ "TopNLazyMaterializationSecondPhasePerBackendRowsRead")
+ .collectMany { line -> numericValues(line) }
+ assert !perBackendRows.isEmpty() :
+ "missing per-backend rows-read values:\n${profileText}"
+ def perBackendRowsSum = perBackendRows.inject(BigDecimal.ZERO) {
sum, value -> sum + value }
+ assert aggregateRows.compareTo(perBackendRowsSum) == 0 :
+ "per-backend rows-read sum ${perBackendRowsSum} should
equal aggregate ${aggregateRows}:\n${profileText}"
+ }
+
+ def assertLocalFullHit = { String profileText ->
+ assert metricValue(profileText,
"TopNLazyMaterializationSecondPhaseLocalIOCount") > 0
+ assert metricValue(profileText,
"TopNLazyMaterializationSecondPhaseLocalIOBytes") > 0
+ assert metricValue(profileText,
"TopNLazyMaterializationSecondPhaseRemoteIOCount")
+ .compareTo(BigDecimal.ZERO) == 0
+ assert metricValue(profileText,
"TopNLazyMaterializationSecondPhaseRemoteIOBytes")
+ .compareTo(BigDecimal.ZERO) == 0
+ assert metricValue(profileText,
"TopNLazyMaterializationSecondPhaseWriteCacheBytes")
+ .compareTo(BigDecimal.ZERO) == 0
+ }
+
+ sql "DROP TABLE IF EXISTS topn_lazy_remote_only_no_row_store"
+ sql """
+ CREATE TABLE topn_lazy_remote_only_no_row_store (
+ k INT NOT NULL,
+ sort_key INT NOT NULL,
+ payload VARCHAR(4096) NOT NULL,
+ pad VARCHAR(4096) NOT NULL
+ ) ENGINE=OLAP
+ DUPLICATE KEY(k)
+ DISTRIBUTED BY HASH(k) BUCKETS 1
+ PROPERTIES (
+ "replication_num" = "1",
+ "disable_auto_compaction" = "true",
+ "store_row_column" = "false"
+ )
+ """
+ sql """
+ INSERT INTO topn_lazy_remote_only_no_row_store
+ SELECT number,
+ 4096 - number,
+ repeat(cast(number as string), 128),
+ repeat('x', 256)
+ FROM numbers("number" = "4096")
+ """
+
+ def noRowStoreQuery =
+ "SELECT k, payload, pad FROM
topn_lazy_remote_only_no_row_store ORDER BY sort_key LIMIT 16"
+ explain {
+ sql noRowStoreQuery
+ contains("MaterializeNode")
+ }
+
+ clearFileCache()
+ sql "set enable_topn_lazy_mat_phase2_no_write_file_cache = true"
+ runProfileQuery("topn_lazy_remote_only_no_row_store_remote_only_miss",
+ noRowStoreQuery, assertRemoteOnlyMiss)
+
+ clearFileCache()
+ sql "set enable_topn_lazy_mat_phase2_no_write_file_cache = false"
+ sql "/* topn_lazy_remote_only_no_row_store_local_full_hit_warm */
${noRowStoreQuery}"
+ sleep(1000)
+
+ sql "set enable_topn_lazy_mat_phase2_no_write_file_cache = true"
+ runProfileQuery("topn_lazy_remote_only_no_row_store_local_full_hit",
+ noRowStoreQuery, assertLocalFullHit)
+
+ clearFileCache()
+ sql "set enable_topn_lazy_mat_phase2_no_write_file_cache = true"
+ sql "set batch_size = 1"
+ try {
+
runProfileQuery("topn_lazy_remote_only_no_row_store_multi_fetch_accumulate",
+ noRowStoreQuery, { profileText ->
+ assertRemoteOnlyMiss(profileText)
+ assertPerBackendRowsMatchAggregate(profileText)
+ })
+ } finally {
+ sql "set batch_size = 4062"
+ }
+
+ sql "DROP TABLE IF EXISTS topn_lazy_remote_only_row_store"
+ sql """
+ CREATE TABLE topn_lazy_remote_only_row_store (
+ k INT NOT NULL,
+ sort_key INT NOT NULL,
+ payload VARCHAR(4096) NOT NULL,
+ pad VARCHAR(4096) NOT NULL
+ ) ENGINE=OLAP
+ DUPLICATE KEY(k)
+ DISTRIBUTED BY HASH(k) BUCKETS 1
+ PROPERTIES (
+ "replication_num" = "1",
+ "disable_auto_compaction" = "true",
+ "store_row_column" = "true"
+ )
+ """
+ sql """
+ INSERT INTO topn_lazy_remote_only_row_store
+ SELECT number,
+ 4096 - number,
+ repeat(cast(number as string), 128),
+ repeat('x', 256)
+ FROM numbers("number" = "4096")
+ """
+
+ def rowStoreQuery =
+ "SELECT k, payload, pad FROM topn_lazy_remote_only_row_store
ORDER BY sort_key LIMIT 16"
+ explain {
+ sql rowStoreQuery
+ contains("MaterializeNode")
+ }
+
+ clearFileCache()
+ sql "set enable_topn_lazy_mat_phase2_no_write_file_cache = true"
+ runProfileQuery("topn_lazy_remote_only_row_store_remote_only_miss",
+ rowStoreQuery, assertRemoteOnlyMiss)
+
+ clearFileCache()
+ sql "set enable_topn_lazy_mat_phase2_no_write_file_cache = false"
+ sql "/* topn_lazy_remote_only_row_store_local_full_hit_warm */
${rowStoreQuery}"
+ sleep(1000)
+
+ sql "set enable_topn_lazy_mat_phase2_no_write_file_cache = true"
+ runProfileQuery("topn_lazy_remote_only_row_store_local_full_hit",
+ rowStoreQuery, assertLocalFullHit)
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]