From fd22af21dadddf1f266bbdecf79960263c97b86c Mon Sep 17 00:00:00 2001 From: mrambacher Date: Sat, 11 Feb 2023 07:32:21 -0500 Subject: [PATCH] live config changes: add RefreshOptions method (#294) Added a method and options to support reading configuration changes to options periodically while a process is running. Every "refresh_options_sec", the periodic task scheduler runs a task to check if the "refresh_options_file" exists. If it does, the file is loaded as an options file and the mutable options from the file are updated in the running process (via SetDBOptions and SetOptions). Once updated, the refresh file is deleted. If an error occurs, it will be written to the log file. Note that the file is read as an Options file and must be a valid Options file. This means that the file must have a DBOptions section and at least one ColumnFamilyOptions section. Only options that are mutable can be in this file -- immutable options will cause an error and abort the processing. Additionally, note that TableFactory options must be specified as part of the ColumnFamilyOptions. For example, to change the block size, the "table_factory.block_size" option should be set in the appropriate ColumnFamilyOptions. Options set in a TableFactory section will be ignored. Currently tested manually via db_bench. Unit tests will be forthcoming. --- db/db_impl/db_impl.cc | 107 ++++++++++ db/db_impl/db_impl.h | 11 +- db/db_options_test.cc | 296 ++++++++++++++++++++++++++ db/periodic_task_scheduler.cc | 2 + db/periodic_task_scheduler.h | 1 + db/periodic_task_scheduler_test.cc | 2 + db_stress_tool/db_stress_common.h | 2 + db_stress_tool/db_stress_gflags.cc | 5 + db_stress_tool/db_stress_test_base.cc | 4 + include/rocksdb/options.h | 8 + options/db_options.cc | 17 ++ options/db_options.h | 2 + options/options_helper.cc | 2 + options/options_settable_test.cc | 3 + tools/db_bench_tool.cc | 8 + 15 files changed, 469 insertions(+), 1 deletion(-) diff --git a/db/db_impl/db_impl.cc b/db/db_impl/db_impl.cc index 028d0cee7c..e7a7abbf95 100644 --- a/db/db_impl/db_impl.cc +++ b/db/db_impl/db_impl.cc @@ -295,6 +295,8 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname, periodic_task_functions_.emplace( PeriodicTaskType::kRecordSeqnoTime, [this]() { this->RecordSeqnoToTimeMapping(); }); + periodic_task_functions_.emplace(PeriodicTaskType::kRefreshOptions, + [this]() { this->RefreshOptions(); }); versions_.reset(new VersionSet(dbname_, &immutable_db_options_, file_options_, table_cache_.get(), write_buffer_manager_, @@ -833,6 +835,15 @@ Status DBImpl::StartPeriodicTaskScheduler() { return s; } } + if (mutable_db_options_.refresh_options_sec > 0) { + Status s = periodic_task_scheduler_.Register( + PeriodicTaskType::kRefreshOptions, + periodic_task_functions_.at(PeriodicTaskType::kRefreshOptions), + mutable_db_options_.refresh_options_sec); + if (!s.ok()) { + return s; + } + } Status s = periodic_task_scheduler_.Register( PeriodicTaskType::kFlushInfoLog, @@ -1144,6 +1155,82 @@ void DBImpl::FlushInfoLog() { LogFlush(immutable_db_options_.info_log); } +#ifndef ROCKSDB_LITE +// Periodically checks to see if the new options should be loaded into the +// process. log. +void DBImpl::RefreshOptions() { + if (shutdown_initiated_) { + return; + } + std::string new_options_file = mutable_db_options_.refresh_options_file; + if (new_options_file.empty()) { + new_options_file = "Options.new"; + } + if (new_options_file[0] != kFilePathSeparator) { + new_options_file = NormalizePath(immutable_db_options_.db_paths[0].path + + kFilePathSeparator + new_options_file); + } + TEST_SYNC_POINT("DBImpl::RefreshOptions::Start"); + Status s = fs_->FileExists(new_options_file, IOOptions(), nullptr); + TEST_SYNC_POINT_CALLBACK("DBImpl::RefreshOptions::FileExists", &s); + if (!s.ok()) { + return; + } + ROCKS_LOG_INFO(immutable_db_options_.info_log, + "Refreshing Options from file: %s\n", + new_options_file.c_str()); + + ConfigOptions cfg_opts; + cfg_opts.ignore_unknown_options = true; + cfg_opts.mutable_options_only = true; + RocksDBOptionsParser op; + s = op.Parse(cfg_opts, new_options_file, fs_.get()); + TEST_SYNC_POINT_CALLBACK("DBImpl::RefreshOptions::Parse", &s); + if (!s.ok()) { + ROCKS_LOG_WARN(immutable_db_options_.info_log, + "Failed to parse Options file (%s): %s\n", + new_options_file.c_str(), s.ToString().c_str()); + } else if (!op.db_opt_map()->empty()) { + s = SetDBOptions(*(op.db_opt_map())); + TEST_SYNC_POINT_CALLBACK("DBImpl::RefreshOptions::SetDBOptions", &s); + if (!s.ok()) { + ROCKS_LOG_WARN(immutable_db_options_.info_log, + "Failed to refresh DBOptions, Aborting: %s\n", + s.ToString().c_str()); + } + } + if (s.ok()) { + int idx = 0; + for (const auto& cf_opt_map : *(op.cf_opt_maps())) { + if (!cf_opt_map.empty()) { + const auto& cf_name = (*op.cf_names())[idx]; + auto cfd = versions_->GetColumnFamilySet()->GetColumnFamily(cf_name); + if (cfd == nullptr) { + ROCKS_LOG_WARN(immutable_db_options_.info_log, + "RefreshOptions failed locating CF: %s\n", + cf_name.c_str()); + } else if (!cfd->IsDropped()) { + s = SetCFOptionsImpl(cfd, cf_opt_map); + TEST_SYNC_POINT_CALLBACK("DBImpl::RefreshOptions::SetCFOptions", &s); + if (!s.ok()) { + ROCKS_LOG_WARN(immutable_db_options_.info_log, + "Failed to refresh CFOptions for CF %s: %s\n", + cf_name.c_str(), s.ToString().c_str()); + } + } + } + idx++; + } + } + s = fs_->DeleteFile(new_options_file, IOOptions(), nullptr); + TEST_SYNC_POINT_CALLBACK("DBImpl::RefreshOptions::DeleteFile", &s); + ROCKS_LOG_INFO(immutable_db_options_.info_log, + "RefreshOptions Complete, deleting options file %s: %s\n", + new_options_file.c_str(), s.ToString().c_str()); + TEST_SYNC_POINT("DBImpl::RefreshOptions::Complete"); +} +#endif // ROCKSDB_LITE + Status DBImpl::TablesRangeTombstoneSummary(ColumnFamilyHandle* column_family, int max_entries_to_print, std::string* out_str) { @@ -1192,7 +1279,14 @@ Status DBImpl::SetOptions( cfd->GetName().c_str()); return Status::InvalidArgument("empty input"); } + return SetCFOptionsImpl(cfd, options_map); +#endif // ROCKSDB_LITE +} +#ifndef ROCKSDB_LITE +Status DBImpl::SetCFOptionsImpl( + ColumnFamilyData* cfd, + const std::unordered_map& options_map) { MutableCFOptions new_options; Status s; Status persist_options_status; @@ -1242,6 +1336,7 @@ Status DBImpl::SetOptions( LogFlush(immutable_db_options_.info_log); return s; } +#endif // ROCKSDB_LITE Status DBImpl::SetDBOptions( const std::unordered_map& options_map) { @@ -1345,6 +1440,18 @@ Status DBImpl::SetDBOptions( new_options.stats_persist_period_sec); } } + if (s.ok()) { + if (new_options.refresh_options_sec == 0) { + s = periodic_task_scheduler_.Unregister( + PeriodicTaskType::kRefreshOptions); + } else { + s = periodic_task_scheduler_.Register( + PeriodicTaskType::kRefreshOptions, + periodic_task_functions_.at(PeriodicTaskType::kRefreshOptions), + new_options.refresh_options_sec); + } + } + mutex_.Lock(); if (!s.ok()) { return s; diff --git a/db/db_impl/db_impl.h b/db/db_impl/db_impl.h index 32aee4298b..62b4fd9439 100644 --- a/db/db_impl/db_impl.h +++ b/db/db_impl/db_impl.h @@ -1218,6 +1218,11 @@ class DBImpl : public DB { // record current sequence number to time mapping void RecordSeqnoToTimeMapping(); +#ifndef ROCKSDB_LITE + // Checks if the options should be updated + void RefreshOptions(); +#endif // ROCKSDB_LITE + // Interface to block and signal the DB in case of stalling writes by // WriteBufferManager. Each DBImpl object contains ptr to WBMStallInterface. // When DB needs to be blocked or signalled by WriteBufferManager, @@ -1821,7 +1826,11 @@ class DBImpl : public DB { ColumnFamilyHandle** handle); Status DropColumnFamilyImpl(ColumnFamilyHandle* column_family); - +#ifndef ROCKSDB_LITE + Status SetCFOptionsImpl( + ColumnFamilyData* cfd, + const std::unordered_map& options_map); +#endif // ROCKSDB_LITE // Delete any unneeded files and stale in-memory entries. void DeleteObsoleteFiles(); // Delete obsolete files and log status and information of file deletion diff --git a/db/db_options_test.cc b/db/db_options_test.cc index 5c90602322..1ca6532271 100644 --- a/db/db_options_test.cc +++ b/db/db_options_test.cc @@ -13,7 +13,9 @@ #include "db/column_family.h" #include "db/db_impl/db_impl.h" #include "db/db_test_util.h" +#include "env/mock_env.h" #include "options/options_helper.h" +#include "options/options_parser.h" #include "port/stack_trace.h" #include "rocksdb/cache.h" #include "rocksdb/convenience.h" @@ -30,6 +32,11 @@ class DBOptionsTest : public DBTestBase { public: DBOptionsTest() : DBTestBase("db_options_test", /*env_do_fsync=*/true) {} + ~DBOptionsTest() override { + SyncPoint::GetInstance()->DisableProcessing(); + SyncPoint::GetInstance()->ClearAllCallBacks(); + } + std::unordered_map GetMutableDBOptionsMap( const DBOptions& options) { std::string options_str; @@ -1190,6 +1197,295 @@ TEST_F(DBOptionsTest, ChangeCompression) { SyncPoint::GetInstance()->DisableProcessing(); } +namespace { +IOStatus WaitForOptionsUpdate(const std::shared_ptr& fs, + const std::string& tmp_options_file, + const std::string& new_options_file) { + auto s = + fs->RenameFile(tmp_options_file, new_options_file, IOOptions(), nullptr); + if (s.ok()) { + TEST_SYNC_POINT("DBOptionsTest::WaitForUpdates"); + s = fs->FileExists(new_options_file, IOOptions(), nullptr); + } + SyncPoint::GetInstance()->LoadDependency( + {{"DBImpl::RefreshOptions::Complete", "DBOptionsTest::WaitForUpdates"}}); + return s; +} +} // namespace + +TEST_F(DBOptionsTest, RefreshOptions) { + Options options = CurrentOptions(); + auto fs = options.env->GetFileSystem(); + options.create_if_missing = true; + options.refresh_options_sec = 1; + options.refresh_options_file = dbname_ + "/Options.new"; + std::string tmp_options_file = dbname_ + "/Options.tmp"; + options.max_background_jobs = 1; + options.max_background_compactions = 2; + options.periodic_compaction_seconds = 100; + ASSERT_OK(TryReopen(options)); + SyncPoint::GetInstance()->LoadDependency( + {{"DBImpl::RefreshOptions::Complete", "DBOptionsTest::WaitForUpdates"}}); + SyncPoint::GetInstance()->EnableProcessing(); + ConfigOptions config_options; + config_options.mutable_options_only = true; + options.max_background_jobs = 10; + options.max_background_compactions = 20; + options.periodic_compaction_seconds = 200; + ASSERT_OK(PersistRocksDBOptions(config_options, options, {"default"}, + {options}, tmp_options_file, fs.get())); + ASSERT_NOK( + WaitForOptionsUpdate(fs, tmp_options_file, options.refresh_options_file)); + + DBOptions new_db_opts = db_->GetDBOptions(); + ASSERT_EQ(new_db_opts.max_background_jobs, 10); + ASSERT_EQ(new_db_opts.max_background_compactions, 20); + auto dcfh = db_->DefaultColumnFamily(); + ColumnFamilyDescriptor dcd; + ASSERT_OK(dcfh->GetDescriptor(&dcd)); + ASSERT_EQ(dcd.options.periodic_compaction_seconds, 200); +} + +TEST_F(DBOptionsTest, RefreshSimpleOptions) { + Options options = CurrentOptions(); + auto fs = options.env->GetFileSystem(); + options.create_if_missing = true; + options.max_background_compactions = 11; + options.refresh_options_sec = 1; + options.refresh_options_file = dbname_ + "/Options.new"; + std::string tmp_options_file = dbname_ + "/Options.tmp"; + options.enable_blob_files = false; + ASSERT_OK(TryReopen(options)); + + SyncPoint::GetInstance()->LoadDependency( + {{"DBImpl::RefreshOptions::Complete", "DBOptionsTest::WaitForUpdates"}}); + SyncPoint::GetInstance()->EnableProcessing(); + + // Test with a file that contains only DBOptions + ASSERT_OK(CreateFile(fs, tmp_options_file, + "[DBOptions]\n" + "max_background_compactions = 22\n" + "[CFOptions \"default\"]\n", + false)); + ASSERT_NOK( + WaitForOptionsUpdate(fs, tmp_options_file, options.refresh_options_file)); + DBOptions new_db_opts = db_->GetDBOptions(); + ASSERT_EQ(new_db_opts.max_background_compactions, 22); + + // Test with a file that contains only ColumnFamilyOptions + ASSERT_OK(CreateFile(fs, tmp_options_file, + "[DBOptions]\n" + "[CFOptions \"default\"]\n" + "enable_blob_files = true\n", + false)); + ASSERT_NOK( + WaitForOptionsUpdate(fs, tmp_options_file, options.refresh_options_file)); + auto dcfh = db_->DefaultColumnFamily(); + ColumnFamilyDescriptor dcd; + ASSERT_OK(dcfh->GetDescriptor(&dcd)); + ASSERT_EQ(dcd.options.enable_blob_files, true); + + // Test with a file that contains a table factory options + ASSERT_OK(CreateFile(fs, tmp_options_file, + "[DBOptions]\n" + "[CFOptions \"default\"]\n" + "table_factory.block_size = 32768\n", + false)); + ASSERT_NOK( + WaitForOptionsUpdate(fs, tmp_options_file, options.refresh_options_file)); + + ASSERT_OK(dcfh->GetDescriptor(&dcd)); + auto bbto = dcd.options.table_factory->GetOptions(); + ASSERT_NE(bbto, nullptr); + ASSERT_EQ(bbto->block_size, 32768); +} + +TEST_F(DBOptionsTest, DifferentOptionsFile) { + Options options = CurrentOptions(); + auto fs = options.env->GetFileSystem(); + options.create_if_missing = true; + options.refresh_options_sec = 1; + options.refresh_options_file = ""; + options.max_background_jobs = 1; + options.max_background_compactions = 2; + options.periodic_compaction_seconds = 100; + std::string tmp_options_file = dbname_ + "/Options.new.tmp"; + ASSERT_OK(TryReopen(options)); + SyncPoint::GetInstance()->LoadDependency( + {{"DBImpl::RefreshOptions::Complete", "DBOptionsTest::WaitForUpdates"}}); + SyncPoint::GetInstance()->EnableProcessing(); + ConfigOptions config_options; + config_options.mutable_options_only = true; + options.refresh_options_file = "Options.tmp.1"; + ASSERT_OK(PersistRocksDBOptions(config_options, options, {"default"}, + {options}, tmp_options_file, fs.get())); + ASSERT_NOK( + WaitForOptionsUpdate(fs, tmp_options_file, dbname_ + "/Options.new")); + + DBOptions new_db_opts = db_->GetDBOptions(); + ASSERT_EQ(new_db_opts.refresh_options_file, "Options.tmp.1"); + + options.refresh_options_file = "Options.tmp.2"; + ASSERT_OK(PersistRocksDBOptions(config_options, options, {"default"}, + {options}, tmp_options_file, fs.get())); + ASSERT_NOK( + WaitForOptionsUpdate(fs, tmp_options_file, dbname_ + "/Options.tmp.1")); + new_db_opts = db_->GetDBOptions(); + ASSERT_EQ(new_db_opts.refresh_options_file, "Options.tmp.2"); + + ASSERT_OK(fs->CreateDir(dbname_ + "/Options.tmp", IOOptions(), nullptr)); + options.refresh_options_file = dbname_ + "/Options.tmp/Options.new"; + ASSERT_OK(PersistRocksDBOptions(config_options, options, {"default"}, + {options}, tmp_options_file, fs.get())); + + ASSERT_NOK( + WaitForOptionsUpdate(fs, tmp_options_file, dbname_ + "/Options.tmp.2")); + + new_db_opts = db_->GetDBOptions(); + ASSERT_EQ(new_db_opts.refresh_options_file, + dbname_ + "/Options.tmp/Options.new"); + + options.max_background_compactions = 4; + ASSERT_OK(PersistRocksDBOptions(config_options, options, {"default"}, + {options}, tmp_options_file, fs.get())); + ASSERT_NOK(WaitForOptionsUpdate(fs, tmp_options_file, + dbname_ + "/Options.tmp/Options.new")); + new_db_opts = db_->GetDBOptions(); + ASSERT_EQ(new_db_opts.max_background_compactions, 4); + ASSERT_OK(fs->DeleteDir(dbname_ + "/Options.tmp", IOOptions(), nullptr)); +} + +TEST_F(DBOptionsTest, RefreshOptionsImmutable) { + Options options = CurrentOptions(); + auto fs = options.env->GetFileSystem(); + options.create_if_missing = true; + options.refresh_options_sec = 1; + options.refresh_options_file = dbname_ + "/Options.new"; + std::string tmp_options_file = dbname_ + "/Options.tmp"; + ASSERT_OK(TryReopen(options)); + SyncPoint::GetInstance()->LoadDependency( + {{"DBImpl::RefreshOptions::Complete", "DBOptionsTest::WaitForUpdates"}}); + SyncPoint::GetInstance()->EnableProcessing(); + ConfigOptions config_options; + + // Test setting an immutable DBOption and see the value + // did not change + std::unique_ptr mock(MockEnv::Create(options.env)); + options.env = mock.get(); + ASSERT_OK(PersistRocksDBOptions(config_options, options, {"default"}, + {options}, tmp_options_file, fs.get())); + ASSERT_NOK( + WaitForOptionsUpdate(fs, tmp_options_file, options.refresh_options_file)); + + // Test setting an immutable ColumnFamilyOption and see the value + // did not change + options = CurrentOptions(); + options.comparator = ReverseBytewiseComparator(); + options.refresh_options_sec = 1; + options.refresh_options_file = dbname_ + "/Options.new"; + ASSERT_OK(PersistRocksDBOptions(config_options, options, {"default"}, + {options}, tmp_options_file, fs.get())); + ASSERT_NOK( + WaitForOptionsUpdate(fs, tmp_options_file, options.refresh_options_file)); + + auto dcfh = db_->DefaultColumnFamily(); + ColumnFamilyDescriptor dcd; + ASSERT_OK(dcfh->GetDescriptor(&dcd)); + ASSERT_EQ(dcd.options.comparator, BytewiseComparator()); +} + +TEST_F(DBOptionsTest, RefreshOptionsBadFile) { + Options options = CurrentOptions(); + auto fs = options.env->GetFileSystem(); + options.create_if_missing = true; + options.refresh_options_sec = 1; + options.refresh_options_file = dbname_ + "/Options.new"; + std::string tmp_options_file = dbname_ + "/Options.tmp"; + ASSERT_OK(TryReopen(options)); + + SyncPoint::GetInstance()->LoadDependency( + {{"DBImpl::RefreshOptions::Complete", "DBOptionsTest::WaitForUpdates"}}); + SyncPoint::GetInstance()->SetCallBack("DBImpl::RefreshOptions::Parse", + [&](void* arg) { + auto s = static_cast(arg); + ASSERT_NOK(*s); + }); + SyncPoint::GetInstance()->EnableProcessing(); + // Test with a file that is not an options file + ASSERT_OK(CreateFile(fs, tmp_options_file, "fred", false)); + ASSERT_NOK( + WaitForOptionsUpdate(fs, tmp_options_file, options.refresh_options_file)); + + // Test with a file that contains no DBOptions section + ASSERT_OK( + CreateFile(fs, tmp_options_file, "[CFOptions \"default\"]\n", false)); + ASSERT_NOK( + WaitForOptionsUpdate(fs, tmp_options_file, options.refresh_options_file)); + + // Test with a file that contains no ColumnFamilyOptions section + ASSERT_OK(CreateFile(fs, tmp_options_file, "[DBOptions]\n", false)); + ASSERT_NOK( + WaitForOptionsUpdate(fs, tmp_options_file, options.refresh_options_file)); + + // Test with a file that contains no default ColumnFamilyOptions section + ASSERT_OK(CreateFile(fs, tmp_options_file, + "[DBOptions]\n" + "[CFOptions \"unknown\"]\n", + false)); + ASSERT_NOK( + WaitForOptionsUpdate(fs, tmp_options_file, options.refresh_options_file)); + + // Test what happens if the refresh_options_file is a directory, not a file + bool exists = false; + SyncPoint::GetInstance()->SetCallBack("DBImpl::RefreshOptions::FileExists", + [&](void* /*arg*/) { exists = true; }); + + ASSERT_OK(fs->CreateDir(options.refresh_options_file, IOOptions(), nullptr)); + TEST_SYNC_POINT("DBOptionsTest::WaitForUpdates"); + ASSERT_TRUE(exists); + ASSERT_OK(fs->FileExists(options.refresh_options_file, IOOptions(), nullptr)); + ASSERT_OK(fs->DeleteDir(options.refresh_options_file, IOOptions(), nullptr)); +} + +TEST_F(DBOptionsTest, RefreshOptionsUnknown) { + Options options = CurrentOptions(); + auto fs = options.env->GetFileSystem(); + options.create_if_missing = true; + options.refresh_options_sec = 1; + options.refresh_options_file = dbname_ + "/Options.new"; + std::string tmp_options_file = dbname_ + "/Options.tmp"; + ASSERT_OK(TryReopen(options)); + SyncPoint::GetInstance()->LoadDependency( + {{"DBImpl::RefreshOptions::Complete", "DBOptionsTest::WaitForUpdates"}}); + SyncPoint::GetInstance()->SetCallBack("DBImpl::RefreshOptions::SetDBOptions", + [&](void* arg) { + auto s = static_cast(arg); + ASSERT_NOK(*s); + }); + SyncPoint::GetInstance()->SetCallBack("DBImpl::RefreshOptions::SetCFOptions", + [&](void* arg) { + auto s = static_cast(arg); + ASSERT_NOK(*s); + }); + SyncPoint::GetInstance()->EnableProcessing(); + // Test with a file that contains a bad DBOptions value + ASSERT_OK(CreateFile(fs, tmp_options_file, + "[DBOptions]\n" + "unknown = value\n" + "[CFOptions \"default\"]\n", + false)); + ASSERT_NOK( + WaitForOptionsUpdate(fs, tmp_options_file, options.refresh_options_file)); + + // Test with a file with a bad ColumnFamilyOptions + ASSERT_OK(CreateFile(fs, tmp_options_file, + "[DBOptions]\n" + "[CFOptions \"default\"]\n" + "unknown = value\n", + false)); + ASSERT_NOK( + WaitForOptionsUpdate(fs, tmp_options_file, options.refresh_options_file)); +} TEST_F(DBOptionsTest, BottommostCompressionOptsWithFallbackType) { // Verify the bottommost compression options still take effect even when the diff --git a/db/periodic_task_scheduler.cc b/db/periodic_task_scheduler.cc index 1306f45da6..e2aef8e924 100644 --- a/db/periodic_task_scheduler.cc +++ b/db/periodic_task_scheduler.cc @@ -26,6 +26,7 @@ static const std::map kDefaultPeriodSeconds = { {PeriodicTaskType::kPersistStats, kInvalidPeriodSec}, {PeriodicTaskType::kFlushInfoLog, 10}, {PeriodicTaskType::kRecordSeqnoTime, kInvalidPeriodSec}, + {PeriodicTaskType::kRefreshOptions, kInvalidPeriodSec}, }; static const std::map kPeriodicTaskTypeNames = { @@ -33,6 +34,7 @@ static const std::map kPeriodicTaskTypeNames = { {PeriodicTaskType::kPersistStats, "pst_st"}, {PeriodicTaskType::kFlushInfoLog, "flush_info_log"}, {PeriodicTaskType::kRecordSeqnoTime, "record_seq_time"}, + {PeriodicTaskType::kRefreshOptions, "refresh_options"}, }; Status PeriodicTaskScheduler::Register(PeriodicTaskType task_type, diff --git a/db/periodic_task_scheduler.h b/db/periodic_task_scheduler.h index 4d129a6797..6cc301fd38 100644 --- a/db/periodic_task_scheduler.h +++ b/db/periodic_task_scheduler.h @@ -22,6 +22,7 @@ enum class PeriodicTaskType : uint8_t { kPersistStats, kFlushInfoLog, kRecordSeqnoTime, + kRefreshOptions, kMax, }; diff --git a/db/periodic_task_scheduler_test.cc b/db/periodic_task_scheduler_test.cc index c1205bcf61..ab6acd311a 100644 --- a/db/periodic_task_scheduler_test.cc +++ b/db/periodic_task_scheduler_test.cc @@ -41,6 +41,7 @@ TEST_F(PeriodicTaskSchedulerTest, Basic) { Options options; options.stats_dump_period_sec = kPeriodSec; options.stats_persist_period_sec = kPeriodSec; + options.refresh_options_sec = 0; options.create_if_missing = true; options.env = mock_env_.get(); @@ -129,6 +130,7 @@ TEST_F(PeriodicTaskSchedulerTest, MultiInstances) { Options options; options.stats_dump_period_sec = kPeriodSec; options.stats_persist_period_sec = kPeriodSec; + options.refresh_options_sec = 0; options.create_if_missing = true; options.env = mock_env_.get(); diff --git a/db_stress_tool/db_stress_common.h b/db_stress_tool/db_stress_common.h index 0ec01254e7..9cfb4c1ecb 100644 --- a/db_stress_tool/db_stress_common.h +++ b/db_stress_tool/db_stress_common.h @@ -323,6 +323,8 @@ DECLARE_bool(two_write_queues); DECLARE_bool(use_only_the_last_commit_time_batch_for_recovery); DECLARE_uint64(wp_snapshot_cache_bits); DECLARE_uint64(wp_commit_cache_bits); +DECLARE_int32(refresh_options_sec); +DECLARE_string(refresh_options_file); DECLARE_bool(adaptive_readahead); DECLARE_bool(async_io); diff --git a/db_stress_tool/db_stress_gflags.cc b/db_stress_tool/db_stress_gflags.cc index a1ea4d4161..a71591d92a 100644 --- a/db_stress_tool/db_stress_gflags.cc +++ b/db_stress_tool/db_stress_gflags.cc @@ -1067,6 +1067,11 @@ DEFINE_uint64( DEFINE_uint64(wp_commit_cache_bits, 23ull, "Number of bits to represent write-prepared transaction db's " "commit cache. Default: 23 (8M entries)"); +DEFINE_int32( + refresh_options_sec, 0, + "Frequency (in secs) to look for a new options file (off by default)"); +DEFINE_string(refresh_options_file, "", + "File in which to look for new options"); DEFINE_bool(adaptive_readahead, false, "Carry forward internal auto readahead size from one file to next " diff --git a/db_stress_tool/db_stress_test_base.cc b/db_stress_tool/db_stress_test_base.cc index 2d9664b50b..8f026c1e7a 100644 --- a/db_stress_tool/db_stress_test_base.cc +++ b/db_stress_tool/db_stress_test_base.cc @@ -3297,6 +3297,10 @@ void InitializeOptionsFromFlags( options.memtable_protection_bytes_per_key = FLAGS_memtable_protection_bytes_per_key; options.block_protection_bytes_per_key = FLAGS_block_protection_bytes_per_key; +#ifndef ROCKSDB_LITE + options.refresh_options_sec = FLAGS_refresh_options_sec; + options.refresh_options_file = FLAGS_refresh_options_file; +#endif // ROCKSDB_LITE options.use_dynamic_delay = FLAGS_use_dynamic_delay; // Integrated BlobDB diff --git a/include/rocksdb/options.h b/include/rocksdb/options.h index 58aad862b1..9d1130fd13 100644 --- a/include/rocksdb/options.h +++ b/include/rocksdb/options.h @@ -1438,6 +1438,14 @@ struct DBOptions { // of the contract leads to undefined behaviors with high possibility of data // inconsistency, e.g. deleted old data become visible again, etc. bool enforce_single_del_contracts = true; + + // If non-zero, a task will be started to check for a new + // "refresh_options_file" If found, the refresh task will update the mutable + // options from the settings in this file + // Defaults to check once per hour. Set to 0 to disable the task. + // Not supported in ROCKSDB_LITE mode! + unsigned int refresh_options_sec = 60 * 60; + std::string refresh_options_file; }; // Options to control the behavior of a database (passed to DB::Open) diff --git a/options/db_options.cc b/options/db_options.cc index afe6141f2e..cc6af358a8 100644 --- a/options/db_options.cc +++ b/options/db_options.cc @@ -102,6 +102,14 @@ static std::unordered_map {offsetof(struct MutableDBOptions, stats_persist_period_sec), OptionType::kUInt, OptionVerificationType::kNormal, OptionTypeFlags::kMutable}}, + {"refresh_options_sec", + {offsetof(struct MutableDBOptions, refresh_options_sec), + OptionType::kUInt, OptionVerificationType::kNormal, + OptionTypeFlags::kMutable}}, + {"refresh_options_file", + {offsetof(struct MutableDBOptions, refresh_options_file), + OptionType::kString, OptionVerificationType::kNormal, + OptionTypeFlags::kMutable}}, {"stats_history_buffer_size", {offsetof(struct MutableDBOptions, stats_history_buffer_size), OptionType::kSizeT, OptionVerificationType::kNormal, @@ -991,6 +999,7 @@ MutableDBOptions::MutableDBOptions() delete_obsolete_files_period_micros(6ULL * 60 * 60 * 1000000), stats_dump_period_sec(600), stats_persist_period_sec(600), + refresh_options_sec(0), stats_history_buffer_size(1024 * 1024), max_open_files(-1), bytes_per_sync(0), @@ -1011,6 +1020,8 @@ MutableDBOptions::MutableDBOptions(const DBOptions& options) options.delete_obsolete_files_period_micros), stats_dump_period_sec(options.stats_dump_period_sec), stats_persist_period_sec(options.stats_persist_period_sec), + refresh_options_sec(options.refresh_options_sec), + refresh_options_file(options.refresh_options_file), stats_history_buffer_size(options.stats_history_buffer_size), max_open_files(options.max_open_files), bytes_per_sync(options.bytes_per_sync), @@ -1042,6 +1053,12 @@ void MutableDBOptions::Dump(Logger* log) const { stats_dump_period_sec); ROCKS_LOG_HEADER(log, " Options.stats_persist_period_sec: %d", stats_persist_period_sec); + ROCKS_LOG_HEADER(log, " Options.refresh_options_sec: %d", + refresh_options_sec); + if (refresh_options_sec > 0 && !refresh_options_file.empty()) { + ROCKS_LOG_HEADER(log, " Options.refresh_options_file: %s", + refresh_options_file.c_str()); + } ROCKS_LOG_HEADER( log, " Options.stats_history_buffer_size: %" ROCKSDB_PRIszt, diff --git a/options/db_options.h b/options/db_options.h index 4753acb222..6d13c60629 100644 --- a/options/db_options.h +++ b/options/db_options.h @@ -130,6 +130,8 @@ struct MutableDBOptions { uint64_t delete_obsolete_files_period_micros; unsigned int stats_dump_period_sec; unsigned int stats_persist_period_sec; + unsigned int refresh_options_sec; + std::string refresh_options_file; size_t stats_history_buffer_size; int max_open_files; uint64_t bytes_per_sync; diff --git a/options/options_helper.cc b/options/options_helper.cc index 9f7760cebd..3ded15e2e5 100644 --- a/options/options_helper.cc +++ b/options/options_helper.cc @@ -179,6 +179,8 @@ DBOptions BuildDBOptions(const ImmutableDBOptions& immutable_db_options, options.lowest_used_cache_tier = immutable_db_options.lowest_used_cache_tier; options.enforce_single_del_contracts = immutable_db_options.enforce_single_del_contracts; + options.refresh_options_sec = mutable_db_options.refresh_options_sec; + options.refresh_options_file = mutable_db_options.refresh_options_file; return options; } diff --git a/options/options_settable_test.cc b/options/options_settable_test.cc index 42fa86d4cf..d941e2aee9 100644 --- a/options/options_settable_test.cc +++ b/options/options_settable_test.cc @@ -252,6 +252,7 @@ TEST_F(OptionsSettableTest, DBOptionsAllFieldsSettable) { sizeof(FileTypeSet)}, {offsetof(struct DBOptions, compaction_service), sizeof(std::shared_ptr)}, + {offsetof(struct DBOptions, refresh_options_file), sizeof(std::string)}, }; char* options_ptr = new char[sizeof(DBOptions)]; @@ -366,6 +367,8 @@ TEST_F(OptionsSettableTest, DBOptionsAllFieldsSettable) { "lowest_used_cache_tier=kNonVolatileBlockTier;" "allow_data_in_errors=false;" "enforce_single_del_contracts=false;" + "refresh_options_sec=0;" + "refresh_options_file=Options.new;" "use_dynamic_delay=true", new_options)); diff --git a/tools/db_bench_tool.cc b/tools/db_bench_tool.cc index ccb44fd074..e596c975fd 100644 --- a/tools/db_bench_tool.cc +++ b/tools/db_bench_tool.cc @@ -1489,6 +1489,12 @@ DEFINE_int32(table_cache_numshardbits, 4, ""); DEFINE_string(filter_uri, "", "URI for registry FilterPolicy"); +DEFINE_int32( + refresh_options_sec, 0, + "Frequency (in secs) to look for a new options file (off by default)"); +DEFINE_string(refresh_options_file, "", + "File in which to look for new options"); + DEFINE_string(env_uri, "", "URI for registry Env lookup. Mutually exclusive with --fs_uri"); DEFINE_string(fs_uri, "", @@ -4495,6 +4501,8 @@ class Benchmark { FLAGS_use_direct_io_for_flush_and_compaction; options.manual_wal_flush = FLAGS_manual_wal_flush; options.wal_compression = FLAGS_wal_compression_e; + options.refresh_options_sec = FLAGS_refresh_options_sec; + options.refresh_options_file = FLAGS_refresh_options_file; options.ttl = FLAGS_fifo_compaction_ttl; options.compaction_options_fifo = CompactionOptionsFIFO( FLAGS_fifo_compaction_max_table_files_size_mb * 1024 * 1024,