rippled
Loading...
Searching...
No Matches
TaggedCache.h
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2012, 2013 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#ifndef RIPPLE_BASICS_TAGGEDCACHE_H_INCLUDED
21#define RIPPLE_BASICS_TAGGEDCACHE_H_INCLUDED
22
23#include <xrpl/basics/Log.h>
24#include <xrpl/basics/UnorderedContainers.h>
25#include <xrpl/basics/hardened_hash.h>
26#include <xrpl/beast/clock/abstract_clock.h>
27#include <xrpl/beast/insight/Insight.h>
28
29#include <atomic>
30#include <functional>
31#include <mutex>
32#include <thread>
33#include <type_traits>
34#include <vector>
35
36namespace ripple {
37
50template <
51 class Key,
52 class T,
53 bool IsKeyCache = false,
54 class Hash = hardened_hash<>,
55 class KeyEqual = std::equal_to<Key>,
56 class Mutex = std::recursive_mutex>
58{
59public:
60 using mutex_type = Mutex;
61 using key_type = Key;
62 using mapped_type = T;
64
65public:
67 std::string const& name,
68 int size,
69 clock_type::duration expiration,
71 beast::Journal journal,
72 beast::insight::Collector::ptr const& collector =
74 : m_journal(journal)
75 , m_clock(clock)
76 , m_stats(
77 name,
78 std::bind(&TaggedCache::collect_metrics, this),
79 collector)
80 , m_name(name)
82 , m_target_age(expiration)
83 , m_cache_count(0)
84 , m_hits(0)
85 , m_misses(0)
86 {
87 }
88
89public:
93 {
94 return m_clock;
95 }
96
99 size() const
100 {
102 return m_cache.size();
103 }
104
105 void
107 {
109 m_target_size = s;
110
111 if (s > 0)
112 {
113 for (auto& partition : m_cache.map())
114 {
115 partition.rehash(static_cast<std::size_t>(
116 (s + (s >> 2)) /
117 (partition.max_load_factor() * m_cache.partitions()) +
118 1));
119 }
120 }
121
122 JLOG(m_journal.debug()) << m_name << " target size set to " << s;
123 }
124
127 {
129 return m_target_age;
130 }
131
132 void
134 {
136 m_target_age = s;
137 JLOG(m_journal.debug())
138 << m_name << " target age set to " << m_target_age.count();
139 }
140
141 int
143 {
145 return m_cache_count;
146 }
147
148 int
150 {
152 return m_cache.size();
153 }
154
155 float
157 {
159 auto const total = static_cast<float>(m_hits + m_misses);
160 return m_hits * (100.0f / std::max(1.0f, total));
161 }
162
163 void
165 {
167 m_cache.clear();
168 m_cache_count = 0;
169 }
170
171 void
173 {
175 m_cache.clear();
176 m_cache_count = 0;
177 m_hits = 0;
178 m_misses = 0;
179 }
180
184 template <class KeyComparable>
185 bool
186 touch_if_exists(KeyComparable const& key)
187 {
189 auto const iter(m_cache.find(key));
190 if (iter == m_cache.end())
191 {
192 ++m_stats.misses;
193 return false;
194 }
195 iter->second.touch(m_clock.now());
196 ++m_stats.hits;
197 return true;
198 }
199
203
204 void
206 {
207 // Keep references to all the stuff we sweep
208 // For performance, each worker thread should exit before the swept data
209 // is destroyed but still within the main cache lock.
211
213 clock_type::time_point when_expire;
214
215 auto const start = std::chrono::steady_clock::now();
216 {
218
219 if (m_target_size == 0 ||
220 (static_cast<int>(m_cache.size()) <= m_target_size))
221 {
222 when_expire = now - m_target_age;
223 }
224 else
225 {
226 when_expire =
228
229 clock_type::duration const minimumAge(std::chrono::seconds(1));
230 if (when_expire > (now - minimumAge))
231 when_expire = now - minimumAge;
232
233 JLOG(m_journal.trace())
234 << m_name << " is growing fast " << m_cache.size() << " of "
235 << m_target_size << " aging at "
236 << (now - when_expire).count() << " of "
237 << m_target_age.count();
238 }
239
241 workers.reserve(m_cache.partitions());
242 std::atomic<int> allRemovals = 0;
243
244 for (std::size_t p = 0; p < m_cache.partitions(); ++p)
245 {
246 workers.push_back(sweepHelper(
247 when_expire,
248 now,
249 m_cache.map()[p],
250 allStuffToSweep[p],
251 allRemovals,
252 lock));
253 }
254 for (std::thread& worker : workers)
255 worker.join();
256
257 m_cache_count -= allRemovals;
258 }
259 // At this point allStuffToSweep will go out of scope outside the lock
260 // and decrement the reference count on each strong pointer.
261 JLOG(m_journal.debug())
262 << m_name << " TaggedCache sweep lock duration "
263 << std::chrono::duration_cast<std::chrono::milliseconds>(
265 .count()
266 << "ms";
267 }
268
269 bool
270 del(const key_type& key, bool valid)
271 {
272 // Remove from cache, if !valid, remove from map too. Returns true if
273 // removed from cache
275
276 auto cit = m_cache.find(key);
277
278 if (cit == m_cache.end())
279 return false;
280
281 Entry& entry = cit->second;
282
283 bool ret = false;
284
285 if (entry.isCached())
286 {
288 entry.ptr.reset();
289 ret = true;
290 }
291
292 if (!valid || entry.isExpired())
293 m_cache.erase(cit);
294
295 return ret;
296 }
297
311public:
312 bool
314 const key_type& key,
315 std::shared_ptr<T>& data,
316 std::function<bool(std::shared_ptr<T> const&)>&& replace)
317 {
318 // Return canonical value, store if needed, refresh in cache
319 // Return values: true=we had the data already
321
322 auto cit = m_cache.find(key);
323
324 if (cit == m_cache.end())
325 {
327 std::piecewise_construct,
331 return false;
332 }
333
334 Entry& entry = cit->second;
335 entry.touch(m_clock.now());
336
337 if (entry.isCached())
338 {
339 if (replace(entry.ptr))
340 {
341 entry.ptr = data;
342 entry.weak_ptr = data;
343 }
344 else
345 {
346 data = entry.ptr;
347 }
348
349 return true;
350 }
351
352 auto cachedData = entry.lock();
353
354 if (cachedData)
355 {
356 if (replace(entry.ptr))
357 {
358 entry.ptr = data;
359 entry.weak_ptr = data;
360 }
361 else
362 {
363 entry.ptr = cachedData;
364 data = cachedData;
365 }
366
368 return true;
369 }
370
371 entry.ptr = data;
372 entry.weak_ptr = data;
374
375 return false;
376 }
377
378 bool
380 const key_type& key,
381 std::shared_ptr<T> const& data)
382 {
383 return canonicalize(
384 key,
385 const_cast<std::shared_ptr<T>&>(data),
386 [](std::shared_ptr<T> const&) { return true; });
387 }
388
389 bool
391 {
392 return canonicalize(
393 key, data, [](std::shared_ptr<T> const&) { return false; });
394 }
395
397 fetch(const key_type& key)
398 {
400 auto ret = initialFetch(key, l);
401 if (!ret)
402 ++m_misses;
403 return ret;
404 }
405
410 template <class ReturnType = bool>
411 auto
412 insert(key_type const& key, T const& value)
414 {
415 auto p = std::make_shared<T>(std::cref(value));
416 return canonicalize_replace_client(key, p);
417 }
418
419 template <class ReturnType = bool>
420 auto
422 {
425 auto [it, inserted] = m_cache.emplace(
426 std::piecewise_construct,
429 if (!inserted)
430 it->second.last_access = now;
431 return inserted;
432 }
433
434 // VFALCO NOTE It looks like this returns a copy of the data in
435 // the output parameter 'data'. This could be expensive.
436 // Perhaps it should work like standard containers, which
437 // simply return an iterator.
438 //
439 bool
440 retrieve(const key_type& key, T& data)
441 {
442 // retrieve the value of the stored data
443 auto entry = fetch(key);
444
445 if (!entry)
446 return false;
447
448 data = *entry;
449 return true;
450 }
451
454 {
455 return m_mutex;
456 }
457
459 getKeys() const
460 {
462
463 {
465 v.reserve(m_cache.size());
466 for (auto const& _ : m_cache)
467 v.push_back(_.first);
468 }
469
470 return v;
471 }
472
473 // CachedSLEs functions.
475 double
476 rate() const
477 {
479 auto const tot = m_hits + m_misses;
480 if (tot == 0)
481 return 0;
482 return double(m_hits) / tot;
483 }
484
490 template <class Handler>
492 fetch(key_type const& digest, Handler const& h)
493 {
494 {
496 if (auto ret = initialFetch(digest, l))
497 return ret;
498 }
499
500 auto sle = h();
501 if (!sle)
502 return {};
503
505 ++m_misses;
506 auto const [it, inserted] =
507 m_cache.emplace(digest, Entry(m_clock.now(), std::move(sle)));
508 if (!inserted)
509 it->second.touch(m_clock.now());
510 return it->second.ptr;
511 }
512 // End CachedSLEs functions.
513
514private:
517 {
518 auto cit = m_cache.find(key);
519 if (cit == m_cache.end())
520 return {};
521
522 Entry& entry = cit->second;
523 if (entry.isCached())
524 {
525 ++m_hits;
526 entry.touch(m_clock.now());
527 return entry.ptr;
528 }
529 entry.ptr = entry.lock();
530 if (entry.isCached())
531 {
532 // independent of cache size, so not counted as a hit
534 entry.touch(m_clock.now());
535 return entry.ptr;
536 }
537
538 m_cache.erase(cit);
539 return {};
540 }
541
542 void
544 {
546
547 {
549 {
551 auto const total(m_hits + m_misses);
552 if (total != 0)
553 hit_rate = (m_hits * 100) / total;
554 }
555 m_stats.hit_rate.set(hit_rate);
556 }
557 }
558
559private:
560 struct Stats
561 {
562 template <class Handler>
564 std::string const& prefix,
565 Handler const& handler,
566 beast::insight::Collector::ptr const& collector)
567 : hook(collector->make_hook(handler))
568 , size(collector->make_gauge(prefix, "size"))
569 , hit_rate(collector->make_gauge(prefix, "hit_rate"))
570 , hits(0)
571 , misses(0)
572 {
573 }
574
578
581 };
582
584 {
585 public:
587
588 explicit KeyOnlyEntry(clock_type::time_point const& last_access_)
589 : last_access(last_access_)
590 {
591 }
592
593 void
595 {
596 last_access = now;
597 }
598 };
599
601 {
602 public:
606
608 clock_type::time_point const& last_access_,
610 : ptr(ptr_), weak_ptr(ptr_), last_access(last_access_)
611 {
612 }
613
614 bool
615 isWeak() const
616 {
617 return ptr == nullptr;
618 }
619 bool
620 isCached() const
621 {
622 return ptr != nullptr;
623 }
624 bool
625 isExpired() const
626 {
627 return weak_ptr.expired();
628 }
631 {
632 return weak_ptr.lock();
633 }
634 void
636 {
637 last_access = now;
638 }
639 };
640
641 typedef
644
647
650
653
654 [[nodiscard]] std::thread
656 clock_type::time_point const& when_expire,
657 [[maybe_unused]] clock_type::time_point const& now,
658 typename KeyValueCacheType::map_type& partition,
659 SweptPointersVector& stuffToSweep,
660 std::atomic<int>& allRemovals,
662 {
663 return std::thread([&, this]() {
664 int cacheRemovals = 0;
665 int mapRemovals = 0;
666
667 // Keep references to all the stuff we sweep
668 // so that we can destroy them outside the lock.
669 stuffToSweep.first.reserve(partition.size());
670 stuffToSweep.second.reserve(partition.size());
671 {
672 auto cit = partition.begin();
673 while (cit != partition.end())
674 {
675 if (cit->second.isWeak())
676 {
677 // weak
678 if (cit->second.isExpired())
679 {
680 stuffToSweep.second.push_back(
681 std::move(cit->second.weak_ptr));
682 ++mapRemovals;
683 cit = partition.erase(cit);
684 }
685 else
686 {
687 ++cit;
688 }
689 }
690 else if (cit->second.last_access <= when_expire)
691 {
692 // strong, expired
693 ++cacheRemovals;
694 if (cit->second.ptr.use_count() == 1)
695 {
696 stuffToSweep.first.push_back(
697 std::move(cit->second.ptr));
698 ++mapRemovals;
699 cit = partition.erase(cit);
700 }
701 else
702 {
703 // remains weakly cached
704 cit->second.ptr.reset();
705 ++cit;
706 }
707 }
708 else
709 {
710 // strong, not expired
711 ++cit;
712 }
713 }
714 }
715
716 if (mapRemovals || cacheRemovals)
717 {
718 JLOG(m_journal.debug())
719 << "TaggedCache partition sweep " << m_name
720 << ": cache = " << partition.size() << "-" << cacheRemovals
721 << ", map-=" << mapRemovals;
722 }
723
724 allRemovals += cacheRemovals;
725 });
726 }
727
728 [[nodiscard]] std::thread
730 clock_type::time_point const& when_expire,
731 clock_type::time_point const& now,
732 typename KeyOnlyCacheType::map_type& partition,
734 std::atomic<int>& allRemovals,
736 {
737 return std::thread([&, this]() {
738 int cacheRemovals = 0;
739 int mapRemovals = 0;
740
741 // Keep references to all the stuff we sweep
742 // so that we can destroy them outside the lock.
743 {
744 auto cit = partition.begin();
745 while (cit != partition.end())
746 {
747 if (cit->second.last_access > now)
748 {
749 cit->second.last_access = now;
750 ++cit;
751 }
752 else if (cit->second.last_access <= when_expire)
753 {
754 cit = partition.erase(cit);
755 }
756 else
757 {
758 ++cit;
759 }
760 }
761 }
762
763 if (mapRemovals || cacheRemovals)
764 {
765 JLOG(m_journal.debug())
766 << "TaggedCache partition sweep " << m_name
767 << ": cache = " << partition.size() << "-" << cacheRemovals
768 << ", map-=" << mapRemovals;
769 }
770
771 allRemovals += cacheRemovals;
772 });
773 };
774
778
780
781 // Used for logging
783
784 // Desired number of cache entries (0 = ignore)
786
787 // Desired maximum cache age
789
790 // Number of items cached
792 cache_type m_cache; // Hold strong reference to recent objects
795};
796
797} // namespace ripple
798
799#endif
A generic endpoint for log messages.
Definition: Journal.h:60
Stream debug() const
Definition: Journal.h:328
Stream trace() const
Severity stream access functions.
Definition: Journal.h:322
virtual time_point now() const =0
Returns the current time.
typename Clock::duration duration
A metric for measuring an integral value.
Definition: Gauge.h:40
void set(value_type value) const
Set the value on the gauge.
Definition: Gauge.h:68
A reference to a handler for performing polled collection.
Definition: Hook.h:32
static std::shared_ptr< Collector > New()
clock_type::time_point last_access
Definition: TaggedCache.h:586
KeyOnlyEntry(clock_type::time_point const &last_access_)
Definition: TaggedCache.h:588
void touch(clock_type::time_point const &now)
Definition: TaggedCache.h:594
clock_type::time_point last_access
Definition: TaggedCache.h:605
std::weak_ptr< mapped_type > weak_ptr
Definition: TaggedCache.h:604
void touch(clock_type::time_point const &now)
Definition: TaggedCache.h:635
std::shared_ptr< mapped_type > lock()
Definition: TaggedCache.h:630
ValueEntry(clock_type::time_point const &last_access_, std::shared_ptr< mapped_type > const &ptr_)
Definition: TaggedCache.h:607
std::shared_ptr< mapped_type > ptr
Definition: TaggedCache.h:603
Map/cache combination.
Definition: TaggedCache.h:58
std::thread sweepHelper(clock_type::time_point const &when_expire, clock_type::time_point const &now, typename KeyValueCacheType::map_type &partition, SweptPointersVector &stuffToSweep, std::atomic< int > &allRemovals, std::lock_guard< std::recursive_mutex > const &)
Definition: TaggedCache.h:655
bool retrieve(const key_type &key, T &data)
Definition: TaggedCache.h:440
int getTrackSize() const
Definition: TaggedCache.h:149
std::conditional< IsKeyCache, KeyOnlyEntry, ValueEntry >::type Entry
Definition: TaggedCache.h:643
std::shared_ptr< T > fetch(const key_type &key)
Definition: TaggedCache.h:397
std::uint64_t m_misses
Definition: TaggedCache.h:794
int getCacheSize() const
Definition: TaggedCache.h:142
auto insert(key_type const &key, T const &value) -> std::enable_if_t<!IsKeyCache, ReturnType >
Insert the element into the container.
Definition: TaggedCache.h:412
clock_type & m_clock
Definition: TaggedCache.h:776
void setTargetAge(clock_type::duration s)
Definition: TaggedCache.h:133
std::shared_ptr< T > initialFetch(key_type const &key, std::lock_guard< mutex_type > const &l)
Definition: TaggedCache.h:516
bool canonicalize_replace_cache(const key_type &key, std::shared_ptr< T > const &data)
Definition: TaggedCache.h:379
beast::Journal m_journal
Definition: TaggedCache.h:775
bool del(const key_type &key, bool valid)
Definition: TaggedCache.h:270
mutex_type m_mutex
Definition: TaggedCache.h:779
void setTargetSize(int s)
Definition: TaggedCache.h:106
bool canonicalize_replace_client(const key_type &key, std::shared_ptr< T > &data)
Definition: TaggedCache.h:390
bool canonicalize(const key_type &key, std::shared_ptr< T > &data, std::function< bool(std::shared_ptr< T > const &)> &&replace)
Replace aliased objects with originals.
Definition: TaggedCache.h:313
std::string m_name
Definition: TaggedCache.h:782
bool touch_if_exists(KeyComparable const &key)
Refresh the last access time on a key if present.
Definition: TaggedCache.h:186
clock_type & clock()
Return the clock associated with the cache.
Definition: TaggedCache.h:92
TaggedCache(std::string const &name, int size, clock_type::duration expiration, clock_type &clock, beast::Journal journal, beast::insight::Collector::ptr const &collector=beast::insight::NullCollector::New())
Definition: TaggedCache.h:66
mutex_type & peekMutex()
Definition: TaggedCache.h:453
beast::abstract_clock< std::chrono::steady_clock > clock_type
Definition: TaggedCache.h:63
clock_type::duration getTargetAge() const
Definition: TaggedCache.h:126
std::vector< key_type > getKeys() const
Definition: TaggedCache.h:459
auto insert(key_type const &key) -> std::enable_if_t< IsKeyCache, ReturnType >
Definition: TaggedCache.h:421
std::size_t size() const
Returns the number of items in the container.
Definition: TaggedCache.h:99
std::thread sweepHelper(clock_type::time_point const &when_expire, clock_type::time_point const &now, typename KeyOnlyCacheType::map_type &partition, SweptPointersVector &, std::atomic< int > &allRemovals, std::lock_guard< std::recursive_mutex > const &)
Definition: TaggedCache.h:729
cache_type m_cache
Definition: TaggedCache.h:792
double rate() const
Returns the fraction of cache hits.
Definition: TaggedCache.h:476
std::uint64_t m_hits
Definition: TaggedCache.h:793
std::shared_ptr< T > fetch(key_type const &digest, Handler const &h)
Fetch an item from the cache.
Definition: TaggedCache.h:492
clock_type::duration m_target_age
Definition: TaggedCache.h:788
iterator erase(const_iterator position)
std::pair< iterator, bool > emplace(std::piecewise_construct_t const &, T &&keyTuple, U &&valueTuple)
void find(key_type const &key, T &it) const
T expired(T... args)
T forward_as_tuple(T... args)
T lock(T... args)
T max(T... args)
TER valid(PreclaimContext const &ctx, AccountID const &src)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
static Hasher::result_type digest(void const *data, std::size_t size) noexcept
Definition: tokens.cpp:156
STL namespace.
T push_back(T... args)
T cref(T... args)
T reserve(T... args)
beast::insight::Hook hook
Definition: TaggedCache.h:575
Stats(std::string const &prefix, Handler const &handler, beast::insight::Collector::ptr const &collector)
Definition: TaggedCache.h:563
beast::insight::Gauge size
Definition: TaggedCache.h:576
beast::insight::Gauge hit_rate
Definition: TaggedCache.h:577