Skip to content

Commit dea312d

Browse files
committed
MB-59452: Don't use xattrs for collection related metadata
The addition of modify collection in 7.2 required some extra work to keep support for "legacy" DCP users, that is to not expose the seqno of the modify event through various APIs (e.g. getAllSeqnos). The orginal change did this by stashing this one-off seqno into an xattr blob attached to the value of the modify event. Thus only the modify of the default collection ever stored this extra data. MB-59452 reminded us that xattrs can introduce a performance cost and the modify of the default collection should not be the trigger for a user to pay that cost. Fix by moving the extra metadata into the flatbuffer data so no xattr values are generated by KV itself. 1) The modify collection "write" path now changes to always stash the special seqno inside the flatbuffers. 2) The warmup path checks for xattr and can load both encodings. Note there is no explicit "sanitise" path. DCP never transmitted the xattr part of the modify and the special seqno was always recalculated by the recipient. In this case a rebalance will result in the xattr blob being removed and never stored again. Change-Id: I33872e304f0ec53ec2ba3de8cf161546ba12ff0c Reviewed-on: https://review.couchbase.org/c/kv_engine/+/201296 Well-Formed: Restriction Checker Tested-by: Jim Walker <[email protected]> Reviewed-by: Dave Rigby <[email protected]>
1 parent 5c49ec2 commit dea312d

File tree

4 files changed

+131
-28
lines changed

4 files changed

+131
-28
lines changed

engines/ep/src/collections/events.fbs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,14 @@ table Collection {
2727
// history: MB-54516: added in neo (7.2) branch. If this field is not part
2828
// of a Collection structure it is defaulted to false.
2929
history:bool = false;
30+
31+
// All fields after here are added from 7.2.4
32+
//
33+
// defaultCollectionMVS: MB-59452: This field exists to assist in the
34+
// tracking of what is the max-visible seqno of the default collection. This
35+
// field is only written to when the event is a modification of the default
36+
// collection and serves no purpose outside of KV-engine.
37+
defaultCollectionMVS:ulong = 0;
3038
}
3139

3240
// FlatBuffers SystemEvents: This is the structure used by DropCollection

engines/ep/src/collections/vbucket_manifest.cc

Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -968,7 +968,8 @@ std::unique_ptr<Item> Manifest::makeCollectionSystemEvent(
968968
std::string_view collectionName,
969969
const ManifestEntry& entry,
970970
SystemEventType type,
971-
OptionalSeqno seq) {
971+
OptionalSeqno seq,
972+
uint64_t defaultCollectionMaxLegacyDCPSeqno) {
972973
flatbuffers::FlatBufferBuilder builder;
973974

974975
switch (type) {
@@ -985,7 +986,9 @@ std::unique_ptr<Item> Manifest::makeCollectionSystemEvent(
985986
: 0,
986987
builder.CreateString(collectionName.data(),
987988
collectionName.size()),
988-
getHistoryFromCanDeduplicate(entry.getCanDeduplicate()));
989+
getHistoryFromCanDeduplicate(entry.getCanDeduplicate()),
990+
cid.isDefaultCollection() ? defaultCollectionMaxLegacyDCPSeqno
991+
: 0);
989992
builder.Finish(collection);
990993
break;
991994
}
@@ -1033,12 +1036,13 @@ uint64_t Manifest::queueCollectionSystemEvent(
10331036
vb.checkpointManager->createNewCheckpoint();
10341037
}
10351038

1036-
auto item = makeCollectionSystemEvent(
1037-
getManifestUid(), cid, collectionName, entry, type, seq);
1038-
1039-
if (type == SystemEventType::Modify && cid.isDefaultCollection()) {
1040-
attachMaxLegacyDCPSeqno(*item);
1041-
}
1039+
auto item = makeCollectionSystemEvent(getManifestUid(),
1040+
cid,
1041+
collectionName,
1042+
entry,
1043+
type,
1044+
seq,
1045+
defaultCollectionMaxLegacyDCPSeqno);
10421046

