Coverage Report

Created: 2026-10-20 00:52

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