Skip to content

Commit 1228ca1

Browse files
committed
MB-42918: Merge branch '6.5.2' into mad-hatter
* 6.5.2: [BP] MB-42918: Do not replace committed item with pending at add Change-Id: I830531172ea4d82b899b7d8030c7589fed2ba6b4
2 parents bcdd342 + 9c481aa commit 1228ca1

File tree

2 files changed

+74
-14
lines changed

2 files changed

+74
-14
lines changed

engines/ep/src/vbucket.cc

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2970,19 +2970,8 @@ void VBucket::deletedOnDiskCbk(const Item& queuedItem, bool deleted) {
29702970
* be done to ensure that the item count is accurate in the case of full
29712971
* eviction. We should only decrement the counter for committed (via
29722972
* mutation or commit) items as we only increment for these.
2973-
* Note this is done irrespective of if a StoredValue was found in the
2974-
* HashTable - the SV may have already been replaced (e.g. with
2975-
* a pending SyncWrite). We "know" the delete happened on disk thus
2976-
* should decrement the total number of items.
2977-
*
2978-
* @todo: About "the SV may have already been replaced (e.g. with
2979-
* a pending SyncWrite)" above.. Need to check if that is true. If it
2980-
* is, that shouldn't happen as we may end up with this at FullEviction:
2981-
* - deleted key queued for persistence and removed from the HT
2982-
* - deletion not persisted yet
2983-
* - frontend read bg-fecthes a previous alive state (if any) from disk
29842973
*/
2985-
if (queuedItem.isCommitted()) {
2974+
if (v && queuedItem.isCommitted()) {
29862975
decrNumTotalItems();
29872976
++opsDelete;
29882977
}
@@ -3397,7 +3386,16 @@ std::pair<AddStatus, boost::optional<VBNotifyCtx>> VBucket::processAdd(
33973386

33983387
std::pair<AddStatus, VBNotifyCtx> rv = {AddStatus::Success, {}};
33993388

3400-
if (v) {
3389+
// We cannot replace a committed SV with a pending one. If we were to do
3390+
// so then a delete that has not yet been persisted could be replaced
3391+
// with a prepare. A subsequent get could trigger a bg fetch that may
3392+
// return the old (not deleted) document from disk if it runs before the
3393+
// flusher. As such, we must keep the unpersisted delete and add a new
3394+
// prepare for the SyncWrite. Any get will see the unpersisted delete
3395+
// and return KEYNOENT.
3396+
auto replacingCommittedItemWithPending =
3397+
v && v == htRes.committed && itm.isPending();
3398+
if (v && !replacingCommittedItemWithPending) {
34013399
if (v->isTempInitialItem() && eviction == EvictionPolicy::Full &&
34023400
maybeKeyExists) {
34033401
// Need to figure out if an item exists on disk
@@ -3421,7 +3419,8 @@ std::pair<AddStatus, boost::optional<VBNotifyCtx>> VBucket::processAdd(
34213419
std::tie(v, std::ignore, rv.second) =
34223420
updateStoredValue(htRes.getHBL(), *v, itm, queueItmCtx);
34233421
} else {
3424-
if (itm.getBySeqno() != StoredValue::state_temp_init) {
3422+
if (itm.getBySeqno() != StoredValue::state_temp_init &&
3423+
!replacingCommittedItemWithPending) {
34253424
if (eviction == EvictionPolicy::Full && maybeKeyExists) {
34263425
return {AddStatus::AddTmpAndBgFetch, VBNotifyCtx()};
34273426
}
@@ -3441,6 +3440,8 @@ std::pair<AddStatus, boost::optional<VBNotifyCtx>> VBucket::processAdd(
34413440

34423441
if (v->isTempItem()) {
34433442
rv.first = AddStatus::BgFetch;
3443+
} else if (replacingCommittedItemWithPending) {
3444+
rv.first = AddStatus::UnDel;
34443445
}
34453446
}
34463447

engines/ep/tests/module_tests/evp_store_test.cc

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -633,6 +633,65 @@ TEST_P(EPStoreEvictionTest, xattrExpiryOnFullyEvictedItem) {
633633
<< "The foo attribute should be gone";
634634
}
635635

636+
TEST_P(EPStoreEvictionTest, UnDelWithPrepare) {
637+
if (GetParam() == "value_only") {
638+
return;
639+
}
640+
641+
store->setVBucketState(
642+
vbid,
643+
vbucket_state_active,
644+
{{"topology", nlohmann::json::array({{"active", "replica"}})}});
645+
646+
// 1) Store, delete, and persist the item. We need to do this to ensure that
647+
// the bloom filter tells us to go to disk when we try gets if there is
648+
// no delete in the HashTable.
649+
auto key = makeStoredDocKey("key");
650+
storeAndDeleteItem(vbid, key, "value");
651+
auto vb = store->getVBucket(vbid);
652+
EXPECT_EQ(0, vb->getNumItems());
653+
654+
// 1) Store the new item and persist
655+
store_item(vbid, key, "value");
656+
flushVBucketToDiskIfPersistent(vbid, 1);
657+
EXPECT_EQ(1, vb->getNumItems());
658+
659+
// 2) Store the new item but don't persist it yet. We want to test what
660+
// happens when it's dirty.
661+
delete_item(vbid, key);
662+
663+
// 1 because we haven't persisted the delete yet
664+
EXPECT_EQ(1, vb->getNumItems());
665+
666+
// 3) Get now returns not found
667+
auto options = static_cast<get_options_t>(
668+
QUEUE_BG_FETCH | HONOR_STATES | TRACK_REFERENCE | DELETE_TEMP |
669+
HIDE_LOCKED_CAS | TRACK_STATISTICS);
670+
auto gv = getInternal(key, vbid, cookie, ForGetReplicaOp::No, options);
671+
EXPECT_EQ(ENGINE_KEY_ENOENT, gv.getStatus());
672+
673+
// 4) Add prepare
674+
auto prepare = makePendingItem(key, "value");
675+
EXPECT_EQ(ENGINE_SYNC_WRITE_PENDING, store->add(*prepare, cookie));
676+
// EXPECT_EQ(ENGINE_SYNC_WRITE_PENDING, addItem(*prepare, cookie));
677+
678+
// 1 because we haven't persisted the delete yet
679+
EXPECT_EQ(1, vb->getNumItems());
680+
681+
// 5) Check that the HashTable state is now correct
682+
{
683+
auto htRes = vb->ht.findForUpdate(key);
684+
ASSERT_TRUE(htRes.committed);
685+
EXPECT_TRUE(htRes.committed->isDeleted());
686+
EXPECT_TRUE(htRes.pending);
687+
}
688+
689+
// @TODO RDB: Rocks item counting is broken and overcounts assuming
690+
// everything is a new item
691+
flushVBucketToDiskIfPersistent(vbid, 2);
692+
EXPECT_EQ(0, vb->getNumItems());
693+
}
694+
636695
/**
637696
* Verify that when getIf is used it only fetches the metdata from disk for
638697
* the filter, and not the complete document.

0 commit comments

Comments
 (0)