Skip to content

Commit 55a4de0

Browse files
committed
MB-54516: Modify history out of sync with many vbuckets
The initial addition of history mistakenly added the flag to the "shared metadata". When >1 active vbucket exists, the first one to be processed updated collection.history, but sets the shared metadata and then generates a Modify. The second vbucket sees no difference in history and does not generate a Modify. The replicas get out of sync, with vb0 correctly deduplicating on active and replica, but vb1 only deduplicating on the active. Change-Id: If653494f31e44c2b9fb44f45d07bd814f639a46a Reviewed-on: https://review.couchbase.org/c/kv_engine/+/184179 Well-Formed: Restriction Checker Reviewed-by: Vesko Karaganev <[email protected]> Tested-by: Jim Walker <[email protected]>
1 parent 43971e0 commit 55a4de0

File tree

11 files changed

+119
-68
lines changed

11 files changed

+119
-68
lines changed

engines/ep/src/collections/collections_types.cc

Lines changed: 10 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -103,22 +103,13 @@ std::string to_string(ManifestUpdateStatus status) {
103103
}
104104

105105
CollectionSharedMetaDataView::CollectionSharedMetaDataView(
106-
std::string_view name,
107-
ScopeID scope,
108-
cb::ExpiryLimit maxTtl,
109-
CanDeduplicate canDeduplicate)
110-
: name(name),
111-
scope(scope),
112-
maxTtl(std::move(maxTtl)),
113-
canDeduplicate(canDeduplicate) {
106+
std::string_view name, ScopeID scope, cb::ExpiryLimit maxTtl)
107+
: name(name), scope(scope), maxTtl(std::move(maxTtl)) {
114108
}
115109

