blob: 1f930f001df2abd6d474526f5998fd2ca39e2ba5 [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
#include "util/cache/cache.h"
#include "util/cache/cache-test.h"
#include <cstring>
#include <memory>
#include <string>
#include <utility>
#include <glog/logging.h>
#include <gtest/gtest.h>
#include "kudu/gutil/macros.h"
#include "kudu/gutil/strings/substitute.h"
#include "kudu/util/mem_tracker.h"
#include "kudu/util/slice.h"
DECLARE_double(lirs_unprotected_percentage);
DECLARE_double(lirs_tombstone_multiple);
namespace impala {
class LIRSCacheTest : public CacheBaseTest {
public:
LIRSCacheTest()
: CacheBaseTest(100) {
FLAGS_lirs_unprotected_percentage = 5.0;
FLAGS_lirs_tombstone_multiple = 2.0;
}
void SetUp() override {
SetupWithParameters(Cache::EvictionPolicy::LIRS, ShardingPolicy::SingleShard);
}
protected:
// This fills the cache with 100 elements (0-99) and verifies that there were no
// evictions. Entries are inserted into the protected space until it is full
// (95 spots, so 0-94), then they are inserted as unprotected (5 spots, so 95-99).
void FillCache() {
for (int i = 0; i < 100; ++i) {
ASSERT_TRUE(Insert(i, i));
}
ASSERT_EQ(evicted_keys_.size(), 0);
ASSERT_EQ(evicted_values_.size(), 0);
}
// This fills the cache and keeps adding elements until it reaches the limit on the
// number of tombstones. This will add 300 elements. 100 remain in the cache at the
// end along with 200 tombstones. This also clears the evicted_key_ and evicted_values_
// to provide a clean slate for tests.
void FillCacheToTombstoneLimit() {
for (int i = 0; i < 300; ++i) {
ASSERT_TRUE(Insert(i, i));
}
// Items 0-94 remain protected.
// Items 95-294 were evicted and are tombstones.
// Items 295-299 are unprotected.
for (int i = 0; i < 200; ++i) {
ASSERT_EQ(evicted_keys_[i], i+95);
ASSERT_EQ(evicted_values_[i], i+95);
}
evicted_keys_.clear();
evicted_values_.clear();
}
// This forces all entries to be evicted. It repeatedly references entirely new
// elements twice in a row, forcing them to become protected. The preexisting
// elements should be evicted starting with the unprotected in FIFO order followed by
// the protected in FIFO order.
void FlushCache() {
for (int i = 0; i < 100; ++i) {
// Insert as unprotected
ASSERT_TRUE(Insert(1000 + i, 1000 + i));
// Lookup moves to protected
ASSERT_EQ(Lookup(1000 + i), 1000 + i);
}
}
};
// This tests a very simple case: insert entries, then flush the cache and verify
// entries are evicted in the appropriate order.
TEST_F(LIRSCacheTest, BasicEvictionOrdering) {
FillCache();
FlushCache();
// There were 5 unprotected elements (95-99). They should be the first to be evicted.
for (int i = 0; i < 5; ++i) {
ASSERT_EQ(evicted_keys_[i], 95+i);
ASSERT_EQ(evicted_values_[i], 95+i);
}
// There were 95 protected elements (0-94). They should be evicted in order.
for (int i = 5; i < 100; ++i) {
ASSERT_EQ(evicted_keys_[i], i-5);
ASSERT_EQ(evicted_values_[i], i-5);
}
}
// Lookup operations can be tagged as NO_UPDATE, in which case, nothing should change
// priority. Verify that adding NO_UPDATE Lookups to the basic case does not change
// the eviction order because nothing moves.
TEST_F(LIRSCacheTest, LookupNoUpdate) {
FillCache();
// Do a NO_UPDATE lookup on an unprotected element (which otherwise would become
// protected)
Lookup(97, Cache::NO_UPDATE);
// Do a NO_UPDATE lookup on a protected element (which otherwise would move in the
// recency queue).
Lookup(55, Cache::NO_UPDATE);
FlushCache();
// There were 5 unprotected elements (95-99). They should be the first to be evicted.
for (int i = 0; i < 5; ++i) {
ASSERT_EQ(evicted_keys_[i], 95+i);
ASSERT_EQ(evicted_values_[i], 95+i);
}
// There were 95 protected elements (0-94). They should be evicted in order.
for (int i = 5; i < 100; ++i) {
ASSERT_EQ(evicted_keys_[i], i-5);
ASSERT_EQ(evicted_values_[i], i-5);
}
}
// Test that Allocate() rejects anything larger than the protected capacity (which is 95)
TEST_F(LIRSCacheTest, RejectLarge) {
// Insert() returns false if Allocate() fails.
ASSERT_FALSE(Insert(100, 100, 96));
ASSERT_EQ(evicted_keys_.size(), 0);
ASSERT_EQ(evicted_values_.size(), 0);
// Allocate() does not reject something of exactly the protected capacity (95)
ASSERT_TRUE(Insert(100, 100, 95));
ASSERT_EQ(evicted_keys_.size(), 0);
ASSERT_EQ(evicted_values_.size(), 0);
}
// This tests that inserting a single large element can evict all of the UNPROTECTED
// elements, along with itself.
TEST_F(LIRSCacheTest, LargeInsertUnprotectedEvict) {
FillCache();
ASSERT_FALSE(Insert(100, 100, 6));
// All 5 UNPROTECTED got evicted, along with the element being inserted.
ASSERT_EQ(evicted_keys_.size(), 6);
ASSERT_EQ(evicted_values_.size(), 6);
for (int i = 0; i < 6; ++i) {
ASSERT_EQ(evicted_keys_[i], 95+i);
ASSERT_EQ(evicted_values_[i], 95+i);
}
evicted_keys_.clear();
evicted_values_.clear();
FlushCache();
// Only the protected remain, and they are all in order
ASSERT_EQ(evicted_keys_.size(), 95);
ASSERT_EQ(evicted_values_.size(), 95);
for (int i = 0; i < 95; ++i) {
ASSERT_EQ(evicted_keys_[i], i);
ASSERT_EQ(evicted_values_[i], i);
}
}
// This tests that updating a single large element evicts all UNPROTECTED elements
TEST_F(LIRSCacheTest, LargeUpdateUnprotectedEvict) {
FillCache();
UpdateCharge(99, 6);
// All 5 UNPROTECTED got evicted, along with the element being updated.
ASSERT_EQ(evicted_keys_.size(), 5);
ASSERT_EQ(evicted_values_.size(), 5);
for (int i = 0; i < 5; ++i) {
ASSERT_EQ(evicted_keys_[i], 95+i);
ASSERT_EQ(evicted_values_[i], 95+i);
}
evicted_keys_.clear();
evicted_values_.clear();
FlushCache();
// Only the protected remain, and they are all in order
ASSERT_EQ(evicted_keys_.size(), 95);
ASSERT_EQ(evicted_values_.size(), 95);
for (int i = 0; i < 95; ++i) {
ASSERT_EQ(evicted_keys_[i], i);
ASSERT_EQ(evicted_values_[i], i);
}
}
// This tests that a PROTECTED element that is larger than the unprotected capacity
// will evict everything including itself when it transitions to UNPROTECTED.
TEST_F(LIRSCacheTest, LargeProtectedToUnprotectedEvict) {
// Insert PROTECTED element that is larger than the unprotected capacity
ASSERT_TRUE(Insert(0, 0, 95));
ASSERT_EQ(evicted_keys_.size(), 0);
ASSERT_EQ(evicted_values_.size(), 0);
// Insert 5 elements, which will be UNPROTECTED
for (int i = 0; i < 5; ++i) {
ASSERT_TRUE(Insert(100+i, 100+i));
}
ASSERT_EQ(evicted_keys_.size(), 0);
ASSERT_EQ(evicted_values_.size(), 0);
// Looking up an UNPROTECTED element will transition it to PROTECTED, evicting
// the very large PROTECTED element. That will evict all four of the UNPROTECTED
// elements along with itself.
ASSERT_EQ(Lookup(104), 104);
ASSERT_EQ(evicted_keys_.size(), 5);
ASSERT_EQ(evicted_values_.size(), 5);
// UNPROTECTED elements were evicted first
for (int i = 0; i < 4; ++i) {
ASSERT_EQ(evicted_keys_[i], 100+i);
ASSERT_EQ(evicted_values_[i], 100+i);
}
// Large formerly PROTECTED element is evicted last
ASSERT_EQ(evicted_keys_[4], 0);
ASSERT_EQ(evicted_values_[4], 0);
}
// Large unprotected entries can transition directly to being a TOMBSTONE on Insert().
// This test verifies that this TOMBSTONE entry (which is larger than the unprotected
// capacity) can be promoted to be PROTECTED if there is another Insert().
TEST_F(LIRSCacheTest, LargeTombstone) {
// One protected element
ASSERT_TRUE(Insert(0, 0, 95));
ASSERT_EQ(Lookup(0), 0);
ASSERT_EQ(evicted_keys_.size(), 0);
ASSERT_EQ(evicted_values_.size(), 0);
// Large unprotected element is immediately tombstone
ASSERT_FALSE(Insert(1, 1, 95));
ASSERT_EQ(evicted_keys_.size(), 1);
ASSERT_EQ(evicted_keys_[0], 1);
ASSERT_EQ(evicted_values_[0], 1);
ASSERT_EQ(Lookup(1), -1);
evicted_keys_.clear();
evicted_values_.clear();
// Insert of tombstone turns it protected, evicting the current protected key
ASSERT_TRUE(Insert(1, 1, 95));
ASSERT_EQ(evicted_keys_.size(), 1);
ASSERT_EQ(evicted_keys_[0], 0);
ASSERT_EQ(evicted_values_[0], 0);
ASSERT_EQ(Lookup(1), 1);
ASSERT_EQ(Lookup(0), -1);
}
// A client could hold a handle for an entry that gets evicted and becomes a TOMBSTONE
// entry. This test verifies that if they call UpdateCharge() on a TOMBSTONE entry,
// then nothing happens.
TEST_F(LIRSCacheTest, UpdateChargeTombstone) {
FillCache();
// Get a handle to an entry
auto handle(cache_->Lookup(EncodeInt(1), Cache::NO_UPDATE));
// Flush the cache
FlushCache();
// Verify our key has been evicted. It can't be found via Lookup().
ASSERT_EQ(Lookup(1, Cache::NO_UPDATE), -1);
evicted_keys_.clear();
evicted_values_.clear();
// Try to update the charge for our tombstone entry
cache_->UpdateCharge(handle, cache_->MaxCharge());
// The charge doesn't actually get updated
ASSERT_EQ(cache_->Charge(handle), 1);
// Nothing gets evicted.
ASSERT_EQ(evicted_keys_.size(), 0);
ASSERT_EQ(evicted_values_.size(), 0);
}
// This tests the behavior of insert when there is already an unprotected element with
// the same key. It should replace the existing value, but the new element should
// continue to be unprotected.
TEST_F(LIRSCacheTest, InsertExistingUnprotected) {
FillCache();
// Replace an unprotected key with a new value
Insert(95, 1095);
ASSERT_EQ(1, evicted_keys_.size());
ASSERT_EQ(evicted_keys_[0], 95);
ASSERT_EQ(evicted_values_[0], 95);
evicted_keys_.clear();
evicted_values_.clear();
FlushCache();
// The only thing we guarantee is that key 95 is still unprotected. None of the other
// unprotected elements should have moved around, but the ordering is not particularly
// important, so this only verifies that the values are still around.
for (int i = 0; i < 5; ++i) {
ASSERT_LT(evicted_keys_[i], 100);
ASSERT_GE(evicted_keys_[i], 95);
if (evicted_keys_[i] == 95) {
ASSERT_EQ(evicted_values_[i], 1095);
} else {
ASSERT_EQ(evicted_values_[i], evicted_keys_[i]);
}
}
// There were 95 protected elements (0-94). They are not impacted, and they are still
// evicted in order.
for (int i = 5; i < 100; ++i) {
ASSERT_EQ(evicted_keys_[i], i-5);
ASSERT_EQ(evicted_values_[i], i-5);
}
}
// This is the same as InsertExistingUnprotected, except that it is verifying that
// replacing an existing protected key will remain protected.
TEST_F(LIRSCacheTest, InsertExistingProtected) {
FillCache();
// Replace a protected key with a new value
Insert(25, 1025);
ASSERT_EQ(1, evicted_keys_.size());
ASSERT_EQ(evicted_keys_[0], 25);
ASSERT_EQ(evicted_values_[0], 25);
evicted_keys_.clear();
evicted_values_.clear();
FlushCache();
// There were 5 unprotected elements (95-99). They are not impacted by changes in
// the protected elements.
for (int i = 0; i < 5; ++i) {
ASSERT_EQ(evicted_keys_[i], 95+i);
ASSERT_EQ(evicted_values_[i], 95+i);
}
// The only thing we guarantee is that key 25 is still protected. None of the other
// protected elements moved around, but the exact ordering is not specified.
for (int i = 5; i < 100; ++i) {
ASSERT_LT(evicted_keys_[i], 95);
ASSERT_GE(evicted_keys_[i], 0);
if (evicted_keys_[i] == 25) {
ASSERT_EQ(evicted_values_[i], 1025);
} else {
ASSERT_EQ(evicted_values_[i], evicted_keys_[i]);
}
}
}
// This is the same as InsertExistingProtected, except that it is verifying that
// replacing an existing protected key that is the last entry on the recency
// list will trim the recency list.
TEST_F(LIRSCacheTest, InsertExistingProtectedNeedsTrim) {
FillCache();
// Lookup every protected value except #25
// This doesn't change which keys are protected vs unprotected, but
// it changes the order of the elements on the recency list.
// 25 will be the oldest element, then there will be all of the
// unprotected elements, then all the other protected elements.
for (int i = 0; i < 95; ++i) {
if (i != 25) {
ASSERT_EQ(Lookup(i), i);
}
}
// Replace this last protected key with a new value. When this entry is inserted,
// it will remove the old protected key. This makes the unprotected entries the
// last on the list, so they will be trimmed off of the recency list.
Insert(25, 1025);
ASSERT_EQ(1, evicted_keys_.size());
ASSERT_EQ(evicted_keys_[0], 25);
ASSERT_EQ(evicted_values_[0], 25);
evicted_keys_.clear();
evicted_values_.clear();
// If the recency list wasn't trimmed appropriately, then this would hit an assert.
FlushCache();
// There were 5 unprotected elements (95-99). They were not impacted by changes in
// the protected elements.
for (int i = 0; i < 5; ++i) {
ASSERT_EQ(evicted_keys_[i], 95+i);
ASSERT_EQ(evicted_values_[i], 95+i);
}
// The only thing we guarantee is that key 25 is still protected. None of the other
// protected elements moved around, but the exact ordering is not specified.
for (int i = 5; i < 100; ++i) {
ASSERT_LT(evicted_keys_[i], 95);
ASSERT_GE(evicted_keys_[i], 0);
if (evicted_keys_[i] == 25) {
ASSERT_EQ(evicted_values_[i], 1025);
} else {
ASSERT_EQ(evicted_values_[i], evicted_keys_[i]);
}
}
}
// This tests the behavior of UpdateCharge on an UNPROTECTED element. It should update
// the change, but the element should continue to be unprotected.
TEST_F(LIRSCacheTest, UpdateExistingUnprotected) {
FillCache();
// Increase the charge
UpdateCharge(96, 2);
ASSERT_EQ(1, evicted_keys_.size());
ASSERT_EQ(evicted_keys_[0], 95);
ASSERT_EQ(evicted_values_[0], 95);
evicted_keys_.clear();
evicted_values_.clear();
FlushCache();
// The only thing we guarantee is that key 96 is still unprotected. None of the other
// unprotected elements should have moved around, but the ordering is not particularly
// important, so this only verifies that the values are still around.
for (int i = 0; i < 4; ++i) {
ASSERT_LT(evicted_keys_[i], 100);
ASSERT_GE(evicted_keys_[i], 96);
}
// There were 95 protected elements (0-94). They are not impacted, and they are still
// evicted in order.
for (int i = 4; i < 99; ++i) {
ASSERT_EQ(evicted_keys_[i], i-4);
ASSERT_EQ(evicted_values_[i], i-4);
}
}
// This is the same as UpdateExistingUnprotected, except that it is verifying that
// updating the charge on a protected key remains protected, and evictions cascade.
TEST_F(LIRSCacheTest, UpdateExistingProtected) {
FillCache();
// Increase the charge on a protected key, pushing a protected key to unprotected and
// evicting an unprotected key.
UpdateCharge(25, 2);
ASSERT_EQ(1, evicted_keys_.size());
ASSERT_EQ(evicted_keys_[0], 95);
ASSERT_EQ(evicted_values_[0], 95);
evicted_keys_.clear();
evicted_values_.clear();
// Evict all unprotected keys.
ASSERT_FALSE(Insert(100, 100, 6));
ASSERT_EQ(6, evicted_keys_.size());
for (int i = 0; i < 4; ++i) {
ASSERT_EQ(evicted_keys_[i], 96+i);
ASSERT_EQ(evicted_values_[i], 96+i);
}
// The last evicted element before the Insert was protected.
ASSERT_EQ(evicted_keys_[4], 0);
ASSERT_EQ(evicted_values_[4], 0);
ASSERT_EQ(evicted_keys_[5], 100);
ASSERT_EQ(evicted_values_[5], 100);
evicted_keys_.clear();
evicted_values_.clear();
FlushCache();
// None of the other protected elements moved around, but the exact ordering is not
// specified.
for (int i = 0; i < 94; ++i) {
ASSERT_LT(evicted_keys_[i], 95);
ASSERT_GE(evicted_keys_[i], 0);
}
}
// This tests the behavior of lookup of an unprotected key that is more recent than
// the oldest protected key (i.e. it should be promoted to be protected).
TEST_F(LIRSCacheTest, UnprotectedToProtected) {
FillCache();
// If we lookup 95, it will move from unprotected to protected.
ASSERT_EQ(Lookup(95), 95);
ASSERT_EQ(0, evicted_keys_.size());
FlushCache();
// The 5 unprotected elements are 96, 97, 98, 99, 0 (pushed out of protected)
// There were 5 unprotected elements (95-99). They are not impacted by changes in
// the protected elements.
for (int i = 0; i < 4; ++i) {
ASSERT_EQ(evicted_keys_[i], 96+i);
ASSERT_EQ(evicted_values_[i], 96+i);
}
ASSERT_EQ(evicted_keys_[4], 0);
ASSERT_EQ(evicted_values_[4], 0);
// 0 was pushed out of protected, and 95 was added (at the tail of the recency queue).
// So, this is just one offset from the case in BasicEvictionOrdering.
for (int i = 5; i < 100; ++i) {
ASSERT_EQ(evicted_keys_[i], i-4);
ASSERT_EQ(evicted_values_[i], i-4);
}
}
// This tests the behavior of insert for a key that has a tombstone element in the
// cache. It should insert the new element as a protected element.
TEST_F(LIRSCacheTest, TombstoneToProtected) {
FillCache();
// Add one more element, which will evict element 95. It is now a tombstone.
Insert(100, 100);
ASSERT_EQ(evicted_values_[0], 95);
ASSERT_EQ(evicted_keys_[0], 95);
ASSERT_EQ(Lookup(95), -1);
// If we insert 95 again, it will become a protected element due to the tombstone.
Insert(95, 95);
ASSERT_EQ(evicted_keys_[1], 96);
ASSERT_EQ(evicted_values_[1], 96);
evicted_values_.clear();
evicted_keys_.clear();
FlushCache();
// The 5 unprotected elements are 97,98,99,100, 0 (pushed out of protected)
// There were 5 unprotected elements (95-99). They are not impacted by changes in
// the protected elements.
for (int i = 0; i < 4; ++i) {
ASSERT_EQ(evicted_keys_[i], 97+i);
ASSERT_EQ(evicted_values_[i], 97+i);
}
ASSERT_EQ(evicted_keys_[4], 0);
ASSERT_EQ(evicted_values_[4], 0);
// 0 was pushed out of protected, and 95 was added (at the tail of the recency queue).
// So, this is just one offset from the case in BasicEvictionOrdering.
for (int i = 5; i < 100; ++i) {
ASSERT_EQ(evicted_keys_[i], i-4);
ASSERT_EQ(evicted_values_[i], i-4);
}
}
// This tests the case where there is a lookup of an unprotected element that has been
// trimmed from the recency queue. In this case, the unprotected element remains
// unprotected.
TEST_F(LIRSCacheTest, UnprotectedToUnprotected) {
FillCache();
// If we lookup every element that is protected, then the unprotected elements
// will be trimmed from the recency queue. At that point, a lookup to the
// unprotected elements will not change them to be protected.
for (int i = 0; i < 95; ++i) {
ASSERT_EQ(Lookup(i), i);
}
ASSERT_EQ(0, evicted_keys_.size());
// The unprotected elements will remain in the unprotected list
for (int i = 95; i < 100; ++i) {
ASSERT_EQ(Lookup(i), i);
}
ASSERT_EQ(0, evicted_keys_.size());
FlushCache();
// All of this means that the results are the same as BasicEvictionOrdering
// There were 5 unprotected elements (95-99). They should be the first to be evicted.
for (int i = 0; i < 5; ++i) {
ASSERT_EQ(evicted_keys_[i], 95+i);
ASSERT_EQ(evicted_values_[i], 95+i);
}
// There were 95 protected elements (0-94). They should be evicted in order.
for (int i = 5; i < 100; ++i) {
ASSERT_EQ(evicted_keys_[i], i-5);
ASSERT_EQ(evicted_values_[i], i-5);
}
}
// This tests the edge case where there is exactly one unprotected element
// and that element is looked up and remains unprotected.
TEST_F(LIRSCacheTest, ExactlyOneUnprotectedToUnprotected) {
FillCache();
// If we lookup every element that is protected, then the unprotected elements
// will be trimmed from the recency queue. At that point, a lookup to the
// unprotected elements will not change them to be protected.
for (int i = 0; i < 95; ++i) {
ASSERT_EQ(Lookup(i), i);
}
ASSERT_EQ(0, evicted_keys_.size());
// There are 5 unprotected right now. Erase 4 of them (95-98) so that only one remains.
for (int i = 95; i < 99; ++i) {
Erase(i);
}
ASSERT_EQ(4, evicted_keys_.size());
// Lookup the only remaining element (#99). This is unprotected without being in
// the recency list, so it stays unprotected.
ASSERT_EQ(Lookup(99), 99);
ASSERT_EQ(4, evicted_keys_.size());
FlushCache();
// All of this means that the results are the same as BasicEvictionOrdering
// There were 5 unprotected elements (95-99). They should be the first to be evicted.
for (int i = 0; i < 5; ++i) {
ASSERT_EQ(evicted_keys_[i], 95+i);
ASSERT_EQ(evicted_values_[i], 95+i);
}
// There were 95 protected elements (0-94). They should be evicted in order.
for (int i = 5; i < 100; ++i) {
ASSERT_EQ(evicted_keys_[i], i-5);
ASSERT_EQ(evicted_values_[i], i-5);
}
}
// This tests that Erase works for both unprotected and protected elements.
TEST_F(LIRSCacheTest, Erase) {
FillCache();
// Erase a protected element
Erase(25);
ASSERT_EQ(evicted_keys_[0], 25);
ASSERT_EQ(evicted_values_[0], 25);
// Erase an unprotected element
Erase(95);
ASSERT_EQ(evicted_keys_[1], 95);
ASSERT_EQ(evicted_values_[1], 95);
evicted_keys_.clear();
evicted_values_.clear();
FlushCache();
// There were 5 unprotected elements (95-99). Element 95 was erased. Verify the
// remaining 4 are appropriate.
for (int i = 0; i < 4; ++i) {
ASSERT_EQ(evicted_keys_[i], 96+i);
ASSERT_EQ(evicted_values_[i], 96+i);
}
// There were 95 protected elements (0-94). Element 25 was erased. Verify the
// two chunks.
for (int i = 0; i < 25; ++i) {
ASSERT_EQ(evicted_keys_[i+4], i);
ASSERT_EQ(evicted_values_[i+4], i);
}
for (int i = 25; i < 94; ++i) {
ASSERT_EQ(evicted_keys_[i+4], i + 1);
ASSERT_EQ(evicted_values_[i+4], i + 1);
}
}
// This is the same as InsertExistingProtectedNeedsTrim, except that it is verifying
// that erasing the last protected key on the recency list will trim the recency
// list.
TEST_F(LIRSCacheTest, EraseNeedsTrim) {
FillCache();
// Lookup every protected value except #25
// This doesn't change which keys are protected vs unprotected, but
// it changes the order of the elements on the recency list.
// 25 will be the oldest element, then there will be all of the
// unprotected elements, then all the other protected elements.
for (int i = 0; i < 95; ++i) {
if (i != 25) {
ASSERT_EQ(Lookup(i), i);
}
}
// Replace this last protected key with a new value. When this entry is inserted,
// it will remove the old protected key. This makes the unprotected entries the
// last on the list, so they will be trimmed off of the recency list.
Erase(25);
ASSERT_EQ(1, evicted_keys_.size());
ASSERT_EQ(evicted_keys_[0], 25);
ASSERT_EQ(evicted_values_[0], 25);
evicted_keys_.clear();
evicted_values_.clear();
// If the recency list wasn't trimmed appropriately, then this would hit an assert.
FlushCache();
// There were 5 unprotected elements (95-99). They were not impacted by changes in
// the protected elements.
for (int i = 0; i < 5; ++i) {
ASSERT_EQ(evicted_keys_[i], 95+i);
ASSERT_EQ(evicted_values_[i], 95+i);
}
// The only thing we guarantee is that key 25 is gone. None of the other
// protected elements moved around, but the exact ordering is not specified.
for (int i = 5; i < 99; ++i) {
ASSERT_NE(evicted_keys_[i], 25);
ASSERT_LT(evicted_keys_[i], 95);
ASSERT_GE(evicted_keys_[i], 0);
ASSERT_EQ(evicted_values_[i], evicted_keys_[i]);
}
}
// This tests the enforcement of the tombstone limit. The lirs_tombstone_multiple is
// 2.0, so we expect a cache with 100 elements to maintain at most 200 tombstones.
TEST_F(LIRSCacheTest, TombstoneLimit1) {
// Fill the cache to the point where there are 100 normal elements and 200 tombstones.
FillCacheToTombstoneLimit();
// Now, add one final element
Insert(300, 300);
// Item 295 was evicted and became a tombstone
ASSERT_EQ(evicted_keys_[0], 295);
ASSERT_EQ(evicted_values_[0], 295);
ASSERT_EQ(evicted_keys_.size(), 1);
evicted_keys_.clear();
evicted_values_.clear();
// Item 95 was a tombstone, but we expect it to be removed due to the tombstone limit.
// When we insert it, it will be unprotected.
Insert(95, 95);
ASSERT_EQ(evicted_keys_.size(), 1);
ASSERT_EQ(evicted_keys_[0], 296);
ASSERT_EQ(evicted_values_[0], 296);
evicted_keys_.clear();
evicted_values_.clear();
FlushCache();
// The 5 unprotected elements are 297, 298, 299, 300, 95
vector<int> unprotected_elems = {297, 298, 299, 300, 95};
for (int i = 0; i < 5; ++i) {
ASSERT_EQ(evicted_keys_[i], unprotected_elems[i]);
ASSERT_EQ(evicted_values_[i], unprotected_elems[i]);
}
// The protected elements are unaffected, so this is the same as BasicEvictionOrdering
for (int i = 5; i < 100; ++i) {
ASSERT_EQ(evicted_keys_[i], i-5);
ASSERT_EQ(evicted_values_[i], i-5);
}
}
// This tests the enforcement of the tombstone limit. The lirs_tombstone_multiple is
// 2.0, so we expect a cache with 100 elements to maintain at most 200 tombstones.
TEST_F(LIRSCacheTest, TombstoneLimit2) {
// Fill the cache to the point where there are 100 normal elements and 200 tombstones.
FillCacheToTombstoneLimit();
// Now, add one final element
Insert(300, 300);
// Item 295 was evicted and became a tombstone
ASSERT_EQ(evicted_keys_[0], 295);
ASSERT_EQ(evicted_values_[0], 295);
ASSERT_EQ(evicted_keys_.size(), 1);
evicted_keys_.clear();
evicted_values_.clear();
// Item 95 was a tombstone, and it should have been removed due to the tombstone limit.
// Item 96 was not removed, so when we insert it, it will be protected.
Insert(96, 96);
ASSERT_EQ(evicted_keys_.size(), 1);
ASSERT_EQ(evicted_keys_[0], 296);
ASSERT_EQ(evicted_values_[0], 296);
evicted_keys_.clear();
evicted_values_.clear();
FlushCache();
// The 5 unprotected elements are 297, 298, 299, 300, 0 (pushed out of protected)
vector<int> unprotected_elems = {297, 298, 299, 300, 0};
for (int i = 0; i < 5; ++i) {
ASSERT_EQ(evicted_keys_[i], unprotected_elems[i]);
ASSERT_EQ(evicted_values_[i], unprotected_elems[i]);
}
// 96 was added a protected element, so items 5-99 are 1-94.
for (int i = 5; i < 99; ++i) {
ASSERT_EQ(evicted_keys_[i], i-4);
ASSERT_EQ(evicted_values_[i], i-4);
}
ASSERT_EQ(evicted_keys_[99], 96);
ASSERT_EQ(evicted_values_[99], 96);
}
// This tests the enforcement of the tombstone limit. This is a simple test that
// verifies we can free multiple tombstones at once.
TEST_F(LIRSCacheTest, TombstoneLimitFreeMultiple) {
// Fill the cache to the point where there are 100 normal elements and 200 tombstones.
FillCacheToTombstoneLimit();
// Inserting an unprotected element with a large size does two things. First, it
// evicts all the current unprotected and makes them tombstones. Second, the number
// of non-tombstones shrank, so this will free multiple tombstones.
// This test is primarily focused on not crashing.
ASSERT_TRUE(Insert(500, 500, 5));
ASSERT_EQ(evicted_keys_.size(), 5);
ASSERT_EQ(evicted_values_.size(), 5);
}
} // namespace impala