Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AsyncFileCached: switch from a random to an LRU cache eviction policy #1506

Merged
merged 7 commits into from
May 17, 2019
17 changes: 13 additions & 4 deletions fdbrpc/AsyncFileCached.actor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,15 @@ EvictablePage::~EvictablePage() {
else
aligned_free(data);
}
if (index > -1) {
pageCache->pages[index] = pageCache->pages.back();
pageCache->pages[index]->index = index;
pageCache->pages.pop_back();
if (EvictablePageCache::RANDOM == FLOW_KNOBS->CACHE_EVICTION_POLICY) {
if (index > -1) {
pageCache->pages[index] = pageCache->pages.back();
pageCache->pages[index]->index = index;
pageCache->pages.pop_back();
}
} else {
// remove it from the LRU
pageCache->lruPages.erase(EvictablePageCache::List::s_iterator_to(*this));
}
}

Expand Down Expand Up @@ -97,6 +102,8 @@ Future<Void> AsyncFileCached::read_write_impl( AsyncFileCached* self, void* data
if ( p == self->pages.end() ) {
AFCPage* page = new AFCPage( self, pageOffset );
p = self->pages.insert( std::make_pair(pageOffset, page) ).first;
} else {
self->pageCache->updateHit(p->second);
}

int bytesInPage = std::min(self->pageCache->pageSize - offsetInPage, remaining);
Expand Down Expand Up @@ -133,6 +140,8 @@ Future<Void> AsyncFileCached::readZeroCopy( void** data, int* length, int64_t of
if ( p == pages.end() ) {
AFCPage* page = new AFCPage( this, offset );
p = pages.insert( std::make_pair(offset, page) ).first;
} else {
p->second->pageCache->updateHit(p->second);
}

*data = p->second->data;
Expand Down
64 changes: 56 additions & 8 deletions fdbrpc/AsyncFileCached.actor.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,21 @@
#elif !defined(FLOW_ASYNCFILECACHED_ACTOR_H)
#define FLOW_ASYNCFILECACHED_ACTOR_H

#include <boost/intrusive/list.hpp>

#include "flow/flow.h"
#include "fdbrpc/IAsyncFile.h"
#include "flow/Knobs.h"
#include "flow/TDMetric.actor.h"
#include "flow/network.h"
#include "flow/actorcompiler.h" // This must be the last #include.

namespace bi = boost::intrusive;
struct EvictablePage {
void* data;
int index;
class Reference<struct EvictablePageCache> pageCache;
bi::list_member_hook<> member_hook;

virtual bool evict() = 0; // true if page was evicted, false if it isn't immediately evictable (but will be evicted regardless if possible)

Expand All @@ -46,30 +50,74 @@ struct EvictablePage {
};

struct EvictablePageCache : ReferenceCounted<EvictablePageCache> {
EvictablePageCache() : pageSize(0), maxPages(0) {}
using List = bi::list< EvictablePage, bi::member_hook< EvictablePage, bi::list_member_hook<>, &EvictablePage::member_hook>>;

EvictablePageCache() : pageSize(0), maxPages(0) {
cacheHits.init(LiteralStringRef("EvictablePageCache.CacheHits"));
cacheMisses.init(LiteralStringRef("EvictablePageCache.CacheMisses"));
cacheEvictions.init(LiteralStringRef("EvictablePageCache.CacheEviction"));
}

explicit EvictablePageCache(int pageSize, int64_t maxSize) : pageSize(pageSize), maxPages(maxSize / pageSize) {}

void allocate(EvictablePage* page) {
try_evict();
try_evict();
page->data = pageSize == 4096 ? FastAllocator<4096>::allocate() : aligned_alloc(4096,pageSize);
page->index = pages.size();
pages.push_back(page);
if (RANDOM == FLOW_KNOBS->CACHE_EVICTION_POLICY) {
page->index = pages.size();
pages.push_back(page);
} else {
lruPages.push_back(*page); // new page is considered the most recently used (placed at LRU tail)
}
cacheMisses++;
}

void updateHit(EvictablePage* page) {
if (RANDOM != FLOW_KNOBS->CACHE_EVICTION_POLICY) {
// on a hit, update page's location in the LRU so that it's most recent (tail)
lruPages.erase(List::s_iterator_to(*page));
lruPages.push_back(*page);
}
cacheHits++;
}

void try_evict() {
if (pages.size() >= (uint64_t)maxPages && !pages.empty()) {
for (int i = 0; i < FLOW_KNOBS->MAX_EVICT_ATTEMPTS; i++) { // If we don't manage to evict anything, just go ahead and exceed the cache limit
int toEvict = g_random->randomInt(0, pages.size());
if (pages[toEvict]->evict())
break;
if (RANDOM == FLOW_KNOBS->CACHE_EVICTION_POLICY) {
if (pages.size() >= (uint64_t)maxPages && !pages.empty()) {
for (int i = 0; i < FLOW_KNOBS->MAX_EVICT_ATTEMPTS; i++) { // If we don't manage to evict anything, just go ahead and exceed the cache limit
int toEvict = g_random->randomInt(0, pages.size());
if (pages[toEvict]->evict()) {
cacheEvictions++;
break;
}
}
}
} else {
// For now, LRU is the only other CACHE_EVICTION option
if (lruPages.size() >= (uint64_t)maxPages) {
int i = 0;
// try the least recently used pages first (starting at head of the LRU list)
for (List::iterator it = lruPages.begin();
it != lruPages.end() && i < FLOW_KNOBS->MAX_EVICT_ATTEMPTS;
++it, ++i) { // If we don't manage to evict anything, just go ahead and exceed the cache limit
if (it->evict()) {
cacheEvictions++;
break;
}
}
}
}
}

std::vector<EvictablePage*> pages;
List lruPages;
int pageSize;
int64_t maxPages;
Int64MetricHandle cacheHits;
Int64MetricHandle cacheMisses;
Int64MetricHandle cacheEvictions;
enum CacheEvictionType { RANDOM = 0, LRU = 1 };
};

struct OpenFileInfo : NonCopyable {
Expand Down
1 change: 1 addition & 0 deletions flow/Knobs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ FlowKnobs::FlowKnobs(bool randomize, bool isSimulated) {
init( BUGGIFY_SIM_PAGE_CACHE_4K, 1e6 );
init( BUGGIFY_SIM_PAGE_CACHE_64K, 1e6 );
init( MAX_EVICT_ATTEMPTS, 100 ); if( randomize && BUGGIFY ) MAX_EVICT_ATTEMPTS = 2;
init( CACHE_EVICTION_POLICY, 0 );
init( PAGE_CACHE_TRUNCATE_LOOKUP_FRACTION, 0.1 ); if( randomize && BUGGIFY ) PAGE_CACHE_TRUNCATE_LOOKUP_FRACTION = 0.0; else if( randomize && BUGGIFY ) PAGE_CACHE_TRUNCATE_LOOKUP_FRACTION = 1.0;

//AsyncFileKAIO
Expand Down
1 change: 1 addition & 0 deletions flow/Knobs.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ class FlowKnobs : public Knobs {
int64_t SIM_PAGE_CACHE_64K;
int64_t BUGGIFY_SIM_PAGE_CACHE_4K;
int64_t BUGGIFY_SIM_PAGE_CACHE_64K;
int CACHE_EVICTION_POLICY; // 0 RANDOM (default), 1 LRU
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We actually do support std::string knobs, and I think cache eviction policy is a good use for one. We could add other policies in the future and strings would be the most user-friendly way to specify them. A member in AsyncFileCached of type CacheEvictionType can then be initialized from the knob's string value.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good! I will change the knob to be a string, add a member (evictionType) and initialize it based on the knob's string value, and update the patch.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@satherton updated with the knob being a string.

int MAX_EVICT_ATTEMPTS;
double PAGE_CACHE_TRUNCATE_LOOKUP_FRACTION;
double TOO_MANY_CONNECTIONS_CLOSED_RESET_DELAY;
Expand Down