Coverage Report

Created: 2026-10-23 23:36

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/root/doris/be/src/util/lru_cache.h
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
18
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
19
// Use of this source code is governed by a BSD-style license that can be
20
// found in the LICENSE file. See the AUTHORS file for names of contributors.
21
22
#pragma once
23
24
#include <butil/macros.h>
25
#include <bvar/bvar.h>
26
#include <glog/logging.h>
27
#include <gtest/gtest_prod.h>
28
29
#include <atomic>
30
#include <cassert>
31
#include <cstdint>
32
#include <cstdlib>
33
#include <cstring>
34
#include <functional>
35
#include <memory>
36
#include <set>
37
#include <string>
38
#include <utility>
39
40
#include "common/metrics/doris_metrics.h"
41
#include "common/metrics/metrics.h"
42
#include "runtime/memory/lru_cache_value_base.h"
43
44
namespace doris {
45
#include "common/compile_check_begin.h"
46
47
class Cache;
48
class LRUCachePolicy;
49
struct LRUHandle;
50
51
enum LRUCacheType {
52
    SIZE, // The capacity of cache is based on the memory size of cache entry, memory size = handle size + charge.
53
    NUMBER // The capacity of cache is based on the number of cache entry, number = charge, the weight of an entry.
54
};
55
56
static constexpr LRUCacheType DEFAULT_LRU_CACHE_TYPE = LRUCacheType::SIZE;
57
static constexpr uint32_t DEFAULT_LRU_CACHE_NUM_SHARDS = 32;
58
static constexpr size_t DEFAULT_LRU_CACHE_ELEMENT_COUNT_CAPACITY = 0;
59
static constexpr bool DEFAULT_LRU_CACHE_IS_LRU_K = false;
60
61
class CacheKey {
62
public:
63
0
    CacheKey() : _size(0) {}
64
    // Create a slice that refers to d[0,n-1].
65
1.39M
    CacheKey(const char* d, size_t n) : _data(d), _size(n) {}
66
67
    // Create a slice that refers to the contents of "s"
68
163k
    CacheKey(const std::string& s) : _data(s.data()), _size(s.size()) {}
69
70
    // Create a slice that refers to s[0,strlen(s)-1]
71
67
    CacheKey(const char* s) : _data(s), _size(strlen(s)) {}
72
73
    ~CacheKey() = default;
74
75
    // Return a pointer to the beginning of the referenced data
76
2.24M
    const char* data() const { return _data; }
77
78
    // Return the length (in bytes) of the referenced data
79
3.78M
    size_t size() const { return _size; }
80
81
    // Return true if the length of the referenced data is zero
82
0
    bool empty() const { return _size == 0; }
83
84
    // Return the ith byte in the referenced data.
85
    // REQUIRES: n < size()
86
0
    char operator[](size_t n) const {
87
0
        assert(n < size());
88
0
        return _data[n];
89
0
    }
90
91
    // Change this slice to refer to an empty array
92
0
    void clear() {
93
0
        _data = nullptr;
94
0
        _size = 0;
95
0
    }
96
97
    // Drop the first "n" bytes from this slice.
98
0
    void remove_prefix(size_t n) {
99
0
        assert(n <= size());
100
0
        _data += n;
101
0
        _size -= n;
102
0
    }
103
104
    // Return a string that contains the copy of the referenced data.
105
0
    std::string to_string() const { return {_data, _size}; }
106
107
377k
    bool operator==(const CacheKey& other) const {
108
377k
        return ((size() == other.size()) && (memcmp(data(), other.data(), size()) == 0));
109
377k
    }
110
111
377k
    bool operator!=(const CacheKey& other) const { return !(*this == other); }
112
113
0
    int compare(const CacheKey& b) const {
114
0
        const size_t min_len = (_size < b._size) ? _size : b._size;
115
0
        int r = memcmp(_data, b._data, min_len);
116
0
        if (r == 0) {
117
0
            if (_size < b._size) {
118
0
                r = -1;
119
0
            } else if (_size > b._size) {
120
0
                r = +1;
121
0
            }
122
0
        }
123
0
        return r;
124
0
    }
125
126
    uint32_t hash(const char* data, size_t n, uint32_t seed) const;
127
128
    // Return true if "x" is a prefix of "*this"
129
0
    bool starts_with(const CacheKey& x) const {
130
0
        return ((_size >= x._size) && (memcmp(_data, x._data, x._size) == 0));
131
0
    }
132
133
private:
134
2.42M
    uint32_t _decode_fixed32(const char* ptr) const {
135
        // Load the raw bytes
136
2.42M
        uint32_t result;
137
2.42M
        memcpy(&result, ptr, sizeof(result)); // gcc optimizes this to a plain load
138
2.42M
        return result;
139
2.42M
    }
140
141
    const char* _data = nullptr;
142
    size_t _size;
143
};
144
145
// The entry with smaller CachePriority will evict firstly
146
enum class CachePriority { NORMAL = 0, DURABLE = 1 };
147
148
using CachePrunePredicate = std::function<bool(const LRUHandle*)>;
149
// CacheValueTimeExtractor can extract timestamp
150
// in cache value through the specified function,
151
// such as last_visit_time in InvertedIndexSearcherCache::CacheValue
152
using CacheValueTimeExtractor = std::function<int64_t(const void*)>;
153
struct PrunedInfo {
154
    int64_t pruned_count = 0;
155
    int64_t pruned_size = 0;
156
};
157
158
class Cache {
159
public:
160
2.28k
    Cache() = default;
161
162
    // Destroys all existing entries by calling the "deleter"
163
    // function that was passed to the constructor.
164
2.27k
    virtual ~Cache() = default;
165
166
    // Opaque handle to an entry stored in the cache.
167
    struct Handle {};
168
169
    // Insert a mapping from key->value into the cache and assign it
170
    // the specified charge against the total cache capacity.
171
    //
172
    // Returns a handle that corresponds to the mapping.  The caller
173
    // must call this->release(handle) when the returned mapping is no
174
    // longer needed.
175
    //
176
    // When the inserted entry is no longer needed, the key and
177
    // value will be passed to "deleter".
178
    //
179
    // if cache is lru k and cache is full, first insert of key will not succeed.
180
    //
181
    // Note: if is ShardedLRUCache, cache capacity = ShardedLRUCache_capacity / num_shards.
182
    virtual Handle* insert(const CacheKey& key, void* value, size_t charge,
183
                           CachePriority priority = CachePriority::NORMAL) = 0;
184
185
    // If the cache has no mapping for "key", returns nullptr.
186
    //
187
    // Else return a handle that corresponds to the mapping.  The caller
188
    // must call this->release(handle) when the returned mapping is no
189
    // longer needed.
190
    virtual Handle* lookup(const CacheKey& key) = 0;
191
192
    // Release a mapping returned by a previous Lookup().
193
    // REQUIRES: handle must not have been released yet.
194
    // REQUIRES: handle must have been returned by a method on *this.
195
    virtual void release(Handle* handle) = 0;
196
197
    // Grow an entry's eviction charge after lazy initialization, without changing its
198
    // memory tracker. The caller must hold a live handle. Stale smaller charges are ignored.
199
    virtual void update_charge(Handle* handle, size_t charge) = 0;
200
201
    // Return the value encapsulated in a handle returned by a
202
    // successful lookup().
203
    // REQUIRES: handle must not have been released yet.
204
    // REQUIRES: handle must have been returned by a method on *this.
205
    virtual void* value(Handle* handle) = 0;
206
207
    // If the cache contains entry for key, erase it.  Note that the
208
    // underlying entry will be kept around until all existing handles
209
    // to it have been released.
210
    virtual void erase(const CacheKey& key) = 0;
211
212
    // Return a new numeric id.  May be used by multiple clients who are
213
    // sharing the same cache to partition the key space.  Typically the
214
    // client will allocate a new id at startup and prepend the id to
215
    // its cache keys.
216
    virtual uint64_t new_id() = 0;
217
218
    // Remove all cache entries that are not actively in use.  Memory-constrained
219
    // applications may wish to call this method to reduce memory usage.
220
    // Default implementation of Prune() does nothing.  Subclasses are strongly
221
    // encouraged to override the default implementation.  A future release of
222
    // leveldb may change prune() to a pure abstract method.
223
    // return num of entries being pruned.
224
0
    virtual PrunedInfo prune() { return {0, 0}; }
225
226
    // Same as prune(), but the entry will only be pruned if the predicate matched.
227
    // NOTICE: the predicate should be simple enough, or the prune_if() function
228
    // may hold lock for a long time to execute predicate.
229
0
    virtual PrunedInfo prune_if(CachePrunePredicate pred, bool lazy_mode = false) { return {0, 0}; }
230
231
    virtual void for_each_entry(const std::function<void(const LRUHandle*)>& visitor) = 0;
232
233
    virtual int64_t get_usage() = 0;
234
235
    virtual PrunedInfo set_capacity(size_t capacity) = 0;
236
    virtual size_t get_capacity() = 0;
237
238
    virtual size_t get_element_count() = 0;
239
240
private:
241
    DISALLOW_COPY_AND_ASSIGN(Cache);
242
};
243
244
// An entry is a variable length heap-allocated structure.  Entries
245
// are kept in a circular doubly linked list ordered by access time.
246
// Note: member variables can only be POD types and raw pointer,
247
// cannot be class objects or smart pointers, because LRUHandle will be created using malloc.
248
struct LRUHandle {
249
    void* value = nullptr;
250
    struct LRUHandle* next_hash = nullptr; // next entry in hash table
251
    struct LRUHandle* next = nullptr;      // next entry in lru list
252
    struct LRUHandle* prev = nullptr;      // previous entry in lru list
253
    size_t charge;
254
    size_t key_length;
255
    size_t total_size; // Entry charge, used to limit cache capacity, LRUCacheType::SIZE including key length.
256
    bool in_cache; // Whether entry is in the cache.
257
    uint32_t refs;
258
    uint32_t hash; // Hash of key(); used for fast sharding and comparisons
259
    CachePriority priority = CachePriority::NORMAL;
260
    LRUCacheType type;
261
    int64_t last_visit_time; // Save the last visit time of this cache entry.
262
    char key_data[1];        // Beginning of key
263
    // Note! key_data must be at the end.
264
265
760k
    CacheKey key() const {
266
        // For cheaper lookups, we allow a temporary Handle object
267
        // to store a pointer to a key in "value".
268
760k
        if (next == this) {
269
0
            return *(reinterpret_cast<CacheKey*>(value));
270
760k
        } else {
271
760k
            return {key_data, key_length};
272
760k
        }
273
760k
    }
274
275
316k
    void free() {
276
316k
        if (value != nullptr) { // value allows null pointer.
277
316k
            delete (LRUCacheValueBase*)value;
278
316k
        }
279
316k
        ::free(this);
280
316k
    }
281
};
282
283
// We provide our own simple hash tablet since it removes a whole bunch
284
// of porting hacks and is also faster than some of the built-in hash
285
// tablet implementations in some of the compiler/runtime combinations
286
// we have tested.  E.g., readrandom speeds up by ~5% over the g++
287
// 4.4.3's builtin hashtable.
288
289
class HandleTable {
290
public:
291
28.6k
    HandleTable() { _resize(); }
292
293
    ~HandleTable();
294
295
    LRUHandle* lookup(const CacheKey& key, uint32_t hash);
296
297
    LRUHandle* insert(LRUHandle* h);
298
299
    // Remove element from hash table by "key" and "hash".
300
    LRUHandle* remove(const CacheKey& key, uint32_t hash);
301
302
    // Remove element from hash table by "h", it would be faster
303
    // than the function above.
304
    // Return whether h is found and removed.
305
    bool remove(const LRUHandle* h);
306
307
    uint32_t element_count() const;
308
309
private:
310
    FRIEND_TEST(CacheTest, HandleTableTest);
311
312
    // The tablet consists of an array of buckets where each bucket is
313
    // a linked list of cache entries that hash into the bucket.
314
    uint32_t _length {};
315
    uint32_t _elems {};
316
    LRUHandle** _list = nullptr;
317
318
    // Return a pointer to slot that points to a cache entry that
319
    // matches key/hash.  If there is no such cache entry, return a
320
    // pointer to the trailing slot in the corresponding linked list.
321
    LRUHandle** _find_pointer(const CacheKey& key, uint32_t hash);
322
323
    void _resize();
324
};
325
326
// pair first is timestatmp, put <timestatmp, LRUHandle*> into asc set,
327
// when need to free space, can first evict the begin of the set,
328
// because the begin element's timestamp is the oldest.
329
using LRUHandleSortedSet = std::set<std::pair<int64_t, LRUHandle*>>;
330
331
// A single shard of sharded cache.
332
class LRUCache {
333
public:
334
    LRUCache(LRUCacheType type, bool is_lru_k = DEFAULT_LRU_CACHE_IS_LRU_K);
335
    ~LRUCache();
336
337
    // visits_lru_cache_key is the hash value of CacheKey.
338
    // If there is a hash conflict, a cache entry may be inserted early
339
    // and another cache entry with the same key hash may be inserted later.
340
    // Otherwise, this does not affect the correctness of the cache.
341
    using visits_lru_cache_key = uint32_t;
342
    using visits_lru_cache_pair = std::pair<visits_lru_cache_key, size_t>;
343
344
    // Separate from constructor so caller can easily make an array of LRUCache
345
    PrunedInfo set_capacity(size_t capacity);
346
28.6k
    void set_element_count_capacity(uint32_t element_count_capacity) {
347
28.6k
        _element_count_capacity = element_count_capacity;
348
28.6k
    }
349
350
    // Like Cache methods, but with an extra "hash" parameter.
351
    // Must call release on the returned handle pointer.
352
    Cache::Handle* insert(const CacheKey& key, uint32_t hash, void* value, size_t charge,
353
                          CachePriority priority = CachePriority::NORMAL);
354
    Cache::Handle* lookup(const CacheKey& key, uint32_t hash);
355
    void release(Cache::Handle* handle);
356
    void update_charge(Cache::Handle* handle, size_t charge);
357
    void erase(const CacheKey& key, uint32_t hash);
358
    PrunedInfo prune();
359
    PrunedInfo prune_if(CachePrunePredicate pred, bool lazy_mode = false);
360
    void for_each_entry(const std::function<void(const LRUHandle*)>& visitor);
361
362
    void set_cache_value_time_extractor(CacheValueTimeExtractor cache_value_time_extractor);
363
    void set_cache_value_check_timestamp(bool cache_value_check_timestamp);
364
365
    uint64_t get_lookup_count();
366
    uint64_t get_hit_count();
367
    uint64_t get_miss_count();
368
    uint64_t get_stampede_count();
369
370
    size_t get_usage();
371
    size_t get_capacity();
372
    size_t get_element_count();
373
374
private:
375
    void _lru_remove(LRUHandle* e);
376
    void _lru_append(LRUHandle* list, LRUHandle* e);
377
    bool _unref(LRUHandle* e);
378
    void _evict_from_lru(size_t total_size, LRUHandle** to_remove_head);
379
    void _evict_from_lru_with_time(size_t total_size, LRUHandle** to_remove_head);
380
    void _evict_one_entry(LRUHandle* e);
381
    bool _check_element_count_limit();
382
    bool _lru_k_insert_visits_list(size_t total_size, visits_lru_cache_key visits_key);
383
384
private:
385
    LRUCacheType _type;
386
387
    // Initialized before use.
388
    size_t _capacity = 0;
389
390
    // _mutex protects the following state.
391
    std::mutex _mutex;
392
    size_t _usage = 0;
393
394
    // Dummy head of LRU list.
395
    // Entries have refs==1 and in_cache==true.
396
    // _lru_normal.prev is newest entry, _lru_normal.next is oldest entry.
397
    LRUHandle _lru_normal;
398
    // _lru_durable.prev is newest entry, _lru_durable.next is oldest entry.
399
    LRUHandle _lru_durable;
400
401
    HandleTable _table;
402
403
    uint64_t _lookup_count = 0; // number of cache lookups
404
    uint64_t _hit_count = 0;    // number of cache hits
405
    uint64_t _miss_count = 0;   // number of cache misses
406
    uint64_t _stampede_count = 0;
407
408
    CacheValueTimeExtractor _cache_value_time_extractor;
409
    bool _cache_value_check_timestamp = false;
410
    LRUHandleSortedSet _sorted_normal_entries_with_timestamp;
411
    LRUHandleSortedSet _sorted_durable_entries_with_timestamp;
412
413
    uint32_t _element_count_capacity = 0;
414
415
    bool _is_lru_k = false; // LRU-K algorithm, K=2
416
    std::list<visits_lru_cache_pair> _visits_lru_cache_list;
417
    std::unordered_map<visits_lru_cache_key, std::list<visits_lru_cache_pair>::iterator>
418
            _visits_lru_cache_map;
419
    size_t _visits_lru_cache_usage = 0;
420
};
421
422
class ShardedLRUCache : public Cache {
423
public:
424
    ~ShardedLRUCache() override;
425
    Handle* insert(const CacheKey& key, void* value, size_t charge,
426
                   CachePriority priority = CachePriority::NORMAL) override;
427
    Handle* lookup(const CacheKey& key) override;
428
    void release(Handle* handle) override;
429
    void update_charge(Handle* handle, size_t charge) override;
430
    void erase(const CacheKey& key) override;
431
    void* value(Handle* handle) override;
432
    uint64_t new_id() override;
433
    PrunedInfo prune() override;
434
    PrunedInfo prune_if(CachePrunePredicate pred, bool lazy_mode = false) override;
435
    void for_each_entry(const std::function<void(const LRUHandle*)>& visitor) override;
436
    int64_t get_usage() override;
437
    size_t get_element_count() override;
438
    PrunedInfo set_capacity(size_t capacity) override;
439
    size_t get_capacity() override;
440
441
private:
442
    // LRUCache can only be created and managed with LRUCachePolicy.
443
    friend class LRUCachePolicy;
444
445
    explicit ShardedLRUCache(const std::string& name, size_t capacity, LRUCacheType type,
446
                             uint32_t num_shards, uint32_t element_count_capacity, bool is_lru_k);
447
    explicit ShardedLRUCache(const std::string& name, size_t capacity, LRUCacheType type,
448
                             uint32_t num_shards,
449
                             CacheValueTimeExtractor cache_value_time_extractor,
450
                             bool cache_value_check_timestamp, uint32_t element_count_capacity,
451
                             bool is_lru_k);
452
453
    void update_cache_metrics() const;
454
455
private:
456
    static uint32_t _hash_slice(const CacheKey& s);
457
1.54M
    uint32_t _shard(uint32_t hash) const {
458
1.54M
        return _num_shard_bits > 0 ? (hash >> (32 - _num_shard_bits)) : 0;
459
1.54M
    }
460
461
    std::string _name;
462
    const int _num_shard_bits;
463
    const uint32_t _num_shards;
464
    LRUCache** _shards = nullptr;
465
    std::atomic<uint64_t> _last_id;
466
    std::mutex _mutex;
467
    size_t _capacity {0};
468
469
    std::shared_ptr<MetricEntity> _entity;
470
    IntGauge* cache_capacity = nullptr;
471
    IntGauge* cache_usage = nullptr;
472
    IntGauge* cache_element_count = nullptr;
473
    DoubleGauge* cache_usage_ratio = nullptr;
474
    IntCounter* cache_lookup_count = nullptr;
475
    IntCounter* cache_hit_count = nullptr;
476
    IntCounter* cache_miss_count = nullptr;
477
    IntCounter* cache_stampede_count = nullptr;
478
    DoubleGauge* cache_hit_ratio = nullptr;
479
    // bvars
480
    std::unique_ptr<bvar::Adder<uint64_t>> _hit_count_bvar;
481
    std::unique_ptr<bvar::PerSecond<bvar::Adder<uint64_t>>> _hit_count_per_second;
482
    std::unique_ptr<bvar::Adder<uint64_t>> _lookup_count_bvar;
483
    std::unique_ptr<bvar::PerSecond<bvar::Adder<uint64_t>>> _lookup_count_per_second;
484
};
485
486
// Compatible with ShardedLRUCache usage, but will not actually cache.
487
class DummyLRUCache : public Cache {
488
public:
489
    // Must call release on the returned handle pointer.
490
    Handle* insert(const CacheKey& key, void* value, size_t charge,
491
                   CachePriority priority = CachePriority::NORMAL) override;
492
205
    Handle* lookup(const CacheKey& key) override { return nullptr; };
493
    void release(Handle* handle) override;
494
0
    void update_charge(Handle* handle, size_t charge) override {}
495
146
    void erase(const CacheKey& key) override {};
496
    void* value(Handle* handle) override;
497
0
    uint64_t new_id() override { return 0; };
498
0
    PrunedInfo prune() override { return {0, 0}; };
499
0
    PrunedInfo prune_if(CachePrunePredicate pred, bool lazy_mode = false) override {
500
0
        return {0, 0};
501
0
    };
502
0
    void for_each_entry(const std::function<void(const LRUHandle*)>& visitor) override {}
503
0
    int64_t get_usage() override { return 0; };
504
0
    PrunedInfo set_capacity(size_t capacity) override { return {0, 0}; };
505
0
    size_t get_capacity() override { return 0; };
506
0
    size_t get_element_count() override { return 0; };
507
};
508
509
} // namespace doris
510
#include "common/compile_check_end.h"