1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
|
#include "catch.hpp"
#include "duckdb/common/lru_cache.hpp"
#include "duckdb/common/string.hpp"
#include "duckdb/storage/object_cache.hpp"
#include "duckdb/storage/buffer_manager.hpp"
#include "duckdb/storage/buffer/buffer_pool.hpp"
#include "test_helpers.hpp"
using namespace duckdb; // NOLINT
namespace {
struct TestObject : public ObjectCacheEntry {
int value;
TestObject(int value) : value(value) {
}
~TestObject() override = default;
string GetObjectType() override {
return ObjectType();
}
static string ObjectType() {
return "TestObject";
}
optional_idx GetEstimatedCacheMemory() const override {
return optional_idx {};
}
};
struct AnotherTestObject : public ObjectCacheEntry {
int value;
AnotherTestObject(int value) : value(value) {
}
~AnotherTestObject() override = default;
string GetObjectType() override {
return ObjectType();
}
static string ObjectType() {
return "AnotherTestObject";
}
optional_idx GetEstimatedCacheMemory() const override {
return optional_idx {};
}
};
struct EvictableTestObject : public ObjectCacheEntry {
int value;
idx_t size;
EvictableTestObject(int value, idx_t size) : value(value), size(size) {
}
~EvictableTestObject() override = default;
string GetObjectType() override {
return ObjectType();
}
static string ObjectType() {
return "EvictableTestObject";
}
optional_idx GetEstimatedCacheMemory() const override {
return optional_idx(size);
}
};
} // namespace
TEST_CASE("Test ObjectCache", "[api][object_cache]") {
DuckDB db;
Connection con(db);
auto &context = *con.context;
auto &cache = ObjectCache::GetObjectCache(context);
REQUIRE(cache.GetObject("test") == nullptr);
cache.Put("test", make_shared_ptr<TestObject>(42));
REQUIRE(cache.GetObject("test") != nullptr);
cache.Delete("test");
REQUIRE(cache.GetObject("test") == nullptr);
REQUIRE(cache.GetOrCreate<TestObject>("test", 42) != nullptr);
REQUIRE(cache.Get<TestObject>("test") != nullptr);
REQUIRE(cache.GetOrCreate<TestObject>("test", 1337)->value == 42);
REQUIRE(cache.Get<TestObject>("test")->value == 42);
REQUIRE(cache.GetOrCreate<AnotherTestObject>("test", 13) == nullptr);
}
TEST_CASE("Test ObjectCache memory accounting", "[api][object_cache]") {
DuckDB db;
Connection con(db);
auto &context = *con.context;
auto &cache = ObjectCache::GetObjectCache(context);
auto &buffer_pool = DatabaseInstance::GetDatabase(context).GetBufferPool();
const idx_t initial_memory = buffer_pool.GetUsedMemory();
// Put and check accountable memory for buffer pool.
constexpr idx_t obj_size = 1024 * 1024;
cache.Put("evictable1", make_shared_ptr<EvictableTestObject>(1, obj_size));
const idx_t after_put_memory = buffer_pool.GetUsedMemory();
REQUIRE(after_put_memory == initial_memory + obj_size);
// Delete and check accountable memory for buffer pool.
cache.Delete("evictable1");
const idx_t after_delete_memory = buffer_pool.GetUsedMemory();
REQUIRE(after_delete_memory == initial_memory);
}
TEST_CASE("Test ObjectCache Manual Eviction", "[api][object_cache]") {
DuckDB db;
Connection con(db);
auto &context = *con.context;
auto &cache = ObjectCache::GetObjectCache(context);
auto &buffer_pool = DatabaseInstance::GetDatabase(context).GetBufferPool();
const idx_t initial_memory = buffer_pool.GetUsedMemory();
REQUIRE(cache.IsEmpty());
// Put and check accountable memory for buffer pool.
constexpr idx_t obj_size = 1024 * 1024;
constexpr idx_t obj_count = 10;
for (idx_t idx = 0; idx < obj_count; ++idx) {
cache.Put(StringUtil::Format("evictable%llu", idx), make_shared_ptr<EvictableTestObject>(idx, obj_size));
}
REQUIRE(cache.GetEntryCount() == 10);
const idx_t after_put_memory = buffer_pool.GetUsedMemory();
REQUIRE(after_put_memory == initial_memory + obj_size * obj_count);
// Evict 5 objects, leaving 5 objects in cache
const idx_t bytes_to_free = 5 * obj_size;
idx_t freed = cache.EvictToReduceMemory(bytes_to_free);
REQUIRE(freed >= bytes_to_free); // Should free at least the requested amount
REQUIRE(cache.GetCurrentMemory() == 5 * obj_size);
REQUIRE(cache.GetEntryCount() == 5);
// First five items should be evicted.
for (idx_t idx = 0; idx < 5; ++idx) {
auto value = cache.GetObject(StringUtil::Format("evictable%llu", idx));
REQUIRE(value == nullptr);
}
// Later five items should be kept.
for (idx_t idx = 5; idx < 10; ++idx) {
auto value = cache.GetObject(StringUtil::Format("evictable%llu", idx));
REQUIRE(value != nullptr);
}
REQUIRE(!cache.IsEmpty());
}
|