Coverage Report

Created: 2026-07-14 19:01

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/io/cache/block_file_cache.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
// This file is copied from
18
// https://github.com/ClickHouse/ClickHouse/blob/master/src/Interpreters/Cache/FileCache.cpp
19
// and modified by Doris
20
21
#include "io/cache/block_file_cache.h"
22
23
#include <gen_cpp/file_cache.pb.h>
24
25
#include <cstdio>
26
#include <exception>
27
#include <fstream>
28
#include <unordered_set>
29
30
#include "common/status.h"
31
#include "cpp/sync_point.h"
32
#include "runtime/exec_env.h"
33
34
#if defined(__APPLE__)
35
#include <sys/mount.h>
36
#else
37
#include <sys/statfs.h>
38
#endif
39
40
#include <chrono> // IWYU pragma: keep
41
#include <mutex>
42
#include <ranges>
43
44
#include "common/cast_set.h"
45
#include "common/check.h"
46
#include "common/config.h"
47
#include "common/logging.h"
48
#include "core/uint128.h"
49
#include "exec/common/sip_hash.h"
50
#include "io/cache/block_file_cache_ttl_mgr.h"
51
#include "io/cache/file_block.h"
52
#include "io/cache/file_cache_common.h"
53
#include "io/cache/fs_file_cache_storage.h"
54
#include "io/cache/mem_file_cache_storage.h"
55
#include "io/cache/remote_scan_cache_write_limiter.h"
56
#include "runtime/runtime_profile.h"
57
#include "util/concurrency_stats.h"
58
#include "util/stack_util.h"
59
#include "util/stopwatch.hpp"
60
#include "util/thread.h"
61
#include "util/time.h"
62
namespace doris::io {
63
64
namespace {
65
66
constexpr std::array<FileCacheType, 4> LRU_LOG_REPLAY_TYPES = {
67
        FileCacheType::TTL, FileCacheType::INDEX, FileCacheType::NORMAL, FileCacheType::DISPOSABLE};
68
69
109k
size_t file_cache_type_index(FileCacheType type) {
70
109k
    return static_cast<size_t>(type);
71
109k
}
72
73
} // namespace
74
75
// Insert a block pointer into one shard while swallowing allocation failures.
76
193k
bool NeedUpdateLRUBlocks::insert(FileBlockSPtr block, size_t max_queue_size) {
77
193k
    if (!block || max_queue_size == 0) {
78
1
        return false;
79
1
    }
80
193k
    bool reserved = false;
81
193k
    try {
82
193k
        auto* raw_ptr = block.get();
83
193k
        auto idx = shard_index(raw_ptr);
84
193k
        auto& shard = _shards[idx];
85
193k
        std::lock_guard lock(shard.mutex);
86
193k
        if (shard.entries.contains(raw_ptr)) {
87
191k
            return false;
88
191k
        }
89
1.56k
        size_t cur_size = _size.load(std::memory_order_relaxed);
90
1.56k
        while (cur_size < max_queue_size) {
91
1.56k
            if (_size.compare_exchange_weak(cur_size, cur_size + 1, std::memory_order_relaxed)) {
92
1.56k
                reserved = true;
93
1.56k
                break;
94
1.56k
            }
95
1.56k
        }
96
1.56k
        if (!reserved) {
97
1
            return false;
98
1
        }
99
1.56k
        auto [_, inserted] = shard.entries.emplace(raw_ptr, std::move(block));
100
1.56k
        DORIS_CHECK(inserted);
101
1.56k
        return true;
102
1.56k
    } catch (const std::exception& e) {
103
0
        if (reserved) {
104
0
            decrease_size(1);
105
0
        }
106
0
        LOG(WARNING) << "Failed to enqueue block for LRU update: " << e.what();
107
0
    } catch (...) {
108
0
        if (reserved) {
109
0
            decrease_size(1);
110
0
        }
111
0
        LOG(WARNING) << "Failed to enqueue block for LRU update: unknown error";
112
0
    }
113
0
    return false;
114
193k
}
115
116
// Drain up to `limit` unique blocks to the caller, keeping the structure consistent on failures.
117
44
size_t NeedUpdateLRUBlocks::drain(size_t limit, std::vector<FileBlockSPtr>* output) {
118
44
    if (limit == 0 || output == nullptr) {
119
2
        return 0;
120
2
    }
121
42
    size_t drained = 0;
122
42
    try {
123
42
        output->reserve(output->size() + std::min(limit, size()));
124
2.64k
        for (auto& shard : _shards) {
125
2.64k
            if (drained >= limit) {
126
1
                break;
127
1
            }
128
2.64k
            std::lock_guard lock(shard.mutex);
129
2.64k
            auto it = shard.entries.begin();
130
2.64k
            size_t shard_drained = 0;
131
2.65k
            while (it != shard.entries.end() && drained + shard_drained < limit) {
132
10
                output->emplace_back(std::move(it->second));
133
10
                it = shard.entries.erase(it);
134
10
                ++shard_drained;
135
10
            }
136
2.64k
            if (shard_drained > 0) {
137
7
                decrease_size(shard_drained);
138
7
                drained += shard_drained;
139
7
            }
140
2.64k
        }
141
42
    } catch (const std::exception& e) {
142
0
        LOG(WARNING) << "Failed to drain LRU update blocks: " << e.what();
143
0
    } catch (...) {
144
0
        LOG(WARNING) << "Failed to drain LRU update blocks: unknown error";
145
0
    }
146
42
    return drained;
147
42
}
148
149
// Remove every pending block, guarding against unexpected exceptions.
150
26
void NeedUpdateLRUBlocks::clear() {
151
26
    try {
152
1.66k
        for (auto& shard : _shards) {
153
1.66k
            std::lock_guard lock(shard.mutex);
154
1.66k
            if (!shard.entries.empty()) {
155
2
                auto removed = shard.entries.size();
156
2
                shard.entries.clear();
157
2
                decrease_size(removed);
158
2
            }
159
1.66k
        }
160
26
    } catch (const std::exception& e) {
161
0
        LOG(WARNING) << "Failed to clear LRU update blocks: " << e.what();
162
0
    } catch (...) {
163
0
        LOG(WARNING) << "Failed to clear LRU update blocks: unknown error";
164
0
    }
165
26
}
166
167
9
void NeedUpdateLRUBlocks::decrease_size(size_t delta) {
168
9
    size_t cur_size = _size.load(std::memory_order_relaxed);
169
9
    while (true) {
170
9
        DORIS_CHECK_GE(cur_size, delta);
171
9
        if (_size.compare_exchange_weak(cur_size, cur_size - delta, std::memory_order_relaxed)) {
172
9
            return;
173
9
        }
174
9
    }
175
9
}
176
177
193k
size_t NeedUpdateLRUBlocks::shard_index(FileBlock* ptr) const {
178
193k
    DCHECK(ptr != nullptr);
179
193k
    return std::hash<FileBlock*> {}(ptr)&kShardMask;
180
193k
}
181
182
namespace {
183
184
13.6k
int64_t steady_clock_seconds() {
185
13.6k
    return std::chrono::duration_cast<std::chrono::seconds>(
186
13.6k
                   std::chrono::steady_clock::now().time_since_epoch())
187
13.6k
            .count();
188
13.6k
}
189
190
} // namespace
191
192
BlockFileCache::BlockFileCache(const std::string& cache_base_path,
193
                               const FileCacheSettings& cache_settings)
194
260
        : _cache_base_path(cache_base_path),
195
260
          _capacity(cache_settings.capacity),
196
260
          _max_file_block_size(cache_settings.max_file_block_size) {
197
260
    _cur_cache_size_metrics = std::make_shared<bvar::Status<size_t>>(_cache_base_path.c_str(),
198
260
                                                                     "file_cache_cache_size", 0);
199
260
    _cache_capacity_metrics = std::make_shared<bvar::Status<size_t>>(
200
260
            _cache_base_path.c_str(), "file_cache_capacity", _capacity);
201
260
    _cur_ttl_cache_size_metrics = std::make_shared<bvar::Status<size_t>>(
202
260
            _cache_base_path.c_str(), "file_cache_ttl_cache_size", 0);
203
260
    _cur_normal_queue_element_count_metrics = std::make_shared<bvar::Status<size_t>>(
204
260
            _cache_base_path.c_str(), "file_cache_normal_queue_element_count", 0);
205
260
    _cur_ttl_cache_lru_queue_cache_size_metrics = std::make_shared<bvar::Status<size_t>>(
206
260
            _cache_base_path.c_str(), "file_cache_ttl_cache_lru_queue_size", 0);
207
260
    _cur_ttl_cache_lru_queue_element_count_metrics = std::make_shared<bvar::Status<size_t>>(
208
260
            _cache_base_path.c_str(), "file_cache_ttl_cache_lru_queue_element_count", 0);
209
260
    _cur_normal_queue_cache_size_metrics = std::make_shared<bvar::Status<size_t>>(
210
260
            _cache_base_path.c_str(), "file_cache_normal_queue_cache_size", 0);
211
260
    _cur_index_queue_element_count_metrics = std::make_shared<bvar::Status<size_t>>(
212
260
            _cache_base_path.c_str(), "file_cache_index_queue_element_count", 0);
213
260
    _cur_index_queue_cache_size_metrics = std::make_shared<bvar::Status<size_t>>(
214
260
            _cache_base_path.c_str(), "file_cache_index_queue_cache_size", 0);
215
260
    _cur_disposable_queue_element_count_metrics = std::make_shared<bvar::Status<size_t>>(
216
260
            _cache_base_path.c_str(), "file_cache_disposable_queue_element_count", 0);
217
260
    _cur_disposable_queue_cache_size_metrics = std::make_shared<bvar::Status<size_t>>(
218
260
            _cache_base_path.c_str(), "file_cache_disposable_queue_cache_size", 0);
219
220
260
    _queue_evict_size_metrics[FileCacheType::DISPOSABLE] = std::make_shared<bvar::Adder<size_t>>(
221
260
            _cache_base_path.c_str(), "file_cache_disposable_queue_evict_size");
222
260
    _queue_evict_size_metrics[FileCacheType::NORMAL] = std::make_shared<bvar::Adder<size_t>>(
223
260
            _cache_base_path.c_str(), "file_cache_normal_queue_evict_size");
224
260
    _queue_evict_size_metrics[FileCacheType::INDEX] = std::make_shared<bvar::Adder<size_t>>(
225
260
            _cache_base_path.c_str(), "file_cache_index_queue_evict_size");
226
260
    _queue_evict_size_metrics[FileCacheType::TTL] = std::make_shared<bvar::Adder<size_t>>(
227
260
            _cache_base_path.c_str(), "file_cache_ttl_cache_evict_size");
228
260
    _total_evict_size_metrics = std::make_shared<bvar::Adder<size_t>>(
229
260
            _cache_base_path.c_str(), "file_cache_total_evict_size");
230
260
    _total_read_size_metrics = std::make_shared<bvar::Adder<size_t>>(_cache_base_path.c_str(),
231
260
                                                                     "file_cache_total_read_size");
232
260
    _total_hit_size_metrics = std::make_shared<bvar::Adder<size_t>>(_cache_base_path.c_str(),
233
260
                                                                    "file_cache_total_hit_size");
234
260
    _gc_evict_bytes_metrics = std::make_shared<bvar::Adder<size_t>>(_cache_base_path.c_str(),
235
260
                                                                    "file_cache_gc_evict_bytes");
236
260
    _gc_evict_count_metrics = std::make_shared<bvar::Adder<size_t>>(_cache_base_path.c_str(),
237
260
                                                                    "file_cache_gc_evict_count");
238
239
260
    _evict_by_time_metrics_matrix[FileCacheType::DISPOSABLE][FileCacheType::NORMAL] =
240
260
            std::make_shared<bvar::Adder<size_t>>(_cache_base_path.c_str(),
241
260
                                                  "file_cache_evict_by_time_disposable_to_normal");
242
260
    _evict_by_time_metrics_matrix[FileCacheType::DISPOSABLE][FileCacheType::INDEX] =
243
260
            std::make_shared<bvar::Adder<size_t>>(_cache_base_path.c_str(),
244
260
                                                  "file_cache_evict_by_time_disposable_to_index");
245
260
    _evict_by_time_metrics_matrix[FileCacheType::DISPOSABLE][FileCacheType::TTL] =
246
260
            std::make_shared<bvar::Adder<size_t>>(_cache_base_path.c_str(),
247
260
                                                  "file_cache_evict_by_time_disposable_to_ttl");
248
260
    _evict_by_time_metrics_matrix[FileCacheType::NORMAL][FileCacheType::DISPOSABLE] =
249
260
            std::make_shared<bvar::Adder<size_t>>(_cache_base_path.c_str(),
250
260
                                                  "file_cache_evict_by_time_normal_to_disposable");
251
260
    _evict_by_time_metrics_matrix[FileCacheType::NORMAL][FileCacheType::INDEX] =
252
260
            std::make_shared<bvar::Adder<size_t>>(_cache_base_path.c_str(),
253
260
                                                  "file_cache_evict_by_time_normal_to_index");
254
260
    _evict_by_time_metrics_matrix[FileCacheType::NORMAL][FileCacheType::TTL] =
255
260
            std::make_shared<bvar::Adder<size_t>>(_cache_base_path.c_str(),
256
260
                                                  "file_cache_evict_by_time_normal_to_ttl");
257
260
    _evict_by_time_metrics_matrix[FileCacheType::INDEX][FileCacheType::DISPOSABLE] =
258
260
            std::make_shared<bvar::Adder<size_t>>(_cache_base_path.c_str(),
259
260
                                                  "file_cache_evict_by_time_index_to_disposable");
260
260
    _evict_by_time_metrics_matrix[FileCacheType::INDEX][FileCacheType::NORMAL] =
261
260
            std::make_shared<bvar::Adder<size_t>>(_cache_base_path.c_str(),
262
260
                                                  "file_cache_evict_by_time_index_to_normal");
263
260
    _evict_by_time_metrics_matrix[FileCacheType::INDEX][FileCacheType::TTL] =
264
260
            std::make_shared<bvar::Adder<size_t>>(_cache_base_path.c_str(),
265
260
                                                  "file_cache_evict_by_time_index_to_ttl");
266
260
    _evict_by_time_metrics_matrix[FileCacheType::TTL][FileCacheType::DISPOSABLE] =
267
260
            std::make_shared<bvar::Adder<size_t>>(_cache_base_path.c_str(),
268
260
                                                  "file_cache_evict_by_time_ttl_to_disposable");
269
260
    _evict_by_time_metrics_matrix[FileCacheType::TTL][FileCacheType::NORMAL] =
270
260
            std::make_shared<bvar::Adder<size_t>>(_cache_base_path.c_str(),
271
260
                                                  "file_cache_evict_by_time_ttl_to_normal");
272
260
    _evict_by_time_metrics_matrix[FileCacheType::TTL][FileCacheType::INDEX] =
273
260
            std::make_shared<bvar::Adder<size_t>>(_cache_base_path.c_str(),
274
260
                                                  "file_cache_evict_by_time_ttl_to_index");
275
276
260
    _evict_by_self_lru_metrics_matrix[FileCacheType::DISPOSABLE] =
277
260
            std::make_shared<bvar::Adder<size_t>>(_cache_base_path.c_str(),
278
260
                                                  "file_cache_evict_by_self_lru_disposable");
279
260
    _evict_by_self_lru_metrics_matrix[FileCacheType::NORMAL] =
280
260
            std::make_shared<bvar::Adder<size_t>>(_cache_base_path.c_str(),
281
260
                                                  "file_cache_evict_by_self_lru_normal");
282
260
    _evict_by_self_lru_metrics_matrix[FileCacheType::INDEX] = std::make_shared<bvar::Adder<size_t>>(
283
260
            _cache_base_path.c_str(), "file_cache_evict_by_self_lru_index");
284
260
    _evict_by_self_lru_metrics_matrix[FileCacheType::TTL] = std::make_shared<bvar::Adder<size_t>>(
285
260
            _cache_base_path.c_str(), "file_cache_evict_by_self_lru_ttl");
286
287
260
    _evict_by_size_metrics_matrix[FileCacheType::DISPOSABLE][FileCacheType::NORMAL] =
288
260
            std::make_shared<bvar::Adder<size_t>>(_cache_base_path.c_str(),
289
260
                                                  "file_cache_evict_by_size_disposable_to_normal");
290
260
    _evict_by_size_metrics_matrix[FileCacheType::DISPOSABLE][FileCacheType::INDEX] =
291
260
            std::make_shared<bvar::Adder<size_t>>(_cache_base_path.c_str(),
292
260
                                                  "file_cache_evict_by_size_disposable_to_index");
293
260
    _evict_by_size_metrics_matrix[FileCacheType::DISPOSABLE][FileCacheType::TTL] =
294
260
            std::make_shared<bvar::Adder<size_t>>(_cache_base_path.c_str(),
295
260
                                                  "file_cache_evict_by_size_disposable_to_ttl");
296
260
    _evict_by_size_metrics_matrix[FileCacheType::NORMAL][FileCacheType::DISPOSABLE] =
297
260
            std::make_shared<bvar::Adder<size_t>>(_cache_base_path.c_str(),
298
260
                                                  "file_cache_evict_by_size_normal_to_disposable");
299
260
    _evict_by_size_metrics_matrix[FileCacheType::NORMAL][FileCacheType::INDEX] =
300
260
            std::make_shared<bvar::Adder<size_t>>(_cache_base_path.c_str(),
301
260
                                                  "file_cache_evict_by_size_normal_to_index");
302
260
    _evict_by_size_metrics_matrix[FileCacheType::NORMAL][FileCacheType::TTL] =
303
260
            std::make_shared<bvar::Adder<size_t>>(_cache_base_path.c_str(),
304
260
                                                  "file_cache_evict_by_size_normal_to_ttl");
305
260
    _evict_by_size_metrics_matrix[FileCacheType::INDEX][FileCacheType::DISPOSABLE] =
306
260
            std::make_shared<bvar::Adder<size_t>>(_cache_base_path.c_str(),
307
260
                                                  "file_cache_evict_by_size_index_to_disposable");
308
260
    _evict_by_size_metrics_matrix[FileCacheType::INDEX][FileCacheType::NORMAL] =
309
260
            std::make_shared<bvar::Adder<size_t>>(_cache_base_path.c_str(),
310
260
                                                  "file_cache_evict_by_size_index_to_normal");
311
260
    _evict_by_size_metrics_matrix[FileCacheType::INDEX][FileCacheType::TTL] =
312
260
            std::make_shared<bvar::Adder<size_t>>(_cache_base_path.c_str(),
313
260
                                                  "file_cache_evict_by_size_index_to_ttl");
314
260
    _evict_by_size_metrics_matrix[FileCacheType::TTL][FileCacheType::DISPOSABLE] =
315
260
            std::make_shared<bvar::Adder<size_t>>(_cache_base_path.c_str(),
316
260
                                                  "file_cache_evict_by_size_ttl_to_disposable");
317
260
    _evict_by_size_metrics_matrix[FileCacheType::TTL][FileCacheType::NORMAL] =
318
260
            std::make_shared<bvar::Adder<size_t>>(_cache_base_path.c_str(),
319
260
                                                  "file_cache_evict_by_size_ttl_to_normal");
320
260
    _evict_by_size_metrics_matrix[FileCacheType::TTL][FileCacheType::INDEX] =
321
260
            std::make_shared<bvar::Adder<size_t>>(_cache_base_path.c_str(),
322
260
                                                  "file_cache_evict_by_size_ttl_to_index");
323
324
260
    _evict_by_try_release = std::make_shared<bvar::Adder<size_t>>(
325
260
            _cache_base_path.c_str(), "file_cache_evict_by_try_release");
326
327
260
    _num_read_blocks = std::make_shared<bvar::Adder<size_t>>(_cache_base_path.c_str(),
328
260
                                                             "file_cache_num_read_blocks");
329
260
    _num_hit_blocks = std::make_shared<bvar::Adder<size_t>>(_cache_base_path.c_str(),
330
260
                                                            "file_cache_num_hit_blocks");
331
260
    _num_removed_blocks = std::make_shared<bvar::Adder<size_t>>(_cache_base_path.c_str(),
332
260
                                                                "file_cache_num_removed_blocks");
333
334
260
    _no_warmup_num_read_blocks = std::make_shared<bvar::Adder<size_t>>(
335
260
            _cache_base_path.c_str(), "file_cache_no_warmup_num_read_blocks");
336
260
    _no_warmup_num_hit_blocks = std::make_shared<bvar::Adder<size_t>>(
337
260
            _cache_base_path.c_str(), "file_cache_no_warmup_num_hit_blocks");
338
339
#ifndef BE_TEST
340
    _num_hit_blocks_5m = std::make_shared<bvar::Window<bvar::Adder<size_t>>>(
341
            _cache_base_path.c_str(), "file_cache_num_hit_blocks_5m", _num_hit_blocks.get(), 300);
342
    _num_read_blocks_5m = std::make_shared<bvar::Window<bvar::Adder<size_t>>>(
343
            _cache_base_path.c_str(), "file_cache_num_read_blocks_5m", _num_read_blocks.get(), 300);
344
    _num_hit_blocks_1h = std::make_shared<bvar::Window<bvar::Adder<size_t>>>(
345
            _cache_base_path.c_str(), "file_cache_num_hit_blocks_1h", _num_hit_blocks.get(), 3600);
346
    _num_read_blocks_1h = std::make_shared<bvar::Window<bvar::Adder<size_t>>>(
347
            _cache_base_path.c_str(), "file_cache_num_read_blocks_1h", _num_read_blocks.get(),
348
            3600);
349
    _no_warmup_num_hit_blocks_5m = std::make_shared<bvar::Window<bvar::Adder<size_t>>>(
350
            _cache_base_path.c_str(), "file_cache_no_warmup_num_hit_blocks_5m",
351
            _no_warmup_num_hit_blocks.get(), 300);
352
    _no_warmup_num_read_blocks_5m = std::make_shared<bvar::Window<bvar::Adder<size_t>>>(
353
            _cache_base_path.c_str(), "file_cache_no_warmup_num_read_blocks_5m",
354
            _no_warmup_num_read_blocks.get(), 300);
355
    _no_warmup_num_hit_blocks_1h = std::make_shared<bvar::Window<bvar::Adder<size_t>>>(
356
            _cache_base_path.c_str(), "file_cache_no_warmup_num_hit_blocks_1h",
357
            _no_warmup_num_hit_blocks.get(), 3600);
358
    _no_warmup_num_read_blocks_1h = std::make_shared<bvar::Window<bvar::Adder<size_t>>>(
359
            _cache_base_path.c_str(), "file_cache_no_warmup_num_read_blocks_1h",
360
            _no_warmup_num_read_blocks.get(), 3600);
361
#endif
362
363
260
    _hit_ratio = std::make_shared<bvar::Status<double>>(_cache_base_path.c_str(),
364
260
                                                        "file_cache_hit_ratio", 0.0);
365
260
    _hit_ratio_5m = std::make_shared<bvar::Status<double>>(_cache_base_path.c_str(),
366
260
                                                           "file_cache_hit_ratio_5m", 0.0);
367
260
    _hit_ratio_1h = std::make_shared<bvar::Status<double>>(_cache_base_path.c_str(),
368
260
                                                           "file_cache_hit_ratio_1h", 0.0);
369
370
260
    _no_warmup_hit_ratio = std::make_shared<bvar::Status<double>>(
371
260
            _cache_base_path.c_str(), "file_cache_no_warmup_hit_ratio", 0.0);
372
260
    _no_warmup_hit_ratio_5m = std::make_shared<bvar::Status<double>>(
373
260
            _cache_base_path.c_str(), "file_cache_no_warmup_hit_ratio_5m", 0.0);
374
260
    _no_warmup_hit_ratio_1h = std::make_shared<bvar::Status<double>>(
375
260
            _cache_base_path.c_str(), "file_cache_no_warmup_hit_ratio_1h", 0.0);
376
377
260
    _disk_limit_mode_metrics = std::make_shared<bvar::Status<size_t>>(
378
260
            _cache_base_path.c_str(), "file_cache_disk_limit_mode", 0);
379
260
    _need_evict_cache_in_advance_metrics = std::make_shared<bvar::Status<size_t>>(
380
260
            _cache_base_path.c_str(), "file_cache_need_evict_cache_in_advance", 0);
381
260
    _meta_store_write_queue_size_metrics = std::make_shared<bvar::Status<size_t>>(
382
260
            _cache_base_path.c_str(), "file_cache_meta_store_write_queue_size", 0);
383
384
260
    _cache_lock_wait_time_us = std::make_shared<bvar::LatencyRecorder>(
385
260
            _cache_base_path.c_str(), "file_cache_cache_lock_wait_time_us");
386
260
    _get_or_set_latency_us = std::make_shared<bvar::LatencyRecorder>(
387
260
            _cache_base_path.c_str(), "file_cache_get_or_set_latency_us");
388
260
    _storage_sync_remove_latency_us = std::make_shared<bvar::LatencyRecorder>(
389
260
            _cache_base_path.c_str(), "file_cache_storage_sync_remove_latency_us");
390
260
    _storage_retry_sync_remove_latency_us = std::make_shared<bvar::LatencyRecorder>(
391
260
            _cache_base_path.c_str(), "file_cache_storage_retry_sync_remove_latency_us");
392
260
    _storage_async_remove_latency_us = std::make_shared<bvar::LatencyRecorder>(
393
260
            _cache_base_path.c_str(), "file_cache_storage_async_remove_latency_us");
394
260
    _evict_in_advance_latency_us = std::make_shared<bvar::LatencyRecorder>(
395
260
            _cache_base_path.c_str(), "file_cache_evict_in_advance_latency_us");
396
260
    _lru_dump_latency_us = std::make_shared<bvar::LatencyRecorder>(
397
260
            _cache_base_path.c_str(), "file_cache_lru_dump_latency_us");
398
260
    _recycle_keys_length_recorder = std::make_shared<bvar::LatencyRecorder>(
399
260
            _cache_base_path.c_str(), "file_cache_recycle_keys_length");
400
260
    _need_update_lru_blocks_length_recorder = std::make_shared<bvar::LatencyRecorder>(
401
260
            _cache_base_path.c_str(), "file_cache_need_update_lru_blocks_length");
402
260
    _need_update_lru_blocks_produce_metrics = std::make_shared<bvar::Adder<size_t>>(
403
260
            _cache_base_path.c_str(), "file_cache_need_update_lru_blocks_produce");
404
260
    _need_update_lru_blocks_consume_metrics = std::make_shared<bvar::Adder<size_t>>(
405
260
            _cache_base_path.c_str(), "file_cache_need_update_lru_blocks_consume");
406
260
    _update_lru_blocks_latency_us = std::make_shared<bvar::LatencyRecorder>(
407
260
            _cache_base_path.c_str(), "file_cache_update_lru_blocks_latency_us");
408
260
    _ttl_gc_latency_us = std::make_shared<bvar::LatencyRecorder>(_cache_base_path.c_str(),
409
260
                                                                 "file_cache_ttl_gc_latency_us");
410
260
    _shadow_queue_levenshtein_distance = std::make_shared<bvar::LatencyRecorder>(
411
260
            _cache_base_path.c_str(), "file_cache_shadow_queue_levenshtein_distance");
412
260
    for (FileCacheType type : {FileCacheType::DISPOSABLE, FileCacheType::NORMAL,
413
1.04k
                               FileCacheType::INDEX, FileCacheType::TTL}) {
414
1.04k
        size_t idx = file_cache_type_index(type);
415
1.04k
        std::string metric_prefix =
416
1.04k
                "file_cache_lru_recorder_" + cache_type_to_string(type) + "_record_queue";
417
1.04k
        _lru_recorder_queue_length_recorder[idx] = std::make_shared<bvar::LatencyRecorder>(
418
1.04k
                _cache_base_path.c_str(), metric_prefix + "_length");
419
1.04k
        _lru_recorder_queue_produce_metrics[idx] = std::make_shared<bvar::Adder<size_t>>(
420
1.04k
                _cache_base_path.c_str(), metric_prefix + "_produce");
421
1.04k
        _lru_recorder_queue_consume_metrics[idx] = std::make_shared<bvar::Adder<size_t>>(
422
1.04k
                _cache_base_path.c_str(), metric_prefix + "_consume");
423
1.04k
        _lru_recorder_shadow_queue_element_count_metrics[idx] =
424
1.04k
                std::make_shared<bvar::Status<size_t>>(_cache_base_path.c_str(),
425
1.04k
                                                       "file_cache_lru_recorder_" +
426
1.04k
                                                               cache_type_to_string(type) +
427
1.04k
                                                               "_shadow_queue_element_count",
428
1.04k
                                                       0);
429
1.04k
    }
430
260
    _lru_recorder_log_replay_idle_metrics = std::make_shared<bvar::Adder<size_t>>(
431
260
            _cache_base_path.c_str(), "file_cache_lru_recorder_log_replay_idle");
432
433
260
    _disposable_queue = LRUQueue(cache_settings.disposable_queue_size,
434
260
                                 cache_settings.disposable_queue_elements, 60 * 60);
435
260
    _index_queue = LRUQueue(cache_settings.index_queue_size, cache_settings.index_queue_elements,
436
260
                            7 * 24 * 60 * 60);
437
260
    _normal_queue = LRUQueue(cache_settings.query_queue_size, cache_settings.query_queue_elements,
438
260
                             24 * 60 * 60);
439
260
    _ttl_queue = LRUQueue(cache_settings.ttl_queue_size, cache_settings.ttl_queue_elements,
440
260
                          std::numeric_limits<int>::max());
441
442
260
    _lru_recorder = std::make_unique<LRUQueueRecorder>(this);
443
260
    _lru_dumper = std::make_unique<CacheLRUDumper>(this, _lru_recorder.get());
444
260
    if (cache_settings.storage == "memory") {
445
33
        _storage = std::make_unique<MemFileCacheStorage>();
446
33
        _cache_base_path = "memory";
447
227
    } else {
448
227
        _storage = std::make_unique<FSFileCacheStorage>();
449
227
    }
450
451
260
    LOG(INFO) << "file cache path= " << _cache_base_path << " " << cache_settings.to_string();
452
260
}
453
454
14.6k
UInt128Wrapper BlockFileCache::hash(const std::string& path) {
455
14.6k
    uint128_t value;
456
14.6k
    sip_hash128(path.data(), path.size(), reinterpret_cast<char*>(&value));
457
14.6k
    return UInt128Wrapper(value);
458
14.6k
}
459
460
BlockFileCache::QueryFileCacheContextHolderPtr BlockFileCache::get_query_context_holder(
461
8
        const TUniqueId& query_id, int file_cache_query_limit_percent) {
462
8
    SCOPED_CACHE_LOCK(_mutex, this);
463
8
    if (!config::enable_file_cache_query_limit) {
464
1
        return {};
465
1
    }
466
467
    /// if enable_filesystem_query_cache_limit is true,
468
    /// we create context query for current query.
469
7
    auto context = get_or_set_query_context(query_id, cache_lock, file_cache_query_limit_percent);
470
7
    return std::make_unique<QueryFileCacheContextHolder>(query_id, this, context);
471
8
}
472
473
BlockFileCache::QueryFileCacheContextPtr BlockFileCache::get_query_context(
474
3.40k
        const TUniqueId& query_id, std::lock_guard<std::mutex>& cache_lock) {
475
3.40k
    auto query_iter = _query_map.find(query_id);
476
3.40k
    return (query_iter == _query_map.end()) ? nullptr : query_iter->second;
477
3.40k
}
478
479
6
void BlockFileCache::remove_query_context(const TUniqueId& query_id) {
480
6
    SCOPED_CACHE_LOCK(_mutex, this);
481
6
    const auto& query_iter = _query_map.find(query_id);
482
483
6
    if (query_iter != _query_map.end() && query_iter->second.use_count() <= 1) {
484
5
        _query_map.erase(query_iter);
485
5
    }
486
6
}
487
488
BlockFileCache::QueryFileCacheContextPtr BlockFileCache::get_or_set_query_context(
489
        const TUniqueId& query_id, std::lock_guard<std::mutex>& cache_lock,
490
7
        int file_cache_query_limit_percent) {
491
7
    if (query_id.lo == 0 && query_id.hi == 0) {
492
1
        return nullptr;
493
1
    }
494
495
6
    auto context = get_query_context(query_id, cache_lock);
496
6
    if (context) {
497
1
        return context;
498
1
    }
499
500
5
    size_t file_cache_query_limit_size = _capacity * file_cache_query_limit_percent / 100;
501
5
    if (file_cache_query_limit_size < 268435456) {
502
5
        LOG(WARNING) << "The user-set file cache query limit (" << file_cache_query_limit_size
503
5
                     << " bytes) is less than the 256MB recommended minimum. "
504
5
                     << "Consider increasing the session variable 'file_cache_query_limit_percent'"
505
5
                     << " from its current value " << file_cache_query_limit_percent << "%.";
506
5
    }
507
5
    auto query_context = std::make_shared<QueryFileCacheContext>(file_cache_query_limit_size);
508
5
    auto query_iter = _query_map.emplace(query_id, query_context).first;
509
5
    return query_iter->second;
510
6
}
511
512
void BlockFileCache::QueryFileCacheContext::remove(const UInt128Wrapper& hash, size_t offset,
513
20
                                                   std::lock_guard<std::mutex>& cache_lock) {
514
20
    auto pair = std::make_pair(hash, offset);
515
20
    auto record = records.find(pair);
516
20
    DCHECK(record != records.end());
517
20
    auto iter = record->second;
518
20
    records.erase(pair);
519
20
    lru_queue.remove(iter, cache_lock);
520
20
}
521
522
void BlockFileCache::QueryFileCacheContext::reserve(const UInt128Wrapper& hash, size_t offset,
523
                                                    size_t size,
524
30
                                                    std::lock_guard<std::mutex>& cache_lock) {
525
30
    auto pair = std::make_pair(hash, offset);
526
30
    if (records.find(pair) == records.end()) {
527
29
        auto queue_iter = lru_queue.add(hash, offset, size, cache_lock);
528
29
        records.insert({pair, queue_iter});
529
29
    }
530
30
}
531
532
231
Status BlockFileCache::initialize() {
533
231
    SCOPED_CACHE_LOCK(_mutex, this);
534
231
    return initialize_unlocked(cache_lock);
535
231
}
536
537
231
Status BlockFileCache::initialize_unlocked(std::lock_guard<std::mutex>& cache_lock) {
538
231
    DCHECK(!_is_initialized);
539
231
    _is_initialized = true;
540
231
    if (config::file_cache_background_lru_dump_tail_record_num > 0) {
541
        // requirements:
542
        // 1. restored data should not overwrite the last dump
543
        // 2. restore should happen before load and async load
544
        // 3. all queues should be restored sequencially to avoid conflict
545
        // TODO(zhengyu): we can parralize them but will increase complexity, so lets check the time cost
546
        // to see if any improvement is a necessary
547
231
        restore_lru_queues_from_disk(cache_lock);
548
231
    }
549
231
    RETURN_IF_ERROR(_storage->init(this));
550
551
231
    if (auto* fs_storage = dynamic_cast<FSFileCacheStorage*>(_storage.get())) {
552
200
        if (auto* meta_store = fs_storage->get_meta_store()) {
553
200
            _ttl_mgr = std::make_unique<BlockFileCacheTtlMgr>(this, meta_store);
554
200
        }
555
200
    }
556
557
231
    _cache_background_monitor_thread = std::thread(&BlockFileCache::run_background_monitor, this);
558
231
    _cache_background_gc_thread = std::thread(&BlockFileCache::run_background_gc, this);
559
231
    _cache_background_evict_in_advance_thread =
560
231
            std::thread(&BlockFileCache::run_background_evict_in_advance, this);
561
231
    _cache_background_block_lru_update_thread =
562
231
            std::thread(&BlockFileCache::run_background_block_lru_update, this);
563
564
    // Initialize LRU dump thread and restore queues
565
231
    _cache_background_lru_dump_thread = std::thread(&BlockFileCache::run_background_lru_dump, this);
566
231
    _cache_background_lru_log_replay_thread =
567
231
            std::thread(&BlockFileCache::run_background_lru_log_replay, this);
568
569
231
    return Status::OK();
570
231
}
571
572
void BlockFileCache::update_block_lru(FileBlockSPtr block,
573
7
                                      std::lock_guard<std::mutex>& cache_lock) {
574
7
    if (!block) {
575
1
        return;
576
1
    }
577
578
6
    FileBlockCell* cell = get_cell(block->get_hash_value(), block->offset(), cache_lock);
579
6
    if (!cell || cell->file_block.get() != block.get()) {
580
1
        return;
581
1
    }
582
583
5
    if (cell->queue_iterator) {
584
5
        auto& queue = get_queue(block->cache_type());
585
5
        queue.move_to_end(*cell->queue_iterator, cache_lock);
586
5
        _lru_recorder->record_queue_event(block->cache_type(), CacheLRULogType::MOVETOBACK,
587
5
                                          block->_key.hash, block->_key.offset,
588
5
                                          block->_block_range.size());
589
5
    }
590
5
    cell->update_atime();
591
5
}
592
593
void BlockFileCache::use_cell(const FileBlockCell& cell, FileBlocks* result, bool move_iter_flag,
594
712k
                              std::lock_guard<std::mutex>& cache_lock) {
595
712k
    if (result) {
596
712k
        result->push_back(cell.file_block);
597
712k
    }
598
599
712k
    auto& queue = get_queue(cell.file_block->cache_type());
600
    /// Move to the end of the queue. The iterator remains valid.
601
712k
    if (!config::enable_file_cache_async_touch_on_get_or_set && cell.queue_iterator &&
602
712k
        move_iter_flag) {
603
520k
        queue.move_to_end(*cell.queue_iterator, cache_lock);
604
520k
        _lru_recorder->record_queue_event(cell.file_block->cache_type(),
605
520k
                                          CacheLRULogType::MOVETOBACK, cell.file_block->_key.hash,
606
520k
                                          cell.file_block->_key.offset, cell.size());
607
520k
    }
608
609
712k
    cell.update_atime();
610
712k
}
611
612
template <class T>
613
    requires IsXLock<T>
614
FileBlockCell* BlockFileCache::get_cell(const UInt128Wrapper& hash, size_t offset,
615
111k
                                        T& /* cache_lock */) {
616
111k
    auto it = _files.find(hash);
617
111k
    if (it == _files.end()) {
618
3
        return nullptr;
619
3
    }
620
621
111k
    auto& offsets = it->second;
622
111k
    auto cell_it = offsets.find(offset);
623
111k
    if (cell_it == offsets.end()) {
624
3
        return nullptr;
625
3
    }
626
627
111k
    return &cell_it->second;
628
111k
}
629
630
904k
bool BlockFileCache::need_to_move(FileCacheType cell_type, FileCacheType query_type) const {
631
904k
    return query_type != FileCacheType::DISPOSABLE && cell_type != FileCacheType::DISPOSABLE;
632
904k
}
633
634
FileBlocks BlockFileCache::get_impl(const UInt128Wrapper& hash, const CacheContext& context,
635
                                    const FileBlock::Range& range,
636
725k
                                    std::lock_guard<std::mutex>& cache_lock) {
637
    /// Given range = [left, right] and non-overlapping ordered set of file blocks,
638
    /// find list [block1, ..., blockN] of blocks which intersect with given range.
639
725k
    auto it = _files.find(hash);
640
725k
    if (it == _files.end()) {
641
7.18k
        if (_async_open_done) {
642
7.17k
            return {};
643
7.17k
        }
644
7
        FileCacheKey key;
645
7
        key.hash = hash;
646
7
        key.meta.type = context.cache_type;
647
7
        key.meta.expiration_time = context.expiration_time;
648
7
        key.meta.tablet_id = context.tablet_id;
649
7
        _storage->load_blocks_directly_unlocked(this, key, cache_lock);
650
651
7
        it = _files.find(hash);
652
7
        if (it == _files.end()) [[unlikely]] {
653
7
            return {};
654
7
        }
655
7
    }
656
657
718k
    auto& file_blocks = it->second;
658
718k
    if (file_blocks.empty()) {
659
0
        LOG(WARNING) << "file_blocks is empty for hash=" << hash.to_string()
660
0
                     << " cache type=" << context.cache_type
661
0
                     << " cache expiration time=" << context.expiration_time
662
0
                     << " cache range=" << range.left << " " << range.right
663
0
                     << " query id=" << context.query_id;
664
0
        DCHECK(false);
665
0
        _files.erase(hash);
666
0
        return {};
667
0
    }
668
669
718k
    FileBlocks result;
670
718k
    auto block_it = file_blocks.lower_bound(range.left);
671
718k
    if (block_it == file_blocks.end()) {
672
        /// N - last cached block for given file hash, block{N}.offset < range.left:
673
        ///   block{N}                       block{N}
674
        /// [________                         [_______]
675
        ///     [__________]         OR                  [________]
676
        ///     ^                                        ^
677
        ///     range.left                               range.left
678
679
6.20k
        const auto& cell = file_blocks.rbegin()->second;
680
6.20k
        if (cell.file_block->range().right < range.left) {
681
6.19k
            return {};
682
6.19k
        }
683
684
14
        use_cell(cell, &result, need_to_move(cell.file_block->cache_type(), context.cache_type),
685
14
                 cache_lock);
686
712k
    } else { /// block_it <-- segmment{k}
687
712k
        if (block_it != file_blocks.begin()) {
688
220
            const auto& prev_cell = std::prev(block_it)->second;
689
220
            const auto& prev_cell_range = prev_cell.file_block->range();
690
691
220
            if (range.left <= prev_cell_range.right) {
692
                ///   block{k-1}  block{k}
693
                ///   [________]   [_____
694
                ///       [___________
695
                ///       ^
696
                ///       range.left
697
698
137
                use_cell(prev_cell, &result,
699
137
                         need_to_move(prev_cell.file_block->cache_type(), context.cache_type),
700
137
                         cache_lock);
701
137
            }
702
220
        }
703
704
        ///  block{k} ...       block{k-1}  block{k}                      block{k}
705
        ///  [______              [______]     [____                        [________
706
        ///  [_________     OR              [________      OR    [______]   ^
707
        ///  ^                              ^                           ^   block{k}.offset
708
        ///  range.left                     range.left                  range.right
709
710
1.42M
        while (block_it != file_blocks.end()) {
711
712k
            const auto& cell = block_it->second;
712
712k
            if (range.right < cell.file_block->range().left) {
713
176
                break;
714
176
            }
715
716
712k
            use_cell(cell, &result, need_to_move(cell.file_block->cache_type(), context.cache_type),
717
712k
                     cache_lock);
718
712k
            ++block_it;
719
712k
        }
720
712k
    }
721
722
712k
    return result;
723
718k
}
724
725
193k
void BlockFileCache::add_need_update_lru_block(FileBlockSPtr block) {
726
193k
    int64_t queue_limit = config::file_cache_background_block_lru_update_queue_max_size;
727
193k
    size_t max_queue_size = queue_limit <= 0 ? 0 : static_cast<size_t>(queue_limit);
728
193k
    if (_need_update_lru_blocks.insert(std::move(block), max_queue_size)) {
729
1.55k
        *_need_update_lru_blocks_produce_metrics << 1;
730
1.55k
        *_need_update_lru_blocks_length_recorder << _need_update_lru_blocks.size();
731
1.55k
    }
732
193k
}
733
734
Status BlockFileCache::get_downloaded_blocks_if_fully_covered(const UInt128Wrapper& hash,
735
                                                              size_t offset, size_t size,
736
                                                              const CacheContext& context,
737
                                                              FileBlocks* blocks,
738
15
                                                              bool* fully_covered) {
739
15
    DCHECK(blocks != nullptr);
740
15
    DCHECK(fully_covered != nullptr);
741
15
    blocks->clear();
742
15
    *fully_covered = false;
743
15
    if (size == 0) {
744
0
        *fully_covered = true;
745
0
        return Status::OK();
746
0
    }
747
748
15
    FileBlock::Range range(offset, offset + size - 1);
749
15
    std::lock_guard cache_lock(_mutex);
750
15
    auto it = _files.find(hash);
751
15
    if (it == _files.end()) {
752
3
        if (_async_open_done) {
753
1
            return Status::OK();
754
1
        }
755
2
        FileCacheKey key;
756
2
        key.hash = hash;
757
2
        key.meta.type = context.cache_type;
758
2
        key.meta.expiration_time = context.expiration_time;
759
2
        key.meta.tablet_id = context.tablet_id;
760
2
        _storage->load_blocks_directly_unlocked(this, key, cache_lock);
761
762
2
        it = _files.find(hash);
763
2
        if (it == _files.end()) {
764
2
            return Status::OK();
765
2
        }
766
2
    }
767
768
12
    auto& file_blocks = it->second;
769
12
    if (file_blocks.empty()) {
770
0
        LOG(WARNING) << "file_blocks is empty for hash=" << hash.to_string()
771
0
                     << " cache type=" << context.cache_type
772
0
                     << " cache expiration time=" << context.expiration_time
773
0
                     << " cache range=" << range.left << " " << range.right
774
0
                     << " query id=" << context.query_id;
775
0
        DCHECK(false);
776
0
        _files.erase(hash);
777
0
        return Status::OK();
778
0
    }
779
780
12
    std::vector<FileBlockCell*> covered_cells;
781
12
    auto block_it = file_blocks.lower_bound(range.left);
782
12
    if (block_it == file_blocks.end() || block_it->second.file_block->range().left > range.left) {
783
10
        if (block_it == file_blocks.begin()) {
784
0
            return Status::OK();
785
0
        }
786
10
        --block_it;
787
10
    }
788
789
12
    size_t current_pos = range.left;
790
26
    while (current_pos <= range.right) {
791
26
        if (block_it == file_blocks.end()) {
792
4
            return Status::OK();
793
4
        }
794
795
22
        auto& cell = block_it->second;
796
22
        const auto& block_range = cell.file_block->range();
797
22
        if (block_range.right < current_pos) {
798
1
            ++block_it;
799
1
            continue;
800
1
        }
801
21
        if (block_range.left > current_pos ||
802
21
            cell.file_block->state() != FileBlock::State::DOWNLOADED) {
803
0
            return Status::OK();
804
0
        }
805
806
21
        covered_cells.push_back(&cell);
807
21
        if (range.right <= block_range.right) {
808
8
            *fully_covered = true;
809
8
            break;
810
8
        }
811
13
        current_pos = block_range.right + 1;
812
13
        ++block_it;
813
13
    }
814
815
8
    if (!*fully_covered) {
816
0
        return Status::OK();
817
0
    }
818
15
    for (const auto* cell : covered_cells) {
819
15
        use_cell(*cell, blocks, need_to_move(cell->file_block->cache_type(), context.cache_type),
820
15
                 cache_lock);
821
15
    }
822
8
    return Status::OK();
823
8
}
824
825
4
std::string BlockFileCache::clear_file_cache_async() {
826
4
    return clear_file_cache_impl(false);
827
4
}
828
829
20
std::string BlockFileCache::clear_file_cache_sync() {
830
20
    return clear_file_cache_impl(true);
831
20
}
832
833
24
std::string BlockFileCache::clear_file_cache_impl(bool sync_remove) {
834
24
    const char* action = sync_remove ? "clear_file_cache_sync" : "clear_file_cache_async";
835
24
    LOG(INFO) << "start " << action << ", path=" << _cache_base_path;
836
24
    _lru_dumper->remove_lru_dump_files();
837
24
    int64_t num_cells_all = 0;
838
24
    int64_t num_cells_to_delete = 0;
839
24
    int64_t num_cells_wait_recycle = 0;
840
24
    int64_t num_files_all = 0;
841
24
    TEST_SYNC_POINT_CALLBACK("BlockFileCache::clear_file_cache_async");
842
24
    {
843
24
        SCOPED_CACHE_LOCK(_mutex, this);
844
845
24
        std::vector<FileBlockCell*> deleting_cells;
846
5.13k
        for (auto& [_, offset_to_cell] : _files) {
847
5.13k
            ++num_files_all;
848
105k
            for (auto& [_1, cell] : offset_to_cell) {
849
105k
                ++num_cells_all;
850
105k
                deleting_cells.push_back(&cell);
851
105k
            }
852
5.13k
        }
853
854
        // Do not erase while walking _files above: remove() may erase the current map element.
855
        //
856
        // sync_remove only changes how already releasable DOWNLOADED blocks are deleted from
857
        // storage. Busy blocks keep the existing holder lifecycle: mark them deleting and leave
858
        // them in _files until the last holder releases them.
859
105k
        for (auto& cell : deleting_cells) {
860
105k
            if (!cell->releasable()) {
861
103
                LOG(INFO) << "cell is not releasable, hash="
862
103
                          << " offset=" << cell->file_block->offset();
863
103
                cell->file_block->set_deleting();
864
103
                ++num_cells_wait_recycle;
865
103
                continue;
866
103
            }
867
105k
            FileBlockSPtr file_block = cell->file_block;
868
105k
            if (file_block) {
869
105k
                std::lock_guard block_lock(file_block->_mutex);
870
105k
                remove(file_block, cache_lock, block_lock, sync_remove);
871
105k
                ++num_cells_to_delete;
872
105k
            }
873
105k
        }
874
24
        clear_need_update_lru_blocks();
875
24
    }
876
877
24
    std::stringstream ss;
878
24
    ss << "finish " << action << ", path=" << _cache_base_path << " sync_remove=" << sync_remove
879
24
       << " num_files_all=" << num_files_all << " num_cells_all=" << num_cells_all
880
24
       << " num_cells_to_delete=" << num_cells_to_delete
881
24
       << " num_cells_wait_recycle=" << num_cells_wait_recycle;
882
24
    auto msg = ss.str();
883
24
    LOG(INFO) << msg;
884
24
    _lru_dumper->remove_lru_dump_files();
885
24
    return msg;
886
24
}
887
888
FileBlocks BlockFileCache::split_range_into_cells(const UInt128Wrapper& hash,
889
                                                  const CacheContext& context, size_t offset,
890
                                                  size_t size, FileBlock::State state,
891
13.5k
                                                  std::lock_guard<std::mutex>& cache_lock) {
892
13.5k
    DCHECK(size > 0);
893
894
13.5k
    auto current_pos = offset;
895
13.5k
    auto end_pos_non_included = offset + size;
896
897
13.5k
    size_t current_size = 0;
898
13.5k
    size_t remaining_size = size;
899
900
13.5k
    FileBlocks file_blocks;
901
27.2k
    while (current_pos < end_pos_non_included) {
902
13.6k
        current_size = std::min(remaining_size, _max_file_block_size);
903
13.6k
        remaining_size -= current_size;
904
13.6k
        auto block_state = state;
905
13.6k
        if (block_state != FileBlock::State::SKIP_CACHE &&
906
13.6k
            context.admit_cache_write_by_remote_scan_limiter) {
907
8
            auto* limiter = context.remote_scan_cache_write_limiter;
908
8
            DCHECK(limiter != nullptr);
909
8
            if (!limiter->try_admit_cache_write(static_cast<int64_t>(current_size))) {
910
4
                block_state = FileBlock::State::SKIP_CACHE;
911
4
            }
912
8
        }
913
13.6k
        if (block_state != FileBlock::State::SKIP_CACHE) {
914
13.6k
            block_state = try_reserve(hash, context, current_pos, current_size, cache_lock)
915
13.6k
                                  ? block_state
916
13.6k
                                  : FileBlock::State::SKIP_CACHE;
917
13.6k
        }
918
13.6k
        if (block_state == FileBlock::State::SKIP_CACHE) [[unlikely]] {
919
66
            FileCacheKey key;
920
66
            key.hash = hash;
921
66
            key.offset = current_pos;
922
66
            key.meta.type = context.cache_type;
923
66
            key.meta.expiration_time = context.expiration_time;
924
66
            key.meta.tablet_id = context.tablet_id;
925
66
            auto file_block = std::make_shared<FileBlock>(key, current_size, this,
926
66
                                                          FileBlock::State::SKIP_CACHE);
927
66
            file_blocks.push_back(std::move(file_block));
928
13.6k
        } else {
929
13.6k
            auto* cell =
930
13.6k
                    add_cell(hash, context, current_pos, current_size, block_state, cache_lock);
931
13.6k
            if (cell) {
932
13.6k
                file_blocks.push_back(cell->file_block);
933
13.6k
                if (!context.is_cold_data) {
934
13.6k
                    cell->update_atime();
935
13.6k
                }
936
13.6k
            }
937
13.6k
            if (_ttl_mgr && context.tablet_id != 0) {
938
1.05k
                _ttl_mgr->register_tablet_id(context.tablet_id);
939
1.05k
            }
940
13.6k
        }
941
942
13.6k
        current_pos += current_size;
943
13.6k
    }
944
945
13.5k
    DCHECK(file_blocks.empty() || offset + size - 1 == file_blocks.back()->range().right);
946
13.5k
    return file_blocks;
947
13.5k
}
948
949
void BlockFileCache::fill_holes_with_empty_file_blocks(FileBlocks& file_blocks,
950
                                                       const UInt128Wrapper& hash,
951
                                                       const CacheContext& context,
952
                                                       const FileBlock::Range& range,
953
712k
                                                       std::lock_guard<std::mutex>& cache_lock) {
954
    /// There are blocks [block1, ..., blockN]
955
    /// (non-overlapping, non-empty, ascending-ordered) which (maybe partially)
956
    /// intersect with given range.
957
958
    /// It can have holes:
959
    /// [____________________]         -- requested range
960
    ///     [____]  [_]   [_________]  -- intersecting cache [block1, ..., blockN]
961
    ///
962
    /// For each such hole create a cell with file block state EMPTY.
963
964
712k
    auto it = file_blocks.begin();
965
712k
    auto block_range = (*it)->range();
966
967
712k
    size_t current_pos = 0;
968
712k
    if (block_range.left < range.left) {
969
        ///    [_______     -- requested range
970
        /// [_______
971
        /// ^
972
        /// block1
973
974
151
        current_pos = block_range.right + 1;
975
151
        ++it;
976
712k
    } else {
977
712k
        current_pos = range.left;
978
712k
    }
979
980
1.42M
    while (current_pos <= range.right && it != file_blocks.end()) {
981
712k
        block_range = (*it)->range();
982
983
712k
        if (current_pos == block_range.left) {
984
712k
            current_pos = block_range.right + 1;
985
712k
            ++it;
986
712k
            continue;
987
712k
        }
988
989
712k
        DCHECK(current_pos < block_range.left);
990
991
117
        auto hole_size = block_range.left - current_pos;
992
993
117
        file_blocks.splice(it, split_range_into_cells(hash, context, current_pos, hole_size,
994
117
                                                      FileBlock::State::EMPTY, cache_lock));
995
996
117
        current_pos = block_range.right + 1;
997
117
        ++it;
998
117
    }
999
1000
712k
    if (current_pos <= range.right) {
1001
        ///   ________]     -- requested range
1002
        ///   _____]
1003
        ///        ^
1004
        /// blockN
1005
1006
94
        auto hole_size = range.right - current_pos + 1;
1007
1008
94
        file_blocks.splice(file_blocks.end(),
1009
94
                           split_range_into_cells(hash, context, current_pos, hole_size,
1010
94
                                                  FileBlock::State::EMPTY, cache_lock));
1011
94
    }
1012
712k
}
1013
1014
FileBlocksHolder BlockFileCache::get_or_set(const UInt128Wrapper& hash, size_t offset, size_t size,
1015
725k
                                            CacheContext& context) {
1016
725k
    FileBlock::Range range(offset, offset + size - 1);
1017
1018
725k
    ReadStatistics* stats = context.stats;
1019
725k
    DCHECK(stats != nullptr);
1020
725k
    MonotonicStopWatch sw;
1021
725k
    sw.start();
1022
725k
    FileBlocks file_blocks;
1023
725k
    std::vector<FileBlockSPtr> need_update_lru_blocks;
1024
725k
    const bool async_touch_on_get_or_set = config::enable_file_cache_async_touch_on_get_or_set;
1025
725k
    int64_t duration = 0;
1026
725k
    {
1027
725k
        ConcurrencyStatsManager::instance().cached_remote_reader_get_or_set_wait_lock->increment();
1028
725k
        std::lock_guard cache_lock(_mutex);
1029
725k
        ConcurrencyStatsManager::instance().cached_remote_reader_get_or_set_wait_lock->decrement();
1030
725k
        stats->lock_wait_timer += sw.elapsed_time();
1031
725k
        SCOPED_RAW_TIMER(&duration);
1032
        /// Get all blocks which intersect with the given range.
1033
725k
        {
1034
725k
            SCOPED_RAW_TIMER(&stats->get_timer);
1035
725k
            file_blocks = get_impl(hash, context, range, cache_lock);
1036
725k
        }
1037
1038
725k
        if (file_blocks.empty()) {
1039
13.3k
            SCOPED_RAW_TIMER(&stats->set_timer);
1040
13.3k
            file_blocks = split_range_into_cells(hash, context, offset, size,
1041
13.3k
                                                 FileBlock::State::EMPTY, cache_lock);
1042
712k
        } else {
1043
712k
            SCOPED_RAW_TIMER(&stats->set_timer);
1044
712k
            fill_holes_with_empty_file_blocks(file_blocks, hash, context, range, cache_lock);
1045
712k
        }
1046
725k
        DCHECK(!file_blocks.empty());
1047
725k
        *_num_read_blocks << file_blocks.size();
1048
725k
        if (!context.is_warmup) {
1049
725k
            *_no_warmup_num_read_blocks << file_blocks.size();
1050
725k
        }
1051
725k
        if (async_touch_on_get_or_set) {
1052
193k
            need_update_lru_blocks.reserve(file_blocks.size());
1053
193k
        }
1054
726k
        for (auto& block : file_blocks) {
1055
726k
            size_t block_size = block->range().size();
1056
726k
            *_total_read_size_metrics << block_size;
1057
726k
            if (block->state_unsafe() == FileBlock::State::DOWNLOADED) {
1058
712k
                *_num_hit_blocks << 1;
1059
712k
                *_total_hit_size_metrics << block_size;
1060
712k
                if (!context.is_warmup) {
1061
712k
                    *_no_warmup_num_hit_blocks << 1;
1062
712k
                }
1063
712k
                if (async_touch_on_get_or_set &&
1064
712k
                    need_to_move(block->cache_type(), context.cache_type)) {
1065
192k
                    need_update_lru_blocks.emplace_back(block);
1066
192k
                }
1067
712k
            }
1068
726k
        }
1069
725k
    }
1070
1071
725k
    if (async_touch_on_get_or_set) {
1072
193k
        for (auto& block : need_update_lru_blocks) {
1073
192k
            add_need_update_lru_block(std::move(block));
1074
192k
        }
1075
193k
    }
1076
1077
725k
    *_get_or_set_latency_us << (duration / 1000);
1078
725k
    return FileBlocksHolder(std::move(file_blocks));
1079
725k
}
1080
1081
FileBlockCell* BlockFileCache::add_cell(const UInt128Wrapper& hash, const CacheContext& context,
1082
                                        size_t offset, size_t size, FileBlock::State state,
1083
113k
                                        std::lock_guard<std::mutex>& cache_lock) {
1084
    /// Create a file block cell and put it in `files` map by [hash][offset].
1085
113k
    if (size == 0) {
1086
0
        return nullptr; /// Empty files are not cached.
1087
0
    }
1088
1089
113k
    VLOG_DEBUG << "Adding file block to cache. size=" << size << " hash=" << hash.to_string()
1090
0
               << " offset=" << offset << " cache_type=" << cache_type_to_string(context.cache_type)
1091
0
               << " expiration_time=" << context.expiration_time
1092
0
               << " tablet_id=" << context.tablet_id;
1093
1094
113k
    if (size > 1024 * 1024 * 1024) {
1095
0
        LOG(WARNING) << "File block size is too large for a block, reject. size=" << size
1096
0
                     << " hash=" << hash.to_string() << " offset=" << offset
1097
0
                     << " stack:" << get_stack_trace();
1098
0
        return nullptr;
1099
0
    }
1100
1101
113k
    auto& offsets = _files[hash];
1102
113k
    auto itr = offsets.find(offset);
1103
113k
    if (itr != offsets.end()) {
1104
5
        VLOG_DEBUG << "Cache already exists for hash: " << hash.to_string()
1105
0
                   << ", offset: " << offset << ", size: " << size
1106
0
                   << ".\nCurrent cache structure: " << dump_structure_unlocked(hash, cache_lock);
1107
5
        return &(itr->second);
1108
5
    }
1109
1110
113k
    FileCacheKey key;
1111
113k
    key.hash = hash;
1112
113k
    key.offset = offset;
1113
113k
    key.meta.type = context.cache_type;
1114
113k
    key.meta.expiration_time = context.expiration_time;
1115
113k
    key.meta.tablet_id = context.tablet_id;
1116
113k
    FileBlockCell cell(std::make_shared<FileBlock>(key, size, this, state), cache_lock);
1117
113k
    Status st;
1118
113k
    if (context.expiration_time == 0 && context.cache_type == FileCacheType::TTL) {
1119
0
        st = cell.file_block->change_cache_type_lock(FileCacheType::NORMAL, cache_lock);
1120
113k
    } else if (context.cache_type != FileCacheType::TTL && context.expiration_time != 0) {
1121
1
        st = cell.file_block->change_cache_type_lock(FileCacheType::TTL, cache_lock);
1122
1
    }
1123
113k
    if (!st.ok()) {
1124
0
        LOG(WARNING) << "Cannot change cache type. expiration_time=" << context.expiration_time
1125
0
                     << " cache_type=" << cache_type_to_string(context.cache_type)
1126
0
                     << " error=" << st.msg();
1127
0
    }
1128
1129
113k
    auto& queue = get_queue(cell.file_block->cache_type());
1130
113k
    cell.queue_iterator = queue.add(hash, offset, size, cache_lock);
1131
113k
    _lru_recorder->record_queue_event(cell.file_block->cache_type(), CacheLRULogType::ADD,
1132
113k
                                      cell.file_block->get_hash_value(), cell.file_block->offset(),
1133
113k
                                      cell.size());
1134
1135
113k
    if (cell.file_block->cache_type() == FileCacheType::TTL) {
1136
3.92k
        _cur_ttl_size += cell.size();
1137
3.92k
    }
1138
113k
    auto [it, _] = offsets.insert(std::make_pair(offset, std::move(cell)));
1139
113k
    _cur_cache_size += size;
1140
113k
    return &(it->second);
1141
113k
}
1142
1143
0
size_t BlockFileCache::try_release() {
1144
0
    SCOPED_CACHE_LOCK(_mutex, this);
1145
0
    std::vector<FileBlockCell*> trash;
1146
0
    for (auto& [hash, blocks] : _files) {
1147
0
        for (auto& [offset, cell] : blocks) {
1148
0
            if (cell.releasable()) {
1149
0
                trash.emplace_back(&cell);
1150
0
            } else {
1151
0
                cell.file_block->set_deleting();
1152
0
            }
1153
0
        }
1154
0
    }
1155
0
    size_t remove_size = 0;
1156
0
    for (auto& cell : trash) {
1157
0
        FileBlockSPtr file_block = cell->file_block;
1158
0
        std::lock_guard lc(cell->file_block->_mutex);
1159
0
        remove_size += file_block->range().size();
1160
0
        remove(file_block, cache_lock, lc);
1161
0
        VLOG_DEBUG << "try_release " << _cache_base_path
1162
0
                   << " hash=" << file_block->get_hash_value().to_string()
1163
0
                   << " offset=" << file_block->offset();
1164
0
    }
1165
0
    *_evict_by_try_release << remove_size;
1166
0
    LOG(INFO) << "Released " << trash.size() << " blocks in file cache " << _cache_base_path;
1167
0
    return trash.size();
1168
0
}
1169
1170
969k
LRUQueue& BlockFileCache::get_queue(FileCacheType type) {
1171
969k
    switch (type) {
1172
14.7k
    case FileCacheType::INDEX:
1173
14.7k
        return _index_queue;
1174
14.5k
    case FileCacheType::DISPOSABLE:
1175
14.5k
        return _disposable_queue;
1176
934k
    case FileCacheType::NORMAL:
1177
934k
        return _normal_queue;
1178
5.80k
    case FileCacheType::TTL:
1179
5.80k
        return _ttl_queue;
1180
0
    default:
1181
0
        DCHECK(false);
1182
969k
    }
1183
0
    return _normal_queue;
1184
969k
}
1185
1186
155
const LRUQueue& BlockFileCache::get_queue(FileCacheType type) const {
1187
155
    switch (type) {
1188
37
    case FileCacheType::INDEX:
1189
37
        return _index_queue;
1190
1
    case FileCacheType::DISPOSABLE:
1191
1
        return _disposable_queue;
1192
117
    case FileCacheType::NORMAL:
1193
117
        return _normal_queue;
1194
0
    case FileCacheType::TTL:
1195
0
        return _ttl_queue;
1196
0
    default:
1197
0
        DCHECK(false);
1198
155
    }
1199
0
    return _normal_queue;
1200
155
}
1201
1202
void BlockFileCache::remove_file_blocks(std::vector<FileBlockCell*>& to_evict,
1203
                                        std::lock_guard<std::mutex>& cache_lock, bool sync,
1204
14.9k
                                        std::string& reason) {
1205
14.9k
    auto remove_file_block_if = [&](FileBlockCell* cell) {
1206
1.36k
        FileBlockSPtr file_block = cell->file_block;
1207
1.36k
        if (file_block) {
1208
1.36k
            std::lock_guard block_lock(file_block->_mutex);
1209
1.36k
            remove(file_block, cache_lock, block_lock, sync);
1210
1.36k
            VLOG_DEBUG << "remove_file_blocks"
1211
0
                       << " hash=" << file_block->get_hash_value().to_string()
1212
0
                       << " offset=" << file_block->offset() << " reason=" << reason;
1213
1.36k
        }
1214
1.36k
    };
1215
14.9k
    std::for_each(to_evict.begin(), to_evict.end(), remove_file_block_if);
1216
14.9k
}
1217
1218
void BlockFileCache::find_evict_candidates(LRUQueue& queue, size_t size, size_t cur_cache_size,
1219
                                           size_t& removed_size,
1220
                                           std::vector<FileBlockCell*>& to_evict,
1221
                                           std::lock_guard<std::mutex>& cache_lock,
1222
1.34k
                                           size_t& cur_removed_size, bool evict_in_advance) {
1223
3.09k
    for (const auto& [entry_key, entry_offset, entry_size] : queue) {
1224
3.09k
        if (!is_overflow(removed_size, size, cur_cache_size, evict_in_advance)) {
1225
1.27k
            break;
1226
1.27k
        }
1227
1.81k
        auto* cell = get_cell(entry_key, entry_offset, cache_lock);
1228
1229
1.81k
        DCHECK(cell) << "Cache became inconsistent. key: " << entry_key.to_string()
1230
0
                     << ", offset: " << entry_offset;
1231
1232
1.81k
        size_t cell_size = cell->size();
1233
1.81k
        DCHECK(entry_size == cell_size);
1234
1235
1.81k
        if (cell->releasable()) {
1236
1.34k
            auto& file_block = cell->file_block;
1237
1238
1.34k
            std::lock_guard block_lock(file_block->_mutex);
1239
1.34k
            DCHECK(file_block->_download_state == FileBlock::State::DOWNLOADED);
1240
1.34k
            to_evict.push_back(cell);
1241
1.34k
            removed_size += cell_size;
1242
1.34k
            cur_removed_size += cell_size;
1243
1.34k
        }
1244
1.81k
    }
1245
1.34k
}
1246
1247
// 1. if async load file cache not finish
1248
//     a. evict from lru queue
1249
// 2. if ttl cache
1250
//     a. evict from disposable/normal/index queue one by one
1251
// 3. if dont reach query limit or dont have query limit
1252
//     a. evict from other queue
1253
//     b. evict from current queue
1254
//         a.1 if the data belong write, then just evict cold data
1255
// 4. if reach query limit
1256
//     a. evict from query queue
1257
//     b. evict from other queue
1258
bool BlockFileCache::try_reserve(const UInt128Wrapper& hash, const CacheContext& context,
1259
                                 size_t offset, size_t size,
1260
13.6k
                                 std::lock_guard<std::mutex>& cache_lock) {
1261
13.6k
    if (!_async_open_done) {
1262
10
        return try_reserve_during_async_load(size, cache_lock);
1263
10
    }
1264
    // use this strategy in scenarios where there is insufficient disk capacity or insufficient number of inodes remaining
1265
    // directly eliminate 5 times the size of the space
1266
13.6k
    if (_disk_resource_limit_mode) {
1267
1
        size = 5 * size;
1268
1
    }
1269
1270
13.6k
    auto query_context = config::enable_file_cache_query_limit &&
1271
13.6k
                                         (context.query_id.hi != 0 || context.query_id.lo != 0)
1272
13.6k
                                 ? get_query_context(context.query_id, cache_lock)
1273
13.6k
                                 : nullptr;
1274
13.6k
    if (!query_context) {
1275
13.6k
        return try_reserve_for_lru(hash, nullptr, context, offset, size, cache_lock);
1276
13.6k
    } else if (query_context->get_cache_size(cache_lock) + size <=
1277
31
               query_context->get_max_cache_size()) {
1278
16
        return try_reserve_for_lru(hash, query_context, context, offset, size, cache_lock);
1279
16
    }
1280
15
    int64_t cur_time = std::chrono::duration_cast<std::chrono::seconds>(
1281
15
                               std::chrono::steady_clock::now().time_since_epoch())
1282
15
                               .count();
1283
15
    auto& queue = get_queue(context.cache_type);
1284
15
    size_t removed_size = 0;
1285
15
    size_t ghost_remove_size = 0;
1286
15
    size_t queue_size = queue.get_capacity(cache_lock);
1287
15
    size_t cur_cache_size = _cur_cache_size;
1288
15
    size_t query_context_cache_size = query_context->get_cache_size(cache_lock);
1289
1290
15
    std::vector<LRUQueue::Iterator> ghost;
1291
15
    std::vector<FileBlockCell*> to_evict;
1292
1293
15
    size_t max_size = queue.get_max_size();
1294
48
    auto is_overflow = [&] {
1295
48
        return _disk_resource_limit_mode ? removed_size < size
1296
48
                                         : cur_cache_size + size - removed_size > _capacity ||
1297
48
                                                   (queue_size + size - removed_size > max_size) ||
1298
48
                                                   (query_context_cache_size + size -
1299
38
                                                            (removed_size + ghost_remove_size) >
1300
38
                                                    query_context->get_max_cache_size());
1301
48
    };
1302
1303
    /// Select the cache from the LRU queue held by query for expulsion.
1304
35
    for (auto iter = query_context->queue().begin(); iter != query_context->queue().end(); iter++) {
1305
33
        if (!is_overflow()) {
1306
13
            break;
1307
13
        }
1308
1309
20
        auto* cell = get_cell(iter->hash, iter->offset, cache_lock);
1310
1311
20
        if (!cell) {
1312
            /// The cache corresponding to this record may be swapped out by
1313
            /// other queries, so it has become invalid.
1314
4
            ghost.push_back(iter);
1315
4
            ghost_remove_size += iter->size;
1316
16
        } else {
1317
16
            size_t cell_size = cell->size();
1318
16
            DCHECK(iter->size == cell_size);
1319
1320
16
            if (cell->releasable()) {
1321
16
                auto& file_block = cell->file_block;
1322
1323
16
                std::lock_guard block_lock(file_block->_mutex);
1324
16
                DCHECK(file_block->_download_state == FileBlock::State::DOWNLOADED);
1325
16
                to_evict.push_back(cell);
1326
16
                removed_size += cell_size;
1327
16
            }
1328
16
        }
1329
20
    }
1330
1331
16
    auto remove_file_block_if = [&](FileBlockCell* cell) {
1332
16
        FileBlockSPtr file_block = cell->file_block;
1333
16
        if (file_block) {
1334
16
            query_context->remove(file_block->get_hash_value(), file_block->offset(), cache_lock);
1335
16
            std::lock_guard block_lock(file_block->_mutex);
1336
16
            remove(file_block, cache_lock, block_lock);
1337
16
        }
1338
16
    };
1339
1340
15
    for (auto& iter : ghost) {
1341
4
        query_context->remove(iter->hash, iter->offset, cache_lock);
1342
4
    }
1343
1344
15
    std::for_each(to_evict.begin(), to_evict.end(), remove_file_block_if);
1345
1346
15
    if (is_overflow() &&
1347
15
        !try_reserve_from_other_queue(context.cache_type, size, cur_time, cache_lock)) {
1348
1
        return false;
1349
1
    }
1350
14
    query_context->reserve(hash, offset, size, cache_lock);
1351
14
    return true;
1352
15
}
1353
1354
4
void BlockFileCache::try_evict_in_advance(size_t size, std::lock_guard<std::mutex>& cache_lock) {
1355
4
    UInt128Wrapper hash = UInt128Wrapper();
1356
4
    size_t offset = 0;
1357
4
    CacheContext context;
1358
    /* we pick NORMAL and TTL cache to evict in advance
1359
     * we reserve for them but won't acutually give space to them
1360
     * on the contrary, NORMAL and TTL may sacrifice by LRU evicting themselves
1361
     * other cache types cannot be exempted because we will evict what they have stolen before LRU evicting
1362
     * in summary: all cache types will shrink somewhat, and NORMAL and TTL shrink the most, to make sure the cache is not full
1363
     */
1364
4
    context.cache_type = FileCacheType::NORMAL;
1365
4
    try_reserve_for_lru(hash, nullptr, context, offset, size, cache_lock, true);
1366
4
    context.cache_type = FileCacheType::TTL;
1367
4
    try_reserve_for_lru(hash, nullptr, context, offset, size, cache_lock, true);
1368
4
}
1369
1370
// remove specific cache synchronously, for critical operations
1371
// if in use, cache meta will be deleted after use and the block file is then deleted asynchronously
1372
11
void BlockFileCache::remove_if_cached(const UInt128Wrapper& file_key) {
1373
11
    std::string reason = "remove_if_cached";
1374
11
    SCOPED_CACHE_LOCK(_mutex, this);
1375
11
    auto iter = _files.find(file_key);
1376
11
    std::vector<FileBlockCell*> to_remove;
1377
11
    if (iter != _files.end()) {
1378
14
        for (auto& [_, cell] : iter->second) {
1379
14
            if (cell.releasable()) {
1380
13
                to_remove.push_back(&cell);
1381
13
            } else {
1382
1
                cell.file_block->set_deleting();
1383
1
            }
1384
14
        }
1385
6
    }
1386
11
    remove_file_blocks(to_remove, cache_lock, true, reason);
1387
11
}
1388
1389
// the async version of remove_if_cached, for background operations
1390
// cache meta is deleted synchronously if not in use, and the block file is deleted asynchronously
1391
// if in use, cache meta will be deleted after use and the block file is then deleted asynchronously
1392
2
void BlockFileCache::remove_if_cached_async(const UInt128Wrapper& file_key) {
1393
2
    std::string reason = "remove_if_cached_async";
1394
2
    SCOPED_CACHE_LOCK(_mutex, this);
1395
1396
2
    auto iter = _files.find(file_key);
1397
2
    std::vector<FileBlockCell*> to_remove;
1398
2
    if (iter != _files.end()) {
1399
2
        for (auto& [_, cell] : iter->second) {
1400
2
            *_gc_evict_bytes_metrics << cell.size();
1401
2
            *_gc_evict_count_metrics << 1;
1402
2
            if (cell.releasable()) {
1403
1
                to_remove.push_back(&cell);
1404
1
            } else {
1405
1
                cell.file_block->set_deleting();
1406
1
            }
1407
2
        }
1408
2
    }
1409
2
    remove_file_blocks(to_remove, cache_lock, false, reason);
1410
2
}
1411
1412
std::vector<FileCacheType> BlockFileCache::get_other_cache_type_without_ttl(
1413
13.6k
        FileCacheType cur_cache_type) {
1414
13.6k
    switch (cur_cache_type) {
1415
3.91k
    case FileCacheType::TTL:
1416
3.91k
        return {FileCacheType::DISPOSABLE, FileCacheType::NORMAL, FileCacheType::INDEX};
1417
673
    case FileCacheType::INDEX:
1418
673
        return {FileCacheType::DISPOSABLE, FileCacheType::NORMAL};
1419
8.46k
    case FileCacheType::NORMAL:
1420
8.46k
        return {FileCacheType::DISPOSABLE, FileCacheType::INDEX};
1421
617
    case FileCacheType::DISPOSABLE:
1422
617
        return {FileCacheType::NORMAL, FileCacheType::INDEX};
1423
0
    default:
1424
0
        return {};
1425
13.6k
    }
1426
0
    return {};
1427
13.6k
}
1428
1429
1.25k
std::vector<FileCacheType> BlockFileCache::get_other_cache_type(FileCacheType cur_cache_type) {
1430
1.25k
    switch (cur_cache_type) {
1431
286
    case FileCacheType::TTL:
1432
286
        return {FileCacheType::DISPOSABLE, FileCacheType::NORMAL, FileCacheType::INDEX};
1433
143
    case FileCacheType::INDEX:
1434
143
        return {FileCacheType::DISPOSABLE, FileCacheType::NORMAL, FileCacheType::TTL};
1435
669
    case FileCacheType::NORMAL:
1436
669
        return {FileCacheType::DISPOSABLE, FileCacheType::INDEX, FileCacheType::TTL};
1437
152
    case FileCacheType::DISPOSABLE:
1438
152
        return {FileCacheType::NORMAL, FileCacheType::INDEX, FileCacheType::TTL};
1439
0
    default:
1440
0
        return {};
1441
1.25k
    }
1442
0
    return {};
1443
1.25k
}
1444
1445
void BlockFileCache::reset_range(const UInt128Wrapper& hash, size_t offset, size_t old_size,
1446
10
                                 size_t new_size, std::lock_guard<std::mutex>& cache_lock) {
1447
10
    DCHECK(_files.find(hash) != _files.end() &&
1448
10
           _files.find(hash)->second.find(offset) != _files.find(hash)->second.end());
1449
10
    FileBlockCell* cell = get_cell(hash, offset, cache_lock);
1450
10
    DCHECK(cell != nullptr);
1451
10
    if (cell == nullptr) {
1452
0
        LOG(WARNING) << "reset_range skipped because cache cell is missing. hash="
1453
0
                     << hash.to_string() << " offset=" << offset << " old_size=" << old_size
1454
0
                     << " new_size=" << new_size;
1455
0
        return;
1456
0
    }
1457
10
    DCHECK_EQ(cell->file_block->_block_range.size(), old_size);
1458
10
    if (cell->queue_iterator) {
1459
10
        auto& queue = get_queue(cell->file_block->cache_type());
1460
10
        DCHECK(queue.contains(hash, offset, cache_lock));
1461
10
        queue.resize(*cell->queue_iterator, new_size, cache_lock);
1462
10
        _lru_recorder->record_queue_event(cell->file_block->cache_type(), CacheLRULogType::RESIZE,
1463
10
                                          cell->file_block->get_hash_value(),
1464
10
                                          cell->file_block->offset(), new_size);
1465
10
    }
1466
10
    cell->file_block->_block_range.right = cell->file_block->_block_range.left + new_size - 1;
1467
10
    _cur_cache_size -= old_size;
1468
10
    _cur_cache_size += new_size;
1469
10
    if (cell->file_block->cache_type() == FileCacheType::TTL) {
1470
0
        _cur_ttl_size -= old_size;
1471
0
        _cur_ttl_size += new_size;
1472
0
    }
1473
10
}
1474
1475
bool BlockFileCache::try_reserve_from_other_queue_by_time_interval(
1476
        FileCacheType cur_type, std::vector<FileCacheType> other_cache_types, size_t size,
1477
13.6k
        int64_t cur_time, std::lock_guard<std::mutex>& cache_lock, bool evict_in_advance) {
1478
13.6k
    size_t removed_size = 0;
1479
13.6k
    size_t cur_cache_size = _cur_cache_size;
1480
13.6k
    std::vector<FileBlockCell*> to_evict;
1481
31.2k
    for (FileCacheType cache_type : other_cache_types) {
1482
31.2k
        auto& queue = get_queue(cache_type);
1483
31.2k
        size_t remove_size_per_type = 0;
1484
31.2k
        for (const auto& [entry_key, entry_offset, entry_size] : queue) {
1485
1.43k
            if (!is_overflow(removed_size, size, cur_cache_size, evict_in_advance)) {
1486
697
                break;
1487
697
            }
1488
741
            auto* cell = get_cell(entry_key, entry_offset, cache_lock);
1489
741
            DCHECK(cell) << "Cache became inconsistent. UInt128Wrapper: " << entry_key.to_string()
1490
0
                         << ", offset: " << entry_offset;
1491
1492
741
            size_t cell_size = cell->size();
1493
741
            DCHECK(entry_size == cell_size);
1494
1495
741
            if (cell->atime == 0 ? true : cell->atime + queue.get_hot_data_interval() > cur_time) {
1496
739
                break;
1497
739
            }
1498
1499
2
            if (cell->releasable()) {
1500
2
                auto& file_block = cell->file_block;
1501
2
                std::lock_guard block_lock(file_block->_mutex);
1502
2
                DCHECK(file_block->_download_state == FileBlock::State::DOWNLOADED);
1503
2
                to_evict.push_back(cell);
1504
2
                removed_size += cell_size;
1505
2
                remove_size_per_type += cell_size;
1506
2
            }
1507
2
        }
1508
31.2k
        *(_evict_by_time_metrics_matrix[cache_type][cur_type]) << remove_size_per_type;
1509
31.2k
    }
1510
13.6k
    bool is_sync_removal = !evict_in_advance;
1511
13.6k
    std::string reason = std::string("try_reserve_by_time ") +
1512
13.6k
                         " evict_in_advance=" + (evict_in_advance ? "true" : "false");
1513
13.6k
    remove_file_blocks(to_evict, cache_lock, is_sync_removal, reason);
1514
1515
13.6k
    return !is_overflow(removed_size, size, cur_cache_size, evict_in_advance);
1516
13.6k
}
1517
1518
bool BlockFileCache::is_overflow(size_t removed_size, size_t need_size, size_t cur_cache_size,
1519
19.4k
                                 bool evict_in_advance) const {
1520
19.4k
    bool ret = false;
1521
19.4k
    if (evict_in_advance) { // we don't need to check _need_evict_cache_in_advance
1522
39
        ret = (removed_size < need_size);
1523
39
        return ret;
1524
39
    }
1525
19.4k
    if (_disk_resource_limit_mode) {
1526
4
        ret = (removed_size < need_size);
1527
19.4k
    } else {
1528
19.4k
        ret = (cur_cache_size + need_size - removed_size > _capacity);
1529
19.4k
    }
1530
19.4k
    return ret;
1531
19.4k
}
1532
1533
bool BlockFileCache::try_reserve_from_other_queue_by_size(
1534
        FileCacheType cur_type, std::vector<FileCacheType> other_cache_types, size_t size,
1535
419
        std::lock_guard<std::mutex>& cache_lock, bool evict_in_advance) {
1536
419
    size_t removed_size = 0;
1537
419
    size_t cur_cache_size = _cur_cache_size;
1538
419
    std::vector<FileBlockCell*> to_evict;
1539
    // we follow the privilege defined in get_other_cache_types to evict
1540
1.25k
    for (FileCacheType cache_type : other_cache_types) {
1541
1.25k
        auto& queue = get_queue(cache_type);
1542
1543
        // we will not drain each of them to the bottom -- i.e., we only
1544
        // evict what they have stolen.
1545
1.25k
        size_t cur_queue_size = queue.get_capacity(cache_lock);
1546
1.25k
        size_t cur_queue_max_size = queue.get_max_size();
1547
1.25k
        if (cur_queue_size <= cur_queue_max_size) {
1548
752
            continue;
1549
752
        }
1550
505
        size_t cur_removed_size = 0;
1551
505
        find_evict_candidates(queue, size, cur_cache_size, removed_size, to_evict, cache_lock,
1552
505
                              cur_removed_size, evict_in_advance);
1553
505
        *(_evict_by_size_metrics_matrix[cache_type][cur_type]) << cur_removed_size;
1554
505
    }
1555
419
    bool is_sync_removal = !evict_in_advance;
1556
419
    std::string reason = std::string("try_reserve_by_size") +
1557
419
                         " evict_in_advance=" + (evict_in_advance ? "true" : "false");
1558
419
    remove_file_blocks(to_evict, cache_lock, is_sync_removal, reason);
1559
419
    return !is_overflow(removed_size, size, cur_cache_size, evict_in_advance);
1560
419
}
1561
1562
bool BlockFileCache::try_reserve_from_other_queue(FileCacheType cur_cache_type, size_t size,
1563
                                                  int64_t cur_time,
1564
                                                  std::lock_guard<std::mutex>& cache_lock,
1565
13.6k
                                                  bool evict_in_advance) {
1566
    // currently, TTL cache is not considered as a candidate
1567
13.6k
    auto other_cache_types = get_other_cache_type_without_ttl(cur_cache_type);
1568
13.6k
    bool reserve_success = try_reserve_from_other_queue_by_time_interval(
1569
13.6k
            cur_cache_type, other_cache_types, size, cur_time, cache_lock, evict_in_advance);
1570
13.6k
    if (reserve_success || !config::file_cache_enable_evict_from_other_queue_by_size) {
1571
12.4k
        return reserve_success;
1572
12.4k
    }
1573
1574
1.25k
    other_cache_types = get_other_cache_type(cur_cache_type);
1575
1.25k
    auto& cur_queue = get_queue(cur_cache_type);
1576
1.25k
    size_t cur_queue_size = cur_queue.get_capacity(cache_lock);
1577
1.25k
    size_t cur_queue_max_size = cur_queue.get_max_size();
1578
    // Hit the soft limit by self, cannot remove from other queues
1579
1.25k
    if (_cur_cache_size + size > _capacity && cur_queue_size + size > cur_queue_max_size) {
1580
831
        return false;
1581
831
    }
1582
419
    return try_reserve_from_other_queue_by_size(cur_cache_type, other_cache_types, size, cache_lock,
1583
419
                                                evict_in_advance);
1584
1.25k
}
1585
1586
bool BlockFileCache::try_reserve_for_lru(const UInt128Wrapper& hash,
1587
                                         QueryFileCacheContextPtr query_context,
1588
                                         const CacheContext& context, size_t offset, size_t size,
1589
                                         std::lock_guard<std::mutex>& cache_lock,
1590
13.6k
                                         bool evict_in_advance) {
1591
13.6k
    int64_t cur_time = steady_clock_seconds();
1592
13.6k
    if (!try_reserve_from_other_queue(context.cache_type, size, cur_time, cache_lock,
1593
13.6k
                                      evict_in_advance)) {
1594
837
        auto& queue = get_queue(context.cache_type);
1595
837
        size_t removed_size = 0;
1596
837
        size_t cur_cache_size = _cur_cache_size;
1597
1598
837
        std::vector<FileBlockCell*> to_evict;
1599
837
        size_t cur_removed_size = 0;
1600
837
        find_evict_candidates(queue, size, cur_cache_size, removed_size, to_evict, cache_lock,
1601
837
                              cur_removed_size, evict_in_advance);
1602
837
        bool is_sync_removal = !evict_in_advance;
1603
837
        std::string reason = std::string("try_reserve for cache type ") +
1604
837
                             cache_type_to_string(context.cache_type) +
1605
837
                             " evict_in_advance=" + (evict_in_advance ? "true" : "false");
1606
837
        remove_file_blocks(to_evict, cache_lock, is_sync_removal, reason);
1607
837
        *(_evict_by_self_lru_metrics_matrix[context.cache_type]) << cur_removed_size;
1608
1609
837
        if (is_overflow(removed_size, size, cur_cache_size, evict_in_advance)) {
1610
65
            return false;
1611
65
        }
1612
837
    }
1613
1614
13.6k
    if (query_context) {
1615
16
        query_context->reserve(hash, offset, size, cache_lock);
1616
16
    }
1617
13.6k
    return true;
1618
13.6k
}
1619
1620
template <class T, class U>
1621
    requires IsXLock<T> && IsXLock<U>
1622
108k
void BlockFileCache::remove(FileBlockSPtr file_block, T& cache_lock, U& block_lock, bool sync) {
1623
108k
    auto hash = file_block->get_hash_value();
1624
108k
    auto offset = file_block->offset();
1625
108k
    auto type = file_block->cache_type();
1626
108k
    auto expiration_time = file_block->expiration_time();
1627
108k
    auto tablet_id = file_block->tablet_id();
1628
108k
    auto* cell = get_cell(hash, offset, cache_lock);
1629
108k
    file_block->cell = nullptr;
1630
    // Holder cleanup can race with prior cache metadata cleanup. In that case,
1631
    // skip the duplicate remove instead of touching a detached or replaced cell.
1632
108k
    if (cell == nullptr) {
1633
1
        LOG(WARNING) << "remove skipped because cache cell is missing. hash=" << hash.to_string()
1634
1
                     << " offset=" << offset << " size=" << file_block->range().size()
1635
1
                     << " type=" << cache_type_to_string(type)
1636
1
                     << " state=" << FileBlock::state_to_string(file_block->state_unsafe())
1637
1
                     << " expiration_time=" << expiration_time << " sync=" << sync;
1638
1
        return;
1639
1
    }
1640
108k
    if (cell->file_block.get() != file_block.get()) {
1641
1
        auto* cell_file_block = cell->file_block.get();
1642
1
        LOG(WARNING)
1643
1
                << "remove skipped because cache cell points to a different file block. hash="
1644
1
                << hash.to_string() << " offset=" << offset
1645
1
                << " size=" << file_block->range().size() << " type=" << cache_type_to_string(type)
1646
1
                << " state=" << FileBlock::state_to_string(file_block->state_unsafe())
1647
1
                << " expiration_time=" << expiration_time << " sync=" << sync << " cell_block_hash="
1648
1
                << (cell_file_block ? cell_file_block->get_hash_value().to_string() : "<null>")
1649
1
                << " cell_block_offset="
1650
1
                << (cell_file_block ? std::to_string(cell_file_block->offset()) : "<null>")
1651
1
                << " cell_block_size="
1652
1
                << (cell_file_block ? std::to_string(cell_file_block->range().size()) : "<null>")
1653
1
                << " cell_block_type="
1654
1
                << (cell_file_block ? cache_type_to_string(cell_file_block->cache_type())
1655
1
                                    : "<null>")
1656
1
                << " cell_block_state="
1657
1
                << (cell_file_block ? FileBlock::state_to_string(cell_file_block->state_unsafe())
1658
1
                                    : "<null>");
1659
1
        return;
1660
1
    }
1661
108k
    DCHECK(cell->queue_iterator);
1662
108k
    if (cell->queue_iterator) {
1663
108k
        auto& queue = get_queue(file_block->cache_type());
1664
108k
        queue.remove(*cell->queue_iterator, cache_lock);
1665
108k
        _lru_recorder->record_queue_event(file_block->cache_type(), CacheLRULogType::REMOVE,
1666
108k
                                          cell->file_block->get_hash_value(),
1667
108k
                                          cell->file_block->offset(), cell->size());
1668
108k
    }
1669
108k
    *_queue_evict_size_metrics[file_cache_type_index(file_block->cache_type())]
1670
108k
            << file_block->range().size();
1671
108k
    *_total_evict_size_metrics << file_block->range().size();
1672
1673
108k
    VLOG_DEBUG << "Removing file block from cache. hash: " << hash.to_string()
1674
0
               << ", offset: " << offset << ", size: " << file_block->range().size()
1675
0
               << ", type: " << cache_type_to_string(type);
1676
1677
108k
    if (file_block->state_unlock(block_lock) == FileBlock::State::DOWNLOADED) {
1678
106k
        FileCacheKey key;
1679
106k
        key.hash = hash;
1680
106k
        key.offset = offset;
1681
106k
        key.meta.type = type;
1682
106k
        key.meta.expiration_time = expiration_time;
1683
106k
        key.meta.tablet_id = tablet_id;
1684
106k
        if (sync) {
1685
106k
            int64_t duration_ns = 0;
1686
106k
            Status st;
1687
106k
            {
1688
106k
                SCOPED_RAW_TIMER(&duration_ns);
1689
106k
                st = _storage->remove(key);
1690
106k
            }
1691
106k
            *_storage_sync_remove_latency_us << (duration_ns / 1000);
1692
106k
            if (!st.ok()) {
1693
0
                LOG_WARNING("").error(st);
1694
0
            }
1695
106k
        } else {
1696
            // the file will be deleted in the bottom half
1697
            // so there will be a window that the file is not in the cache but still in the storage
1698
            // but it's ok, because the rowset is stale already
1699
49
            bool ret = _recycle_keys.enqueue(key);
1700
49
            if (ret) [[likely]] {
1701
49
                *_recycle_keys_length_recorder << _recycle_keys.size_approx();
1702
49
            } else {
1703
0
                LOG_WARNING("Failed to push recycle key to queue, do it synchronously");
1704
0
                int64_t duration_ns = 0;
1705
0
                Status st;
1706
0
                {
1707
0
                    SCOPED_RAW_TIMER(&duration_ns);
1708
0
                    st = _storage->remove(key);
1709
0
                }
1710
0
                *_storage_retry_sync_remove_latency_us << (duration_ns / 1000);
1711
0
                if (!st.ok()) {
1712
0
                    LOG_WARNING("").error(st);
1713
0
                }
1714
0
            }
1715
49
        }
1716
106k
    } else if (file_block->state_unlock(block_lock) == FileBlock::State::DOWNLOADING) {
1717
100
        file_block->set_deleting();
1718
100
        return;
1719
100
    }
1720
108k
    _cur_cache_size -= file_block->range().size();
1721
108k
    if (FileCacheType::TTL == type) {
1722
1.29k
        _cur_ttl_size -= file_block->range().size();
1723
1.29k
    }
1724
108k
    auto it = _files.find(hash);
1725
108k
    if (it != _files.end()) {
1726
108k
        it->second.erase(file_block->offset());
1727
108k
        if (it->second.empty()) {
1728
6.47k
            _files.erase(hash);
1729
6.47k
        }
1730
108k
    }
1731
108k
    *_num_removed_blocks << 1;
1732
108k
}
1733
1734
46
size_t BlockFileCache::get_used_cache_size(FileCacheType cache_type) const {
1735
46
    SCOPED_CACHE_LOCK(_mutex, this);
1736
46
    return get_used_cache_size_unlocked(cache_type, cache_lock);
1737
46
}
1738
1739
size_t BlockFileCache::get_used_cache_size_unlocked(FileCacheType cache_type,
1740
46
                                                    std::lock_guard<std::mutex>& cache_lock) const {
1741
46
    return get_queue(cache_type).get_capacity(cache_lock);
1742
46
}
1743
1744
0
size_t BlockFileCache::get_available_cache_size(FileCacheType cache_type) const {
1745
0
    SCOPED_CACHE_LOCK(_mutex, this);
1746
0
    return get_available_cache_size_unlocked(cache_type, cache_lock);
1747
0
}
1748
1749
size_t BlockFileCache::get_available_cache_size_unlocked(
1750
0
        FileCacheType cache_type, std::lock_guard<std::mutex>& cache_lock) const {
1751
0
    return get_queue(cache_type).get_max_element_size() -
1752
0
           get_used_cache_size_unlocked(cache_type, cache_lock);
1753
0
}
1754
1755
106
size_t BlockFileCache::get_file_blocks_num(FileCacheType cache_type) const {
1756
106
    SCOPED_CACHE_LOCK(_mutex, this);
1757
106
    return get_file_blocks_num_unlocked(cache_type, cache_lock);
1758
106
}
1759
1760
size_t BlockFileCache::get_file_blocks_num_unlocked(FileCacheType cache_type,
1761
106
                                                    std::lock_guard<std::mutex>& cache_lock) const {
1762
106
    return get_queue(cache_type).get_elements_num(cache_lock);
1763
106
}
1764
1765
FileBlockCell::FileBlockCell(FileBlockSPtr file_block, std::lock_guard<std::mutex>& cache_lock)
1766
113k
        : file_block(file_block) {
1767
113k
    file_block->cell = this;
1768
    /**
1769
     * Cell can be created with either DOWNLOADED or EMPTY file block's state.
1770
     * File block acquires DOWNLOADING state and creates LRUQueue iterator on first
1771
     * successful getOrSetDownaloder call.
1772
     */
1773
1774
113k
    switch (file_block->_download_state) {
1775
100k
    case FileBlock::State::DOWNLOADED:
1776
113k
    case FileBlock::State::EMPTY:
1777
113k
    case FileBlock::State::SKIP_CACHE: {
1778
113k
        break;
1779
113k
    }
1780
0
    default:
1781
0
        DCHECK(false) << "Can create cell with either EMPTY, DOWNLOADED, SKIP_CACHE state, got: "
1782
0
                      << FileBlock::state_to_string(file_block->_download_state);
1783
113k
    }
1784
113k
    if (file_block->cache_type() == FileCacheType::TTL) {
1785
3.92k
        update_atime();
1786
3.92k
    }
1787
113k
}
1788
1789
LRUQueue::Iterator LRUQueue::add(const UInt128Wrapper& hash, size_t offset, size_t size,
1790
226k
                                 std::lock_guard<std::mutex>& /* cache_lock */) {
1791
226k
    cache_size += size;
1792
226k
    auto iter = queue.insert(queue.end(), FileKeyAndOffset(hash, offset, size));
1793
226k
    map.insert(std::make_pair(std::make_pair(hash, offset), iter));
1794
226k
    return iter;
1795
226k
}
1796
1797
0
void LRUQueue::remove_all(std::lock_guard<std::mutex>& /* cache_lock */) {
1798
0
    queue.clear();
1799
0
    map.clear();
1800
0
    cache_size = 0;
1801
0
}
1802
1803
15
bool LRUQueue::pop_front(std::lock_guard<std::mutex>& /* cache_lock */) {
1804
15
    if (queue.empty()) {
1805
0
        return false;
1806
0
    }
1807
15
    auto queue_it = queue.begin();
1808
15
    cache_size -= queue_it->size;
1809
15
    map.erase(std::make_pair(queue_it->hash, queue_it->offset));
1810
15
    queue.erase(queue_it);
1811
15
    return true;
1812
15
}
1813
1814
1.03M
void LRUQueue::move_to_end(Iterator queue_it, std::lock_guard<std::mutex>& /* cache_lock */) {
1815
1.03M
    queue.splice(queue.end(), queue, queue_it);
1816
1.03M
}
1817
1818
void LRUQueue::resize(Iterator queue_it, size_t new_size,
1819
19
                      std::lock_guard<std::mutex>& /* cache_lock */) {
1820
19
    cache_size -= queue_it->size;
1821
19
    queue_it->size = new_size;
1822
19
    cache_size += new_size;
1823
19
}
1824
bool LRUQueue::contains(const UInt128Wrapper& hash, size_t offset,
1825
10
                        std::lock_guard<std::mutex>& /* cache_lock */) const {
1826
10
    return map.find(std::make_pair(hash, offset)) != map.end();
1827
10
}
1828
1829
LRUQueue::Iterator LRUQueue::get(const UInt128Wrapper& hash, size_t offset,
1830
629k
                                 std::lock_guard<std::mutex>& /* cache_lock */) const {
1831
629k
    auto itr = map.find(std::make_pair(hash, offset));
1832
629k
    if (itr != map.end()) {
1833
629k
        return itr->second;
1834
629k
    }
1835
127
    return std::list<FileKeyAndOffset>::iterator();
1836
629k
}
1837
1838
2
std::string LRUQueue::to_string(std::lock_guard<std::mutex>& /* cache_lock */) const {
1839
2
    std::string result;
1840
18
    for (const auto& [hash, offset, size] : queue) {
1841
18
        if (!result.empty()) {
1842
16
            result += ", ";
1843
16
        }
1844
18
        result += fmt::format("{}: [{}, {}]", hash.to_string(), offset, offset + size - 1);
1845
18
    }
1846
2
    return result;
1847
2
}
1848
1849
size_t LRUQueue::levenshtein_distance_from(LRUQueue& base,
1850
5
                                           std::lock_guard<std::mutex>& cache_lock) {
1851
5
    std::list<FileKeyAndOffset> target_queue = this->queue;
1852
5
    std::list<FileKeyAndOffset> base_queue = base.queue;
1853
5
    std::vector<FileKeyAndOffset> vec1(target_queue.begin(), target_queue.end());
1854
5
    std::vector<FileKeyAndOffset> vec2(base_queue.begin(), base_queue.end());
1855
1856
5
    size_t m = vec1.size();
1857
5
    size_t n = vec2.size();
1858
1859
    // Create a 2D vector (matrix) to store the Levenshtein distances
1860
    // dp[i][j] will hold the distance between the first i elements of vec1 and the first j elements of vec2
1861
5
    std::vector<std::vector<size_t>> dp(m + 1, std::vector<size_t>(n + 1, 0));
1862
1863
    // Initialize the first row and column of the matrix
1864
    // The distance between an empty list and a list of length k is k (all insertions or deletions)
1865
22
    for (size_t i = 0; i <= m; ++i) {
1866
17
        dp[i][0] = i;
1867
17
    }
1868
20
    for (size_t j = 0; j <= n; ++j) {
1869
15
        dp[0][j] = j;
1870
15
    }
1871
1872
    // Fill the matrix using dynamic programming
1873
17
    for (size_t i = 1; i <= m; ++i) {
1874
38
        for (size_t j = 1; j <= n; ++j) {
1875
            // Check if the current elements of both vectors are equal
1876
26
            size_t cost = (vec1[i - 1].hash == vec2[j - 1].hash &&
1877
26
                           vec1[i - 1].offset == vec2[j - 1].offset)
1878
26
                                  ? 0
1879
26
                                  : 1;
1880
            // Calculate the minimum cost of three possible operations:
1881
            // 1. Insertion: dp[i][j-1] + 1
1882
            // 2. Deletion: dp[i-1][j] + 1
1883
            // 3. Substitution: dp[i-1][j-1] + cost (0 if elements are equal, 1 if not)
1884
26
            dp[i][j] = std::min({dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + cost});
1885
26
        }
1886
12
    }
1887
    // The bottom-right cell of the matrix contains the Levenshtein distance
1888
5
    return dp[m][n];
1889
5
}
1890
1891
1
std::string BlockFileCache::dump_structure(const UInt128Wrapper& hash) {
1892
1
    SCOPED_CACHE_LOCK(_mutex, this);
1893
1
    return dump_structure_unlocked(hash, cache_lock);
1894
1
}
1895
1896
std::string BlockFileCache::dump_structure_unlocked(const UInt128Wrapper& hash,
1897
1
                                                    std::lock_guard<std::mutex>&) {
1898
1
    std::stringstream result;
1899
1
    auto it = _files.find(hash);
1900
1
    if (it == _files.end()) {
1901
0
        return std::string("");
1902
0
    }
1903
1
    const auto& cells_by_offset = it->second;
1904
1905
1
    for (const auto& [_, cell] : cells_by_offset) {
1906
1
        result << cell.file_block->get_info_for_log() << " "
1907
1
               << cache_type_to_string(cell.file_block->cache_type()) << "\n";
1908
1
    }
1909
1910
1
    return result.str();
1911
1
}
1912
1913
0
std::string BlockFileCache::dump_single_cache_type(const UInt128Wrapper& hash, size_t offset) {
1914
0
    SCOPED_CACHE_LOCK(_mutex, this);
1915
0
    return dump_single_cache_type_unlocked(hash, offset, cache_lock);
1916
0
}
1917
1918
std::string BlockFileCache::dump_single_cache_type_unlocked(const UInt128Wrapper& hash,
1919
                                                            size_t offset,
1920
0
                                                            std::lock_guard<std::mutex>&) {
1921
0
    std::stringstream result;
1922
0
    auto it = _files.find(hash);
1923
0
    if (it == _files.end()) {
1924
0
        return std::string("");
1925
0
    }
1926
0
    const auto& cells_by_offset = it->second;
1927
0
    const auto& cell = cells_by_offset.find(offset);
1928
1929
0
    return cache_type_to_string(cell->second.file_block->cache_type());
1930
0
}
1931
1932
void BlockFileCache::change_cache_type(const UInt128Wrapper& hash, size_t offset,
1933
                                       FileCacheType new_type,
1934
9
                                       std::lock_guard<std::mutex>& cache_lock) {
1935
9
    if (auto iter = _files.find(hash); iter != _files.end()) {
1936
9
        auto& file_blocks = iter->second;
1937
9
        if (auto cell_it = file_blocks.find(offset); cell_it != file_blocks.end()) {
1938
8
            FileBlockCell& cell = cell_it->second;
1939
8
            auto& cur_queue = get_queue(cell.file_block->cache_type());
1940
8
            DCHECK(cell.queue_iterator.has_value());
1941
8
            cur_queue.remove(*cell.queue_iterator, cache_lock);
1942
8
            _lru_recorder->record_queue_event(
1943
8
                    cell.file_block->cache_type(), CacheLRULogType::REMOVE,
1944
8
                    cell.file_block->get_hash_value(), cell.file_block->offset(), cell.size());
1945
8
            auto& new_queue = get_queue(new_type);
1946
8
            cell.queue_iterator =
1947
8
                    new_queue.add(hash, offset, cell.file_block->range().size(), cache_lock);
1948
8
            _lru_recorder->record_queue_event(new_type, CacheLRULogType::ADD,
1949
8
                                              cell.file_block->get_hash_value(),
1950
8
                                              cell.file_block->offset(), cell.size());
1951
8
        }
1952
9
    }
1953
9
}
1954
1955
// @brief: get a path's disk capacity used percent, inode used percent
1956
// @param: path
1957
// @param: percent.first disk used percent, percent.second inode used percent
1958
225
int disk_used_percentage(const std::string& path, std::pair<int, int>* percent) {
1959
225
    struct statfs stat;
1960
225
    int ret = statfs(path.c_str(), &stat);
1961
225
    if (ret != 0) {
1962
2
        return ret;
1963
2
    }
1964
    // https://github.com/coreutils/coreutils/blob/master/src/df.c#L1195
1965
    // v->used = stat.f_blocks - stat.f_bfree
1966
    // nonroot_total = stat.f_blocks - stat.f_bfree + stat.f_bavail
1967
223
    uintmax_t u100 = (stat.f_blocks - stat.f_bfree) * 100;
1968
223
    uintmax_t nonroot_total = stat.f_blocks - stat.f_bfree + stat.f_bavail;
1969
223
    int capacity_percentage = int(u100 / nonroot_total + (u100 % nonroot_total != 0));
1970
1971
223
    unsigned long long inode_free = stat.f_ffree;
1972
223
    unsigned long long inode_total = stat.f_files;
1973
223
    int inode_percentage = cast_set<int>(inode_free * 100 / inode_total);
1974
223
    percent->first = capacity_percentage;
1975
223
    percent->second = 100 - inode_percentage;
1976
1977
    // Add sync point for testing
1978
223
    TEST_SYNC_POINT_CALLBACK("BlockFileCache::disk_used_percentage:1", percent);
1979
1980
223
    return 0;
1981
225
}
1982
1983
10
std::string BlockFileCache::reset_capacity(size_t new_capacity) {
1984
10
    using namespace std::chrono;
1985
10
    int64_t space_released = 0;
1986
10
    size_t old_capacity = 0;
1987
10
    std::stringstream ss;
1988
10
    ss << "finish reset_capacity, path=" << _cache_base_path;
1989
10
    auto adjust_start_time = steady_clock::time_point();
1990
10
    {
1991
10
        SCOPED_CACHE_LOCK(_mutex, this);
1992
10
        if (new_capacity < _capacity && new_capacity < _cur_cache_size) {
1993
1
            int64_t need_remove_size = _cur_cache_size - new_capacity;
1994
4
            auto remove_blocks = [&](LRUQueue& queue) -> int64_t {
1995
4
                int64_t queue_released = 0;
1996
4
                std::vector<FileBlockCell*> to_evict;
1997
13
                for (const auto& [entry_key, entry_offset, entry_size] : queue) {
1998
13
                    if (need_remove_size <= 0) {
1999
1
                        break;
2000
1
                    }
2001
12
                    need_remove_size -= entry_size;
2002
12
                    space_released += entry_size;
2003
12
                    queue_released += entry_size;
2004
12
                    auto* cell = get_cell(entry_key, entry_offset, cache_lock);
2005
12
                    if (!cell->releasable()) {
2006
0
                        cell->file_block->set_deleting();
2007
0
                        continue;
2008
0
                    }
2009
12
                    to_evict.push_back(cell);
2010
12
                }
2011
12
                for (auto& cell : to_evict) {
2012
12
                    FileBlockSPtr file_block = cell->file_block;
2013
12
                    std::lock_guard block_lock(file_block->_mutex);
2014
12
                    remove(file_block, cache_lock, block_lock);
2015
12
                }
2016
4
                return queue_released;
2017
4
            };
2018
1
            int64_t queue_released = remove_blocks(_disposable_queue);
2019
1
            ss << " disposable_queue released " << queue_released;
2020
1
            queue_released = remove_blocks(_normal_queue);
2021
1
            ss << " normal_queue released " << queue_released;
2022
1
            queue_released = remove_blocks(_index_queue);
2023
1
            ss << " index_queue released " << queue_released;
2024
1
            queue_released = remove_blocks(_ttl_queue);
2025
1
            ss << " ttl_queue released " << queue_released;
2026
2027
1
            _disk_resource_limit_mode = true;
2028
1
            _disk_limit_mode_metrics->set_value(1);
2029
1
            ss << " total_space_released=" << space_released;
2030
1
        }
2031
10
        old_capacity = _capacity;
2032
10
        _capacity = new_capacity;
2033
10
        _cache_capacity_metrics->set_value(_capacity);
2034
10
    }
2035
10
    auto use_time = duration_cast<milliseconds>(steady_clock::time_point() - adjust_start_time);
2036
10
    LOG(INFO) << "Finish tag deleted block. path=" << _cache_base_path
2037
10
              << " use_time=" << cast_set<int64_t>(use_time.count());
2038
10
    ss << " old_capacity=" << old_capacity << " new_capacity=" << new_capacity;
2039
10
    LOG(INFO) << ss.str();
2040
10
    return ss.str();
2041
10
}
2042
2043
242
void BlockFileCache::check_disk_resource_limit() {
2044
242
    if (_storage->get_type() != FileCacheStorageType::DISK) {
2045
32
        return;
2046
32
    }
2047
2048
210
    bool previous_mode = _disk_resource_limit_mode;
2049
210
    if (_capacity > _cur_cache_size) {
2050
208
        _disk_resource_limit_mode = false;
2051
208
        _disk_limit_mode_metrics->set_value(0);
2052
208
    }
2053
210
    std::pair<int, int> percent;
2054
210
    int ret = disk_used_percentage(_cache_base_path, &percent);
2055
210
    if (ret != 0) {
2056
1
        LOG_ERROR("").tag("file cache path", _cache_base_path).tag("error", strerror(errno));
2057
1
        return;
2058
1
    }
2059
209
    auto [space_percentage, inode_percentage] = percent;
2060
418
    auto is_insufficient = [](const int& percentage) {
2061
418
        return percentage >= config::file_cache_enter_disk_resource_limit_mode_percent;
2062
418
    };
2063
209
    DCHECK_GE(space_percentage, 0);
2064
209
    DCHECK_LE(space_percentage, 100);
2065
209
    DCHECK_GE(inode_percentage, 0);
2066
209
    DCHECK_LE(inode_percentage, 100);
2067
    // ATTN: due to that can be changed dynamically, set it to default value if it's invalid
2068
    // FIXME: reject with config validator
2069
209
    if (config::file_cache_enter_disk_resource_limit_mode_percent <
2070
209
        config::file_cache_exit_disk_resource_limit_mode_percent) {
2071
1
        LOG_WARNING("config error, set to default value")
2072
1
                .tag("enter", config::file_cache_enter_disk_resource_limit_mode_percent)
2073
1
                .tag("exit", config::file_cache_exit_disk_resource_limit_mode_percent);
2074
1
        config::file_cache_enter_disk_resource_limit_mode_percent = 88;
2075
1
        config::file_cache_exit_disk_resource_limit_mode_percent = 80;
2076
1
    }
2077
209
    bool is_space_insufficient = is_insufficient(space_percentage);
2078
209
    bool is_inode_insufficient = is_insufficient(inode_percentage);
2079
209
    if (is_space_insufficient || is_inode_insufficient) {
2080
1
        _disk_resource_limit_mode = true;
2081
1
        _disk_limit_mode_metrics->set_value(1);
2082
208
    } else if (_disk_resource_limit_mode &&
2083
208
               (space_percentage < config::file_cache_exit_disk_resource_limit_mode_percent) &&
2084
208
               (inode_percentage < config::file_cache_exit_disk_resource_limit_mode_percent)) {
2085
0
        _disk_resource_limit_mode = false;
2086
0
        _disk_limit_mode_metrics->set_value(0);
2087
0
    }
2088
209
    if (previous_mode != _disk_resource_limit_mode) {
2089
        // add log for disk resource limit mode switching
2090
2
        if (_disk_resource_limit_mode) {
2091
1
            LOG(WARNING) << "Entering disk resource limit mode: file_cache=" << get_base_path()
2092
1
                         << " space_percent=" << space_percentage
2093
1
                         << " inode_percent=" << inode_percentage
2094
1
                         << " is_space_insufficient=" << is_space_insufficient
2095
1
                         << " is_inode_insufficient=" << is_inode_insufficient
2096
1
                         << " enter threshold="
2097
1
                         << config::file_cache_enter_disk_resource_limit_mode_percent;
2098
1
        } else {
2099
1
            LOG(INFO) << "Exiting disk resource limit mode: file_cache=" << get_base_path()
2100
1
                      << " space_percent=" << space_percentage
2101
1
                      << " inode_percent=" << inode_percentage << " exit threshold="
2102
1
                      << config::file_cache_exit_disk_resource_limit_mode_percent;
2103
1
        }
2104
207
    } else if (_disk_resource_limit_mode) {
2105
        // print log for disk resource limit mode running, but less frequently
2106
0
        LOG_EVERY_N(WARNING, 10) << "file_cache=" << get_base_path()
2107
0
                                 << " space_percent=" << space_percentage
2108
0
                                 << " inode_percent=" << inode_percentage
2109
0
                                 << " is_space_insufficient=" << is_space_insufficient
2110
0
                                 << " is_inode_insufficient=" << is_inode_insufficient
2111
0
                                 << " mode run in resource limit";
2112
0
    }
2113
209
}
2114
2115
16
void BlockFileCache::check_need_evict_cache_in_advance() {
2116
16
    if (_storage->get_type() != FileCacheStorageType::DISK) {
2117
1
        return;
2118
1
    }
2119
2120
15
    std::pair<int, int> percent;
2121
15
    int ret = disk_used_percentage(_cache_base_path, &percent);
2122
15
    if (ret != 0) {
2123
1
        LOG_ERROR("").tag("file cache path", _cache_base_path).tag("error", strerror(errno));
2124
1
        return;
2125
1
    }
2126
14
    auto [space_percentage, inode_percentage] = percent;
2127
14
    int size_percentage = static_cast<int>(_cur_cache_size * 100 / _capacity);
2128
42
    auto is_insufficient = [](const int& percentage) {
2129
42
        return percentage >= config::file_cache_enter_need_evict_cache_in_advance_percent;
2130
42
    };
2131
14
    DCHECK_GE(space_percentage, 0);
2132
14
    DCHECK_LE(space_percentage, 100);
2133
14
    DCHECK_GE(inode_percentage, 0);
2134
14
    DCHECK_LE(inode_percentage, 100);
2135
    // ATTN: due to that can be changed dynamically, set it to default value if it's invalid
2136
    // FIXME: reject with config validator
2137
14
    if (config::file_cache_enter_need_evict_cache_in_advance_percent <=
2138
14
        config::file_cache_exit_need_evict_cache_in_advance_percent) {
2139
1
        LOG_WARNING("config error, set to default value")
2140
1
                .tag("enter", config::file_cache_enter_need_evict_cache_in_advance_percent)
2141
1
                .tag("exit", config::file_cache_exit_need_evict_cache_in_advance_percent);
2142
1
        config::file_cache_enter_need_evict_cache_in_advance_percent = 78;
2143
1
        config::file_cache_exit_need_evict_cache_in_advance_percent = 75;
2144
1
    }
2145
14
    bool previous_mode = _need_evict_cache_in_advance;
2146
14
    bool is_space_insufficient = is_insufficient(space_percentage);
2147
14
    bool is_inode_insufficient = is_insufficient(inode_percentage);
2148
14
    bool is_size_insufficient = is_insufficient(size_percentage);
2149
14
    if (is_space_insufficient || is_inode_insufficient || is_size_insufficient) {
2150
9
        _need_evict_cache_in_advance = true;
2151
9
        _need_evict_cache_in_advance_metrics->set_value(1);
2152
9
    } else if (_need_evict_cache_in_advance &&
2153
5
               (space_percentage < config::file_cache_exit_need_evict_cache_in_advance_percent) &&
2154
5
               (inode_percentage < config::file_cache_exit_need_evict_cache_in_advance_percent) &&
2155
5
               (size_percentage < config::file_cache_exit_need_evict_cache_in_advance_percent)) {
2156
1
        _need_evict_cache_in_advance = false;
2157
1
        _need_evict_cache_in_advance_metrics->set_value(0);
2158
1
    }
2159
14
    if (previous_mode != _need_evict_cache_in_advance) {
2160
        // add log for evict cache in advance mode switching
2161
6
        if (_need_evict_cache_in_advance) {
2162
5
            LOG(WARNING) << "Entering evict cache in advance mode: "
2163
5
                         << "file_cache=" << get_base_path()
2164
5
                         << " space_percent=" << space_percentage
2165
5
                         << " inode_percent=" << inode_percentage
2166
5
                         << " size_percent=" << size_percentage
2167
5
                         << " is_space_insufficient=" << is_space_insufficient
2168
5
                         << " is_inode_insufficient=" << is_inode_insufficient
2169
5
                         << " is_size_insufficient=" << is_size_insufficient << " enter threshold="
2170
5
                         << config::file_cache_enter_need_evict_cache_in_advance_percent;
2171
5
        } else {
2172
1
            LOG(INFO) << "Exiting evict cache in advance mode: "
2173
1
                      << "file_cache=" << get_base_path() << " space_percent=" << space_percentage
2174
1
                      << " inode_percent=" << inode_percentage
2175
1
                      << " size_percent=" << size_percentage << " exit threshold="
2176
1
                      << config::file_cache_exit_need_evict_cache_in_advance_percent;
2177
1
        }
2178
8
    } else if (_need_evict_cache_in_advance) {
2179
        // print log for evict cache in advance mode running, but less frequently
2180
4
        LOG_EVERY_N(WARNING, 10) << "file_cache=" << get_base_path()
2181
1
                                 << " space_percent=" << space_percentage
2182
1
                                 << " inode_percent=" << inode_percentage
2183
1
                                 << " size_percent=" << size_percentage
2184
1
                                 << " is_space_insufficient=" << is_space_insufficient
2185
1
                                 << " is_inode_insufficient=" << is_inode_insufficient
2186
1
                                 << " is_size_insufficient=" << is_size_insufficient
2187
1
                                 << " need evict cache in advance";
2188
4
    }
2189
14
}
2190
2191
231
void BlockFileCache::run_background_monitor() {
2192
231
    Thread::set_self_name("run_background_monitor");
2193
242
    while (!_close) {
2194
242
        int64_t interval_ms = config::file_cache_background_monitor_interval_ms;
2195
242
        TEST_SYNC_POINT_CALLBACK("BlockFileCache::set_sleep_time", &interval_ms);
2196
242
        check_disk_resource_limit();
2197
242
        if (config::enable_evict_file_cache_in_advance) {
2198
8
            check_need_evict_cache_in_advance();
2199
234
        } else {
2200
234
            _need_evict_cache_in_advance = false;
2201
234
            _need_evict_cache_in_advance_metrics->set_value(0);
2202
234
        }
2203
2204
242
        {
2205
242
            std::unique_lock close_lock(_close_mtx);
2206
242
            _close_cv.wait_for(close_lock, std::chrono::milliseconds(interval_ms));
2207
242
            if (_close) {
2208
231
                break;
2209
231
            }
2210
242
        }
2211
        // report
2212
11
        {
2213
11
            SCOPED_CACHE_LOCK(_mutex, this);
2214
11
            _cur_cache_size_metrics->set_value(_cur_cache_size);
2215
11
            _cur_ttl_cache_size_metrics->set_value(_cur_cache_size -
2216
11
                                                   _index_queue.get_capacity(cache_lock) -
2217
11
                                                   _normal_queue.get_capacity(cache_lock) -
2218
11
                                                   _disposable_queue.get_capacity(cache_lock));
2219
11
            _cur_ttl_cache_lru_queue_cache_size_metrics->set_value(
2220
11
                    _ttl_queue.get_capacity(cache_lock));
2221
11
            _cur_ttl_cache_lru_queue_element_count_metrics->set_value(
2222
11
                    _ttl_queue.get_elements_num(cache_lock));
2223
11
            _cur_normal_queue_cache_size_metrics->set_value(_normal_queue.get_capacity(cache_lock));
2224
11
            _cur_normal_queue_element_count_metrics->set_value(
2225
11
                    _normal_queue.get_elements_num(cache_lock));
2226
11
            _cur_index_queue_cache_size_metrics->set_value(_index_queue.get_capacity(cache_lock));
2227
11
            _cur_index_queue_element_count_metrics->set_value(
2228
11
                    _index_queue.get_elements_num(cache_lock));
2229
11
            _cur_disposable_queue_cache_size_metrics->set_value(
2230
11
                    _disposable_queue.get_capacity(cache_lock));
2231
11
            _cur_disposable_queue_element_count_metrics->set_value(
2232
11
                    _disposable_queue.get_elements_num(cache_lock));
2233
2234
            // Update meta store write queue size if storage is FSFileCacheStorage
2235
11
            if (_storage->get_type() == FileCacheStorageType::DISK) {
2236
10
                auto* fs_storage = dynamic_cast<FSFileCacheStorage*>(_storage.get());
2237
10
                if (fs_storage != nullptr) {
2238
10
                    auto* meta_store = fs_storage->get_meta_store();
2239
10
                    if (meta_store != nullptr) {
2240
10
                        _meta_store_write_queue_size_metrics->set_value(
2241
10
                                meta_store->get_write_queue_size());
2242
10
                    }
2243
10
                }
2244
10
            }
2245
2246
11
            if (_num_read_blocks->get_value() > 0) {
2247
11
                _hit_ratio->set_value((double)_num_hit_blocks->get_value() /
2248
11
                                      (double)_num_read_blocks->get_value());
2249
11
            }
2250
11
            if (_num_read_blocks_5m && _num_read_blocks_5m->get_value() > 0) {
2251
0
                _hit_ratio_5m->set_value((double)_num_hit_blocks_5m->get_value() /
2252
0
                                         (double)_num_read_blocks_5m->get_value());
2253
0
            }
2254
11
            if (_num_read_blocks_1h && _num_read_blocks_1h->get_value() > 0) {
2255
0
                _hit_ratio_1h->set_value((double)_num_hit_blocks_1h->get_value() /
2256
0
                                         (double)_num_read_blocks_1h->get_value());
2257
0
            }
2258
2259
11
            if (_no_warmup_num_read_blocks->get_value() > 0) {
2260
11
                _no_warmup_hit_ratio->set_value((double)_no_warmup_num_hit_blocks->get_value() /
2261
11
                                                (double)_no_warmup_num_read_blocks->get_value());
2262
11
            }
2263
11
            if (_no_warmup_num_read_blocks_5m && _no_warmup_num_read_blocks_5m->get_value() > 0) {
2264
0
                _no_warmup_hit_ratio_5m->set_value(
2265
0
                        (double)_no_warmup_num_hit_blocks_5m->get_value() /
2266
0
                        (double)_no_warmup_num_read_blocks_5m->get_value());
2267
0
            }
2268
11
            if (_no_warmup_num_read_blocks_1h && _no_warmup_num_read_blocks_1h->get_value() > 0) {
2269
0
                _no_warmup_hit_ratio_1h->set_value(
2270
0
                        (double)_no_warmup_num_hit_blocks_1h->get_value() /
2271
0
                        (double)_no_warmup_num_read_blocks_1h->get_value());
2272
0
            }
2273
11
        }
2274
11
        _lru_recorder->update_shadow_queue_element_count_metrics();
2275
11
    }
2276
231
}
2277
2278
231
void BlockFileCache::run_background_gc() {
2279
231
    Thread::set_self_name("run_background_gc");
2280
231
    FileCacheKey key;
2281
231
    size_t batch_count = 0;
2282
1.34k
    while (!_close) {
2283
1.34k
        int64_t interval_ms = config::file_cache_background_gc_interval_ms;
2284
1.34k
        size_t batch_limit = config::file_cache_remove_block_qps_limit * interval_ms / 1000;
2285
1.34k
        {
2286
1.34k
            std::unique_lock close_lock(_close_mtx);
2287
1.34k
            _close_cv.wait_for(close_lock, std::chrono::milliseconds(interval_ms));
2288
1.34k
            if (_close) {
2289
231
                break;
2290
231
            }
2291
1.34k
        }
2292
2293
1.11k
        while (batch_count < batch_limit && _recycle_keys.try_dequeue(key)) {
2294
9
            int64_t duration_ns = 0;
2295
9
            Status st;
2296
9
            {
2297
9
                SCOPED_RAW_TIMER(&duration_ns);
2298
9
                st = _storage->remove(key);
2299
9
            }
2300
9
            *_storage_async_remove_latency_us << (duration_ns / 1000);
2301
2302
9
            if (!st.ok()) {
2303
0
                LOG_WARNING("").error(st);
2304
0
            }
2305
9
            batch_count++;
2306
9
        }
2307
1.11k
        *_recycle_keys_length_recorder << _recycle_keys.size_approx();
2308
1.11k
        batch_count = 0;
2309
1.11k
    }
2310
231
}
2311
2312
231
void BlockFileCache::run_background_evict_in_advance() {
2313
231
    Thread::set_self_name("run_background_evict_in_advance");
2314
231
    LOG(INFO) << "Starting background evict in advance thread";
2315
231
    int64_t batch = 0;
2316
343
    while (!_close) {
2317
343
        {
2318
343
            std::unique_lock close_lock(_close_mtx);
2319
343
            _close_cv.wait_for(
2320
343
                    close_lock,
2321
343
                    std::chrono::milliseconds(config::file_cache_evict_in_advance_interval_ms));
2322
343
            if (_close) {
2323
231
                LOG(INFO) << "Background evict in advance thread exiting due to cache closing";
2324
231
                break;
2325
231
            }
2326
343
        }
2327
112
        batch = config::file_cache_evict_in_advance_batch_bytes;
2328
2329
        // Skip if eviction not needed or too many pending recycles
2330
112
        if (!_need_evict_cache_in_advance ||
2331
112
            _recycle_keys.size_approx() >=
2332
108
                    config::file_cache_evict_in_advance_recycle_keys_num_threshold) {
2333
108
            continue;
2334
108
        }
2335
2336
4
        int64_t duration_ns = 0;
2337
4
        {
2338
4
            SCOPED_CACHE_LOCK(_mutex, this);
2339
4
            SCOPED_RAW_TIMER(&duration_ns);
2340
4
            try_evict_in_advance(batch, cache_lock);
2341
4
        }
2342
4
        *_evict_in_advance_latency_us << (duration_ns / 1000);
2343
4
    }
2344
231
}
2345
2346
231
void BlockFileCache::run_background_block_lru_update() {
2347
231
    Thread::set_self_name("run_background_block_lru_update");
2348
231
    std::vector<FileBlockSPtr> batch;
2349
269
    while (!_close) {
2350
269
        int64_t interval_ms = config::file_cache_background_block_lru_update_interval_ms;
2351
269
        size_t batch_limit =
2352
269
                config::file_cache_background_block_lru_update_qps_limit * interval_ms / 1000;
2353
269
        {
2354
269
            std::unique_lock close_lock(_close_mtx);
2355
269
            _close_cv.wait_for(close_lock, std::chrono::milliseconds(interval_ms));
2356
269
            if (_close) {
2357
231
                break;
2358
231
            }
2359
269
        }
2360
2361
38
        batch.clear();
2362
38
        batch.reserve(batch_limit);
2363
38
        size_t drained = _need_update_lru_blocks.drain(batch_limit, &batch);
2364
38
        if (drained == 0) {
2365
33
            *_need_update_lru_blocks_length_recorder << _need_update_lru_blocks.size();
2366
33
            continue;
2367
33
        }
2368
5
        *_need_update_lru_blocks_consume_metrics << drained;
2369
2370
5
        int64_t duration_ns = 0;
2371
5
        {
2372
5
            SCOPED_CACHE_LOCK(_mutex, this);
2373
5
            SCOPED_RAW_TIMER(&duration_ns);
2374
5
            for (auto& block : batch) {
2375
5
                update_block_lru(block, cache_lock);
2376
5
            }
2377
5
        }
2378
5
        *_update_lru_blocks_latency_us << (duration_ns / 1000);
2379
5
        *_need_update_lru_blocks_length_recorder << _need_update_lru_blocks.size();
2380
5
    }
2381
231
}
2382
2383
std::vector<std::tuple<size_t, size_t, FileCacheType, uint64_t>>
2384
2
BlockFileCache::get_hot_blocks_meta(const UInt128Wrapper& hash) const {
2385
2
    int64_t cur_time = std::chrono::duration_cast<std::chrono::seconds>(
2386
2
                               std::chrono::steady_clock::now().time_since_epoch())
2387
2
                               .count();
2388
2
    SCOPED_CACHE_LOCK(_mutex, this);
2389
2
    std::vector<std::tuple<size_t, size_t, FileCacheType, uint64_t>> blocks_meta;
2390
2
    if (auto iter = _files.find(hash); iter != _files.end()) {
2391
5
        for (auto& pair : _files.find(hash)->second) {
2392
5
            const FileBlockCell* cell = &pair.second;
2393
5
            if (cell->file_block->cache_type() != FileCacheType::DISPOSABLE) {
2394
4
                if (cell->file_block->cache_type() == FileCacheType::TTL ||
2395
4
                    (cell->atime != 0 &&
2396
3
                     cur_time - cell->atime <
2397
3
                             get_queue(cell->file_block->cache_type()).get_hot_data_interval())) {
2398
3
                    blocks_meta.emplace_back(pair.first, cell->size(),
2399
3
                                             cell->file_block->cache_type(),
2400
3
                                             cell->file_block->expiration_time());
2401
3
                }
2402
4
            }
2403
5
        }
2404
2
    }
2405
2
    return blocks_meta;
2406
2
}
2407
2408
bool BlockFileCache::try_reserve_during_async_load(size_t size,
2409
10
                                                   std::lock_guard<std::mutex>& cache_lock) {
2410
10
    size_t removed_size = 0;
2411
10
    size_t normal_queue_size = _normal_queue.get_capacity(cache_lock);
2412
10
    size_t disposable_queue_size = _disposable_queue.get_capacity(cache_lock);
2413
10
    size_t index_queue_size = _index_queue.get_capacity(cache_lock);
2414
2415
10
    std::vector<FileBlockCell*> to_evict;
2416
10
    auto collect_eliminate_fragments = [&](LRUQueue& queue) {
2417
6
        for (const auto& [entry_key, entry_offset, entry_size] : queue) {
2418
6
            if (!_disk_resource_limit_mode || removed_size >= size) {
2419
5
                break;
2420
5
            }
2421
1
            auto* cell = get_cell(entry_key, entry_offset, cache_lock);
2422
2423
1
            DCHECK(cell) << "Cache became inconsistent. UInt128Wrapper: " << entry_key.to_string()
2424
0
                         << ", offset: " << entry_offset;
2425
2426
1
            size_t cell_size = cell->size();
2427
1
            DCHECK(entry_size == cell_size);
2428
2429
1
            if (cell->releasable()) {
2430
1
                auto& file_block = cell->file_block;
2431
2432
1
                std::lock_guard block_lock(file_block->_mutex);
2433
1
                DCHECK(file_block->_download_state == FileBlock::State::DOWNLOADED);
2434
1
                to_evict.push_back(cell);
2435
1
                removed_size += cell_size;
2436
1
            }
2437
1
        }
2438
5
    };
2439
10
    if (disposable_queue_size != 0) {
2440
0
        collect_eliminate_fragments(get_queue(FileCacheType::DISPOSABLE));
2441
0
    }
2442
10
    if (normal_queue_size != 0) {
2443
4
        collect_eliminate_fragments(get_queue(FileCacheType::NORMAL));
2444
4
    }
2445
10
    if (index_queue_size != 0) {
2446
1
        collect_eliminate_fragments(get_queue(FileCacheType::INDEX));
2447
1
    }
2448
10
    std::string reason = "async load";
2449
10
    remove_file_blocks(to_evict, cache_lock, true, reason);
2450
2451
10
    return !_disk_resource_limit_mode || removed_size >= size;
2452
10
}
2453
2454
24
void BlockFileCache::clear_need_update_lru_blocks() {
2455
24
    _need_update_lru_blocks.clear();
2456
24
    *_need_update_lru_blocks_length_recorder << _need_update_lru_blocks.size();
2457
24
}
2458
2459
5.16k
std::map<size_t, FileBlockSPtr> BlockFileCache::get_blocks_by_key(const UInt128Wrapper& hash) {
2460
5.16k
    std::map<size_t, FileBlockSPtr> offset_to_block;
2461
5.16k
    SCOPED_CACHE_LOCK(_mutex, this);
2462
5.16k
    if (_files.contains(hash)) {
2463
5.15k
        for (auto& [offset, cell] : _files[hash]) {
2464
5.15k
            if (cell.file_block->state() == FileBlock::State::DOWNLOADED) {
2465
5.15k
                cell.file_block->_owned_by_cached_reader = true;
2466
5.15k
                offset_to_block.emplace(offset, cell.file_block);
2467
5.15k
            }
2468
5.15k
        }
2469
5.13k
    }
2470
5.16k
    return offset_to_block;
2471
5.16k
}
2472
2473
0
void BlockFileCache::update_ttl_atime(const UInt128Wrapper& hash) {
2474
0
    SCOPED_CACHE_LOCK(_mutex, this);
2475
0
    if (auto iter = _files.find(hash); iter != _files.end()) {
2476
0
        for (auto& [_, cell] : iter->second) {
2477
0
            cell.update_atime();
2478
0
        }
2479
0
    };
2480
0
}
2481
2482
231
void BlockFileCache::run_background_lru_log_replay() {
2483
231
    Thread::set_self_name("run_background_lru_log_replay");
2484
100k
    while (!_close) {
2485
100k
        int64_t interval_ms = config::file_cache_background_lru_log_replay_interval_ms;
2486
100k
        {
2487
100k
            std::unique_lock close_lock(_close_mtx);
2488
100k
            _close_cv.wait_for(close_lock, std::chrono::milliseconds(interval_ms));
2489
100k
            if (_close) {
2490
210
                break;
2491
210
            }
2492
100k
        }
2493
2494
100k
        replay_lru_logs_once();
2495
100k
    }
2496
231
}
2497
2498
100k
size_t BlockFileCache::replay_lru_logs_once() {
2499
100k
    size_t replayed = 0;
2500
400k
    for (FileCacheType type : LRU_LOG_REPLAY_TYPES) {
2501
400k
        replayed += _lru_recorder->replay_queue_event(type);
2502
400k
    }
2503
2504
100k
    if (replayed == 0) {
2505
85.8k
        *_lru_recorder_log_replay_idle_metrics << 1;
2506
85.8k
    }
2507
2508
100k
    if (config::enable_evaluate_shadow_queue_diff) {
2509
0
        SCOPED_CACHE_LOCK(_mutex, this);
2510
0
        _lru_recorder->evaluate_queue_diff(_ttl_queue, "ttl", cache_lock);
2511
0
        _lru_recorder->evaluate_queue_diff(_index_queue, "index", cache_lock);
2512
0
        _lru_recorder->evaluate_queue_diff(_normal_queue, "normal", cache_lock);
2513
0
        _lru_recorder->evaluate_queue_diff(_disposable_queue, "disposable", cache_lock);
2514
0
    }
2515
100k
    return replayed;
2516
100k
}
2517
2518
13
void BlockFileCache::dump_lru_queues(bool force) {
2519
13
    std::unique_lock dump_lock(_dump_lru_queues_mtx);
2520
13
    if (config::file_cache_background_lru_dump_tail_record_num > 0 &&
2521
13
        !ExecEnv::GetInstance()->get_is_upgrading()) {
2522
13
        _lru_dumper->dump_queue("disposable", force);
2523
13
        _lru_dumper->dump_queue("normal", force);
2524
13
        _lru_dumper->dump_queue("index", force);
2525
13
        _lru_dumper->dump_queue("ttl", force);
2526
13
        _lru_dumper->set_first_dump_done();
2527
13
    }
2528
13
}
2529
2530
231
void BlockFileCache::run_background_lru_dump() {
2531
231
    Thread::set_self_name("run_background_lru_dump");
2532
244
    while (!_close) {
2533
244
        int64_t interval_ms = config::file_cache_background_lru_dump_interval_ms;
2534
244
        {
2535
244
            std::unique_lock close_lock(_close_mtx);
2536
244
            _close_cv.wait_for(close_lock, std::chrono::milliseconds(interval_ms));
2537
244
            if (_close) {
2538
231
                break;
2539
231
            }
2540
244
        }
2541
13
        dump_lru_queues(false);
2542
13
    }
2543
231
}
2544
2545
231
void BlockFileCache::restore_lru_queues_from_disk(std::lock_guard<std::mutex>& cache_lock) {
2546
    // keep this order coz may be duplicated in different queue, we use the first appearence
2547
231
    _lru_dumper->restore_queue(_ttl_queue, "ttl", cache_lock);
2548
231
    _lru_dumper->restore_queue(_index_queue, "index", cache_lock);
2549
231
    _lru_dumper->restore_queue(_normal_queue, "normal", cache_lock);
2550
231
    _lru_dumper->restore_queue(_disposable_queue, "disposable", cache_lock);
2551
231
}
2552
2553
0
std::map<std::string, double> BlockFileCache::get_stats() {
2554
0
    std::map<std::string, double> stats;
2555
0
    stats["hits_ratio"] = (double)_hit_ratio->get_value();
2556
0
    stats["hits_ratio_5m"] = (double)_hit_ratio_5m->get_value();
2557
0
    stats["hits_ratio_1h"] = (double)_hit_ratio_1h->get_value();
2558
2559
0
    stats["index_queue_max_size"] = (double)_index_queue.get_max_size();
2560
0
    stats["index_queue_curr_size"] = (double)_cur_index_queue_cache_size_metrics->get_value();
2561
0
    stats["index_queue_max_elements"] = (double)_index_queue.get_max_element_size();
2562
0
    stats["index_queue_curr_elements"] =
2563
0
            (double)_cur_index_queue_element_count_metrics->get_value();
2564
2565
0
    stats["ttl_queue_max_size"] = (double)_ttl_queue.get_max_size();
2566
0
    stats["ttl_queue_curr_size"] = (double)_cur_ttl_cache_lru_queue_cache_size_metrics->get_value();
2567
0
    stats["ttl_queue_max_elements"] = (double)_ttl_queue.get_max_element_size();
2568
0
    stats["ttl_queue_curr_elements"] =
2569
0
            (double)_cur_ttl_cache_lru_queue_element_count_metrics->get_value();
2570
2571
0
    stats["normal_queue_max_size"] = (double)_normal_queue.get_max_size();
2572
0
    stats["normal_queue_curr_size"] = (double)_cur_normal_queue_cache_size_metrics->get_value();
2573
0
    stats["normal_queue_max_elements"] = (double)_normal_queue.get_max_element_size();
2574
0
    stats["normal_queue_curr_elements"] =
2575
0
            (double)_cur_normal_queue_element_count_metrics->get_value();
2576
2577
0
    stats["disposable_queue_max_size"] = (double)_disposable_queue.get_max_size();
2578
0
    stats["disposable_queue_curr_size"] =
2579
0
            (double)_cur_disposable_queue_cache_size_metrics->get_value();
2580
0
    stats["disposable_queue_max_elements"] = (double)_disposable_queue.get_max_element_size();
2581
0
    stats["disposable_queue_curr_elements"] =
2582
0
            (double)_cur_disposable_queue_element_count_metrics->get_value();
2583
2584
0
    stats["lru_recorder_index_shadow_queue_curr_elements"] =
2585
0
            (double)_lru_recorder_shadow_queue_element_count_metrics[FileCacheType::INDEX]
2586
0
                    ->get_value();
2587
0
    stats["lru_recorder_ttl_shadow_queue_curr_elements"] =
2588
0
            (double)_lru_recorder_shadow_queue_element_count_metrics[FileCacheType::TTL]
2589
0
                    ->get_value();
2590
0
    stats["lru_recorder_normal_shadow_queue_curr_elements"] =
2591
0
            (double)_lru_recorder_shadow_queue_element_count_metrics[FileCacheType::NORMAL]
2592
0
                    ->get_value();
2593
0
    stats["lru_recorder_disposable_shadow_queue_curr_elements"] =
2594
0
            (double)_lru_recorder_shadow_queue_element_count_metrics[FileCacheType::DISPOSABLE]
2595
0
                    ->get_value();
2596
2597
0
    stats["need_evict_cache_in_advance"] = (double)_need_evict_cache_in_advance;
2598
0
    stats["disk_resource_limit_mode"] = (double)_disk_resource_limit_mode;
2599
2600
0
    stats["total_removed_counts"] = (double)_num_removed_blocks->get_value();
2601
0
    stats["total_hit_counts"] = (double)_num_hit_blocks->get_value();
2602
0
    stats["total_read_counts"] = (double)_num_read_blocks->get_value();
2603
2604
0
    stats["total_read_size"] = (double)_total_read_size_metrics->get_value();
2605
0
    stats["total_hit_size"] = (double)_total_hit_size_metrics->get_value();
2606
0
    stats["total_removed_size"] = (double)_total_evict_size_metrics->get_value();
2607
2608
0
    return stats;
2609
0
}
2610
2611
// for be UTs
2612
176
std::map<std::string, double> BlockFileCache::get_stats_unsafe() {
2613
176
    std::map<std::string, double> stats;
2614
176
    stats["hits_ratio"] = (double)_hit_ratio->get_value();
2615
176
    stats["hits_ratio_5m"] = (double)_hit_ratio_5m->get_value();
2616
176
    stats["hits_ratio_1h"] = (double)_hit_ratio_1h->get_value();
2617
2618
176
    stats["index_queue_max_size"] = (double)_index_queue.get_max_size();
2619
176
    stats["index_queue_curr_size"] = (double)_index_queue.get_capacity_unsafe();
2620
176
    stats["index_queue_max_elements"] = (double)_index_queue.get_max_element_size();
2621
176
    stats["index_queue_curr_elements"] = (double)_index_queue.get_elements_num_unsafe();
2622
2623
176
    stats["ttl_queue_max_size"] = (double)_ttl_queue.get_max_size();
2624
176
    stats["ttl_queue_curr_size"] = (double)_ttl_queue.get_capacity_unsafe();
2625
176
    stats["ttl_queue_max_elements"] = (double)_ttl_queue.get_max_element_size();
2626
176
    stats["ttl_queue_curr_elements"] = (double)_ttl_queue.get_elements_num_unsafe();
2627
2628
176
    stats["normal_queue_max_size"] = (double)_normal_queue.get_max_size();
2629
176
    stats["normal_queue_curr_size"] = (double)_normal_queue.get_capacity_unsafe();
2630
176
    stats["normal_queue_max_elements"] = (double)_normal_queue.get_max_element_size();
2631
176
    stats["normal_queue_curr_elements"] = (double)_normal_queue.get_elements_num_unsafe();
2632
2633
176
    stats["disposable_queue_max_size"] = (double)_disposable_queue.get_max_size();
2634
176
    stats["disposable_queue_curr_size"] = (double)_disposable_queue.get_capacity_unsafe();
2635
176
    stats["disposable_queue_max_elements"] = (double)_disposable_queue.get_max_element_size();
2636
176
    stats["disposable_queue_curr_elements"] = (double)_disposable_queue.get_elements_num_unsafe();
2637
176
    stats["lru_recorder_index_shadow_queue_curr_elements"] =
2638
176
            (double)_lru_recorder_shadow_queue_element_count_metrics[FileCacheType::INDEX]
2639
176
                    ->get_value();
2640
176
    stats["lru_recorder_ttl_shadow_queue_curr_elements"] =
2641
176
            (double)_lru_recorder_shadow_queue_element_count_metrics[FileCacheType::TTL]
2642
176
                    ->get_value();
2643
176
    stats["lru_recorder_normal_shadow_queue_curr_elements"] =
2644
176
            (double)_lru_recorder_shadow_queue_element_count_metrics[FileCacheType::NORMAL]
2645
176
                    ->get_value();
2646
176
    stats["lru_recorder_disposable_shadow_queue_curr_elements"] =
2647
176
            (double)_lru_recorder_shadow_queue_element_count_metrics[FileCacheType::DISPOSABLE]
2648
176
                    ->get_value();
2649
2650
176
    stats["need_evict_cache_in_advance"] = (double)_need_evict_cache_in_advance;
2651
176
    stats["disk_resource_limit_mode"] = (double)_disk_resource_limit_mode;
2652
2653
176
    stats["total_removed_counts"] = (double)_num_removed_blocks->get_value();
2654
176
    stats["total_hit_counts"] = (double)_num_hit_blocks->get_value();
2655
176
    stats["total_read_counts"] = (double)_num_read_blocks->get_value();
2656
2657
176
    stats["total_read_size"] = (double)_total_read_size_metrics->get_value();
2658
176
    stats["total_hit_size"] = (double)_total_hit_size_metrics->get_value();
2659
176
    stats["total_removed_size"] = (double)_total_evict_size_metrics->get_value();
2660
2661
176
    return stats;
2662
176
}
2663
2664
template void BlockFileCache::remove(FileBlockSPtr file_block,
2665
                                     std::lock_guard<std::mutex>& cache_lock,
2666
                                     std::lock_guard<std::mutex>& block_lock, bool sync);
2667
2668
1
Status BlockFileCache::report_file_cache_inconsistency(std::vector<std::string>& results) {
2669
1
    InconsistencyContext inconsistency_context;
2670
1
    RETURN_IF_ERROR(check_file_cache_consistency(inconsistency_context));
2671
1
    auto n = inconsistency_context.types.size();
2672
1
    results.reserve(n);
2673
1
    for (size_t i = 0; i < n; i++) {
2674
0
        std::string result;
2675
0
        result += "File cache info in manager:\n";
2676
0
        result += inconsistency_context.infos_in_manager[i].to_string();
2677
0
        result += "File cache info in storage:\n";
2678
0
        result += inconsistency_context.infos_in_storage[i].to_string();
2679
0
        result += inconsistency_context.types[i].to_string();
2680
0
        result += "\n";
2681
0
        results.push_back(std::move(result));
2682
0
    }
2683
1
    return Status::OK();
2684
1
}
2685
2686
1
Status BlockFileCache::check_file_cache_consistency(InconsistencyContext& inconsistency_context) {
2687
1
    std::lock_guard<std::mutex> cache_lock(_mutex);
2688
1
    std::vector<FileCacheInfo> infos_in_storage;
2689
1
    RETURN_IF_ERROR(_storage->get_file_cache_infos(infos_in_storage, cache_lock));
2690
1
    std::unordered_set<AccessKeyAndOffset, KeyAndOffsetHash> confirmed_blocks;
2691
1
    for (const auto& info_in_storage : infos_in_storage) {
2692
0
        confirmed_blocks.insert({info_in_storage.hash, info_in_storage.offset});
2693
0
        auto* cell = get_cell(info_in_storage.hash, info_in_storage.offset, cache_lock);
2694
0
        if (cell == nullptr || cell->file_block == nullptr) {
2695
0
            inconsistency_context.infos_in_manager.emplace_back();
2696
0
            inconsistency_context.infos_in_storage.push_back(info_in_storage);
2697
0
            inconsistency_context.types.emplace_back(InconsistencyType::NOT_LOADED);
2698
0
            continue;
2699
0
        }
2700
0
        FileCacheInfo info_in_manager {
2701
0
                .hash = info_in_storage.hash,
2702
0
                .expiration_time = cell->file_block->expiration_time(),
2703
0
                .size = cell->size(),
2704
0
                .offset = info_in_storage.offset,
2705
0
                .is_tmp = cell->file_block->state() == FileBlock::State::DOWNLOADING,
2706
0
                .cache_type = cell->file_block->cache_type()};
2707
0
        InconsistencyType inconsistent_type;
2708
0
        if (info_in_storage.is_tmp != info_in_manager.is_tmp) {
2709
0
            inconsistent_type |= InconsistencyType::TMP_FILE_EXPECT_DOWNLOADING_STATE;
2710
0
        }
2711
0
        size_t expected_size =
2712
0
                info_in_manager.is_tmp ? cell->dowloading_size() : info_in_manager.size;
2713
0
        if (info_in_storage.size != expected_size) {
2714
0
            inconsistent_type |= InconsistencyType::SIZE_INCONSISTENT;
2715
0
        }
2716
        // Only if it is not a tmp file need we check the cache type.
2717
0
        if ((inconsistent_type & InconsistencyType::TMP_FILE_EXPECT_DOWNLOADING_STATE) == 0 &&
2718
0
            info_in_storage.cache_type != info_in_manager.cache_type) {
2719
0
            inconsistent_type |= InconsistencyType::CACHE_TYPE_INCONSISTENT;
2720
0
        }
2721
0
        if (info_in_storage.expiration_time != info_in_manager.expiration_time) {
2722
0
            inconsistent_type |= InconsistencyType::EXPIRATION_TIME_INCONSISTENT;
2723
0
        }
2724
0
        if (inconsistent_type != InconsistencyType::NONE) {
2725
0
            inconsistency_context.infos_in_manager.push_back(info_in_manager);
2726
0
            inconsistency_context.infos_in_storage.push_back(info_in_storage);
2727
0
            inconsistency_context.types.push_back(inconsistent_type);
2728
0
        }
2729
0
    }
2730
2731
1
    for (const auto& [hash, offset_to_cell] : _files) {
2732
0
        for (const auto& [offset, cell] : offset_to_cell) {
2733
0
            if (confirmed_blocks.contains({hash, offset})) {
2734
0
                continue;
2735
0
            }
2736
0
            const auto& block = cell.file_block;
2737
0
            inconsistency_context.infos_in_manager.emplace_back(
2738
0
                    hash, block->expiration_time(), cell.size(), offset,
2739
0
                    cell.file_block->state() == FileBlock::State::DOWNLOADING, block->cache_type());
2740
0
            inconsistency_context.infos_in_storage.emplace_back();
2741
0
            inconsistency_context.types.emplace_back(InconsistencyType::MISSING_IN_STORAGE);
2742
0
        }
2743
0
    }
2744
1
    return Status::OK();
2745
1
}
2746
2747
} // namespace doris::io