Skip to content

Commit 3071fdc

Browse files
committed
MB-60046: Merge branch 'couchbase/7.1.x' into 'couchbase/neo'
* couchbase/7.1.x: MB-60046: Ephemeral: Don't auto-delete committed and pending keys. Change-Id: Ib0f40115ddbfe3dce7b63c7cc384aecf14a8385c
2 parents ee91945 + bcbf13e commit 3071fdc

File tree

5 files changed

+132
-1
lines changed

5 files changed

+132
-1
lines changed

engines/ep/src/ephemeral_vb.cc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,15 @@ bool EphemeralVBucket::eligibleToPageOut(const HashTable::HashBucketLock& lh,
156156
// The system event documents must not be paged-out
157157
return false;
158158
}
159+
160+
// Cannot auto-delete this v if it has a pending v. Simplest is to
161+
// look again for a pending v in the hash-table.
162+
// @todo MB-60105: Tracks that scanning the hash-chain again is not the most
163+
// efficient fix.
164+
if (ht.unlocked_find(lh, v.getKey()).pendingSV) {
165+
return false;
166+
}
167+
159168
return true;
160169
}
161170

engines/ep/src/hash_table.cc

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,12 @@ HashTable::FindInnerResult HashTable::findInner(const DocKey& key) {
337337
"non-active object");
338338
}
339339
HashBucketLock hbl = getLockedBucket(key);
340+
auto rv = unlocked_find(hbl, key);
341+
return {std::move(hbl), rv.committedSV, rv.pendingSV};
342+
}
343+
344+
HashTable::UnlockedFindResult HashTable::unlocked_find(
345+
const HashBucketLock& hbl, const DocKey& key) const {
340346
// Scan through all elements in the hash bucket chain looking for Committed
341347
// and Pending items with the same key.
342348
StoredValue* foundCmt = nullptr;
@@ -354,7 +360,7 @@ HashTable::FindInnerResult HashTable::findInner(const DocKey& key) {
354360
}
355361
}
356362

357-
return {std::move(hbl), foundCmt, foundPend};
363+
return {foundCmt, foundPend};
358364
}
359365

360366
HashTable::RandomKeyVisitor::RandomKeyVisitor(size_t size, int random)

engines/ep/src/hash_table.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1199,6 +1199,26 @@ class HashTable {
11991199
StoredValue& value,
12001200
CommittedState state);
12011201

1202+
struct UnlockedFindResult {
1203+
/// The Committed StoredValue with this key if in HashTable, else
1204+
/// nullptr.
1205+
StoredValue* committedSV{nullptr};
1206+
/// The Committed StoredValue with this key if in HashTable, else
1207+
/// nullptr.
1208+
StoredValue* pendingSV{nullptr};
1209+
};
1210+
1211+
/**
1212+
* Attempt to find the pending and committed versions of key in the given
1213+
* hbl.getBucketNum hash bucket.
1214+
*
1215+
* @param hbl The structure which holds the lock and the bucket number
1216+
* @param key The key to find
1217+
* @return pair of pointers, null when nothing found.
1218+
*/
1219+
UnlockedFindResult unlocked_find(const HashBucketLock& hbl,
1220+
const DocKey& key) const;
1221+
12021222
/**
12031223
* Releases an item(StoredValue) in the hash table, but does not delete it.
12041224
* It will pass out the removed item to the caller who can decide whether to

engines/ep/tests/module_tests/ephemeral_vb_test.cc

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,29 @@ TEST_F(EphemeralVBucketTest, SoftDeleteDuringBackfill) {
473473
mockEpheVB->public_getNumListItems());
474474
}
475475

476+
// MB-60046: The ephemeral auto-delete was able to delete a committed item which
477+
// has a pending write (disrupting DCP). Test checks eligibleToPageOut now
478+
// detects this case.
479+
TEST_F(EphemeralVBucketTest, CannotPageOutCommittedAndPendingKey) {
480+
auto meta = nlohmann::json{
481+
{"topology", nlohmann::json::array({{"active", "replica"}})}};
482+
vbucket->setState(vbucket_state_active, &meta);
483+
// Commit and item and then also make it pending.
484+
auto key = makeStoredDocKey("key");
485+
auto item = makeCommittedItem(key, "value");
486+
ASSERT_EQ(MutationStatus::WasClean,
487+
public_processSet(*item, item->getCas()));
488+
item = makePendingItem(key, "value");
489+
VBQueueItemCtx ctx{CanDeduplicate::Yes};
490+
ctx.durability =
491+
DurabilityItemCtx{item->getDurabilityReqs(), nullptr /*cookie*/};
492+
ASSERT_EQ(MutationStatus::WasClean,
493+
public_processSet(*item, item->getCas(), ctx));
494+
auto result = vbucket->ht.findForRead(key);
495+
ASSERT_TRUE(result.storedValue);
496+
ASSERT_FALSE(vbucket->eligibleToPageOut(result.lock, *result.storedValue));
497+
}
498+
476499
// EphemeralVB Tombstone Purging //////////////////////////////////////////////
477500

