Skip to content

Commit cbee4c1

Browse files
jimwwalkerdaverigby
authored andcommitted
MB-43199: Run compaction with a config representing multiple requests
If multiple compaction requests occurred before the task runs, the config of the last schedule request is what will be used in the run. For example a user may issue a manual compaction (via cbepctl) which requests all deletes are dropped, whilst ns_server requests a normal compaction. The user's command may return success, yet a compaction with drop_deletes=true may never of executed. Prior to MB-41782 the user's request would of been honoured because separate compactions would of been scheduled. This commit uses a merge function on the CompactionConfig so that all schedules 'merge' the requested config into the scheduled task's config. When the CompactionTask runs it now has a config which respects all requests. Change-Id: I6e2d373e0c6f36dd946db06c3d6953a7481c1eae Reviewed-on: http://review.couchbase.org/c/kv_engine/+/143381 Tested-by: Build Bot <[email protected]> Reviewed-by: Dave Rigby <[email protected]>
1 parent 7b6cf23 commit cbee4c1

File tree

4 files changed

+104
-6
lines changed

4 files changed

+104
-6
lines changed

engines/ep/src/kvstore.cc

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -622,9 +622,39 @@ bool IORequest::isDelete() const {
622622
return item->isDeleted() && !item->isPending();
623623
}
624624

625+
CompactionConfig::CompactionConfig(CompactionConfig&& other) {
626+
*this = std::move(other);
627+
}
628+
629+
CompactionConfig& CompactionConfig::operator=(CompactionConfig&& other) {
630+
if (*this != other) {
631+
purge_before_ts = other.purge_before_ts;
632+
purge_before_seq = other.purge_before_seq;
633+
drop_deletes = other.drop_deletes;
634+
retain_erroneous_tombstones = other.retain_erroneous_tombstones;
635+
other = CompactionConfig{}; // back to default values
636+
}
637+
return *this;
638+
}
639+
625640
bool CompactionConfig::operator==(const CompactionConfig& other) const {
626641
return purge_before_ts == other.purge_before_ts &&
627642
purge_before_seq == other.purge_before_seq &&
628643
drop_deletes == other.drop_deletes &&
629644
retain_erroneous_tombstones == other.retain_erroneous_tombstones;
630645
}
646+
647+
// Merge other into this.
648+
// drop_deletes and retain_erroneous_tombstones are 'sticky' once true
649+
// they should stay true for all subsequent merges.
650+
// purge_before_ts and purge_before_seq should be the largest value out of
651+
// the current and the input
652+
void CompactionConfig::merge(const CompactionConfig& other) {
653+
retain_erroneous_tombstones =
654+
retain_erroneous_tombstones || other.retain_erroneous_tombstones;
655+
drop_deletes = drop_deletes || other.drop_deletes;
656+
purge_before_ts =
657+
std::max<uint64_t>(purge_before_ts, other.purge_before_ts);
658+
purge_before_seq =
659+
std::max<uint64_t>(purge_before_seq, other.purge_before_seq);
660+
}

engines/ep/src/kvstore.h

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,14 +98,44 @@ struct CompactionStats {
9898
};
9999

100100
struct CompactionConfig {
101-
uint64_t purge_before_ts = 0;
102-
uint64_t purge_before_seq = 0;
103-
uint8_t drop_deletes = 0;
104-
bool retain_erroneous_tombstones = false;
101+
CompactionConfig() = default;
102+
CompactionConfig(uint64_t purge_before_ts,
103+
uint64_t purge_before_seq,
104+
bool drop_deletes,
105+
bool retain_erroneous_tombstones)
106+
: purge_before_ts(purge_before_ts),
107+
purge_before_seq(purge_before_seq),
108+
drop_deletes(drop_deletes),
109+
retain_erroneous_tombstones(retain_erroneous_tombstones) {
110+
}
111+
112+
CompactionConfig(const CompactionConfig&) = default;
113+
CompactionConfig& operator=(const CompactionConfig& other) = default;
114+
115+
/// Move will value copy from 'other' and leave 'other' default constructed
116+
CompactionConfig(CompactionConfig&& other);
117+
/// Move will value copy from 'other' and leave 'other' default constructed
118+
CompactionConfig& operator=(CompactionConfig&& other);
119+
105120
bool operator==(const CompactionConfig& c) const;
106121
bool operator!=(const CompactionConfig& c) const {
107122
return !(*this == c);
108123
}
124+
125+
/**
126+
* Merge 'other' into this instance. Merge results in this object being
127+
* representative of the current config and the other config.
128+
*
129+
* - drop_deletes/retain_erroneous_tombstones are 'sticky', once true
130+
* they will remain true.
131+
* - purge_before_ts and purge_before_seq become the max of this vs other
132+
*/
133+
void merge(const CompactionConfig& other);
134+
135+
uint64_t purge_before_ts = 0;
136+
uint64_t purge_before_seq = 0;
137+
bool drop_deletes = false;
138+
bool retain_erroneous_tombstones = false;
109139
};
110140

