Skip to content

Commit 4b3c037

Browse files
committed
MB-49040: 6/n Only check limits when limits are present
Avoid a scope map lookup + value compare if there are no limits to check. A VB:Manifest flag now exists that is set everytime a VB::Manifest is updated against a Manifest from the cluster. If any scope is found to have a limit, we enable the flag or disable if no scopes with limits are found. Change-Id: I83a7062822d6eb7805164e411600a6287b3bf737 Reviewed-on: http://review.couchbase.org/c/kv_engine/+/164342 Tested-by: Build Bot <build@couchbase.com> Reviewed-by: Dave Rigby <daver@couchbase.com>
1 parent 6f2ce83 commit 4b3c037

File tree

5 files changed

+232
-35
lines changed

5 files changed

+232
-35
lines changed

engines/ep/src/collections/manifest.cc

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -769,7 +769,7 @@ std::ostream& operator<<(std::ostream& os, const Manifest& manifest) {
769769
<< ", uid:" << manifest.uid
770770
<< ", collections.size:" << manifest.collections.size() << std::endl;
771771
for (const auto& entry : manifest.scopes) {
772-
os << "scope:{" << entry.first.to_string() << ", " << entry.second.name;
772+
os << "scope:{" << entry.first << ", " << entry.second.name;
773773

774774
if (entry.second.dataLimit) {
775775
os << ", dataLimit:" << entry.second.dataLimit.value()
@@ -779,8 +779,8 @@ std::ostream& operator<<(std::ostream& os, const Manifest& manifest) {
779779
os << ", collections:[";
780780

781781
for (const auto& collection : entry.second.collections) {
782-
os << "{cid:" << collection.cid.to_string() << ", sid:" << std::hex
783-
<< collection.sid << ", " << collection.name
782+
os << "{cid:" << collection.cid << ", sid:" << collection.sid
783+
<< ", " << collection.name
784784
<< ", ttl:" << (collection.maxTtl.has_value() ? "yes" : "no")
785785
<< ":"
786786
<< collection.maxTtl.value_or(std::chrono::seconds(0)).count()
@@ -790,9 +790,8 @@ std::ostream& operator<<(std::ostream& os, const Manifest& manifest) {
790790
}
791791

792792
for (const auto& [key, collection] : manifest.collections) {
793-
os << "{cid key:" << key.to_string()
794-
<< ", value:" << collection.cid.to_string() << ", sid:" << std::hex
795-
<< collection.sid << ", " << collection.name
793+
os << "{cid key:" << key.to_string() << ", value:" << collection.cid
794+
<< ", sid:" << collection.sid << ", " << collection.name
796795
<< ", ttl:" << (collection.maxTtl.has_value() ? "yes" : "no") << ":"
797796
<< collection.maxTtl.value_or(std::chrono::seconds(0)).count()
798797
<< "}";

engines/ep/src/collections/vbucket_manifest.cc

Lines changed: 52 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -237,27 +237,26 @@ ManifestUpdateStatus Manifest::update(VBucket& vb,
237237

238238
// If manifest was equal.
239239
if (manifest.getUid() == manifestUid) {
240-
if (changes.empty()) {
240+
if (changes.none()) {
241241
return ManifestUpdateStatus::Success;
242-
} else if (changes.onlyScopesModified()) {
243-
EP_LOG_WARN("Manifest::update {} equal uid and {} scopes modified",
244-
vb.getId(),
245-
changes.scopesToModify.size());
246-
} else {
247-
// Log verbosely for this case
242+
} else if (changes.wouldCreateOrDrop()) {
243+
// Log verbosely for this case. An equal manifest should not change
244+
// the scope or collection membership
248245
EP_LOG_WARN(
249246
"Manifest::update {} with equal uid:{:#x} but differences "
250-
"scopes+:{}, collections+:{}, scopes-:{}, collections-:{}, "
251-
"scopes-modified:{}",
247+
"scopes+:{}, collections+:{}, scopes-:{}, collections-:{}",
252248
vb.getId(),
253249
manifestUid,
254250
changes.scopesToCreate.size(),
255251
changes.collectionsToCreate.size(),
256252
changes.scopesToDrop.size(),
257-
changes.collectionsToDrop.size(),
258-
changes.scopesToModify.size());
253+
changes.collectionsToDrop.size());
259254
return ManifestUpdateStatus::EqualUidWithDifferences;
260255
}
256+
// else a scope modification or change of scopeWithDataLimitExists is
257+
// allowed when the uid is equal
258+
Expects(!changes.scopesToModify.empty() ||
259+
changes.changeScopeWithDataLimitExists);
261260
}
262261

263262
completeUpdate(std::move(upgradeLock), vb, changes);
@@ -292,25 +291,39 @@ void Manifest::completeUpdate(mutex_type::UpgradeHolder&& upgradeLock,
292291
WriteHandle wHandle(*this, std::move(upgradeLock));
293292

294293
// Capture the UID
295-
if (changeset.empty()) {
294+
if (changeset.none()) {
296295
updateUid(changeset.uid, false /*reset*/);
297296
return;
298297
}
299298

299+
if (changeset.changeScopeWithDataLimitExists) {
300+
EP_LOG_INFO(
301+
"Manifest::completeUpdate {} toggling scopeWithDataLimitExists "
302+
"{} -> {}",
303+
vb.getId(),
304+
scopeWithDataLimitExists,
305+
changeset.changeScopeWithDataLimitExists.value());
306+
scopeWithDataLimitExists =
307+
changeset.changeScopeWithDataLimitExists.value();
308+
}
309+
300310
auto finalDeletion = applyDrops(wHandle, vb, changeset.collectionsToDrop);
301311
if (finalDeletion) {
302-
auto uid = changeset.empty() ? changeset.uid : manifestUid;
303-
dropCollection(
304-
wHandle, vb, uid, *finalDeletion, OptionalSeqno{/*no-seqno*/});
312+
// if the changeset is now 'drained' of create/drop, then this is final
313+
// the final change to make - so use changeset.uid
314+
dropCollection(wHandle,
315+
vb,
316+
changeset.getUidForChange(manifestUid),
317+
*finalDeletion,
318+
OptionalSeqno{/*no-seqno*/});
305319
}
306320

307321
auto finalScopeCreate =
308322
applyScopeCreates(wHandle, vb, changeset.scopesToCreate);
309323
if (finalScopeCreate) {
310-
auto uid = changeset.empty() ? changeset.uid : manifestUid;
311324
createScope(wHandle,
312325
vb,
313-
uid,
326+
changeset.getUidForChange(manifestUid),
314327
finalScopeCreate.value().sid,
315328
finalScopeCreate.value().name,
316329
finalScopeCreate.value().dataLimit,
@@ -321,10 +334,9 @@ void Manifest::completeUpdate(mutex_type::UpgradeHolder&& upgradeLock,
321334
applyCreates(wHandle, vb, changeset.collectionsToCreate);
322335

323336
if (finalAddition) {
324-
auto uid = changeset.empty() ? changeset.uid : manifestUid;
325337
createCollection(wHandle,
326338
vb,
327-
uid,
339+
changeset.getUidForChange(manifestUid),
328340
finalAddition.value().identifiers,
329341
finalAddition.value().name,
330342
finalAddition.value().maxTtl,
@@ -346,6 +358,7 @@ void Manifest::completeUpdate(mutex_type::UpgradeHolder&& upgradeLock,
346358
// Can do the scope modifications last - these generate no events but will
347359
// just sync any scope to the manifest
348360
for (const auto& modified : changeset.scopesToModify) {
361+
// Here the changeset.uid is used as it's for logging
349362
modifyScope(wHandle, vb, changeset.uid, modified);
350363
}
351364
}
@@ -696,6 +709,8 @@ Manifest::ManifestChanges Manifest::processManifest(
696709
const Collections::Manifest& manifest) {
697710
ManifestChanges rv{manifest.getUid()};
698711

712+
bool scopeWithDataLimitExists{false};
713+
699714
// First iterate through the collections of this VB::Manifest
700715
for (const auto& [cid, entry] : map) {
701716
// Look-up the collection inside the new manifest
@@ -737,6 +752,10 @@ Manifest::ManifestChanges Manifest::processManifest(
737752
}
738753
}
739754

755+
if (scopeItr->second.dataLimit) {
756+
scopeWithDataLimitExists = true;
757+
}
758+
740759
for (const auto& m : scopeItr->second.collections) {
741760
auto mapItr = map.find(m.cid);
742761

@@ -747,6 +766,10 @@ Manifest::ManifestChanges Manifest::processManifest(
747766
}
748767
}
749768

769+
if (this->scopeWithDataLimitExists != scopeWithDataLimitExists) {
770+
rv.changeScopeWithDataLimitExists = scopeWithDataLimitExists;
771+
}
772+
750773
return rv;
751774
}
752775

@@ -1167,6 +1190,10 @@ bool Manifest::addScopeStats(Vbid vbid, const StatCollector& collector) const {
11671190
format_to(key, "vb_{}:manifest:uid", vbid.get());
11681191
collector.addStat(std::string_view(key.data(), key.size()), manifestUid);
11691192

1193+
format_to(key, "vb_{}:scope_data_limit", vbid.get());
1194+
collector.addStat(std::string_view(key.data(), key.size()),
1195+
scopeWithDataLimitExists);
1196+
11701197
for (const auto& [sid, value] : scopes) {
11711198
key.resize(0);
11721199
format_to(key, "vb_{}:{}:name:", vbid.get(), sid);
@@ -1273,6 +1300,10 @@ bool Manifest::operator!=(const Manifest& rhs) const {
12731300

12741301
cb::engine_errc Manifest::getScopeDataLimitStatus(
12751302
const container::const_iterator itr, size_t nBytes) const {
1303+
if (!scopeWithDataLimitExists) {
1304+
return cb::engine_errc::success;
1305+
}
1306+
12761307
const auto& entry = getScopeEntry(itr->second.getScopeID());
12771308

12781309
if (entry.getDataLimit() &&
@@ -1446,6 +1477,8 @@ std::ostream& operator<<(std::ostream& os,
14461477
std::ostream& operator<<(std::ostream& os, const Manifest& manifest) {
14471478
os << "VB::Manifest: "
14481479
<< "uid:" << manifest.manifestUid
1480+
<< "scopeWithDataLimitExists:" << manifest.scopeWithDataLimitExists
1481+
<< "dropInProgress:" << manifest.dropInProgress.load()
14491482
<< ", scopes.size:" << manifest.scopes.size()
14501483
<< ", map.size:" << manifest.map.size() << std::endl;
14511484
for (const auto& [cid, entry] : manifest.map) {

engines/ep/src/collections/vbucket_manifest.h

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -318,18 +318,29 @@ class Manifest {
318318
std::vector<ScopeModified> scopesToModify;
319319
std::vector<CollectionCreation> collectionsToCreate;
320320
std::vector<CollectionID> collectionsToDrop;
321+
// stores any new value (if needed)
322+
std::optional<bool> changeScopeWithDataLimitExists;
323+
321324
const ManifestUid uid{0};
322325

323-
bool empty() const {
326+
/// @return true if no changes are to be made
327+
bool none() const {
324328
return scopesToCreate.empty() && scopesToModify.empty() &&
325329
scopesToDrop.empty() && collectionsToCreate.empty() &&
326-
collectionsToDrop.empty();
330+
collectionsToDrop.empty() && !changeScopeWithDataLimitExists;
331+
}
332+
333+
/// @return true if there are collections/scopes to create or drop
334+
bool wouldCreateOrDrop() const {
335+
return !(scopesToCreate.empty() && scopesToDrop.empty() &&
336+
collectionsToCreate.empty() && collectionsToDrop.empty());
327337
}
328338

329-
bool onlyScopesModified() const {
330-
return scopesToCreate.empty() && scopesToDrop.empty() &&
331-
collectionsToCreate.empty() && collectionsToDrop.empty() &&
332-
!scopesToModify.empty();
339+
ManifestUid getUidForChange(ManifestUid theirUid) {
340+
// If the set of changes no longer will create or drop, i.e. is now
341+
// 'drained' of creates or drops then the correct uid to now use is
342+
// the change set uid
343+
return !wouldCreateOrDrop() ? uid : theirUid;
333344
}
334345
};
335346

@@ -1035,6 +1046,12 @@ class Manifest {
10351046
/// Manager of collections
10361047
const std::shared_ptr<Manager> manager;
10371048

1049+
/**
1050+
* Flag to help avoid limit checking when there are no limits to check.
1051+
* This is written with the write lock and only readable via the read lock.
1052+
*/
1053+
bool scopeWithDataLimitExists{false};
1054+
10381055
/**
10391056
* Flag value which indicates if dropped collections exist, that includes
10401057
* documents and metadata. This is atomic because we don't require the

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

Lines changed: 80 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ TEST_P(CollectionsDcpParameterizedTest,
4646
store->setVBucketState(vbid, vbucket_state_replica);
4747

4848
// No active vbuckets, so shop1 isn't applied anywhere yet
49-
const size_t limit = 1024;
49+
const size_t limit = 10;
5050
CollectionsManifest cm;
5151
cm.add(ScopeEntry::shop1,
5252
limit * store->getEPEngine().getConfiguration().getMaxVbuckets());
@@ -83,6 +83,36 @@ TEST_P(CollectionsDcpParameterizedTest,
8383
EXPECT_TRUE(vb->getManifest().lock().getDataLimit(ScopeEntry::shop1));
8484
EXPECT_EQ(limit,
8585
vb->getManifest().lock().getDataLimit(ScopeEntry::shop1).value());
86+
87+
// Check we fail limits on the active vbucket
88+
store_item(vbid,
89+
StoredDocKey{"k1", CollectionEntry::fruit},
90+
std::string(limit + 1, 'a'),
91+
0,
92+
{cb::engine_errc::scope_size_limit_exceeded});
93+
store_item(vbid,
94+
StoredDocKey{"k1", CollectionEntry::fruit},
95+
std::string(limit, 'a'));
96+
97+
// for completeness - the other vb will reject for other reasons
98+
store_item(replicaVB,
99+
StoredDocKey{"k1", CollectionEntry::fruit},
100+
std::string(limit + 1, 'a'),
101+
0,
102+
{cb::engine_errc::not_my_vbucket});
103+
104+
// And check when active it rejects
105+
store->setVBucketState(replicaVB, vbucket_state_active);
106+
107+
store_item(replicaVB,
108+
StoredDocKey{"k1", CollectionEntry::fruit},
109+
std::string(limit + 1, 'a'),
110+
0,
111+
{cb::engine_errc::scope_size_limit_exceeded});
112+
// but ok for at the limit
113+
store_item(replicaVB,
114+
StoredDocKey{"k1", CollectionEntry::fruit},
115+
std::string(limit, 'a'));
86116
}
87117

88118
// Test that when an active vbucket has already applied the scope with limit
@@ -101,7 +131,7 @@ TEST_P(CollectionsDcpParameterizedTest,
101131
TEST_P(CollectionsDcpParameterizedTest,
102132
replica_gets_scope_with_limit_from_active) {
103133
CollectionsManifest cm;
104-
const size_t limit = 1024;
134+
const size_t limit = 10;
105135
cm.add(ScopeEntry::shop1,
106136
limit * store->getEPEngine().getConfiguration().getMaxVbuckets());
107137
cm.add(CollectionEntry::fruit, ScopeEntry::shop1);
@@ -146,6 +176,29 @@ TEST_P(CollectionsDcpParameterizedTest,
146176
EXPECT_TRUE(vb->getManifest().lock().getDataLimit(ScopeEntry::shop1));
147177
EXPECT_EQ(limit,
148178
vb->getManifest().lock().getDataLimit(ScopeEntry::shop1).value());
179+
180+
// Check we fail limits on both vbuckets if the size is right.
181+
// If we have the scopeWithDataLimitExists code wrong, the limits wouldn't
182+
// be checked
183+
store_item(vbid,
184+
StoredDocKey{"k1", CollectionEntry::fruit},
185+
std::string(limit + 1, 'a'),
186+
0,
187+
{cb::engine_errc::scope_size_limit_exceeded});
188+
189+
store_item(replicaVB,
190+
StoredDocKey{"k1", CollectionEntry::fruit},
191+
std::string(limit + 1, 'a'),
192+
0,
193+
{cb::engine_errc::scope_size_limit_exceeded});
194+
195+
// But can write upto the limit
196+
store_item(replicaVB,
197+
StoredDocKey{"k1", CollectionEntry::fruit},
198+
std::string(limit, 'a'));
199+
store_item(vbid,
200+
StoredDocKey{"k1", CollectionEntry::fruit},
201+
std::string(limit, 'a'));
149202
}
150203

151204
// Test that when only replicas exist and they are the first to create a scope
@@ -203,7 +256,7 @@ TEST_P(CollectionsDcpParameterizedTest, active_updates_limit) {
203256

204257
// Now set shop1 with limit
205258
CollectionsManifest cm;
206-
const size_t limit = 1024;
259+
const size_t limit = 10;
207260
cm.add(ScopeEntry::shop1,
208261
limit * store->getEPEngine().getConfiguration().getMaxVbuckets());
209262
cm.add(CollectionEntry::fruit, ScopeEntry::shop1);
@@ -220,6 +273,29 @@ TEST_P(CollectionsDcpParameterizedTest, active_updates_limit) {
220273
.lock()
221274
.getDataLimit(ScopeEntry::shop1)
222275
.value());
276+
277+
// Check we fail limits on both vbuckets if the size is right.
278+
// If we have the scopeWithDataLimitExists code wrong, the limits wouldn't
279+
// be checked
280+
store_item(vbid,
281+
StoredDocKey{"k1", CollectionEntry::fruit},
282+
std::string(limit + 1, 'a'),
283+
0,
284+
{cb::engine_errc::scope_size_limit_exceeded});
285+
286+
store_item(replicaVB,
287+
StoredDocKey{"k1", CollectionEntry::fruit},
288+
std::string(limit + 1, 'a'),
289+
0,
290+
{cb::engine_errc::scope_size_limit_exceeded});
291+
292+
// But can write upto the limit
293+
store_item(replicaVB,
294+
StoredDocKey{"k1", CollectionEntry::fruit},
295+
std::string(limit, 'a'));
296+
store_item(vbid,
297+
StoredDocKey{"k1", CollectionEntry::fruit},
298+
std::string(limit, 'a'));
223299
}
224300

225301
TEST_F(CollectionsTest, ScopeWithManyCollectionsWarmup) {
@@ -253,4 +329,4 @@ TEST_F(CollectionsTest, ScopeWithManyCollectionsWarmup) {
253329
EXPECT_EQ(ds,
254330
store->getVBucket(vbid)->getManifest().lock().getDataSize(
255331
ScopeEntry::shop1));
256-
}
332+
}

0 commit comments

Comments
 (0)