be/src/io/cache/cached_remote_file_reader.cpp
Line | Count | Source |
1 | | // Licensed to the Apache Software Foundation (ASF) under one |
2 | | // or more contributor license agreements. See the NOTICE file |
3 | | // distributed with this work for additional information |
4 | | // regarding copyright ownership. The ASF licenses this file |
5 | | // to you under the Apache License, Version 2.0 (the |
6 | | // "License"); you may not use this file except in compliance |
7 | | // with the License. You may obtain a copy of the License at |
8 | | // |
9 | | // http://www.apache.org/licenses/LICENSE-2.0 |
10 | | // |
11 | | // Unless required by applicable law or agreed to in writing, |
12 | | // software distributed under the License is distributed on an |
13 | | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
14 | | // KIND, either express or implied. See the License for the |
15 | | // specific language governing permissions and limitations |
16 | | // under the License. |
17 | | |
18 | | #include "io/cache/cached_remote_file_reader.h" |
19 | | |
20 | | #include <brpc/controller.h> |
21 | | #include <bthread/bthread.h> |
22 | | #include <bthread/condition_variable.h> |
23 | | #include <bthread/mutex.h> |
24 | | #include <fmt/format.h> |
25 | | #include <gen_cpp/Types_types.h> |
26 | | #include <gen_cpp/internal_service.pb.h> |
27 | | #include <glog/logging.h> |
28 | | |
29 | | #include <algorithm> |
30 | | #include <atomic> |
31 | | #include <condition_variable> |
32 | | #include <cstring> |
33 | | #include <functional> |
34 | | #include <list> |
35 | | #include <memory> |
36 | | #include <mutex> |
37 | | #include <thread> |
38 | | #include <vector> |
39 | | |
40 | | #include "cloud/cloud_cluster_info.h" |
41 | | #include "cloud/cloud_warm_up_manager.h" |
42 | | #include "cloud/config.h" |
43 | | #include "common/compiler_util.h" // IWYU pragma: keep |
44 | | #include "common/config.h" |
45 | | #include "common/metrics/doris_metrics.h" |
46 | | #include "cpp/sync_point.h" |
47 | | #include "io/cache/block_file_cache.h" |
48 | | #include "io/cache/block_file_cache_factory.h" |
49 | | #include "io/cache/block_file_cache_profile.h" |
50 | | #include "io/cache/file_block.h" |
51 | | #include "io/cache/peer_file_cache_reader.h" |
52 | | #include "io/cache/remote_scan_cache_write_limiter.h" |
53 | | #include "io/fs/file_reader.h" |
54 | | #include "io/fs/local_file_system.h" |
55 | | #include "io/io_common.h" |
56 | | #include "runtime/exec_env.h" |
57 | | #include "runtime/runtime_profile.h" |
58 | | #include "runtime/thread_context.h" |
59 | | #include "runtime/workload_management/io_throttle.h" |
60 | | #include "service/backend_options.h" |
61 | | #include "util/bit_util.h" |
62 | | #include "util/brpc_client_cache.h" // BrpcClientCache |
63 | | #include "util/bthread_utils.h" |
64 | | #include "util/client_cache.h" |
65 | | #include "util/concurrency_stats.h" |
66 | | #include "util/debug_points.h" |
67 | | #include "util/defer_op.h" |
68 | | |
69 | | namespace doris::io { |
70 | | |
71 | | bvar::Adder<uint64_t> s3_read_counter("cached_remote_reader_s3_read"); |
72 | | bvar::Adder<uint64_t> peer_read_counter("cached_remote_reader_peer_read"); |
73 | | bvar::LatencyRecorder g_skip_cache_num("cached_remote_reader_skip_cache_num"); |
74 | | bvar::Adder<uint64_t> g_skip_cache_sum("cached_remote_reader_skip_cache_sum"); |
75 | | bvar::Adder<uint64_t> g_skip_local_cache_io_sum_bytes( |
76 | | "cached_remote_reader_skip_local_cache_io_sum_bytes"); |
77 | | bvar::Adder<uint64_t> g_read_cache_direct_whole_num("cached_remote_reader_cache_direct_whole_num"); |
78 | | bvar::Adder<uint64_t> g_read_cache_direct_partial_num( |
79 | | "cached_remote_reader_cache_direct_partial_num"); |
80 | | bvar::Adder<uint64_t> g_read_cache_indirect_num("cached_remote_reader_cache_indirect_num"); |
81 | | bvar::Adder<uint64_t> g_read_cache_direct_whole_bytes( |
82 | | "cached_remote_reader_cache_direct_whole_bytes"); |
83 | | bvar::Adder<uint64_t> g_read_cache_direct_partial_bytes( |
84 | | "cached_remote_reader_cache_direct_partial_bytes"); |
85 | | bvar::Adder<uint64_t> g_read_cache_indirect_bytes("cached_remote_reader_cache_indirect_bytes"); |
86 | | bvar::Adder<uint64_t> g_read_cache_indirect_total_bytes( |
87 | | "cached_remote_reader_cache_indirect_total_bytes"); |
88 | | bvar::Adder<uint64_t> g_read_cache_self_heal_on_not_found( |
89 | | "cached_remote_reader_self_heal_on_not_found"); |
90 | | bvar::Window<bvar::Adder<uint64_t>> g_read_cache_indirect_bytes_1min_window( |
91 | | "cached_remote_reader_indirect_bytes_1min_window", &g_read_cache_indirect_bytes, 60); |
92 | | bvar::Window<bvar::Adder<uint64_t>> g_read_cache_indirect_total_bytes_1min_window( |
93 | | "cached_remote_reader_indirect_total_bytes_1min_window", &g_read_cache_indirect_total_bytes, |
94 | | 60); |
95 | | bvar::Adder<uint64_t> g_failed_get_peer_addr_counter( |
96 | | "cached_remote_reader_failed_get_peer_addr_counter"); |
97 | | |
98 | | static std::atomic<int> g_active_peer_races {0}; |
99 | | bvar::PassiveStatus<int> g_active_peer_races_bvar( |
100 | | "peer_race_active_count", |
101 | 3.29k | [](void*) { return g_active_peer_races.load(std::memory_order_relaxed); }, nullptr); |
102 | | // Cross-CG peer read race statistics |
103 | | bvar::Adder<uint64_t> g_peer_race_peer_win("peer_race_peer_win"); |
104 | | bvar::Adder<uint64_t> g_peer_race_s3_win("peer_race_s3_win"); |
105 | | bvar::Adder<uint64_t> g_peer_race_both_fail("peer_race_both_fail"); |
106 | | bvar::Adder<uint64_t> g_peer_cross_compute_group_read("peer_cross_compute_group_read"); |
107 | | bvar::Adder<uint64_t> g_peer_same_compute_group_read("peer_same_compute_group_read"); |
108 | | bvar::Adder<uint64_t> g_peer_lazy_fetch_triggered("peer_lazy_fetch_triggered"); |
109 | | |
110 | 718k | static bool use_remote_only_on_cache_miss(const IOContext* io_ctx) { |
111 | 718k | if (io_ctx->file_cache_miss_policy == FileCacheMissPolicy::REMOTE_ONLY_ON_MISS) { |
112 | 3 | return true; |
113 | 3 | } |
114 | 718k | auto* limiter = io_ctx->remote_scan_cache_write_limiter; |
115 | 718k | return limiter != nullptr && limiter->remote_only_on_miss(); |
116 | 718k | } |
117 | | |
118 | | CachedRemoteFileReader::CachedRemoteFileReader(FileReaderSPtr remote_file_reader, |
119 | | const FileReaderOptions& opts) |
120 | 7.18k | : _is_doris_table(opts.is_doris_table), |
121 | 7.18k | _tablet_id(opts.tablet_id), |
122 | 7.18k | _storage_resource_id(opts.storage_resource_id), |
123 | 7.18k | _remote_file_reader(std::move(remote_file_reader)) { |
124 | 7.18k | DCHECK(!_is_doris_table || _tablet_id > 0); |
125 | 7.18k | if (_is_doris_table) { |
126 | 7.17k | _init_doris_table_cache(); |
127 | 7.17k | } else { |
128 | 9 | _init_external_table_cache(opts); |
129 | 9 | } |
130 | 7.18k | } |
131 | | |
132 | 7.17k | void CachedRemoteFileReader::_init_doris_table_cache() { |
133 | 7.17k | _cache_hash = BlockFileCache::hash(path().filename().native()); |
134 | 7.17k | _cache = FileCacheFactory::instance()->get_by_path(_cache_hash); |
135 | 7.17k | if (_can_read_cache_file_directly()) { |
136 | | // this is designed for and test in doris table, external table need extra tests |
137 | 19 | _cache_file_readers = _cache->get_blocks_by_key(_cache_hash); |
138 | 19 | } |
139 | 7.17k | } |
140 | | |
141 | 10 | void CachedRemoteFileReader::_init_external_table_cache(const FileReaderOptions& opts) { |
142 | | // Use path and modification time to build cache key. |
143 | 10 | std::string unique_path = fmt::format("{}:{}", path().native(), opts.mtime); |
144 | 10 | _cache_hash = BlockFileCache::hash(unique_path); |
145 | 10 | if (opts.cache_base_path.empty()) { |
146 | | // If cache path is not specified by session variable, choose randomly. |
147 | 1 | _cache = FileCacheFactory::instance()->get_by_path(_cache_hash); |
148 | 1 | return; |
149 | 1 | } |
150 | | |
151 | | // From query session variable: file_cache_base_path. |
152 | 9 | _cache = FileCacheFactory::instance()->get_by_path(opts.cache_base_path); |
153 | 9 | if (_cache != nullptr) { |
154 | 8 | return; |
155 | 8 | } |
156 | | |
157 | 9 | LOG(WARNING) << "Can't get cache from base path: " << opts.cache_base_path |
158 | 1 | << ", using random instead."; |
159 | 1 | _cache = FileCacheFactory::instance()->get_by_path(_cache_hash); |
160 | 1 | } |
161 | | |
162 | 1.43M | bool CachedRemoteFileReader::_can_read_cache_file_directly() const { |
163 | 1.43M | return _is_doris_table && config::enable_read_cache_file_directly; |
164 | 1.43M | } |
165 | | |
166 | 5.18k | bool CachedRemoteFileReader::_should_read_from_peer(const IOContext* io_ctx) const { |
167 | 5.18k | return doris::config::is_cloud_mode() && _is_doris_table && _tablet_id > 0 && |
168 | 5.18k | !io_ctx->is_warmup && !io_ctx->bypass_peer_read && |
169 | 5.18k | doris::config::enable_cache_read_from_peer; |
170 | 5.18k | } |
171 | | |
172 | 717k | void CachedRemoteFileReader::_insert_file_reader(FileBlockSPtr file_block) { |
173 | 717k | if (_can_read_cache_file_directly()) { |
174 | 36 | std::lock_guard lock(_mtx); |
175 | 36 | DCHECK(file_block->state() == FileBlock::State::DOWNLOADED); |
176 | 36 | file_block->_owned_by_cached_reader = true; |
177 | 36 | _cache_file_readers.emplace(file_block->offset(), std::move(file_block)); |
178 | 36 | } |
179 | 717k | } |
180 | | |
181 | 7.18k | CachedRemoteFileReader::~CachedRemoteFileReader() { |
182 | 7.18k | for (auto& it : _cache_file_readers) { |
183 | 57 | it.second->_owned_by_cached_reader = false; |
184 | 57 | } |
185 | 7.18k | static_cast<void>(close()); |
186 | 7.18k | } |
187 | | |
188 | 8.20k | Status CachedRemoteFileReader::close() { |
189 | 8.20k | return _remote_file_reader->close(); |
190 | 8.20k | } |
191 | | |
192 | | std::pair<size_t, size_t> CachedRemoteFileReader::s_align_size(size_t offset, size_t read_size, |
193 | 718k | size_t length) { |
194 | 718k | size_t left = offset; |
195 | 718k | size_t right = offset + read_size - 1; |
196 | 718k | size_t align_left = |
197 | 718k | (left / config::file_cache_each_block_size) * config::file_cache_each_block_size; |
198 | 718k | size_t align_right = |
199 | 718k | (right / config::file_cache_each_block_size + 1) * config::file_cache_each_block_size; |
200 | 718k | align_right = align_right < length ? align_right : length; |
201 | 718k | size_t align_size = align_right - align_left; |
202 | 718k | if (align_size < config::file_cache_each_block_size && align_left != 0) { |
203 | 3 | align_size += config::file_cache_each_block_size; |
204 | 3 | align_left -= config::file_cache_each_block_size; |
205 | 3 | } |
206 | 718k | return std::make_pair(align_left, align_size); |
207 | 718k | } |
208 | | |
209 | | namespace { |
210 | | struct PeerFetchLayout { |
211 | | std::vector<size_t> block_offsets; |
212 | | std::vector<size_t> block_sizes; |
213 | | size_t total_size = 0; |
214 | | }; |
215 | | |
216 | 9 | bool is_fill_not_found(const Status& st, bool request_fill) { |
217 | 9 | return request_fill && st.is<ErrorCode::NOT_FOUND>(); |
218 | 9 | } |
219 | | |
220 | 48 | bool contains_file_block(const PeerFetchedBlockSet& fetched_blocks, const FileBlockSPtr& block) { |
221 | 48 | return fetched_blocks.contains(block.get()); |
222 | 48 | } |
223 | | |
224 | 5.25k | size_t clip_peer_block_size(const FileBlock::Range& range, size_t file_size) { |
225 | 5.25k | if (range.left >= file_size) { |
226 | 0 | return 0; |
227 | 0 | } |
228 | 5.25k | return std::min(file_size - range.left, range.size()); |
229 | 5.25k | } |
230 | | |
231 | | PeerFetchLayout build_peer_fetch_layout(const std::vector<FileBlockSPtr>& blocks, |
232 | 5.18k | size_t file_size) { |
233 | 5.18k | PeerFetchLayout layout; |
234 | 5.18k | layout.block_offsets.reserve(blocks.size()); |
235 | 5.18k | layout.block_sizes.reserve(blocks.size()); |
236 | 5.25k | for (const auto& block : blocks) { |
237 | 5.25k | const size_t block_size = clip_peer_block_size(block->range(), file_size); |
238 | 5.25k | layout.block_offsets.push_back(layout.total_size); |
239 | 5.25k | layout.block_sizes.push_back(block_size); |
240 | 5.25k | layout.total_size += block_size; |
241 | 5.25k | } |
242 | 5.18k | return layout; |
243 | 5.18k | } |
244 | | |
245 | | Status write_peer_payloads_into_block(const FileBlockSPtr& block, |
246 | | std::vector<const PeerFetchChunk*>& chunks, |
247 | 40 | size_t* block_size) { |
248 | 40 | if (block_size == nullptr) { |
249 | 0 | return Status::InvalidArgument("peer block write requires non-null block_size"); |
250 | 0 | } |
251 | 40 | *block_size = 0; |
252 | 40 | if (chunks.empty()) { |
253 | 0 | return Status::OK(); |
254 | 0 | } |
255 | 40 | std::sort(chunks.begin(), chunks.end(), |
256 | 40 | [](const PeerFetchChunk* lhs, const PeerFetchChunk* rhs) { |
257 | 0 | return lhs->block_offset < rhs->block_offset; |
258 | 0 | }); |
259 | 40 | butil::IOBuf payload; |
260 | 40 | for (const auto* chunk : chunks) { |
261 | 40 | *block_size += chunk->payload.length(); |
262 | 40 | payload.append(chunk->payload); |
263 | 40 | } |
264 | 40 | DCHECK(*block_size != 0); |
265 | 40 | return block->append_iobuf(payload); |
266 | 40 | } |
267 | | |
268 | | void copy_peer_chunk_to_result(const PeerFetchChunk& chunk, size_t offset, size_t right_offset, |
269 | | size_t already_read, Slice result, size_t& indirect_read_bytes, |
270 | 37 | SourceReadBreakdown& source_read_breakdown) { |
271 | 37 | const size_t payload_size = chunk.payload.length(); |
272 | 37 | if (payload_size == 0) { |
273 | 0 | return; |
274 | 0 | } |
275 | 37 | const size_t chunk_left = chunk.block_offset; |
276 | 37 | const size_t chunk_right = chunk_left + payload_size - 1; |
277 | 37 | const size_t copy_left_offset = std::max(offset + already_read, chunk_left); |
278 | 37 | const size_t copy_right_offset = std::min(right_offset, chunk_right); |
279 | 37 | if (copy_left_offset > copy_right_offset) { |
280 | 0 | return; |
281 | 0 | } |
282 | 37 | const size_t copy_offset = copy_left_offset - chunk_left; |
283 | 37 | const size_t copy_size = copy_right_offset - copy_left_offset + 1; |
284 | 37 | char* dst = result.data + (copy_left_offset - offset); |
285 | 37 | chunk.payload.copy_to(dst, copy_size, copy_offset); |
286 | 37 | indirect_read_bytes += copy_size; |
287 | 37 | source_read_breakdown.peer_bytes += copy_size; |
288 | 37 | } |
289 | | |
290 | | // Execute peer read targeting a specific host:port. |
291 | | Status execute_peer_read(const std::vector<FileBlockSPtr>& empty_blocks, |
292 | | PeerFetchResult* peer_result, const std::string& file_path, |
293 | | size_t file_size, bool is_doris_table, ReadStatistics& stats, |
294 | 12 | const IOContext* io_ctx, const std::string& host, int32_t port) { |
295 | 12 | VLOG_DEBUG << "PeerFileCacheReader read from peer, host=" << host << ", port=" << port |
296 | 0 | << ", file_path=" << file_path; |
297 | | |
298 | 12 | if (host.empty() || port == 0) { |
299 | 0 | g_failed_get_peer_addr_counter << 1; |
300 | 0 | LOG_EVERY_N(WARNING, 100) << "PeerFileCacheReader host or port is empty" |
301 | 0 | << ", host=" << host << ", port=" << port |
302 | 0 | << ", file_path=" << file_path; |
303 | 0 | return Status::InternalError<false>("host or port is empty"); |
304 | 0 | } |
305 | 12 | SCOPED_RAW_TIMER(&stats.peer_read_timer); |
306 | 12 | peer_read_counter << 1; |
307 | 12 | PeerFileCacheReader peer_reader(file_path, is_doris_table, host, port); |
308 | | // Serial peer read: source BE has the data from rebalance warm-up; no fill needed. |
309 | 12 | auto st = peer_reader.fetch_blocks(empty_blocks, peer_result, file_size, io_ctx, |
310 | 12 | /*request_fill=*/false); |
311 | 12 | if (!st.ok()) { |
312 | 4 | LOG_WARNING("PeerFileCacheReader read from peer failed") |
313 | 4 | .tag("host", host) |
314 | 4 | .tag("port", port) |
315 | 4 | .tag("error", st.msg()); |
316 | 4 | } |
317 | 12 | stats.from_peer_cache = st.ok(); |
318 | 12 | return st; |
319 | 12 | } |
320 | | |
321 | | // Execute S3 read |
322 | | Status execute_s3_read(size_t empty_start, size_t& size, std::unique_ptr<char[]>& buffer, |
323 | | ReadStatistics& stats, const IOContext* io_ctx, |
324 | 5.16k | FileReaderSPtr remote_file_reader) { |
325 | 5.16k | s3_read_counter << 1; |
326 | 5.16k | SCOPED_RAW_TIMER(&stats.remote_read_timer); |
327 | 5.16k | stats.from_peer_cache = false; |
328 | 5.16k | return remote_file_reader->read_at(empty_start, Slice(buffer.get(), size), &size, io_ctx); |
329 | 5.16k | } |
330 | | |
331 | 18 | CloudWarmUpManager& get_warm_up_manager() { |
332 | 18 | return ExecEnv::GetInstance()->storage_engine().to_cloud().cloud_warm_up_manager(); |
333 | 18 | } |
334 | | |
335 | | // Shared state for peer-vs-S3 winner race. |
336 | | // Uses bthread primitives — never std::mutex/condition_variable in bthread context. |
337 | | struct RaceState { |
338 | | bthread::Mutex mtx; |
339 | | bthread::ConditionVariable cv; |
340 | | int winner = -1; // 0=peer won, 1=s3 won, -1=undecided, -2=both failed |
341 | | bool peer_done = false; |
342 | | bool s3_done = false; |
343 | | Status peer_status; |
344 | | Status s3_status; |
345 | | std::unique_ptr<char[]> s3_buf; |
346 | | PeerFetchResult peer_res; |
347 | | std::string peer_winner_cg_id; // compute_group_id of the winning peer candidate |
348 | | std::string peer_winner_host; // host of the winning peer candidate |
349 | | int64_t peer_elapsed_ns = 0; // wall-clock time of the entire peer path (including retries) |
350 | | int64_t peer_winner_io_ns = 0; // I/O time of the winning candidate only |
351 | | }; |
352 | | |
353 | | // Peer race logic: try candidates sequentially until one succeeds or all fail. |
354 | | // NOTE: Do NOT capture io_ctx here — it points into the caller's stack which may be destroyed |
355 | | // when S3 wins the race and the caller returns before this bthread finishes. |
356 | | void run_peer_race(std::shared_ptr<RaceState> race, std::vector<FileBlockSPtr> empty_blocks, |
357 | | const std::string& file_path, size_t file_sz, bool is_doris, |
358 | | std::shared_ptr<CloudWarmUpManager> manager, |
359 | | std::vector<doris::PeerCandidate> candidates, int64_t tablet_id, |
360 | 14 | std::string resource_id, std::shared_ptr<ResourceContext> parent_resource_ctx) { |
361 | 14 | std::unique_ptr<AttachTask> attach_task; |
362 | 14 | if (parent_resource_ctx != nullptr) { |
363 | 3 | attach_task = std::make_unique<AttachTask>(parent_resource_ctx); |
364 | 3 | } |
365 | | |
366 | 14 | bool all_tried = true; |
367 | 14 | MonotonicStopWatch peer_sw; |
368 | 14 | peer_sw.start(); |
369 | | |
370 | 22 | for (size_t i = 0; i < candidates.size(); ++i) { |
371 | | // Before issuing the next RPC, check if S3 already won. |
372 | 17 | if (i > 0) { |
373 | 3 | TEST_SYNC_POINT("run_peer_race::between_candidates"); |
374 | 3 | std::unique_lock<bthread::Mutex> lk(race->mtx); |
375 | 3 | if (race->winner > 0) { |
376 | | // S3 already won — stop, but not all candidates were tried. |
377 | 1 | all_tried = false; |
378 | 1 | break; |
379 | 1 | } |
380 | 3 | } |
381 | | |
382 | 16 | const auto& cand = candidates[i]; |
383 | 16 | peer_read_counter << 1; |
384 | 16 | PeerFileCacheReader peer_reader(file_path, is_doris, cand.host, cand.brpc_port); |
385 | 16 | PeerFetchResult local_peer_res; |
386 | 16 | const bool request_fill = |
387 | 16 | !config::peer_cache_fill_compute_group_id.empty() && |
388 | 16 | cand.compute_group_id == config::peer_cache_fill_compute_group_id && |
389 | 16 | !resource_id.empty() && !file_path.empty(); |
390 | 16 | MonotonicStopWatch cand_sw; |
391 | 16 | cand_sw.start(); |
392 | 16 | auto st = peer_reader.fetch_blocks(empty_blocks, &local_peer_res, file_sz, |
393 | 16 | /*ctx=*/nullptr, request_fill, tablet_id, resource_id); |
394 | 16 | if (st.ok()) { |
395 | 7 | manager->update_peer_candidate_on_success(tablet_id, cand.compute_group_id); |
396 | 7 | std::unique_lock<bthread::Mutex> lk(race->mtx); |
397 | 7 | if (race->winner < 0) { |
398 | 7 | race->winner = 0; |
399 | 7 | race->peer_res = std::move(local_peer_res); |
400 | 7 | race->peer_winner_cg_id = cand.compute_group_id; |
401 | 7 | race->peer_winner_host = cand.host; |
402 | 7 | race->peer_elapsed_ns = peer_sw.elapsed_time(); |
403 | 7 | race->peer_winner_io_ns = cand_sw.elapsed_time(); |
404 | 7 | } |
405 | 7 | race->peer_done = true; |
406 | 7 | race->peer_status = Status::OK(); |
407 | 7 | race->cv.notify_all(); |
408 | 7 | return; |
409 | 7 | } |
410 | | |
411 | | // Handle per-candidate failure. |
412 | 9 | if (st.template is<ErrorCode::TOO_MANY_TASKS>()) { |
413 | 0 | all_tried = false; |
414 | 0 | break; |
415 | 0 | } |
416 | 9 | if (is_fill_not_found(st, request_fill)) { |
417 | | // Pull-through fill already told us this designated fill CG could not serve the block |
418 | | // in time. Do not serially retry additional candidates in the same race; let S3 win |
419 | | // instead of paying more peer RPC latency. |
420 | 1 | manager->rotate_peer_candidate_on_cache_miss(tablet_id, cand.host, cand.brpc_port); |
421 | 1 | all_tried = false; |
422 | 1 | break; |
423 | 1 | } |
424 | 8 | if (st.template is<ErrorCode::NOT_FOUND>()) { |
425 | 5 | manager->rotate_peer_candidate_on_cache_miss(tablet_id, cand.host, cand.brpc_port); |
426 | 5 | } else { |
427 | 3 | manager->update_peer_candidate_on_rpc_failure(tablet_id, cand.host, cand.brpc_port); |
428 | 3 | } |
429 | 8 | } |
430 | | |
431 | 7 | if (all_tried) { |
432 | 5 | manager->record_peer_all_miss(tablet_id); |
433 | 5 | } |
434 | 7 | std::unique_lock<bthread::Mutex> lk(race->mtx); |
435 | 7 | race->peer_done = true; |
436 | 7 | race->peer_status = Status::InternalError<false>("peer: all candidates failed"); |
437 | 7 | if (race->winner < 0 && race->s3_done) { |
438 | 0 | race->winner = race->s3_status.ok() ? 1 : -2; |
439 | 0 | } |
440 | 7 | race->cv.notify_all(); |
441 | 7 | } |
442 | | |
443 | | // Apply hedge delay, then submit S3 read to the thread pool (or run inline). |
444 | | void launch_s3_race(std::shared_ptr<RaceState> race, size_t empty_start, size_t span_size, |
445 | | const IOContext* io_ctx, FileReaderSPtr remote_reader, |
446 | | std::shared_ptr<ResourceContext> parent_resource_ctx, |
447 | 14 | std::shared_ptr<CachedRemoteFileReader> owner) { |
448 | | // Raw S3 read body. |
449 | | // `owner` keeps the CachedRemoteFileReader alive until the S3 task finishes, |
450 | | // preventing close() from being called on remote_reader while we are still reading. |
451 | | // Do NOT capture io_ctx: it points into the caller's stack/iterator which may be |
452 | | // destroyed when the query is cancelled before this background task runs. The S3 |
453 | | // leg of the race is a best-effort background task whose result is discarded if the |
454 | | // peer wins; passing nullptr is safe because S3FileReader::read_at_impl ignores it. |
455 | 14 | auto do_s3_read = [race, empty_start, span_size, remote_reader, owner]() { |
456 | 8 | (void)owner; |
457 | 8 | auto s3_buf = std::make_unique<char[]>(span_size); |
458 | 8 | size_t read_size = span_size; |
459 | 8 | s3_read_counter << 1; |
460 | 8 | TEST_SYNC_POINT("CachedRemoteFileReader::_execute_winner_race::s3_before_read"); |
461 | 8 | auto st = remote_reader->read_at(empty_start, Slice(s3_buf.get(), span_size), &read_size, |
462 | 8 | nullptr); |
463 | 8 | std::unique_lock<bthread::Mutex> lk(race->mtx); |
464 | 8 | race->s3_done = true; |
465 | 8 | race->s3_status = st; |
466 | 8 | if (st.ok() && race->winner < 0) { |
467 | 7 | race->winner = 1; |
468 | 7 | race->s3_buf = std::move(s3_buf); |
469 | 7 | } |
470 | 8 | race->cv.notify_all(); |
471 | 8 | }; |
472 | | |
473 | | // Hedge delay: give peer a head start, but wake up early if peer finishes. |
474 | | // Uses cv.wait_for() instead of bthread_usleep() so the calling thread is |
475 | | // unblocked as soon as the peer bthread signals completion, avoiding the |
476 | | // unconditional 20ms sleep that dominated latency on cache-miss-heavy queries. |
477 | 14 | bool peer_already_won = false; |
478 | 14 | if (config::peer_race_hedge_delay_ms > 0) { |
479 | 13 | std::unique_lock<bthread::Mutex> lk(race->mtx); |
480 | 13 | if (!race->peer_done) { |
481 | 13 | race->cv.wait_for(lk, static_cast<long>(config::peer_race_hedge_delay_ms) * 1000); |
482 | 13 | } |
483 | 13 | peer_already_won = (race->winner == 0); |
484 | 13 | if (peer_already_won) { |
485 | 6 | race->s3_done = true; |
486 | 6 | race->s3_status = Status::InternalError<false>("skipped: peer won during hedge delay"); |
487 | 6 | } |
488 | 13 | } |
489 | | |
490 | 14 | if (!peer_already_won) { |
491 | 8 | auto s3_fn = [do_s3_read, parent_resource_ctx]() mutable { |
492 | 7 | std::unique_ptr<AttachTask> attach_task; |
493 | 7 | if (parent_resource_ctx != nullptr) { |
494 | 1 | attach_task = std::make_unique<AttachTask>(parent_resource_ctx); |
495 | 1 | } |
496 | 7 | do_s3_read(); |
497 | 7 | }; |
498 | 8 | auto* s3_pool = ExecEnv::GetInstance()->peer_race_s3_thread_pool(); |
499 | 8 | if (s3_pool == nullptr || !s3_pool->submit_func(s3_fn).ok()) { |
500 | 1 | do_s3_read(); |
501 | 1 | } |
502 | 8 | } |
503 | 14 | } |
504 | | |
505 | | // Wait for the race to finish and populate the output accordingly. |
506 | | Status collect_race_result(std::shared_ptr<RaceState> race, size_t span_size, |
507 | | std::unique_ptr<char[]>& buffer, PeerFetchResult* peer_result, |
508 | 14 | ReadStatistics& stats, const IOContext* io_ctx) { |
509 | 14 | { |
510 | 14 | std::unique_lock<bthread::Mutex> lk(race->mtx); |
511 | 21 | while (race->winner < 0 && !(race->peer_done && race->s3_done)) { |
512 | 7 | race->cv.wait(lk); |
513 | 7 | } |
514 | 14 | } |
515 | 14 | g_active_peer_races.fetch_sub(1, std::memory_order_relaxed); |
516 | | |
517 | 14 | const std::string self_cg_id = |
518 | 14 | static_cast<CloudClusterInfo*>(ExecEnv::GetInstance()->cluster_info()) |
519 | 14 | ->cloud_compute_group_id(); |
520 | | |
521 | 14 | if (race->winner == 0) { |
522 | | // Peer won. |
523 | 7 | if (peer_result != nullptr) { |
524 | 7 | *peer_result = std::move(race->peer_res); |
525 | 7 | } |
526 | 7 | stats.from_peer_cache = true; |
527 | 7 | stats.peer_read_timer += race->peer_elapsed_ns; |
528 | 7 | g_peer_race_peer_win << 1; |
529 | 7 | const bool is_cross_cg = |
530 | 7 | !race->peer_winner_cg_id.empty() && race->peer_winner_cg_id != self_cg_id; |
531 | 7 | if (is_cross_cg) { |
532 | 5 | g_peer_cross_compute_group_read << 1; |
533 | 5 | } else { |
534 | 2 | g_peer_same_compute_group_read << 1; |
535 | 2 | } |
536 | 7 | if (io_ctx != nullptr && io_ctx->file_cache_stats != nullptr) { |
537 | 7 | io_ctx->file_cache_stats->num_peer_race_peer_win++; |
538 | 7 | io_ctx->file_cache_stats->peer_hosts.insert(race->peer_winner_host); |
539 | 7 | if (is_cross_cg) { |
540 | 5 | io_ctx->file_cache_stats->num_cross_cg_peer_io_total++; |
541 | 5 | io_ctx->file_cache_stats->bytes_read_from_cross_cg_peer += span_size; |
542 | 5 | io_ctx->file_cache_stats->cross_cg_peer_io_timer += race->peer_winner_io_ns; |
543 | 5 | } else { |
544 | 2 | io_ctx->file_cache_stats->num_same_cg_peer_io_total++; |
545 | 2 | io_ctx->file_cache_stats->bytes_read_from_same_cg_peer += span_size; |
546 | 2 | io_ctx->file_cache_stats->same_cg_peer_io_timer += race->peer_winner_io_ns; |
547 | 2 | } |
548 | 7 | } |
549 | 7 | return Status::OK(); |
550 | 7 | } else if (race->winner == 1) { |
551 | | // S3 won. |
552 | 7 | buffer = std::move(race->s3_buf); |
553 | 7 | stats.from_peer_cache = false; |
554 | 7 | g_peer_race_s3_win << 1; |
555 | 7 | if (io_ctx != nullptr && io_ctx->file_cache_stats != nullptr) { |
556 | 7 | io_ctx->file_cache_stats->num_peer_race_s3_win++; |
557 | 7 | } |
558 | 7 | return Status::OK(); |
559 | 7 | } |
560 | 0 | g_peer_race_both_fail << 1; |
561 | 0 | return Status::InternalError<false>("peer race: both peer and s3 failed"); |
562 | 14 | } |
563 | | |
564 | | } // anonymous namespace |
565 | | |
566 | | Status CachedRemoteFileReader::_execute_s3_fallback(size_t empty_start, size_t span_size, |
567 | | std::unique_ptr<char[]>& buffer, |
568 | | PeerFetchResult* peer_result, |
569 | | ReadStatistics& stats, |
570 | 5.16k | const IOContext* io_ctx) { |
571 | 5.16k | if (peer_result != nullptr) { |
572 | 5.16k | peer_result->clear(); |
573 | 5.16k | } |
574 | 5.16k | buffer.reset(new char[span_size]); |
575 | 5.16k | size_t read_size = span_size; |
576 | 5.16k | return execute_s3_read(empty_start, read_size, buffer, stats, io_ctx, _remote_file_reader); |
577 | 5.16k | } |
578 | | |
579 | | Status CachedRemoteFileReader::_execute_sequential_peer_read( |
580 | | const std::vector<FileBlockSPtr>& empty_blocks, size_t empty_start, size_t span_size, |
581 | | std::unique_ptr<char[]>& buffer, PeerFetchResult* peer_result, ReadStatistics& stats, |
582 | | const IOContext* io_ctx, const std::vector<doris::PeerCandidate>& candidates, |
583 | 1 | int64_t tablet_id) { |
584 | | // candidates[0] already reflects last_successful_compute_group_id affinity: |
585 | | // get_peer_candidates() applies stable_partition before returning. |
586 | 1 | if (candidates.empty()) { |
587 | 0 | return _execute_s3_fallback(empty_start, span_size, buffer, peer_result, stats, io_ctx); |
588 | 0 | } |
589 | | |
590 | 1 | auto& manager = get_warm_up_manager(); |
591 | 1 | PeerFetchResult serial_res; |
592 | 1 | const int64_t timer_before = stats.peer_read_timer; |
593 | 1 | auto st = execute_peer_read(empty_blocks, &serial_res, path().native(), this->size(), |
594 | 1 | _is_doris_table, stats, io_ctx, candidates[0].host, |
595 | 1 | candidates[0].brpc_port); |
596 | 1 | if (st.ok()) { |
597 | 1 | manager.update_peer_candidate_on_success(tablet_id, candidates[0].compute_group_id); |
598 | 1 | if (peer_result != nullptr) { |
599 | 1 | *peer_result = std::move(serial_res); |
600 | 1 | } |
601 | | // Update profile counters for cross/same CG stats. |
602 | 1 | const std::string self_cg_id = |
603 | 1 | static_cast<CloudClusterInfo*>(ExecEnv::GetInstance()->cluster_info()) |
604 | 1 | ->cloud_compute_group_id(); |
605 | 1 | const bool is_cross_cg = !candidates[0].compute_group_id.empty() && |
606 | 1 | candidates[0].compute_group_id != self_cg_id; |
607 | 1 | if (is_cross_cg) { |
608 | 1 | g_peer_cross_compute_group_read << 1; |
609 | 1 | } else { |
610 | 0 | g_peer_same_compute_group_read << 1; |
611 | 0 | } |
612 | 1 | if (io_ctx != nullptr && io_ctx->file_cache_stats != nullptr) { |
613 | 1 | io_ctx->file_cache_stats->peer_hosts.insert(candidates[0].host); |
614 | 1 | if (is_cross_cg) { |
615 | 1 | io_ctx->file_cache_stats->num_cross_cg_peer_io_total++; |
616 | 1 | io_ctx->file_cache_stats->bytes_read_from_cross_cg_peer += span_size; |
617 | 1 | io_ctx->file_cache_stats->cross_cg_peer_io_timer += |
618 | 1 | stats.peer_read_timer - timer_before; |
619 | 1 | } else { |
620 | 0 | io_ctx->file_cache_stats->num_same_cg_peer_io_total++; |
621 | 0 | io_ctx->file_cache_stats->bytes_read_from_same_cg_peer += span_size; |
622 | 0 | io_ctx->file_cache_stats->same_cg_peer_io_timer += |
623 | 0 | stats.peer_read_timer - timer_before; |
624 | 0 | } |
625 | 1 | } |
626 | 1 | return st; |
627 | 1 | } |
628 | | // Track failure so affinity / eviction logic stays consistent with the race path. |
629 | 0 | if (st.is<ErrorCode::TOO_MANY_TASKS>()) { |
630 | | // Server healthy but overloaded — don't penalize candidate. |
631 | 0 | } else if (st.is<ErrorCode::NOT_FOUND>()) { |
632 | 0 | manager.rotate_peer_candidate_on_cache_miss(tablet_id, candidates[0].host, |
633 | 0 | candidates[0].brpc_port); |
634 | 0 | } else { |
635 | 0 | manager.update_peer_candidate_on_rpc_failure(tablet_id, candidates[0].host, |
636 | 0 | candidates[0].brpc_port); |
637 | 0 | } |
638 | 0 | return _execute_s3_fallback(empty_start, span_size, buffer, peer_result, stats, io_ctx); |
639 | 1 | } |
640 | | |
641 | | Status CachedRemoteFileReader::_execute_remote_read(const std::vector<FileBlockSPtr>& empty_blocks, |
642 | | size_t empty_start, size_t span_size, |
643 | | std::unique_ptr<char[]>& buffer, |
644 | | PeerFetchResult* peer_result, |
645 | | ReadStatistics& stats, |
646 | 5.18k | const IOContext* io_ctx) { |
647 | | // --- Non-peer path: direct S3 --- |
648 | 5.18k | if (!_should_read_from_peer(io_ctx)) { |
649 | 5.15k | return _execute_s3_fallback(empty_start, span_size, buffer, peer_result, stats, io_ctx); |
650 | 5.15k | } |
651 | | |
652 | | // --- UT debug point: injected peer address --- |
653 | 28 | DBUG_EXECUTE_IF("PeerFileCacheReader::_fetch_from_peer_cache_blocks", { |
654 | 28 | std::string dp_host = dp->param<std::string>("host", "127.0.0.1"); |
655 | 28 | int32_t dp_port = dp->param("port", 9060); |
656 | 28 | buffer.reset(); |
657 | 28 | DCHECK(peer_result != nullptr); |
658 | 28 | peer_result->clear(); |
659 | 28 | auto st = execute_peer_read(empty_blocks, peer_result, path().native(), this->size(), |
660 | 28 | _is_doris_table, stats, io_ctx, dp_host, dp_port); |
661 | 28 | if (st.ok()) return st; |
662 | 28 | return _execute_s3_fallback(empty_start, span_size, buffer, peer_result, stats, io_ctx); |
663 | 28 | }); |
664 | | |
665 | | // --- Resolve tablet and obtain peer candidates --- |
666 | 17 | int64_t tablet_id = _tablet_id; |
667 | 17 | auto& manager = get_warm_up_manager(); |
668 | 17 | auto candidates = manager.get_peer_candidates(tablet_id); |
669 | 17 | if (candidates.empty()) { |
670 | 2 | if (!manager.is_peer_cooldown(tablet_id)) { |
671 | | // Cold miss: trigger background FE fetch and fall back to S3. |
672 | 1 | g_peer_lazy_fetch_triggered << 1; |
673 | 1 | auto manager_ptr = |
674 | 1 | ExecEnv::GetInstance()->storage_engine().to_cloud().cloud_warm_up_manager_ptr(); |
675 | 1 | start_bthread([manager_ptr = std::move(manager_ptr), tablet_id]() { |
676 | 1 | manager_ptr->fetch_candidates_from_fe(tablet_id); |
677 | 1 | }); |
678 | 1 | } |
679 | 2 | return _execute_s3_fallback(empty_start, span_size, buffer, peer_result, stats, io_ctx); |
680 | 2 | } |
681 | | |
682 | | // --- Dispatch: concurrent race or sequential fallback --- |
683 | | // Candidates are already sorted by last_successful_compute_group_id affinity |
684 | | // (stable_partition in get_peer_candidates), so the winner race peer bthread |
685 | | // naturally tries the most promising candidate first — whether same-CG or cross-CG. |
686 | 15 | if (config::enable_peer_s3_race) { |
687 | 14 | return _execute_winner_race(empty_blocks, empty_start, span_size, buffer, peer_result, |
688 | 14 | stats, io_ctx, candidates, tablet_id); |
689 | 14 | } |
690 | 1 | return _execute_sequential_peer_read(empty_blocks, empty_start, span_size, buffer, peer_result, |
691 | 1 | stats, io_ctx, candidates, tablet_id); |
692 | 15 | } |
693 | | |
694 | | Status CachedRemoteFileReader::_execute_winner_race( |
695 | | const std::vector<FileBlockSPtr>& empty_blocks, size_t empty_start, size_t span_size, |
696 | | std::unique_ptr<char[]>& buffer, PeerFetchResult* peer_result, ReadStatistics& stats, |
697 | | const IOContext* io_ctx, const std::vector<doris::PeerCandidate>& candidates, |
698 | 14 | int64_t tablet_id) { |
699 | | // Reserve a race slot; degrade to sequential if at limit. |
700 | 14 | if (g_active_peer_races.fetch_add(1, std::memory_order_relaxed) >= |
701 | 14 | config::max_concurrent_peer_races) { |
702 | 0 | g_active_peer_races.fetch_sub(1, std::memory_order_relaxed); |
703 | 0 | return _execute_sequential_peer_read(empty_blocks, empty_start, span_size, buffer, |
704 | 0 | peer_result, stats, io_ctx, candidates, tablet_id); |
705 | 0 | } |
706 | | |
707 | 14 | auto race = std::make_shared<RaceState>(); |
708 | 14 | auto manager = ExecEnv::GetInstance()->storage_engine().to_cloud().cloud_warm_up_manager_ptr(); |
709 | | |
710 | | // Capture context for child threads. |
711 | 14 | const std::string file_path = path().native(); |
712 | 14 | const size_t file_sz = this->size(); |
713 | 14 | const bool is_doris = _is_doris_table; |
714 | 14 | auto remote_reader = _remote_file_reader; |
715 | 14 | std::shared_ptr<ResourceContext> parent_resource_ctx; |
716 | 14 | auto* parent_thread_context = thread_context(); |
717 | 14 | if (parent_thread_context != nullptr && parent_thread_context->is_attach_task()) { |
718 | 3 | parent_resource_ctx = parent_thread_context->resource_ctx(); |
719 | 3 | } |
720 | | |
721 | | // Launch peer bthread. |
722 | 14 | start_bthread( |
723 | 14 | [race, empty_blocks = std::move(empty_blocks), file_path, file_sz, is_doris, |
724 | 14 | manager = std::move(manager), candidates = std::move(candidates), tablet_id, |
725 | 14 | resource_id = _storage_resource_id, parent_resource_ctx]() mutable { |
726 | 14 | run_peer_race(race, std::move(empty_blocks), file_path, file_sz, is_doris, |
727 | 14 | std::move(manager), std::move(candidates), tablet_id, |
728 | 14 | std::move(resource_id), parent_resource_ctx); |
729 | 14 | }, |
730 | 14 | /*init_thread_ctx=*/true); |
731 | | |
732 | | // Launch S3 (with optional hedge delay). |
733 | | // Pass shared_from_this() so the background S3 task holds a reference to this |
734 | | // reader, preventing destruction (and close()) until the S3 task completes. |
735 | 14 | launch_s3_race(race, empty_start, span_size, io_ctx, remote_reader, parent_resource_ctx, |
736 | 14 | shared_from_this()); |
737 | | |
738 | | // Collect race result. |
739 | 14 | return collect_race_result(race, span_size, buffer, peer_result, stats, io_ctx); |
740 | 14 | } |
741 | | |
742 | | bool CachedRemoteFileReader::_try_read_from_cached_files_directly( |
743 | | size_t offset, Slice result, size_t bytes_req, bool is_dryrun, ReadStatistics& stats, |
744 | 718k | SourceReadBreakdown& source_read_breakdown, size_t& already_read, size_t* bytes_read) { |
745 | 718k | if (!_can_read_cache_file_directly()) { |
746 | 717k | return false; |
747 | 717k | } |
748 | | |
749 | 1.04k | SCOPED_RAW_TIMER(&stats.read_cache_file_directly_timer); |
750 | 1.04k | size_t need_read_size = bytes_req; |
751 | 1.04k | std::shared_lock lock(_mtx); |
752 | 1.04k | if (_cache_file_readers.empty()) { |
753 | 16 | return false; |
754 | 16 | } |
755 | | |
756 | 1.03k | auto iter = _cache_file_readers.upper_bound(offset); |
757 | 1.03k | if (iter != _cache_file_readers.begin()) { |
758 | 1.01k | --iter; |
759 | 1.01k | } |
760 | | |
761 | 1.03k | size_t current_offset = offset; |
762 | 2.45k | while (need_read_size != 0 && iter != _cache_file_readers.end()) { |
763 | 1.42k | if (iter->second->offset() > current_offset || |
764 | 1.42k | iter->second->range().right < current_offset) { |
765 | 7 | break; |
766 | 7 | } |
767 | | |
768 | 1.41k | size_t file_offset = current_offset - iter->second->offset(); |
769 | 1.41k | size_t reserve_bytes = std::min(need_read_size, iter->second->range().size() - file_offset); |
770 | 1.41k | if (is_dryrun) [[unlikely]] { |
771 | 1 | g_skip_local_cache_io_sum_bytes << reserve_bytes; |
772 | 1.41k | } else { |
773 | 1.41k | SCOPED_RAW_TIMER(&stats.local_read_timer); |
774 | 1.41k | if (!iter->second |
775 | 1.41k | ->read(Slice(result.data + (current_offset - offset), reserve_bytes), |
776 | 1.41k | file_offset) |
777 | 1.41k | .ok()) { // TODO: maybe read failed because block evict, should handle error |
778 | 0 | break; |
779 | 0 | } |
780 | 1.41k | source_read_breakdown.local_bytes += reserve_bytes; |
781 | 1.41k | } |
782 | | |
783 | 1.41k | _cache->add_need_update_lru_block(iter->second); |
784 | 1.41k | need_read_size -= reserve_bytes; |
785 | 1.41k | current_offset += reserve_bytes; |
786 | 1.41k | already_read += reserve_bytes; |
787 | 1.41k | ++iter; |
788 | 1.41k | } |
789 | | |
790 | 1.03k | if (need_read_size == 0) { |
791 | 1.00k | *bytes_read = bytes_req; |
792 | 1.00k | stats.hit_cache = true; |
793 | 1.00k | g_read_cache_direct_whole_num << 1; |
794 | 1.00k | g_read_cache_direct_whole_bytes << bytes_req; |
795 | 1.00k | return true; |
796 | 1.00k | } |
797 | | |
798 | 24 | g_read_cache_direct_partial_num << 1; |
799 | 24 | g_read_cache_direct_partial_bytes << already_read; |
800 | 24 | return false; |
801 | 1.03k | } |
802 | | |
803 | | std::vector<FileBlockSPtr> CachedRemoteFileReader::_collect_remote_read_blocks( |
804 | 717k | const FileBlocksHolder& holder, ReadStatistics& stats) { |
805 | 717k | std::vector<FileBlockSPtr> empty_blocks; |
806 | 717k | for (auto& block : holder.file_blocks) { |
807 | 717k | switch (block->state()) { |
808 | 5.24k | case FileBlock::State::EMPTY: |
809 | 5.24k | VLOG_DEBUG << fmt::format("Block EMPTY path={} hash={}:{}:{} offset={} cache_path={}", |
810 | 0 | path().native(), _cache_hash.to_string(), _cache_hash.high(), |
811 | 0 | _cache_hash.low(), block->offset(), block->get_cache_file()); |
812 | 5.24k | block->get_or_set_downloader(); |
813 | 5.24k | if (block->is_downloader()) { |
814 | 5.24k | empty_blocks.push_back(block); |
815 | 5.24k | TEST_SYNC_POINT_CALLBACK("CachedRemoteFileReader::EMPTY"); |
816 | 5.24k | } |
817 | 5.24k | stats.hit_cache = false; |
818 | 5.24k | break; |
819 | 4 | case FileBlock::State::SKIP_CACHE: |
820 | 4 | VLOG_DEBUG << fmt::format( |
821 | 0 | "Block SKIP_CACHE path={} hash={}:{}:{} offset={} cache_path={}", |
822 | 0 | path().native(), _cache_hash.to_string(), _cache_hash.high(), _cache_hash.low(), |
823 | 0 | block->offset(), block->get_cache_file()); |
824 | 4 | empty_blocks.push_back(block); |
825 | 4 | stats.hit_cache = false; |
826 | 4 | stats.skip_cache = true; |
827 | 4 | break; |
828 | 2 | case FileBlock::State::DOWNLOADING: |
829 | 2 | stats.hit_cache = false; |
830 | 2 | break; |
831 | 712k | case FileBlock::State::DOWNLOADED: |
832 | 712k | _insert_file_reader(block); |
833 | 712k | break; |
834 | 717k | } |
835 | 717k | } |
836 | 717k | return empty_blocks; |
837 | 717k | } |
838 | | |
839 | | Status CachedRemoteFileReader::_read_remote_blocks_into_cache( |
840 | | const std::vector<FileBlockSPtr>& empty_blocks, size_t offset, size_t bytes_req, |
841 | | size_t already_read, Slice result, bool is_dryrun, ReadStatistics& stats, |
842 | | SourceReadBreakdown& source_read_breakdown, const IOContext* io_ctx, |
843 | | size_t& indirect_read_bytes, size_t& empty_start, size_t& empty_end, |
844 | 717k | PeerFetchedBlockSet& peer_fetched_blocks) { |
845 | 717k | empty_start = 0; |
846 | 717k | empty_end = 0; |
847 | 717k | peer_fetched_blocks.clear(); |
848 | 717k | if (empty_blocks.empty()) { |
849 | 712k | return Status::OK(); |
850 | 712k | } |
851 | | |
852 | 5.18k | empty_start = empty_blocks.front()->range().left; |
853 | 5.18k | empty_end = empty_blocks.back()->range().right; |
854 | 5.18k | const size_t span_read_size = empty_end - empty_start + 1; |
855 | 5.18k | const auto peer_fetch_layout = build_peer_fetch_layout(empty_blocks, size()); |
856 | 5.18k | std::unique_ptr<char[]> buffer; |
857 | 5.18k | PeerFetchResult peer_result; |
858 | | |
859 | 5.18k | RETURN_IF_ERROR(_execute_remote_read(empty_blocks, empty_start, span_read_size, buffer, |
860 | 5.18k | &peer_result, stats, io_ctx)); |
861 | | |
862 | 5.18k | std::vector<std::vector<const PeerFetchChunk*>> peer_chunks_by_block; |
863 | 5.18k | if (stats.from_peer_cache) { |
864 | | // Peer returns sparse payloads; remember the exact sparse blocks that were filled. |
865 | 15 | peer_fetched_blocks.reserve(empty_blocks.size()); |
866 | 40 | for (const auto& block : empty_blocks) { |
867 | 40 | peer_fetched_blocks.insert(block.get()); |
868 | 40 | } |
869 | 15 | peer_chunks_by_block.resize(empty_blocks.size()); |
870 | 40 | for (const auto& chunk : peer_result.chunks) { |
871 | 40 | DCHECK_LT(chunk.block_index, empty_blocks.size()); |
872 | 40 | peer_chunks_by_block[chunk.block_index].push_back(&chunk); |
873 | 40 | } |
874 | 15 | } |
875 | | |
876 | 5.18k | SCOPED_CONCURRENCY_COUNT(ConcurrencyStatsManager::instance().cached_remote_reader_write_back); |
877 | 10.4k | for (size_t idx = 0; idx < empty_blocks.size(); ++idx) { |
878 | 5.25k | auto& block = empty_blocks[idx]; |
879 | 5.25k | if (block->state() == FileBlock::State::SKIP_CACHE) { |
880 | 4 | continue; |
881 | 4 | } |
882 | | |
883 | 5.24k | SCOPED_RAW_TIMER(&stats.local_write_timer); |
884 | 5.24k | size_t block_size = block->range().size(); |
885 | 5.24k | Status st; |
886 | 5.24k | if (stats.from_peer_cache) { |
887 | 40 | block_size = peer_fetch_layout.block_sizes[idx]; |
888 | 40 | if (block_size == 0) { |
889 | 0 | continue; |
890 | 0 | } |
891 | 40 | st = write_peer_payloads_into_block(block, peer_chunks_by_block[idx], &block_size); |
892 | 5.20k | } else { |
893 | 5.20k | char* current_ptr = buffer.get() + block->range().left - empty_start; |
894 | 5.20k | st = block->append(Slice(current_ptr, block_size)); |
895 | 5.20k | } |
896 | 5.24k | if (st.ok()) { |
897 | 5.24k | st = block->finalize(); |
898 | 5.24k | } |
899 | 5.24k | if (!st.ok()) { |
900 | 2 | LOG(WARNING) << "write data to file cache failed, source=" |
901 | 2 | << (stats.from_peer_cache ? "peer" : "remote") |
902 | 2 | << ", path=" << path().native() << ", tablet_id=" << _tablet_id |
903 | 2 | << ", file_size=" << size() << ", cache_hash=" << _cache_hash.to_string() |
904 | 2 | << ", write_block_size=" << block_size |
905 | 2 | << ", block=" << block->get_info_for_log() |
906 | 2 | << ", cache_file=" << block->get_cache_file() << ", err=" << st; |
907 | 5.24k | } else { |
908 | 5.24k | _insert_file_reader(block); |
909 | 5.24k | stats.bytes_write_into_file_cache += block_size; |
910 | 5.24k | } |
911 | 5.24k | } |
912 | | |
913 | 5.18k | const size_t right_offset = offset + bytes_req - 1; |
914 | 5.18k | if (stats.from_peer_cache) { |
915 | 15 | if (is_dryrun) { |
916 | 1 | return Status::OK(); |
917 | 1 | } |
918 | 37 | for (const auto& chunk : peer_result.chunks) { |
919 | 37 | copy_peer_chunk_to_result(chunk, offset, right_offset, already_read, result, |
920 | 37 | indirect_read_bytes, source_read_breakdown); |
921 | 37 | } |
922 | 14 | return Status::OK(); |
923 | 15 | } |
924 | | |
925 | 5.17k | if (empty_start <= right_offset && empty_end >= offset + already_read && !is_dryrun) { |
926 | 5.16k | size_t copy_left_offset = std::max(offset + already_read, empty_start); |
927 | 5.16k | size_t copy_right_offset = std::min(right_offset, empty_end); |
928 | 5.16k | char* dst = result.data + (copy_left_offset - offset); |
929 | 5.16k | char* src = buffer.get() + (copy_left_offset - empty_start); |
930 | 5.16k | size_t copy_size = copy_right_offset - copy_left_offset + 1; |
931 | 5.16k | memcpy(dst, src, copy_size); |
932 | 5.16k | indirect_read_bytes += copy_size; |
933 | 5.16k | source_read_breakdown.remote_bytes += copy_size; |
934 | 5.16k | } |
935 | 5.16k | return Status::OK(); |
936 | 5.18k | } |
937 | | |
938 | | Status CachedRemoteFileReader::_read_remaining_blocks_from_cache( |
939 | | const FileBlocksHolder& holder, size_t offset, size_t bytes_req, Slice result, |
940 | | bool is_dryrun, size_t empty_start, size_t empty_end, |
941 | | const PeerFetchedBlockSet& peer_fetched_blocks, ReadStatistics& stats, |
942 | | SourceReadBreakdown& source_read_breakdown, size_t& indirect_read_bytes, size_t* bytes_read, |
943 | 717k | const IOContext* io_ctx) { |
944 | 717k | size_t current_offset = offset + *bytes_read; |
945 | 717k | size_t end_offset = offset + bytes_req - 1; |
946 | 717k | bool need_self_heal = false; |
947 | 717k | for (auto& block : holder.file_blocks) { |
948 | 717k | if (current_offset > end_offset) { |
949 | 0 | break; |
950 | 0 | } |
951 | | |
952 | 717k | size_t left = block->range().left; |
953 | 717k | size_t right = block->range().right; |
954 | 717k | if (right < offset) { |
955 | 1 | continue; |
956 | 1 | } |
957 | | |
958 | 717k | size_t read_size = |
959 | 717k | end_offset > right ? right - current_offset + 1 : end_offset - current_offset + 1; |
960 | 717k | if (!peer_fetched_blocks.empty() && contains_file_block(peer_fetched_blocks, block)) { |
961 | | // For sparse peer reads, skip only blocks fetched from peer. Other blocks inside the |
962 | | // enclosing span may still come from local cache. |
963 | 41 | *bytes_read += read_size; |
964 | 41 | current_offset = right + 1; |
965 | 41 | continue; |
966 | 41 | } |
967 | 717k | if (peer_fetched_blocks.empty() && empty_start <= left && right <= empty_end) { |
968 | 5.21k | *bytes_read += read_size; |
969 | 5.21k | current_offset = right + 1; |
970 | 5.21k | continue; |
971 | 5.21k | } |
972 | | |
973 | 712k | FileBlock::State block_state = block->state(); |
974 | 712k | int64_t wait_time = 0; |
975 | 712k | static int64_t max_wait_time = 10; |
976 | 712k | TEST_SYNC_POINT_CALLBACK("CachedRemoteFileReader::max_wait_time", &max_wait_time); |
977 | 712k | if (block_state != FileBlock::State::DOWNLOADED) { |
978 | 3 | SCOPED_CONCURRENCY_COUNT( |
979 | 3 | ConcurrencyStatsManager::instance().cached_remote_reader_blocking); |
980 | 4 | do { |
981 | 4 | SCOPED_RAW_TIMER(&stats.remote_wait_timer); |
982 | 4 | TEST_SYNC_POINT_CALLBACK("CachedRemoteFileReader::DOWNLOADING"); |
983 | 4 | block_state = block->wait(); |
984 | 4 | if (block_state != FileBlock::State::DOWNLOADING) { |
985 | 2 | break; |
986 | 2 | } |
987 | 4 | } while (++wait_time < max_wait_time); |
988 | 3 | } |
989 | 712k | if (wait_time == max_wait_time) [[unlikely]] { |
990 | 1 | LOG_WARNING("Waiting too long for the download to complete"); |
991 | 1 | } |
992 | | |
993 | 712k | Status st; |
994 | | /* |
995 | | * If block_state == EMPTY, the thread reads the data from remote. |
996 | | * If block_state == DOWNLOADED, when the cache file is deleted by the other process, |
997 | | * the thread reads the data from remote too. |
998 | | */ |
999 | 712k | if (block_state == FileBlock::State::DOWNLOADED) { |
1000 | 712k | if (is_dryrun) [[unlikely]] { |
1001 | 1 | g_skip_local_cache_io_sum_bytes << read_size; |
1002 | 712k | } else { |
1003 | 712k | size_t file_offset = current_offset - left; |
1004 | 712k | SCOPED_RAW_TIMER(&stats.local_read_timer); |
1005 | 712k | SCOPED_CONCURRENCY_COUNT( |
1006 | 712k | ConcurrencyStatsManager::instance().cached_remote_reader_local_read); |
1007 | 712k | st = block->read(Slice(result.data + (current_offset - offset), read_size), |
1008 | 712k | file_offset); |
1009 | 712k | indirect_read_bytes += read_size; |
1010 | 712k | if (st.ok()) { |
1011 | 712k | source_read_breakdown.local_bytes += read_size; |
1012 | 712k | } |
1013 | 712k | } |
1014 | 712k | if (block_state == FileBlock::State::DOWNLOADED && st.is<ErrorCode::NOT_FOUND>()) { |
1015 | 1 | need_self_heal = true; |
1016 | 1 | g_read_cache_self_heal_on_not_found << 1; |
1017 | 1 | LOG_EVERY_N(WARNING, 100) |
1018 | 1 | << "Cache block file is missing, will self-heal by clearing cache hash. " |
1019 | 1 | << "path=" << path().native() << ", hash=" << _cache_hash.to_string() |
1020 | 1 | << ", offset=" << left << ", err=" << st.msg(); |
1021 | 1 | } |
1022 | 712k | } |
1023 | 712k | if (!st || block_state != FileBlock::State::DOWNLOADED) { |
1024 | 4 | if (is_dryrun) [[unlikely]] { |
1025 | 0 | *bytes_read += read_size; |
1026 | 0 | current_offset = right + 1; |
1027 | 0 | continue; |
1028 | 0 | } |
1029 | 4 | LOG(WARNING) << "Read data failed from file cache downloaded by others. err=" |
1030 | 4 | << st.msg() << ", block state=" << block_state; |
1031 | 4 | size_t remote_bytes_read {0}; |
1032 | 4 | stats.hit_cache = false; |
1033 | 4 | stats.from_peer_cache = false; |
1034 | 4 | s3_read_counter << 1; |
1035 | 4 | SCOPED_RAW_TIMER(&stats.remote_read_timer); |
1036 | 4 | RETURN_IF_ERROR(_remote_file_reader->read_at( |
1037 | 4 | current_offset, Slice(result.data + (current_offset - offset), read_size), |
1038 | 4 | &remote_bytes_read, io_ctx)); |
1039 | 3 | indirect_read_bytes += read_size; |
1040 | 3 | source_read_breakdown.remote_bytes += remote_bytes_read; |
1041 | 3 | DCHECK(remote_bytes_read == read_size); |
1042 | 3 | } |
1043 | | |
1044 | 712k | *bytes_read += read_size; |
1045 | 712k | current_offset = right + 1; |
1046 | 712k | } |
1047 | 717k | if (need_self_heal && _cache != nullptr) { |
1048 | 1 | _cache->remove_if_cached_async(_cache_hash); |
1049 | 1 | } |
1050 | 717k | return Status::OK(); |
1051 | 717k | } |
1052 | | |
1053 | | Status CachedRemoteFileReader::_read_from_indirect_cache(size_t offset, Slice result, |
1054 | | size_t bytes_req, size_t already_read, |
1055 | | bool is_dryrun, size_t* bytes_read, |
1056 | | ReadStatistics& stats, |
1057 | | SourceReadBreakdown& source_read_breakdown, |
1058 | 717k | const IOContext* io_ctx) { |
1059 | 717k | g_read_cache_indirect_num << 1; |
1060 | 717k | size_t indirect_read_bytes = 0; |
1061 | 717k | auto [align_left, align_size] = |
1062 | 717k | s_align_size(offset + already_read, bytes_req - already_read, size()); |
1063 | 717k | CacheContext cache_context(io_ctx); |
1064 | 717k | cache_context.stats = &stats; |
1065 | 717k | MonotonicStopWatch sw; |
1066 | 717k | sw.start(); |
1067 | 717k | ConcurrencyStatsManager::instance().cached_remote_reader_get_or_set->increment(); |
1068 | 717k | FileBlocksHolder holder = |
1069 | 717k | _cache->get_or_set(_cache_hash, align_left, align_size, cache_context); |
1070 | 717k | ConcurrencyStatsManager::instance().cached_remote_reader_get_or_set->decrement(); |
1071 | 717k | stats.cache_get_or_set_timer += sw.elapsed_time(); |
1072 | | |
1073 | 717k | auto empty_blocks = _collect_remote_read_blocks(holder, stats); |
1074 | 717k | size_t empty_start = 0; |
1075 | 717k | size_t empty_end = 0; |
1076 | 717k | PeerFetchedBlockSet peer_fetched_blocks; |
1077 | 717k | RETURN_IF_ERROR(_read_remote_blocks_into_cache(empty_blocks, offset, bytes_req, already_read, |
1078 | 717k | result, is_dryrun, stats, source_read_breakdown, |
1079 | 717k | io_ctx, indirect_read_bytes, empty_start, |
1080 | 717k | empty_end, peer_fetched_blocks)); |
1081 | 717k | *bytes_read = already_read; |
1082 | 717k | RETURN_IF_ERROR(_read_remaining_blocks_from_cache(holder, offset, bytes_req, result, is_dryrun, |
1083 | 717k | empty_start, empty_end, peer_fetched_blocks, |
1084 | 717k | stats, source_read_breakdown, |
1085 | 717k | indirect_read_bytes, bytes_read, io_ctx)); |
1086 | 717k | g_read_cache_indirect_bytes << indirect_read_bytes; |
1087 | 717k | g_read_cache_indirect_total_bytes << *bytes_read; |
1088 | 717k | DCHECK(*bytes_read == bytes_req); |
1089 | 717k | return Status::OK(); |
1090 | 717k | } |
1091 | | |
1092 | | Status CachedRemoteFileReader::_read_remote_only_on_cache_miss( |
1093 | | size_t offset, Slice result, size_t bytes_req, bool is_dryrun, size_t* bytes_read, |
1094 | | ReadStatistics& stats, SourceReadBreakdown& source_read_breakdown, |
1095 | 5 | const IOContext* io_ctx) { |
1096 | 5 | auto read_remote = [&]() -> Status { |
1097 | 3 | stats.hit_cache = false; |
1098 | 3 | stats.from_peer_cache = false; |
1099 | 3 | stats.skip_cache = true; |
1100 | 3 | s3_read_counter << 1; |
1101 | 3 | if (is_dryrun) [[unlikely]] { |
1102 | 0 | *bytes_read = bytes_req; |
1103 | 0 | g_read_cache_indirect_bytes << 0; |
1104 | 0 | g_read_cache_indirect_total_bytes << bytes_req; |
1105 | 0 | return Status::OK(); |
1106 | 0 | } |
1107 | | |
1108 | 3 | size_t remote_bytes_read = bytes_req; |
1109 | 3 | SCOPED_RAW_TIMER(&stats.remote_read_timer); |
1110 | 3 | RETURN_IF_ERROR(_remote_file_reader->read_at(offset, Slice(result.data, bytes_req), |
1111 | 3 | &remote_bytes_read, io_ctx)); |
1112 | 3 | *bytes_read = remote_bytes_read; |
1113 | 3 | DCHECK_EQ(*bytes_read, bytes_req); |
1114 | 3 | source_read_breakdown.remote_bytes += remote_bytes_read; |
1115 | 3 | g_read_cache_indirect_bytes << remote_bytes_read; |
1116 | 3 | g_read_cache_indirect_total_bytes << remote_bytes_read; |
1117 | 3 | return Status::OK(); |
1118 | 3 | }; |
1119 | | |
1120 | 5 | g_read_cache_indirect_num << 1; |
1121 | 5 | CacheContext cache_context(io_ctx); |
1122 | 5 | cache_context.stats = &stats; |
1123 | 5 | cache_context.tablet_id = _tablet_id; |
1124 | 5 | FileBlocks file_blocks; |
1125 | 5 | bool fully_covered = false; |
1126 | 5 | { |
1127 | 5 | SCOPED_RAW_TIMER(&stats.get_timer); |
1128 | 5 | RETURN_IF_ERROR(_cache->get_downloaded_blocks_if_fully_covered( |
1129 | 5 | _cache_hash, offset, bytes_req, cache_context, &file_blocks, &fully_covered)); |
1130 | 5 | } |
1131 | 5 | if (!fully_covered) { |
1132 | 3 | return read_remote(); |
1133 | 3 | } |
1134 | | |
1135 | 2 | size_t local_read_bytes = 0; |
1136 | 2 | size_t current_offset = offset; |
1137 | 2 | size_t end_offset = offset + bytes_req - 1; |
1138 | 2 | for (auto& block : file_blocks) { |
1139 | 2 | if (current_offset > end_offset) { |
1140 | 0 | break; |
1141 | 0 | } |
1142 | 2 | const auto& block_range = block->range(); |
1143 | 2 | if (block_range.right < current_offset) { |
1144 | 0 | continue; |
1145 | 0 | } |
1146 | | |
1147 | 2 | size_t read_left = std::max(current_offset, block_range.left); |
1148 | 2 | size_t read_right = std::min(end_offset, block_range.right); |
1149 | 2 | size_t read_size = read_right - read_left + 1; |
1150 | 2 | if (is_dryrun) [[unlikely]] { |
1151 | 0 | g_skip_local_cache_io_sum_bytes << read_size; |
1152 | 2 | } else { |
1153 | 2 | SCOPED_RAW_TIMER(&stats.local_read_timer); |
1154 | 2 | Status st = block->read(Slice(result.data + (read_left - offset), read_size), |
1155 | 2 | read_left - block_range.left); |
1156 | 2 | if (!st.ok()) { |
1157 | 0 | if (st.is<ErrorCode::NOT_FOUND>()) { |
1158 | 0 | _cache->remove_if_cached_async(_cache_hash); |
1159 | 0 | } |
1160 | 0 | LOG_EVERY_N(WARNING, 100) |
1161 | 0 | << "Read data failed from file cache in remote-only-on-miss path. " |
1162 | 0 | << "Fallback to remote. err=" << st.msg() |
1163 | 0 | << ", block state=" << block->state(); |
1164 | 0 | return read_remote(); |
1165 | 0 | } |
1166 | 2 | source_read_breakdown.local_bytes += read_size; |
1167 | 2 | local_read_bytes += read_size; |
1168 | 2 | } |
1169 | 2 | current_offset = read_right + 1; |
1170 | 2 | } |
1171 | | |
1172 | 2 | *bytes_read = bytes_req; |
1173 | 2 | stats.hit_cache = true; |
1174 | 2 | g_read_cache_indirect_bytes << local_read_bytes; |
1175 | 2 | g_read_cache_indirect_total_bytes << bytes_req; |
1176 | 2 | return Status::OK(); |
1177 | 2 | } |
1178 | | |
1179 | | Status CachedRemoteFileReader::read_at_impl(size_t offset, Slice result, size_t* bytes_read, |
1180 | 718k | const IOContext* io_ctx) { |
1181 | 718k | SCOPED_CONCURRENCY_COUNT(ConcurrencyStatsManager::instance().cached_remote_reader_read_at); |
1182 | 718k | IOContext default_io_ctx; |
1183 | 718k | if (io_ctx == nullptr) { |
1184 | 1 | io_ctx = &default_io_ctx; |
1185 | 1 | } |
1186 | 718k | DCHECK(io_ctx); |
1187 | 718k | DCHECK(!closed()); |
1188 | | |
1189 | 718k | const bool is_dryrun = io_ctx->is_dryrun; |
1190 | 718k | if (offset > size()) { |
1191 | 1 | return Status::InvalidArgument( |
1192 | 1 | fmt::format("offset exceeds file size(offset: {}, file size: {}, path: {})", offset, |
1193 | 1 | size(), path().native())); |
1194 | 1 | } |
1195 | | |
1196 | 718k | size_t bytes_req = std::min(result.size, size() - offset); |
1197 | 718k | if (UNLIKELY(bytes_req == 0)) { |
1198 | 1 | *bytes_read = 0; |
1199 | 1 | return Status::OK(); |
1200 | 1 | } |
1201 | | |
1202 | 718k | ReadStatistics stats; |
1203 | 718k | SourceReadBreakdown source_read_breakdown; |
1204 | 718k | Status read_st = Status::OK(); |
1205 | 718k | MonotonicStopWatch read_at_sw; |
1206 | 718k | read_at_sw.start(); |
1207 | 718k | stats.bytes_read += bytes_req; |
1208 | 718k | Defer defer {[&]() { |
1209 | 718k | if (config::print_stack_when_cache_miss) { |
1210 | 0 | if (io_ctx->file_cache_stats == nullptr && !stats.hit_cache && !io_ctx->is_warmup) { |
1211 | 0 | LOG_INFO("[verbose] {}", Status::InternalError<true>("not hit cache")); |
1212 | 0 | } |
1213 | 0 | } |
1214 | 718k | if (!stats.hit_cache && config::read_cluster_cache_opt_verbose_log) { |
1215 | 0 | LOG_INFO( |
1216 | 0 | "[verbose] not hit cache, path: {}, offset: {}, size: {}, cost: {} ms, warmup: " |
1217 | 0 | "{}", |
1218 | 0 | path().native(), offset, bytes_req, read_at_sw.elapsed_time_milliseconds(), |
1219 | 0 | io_ctx->is_warmup); |
1220 | 0 | } |
1221 | 718k | if (read_st.ok() && !is_dryrun) { |
1222 | | // Only successful reads contribute to query profile and file-cache metrics. |
1223 | 718k | const auto file_cache_read_type = |
1224 | 718k | io_ctx->is_inverted_index |
1225 | 718k | ? FileCacheReadType::INVERTED_INDEX |
1226 | 718k | : (io_ctx->is_index_data ? FileCacheReadType::SEGMENT_FOOTER_INDEX |
1227 | 718k | : FileCacheReadType::DATA); |
1228 | 718k | if (io_ctx->file_cache_stats) { |
1229 | 709k | _update_stats(stats, source_read_breakdown, io_ctx->file_cache_stats, |
1230 | 709k | file_cache_read_type); |
1231 | 709k | auto* limiter = io_ctx->remote_scan_cache_write_limiter; |
1232 | 709k | if (limiter != nullptr) { |
1233 | 5 | io_ctx->file_cache_stats->remote_only_on_miss_triggered = |
1234 | 5 | io_ctx->file_cache_stats->remote_only_on_miss_triggered || |
1235 | 5 | limiter->remote_only_on_miss(); |
1236 | 5 | io_ctx->file_cache_stats->remote_only_on_miss_threshold_bytes = |
1237 | 5 | limiter->threshold_bytes(); |
1238 | 5 | } |
1239 | 709k | } |
1240 | 718k | if (!io_ctx->is_warmup) { |
1241 | 718k | FileCacheStatistics fcache_stats_increment; |
1242 | 718k | _update_stats(stats, source_read_breakdown, &fcache_stats_increment, |
1243 | 718k | file_cache_read_type); |
1244 | 718k | io::FileCacheMetrics::instance().update(&fcache_stats_increment); |
1245 | 718k | } |
1246 | 718k | } |
1247 | 718k | }}; |
1248 | | |
1249 | 718k | if (use_remote_only_on_cache_miss(io_ctx)) { |
1250 | 5 | read_st = _read_remote_only_on_cache_miss(offset, result, bytes_req, is_dryrun, bytes_read, |
1251 | 5 | stats, source_read_breakdown, io_ctx); |
1252 | 5 | return read_st; |
1253 | 5 | } |
1254 | | |
1255 | 718k | size_t already_read = 0; |
1256 | 718k | if (_try_read_from_cached_files_directly(offset, result, bytes_req, is_dryrun, stats, |
1257 | 718k | source_read_breakdown, already_read, bytes_read)) { |
1258 | 1.00k | return Status::OK(); |
1259 | 1.00k | } |
1260 | | |
1261 | 717k | read_st = _read_from_indirect_cache(offset, result, bytes_req, already_read, is_dryrun, |
1262 | 717k | bytes_read, stats, source_read_breakdown, io_ctx); |
1263 | 717k | return read_st; |
1264 | 718k | } |
1265 | | |
1266 | 0 | void CachedRemoteFileReader::prefetch_range(size_t offset, size_t size, const IOContext* io_ctx) { |
1267 | 0 | if (offset >= this->size() || size == 0) { |
1268 | 0 | return; |
1269 | 0 | } |
1270 | | |
1271 | 0 | size = std::min(size, this->size() - offset); |
1272 | |
|
1273 | 0 | ThreadPool* pool = ExecEnv::GetInstance()->segment_prefetch_thread_pool(); |
1274 | 0 | if (pool == nullptr) { |
1275 | 0 | return; |
1276 | 0 | } |
1277 | | |
1278 | 0 | IOContext dryrun_ctx; |
1279 | 0 | if (io_ctx != nullptr) { |
1280 | 0 | dryrun_ctx = *io_ctx; |
1281 | 0 | } |
1282 | 0 | dryrun_ctx.is_dryrun = true; |
1283 | 0 | dryrun_ctx.query_id = nullptr; |
1284 | 0 | dryrun_ctx.file_cache_stats = nullptr; |
1285 | 0 | dryrun_ctx.file_reader_stats = nullptr; |
1286 | 0 | dryrun_ctx.remote_scan_cache_write_limiter = nullptr; |
1287 | |
|
1288 | 0 | LOG_IF(INFO, config::enable_segment_prefetch_verbose_log) |
1289 | 0 | << fmt::format("[verbose] Submitting prefetch task for offset={} size={}, file={}", |
1290 | 0 | offset, size, path().filename().native()); |
1291 | 0 | std::weak_ptr<CachedRemoteFileReader> weak_this = shared_from_this(); |
1292 | 0 | auto st = pool->submit_func([weak_this, offset, size, dryrun_ctx]() { |
1293 | 0 | auto self = weak_this.lock(); |
1294 | 0 | if (self == nullptr) { |
1295 | 0 | return; |
1296 | 0 | } |
1297 | 0 | size_t bytes_read = 0; |
1298 | 0 | Slice dummy_buffer((char*)nullptr, size); |
1299 | 0 | (void)self->read_at_impl(offset, dummy_buffer, &bytes_read, &dryrun_ctx); |
1300 | 0 | LOG_IF(INFO, config::enable_segment_prefetch_verbose_log) |
1301 | 0 | << fmt::format("[verbose] Prefetch task completed for offset={} size={}, file={}", |
1302 | 0 | offset, size, self->path().filename().native()); |
1303 | 0 | }); |
1304 | |
|
1305 | 0 | if (!st.ok()) { |
1306 | 0 | VLOG_DEBUG << "Failed to submit prefetch task for offset=" << offset << " size=" << size |
1307 | 0 | << " error=" << st.to_string(); |
1308 | 0 | } |
1309 | 0 | } |
1310 | | |
1311 | | void CachedRemoteFileReader::_update_stats(const ReadStatistics& read_stats, |
1312 | | const SourceReadBreakdown& source_read_breakdown, |
1313 | | FileCacheStatistics* statis, |
1314 | 1.42M | FileCacheReadType read_type) const { |
1315 | 1.42M | if (statis == nullptr) { |
1316 | 0 | return; |
1317 | 0 | } |
1318 | 1.42M | const bool has_source_bytes = source_read_breakdown.local_bytes != 0 || |
1319 | 1.42M | source_read_breakdown.remote_bytes != 0 || |
1320 | 1.42M | source_read_breakdown.peer_bytes != 0; |
1321 | 1.42M | if (has_source_bytes) { |
1322 | 1.42M | if (source_read_breakdown.local_bytes != 0) { |
1323 | 1.41M | statis->num_local_io_total++; |
1324 | 1.41M | statis->bytes_read_from_local += source_read_breakdown.local_bytes; |
1325 | 1.41M | } |
1326 | 1.42M | if (source_read_breakdown.peer_bytes != 0 || read_stats.from_peer_cache) { |
1327 | | // Count peer IO whenever peer was used, even if its fetched blocks were entirely |
1328 | | // outside the copy range (e.g., backward-aligned prefetch block before |
1329 | | // offset+already_read). In that case peer_bytes==0 but the peer RPC did happen |
1330 | | // and wrote data into the local file cache. |
1331 | 28 | statis->num_peer_io_total++; |
1332 | 28 | statis->bytes_read_from_peer += source_read_breakdown.peer_bytes; |
1333 | 28 | statis->peer_io_timer += read_stats.peer_read_timer; |
1334 | 28 | } |
1335 | 1.42M | if (source_read_breakdown.remote_bytes != 0) { |
1336 | 10.3k | statis->num_remote_io_total++; |
1337 | 10.3k | statis->bytes_read_from_remote += source_read_breakdown.remote_bytes; |
1338 | 10.3k | statis->remote_io_timer += read_stats.remote_read_timer; |
1339 | 10.3k | } |
1340 | 18.4E | } else if (read_stats.hit_cache) { |
1341 | 0 | statis->num_local_io_total++; |
1342 | 0 | statis->bytes_read_from_local += read_stats.bytes_read; |
1343 | 18.4E | } else if (read_stats.from_peer_cache) { |
1344 | 0 | statis->num_peer_io_total++; |
1345 | 0 | statis->bytes_read_from_peer += read_stats.bytes_read; |
1346 | 0 | statis->peer_io_timer += read_stats.peer_read_timer; |
1347 | 18.4E | } else { |
1348 | 18.4E | statis->num_remote_io_total++; |
1349 | 18.4E | statis->bytes_read_from_remote += read_stats.bytes_read; |
1350 | 18.4E | statis->remote_io_timer += read_stats.remote_read_timer; |
1351 | 18.4E | } |
1352 | 1.42M | statis->remote_wait_timer += read_stats.remote_wait_timer; |
1353 | 1.42M | statis->local_io_timer += read_stats.local_read_timer; |
1354 | 1.42M | statis->num_skip_cache_io_total += read_stats.skip_cache; |
1355 | 1.42M | statis->bytes_write_into_cache += read_stats.bytes_write_into_file_cache; |
1356 | 1.42M | statis->write_cache_io_timer += read_stats.local_write_timer; |
1357 | | |
1358 | 1.42M | statis->read_cache_file_directly_timer += read_stats.read_cache_file_directly_timer; |
1359 | 1.42M | statis->cache_get_or_set_timer += read_stats.cache_get_or_set_timer; |
1360 | 1.42M | statis->lock_wait_timer += read_stats.lock_wait_timer; |
1361 | 1.42M | statis->get_timer += read_stats.get_timer; |
1362 | 1.42M | statis->set_timer += read_stats.set_timer; |
1363 | | |
1364 | 1.42M | auto update_index_stats = [&](int64_t& num_local_io_total, int64_t& num_remote_io_total, |
1365 | 1.42M | int64_t& num_peer_io_total, int64_t& bytes_read_from_local, |
1366 | 1.42M | int64_t& bytes_read_from_remote, int64_t& bytes_read_from_peer, |
1367 | 1.42M | int64_t& local_io_timer, int64_t& remote_io_timer, |
1368 | 1.42M | int64_t& peer_io_timer, int64_t& write_cache_io_timer, |
1369 | 1.42M | int64_t& bytes_write_into_cache) { |
1370 | 6 | if (has_source_bytes) { |
1371 | 6 | if (source_read_breakdown.local_bytes != 0) { |
1372 | 0 | num_local_io_total++; |
1373 | 0 | bytes_read_from_local += source_read_breakdown.local_bytes; |
1374 | 0 | } |
1375 | 6 | if (source_read_breakdown.peer_bytes != 0 || read_stats.from_peer_cache) { |
1376 | 0 | num_peer_io_total++; |
1377 | 0 | bytes_read_from_peer += source_read_breakdown.peer_bytes; |
1378 | 0 | peer_io_timer += read_stats.peer_read_timer; |
1379 | 0 | } |
1380 | 6 | if (source_read_breakdown.remote_bytes != 0) { |
1381 | 6 | num_remote_io_total++; |
1382 | 6 | bytes_read_from_remote += source_read_breakdown.remote_bytes; |
1383 | 6 | remote_io_timer += read_stats.remote_read_timer; |
1384 | 6 | } |
1385 | 6 | } else if (read_stats.hit_cache) { |
1386 | 0 | num_local_io_total++; |
1387 | 0 | bytes_read_from_local += read_stats.bytes_read; |
1388 | 0 | } else if (read_stats.from_peer_cache) { |
1389 | 0 | num_peer_io_total++; |
1390 | 0 | bytes_read_from_peer += read_stats.bytes_read; |
1391 | 0 | peer_io_timer += read_stats.peer_read_timer; |
1392 | 0 | } else { |
1393 | 0 | num_remote_io_total++; |
1394 | 0 | bytes_read_from_remote += read_stats.bytes_read; |
1395 | 0 | remote_io_timer += read_stats.remote_read_timer; |
1396 | 0 | } |
1397 | 6 | local_io_timer += read_stats.local_read_timer; |
1398 | 6 | write_cache_io_timer += read_stats.local_write_timer; |
1399 | 6 | bytes_write_into_cache += read_stats.bytes_write_into_file_cache; |
1400 | 6 | }; |
1401 | | |
1402 | 1.42M | switch (read_type) { |
1403 | 1.42M | case FileCacheReadType::DATA: |
1404 | 1.42M | break; |
1405 | 2 | case FileCacheReadType::INVERTED_INDEX: |
1406 | 2 | update_index_stats( |
1407 | 2 | statis->inverted_index_num_local_io_total, |
1408 | 2 | statis->inverted_index_num_remote_io_total, |
1409 | 2 | statis->inverted_index_num_peer_io_total, |
1410 | 2 | statis->inverted_index_bytes_read_from_local, |
1411 | 2 | statis->inverted_index_bytes_read_from_remote, |
1412 | 2 | statis->inverted_index_bytes_read_from_peer, statis->inverted_index_local_io_timer, |
1413 | 2 | statis->inverted_index_remote_io_timer, statis->inverted_index_peer_io_timer, |
1414 | 2 | statis->inverted_index_write_cache_io_timer, |
1415 | 2 | statis->inverted_index_bytes_write_into_cache); |
1416 | 2 | break; |
1417 | 4 | case FileCacheReadType::SEGMENT_FOOTER_INDEX: |
1418 | 4 | update_index_stats(statis->segment_footer_index_num_local_io_total, |
1419 | 4 | statis->segment_footer_index_num_remote_io_total, |
1420 | 4 | statis->segment_footer_index_num_peer_io_total, |
1421 | 4 | statis->segment_footer_index_bytes_read_from_local, |
1422 | 4 | statis->segment_footer_index_bytes_read_from_remote, |
1423 | 4 | statis->segment_footer_index_bytes_read_from_peer, |
1424 | 4 | statis->segment_footer_index_local_io_timer, |
1425 | 4 | statis->segment_footer_index_remote_io_timer, |
1426 | 4 | statis->segment_footer_index_peer_io_timer, |
1427 | 4 | statis->segment_footer_index_write_cache_io_timer, |
1428 | 4 | statis->segment_footer_index_bytes_write_into_cache); |
1429 | 4 | break; |
1430 | 1.42M | } |
1431 | | |
1432 | 1.42M | g_skip_cache_sum << read_stats.skip_cache; |
1433 | 1.42M | } |
1434 | | |
1435 | | } // namespace doris::io |