From 607e2039e2cfab99ecca8b8c53406e53a2523025 Mon Sep 17 00:00:00 2001 From: JoelKatz Date: Tue, 8 Jan 2013 14:50:03 -0800 Subject: [PATCH] Add negative caching for HashedObject class. This massively reduces contention for the database lock under high network ledger fetch load. --- src/cpp/ripple/HashedObject.cpp | 13 +++- src/cpp/ripple/HashedObject.h | 6 +- src/cpp/ripple/KeyCache.h | 109 ++++++++++++++++++++++++++++++++ 3 files changed, 125 insertions(+), 3 deletions(-) create mode 100644 src/cpp/ripple/KeyCache.h diff --git a/src/cpp/ripple/HashedObject.cpp b/src/cpp/ripple/HashedObject.cpp index 0bd2c8492..288639f63 100644 --- a/src/cpp/ripple/HashedObject.cpp +++ b/src/cpp/ripple/HashedObject.cpp @@ -33,6 +33,7 @@ bool HashedObjectStore::store(HashedObjectType type, uint32 index, } assert(hash == Serializer::getSHA512Half(data)); + mNegativeCache.del(hash); HashedObject::pointer object = boost::make_shared(type, index, data, hash); if (!mCache.canonicalize(hash, object)) { @@ -115,6 +116,7 @@ void HashedObjectStore::bulkWrite() HashedObject::pointer HashedObjectStore::retrieve(const uint256& hash) { + HashedObject::pointer obj; { obj = mCache.fetch(hash); @@ -125,6 +127,9 @@ HashedObject::pointer HashedObjectStore::retrieve(const uint256& hash) } } + if (mNegativeCache.isPresent(hash)) + return HashedObject::pointer(); + if (!theApp || !theApp->getHashNodeDB()) return HashedObject::pointer(); std::string sql = "SELECT * FROM CommittedObjects WHERE Hash='"; @@ -139,12 +144,17 @@ HashedObject::pointer HashedObjectStore::retrieve(const uint256& hash) if (!db->executeSQL(sql) || !db->startIterRows()) { // cLog(lsTRACE) << "HOS: " << hash << " fetch: not in db"; + mNegativeCache.add(hash); return HashedObject::pointer(); } std::string type; db->getStr("ObjType", type); - if (type.size() == 0) return HashedObject::pointer(); + if (type.size() == 0) + { + mNegativeCache.add(hash); + return HashedObject::pointer(); + } uint32 index = db->getBigInt("LedgerIndex"); @@ -164,6 +174,7 @@ HashedObject::pointer HashedObjectStore::retrieve(const uint256& hash) case 'N': htype = hotTRANSACTION_NODE; break; default: cLog(lsERROR) << "Invalid hashed object"; + mNegativeCache.add(hash); return HashedObject::pointer(); } diff --git a/src/cpp/ripple/HashedObject.h b/src/cpp/ripple/HashedObject.h index 5dd6e641a..05883c2ca 100644 --- a/src/cpp/ripple/HashedObject.h +++ b/src/cpp/ripple/HashedObject.h @@ -10,6 +10,7 @@ #include "uint256.h" #include "ScopedLock.h" #include "TaggedCache.h" +#include "KeyCache.h" #include "InstanceCounter.h" DEFINE_INSTANCE(HashedObject); @@ -45,7 +46,8 @@ public: class HashedObjectStore { protected: - TaggedCache mCache; + TaggedCache mCache; + KeyCache mNegativeCache; boost::mutex mWriteMutex; boost::condition_variable mWriteCondition; @@ -65,7 +67,7 @@ public: void bulkWrite(); void waitWrite(); - void sweep() { mCache.sweep(); } + void sweep() { mCache.sweep(); mNegativeCache.sweep(); } }; #endif diff --git a/src/cpp/ripple/KeyCache.h b/src/cpp/ripple/KeyCache.h new file mode 100644 index 000000000..e5ec5262d --- /dev/null +++ b/src/cpp/ripple/KeyCache.h @@ -0,0 +1,109 @@ +#ifndef KEY_CACHE__H +#define KEY_CACHE__H + +#include +#include + +template class KeyCache +{ // Maintains a cache of keys with no associated data +public: + typedef c_Key key_type; + typedef boost::unordered_map map_type; + typedef typename map_type::iterator map_iterator; + +protected: + boost::mutex mNCLock; + map_type mCache; + int mTargetSize, mTargetAge; + + uint64_t mHits, mMisses; + +public: + + KeyCache(int size = 0, int age = 120) : mTargetSize(size), mTargetAge(age), mHits(0), mMisses(0) + { + assert((mTargetSize >= 0) && (mTargetAge > 2)); + } + + void getStats(int& size, uint64_t& hits, uint64_t& misses) + { + boost::mutex::scoped_lock sl(mNCLock); + + size = mCache.size(); + hits = mHits; + misses = mMisses; + } + + bool isPresent(const key_type& key) + { // Check if an entry is cached, refresh it if so + boost::mutex::scoped_lock sl(mNCLock); + + map_iterator it = mCache.find(key); + if (it == mCache.end()) + { + ++mMisses; + return false; + } + it->second = time(NULL); + ++mHits; + return true; + } + + bool del(const key_type& key) + { // Remove an entry from the cache, return false if not-present + boost::mutex::scoped_lock sl(mNCLock); + + map_iterator it = mCache.find(key); + if (it == mCache.end()) + return false; + + mCache.erase(it); + return true; + } + + bool add(const key_type& key) + { // Add an entry to the cache, return true if it is new + boost::mutex::scoped_lock sl(mNCLock); + + map_iterator it = mCache.find(key); + if (it != mCache.end()) + { + it->second = time(NULL); + return false; + } + mCache.insert(std::make_pair(key, time(NULL))); + return true; + } + + void sweep() + { // Remove stale entries from the cache + time_t now = time(NULL); + boost::mutex::scoped_lock sl(mNCLock); + + time_t target; + if ((mTargetSize == 0) || (mCache.size() <= mTargetSize)) + target = now - mTargetAge; + else + { + target = now - (mTargetAge * mTargetSize / mCache.size()); + if (target > (now - 2)) + target = now - 2; + } + + map_iterator it = mCache.begin(); + while (it != mCache.end()) + { + if (it->second > now) + { + it->second = now; + ++it; + } + else if (it->second < target) + it = mCache.erase(it); + else + ++it; + } + } +}; + +#endif