478501
class EphTombstoneTest : public EphemeralVBucketTest {

engines/ep/tests/module_tests/item_pager_test.cc

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1643,6 +1643,79 @@ TEST_P(STEphemeralAutoDeleteItemPagerTest, MB_59368) {
16431643
EXPECT_TRUE(softDeleteCalled) << "Nothing was deleted";
16441644
}
16451645

1646+
// Test covers MB-60046. A pending and committed key must not find itself auto
1647+
// deleted, else the delete of the commit will be rejected by the DCP replica
1648+
// and cause disconnects.
1649+
TEST_P(STEphemeralAutoDeleteItemPagerTest, MB_60046) {
1650+
setVBucketState(
1651+
vbid,
1652+
vbucket_state_active,
1653+
{{"topology", nlohmann::json::array({{"active", "replica"}})}});
1654+
auto setActive = [this](int count) {
1655+
auto item =
1656+
make_item(vbid,
1657+
makeStoredDocKey("active_" + std::to_string(count)),
1658+
std::string(512, 'V'));
1659+
item.setFreqCounterValue(0);
1660+
storeItem(item);
1661+
};
1662+
int count = 0;
1663+
do {
1664+
setActive(++count);
1665+
} while (store->getPageableMemCurrent() <
1666+
store->getPageableMemHighWatermark());
1667+
auto key = makeStoredDocKey("active_" + std::to_string(count));
1668+
// Now make the last key written in the loop also pending.
1669+
ASSERT_EQ(cb::engine_errc::would_block,
1670+
storeItem(*makePendingItem(key, "pending-value")));
1671+
std::vector<queued_item> items;
1672+
auto& vb = *store->getVBucket(vbid);
1673+
auto cursor = vb.checkpointManager->registerCursorBySeqno(
1674+
"test", 1, CheckpointCursor::Droppable::No);
1675+
ASSERT_FALSE(cursor.tryBackfill) << *vb.checkpointManager;
1676+
vb.checkpointManager->getNextItemsForCursor(cursor.cursor.lock().get(),
1677+
items);
1678+
EXPECT_NE(0, items.size()) << *vb.checkpointManager;
1679+
bool pendingFound{false};
1680+
bool committedFound{false};
1681+
for (const auto& item : items) {
1682+
if (item->getKey() == key) {
1683+
if (item->isPending()) {
1684+
ASSERT_FALSE(pendingFound);
1685+
pendingFound = true;
1686+
} else {
1687+
ASSERT_FALSE(committedFound);
1688+
committedFound = true;
1689+
}
1690+
}
1691+
}
1692+
// Both pending and committed must be seen before we trigger auto-delete
1693+
ASSERT_TRUE(pendingFound);
1694+
ASSERT_TRUE(committedFound);
1695+
items.clear();
1696+
// Now trigger the auto-delete paging
1697+
runHighMemoryPager();
1698+
// Iterate through the checkpoint and check all deletes.
1699+
vb.checkpointManager->getNextItemsForCursor(cursor.cursor.lock().get(),
1700+
items);
1701+
// Should be some new mutations (deletes)
1702+
EXPECT_NE(0, items.size()) << *vb.checkpointManager;
1703+
int deleteCount{0};
1704+
for (const auto& item : items) {
1705+
if (item->getKey() == key) {
1706+
// The key should not be found in the new batch of items. The
1707+
// original commit+pending were in the first batch and not expected
1708+
// to see again. Prior to the fix, a delete(key) was seen here.
1709+
FAIL() << *item;
1710+
}
1711+
if (item->isDeleted()) {
1712+
++deleteCount;
1713+
}
1714+
}
1715+
// We certainly should see deletes.
1716+
ASSERT_NE(0, deleteCount);
1717+
}
1718+
16461719
/**
16471720
* Test fixture for expiry pager tests - enables the Expiry Pager (in addition
16481721
* to what the parent class does).

0 commit comments

Comments
 (0)