Skip to content

Commit 55dedbb

Browse files
jimwwalkerdaverigby
authored andcommitted
MB-33919: Generate delete time for newly deleted items
Rather than allowing the existing expiry time to become the delete-time, which is flawed as per the MB, allow the VBQueueItemCtx to determine how the delete-time should be set. DCP consumer and the with-meta interface should not generate a delete-time, the incoming 'replicated' value should be accepted. However a value of zero is not allowed, and will be ignored/regenerated regardless of the VBQueueItemCtx setting. Genuine deletes/expirys should generate a new delete-time in-line with the issues highlighted in the MB. Change-Id: I7ea6ed8575eaa510a02cceb37a86628a28405fb5 Reviewed-on: http://review.couchbase.org/108393 Well-Formed: Build Bot <[email protected]> Tested-by: Build Bot <[email protected]> Reviewed-by: Dave Rigby <[email protected]>
1 parent 7a2667a commit 55dedbb

File tree

9 files changed

+183
-10
lines changed

9 files changed

+183
-10
lines changed

engines/ep/src/ep_engine.cc

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1085,8 +1085,14 @@ static ENGINE_ERROR_CODE compactDB(EventuallyPersistentEngine* e,
10851085
break;
10861086
case ENGINE_EWOULDBLOCK:
10871087
LOG(EXTENSION_LOG_NOTICE,
1088-
"Compaction of db file id: %d scheduled "
1089-
"(awaiting completion).", compactreq.db_file_id);
1088+
"Compaction of db file id:%d, purge_before_ts:%" PRIu64
1089+
", purge_before_seq:%" PRIu64
1090+
", drop_deletes:%d scheduled "
1091+
"(awaiting completion).",
1092+
compactreq.db_file_id,
1093+
compactreq.purge_before_ts,
1094+
compactreq.purge_before_seq,
1095+
compactreq.drop_deletes);
10901096
e->storeEngineSpecific(cookie, req);
10911097
return ENGINE_EWOULDBLOCK;
10921098
case ENGINE_TMPFAIL:

engines/ep/src/ep_types.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ using queued_item = SingleThreadedRCPtr<Item, Item*, std::default_delete<Item>>;
3838
enum class GenerateBySeqno { No, Yes };
3939
enum class GenerateRevSeqno { No, Yes };
4040
enum class GenerateCas { No, Yes };
41+
enum class GenerateDeleteTime { No, Yes };
4142
enum class TrackCasDrift { No, Yes };
4243
enum class WantsDeleted { No, Yes };
4344
enum class TrackReference { No, Yes };

engines/ep/src/ephemeral_vb.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ bool EphemeralVBucket::pageOut(const HashTable::HashBucketLock& lh,
9292
}
9393
VBQueueItemCtx queueCtx(GenerateBySeqno::Yes,
9494
GenerateCas::Yes,
95+
GenerateDeleteTime::Yes,
9596
TrackCasDrift::No,
9697
/*isBackfill*/ false,
9798
nullptr);

