Coverage Report

Created: 2026-03-16 13:13

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/util/lru_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
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
18
// Use of this source code is governed by a BSD-style license that can be
19
// found in the LICENSE file. See the AUTHORS file for names of contributors.
20
21
#include "util/lru_cache.h"
22
23
#include <cstdlib>
24
#include <mutex>
25
#include <new>
26
#include <sstream>
27
#include <string>
28
29
#include "common/metrics/metrics.h"
30
#include "util/time.h"
31
32
using std::string;
33
using std::stringstream;
34
35
namespace doris {
36
#include "common/compile_check_begin.h"
37
38
DEFINE_GAUGE_METRIC_PROTOTYPE_2ARG(cache_capacity, MetricUnit::BYTES);
39
DEFINE_GAUGE_METRIC_PROTOTYPE_2ARG(cache_usage, MetricUnit::BYTES);
40
DEFINE_GAUGE_METRIC_PROTOTYPE_2ARG(cache_element_count, MetricUnit::NOUNIT);
41
DEFINE_GAUGE_METRIC_PROTOTYPE_2ARG(cache_usage_ratio, MetricUnit::NOUNIT);
42
DEFINE_COUNTER_METRIC_PROTOTYPE_2ARG(cache_lookup_count, MetricUnit::OPERATIONS);
43
DEFINE_COUNTER_METRIC_PROTOTYPE_2ARG(cache_hit_count, MetricUnit::OPERATIONS);
44
DEFINE_COUNTER_METRIC_PROTOTYPE_2ARG(cache_miss_count, MetricUnit::OPERATIONS);
45
DEFINE_COUNTER_METRIC_PROTOTYPE_2ARG(cache_stampede_count, MetricUnit::OPERATIONS);
46
DEFINE_GAUGE_METRIC_PROTOTYPE_2ARG(cache_hit_ratio, MetricUnit::NOUNIT);
47
48
32.4M
uint32_t CacheKey::hash(const char* data, size_t n, uint32_t seed) const {
49
    // Similar to murmur hash
50
32.4M
    const uint32_t m = 0xc6a4a793;
51
32.4M
    const uint32_t r = 24;
52
32.4M
    const char* limit = data + n;
53
32.4M
    uint32_t h = seed ^ (static_cast<uint32_t>(n) * m);
54
55
    // Pick up four bytes at a time
56
692M
    while (data + 4 <= limit) {
57
660M
        uint32_t w = _decode_fixed32(data);
58
660M
        data += 4;
59
660M
        h += w;
60
660M
        h *= m;
61
660M
        h ^= (h >> 16);
62
660M
    }
63
64
    // Pick up remaining bytes
65
32.4M
    switch (limit - data) {
66
1.50M
    case 3:
67
1.50M
        h += static_cast<unsigned char>(data[2]) << 16;
68
69
        // fall through
70
4.96M
    case 2:
71
4.96M
        h += static_cast<unsigned char>(data[1]) << 8;
72
73
        // fall through
74
14.3M
    case 1:
75
14.3M
        h += static_cast<unsigned char>(data[0]);
76
14.3M
        h *= m;
77
14.3M
        h ^= (h >> r);
78
14.3M
        break;
79
80
18.1M
    default:
81
18.1M
        break;
82
32.4M
    }
83
84
32.3M
    return h;
85
32.4M
}
86
87
21.3k
HandleTable::~HandleTable() {
88
21.3k
    delete[] _list;
89
21.3k
}
90
91
// LRU cache implementation
92
28.9M
LRUHandle* HandleTable::lookup(const CacheKey& key, uint32_t hash) {
93
28.9M
    return *_find_pointer(key, hash);
94
28.9M
}
95
96
3.53M
LRUHandle* HandleTable::insert(LRUHandle* h) {
97
3.53M
    LRUHandle** ptr = _find_pointer(h->key(), h->hash);
98
3.53M
    LRUHandle* old = *ptr;
99
3.53M
    h->next_hash = old ? old->next_hash : nullptr;
100
3.53M
    *ptr = h;
101
102
3.53M
    if (old == nullptr) {
103
3.36M
        ++_elems;
104
3.36M
        if (_elems > _length) {
105
            // Since each cache entry is fairly large, we aim for a small
106
            // average linked list length (<= 1).
107
11.3k
            _resize();
108
11.3k
        }
109
3.36M
    }
110
111
3.53M
    return old;
112
3.53M
}
113
114
126k
LRUHandle* HandleTable::remove(const CacheKey& key, uint32_t hash) {
115
126k
    LRUHandle** ptr = _find_pointer(key, hash);
116
126k
    LRUHandle* result = *ptr;
117
118
126k
    if (result != nullptr) {
119
55.0k
        *ptr = result->next_hash;
120
55.0k
        _elems--;
121
55.0k
    }
122
123
126k
    return result;
124
126k
}
125
126
2.35M
bool HandleTable::remove(const LRUHandle* h) {
127
2.35M
    LRUHandle** ptr = &(_list[h->hash & (_length - 1)]);
128
2.59M
    while (*ptr != nullptr && *ptr != h) {
129
243k
        ptr = &(*ptr)->next_hash;
130
243k
    }
131
132
2.35M
    LRUHandle* result = *ptr;
133
2.35M
    if (result != nullptr) {
134
2.35M
        *ptr = result->next_hash;
135
2.35M
        _elems--;
136
2.35M
        return true;
137
2.35M
    }
138
18.4E
    return false;
139
2.35M
}
140
141
32.5M
LRUHandle** HandleTable::_find_pointer(const CacheKey& key, uint32_t hash) {
142
32.5M
    LRUHandle** ptr = &(_list[hash & (_length - 1)]);
143
49.0M
    while (*ptr != nullptr && ((*ptr)->hash != hash || key != (*ptr)->key())) {
144
16.4M
        ptr = &(*ptr)->next_hash;
145
16.4M
    }
146
147
32.5M
    return ptr;
148
32.5M
}
149
150
40.9k
void HandleTable::_resize() {
151
40.9k
    uint32_t new_length = 16;
152
72.7k
    while (new_length < _elems * 1.5) {
153
31.7k
        new_length *= 2;
154
31.7k
    }
155
156
40.9k
    auto** new_list = new (std::nothrow) LRUHandle*[new_length];
157
40.9k
    memset(new_list, 0, sizeof(new_list[0]) * new_length);
158
159
40.9k
    uint32_t count = 0;
160
2.23M
    for (uint32_t i = 0; i < _length; i++) {
161
2.19M
        LRUHandle* h = _list[i];
162
4.40M
        while (h != nullptr) {
163
2.20M
            LRUHandle* next = h->next_hash;
164
2.20M
            uint32_t hash = h->hash;
165
2.20M
            LRUHandle** ptr = &new_list[hash & (new_length - 1)];
166
2.20M
            h->next_hash = *ptr;
167
2.20M
            *ptr = h;
168
2.20M
            h = next;
169
2.20M
            count++;
170
2.20M
        }
171
2.19M
    }
172
173
40.9k
    DCHECK_EQ(_elems, count);
174
40.9k
    delete[] _list;
175
40.9k
    _list = new_list;
176
40.9k
    _length = new_length;
177
40.9k
}
178
179
1.80M
uint32_t HandleTable::element_count() const {
180
1.80M
    return _elems;
181
1.80M
}
182
183
29.6k
LRUCache::LRUCache(LRUCacheType type, bool is_lru_k) : _type(type), _is_lru_k(is_lru_k) {
184
    // Make empty circular linked list
185
29.6k
    _lru_normal.next = &_lru_normal;
186
29.6k
    _lru_normal.prev = &_lru_normal;
187
29.6k
    _lru_durable.next = &_lru_durable;
188
29.6k
    _lru_durable.prev = &_lru_durable;
189
29.6k
}
190
191
21.3k
LRUCache::~LRUCache() {
192
21.3k
    prune();
193
21.3k
}
194
195
29.6k
PrunedInfo LRUCache::set_capacity(size_t capacity) {
196
29.6k
    LRUHandle* last_ref_list = nullptr;
197
29.6k
    {
198
29.6k
        std::lock_guard l(_mutex);
199
29.6k
        if (capacity > _capacity) {
200
29.6k
            _capacity = capacity;
201
29.6k
            return {0, 0};
202
29.6k
        }
203
8
        _capacity = capacity;
204
8
        _evict_from_lru(0, &last_ref_list);
205
8
    }
206
207
0
    int64_t pruned_count = 0;
208
8
    int64_t pruned_size = 0;
209
56.0k
    while (last_ref_list != nullptr) {
210
55.9k
        ++pruned_count;
211
55.9k
        pruned_size += last_ref_list->total_size;
212
55.9k
        LRUHandle* next = last_ref_list->next;
213
55.9k
        last_ref_list->free();
214
55.9k
        last_ref_list = next;
215
55.9k
    }
216
8
    return {pruned_count, pruned_size};
217
29.6k
}
218
219
1.78M
uint64_t LRUCache::get_lookup_count() {
220
1.78M
    std::lock_guard l(_mutex);
221
1.78M
    return _lookup_count;
222
1.78M
}
223
224
1.78M
uint64_t LRUCache::get_hit_count() {
225
1.78M
    std::lock_guard l(_mutex);
226
1.78M
    return _hit_count;
227
1.78M
}
228
229
1.78M
uint64_t LRUCache::get_stampede_count() {
230
1.78M
    std::lock_guard l(_mutex);
231
1.78M
    return _stampede_count;
232
1.78M
}
233
234
1.78M
uint64_t LRUCache::get_miss_count() {
235
1.78M
    std::lock_guard l(_mutex);
236
1.78M
    return _miss_count;
237
1.78M
}
238
239
1.88M
size_t LRUCache::get_usage() {
240
1.88M
    std::lock_guard l(_mutex);
241
1.88M
    return _usage;
242
1.88M
}
243
244
1.78M
size_t LRUCache::get_capacity() {
245
1.78M
    std::lock_guard l(_mutex);
246
1.78M
    return _capacity;
247
1.78M
}
248
249
1.78M
size_t LRUCache::get_element_count() {
250
1.78M
    std::lock_guard l(_mutex);
251
1.78M
    return _table.element_count();
252
1.78M
}
253
254
26.1M
bool LRUCache::_unref(LRUHandle* e) {
255
26.1M
    DCHECK(e->refs > 0);
256
26.1M
    e->refs--;
257
26.1M
    return e->refs == 0;
258
26.1M
}
259
260
19.6M
void LRUCache::_lru_remove(LRUHandle* e) {
261
19.6M
    e->next->prev = e->prev;
262
19.6M
    e->prev->next = e->next;
263
19.6M
    e->prev = e->next = nullptr;
264
265
19.6M
    if (_cache_value_check_timestamp) {
266
7.98k
        if (e->priority == CachePriority::NORMAL) {
267
7.98k
            auto pair = std::make_pair(_cache_value_time_extractor(e->value), e);
268
7.98k
            auto found_it = _sorted_normal_entries_with_timestamp.find(pair);
269
7.99k
            if (found_it != _sorted_normal_entries_with_timestamp.end()) {
270
7.99k
                _sorted_normal_entries_with_timestamp.erase(found_it);
271
7.99k
            }
272
7.98k
        } else if (e->priority == CachePriority::DURABLE) {
273
0
            auto pair = std::make_pair(_cache_value_time_extractor(e->value), e);
274
0
            auto found_it = _sorted_durable_entries_with_timestamp.find(pair);
275
0
            if (found_it != _sorted_durable_entries_with_timestamp.end()) {
276
0
                _sorted_durable_entries_with_timestamp.erase(found_it);
277
0
            }
278
0
        }
279
7.98k
    }
280
19.6M
}
281
282
20.5M
void LRUCache::_lru_append(LRUHandle* list, LRUHandle* e) {
283
    // Make "e" newest entry by inserting just before *list
284
20.5M
    e->next = list;
285
20.5M
    e->prev = list->prev;
286
20.5M
    e->prev->next = e;
287
20.5M
    e->next->prev = e;
288
289
    // _cache_value_check_timestamp is true,
290
    // means evict entry will depends on the timestamp asc set,
291
    // the timestamp is updated by higher level caller,
292
    // and the timestamp of hit entry is different with the insert entry,
293
    // that is why need check timestamp to evict entry,
294
    // in order to keep the survival time of hit entries
295
    // longer than the entries just inserted,
296
    // so use asc set to sorted these entries's timestamp and LRUHandle*
297
20.5M
    if (_cache_value_check_timestamp) {
298
18.4k
        if (e->priority == CachePriority::NORMAL) {
299
18.4k
            _sorted_normal_entries_with_timestamp.insert(
300
18.4k
                    std::make_pair(_cache_value_time_extractor(e->value), e));
301
18.4E
        } else if (e->priority == CachePriority::DURABLE) {
302
0
            _sorted_durable_entries_with_timestamp.insert(
303
0
                    std::make_pair(_cache_value_time_extractor(e->value), e));
304
0
        }
305
18.4k
    }
306
20.5M
}
307
308
28.7M
Cache::Handle* LRUCache::lookup(const CacheKey& key, uint32_t hash) {
309
28.7M
    std::lock_guard l(_mutex);
310
28.7M
    ++_lookup_count;
311
28.7M
    LRUHandle* e = _table.lookup(key, hash);
312
28.7M
    if (e != nullptr) {
313
        // we get it from _table, so in_cache must be true
314
20.7M
        DCHECK(e->in_cache);
315
20.7M
        if (e->refs == 1) {
316
            // only in LRU free list, remove it from list
317
17.1M
            _lru_remove(e);
318
17.1M
        }
319
20.7M
        e->refs++;
320
20.7M
        ++_hit_count;
321
20.7M
        e->last_visit_time = UnixMillis();
322
20.7M
    } else {
323
8.00M
        ++_miss_count;
324
8.00M
    }
325
326
    // If key not exist in cache, and is lru k cache, and key in visits list,
327
    // then move the key to beginning of the visits list.
328
    // key in visits list indicates that the key has been inserted once after the cache is full.
329
28.7M
    if (e == nullptr && _is_lru_k) {
330
4.90M
        auto it = _visits_lru_cache_map.find(hash);
331
4.90M
        if (it != _visits_lru_cache_map.end()) {
332
4.28k
            _visits_lru_cache_list.splice(_visits_lru_cache_list.begin(), _visits_lru_cache_list,
333
4.28k
                                          it->second);
334
4.28k
        }
335
4.90M
    }
336
28.7M
    return reinterpret_cast<Cache::Handle*>(e);
337
28.7M
}
338
339
23.5M
void LRUCache::release(Cache::Handle* handle) {
340
23.5M
    if (handle == nullptr) {
341
0
        return;
342
0
    }
343
23.5M
    auto* e = reinterpret_cast<LRUHandle*>(handle);
344
23.5M
    bool last_ref = false;
345
23.5M
    {
346
23.5M
        std::lock_guard l(_mutex);
347
        // if last_ref is true, key may have been evict from the cache,
348
        // or if it is lru k, first insert of key may have failed.
349
23.5M
        last_ref = _unref(e);
350
23.5M
        if (e->in_cache && e->refs == 1) {
351
            // only exists in cache
352
20.6M
            if (_usage > _capacity) {
353
                // take this opportunity and remove the item
354
24.3k
                bool removed = _table.remove(e);
355
24.3k
                DCHECK(removed);
356
24.3k
                e->in_cache = false;
357
24.3k
                _unref(e);
358
                // `entry->in_cache = false` and `_usage -= entry->total_size;` and `_unref(entry)` should appear together.
359
                // see the comment for old entry in `LRUCache::insert`.
360
24.3k
                _usage -= e->total_size;
361
24.3k
                last_ref = true;
362
20.5M
            } else {
363
                // put it to LRU free list
364
20.6M
                if (e->priority == CachePriority::NORMAL) {
365
20.6M
                    _lru_append(&_lru_normal, e);
366
18.4E
                } else if (e->priority == CachePriority::DURABLE) {
367
18
                    _lru_append(&_lru_durable, e);
368
18
                }
369
20.5M
            }
370
20.6M
        }
371
23.5M
    }
372
373
    // free handle out of mutex
374
23.5M
    if (last_ref) {
375
69.5k
        e->free();
376
69.5k
    }
377
23.5M
}
378
379
11.7k
void LRUCache::_evict_from_lru_with_time(size_t total_size, LRUHandle** to_remove_head) {
380
    // 1. evict normal cache entries
381
11.7k
    while ((_usage + total_size > _capacity || _check_element_count_limit()) &&
382
11.7k
           !_sorted_normal_entries_with_timestamp.empty()) {
383
23
        auto entry_pair = _sorted_normal_entries_with_timestamp.begin();
384
23
        LRUHandle* remove_handle = entry_pair->second;
385
23
        DCHECK(remove_handle != nullptr);
386
23
        DCHECK(remove_handle->priority == CachePriority::NORMAL);
387
23
        _evict_one_entry(remove_handle);
388
23
        remove_handle->next = *to_remove_head;
389
23
        *to_remove_head = remove_handle;
390
23
    }
391
392
    // 2. evict durable cache entries if need
393
11.7k
    while ((_usage + total_size > _capacity || _check_element_count_limit()) &&
394
11.7k
           !_sorted_durable_entries_with_timestamp.empty()) {
395
0
        auto entry_pair = _sorted_durable_entries_with_timestamp.begin();
396
0
        LRUHandle* remove_handle = entry_pair->second;
397
0
        DCHECK(remove_handle != nullptr);
398
0
        DCHECK(remove_handle->priority == CachePriority::DURABLE);
399
0
        _evict_one_entry(remove_handle);
400
0
        remove_handle->next = *to_remove_head;
401
0
        *to_remove_head = remove_handle;
402
0
    }
403
11.7k
}
404
405
3.52M
void LRUCache::_evict_from_lru(size_t total_size, LRUHandle** to_remove_head) {
406
    // 1. evict normal cache entries
407
4.04M
    while ((_usage + total_size > _capacity || _check_element_count_limit()) &&
408
4.04M
           _lru_normal.next != &_lru_normal) {
409
523k
        LRUHandle* old = _lru_normal.next;
410
523k
        DCHECK(old->priority == CachePriority::NORMAL);
411
523k
        _evict_one_entry(old);
412
523k
        old->next = *to_remove_head;
413
523k
        *to_remove_head = old;
414
523k
    }
415
    // 2. evict durable cache entries if need
416
3.52M
    while ((_usage + total_size > _capacity || _check_element_count_limit()) &&
417
3.52M
           _lru_durable.next != &_lru_durable) {
418
4
        LRUHandle* old = _lru_durable.next;
419
4
        DCHECK(old->priority == CachePriority::DURABLE);
420
4
        _evict_one_entry(old);
421
4
        old->next = *to_remove_head;
422
4
        *to_remove_head = old;
423
4
    }
424
3.52M
}
425
426
2.33M
void LRUCache::_evict_one_entry(LRUHandle* e) {
427
2.33M
    DCHECK(e->in_cache);
428
2.33M
    DCHECK(e->refs == 1); // LRU list contains elements which may be evicted
429
2.33M
    _lru_remove(e);
430
2.33M
    bool removed = _table.remove(e);
431
2.33M
    DCHECK(removed);
432
2.33M
    e->in_cache = false;
433
2.33M
    _unref(e);
434
    // `entry->in_cache = false` and `_usage -= entry->total_size;` and `_unref(entry)` should appear together.
435
    // see the comment for old entry in `LRUCache::insert`.
436
2.33M
    _usage -= e->total_size;
437
2.33M
}
438
439
9.24M
bool LRUCache::_check_element_count_limit() {
440
9.24M
    return _element_count_capacity != 0 && _table.element_count() >= _element_count_capacity;
441
9.24M
}
442
443
// After cache is full,
444
// 1.Return false. If key has been inserted into the visits list before,
445
// key is allowed to be inserted into cache this time (this will trigger cache evict),
446
// and key is removed from the visits list.
447
// 2. Return true. If key not in visits list, insert it into visits list.
448
2.25M
bool LRUCache::_lru_k_insert_visits_list(size_t total_size, visits_lru_cache_key visits_key) {
449
2.25M
    if (_usage + total_size > _capacity ||
450
2.25M
        _check_element_count_limit()) { // this line no lock required
451
15.5k
        auto it = _visits_lru_cache_map.find(visits_key);
452
15.5k
        if (it != _visits_lru_cache_map.end()) {
453
4.45k
            _visits_lru_cache_usage -= it->second->second;
454
4.45k
            _visits_lru_cache_list.erase(it->second);
455
4.45k
            _visits_lru_cache_map.erase(it);
456
11.1k
        } else {
457
            // _visits_lru_cache_list capacity is same as the cache itself.
458
            // If _visits_lru_cache_list is full, some keys will also be evict.
459
11.7k
            while (_visits_lru_cache_usage + total_size > _capacity &&
460
11.7k
                   _visits_lru_cache_usage != 0) {
461
631
                DCHECK(!_visits_lru_cache_map.empty());
462
631
                _visits_lru_cache_usage -= _visits_lru_cache_list.back().second;
463
631
                _visits_lru_cache_map.erase(_visits_lru_cache_list.back().first);
464
631
                _visits_lru_cache_list.pop_back();
465
631
            }
466
            // 1. If true, insert key at the beginning of _visits_lru_cache_list.
467
            // 2. If false, it means total_size > cache _capacity, preventing this insert.
468
11.1k
            if (_visits_lru_cache_usage + total_size <= _capacity) {
469
6.96k
                _visits_lru_cache_list.emplace_front(visits_key, total_size);
470
6.96k
                _visits_lru_cache_map[visits_key] = _visits_lru_cache_list.begin();
471
6.96k
                _visits_lru_cache_usage += total_size;
472
6.96k
            }
473
11.1k
            return true;
474
11.1k
        }
475
15.5k
    }
476
2.24M
    return false;
477
2.25M
}
478
479
Cache::Handle* LRUCache::insert(const CacheKey& key, uint32_t hash, void* value, size_t charge,
480
3.54M
                                CachePriority priority) {
481
3.54M
    size_t handle_size = sizeof(LRUHandle) - 1 + key.size();
482
3.54M
    auto* e = reinterpret_cast<LRUHandle*>(malloc(handle_size));
483
3.54M
    e->value = value;
484
3.54M
    e->charge = charge;
485
3.54M
    e->key_length = key.size();
486
    // if LRUCacheType::NUMBER, charge not add handle_size,
487
    // because charge at this time is no longer the memory size, but an weight.
488
3.54M
    e->total_size = (_type == LRUCacheType::SIZE ? handle_size + charge : charge);
489
3.54M
    e->hash = hash;
490
3.54M
    e->refs = 1; // only one for the returned handle.
491
3.54M
    e->next = e->prev = nullptr;
492
3.54M
    e->in_cache = false;
493
3.54M
    e->priority = priority;
494
3.54M
    e->type = _type;
495
3.54M
    memcpy(e->key_data, key.data(), key.size());
496
3.54M
    e->last_visit_time = UnixMillis();
497
498
3.54M
    LRUHandle* to_remove_head = nullptr;
499
3.54M
    {
500
3.54M
        std::lock_guard l(_mutex);
501
502
3.54M
        if (_is_lru_k && _lru_k_insert_visits_list(e->total_size, hash)) {
503
11.1k
            return reinterpret_cast<Cache::Handle*>(e);
504
11.1k
        }
505
506
        // Free the space following strict LRU policy until enough space
507
        // is freed or the lru list is empty
508
3.53M
        if (_cache_value_check_timestamp) {
509
11.7k
            _evict_from_lru_with_time(e->total_size, &to_remove_head);
510
3.51M
        } else {
511
3.51M
            _evict_from_lru(e->total_size, &to_remove_head);
512
3.51M
        }
513
514
        // insert into the cache
515
        // note that the cache might get larger than its capacity if not enough
516
        // space was freed
517
3.53M
        auto* old = _table.insert(e);
518
3.53M
        e->in_cache = true;
519
3.53M
        _usage += e->total_size;
520
3.53M
        e->refs++; // one for the returned handle, one for LRUCache.
521
3.53M
        if (old != nullptr) {
522
174k
            _stampede_count++;
523
174k
            old->in_cache = false;
524
            // `entry->in_cache = false` and `_usage -= entry->total_size;` and `_unref(entry)` should appear together.
525
            // Whether the reference of the old entry is 0, the cache usage is subtracted here,
526
            // because the old entry has been removed from the cache and should not be counted in the cache capacity,
527
            // but the memory of the old entry is still tracked by the cache memory_tracker.
528
            // After all the old handles are released, the old entry will be freed and the memory of the old entry
529
            // will be released from the cache memory_tracker.
530
174k
            _usage -= old->total_size;
531
            // if false, old entry is being used externally, just ref-- and sub _usage,
532
174k
            if (_unref(old)) {
533
                // old is on LRU because it's in cache and its reference count
534
                // was just 1 (Unref returned 0)
535
134k
                _lru_remove(old);
536
134k
                old->next = to_remove_head;
537
134k
                to_remove_head = old;
538
134k
            }
539
174k
        }
540
3.53M
    }
541
542
    // we free the entries here outside of mutex for
543
    // performance reasons
544
4.13M
    while (to_remove_head != nullptr) {
545
602k
        LRUHandle* next = to_remove_head->next;
546
602k
        to_remove_head->free();
547
602k
        to_remove_head = next;
548
602k
    }
549
550
3.53M
    return reinterpret_cast<Cache::Handle*>(e);
551
3.54M
}
552
553
126k
void LRUCache::erase(const CacheKey& key, uint32_t hash) {
554
126k
    LRUHandle* e = nullptr;
555
126k
    bool last_ref = false;
556
126k
    {
557
126k
        std::lock_guard l(_mutex);
558
126k
        e = _table.remove(key, hash);
559
126k
        if (e != nullptr) {
560
55.0k
            last_ref = _unref(e);
561
            // if last_ref is false or in_cache is false, e must not be in lru
562
55.0k
            if (last_ref && e->in_cache) {
563
                // locate in free list
564
55.0k
                _lru_remove(e);
565
55.0k
            }
566
55.0k
            e->in_cache = false;
567
            // `entry->in_cache = false` and `_usage -= entry->total_size;` and `_unref(entry)` should appear together.
568
            // see the comment for old entry in `LRUCache::insert`.
569
55.0k
            _usage -= e->total_size;
570
55.0k
        }
571
126k
    }
572
    // free handle out of mutex, when last_ref is true, e must not be nullptr
573
126k
    if (last_ref) {
574
55.0k
        e->free();
575
55.0k
    }
576
126k
}
577
578
21.3k
PrunedInfo LRUCache::prune() {
579
21.3k
    LRUHandle* to_remove_head = nullptr;
580
21.3k
    {
581
21.3k
        std::lock_guard l(_mutex);
582
388k
        while (_lru_normal.next != &_lru_normal) {
583
367k
            LRUHandle* old = _lru_normal.next;
584
367k
            _evict_one_entry(old);
585
367k
            old->next = to_remove_head;
586
367k
            to_remove_head = old;
587
367k
        }
588
21.3k
        while (_lru_durable.next != &_lru_durable) {
589
6
            LRUHandle* old = _lru_durable.next;
590
6
            _evict_one_entry(old);
591
6
            old->next = to_remove_head;
592
6
            to_remove_head = old;
593
6
        }
594
21.3k
    }
595
21.3k
    int64_t pruned_count = 0;
596
21.3k
    int64_t pruned_size = 0;
597
388k
    while (to_remove_head != nullptr) {
598
367k
        ++pruned_count;
599
367k
        pruned_size += to_remove_head->total_size;
600
367k
        LRUHandle* next = to_remove_head->next;
601
367k
        to_remove_head->free();
602
367k
        to_remove_head = next;
603
367k
    }
604
21.3k
    return {pruned_count, pruned_size};
605
21.3k
}
606
607
40.5k
PrunedInfo LRUCache::prune_if(CachePrunePredicate pred, bool lazy_mode) {
608
40.5k
    LRUHandle* to_remove_head = nullptr;
609
40.5k
    {
610
40.5k
        std::lock_guard l(_mutex);
611
40.5k
        LRUHandle* p = _lru_normal.next;
612
1.47M
        while (p != &_lru_normal) {
613
1.47M
            LRUHandle* next = p->next;
614
1.47M
            if (pred(p)) {
615
1.43M
                _evict_one_entry(p);
616
1.43M
                p->next = to_remove_head;
617
1.43M
                to_remove_head = p;
618
1.43M
            } else if (lazy_mode) {
619
35.3k
                break;
620
35.3k
            }
621
1.43M
            p = next;
622
1.43M
        }
623
624
40.5k
        p = _lru_durable.next;
625
40.5k
        while (p != &_lru_durable) {
626
10
            LRUHandle* next = p->next;
627
10
            if (pred(p)) {
628
2
                _evict_one_entry(p);
629
2
                p->next = to_remove_head;
630
2
                to_remove_head = p;
631
8
            } else if (lazy_mode) {
632
3
                break;
633
3
            }
634
7
            p = next;
635
7
        }
636
40.5k
    }
637
40.5k
    int64_t pruned_count = 0;
638
40.5k
    int64_t pruned_size = 0;
639
1.47M
    while (to_remove_head != nullptr) {
640
1.43M
        ++pruned_count;
641
1.43M
        pruned_size += to_remove_head->total_size;
642
1.43M
        LRUHandle* next = to_remove_head->next;
643
1.43M
        to_remove_head->free();
644
1.43M
        to_remove_head = next;
645
1.43M
    }
646
40.5k
    return {pruned_count, pruned_size};
647
40.5k
}
648
649
512
void LRUCache::for_each_entry(const std::function<void(const LRUHandle*)>& visitor) {
650
512
    std::lock_guard l(_mutex);
651
38.1k
    for (LRUHandle* p = _lru_normal.next; p != &_lru_normal; p = p->next) {
652
37.6k
        visitor(p);
653
37.6k
    }
654
512
    for (LRUHandle* p = _lru_durable.next; p != &_lru_durable; p = p->next) {
655
0
        visitor(p);
656
0
    }
657
512
}
658
659
1.90k
void LRUCache::set_cache_value_time_extractor(CacheValueTimeExtractor cache_value_time_extractor) {
660
1.90k
    _cache_value_time_extractor = cache_value_time_extractor;
661
1.90k
}
662
663
1.90k
void LRUCache::set_cache_value_check_timestamp(bool cache_value_check_timestamp) {
664
1.90k
    _cache_value_check_timestamp = cache_value_check_timestamp;
665
1.90k
}
666
667
32.4M
inline uint32_t ShardedLRUCache::_hash_slice(const CacheKey& s) {
668
32.4M
    return s.hash(s.data(), s.size(), 0);
669
32.4M
}
670
671
ShardedLRUCache::ShardedLRUCache(const std::string& name, size_t capacity, LRUCacheType type,
672
                                 uint32_t num_shards, uint32_t total_element_count_capacity,
673
                                 bool is_lru_k)
674
1.19k
        : _name(name),
675
1.19k
          _num_shard_bits(__builtin_ctz(num_shards)),
676
1.19k
          _num_shards(num_shards),
677
1.19k
          _last_id(1),
678
1.19k
          _capacity(capacity) {
679
1.19k
    CHECK(num_shards > 0) << "num_shards cannot be 0";
680
1.19k
    CHECK_EQ((num_shards & (num_shards - 1)), 0)
681
0
            << "num_shards should be power of two, but got " << num_shards;
682
683
1.19k
    const size_t per_shard = (capacity + (_num_shards - 1)) / _num_shards;
684
1.19k
    const uint32_t per_shard_element_count_capacity =
685
1.19k
            (total_element_count_capacity + (_num_shards - 1)) / _num_shards;
686
1.19k
    auto** shards = new (std::nothrow) LRUCache*[_num_shards];
687
30.8k
    for (int s = 0; s < _num_shards; s++) {
688
29.6k
        shards[s] = new LRUCache(type, is_lru_k);
689
29.6k
        shards[s]->set_capacity(per_shard);
690
29.6k
        shards[s]->set_element_count_capacity(per_shard_element_count_capacity);
691
29.6k
    }
692
1.19k
    _shards = shards;
693
694
1.19k
    _entity = DorisMetrics::instance()->metric_registry()->register_entity(
695
1.19k
            std::string("lru_cache:") + name, {{"name", name}});
696
1.19k
    _entity->register_hook(name, std::bind(&ShardedLRUCache::update_cache_metrics, this));
697
1.19k
    INT_GAUGE_METRIC_REGISTER(_entity, cache_capacity);
698
1.19k
    INT_GAUGE_METRIC_REGISTER(_entity, cache_usage);
699
1.19k
    INT_GAUGE_METRIC_REGISTER(_entity, cache_element_count);
700
1.19k
    DOUBLE_GAUGE_METRIC_REGISTER(_entity, cache_usage_ratio);
701
1.19k
    INT_COUNTER_METRIC_REGISTER(_entity, cache_lookup_count);
702
1.19k
    INT_COUNTER_METRIC_REGISTER(_entity, cache_hit_count);
703
1.19k
    INT_COUNTER_METRIC_REGISTER(_entity, cache_stampede_count);
704
1.19k
    INT_COUNTER_METRIC_REGISTER(_entity, cache_miss_count);
705
1.19k
    DOUBLE_GAUGE_METRIC_REGISTER(_entity, cache_hit_ratio);
706
707
1.19k
    _hit_count_bvar.reset(new bvar::Adder<uint64_t>("doris_cache", _name));
708
1.19k
    _hit_count_per_second.reset(new bvar::PerSecond<bvar::Adder<uint64_t>>(
709
1.19k
            "doris_cache", _name + "_persecond", _hit_count_bvar.get(), 60));
710
1.19k
    _lookup_count_bvar.reset(new bvar::Adder<uint64_t>("doris_cache", _name));
711
1.19k
    _lookup_count_per_second.reset(new bvar::PerSecond<bvar::Adder<uint64_t>>(
712
1.19k
            "doris_cache", _name + "_persecond", _lookup_count_bvar.get(), 60));
713
1.19k
}
714
715
ShardedLRUCache::ShardedLRUCache(const std::string& name, size_t capacity, LRUCacheType type,
716
                                 uint32_t num_shards,
717
                                 CacheValueTimeExtractor cache_value_time_extractor,
718
                                 bool cache_value_check_timestamp,
719
                                 uint32_t total_element_count_capacity, bool is_lru_k)
720
124
        : ShardedLRUCache(name, capacity, type, num_shards, total_element_count_capacity,
721
124
                          is_lru_k) {
722
2.03k
    for (int s = 0; s < _num_shards; s++) {
723
1.90k
        _shards[s]->set_cache_value_time_extractor(cache_value_time_extractor);
724
1.90k
        _shards[s]->set_cache_value_check_timestamp(cache_value_check_timestamp);
725
1.90k
    }
726
124
}
727
728
1.11k
ShardedLRUCache::~ShardedLRUCache() {
729
1.11k
    _entity->deregister_hook(_name);
730
1.11k
    DorisMetrics::instance()->metric_registry()->deregister_entity(_entity);
731
1.11k
    if (_shards) {
732
22.4k
        for (int s = 0; s < _num_shards; s++) {
733
21.3k
            delete _shards[s];
734
21.3k
        }
735
1.11k
        delete[] _shards;
736
1.11k
    }
737
1.11k
}
738
739
14
PrunedInfo ShardedLRUCache::set_capacity(size_t capacity) {
740
14
    std::lock_guard l(_mutex);
741
14
    PrunedInfo pruned_info;
742
14
    const size_t per_shard = (capacity + (_num_shards - 1)) / _num_shards;
743
28
    for (int s = 0; s < _num_shards; s++) {
744
14
        PrunedInfo info = _shards[s]->set_capacity(per_shard);
745
14
        pruned_info.pruned_count += info.pruned_count;
746
14
        pruned_info.pruned_size += info.pruned_size;
747
14
    }
748
14
    _capacity = capacity;
749
14
    return pruned_info;
750
14
}
751
752
16.0k
size_t ShardedLRUCache::get_capacity() {
753
16.0k
    std::lock_guard l(_mutex);
754
16.0k
    return _capacity;
755
16.0k
}
756
757
Cache::Handle* ShardedLRUCache::insert(const CacheKey& key, void* value, size_t charge,
758
3.54M
                                       CachePriority priority) {
759
3.54M
    const uint32_t hash = _hash_slice(key);
760
3.54M
    return _shards[_shard(hash)]->insert(key, hash, value, charge, priority);
761
3.54M
}
762
763
28.8M
Cache::Handle* ShardedLRUCache::lookup(const CacheKey& key) {
764
28.8M
    const uint32_t hash = _hash_slice(key);
765
28.8M
    return _shards[_shard(hash)]->lookup(key, hash);
766
28.8M
}
767
768
23.5M
void ShardedLRUCache::release(Handle* handle) {
769
23.5M
    auto* h = reinterpret_cast<LRUHandle*>(handle);
770
23.5M
    _shards[_shard(h->hash)]->release(handle);
771
23.5M
}
772
773
126k
void ShardedLRUCache::erase(const CacheKey& key) {
774
126k
    const uint32_t hash = _hash_slice(key);
775
126k
    _shards[_shard(hash)]->erase(key, hash);
776
126k
}
777
778
21.6M
void* ShardedLRUCache::value(Handle* handle) {
779
21.6M
    return reinterpret_cast<LRUHandle*>(handle)->value;
780
21.6M
}
781
782
2
uint64_t ShardedLRUCache::new_id() {
783
2
    return _last_id.fetch_add(1, std::memory_order_relaxed);
784
2
}
785
786
0
PrunedInfo ShardedLRUCache::prune() {
787
0
    PrunedInfo pruned_info;
788
0
    for (int s = 0; s < _num_shards; s++) {
789
0
        PrunedInfo info = _shards[s]->prune();
790
0
        pruned_info.pruned_count += info.pruned_count;
791
0
        pruned_info.pruned_size += info.pruned_size;
792
0
    }
793
0
    return pruned_info;
794
0
}
795
796
615
PrunedInfo ShardedLRUCache::prune_if(CachePrunePredicate pred, bool lazy_mode) {
797
615
    PrunedInfo pruned_info;
798
41.1k
    for (int s = 0; s < _num_shards; s++) {
799
40.4k
        PrunedInfo info = _shards[s]->prune_if(pred, lazy_mode);
800
40.4k
        pruned_info.pruned_count += info.pruned_count;
801
40.4k
        pruned_info.pruned_size += info.pruned_size;
802
40.4k
    }
803
615
    return pruned_info;
804
615
}
805
806
2
void ShardedLRUCache::for_each_entry(const std::function<void(const LRUHandle*)>& visitor) {
807
514
    for (int s = 0; s < _num_shards; s++) {
808
512
        _shards[s]->for_each_entry(visitor);
809
512
    }
810
2
}
811
812
18.4k
int64_t ShardedLRUCache::get_usage() {
813
18.4k
    size_t total_usage = 0;
814
123k
    for (int i = 0; i < _num_shards; i++) {
815
104k
        total_usage += _shards[i]->get_usage();
816
104k
    }
817
18.4k
    return total_usage;
818
18.4k
}
819
820
10
size_t ShardedLRUCache::get_element_count() {
821
10
    size_t total_element_count = 0;
822
202
    for (int i = 0; i < _num_shards; i++) {
823
192
        total_element_count += _shards[i]->get_element_count();
824
192
    }
825
10
    return total_element_count;
826
10
}
827
828
16.1k
void ShardedLRUCache::update_cache_metrics() const {
829
16.1k
    size_t capacity = 0;
830
16.1k
    size_t total_usage = 0;
831
16.1k
    size_t total_lookup_count = 0;
832
16.1k
    size_t total_hit_count = 0;
833
16.1k
    size_t total_element_count = 0;
834
16.1k
    size_t total_miss_count = 0;
835
16.1k
    size_t total_stampede_count = 0;
836
837
1.79M
    for (int i = 0; i < _num_shards; i++) {
838
1.78M
        capacity += _shards[i]->get_capacity();
839
1.78M
        total_usage += _shards[i]->get_usage();
840
1.78M
        total_lookup_count += _shards[i]->get_lookup_count();
841
1.78M
        total_hit_count += _shards[i]->get_hit_count();
842
1.78M
        total_element_count += _shards[i]->get_element_count();
843
1.78M
        total_miss_count += _shards[i]->get_miss_count();
844
1.78M
        total_stampede_count += _shards[i]->get_stampede_count();
845
1.78M
    }
846
847
16.1k
    cache_capacity->set_value(capacity);
848
16.1k
    cache_usage->set_value(total_usage);
849
16.1k
    cache_element_count->set_value(total_element_count);
850
16.1k
    cache_lookup_count->set_value(total_lookup_count);
851
16.1k
    cache_hit_count->set_value(total_hit_count);
852
16.1k
    cache_miss_count->set_value(total_miss_count);
853
16.1k
    cache_stampede_count->set_value(total_stampede_count);
854
16.1k
    cache_usage_ratio->set_value(
855
16.1k
            capacity == 0 ? 0 : (static_cast<double>(total_usage) / static_cast<double>(capacity)));
856
16.1k
    cache_hit_ratio->set_value(total_lookup_count == 0 ? 0
857
16.1k
                                                       : (static_cast<double>(total_hit_count) /
858
9.61k
                                                          static_cast<double>(total_lookup_count)));
859
16.1k
}
860
861
Cache::Handle* DummyLRUCache::insert(const CacheKey& key, void* value, size_t charge,
862
187
                                     CachePriority priority) {
863
187
    size_t handle_size = sizeof(LRUHandle);
864
187
    auto* e = reinterpret_cast<LRUHandle*>(malloc(handle_size));
865
187
    e->value = value;
866
187
    e->charge = charge;
867
187
    e->key_length = 0;
868
187
    e->total_size = 0;
869
187
    e->hash = 0;
870
187
    e->refs = 1; // only one for the returned handle
871
187
    e->next = e->prev = nullptr;
872
187
    e->in_cache = false;
873
187
    return reinterpret_cast<Cache::Handle*>(e);
874
187
}
875
876
187
void DummyLRUCache::release(Cache::Handle* handle) {
877
187
    if (handle == nullptr) {
878
0
        return;
879
0
    }
880
187
    auto* e = reinterpret_cast<LRUHandle*>(handle);
881
187
    e->free();
882
187
}
883
884
0
void* DummyLRUCache::value(Handle* handle) {
885
0
    return reinterpret_cast<LRUHandle*>(handle)->value;
886
0
}
887
888
} // namespace doris