10431047
// Create and transfer Item ownership to the VBucket
10441048
auto rv = vb.addSystemEventItem(
@@ -1069,12 +1073,13 @@ void Manifest::setDefaultCollectionLegacySeqnos(uint64_t maxCommittedSeqno,
10691073
}
10701074

10711075
// Build an XATTR pair which records the defaultCollectionMaxLegacyDCPSeqno
1072-
void Manifest::attachMaxLegacyDCPSeqno(Item& item) const {
1076+
void Manifest::attachMaxLegacyDCPSeqno(
1077+
Item& item, uint64_t seqno) {
10731078
Expects(!mcbp::datatype::is_xattr(item.getDataType()));
10741079
cb::xattr::Blob xattrs;
10751080
xattrs.set(
10761081
LegacyXattrKey,
1077-
fmt::format(LegacyJSONFormat, defaultCollectionMaxLegacyDCPSeqno));
1082+
fmt::format(LegacyJSONFormat, seqno));
10781083

10791084
std::string xattrValue;
10801085
xattrValue.reserve(xattrs.size() + item.getNBytes());
@@ -1110,16 +1115,29 @@ uint64_t Manifest::computeDefaultCollectionMaxLegacyDCPSeqno(
11101115
// The default collections current high-seqno is the maxLegacyDCP value
11111116
return highSeqno;
11121117
}
1118+
// It cannot be greater
1119+
Expects(uint64_t(item.getBySeqno()) == highSeqno);
1120+
1121+
// else the modify is the highSeqno and it stores the correct legacy seqno.
1122+
// Retrieve the data which is either in xattr or the value
11131123

1114-
// else the modify is the highSeqno and it stores the correct legacy seqno
1115-
// see attachMaxLegacyDCPSeqno which is the encoder of this data.
1124+
uint64_t storedSeqno{0};
1125+
// Get the seqno from the correct part of the value.
1126+
if (mcbp::datatype::is_xattr(item.getDataType())) {
1127+
// 7.2 began by stashing in xattrs.
1128+
storedSeqno = getSeqnoFromXattr(item);
1129+
} else {
1130+
storedSeqno = getSeqnoFromFlatBuffer(item);
1131+
}
1132+
Expects(storedSeqno < highSeqno);
1133+
return storedSeqno;
1134+
}
11161135

1136+
uint64_t Manifest::getSeqnoFromXattr(const Item& item) {
11171137
// Found it. All modify events have xattr
11181138
Expects(mcbp::datatype::is_xattr(item.getDataType()));
11191139
// This should be decompressed
11201140
Expects(!mcbp::datatype::is_snappy(item.getDataType()));
1121-
// It cannot be greater
1122-
Expects(uint64_t(item.getBySeqno()) == highSeqno);
11231141

11241142
// pull out the stashed seqno which the VB:Manifest needs
11251143
cb::xattr::Blob xattr({const_cast<char*>(item.getData()), item.getNBytes()},
@@ -1135,12 +1153,18 @@ uint64_t Manifest::computeDefaultCollectionMaxLegacyDCPSeqno(
11351153
auto stashedSeqno = std::stoull(max->get<std::string>());
11361154

11371155
Expects(stashedSeqno < uint64_t(item.getBySeqno()));
1138-
Expects(stashedSeqno < highSeqno);
11391156

11401157
// return this value to use as the max-legacy seqno
11411158
return stashedSeqno;
11421159
}
11431160

1161+
uint64_t Manifest::getSeqnoFromFlatBuffer(const Item& item) {
1162+
Expects(!mcbp::datatype::is_xattr(item.getDataType()));
1163+
const auto& collection =
1164+
Collections::VB::Manifest::getCollectionFlatbuffer(item);
1165+
return collection.defaultCollectionMVS();
1166+
}
1167+
11441168
size_t Manifest::getSystemEventItemCount() const {
11451169
// Every 'live' collection has 1 'live' item, except for the default
11461170
// collection which has no creation event.

engines/ep/src/collections/vbucket_manifest.h

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,15 @@ class Manifest {
370370
std::string_view collectionName,
371371
const ManifestEntry& entry,
372372
SystemEventType type,
373-
OptionalSeqno seq);
373+
OptionalSeqno seq,
374+
uint64_t defaultCollectionMaxLegacyDCPSeqno = 0);
375+
376+
/**
377+
* Store "seqno" into the item using an xattr. This function only exists for
378+
* use by tests checking that KV can warm-up when a 7.2 modify default
379+
* collection system event is on-disk. See MB-59452
380+
*/
381+
static void attachMaxLegacyDCPSeqno(Item& item, uint64_t seqno);
374382

375383
bool operator==(const Manifest& rhs) const;
376384

@@ -1159,13 +1167,6 @@ class Manifest {
11591167
*/
11601168
CanDeduplicate getCanDeduplicate(CollectionID cid) const;
11611169

1162-
/**
1163-
* Store the value of defaultCollectionMaxLegacyDCPSeqno inside the Item
1164-
* This function utilises xattrs so that only a modify of the default
1165-
* collection carries this extra data. No other event needs this data.
1166-
*/
1167-
void attachMaxLegacyDCPSeqno(Item& item) const;
1168-
11691170
/**
11701171
* Function returns the correct value for defaultCollectionMaxLegacyDCPSeqno
11711172
* by reading any modify of the default collection from KVStore and then
@@ -1180,6 +1181,22 @@ class Manifest {
11801181
Vbid vb,
11811182
KVStoreIface& kvs);
11821183

1184+
/**
1185+
* Retrieve from the Item the stored max visible default collection seqno
1186+
* that is assumed to be stored within the Item. Caller is expected to have
1187+
* checked that the Item is a modify of the default collection and the
1188+
* datatype is xattr.
1189+
*/
1190+
static uint64_t getSeqnoFromXattr(const Item& item);
1191+
1192+
/**
1193+
* Retrieve from the Item the stored max visible default collection seqno
1194+
* that is assumed to be stored within the Item. Caller is expected to have
1195+
* checked that the Item is a modify of the default collection and the
1196+
* datatype is not xattr (value is thus inside the flatbuffer value.
1197+
*/
1198+
static uint64_t getSeqnoFromFlatBuffer(const Item& item);
1199+
11831200
/**
11841201
* Return a string for use in throwException, returns:
11851202
* "VB::Manifest::<thrower>:<error>, this:<ostream *this>"

engines/ep/tests/module_tests/collections/evp_store_collections_dcp_test.cc

Lines changed: 59 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3006,6 +3006,8 @@ class CollectionsDcpPersistentOnly : public CollectionsDcpParameterizedTest {
30063006
public:
30073007
void resurrectionTest(bool dropAtEnd, bool updateItemPath, bool deleteItem);
30083008
void resurrectionStatsTest(bool reproduceUnderflow, bool updateItemDropped);
3009+
void defaultCollectionLegacySeqnos(bool modifyWithoutXattr);
3010+
std::unique_ptr<Item> makeModifyWithXattr();
30093011
};
30103012

30113013
// Observed in MB-39864, the data we store in _local had a collection with
@@ -4605,7 +4607,8 @@ TEST_P(CollectionsDcpPersistentOnly, ModifyFilteredCollection) {
46054607
EXPECT_EQ(producers->last_can_deduplicate, CanDeduplicate::Yes);
46064608
}
46074609

4608-
TEST_P(CollectionsDcpPersistentOnly, DefaultCollectionLegacySeqnos) {
4610+
void CollectionsDcpPersistentOnly::defaultCollectionLegacySeqnos(
4611+
bool modifyWithoutXattr) {
46094612
using namespace cb::mcbp;
46104613
using namespace mcbp::systemevent;
46114614
using namespace CollectionEntry;
@@ -4663,10 +4666,23 @@ TEST_P(CollectionsDcpPersistentOnly, DefaultCollectionLegacySeqnos) {
46634666
validate(2, 2, 2);
46644667
}
46654668

4666-
// Modify default collection, so only high-seqno moves
4667-
CollectionsManifest cm;
4668-
cm.update(defaultC, cb::NoExpiryLimit, true /*history*/);
4669-
setCollections(cookie, cm);
4669+
if (modifyWithoutXattr) {
4670+
// Modify default collection, so only high-seqno moves
4671+
CollectionsManifest cm;
4672+
cm.update(defaultC, cb::NoExpiryLimit, true /*history*/);
4673+
setCollections(cookie, cm);
4674+
} else {
4675+
// by-pass the manifest update path as this variant of the test needs to
4676+
// create an xattr system-event as per 7.2
4677+
EXPECT_EQ(3,
4678+
store->getVBucket(vbid)->addSystemEventItem(
4679+
makeModifyWithXattr(),
4680+
{},
4681+
CollectionID::Default,
4682+
store->getVBucket(vbid)->getManifest().wlock(),
4683+
[](uint64_t) {}));
4684+
}
4685+
46704686
{
46714687
SCOPED_TRACE("Modify collection @ seqno 3");
46724688
validate(2, 2, 3);
@@ -4730,6 +4746,44 @@ TEST_P(CollectionsDcpPersistentOnly, DefaultCollectionLegacySeqnos) {
47304746
}
47314747
}
47324748

4749+
TEST_P(CollectionsDcpPersistentOnly, DefaultCollectionLegacySeqnos) {
4750+
defaultCollectionLegacySeqnos(true);
4751+
}
4752+
4753+
std::unique_ptr<Item> CollectionsDcpPersistentOnly::makeModifyWithXattr() {
4754+
// Here we have to manually inject the 7.2 modify event. Cannot use a real
4755+
// manifest driven event as it will be using flatbuffers.
4756+
// Build the system event value, enable history
4757+
flatbuffers::FlatBufferBuilder builder;
4758+
std::string name{"_default"};
4759+
auto collection = Collections::VB::CreateCollection(
4760+
builder,
4761+
1, // uid
4762+
uint32_t(ScopeID::Default),
4763+
uint32_t(CollectionID::Default),
4764+
false, // no maxTTL
4765+
0, // no maxTTL
4766+
builder.CreateString(name.data(), name.size()),
4767+
true, // history enabled
4768+
0);
4769+
builder.Finish(collection);
4770+
// Create the Item (so it has the system event key etc...)
4771+
auto event = SystemEventFactory::makeModifyCollectionEvent(
4772+
CollectionID::Default,
4773+
{builder.GetBufferPointer(), builder.GetSize()},
4774+
{});
4775+
4776+
// Use the code which remains in VB::Manifest that glues a seqno into the
4777+
// event using xattr. Here we add 2 as the legacy seqno
4778+
Collections::VB::Manifest::attachMaxLegacyDCPSeqno(*event, 2);
4779+
4780+
return event;
4781+
}
4782+
4783+
TEST_P(CollectionsDcpPersistentOnly, DefaultCollectionLegacySeqnoFromXattr) {
4784+
defaultCollectionLegacySeqnos(false);
4785+
}
4786+
47334787
TEST_P(CollectionsDcpPersistentOnly, getRangeCountingSystemEvents) {
47344788
using namespace CollectionEntry;
47354789

0 commit comments

Comments
 (0)