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<
46 Value>{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<
171 Key,
172 Value,
173 Hash,
174 Equal>::ExpirableLruCache(size_t ways, size_t way_size, const Hash& hash, const Equal& equal)
175 : lru_(ways, way_size, hash, equal),
176 mutex_set_{ways, way_size, hash, equal}
177{}
178
179template <typename Key, typename Value, typename Hash, typename Equal>
180ExpirableLruCache<Key, Value, Hash, Equal>::~ExpirableLruCache() {
181 wait_token_storage_.WaitForAllTokens();
182}
183
184template <typename Key, typename Value, typename Hash, typename Equal>
185void ExpirableLruCache<Key, Value, Hash, Equal>::SetWaySize(size_t way_size) {
186 lru_.UpdateWaySize(way_size);
187}
188
189template <typename Key, typename Value, typename Hash, typename Equal>
190std::chrono::milliseconds ExpirableLruCache<Key, Value, Hash, Equal>::GetMaxLifetime() const noexcept {
191 return max_lifetime_.load();
192}
193
194template <typename Key, typename Value, typename Hash, typename Equal>
195void ExpirableLruCache<Key, Value, Hash, Equal>::SetMaxLifetime(std::chrono::milliseconds max_lifetime) {
196 max_lifetime_ = max_lifetime;
197}
198
199template <typename Key, typename Value, typename Hash, typename Equal>
200void ExpirableLruCache<Key, Value, Hash, Equal>::SetBackgroundUpdate(BackgroundUpdateMode background_update) {
201 background_update_mode_ = background_update;
202}
203
204template <typename Key, typename Value, typename Hash, typename Equal>
205Value ExpirableLruCache<
206 Key,
207 Value,
208 Hash,
209 Equal>::Get(const Key& key, const UpdateValueFunc& update_func, ReadMode read_mode) {
210 auto now = utils::datetime::SteadyNow();
211 auto opt_old_value = GetOptional(key, update_func);
212 if (opt_old_value) {
213 return std::move(*opt_old_value);
214 }
215
216 auto mutex = mutex_set_.GetMutexForKey(key);
217 const std::lock_guard lock(mutex);
218 // Test one more time - concurrent ExpirableLruCache::Get()
219 // might have put the value
220 auto old_value = lru_.Get(key);
221 if (old_value && !IsExpired(old_value->update_time, now)) {
222 return std::move(old_value->value);
223 }
224
225 auto value = update_func(key);
226 if (read_mode == ReadMode::kUseCache) {
227 lru_.Put(key, {value, now});
228 }
229 return value;
230}
231
232template <typename Key, typename Value, typename Hash, typename Equal>
233std::optional<Value> ExpirableLruCache<
234 Key,
235 Value,
236 Hash,
237 Equal>::GetOptional(const Key& key, const UpdateValueFunc& update_func) {
238 auto now = utils::datetime::SteadyNow();
239 auto old_value = lru_.Get(key);
240
241 if (old_value) {
242 if (!IsExpired(old_value->update_time, now)) {
243 impl::CacheHit(stats_);
244
245 if (ShouldUpdate(old_value->update_time, now)) {
246 UpdateInBackground(key, update_func);
247 }
248
249 return std::move(old_value->value);
250 } else {
251 impl::CacheStale(stats_);
252 }
253 }
254 impl::CacheMiss(stats_);
255
256 return std::nullopt;
257}
258
259template <typename Key, typename Value, typename Hash, typename Equal>
260std::optional<Value> ExpirableLruCache<Key, Value, Hash, Equal>::GetOptionalUnexpirable(const Key& key) {
261 auto old_value = lru_.Get(key);
262
263 if (old_value) {
264 impl::CacheHit(stats_);
265 return old_value->value;
266 }
267 impl::CacheMiss(stats_);
268
269 return std::nullopt;
270}
271
272template <typename Key, typename Value, typename Hash, typename Equal>
273std::optional<Value> ExpirableLruCache<
274 Key,
275 Value,
276 Hash,
277 Equal>::GetOptionalUnexpirableWithUpdate(const Key& key, const UpdateValueFunc& update_func) {
278 auto now = utils::datetime::SteadyNow();
279 auto old_value = lru_.Get(key);
280
281 if (old_value) {
282 impl::CacheHit(stats_);
283
284 if (ShouldUpdate(old_value->update_time, now)) {
285 UpdateInBackground(key, update_func);
286 }
287
288 return old_value->value;
289 }
290 impl::CacheMiss(stats_);
291
292 return std::nullopt;
293}
294
295template <typename Key, typename Value, typename Hash, typename Equal>
296std::optional<impl::ExpirableValue<Value>> ExpirableLruCache<
297 Key,
298 Value,
299 Hash,
300 Equal>::GetOptionalNoUpdateWithLastUpdateTime(const Key& key) {
301 const auto now = utils::datetime::SteadyNow();
302 const auto old_value = lru_.Get(key);
303 if (old_value) {
304 if (!IsExpired(old_value->update_time, now)) {
305 impl::CacheHit(stats_);
306 return old_value;
307 } else {
308 impl::CacheStale(stats_);
309 }
310 }
311 impl::CacheMiss(stats_);
312 return std::nullopt;
313}
314
315template <typename Key, typename Value, typename Hash, typename Equal>
316std::optional<Value> ExpirableLruCache<Key, Value, Hash, Equal>::GetOptionalNoUpdate(const Key& key) {
317 auto value_with_update_time = GetOptionalNoUpdateWithLastUpdateTime(key);
318 if (value_with_update_time.has_value()) {
319 return value_with_update_time->value;
320 }
321 return std::nullopt;
322}
323
324template <typename Key, typename Value, typename Hash, typename Equal>
325void ExpirableLruCache<Key, Value, Hash, Equal>::Put(const Key& key, const Value& value) {
326 lru_.Put(key, {value, utils::datetime::SteadyNow()});
327}
328
329template <typename Key, typename Value, typename Hash, typename Equal>
330void ExpirableLruCache<Key, Value, Hash, Equal>::Put(const Key& key, Value&& value) {
331 lru_.Put(key, {std::move(value), utils::datetime::SteadyNow()});
332}
333
334template <typename Key, typename Value, typename Hash, typename Equal>
335const impl::ExpirableLruCacheStatistics& ExpirableLruCache<Key, Value, Hash, Equal>::GetStatistics() const {
336 return stats_;
337}
338
339template <typename Key, typename Value, typename Hash, typename Equal>
340size_t ExpirableLruCache<Key, Value, Hash, Equal>::GetSizeApproximate() const {
341 return lru_.GetSize();
342}
343
344template <typename Key, typename Value, typename Hash, typename Equal>
345void ExpirableLruCache<Key, Value, Hash, Equal>::Invalidate() {
346 lru_.Invalidate();
347}
348
349template <typename Key, typename Value, typename Hash, typename Equal>
350void ExpirableLruCache<Key, Value, Hash, Equal>::InvalidateByKey(const Key& key) {
351 lru_.InvalidateByKey(key);
352}
353
354template <typename Key, typename Value, typename Hash, typename Equal>
355template <typename Predicate>
356void ExpirableLruCache<Key, Value, Hash, Equal>::InvalidateByKeyIf(const Key& key, Predicate pred) {
357 auto now = utils::datetime::SteadyNow();
358
359 auto mutex = mutex_set_.GetMutexForKey(key);
360 const std::lock_guard lock(mutex);
361 const auto cur_value = lru_.Get(key);
362
363 if (cur_value.has_value() && !IsExpired(cur_value->update_time, now) && pred(cur_value->value)) {
365 }
366}
367
368template <typename Key, typename Value, typename Hash, typename Equal>
369void ExpirableLruCache<Key, Value, Hash, Equal>::UpdateInBackground(const Key& key, UpdateValueFunc update_func) {
370 impl::CacheBackgroundUpdate(stats_);
371
372 // cache will wait for all detached tasks in ~ExpirableLruCache()
374 engine::AsyncNoSpan([token = wait_token_storage_.GetToken(), this, key, update_func = std::move(update_func)] {
375 auto mutex = mutex_set_.GetMutexForKey(key);
376 const std::unique_lock lock(mutex, std::try_to_lock);
377 if (!lock) {
378 // someone is updating the key right now
379 return;
380 }
381
382 auto now = utils::datetime::SteadyNow();
383 auto value = update_func(key);
384 lru_.Put(key, {value, now});
385 })
386 );
387}
388
389template <typename Key, typename Value, typename Hash, typename Equal>
390bool ExpirableLruCache<
391 Key,
392 Value,
393 Hash,
394 Equal>::IsExpired(std::chrono::steady_clock::time_point update_time, std::chrono::steady_clock::time_point now)
395 const {
396 auto max_lifetime = max_lifetime_.load();
397 return max_lifetime.count() != 0 && update_time + max_lifetime < now;
398}
399
400template <typename Key, typename Value, typename Hash, typename Equal>
401bool ExpirableLruCache<
402 Key,
403 Value,
404 Hash,
405 Equal>::ShouldUpdate(std::chrono::steady_clock::time_point update_time, std::chrono::steady_clock::time_point now)
406 const {
407 auto max_lifetime = max_lifetime_.load();
408 return (background_update_mode_.load() == BackgroundUpdateMode::kEnabled) && max_lifetime.count() != 0 &&
409 update_time + max_lifetime / 2 < now;
410}
411
412template <typename Key, typename Value, typename Hash = std::hash<Key>, typename Equal = std::equal_to<Key>>
413class LruCacheWrapper final {
414public:
415 using Cache = ExpirableLruCache<Key, Value, Hash, Equal>;
416 using ReadMode = typename Cache::ReadMode;
417
418 LruCacheWrapper(std::shared_ptr<Cache> cache, typename Cache::UpdateValueFunc update_func)
419 : cache_(std::move(cache)),
420 update_func_(std::move(update_func))
421 {}
422
423 /// Get cached value or evaluates if "key" is missing in cache
424 Value Get(const Key& key, ReadMode read_mode = ReadMode::kUseCache) {
425 return cache_->Get(key, update_func_, read_mode);
426 }
427
428 /// Get cached value or "nullopt" if "key" is missing in cache
429 std::optional<Value> GetOptional(const Key& key) { return cache_->GetOptional(key, update_func_); }
430
431 void InvalidateByKey(const Key& key) { cache_->InvalidateByKey(key); }
432
433 template <typename Predicate>
434 void InvalidateByKeyIf(const Key& key, Predicate pred) {
435 cache_->InvalidateByKeyIf(key, pred);
436 }
437
438 /// Update cached value in background
439 void UpdateInBackground(const Key& key) { cache_->UpdateInBackground(key, update_func_); }
440
441 /// Get raw cache. For internal use.
442 std::shared_ptr<Cache> GetCache() { return cache_; }
443
444private:
445 std::shared_ptr<Cache> cache_;
446 typename Cache::UpdateValueFunc update_func_;
447};
448
449template <typename Key, typename Value, typename Hash, typename Equal>
450void ExpirableLruCache<Key, Value, Hash, Equal>::Write(dump::Writer& writer) const {
451 utils::impl::UpdateGlobalTime();
452 lru_.Write(writer);
453}
454
455template <typename Key, typename Value, typename Hash, typename Equal>
456void ExpirableLruCache<Key, Value, Hash, Equal>::Read(dump::Reader& reader) {
457 utils::impl::UpdateGlobalTime();
458 lru_.Read(reader);
459}
460
461template <typename Key, typename Value, typename Hash, typename Equal>
462void ExpirableLruCache<Key, Value, Hash, Equal>::SetDumper(std::shared_ptr<dump::Dumper> dumper) {
463 lru_.SetDumper(std::move(dumper));
464}
465
466template <typename Key, typename Value, typename Hash, typename Equal>
467void DumpMetric(utils::statistics::Writer& writer, const ExpirableLruCache<Key, Value, Hash, Equal>& cache) {
468 writer["current-documents-count"] = cache.GetSizeApproximate();
469 writer = cache.GetStatistics();
470}
471
472} // namespace cache
473
474USERVER_NAMESPACE_END