Skip to content

Commit 90eadc1

Browse files
BenHuddlestondaverigby
authored andcommitted
MB-31327: Ephemeral backfill does not respect purge seqno.
There exists a bug in Ephemeral backfill_memory such that we can start a backfill for a startSeqno > purgeSeqno of a given vBucket. This leads to data inconsistencies as we may not replciate a delete. Fix the bug by checking that the purgeSeqno < startSeqno of a backfill when startSeqno != 1. Only fix for buffered memory backfill as the non-buffered backfill is not used and will be deleted in master. Change-Id: Ia91d6d1e82fcfb7ea2d6463c76a531ea0d8aa182 Reviewed-on: http://review.couchbase.org/99713 Well-Formed: Build Bot <[email protected]> Reviewed-by: David Haikney <[email protected]> Reviewed-by: Dave Rigby <[email protected]> Tested-by: Build Bot <[email protected]>
1 parent 0540f14 commit 90eadc1

File tree

6 files changed

+183
-28
lines changed

6 files changed

+183
-28
lines changed

engines/ep/src/dcp/backfill_memory.cc

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,22 @@ backfill_status_t DCPBackfillMemoryBuffered::create() {
204204
return backfill_snooze;
205205
}
206206

207+
/* Check startSeqno against the purge-seqno of the vb.
208+
* If the startSeqno != 1 (a 0 to n request) then startSeqno must be
209+
* greater than purgeSeqno. */
210+
if (startSeqno != 1 && (startSeqno <= evb->getPurgeSeqno())) {
211+
LOG(EXTENSION_LOG_WARNING,
212+
"DCPBackfillMemoryBuffered::create(): "
213+
"(vb:%" PRIu16
214+
") running backfill failed because the startSeqno:%" PRIu64
215+
" is < purgeSeqno:%" PRIu64,
216+
getVBucketId(),
217+
startSeqno,
218+
evb->getPurgeSeqno());
219+
stream->setDead(END_STREAM_ROLLBACK);
220+
return backfill_finished;
221+
}
222+
207223
/* Advance the cursor till start, mark snapshot and update backfill
208224
remaining count */
209225
while (rangeItr.curr() != rangeItr.end()) {

engines/ep/tests/mock/mock_stream.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,8 @@ class MockActiveStream : public ActiveStream {
158158

159159
/// A callback to allow tests to inject code before we access the checkpoint
160160
std::function<void()> preGetOutstandingItemsCallback = [] { return; };
161+
162+
bool isDead() { return ActiveStream::getState() == StreamState::Dead; };
161163
};
162164

163165
/**

engines/ep/tests/module_tests/dcp_test.cc

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1119,6 +1119,23 @@ TEST_P(StreamTest, backfillGetsNoItems) {
11191119
}
11201120
}
11211121

1122+
TEST_P(StreamTest, bufferedMemoryBackfillPurgeGreaterThanStart) {
1123+
if (engine->getConfiguration().getBucketType() == "ephemeral") {
1124+
setup_dcp_stream(0, IncludeValue::No, IncludeXattrs::No);
1125+
auto evb = std::shared_ptr<EphemeralVBucket>(
1126+
std::dynamic_pointer_cast<EphemeralVBucket>(vb0));
1127+
1128+
// Force the purgeSeqno because it's easier than creating and
1129+
// deleting items
1130+
evb->setPurgeSeqno(3);
1131+
1132+
// Backfill with start != 1 and start != end and start < purge
1133+
DCPBackfillMemoryBuffered dcpbfm (evb, stream, 2, 4);
1134+
dcpbfm.run();
1135+
EXPECT_TRUE(stream->isDead());
1136+
}
1137+
}
1138+
11221139
/* Regression test for MB-17766 - ensure that when an ActiveStream is preparing
11231140
* queued items to be sent out via a DCP consumer, that nextCheckpointItem()
11241141
* doesn't incorrectly return false (meaning that there are no more checkpoint

engines/ep/tests/module_tests/evp_store_single_threaded_test.cc

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include "bgfetcher.h"
2727
#include "checkpoint.h"
2828
#include "dcp/dcpconnmap.h"
29+
#include "ephemeral_tombstone_purger.h"
2930
#include "ep_time.h"
3031
#include "evp_store_test.h"
3132
#include "failover-table.h"
@@ -42,6 +43,7 @@
4243
#include <xattr/utils.h>
4344

4445
#include <thread>
46+
#include <engines/ep/src/ephemeral_vb.h>
4547

4648
ProcessClock::time_point SingleThreadedKVBucketTest::runNextTask(
4749
TaskQueue& taskQ, const std::string& expectedTaskName) {
@@ -290,6 +292,111 @@ void SingleThreadedKVBucketTest::runCompaction(uint64_t purgeBeforeTime,
290292
"Compact DB file 0");
291293
}
292294

295+
/*
296+
* MB-31175
297+
* The following test checks to see that when we call handleSlowStream in an
298+
* in memory state and drop the cursor/schedule a backfill as a result, the
299+
* resulting backfill checks the purgeSeqno and tells the stream to rollback
300+
* if purgeSeqno > startSeqno.
301+
*/
302+
TEST_P(STParameterizedBucketTest, SlowStreamBackfillPurgeSeqnoCheck) {
303+
// Make vbucket active.
304+
setVBucketStateAndRunPersistTask(vbid, vbucket_state_active);
305+
auto vb = store->getVBuckets().getBucket(vbid);
306+
ASSERT_TRUE(vb.get());
307+
308+
// Store two items
309+
std::array<std::string, 2> initialKeys = {{"k1", "k2"}};
310+
for (const auto& key : initialKeys) {
311+
store_item(vbid, makeStoredDocKey(key), key);
312+
}
313+
flushVBucketToDiskIfPersistent(vbid, initialKeys.size());
314+
315+
// Delete the items so that we can advance the purgeSeqno using
316+
// compaction later
317+
for (const auto& key : initialKeys) {
318+
delete_item(vbid, makeStoredDocKey(key));
319+
}
320+
flushVBucketToDiskIfPersistent(vbid, initialKeys.size());
321+
322+
auto& ckpt_mgr = *vb->checkpointManager;
323+
324+
// Create a Mock Dcp producer
325+
// Create the Mock Active Stream with a startSeqno of 1
326+
// as a startSeqno is always valid
327+
auto producer = std::make_shared<MockDcpProducer>(
328+
*engine,
329+
cookie,
330+
"test_producer",
331+
/*notifyOnly*/ false,
332+
cb::const_byte_buffer() /*no json*/);
333+
// Create a Mock Active Stream
334+
auto mock_stream = std::make_shared<MockActiveStream>(
335+
static_cast<EventuallyPersistentEngine*>(engine.get()),
336+
producer,
337+
/*flags*/ 0,
338+
/*opaque*/ 0,
339+
*vb,
340+
/*st_seqno*/ 1,
341+
/*en_seqno*/ ~0,
342+
/*vb_uuid*/ 0xabcd,
343+
/*snap_start_seqno*/ 0,
344+
/*snap_end_seqno*/ ~0,
345+
IncludeValue::Yes,
346+
IncludeXattrs::Yes);
347+
348+
producer->createCheckpointProcessorTask();
349+
producer->scheduleCheckpointProcessorTask();
350+
351+
mock_stream->transitionStateToBackfilling();
352+
ASSERT_TRUE(mock_stream->isInMemory())
353+
<< "stream state should have transitioned to InMemory";
354+
355+
// Check number of expected cursors (might not have persistence cursor)
356+
int expectedCursors = persistent() ? 2 : 1;
357+
EXPECT_EQ(expectedCursors, ckpt_mgr.getNumOfCursors());
358+
359+
EXPECT_TRUE(mock_stream->handleSlowStream());
360+
EXPECT_TRUE(mock_stream->public_getPendingBackfill());
361+
362+
// Might not have persistence cursor
363+
expectedCursors = persistent() ? 1 : 0;
364+
EXPECT_EQ(expectedCursors, ckpt_mgr.getNumOfCursors())
365+
<< "stream cursor should have been dropped";
366+
367+
// This will schedule the backfill
368+
mock_stream->transitionStateToBackfilling();
369+
ASSERT_TRUE(mock_stream->isBackfilling());
370+
371+
// Advance the purgeSeqno
372+
if (persistent()) {
373+
runCompaction(~0, 3);
374+
} else {
375+
EphemeralVBucket::HTTombstonePurger purger(0);
376+
auto vbptr = store->getVBucket(vbid);
377+
EphemeralVBucket* evb = dynamic_cast<EphemeralVBucket*>(vbptr.get());
378+
purger.setCurrentVBucket(*evb);
379+
evb->ht.visit(purger);
380+
evb->purgeStaleItems();
381+
}
382+
383+
ASSERT_EQ(3, vb->getPurgeSeqno());
384+
385+
// Run the backfill we scheduled when we transitioned to the backfilling
386+
// state
387+
auto& bfm = producer->getBFM();
388+
bfm.backfill();
389+
390+
// The backfill should have set the stream state to dead because
391+
// purgeSeqno > startSeqno
392+
EXPECT_TRUE(mock_stream->isDead());
393+
394+
// Stop Producer checkpoint processor task
395+
producer->cancelCheckpointCreatorTask();
396+
397+
cancelAndPurgeTasks();
398+
}
399+
293400
/*
294401
* The following test checks to see if we call handleSlowStream when in a
295402
* backfilling state, but the backfillTask is not running, we
@@ -3380,3 +3487,13 @@ INSTANTIATE_TEST_CASE_P(XattrCompressedTest,
33803487
XattrCompressedTest,
33813488
::testing::Combine(::testing::Bool(),
33823489
::testing::Bool()), );
3490+
3491+
static auto allConfigValues = ::testing::Values(
3492+
std::make_tuple(std::string("ephemeral"), std::string("auto_delete")),
3493+
std::make_tuple(std::string("ephemeral"), std::string("fail_new_data")),
3494+
std::make_tuple(std::string("persistent"), std::string{}));
3495+
3496+
// Test cases which run for persistent and ephemeral buckets
3497+
INSTANTIATE_TEST_CASE_P(EphemeralOrPersistent,
3498+
STParameterizedBucketTest,
3499+
allConfigValues, );

engines/ep/tests/module_tests/evp_store_single_threaded_test.h

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,3 +160,34 @@ class SingleThreadedEPBucketTest : public SingleThreadedKVBucketTest {
160160
return dynamic_cast<EPBucket&>(*store);
161161
}
162162
};
163+
164+
/**
165+
* Test fixture for KVBucket tests running in single-threaded mode.
166+
*
167+
* Parameterised on a pair of:
168+
* - bucket_type (ephemeral of persistent)
169+
* - ephemeral_full_policy (for specifying ephemeral auto-delete & fail_new_data
170+
* eviction modes). If empty then unused (persistent buckets).
171+
*/
172+
class STParameterizedBucketTest
173+
: public SingleThreadedEPBucketTest,
174+
public ::testing::WithParamInterface<
175+
std::tuple<std::string, std::string>> {
176+
public:
177+
bool persistent() {
178+
return std::get<0>(GetParam()) == "persistent";
179+
}
180+
181+
protected:
182+
void SetUp() {
183+
if (!config_string.empty()) {
184+
config_string += ";";
185+
}
186+
config_string += "bucket_type=" + std::get<0>(GetParam());
187+
auto ephFullPolicy = std::get<1>(GetParam());
188+
if (!ephFullPolicy.empty()) {
189+
config_string += ";ephemeral_full_policy=" + ephFullPolicy;
190+
}
191+
SingleThreadedEPBucketTest::SetUp();
192+
}
193+
};

engines/ep/tests/module_tests/item_pager_test.cc

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -36,34 +36,6 @@
3636
#include <xattr/blob.h>
3737
#include <xattr/utils.h>
3838

39-
/**
40-
* Test fixture for KVBucket tests running in single-threaded mode.
41-
*
42-
* Parameterised on a pair of:
43-
* - bucket_type (ephemeral of persistent)
44-
* - ephemeral_full_policy (for specifying ephemeral auto-delete & fail_new_data
45-
* eviction modes). If empty then unused (persistent buckets).
46-
*/
47-
class STParameterizedBucketTest
48-
: public SingleThreadedEPBucketTest,
49-
public ::testing::WithParamInterface<
50-
std::tuple<std::string, std::string>> {
51-
protected:
52-
void SetUp() override;
53-
};
54-
55-
void STParameterizedBucketTest::SetUp() {
56-
if (!config_string.empty()) {
57-
config_string += ";";
58-
}
59-
config_string += "bucket_type=" + std::get<0>(GetParam());
60-
auto ephFullPolicy = std::get<1>(GetParam());
61-
if (!ephFullPolicy.empty()) {
62-
config_string += ";ephemeral_full_policy=" + ephFullPolicy;
63-
}
64-
SingleThreadedEPBucketTest::SetUp();
65-
}
66-
6739
/**
6840
* Test fixture for bucket quota tests. Sets quota (max_size) to 200KB and
6941
* enables the MemoryTracker.

0 commit comments

Comments
 (0)