Skip to content

Commit fe9c125

Browse files
authored
Eviction for mutable cache. (#805)
If mutable cache reached max_disk_storage size, user will not be able to put new values. Removing least recently used items allows to use the cache further. Eviction process strarts after 90% threshold and goes till 85%, so all values can be removed in one batch and number of disk calls is minimized. Resolves: OLPEDGE-1592, OLPEDGE-1822 Signed-off-by: Kostiantyn Zvieriev <ext-kostiantyn.zvieriev@here.com>
1 parent 0354d97 commit fe9c125

File tree

5 files changed

+273
-35
lines changed

5 files changed

+273
-35
lines changed

olp-cpp-sdk-core/src/cache/DefaultCacheImpl.cpp

Lines changed: 71 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ namespace {
3030
constexpr auto kLogTag = "DefaultCache";
3131
constexpr auto kExpirySuffix = "::expiry";
3232
constexpr auto kMaxDiskSize = std::uint64_t(-1);
33+
constexpr auto kMinDiskUsedThreshold = 0.85f;
34+
constexpr auto kMaxDiskUsedThreshold = 0.9f;
3335

3436
// current epoch time contains 10 digits.
3537
constexpr auto kExpiryValueSize = 10;
@@ -73,14 +75,12 @@ void PurgeDiskItem(const std::string& key, olp::cache::DiskCache& disk_cache,
7375
removed_data_size += data_size;
7476
}
7577

76-
size_t StoreExpiry(const std::string& key, olp::cache::DiskCache& disk_cache,
78+
size_t StoreExpiry(const std::string& key, leveldb::WriteBatch& batch,
7779
time_t expiry) {
7880
auto expiry_key = CreateExpiryKey(key);
7981
auto time_str = std::to_string(
8082
expiry + olp::cache::InMemoryCache::DefaultTimeProvider()());
81-
if (!disk_cache.Put(expiry_key, time_str)) {
82-
return 0;
83-
}
83+
batch.Put(expiry_key, time_str);
8484

8585
return expiry_key.size() + time_str.size();
8686
}
@@ -261,9 +261,7 @@ bool DefaultCacheImpl::Remove(const std::string& key) {
261261

262262
if (mutable_cache_) {
263263
uint64_t removed_data_size = 0;
264-
if (!mutable_cache_->Remove(key, removed_data_size)) {
265-
return false;
266-
}
264+
PurgeDiskItem(key, *mutable_cache_, removed_data_size);
267265

268266
mutable_cache_data_size_ -= removed_data_size;
269267
}
@@ -301,9 +299,6 @@ void DefaultCacheImpl::InitializeLru() {
301299
settings_.eviction_policy == EvictionPolicy::kLeastRecentlyUsed) {
302300
mutable_cache_lru_ =
303301
std::make_unique<DiskLruCache>(settings_.max_disk_storage);
304-
}
305-
306-
if (mutable_cache_lru_) {
307302
OLP_SDK_LOG_INFO_F(kLogTag, "Initializing mutable lru cache.");
308303
}
309304

@@ -315,27 +310,18 @@ void DefaultCacheImpl::InitializeLru() {
315310
const auto& value = it->value();
316311
mutable_cache_data_size_ += key.size() + value.size();
317312

318-
if (mutable_cache_lru_) {
319-
// Skip expiry keys
320-
if (IsExpiryKey(key)) {
321-
continue;
322-
}
323-
313+
if (mutable_cache_lru_ && !IsExpiryKey(key)) {
324314
++count;
325315
mutable_cache_lru_->Insert(key, value.size());
326316
}
327317
}
328318

329-
if (mutable_cache_lru_) {
330-
const int64_t elapsed =
331-
std::chrono::duration_cast<std::chrono::milliseconds>(
332-
std::chrono::steady_clock::now() - start)
333-
.count();
334-
OLP_SDK_LOG_INFO_F(kLogTag,
335-
"Mutable lru cache initialized: items=%" PRIu32
336-
", time=%" PRId64 " ms",
337-
count, elapsed);
338-
}
319+
const int64_t elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
320+
std::chrono::steady_clock::now() - start)
321+
.count();
322+
OLP_SDK_LOG_INFO_F(
323+
kLogTag, "Cache initialized, items=%" PRIu32 ", time=%" PRId64 " ms",
324+
count, elapsed);
339325
}
340326

341327
void DefaultCacheImpl::RemoveKeyLru(const std::string& key) {
@@ -371,6 +357,53 @@ bool DefaultCacheImpl::PromoteKeyLru(const std::string& key) {
371357
return true;
372358
}
373359

360+
uint64_t DefaultCacheImpl::MaybeEvictData(leveldb::WriteBatch& batch) {
361+
if (!mutable_cache_ || !mutable_cache_lru_) {
362+
return 0;
363+
}
364+
365+
const auto max_size = kMaxDiskUsedThreshold * settings_.max_disk_storage;
366+
if (mutable_cache_data_size_ < max_size) {
367+
return 0;
368+
}
369+
370+
const auto start = std::chrono::steady_clock::now();
371+
int64_t evicted = 0u;
372+
auto count = 0u;
373+
const auto min_size = kMinDiskUsedThreshold * settings_.max_disk_storage;
374+
for (auto it = mutable_cache_lru_->rbegin();
375+
it != mutable_cache_lru_->rend() &&
376+
mutable_cache_data_size_ - evicted > min_size;) {
377+
const auto& key = it->key();
378+
const auto expiry_key = CreateExpiryKey(key);
379+
auto expiry_value = mutable_cache_->Get(expiry_key);
380+
if (expiry_value) {
381+
evicted += expiry_key.size() + kExpiryValueSize;
382+
batch.Delete(expiry_key);
383+
}
384+
evicted += key.size() + it->value();
385+
++count;
386+
387+
if (memory_cache_) {
388+
memory_cache_->Remove(it->key());
389+
}
390+
391+
batch.Delete(key);
392+
mutable_cache_lru_->Erase(it);
393+
it = mutable_cache_lru_->rbegin();
394+
}
395+
396+
int64_t elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
397+
std::chrono::steady_clock::now() - start)
398+
.count();
399+
OLP_SDK_LOG_INFO_F(kLogTag,
400+
"Evicted from mutable cache, items=%" PRId32
401+
", time=%" PRId64 " ms, size=%" PRIu64,
402+
count, elapsed, evicted);
403+
404+
return evicted;
405+
}
406+
374407
bool DefaultCacheImpl::PutMutableCache(const std::string& key,
375408
const leveldb::Slice& value,
376409
time_t expiry) {
@@ -387,20 +420,23 @@ bool DefaultCacheImpl::PutMutableCache(const std::string& key,
387420
return false;
388421
}
389422

390-
uint64_t data_size_change = 0u;
423+
uint64_t added_data_size = 0u;
424+
auto batch = std::make_unique<leveldb::WriteBatch>();
425+
batch->Put(key, value);
426+
added_data_size += key.size() + item_size;
427+
391428
if (IsExpiryValid(expiry)) {
392-
auto expiry_data_size = StoreExpiry(key, *mutable_cache_, expiry);
393-
if (expiry_data_size == 0) {
394-
return false;
395-
}
396-
data_size_change += expiry_data_size;
429+
added_data_size += StoreExpiry(key, *batch, expiry);
397430
}
398-
data_size_change += key.size() + item_size;
399431

400-
if (!mutable_cache_->Put(key, value)) {
432+
auto removed_data_size = MaybeEvictData(*batch);
433+
434+
auto result = mutable_cache_->ApplyBatch(std::move(batch));
435+
if (!result.IsSuccessful()) {
401436
return false;
402437
}
403-
mutable_cache_data_size_ += data_size_change;
438+
mutable_cache_data_size_ += added_data_size;
439+
mutable_cache_data_size_ -= removed_data_size;
404440

405441
if (mutable_cache_lru_) {
406442
const auto result = mutable_cache_lru_->InsertOrAssign(key, item_size);

olp-cpp-sdk-core/src/cache/DefaultCacheImpl.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ class DefaultCacheImpl {
8888
/// Returns true if key is found in the LRU cache, false - otherwise.
8989
bool PromoteKeyLru(const std::string& key);
9090

91+
/// Returns evicted data size.
92+
uint64_t MaybeEvictData(leveldb::WriteBatch& batch);
93+
9194
/// Puts data into the mutable cache
9295
bool PutMutableCache(const std::string& key, const leveldb::Slice& value,
9396
time_t expiry);

olp-cpp-sdk-core/tests/cache/DefaultCacheImplTest.cpp

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,24 @@ TEST(DefaultCacheImplTest, MutableCacheSize) {
399399
EXPECT_EQ(0u, cache.Size());
400400
}
401401

402+
{
403+
SCOPED_TRACE("Remove from cache with expiry");
404+
405+
DefaultCacheImplHelper cache(settings);
406+
cache.Open();
407+
cache.Clear();
408+
409+
cache.Put(key1, data_ptr, expiry);
410+
cache.Put(
411+
key2, data_string, [=]() { return data_string; }, expiry);
412+
413+
cache.Remove(key1);
414+
cache.Remove(key2);
415+
cache.Remove(invalid_key);
416+
417+
EXPECT_EQ(0u, cache.Size());
418+
}
419+
402420
{
403421
SCOPED_TRACE("RemoveWithPrefix");
404422

@@ -563,6 +581,72 @@ TEST(DefaultCacheImplTest, LruCacheEviction) {
563581
}
564582
cache.Clear();
565583
}
584+
585+
{
586+
SCOPED_TRACE("kLeastRecentlyUsed eviction");
587+
588+
const auto prefix{"somekey"};
589+
const auto data_size = 1024u;
590+
std::vector<unsigned char> binary_data(data_size);
591+
olp::cache::CacheSettings settings;
592+
settings.disk_path_mutable = cache_path;
593+
settings.eviction_policy = EvictionPolicy::kLeastRecentlyUsed;
594+
settings.max_disk_storage = 2u * 1024u * 1024u;
595+
DefaultCacheImplHelper cache(settings);
596+
597+
cache.Open();
598+
cache.Clear();
599+
600+
const auto promote_key = prefix + std::to_string(0);
601+
const auto evicted_key = prefix + std::to_string(1);
602+
cache.Put(promote_key,
603+
std::make_shared<std::vector<unsigned char>>(binary_data),
604+
(std::numeric_limits<time_t>::max)());
605+
606+
// overflow the mutable cache
607+
auto count = 0u;
608+
std::string key;
609+
const auto max_count = settings.max_disk_storage / data_size;
610+
for (; count < max_count; ++count) {
611+
key = prefix + std::to_string(count);
612+
const auto result = cache.Put(
613+
key, std::make_shared<std::vector<unsigned char>>(binary_data),
614+
(std::numeric_limits<time_t>::max)());
615+
616+
ASSERT_TRUE(result);
617+
618+
// promote the key so its not evicted.
619+
cache.Get(promote_key);
620+
621+
EXPECT_TRUE(cache.ContainsMutableCache(key));
622+
EXPECT_TRUE(cache.ContainsMemoryCache(key));
623+
EXPECT_TRUE(cache.ContainsLru(key));
624+
EXPECT_TRUE(cache.ContainsMutableCache(promote_key));
625+
EXPECT_TRUE(cache.ContainsMemoryCache(promote_key));
626+
EXPECT_TRUE(cache.ContainsLru(promote_key));
627+
}
628+
const auto promote_value = cache.Get(promote_key);
629+
const auto value = cache.Get(key);
630+
631+
// maximum is reached.
632+
ASSERT_TRUE(count == max_count);
633+
EXPECT_TRUE(cache.HasLruCache());
634+
EXPECT_TRUE(value.get() != nullptr);
635+
EXPECT_TRUE(promote_value.get() != nullptr);
636+
637+
EXPECT_TRUE(cache.ContainsMutableCache(promote_key));
638+
EXPECT_TRUE(cache.ContainsMemoryCache(promote_key));
639+
EXPECT_TRUE(cache.ContainsLru(promote_key));
640+
EXPECT_TRUE(cache.ContainsMutableCache(key));
641+
EXPECT_TRUE(cache.ContainsMemoryCache(key));
642+
EXPECT_TRUE(cache.ContainsLru(key));
643+
644+
// some items are removed, because eviction starts before the cache is full
645+
EXPECT_FALSE(cache.ContainsMutableCache(evicted_key));
646+
EXPECT_FALSE(cache.ContainsMemoryCache(evicted_key));
647+
EXPECT_FALSE(cache.ContainsLru(evicted_key));
648+
cache.Clear();
649+
}
566650
}
567651

568652
} // namespace

tests/common/matchers/NetworkUrlMatchers.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,18 @@ MATCHER_P(IsGetRequest, url, "") {
3434
url == arg.GetUrl() && (!arg.GetBody() || arg.GetBody()->empty());
3535
}
3636

37+
MATCHER_P(IsGetRequestPrefix, url, "") {
38+
if (olp::http::NetworkRequest::HttpVerb::GET != arg.GetVerb()) {
39+
return false;
40+
}
41+
42+
std::string url_string(url);
43+
auto res =
44+
std::mismatch(url_string.begin(), url_string.end(), arg.GetUrl().begin());
45+
46+
return (res.first == url_string.end());
47+
}
48+
3749
MATCHER_P(IsPutRequest, url, "") {
3850
return olp::http::NetworkRequest::HttpVerb::PUT == arg.GetVerb() &&
3951
url == arg.GetUrl();

0 commit comments

Comments
 (0)