Coverage Report

Created: 2026-07-07 06:19

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