be/src/io/cache/file_cache_common.h
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 | | // This file is copied from |
18 | | // https://github.com/ClickHouse/ClickHouse/blob/master/src/Interpreters/Cache/FileCache_fwd.h |
19 | | // and modified by Doris |
20 | | |
21 | | #pragma once |
22 | | #include <cstdint> |
23 | | #include <list> |
24 | | #include <optional> |
25 | | #include <vector> |
26 | | |
27 | | #include "common/config.h" |
28 | | #include "core/uint128.h" |
29 | | #include "io/io_common.h" |
30 | | |
31 | | namespace doris::io { |
32 | | |
33 | | inline static constexpr size_t REMOTE_FS_OBJECTS_CACHE_DEFAULT_ELEMENTS = 100 * 1024; |
34 | | inline static constexpr size_t FILE_CACHE_MAX_FILE_BLOCK_SIZE = 1 * 1024 * 1024; |
35 | | inline static constexpr size_t DEFAULT_NORMAL_PERCENT = 40; |
36 | | inline static constexpr size_t DEFAULT_DISPOSABLE_PERCENT = 5; |
37 | | inline static constexpr size_t DEFAULT_INDEX_PERCENT = 5; |
38 | | inline static constexpr size_t DEFAULT_TTL_PERCENT = 50; |
39 | | |
40 | | using uint128_t = UInt128; |
41 | | |
42 | | enum FileCacheType { |
43 | | INDEX = 2, |
44 | | NORMAL = 1, |
45 | | DISPOSABLE = 0, |
46 | | TTL = 3, |
47 | | }; |
48 | | |
49 | 43.6M | inline size_t file_cache_type_index(FileCacheType type) { |
50 | 43.6M | return static_cast<size_t>(type); |
51 | 43.6M | } |
52 | | |
53 | | // A block has a class (INDEX, NORMAL or DISPOSABLE) that is fixed when it is written, and it sits |
54 | | // in the TTL queue while its tablet's TTL protects it. TTL is a queue, never a class: a block |
55 | | // recorded as TTL by an older version lost its class there, and NORMAL is what it was put back to. |
56 | 1.10M | inline FileCacheType cache_class_of(FileCacheType type) { |
57 | 1.10M | return type == FileCacheType::TTL ? FileCacheType::NORMAL : type; |
58 | 1.10M | } |
59 | | |
60 | | std::string cache_type_to_surfix(FileCacheType type); |
61 | | FileCacheType surfix_to_cache_type(const std::string& str); |
62 | | |
63 | | FileCacheType string_to_cache_type(const std::string& str); |
64 | | std::string cache_type_to_string(FileCacheType type); |
65 | | |
66 | | struct UInt128Wrapper { |
67 | | uint128_t value_; |
68 | | [[nodiscard]] std::string to_string() const; |
69 | | |
70 | | UInt128Wrapper() = default; |
71 | 484k | explicit UInt128Wrapper(const uint128_t& value) : value_(value) {} |
72 | | |
73 | 903M | bool operator==(const UInt128Wrapper& other) const { return value_ == other.value_; } |
74 | | |
75 | 504k | uint64_t high() const { return static_cast<uint64_t>(value_ >> 64); } |
76 | 504k | uint64_t low() const { return static_cast<uint64_t>(value_); } |
77 | | |
78 | | friend std::ostream& operator<<(std::ostream& os, const UInt128Wrapper& wrapper) { |
79 | | os << "UInt128Wrapper(" << wrapper.high() << ", " << wrapper.low() << ")"; |
80 | | return os; |
81 | | } |
82 | | }; |
83 | | |
84 | | struct ReadStatistics { |
85 | | bool hit_cache = true; |
86 | | bool from_peer_cache = false; |
87 | | bool skip_cache = false; |
88 | | int64_t bytes_read = 0; |
89 | | int64_t bytes_read_from_local = 0; |
90 | | int64_t bytes_read_from_remote = 0; |
91 | | int64_t bytes_read_from_peer = 0; |
92 | | int64_t remote_physical_read_bytes = 0; |
93 | | int64_t bytes_write_into_file_cache = 0; |
94 | | int64_t remote_read_timer = 0; |
95 | | int64_t peer_read_timer = 0; |
96 | | int64_t remote_wait_timer = 0; // wait for other downloader |
97 | | int64_t local_read_timer = 0; |
98 | | int64_t local_write_timer = 0; |
99 | | int64_t read_cache_file_directly_timer = 0; |
100 | | int64_t cache_get_or_set_timer = 0; |
101 | | int64_t lock_wait_timer = 0; |
102 | | int64_t get_timer = 0; |
103 | | int64_t set_timer = 0; |
104 | | int64_t async_cache_write_submitted = 0; |
105 | | int64_t async_cache_write_rejected = 0; |
106 | | int64_t async_cache_write_buffer_alloc_fail = 0; |
107 | | int64_t async_cache_write_drop_stale_epoch = 0; |
108 | | int64_t inflight_write_buffer_index_hit = 0; |
109 | | int64_t inflight_write_buffer_index_miss = 0; |
110 | | int64_t probe_downloaded_hit = 0; |
111 | | int64_t probe_downloading_hit = 0; |
112 | | int64_t probe_miss = 0; |
113 | | int64_t block_wait_success = 0; |
114 | | int64_t block_wait_timeout = 0; |
115 | | }; |
116 | | |
117 | | class BlockFileCache; |
118 | | struct FileBlocksHolder; |
119 | | using FileBlocksHolderPtr = std::unique_ptr<FileBlocksHolder>; |
120 | | |
121 | | struct FileCacheAllocatorBuilder { |
122 | | bool _is_cold_data; |
123 | | uint64_t _expiration_time; |
124 | | UInt128Wrapper _cache_hash; |
125 | | BlockFileCache* _cache; // Only one ref, the lifetime is owned by FileCache |
126 | | FileBlocksHolderPtr allocate_cache_holder(size_t offset, size_t size, int64_t tablet_id) const; |
127 | | }; |
128 | | |
129 | | struct KeyHash { |
130 | 905M | std::size_t operator()(const UInt128Wrapper& w) const { |
131 | 905M | return util_hash::HashLen16(w.value_.low(), w.value_.high()); |
132 | 905M | } |
133 | | }; |
134 | | |
135 | | using AccessKeyAndOffset = std::pair<UInt128Wrapper, size_t>; |
136 | | struct KeyAndOffsetHash { |
137 | 2.94M | std::size_t operator()(const AccessKeyAndOffset& key) const { |
138 | 2.94M | return KeyHash()(key.first) ^ std::hash<uint64_t>()(key.second); |
139 | 2.94M | } |
140 | | }; |
141 | | |
142 | | struct KeyMeta { |
143 | | // The TTL deadline the block was written with (absolute time). Whether the block is protected |
144 | | // is decided by its tablet's current policy (TabletCachePolicyRegistry); this is only the |
145 | | // fallback for a tablet whose policy is not known, and what an older version reads back. |
146 | | uint64_t expiration_time; |
147 | | // The queue the block is in right now: its class, or TTL while it is protected. |
148 | | FileCacheType type; |
149 | | int64_t tablet_id {0}; |
150 | | // The class of the block, see cache_class_of(). Persisted; never TTL. |
151 | | FileCacheType cache_class {FileCacheType::NORMAL}; |
152 | | }; |
153 | | |
154 | | struct FileCacheKey { |
155 | | UInt128Wrapper hash; |
156 | | size_t offset; |
157 | | KeyMeta meta; |
158 | | }; |
159 | | |
160 | | struct FileCacheSettings { |
161 | | size_t capacity {0}; |
162 | | size_t disposable_queue_size {0}; |
163 | | size_t disposable_queue_elements {0}; |
164 | | size_t index_queue_size {0}; |
165 | | size_t index_queue_elements {0}; |
166 | | size_t query_queue_size {0}; |
167 | | size_t query_queue_elements {0}; |
168 | | size_t ttl_queue_size {0}; |
169 | | size_t ttl_queue_elements {0}; |
170 | | size_t max_file_block_size {0}; |
171 | | size_t max_query_cache_size {0}; |
172 | | std::string storage; |
173 | | |
174 | | // to string |
175 | | std::string to_string() const; |
176 | | }; |
177 | | |
178 | | FileCacheSettings get_file_cache_settings(size_t capacity, size_t max_query_cache_size, |
179 | | size_t normal_percent = DEFAULT_NORMAL_PERCENT, |
180 | | size_t disposable_percent = DEFAULT_DISPOSABLE_PERCENT, |
181 | | size_t index_percent = DEFAULT_INDEX_PERCENT, |
182 | | size_t ttl_percent = DEFAULT_TTL_PERCENT, |
183 | | const std::string& storage = "disk"); |
184 | | |
185 | | struct CacheContext { |
186 | | // Every reader-side context has to say which tablet the blocks belong to (0 for data outside |
187 | | // any Doris tablet). Taking it here rather than assigning it afterwards keeps a new call site |
188 | | // from silently caching blocks under tablet 0. |
189 | 995k | CacheContext(const IOContext* io_context, int64_t tablet_id_) : tablet_id(tablet_id_) { |
190 | | // The class only; whether the blocks go to the TTL queue is the cache's decision, see |
191 | | // TabletCachePolicyRegistry. |
192 | 995k | expiration_time = io_context->expiration_time; |
193 | 995k | if (io_context->is_index_data) { |
194 | 156k | cache_type = FileCacheType::INDEX; |
195 | 839k | } else if (io_context->is_disposable) { |
196 | 51.9k | cache_type = FileCacheType::DISPOSABLE; |
197 | 787k | } else { |
198 | 787k | cache_type = FileCacheType::NORMAL; |
199 | 787k | } |
200 | 995k | query_id = io_context->query_id ? *io_context->query_id : TUniqueId(); |
201 | 995k | is_warmup = io_context->is_warmup; |
202 | 995k | remote_scan_cache_write_limiter = io_context->remote_scan_cache_write_limiter; |
203 | 995k | admit_cache_write_by_remote_scan_limiter = |
204 | 995k | remote_scan_cache_write_limiter != nullptr && |
205 | 995k | io_context->reader_type == ReaderType::READER_QUERY && |
206 | 995k | (!io_context->is_index_data || io_context->is_inverted_index || |
207 | 9 | config::enable_file_cache_query_limit_segment_meta) && |
208 | 995k | !io_context->is_warmup; |
209 | 995k | } |
210 | 70.7k | CacheContext() = default; |
211 | | bool operator==(const CacheContext& rhs) const { |
212 | | return query_id == rhs.query_id && cache_type == rhs.cache_type && |
213 | | expiration_time == rhs.expiration_time && is_cold_data == rhs.is_cold_data; |
214 | | } |
215 | | TUniqueId query_id {}; |
216 | | FileCacheType cache_type {FileCacheType::NORMAL}; |
217 | | int64_t expiration_time {0}; |
218 | | bool is_cold_data {false}; |
219 | | ReadStatistics* stats {nullptr}; |
220 | | bool is_warmup {false}; |
221 | | int64_t tablet_id {0}; |
222 | | RemoteScanCacheWriteLimiter* remote_scan_cache_write_limiter = nullptr; |
223 | | bool admit_cache_write_by_remote_scan_limiter {false}; |
224 | | }; |
225 | | |
226 | | template <class Lock> |
227 | | concept IsXLock = std::same_as<Lock, std::lock_guard<std::mutex>> || |
228 | | std::same_as<Lock, std::unique_lock<std::mutex>>; |
229 | | |
230 | | class LRUQueue { |
231 | | public: |
232 | 2.85k | LRUQueue() = default; |
233 | | LRUQueue(size_t max_size, size_t max_element_size, int64_t hot_data_interval) |
234 | 1.39k | : max_size(max_size), |
235 | 1.39k | max_element_size(max_element_size), |
236 | 1.39k | hot_data_interval(hot_data_interval) {} |
237 | | |
238 | | struct HashFileKeyAndOffset { |
239 | 2.47M | std::size_t operator()(const std::pair<UInt128Wrapper, size_t>& pair) const { |
240 | 2.47M | return KeyHash()(pair.first) + pair.second; |
241 | 2.47M | } |
242 | | }; |
243 | | |
244 | | struct FileKeyAndOffset { |
245 | | UInt128Wrapper hash; |
246 | | size_t offset; |
247 | | size_t size; |
248 | | |
249 | | FileKeyAndOffset(const UInt128Wrapper& hash, size_t offset, size_t size) |
250 | 717k | : hash(hash), offset(offset), size(size) {} |
251 | | }; |
252 | | |
253 | | using Iterator = typename std::list<FileKeyAndOffset>::iterator; |
254 | | |
255 | 778k | size_t get_max_size() const { return max_size; } |
256 | 740 | size_t get_max_element_size() const { return max_element_size; } |
257 | | |
258 | | template <class T> |
259 | | requires IsXLock<T> |
260 | 785k | size_t get_capacity(T& /* cache_lock */) const { |
261 | 785k | return cache_size; |
262 | 785k | } |
263 | | |
264 | 737 | size_t get_capacity_unsafe() const { return cache_size; } |
265 | | |
266 | 769 | size_t get_elements_num_unsafe() const { return queue.size(); } |
267 | | |
268 | 887k | size_t get_elements_num(std::lock_guard<std::mutex>& /* cache_lock */) const { |
269 | 887k | return queue.size(); |
270 | 887k | } |
271 | | |
272 | | Iterator add(const UInt128Wrapper& hash, size_t offset, size_t size, |
273 | | std::lock_guard<std::mutex>& cache_lock); |
274 | | // Adds at the cold end, for an entry moved in from another queue that is not known to be |
275 | | // any warmer than what is already there. |
276 | | Iterator add_front(const UInt128Wrapper& hash, size_t offset, size_t size, |
277 | | std::lock_guard<std::mutex>& cache_lock); |
278 | | template <class T> |
279 | | requires IsXLock<T> |
280 | 684k | void remove(Iterator queue_it, T& /* cache_lock */) { |
281 | 684k | skip_sweep_cursor(queue_it); |
282 | 684k | cache_size -= queue_it->size; |
283 | 684k | map.erase(std::make_pair(queue_it->hash, queue_it->offset)); |
284 | 684k | queue.erase(queue_it); |
285 | 684k | } |
286 | | |
287 | | void move_to_end(Iterator queue_it, std::lock_guard<std::mutex>& cache_lock); |
288 | | |
289 | | void resize(Iterator queue_it, size_t new_size, std::lock_guard<std::mutex>& cache_lock); |
290 | | |
291 | | std::string to_string(std::lock_guard<std::mutex>& cache_lock) const; |
292 | | |
293 | | bool contains(const UInt128Wrapper& hash, size_t offset, |
294 | | std::lock_guard<std::mutex>& cache_lock) const; |
295 | | |
296 | 1.68M | Iterator begin() { return queue.begin(); } |
297 | | |
298 | 5.41M | Iterator end() { return queue.end(); } |
299 | | |
300 | | void remove_all(std::lock_guard<std::mutex>& cache_lock); |
301 | | |
302 | | bool pop_front(std::lock_guard<std::mutex>& cache_lock); |
303 | | |
304 | | Iterator get(const UInt128Wrapper& hash, size_t offset, |
305 | | std::lock_guard<std::mutex>& /* cache_lock */) const; |
306 | | |
307 | 682k | int64_t get_hot_data_interval() const { return hot_data_interval; } |
308 | | |
309 | 0 | void clear(std::lock_guard<std::mutex>& cache_lock) { |
310 | 0 | sweep_cursor.reset(); |
311 | 0 | queue.clear(); |
312 | 0 | map.clear(); |
313 | 0 | cache_size = 0; |
314 | 0 | } |
315 | | |
316 | | // Moves the sweep cursor past `queue_it` if it points there, before the entry leaves its |
317 | | // place in the queue. |
318 | 2.01M | void skip_sweep_cursor(Iterator queue_it) { |
319 | 2.01M | if (sweep_cursor == queue_it) { |
320 | 2.15k | ++*sweep_cursor; |
321 | 2.15k | } |
322 | 2.01M | } |
323 | | |
324 | | size_t levenshtein_distance_from(LRUQueue& base, std::lock_guard<std::mutex>& cache_lock); |
325 | | |
326 | | size_t max_size; |
327 | | size_t max_element_size; |
328 | | std::list<FileKeyAndOffset> queue; |
329 | | std::unordered_map<std::pair<UInt128Wrapper, size_t>, Iterator, HashFileKeyAndOffset> map; |
330 | | // Where the background queue sweep resumes, kept across releases of the cache lock. An |
331 | | // entry that leaves its place moves the cursor past itself first, so it never dangles. |
332 | | std::optional<Iterator> sweep_cursor; |
333 | | size_t cache_size = 0; |
334 | | int64_t hot_data_interval {0}; |
335 | | }; |
336 | | struct FileCacheInfo { |
337 | | UInt128Wrapper hash {0}; |
338 | | uint64_t expiration_time {0}; |
339 | | uint64_t size {0}; |
340 | | size_t offset {0}; |
341 | | bool is_tmp {false}; |
342 | | FileCacheType cache_type {NORMAL}; |
343 | | |
344 | | std::string to_string() const; |
345 | | }; |
346 | | |
347 | | class InconsistencyType { |
348 | | uint32_t type; |
349 | | |
350 | | public: |
351 | | enum : uint32_t { |
352 | | // No anomaly |
353 | | NONE = 0, |
354 | | // Missing a block cache metadata in _files |
355 | | NOT_LOADED = 1 << 0, |
356 | | // A block cache is missing in storage |
357 | | MISSING_IN_STORAGE = 1 << 1, |
358 | | // Size of a block cache recorded in _files is inconsistent with the storage |
359 | | SIZE_INCONSISTENT = 1 << 2, |
360 | | // Cache type of a block cache recorded in _files is inconsistent with the storage |
361 | | CACHE_TYPE_INCONSISTENT = 1 << 3, |
362 | | // Expiration time of a block cache recorded in _files is inconsistent with the storage |
363 | | EXPIRATION_TIME_INCONSISTENT = 1 << 4, |
364 | | // File in storage has a _tmp suffix, but the state of block cache in _files is not set to downloading |
365 | | TMP_FILE_EXPECT_DOWNLOADING_STATE = 1 << 5 |
366 | | }; |
367 | 0 | InconsistencyType(uint32_t t = 0) : type(t) {} |
368 | 0 | operator uint32_t&() { return type; } |
369 | | |
370 | | std::string to_string() const; |
371 | | }; |
372 | | |
373 | | struct InconsistencyContext { |
374 | | // The infos in _files of BlockFileCache. |
375 | | std::vector<FileCacheInfo> infos_in_manager; |
376 | | std::vector<FileCacheInfo> infos_in_storage; |
377 | | std::vector<InconsistencyType> types; |
378 | | }; |
379 | | |
380 | | std::optional<int64_t> get_tablet_id(std::string file_path); |
381 | | |
382 | | } // namespace doris::io |