Skip to content

Commit 8855aeb

Browse files
jameseh96daverigby
authored andcommitted
MB-52793: Ensure StoredValue::del updates datatype
If a StoredValue is deleted, but does not have a resident value (but _may_ have one on disk, containing xattrs), previously `del` erroneously skipped updating the datatype. This situation has only been observed to occur on replicas, via PassiveStream calling deleteWithMeta for an already deleted item. This may occur when xattrs are removed from a deleted document. See MB for more details. Change-Id: I213cefb3907c4e290c2857c8526477f69a9ce764 Reviewed-on: https://review.couchbase.org/c/kv_engine/+/177197 Well-Formed: Restriction Checker Reviewed-by: Trond Norbye <[email protected]> Tested-by: Build Bot <[email protected]>
1 parent 35086bc commit 8855aeb

File tree

4 files changed

+183
-6
lines changed

4 files changed

+183
-6
lines changed

engines/ep/src/kv_bucket.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -724,6 +724,10 @@ class KVBucket : public KVBucketIface {
724724

725725
cb::durability::Level getMinDurabilityLevel() const;
726726

727+
EvictionPolicy getEvictionPolicy() const {
728+
return eviction_policy;
729+
}
730+
727731
protected:
728732
GetValue getInternal(const DocKey& key,
729733
Vbid vbucket,

engines/ep/src/stored-value.cc

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -323,12 +323,6 @@ bool StoredValue::operator!=(const StoredValue& other) const {
323323
}
324324

325325
bool StoredValue::deleteImpl(DeleteSource delSource) {
326-
if (isDeleted() && !getValue()) {
327-
// SV is already marked as deleted and has no value - no further
328-
// deletion possible.
329-
return false;
330-
}
331-
332326
resetValue();
333327
setDatatype(PROTOCOL_BINARY_RAW_BYTES);
334328
setPendingSeqno();

engines/ep/tests/module_tests/evp_store_single_threaded_test.cc

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5855,3 +5855,144 @@ INSTANTIATE_TEST_CASE_P(Persistent,
58555855
STParamPersistentBucketTest,
58565856
STParameterizedBucketTest::persistentConfigValues(),
58575857
STParameterizedBucketTest::PrintToStringParamName);
5858+
5859+
TEST_P(STParamPersistentBucketTest,
5860+
RemovingXattrDoesNotCauseIncorrectDatatypeOnReplica) {
5861+
// MB-52793: Under value eviction, if a replica:
5862+
// 1. Is a version without the fix for MB-50423 (6.6.5, 7.0.4 and down)
5863+
// 2. Has metadata for a deleted item with xattrs, but a non-resident value
5864+
// (e.g., has been bgfetched and evicted)
5865+
// 3. Receives a deletion over DCP changing a document from
5866+
// Xattrs+value -> no xattrs+no value
5867+
// The replica will persist a deleted item with datatype=xattrs but no
5868+
// value.
5869+
// NOTE: this has already been (incidentally) prevented by the fix for
5870+
// MB-50423 in some versions, this test is to guard against future
5871+
// regressions in this specific case.
5872+
5873+
setVBucketStateAndRunPersistTask(vbid, vbucket_state_replica);
5874+
5875+
// 1) Store an item with a (system) xattr
5876+
auto key = makeStoredDocKey("key");
5877+
5878+
cb::xattr::Blob xattrBlob;
5879+
5880+
xattrBlob.set("_sync", "somexattrvalue");
5881+
auto xattrs = xattrBlob.finalize();
5882+
auto xattrsStr = std::string(xattrs.begin(), xattrs.end());
5883+
5884+
// store the deleted item
5885+
{
5886+
auto item = make_item(
5887+
vbid, key, xattrsStr, 0, PROTOCOL_BINARY_DATATYPE_XATTR);
5888+
5889+
item.setCas(1234);
5890+
item.setDeleted(DeleteSource::Explicit);
5891+
5892+
uint64_t seqno;
5893+
ASSERT_EQ(ENGINE_SUCCESS,
5894+
store->setWithMeta(item,
5895+
0 /* cas */,
5896+
&seqno,
5897+
cookie,
5898+
{vbucket_state_replica},
5899+
CheckConflicts::No,
5900+
/*allowExisting*/ true));
5901+
}
5902+
5903+
flushVBucketToDiskIfPersistent(vbid, 1);
5904+
5905+
auto vb = store->getVBucket(vbid);
5906+
auto& ht = vb->ht;
5907+
5908+
// persistence callback removes the delete from the HT
5909+
{
5910+
auto res = ht.findForRead(key,
5911+
TrackReference::No,
5912+
WantsDeleted::Yes,
5913+
ForGetReplicaOp::No);
5914+
ASSERT_FALSE(res.storedValue);
5915+
}
5916+
5917+
{
5918+
// cause the deleted item to be bgfetched back into the HT
5919+
get_options_t options =
5920+
static_cast<get_options_t>(QUEUE_BG_FETCH | GET_DELETED_VALUE);
5921+
auto gv = store->get(key, vbid, cookie, options);
5922+
ASSERT_EQ(ENGINE_EWOULDBLOCK, gv.getStatus());
5923+
5924+
runBGFetcherTask();
5925+
gv = store->get(key, vbid, cookie, options);
5926+
ASSERT_EQ(ENGINE_SUCCESS, gv.getStatus());
5927+
ASSERT_EQ(PROTOCOL_BINARY_DATATYPE_XATTR, gv.item->getDataType());
5928+
ASSERT_NE(0, gv.item->getNBytes());
5929+
ASSERT_EQ(xattrsStr, gv.item->getValue()->to_s());
5930+
}
5931+
5932+
// now evict the value
5933+
{
5934+
auto res = ht.findForWrite(key, WantsDeleted::Yes);
5935+
ht.unlocked_ejectItem(
5936+
res.lock, res.storedValue, store->getEvictionPolicy());
5937+
}
5938+
5939+
// check it exists in the desired state
5940+
{
5941+
auto res = ht.findForRead(key,
5942+
TrackReference::No,
5943+
WantsDeleted::Yes,
5944+
ForGetReplicaOp::No);
5945+
const auto* v = res.storedValue;
5946+
if (fullEviction()) {
5947+
// Item should be entirely removed.
5948+
EXPECT_FALSE(v);
5949+
} else {
5950+
// Item should still be present.
5951+
ASSERT_TRUE(v);
5952+
ASSERT_TRUE(v->isDeleted());
5953+
ASSERT_FALSE(v->isResident());
5954+
ASSERT_EQ(PROTOCOL_BINARY_DATATYPE_XATTR, v->getDatatype());
5955+
}
5956+
}
5957+
5958+
// now drop the xattrs and store again, as if the only xattr has been
5959+
// removed by subdoc (as per what SyncGW does).
5960+
{
5961+
auto item = make_item(vbid, key, "", 0, PROTOCOL_BINARY_RAW_BYTES);
5962+
item.setCas(5678);
5963+
item.setDeleted(DeleteSource::Explicit);
5964+
5965+
uint64_t cas = 0;
5966+
uint64_t seqno;
5967+
5968+
EXPECT_EQ(ENGINE_SUCCESS,
5969+
store->deleteWithMeta(key,
5970+
cas,
5971+
&seqno,
5972+
vbid,
5973+
cookie,
5974+
{vbucket_state_replica},
5975+
CheckConflicts::No,
5976+
item.getMetaData(),
5977+
GenerateBySeqno::Yes,
5978+
GenerateCas::No,
5979+
0,
5980+
nullptr,
5981+
DeleteSource::Explicit));
5982+
}
5983+
5984+
// At this point the damage has been done - the document has been corrupted
5985+
// (zero byte value but datatype==XATTR) both on-disk and in-memory.
5986+
5987+
// MB-50423: The item _must_ have datatype RAW_BYTES now, as it does not
5988+
// have any xattrs or a value.
5989+
{
5990+
auto res = ht.findForRead(key,
5991+
TrackReference::No,
5992+
WantsDeleted::Yes,
5993+
ForGetReplicaOp::No);
5994+
const auto* v = res.storedValue;
5995+
EXPECT_TRUE(v->isDeleted());
5996+
EXPECT_EQ(PROTOCOL_BINARY_RAW_BYTES, v->getDatatype());
5997+
}
5998+
}

engines/ep/tests/module_tests/stored_value_test.cc

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include "stats.h"
2727
#include "stored_value_factories.h"
2828
#include "tests/module_tests/test_helpers.h"
29+
#include <xattr/blob.h>
2930

3031
#include <folly/portability/GTest.h>
3132

@@ -436,6 +437,43 @@ TYPED_TEST(ValueTest, MB_32568) {
436437
EXPECT_EQ(DeleteSource::TTL, this->sv->getDeletionSource());
437438
}
438439

440+
TYPED_TEST(ValueTest, DeleteUpdatesDatatype) {
441+
// MB-52793: Ensure that calling StoredValue::del() on a non-resident,
442+
// deleted item correctly changes the datatype to RAW_BYTES.
443+
// See MB/patch for scenario and other tests.
444+
auto key = makeStoredDocKey("key");
445+
446+
cb::xattr::Blob xattrBlob;
447+
xattrBlob.set("_sync", "somexattrvalue");
448+
auto xattrs = xattrBlob.finalize();
449+
std::string value = std::string(xattrs.begin(), xattrs.end());
450+
451+
auto item = make_item(Vbid(0),
452+
key,
453+
value,
454+
0 /* expiry */,
455+
PROTOCOL_BINARY_DATATYPE_XATTR);
456+
457+
item.setDeleted(DeleteSource::Explicit);
458+
459+
// create a deleted stored value with xattrs
460+
auto sv = this->factory(item, {});
461+
462+
sv->ejectValue();
463+
464+
EXPECT_FALSE(sv->isResident());
465+
EXPECT_EQ(PROTOCOL_BINARY_DATATYPE_XATTR, sv->getDatatype());
466+
467+
// delete the stored value "again". MB-52793: this occurred when removing
468+
// xattrs from a deleted document, leaving it with no value, but erroneously
469+
// keeping xattr datatype. See MB.
470+
sv->del(DeleteSource::Explicit);
471+
472+
// check the datatype has correctly changed, even though the value is
473+
// not resident.
474+
EXPECT_EQ(PROTOCOL_BINARY_RAW_BYTES, sv->getDatatype());
475+
}
476+
439477
/**
440478
* Test fixture for OrderedStoredValue-only tests.
441479
*/

0 commit comments

Comments
 (0)