userver: userver/cache/nway_lru_cache.hpp Source File
⚠️ This is the documentation for an old userver version. Click here to switch to the latest version.
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages Concepts
nway_lru_cache.hpp
1#pragma once
2
3#include <functional>
4#include <optional>
5#include <vector>
6
7#include <boost/container_hash/hash.hpp>
8
9#include <userver/cache/lru_map.hpp>
10#include <userver/dump/dumper.hpp>
11#include <userver/dump/operations.hpp>
12#include <userver/engine/mutex.hpp>
13
14USERVER_NAMESPACE_BEGIN
15
16namespace cache {
17
18/// @ingroup userver_containers
19template <typename T, typename U, typename Hash = std::hash<T>,
20 typename Equal = std::equal_to<T>>
21class NWayLRU final {
22 public:
23 /// @param ways is the number of ways (a.k.a. shards, internal hash-maps),
24 /// into which elements are distributed based on their hash. Each shard is
25 /// protected by an individual mutex. Larger `ways` means more internal
26 /// hash-map instances and more memory usage, but less contention. A good
27 /// starting point is `ways=16`. If you encounter contention, you can increase
28 /// `ways` to something on the order of `256` or whatever your RAM constraints
29 /// allow.
30 ///
31 /// @param way_size is the maximum allowed amount of elements per way. When
32 /// the size of a way reaches this number, existing elements are deleted
33 /// according to the LRU policy.
34 ///
35 /// The maximum total number of elements is `ways * way_size`.
36 NWayLRU(size_t ways, size_t way_size, const Hash& hash = Hash(),
37 const Equal& equal = Equal());
38
39 void Put(const T& key, U value);
40
41 template <typename Validator>
42 std::optional<U> Get(const T& key, Validator validator);
43
44 std::optional<U> Get(const T& key) {
45 return Get(key, [](const U&) { return true; });
46 }
47
48 U GetOr(const T& key, const U& default_value);
49
50 void Invalidate();
51
52 void InvalidateByKey(const T& key);
53
54 /// Iterates over all items. May be slow for big caches.
55 template <typename Function>
56 void VisitAll(Function func) const;
57
58 size_t GetSize() const;
59
60 /// For the description of `way_size`,
61 /// see the cache::NWayLRU::NWayLRU constructor.
62 void UpdateWaySize(size_t way_size);
63
64 void Write(dump::Writer& writer) const;
65 void Read(dump::Reader& reader);
66
67 /// The dump::Dumper will be notified of any cache updates. This method is not
68 /// thread-safe.
69 void SetDumper(std::shared_ptr<dump::Dumper> dumper);
70
71 private:
72 struct Way {
73 Way(Way&& other) noexcept : cache(std::move(other.cache)) {}
74
75 // max_size is not used, will be reset by Resize() in NWayLRU::NWayLRU
76 Way(const Hash& hash, const Equal& equal) : cache(1, hash, equal) {}
77
78 mutable engine::Mutex mutex;
79 LruMap<T, U, Hash, Equal> cache;
80 };
81
82 Way& GetWay(const T& key);
83
84 void NotifyDumper();
85
86 std::vector<Way> caches_;
87 Hash hash_fn_;
88 std::shared_ptr<dump::Dumper> dumper_{nullptr};
89};
90
91template <typename T, typename U, typename Hash, typename Eq>
92NWayLRU<T, U, Hash, Eq>::NWayLRU(size_t ways, size_t way_size, const Hash& hash,
93 const Eq& equal)
94 : caches_(), hash_fn_(hash) {
95 caches_.reserve(ways);
96 for (size_t i = 0; i < ways; ++i) caches_.emplace_back(hash, equal);
97 if (ways == 0) throw std::logic_error("Ways must be positive");
98
99 for (auto& way : caches_) way.cache.SetMaxSize(way_size);
100}
101
102template <typename T, typename U, typename Hash, typename Eq>
103void NWayLRU<T, U, Hash, Eq>::Put(const T& key, U value) {
104 auto& way = GetWay(key);
105 {
106 std::unique_lock<engine::Mutex> lock(way.mutex);
107 way.cache.Put(key, std::move(value));
108 }
109 NotifyDumper();
110}
111
112template <typename T, typename U, typename Hash, typename Eq>
113template <typename Validator>
114std::optional<U> NWayLRU<T, U, Hash, Eq>::Get(const T& key,
115 Validator validator) {
116 auto& way = GetWay(key);
117 std::unique_lock<engine::Mutex> lock(way.mutex);
118 auto* value = way.cache.Get(key);
119
120 if (value) {
121 if (validator(*value)) return *value;
122 way.cache.Erase(key);
123 }
124
125 return std::nullopt;
126}
127
128template <typename T, typename U, typename Hash, typename Eq>
129void NWayLRU<T, U, Hash, Eq>::InvalidateByKey(const T& key) {
130 auto& way = GetWay(key);
131 {
132 std::unique_lock<engine::Mutex> lock(way.mutex);
133 way.cache.Erase(key);
134 }
135 NotifyDumper();
136}
137
138template <typename T, typename U, typename Hash, typename Eq>
139U NWayLRU<T, U, Hash, Eq>::GetOr(const T& key, const U& default_value) {
140 auto& way = GetWay(key);
141 std::unique_lock<engine::Mutex> lock(way.mutex);
142 return way.cache.GetOr(key, default_value);
143}
144
145template <typename T, typename U, typename Hash, typename Eq>
146void NWayLRU<T, U, Hash, Eq>::Invalidate() {
147 for (auto& way : caches_) {
148 std::unique_lock<engine::Mutex> lock(way.mutex);
149 way.cache.Clear();
150 }
151 NotifyDumper();
152}
153
154template <typename T, typename U, typename Hash, typename Eq>
155template <typename Function>
156void NWayLRU<T, U, Hash, Eq>::VisitAll(Function func) const {
157 for (const auto& way : caches_) {
158 std::unique_lock<engine::Mutex> lock(way.mutex);
159 way.cache.VisitAll(func);
160 }
161}
162
163template <typename T, typename U, typename Hash, typename Eq>
164size_t NWayLRU<T, U, Hash, Eq>::GetSize() const {
165 size_t size{0};
166 for (const auto& way : caches_) {
167 std::unique_lock<engine::Mutex> lock(way.mutex);
168 size += way.cache.GetSize();
169 }
170 return size;
171}
172
173template <typename T, typename U, typename Hash, typename Eq>
174void NWayLRU<T, U, Hash, Eq>::UpdateWaySize(size_t way_size) {
175 for (auto& way : caches_) {
176 std::unique_lock<engine::Mutex> lock(way.mutex);
177 way.cache.SetMaxSize(way_size);
178 }
179}
180
181template <typename T, typename U, typename Hash, typename Eq>
182typename NWayLRU<T, U, Hash, Eq>::Way& NWayLRU<T, U, Hash, Eq>::GetWay(
183 const T& key) {
184 /// It is needed to twist hash because there is hash map in LruMap. Otherwise
185 /// nodes will fall into one bucket. According to
186 /// https://www.boost.org/doc/libs/1_83_0/libs/container_hash/doc/html/hash.html#notes_hash_combine
187 /// hash_combine can be treated as hash itself
188 auto seed = hash_fn_(key);
189 boost::hash_combine(seed, 0);
190 auto n = seed % caches_.size();
191 return caches_[n];
192}
193
194template <typename T, typename U, typename Hash, typename Equal>
195void NWayLRU<T, U, Hash, Equal>::Write(dump::Writer& writer) const {
196 writer.Write(caches_.size());
197
198 for (const Way& way : caches_) {
199 std::unique_lock<engine::Mutex> lock(way.mutex);
200
201 writer.Write(way.cache.GetSize());
202
203 way.cache.VisitAll([&writer](const T& key, const U& value) {
204 writer.Write(key);
205 writer.Write(value);
206 });
207 }
208}
209
210template <typename T, typename U, typename Hash, typename Equal>
211void NWayLRU<T, U, Hash, Equal>::Read(dump::Reader& reader) {
212 Invalidate();
213
214 const auto ways = reader.Read<std::size_t>();
215 for (std::size_t i = 0; i < ways; ++i) {
216 const auto elements_in_way = reader.Read<std::size_t>();
217 for (std::size_t j = 0; j < elements_in_way; ++j) {
218 auto key = reader.Read<T>();
219 auto value = reader.Read<U>();
220 Put(std::move(key), std::move(value));
221 }
222 }
223}
224
225template <typename T, typename U, typename Hash, typename Equal>
226void NWayLRU<T, U, Hash, Equal>::NotifyDumper() {
227 if (dumper_ != nullptr) {
228 dumper_->OnUpdateCompleted();
229 }
230}
231
232template <typename T, typename U, typename Hash, typename Equal>
233void NWayLRU<T, U, Hash, Equal>::SetDumper(
234 std::shared_ptr<dump::Dumper> dumper) {
235 dumper_ = std::move(dumper);
236}
237
238} // namespace cache
239
240USERVER_NAMESPACE_END