blob: 7dca1d1517f6805dcebafdf8835b9adaacb78bc1 [file]
// 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 "cloud/cloud_internal_service.h"
#include <brpc/controller.h>
#include <bthread/countdown_event.h>
#include <butil/iobuf.h>
#include <fmt/format.h>
#include <algorithm>
#include <chrono>
#include <limits>
#include <list>
#include <memory>
#include <optional>
#include <thread>
#include <unordered_map>
#include <vector>
#include "cloud/cloud_storage_engine.h"
#include "cloud/cloud_tablet.h"
#include "cloud/cloud_tablet_mgr.h"
#include "cloud/cloud_warm_up_manager.h"
#include "cloud/cloud_warmup_metrics.h"
#include "cloud/config.h"
#include "io/cache/block_file_cache.h"
#include "io/cache/block_file_cache_downloader.h"
#include "io/cache/block_file_cache_factory.h"
#include "io/fs/path.h"
#include "runtime/thread_context.h"
#include "runtime/workload_management/io_throttle.h"
#include "storage/storage_policy.h"
#include "util/async_io.h"
#include "util/bvar_windowed_adder.h"
#include "util/debug_points.h"
namespace doris {
#include "common/compile_check_avoid_begin.h"
bvar::Adder<uint64_t> g_file_cache_get_by_peer_num("file_cache_get_by_peer_num");
bvar::Adder<uint64_t> g_file_cache_get_by_peer_blocks_num("file_cache_get_by_peer_blocks_num");
bvar::Adder<uint64_t> g_file_cache_get_by_peer_success_num("file_cache_get_by_peer_success_num");
bvar::Adder<uint64_t> g_file_cache_get_by_peer_failed_num("file_cache_get_by_peer_failed_num");
bvar::LatencyRecorder g_file_cache_get_by_peer_server_latency(
"file_cache_get_by_peer_server_latency");
bvar::LatencyRecorder g_file_cache_get_by_peer_read_cache_file_latency(
"file_cache_get_by_peer_read_cache_file_latency");
bvar::Adder<uint64_t> g_file_cache_get_by_peer_offer_failed_num(
"file_cache_get_by_peer_offer_failed_num");
bvar::Adder<uint64_t> g_file_cache_get_by_peer_queue_timeout_num(
"file_cache_get_by_peer_queue_timeout_num");
bvar::LatencyRecorder g_file_cache_get_by_peer_queue_wait_latency(
"file_cache_get_by_peer_queue_wait_latency");
bvar::LatencyRecorder g_file_cache_get_by_peer_handle_cache_block_req_latency(
"file_cache_get_by_peer_handle_cache_block_req_latency");
bvar::LatencyRecorder g_file_cache_get_by_peer_get_cache_latency(
"file_cache_get_by_peer_get_cache_latency");
bvar::LatencyRecorder g_file_cache_get_by_peer_get_or_set_latency(
"file_cache_get_by_peer_get_or_set_latency");
bvar::Adder<uint64_t> g_file_cache_get_by_peer_get_or_set_calls(
"file_cache_get_by_peer_get_or_set_calls");
bvar::Adder<uint64_t> g_file_cache_get_by_peer_get_or_set_blocks_total(
"file_cache_get_by_peer_get_or_set_blocks_total");
bvar::Adder<uint64_t> g_file_cache_get_by_peer_request_blocks_total(
"file_cache_get_by_peer_request_blocks_total");
bvar::LatencyRecorder g_file_cache_get_by_peer_request_blocks_per_rpc(
"file_cache_get_by_peer_request_blocks_per_rpc");
bvar::Adder<uint64_t> g_file_cache_get_by_peer_response_blocks_total(
"file_cache_get_by_peer_response_blocks_total");
bvar::Adder<uint64_t> g_file_cache_get_by_peer_response_bytes_total(
"file_cache_get_by_peer_response_bytes_total");
bvar::Adder<uint64_t> g_file_cache_get_by_peer_not_downloaded_block_num(
"file_cache_get_by_peer_not_downloaded_block_num");
bvar::LatencyRecorder g_file_cache_get_by_peer_read_file_block_total_latency(
"file_cache_get_by_peer_read_file_block_total_latency");
bvar::LatencyRecorder g_file_cache_get_by_peer_set_response_data_latency(
"file_cache_get_by_peer_set_response_data_latency");
bvar::Adder<uint64_t> g_file_cache_get_by_peer_attachment_response_num(
"file_cache_get_by_peer_attachment_response_num");
bvar::Adder<uint64_t> g_file_cache_get_by_peer_pb_response_num(
"file_cache_get_by_peer_pb_response_num");
bvar::Adder<int64_t> g_peer_server_fill_requested("peer_server_fill_requested");
bvar::Adder<int64_t> g_peer_server_fill_success("peer_server_fill_success");
bvar::Adder<int64_t> g_peer_server_fill_timeout("peer_server_fill_timeout");
bvar::Adder<int64_t> g_peer_server_fill_rejected("peer_server_fill_rejected");
bvar::LatencyRecorder g_peer_server_fill_latency("peer_server_fill_latency");
bvar::LatencyRecorder g_cloud_internal_service_get_file_cache_meta_by_tablet_id_latency(
"cloud_internal_service_get_file_cache_meta_by_tablet_id_latency");
// Concurrency guard for server-side S3 pull-through fills.
static std::atomic<int32_t> g_active_server_fills {0};
bvar::PassiveStatus<int32_t> g_peer_active_fills(
"peer_active_fills",
[](void*) { return g_active_server_fills.load(std::memory_order_relaxed); }, nullptr);
CloudInternalServiceImpl::CloudInternalServiceImpl(CloudStorageEngine& engine, ExecEnv* exec_env)
: PInternalService(exec_env), _engine(engine) {}
CloudInternalServiceImpl::~CloudInternalServiceImpl() = default;
void CloudInternalServiceImpl::alter_vault_sync(google::protobuf::RpcController* controller,
const doris::PAlterVaultSyncRequest* request,
PAlterVaultSyncResponse* response,
google::protobuf::Closure* done) {
LOG(INFO) << "alter be to sync vault info from Meta Service";
// If the vaults containing hdfs vault then it would try to create hdfs connection using jni
// which would acuiqre one thread local jniEnv. But bthread context can't guarantee that the brpc
// worker thread wouldn't do bthread switch between worker threads.
bool ret = _heavy_work_pool.try_offer([this, done]() {
brpc::ClosureGuard closure_guard(done);
_engine.sync_storage_vault();
});
if (!ret) {
brpc::ClosureGuard closure_guard(done);
LOG(WARNING) << "fail to offer alter_vault_sync request to the work pool, pool="
<< _heavy_work_pool.get_info();
}
}
FileCacheType cache_type_to_pb(io::FileCacheType type) {
switch (type) {
case io::FileCacheType::TTL:
return FileCacheType::TTL;
case io::FileCacheType::INDEX:
return FileCacheType::INDEX;
case io::FileCacheType::NORMAL:
return FileCacheType::NORMAL;
default:
DCHECK(false);
}
return FileCacheType::NORMAL;
}
static int64_t current_unix_time_us() {
return std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::system_clock::now().time_since_epoch())
.count();
}
static std::optional<int64_t> warm_up_rowset_cross_host_latency_us(int64_t start_unix_ts_us,
int64_t end_unix_ts_us) {
// The start timestamp is generated by the caller BE. Mixed-version callers may omit it, and
// system clocks across BEs are not guaranteed to be ordered.
if (start_unix_ts_us <= 0 || end_unix_ts_us < start_unix_ts_us) {
return std::nullopt;
}
return end_unix_ts_us - start_unix_ts_us;
}
static void add_file_cache_block_meta_to_response(
PGetFileCacheMetaResponse* resp, int64_t tablet_id, const std::string& rowset_id,
int32_t segment_id, const std::string& file_name,
const std::tuple<int64_t, int64_t, io::FileCacheType, int64_t>& tuple,
const RowsetSharedPtr& rowset, bool is_index) {
FileCacheBlockMeta* meta = resp->add_file_cache_block_metas();
meta->set_tablet_id(tablet_id);
meta->set_rowset_id(rowset_id);
meta->set_segment_id(segment_id);
meta->set_file_name(file_name);
if (!is_index) {
// .dat
meta->set_file_size(rowset->rowset_meta()->segment_file_size(segment_id));
meta->set_file_type(doris::FileType::SEGMENT_FILE);
} else {
// .idx
const auto& idx_file_info = rowset->rowset_meta()->inverted_index_file_info(segment_id);
meta->set_file_size(idx_file_info.has_index_size() ? idx_file_info.index_size() : -1);
meta->set_file_type(doris::FileType::INVERTED_INDEX_FILE);
}
meta->set_offset(std::get<0>(tuple));
meta->set_size(std::get<1>(tuple));
meta->set_cache_type(cache_type_to_pb(std::get<2>(tuple)));
meta->set_expiration_time(std::get<3>(tuple));
}
static void process_segment_file_cache_meta(PGetFileCacheMetaResponse* resp,
const RowsetSharedPtr& rowset, int64_t tablet_id,
const std::string& rowset_id, int32_t segment_id,
bool is_index) {
const char* extension = is_index ? ".idx" : ".dat";
std::string file_name = fmt::format("{}_{}{}", rowset_id, segment_id, extension);
auto cache_key = io::BlockFileCache::hash(file_name);
auto* cache = io::FileCacheFactory::instance()->get_by_path(cache_key);
if (!cache) return;
auto segments_meta = cache->get_hot_blocks_meta(cache_key);
for (const auto& tuple : segments_meta) {
add_file_cache_block_meta_to_response(resp, tablet_id, rowset_id, segment_id, file_name,
tuple, rowset, is_index);
}
}
void CloudInternalServiceImpl::get_file_cache_meta_by_tablet_id(
google::protobuf::RpcController* controller [[maybe_unused]],
const PGetFileCacheMetaRequest* request, PGetFileCacheMetaResponse* response,
google::protobuf::Closure* done) {
brpc::ClosureGuard closure_guard(done);
if (!config::enable_file_cache) {
LOG_WARNING("try to access tablet file cache meta, but file cache not enabled");
return;
}
auto begin_ts = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now().time_since_epoch())
.count();
std::ostringstream tablet_ids_stream;
int count = 0;
for (const auto& tablet_id : request->tablet_ids()) {
tablet_ids_stream << tablet_id << ", ";
count++;
if (count >= 10) {
break;
}
}
LOG(INFO) << "warm up get meta from this be, tablets num=" << request->tablet_ids().size()
<< ", first 10 tablet_ids=[ " << tablet_ids_stream.str() << " ]";
for (const auto& tablet_id : request->tablet_ids()) {
auto res = _engine.tablet_mgr().get_tablet(tablet_id);
if (!res.has_value()) {
LOG(ERROR) << "failed to get tablet: " << tablet_id
<< " err msg: " << res.error().msg();
continue;
}
CloudTabletSPtr tablet = std::move(res.value());
auto st = tablet->sync_rowsets();
if (!st) {
// just log failed, try it best
LOG(WARNING) << "failed to sync rowsets: " << tablet_id
<< " err msg: " << st.to_string();
}
auto rowsets = tablet->get_snapshot_rowset();
for (const RowsetSharedPtr& rowset : rowsets) {
std::string rowset_id = rowset->rowset_id().to_string();
for (int32_t segment_id = 0; segment_id < rowset->num_segments(); ++segment_id) {
process_segment_file_cache_meta(response, rowset, tablet_id, rowset_id, segment_id,
false);
process_segment_file_cache_meta(response, rowset, tablet_id, rowset_id, segment_id,
true);
}
}
}
auto end_ts = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now().time_since_epoch())
.count();
g_cloud_internal_service_get_file_cache_meta_by_tablet_id_latency << (end_ts - begin_ts);
LOG(INFO) << "get file cache meta by tablet ids = [ " << tablet_ids_stream.str() << " ] took "
<< end_ts - begin_ts << " us";
VLOG_DEBUG << "get file cache meta by tablet id request=" << request->DebugString()
<< ", response=" << response->DebugString();
}
namespace {
// Helper functions for fetch_peer_data
inline int64_t elapsed_us(std::chrono::steady_clock::time_point start) {
return std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() -
start)
.count();
}
int64_t get_rowset_meta_tablet_id_from_request(const PFetchPeerDataRequest* request) {
return request->has_rowset_meta() && request->rowset_meta().has_tablet_id()
? request->rowset_meta().tablet_id()
: -1;
}
std::string get_rowset_meta_resource_id_from_request(const PFetchPeerDataRequest* request) {
if (request->has_rowset_meta() && request->rowset_meta().has_resource_id()) {
return request->rowset_meta().resource_id();
}
return "";
}
std::string get_peer_cache_filename(std::string_view path) {
return io::Path(std::string(path)).filename().native();
}
std::string format_peer_request_context(const PFetchPeerDataRequest* request,
const io::UInt128Wrapper& hash, size_t file_size) {
const std::string file_size_str =
request->has_file_size() ? std::to_string(request->file_size()) : "unknown";
return fmt::format(
"type={}, path={}, cache_hash={}, request_fill={}, fill_tablet_id={}, "
"fill_remote_path={}, fill_resource_id={}, file_size={}, resolved_file_size={}, "
"cache_req_count={}, support_attachment={}",
request->type(), request->path(), hash.to_string(),
request->has_request_cache_fill() && request->request_cache_fill(),
get_rowset_meta_tablet_id_from_request(request), request->path(),
get_rowset_meta_resource_id_from_request(request), file_size_str,
file_size == std::numeric_limits<size_t>::max() ? std::string("unknown")
: std::to_string(file_size),
request->cache_req_size(),
request->has_support_attachment() && request->support_attachment());
}
std::string format_peer_cache_block_context(const PFetchPeerDataRequest* request,
const CacheBlockReqest& cb_req,
const io::FileBlockSPtr& fb,
const io::UInt128Wrapper& hash, size_t file_size,
bool do_fill) {
return fmt::format("{}, req_block=[offset={}, size={}], do_fill={}, block={}, cache_file={}",
format_peer_request_context(request, hash, file_size), cb_req.block_offset(),
cb_req.block_size(), do_fill, fb->get_info_for_log(), fb->get_cache_file());
}
std::string format_peer_fill_context(const io::FileBlockSPtr& fb, int64_t fill_tablet_id,
const std::string& filename, const std::string& resource_id,
const std::string& remote_path, int64_t file_size,
int64_t offset, int64_t size, int32_t timeout_ms) {
return fmt::format(
"tablet_id={}, filename={}, resource_id={}, remote_path={}, file_size={}, "
"request_range=[offset={}, size={}], timeout_ms={}, block={}, cache_file={}",
fill_tablet_id, filename, resource_id.empty() ? "<unknown>" : resource_id,
remote_path.empty() ? "<unknown>" : remote_path, file_size, offset, size, timeout_ms,
fb->get_info_for_log(), fb->get_cache_file());
}
bool wait_for_file_block_state(const io::FileBlockSPtr& fb, int32_t timeout_ms) {
const auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(timeout_ms);
while (true) {
const auto state = fb->state();
if (state == io::FileBlock::State::DOWNLOADED ||
state == io::FileBlock::State::SKIP_CACHE || state == io::FileBlock::State::EMPTY) {
return true;
}
if (std::chrono::steady_clock::now() >= deadline) {
return false;
}
fb->wait();
}
}
Status handle_peer_file_range_request(const std::string& path, PFetchPeerDataResponse* response) {
// Legacy path: PEER_FILE_RANGE still returns payload via protobuf bytes.
// Keep this for compatibility until range path is migrated to attachment mode.
// Read specific range [file_offset, file_offset+file_size) across cached blocks
auto datas =
io::FileCacheFactory::instance()->get_cache_data_by_path(get_peer_cache_filename(path));
for (auto& cb : datas) {
*(response->add_datas()) = std::move(cb);
}
return Status::OK();
}
void set_error_response(PFetchPeerDataResponse* response, const std::string& error_msg) {
response->mutable_status()->add_error_msgs(error_msg);
response->mutable_status()->set_status_code(TStatusCode::INTERNAL_ERROR);
}
void set_too_many_tasks_response(PFetchPeerDataResponse* response, const std::string& error_msg) {
response->mutable_status()->add_error_msgs(error_msg);
response->mutable_status()->set_status_code(TStatusCode::TOO_MANY_TASKS);
}
bool try_reject_if_queue_timed_out(std::chrono::steady_clock::time_point enqueue_ts,
PFetchPeerDataResponse* response) {
auto wait_us = elapsed_us(enqueue_ts);
g_file_cache_get_by_peer_queue_wait_latency << wait_us;
auto wait_ms = wait_us / 1000;
if (wait_ms <= config::peer_fetch_queue_timeout_ms) {
return false;
}
const std::string msg = fmt::format("fetch peer data queue timeout, wait_ms={}, timeout_ms={}",
wait_ms, config::peer_fetch_queue_timeout_ms);
g_file_cache_get_by_peer_queue_timeout_num << 1;
set_too_many_tasks_response(response, msg);
return true;
}
Status read_file_block(const std::shared_ptr<io::FileBlock>& file_block, size_t file_size,
doris::CacheBlockPB* output, butil::IOBuf* response_attachment) {
auto total_start = std::chrono::steady_clock::now();
int64_t set_data_us = 0;
Defer report {[&]() {
g_file_cache_get_by_peer_read_file_block_total_latency << elapsed_us(total_start);
if (set_data_us > 0) {
g_file_cache_get_by_peer_set_response_data_latency << set_data_us;
}
}};
// ATTN: calculate the rightmost boundary value of the block, due to inaccurate current block meta information.
// see CachedRemoteFileReader::read_at_impl for more details.
// Ensure file_size >= file_block->offset() to avoid underflow
if (file_size < file_block->offset()) {
LOG(WARNING) << "file_size (" << file_size << ") < file_block->offset("
<< file_block->offset() << ")";
return Status::InternalError<false>("file_size less than block offset");
}
size_t read_size = std::min(static_cast<size_t>(file_size - file_block->offset()),
file_block->range().size());
output->set_block_offset(static_cast<int64_t>(file_block->offset()));
output->set_block_size(static_cast<int64_t>(read_size));
if (read_size == 0) {
return Status::OK();
}
Status read_st = Status::OK();
// Attachment payload mode: protobuf carries metadata only, payload goes to attachment.
// This allows FS cache to use a file-descriptor->IOBuf path directly.
if (response_attachment != nullptr) {
size_t bytes_read = 0;
auto begin_read_file_ts = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now().time_since_epoch())
.count();
SCOPED_ATTACH_TASK(ExecEnv::GetInstance()->s3_file_buffer_tracker());
read_st = file_block->read_to_iobuf(response_attachment, /*read_offset=*/0, read_size,
&bytes_read);
auto end_read_file_ts = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now().time_since_epoch())
.count();
g_file_cache_get_by_peer_read_cache_file_latency << (end_read_file_ts - begin_read_file_ts);
if (read_st.ok()) {
if (bytes_read != read_size) {
return Status::InternalError<false>(
"peer cache read short data, expected={}, actual={}", read_size,
bytes_read);
}
g_file_cache_get_by_peer_response_bytes_total << bytes_read;
return Status::OK();
}
} else {
std::string data;
data.resize(read_size);
auto begin_read_file_ts = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now().time_since_epoch())
.count();
SCOPED_ATTACH_TASK(ExecEnv::GetInstance()->s3_file_buffer_tracker());
Slice slice(data.data(), data.size());
read_st = file_block->read(slice, /*read_offset=*/0);
auto end_read_file_ts = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now().time_since_epoch())
.count();
g_file_cache_get_by_peer_read_cache_file_latency << (end_read_file_ts - begin_read_file_ts);
if (read_st.ok()) {
auto set_data_start = std::chrono::steady_clock::now();
output->set_data(std::move(data));
set_data_us = elapsed_us(set_data_start);
g_file_cache_get_by_peer_response_bytes_total << read_size;
return Status::OK();
}
}
g_file_cache_get_by_peer_failed_num << 1;
LOG(WARNING) << "read cache block failed, file_size=" << file_size
<< ", block=" << file_block->get_info_for_log()
<< ", cache_file=" << file_block->get_cache_file() << ", err=" << read_st;
return read_st;
}
// Trigger S3 -> local cache fill for the given file block.
// Returns OK when the block is DOWNLOADED after the fill.
// Returns TOO_MANY_TASKS when the fill slot is exhausted (server healthy but overloaded):
// client should not rotate or evict, just fall back to S3 and retry same candidate later.
// Returns NOT_FOUND for soft misses (tablet not found, fill incomplete, timeout):
// client should rotate the candidate to try a different CG next time.
// The peer uses request.path as the full remote path. tablet_id/filename are kept for logging.
Status trigger_peer_server_fill(io::FileBlockSPtr& fb, int64_t fill_tablet_id,
const std::string& filename, const std::string& resource_id,
const std::string& remote_path, int64_t file_size, int64_t offset,
int64_t size, int32_t timeout_ms) {
g_peer_server_fill_requested << 1;
// Concurrency guard: atomically reserve a fill slot.
// Excess requests are rejected so the client falls back to its own S3 read.
// Return NOT_FOUND so the client rotates the candidate instead of evicting it.
if (g_active_server_fills.fetch_add(1, std::memory_order_relaxed) >=
config::max_concurrent_peer_server_fills) {
g_active_server_fills.fetch_sub(1, std::memory_order_relaxed);
g_peer_server_fill_rejected << 1;
VLOG_DEBUG << "trigger_peer_server_fill: rejected (concurrency limit "
<< config::max_concurrent_peer_server_fills << "), tablet_id=" << fill_tablet_id;
// TOO_MANY_TASKS: server is healthy but overloaded. Client must not rotate or evict;
// just fall back to S3 for this request and retry the same candidate next time.
return Status::Error<ErrorCode::TOO_MANY_TASKS, false>("fill slot exhausted");
}
// RAII decrement: runs on every return path below.
Defer fill_guard {[]() { g_active_server_fills.fetch_sub(1, std::memory_order_relaxed); }};
if (remote_path.empty() || resource_id.empty()) {
const std::string ctx =
format_peer_fill_context(fb, fill_tablet_id, filename, resource_id, remote_path,
file_size, offset, size, timeout_ms);
LOG(WARNING) << "trigger_peer_server_fill: missing remote_path or resource_id, " << ctx;
g_peer_server_fill_rejected << 1;
return Status::NotFound<false>("fill: missing remote_path or resource_id, {}", ctx);
}
auto storage_resource = doris::get_storage_resource(resource_id);
if (!storage_resource.has_value()) {
const std::string ctx =
format_peer_fill_context(fb, fill_tablet_id, filename, resource_id, remote_path,
file_size, offset, size, timeout_ms);
LOG(WARNING) << "trigger_peer_server_fill: storage resource not found, " << ctx;
g_peer_server_fill_rejected << 1;
return Status::NotFound<false>("fill: storage resource not found, {}", ctx);
}
auto fs = storage_resource->first.fs;
const auto initial_state = fb->state();
if (initial_state == io::FileBlock::State::DOWNLOADING) {
// Another thread already owns the block downloader. Wait up to the request timeout instead
// of the shorter per-wait timeout in FileBlock::wait().
[[maybe_unused]] const bool completed = wait_for_file_block_state(fb, timeout_ms);
const std::string ctx =
format_peer_fill_context(fb, fill_tablet_id, filename, resource_id, remote_path,
file_size, offset, size, timeout_ms);
return fb->state() == io::FileBlock::State::DOWNLOADED
? Status::OK()
: Status::NotFound<false>("fill: concurrent download incomplete, {}", ctx);
}
if (initial_state != io::FileBlock::State::EMPTY) {
const std::string ctx =
format_peer_fill_context(fb, fill_tablet_id, filename, resource_id, remote_path,
file_size, offset, size, timeout_ms);
return initial_state == io::FileBlock::State::DOWNLOADED
? Status::OK()
: Status::NotFound<false>("fill: unexpected initial block state, {}", ctx);
}
auto fill_start = std::chrono::steady_clock::now();
auto fill_done = std::make_shared<bthread::CountdownEvent>(1);
auto fill_status = std::make_shared<Status>(Status::OK());
io::DownloadFileMeta download_meta {
.path = remote_path,
.file_size = file_size,
.offset = offset,
.download_size = size,
.file_system = fs,
.ctx = {.is_dryrun = config::enable_reader_dryrun_when_download_file_cache,
// Pull-through fill must go straight to remote storage. If this download
// re-enters peer race, the original block can remain DOWNLOADING for the
// duration of nested peer retries and timeouts.
.is_warmup = false,
.bypass_peer_read = true},
.download_done =
[fill_done, fill_status](Status st) {
*fill_status = std::move(st);
fill_done->signal();
},
.tablet_id = fill_tablet_id,
};
io::DownloadTask task(std::move(download_meta));
ExecEnv::GetInstance()
->storage_engine()
.to_cloud()
.file_cache_block_downloader()
.submit_download_task(std::move(task));
const timespec due_time = butil::milliseconds_from_now(timeout_ms);
const bool timed_out = fill_done->timed_wait(due_time) != 0;
int64_t fill_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - fill_start)
.count();
g_peer_server_fill_latency << fill_ms * 1000; // LatencyRecorder takes microseconds
if (!timed_out && fill_status->ok() && fb->state() == io::FileBlock::State::DOWNLOADING) {
const int32_t settle_timeout_ms =
std::max<int32_t>(1, timeout_ms - static_cast<int32_t>(fill_ms));
[[maybe_unused]] const bool settled = wait_for_file_block_state(fb, settle_timeout_ms);
}
auto final_state = fb->state();
if (final_state == io::FileBlock::State::DOWNLOADED) {
g_peer_server_fill_success << 1;
return Status::OK();
}
if (timed_out) {
LOG(WARNING) << "trigger_peer_server_fill: fill timeout, elapsed_ms=" << fill_ms << ", "
<< format_peer_fill_context(fb, fill_tablet_id, filename, resource_id,
remote_path, file_size, offset, size, timeout_ms);
g_peer_server_fill_timeout << 1;
} else if (!fill_status->ok()) {
LOG(WARNING) << "trigger_peer_server_fill: fill failed, elapsed_ms=" << fill_ms
<< ", status=" << fill_status->to_string() << ", "
<< format_peer_fill_context(fb, fill_tablet_id, filename, resource_id,
remote_path, file_size, offset, size, timeout_ms);
}
// Any non-DOWNLOADED outcome is a soft failure: the server is otherwise healthy so the
// client should rotate the candidate rather than evict it.
return Status::NotFound<false>(
"fill: block not downloaded, {}",
format_peer_fill_context(fb, fill_tablet_id, filename, resource_id, remote_path,
file_size, offset, size, timeout_ms));
}
Status handle_peer_file_cache_block_request(const PFetchPeerDataRequest* request,
PFetchPeerDataResponse* response,
brpc::Controller* cntl) {
auto handle_start = std::chrono::steady_clock::now();
const uint64_t request_blocks = request->cache_req_size();
uint64_t response_blocks = 0;
uint64_t get_or_set_calls = 0;
uint64_t get_or_set_blocks = 0;
int64_t get_cache_us = 0;
Defer report {[&]() {
g_file_cache_get_by_peer_handle_cache_block_req_latency << elapsed_us(handle_start);
g_file_cache_get_by_peer_get_cache_latency << get_cache_us;
g_file_cache_get_by_peer_get_or_set_calls << get_or_set_calls;
g_file_cache_get_by_peer_get_or_set_blocks_total << get_or_set_blocks;
g_file_cache_get_by_peer_request_blocks_total << request_blocks;
g_file_cache_get_by_peer_request_blocks_per_rpc << request_blocks;
g_file_cache_get_by_peer_response_blocks_total << response_blocks;
}};
const auto& path = request->path();
const auto cache_key_path = get_peer_cache_filename(path);
auto hash = io::BlockFileCache::hash(cache_key_path);
auto get_cache_start = std::chrono::steady_clock::now();
auto* cache = io::FileCacheFactory::instance()->get_by_path(hash);
get_cache_us = elapsed_us(get_cache_start);
if (cache == nullptr) {
g_file_cache_get_by_peer_failed_num << 1;
set_error_response(response, "can't get file cache instance");
return Status::InternalError<false>("can't get file cache instance");
}
io::CacheContext ctx {};
io::ReadStatistics local_stats;
ctx.stats = &local_stats;
const size_t file_size =
request->has_file_size()
? static_cast<size_t>(std::max<int64_t>(0, request->file_size()))
: std::numeric_limits<size_t>::max();
// Enable attachment mode only when client advertises support.
// This keeps mixed-version rolling upgrades safe.
const bool use_attachment =
cntl != nullptr && request->has_support_attachment() && request->support_attachment();
response->set_data_in_attachment(use_attachment);
if (use_attachment) {
g_file_cache_get_by_peer_attachment_response_num << 1;
} else {
g_file_cache_get_by_peer_pb_response_num << 1;
}
const bool do_fill = request->has_request_cache_fill() && request->request_cache_fill() &&
config::enable_peer_server_cache_fill;
for (const auto& cb_req : request->cache_req()) {
size_t offset = static_cast<size_t>(std::max<int64_t>(0, cb_req.block_offset()));
size_t size = static_cast<size_t>(std::max<int64_t>(0, cb_req.block_size()));
if (offset >= file_size) {
continue;
}
// Clip tail requests before get_or_set so peer reads do not synthesize EMPTY blocks past
// EOF and then fail the whole RPC.
size = std::min(size, file_size - offset);
if (size == 0) {
continue;
}
DBUG_EXECUTE_IF(
"CloudInternalServiceImpl::handle_peer_file_cache_block_request_hold_before_get_or_"
"set",
{
int sleep_ms = dp->param<int>("sleep_ms", 300);
bthread_usleep(sleep_ms * 1000);
});
auto get_or_set_start = std::chrono::steady_clock::now();
auto holder = cache->get_or_set(hash, offset, size, ctx);
g_file_cache_get_by_peer_get_or_set_latency << elapsed_us(get_or_set_start);
++get_or_set_calls;
get_or_set_blocks += holder.file_blocks.size();
for (auto& fb : holder.file_blocks) {
auto fb_state = fb->state();
if (fb_state == io::FileBlock::State::DOWNLOADING) {
if (do_fill) {
// Only peer fill requests should wait longer here. Plain peer-cache reads keep
// the short wait semantics so they can fail fast and let the client race S3.
[[maybe_unused]] const bool completed = wait_for_file_block_state(
fb, config::peer_server_cache_fill_timeout_ms);
fb_state = fb->state();
} else {
// Wait for in-progress download to complete using the normal short timeout.
fb_state = fb->wait();
}
}
if (fb_state == io::FileBlock::State::EMPTY) {
if (!do_fill) {
const std::string msg =
fmt::format("cache block not downloaded, {}",
format_peer_cache_block_context(request, cb_req, fb, hash,
file_size, do_fill));
g_file_cache_get_by_peer_failed_num << 1;
g_file_cache_get_by_peer_not_downloaded_block_num << 1;
LOG(WARNING) << msg;
// Use NOT_FOUND so the client can distinguish "block not cached"
// from an actual RPC/server error. On NOT_FOUND the client rotates
// the candidate to the end of its list (trying another CG next time)
// rather than incrementing the RPC-failure eviction counter.
response->mutable_status()->add_error_msgs(msg);
response->mutable_status()->set_status_code(TStatusCode::NOT_FOUND);
return Status::NotFound<false>(msg);
}
// Server-side fill: request.path already carries the full remote path.
auto fill_st = trigger_peer_server_fill(
fb, get_rowset_meta_tablet_id_from_request(request), cache_key_path,
get_rowset_meta_resource_id_from_request(request), path,
request->has_file_size() ? request->file_size() : -1,
static_cast<int64_t>(fb->range().left),
static_cast<int64_t>(fb->range().size()),
config::peer_server_cache_fill_timeout_ms);
if (!fill_st.ok()) {
g_file_cache_get_by_peer_failed_num << 1;
g_file_cache_get_by_peer_not_downloaded_block_num << 1;
if (fill_st.is<ErrorCode::TOO_MANY_TASKS>()) {
// Server slot exhausted: healthy but overloaded. Client must not rotate
// or evict — just fall back to S3 and retry same candidate next time.
response->mutable_status()->add_error_msgs(std::string(fill_st.msg()));
response->mutable_status()->set_status_code(TStatusCode::TOO_MANY_TASKS);
} else if (fill_st.is<ErrorCode::NOT_FOUND>()) {
// Soft miss (fill incomplete, timeout, unexpected state) — client rotates,
// not evicts.
response->mutable_status()->add_error_msgs(std::string(fill_st.msg()));
response->mutable_status()->set_status_code(TStatusCode::NOT_FOUND);
} else {
LOG(WARNING) << "cache block fill failed, status=" << fill_st << ", "
<< format_peer_cache_block_context(request, cb_req, fb, hash,
file_size, do_fill);
set_error_response(response, "cache block not ready");
}
return fill_st;
}
fb_state = io::FileBlock::State::DOWNLOADED;
}
if (fb_state != io::FileBlock::State::DOWNLOADED) {
// A concurrent download was in progress (DOWNLOADING at request time) but its
// wait() returned a non-DOWNLOADED state (e.g., timed-out while still
// DOWNLOADING, or some other non-EMPTY intermediate state). The server is
// healthy; the block just isn't available yet. Return NOT_FOUND so the client
// rotates the candidate instead of evicting it.
const std::string msg =
fmt::format("cache block not ready after wait, {}",
format_peer_cache_block_context(request, cb_req, fb, hash,
file_size, do_fill));
g_file_cache_get_by_peer_failed_num << 1;
g_file_cache_get_by_peer_not_downloaded_block_num << 1;
LOG(WARNING) << msg;
response->mutable_status()->add_error_msgs(msg);
response->mutable_status()->set_status_code(TStatusCode::NOT_FOUND);
return Status::NotFound<false>(msg);
}
g_file_cache_get_by_peer_blocks_num << 1;
doris::CacheBlockPB* out = response->add_datas();
// In attachment mode, metadata order must match attachment append order because the
// client consumes attachment payload sequentially using resp.datas() order.
Status read_status = read_file_block(
fb, file_size, out, use_attachment ? &cntl->response_attachment() : nullptr);
if (!read_status.ok()) {
set_error_response(response, "read cache file error");
return read_status;
}
++response_blocks;
}
}
return Status::OK();
}
} // namespace
#ifdef BE_TEST
Status test_handle_peer_file_cache_block_request(const PFetchPeerDataRequest* request,
PFetchPeerDataResponse* response,
brpc::Controller* cntl) {
return handle_peer_file_cache_block_request(request, response, cntl);
}
bool test_try_reject_if_queue_timed_out(std::chrono::steady_clock::time_point enqueue_ts,
PFetchPeerDataResponse* response) {
return try_reject_if_queue_timed_out(enqueue_ts, response);
}
#endif
void CloudInternalServiceImpl::fetch_peer_data(google::protobuf::RpcController* controller,
const PFetchPeerDataRequest* request,
PFetchPeerDataResponse* response,
google::protobuf::Closure* done) {
auto enqueue_ts = std::chrono::steady_clock::now();
// Lifetime: cntl is owned by brpc framework and valid until done->Run() is called.
// The ClosureGuard inside the lambda ensures done->Run() happens after all cntl usage,
// so capturing the raw pointer by value is safe.
auto* cntl = static_cast<brpc::Controller*>(controller);
bool ret = _peer_fetch_pool.try_offer([request, response, done, enqueue_ts, cntl]() {
brpc::ClosureGuard closure_guard(done);
g_file_cache_get_by_peer_num << 1;
if (try_reject_if_queue_timed_out(enqueue_ts, response)) {
return;
}
if (!config::enable_file_cache) {
LOG_WARNING("try to access file cache data, but file cache not enabled");
return;
}
auto begin_ts = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now().time_since_epoch())
.count();
const auto type = request->type();
const auto& path = request->path();
response->mutable_status()->set_status_code(TStatusCode::OK);
Status status = Status::OK();
if (type == PFetchPeerDataRequest_Type_PEER_FILE_RANGE) {
status = handle_peer_file_range_request(path, response);
} else if (type == PFetchPeerDataRequest_Type_PEER_FILE_CACHE_BLOCK) {
status = handle_peer_file_cache_block_request(request, response, cntl);
}
if (!status.ok()) {
const std::string msg =
"fetch peer data failed: " + status.to_string() + ", " +
format_peer_request_context(
request, io::BlockFileCache::hash(get_peer_cache_filename(path)),
request->has_file_size() ? static_cast<size_t>(std::max<int64_t>(
0, request->file_size()))
: std::numeric_limits<size_t>::max());
if (status.is<ErrorCode::NOT_FOUND>() || status.is<ErrorCode::TOO_MANY_TASKS>()) {
VLOG_DEBUG << msg;
} else {
LOG(WARNING) << msg;
}
auto* resp_status = response->mutable_status();
if (resp_status->status_code() == TStatusCode::OK) {
set_error_response(response, status.to_string());
} else if (resp_status->error_msgs().empty()) {
resp_status->add_error_msgs(status.to_string());
}
}
DBUG_EXECUTE_IF("CloudInternalServiceImpl::fetch_peer_data_slower", {
int st_us = dp->param<int>("sleep", 1000);
LOG_WARNING("CloudInternalServiceImpl::fetch_peer_data_slower").tag("sleep", st_us);
bthread_usleep(st_us);
});
auto end_ts = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now().time_since_epoch())
.count();
// Latency covers every completed callback (including failures) so the
// server-side fail-fast paths still show up in the latency histogram.
// success_num must only count actual OK results, otherwise dedup
// TOO_MANY_TASKS / NOT_FOUND / handler errors all fall through here
// and the success rate is meaningless. Use file_cache_get_by_peer_num
// for the total completed-callback count.
g_file_cache_get_by_peer_server_latency << (end_ts - begin_ts);
if (status.ok()) {
g_file_cache_get_by_peer_success_num << 1;
}
VLOG_DEBUG << "fetch cache request=" << request->DebugString()
<< ", response=" << response->DebugString();
});
if (!ret) {
g_file_cache_get_by_peer_offer_failed_num << 1;
brpc::ClosureGuard closure_guard(done);
const std::string msg = fmt::format(
"fail to offer fetch peer data request to the peer fetch work pool, pool={}",
_peer_fetch_pool.get_info());
set_too_many_tasks_response(response, msg);
LOG(WARNING) << msg;
}
}
bvar::Adder<uint64_t> g_file_cache_event_driven_warm_up_submitted_segment_num(
"file_cache_event_driven_warm_up_submitted_segment_num");
bvar::Adder<uint64_t> g_file_cache_event_driven_warm_up_finished_segment_num(
"file_cache_event_driven_warm_up_finished_segment_num");
bvar::Adder<uint64_t> g_file_cache_event_driven_warm_up_failed_segment_num(
"file_cache_event_driven_warm_up_failed_segment_num");
bvar::Adder<uint64_t> g_file_cache_event_driven_warm_up_submitted_segment_size(
"file_cache_event_driven_warm_up_submitted_segment_size");
bvar::Adder<uint64_t> g_file_cache_event_driven_warm_up_finished_segment_size(
"file_cache_event_driven_warm_up_finished_segment_size");
bvar::Adder<uint64_t> g_file_cache_event_driven_warm_up_failed_segment_size(
"file_cache_event_driven_warm_up_failed_segment_size");
bvar::Adder<uint64_t> g_file_cache_event_driven_warm_up_submitted_index_num(
"file_cache_event_driven_warm_up_submitted_index_num");
bvar::Adder<uint64_t> g_file_cache_event_driven_warm_up_finished_index_num(
"file_cache_event_driven_warm_up_finished_index_num");
bvar::Adder<uint64_t> g_file_cache_event_driven_warm_up_failed_index_num(
"file_cache_event_driven_warm_up_failed_index_num");
bvar::Adder<uint64_t> g_file_cache_event_driven_warm_up_submitted_index_size(
"file_cache_event_driven_warm_up_submitted_index_size");
bvar::Adder<uint64_t> g_file_cache_event_driven_warm_up_finished_index_size(
"file_cache_event_driven_warm_up_finished_index_size");
bvar::Adder<uint64_t> g_file_cache_event_driven_warm_up_failed_index_size(
"file_cache_event_driven_warm_up_failed_index_size");
bvar::Status<int64_t> g_file_cache_warm_up_rowset_last_handle_unix_ts(
"file_cache_warm_up_rowset_last_handle_unix_ts", 0);
bvar::Status<int64_t> g_file_cache_warm_up_rowset_last_finish_unix_ts(
"file_cache_warm_up_rowset_last_finish_unix_ts", 0);
bvar::LatencyRecorder g_file_cache_warm_up_rowset_latency("file_cache_warm_up_rowset_latency");
bvar::LatencyRecorder g_file_cache_warm_up_rowset_request_to_handle_latency(
"file_cache_warm_up_rowset_request_to_handle_latency");
bvar::LatencyRecorder g_file_cache_warm_up_rowset_handle_to_finish_latency(
"file_cache_warm_up_rowset_handle_to_finish_latency");
bvar::Adder<uint64_t> g_file_cache_warm_up_rowset_slow_count(
"file_cache_warm_up_rowset_slow_count");
bvar::Adder<uint64_t> g_file_cache_warm_up_rowset_request_to_handle_slow_count(
"file_cache_warm_up_rowset_request_to_handle_slow_count");
bvar::Adder<uint64_t> g_file_cache_warm_up_rowset_handle_to_finish_slow_count(
"file_cache_warm_up_rowset_handle_to_finish_slow_count");
bvar::Adder<uint64_t> g_file_cache_warm_up_rowset_wait_for_compaction_num(
"file_cache_warm_up_rowset_wait_for_compaction_num");
bvar::Adder<uint64_t> g_file_cache_warm_up_rowset_wait_for_compaction_timeout_num(
"file_cache_warm_up_rowset_wait_for_compaction_timeout_num");
// Per-job windowed metrics for target BE
// bvar::Window enforces MAX_SECONDS_LIMIT = 3600, so the longest window is 1h.
static constexpr int WINDOW_5M = 300;
static constexpr int WINDOW_30M = 1800;
static constexpr int WINDOW_1H = 3600;
MBvarWindowedAdder g_warmup_ed_finish_segment_num("warmup_ed_finish_segment_num", {"job_id"},
{WINDOW_5M, WINDOW_30M, WINDOW_1H}, false);
MBvarWindowedAdder g_warmup_ed_finish_segment_size("warmup_ed_finish_segment_size", {"job_id"},
{WINDOW_5M, WINDOW_30M, WINDOW_1H}, false);
MBvarWindowedAdder g_warmup_ed_finish_index_num("warmup_ed_finish_index_num", {"job_id"},
{WINDOW_5M, WINDOW_30M, WINDOW_1H}, false);
MBvarWindowedAdder g_warmup_ed_finish_index_size("warmup_ed_finish_index_size", {"job_id"},
{WINDOW_5M, WINDOW_30M, WINDOW_1H}, false);
MBvarWindowedAdder g_warmup_ed_fail_segment_num("warmup_ed_fail_segment_num", {"job_id"},
{WINDOW_5M, WINDOW_30M, WINDOW_1H}, false);
MBvarWindowedAdder g_warmup_ed_fail_segment_size("warmup_ed_fail_segment_size", {"job_id"},
{WINDOW_5M, WINDOW_30M, WINDOW_1H}, false);
MBvarWindowedAdder g_warmup_ed_fail_index_num("warmup_ed_fail_index_num", {"job_id"},
{WINDOW_5M, WINDOW_30M, WINDOW_1H}, false);
MBvarWindowedAdder g_warmup_ed_fail_index_size("warmup_ed_fail_index_size", {"job_id"},
{WINDOW_5M, WINDOW_30M, WINDOW_1H}, false);
bvar::MultiDimension<bvar::Status<int64_t>> g_warmup_ed_last_finish_ts({"job_id"});
void update_warmup_ed_last_finish_ts(const std::string& job_id_str) {
auto* finish_ts = g_warmup_ed_last_finish_ts.get_stats(std::list<std::string> {job_id_str});
if (finish_ts) {
finish_ts->set_value(std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch())
.count());
}
}
void record_warmup_ed_finish_segment(const std::string& job_id_str, int64_t segment_size) {
g_warmup_ed_finish_segment_num.put({job_id_str}, 1);
g_warmup_ed_finish_segment_size.put({job_id_str}, segment_size);
update_warmup_ed_last_finish_ts(job_id_str);
}
void record_warmup_ed_finish_index(const std::string& job_id_str, int64_t idx_size) {
g_warmup_ed_finish_index_num.put({job_id_str}, 1);
g_warmup_ed_finish_index_size.put({job_id_str}, idx_size);
update_warmup_ed_last_finish_ts(job_id_str);
}
void record_warmup_ed_fail_segment(const std::string& job_id_str, int64_t segment_size) {
g_warmup_ed_fail_segment_num.put({job_id_str}, 1);
g_warmup_ed_fail_segment_size.put({job_id_str}, segment_size);
}
void record_warmup_ed_fail_index(const std::string& job_id_str, int64_t idx_size) {
g_warmup_ed_fail_index_num.put({job_id_str}, 1);
g_warmup_ed_fail_index_size.put({job_id_str}, idx_size);
}
void record_warmup_ed_skipped_rowset_as_finished(RowsetMeta& rs_meta,
const std::string& job_id_str) {
auto schema_ptr = rs_meta.tablet_schema();
bool has_inverted_index = schema_ptr->has_inverted_index() || schema_ptr->has_ann_index();
auto idx_version = schema_ptr->get_inverted_index_storage_format();
for (int64_t segment_id = 0; segment_id < rs_meta.num_segments(); segment_id++) {
record_warmup_ed_finish_segment(job_id_str, rs_meta.segment_file_size(segment_id));
if (!has_inverted_index) {
continue;
}
auto&& inverted_index_info = rs_meta.inverted_index_file_info(segment_id);
if (idx_version == InvertedIndexStorageFormatPB::V1) {
std::unordered_map<int64_t, int64_t> index_size_map;
for (const auto& info : inverted_index_info.index_info()) {
if (info.index_file_size() != -1) {
index_size_map[info.index_id()] = info.index_file_size();
} else {
VLOG_DEBUG << "Invalid index_file_size for segment_id " << segment_id
<< ", index_id " << info.index_id();
}
}
for (const auto& index : schema_ptr->inverted_indexes()) {
record_warmup_ed_finish_index(job_id_str, index_size_map[index->index_id()]);
}
} else { // InvertedIndexStorageFormatPB::V2
int64_t idx_size = 0;
if (inverted_index_info.has_index_size()) {
idx_size = inverted_index_info.index_size();
} else {
VLOG_DEBUG << "index_size is not set for segment " << segment_id;
}
record_warmup_ed_finish_index(job_id_str, idx_size);
}
}
}
void handle_segment_download_done(Status st, int64_t tablet_id, const RowsetId& rowset_id,
int64_t segment_id, std::shared_ptr<CloudTablet> tablet,
std::shared_ptr<bthread::CountdownEvent> wait, Version version,
int64_t segment_size, int64_t request_ts, int64_t handle_ts,
std::string job_id_str, int64_t upstream_trigger_ts_ms) {
DBUG_EXECUTE_IF("CloudInternalServiceImpl::warm_up_rowset.download_segment", {
auto sleep_time = dp->param<int>("sleep", 3);
LOG_INFO("[verbose] block download for rowset={}, version={}, sleep={}",
rowset_id.to_string(), version.to_string(), sleep_time);
std::this_thread::sleep_for(std::chrono::seconds(sleep_time));
});
DBUG_EXECUTE_IF(
"CloudInternalServiceImpl::warm_up_rowset.download_segment.inject_"
"error",
{
st = Status::InternalError("injected error");
LOG_INFO("[verbose] inject error, tablet={}, rowset={}, st={}", tablet_id,
rowset_id.to_string(), st.to_string());
});
if (st.ok()) {
g_file_cache_event_driven_warm_up_finished_segment_num << 1;
g_file_cache_event_driven_warm_up_finished_segment_size << segment_size;
record_warmup_ed_finish_segment(job_id_str, segment_size);
int64_t now_ts = current_unix_time_us();
g_file_cache_warm_up_rowset_last_finish_unix_ts.set_value(now_ts);
auto rowset_latency_us = warm_up_rowset_cross_host_latency_us(request_ts, now_ts);
if (rowset_latency_us.has_value()) {
g_file_cache_warm_up_rowset_latency << *rowset_latency_us;
}
g_file_cache_warm_up_rowset_handle_to_finish_latency << (now_ts - handle_ts);
if (rowset_latency_us.has_value() &&
*rowset_latency_us > config::warm_up_rowset_slow_log_ms * 1000) {
g_file_cache_warm_up_rowset_slow_count << 1;
LOG(INFO) << "warm up rowset took " << *rowset_latency_us
<< " us, tablet_id: " << tablet_id << ", rowset_id: " << rowset_id.to_string()
<< ", segment_id: " << segment_id;
}
if (now_ts - handle_ts > config::warm_up_rowset_slow_log_ms * 1000) {
g_file_cache_warm_up_rowset_handle_to_finish_slow_count << 1;
LOG(INFO) << "warm up rowset (handle to finish) took " << now_ts - handle_ts
<< " us, tablet_id: " << tablet_id << ", rowset_id: " << rowset_id.to_string()
<< ", segment_id: " << segment_id;
}
} else {
g_file_cache_event_driven_warm_up_failed_segment_num << 1;
g_file_cache_event_driven_warm_up_failed_segment_size << segment_size;
record_warmup_ed_fail_segment(job_id_str, segment_size);
LOG(WARNING) << "download segment failed, tablet_id: " << tablet_id
<< " rowset_id: " << rowset_id.to_string() << ", error: " << st;
}
if (tablet->complete_rowset_segment_warmup(WarmUpTriggerSource::EVENT_DRIVEN, rowset_id, st, 1,
0)
.trigger_source == WarmUpTriggerSource::EVENT_DRIVEN) {
VLOG_DEBUG << "warmup rowset " << version.to_string() << "(" << rowset_id.to_string()
<< ") completed";
}
g_warmup_ed_downstream_progress_tracker.record_task_done(job_id_str, upstream_trigger_ts_ms);
if (wait) {
wait->signal();
}
}
void handle_inverted_index_download_done(Status st, int64_t tablet_id, const RowsetId& rowset_id,
int64_t segment_id, std::string index_path,
std::shared_ptr<CloudTablet> tablet,
std::shared_ptr<bthread::CountdownEvent> wait,
Version version, uint64_t idx_size, int64_t request_ts,
int64_t handle_ts, std::string job_id_str,
int64_t upstream_trigger_ts_ms) {
DBUG_EXECUTE_IF("CloudInternalServiceImpl::warm_up_rowset.download_inverted_idx", {
auto sleep_time = dp->param<int>("sleep", 3);
LOG_INFO(
"[verbose] block download for rowset={}, inverted index "
"file={}, sleep={}",
rowset_id.to_string(), index_path, sleep_time);
std::this_thread::sleep_for(std::chrono::seconds(sleep_time));
});
if (st.ok()) {
g_file_cache_event_driven_warm_up_finished_index_num << 1;
g_file_cache_event_driven_warm_up_finished_index_size << idx_size;
record_warmup_ed_finish_index(job_id_str, static_cast<int64_t>(idx_size));
int64_t now_ts = current_unix_time_us();
g_file_cache_warm_up_rowset_last_finish_unix_ts.set_value(now_ts);
auto rowset_latency_us = warm_up_rowset_cross_host_latency_us(request_ts, now_ts);
if (rowset_latency_us.has_value()) {
g_file_cache_warm_up_rowset_latency << *rowset_latency_us;
}
g_file_cache_warm_up_rowset_handle_to_finish_latency << (now_ts - handle_ts);
if (rowset_latency_us.has_value() &&
*rowset_latency_us > config::warm_up_rowset_slow_log_ms * 1000) {
g_file_cache_warm_up_rowset_slow_count << 1;
LOG(INFO) << "warm up rowset took " << *rowset_latency_us
<< " us, tablet_id: " << tablet_id << ", rowset_id: " << rowset_id.to_string()
<< ", segment_id: " << segment_id;
}
if (now_ts - handle_ts > config::warm_up_rowset_slow_log_ms * 1000) {
g_file_cache_warm_up_rowset_handle_to_finish_slow_count << 1;
LOG(INFO) << "warm up rowset (handle to finish) took " << now_ts - handle_ts
<< " us, tablet_id: " << tablet_id << ", rowset_id: " << rowset_id.to_string()
<< ", segment_id: " << segment_id;
}
} else {
g_file_cache_event_driven_warm_up_failed_index_num << 1;
g_file_cache_event_driven_warm_up_failed_index_size << idx_size;
record_warmup_ed_fail_index(job_id_str, static_cast<int64_t>(idx_size));
LOG(WARNING) << "download inverted index failed, tablet_id: " << tablet_id
<< " rowset_id: " << rowset_id << ", error: " << st;
}
if (tablet->complete_rowset_segment_warmup(WarmUpTriggerSource::EVENT_DRIVEN, rowset_id, st, 0,
1)
.trigger_source == WarmUpTriggerSource::EVENT_DRIVEN) {
VLOG_DEBUG << "warmup rowset " << version.to_string() << "(" << rowset_id.to_string()
<< ") completed";
}
g_warmup_ed_downstream_progress_tracker.record_task_done(job_id_str, upstream_trigger_ts_ms);
if (wait) {
wait->signal();
}
}
void CloudInternalServiceImpl::warm_up_rowset(google::protobuf::RpcController* controller
[[maybe_unused]],
const PWarmUpRowsetRequest* request,
PWarmUpRowsetResponse* response,
google::protobuf::Closure* done) {
brpc::ClosureGuard closure_guard(done);
std::shared_ptr<bthread::CountdownEvent> wait = nullptr;
timespec due_time;
if (request->has_sync_wait_timeout_ms() && request->sync_wait_timeout_ms() > 0) {
g_file_cache_warm_up_rowset_wait_for_compaction_num << 1;
wait = std::make_shared<bthread::CountdownEvent>(0);
VLOG_DEBUG << "sync_wait_timeout: " << request->sync_wait_timeout_ms() << " ms";
due_time = butil::milliseconds_from_now(request->sync_wait_timeout_ms());
}
// Extract job_id from request (0 if not set, for backward compatibility)
std::string job_id_str = std::to_string(request->has_job_id() ? request->job_id() : 0);
int64_t upstream_trigger_ts_ms =
request->has_upstream_trigger_ts_ms() ? request->upstream_trigger_ts_ms() : 0;
for (auto& rs_meta_pb : request->rowset_metas()) {
RowsetMeta rs_meta;
rs_meta.init_from_pb(rs_meta_pb);
auto storage_resource = rs_meta.remote_storage_resource();
if (!storage_resource) {
LOG(WARNING) << storage_resource.error();
continue;
}
int64_t tablet_id = rs_meta.tablet_id();
auto rowset_id = rs_meta.rowset_id();
bool local_only = !(request->has_skip_existence_check() && request->skip_existence_check());
auto res = _engine.tablet_mgr().get_tablet(tablet_id, /* warmup_data = */ false,
/* sync_delete_bitmap = */ true,
/* sync_stats = */ nullptr,
/* local_only = */ local_only);
if (!res.has_value()) {
LOG_WARNING("Warm up error ").tag("tablet_id", tablet_id).error(res.error());
if (res.error().msg().find("local_only=true") != std::string::npos ||
res.error().msg().find("force_use_only_cached=true") != std::string::npos) {
res.error().set_code(ErrorCode::TABLE_NOT_FOUND);
}
res.error().to_protobuf(response->mutable_status());
continue;
}
auto tablet = res.value();
auto tablet_meta = tablet->tablet_meta();
int64_t handle_ts = current_unix_time_us();
g_file_cache_warm_up_rowset_last_handle_unix_ts.set_value(handle_ts);
int64_t request_ts = request->has_unix_ts_us() ? request->unix_ts_us() : 0;
auto request_to_handle_latency_us =
warm_up_rowset_cross_host_latency_us(request_ts, handle_ts);
if (request_to_handle_latency_us.has_value()) {
g_file_cache_warm_up_rowset_request_to_handle_latency << *request_to_handle_latency_us;
}
if (request_to_handle_latency_us.has_value() &&
*request_to_handle_latency_us > config::warm_up_rowset_slow_log_ms * 1000) {
g_file_cache_warm_up_rowset_request_to_handle_slow_count << 1;
LOG(INFO) << "warm up rowset (request to handle) took " << *request_to_handle_latency_us
<< " us, tablet_id: " << rs_meta.tablet_id()
<< ", rowset_id: " << rowset_id.to_string();
}
int64_t expiration_time = tablet_meta->ttl_seconds();
if (!tablet->add_rowset_warmup_state(rs_meta, WarmUpTriggerSource::EVENT_DRIVEN)) {
LOG(INFO) << "found duplicate warmup task for rowset " << rowset_id.to_string()
<< ", skip it";
g_warmup_ed_downstream_progress_tracker.record_task_done(job_id_str,
upstream_trigger_ts_ms);
record_warmup_ed_skipped_rowset_as_finished(rs_meta, job_id_str);
continue;
}
if (rs_meta.num_segments() == 0) {
g_warmup_ed_downstream_progress_tracker.record_task_done(job_id_str,
upstream_trigger_ts_ms);
}
for (int64_t segment_id = 0; segment_id < rs_meta.num_segments(); segment_id++) {
if (!config::file_cache_enable_only_warm_up_idx) {
auto segment_size = rs_meta.segment_file_size(segment_id);
// Use rs_meta.fs() instead of storage_resource.value()->fs to support packed files.
// PackedFileSystem wrapper in rs_meta.fs() handles the index_map lookup and
// reads from the correct packed file.
io::DownloadFileMeta download_meta {
.path = storage_resource.value()->remote_segment_path(rs_meta, segment_id),
.file_size = segment_size,
.offset = 0,
.download_size = segment_size,
.file_system = rs_meta.fs(),
.ctx = {.is_index_data = false,
.expiration_time = expiration_time,
.is_dryrun = config::enable_reader_dryrun_when_download_file_cache,
.is_warmup = true},
.download_done =
[=, version = rs_meta.version()](Status st) {
handle_segment_download_done(
st, tablet_id, rowset_id, segment_id, tablet, wait,
version, segment_size, request_ts, handle_ts,
job_id_str, upstream_trigger_ts_ms);
},
.tablet_id = tablet_id};
g_file_cache_event_driven_warm_up_submitted_segment_num << 1;
g_file_cache_event_driven_warm_up_submitted_segment_size << segment_size;
if (wait) {
wait->add_count();
}
g_warmup_ed_downstream_progress_tracker.record_task_submit(job_id_str,
upstream_trigger_ts_ms);
_engine.file_cache_block_downloader().submit_download_task(download_meta);
}
// Use rs_meta.fs() to support packed files for inverted index download.
auto download_inverted_index = [&, tablet, job_id_str](std::string index_path,
uint64_t idx_size) {
io::DownloadFileMeta download_meta {
.path = io::Path(index_path),
.file_size = static_cast<int64_t>(idx_size),
.file_system = rs_meta.fs(),
.ctx = {.is_index_data = false, // DORIS-20877
.expiration_time = expiration_time,
.is_dryrun = config::enable_reader_dryrun_when_download_file_cache,
.is_warmup = true},
.download_done =
[=, version = rs_meta.version()](Status st) {
handle_inverted_index_download_done(
st, tablet_id, rowset_id, segment_id, index_path,
tablet, wait, version, idx_size, request_ts, handle_ts,
job_id_str, upstream_trigger_ts_ms);
},
.tablet_id = tablet_id,
};
g_file_cache_event_driven_warm_up_submitted_index_num << 1;
g_file_cache_event_driven_warm_up_submitted_index_size << idx_size;
tablet->update_rowset_warmup_state_inverted_idx_num(
WarmUpTriggerSource::EVENT_DRIVEN, rowset_id, 1);
if (wait) {
wait->add_count();
}
g_warmup_ed_downstream_progress_tracker.record_task_submit(job_id_str,
upstream_trigger_ts_ms);
_engine.file_cache_block_downloader().submit_download_task(download_meta);
};
// inverted index
auto schema_ptr = rs_meta.tablet_schema();
auto idx_version = schema_ptr->get_inverted_index_storage_format();
if (schema_ptr->has_inverted_index() || schema_ptr->has_ann_index()) {
if (idx_version == InvertedIndexStorageFormatPB::V1) {
auto&& inverted_index_info = rs_meta.inverted_index_file_info(segment_id);
std::unordered_map<int64_t, int64_t> index_size_map;
for (const auto& info : inverted_index_info.index_info()) {
if (info.index_file_size() != -1) {
index_size_map[info.index_id()] = info.index_file_size();
} else {
VLOG_DEBUG << "Invalid index_file_size for segment_id " << segment_id
<< ", index_id " << info.index_id();
}
}
for (const auto& index : schema_ptr->inverted_indexes()) {
auto idx_path = storage_resource.value()->remote_idx_v1_path(
rs_meta, segment_id, index->index_id(), index->get_index_suffix());
download_inverted_index(idx_path, index_size_map[index->index_id()]);
}
} else { // InvertedIndexStorageFormatPB::V2
auto&& inverted_index_info = rs_meta.inverted_index_file_info(segment_id);
int64_t idx_size = 0;
if (inverted_index_info.has_index_size()) {
idx_size = inverted_index_info.index_size();
} else {
VLOG_DEBUG << "index_size is not set for segment " << segment_id;
}
auto idx_path =
storage_resource.value()->remote_idx_v2_path(rs_meta, segment_id);
download_inverted_index(idx_path, idx_size);
}
}
}
}
if (wait && wait->timed_wait(due_time)) {
g_file_cache_warm_up_rowset_wait_for_compaction_timeout_num << 1;
LOG_WARNING("the time spent warming up {} rowsets exceeded {} ms",
request->rowset_metas().size(), request->sync_wait_timeout_ms());
}
}
bvar::Adder<uint64_t> g_file_cache_recycle_cache_finished_segment_num(
"file_cache_recycle_cache_finished_segment_num");
bvar::Adder<uint64_t> g_file_cache_recycle_cache_finished_index_num(
"file_cache_recycle_cache_finished_index_num");
void CloudInternalServiceImpl::recycle_cache(google::protobuf::RpcController* controller
[[maybe_unused]],
const PRecycleCacheRequest* request,
PRecycleCacheResponse* response,
google::protobuf::Closure* done) {
brpc::ClosureGuard closure_guard(done);
if (!config::enable_file_cache) {
return;
}
for (const auto& meta : request->cache_metas()) {
for (int64_t segment_id = 0; segment_id < meta.num_segments(); segment_id++) {
auto file_key = Segment::file_cache_key(meta.rowset_id(), segment_id);
auto* file_cache = io::FileCacheFactory::instance()->get_by_path(file_key);
file_cache->remove_if_cached_async(file_key);
g_file_cache_recycle_cache_finished_segment_num << 1;
}
// inverted index
for (const auto& file_name : meta.index_file_names()) {
auto file_key = io::BlockFileCache::hash(file_name);
auto* file_cache = io::FileCacheFactory::instance()->get_by_path(file_key);
file_cache->remove_if_cached_async(file_key);
g_file_cache_recycle_cache_finished_index_num << 1;
}
}
}
#include "common/compile_check_avoid_end.h"
} // namespace doris