Coverage Report

Created: 2025-04-15 00:19

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