summaryrefslogtreecommitdiff
path: root/src/mongo/db/query/plan_cache.h
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/db/query/plan_cache.h')
-rw-r--r--src/mongo/db/query/plan_cache.h199
1 files changed, 126 insertions, 73 deletions
diff --git a/src/mongo/db/query/plan_cache.h b/src/mongo/db/query/plan_cache.h
index e193309230e..dc52b10c4f4 100644
--- a/src/mongo/db/query/plan_cache.h
+++ b/src/mongo/db/query/plan_cache.h
@@ -31,7 +31,6 @@
#include "mongo/db/catalog/util/partitioned.h"
#include "mongo/db/query/lru_key_value.h"
-#include "mongo/db/query/partitioned_cache.h"
#include "mongo/db/query/plan_cache_callbacks.h"
#include "mongo/db/query/plan_cache_debug_info.h"
#include "mongo/platform/mutex.h"
@@ -282,34 +281,21 @@ private:
*/
template <class KeyType,
class CachedPlanType,
- class KeyBudgetEstimator,
+ class BudgetEstimator,
class DebugInfoType,
class Partitioner,
class KeyHasher = std::hash<KeyType>>
-class PlanCacheBase
- : public PartitionedCache<
- KeyType,
- // The 'Value' being "std::shared_ptr<const Entry>" is because we allow readers to clone
- // cache entries out of the lock, therefore it is illegal to mutate the pieces of a cache
- // entry that can be cloned whether you are holding a lock or not.
- std::shared_ptr<const PlanCacheEntryBase<CachedPlanType, DebugInfoType>>,
- KeyBudgetEstimator,
- Partitioner,
- NoopInsertionEvictionListener,
- KeyHasher> {
+class PlanCacheBase {
private:
PlanCacheBase(const PlanCacheBase&) = delete;
PlanCacheBase& operator=(const PlanCacheBase&) = delete;
public:
- using Base =
- PartitionedCache<KeyType,
- std::shared_ptr<const PlanCacheEntryBase<CachedPlanType, DebugInfoType>>,
- KeyBudgetEstimator,
- Partitioner,
- NoopInsertionEvictionListener,
- KeyHasher>;
using Entry = PlanCacheEntryBase<CachedPlanType, DebugInfoType>;
+ // The 'Value' being "std::shared_ptr<const Entry>" is because we allow readers to clone cache
+ // entries out of the lock, therefore it is illegal to mutate the pieces of a cache entry that
+ // can be cloned whether you are holding a lock or not.
+ using Lru = LRUKeyValue<KeyType, std::shared_ptr<const Entry>, BudgetEstimator, KeyHasher>;
// We have three states for a cache entry to be in. Rather than just 'present' or 'not
// present', we use a notion of 'inactive entries' as a way of remembering how performant our
@@ -342,7 +328,11 @@ public:
* Initialize plan cache with the total cache size in bytes and number of partitions.
*/
explicit PlanCacheBase(size_t cacheSize, size_t numPartitions = 1)
- : Base(cacheSize, numPartitions) {}
+ : _numPartitions(numPartitions) {
+ invariant(numPartitions > 0);
+ Lru lru{cacheSize / numPartitions};
+ _partitionedCache = std::make_unique<Partitioned<Lru, Partitioner>>(numPartitions, lru);
+ }
~PlanCacheBase() = default;
@@ -385,11 +375,7 @@ public:
}},
why.stats);
- auto oldEntryWithPartitionLock = this->getWithPartitionLock(key);
- // Can't use reference to structured bindings in a lambda until C++20 so manually
- // destructure it here.
- auto partitionLock = std::move(oldEntryWithPartitionLock.second);
- auto oldEntryWithStatus = std::move(oldEntryWithPartitionLock.first);
+ auto partition = _partitionedCache->lockOnePartition(key);
auto [queryHash, planCacheKey, isNewEntryActive, shouldBeCreated, increasedWorks] = [&]() {
if (internalQueryCacheDisableInactiveEntries.load()) {
// All entries are always active.
@@ -399,34 +385,32 @@ public:
true /* shouldBeCreated */,
boost::optional<size_t>(boost::none));
} else {
+ auto oldEntryWithStatus = partition->get(key);
tassert(6007020,
"LRU store must get value or NoSuchKey error code",
oldEntryWithStatus.isOK() ||
oldEntryWithStatus.getStatus() == ErrorCodes::NoSuchKey);
- bool hasOldEntry = oldEntryWithStatus.isOK();
+ auto oldEntry =
+ oldEntryWithStatus.isOK() ? oldEntryWithStatus.getValue()->second : nullptr;
const auto newState = getNewEntryState(
key,
- // Deference the pointer, then the shared_ptr, and then back to a raw pointer.
- hasOldEntry ? &**oldEntryWithStatus.getValue() : nullptr,
+ oldEntry.get(),
newWorks,
worksGrowthCoefficient.get_value_or(internalQueryCacheWorksGrowthCoefficient),
callbacks);
// Avoid recomputing the hashes if we've got an old entry to grab them from.
- auto [queryHash, planCacheKey] = [&]() {
- if (hasOldEntry) {
- auto&& oldEntry = &**oldEntryWithStatus.getValue();
- return std::make_pair(oldEntry->queryHash, oldEntry->planCacheKey);
- } else {
- return std::make_pair(key.queryHash(), key.planCacheKeyHash());
- }
- }();
- return std::make_tuple(queryHash,
- planCacheKey,
- newState.shouldBeActive,
- newState.shouldBeCreated,
- newState.increasedWorks);
+ return oldEntry ? std::make_tuple(oldEntry->queryHash,
+ oldEntry->planCacheKey,
+ newState.shouldBeActive,
+ newState.shouldBeCreated,
+ newState.increasedWorks)
+ : std::make_tuple(key.queryHash(),
+ key.planCacheKeyHash(),
+ newState.shouldBeActive,
+ newState.shouldBeCreated,
+ newState.increasedWorks);
}
}();
@@ -450,7 +434,7 @@ public:
increasedWorks ? *increasedWorks : newWorks,
callbacks->buildDebugInfo());
- this->put(key, std::move(newEntry), partitionLock);
+ partition->add(key, std::move(newEntry));
return Status::OK();
}
@@ -470,7 +454,10 @@ public:
indexFilterKey,
now,
std::move(debugInfo));
- this->put(key, std::move(entry));
+ auto partition = _partitionedCache->lockOnePartition(key);
+ // We're not interested in the number of evicted entries if the cache store exceeds the
+ // budget after add(), so we just ignore the return value.
+ partition->add(key, std::move(entry));
}
/**
@@ -484,8 +471,8 @@ public:
return;
}
- auto [entry, partitionLock] = this->getWithPartitionLock(key);
-
+ auto partition = _partitionedCache->lockOnePartition(key);
+ auto entry = partition->get(key);
if (!entry.isOK()) {
tassert(6007021,
"Unexpected error code from LRU store",
@@ -493,11 +480,11 @@ public:
return;
}
- auto entryPtr = *entry.getValue();
+ auto entryPtr = entry.getValue()->second;
if (entryPtr->isActive == true) {
std::shared_ptr<Entry> newEntry = entryPtr->clone();
newEntry->isActive = false;
- this->put(key, std::move(newEntry), partitionLock);
+ partition->add(key, std::move(newEntry));
}
}
@@ -509,26 +496,29 @@ public:
* for the query (if there is one).
*/
GetResult get(const KeyType& key) const {
- std::shared_ptr<const Entry> entrySharedPtr;
+ std::shared_ptr<const Entry> entryPtr;
CacheEntryState state;
{
- auto [entry, partitionLock] = this->getWithPartitionLock(key);
+ auto partition = _partitionedCache->lockOnePartition(key);
+ auto entry = partition->get(key);
if (!entry.isOK()) {
tassert(6007023,
"Unexpected error code from LRU store",
entry.getStatus() == ErrorCodes::NoSuchKey);
return {CacheEntryState::kNotPresent, nullptr};
}
- entrySharedPtr = *entry.getValue();
- state = entrySharedPtr->isActive ? CacheEntryState::kPresentActive
- : CacheEntryState::kPresentInactive;
+ entryPtr = entry.getValue()->second;
+ state = entryPtr->isActive ? CacheEntryState::kPresentActive
+ : CacheEntryState::kPresentInactive;
}
- // The purpose of cloning 'entry' (in CachedPlanHolder ctor) after we release the lock
- // is to allow multiple threads to clone the same plan cache entry at once. 'entry'
- // cannot be deleted by another thread even if the plan cache is being concurrently
- // modified by other threads because we are holding a std::shared_ptr to this entry.
+ // The purpose of cloning 'entry' after we release the lock is to allow multiple threads to
+ // clone the same plan cache entry at once. 'entry' cannot be deleted by another thread even
+ // if the plan cache is being concurrently modified by other threads because we are holding
+ // a std::shared_ptr to this entry.
+ tassert(6007024, "LRU store must get a value or an error code", entryPtr);
+
return {state,
- std::make_unique<CachedPlanHolder<CachedPlanType, DebugInfoType>>(*entrySharedPtr)};
+ std::make_unique<CachedPlanHolder<CachedPlanType, DebugInfoType>>(*entryPtr)};
}
/**
@@ -547,16 +537,59 @@ public:
}
/**
+ * Remove the entry with the 'key' from the cache. If there is no entry for the given key in
+ * the cache, this call is a no-op.
+ */
+ void remove(const KeyType& key) {
+ _partitionedCache->erase(key);
+ }
+
+ /**
+ * Remove all the entries for keys for which the predicate returns true. Return the number of
+ * removed entries.
+ */
+ template <typename UnaryPredicate>
+ size_t removeIf(UnaryPredicate predicate) {
+ size_t nRemoved = 0;
+ for (size_t partitionId = 0; partitionId < _numPartitions; ++partitionId) {
+ auto lockedPartition = _partitionedCache->lockOnePartitionById(partitionId);
+ nRemoved += lockedPartition->removeIf(predicate);
+ }
+ return nRemoved;
+ }
+
+ /**
+ * Remove *all* cached plans. Does not clear index information.
+ */
+ void clear() {
+ _partitionedCache->clear();
+ }
+
+ /**
+ * Reset total cache size. If the size is set to a smaller value than before, enough entries are
+ * evicted in order to ensure that the cache fits within the new budget.
+ */
+ void reset(size_t cacheSize) {
+ for (size_t partitionId = 0; partitionId < _numPartitions; ++partitionId) {
+ auto lockedPartition = _partitionedCache->lockOnePartitionById(partitionId);
+ lockedPartition->reset(cacheSize / _numPartitions);
+ }
+ }
+
+ /**
* Returns a copy of a cache entry, looked up by the plan cache key.
*
* If there is no entry in the cache for the 'query', returns an error Status.
*/
StatusWith<std::unique_ptr<Entry>> getEntry(const KeyType& key) const {
- auto result = this->lookup(key);
- if (!result.isOK()) {
- return {result.getStatus()};
+ auto partition = _partitionedCache->lockOnePartition(key);
+ auto entry = partition->get(key);
+ if (!entry.isOK()) {
+ return entry.getStatus();
}
- return {result.getValue()->get()->clone()};
+ invariant(entry.getValue()->second);
+
+ return std::unique_ptr<Entry>(entry.getValue()->second->clone());
}
/**
@@ -565,14 +598,26 @@ public:
std::vector<std::unique_ptr<Entry>> getAllEntries() const {
std::vector<std::unique_ptr<Entry>> entries;
- this->forEach([&](const KeyType& key, const std::shared_ptr<Entry>& entry) {
- entries.emplace_back(entry);
- });
+ for (size_t partitionId = 0; partitionId < _numPartitions; ++partitionId) {
+ auto lockedPartition = _partitionedCache->lockOnePartitionById(partitionId);
+
+ for (auto&& [key, entry] : *lockedPartition) {
+ entries.emplace_back(entry->clone());
+ }
+ }
return entries;
}
/**
+ * Returns the size of the cache.
+ * Used for testing.
+ */
+ size_t size() const {
+ return _partitionedCache->size();
+ }
+
+ /**
* Iterates over the plan cache. For each entry, first filters according to the predicate
* function 'cacheKeyFilterFunc', (Note that 'cacheKeyFilterFunc' could be empty, if so, we
* don't filter by plan cache key.), then serializes the PlanCacheEntryBase according to
@@ -589,15 +634,20 @@ public:
std::vector<BSONObj> results;
- this->forEach([&](const KeyType& key, const std::shared_ptr<const Entry>& entry) {
- if (cacheKeyFilterFunc && !cacheKeyFilterFunc(key)) {
- return;
- }
- auto serializedEntry = serializationFunc(*entry);
- if (filterFunc(serializedEntry)) {
- results.push_back(serializedEntry);
+ for (size_t partitionId = 0; partitionId < _numPartitions; ++partitionId) {
+ auto lockedPartition = _partitionedCache->lockOnePartitionById(partitionId);
+
+ for (auto&& cacheEntry : *lockedPartition) {
+ if (cacheKeyFilterFunc && !cacheKeyFilterFunc(cacheEntry.first)) {
+ continue;
+ }
+ const auto& entry = cacheEntry.second;
+ auto serializedEntry = serializationFunc(*entry);
+ if (filterFunc(serializedEntry)) {
+ results.push_back(serializedEntry);
+ }
}
- });
+ }
return results;
}
@@ -685,6 +735,9 @@ private:
return res;
}
+
+ std::size_t _numPartitions;
+ std::unique_ptr<Partitioned<Lru, Partitioner>> _partitionedCache;
};
} // namespace mongo