mirror of
https://github.com/facebook/rocksdb.git
synced 2026-07-07 14:47:40 +08:00
Add a new periodic task to trigger compactions (#13736)
Summary: address an existing limitation on compaction triggering mechanism that relies on events like flush/compaction/SetOptions. This is important for periodic compactions where files can become eligible without any of these events. The periodic task now runs every 12 hours and check CFs that enables `periodic_compaction_second` (TBD if we want to expand to all CFs) for eligible compactions. Some of the periodic tasks probably don't need to run immediately after Register(). I'm keeping the existing behavior for now for patch release and to makes tests happy. Pull Request resolved: https://github.com/facebook/rocksdb/pull/13736 Test Plan: - new unit test that fails before this change. - ran crash test for hours with the periodic task running every 5 seconds: `python3 ./tools/db_crashtest.py blackbox --test_batches_snapshot=0 --periodic_compaction_seconds=10` Reviewed By: pdillinger Differential Revision: D77460715 Pulled By: cbi42 fbshipit-source-id: 00f61502753185e76830c9ed44c5ccc4f4f16bfa
This commit is contained in:
committed by
Facebook GitHub Bot
parent
3cc76aae83
commit
4f7d3a0cb2
@@ -1633,6 +1633,8 @@ Status ColumnFamilyData::SetOptions(
|
||||
Status s = GetColumnFamilyOptionsFromMap(config_opts, cf_opts, options_map,
|
||||
&cf_opts);
|
||||
if (s.ok()) {
|
||||
// FIXME: we should call SanitizeOptions() too or consolidate it with
|
||||
// ValidateOptions().
|
||||
s = ValidateOptions(db_opts, cf_opts);
|
||||
}
|
||||
if (s.ok()) {
|
||||
|
||||
@@ -11093,6 +11093,59 @@ TEST_F(DBCompactionTest, RecordNewestKeyTimeForTtlCompaction) {
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0), 0);
|
||||
}
|
||||
|
||||
class PeriodicCompactionListener : public EventListener {
|
||||
public:
|
||||
explicit PeriodicCompactionListener() {}
|
||||
void OnCompactionBegin(DB* /*db*/, const CompactionJobInfo& ci) override {
|
||||
if (ci.compaction_reason == CompactionReason::kPeriodicCompaction) {
|
||||
++num_periodic_compactions;
|
||||
}
|
||||
}
|
||||
|
||||
std::atomic<int> num_periodic_compactions = 0;
|
||||
};
|
||||
|
||||
TEST_F(DBCompactionTest, PeriodicTask) {
|
||||
// Tests that when no trigger event is fired (flush/compaction/setoptions),
|
||||
// periodic compaction is still triggered by a scheduled periodic function.
|
||||
auto mock_clock = std::make_shared<MockSystemClock>(env_->GetSystemClock());
|
||||
mock_clock->SetCurrentTime(100);
|
||||
mock_clock->InstallTimedWaitFixCallback();
|
||||
auto mock_env = std::make_unique<CompositeEnvWrapper>(env_, mock_clock);
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::StartPeriodicTaskScheduler:Init", [&](void* arg) {
|
||||
auto periodic_task_scheduler_ptr =
|
||||
static_cast<PeriodicTaskScheduler*>(arg);
|
||||
periodic_task_scheduler_ptr->TEST_OverrideTimer(mock_clock.get());
|
||||
});
|
||||
|
||||
Options options;
|
||||
options.env = mock_env.get();
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.statistics = CreateDBStatistics();
|
||||
int kPeriodicCompactionSeconds = 7 * 24 * 60 * 60; // 1 week
|
||||
options.periodic_compaction_seconds = kPeriodicCompactionSeconds;
|
||||
options.num_levels = 50;
|
||||
auto listener = std::make_shared<PeriodicCompactionListener>();
|
||||
options.listeners.push_back(listener);
|
||||
ASSERT_OK(TryReopen(options));
|
||||
|
||||
Random* rnd = Random::GetTLSInstance();
|
||||
for (int k = 0; k < 10; ++k) {
|
||||
ASSERT_OK(Put(Key(k), rnd->RandomString(100)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(db_->CompactRange({}, nullptr, nullptr));
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(49));
|
||||
|
||||
dbfull()->TEST_WaitForPeriodicTaskRun(
|
||||
[&] { mock_clock->MockSleepForSeconds(kPeriodicCompactionSeconds + 1); });
|
||||
ASSERT_OK(db_->WaitForCompact({}));
|
||||
|
||||
ASSERT_EQ(listener->num_periodic_compactions, 1);
|
||||
Close();
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+48
-6
@@ -253,6 +253,9 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname,
|
||||
periodic_task_functions_.emplace(
|
||||
PeriodicTaskType::kRecordSeqnoTime,
|
||||
[this]() { this->RecordSeqnoToTimeMapping(); });
|
||||
periodic_task_functions_.emplace(
|
||||
PeriodicTaskType::kTriggerCompaction,
|
||||
[this]() { this->TriggerPeriodicCompaction(); });
|
||||
|
||||
versions_.reset(new VersionSet(
|
||||
dbname_, &immutable_db_options_, file_options_, table_cache_.get(),
|
||||
@@ -787,7 +790,8 @@ Status DBImpl::StartPeriodicTaskScheduler() {
|
||||
Status s = periodic_task_scheduler_.Register(
|
||||
PeriodicTaskType::kDumpStats,
|
||||
periodic_task_functions_.at(PeriodicTaskType::kDumpStats),
|
||||
mutable_db_options_.stats_dump_period_sec);
|
||||
mutable_db_options_.stats_dump_period_sec,
|
||||
/*run_immediately=*/true);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -796,7 +800,8 @@ Status DBImpl::StartPeriodicTaskScheduler() {
|
||||
Status s = periodic_task_scheduler_.Register(
|
||||
PeriodicTaskType::kPersistStats,
|
||||
periodic_task_functions_.at(PeriodicTaskType::kPersistStats),
|
||||
mutable_db_options_.stats_persist_period_sec);
|
||||
mutable_db_options_.stats_persist_period_sec,
|
||||
/*run_immediately=*/true);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -804,7 +809,15 @@ Status DBImpl::StartPeriodicTaskScheduler() {
|
||||
|
||||
Status s = periodic_task_scheduler_.Register(
|
||||
PeriodicTaskType::kFlushInfoLog,
|
||||
periodic_task_functions_.at(PeriodicTaskType::kFlushInfoLog));
|
||||
periodic_task_functions_.at(PeriodicTaskType::kFlushInfoLog),
|
||||
/*run_immediately=*/true);
|
||||
|
||||
if (s.ok()) {
|
||||
s = periodic_task_scheduler_.Register(
|
||||
PeriodicTaskType::kTriggerCompaction,
|
||||
periodic_task_functions_.at(PeriodicTaskType::kTriggerCompaction),
|
||||
/*run_immediately=*/false);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
@@ -855,7 +868,7 @@ Status DBImpl::RegisterRecordSeqnoTimeWorker() {
|
||||
s = periodic_task_scheduler_.Register(
|
||||
PeriodicTaskType::kRecordSeqnoTime,
|
||||
periodic_task_functions_.at(PeriodicTaskType::kRecordSeqnoTime),
|
||||
seqno_time_cadence);
|
||||
seqno_time_cadence, /*run_immediately=*/true);
|
||||
}
|
||||
|
||||
return s;
|
||||
@@ -1365,7 +1378,7 @@ Status DBImpl::SetDBOptions(
|
||||
s = periodic_task_scheduler_.Register(
|
||||
PeriodicTaskType::kDumpStats,
|
||||
periodic_task_functions_.at(PeriodicTaskType::kDumpStats),
|
||||
new_options.stats_dump_period_sec);
|
||||
new_options.stats_dump_period_sec, /*run_immediately=*/true);
|
||||
}
|
||||
if (new_options.max_total_wal_size !=
|
||||
mutable_db_options_.max_total_wal_size) {
|
||||
@@ -1380,7 +1393,7 @@ Status DBImpl::SetDBOptions(
|
||||
s = periodic_task_scheduler_.Register(
|
||||
PeriodicTaskType::kPersistStats,
|
||||
periodic_task_functions_.at(PeriodicTaskType::kPersistStats),
|
||||
new_options.stats_persist_period_sec);
|
||||
new_options.stats_persist_period_sec, /*run_immediately=*/true);
|
||||
}
|
||||
}
|
||||
mutex_.Lock();
|
||||
@@ -6882,6 +6895,35 @@ void DBImpl::RecordSeqnoToTimeMapping() {
|
||||
sv_context.Clean();
|
||||
}
|
||||
|
||||
void DBImpl::TriggerPeriodicCompaction() {
|
||||
TEST_SYNC_POINT("DBImpl::TriggerPeriodicCompaction:StartRunning");
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"Running the periodic task to trigger compactions.");
|
||||
|
||||
for (ColumnFamilyData* cfd : *versions_->GetColumnFamilySet()) {
|
||||
if (cfd->IsDropped()) {
|
||||
continue;
|
||||
}
|
||||
if (cfd->GetLatestCFOptions().periodic_compaction_seconds &&
|
||||
!cfd->queued_for_compaction()) {
|
||||
cfd->current()->storage_info()->ComputeCompactionScore(
|
||||
cfd->ioptions(), cfd->GetLatestMutableCFOptions());
|
||||
EnqueuePendingCompaction(cfd);
|
||||
if (cfd->queued_for_compaction()) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"Periodic task to trigger compaction queued Column "
|
||||
"family [%s] for compaction.",
|
||||
cfd->GetName().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
bg_cv_.SignalAll();
|
||||
}
|
||||
}
|
||||
|
||||
void DBImpl::TrackOrUntrackFiles(
|
||||
const std::vector<std::string>& existing_data_files, bool track) {
|
||||
auto sfm = static_cast_with_check<SstFileManagerImpl>(
|
||||
|
||||
@@ -1286,6 +1286,12 @@ class DBImpl : public DB {
|
||||
// For the background timer job
|
||||
void RecordSeqnoToTimeMapping();
|
||||
|
||||
// Compactions rely on an event triggers like flush/compaction/SetOptions.
|
||||
// We need to trigger periodic compactions even when there is no such trigger.
|
||||
// This function checks and schedules available compactions and will run
|
||||
// periodically.
|
||||
void TriggerPeriodicCompaction();
|
||||
|
||||
// REQUIRES: DB mutex held
|
||||
std::pair<SequenceNumber, uint64_t> GetSeqnoToTimeSample() const;
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ static const std::map<PeriodicTaskType, uint64_t> kDefaultPeriodSeconds = {
|
||||
{PeriodicTaskType::kPersistStats, kInvalidPeriodSec},
|
||||
{PeriodicTaskType::kFlushInfoLog, 10},
|
||||
{PeriodicTaskType::kRecordSeqnoTime, kInvalidPeriodSec},
|
||||
{PeriodicTaskType::kTriggerCompaction, 12 * 60 * 60} // 12 hours
|
||||
};
|
||||
|
||||
static const std::map<PeriodicTaskType, std::string> kPeriodicTaskTypeNames = {
|
||||
@@ -33,16 +34,20 @@ static const std::map<PeriodicTaskType, std::string> kPeriodicTaskTypeNames = {
|
||||
{PeriodicTaskType::kPersistStats, "pst_st"},
|
||||
{PeriodicTaskType::kFlushInfoLog, "flush_info_log"},
|
||||
{PeriodicTaskType::kRecordSeqnoTime, "record_seq_time"},
|
||||
{PeriodicTaskType::kTriggerCompaction, "trigger_compaction"},
|
||||
};
|
||||
|
||||
Status PeriodicTaskScheduler::Register(PeriodicTaskType task_type,
|
||||
const PeriodicTaskFunc& fn) {
|
||||
return Register(task_type, fn, kDefaultPeriodSeconds.at(task_type));
|
||||
const PeriodicTaskFunc& fn,
|
||||
bool run_immediately) {
|
||||
return Register(task_type, fn, kDefaultPeriodSeconds.at(task_type),
|
||||
run_immediately);
|
||||
}
|
||||
|
||||
Status PeriodicTaskScheduler::Register(PeriodicTaskType task_type,
|
||||
const PeriodicTaskFunc& fn,
|
||||
uint64_t repeat_period_seconds) {
|
||||
uint64_t repeat_period_seconds,
|
||||
bool run_immediately) {
|
||||
MutexLock l(&timer_mutex);
|
||||
static std::atomic<uint64_t> initial_delay(0);
|
||||
|
||||
@@ -65,10 +70,13 @@ Status PeriodicTaskScheduler::Register(PeriodicTaskType task_type,
|
||||
std::string unique_id =
|
||||
kPeriodicTaskTypeNames.at(task_type) + std::to_string(id_++);
|
||||
|
||||
bool succeeded = timer_->Add(
|
||||
fn, unique_id,
|
||||
(initial_delay.fetch_add(1) % repeat_period_seconds) * kMicrosInSecond,
|
||||
repeat_period_seconds * kMicrosInSecond);
|
||||
uint64_t initial_delay_micros =
|
||||
(initial_delay.fetch_add(1) % repeat_period_seconds) * kMicrosInSecond;
|
||||
if (!run_immediately) {
|
||||
initial_delay_micros += repeat_period_seconds * kMicrosInSecond;
|
||||
}
|
||||
bool succeeded = timer_->Add(fn, unique_id, initial_delay_micros,
|
||||
repeat_period_seconds * kMicrosInSecond);
|
||||
if (!succeeded) {
|
||||
return Status::Aborted("Failed to register periodic task");
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ enum class PeriodicTaskType : uint8_t {
|
||||
kPersistStats,
|
||||
kFlushInfoLog,
|
||||
kRecordSeqnoTime,
|
||||
kTriggerCompaction,
|
||||
kMax,
|
||||
};
|
||||
|
||||
@@ -42,13 +43,16 @@ class PeriodicTaskScheduler {
|
||||
PeriodicTaskScheduler& operator=(PeriodicTaskScheduler&&) = delete;
|
||||
|
||||
// Register a task with its default repeat period. Thread safe call.
|
||||
Status Register(PeriodicTaskType task_type, const PeriodicTaskFunc& fn);
|
||||
// @param run_immediately If true, the task will run soon after it's
|
||||
// scheduled, instead of waiting for the repeat period.
|
||||
Status Register(PeriodicTaskType task_type, const PeriodicTaskFunc& fn,
|
||||
bool run_immediately);
|
||||
|
||||
// Register a task with specified repeat period. 0 is an invalid argument
|
||||
// (kInvalidPeriodSec). To stop the task, please use Unregister().
|
||||
// Thread safe call.
|
||||
Status Register(PeriodicTaskType task_type, const PeriodicTaskFunc& fn,
|
||||
uint64_t repeat_period_seconds);
|
||||
uint64_t repeat_period_seconds, bool run_immediately);
|
||||
|
||||
// Unregister the task. Thread safe call.
|
||||
Status Unregister(PeriodicTaskType task_type);
|
||||
|
||||
@@ -56,6 +56,12 @@ TEST_F(PeriodicTaskSchedulerTest, Basic) {
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::FlushInfoLog:StartRunning",
|
||||
[&](void*) { flush_info_log_counter++; });
|
||||
|
||||
int trigger_compaction_counter = 0;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::TriggerPeriodicCompaction:StartRunning",
|
||||
[&](void*) { trigger_compaction_counter++; });
|
||||
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Reopen(options);
|
||||
@@ -70,7 +76,7 @@ TEST_F(PeriodicTaskSchedulerTest, Basic) {
|
||||
|
||||
const PeriodicTaskScheduler& scheduler =
|
||||
dbfull()->TEST_GetPeriodicTaskScheduler();
|
||||
ASSERT_EQ(3, scheduler.TEST_GetValidTaskNum());
|
||||
ASSERT_EQ((int)PeriodicTaskType::kMax - 1, scheduler.TEST_GetValidTaskNum());
|
||||
|
||||
ASSERT_EQ(1, dump_st_counter);
|
||||
ASSERT_EQ(1, pst_st_counter);
|
||||
@@ -103,14 +109,14 @@ TEST_F(PeriodicTaskSchedulerTest, Basic) {
|
||||
ASSERT_EQ(3, pst_st_counter);
|
||||
ASSERT_EQ(4, flush_info_log_counter);
|
||||
|
||||
ASSERT_EQ(1u, scheduler.TEST_GetValidTaskNum());
|
||||
ASSERT_EQ(2u, scheduler.TEST_GetValidTaskNum());
|
||||
|
||||
// Re-enable one task
|
||||
ASSERT_OK(dbfull()->SetDBOptions({{"stats_dump_period_sec", "5"}}));
|
||||
ASSERT_EQ(5u, dbfull()->GetDBOptions().stats_dump_period_sec);
|
||||
ASSERT_EQ(0u, dbfull()->GetDBOptions().stats_persist_period_sec);
|
||||
|
||||
ASSERT_EQ(2, scheduler.TEST_GetValidTaskNum());
|
||||
ASSERT_EQ(3, scheduler.TEST_GetValidTaskNum());
|
||||
|
||||
dbfull()->TEST_WaitForPeriodicTaskRun(
|
||||
[&] { mock_clock_->MockSleepForSeconds(static_cast<int>(kPeriodSec)); });
|
||||
@@ -118,6 +124,16 @@ TEST_F(PeriodicTaskSchedulerTest, Basic) {
|
||||
ASSERT_EQ(3, pst_st_counter);
|
||||
ASSERT_EQ(5, flush_info_log_counter);
|
||||
|
||||
ASSERT_EQ(0, trigger_compaction_counter);
|
||||
dbfull()->TEST_WaitForPeriodicTaskRun([&] {
|
||||
mock_clock_->MockSleepForSeconds(static_cast<int>(12 * 60 * 60));
|
||||
});
|
||||
ASSERT_EQ(1, trigger_compaction_counter);
|
||||
dbfull()->TEST_WaitForPeriodicTaskRun([&] {
|
||||
mock_clock_->MockSleepForSeconds(static_cast<int>(12 * 60 * 60));
|
||||
});
|
||||
ASSERT_EQ(2, trigger_compaction_counter);
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
@@ -150,7 +166,9 @@ TEST_F(PeriodicTaskSchedulerTest, MultiInstances) {
|
||||
auto dbi = static_cast_with_check<DBImpl>(dbs[kInstanceNum - 1]);
|
||||
|
||||
const PeriodicTaskScheduler& scheduler = dbi->TEST_GetPeriodicTaskScheduler();
|
||||
ASSERT_EQ(kInstanceNum * 3, scheduler.TEST_GetValidTaskNum());
|
||||
// kRecordSeqnoTime is not registered since the feature is not enabled
|
||||
ASSERT_EQ(kInstanceNum * ((int)PeriodicTaskType::kMax - 1),
|
||||
scheduler.TEST_GetValidTaskNum());
|
||||
|
||||
int expected_run = kInstanceNum;
|
||||
dbi->TEST_WaitForPeriodicTaskRun(
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
* RocksDB now triggers eligible compactions every 12 hours when periodic compaction is configured. This solves a limitation of the compaction trigger mechanism, which would only trigger compaction after specific events like flush, compaction, or SetOptions.
|
||||
Reference in New Issue
Block a user