111141
struct CompactionContext {

engines/ep/src/tasks.cc

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,14 +120,18 @@ std::pair<CompactionConfig, std::vector<const void*>>
120120
CompactTask::preDoCompact() {
121121
auto lockedState = compaction.wlock();
122122
lockedState->rescheduleRequired = false;
123-
return {lockedState->config, std::move(lockedState->cookiesWaiting)};
123+
// config and cookiesWaiting are moved out to the task run loop, any
124+
// subsequent schedule now operates on the 'empty' objects whilst run
125+
// gets on with compacting using the returned values.
126+
return {std::move(lockedState->config),
127+
std::move(lockedState->cookiesWaiting)};
124128
}
125129

126130
void CompactTask::runCompactionWithConfig(
127131
std::optional<CompactionConfig> config, const void* cookie) {
128132
auto lockedState = compaction.wlock();
129133
if (config) {
130-
lockedState->config = config.value();
134+
lockedState->config.merge(config.value());
131135
}
132136
if (cookie) {
133137
lockedState->cookiesWaiting.push_back(cookie);

engines/ep/tests/module_tests/evp_store_test.cc

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1671,6 +1671,40 @@ TEST_P(EPBucketTestNoRocksDb, ScheduleCompactionWithNewConfig) {
16711671
EXPECT_FALSE(task);
16721672
}
16731673

1674+
// Test that scheduling compaction means the task which runs, runs with a merged
1675+
// configuration that meets all requests.
1676+
TEST_P(EPBucketTestNoRocksDb, ScheduleCompactionAndMergeNewConfig) {
1677+
auto* mockEPBucket = dynamic_cast<MockEPBucket*>(engine->getKVBucket());
1678+
// Array of configs to use for each call to schedule, it should result
1679+
// in a config for the run which is the 'merge of all'.
1680+
std::array<CompactionConfig, 5> configs = {{{0, 0, false, false},
1681+
{0, 1000, false, false},
1682+
{1000, 0, false, false},
1683+
{9, 900, false, true},
1684+
{9, 900, true, false}}};
1685+
1686+
for (const auto& config : configs) {
1687+
EXPECT_EQ(ENGINE_EWOULDBLOCK,
1688+
mockEPBucket->scheduleCompaction(
1689+
vbid, config, nullptr, std::chrono::seconds(0)));
1690+
}
1691+
auto task = mockEPBucket->getCompactionTask(vbid);
1692+
ASSERT_TRUE(task);
1693+
auto finalConfig = task->getCurrentConfig();
1694+
1695+
// Merged values, max for 'purge_before_' and true for the bools
1696+
EXPECT_EQ(1000, finalConfig.purge_before_ts);
1697+
EXPECT_EQ(1000, finalConfig.purge_before_seq);
1698+
EXPECT_TRUE(finalConfig.drop_deletes);
1699+
EXPECT_TRUE(finalConfig.retain_erroneous_tombstones);
1700+
1701+
// no reschedule needed
1702+
EXPECT_FALSE(task->run());
1703+
1704+
task = mockEPBucket->getCompactionTask(vbid);
1705+
EXPECT_FALSE(task);
1706+
}
1707+
16741708
// Test that scheduling compaction when a task is already running the task
16751709
// will reschedule *and* the reschedule picks up the new config.
16761710
TEST_P(EPBucketTestNoRocksDb, ScheduleCompactionReschedules) {

0 commit comments

Comments
 (0)