Coverage Report

Created: 2026-08-03 07:19

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
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 <array>
23
#include <cstdint>
24
#include <vector>
25
26
#include "common/config.h"
27
#include "core/uint128.h"
28
#include "io/io_common.h"
29
30
namespace doris::io {
31
32
inline static constexpr size_t REMOTE_FS_OBJECTS_CACHE_DEFAULT_ELEMENTS = 100 * 1024;
33
inline static constexpr size_t FILE_CACHE_MAX_FILE_BLOCK_SIZE = 1 * 1024 * 1024;
34
inline static constexpr size_t DEFAULT_NORMAL_PERCENT = 40;
35
inline static constexpr size_t DEFAULT_DISPOSABLE_PERCENT = 5;
36
inline static constexpr size_t DEFAULT_INDEX_PERCENT = 5;
37
inline static constexpr size_t DEFAULT_TTL_PERCENT = 50;
38
39
using uint128_t = UInt128;
40
41
enum FileCacheType {
42
    INDEX = 2,
43
    NORMAL = 1,
44
    DISPOSABLE = 0,
45
    TTL = 3,
46
    COLD_NORMAL = 4,
47
};
48
49
// Newly admitted ordinary data starts in the cold queue when 2Q LRU is enabled.
50
// Restore paths choose their cache type separately and must not use this helper.
51
841k
inline FileCacheType normal_cache_type_for_admission() {
52
841k
    return config::enable_file_cache_normal_queue_2qlru ? FileCacheType::COLD_NORMAL
53
841k
                                                        : FileCacheType::NORMAL;
54
841k
}
55
56
inline static constexpr size_t FILE_CACHE_TYPE_COUNT =
57
        static_cast<size_t>(FileCacheType::COLD_NORMAL) + 1;
58
inline static constexpr std::array<FileCacheType, FILE_CACHE_TYPE_COUNT> FILE_CACHE_TYPES = {
59
        FileCacheType::DISPOSABLE, FileCacheType::NORMAL, FileCacheType::INDEX, FileCacheType::TTL,
60
        FileCacheType::COLD_NORMAL};
61
62
std::string cache_type_to_surfix(FileCacheType type);
63
FileCacheType surfix_to_cache_type(const std::string& str);
64
65
FileCacheType string_to_cache_type(const std::string& str);
66
std::string cache_type_to_string(FileCacheType type);
67
68
struct UInt128Wrapper {
69
    uint128_t value_;
70
    [[nodiscard]] std::string to_string() const;
71
72
    UInt128Wrapper() = default;
73
598k
    explicit UInt128Wrapper(const uint128_t& value) : value_(value) {}
74
75
197M
    bool operator==(const UInt128Wrapper& other) const { return value_ == other.value_; }
76
77
420k
    uint64_t high() const { return static_cast<uint64_t>(value_ >> 64); }
78
420k
    uint64_t low() const { return static_cast<uint64_t>(value_); }
79
80
    friend std::ostream& operator<<(std::ostream& os, const UInt128Wrapper& wrapper) {
81
        os << "UInt128Wrapper(" << wrapper.high() << ", " << wrapper.low() << ")";
82
        return os;
83
    }
84
};
85
86
struct ReadStatistics {
87
    bool hit_cache = true;
88
    bool from_peer_cache = false;
89
    bool skip_cache = false;
90
    int64_t bytes_read = 0;
91
    int64_t bytes_read_from_local = 0;
92
    int64_t bytes_read_from_remote = 0;
93
    int64_t bytes_read_from_peer = 0;
94
    int64_t bytes_write_into_file_cache = 0;
95
    int64_t remote_read_timer = 0;
96
    int64_t peer_read_timer = 0;
97
    int64_t remote_wait_timer = 0; // wait for other downloader
98
    int64_t local_read_timer = 0;
99
    int64_t local_write_timer = 0;
100
    int64_t read_cache_file_directly_timer = 0;
101
    int64_t cache_get_or_set_timer = 0;
102
    int64_t lock_wait_timer = 0;
103
    int64_t get_timer = 0;
104
    int64_t set_timer = 0;
105
};
106
107
class BlockFileCache;
108
struct FileBlocksHolder;
109
using FileBlocksHolderPtr = std::unique_ptr<FileBlocksHolder>;
110
111
struct FileCacheAllocatorBuilder {
112
    bool _is_cold_data;
113
    uint64_t _expiration_time;
114
    UInt128Wrapper _cache_hash;
115
    BlockFileCache* _cache; // Only one ref, the lifetime is owned by FileCache
116
    FileBlocksHolderPtr allocate_cache_holder(size_t offset, size_t size, int64_t tablet_id) const;
117
};
118
119
struct KeyHash {
120
198M
    std::size_t operator()(const UInt128Wrapper& w) const {
121
198M
        return util_hash::HashLen16(w.value_.low(), w.value_.high());
122
198M
    }
123
};
124
125
using AccessKeyAndOffset = std::pair<UInt128Wrapper, size_t>;
126
struct KeyAndOffsetHash {
127
2.09M
    std::size_t operator()(const AccessKeyAndOffset& key) const {
128
2.09M
        return KeyHash()(key.first) ^ std::hash<uint64_t>()(key.second);
129
2.09M
    }
130
};
131
132
struct KeyMeta {
133
    uint64_t expiration_time; // absolute time
134
    FileCacheType type;
135
    int64_t tablet_id {0};
136
};
137
138
struct FileCacheKey {
139
    UInt128Wrapper hash;
140
    size_t offset;
141
    KeyMeta meta;
142
};
143
144
struct FileCacheSettings {
145
    size_t capacity {0};
146
    size_t disposable_queue_size {0};
147
    size_t disposable_queue_elements {0};
148
    size_t index_queue_size {0};
149
    size_t index_queue_elements {0};
150
    size_t query_queue_size {0};
151
    size_t query_queue_elements {0};
152
    size_t cold_query_queue_size {0};
153
    size_t cold_query_queue_elements {0};
154
    size_t ttl_queue_size {0};
155
    size_t ttl_queue_elements {0};
156
    size_t max_file_block_size {0};
157
    size_t max_query_cache_size {0};
158
    std::string storage;
159
160
    // to string
161
    std::string to_string() const;
162
};
163
164
FileCacheSettings get_file_cache_settings(size_t capacity, size_t max_query_cache_size,
165
                                          size_t normal_percent = DEFAULT_NORMAL_PERCENT,
166
                                          size_t disposable_percent = DEFAULT_DISPOSABLE_PERCENT,
167
                                          size_t index_percent = DEFAULT_INDEX_PERCENT,
168
                                          size_t ttl_percent = DEFAULT_TTL_PERCENT,
169
                                          const std::string& storage = "disk");
170
171
struct CacheContext {
172
940k
    CacheContext(const IOContext* io_context) {
173
940k
        if (io_context->expiration_time != 0) {
174
2
            cache_type = FileCacheType::TTL;
175
2
            expiration_time = io_context->expiration_time;
176
940k
        } else if (io_context->is_index_data) {
177
96.8k
            cache_type = FileCacheType::INDEX;
178
843k
        } else if (io_context->is_disposable) {
179
71.1k
            cache_type = FileCacheType::DISPOSABLE;
180
772k
        } else {
181
772k
            cache_type = normal_cache_type_for_admission();
182
772k
        }
183
940k
        query_id = io_context->query_id ? *io_context->query_id : TUniqueId();
184
940k
        is_warmup = io_context->is_warmup;
185
940k
        remote_scan_cache_write_limiter = io_context->remote_scan_cache_write_limiter;
186
940k
        admit_cache_write_by_remote_scan_limiter =
187
940k
                remote_scan_cache_write_limiter != nullptr &&
188
940k
                io_context->reader_type == ReaderType::READER_QUERY &&
189
940k
                (!io_context->is_index_data || io_context->is_inverted_index ||
190
9
                 config::enable_file_cache_query_limit_segment_meta) &&
191
940k
                !io_context->is_warmup;
192
940k
    }
193
71.7k
    CacheContext() = default;
194
    bool operator==(const CacheContext& rhs) const {
195
        return query_id == rhs.query_id && cache_type == rhs.cache_type &&
196
               expiration_time == rhs.expiration_time && is_cold_data == rhs.is_cold_data;
197
    }
198
    TUniqueId query_id {};
199
    FileCacheType cache_type {FileCacheType::NORMAL};
200
    int64_t expiration_time {0};
201
    bool is_cold_data {false};
202
    ReadStatistics* stats {nullptr};
203
    bool is_warmup {false};
204
    int64_t tablet_id {0};
205
    RemoteScanCacheWriteLimiter* remote_scan_cache_write_limiter = nullptr;
206
    bool admit_cache_write_by_remote_scan_limiter {false};
207
};
208
209
template <class Lock>
210
concept IsXLock = std::same_as<Lock, std::lock_guard<std::mutex>> ||
211
                  std::same_as<Lock, std::unique_lock<std::mutex>>;
212
213
class LRUQueue {
214
public:
215
2.93k
    LRUQueue() = default;
216
    LRUQueue(size_t max_size, size_t max_element_size, int64_t hot_data_interval)
217
1.45k
            : max_size(max_size),
218
1.45k
              max_element_size(max_element_size),
219
1.45k
              hot_data_interval(hot_data_interval) {}
220
221
    struct HashFileKeyAndOffset {
222
1.88M
        std::size_t operator()(const std::pair<UInt128Wrapper, size_t>& pair) const {
223
1.88M
            return KeyHash()(pair.first) + pair.second;
224
1.88M
        }
225
    };
226
227
    struct FileKeyAndOffset {
228
        UInt128Wrapper hash;
229
        size_t offset;
230
        size_t size;
231
232
        FileKeyAndOffset(const UInt128Wrapper& hash, size_t offset, size_t size)
233
508k
                : hash(hash), offset(offset), size(size) {}
234
    };
235
236
    using Iterator = typename std::list<FileKeyAndOffset>::iterator;
237
238
175k
    size_t get_max_size() const { return max_size; }
239
3.09k
    size_t get_max_element_size() const { return max_element_size; }
240
241
    template <class T>
242
        requires IsXLock<T>
243
216k
    size_t get_capacity(T& /* cache_lock */) const {
244
216k
        return cache_size;
245
216k
    }
246
247
1.10k
    size_t get_capacity_unsafe() const { return cache_size; }
248
249
1.16k
    size_t get_elements_num_unsafe() const { return queue.size(); }
250
251
681k
    size_t get_elements_num(std::lock_guard<std::mutex>& /* cache_lock */) const {
252
681k
        return queue.size();
253
681k
    }
254
255
    Iterator add(const UInt128Wrapper& hash, size_t offset, size_t size,
256
                 std::lock_guard<std::mutex>& cache_lock);
257
    template <class T>
258
        requires IsXLock<T>
259
478k
    void remove(Iterator queue_it, T& /* cache_lock */) {
260
478k
        cache_size -= queue_it->size;
261
478k
        map.erase(std::make_pair(queue_it->hash, queue_it->offset));
262
478k
        queue.erase(queue_it);
263
478k
    }
264
265
    void move_to_end(Iterator queue_it, std::lock_guard<std::mutex>& cache_lock);
266
267
    void resize(Iterator queue_it, size_t new_size, std::lock_guard<std::mutex>& cache_lock);
268
269
    std::string to_string(std::lock_guard<std::mutex>& cache_lock) const;
270
271
    bool contains(const UInt128Wrapper& hash, size_t offset,
272
                  std::lock_guard<std::mutex>& cache_lock) const;
273
274
1.07M
    Iterator begin() { return queue.begin(); }
275
276
1.07M
    Iterator end() { return queue.end(); }
277
278
    void remove_all(std::lock_guard<std::mutex>& cache_lock);
279
280
    bool pop_front(std::lock_guard<std::mutex>& cache_lock);
281
282
    Iterator get(const UInt128Wrapper& hash, size_t offset,
283
                 std::lock_guard<std::mutex>& /* cache_lock */) const;
284
285
301k
    int64_t get_hot_data_interval() const { return hot_data_interval; }
286
287
0
    void clear(std::lock_guard<std::mutex>& cache_lock) {
288
0
        queue.clear();
289
0
        map.clear();
290
0
        cache_size = 0;
291
0
    }
292
293
    size_t levenshtein_distance_from(LRUQueue& base, std::lock_guard<std::mutex>& cache_lock);
294
295
    size_t max_size;
296
    size_t max_element_size;
297
    std::list<FileKeyAndOffset> queue;
298
    std::unordered_map<std::pair<UInt128Wrapper, size_t>, Iterator, HashFileKeyAndOffset> map;
299
    size_t cache_size = 0;
300
    int64_t hot_data_interval {0};
301
};
302
struct FileCacheInfo {
303
    UInt128Wrapper hash {0};
304
    uint64_t expiration_time {0};
305
    uint64_t size {0};
306
    size_t offset {0};
307
    bool is_tmp {false};
308
    FileCacheType cache_type {NORMAL};
309
310
    std::string to_string() const;
311
};
312
313
class InconsistencyType {
314
    uint32_t type;
315
316
public:
317
    enum : uint32_t {
318
        // No anomaly
319
        NONE = 0,
320
        // Missing a block cache metadata in _files
321
        NOT_LOADED = 1 << 0,
322
        // A block cache is missing in storage
323
        MISSING_IN_STORAGE = 1 << 1,
324
        // Size of a block cache recorded in _files is inconsistent with the storage
325
        SIZE_INCONSISTENT = 1 << 2,
326
        // Cache type of a block cache recorded in _files is inconsistent with the storage
327
        CACHE_TYPE_INCONSISTENT = 1 << 3,
328
        // Expiration time of a block cache recorded in _files is inconsistent with the storage
329
        EXPIRATION_TIME_INCONSISTENT = 1 << 4,
330
        // File in storage has a _tmp suffix, but the state of block cache in _files is not set to downloading
331
        TMP_FILE_EXPECT_DOWNLOADING_STATE = 1 << 5
332
    };
333
0
    InconsistencyType(uint32_t t = 0) : type(t) {}
334
0
    operator uint32_t&() { return type; }
335
336
    std::string to_string() const;
337
};
338
339
struct InconsistencyContext {
340
    // The infos in _files of BlockFileCache.
341
    std::vector<FileCacheInfo> infos_in_manager;
342
    std::vector<FileCacheInfo> infos_in_storage;
343
    std::vector<InconsistencyType> types;
344
};
345
346
std::optional<int64_t> get_tablet_id(std::string file_path);
347
348
} // namespace doris::io