engines/ep/src/vbucket.cc

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -709,6 +709,7 @@ VBNotifyCtx VBucket::queueDirty(
709709
StoredValue& v,
710710
const GenerateBySeqno generateBySeqno,
711711
const GenerateCas generateCas,
712+
const GenerateDeleteTime generateDeleteTime,
712713
const bool isBackfillItem,
713714
PreLinkDocumentContext* preLinkDocumentContext) {
714715
VBNotifyCtx notifyCtx;
@@ -720,7 +721,8 @@ VBNotifyCtx VBucket::queueDirty(
720721
// our tombstone purger can use to determine which tombstones to purge. A
721722
// DCP replicated or deleteWithMeta created delete may already have a time
722723
// assigned to it.
723-
if (qi->isDeleted() && qi->getDeleteTime() == 0) {
724+
if (qi->isDeleted() && (generateDeleteTime == GenerateDeleteTime::Yes ||
725+
qi->getExptime() == 0)) {
724726
qi->setExpTime(ep_real_time());
725727
}
726728

@@ -1052,8 +1054,12 @@ ENGINE_ERROR_CODE VBucket::addBackfillItem(Item& itm,
10521054
v->unlock();
10531055
}
10541056

1057+
// MB-33919: DCP backfill - we must respect the value set by the producer
1058+
// unless it is zero, which queueDirty will fix-up. Not mutations and
1059+
// deletions come through this path.
10551060
VBQueueItemCtx queueItmCtx(genBySeqno,
10561061
GenerateCas::No,
1062+
GenerateDeleteTime::No,
10571063
TrackCasDrift::No,
10581064
/*isBackfillItem*/ true,
10591065
nullptr /* No pre link should happen */);
@@ -1176,8 +1182,12 @@ ENGINE_ERROR_CODE VBucket::setWithMeta(
11761182
v->unlock();
11771183
}
11781184

1185+
// MB-33919: Do not generate the delete-time - delete's can come through
1186+
// this path and the delete time from the input should be used (unless it
1187+
// is 0, where it must be regenerated)
11791188
VBQueueItemCtx queueItmCtx(genBySeqno,
11801189
genCas,
1190+
GenerateDeleteTime::No,
11811191
TrackCasDrift::Yes,
11821192
/*isBackfillItem*/ false,
11831193
nullptr /* No pre link step needed */);
@@ -1314,6 +1324,7 @@ ENGINE_ERROR_CODE VBucket::deleteItem(
13141324
metadata,
13151325
VBQueueItemCtx(GenerateBySeqno::Yes,
13161326
GenerateCas::Yes,
1327+
GenerateDeleteTime::Yes,
13171328
TrackCasDrift::No,
13181329
/*isBackfillItem*/ false,
13191330
nullptr /* no pre link */),
@@ -1474,8 +1485,12 @@ ENGINE_ERROR_CODE VBucket::deleteWithMeta(
14741485
delrv = MutationStatus::NeedBgFetch;
14751486
metaBgFetch = false;
14761487
} else {
1488+
// MB-33919: The incoming meta.exptime should be used as the delete-time
1489+
// so request GenerateDeleteTime::No, if the incoming value is 0, a new
1490+
// delete-time will be generated.
14771491
VBQueueItemCtx queueItmCtx(genBySeqno,
14781492
generateCas,
1493+
GenerateDeleteTime::No,
14791494
TrackCasDrift::Yes,
14801495
backfill,
14811496
nullptr /* No pre link step needed */);
@@ -2482,8 +2497,10 @@ VBucket::processExpiredItem(const HashTable::HashBucketLock& hbl,
24822497
return std::make_tuple(MutationStatus::NeedBgFetch,
24832498
&v,
24842499
queueDirty(v,
2500+
24852501
GenerateBySeqno::Yes,
24862502
GenerateCas::Yes,
2503+
GenerateDeleteTime::Yes,
24872504
/*isBackfillItem*/ false));
24882505
}
24892506

@@ -2506,6 +2523,7 @@ VBucket::processExpiredItem(const HashTable::HashBucketLock& hbl,
25062523
onlyMarkDeleted,
25072524
VBQueueItemCtx(GenerateBySeqno::Yes,
25082525
GenerateCas::Yes,
2526+
GenerateDeleteTime::Yes,
25092527
TrackCasDrift::No,
25102528
/*isBackfillItem*/ false,
25112529
nullptr /* no pre link */),
@@ -2601,6 +2619,7 @@ VBNotifyCtx VBucket::queueDirty(StoredValue& v,
26012619
return queueDirty(v,
26022620
queueItmCtx.genBySeqno,
26032621
queueItmCtx.genCas,
2622+
queueItmCtx.generateDeleteTime,
26042623
queueItmCtx.isBackfillItem,
26052624
queueItmCtx.preLinkDocumentContext);
26062625
}

engines/ep/src/vbucket.h

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,23 +66,59 @@ struct VBNotifyCtx {
6666
/**
6767
* Structure that holds info needed to queue an item in chkpt or vb backfill
6868
* queue
69+
*
70+
* GenerateDeleteTime - Only the queueing of items which are isDeleted() == true
71+
* will this parameter have any affect. E.g. an add of an Item with this set to
72+
* Yes will have no effect, whilst an explicit delete this parameter will have
73+
* have an effect.
74+
*
75+
* Note that when queueing a delete with and expiry time of 0, the delete time
76+
* is always generated. It is invalid to queue an Item with a 0 delete time,
77+
* in this case the GenerateDeleteTime setting is ignored.
78+
*
6979
*/
7080
struct VBQueueItemCtx {
81+
/**
82+
* For code which needs to determine if a delete time should be generated
83+
* that is for example del and set-with-meta, expiry, deletes this
84+
* constructor allows for the GenerateDeleteTime to be set as Yes or No.
85+
*/
86+
VBQueueItemCtx(GenerateBySeqno genBySeqno,
87+
GenerateCas genCas,
88+
GenerateDeleteTime generateDeleteTime,
89+
TrackCasDrift trackCasDrift,
90+
bool isBackfillItem,
91+
PreLinkDocumentContext* preLinkDocumentContext)
92+
: genBySeqno(genBySeqno),
93+
genCas(genCas),
94+
generateDeleteTime(generateDeleteTime),
95+
trackCasDrift(trackCasDrift),
96+
isBackfillItem(isBackfillItem),
97+
preLinkDocumentContext(preLinkDocumentContext) {
98+
}
99+
100+
/**
101+
* For code which is queueing Item's that are not deleted and the generate
102+
* delete time has no affect, this constructor should be used.
103+
*/
71104
VBQueueItemCtx(GenerateBySeqno genBySeqno,
72105
GenerateCas genCas,
73106
TrackCasDrift trackCasDrift,
74107
bool isBackfillItem,
75-
PreLinkDocumentContext* preLinkDocumentContext_)
108+
PreLinkDocumentContext* preLinkDocumentContext)
76109
: genBySeqno(genBySeqno),
77110
genCas(genCas),
111+
generateDeleteTime(GenerateDeleteTime::No),
78112
trackCasDrift(trackCasDrift),
79113
isBackfillItem(isBackfillItem),
80-
preLinkDocumentContext(preLinkDocumentContext_) {
114+
preLinkDocumentContext(preLinkDocumentContext) {
81115
}
116+
82117
/* Indicates if we should queue an item or not. If this is false other
83118
members should not be used */
84119
GenerateBySeqno genBySeqno;
85120
GenerateCas genCas;
121+
GenerateDeleteTime generateDeleteTime;
86122
TrackCasDrift trackCasDrift;
87123
bool isBackfillItem;
88124
PreLinkDocumentContext* preLinkDocumentContext;
@@ -1441,6 +1477,8 @@ class VBucket : public std::enable_shared_from_this<VBucket> {
14411477
* flags passed
14421478
* @param generateBySeqno request that the seqno is generated by this call
14431479
* @param generateCas request that the CAS is generated by this call
1480+
* @param generateDeleteTime for queueing deletes, should the queue item
1481+
* have its delete time generated.
14441482
* @param isBackfillItem indicates if the item must be put onto vb queue or
14451483
* onto checkpoint
14461484
* @param preLinkDocumentContext context object which allows running the
@@ -1454,6 +1492,7 @@ class VBucket : public std::enable_shared_from_this<VBucket> {
14541492
StoredValue& v,
14551493
GenerateBySeqno generateBySeqno = GenerateBySeqno::Yes,
14561494
GenerateCas generateCas = GenerateCas::Yes,
1495+
GenerateDeleteTime generateDeleteTime = GenerateDeleteTime::Yes,
14571496
bool isBackfillItem = false,
14581497
PreLinkDocumentContext* preLinkDocumentContext = nullptr);
14591498

engines/ep/tests/ep_testsuite.cc

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1487,6 +1487,69 @@ static enum test_result test_vbucket_compact(ENGINE_HANDLE *h,
14871487
return SUCCESS;
14881488
}
14891489

1490+
static enum test_result test_MB_33919(ENGINE_HANDLE* h, ENGINE_HANDLE_V1* h1) {
1491+
const char* expKey = "expiryKey";
1492+
const char* otherKey = "otherKey";
1493+
const char* value = "value";
1494+
1495+
// Test runs in the future as we need to set times in the past without
1496+
// falling foul of the expiry time being behind server start
1497+
auto fiveDays =
1498+
std::chrono::system_clock::now() + std::chrono::hours(5 * 24);
1499+
1500+
// Calculate expiry and purgeBefore times
1501+
auto threeDaysAgo = std::chrono::system_clock::to_time_t(
1502+
fiveDays - std::chrono::hours(3 * 24));
1503+
auto fourDaysAgo = std::chrono::system_clock::to_time_t(
1504+
fiveDays - std::chrono::hours(4 * 24));
1505+
// Time travel forwards by 5 days to give the illusion of 5 days uptime.
1506+
testHarness.time_travel((86400 * 5));
1507+
1508+
// Set expiring key, expiry 4 days ago
1509+
checkeq(ENGINE_SUCCESS,
1510+
store(h,
1511+
h1,
1512+
nullptr,
1513+
OPERATION_SET,
1514+
expKey,
1515+
value,
1516+
nullptr,
1517+
0,
1518+
0,
1519+
fourDaysAgo),
1520+
"Failed to set expiring key");
1521+
1522+
// Force it to expire
1523+
ENGINE_ERROR_CODE val = verify_key(h, h1, expKey);
1524+
checkeq(ENGINE_KEY_ENOENT, val, "expiryKey has not expired.");
1525+
1526+
// And a second key (after expiry), compaction won't purge the high-seqno
1527+
checkeq(ENGINE_SUCCESS,
1528+
store(h, h1, nullptr, OPERATION_SET, otherKey, value),
1529+
"Failed to set non-expiring key");
1530+
1531+
// Wait for the item to be expired
1532+
wait_for_stat_to_be(h, h1, "vb_active_expired", 1);
1533+
1534+
wait_for_flusher_to_settle(h, h1);
1535+
1536+
checkeq(0,
1537+
get_int_stat(h, h1, "vb_0:purge_seqno", "vbucket-seqno"),
1538+
"purge_seqno not found to be zero before compaction");
1539+
1540+
// Compaction on VBucket
1541+
compact_db(
1542+
h, h1, 0 /* vbucket_id */, 0 /* db_file_id */, threeDaysAgo, 0, 0);
1543+
wait_for_stat_to_be(h, h1, "ep_pending_compactions", 0);
1544+
1545+
// Purge-seqno should not of moved, without the fix it would
1546+
checkeq(0,
1547+
get_int_stat(h, h1, "vb_0:purge_seqno", "vbucket-seqno"),
1548+
"purge_seqno not found to be zero before compaction");
1549+
1550+
return SUCCESS;
1551+
}
1552+
14901553
static enum test_result test_compaction_config(ENGINE_HANDLE *h,
14911554
ENGINE_HANDLE_V1 *h1) {
14921555

@@ -8169,6 +8232,12 @@ BaseTestCase testsuite_testcases[] = {
81698232
TestCase("test_MB-23640_get_document_of_any_state", test_mb23640,
81708233
test_setup, teardown, nullptr, prepare, cleanup),
81718234

8172-
TestCase(NULL, NULL, NULL, NULL, NULL, prepare, cleanup)
8173-
};
8235+
TestCase("test MB-33919 past tombstone",
8236+
test_MB_33919,
8237+
test_setup,
8238+
teardown,
8239+
nullptr,
8240+
prepare,
8241+
cleanup),
81748242

8243+
TestCase(NULL, NULL, NULL, NULL, NULL, prepare, cleanup)};

engines/ep/tests/module_tests/evp_store_single_threaded_test.cc

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2755,8 +2755,9 @@ TEST_F(WarmupTest, produce_delete_times) {
27552755
(sizeof("KEY2") - 1);
27562756
EXPECT_EQ(expectedBytes, producer->getBytesOutstanding());
27572757

2758-
// Finally expire a key and check that the delete_time we receive is the
2759-
// expiry time, not actually the time it was deleted.
2758+
// Finally expire a key and check that the delete_time we receive is not the
2759+
// expiry time, the delete time should always be re-created by the server to
2760+
// ensure old/future expiry times don't disrupt tombstone purging (MB-33919)
27602761
auto expiryTime = ep_real_time() + 32000;
27612762
store_item(vbid,
27622763
{"KEY3", DocNamespace::DefaultCollection},
@@ -2779,7 +2780,7 @@ TEST_F(WarmupTest, produce_delete_times) {
27792780

27802781
step(true);
27812782

2782-
EXPECT_EQ(expiryTime, dcp_last_delete_time);
2783+
EXPECT_NE(expiryTime, dcp_last_delete_time);
27832784
EXPECT_EQ(PROTOCOL_BINARY_CMD_DCP_DELETION, dcp_last_op);
27842785
EXPECT_EQ("KEY3", dcp_last_key);
27852786
expectedBytes += SnapshotMarker::baseMsgBytes +

engines/ep/tests/module_tests/evp_store_with_meta.cc

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1356,6 +1356,42 @@ TEST_P(DelWithMetaTest, setting_deleteTime) {
13561356
EXPECT_EQ(itemMeta.exptime, metadata.exptime);
13571357
}
13581358

1359+
// Perform a DeleteWithMeta with a deleteTime of 0 and verify that a time is
1360+
// generated.
1361+
TEST_P(DelWithMetaTest, setting_zero_deleteTime) {
1362+
ItemMetaData itemMeta{0xdeadbeef, 0xf00dcafe, 0xfacefeed, 0};
1363+
oneOpAndCheck(op,
1364+
itemMeta,
1365+
0, // no-options
1366+
withValue,
1367+
PROTOCOL_BINARY_RESPONSE_SUCCESS,
1368+
withValue ? ENGINE_SUCCESS : ENGINE_EWOULDBLOCK);
1369+
1370+
EXPECT_EQ(std::make_pair(false, size_t(1)),
1371+
getEPBucket().flushVBucket(vbid));
1372+
1373+
ItemMetaData metadata;
1374+
uint32_t deleted = 0;
1375+
uint8_t datatype = 0;
1376+
EXPECT_EQ(ENGINE_EWOULDBLOCK,
1377+
store->getMetaData({"mykey", DocNamespace::DefaultCollection},
1378+
vbid,
1379+
cookie,
1380+
metadata,
1381+
deleted,
1382+
datatype));
1383+
MockGlobalTask mockTask(engine->getTaskable(), TaskId::MultiBGFetcherTask);
1384+
store->getVBucket(vbid)->getShard()->getBgFetcher()->run(&mockTask);
1385+
EXPECT_EQ(ENGINE_SUCCESS,
1386+
store->getMetaData({"mykey", DocNamespace::DefaultCollection},
1387+
vbid,
1388+
cookie,
1389+
metadata,
1390+
deleted,
1391+
datatype));
1392+
EXPECT_NE(0, metadata.exptime);
1393+
}
1394+
13591395
TEST_P(DelWithMetaTest, MB_31141) {
13601396
ItemMetaData itemMeta{0xdeadbeef, 0xf00dcafe, 0xfacefeed, expiry};
13611397
// Do a delete with valid extended meta

engines/ep/tests/module_tests/vbucket_test.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ MutationStatus VBucketTestBase::public_processSoftDelete(const DocKey& key,
234234
metadata,
235235
VBQueueItemCtx(GenerateBySeqno::Yes,
236236
GenerateCas::Yes,
237+
GenerateDeleteTime::Yes,
237238
TrackCasDrift::No,
238239
/*isBackfillItem*/ false,
239240
/*preLinkDocCtx*/ nullptr),

0 commit comments

Comments
 (0)