2016-02-09 23:12:00 +00:00
|
|
|
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
2013-10-16 21:59:46 +00:00
|
|
|
// This source code is licensed under the BSD-style license found in the
|
|
|
|
// LICENSE file in the root directory of this source tree. An additional grant
|
|
|
|
// of patent rights can be found in the PATENTS file in the same directory.
|
|
|
|
//
|
2011-03-18 22:37:00 +00:00
|
|
|
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
|
|
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
|
|
|
|
|
|
|
#include <assert.h>
|
2011-06-29 00:30:50 +00:00
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
2011-03-18 22:37:00 +00:00
|
|
|
|
|
|
|
#include "port/port.h"
|
2016-05-24 06:35:23 +00:00
|
|
|
#include "rocksdb/cache.h"
|
2014-01-02 19:26:57 +00:00
|
|
|
#include "util/autovector.h"
|
2011-03-18 22:37:00 +00:00
|
|
|
#include "util/hash.h"
|
2016-05-24 06:35:23 +00:00
|
|
|
#include "util/lru_cache_handle.h"
|
2011-03-18 22:37:00 +00:00
|
|
|
#include "util/mutexlock.h"
|
|
|
|
|
2013-10-04 04:49:15 +00:00
|
|
|
namespace rocksdb {
|
2011-03-18 22:37:00 +00:00
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
// LRU cache implementation
|
|
|
|
|
2011-06-29 00:30:50 +00:00
|
|
|
// We provide our own simple hash table since it removes a whole bunch
|
|
|
|
// of porting hacks and is also faster than some of the built-in hash
|
|
|
|
// table implementations in some of the compiler/runtime combinations
|
|
|
|
// we have tested. E.g., readrandom speeds up by ~5% over the g++
|
|
|
|
// 4.4.3's builtin hashtable.
|
|
|
|
class HandleTable {
|
|
|
|
public:
|
2013-03-01 02:04:58 +00:00
|
|
|
HandleTable() : length_(0), elems_(0), list_(nullptr) { Resize(); }
|
Modifed the LRU cache eviction code so that it doesn't evict blocks which have exteranl references
Summary:
Currently, blocks which have more than one reference (ie referenced by something other than cache itself) are evicted from cache. This doesn't make much sense:
- blocks are still in RAM, so the RAM usage reported by the cache is incorrect
- if the same block is needed by another iterator, it will be loaded and decompressed again
This diff changes the reference counting scheme a bit. Previously, if the cache contained the block, this was accounted for in its refcount. After this change, the refcount is only used to track external references. There is a boolean flag which indicates whether or not the block is contained in the cache.
This diff also changes how LRU list is used. Previously, both hashtable and the LRU list contained all blocks. After this change, the LRU list contains blocks with the refcount==0, ie those which can be evicted from the cache.
Note that this change still allows for cache to grow beyond its capacity. This happens when all blocks are pinned (ie refcount>0). This is consistent with the current behavior. The cache's insert function never fails. I spent lots of time trying to make table_reader and other places work with the insert which might failed. It turned out to be pretty hard. It might really destabilize some customers, so finally, I decided against doing this.
table_cache_remove_scan_count_limit option will be unneeded after this change, but I will remove it in the following diff, if this one gets approved
Test Plan: Ran tests, made sure they pass
Reviewers: sdong, ljin
Differential Revision: https://reviews.facebook.net/D25503
2014-10-21 18:49:13 +00:00
|
|
|
|
|
|
|
template <typename T>
|
|
|
|
void ApplyToAllCacheEntries(T func) {
|
|
|
|
for (uint32_t i = 0; i < length_; i++) {
|
|
|
|
LRUHandle* h = list_[i];
|
|
|
|
while (h != nullptr) {
|
|
|
|
auto n = h->next_hash;
|
|
|
|
assert(h->in_cache);
|
|
|
|
func(h);
|
|
|
|
h = n;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
~HandleTable() {
|
|
|
|
ApplyToAllCacheEntries([](LRUHandle* h) {
|
|
|
|
if (h->refs == 1) {
|
|
|
|
h->Free();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
delete[] list_;
|
|
|
|
}
|
2011-06-29 00:30:50 +00:00
|
|
|
|
2011-08-22 21:08:51 +00:00
|
|
|
LRUHandle* Lookup(const Slice& key, uint32_t hash) {
|
|
|
|
return *FindPointer(key, hash);
|
2011-06-29 00:30:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
LRUHandle* Insert(LRUHandle* h) {
|
2011-08-22 21:08:51 +00:00
|
|
|
LRUHandle** ptr = FindPointer(h->key(), h->hash);
|
2011-06-29 00:30:50 +00:00
|
|
|
LRUHandle* old = *ptr;
|
2013-03-01 02:04:58 +00:00
|
|
|
h->next_hash = (old == nullptr ? nullptr : old->next_hash);
|
2011-06-29 00:30:50 +00:00
|
|
|
*ptr = h;
|
2013-03-01 02:04:58 +00:00
|
|
|
if (old == nullptr) {
|
2011-06-29 00:30:50 +00:00
|
|
|
++elems_;
|
|
|
|
if (elems_ > length_) {
|
|
|
|
// Since each cache entry is fairly large, we aim for a small
|
|
|
|
// average linked list length (<= 1).
|
|
|
|
Resize();
|
|
|
|
}
|
2011-03-18 22:37:00 +00:00
|
|
|
}
|
2011-06-29 00:30:50 +00:00
|
|
|
return old;
|
|
|
|
}
|
|
|
|
|
2011-08-22 21:08:51 +00:00
|
|
|
LRUHandle* Remove(const Slice& key, uint32_t hash) {
|
|
|
|
LRUHandle** ptr = FindPointer(key, hash);
|
2011-06-29 00:30:50 +00:00
|
|
|
LRUHandle* result = *ptr;
|
2013-03-01 02:04:58 +00:00
|
|
|
if (result != nullptr) {
|
2011-06-29 00:30:50 +00:00
|
|
|
*ptr = result->next_hash;
|
|
|
|
--elems_;
|
2011-03-18 22:37:00 +00:00
|
|
|
}
|
2011-06-29 00:30:50 +00:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
// The table consists of an array of buckets where each bucket is
|
|
|
|
// a linked list of cache entries that hash into the bucket.
|
|
|
|
uint32_t length_;
|
|
|
|
uint32_t elems_;
|
|
|
|
LRUHandle** list_;
|
|
|
|
|
|
|
|
// Return a pointer to slot that points to a cache entry that
|
2011-08-22 21:08:51 +00:00
|
|
|
// matches key/hash. If there is no such cache entry, return a
|
|
|
|
// pointer to the trailing slot in the corresponding linked list.
|
|
|
|
LRUHandle** FindPointer(const Slice& key, uint32_t hash) {
|
2011-06-29 00:30:50 +00:00
|
|
|
LRUHandle** ptr = &list_[hash & (length_ - 1)];
|
2016-05-24 06:35:23 +00:00
|
|
|
while (*ptr != nullptr && ((*ptr)->hash != hash || key != (*ptr)->key())) {
|
2011-06-29 00:30:50 +00:00
|
|
|
ptr = &(*ptr)->next_hash;
|
2011-03-18 22:37:00 +00:00
|
|
|
}
|
2011-06-29 00:30:50 +00:00
|
|
|
return ptr;
|
|
|
|
}
|
2011-03-18 22:37:00 +00:00
|
|
|
|
2011-06-29 00:30:50 +00:00
|
|
|
void Resize() {
|
2013-12-11 16:33:29 +00:00
|
|
|
uint32_t new_length = 16;
|
|
|
|
while (new_length < elems_ * 1.5) {
|
2011-06-29 00:30:50 +00:00
|
|
|
new_length *= 2;
|
|
|
|
}
|
|
|
|
LRUHandle** new_list = new LRUHandle*[new_length];
|
|
|
|
memset(new_list, 0, sizeof(new_list[0]) * new_length);
|
|
|
|
uint32_t count = 0;
|
2011-08-06 00:19:37 +00:00
|
|
|
for (uint32_t i = 0; i < length_; i++) {
|
2011-06-29 00:30:50 +00:00
|
|
|
LRUHandle* h = list_[i];
|
2013-03-01 02:04:58 +00:00
|
|
|
while (h != nullptr) {
|
2011-06-29 00:30:50 +00:00
|
|
|
LRUHandle* next = h->next_hash;
|
2011-08-22 21:08:51 +00:00
|
|
|
uint32_t hash = h->hash;
|
2011-06-29 00:30:50 +00:00
|
|
|
LRUHandle** ptr = &new_list[hash & (new_length - 1)];
|
|
|
|
h->next_hash = *ptr;
|
|
|
|
*ptr = h;
|
|
|
|
h = next;
|
|
|
|
count++;
|
|
|
|
}
|
2011-03-18 22:37:00 +00:00
|
|
|
}
|
2011-06-29 00:30:50 +00:00
|
|
|
assert(elems_ == count);
|
|
|
|
delete[] list_;
|
|
|
|
list_ = new_list;
|
|
|
|
length_ = new_length;
|
|
|
|
}
|
|
|
|
};
|
2011-03-18 22:37:00 +00:00
|
|
|
|
2011-08-22 21:08:51 +00:00
|
|
|
// A single shard of sharded cache.
|
|
|
|
class LRUCache {
|
2011-03-18 22:37:00 +00:00
|
|
|
public:
|
2011-08-22 21:08:51 +00:00
|
|
|
LRUCache();
|
|
|
|
~LRUCache();
|
2011-03-18 22:37:00 +00:00
|
|
|
|
2011-08-22 21:08:51 +00:00
|
|
|
// Separate from constructor so caller can easily make an array of LRUCache
|
2015-04-24 21:12:58 +00:00
|
|
|
// if current usage is more than new capacity, the function will attempt to
|
|
|
|
// free the needed space
|
|
|
|
void SetCapacity(size_t capacity);
|
2011-08-22 21:08:51 +00:00
|
|
|
|
2016-03-11 01:35:19 +00:00
|
|
|
// Set the flag to reject insertion if cache if full.
|
|
|
|
void SetStrictCapacityLimit(bool strict_capacity_limit);
|
|
|
|
|
2011-08-22 21:08:51 +00:00
|
|
|
// Like Cache methods, but with an extra "hash" parameter.
|
2016-03-11 01:35:19 +00:00
|
|
|
Status Insert(const Slice& key, uint32_t hash, void* value, size_t charge,
|
|
|
|
void (*deleter)(const Slice& key, void* value),
|
|
|
|
Cache::Handle** handle);
|
2011-08-22 21:08:51 +00:00
|
|
|
Cache::Handle* Lookup(const Slice& key, uint32_t hash);
|
|
|
|
void Release(Cache::Handle* handle);
|
|
|
|
void Erase(const Slice& key, uint32_t hash);
|
2015-06-18 20:56:31 +00:00
|
|
|
|
2013-12-13 23:43:05 +00:00
|
|
|
// Although in some platforms the update of size_t is atomic, to make sure
|
2015-06-18 20:56:31 +00:00
|
|
|
// GetUsage() and GetPinnedUsage() work correctly under any platform, we'll
|
|
|
|
// protect them with mutex_.
|
|
|
|
|
2013-12-13 23:43:05 +00:00
|
|
|
size_t GetUsage() const {
|
|
|
|
MutexLock l(&mutex_);
|
|
|
|
return usage_;
|
|
|
|
}
|
2011-03-18 22:37:00 +00:00
|
|
|
|
2015-06-18 20:56:31 +00:00
|
|
|
size_t GetPinnedUsage() const {
|
|
|
|
MutexLock l(&mutex_);
|
|
|
|
assert(usage_ >= lru_usage_);
|
|
|
|
return usage_ - lru_usage_;
|
|
|
|
}
|
|
|
|
|
2014-05-02 20:24:04 +00:00
|
|
|
void ApplyToAllCacheEntries(void (*callback)(void*, size_t),
|
|
|
|
bool thread_safe);
|
|
|
|
|
Adding pin_l0_filter_and_index_blocks_in_cache feature and related fixes.
Summary:
When a block based table file is opened, if prefetch_index_and_filter is true, it will prefetch the index and filter blocks, putting them into the block cache.
What this feature adds: when a L0 block based table file is opened, if pin_l0_filter_and_index_blocks_in_cache is true in the options (and prefetch_index_and_filter is true), then the filter and index blocks aren't released back to the block cache at the end of BlockBasedTableReader::Open(). Instead the table reader takes ownership of them, hence pinning them, ie. the LRU cache will never push them out. Meanwhile in the table reader, further accesses will not hit the block cache, thus avoiding lock contention.
Test Plan:
'export TEST_TMPDIR=/dev/shm/ && DISABLE_JEMALLOC=1 OPT=-g make all valgrind_check -j32' is OK.
I didn't run the Java tests, I don't have Java set up on my devserver.
Reviewers: sdong
Reviewed By: sdong
Subscribers: andrewkr, dhruba
Differential Revision: https://reviews.facebook.net/D56133
2016-04-01 17:42:39 +00:00
|
|
|
void EraseUnRefEntries();
|
|
|
|
|
2011-03-18 22:37:00 +00:00
|
|
|
private:
|
|
|
|
void LRU_Remove(LRUHandle* e);
|
|
|
|
void LRU_Append(LRUHandle* e);
|
2013-10-07 22:37:40 +00:00
|
|
|
// Just reduce the reference count by 1.
|
|
|
|
// Return true if last reference
|
|
|
|
bool Unref(LRUHandle* e);
|
2011-03-18 22:37:00 +00:00
|
|
|
|
2015-04-24 21:12:58 +00:00
|
|
|
// Free some space following strict LRU policy until enough space
|
|
|
|
// to hold (usage_ + charge) is freed or the lru list is empty
|
|
|
|
// This function is not thread safe - it needs to be executed while
|
|
|
|
// holding the mutex_
|
2016-05-24 06:35:23 +00:00
|
|
|
void EvictFromLRU(size_t charge, autovector<LRUHandle*>* deleted);
|
2015-04-24 21:12:58 +00:00
|
|
|
|
2011-08-22 21:08:51 +00:00
|
|
|
// Initialized before use.
|
|
|
|
size_t capacity_;
|
2011-03-18 22:37:00 +00:00
|
|
|
|
2015-06-18 20:56:31 +00:00
|
|
|
// Memory size for entries residing in the cache
|
|
|
|
size_t usage_;
|
|
|
|
|
|
|
|
// Memory size for entries residing only in the LRU list
|
|
|
|
size_t lru_usage_;
|
|
|
|
|
2016-03-11 01:35:19 +00:00
|
|
|
// Whether to reject insertion if cache reaches its full capacity.
|
|
|
|
bool strict_capacity_limit_;
|
|
|
|
|
2011-03-18 22:37:00 +00:00
|
|
|
// mutex_ protects the following state.
|
2013-12-13 23:43:05 +00:00
|
|
|
// We don't count mutex_ as the cache's internal state so semantically we
|
|
|
|
// don't mind mutex_ invoking the non-const actions.
|
|
|
|
mutable port::Mutex mutex_;
|
2011-03-18 22:37:00 +00:00
|
|
|
|
|
|
|
// Dummy head of LRU list.
|
|
|
|
// lru.prev is newest entry, lru.next is oldest entry.
|
Modifed the LRU cache eviction code so that it doesn't evict blocks which have exteranl references
Summary:
Currently, blocks which have more than one reference (ie referenced by something other than cache itself) are evicted from cache. This doesn't make much sense:
- blocks are still in RAM, so the RAM usage reported by the cache is incorrect
- if the same block is needed by another iterator, it will be loaded and decompressed again
This diff changes the reference counting scheme a bit. Previously, if the cache contained the block, this was accounted for in its refcount. After this change, the refcount is only used to track external references. There is a boolean flag which indicates whether or not the block is contained in the cache.
This diff also changes how LRU list is used. Previously, both hashtable and the LRU list contained all blocks. After this change, the LRU list contains blocks with the refcount==0, ie those which can be evicted from the cache.
Note that this change still allows for cache to grow beyond its capacity. This happens when all blocks are pinned (ie refcount>0). This is consistent with the current behavior. The cache's insert function never fails. I spent lots of time trying to make table_reader and other places work with the insert which might failed. It turned out to be pretty hard. It might really destabilize some customers, so finally, I decided against doing this.
table_cache_remove_scan_count_limit option will be unneeded after this change, but I will remove it in the following diff, if this one gets approved
Test Plan: Ran tests, made sure they pass
Reviewers: sdong, ljin
Differential Revision: https://reviews.facebook.net/D25503
2014-10-21 18:49:13 +00:00
|
|
|
// LRU contains items which can be evicted, ie reference only by cache
|
2011-03-18 22:37:00 +00:00
|
|
|
LRUHandle lru_;
|
|
|
|
|
|
|
|
HandleTable table_;
|
|
|
|
};
|
|
|
|
|
2015-06-18 20:56:31 +00:00
|
|
|
LRUCache::LRUCache() : usage_(0), lru_usage_(0) {
|
2011-03-18 22:37:00 +00:00
|
|
|
// Make empty circular linked list
|
|
|
|
lru_.next = &lru_;
|
|
|
|
lru_.prev = &lru_;
|
|
|
|
}
|
|
|
|
|
Modifed the LRU cache eviction code so that it doesn't evict blocks which have exteranl references
Summary:
Currently, blocks which have more than one reference (ie referenced by something other than cache itself) are evicted from cache. This doesn't make much sense:
- blocks are still in RAM, so the RAM usage reported by the cache is incorrect
- if the same block is needed by another iterator, it will be loaded and decompressed again
This diff changes the reference counting scheme a bit. Previously, if the cache contained the block, this was accounted for in its refcount. After this change, the refcount is only used to track external references. There is a boolean flag which indicates whether or not the block is contained in the cache.
This diff also changes how LRU list is used. Previously, both hashtable and the LRU list contained all blocks. After this change, the LRU list contains blocks with the refcount==0, ie those which can be evicted from the cache.
Note that this change still allows for cache to grow beyond its capacity. This happens when all blocks are pinned (ie refcount>0). This is consistent with the current behavior. The cache's insert function never fails. I spent lots of time trying to make table_reader and other places work with the insert which might failed. It turned out to be pretty hard. It might really destabilize some customers, so finally, I decided against doing this.
table_cache_remove_scan_count_limit option will be unneeded after this change, but I will remove it in the following diff, if this one gets approved
Test Plan: Ran tests, made sure they pass
Reviewers: sdong, ljin
Differential Revision: https://reviews.facebook.net/D25503
2014-10-21 18:49:13 +00:00
|
|
|
LRUCache::~LRUCache() {}
|
2011-03-18 22:37:00 +00:00
|
|
|
|
2013-10-07 22:37:40 +00:00
|
|
|
bool LRUCache::Unref(LRUHandle* e) {
|
2011-03-18 22:37:00 +00:00
|
|
|
assert(e->refs > 0);
|
|
|
|
e->refs--;
|
2013-10-07 22:37:40 +00:00
|
|
|
return e->refs == 0;
|
|
|
|
}
|
2013-04-04 01:53:42 +00:00
|
|
|
|
Modifed the LRU cache eviction code so that it doesn't evict blocks which have exteranl references
Summary:
Currently, blocks which have more than one reference (ie referenced by something other than cache itself) are evicted from cache. This doesn't make much sense:
- blocks are still in RAM, so the RAM usage reported by the cache is incorrect
- if the same block is needed by another iterator, it will be loaded and decompressed again
This diff changes the reference counting scheme a bit. Previously, if the cache contained the block, this was accounted for in its refcount. After this change, the refcount is only used to track external references. There is a boolean flag which indicates whether or not the block is contained in the cache.
This diff also changes how LRU list is used. Previously, both hashtable and the LRU list contained all blocks. After this change, the LRU list contains blocks with the refcount==0, ie those which can be evicted from the cache.
Note that this change still allows for cache to grow beyond its capacity. This happens when all blocks are pinned (ie refcount>0). This is consistent with the current behavior. The cache's insert function never fails. I spent lots of time trying to make table_reader and other places work with the insert which might failed. It turned out to be pretty hard. It might really destabilize some customers, so finally, I decided against doing this.
table_cache_remove_scan_count_limit option will be unneeded after this change, but I will remove it in the following diff, if this one gets approved
Test Plan: Ran tests, made sure they pass
Reviewers: sdong, ljin
Differential Revision: https://reviews.facebook.net/D25503
2014-10-21 18:49:13 +00:00
|
|
|
// Call deleter and free
|
2011-03-18 22:37:00 +00:00
|
|
|
|
Adding pin_l0_filter_and_index_blocks_in_cache feature and related fixes.
Summary:
When a block based table file is opened, if prefetch_index_and_filter is true, it will prefetch the index and filter blocks, putting them into the block cache.
What this feature adds: when a L0 block based table file is opened, if pin_l0_filter_and_index_blocks_in_cache is true in the options (and prefetch_index_and_filter is true), then the filter and index blocks aren't released back to the block cache at the end of BlockBasedTableReader::Open(). Instead the table reader takes ownership of them, hence pinning them, ie. the LRU cache will never push them out. Meanwhile in the table reader, further accesses will not hit the block cache, thus avoiding lock contention.
Test Plan:
'export TEST_TMPDIR=/dev/shm/ && DISABLE_JEMALLOC=1 OPT=-g make all valgrind_check -j32' is OK.
I didn't run the Java tests, I don't have Java set up on my devserver.
Reviewers: sdong
Reviewed By: sdong
Subscribers: andrewkr, dhruba
Differential Revision: https://reviews.facebook.net/D56133
2016-04-01 17:42:39 +00:00
|
|
|
void LRUCache::EraseUnRefEntries() {
|
|
|
|
autovector<LRUHandle*> last_reference_list;
|
|
|
|
{
|
|
|
|
MutexLock l(&mutex_);
|
|
|
|
while (lru_.next != &lru_) {
|
|
|
|
LRUHandle* old = lru_.next;
|
|
|
|
assert(old->in_cache);
|
|
|
|
assert(old->refs ==
|
|
|
|
1); // LRU list contains elements which may be evicted
|
|
|
|
LRU_Remove(old);
|
|
|
|
table_.Remove(old->key(), old->hash);
|
|
|
|
old->in_cache = false;
|
|
|
|
Unref(old);
|
|
|
|
usage_ -= old->charge;
|
|
|
|
last_reference_list.push_back(old);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (auto entry : last_reference_list) {
|
|
|
|
entry->Free();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-05-02 20:24:04 +00:00
|
|
|
void LRUCache::ApplyToAllCacheEntries(void (*callback)(void*, size_t),
|
|
|
|
bool thread_safe) {
|
|
|
|
if (thread_safe) {
|
|
|
|
mutex_.Lock();
|
|
|
|
}
|
2016-05-24 06:35:23 +00:00
|
|
|
table_.ApplyToAllCacheEntries(
|
|
|
|
[callback](LRUHandle* h) { callback(h->value, h->charge); });
|
2014-05-02 20:24:04 +00:00
|
|
|
if (thread_safe) {
|
|
|
|
mutex_.Unlock();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-03-18 22:37:00 +00:00
|
|
|
void LRUCache::LRU_Remove(LRUHandle* e) {
|
Modifed the LRU cache eviction code so that it doesn't evict blocks which have exteranl references
Summary:
Currently, blocks which have more than one reference (ie referenced by something other than cache itself) are evicted from cache. This doesn't make much sense:
- blocks are still in RAM, so the RAM usage reported by the cache is incorrect
- if the same block is needed by another iterator, it will be loaded and decompressed again
This diff changes the reference counting scheme a bit. Previously, if the cache contained the block, this was accounted for in its refcount. After this change, the refcount is only used to track external references. There is a boolean flag which indicates whether or not the block is contained in the cache.
This diff also changes how LRU list is used. Previously, both hashtable and the LRU list contained all blocks. After this change, the LRU list contains blocks with the refcount==0, ie those which can be evicted from the cache.
Note that this change still allows for cache to grow beyond its capacity. This happens when all blocks are pinned (ie refcount>0). This is consistent with the current behavior. The cache's insert function never fails. I spent lots of time trying to make table_reader and other places work with the insert which might failed. It turned out to be pretty hard. It might really destabilize some customers, so finally, I decided against doing this.
table_cache_remove_scan_count_limit option will be unneeded after this change, but I will remove it in the following diff, if this one gets approved
Test Plan: Ran tests, made sure they pass
Reviewers: sdong, ljin
Differential Revision: https://reviews.facebook.net/D25503
2014-10-21 18:49:13 +00:00
|
|
|
assert(e->next != nullptr);
|
|
|
|
assert(e->prev != nullptr);
|
2011-03-18 22:37:00 +00:00
|
|
|
e->next->prev = e->prev;
|
|
|
|
e->prev->next = e->next;
|
Modifed the LRU cache eviction code so that it doesn't evict blocks which have exteranl references
Summary:
Currently, blocks which have more than one reference (ie referenced by something other than cache itself) are evicted from cache. This doesn't make much sense:
- blocks are still in RAM, so the RAM usage reported by the cache is incorrect
- if the same block is needed by another iterator, it will be loaded and decompressed again
This diff changes the reference counting scheme a bit. Previously, if the cache contained the block, this was accounted for in its refcount. After this change, the refcount is only used to track external references. There is a boolean flag which indicates whether or not the block is contained in the cache.
This diff also changes how LRU list is used. Previously, both hashtable and the LRU list contained all blocks. After this change, the LRU list contains blocks with the refcount==0, ie those which can be evicted from the cache.
Note that this change still allows for cache to grow beyond its capacity. This happens when all blocks are pinned (ie refcount>0). This is consistent with the current behavior. The cache's insert function never fails. I spent lots of time trying to make table_reader and other places work with the insert which might failed. It turned out to be pretty hard. It might really destabilize some customers, so finally, I decided against doing this.
table_cache_remove_scan_count_limit option will be unneeded after this change, but I will remove it in the following diff, if this one gets approved
Test Plan: Ran tests, made sure they pass
Reviewers: sdong, ljin
Differential Revision: https://reviews.facebook.net/D25503
2014-10-21 18:49:13 +00:00
|
|
|
e->prev = e->next = nullptr;
|
2015-06-18 20:56:31 +00:00
|
|
|
lru_usage_ -= e->charge;
|
2011-03-18 22:37:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void LRUCache::LRU_Append(LRUHandle* e) {
|
|
|
|
// Make "e" newest entry by inserting just before lru_
|
Modifed the LRU cache eviction code so that it doesn't evict blocks which have exteranl references
Summary:
Currently, blocks which have more than one reference (ie referenced by something other than cache itself) are evicted from cache. This doesn't make much sense:
- blocks are still in RAM, so the RAM usage reported by the cache is incorrect
- if the same block is needed by another iterator, it will be loaded and decompressed again
This diff changes the reference counting scheme a bit. Previously, if the cache contained the block, this was accounted for in its refcount. After this change, the refcount is only used to track external references. There is a boolean flag which indicates whether or not the block is contained in the cache.
This diff also changes how LRU list is used. Previously, both hashtable and the LRU list contained all blocks. After this change, the LRU list contains blocks with the refcount==0, ie those which can be evicted from the cache.
Note that this change still allows for cache to grow beyond its capacity. This happens when all blocks are pinned (ie refcount>0). This is consistent with the current behavior. The cache's insert function never fails. I spent lots of time trying to make table_reader and other places work with the insert which might failed. It turned out to be pretty hard. It might really destabilize some customers, so finally, I decided against doing this.
table_cache_remove_scan_count_limit option will be unneeded after this change, but I will remove it in the following diff, if this one gets approved
Test Plan: Ran tests, made sure they pass
Reviewers: sdong, ljin
Differential Revision: https://reviews.facebook.net/D25503
2014-10-21 18:49:13 +00:00
|
|
|
assert(e->next == nullptr);
|
|
|
|
assert(e->prev == nullptr);
|
2011-03-18 22:37:00 +00:00
|
|
|
e->next = &lru_;
|
|
|
|
e->prev = lru_.prev;
|
|
|
|
e->prev->next = e;
|
|
|
|
e->next->prev = e;
|
2015-06-18 20:56:31 +00:00
|
|
|
lru_usage_ += e->charge;
|
2011-03-18 22:37:00 +00:00
|
|
|
}
|
|
|
|
|
2016-05-24 06:35:23 +00:00
|
|
|
void LRUCache::EvictFromLRU(size_t charge, autovector<LRUHandle*>* deleted) {
|
2015-04-24 21:12:58 +00:00
|
|
|
while (usage_ + charge > capacity_ && lru_.next != &lru_) {
|
|
|
|
LRUHandle* old = lru_.next;
|
|
|
|
assert(old->in_cache);
|
|
|
|
assert(old->refs == 1); // LRU list contains elements which may be evicted
|
|
|
|
LRU_Remove(old);
|
|
|
|
table_.Remove(old->key(), old->hash);
|
|
|
|
old->in_cache = false;
|
|
|
|
Unref(old);
|
|
|
|
usage_ -= old->charge;
|
|
|
|
deleted->push_back(old);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void LRUCache::SetCapacity(size_t capacity) {
|
|
|
|
autovector<LRUHandle*> last_reference_list;
|
|
|
|
{
|
|
|
|
MutexLock l(&mutex_);
|
|
|
|
capacity_ = capacity;
|
|
|
|
EvictFromLRU(0, &last_reference_list);
|
|
|
|
}
|
|
|
|
// we free the entries here outside of mutex for
|
|
|
|
// performance reasons
|
|
|
|
for (auto entry : last_reference_list) {
|
|
|
|
entry->Free();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-11 01:35:19 +00:00
|
|
|
void LRUCache::SetStrictCapacityLimit(bool strict_capacity_limit) {
|
|
|
|
MutexLock l(&mutex_);
|
|
|
|
strict_capacity_limit_ = strict_capacity_limit;
|
|
|
|
}
|
|
|
|
|
2011-08-22 21:08:51 +00:00
|
|
|
Cache::Handle* LRUCache::Lookup(const Slice& key, uint32_t hash) {
|
2011-03-18 22:37:00 +00:00
|
|
|
MutexLock l(&mutex_);
|
2011-08-22 21:08:51 +00:00
|
|
|
LRUHandle* e = table_.Lookup(key, hash);
|
2013-03-01 02:04:58 +00:00
|
|
|
if (e != nullptr) {
|
Modifed the LRU cache eviction code so that it doesn't evict blocks which have exteranl references
Summary:
Currently, blocks which have more than one reference (ie referenced by something other than cache itself) are evicted from cache. This doesn't make much sense:
- blocks are still in RAM, so the RAM usage reported by the cache is incorrect
- if the same block is needed by another iterator, it will be loaded and decompressed again
This diff changes the reference counting scheme a bit. Previously, if the cache contained the block, this was accounted for in its refcount. After this change, the refcount is only used to track external references. There is a boolean flag which indicates whether or not the block is contained in the cache.
This diff also changes how LRU list is used. Previously, both hashtable and the LRU list contained all blocks. After this change, the LRU list contains blocks with the refcount==0, ie those which can be evicted from the cache.
Note that this change still allows for cache to grow beyond its capacity. This happens when all blocks are pinned (ie refcount>0). This is consistent with the current behavior. The cache's insert function never fails. I spent lots of time trying to make table_reader and other places work with the insert which might failed. It turned out to be pretty hard. It might really destabilize some customers, so finally, I decided against doing this.
table_cache_remove_scan_count_limit option will be unneeded after this change, but I will remove it in the following diff, if this one gets approved
Test Plan: Ran tests, made sure they pass
Reviewers: sdong, ljin
Differential Revision: https://reviews.facebook.net/D25503
2014-10-21 18:49:13 +00:00
|
|
|
assert(e->in_cache);
|
|
|
|
if (e->refs == 1) {
|
|
|
|
LRU_Remove(e);
|
|
|
|
}
|
2011-03-18 22:37:00 +00:00
|
|
|
e->refs++;
|
|
|
|
}
|
2011-08-22 21:08:51 +00:00
|
|
|
return reinterpret_cast<Cache::Handle*>(e);
|
2011-03-18 22:37:00 +00:00
|
|
|
}
|
|
|
|
|
2011-08-22 21:08:51 +00:00
|
|
|
void LRUCache::Release(Cache::Handle* handle) {
|
2016-03-11 01:35:19 +00:00
|
|
|
if (handle == nullptr) {
|
|
|
|
return;
|
|
|
|
}
|
2013-10-07 22:37:40 +00:00
|
|
|
LRUHandle* e = reinterpret_cast<LRUHandle*>(handle);
|
|
|
|
bool last_reference = false;
|
|
|
|
{
|
|
|
|
MutexLock l(&mutex_);
|
|
|
|
last_reference = Unref(e);
|
Modifed the LRU cache eviction code so that it doesn't evict blocks which have exteranl references
Summary:
Currently, blocks which have more than one reference (ie referenced by something other than cache itself) are evicted from cache. This doesn't make much sense:
- blocks are still in RAM, so the RAM usage reported by the cache is incorrect
- if the same block is needed by another iterator, it will be loaded and decompressed again
This diff changes the reference counting scheme a bit. Previously, if the cache contained the block, this was accounted for in its refcount. After this change, the refcount is only used to track external references. There is a boolean flag which indicates whether or not the block is contained in the cache.
This diff also changes how LRU list is used. Previously, both hashtable and the LRU list contained all blocks. After this change, the LRU list contains blocks with the refcount==0, ie those which can be evicted from the cache.
Note that this change still allows for cache to grow beyond its capacity. This happens when all blocks are pinned (ie refcount>0). This is consistent with the current behavior. The cache's insert function never fails. I spent lots of time trying to make table_reader and other places work with the insert which might failed. It turned out to be pretty hard. It might really destabilize some customers, so finally, I decided against doing this.
table_cache_remove_scan_count_limit option will be unneeded after this change, but I will remove it in the following diff, if this one gets approved
Test Plan: Ran tests, made sure they pass
Reviewers: sdong, ljin
Differential Revision: https://reviews.facebook.net/D25503
2014-10-21 18:49:13 +00:00
|
|
|
if (last_reference) {
|
|
|
|
usage_ -= e->charge;
|
|
|
|
}
|
|
|
|
if (e->refs == 1 && e->in_cache) {
|
|
|
|
// The item is still in cache, and nobody else holds a reference to it
|
|
|
|
if (usage_ > capacity_) {
|
|
|
|
// the cache is full
|
|
|
|
// The LRU list must be empty since the cache is full
|
|
|
|
assert(lru_.next == &lru_);
|
|
|
|
// take this opportunity and remove the item
|
|
|
|
table_.Remove(e->key(), e->hash);
|
|
|
|
e->in_cache = false;
|
|
|
|
Unref(e);
|
|
|
|
usage_ -= e->charge;
|
|
|
|
last_reference = true;
|
|
|
|
} else {
|
|
|
|
// put the item on the list to be potentially freed
|
|
|
|
LRU_Append(e);
|
|
|
|
}
|
|
|
|
}
|
2013-10-07 22:37:40 +00:00
|
|
|
}
|
Modifed the LRU cache eviction code so that it doesn't evict blocks which have exteranl references
Summary:
Currently, blocks which have more than one reference (ie referenced by something other than cache itself) are evicted from cache. This doesn't make much sense:
- blocks are still in RAM, so the RAM usage reported by the cache is incorrect
- if the same block is needed by another iterator, it will be loaded and decompressed again
This diff changes the reference counting scheme a bit. Previously, if the cache contained the block, this was accounted for in its refcount. After this change, the refcount is only used to track external references. There is a boolean flag which indicates whether or not the block is contained in the cache.
This diff also changes how LRU list is used. Previously, both hashtable and the LRU list contained all blocks. After this change, the LRU list contains blocks with the refcount==0, ie those which can be evicted from the cache.
Note that this change still allows for cache to grow beyond its capacity. This happens when all blocks are pinned (ie refcount>0). This is consistent with the current behavior. The cache's insert function never fails. I spent lots of time trying to make table_reader and other places work with the insert which might failed. It turned out to be pretty hard. It might really destabilize some customers, so finally, I decided against doing this.
table_cache_remove_scan_count_limit option will be unneeded after this change, but I will remove it in the following diff, if this one gets approved
Test Plan: Ran tests, made sure they pass
Reviewers: sdong, ljin
Differential Revision: https://reviews.facebook.net/D25503
2014-10-21 18:49:13 +00:00
|
|
|
|
|
|
|
// free outside of mutex
|
2013-10-07 22:37:40 +00:00
|
|
|
if (last_reference) {
|
Modifed the LRU cache eviction code so that it doesn't evict blocks which have exteranl references
Summary:
Currently, blocks which have more than one reference (ie referenced by something other than cache itself) are evicted from cache. This doesn't make much sense:
- blocks are still in RAM, so the RAM usage reported by the cache is incorrect
- if the same block is needed by another iterator, it will be loaded and decompressed again
This diff changes the reference counting scheme a bit. Previously, if the cache contained the block, this was accounted for in its refcount. After this change, the refcount is only used to track external references. There is a boolean flag which indicates whether or not the block is contained in the cache.
This diff also changes how LRU list is used. Previously, both hashtable and the LRU list contained all blocks. After this change, the LRU list contains blocks with the refcount==0, ie those which can be evicted from the cache.
Note that this change still allows for cache to grow beyond its capacity. This happens when all blocks are pinned (ie refcount>0). This is consistent with the current behavior. The cache's insert function never fails. I spent lots of time trying to make table_reader and other places work with the insert which might failed. It turned out to be pretty hard. It might really destabilize some customers, so finally, I decided against doing this.
table_cache_remove_scan_count_limit option will be unneeded after this change, but I will remove it in the following diff, if this one gets approved
Test Plan: Ran tests, made sure they pass
Reviewers: sdong, ljin
Differential Revision: https://reviews.facebook.net/D25503
2014-10-21 18:49:13 +00:00
|
|
|
e->Free();
|
2013-10-07 22:37:40 +00:00
|
|
|
}
|
2011-03-18 22:37:00 +00:00
|
|
|
}
|
|
|
|
|
2016-03-11 01:35:19 +00:00
|
|
|
Status LRUCache::Insert(const Slice& key, uint32_t hash, void* value,
|
|
|
|
size_t charge,
|
|
|
|
void (*deleter)(const Slice& key, void* value),
|
|
|
|
Cache::Handle** handle) {
|
Modifed the LRU cache eviction code so that it doesn't evict blocks which have exteranl references
Summary:
Currently, blocks which have more than one reference (ie referenced by something other than cache itself) are evicted from cache. This doesn't make much sense:
- blocks are still in RAM, so the RAM usage reported by the cache is incorrect
- if the same block is needed by another iterator, it will be loaded and decompressed again
This diff changes the reference counting scheme a bit. Previously, if the cache contained the block, this was accounted for in its refcount. After this change, the refcount is only used to track external references. There is a boolean flag which indicates whether or not the block is contained in the cache.
This diff also changes how LRU list is used. Previously, both hashtable and the LRU list contained all blocks. After this change, the LRU list contains blocks with the refcount==0, ie those which can be evicted from the cache.
Note that this change still allows for cache to grow beyond its capacity. This happens when all blocks are pinned (ie refcount>0). This is consistent with the current behavior. The cache's insert function never fails. I spent lots of time trying to make table_reader and other places work with the insert which might failed. It turned out to be pretty hard. It might really destabilize some customers, so finally, I decided against doing this.
table_cache_remove_scan_count_limit option will be unneeded after this change, but I will remove it in the following diff, if this one gets approved
Test Plan: Ran tests, made sure they pass
Reviewers: sdong, ljin
Differential Revision: https://reviews.facebook.net/D25503
2014-10-21 18:49:13 +00:00
|
|
|
// Allocate the memory here outside of the mutex
|
|
|
|
// If the cache is full, we'll have to release it
|
|
|
|
// It shouldn't happen very often though.
|
2015-12-04 23:12:07 +00:00
|
|
|
LRUHandle* e = reinterpret_cast<LRUHandle*>(
|
2016-05-24 06:35:23 +00:00
|
|
|
new char[sizeof(LRUHandle) - 1 + key.size()]);
|
2016-03-11 01:35:19 +00:00
|
|
|
Status s;
|
2014-01-02 19:26:57 +00:00
|
|
|
autovector<LRUHandle*> last_reference_list;
|
2013-12-11 16:33:29 +00:00
|
|
|
|
|
|
|
e->value = value;
|
|
|
|
e->deleter = deleter;
|
|
|
|
e->charge = charge;
|
|
|
|
e->key_length = key.size();
|
|
|
|
e->hash = hash;
|
2016-03-11 01:35:19 +00:00
|
|
|
e->refs = (handle == nullptr
|
|
|
|
? 1
|
|
|
|
: 2); // One from LRUCache, one for the returned handle
|
Modifed the LRU cache eviction code so that it doesn't evict blocks which have exteranl references
Summary:
Currently, blocks which have more than one reference (ie referenced by something other than cache itself) are evicted from cache. This doesn't make much sense:
- blocks are still in RAM, so the RAM usage reported by the cache is incorrect
- if the same block is needed by another iterator, it will be loaded and decompressed again
This diff changes the reference counting scheme a bit. Previously, if the cache contained the block, this was accounted for in its refcount. After this change, the refcount is only used to track external references. There is a boolean flag which indicates whether or not the block is contained in the cache.
This diff also changes how LRU list is used. Previously, both hashtable and the LRU list contained all blocks. After this change, the LRU list contains blocks with the refcount==0, ie those which can be evicted from the cache.
Note that this change still allows for cache to grow beyond its capacity. This happens when all blocks are pinned (ie refcount>0). This is consistent with the current behavior. The cache's insert function never fails. I spent lots of time trying to make table_reader and other places work with the insert which might failed. It turned out to be pretty hard. It might really destabilize some customers, so finally, I decided against doing this.
table_cache_remove_scan_count_limit option will be unneeded after this change, but I will remove it in the following diff, if this one gets approved
Test Plan: Ran tests, made sure they pass
Reviewers: sdong, ljin
Differential Revision: https://reviews.facebook.net/D25503
2014-10-21 18:49:13 +00:00
|
|
|
e->next = e->prev = nullptr;
|
|
|
|
e->in_cache = true;
|
2013-12-11 16:33:29 +00:00
|
|
|
memcpy(e->key_data, key.data(), key.size());
|
2013-10-07 22:37:40 +00:00
|
|
|
|
|
|
|
{
|
|
|
|
MutexLock l(&mutex_);
|
|
|
|
|
2013-10-10 00:04:40 +00:00
|
|
|
// Free the space following strict LRU policy until enough space
|
Modifed the LRU cache eviction code so that it doesn't evict blocks which have exteranl references
Summary:
Currently, blocks which have more than one reference (ie referenced by something other than cache itself) are evicted from cache. This doesn't make much sense:
- blocks are still in RAM, so the RAM usage reported by the cache is incorrect
- if the same block is needed by another iterator, it will be loaded and decompressed again
This diff changes the reference counting scheme a bit. Previously, if the cache contained the block, this was accounted for in its refcount. After this change, the refcount is only used to track external references. There is a boolean flag which indicates whether or not the block is contained in the cache.
This diff also changes how LRU list is used. Previously, both hashtable and the LRU list contained all blocks. After this change, the LRU list contains blocks with the refcount==0, ie those which can be evicted from the cache.
Note that this change still allows for cache to grow beyond its capacity. This happens when all blocks are pinned (ie refcount>0). This is consistent with the current behavior. The cache's insert function never fails. I spent lots of time trying to make table_reader and other places work with the insert which might failed. It turned out to be pretty hard. It might really destabilize some customers, so finally, I decided against doing this.
table_cache_remove_scan_count_limit option will be unneeded after this change, but I will remove it in the following diff, if this one gets approved
Test Plan: Ran tests, made sure they pass
Reviewers: sdong, ljin
Differential Revision: https://reviews.facebook.net/D25503
2014-10-21 18:49:13 +00:00
|
|
|
// is freed or the lru list is empty
|
2015-04-24 21:12:58 +00:00
|
|
|
EvictFromLRU(charge, &last_reference_list);
|
Modifed the LRU cache eviction code so that it doesn't evict blocks which have exteranl references
Summary:
Currently, blocks which have more than one reference (ie referenced by something other than cache itself) are evicted from cache. This doesn't make much sense:
- blocks are still in RAM, so the RAM usage reported by the cache is incorrect
- if the same block is needed by another iterator, it will be loaded and decompressed again
This diff changes the reference counting scheme a bit. Previously, if the cache contained the block, this was accounted for in its refcount. After this change, the refcount is only used to track external references. There is a boolean flag which indicates whether or not the block is contained in the cache.
This diff also changes how LRU list is used. Previously, both hashtable and the LRU list contained all blocks. After this change, the LRU list contains blocks with the refcount==0, ie those which can be evicted from the cache.
Note that this change still allows for cache to grow beyond its capacity. This happens when all blocks are pinned (ie refcount>0). This is consistent with the current behavior. The cache's insert function never fails. I spent lots of time trying to make table_reader and other places work with the insert which might failed. It turned out to be pretty hard. It might really destabilize some customers, so finally, I decided against doing this.
table_cache_remove_scan_count_limit option will be unneeded after this change, but I will remove it in the following diff, if this one gets approved
Test Plan: Ran tests, made sure they pass
Reviewers: sdong, ljin
Differential Revision: https://reviews.facebook.net/D25503
2014-10-21 18:49:13 +00:00
|
|
|
|
2016-03-11 01:35:19 +00:00
|
|
|
if (strict_capacity_limit_ && usage_ - lru_usage_ + charge > capacity_) {
|
|
|
|
if (handle == nullptr) {
|
|
|
|
last_reference_list.push_back(e);
|
|
|
|
} else {
|
|
|
|
delete[] reinterpret_cast<char*>(e);
|
|
|
|
*handle = nullptr;
|
|
|
|
}
|
|
|
|
s = Status::Incomplete("Insert failed due to LRU cache being full.");
|
|
|
|
} else {
|
|
|
|
// insert into the cache
|
|
|
|
// note that the cache might get larger than its capacity if not enough
|
|
|
|
// space was freed
|
|
|
|
LRUHandle* old = table_.Insert(e);
|
|
|
|
usage_ += e->charge;
|
|
|
|
if (old != nullptr) {
|
|
|
|
old->in_cache = false;
|
|
|
|
if (Unref(old)) {
|
|
|
|
usage_ -= old->charge;
|
|
|
|
// old is on LRU because it's in cache and its reference count
|
|
|
|
// was just 1 (Unref returned 0)
|
|
|
|
LRU_Remove(old);
|
|
|
|
last_reference_list.push_back(old);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (handle == nullptr) {
|
|
|
|
LRU_Append(e);
|
|
|
|
} else {
|
|
|
|
*handle = reinterpret_cast<Cache::Handle*>(e);
|
2013-10-07 22:37:40 +00:00
|
|
|
}
|
2016-03-11 01:35:19 +00:00
|
|
|
s = Status::OK();
|
2013-10-07 22:37:40 +00:00
|
|
|
}
|
2011-03-18 22:37:00 +00:00
|
|
|
}
|
|
|
|
|
2013-10-07 22:37:40 +00:00
|
|
|
// we free the entries here outside of mutex for
|
|
|
|
// performance reasons
|
|
|
|
for (auto entry : last_reference_list) {
|
Modifed the LRU cache eviction code so that it doesn't evict blocks which have exteranl references
Summary:
Currently, blocks which have more than one reference (ie referenced by something other than cache itself) are evicted from cache. This doesn't make much sense:
- blocks are still in RAM, so the RAM usage reported by the cache is incorrect
- if the same block is needed by another iterator, it will be loaded and decompressed again
This diff changes the reference counting scheme a bit. Previously, if the cache contained the block, this was accounted for in its refcount. After this change, the refcount is only used to track external references. There is a boolean flag which indicates whether or not the block is contained in the cache.
This diff also changes how LRU list is used. Previously, both hashtable and the LRU list contained all blocks. After this change, the LRU list contains blocks with the refcount==0, ie those which can be evicted from the cache.
Note that this change still allows for cache to grow beyond its capacity. This happens when all blocks are pinned (ie refcount>0). This is consistent with the current behavior. The cache's insert function never fails. I spent lots of time trying to make table_reader and other places work with the insert which might failed. It turned out to be pretty hard. It might really destabilize some customers, so finally, I decided against doing this.
table_cache_remove_scan_count_limit option will be unneeded after this change, but I will remove it in the following diff, if this one gets approved
Test Plan: Ran tests, made sure they pass
Reviewers: sdong, ljin
Differential Revision: https://reviews.facebook.net/D25503
2014-10-21 18:49:13 +00:00
|
|
|
entry->Free();
|
2011-03-18 22:37:00 +00:00
|
|
|
}
|
|
|
|
|
2016-03-11 01:35:19 +00:00
|
|
|
return s;
|
2011-03-18 22:37:00 +00:00
|
|
|
}
|
|
|
|
|
2011-08-22 21:08:51 +00:00
|
|
|
void LRUCache::Erase(const Slice& key, uint32_t hash) {
|
2013-10-07 22:37:40 +00:00
|
|
|
LRUHandle* e;
|
|
|
|
bool last_reference = false;
|
|
|
|
{
|
|
|
|
MutexLock l(&mutex_);
|
|
|
|
e = table_.Remove(key, hash);
|
|
|
|
if (e != nullptr) {
|
|
|
|
last_reference = Unref(e);
|
Modifed the LRU cache eviction code so that it doesn't evict blocks which have exteranl references
Summary:
Currently, blocks which have more than one reference (ie referenced by something other than cache itself) are evicted from cache. This doesn't make much sense:
- blocks are still in RAM, so the RAM usage reported by the cache is incorrect
- if the same block is needed by another iterator, it will be loaded and decompressed again
This diff changes the reference counting scheme a bit. Previously, if the cache contained the block, this was accounted for in its refcount. After this change, the refcount is only used to track external references. There is a boolean flag which indicates whether or not the block is contained in the cache.
This diff also changes how LRU list is used. Previously, both hashtable and the LRU list contained all blocks. After this change, the LRU list contains blocks with the refcount==0, ie those which can be evicted from the cache.
Note that this change still allows for cache to grow beyond its capacity. This happens when all blocks are pinned (ie refcount>0). This is consistent with the current behavior. The cache's insert function never fails. I spent lots of time trying to make table_reader and other places work with the insert which might failed. It turned out to be pretty hard. It might really destabilize some customers, so finally, I decided against doing this.
table_cache_remove_scan_count_limit option will be unneeded after this change, but I will remove it in the following diff, if this one gets approved
Test Plan: Ran tests, made sure they pass
Reviewers: sdong, ljin
Differential Revision: https://reviews.facebook.net/D25503
2014-10-21 18:49:13 +00:00
|
|
|
if (last_reference) {
|
|
|
|
usage_ -= e->charge;
|
|
|
|
}
|
|
|
|
if (last_reference && e->in_cache) {
|
|
|
|
LRU_Remove(e);
|
|
|
|
}
|
|
|
|
e->in_cache = false;
|
2013-10-07 22:37:40 +00:00
|
|
|
}
|
|
|
|
}
|
Modifed the LRU cache eviction code so that it doesn't evict blocks which have exteranl references
Summary:
Currently, blocks which have more than one reference (ie referenced by something other than cache itself) are evicted from cache. This doesn't make much sense:
- blocks are still in RAM, so the RAM usage reported by the cache is incorrect
- if the same block is needed by another iterator, it will be loaded and decompressed again
This diff changes the reference counting scheme a bit. Previously, if the cache contained the block, this was accounted for in its refcount. After this change, the refcount is only used to track external references. There is a boolean flag which indicates whether or not the block is contained in the cache.
This diff also changes how LRU list is used. Previously, both hashtable and the LRU list contained all blocks. After this change, the LRU list contains blocks with the refcount==0, ie those which can be evicted from the cache.
Note that this change still allows for cache to grow beyond its capacity. This happens when all blocks are pinned (ie refcount>0). This is consistent with the current behavior. The cache's insert function never fails. I spent lots of time trying to make table_reader and other places work with the insert which might failed. It turned out to be pretty hard. It might really destabilize some customers, so finally, I decided against doing this.
table_cache_remove_scan_count_limit option will be unneeded after this change, but I will remove it in the following diff, if this one gets approved
Test Plan: Ran tests, made sure they pass
Reviewers: sdong, ljin
Differential Revision: https://reviews.facebook.net/D25503
2014-10-21 18:49:13 +00:00
|
|
|
|
2013-10-07 22:37:40 +00:00
|
|
|
// mutex not held here
|
|
|
|
// last_reference will only be true if e != nullptr
|
|
|
|
if (last_reference) {
|
Modifed the LRU cache eviction code so that it doesn't evict blocks which have exteranl references
Summary:
Currently, blocks which have more than one reference (ie referenced by something other than cache itself) are evicted from cache. This doesn't make much sense:
- blocks are still in RAM, so the RAM usage reported by the cache is incorrect
- if the same block is needed by another iterator, it will be loaded and decompressed again
This diff changes the reference counting scheme a bit. Previously, if the cache contained the block, this was accounted for in its refcount. After this change, the refcount is only used to track external references. There is a boolean flag which indicates whether or not the block is contained in the cache.
This diff also changes how LRU list is used. Previously, both hashtable and the LRU list contained all blocks. After this change, the LRU list contains blocks with the refcount==0, ie those which can be evicted from the cache.
Note that this change still allows for cache to grow beyond its capacity. This happens when all blocks are pinned (ie refcount>0). This is consistent with the current behavior. The cache's insert function never fails. I spent lots of time trying to make table_reader and other places work with the insert which might failed. It turned out to be pretty hard. It might really destabilize some customers, so finally, I decided against doing this.
table_cache_remove_scan_count_limit option will be unneeded after this change, but I will remove it in the following diff, if this one gets approved
Test Plan: Ran tests, made sure they pass
Reviewers: sdong, ljin
Differential Revision: https://reviews.facebook.net/D25503
2014-10-21 18:49:13 +00:00
|
|
|
e->Free();
|
2011-03-18 22:37:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-07 20:51:47 +00:00
|
|
|
static int kNumShardBits = 6; // default values, can be overridden
|
2011-08-22 21:08:51 +00:00
|
|
|
|
|
|
|
class ShardedLRUCache : public Cache {
|
|
|
|
private:
|
2013-12-11 00:21:49 +00:00
|
|
|
LRUCache* shards_;
|
2011-08-22 21:08:51 +00:00
|
|
|
port::Mutex id_mutex_;
|
2015-04-24 21:12:58 +00:00
|
|
|
port::Mutex capacity_mutex_;
|
2011-08-22 21:08:51 +00:00
|
|
|
uint64_t last_id_;
|
2013-12-11 00:21:49 +00:00
|
|
|
int num_shard_bits_;
|
2012-09-30 01:02:02 +00:00
|
|
|
size_t capacity_;
|
2016-03-11 01:35:19 +00:00
|
|
|
bool strict_capacity_limit_;
|
2011-08-22 21:08:51 +00:00
|
|
|
|
|
|
|
static inline uint32_t HashSlice(const Slice& s) {
|
|
|
|
return Hash(s.data(), s.size(), 0);
|
|
|
|
}
|
|
|
|
|
2012-08-29 16:47:53 +00:00
|
|
|
uint32_t Shard(uint32_t hash) {
|
2013-04-04 01:53:42 +00:00
|
|
|
// Note, hash >> 32 yields hash in gcc, not the zero we expect!
|
2013-12-11 00:21:49 +00:00
|
|
|
return (num_shard_bits_ > 0) ? (hash >> (32 - num_shard_bits_)) : 0;
|
2011-08-22 21:08:51 +00:00
|
|
|
}
|
|
|
|
|
2015-03-17 22:04:37 +00:00
|
|
|
public:
|
2016-03-11 01:35:19 +00:00
|
|
|
ShardedLRUCache(size_t capacity, int num_shard_bits,
|
|
|
|
bool strict_capacity_limit)
|
|
|
|
: last_id_(0),
|
|
|
|
num_shard_bits_(num_shard_bits),
|
|
|
|
capacity_(capacity),
|
|
|
|
strict_capacity_limit_(strict_capacity_limit) {
|
2013-12-11 00:21:49 +00:00
|
|
|
int num_shards = 1 << num_shard_bits_;
|
|
|
|
shards_ = new LRUCache[num_shards];
|
|
|
|
const size_t per_shard = (capacity + (num_shards - 1)) / num_shards;
|
|
|
|
for (int s = 0; s < num_shards; s++) {
|
|
|
|
shards_[s].SetCapacity(per_shard);
|
2016-03-11 01:35:19 +00:00
|
|
|
shards_[s].SetStrictCapacityLimit(strict_capacity_limit);
|
2011-08-22 21:08:51 +00:00
|
|
|
}
|
|
|
|
}
|
2016-05-24 06:35:23 +00:00
|
|
|
virtual ~ShardedLRUCache() { delete[] shards_; }
|
2015-04-24 21:45:12 +00:00
|
|
|
virtual void SetCapacity(size_t capacity) override {
|
2015-04-24 21:12:58 +00:00
|
|
|
int num_shards = 1 << num_shard_bits_;
|
|
|
|
const size_t per_shard = (capacity + (num_shards - 1)) / num_shards;
|
|
|
|
MutexLock l(&capacity_mutex_);
|
|
|
|
for (int s = 0; s < num_shards; s++) {
|
|
|
|
shards_[s].SetCapacity(per_shard);
|
|
|
|
}
|
|
|
|
capacity_ = capacity;
|
|
|
|
}
|
2016-03-11 01:35:19 +00:00
|
|
|
virtual void SetStrictCapacityLimit(bool strict_capacity_limit) override {
|
|
|
|
int num_shards = 1 << num_shard_bits_;
|
|
|
|
for (int s = 0; s < num_shards; s++) {
|
|
|
|
shards_[s].SetStrictCapacityLimit(strict_capacity_limit);
|
|
|
|
}
|
|
|
|
strict_capacity_limit_ = strict_capacity_limit;
|
|
|
|
}
|
|
|
|
virtual Status Insert(const Slice& key, void* value, size_t charge,
|
|
|
|
void (*deleter)(const Slice& key, void* value),
|
|
|
|
Handle** handle) override {
|
2011-08-22 21:08:51 +00:00
|
|
|
const uint32_t hash = HashSlice(key);
|
2016-03-11 01:35:19 +00:00
|
|
|
return shards_[Shard(hash)].Insert(key, hash, value, charge, deleter,
|
|
|
|
handle);
|
2011-08-22 21:08:51 +00:00
|
|
|
}
|
2015-02-26 19:28:41 +00:00
|
|
|
virtual Handle* Lookup(const Slice& key) override {
|
2011-08-22 21:08:51 +00:00
|
|
|
const uint32_t hash = HashSlice(key);
|
2013-12-11 00:21:49 +00:00
|
|
|
return shards_[Shard(hash)].Lookup(key, hash);
|
2011-08-22 21:08:51 +00:00
|
|
|
}
|
2015-02-26 19:28:41 +00:00
|
|
|
virtual void Release(Handle* handle) override {
|
2011-08-22 21:08:51 +00:00
|
|
|
LRUHandle* h = reinterpret_cast<LRUHandle*>(handle);
|
2013-12-11 00:21:49 +00:00
|
|
|
shards_[Shard(h->hash)].Release(handle);
|
2011-08-22 21:08:51 +00:00
|
|
|
}
|
2015-02-26 19:28:41 +00:00
|
|
|
virtual void Erase(const Slice& key) override {
|
2011-08-22 21:08:51 +00:00
|
|
|
const uint32_t hash = HashSlice(key);
|
2013-12-11 00:21:49 +00:00
|
|
|
shards_[Shard(hash)].Erase(key, hash);
|
2011-08-22 21:08:51 +00:00
|
|
|
}
|
2015-02-26 19:28:41 +00:00
|
|
|
virtual void* Value(Handle* handle) override {
|
2011-08-22 21:08:51 +00:00
|
|
|
return reinterpret_cast<LRUHandle*>(handle)->value;
|
|
|
|
}
|
2015-02-26 19:28:41 +00:00
|
|
|
virtual uint64_t NewId() override {
|
2011-08-22 21:08:51 +00:00
|
|
|
MutexLock l(&id_mutex_);
|
|
|
|
return ++(last_id_);
|
|
|
|
}
|
2015-02-26 19:28:41 +00:00
|
|
|
virtual size_t GetCapacity() const override { return capacity_; }
|
2014-01-28 18:35:48 +00:00
|
|
|
|
2016-03-11 01:35:19 +00:00
|
|
|
virtual bool HasStrictCapacityLimit() const override {
|
|
|
|
return strict_capacity_limit_;
|
|
|
|
}
|
|
|
|
|
2015-02-26 19:28:41 +00:00
|
|
|
virtual size_t GetUsage() const override {
|
2013-12-11 00:21:49 +00:00
|
|
|
// We will not lock the cache when getting the usage from shards.
|
|
|
|
int num_shards = 1 << num_shard_bits_;
|
|
|
|
size_t usage = 0;
|
|
|
|
for (int s = 0; s < num_shards; s++) {
|
|
|
|
usage += shards_[s].GetUsage();
|
|
|
|
}
|
|
|
|
return usage;
|
|
|
|
}
|
2015-10-07 22:17:20 +00:00
|
|
|
|
|
|
|
virtual size_t GetUsage(Handle* handle) const override {
|
|
|
|
return reinterpret_cast<LRUHandle*>(handle)->charge;
|
|
|
|
}
|
|
|
|
|
2015-06-18 20:56:31 +00:00
|
|
|
virtual size_t GetPinnedUsage() const override {
|
|
|
|
// We will not lock the cache when getting the usage from shards.
|
|
|
|
int num_shards = 1 << num_shard_bits_;
|
|
|
|
size_t usage = 0;
|
|
|
|
for (int s = 0; s < num_shards; s++) {
|
|
|
|
usage += shards_[s].GetPinnedUsage();
|
|
|
|
}
|
|
|
|
return usage;
|
|
|
|
}
|
2014-01-28 18:35:48 +00:00
|
|
|
|
2015-02-26 19:28:41 +00:00
|
|
|
virtual void DisownData() override { shards_ = nullptr; }
|
2014-05-02 20:24:04 +00:00
|
|
|
|
|
|
|
virtual void ApplyToAllCacheEntries(void (*callback)(void*, size_t),
|
|
|
|
bool thread_safe) override {
|
|
|
|
int num_shards = 1 << num_shard_bits_;
|
|
|
|
for (int s = 0; s < num_shards; s++) {
|
|
|
|
shards_[s].ApplyToAllCacheEntries(callback, thread_safe);
|
|
|
|
}
|
|
|
|
}
|
Adding pin_l0_filter_and_index_blocks_in_cache feature and related fixes.
Summary:
When a block based table file is opened, if prefetch_index_and_filter is true, it will prefetch the index and filter blocks, putting them into the block cache.
What this feature adds: when a L0 block based table file is opened, if pin_l0_filter_and_index_blocks_in_cache is true in the options (and prefetch_index_and_filter is true), then the filter and index blocks aren't released back to the block cache at the end of BlockBasedTableReader::Open(). Instead the table reader takes ownership of them, hence pinning them, ie. the LRU cache will never push them out. Meanwhile in the table reader, further accesses will not hit the block cache, thus avoiding lock contention.
Test Plan:
'export TEST_TMPDIR=/dev/shm/ && DISABLE_JEMALLOC=1 OPT=-g make all valgrind_check -j32' is OK.
I didn't run the Java tests, I don't have Java set up on my devserver.
Reviewers: sdong
Reviewed By: sdong
Subscribers: andrewkr, dhruba
Differential Revision: https://reviews.facebook.net/D56133
2016-04-01 17:42:39 +00:00
|
|
|
|
|
|
|
virtual void EraseUnRefEntries() override {
|
|
|
|
int num_shards = 1 << num_shard_bits_;
|
|
|
|
for (int s = 0; s < num_shards; s++) {
|
|
|
|
shards_[s].EraseUnRefEntries();
|
|
|
|
}
|
|
|
|
}
|
2011-08-22 21:08:51 +00:00
|
|
|
};
|
2011-03-18 22:37:00 +00:00
|
|
|
|
|
|
|
} // end anonymous namespace
|
|
|
|
|
2016-05-24 06:35:23 +00:00
|
|
|
std::shared_ptr<Cache> NewLRUCache(size_t capacity) {
|
2016-03-11 01:35:19 +00:00
|
|
|
return NewLRUCache(capacity, kNumShardBits, false);
|
2011-03-18 22:37:00 +00:00
|
|
|
}
|
|
|
|
|
2016-05-24 06:35:23 +00:00
|
|
|
std::shared_ptr<Cache> NewLRUCache(size_t capacity, int num_shard_bits) {
|
2016-03-11 01:35:19 +00:00
|
|
|
return NewLRUCache(capacity, num_shard_bits, false);
|
|
|
|
}
|
|
|
|
|
2016-05-24 06:35:23 +00:00
|
|
|
std::shared_ptr<Cache> NewLRUCache(size_t capacity, int num_shard_bits,
|
|
|
|
bool strict_capacity_limit) {
|
2013-12-11 00:21:49 +00:00
|
|
|
if (num_shard_bits >= 20) {
|
2013-03-01 02:04:58 +00:00
|
|
|
return nullptr; // the cache cannot be sharded into too many fine pieces
|
2012-08-29 16:47:53 +00:00
|
|
|
}
|
2016-03-11 01:35:19 +00:00
|
|
|
return std::make_shared<ShardedLRUCache>(capacity, num_shard_bits,
|
|
|
|
strict_capacity_limit);
|
2012-05-17 00:22:33 +00:00
|
|
|
}
|
|
|
|
|
2013-10-04 04:49:15 +00:00
|
|
|
} // namespace rocksdb
|