Coverage Report

Created: 2026-07-16 15:06

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