Coverage Report

Created: 2025-06-09 16:40

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