diff --git a/src/include/duckdb/main/config.hpp b/src/include/duckdb/main/config.hpp index b9120f9a7fe0..400f3977c8bd 100644 --- a/src/include/duckdb/main/config.hpp +++ b/src/include/duckdb/main/config.hpp @@ -243,7 +243,9 @@ struct DBConfigOptions { //! Whether to print bindings when printing the plan (debug mode only) static bool debug_print_bindings; // NOLINT: debug setting //! The peak allocation threshold at which to flush the allocator after completing a task (1 << 27, ~128MB) - idx_t allocator_flush_threshold = 134217728; + idx_t allocator_flush_threshold = 134217728ULL; + //! If bulk deallocation larger than this occurs, flush outstanding allocations (1 << 30, ~1GB) + idx_t allocator_bulk_deallocation_flush_threshold = 1073741824ULL; //! Whether the allocator background thread is enabled bool allocator_background_threads = false; //! DuckDB API surface @@ -258,11 +260,11 @@ struct DBConfigOptions { bool abort_on_wal_failure = false; //! The index_scan_percentage sets a threshold for index scans. //! If fewer than MAX(index_scan_max_count, index_scan_percentage * total_row_count) - // rows match, we perform an index scan instead of a table scan. + //! rows match, we perform an index scan instead of a table scan. double index_scan_percentage = 0.001; //! The index_scan_max_count sets a threshold for index scans. //! If fewer than MAX(index_scan_max_count, index_scan_percentage * total_row_count) - // rows match, we perform an index scan instead of a table scan. + //! rows match, we perform an index scan instead of a table scan. idx_t index_scan_max_count = STANDARD_VECTOR_SIZE; //! The maximum number of schemas we will look through for "did you mean..." style errors in the catalog idx_t catalog_error_max_schemas = 100; diff --git a/src/include/duckdb/main/settings.hpp b/src/include/duckdb/main/settings.hpp index 52739312d5bf..538a75b85e4f 100644 --- a/src/include/duckdb/main/settings.hpp +++ b/src/include/duckdb/main/settings.hpp @@ -886,7 +886,7 @@ struct UsernameSetting { static Value GetSetting(const ClientContext &context); }; -struct FlushAllocatorSetting { +struct AllocatorFlushThreshold { static constexpr const char *Name = "allocator_flush_threshold"; static constexpr const char *Description = "Peak allocation threshold at which to flush the allocator after completing a task."; @@ -896,6 +896,16 @@ struct FlushAllocatorSetting { static Value GetSetting(const ClientContext &context); }; +struct AllocatorBulkDeallocationFlushThreshold { + static constexpr const char *Name = "allocator_bulk_deallocation_flush_threshold"; + static constexpr const char *Description = + "If a bulk deallocation larger than this occurs, flush outstanding allocations."; + static constexpr const LogicalTypeId InputType = LogicalTypeId::VARCHAR; + static void SetGlobal(DatabaseInstance *db, DBConfig &config, const Value ¶meter); + static void ResetGlobal(DatabaseInstance *db, DBConfig &config); + static Value GetSetting(const ClientContext &context); +}; + struct AllocatorBackgroundThreadsSetting { static constexpr const char *Name = "allocator_background_threads"; static constexpr const char *Description = "Whether to enable the allocator background thread."; diff --git a/src/include/duckdb/storage/buffer/buffer_pool.hpp b/src/include/duckdb/storage/buffer/buffer_pool.hpp index edc79c6c20c4..bf26f80b3d6c 100644 --- a/src/include/duckdb/storage/buffer/buffer_pool.hpp +++ b/src/include/duckdb/storage/buffer/buffer_pool.hpp @@ -48,6 +48,9 @@ class BufferPool { //! blocks can be evicted void SetLimit(idx_t limit, const char *exception_postscript); + //! If bulk deallocation larger than this occurs, flush outstanding allocations + void SetAllocatorBulkDeallocationFlushThreshold(idx_t threshold); + void UpdateUsedMemory(MemoryTag tag, int64_t size); idx_t GetUsedMemory() const; @@ -135,6 +138,8 @@ class BufferPool { mutex limit_lock; //! The maximum amount of memory that the buffer manager can keep (in bytes) atomic maximum_memory; + //! If bulk deallocation larger than this occurs, flush outstanding allocations + atomic allocator_bulk_deallocation_flush_threshold; //! Record timestamps of buffer manager unpin() events. Usable by custom eviction policies. bool track_eviction_timestamps; //! Eviction queues diff --git a/src/main/config.cpp b/src/main/config.cpp index 3a46fc9c33f0..25af4eeed3a1 100644 --- a/src/main/config.cpp +++ b/src/main/config.cpp @@ -2,8 +2,8 @@ #include "duckdb/common/cgroups.hpp" #include "duckdb/common/file_system.hpp" -#include "duckdb/common/operator/multiply.hpp" #include "duckdb/common/operator/cast_operators.hpp" +#include "duckdb/common/operator/multiply.hpp" #include "duckdb/common/string_util.hpp" #include "duckdb/main/settings.hpp" #include "duckdb/storage/storage_extension.hpp" @@ -145,7 +145,8 @@ static const ConfigurationOption internal_options[] = { DUCKDB_GLOBAL_ALIAS("user", UsernameSetting), DUCKDB_GLOBAL_ALIAS("wal_autocheckpoint", CheckpointThresholdSetting), DUCKDB_GLOBAL_ALIAS("worker_threads", ThreadsSetting), - DUCKDB_GLOBAL(FlushAllocatorSetting), + DUCKDB_GLOBAL(AllocatorFlushThreshold), + DUCKDB_GLOBAL(AllocatorBulkDeallocationFlushThreshold), DUCKDB_GLOBAL(AllocatorBackgroundThreadsSetting), DUCKDB_GLOBAL(DuckDBApiSetting), DUCKDB_GLOBAL(CustomUserAgentSetting), diff --git a/src/main/settings/settings.cpp b/src/main/settings/settings.cpp index 61a7f2642364..8cdcf42efd2f 100644 --- a/src/main/settings/settings.cpp +++ b/src/main/settings/settings.cpp @@ -13,6 +13,7 @@ #include "duckdb/parallel/task_scheduler.hpp" #include "duckdb/parser/parser.hpp" #include "duckdb/planner/expression_binder.hpp" +#include "duckdb/storage/buffer/buffer_pool.hpp" #include "duckdb/storage/buffer_manager.hpp" #include "duckdb/storage/storage_manager.hpp" @@ -1907,27 +1908,52 @@ Value UsernameSetting::GetSetting(const ClientContext &context) { //===--------------------------------------------------------------------===// // Allocator Flush Threshold //===--------------------------------------------------------------------===// -void FlushAllocatorSetting::SetGlobal(DatabaseInstance *db, DBConfig &config, const Value &input) { +void AllocatorFlushThreshold::SetGlobal(DatabaseInstance *db, DBConfig &config, const Value &input) { config.options.allocator_flush_threshold = DBConfig::ParseMemoryLimit(input.ToString()); if (db) { TaskScheduler::GetScheduler(*db).SetAllocatorFlushTreshold(config.options.allocator_flush_threshold); } } -void FlushAllocatorSetting::ResetGlobal(DatabaseInstance *db, DBConfig &config) { +void AllocatorFlushThreshold::ResetGlobal(DatabaseInstance *db, DBConfig &config) { config.options.allocator_flush_threshold = DBConfig().options.allocator_flush_threshold; if (db) { TaskScheduler::GetScheduler(*db).SetAllocatorFlushTreshold(config.options.allocator_flush_threshold); } } -Value FlushAllocatorSetting::GetSetting(const ClientContext &context) { +Value AllocatorFlushThreshold::GetSetting(const ClientContext &context) { auto &config = DBConfig::GetConfig(context); return Value(StringUtil::BytesToHumanReadableString(config.options.allocator_flush_threshold)); } //===--------------------------------------------------------------------===// -// Allocator Background Thread +// Allocator Bulk Deallocation Flush Threshold +//===--------------------------------------------------------------------===// +void AllocatorBulkDeallocationFlushThreshold::SetGlobal(DatabaseInstance *db, DBConfig &config, const Value &input) { + config.options.allocator_bulk_deallocation_flush_threshold = DBConfig::ParseMemoryLimit(input.ToString()); + if (db) { + BufferManager::GetBufferManager(*db).GetBufferPool().SetAllocatorBulkDeallocationFlushThreshold( + config.options.allocator_bulk_deallocation_flush_threshold); + } +} + +void AllocatorBulkDeallocationFlushThreshold::ResetGlobal(DatabaseInstance *db, DBConfig &config) { + config.options.allocator_bulk_deallocation_flush_threshold = + DBConfig().options.allocator_bulk_deallocation_flush_threshold; + if (db) { + BufferManager::GetBufferManager(*db).GetBufferPool().SetAllocatorBulkDeallocationFlushThreshold( + config.options.allocator_bulk_deallocation_flush_threshold); + } +} + +Value AllocatorBulkDeallocationFlushThreshold::GetSetting(const ClientContext &context) { + auto &config = DBConfig::GetConfig(context); + return Value(StringUtil::BytesToHumanReadableString(config.options.allocator_bulk_deallocation_flush_threshold)); +} + +//===--------------------------------------------------------------------===// +// Allocator Background Threads //===--------------------------------------------------------------------===// void AllocatorBackgroundThreadsSetting::SetGlobal(DatabaseInstance *db, DBConfig &config, const Value &input) { config.options.allocator_background_threads = input.GetValue(); diff --git a/src/storage/buffer/buffer_pool.cpp b/src/storage/buffer/buffer_pool.cpp index c95abbc7b323..76382fdf3b2c 100644 --- a/src/storage/buffer/buffer_pool.cpp +++ b/src/storage/buffer/buffer_pool.cpp @@ -309,6 +309,8 @@ BufferPool::EvictionResult BufferPool::EvictBlocksInternal(EvictionQueue &queue, if (!found) { r.Resize(0); + } else if (Allocator::SupportsFlush() && extra_memory > allocator_bulk_deallocation_flush_threshold) { + Allocator::FlushAll(); } return {found, std::move(r)}; @@ -401,6 +403,10 @@ void BufferPool::SetLimit(idx_t limit, const char *exception_postscript) { } } +void BufferPool::SetAllocatorBulkDeallocationFlushThreshold(idx_t threshold) { + allocator_bulk_deallocation_flush_threshold = threshold; +} + BufferPool::MemoryUsage::MemoryUsage() { for (auto &v : memory_usage) { v = 0; diff --git a/test/api/test_reset.cpp b/test/api/test_reset.cpp index cc0f5d0c77b1..3e58d3fa75af 100644 --- a/test/api/test_reset.cpp +++ b/test/api/test_reset.cpp @@ -105,7 +105,8 @@ OptionValueSet GetValueForOption(const string &name, LogicalTypeId type) { {"http_proxy_username", {"john"}}, {"http_proxy_password", {"doe"}}, {"http_logging_output", {"my_cool_outputfile"}}, - {"allocator_flush_threshold", {"4.0 GiB"}}}; + {"allocator_flush_threshold", {"4.0 GiB"}}, + {"allocator_bulk_deallocation_flush_threshold", {"4.0 GiB"}}}; // Every option that's not excluded has to be part of this map if (!value_map.count(name)) { switch (type) {