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#include <atomic>
29#include <functional>
30#include <mutex>
31#include <thread>
32#include <type_traits>
33#include <vector>
34
35namespace ripple {
36
49template <
50 class Key,
51 class T,
52 bool IsKeyCache = false,
53 class Hash = hardened_hash<>,
54 class KeyEqual = std::equal_to<Key>,
55 class Mutex = std::recursive_mutex>
57{
58public:
59 using mutex_type = Mutex;
60 using key_type = Key;
61 using mapped_type = T;
63
64public:
66 std::string const& name,
67 int size,
68 clock_type::duration expiration,
70 beast::Journal journal,
71 beast::insight::Collector::ptr const& collector =
73 : m_journal(journal)
74 , m_clock(clock)
75 , m_stats(
76 name,
77 std::bind(&TaggedCache::collect_metrics, this),
78 collector)
79 , m_name(name)
81 , m_target_age(expiration)
82 , m_cache_count(0)
83 , m_hits(0)
84 , m_misses(0)
85 {
86 }
87
88public:
92 {
93 return m_clock;
94 }
95
98 size() const
99 {
101 return m_cache.size();
102 }
103
104 void
106 {
108 m_target_size = s;
109
110 if (s > 0)
111 {
112 for (auto& partition : m_cache.map())
113 {
114 partition.rehash(static_cast<std::size_t>(
115 (s + (s >> 2)) /
116 (partition.max_load_factor() * m_cache.partitions()) +
117 1));
118 }
119 }
120
121 JLOG(m_journal.debug()) << m_name << " target size set to " << s;
122 }
123
126 {
128 return m_target_age;
129 }
130
131 void
133 {
135 m_target_age = s;
136 JLOG(m_journal.debug())
137 << m_name << " target age set to " << m_target_age.count();
138 }
139
140 int
142 {
144 return m_cache_count;
145 }
146
147 int
149 {
151 return m_cache.size();
152 }
153
154 float
156 {
158 auto const total = static_cast<float>(m_hits + m_misses);
159 return m_hits * (100.0f / std::max(1.0f, total));
160 }
161
162 void
164 {
166 m_cache.clear();
167 m_cache_count = 0;
168 }
169
170 void
172 {
174 m_cache.clear();
175 m_cache_count = 0;
176 m_hits = 0;
177 m_misses = 0;
178 }
179
183 template <class KeyComparable>
184 bool
185 touch_if_exists(KeyComparable const& key)
186 {
188 auto const iter(m_cache.find(key));
189 if (iter == m_cache.end())
190 {
191 ++m_stats.misses;
192 return false;
193 }
194 iter->second.touch(m_clock.now());
195 ++m_stats.hits;
196 return true;
197 }
198
202
203 void
205 {
206 // Keep references to all the stuff we sweep
207 // For performance, each worker thread should exit before the swept data
208 // is destroyed but still within the main cache lock.
210
212 clock_type::time_point when_expire;
213
214 auto const start = std::chrono::steady_clock::now();
215 {
217
218 if (m_target_size == 0 ||
219 (static_cast<int>(m_cache.size()) <= m_target_size))
220 {
221 when_expire = now - m_target_age;
222 }
223 else
224 {
225 when_expire =
227
228 clock_type::duration const minimumAge(std::chrono::seconds(1));
229 if (when_expire > (now - minimumAge))
230 when_expire = now - minimumAge;
231
232 JLOG(m_journal.trace())
233 << m_name << " is growing fast " << m_cache.size() << " of "
234 << m_target_size << " aging at "
235 << (now - when_expire).count() << " of "
236 << m_target_age.count();
237 }
238
240 workers.reserve(m_cache.partitions());
241 std::atomic<int> allRemovals = 0;
242
243 for (std::size_t p = 0; p < m_cache.partitions(); ++p)
244 {
245 workers.push_back(sweepHelper(
246 when_expire,
247 now,
248 m_cache.map()[p],
249 allStuffToSweep[p],
250 allRemovals,
251 lock));
252 }
253 for (std::thread& worker : workers)
254 worker.join();
255
256 m_cache_count -= allRemovals;
257 }
258 // At this point allStuffToSweep will go out of scope outside the lock
259 // and decrement the reference count on each strong pointer.
260 JLOG(m_journal.debug())
261 << m_name << " TaggedCache sweep lock duration "
262 << std::chrono::duration_cast<std::chrono::milliseconds>(
264 .count()
265 << "ms";
266 }
267
268 bool
269 del(const key_type& key, bool valid)
270 {
271 // Remove from cache, if !valid, remove from map too. Returns true if
272 // removed from cache
274
275 auto cit = m_cache.find(key);
276
277 if (cit == m_cache.end())
278 return false;
279
280 Entry& entry = cit->second;
281
282 bool ret = false;
283
284 if (entry.isCached())
285 {
287 entry.ptr.reset();
288 ret = true;
289 }
290
291 if (!valid || entry.isExpired())
292 m_cache.erase(cit);
293
294 return ret;
295 }
296
310public:
311 bool
313 const key_type& key,
314 std::shared_ptr<T>& data,
315 std::function<bool(std::shared_ptr<T> const&)>&& replace)
316 {
317 // Return canonical value, store if needed, refresh in cache
318 // Return values: true=we had the data already
320
321 auto cit = m_cache.find(key);
322
323 if (cit == m_cache.end())
324 {
326 std::piecewise_construct,
330 return false;
331 }
332
333 Entry& entry = cit->second;
334 entry.touch(m_clock.now());
335
336 if (entry.isCached())
337 {
338 if (replace(entry.ptr))
339 {
340 entry.ptr = data;
341 entry.weak_ptr = data;
342 }
343 else
344 {
345 data = entry.ptr;
346 }
347
348 return true;
349 }
350
351 auto cachedData = entry.lock();
352
353 if (cachedData)
354 {
355 if (replace(entry.ptr))
356 {
357 entry.ptr = data;
358 entry.weak_ptr = data;
359 }
360 else
361 {
362 entry.ptr = cachedData;
363 data = cachedData;
364 }
365
367 return true;
368 }
369
370 entry.ptr = data;
371 entry.weak_ptr = data;
373
374 return false;
375 }
376
377 bool
379 const key_type& key,
380 std::shared_ptr<T> const& data)
381 {
382 return canonicalize(
383 key,
384 const_cast<std::shared_ptr<T>&>(data),
385 [](std::shared_ptr<T> const&) { return true; });
386 }
387
388 bool
390 {
391 return canonicalize(
392 key, data, [](std::shared_ptr<T> const&) { return false; });
393 }
394
396 fetch(const key_type& key)
397 {
399 auto ret = initialFetch(key, l);
400 if (!ret)
401 ++m_misses;
402 return ret;
403 }
404
409 template <class ReturnType = bool>
410 auto
411 insert(key_type const& key, T const& value)
413 {
414 auto p = std::make_shared<T>(std::cref(value));
415 return canonicalize_replace_client(key, p);
416 }
417
418 template <class ReturnType = bool>
419 auto
421 {
424 auto [it, inserted] = m_cache.emplace(
425 std::piecewise_construct,
428 if (!inserted)
429 it->second.last_access = now;
430 return inserted;
431 }
432
433 // VFALCO NOTE It looks like this returns a copy of the data in
434 // the output parameter 'data'. This could be expensive.
435 // Perhaps it should work like standard containers, which
436 // simply return an iterator.
437 //
438 bool
439 retrieve(const key_type& key, T& data)
440 {
441 // retrieve the value of the stored data
442 auto entry = fetch(key);
443
444 if (!entry)
445 return false;
446
447 data = *entry;
448 return true;
449 }
450
453 {
454 return m_mutex;
455 }
456
458 getKeys() const
459 {
461
462 {
464 v.reserve(m_cache.size());
465 for (auto const& _ : m_cache)
466 v.push_back(_.first);
467 }
468
469 return v;
470 }
471
472 // CachedSLEs functions.
474 double
475 rate() const
476 {
478 auto const tot = m_hits + m_misses;
479 if (tot == 0)
480 return 0;
481 return double(m_hits) / tot;
482 }
483
489 template <class Handler>
491 fetch(key_type const& digest, Handler const& h)
492 {
493 {
495 if (auto ret = initialFetch(digest, l))
496 return ret;
497 }
498
499 auto sle = h();
500 if (!sle)
501 return {};
502
504 ++m_misses;
505 auto const [it, inserted] =
506 m_cache.emplace(digest, Entry(m_clock.now(), std::move(sle)));
507 if (!inserted)
508 it->second.touch(m_clock.now());
509 return it->second.ptr;
510 }
511 // End CachedSLEs functions.
512
513private:
516 {
517 auto cit = m_cache.find(key);
518 if (cit == m_cache.end())
519 return {};
520
521 Entry& entry = cit->second;
522 if (entry.isCached())
523 {
524 ++m_hits;
525 entry.touch(m_clock.now());
526 return entry.ptr;
527 }
528 entry.ptr = entry.lock();
529 if (entry.isCached())
530 {
531 // independent of cache size, so not counted as a hit
533 entry.touch(m_clock.now());
534 return entry.ptr;
535 }
536
537 m_cache.erase(cit);
538 return {};
539 }
540
541 void
543 {
545
546 {
548 {
550 auto const total(m_hits + m_misses);
551 if (total != 0)
552 hit_rate = (m_hits * 100) / total;
553 }
554 m_stats.hit_rate.set(hit_rate);
555 }
556 }
557
558private:
559 struct Stats
560 {
561 template <class Handler>
563 std::string const& prefix,
564 Handler const& handler,
565 beast::insight::Collector::ptr const& collector)
566 : hook(collector->make_hook(handler))
567 , size(collector->make_gauge(prefix, "size"))
568 , hit_rate(collector->make_gauge(prefix, "hit_rate"))
569 , hits(0)
570 , misses(0)
571 {
572 }
573
577
580 };
581
583 {
584 public:
586
587 explicit KeyOnlyEntry(clock_type::time_point const& last_access_)
588 : last_access(last_access_)
589 {
590 }
591
592 void
594 {
595 last_access = now;
596 }
597 };
598
600 {
601 public:
605
607 clock_type::time_point const& last_access_,
609 : ptr(ptr_), weak_ptr(ptr_), last_access(last_access_)
610 {
611 }
612
613 bool
614 isWeak() const
615 {
616 return ptr == nullptr;
617 }
618 bool
619 isCached() const
620 {
621 return ptr != nullptr;
622 }
623 bool
624 isExpired() const
625 {
626 return weak_ptr.expired();
627 }
630 {
631 return weak_ptr.lock();
632 }
633 void
635 {
636 last_access = now;
637 }
638 };
639
640 typedef
643
646
649
652
653 [[nodiscard]] std::thread
655 clock_type::time_point const& when_expire,
656 [[maybe_unused]] clock_type::time_point const& now,
657 typename KeyValueCacheType::map_type& partition,
658 SweptPointersVector& stuffToSweep,
659 std::atomic<int>& allRemovals,
661 {
662 return std::thread([&, this]() {
663 int cacheRemovals = 0;
664 int mapRemovals = 0;
665
666 // Keep references to all the stuff we sweep
667 // so that we can destroy them outside the lock.
668 stuffToSweep.first.reserve(partition.size());
669 stuffToSweep.second.reserve(partition.size());
670 {
671 auto cit = partition.begin();
672 while (cit != partition.end())
673 {
674 if (cit->second.isWeak())
675 {
676 // weak
677 if (cit->second.isExpired())
678 {
679 stuffToSweep.second.push_back(
680 std::move(cit->second.weak_ptr));
681 ++mapRemovals;
682 cit = partition.erase(cit);
683 }
684 else
685 {
686 ++cit;
687 }
688 }
689 else if (cit->second.last_access <= when_expire)
690 {
691 // strong, expired
692 ++cacheRemovals;
693 if (cit->second.ptr.use_count() == 1)
694 {
695 stuffToSweep.first.push_back(
696 std::move(cit->second.ptr));
697 ++mapRemovals;
698 cit = partition.erase(cit);
699 }
700 else
701 {
702 // remains weakly cached
703 cit->second.ptr.reset();
704 ++cit;
705 }
706 }
707 else
708 {
709 // strong, not expired
710 ++cit;
711 }
712 }
713 }
714
715 if (mapRemovals || cacheRemovals)
716 {
717 JLOG(m_journal.debug())
718 << "TaggedCache partition sweep " << m_name
719 << ": cache = " << partition.size() << "-" << cacheRemovals
720 << ", map-=" << mapRemovals;
721 }
722
723 allRemovals += cacheRemovals;
724 });
725 }
726
727 [[nodiscard]] std::thread
729 clock_type::time_point const& when_expire,
730 clock_type::time_point const& now,
731 typename KeyOnlyCacheType::map_type& partition,
733 std::atomic<int>& allRemovals,
735 {
736 return std::thread([&, this]() {
737 int cacheRemovals = 0;
738 int mapRemovals = 0;
739
740 // Keep references to all the stuff we sweep
741 // so that we can destroy them outside the lock.
742 {
743 auto cit = partition.begin();
744 while (cit != partition.end())
745 {
746 if (cit->second.last_access > now)
747 {
748 cit->second.last_access = now;
749 ++cit;
750 }
751 else if (cit->second.last_access <= when_expire)
752 {
753 cit = partition.erase(cit);
754 }
755 else
756 {
757 ++cit;
758 }
759 }
760 }
761
762 if (mapRemovals || cacheRemovals)
763 {
764 JLOG(m_journal.debug())
765 << "TaggedCache partition sweep " << m_name
766 << ": cache = " << partition.size() << "-" << cacheRemovals
767 << ", map-=" << mapRemovals;
768 }
769
770 allRemovals += cacheRemovals;
771 });
772 };
773
777
779
780 // Used for logging
782
783 // Desired number of cache entries (0 = ignore)
785
786 // Desired maximum cache age
788
789 // Number of items cached
791 cache_type m_cache; // Hold strong reference to recent objects
794};
795
796} // namespace ripple
797
798#endif
A generic endpoint for log messages.
Definition: Journal.h:59
Stream debug() const
Definition: Journal.h:317
Stream trace() const
Severity stream access functions.
Definition: Journal.h:311
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:585
KeyOnlyEntry(clock_type::time_point const &last_access_)
Definition: TaggedCache.h:587
void touch(clock_type::time_point const &now)
Definition: TaggedCache.h:593
clock_type::time_point last_access
Definition: TaggedCache.h:604
std::weak_ptr< mapped_type > weak_ptr
Definition: TaggedCache.h:603
void touch(clock_type::time_point const &now)
Definition: TaggedCache.h:634
std::shared_ptr< mapped_type > lock()
Definition: TaggedCache.h:629
ValueEntry(clock_type::time_point const &last_access_, std::shared_ptr< mapped_type > const &ptr_)
Definition: TaggedCache.h:606
std::shared_ptr< mapped_type > ptr
Definition: TaggedCache.h:602
Map/cache combination.
Definition: TaggedCache.h:57
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:654
bool retrieve(const key_type &key, T &data)
Definition: TaggedCache.h:439
int getTrackSize() const
Definition: TaggedCache.h:148
std::conditional< IsKeyCache, KeyOnlyEntry, ValueEntry >::type Entry
Definition: TaggedCache.h:642
std::shared_ptr< T > fetch(const key_type &key)
Definition: TaggedCache.h:396
std::uint64_t m_misses
Definition: TaggedCache.h:793
int getCacheSize() const
Definition: TaggedCache.h:141
auto insert(key_type const &key, T const &value) -> std::enable_if_t<!IsKeyCache, ReturnType >
Insert the element into the container.
Definition: TaggedCache.h:411
clock_type & m_clock
Definition: TaggedCache.h:775
void setTargetAge(clock_type::duration s)
Definition: TaggedCache.h:132
std::shared_ptr< T > initialFetch(key_type const &key, std::lock_guard< mutex_type > const &l)
Definition: TaggedCache.h:515
bool canonicalize_replace_cache(const key_type &key, std::shared_ptr< T > const &data)
Definition: TaggedCache.h:378
beast::Journal m_journal
Definition: TaggedCache.h:774
bool del(const key_type &key, bool valid)
Definition: TaggedCache.h:269
mutex_type m_mutex
Definition: TaggedCache.h:778
void setTargetSize(int s)
Definition: TaggedCache.h:105
bool canonicalize_replace_client(const key_type &key, std::shared_ptr< T > &data)
Definition: TaggedCache.h:389
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:312
std::string m_name
Definition: TaggedCache.h:781
bool touch_if_exists(KeyComparable const &key)
Refresh the last access time on a key if present.
Definition: TaggedCache.h:185
clock_type & clock()
Return the clock associated with the cache.
Definition: TaggedCache.h:91
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:65
mutex_type & peekMutex()
Definition: TaggedCache.h:452
beast::abstract_clock< std::chrono::steady_clock > clock_type
Definition: TaggedCache.h:62
clock_type::duration getTargetAge() const
Definition: TaggedCache.h:125
std::vector< key_type > getKeys() const
Definition: TaggedCache.h:458
auto insert(key_type const &key) -> std::enable_if_t< IsKeyCache, ReturnType >
Definition: TaggedCache.h:420
std::size_t size() const
Returns the number of items in the container.
Definition: TaggedCache.h:98
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:728
cache_type m_cache
Definition: TaggedCache.h:791
double rate() const
Returns the fraction of cache hits.
Definition: TaggedCache.h:475
std::uint64_t m_hits
Definition: TaggedCache.h:792
std::shared_ptr< T > fetch(key_type const &digest, Handler const &h)
Fetch an item from the cache.
Definition: TaggedCache.h:491
clock_type::duration m_target_age
Definition: TaggedCache.h:787
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:152
STL namespace.
T push_back(T... args)
T cref(T... args)
T reserve(T... args)
beast::insight::Hook hook
Definition: TaggedCache.h:574
Stats(std::string const &prefix, Handler const &handler, beast::insight::Collector::ptr const &collector)
Definition: TaggedCache.h:562
beast::insight::Gauge size
Definition: TaggedCache.h:575
beast::insight::Gauge hit_rate
Definition: TaggedCache.h:576