userver: userver/cache/expirable_lru_cache.hpp Source File
Loading...
Searching...
No Matches
expirable_lru_cache.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/cache/expirable_lru_cache.hpp
4/// @brief @copybrief cache::ExpirableLruCache
5
6#include <atomic>
7#include <chrono>
8#include <optional>
9
10#include <userver/cache/lru_cache_config.hpp>
11#include <userver/cache/lru_cache_statistics.hpp>
12#include <userver/cache/nway_lru_cache.hpp>
13#include <userver/concurrent/mutex_set.hpp>
14#include <userver/dump/common.hpp>
15#include <userver/dump/dumper.hpp>
16#include <userver/engine/async.hpp>
17#include <userver/utils/datetime.hpp>
18#include <userver/utils/impl/cached_time.hpp>
19#include <userver/utils/impl/wait_token_storage.hpp>
20#include <userver/utils/statistics/writer.hpp>
21
22USERVER_NAMESPACE_BEGIN
23
24namespace cache {
25
26namespace impl {
27
28template <typename Value>
29struct ExpirableValue final {
30 Value value;
31 std::chrono::steady_clock::time_point update_time;
32};
33
34template <typename Value>
35void Write(dump::Writer& writer, const impl::ExpirableValue<Value>& value) {
36 const auto [now, steady_now] = utils::impl::GetGlobalTime();
37 writer.Write(value.value);
38 writer.Write(value.update_time - steady_now + now);
39}
40
41template <typename Value>
42impl::ExpirableValue<Value> Read(dump::Reader& reader, dump::To<impl::ExpirableValue<Value>>) {
43 const auto [now, steady_now] = utils::impl::GetGlobalTime();
44 // Evaluation order of arguments is guaranteed in brace-initialization.
45 return impl::ExpirableValue<Value>{
46 reader.Read<Value>(), reader.Read<std::chrono::system_clock::time_point>() - now + steady_now};
47}
48
49} // namespace impl
50
51/// @ingroup userver_containers
52/// @brief Class for expirable LRU cache. Use cache::LruMap for not expirable
53/// LRU Cache.
54///
55/// Example usage:
56///
57/// @snippet cache/expirable_lru_cache_test.cpp Sample ExpirableLruCache
58template <typename Key, typename Value, typename Hash = std::hash<Key>, typename Equal = std::equal_to<Key>>
59class ExpirableLruCache final {
60public:
61 using UpdateValueFunc = std::function<Value(const Key&)>;
62
63 /// Cache read mode
64 enum class ReadMode {
65 kSkipCache, ///< Do not cache value got from update function
66 kUseCache, ///< Cache value got from update function
67 };
68
69 /// For the description of `ways` and `way_size`,
70 /// see the cache::NWayLRU::NWayLRU constructor.
71 ExpirableLruCache(size_t ways, size_t way_size, const Hash& hash = Hash(), const Equal& equal = Equal());
72
73 ~ExpirableLruCache();
74
75 /// For the description of `way_size`,
76 /// see the cache::NWayLRU::NWayLRU constructor.
77 void SetWaySize(size_t way_size);
78
79 std::chrono::milliseconds GetMaxLifetime() const noexcept;
80
81 void SetMaxLifetime(std::chrono::milliseconds max_lifetime);
82
83 /**
84 * Sets background update mode. If "background_update" mode is kDisabled,
85 * expiring values are not updated in background (asynchronously) or are
86 * updated if "background_update" is kEnabled.
87 */
88 void SetBackgroundUpdate(BackgroundUpdateMode background_update);
89
90 /**
91 * @returns GetOptional("key", update_func) if it is not std::nullopt.
92 * Otherwise the result of update_func(key) is returned, and additionally
93 * stored in cache if "read_mode" is kUseCache.
94 */
95 Value Get(const Key& key, const UpdateValueFunc& update_func, ReadMode read_mode = ReadMode::kUseCache);
96
97 /**
98 * Update value in cache by "update_func" if background update mode is
99 * kEnabled and "key" is in cache and not expired but its lifetime ends soon.
100 * @returns value by key if key is in cache and not expired, or std::nullopt
101 * otherwise
102 */
103 std::optional<Value> GetOptional(const Key& key, const UpdateValueFunc& update_func);
104
105 /**
106 * GetOptional, but without expiry checks and value updates.
107 *
108 * Used during fallback in FallbackELruCache.
109 */
110 std::optional<Value> GetOptionalUnexpirable(const Key& key);
111
112 /**
113 * GetOptional, but without expiry check.
114 *
115 * Used during fallback in FallbackELruCache.
116 */
117 std::optional<Value> GetOptionalUnexpirableWithUpdate(const Key& key, const UpdateValueFunc& update_func);
118
119 /**
120 * GetOptional, but without value updates.
121 */
122 std::optional<Value> GetOptionalNoUpdate(const Key& key);
123
124 std::optional<impl::ExpirableValue<Value>> GetOptionalNoUpdateWithLastUpdateTime(const Key& key);
125
126 void Put(const Key& key, const Value& value);
127
128 void Put(const Key& key, Value&& value);
129
130 const impl::ExpirableLruCacheStatistics& GetStatistics() const;
131
132 size_t GetSizeApproximate() const;
133
134 /// Clear cache
136
137 /// Erase key from cache
138 void InvalidateByKey(const Key& key);
139
140 /// Erase key from cache conditionally
141 template <typename Predicate>
142 void InvalidateByKeyIf(const Key& key, Predicate pred);
143
144 /// Add async task for updating value by update_func(key)
145 void UpdateInBackground(const Key& key, UpdateValueFunc update_func);
146
147 void Write(dump::Writer& writer) const;
148
149 void Read(dump::Reader& reader);
150
151 /// The dump::Dumper will be notified of any cache updates. This method is not
152 /// thread-safe.
153 void SetDumper(std::shared_ptr<dump::Dumper> dumper);
154
155private:
156 bool IsExpired(std::chrono::steady_clock::time_point update_time, std::chrono::steady_clock::time_point now) const;
157
158 bool ShouldUpdate(std::chrono::steady_clock::time_point update_time, std::chrono::steady_clock::time_point now)
159 const;
160
161 cache::NWayLRU<Key, impl::ExpirableValue<Value>, Hash, Equal> lru_;
162 std::atomic<std::chrono::milliseconds> max_lifetime_{std::chrono::milliseconds(0)};
163 std::atomic<BackgroundUpdateMode> background_update_mode_{BackgroundUpdateMode::kDisabled};
164 impl::ExpirableLruCacheStatistics stats_;
165 concurrent::MutexSet<Key, Hash, Equal> mutex_set_;
166 utils::impl::WaitTokenStorage wait_token_storage_;
167};
168
169template <typename Key, typename Value, typename Hash, typename Equal>
170ExpirableLruCache<Key, Value, Hash, Equal>::ExpirableLruCache(
171 size_t ways,
172 size_t way_size,
173 const Hash& hash,
174 const Equal& equal
175)
176 : lru_(ways, way_size, hash, equal), mutex_set_{ways, way_size, hash, equal} {}
177
178template <typename Key, typename Value, typename Hash, typename Equal>
179ExpirableLruCache<Key, Value, Hash, Equal>::~ExpirableLruCache() {
180 wait_token_storage_.WaitForAllTokens();
181}
182
183template <typename Key, typename Value, typename Hash, typename Equal>
184void ExpirableLruCache<Key, Value, Hash, Equal>::SetWaySize(size_t way_size) {
185 lru_.UpdateWaySize(way_size);
186}
187
188template <typename Key, typename Value, typename Hash, typename Equal>
189std::chrono::milliseconds ExpirableLruCache<Key, Value, Hash, Equal>::GetMaxLifetime() const noexcept {
190 return max_lifetime_.load();
191}
192
193template <typename Key, typename Value, typename Hash, typename Equal>
194void ExpirableLruCache<Key, Value, Hash, Equal>::SetMaxLifetime(std::chrono::milliseconds max_lifetime) {
195 max_lifetime_ = max_lifetime;
196}
197
198template <typename Key, typename Value, typename Hash, typename Equal>
199void ExpirableLruCache<Key, Value, Hash, Equal>::SetBackgroundUpdate(BackgroundUpdateMode background_update) {
200 background_update_mode_ = background_update;
201}
202
203template <typename Key, typename Value, typename Hash, typename Equal>
204Value ExpirableLruCache<Key, Value, Hash, Equal>::Get(
205 const Key& key,
206 const UpdateValueFunc& update_func,
207 ReadMode read_mode
208) {
210 auto opt_old_value = GetOptional(key, update_func);
211 if (opt_old_value) {
212 return std::move(*opt_old_value);
213 }
214
215 auto mutex = mutex_set_.GetMutexForKey(key);
216 const std::lock_guard lock(mutex);
217 // Test one more time - concurrent ExpirableLruCache::Get()
218 // might have put the value
219 auto old_value = lru_.Get(key);
220 if (old_value && !IsExpired(old_value->update_time, now)) {
221 return std::move(old_value->value);
222 }
223
224 auto value = update_func(key);
225 if (read_mode == ReadMode::kUseCache) {
226 lru_.Put(key, {value, now});
227 }
228 return value;
229}
230
231template <typename Key, typename Value, typename Hash, typename Equal>
232std::optional<Value>
233ExpirableLruCache<Key, Value, Hash, Equal>::GetOptional(const Key& key, const UpdateValueFunc& update_func) {
235 auto old_value = lru_.Get(key);
236
237 if (old_value) {
238 if (!IsExpired(old_value->update_time, now)) {
239 impl::CacheHit(stats_);
240
241 if (ShouldUpdate(old_value->update_time, now)) {
242 UpdateInBackground(key, update_func);
243 }
244
245 return std::move(old_value->value);
246 } else {
247 impl::CacheStale(stats_);
248 }
249 }
250 impl::CacheMiss(stats_);
251
252 return std::nullopt;
253}
254
255template <typename Key, typename Value, typename Hash, typename Equal>
256std::optional<Value> ExpirableLruCache<Key, Value, Hash, Equal>::GetOptionalUnexpirable(const Key& key) {
257 auto old_value = lru_.Get(key);
258
259 if (old_value) {
260 impl::CacheHit(stats_);
261 return old_value->value;
262 }
263 impl::CacheMiss(stats_);
264
265 return std::nullopt;
266}
267
268template <typename Key, typename Value, typename Hash, typename Equal>
269std::optional<Value> ExpirableLruCache<Key, Value, Hash, Equal>::GetOptionalUnexpirableWithUpdate(
270 const Key& key,
271 const UpdateValueFunc& update_func
272) {
274 auto old_value = lru_.Get(key);
275
276 if (old_value) {
277 impl::CacheHit(stats_);
278
279 if (ShouldUpdate(old_value->update_time, now)) {
280 UpdateInBackground(key, update_func);
281 }
282
283 return old_value->value;
284 }
285 impl::CacheMiss(stats_);
286
287 return std::nullopt;
288}
289
290template <typename Key, typename Value, typename Hash, typename Equal>
291std::optional<impl::ExpirableValue<Value>>
292ExpirableLruCache<Key, Value, Hash, Equal>::GetOptionalNoUpdateWithLastUpdateTime(const Key& key) {
293 const auto now = utils::datetime::SteadyNow();
294 const auto old_value = lru_.Get(key);
295 if (old_value) {
296 if (!IsExpired(old_value->update_time, now)) {
297 impl::CacheHit(stats_);
298 return old_value;
299 } else {
300 impl::CacheStale(stats_);
301 }
302 }
303 impl::CacheMiss(stats_);
304 return std::nullopt;
305}
306
307template <typename Key, typename Value, typename Hash, typename Equal>
308std::optional<Value> ExpirableLruCache<Key, Value, Hash, Equal>::GetOptionalNoUpdate(const Key& key) {
309 auto value_with_update_time = GetOptionalNoUpdateWithLastUpdateTime(key);
310 if (value_with_update_time.has_value()) {
311 return value_with_update_time->value;
312 }
313 return std::nullopt;
314}
315
316template <typename Key, typename Value, typename Hash, typename Equal>
317void ExpirableLruCache<Key, Value, Hash, Equal>::Put(const Key& key, const Value& value) {
318 lru_.Put(key, {value, utils::datetime::SteadyNow()});
319}
320
321template <typename Key, typename Value, typename Hash, typename Equal>
322void ExpirableLruCache<Key, Value, Hash, Equal>::Put(const Key& key, Value&& value) {
323 lru_.Put(key, {std::move(value), utils::datetime::SteadyNow()});
324}
325
326template <typename Key, typename Value, typename Hash, typename Equal>
327const impl::ExpirableLruCacheStatistics& ExpirableLruCache<Key, Value, Hash, Equal>::GetStatistics() const {
328 return stats_;
329}
330
331template <typename Key, typename Value, typename Hash, typename Equal>
332size_t ExpirableLruCache<Key, Value, Hash, Equal>::GetSizeApproximate() const {
333 return lru_.GetSize();
334}
335
336template <typename Key, typename Value, typename Hash, typename Equal>
337void ExpirableLruCache<Key, Value, Hash, Equal>::Invalidate() {
338 lru_.Invalidate();
339}
340
341template <typename Key, typename Value, typename Hash, typename Equal>
342void ExpirableLruCache<Key, Value, Hash, Equal>::InvalidateByKey(const Key& key) {
343 lru_.InvalidateByKey(key);
344}
345
346template <typename Key, typename Value, typename Hash, typename Equal>
347template <typename Predicate>
348void ExpirableLruCache<Key, Value, Hash, Equal>::InvalidateByKeyIf(const Key& key, Predicate pred) {
350
351 auto mutex = mutex_set_.GetMutexForKey(key);
352 const std::lock_guard lock(mutex);
353 const auto cur_value = lru_.Get(key);
354
355 if (cur_value.has_value() && !IsExpired(cur_value->update_time, now) && pred(cur_value->value)) {
357 }
358}
359
360template <typename Key, typename Value, typename Hash, typename Equal>
361void ExpirableLruCache<Key, Value, Hash, Equal>::UpdateInBackground(const Key& key, UpdateValueFunc update_func) {
362 impl::CacheBackgroundUpdate(stats_);
363
364 // cache will wait for all detached tasks in ~ExpirableLruCache()
366 engine::AsyncNoSpan([token = wait_token_storage_.GetToken(), this, key, update_func = std::move(update_func)] {
367 auto mutex = mutex_set_.GetMutexForKey(key);
368 const std::unique_lock lock(mutex, std::try_to_lock);
369 if (!lock) {
370 // someone is updating the key right now
371 return;
372 }
373
375 auto value = update_func(key);
376 lru_.Put(key, {value, now});
377 })
378 );
379}
380
381template <typename Key, typename Value, typename Hash, typename Equal>
382bool ExpirableLruCache<Key, Value, Hash, Equal>::IsExpired(
383 std::chrono::steady_clock::time_point update_time,
384 std::chrono::steady_clock::time_point now
385) const {
386 auto max_lifetime = max_lifetime_.load();
387 return max_lifetime.count() != 0 && update_time + max_lifetime < now;
388}
389
390template <typename Key, typename Value, typename Hash, typename Equal>
391bool ExpirableLruCache<Key, Value, Hash, Equal>::ShouldUpdate(
392 std::chrono::steady_clock::time_point update_time,
393 std::chrono::steady_clock::time_point now
394) const {
395 auto max_lifetime = max_lifetime_.load();
396 return (background_update_mode_.load() == BackgroundUpdateMode::kEnabled) && max_lifetime.count() != 0 &&
397 update_time + max_lifetime / 2 < now;
398}
399
400template <typename Key, typename Value, typename Hash = std::hash<Key>, typename Equal = std::equal_to<Key>>
401class LruCacheWrapper final {
402public:
403 using Cache = ExpirableLruCache<Key, Value, Hash, Equal>;
404 using ReadMode = typename Cache::ReadMode;
405
406 LruCacheWrapper(std::shared_ptr<Cache> cache, typename Cache::UpdateValueFunc update_func)
407 : cache_(std::move(cache)), update_func_(std::move(update_func)) {}
408
409 /// Get cached value or evaluates if "key" is missing in cache
410 Value Get(const Key& key, ReadMode read_mode = ReadMode::kUseCache) {
411 return cache_->Get(key, update_func_, read_mode);
412 }
413
414 /// Get cached value or "nullopt" if "key" is missing in cache
415 std::optional<Value> GetOptional(const Key& key) { return cache_->GetOptional(key, update_func_); }
416
417 void InvalidateByKey(const Key& key) { cache_->InvalidateByKey(key); }
418
419 template <typename Predicate>
420 void InvalidateByKeyIf(const Key& key, Predicate pred) {
421 cache_->InvalidateByKeyIf(key, pred);
422 }
423
424 /// Update cached value in background
425 void UpdateInBackground(const Key& key) { cache_->UpdateInBackground(key, update_func_); }
426
427 /// Get raw cache. For internal use.
428 std::shared_ptr<Cache> GetCache() { return cache_; }
429
430private:
431 std::shared_ptr<Cache> cache_;
432 typename Cache::UpdateValueFunc update_func_;
433};
434
435template <typename Key, typename Value, typename Hash, typename Equal>
436void ExpirableLruCache<Key, Value, Hash, Equal>::Write(dump::Writer& writer) const {
437 utils::impl::UpdateGlobalTime();
438 lru_.Write(writer);
439}
440
441template <typename Key, typename Value, typename Hash, typename Equal>
442void ExpirableLruCache<Key, Value, Hash, Equal>::Read(dump::Reader& reader) {
443 utils::impl::UpdateGlobalTime();
444 lru_.Read(reader);
445}
446
447template <typename Key, typename Value, typename Hash, typename Equal>
448void ExpirableLruCache<Key, Value, Hash, Equal>::SetDumper(std::shared_ptr<dump::Dumper> dumper) {
449 lru_.SetDumper(std::move(dumper));
450}
451
452template <typename Key, typename Value, typename Hash, typename Equal>
453void DumpMetric(utils::statistics::Writer& writer, const ExpirableLruCache<Key, Value, Hash, Equal>& cache) {
454 writer["current-documents-count"] = cache.GetSizeApproximate();
455 writer = cache.GetStatistics();
456}
457
458} // namespace cache
459
460USERVER_NAMESPACE_END