116110
CollectionSharedMetaDataView::CollectionSharedMetaDataView(
117111
const CollectionSharedMetaData& meta)
118-
: name(meta.name),
119-
scope(meta.scope),
120-
maxTtl(meta.maxTtl),
121-
canDeduplicate(meta.canDeduplicate) {
112+
: name(meta.name), scope(meta.scope), maxTtl(meta.maxTtl) {
122113
}
123114

124115
std::string CollectionSharedMetaDataView::to_string() const {
@@ -127,33 +118,23 @@ std::string CollectionSharedMetaDataView::to_string() const {
127118
if (maxTtl) {
128119
rv += ", maxTtl:" + std::to_string(maxTtl.value().count());
129120
}
130-
rv += ", " + ::to_string(canDeduplicate);
131121
return rv;
132122
}
133123

134-
CollectionSharedMetaData::CollectionSharedMetaData(
135-
std::string_view name,
136-
ScopeID scope,
137-
cb::ExpiryLimit maxTtl,
138-
CanDeduplicate canDeduplicate)
139-
: name(name),
140-
scope(scope),
141-
maxTtl(std::move(maxTtl)),
142-
canDeduplicate(canDeduplicate) {
124+
CollectionSharedMetaData::CollectionSharedMetaData(std::string_view name,
125+
ScopeID scope,
126+
cb::ExpiryLimit maxTtl)
127+
: name(name), scope(scope), maxTtl(std::move(maxTtl)) {
143128
}
144129

145130
CollectionSharedMetaData::CollectionSharedMetaData(
146131
const CollectionSharedMetaDataView& view)
147-
: name(view.name),
148-
scope(view.scope),
149-
maxTtl(view.maxTtl),
150-
canDeduplicate(view.canDeduplicate) {
132+
: name(view.name), scope(view.scope), maxTtl(view.maxTtl) {
151133
}
152134

153135
bool CollectionSharedMetaData::operator==(
154136
const CollectionSharedMetaDataView& view) const {
155-
return name == view.name && scope == view.scope && maxTtl == view.maxTtl &&
156-
canDeduplicate == view.canDeduplicate;
137+
return name == view.name && scope == view.scope && maxTtl == view.maxTtl;
157138
}
158139

159140
bool CollectionSharedMetaData::operator==(
@@ -167,7 +148,7 @@ std::ostream& operator<<(std::ostream& os,
167148
if (meta.maxTtl) {
168149
os << ", maxTtl:" << meta.maxTtl.value().count();
169150
}
170-
return os << ", " << to_string(meta.canDeduplicate);
151+
return os;
171152
}
172153

173154
ScopeSharedMetaDataView::ScopeSharedMetaDataView(

engines/ep/src/collections/collections_types.h

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -297,23 +297,20 @@ class CollectionSharedMetaDataView {
297297
public:
298298
CollectionSharedMetaDataView(std::string_view name,
299299
ScopeID scope,
300-
cb::ExpiryLimit maxTtl,
301-
CanDeduplicate canDeduplicate);
300+
cb::ExpiryLimit maxTtl);
302301
CollectionSharedMetaDataView(const CollectionSharedMetaData&);
303302
std::string to_string() const;
304303
std::string_view name;
305304
const ScopeID scope;
306305
const cb::ExpiryLimit maxTtl;
307-
const CanDeduplicate canDeduplicate;
308306
};
309307

310308
// The type stored by the Manager SharedMetaDataTable
311309
class CollectionSharedMetaData : public RCValue {
312310
public:
313311
CollectionSharedMetaData(std::string_view name,
314312
ScopeID scope,
315-
cb::ExpiryLimit maxTtl,
316-
CanDeduplicate canDeduplicate);
313+
cb::ExpiryLimit maxTtl);
317314
CollectionSharedMetaData(const CollectionSharedMetaDataView& view);
318315
bool operator==(const CollectionSharedMetaDataView& view) const;
319316
bool operator!=(const CollectionSharedMetaDataView& view) const {
@@ -327,13 +324,8 @@ class CollectionSharedMetaData : public RCValue {
327324
const std::string name;
328325
const ScopeID scope;
329326
const cb::ExpiryLimit maxTtl;
330-
331-
// atomic: can be changed by any thread
332-
// mutable: the VB::Manifest "handle" provides a const interface (the map of
333-
// collections cannot be changed), but allows some changes to be made to
334-
// the values in that map.
335-
mutable std::atomic<CanDeduplicate> canDeduplicate;
336327
};
328+
337329
std::ostream& operator<<(std::ostream& os,
338330
const CollectionSharedMetaData& meta);
339331

engines/ep/src/collections/vbucket_manifest.cc

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -83,14 +83,13 @@ Manifest::Manifest(Manifest& other) : manager(other.manager) {
8383

8484
// Collection/Scope maps are not trivially copyable, so we do it manually
8585
for (auto& [cid, entry] : other.map) {
86-
CollectionSharedMetaDataView meta{entry.getName(),
87-
entry.getScopeID(),
88-
entry.getMaxTtl(),
89-
entry.getCanDeduplicate()};
86+
CollectionSharedMetaDataView meta{
87+
entry.getName(), entry.getScopeID(), entry.getMaxTtl()};
9088
auto [itr, inserted] =
9189
map.try_emplace(cid,
9290
other.manager->createOrReferenceMeta(cid, meta),
93-
entry.getStartSeqno());
91+
entry.getStartSeqno(),
92+
entry.getCanDeduplicate());
9493
Expects(inserted);
9594
itr->second.setItemCount(entry.getItemCount());
9695
itr->second.setDiskSize(entry.getDiskSize());
@@ -516,11 +515,12 @@ ManifestEntry& Manifest::addNewCollectionEntry(ScopeCollectionPair identifiers,
516515
CanDeduplicate canDeduplicate,
517516
int64_t startSeqno) {
518517
CollectionSharedMetaDataView meta{
519-
collectionName, identifiers.first, maxTtl, canDeduplicate};
518+
collectionName, identifiers.first, maxTtl};
520519
auto [itr, inserted] = map.try_emplace(
521520
identifiers.second,
522521
manager->createOrReferenceMeta(identifiers.second, meta),
523-
startSeqno);
522+
startSeqno,
523+
canDeduplicate);
524524

525525
if (!inserted) {
526526
throwException<std::logic_error>(

engines/ep/src/collections/vbucket_manifest_entry.cc

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ bool Collections::VB::ManifestEntry::operator==(
2323
itemCount == other.itemCount && diskSize == other.diskSize &&
2424
persistedHighSeqno == other.persistedHighSeqno &&
2525
numOpsGet == other.numOpsGet && numOpsDelete == other.numOpsDelete &&
26-
numOpsStore == other.numOpsStore && meta == other.meta;
26+
numOpsStore == other.numOpsStore && meta == other.meta &&
27+
canDeduplicate == other.canDeduplicate;
2728
}
2829

2930
std::string Collections::VB::ManifestEntry::getExceptionString(
@@ -87,5 +88,7 @@ std::ostream& Collections::VB::operator<<(
8788
if (manifestEntry.getMaxTtl()) {
8889
os << ", maxTtl:" << manifestEntry.getMaxTtl().value().count();
8990
}
91+
92+
os << ", " << manifestEntry.getCanDeduplicate();
9093
return os;
9194
}

engines/ep/src/collections/vbucket_manifest_entry.h

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,14 @@ namespace VB {
3636
class ManifestEntry {
3737
public:
3838
ManifestEntry(SingleThreadedRCPtr<const VB::CollectionSharedMetaData> meta,
39-
uint64_t startSeqno)
39+
uint64_t startSeqno,
40+
CanDeduplicate canDeduplicate)
4041
: startSeqno(startSeqno),
4142
itemCount(0),
4243
highSeqno(startSeqno),
4344
persistedHighSeqno(startSeqno),
44-
meta(std::move(meta)) {
45+
meta(std::move(meta)),
46+
canDeduplicate(canDeduplicate) {
4547
}
4648

4749
// Mark copy and move as deleted as it simplifies the lifetime of
@@ -203,11 +205,11 @@ class ManifestEntry {
203205

204206
/// @return Yes/No if this collection removes duplicate keys when possible
205207
CanDeduplicate getCanDeduplicate() const {
206-
return meta->canDeduplicate;
208+
return canDeduplicate;
207209
}
208210

209211
void setCanDeduplicate(CanDeduplicate value) {
210-
meta->canDeduplicate.store(value);
212+
canDeduplicate.store(value);
211213
}
212214

213215
private:
@@ -302,6 +304,8 @@ class ManifestEntry {
302304
* a reference to the data, which is stored inside the Manager.
303305
*/
304306
SingleThreadedRCPtr<const CollectionSharedMetaData> meta;
307+
308+
std::atomic<CanDeduplicate> canDeduplicate;
305309
};
306310

307311
std::ostream& operator<<(std::ostream& os, const ManifestEntry& manifestEntry);

engines/ep/tests/mock/mock_dcp.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -561,4 +561,5 @@ void MockDcpMessageProducers::clear_dcp_data() {
561561
last_end_status = cb::mcbp::DcpStreamEndStatus::Ok;
562562
last_high_completed_seqno = std::nullopt;
563563
last_max_visible_seqno = std::nullopt;
564+
last_can_deduplicate = CanDeduplicate::Yes;
564565
}

engines/ep/tests/mock/mock_dcp.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#pragma once
1313

1414
#include <dcp/dcp-types.h>
15+
#include <ep_types.h>
1516
#include <mcbp/protocol/opcode.h>
1617
#include <memcached/dcp.h>
1718
#include <memcached/engine.h>
@@ -196,6 +197,7 @@ class MockDcpMessageProducers : public DcpMessageProducersIface {
196197
std::optional<uint64_t> last_high_completed_seqno;
197198
std::optional<uint64_t> last_max_visible_seqno;
198199
bool isCollectionsSupported = false;
200+
CanDeduplicate last_can_deduplicate = CanDeduplicate::Yes;
199201

200202
protected:
201203
/// Helper method for deletion / deletion_v2 / expiration

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,8 @@ cb::engine_errc CollectionsDcpTestProducers::systemEventVersion2(
381381
last_collection_manifest_uid = collection->uid();
382382
last_key.assign(reinterpret_cast<const char*>(key.data()), key.size());
383383
EXPECT_NE(0, key.size());
384+
last_can_deduplicate =
385+
getCanDeduplicateFromHistory(collection->history());
384386
break;
385387
}
386388
case mcbp::systemevent::id::DeleteCollection: {

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

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4486,6 +4486,74 @@ TEST_P(CollectionsDcpPersistentOnly, ModifyCollectionNotReplicated) {
44864486
EXPECT_EQ(store->getVBucket(vbid)->getHighSeqno(), producers->last_byseqno);
44874487
}
44884488

4489+
TEST_P(CollectionsDcpPersistentOnly, ModifyCollectionTwoVbuckets) {
4490+
using namespace cb::mcbp;
4491+
using namespace mcbp::systemevent;
4492+
using namespace CollectionEntry;
4493+
4494+
// Create a second producer to simplify the step/expect. 1 VB per producer
4495+
auto* cookieP2 = create_mock_cookie(engine.get());
4496+
4497+
auto producer2 = SingleThreadedKVBucketTest::createDcpProducer(
4498+
cookieP2, IncludeDeleteTime::Yes, true /*FlatBuffers*/);
4499+
auto& mockConnMap = static_cast<MockDcpConnMap&>(engine->getDcpConnMap());
4500+
mockConnMap.addConn(cookieP2, producer);
4501+
4502+
// No transfer to consumer in this test - just check the data passed in
4503+
// each step
4504+
producers->consumer = nullptr;
4505+
4506+
// test uses 2 active VBuckets
4507+
store->setVBucketState(replicaVB, vbucket_state_active);
4508+
4509+
uint64_t rollbackSeqno = 0;
4510+
ASSERT_EQ(cb::engine_errc::success,
4511+
producer2->streamRequest(0, // flags
4512+
1, // opaque
4513+
replicaVB,
4514+
0,
4515+
~0ull, // end_seqno
4516+
0,
4517+
0,
4518+
0,
4519+
&rollbackSeqno,
4520+
&CollectionsDcpTest::dcpAddFailoverLog,
4521+
{{nullptr, 0}}));
4522+
4523+
// Create with history and then disable
4524+
CollectionsManifest cm;
4525+
cm.add(CollectionEntry::vegetable, cb::NoExpiryLimit, true);
4526+
setCollections(cookie, cm);
4527+
cm.update(vegetable, cb::NoExpiryLimit);
4528+
setCollections(cookie, cm);
4529+
4530+
// expect 4 events
4531+
// create, modify on two vbuckets.
4532+
std::array<std::shared_ptr<MockDcpProducer>, 2> dcpProducers{
4533+
{producer, producer2}};
4534+
for (auto& dcp : dcpProducers) {
4535+
SingleThreadedKVBucketTest::notifyAndStepToCheckpoint(
4536+
*dcp,
4537+
*producers,
4538+
ClientOpcode::DcpSnapshotMarker,
4539+
true /*memory*/);
4540+
dcp->stepAndExpect(*producers, ClientOpcode::DcpSystemEvent);
4541+
EXPECT_EQ(producers->last_system_event, id::CreateCollection);
4542+
EXPECT_EQ(producers->last_collection_id, vegetable.getId());
4543+
EXPECT_EQ(producers->last_can_deduplicate, CanDeduplicate::No);
4544+
4545+
// checkpoint split...
4546+
dcp->stepAndExpect(*producers, ClientOpcode::DcpSnapshotMarker);
4547+
dcp->stepAndExpect(*producers, ClientOpcode::DcpSystemEvent);
4548+
EXPECT_EQ(producers->last_system_event, id::ModifyCollection);
4549+
EXPECT_EQ(producers->last_collection_id, vegetable.getId());
4550+
EXPECT_EQ(producers->last_can_deduplicate, CanDeduplicate::Yes);
4551+
}
4552+
producer2->closeAllStreams();
4553+
mockConnMap.removeConn(cookieP2);
4554+
destroy_mock_cookie(cookieP2);
4555+
}
4556+
44894557
// Test cases which run for persistent and ephemeral buckets
44904558
INSTANTIATE_TEST_SUITE_P(CollectionsDcpEphemeralOrPersistent,
44914559
CollectionsDcpParameterizedTest,

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

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -72,20 +72,18 @@ class CollectionsVBFilterTest : public CollectionsTest {
7272
// Tests need entry data which appear in default and 'shop1' scopes
7373
std::unique_ptr<Collections::VB::ManifestEntry> entryInDefaultScope =
7474
std::make_unique<Collections::VB::ManifestEntry>(
75-
manager->createOrReferenceMeta(CollectionID::Default,
76-
{"name1",
77-
ScopeID::Default,
78-
{},
79-
CanDeduplicate::Yes}),
80-
0);
75+
manager->createOrReferenceMeta(
76+
CollectionID::Default,
77+
{"name1", ScopeID::Default, {}}),
78+
0,
79+
CanDeduplicate::Yes);
8180
std::unique_ptr<Collections::VB::ManifestEntry> entryInShop1Scope =
8281
std::make_unique<Collections::VB::ManifestEntry>(
83-
manager->createOrReferenceMeta(CollectionID::Default,
84-
{"name2",
85-
ScopeUid::shop1,
86-
{},
87-
CanDeduplicate::Yes}),
88-
0);
82+
manager->createOrReferenceMeta(
83+
CollectionID::Default,
84+
{"name2", ScopeUid::shop1, {}}),
85+
0,
86+
CanDeduplicate::Yes);
8987

9088
CookieIface* cookie = nullptr;
9189
};

0 commit comments

Comments
 (0)