blob: e7fef3d6df5d8d706514527c0023aa049decc9c9 [file] [log] [blame]
/**
* 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 <algorithm>
#include <array>
#include <chrono>
#include <memory>
#include <thread> // NOLINT(build/c++11)
#include <unordered_set>
#include "gtest/gtest.h"
#include "storage/EvictionPolicy.hpp"
#include "storage/StorageBlockInfo.hpp"
namespace quickstep {
class EvictionPolicyTest : public ::testing::Test {
protected:
enum class Config : unsigned char {
kUniformRandom = 0,
kLRU1,
kLRU2,
kLRU3,
kEvictAnyBlock,
kEnd
};
Config current_config_ = Config::kUniformRandom;
std::unique_ptr<EvictionPolicy> eviction_policy_;
void initEvictionPolicy() {
switch (current_config_) {
case Config::kUniformRandom:
eviction_policy_.reset(new UniformRandomEvictionPolicy);
break;
case Config::kLRU1:
eviction_policy_.reset(LRUKEvictionPolicyFactory::ConstructLRUKEvictionPolicy(1,
std::chrono::seconds(100)));
break;
case Config::kLRU2:
eviction_policy_.reset(LRUKEvictionPolicyFactory::ConstructLRUKEvictionPolicy(2,
std::chrono::seconds(100)));
break;
case Config::kLRU3:
eviction_policy_.reset(LRUKEvictionPolicyFactory::ConstructLRUKEvictionPolicy(3,
std::chrono::seconds(100)));
break;
case Config::kEvictAnyBlock:
eviction_policy_.reset(new EvictAnyBlockEvictionPolicy);
break;
case Config::kEnd:
ASSERT_TRUE(false);
break;
}
}
public:
EvictionPolicyTest() : ::testing::Test() {
initEvictionPolicy();
}
protected:
bool nextConfig() {
current_config_ = static_cast<Config>(static_cast<unsigned char>(current_config_) + 1);
if (current_config_ == Config::kEnd) {
current_config_ = Config::kUniformRandom;
return false;
}
initEvictionPolicy();
return true;
}
};
TEST_F(EvictionPolicyTest, DoesNotEvictReferencedBlocks) {
EvictionPolicy::Status status;
block_id block;
do {
// Simulate some access patterns
std::unordered_set<block_id> blocks;
for (int i = 0; i < 1000; ++i) {
blocks.insert(BlockIdUtil::GetBlockId(0 /* domain */, i));
}
status = eviction_policy_->chooseBlockToEvict(&block);
ASSERT_EQ(EvictionPolicy::Status::kBlockNotFound, status);
// Make all blocks known to eviction_policy_
for (int i = 0; i < 1000; ++i) {
eviction_policy_->blockReferenced(BlockIdUtil::GetBlockId(0 /* domain */, i));
eviction_policy_->blockUnreferenced(BlockIdUtil::GetBlockId(0 /* domain */, i));
}
// Reference every 10th one
for (int i = 0; i < 1000; i += 10) {
eviction_policy_->blockReferenced(BlockIdUtil::GetBlockId(0 /* domain */, i));
}
// Make sure they don't get chosen for eviction
for (int i = 0; i < 900; ++i) {
status = eviction_policy_->chooseBlockToEvict(&block);
// Expect that status is okay and that it didn't evict a referenced block
ASSERT_EQ(EvictionPolicy::Status::kOk, status);
ASSERT_NE(0u, BlockIdUtil::Counter(block) % 10);
blocks.erase(block);
}
// Make sure that we now always get an error. We just try 10 times.
for (int i = 0; i < 10; ++i) {
status = eviction_policy_->chooseBlockToEvict(&block);
ASSERT_EQ(EvictionPolicy::Status::kBlockNotFound, status);
}
// Now unreference and evict the rest of the blocks
for (int i = 0; i < 1000; i += 10) {
eviction_policy_->blockUnreferenced(BlockIdUtil::GetBlockId(0 /* domain */, i));
status = eviction_policy_->chooseBlockToEvict(&block);
ASSERT_EQ(EvictionPolicy::Status::kOk, status);
blocks.erase(block);
ASSERT_EQ(static_cast<block_id_counter>(i), BlockIdUtil::Counter(block));
}
// Finally, ensure that EvictionPolicy doesn't believe in any non-existent blocks
for (int i = 0; i < 10; ++i) {
status = eviction_policy_->chooseBlockToEvict(&block);
ASSERT_EQ(EvictionPolicy::Status::kBlockNotFound, status);
}
} while (nextConfig());
}
TEST(LRUKEvictionPolicyTest, LRU2IsCorrect) {
std::array<std::chrono::milliseconds, 3> corrl_reference_periods = {{
std::chrono::milliseconds(0),
std::chrono::milliseconds(100),
std::chrono::milliseconds(300)
}};
for (std::chrono::milliseconds corrl_reference_period : corrl_reference_periods) {
// Create an array of block IDs from [1, 100] and shuffle it.
std::array<block_id, 100> blocks;
int n = 1;
std::generate(blocks.begin(), blocks.end(), [&n]{ return BlockIdUtil::GetBlockId(0 /* domain */, n++); });
std::random_shuffle(blocks.begin(), blocks.end());
// Create an LRU-2 EvictionPolicy
std::unique_ptr<EvictionPolicy> eviction_policy(
LRUKEvictionPolicyFactory::ConstructLRUKEvictionPolicy(
2,
corrl_reference_period));
// Reference all the blocks
for (const block_id block : blocks) {
eviction_policy->blockReferenced(block);
eviction_policy->blockUnreferenced(block);
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
// Shuffle them again
std::array<block_id, 100> shuffled_blocks = blocks;
std::random_shuffle(shuffled_blocks.begin(), shuffled_blocks.end());
// Ensure that the next set of references is at least corrl_reference_period after the first set.
std::this_thread::sleep_for(corrl_reference_period);
// Reference each block again in a random order
for (const block_id block : shuffled_blocks) {
eviction_policy->blockReferenced(block);
eviction_policy->blockUnreferenced(block);
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
// Ensure that no block has been referenced in the last corrl_reference_period.
std::this_thread::sleep_for(corrl_reference_period + std::chrono::milliseconds(500));
// Assure that the blocks are evicted in descending order
for (const block_id block : blocks) {
block_id evicted_block;
EvictionPolicy::Status status = eviction_policy->chooseBlockToEvict(&evicted_block);
ASSERT_EQ(EvictionPolicy::Status::kOk, status);
ASSERT_EQ(block, evicted_block);
}
}
}
} // namespace quickstep