Repository: duckdb/duckdb
File: src/include/duckdb/storage/object_cache.hpp (C++)

Code before:

namespace duckdb {

//! ObjectCache is the base class for objects caches in DuckDB
class ObjectCacheEntry {
public:
	virtual ~ObjectCacheEntry() {
	}

	virtual string GetObjectType() = 0;

	//! Get the rough cache memory usage in bytes for this entry.
	//! Used for eviction decisions. Return invalid index to prevent eviction.
	virtual optional_idx GetEstimatedCacheMemory() const = 0;
};

class ObjectCache {
public:
	//! Default max memory 8GiB for non-evictable cache entries.
	//
	// TODO(hjiang): Hard-code a large enough memory consumption upper bound, which is likely a non-regression change.
	// I will followup with another PR before v1.5.0 release to provide a user option to tune.
	//
	// A few consideration here: should we cap object cache memory consumption with duckdb max memory or separate.
	static constexpr idx_t DEFAULT_MAX_MEMORY = 8ULL * 1024 * 1024 * 1024;

	ObjectCache() : ObjectCache(DEFAULT_MAX_MEMORY) {
	}

	explicit ObjectCache(idx_t max_memory) : lru_cache(max_memory) {
	}

	shared_ptr<ObjectCacheEntry> GetObject(const string &key) {
		const lock_guard<mutex> lock(lock_mutex);
		auto non_evictable_it = non_evictable_entries.find(key);
		if (non_evictable_it != non_evictable_entries.end()) {
			return non_evictable_it->second;
		}
		return lru_cache.Get(key);
	}

	template <class T>
	shared_ptr<T> Get(const string &key) {
		shared_ptr<ObjectCacheEntry> object = GetObject(key);
		if (!object || object->GetObjectType() != T::ObjectType()) {
			return nullptr;
		}
		return shared_ptr_cast<ObjectCacheEntry, T>(object);
	}

	template <class T, class... ARGS>

Review comment (performance):
I think we have all the necessary components to do proper object cache eviction. You can do this in a follow-up, but in short, the approach would be:
1. Add a `BufferPoolReservation` to each entry in the cache with the appropriate (estimated) size
2. Evict from the object cache in `BufferPool::EvictBlocks` (after the loop over all the pools)

This will cause DuckDB to count the bytes, as well as evict from the cache when running low on memory.

Code after:
#include "duckdb/main/client_context.hpp"
#include "duckdb/main/database.hpp"

namespace duckdb {

//! ObjectCache is the base class for objects caches in DuckDB
class ObjectCacheEntry {
public:
	virtual ~ObjectCacheEntry() {
	}

	virtual string GetObjectType() = 0;

	//! Get the rough cache memory usage in bytes for this entry.
	//! Used for eviction decisions. Return invalid index to prevent eviction.
	virtual optional_idx GetEstimatedCacheMemory() const = 0;
};

class ObjectCache {
public:
	//! Default max memory 8GiB for non-evictable cache entries.
	//
	// TODO(hjiang): Hard-code a large enough memory consumption upper bound, which is likely a non-regression change.
	// I will followup with another PR before v1.5.0 release to provide a user option to tune.
	//
	// A few consideration here: should we cap object cache memory consumption with duckdb max memory or separate.
	static constexpr idx_t DEFAULT_MAX_MEMORY = 8ULL * 1024 * 1024 * 1024;

	ObjectCache() : ObjectCache(DEFAULT_MAX_MEMORY) {
	}

	explicit ObjectCache(idx_t max_memory) : lru_cache(max_memory) {
	}

	shared_ptr<ObjectCacheEntry> GetObject(const string &key) {
		const lock_guard<mutex> lock(lock_mutex);
		auto non_evictable_it = non_evictable_entries.find(key);
		if (non_evictable_it != non_evictable_entries.end()) {
			return non_evictable_it->second;
		}
		return lru_cache.Get(key);
	}

	template <class T>
	shared_ptr<T> Get(const string &key) {
		shared_ptr<ObjectCacheEntry> object = GetObject(key);
		if (!object || object->GetObjectType() != T::ObjectType()) {
			return nullptr;
		}
		return shared_ptr_cast<ObjectCacheEntry, T>(object);
	}