1 /* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
3 * Copyright 2017 Couchbase, Inc
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
19 * Tests specific to Ephemeral VBuckets.
24 #include "../mock/mock_ephemeral_vb.h"
25 #include "failover-table.h"
26 #include "test_helpers.h"
27 #include "thread_gate.h"
28 #include "vbucket_test.h"
32 class EphemeralVBucketTest : public VBucketTest {
35 /* to test ephemeral vbucket specific stuff */
36 mockEpheVB = new MockEphemeralVBucket(0,
45 /*newSeqnoCb*/ nullptr,
48 /* vbucket manages the life time of mockEpheVB and is a base test class
50 vbucket.reset(mockEpheVB);
57 /* We want a ptr to MockEphemeralVBucket as we test ephemeral vbucket
58 specific stuff in this class */
59 MockEphemeralVBucket* mockEpheVB;
62 CheckpointConfig checkpoint_config;
66 // Verify that attempting to pageOut an item twice has no effect the second
68 TEST_F(EphemeralVBucketTest, DoublePageOut) {
69 auto key = makeStoredDocKey("key");
70 ASSERT_EQ(AddStatus::Success, addOne(key));
71 ASSERT_EQ(1, vbucket->getNumItems());
73 auto lock_sv = lockAndFind(key);
74 auto* storedVal = lock_sv.second;
75 ASSERT_FALSE(storedVal->isDeleted());
77 // Page out the item (once).
78 EXPECT_TRUE(vbucket->pageOut(lock_sv.first, storedVal));
79 EXPECT_EQ(0, vbucket->getNumItems());
80 EXPECT_TRUE(storedVal->isDeleted());
82 // Attempt to page out again - should not be possible.
83 EXPECT_FALSE(vbucket->pageOut(lock_sv.first, storedVal));
84 EXPECT_EQ(0, vbucket->getNumItems());
85 EXPECT_TRUE(storedVal->isDeleted());
89 // Verify that we can pageOut deleted items which have a value associated with
90 // them - and afterwards the value is null.
91 TEST_F(EphemeralVBucketTest, PageOutAfterDeleteWithValue) {
92 // Add an item which is marked as deleted, but has a body (e.g. system
94 auto key = makeStoredDocKey("key");
95 std::string value = "deleted value";
96 Item item(key, 0, /*expiry*/0, value.data(), value.size());
98 ASSERT_EQ(AddStatus::Success, public_processAdd(item));
99 ASSERT_EQ(0, vbucket->getNumItems());
101 // Check preconditions
102 auto lock_sv = lockAndFind(key);
103 auto* storedVal = lock_sv.second;
104 ASSERT_TRUE(storedVal->isDeleted());
105 ASSERT_EQ(value, storedVal->getValue()->to_s());
108 EXPECT_TRUE(vbucket->pageOut(lock_sv.first, storedVal));
109 EXPECT_EQ(0, vbucket->getNumItems());
110 EXPECT_TRUE(storedVal->isDeleted());
111 EXPECT_FALSE(storedVal->getValue());
114 // NRU: check the seqlist has correct statistics for a create, pageout,
115 // and (re)create of the same key.
116 TEST_F(EphemeralVBucketTest, CreatePageoutCreate) {
117 auto key = makeStoredDocKey("key");
119 // Add a key, then page out.
120 ASSERT_EQ(AddStatus::Success, addOne(key));
122 auto lock_sv = lockAndFind(key);
123 EXPECT_TRUE(vbucket->pageOut(lock_sv.first, lock_sv.second));
125 // Sanity check - should have just the one deleted item.
126 ASSERT_EQ(0, vbucket->getNumItems());
127 ASSERT_EQ(1, mockEpheVB->getLL()->getNumDeletedItems());
129 // Test: Set the key again.
130 ASSERT_EQ(MutationStatus::WasDirty, setOne(key));
132 EXPECT_EQ(1, vbucket->getNumItems());
133 EXPECT_EQ(0, mockEpheVB->getLL()->getNumDeletedItems());
135 // Finally for good measure, delete again and check the numbers are correct.
137 auto lock_sv = lockAndFind(key);
138 EXPECT_TRUE(vbucket->pageOut(lock_sv.first, lock_sv.second));
140 EXPECT_EQ(0, vbucket->getNumItems());
141 EXPECT_EQ(1, mockEpheVB->getLL()->getNumDeletedItems());
144 TEST_F(EphemeralVBucketTest, SetItems) {
145 const int numItems = 3;
147 auto keys = generateKeys(numItems);
148 setMany(keys, MutationStatus::WasClean);
150 EXPECT_EQ(numItems, vbucket->getNumItems());
151 EXPECT_EQ(numItems, vbucket->getHighSeqno());
154 TEST_F(EphemeralVBucketTest, UpdateItems) {
155 /* Add 3 items and then update all of them */
156 const int numItems = 3;
158 auto keys = generateKeys(numItems);
159 setMany(keys, MutationStatus::WasClean);
161 /* Update the items */
162 setMany(keys, MutationStatus::WasDirty);
164 EXPECT_EQ(numItems * 2, vbucket->getHighSeqno());
165 EXPECT_EQ(numItems, vbucket->getNumItems());
168 TEST_F(EphemeralVBucketTest, SoftDelete) {
169 /* Add 3 items and then delete all of them */
170 const int numItems = 3;
172 auto keys = generateKeys(numItems);
173 setMany(keys, MutationStatus::WasClean);
175 /* soft delete all */
176 softDeleteMany(keys, MutationStatus::WasDirty);
178 EXPECT_EQ(numItems * 2, vbucket->getHighSeqno());
179 EXPECT_EQ(0, vbucket->getNumItems());
182 TEST_F(EphemeralVBucketTest, Backfill) {
183 /* Add 3 items and get them by backfill */
184 const int numItems = 3;
186 auto keys = generateKeys(numItems);
187 setMany(keys, MutationStatus::WasClean);
189 auto res = mockEpheVB->inMemoryBackfill(1, numItems);
190 EXPECT_EQ(ENGINE_SUCCESS, std::get<0>(res));
191 EXPECT_EQ(numItems, std::get<1>(res).size());
194 TEST_F(EphemeralVBucketTest, UpdateDuringBackfill) {
195 /* Add 5 items and then update all of them */
196 const int numItems = 5;
198 auto keys = generateKeys(numItems);
199 setMany(keys, MutationStatus::WasClean);
201 /* Set up a mock backfill by setting the range of the backfill */
202 mockEpheVB->registerFakeReadRange(2, numItems - 1);
204 /* Update the first, middle and last item in the range read and 2 items
205 that are outside (before and after) range read */
206 ASSERT_EQ(MutationStatus::WasDirty, setOne(keys[0]));
207 for (int i = 1; i < numItems - 1; ++i) {
208 ASSERT_EQ(MutationStatus::WasClean, setOne(keys[i]));
210 ASSERT_EQ(MutationStatus::WasDirty, setOne(keys[numItems - 1]));
212 /* Hash table must have only recent (updated) items */
213 EXPECT_EQ(numItems, vbucket->getNumItems());
215 /* High Seqno must be 2 * numItems */
216 EXPECT_EQ(numItems * 2, vbucket->getHighSeqno());
218 /* LinkedList must have 3 stale items */
219 EXPECT_EQ(3, mockEpheVB->public_getNumStaleItems());
221 EXPECT_EQ(numItems * 2 - /* since 2 items are deduped*/ 2,
222 mockEpheVB->public_getNumListItems());
225 TEST_F(EphemeralVBucketTest, GetAndUpdateTtl) {
226 const int numItems = 2;
229 auto keys = generateKeys(numItems);
230 setMany(keys, MutationStatus::WasClean);
232 ASSERT_EQ(numItems, vbucket->getNumItems());
233 EXPECT_EQ(numItems, vbucket->getHighSeqno());
234 EXPECT_EQ(0, mockEpheVB->public_getNumStaleItems());
236 /* --- basic test --- */
237 /* set the ttl of one item */
238 GetValue gv1 = public_getAndUpdateTtl(keys[0], 100);
240 /* New seqno should have been used */
241 EXPECT_EQ(numItems + 1, vbucket->getHighSeqno());
243 /* No.of items in the bucket should NOT change */
244 EXPECT_EQ(numItems, vbucket->getNumItems());
246 /* No.of items in the list should NOT change */
247 EXPECT_EQ(numItems, mockEpheVB->public_getNumListItems());
249 /* There should be NO stale items */
250 EXPECT_EQ(0, mockEpheVB->public_getNumStaleItems());
252 /* --- Repeat the above test with a similated ReadRange --- */
253 mockEpheVB->registerFakeReadRange(1, numItems);
254 GetValue gv2 = public_getAndUpdateTtl(keys[1], 101);
256 /* New seqno should have been used */
257 EXPECT_EQ(numItems + 2, vbucket->getHighSeqno());
259 /* No.of items in the bucket should remain the same */
260 EXPECT_EQ(numItems, vbucket->getNumItems());
262 /* No.of items in the sequence list should inc by 1 */
263 EXPECT_EQ(numItems + 1, mockEpheVB->public_getNumListItems());
265 /* There should be 1 stale item */
266 EXPECT_EQ(1, mockEpheVB->public_getNumStaleItems());
268 auto seqNoVec = mockEpheVB->getLL()->getAllSeqnoForVerification();
269 seqno_t prevSeqNo = 0;
271 for (const auto& seqNo : seqNoVec) {
272 EXPECT_GT(seqNo, prevSeqNo);
276 // explicit delete to keep valgrind happy
277 delete (Item*)gv1.getValue();
278 delete (Item*)gv2.getValue();
281 TEST_F(EphemeralVBucketTest, SoftDeleteDuringBackfill) {
282 /* Add 5 items and then soft delete all of them */
283 const int numItems = 5;
285 auto keys = generateKeys(numItems);
286 setMany(keys, MutationStatus::WasClean);
288 /* Set up a mock backfill by setting the range of the backfill */
289 mockEpheVB->registerFakeReadRange(2, numItems - 1);
291 /* Update the first, middle and last item in the range read and 2 items
292 that are outside (before and after) range read */
293 softDeleteMany(keys, MutationStatus::WasDirty);
295 /* Hash table must have only recent (updated) items */
296 EXPECT_EQ(0, vbucket->getNumItems());
298 /* High Seqno must be 2 * numItems */
299 EXPECT_EQ(numItems * 2, vbucket->getHighSeqno());
301 /* LinkedList must have 3 stale items */
302 EXPECT_EQ(3, mockEpheVB->public_getNumStaleItems());
304 EXPECT_EQ(numItems * 2 - /* since 2 items are deduped*/ 2,
305 mockEpheVB->public_getNumListItems());
308 // EphemeralVB Tombstone Purging //////////////////////////////////////////////
310 class EphTombstoneTest : public EphemeralVBucketTest {
312 void SetUp() override {
313 EphemeralVBucketTest::SetUp();
315 // Store three items to work with.
316 keys = generateKeys(3);
317 setMany(keys, MutationStatus::WasClean);
318 ASSERT_EQ(3, vbucket->getNumItems());
320 std::vector<StoredDocKey> keys;
323 // Check an empty seqList is handled correctly.
324 TEST_F(EphTombstoneTest, ZeroElementPurge) {
325 // Create a new empty VB (using parent class SetUp).
326 EphemeralVBucketTest::SetUp();
327 ASSERT_EQ(0, mockEpheVB->public_getNumListItems());
329 EXPECT_EQ(0, mockEpheVB->purgeTombstones(0));
332 // Check a seqList with one element is handled correctly.
333 TEST_F(EphTombstoneTest, OneElementPurge) {
334 // Create a new empty VB (using parent class SetUp).
335 EphemeralVBucketTest::SetUp();
336 ASSERT_EQ(MutationStatus::WasClean, setOne(makeStoredDocKey("one")));
337 ASSERT_EQ(1, mockEpheVB->public_getNumListItems());
339 EXPECT_EQ(0, mockEpheVB->purgeTombstones(0));
342 // Check that nothing is purged if no items are stale.
343 TEST_F(EphTombstoneTest, NoPurgeIfNoneStale) {
344 // Run purger - nothing should be removed.
345 EXPECT_EQ(0, mockEpheVB->purgeTombstones(0));
346 EXPECT_EQ(keys.size(), vbucket->getNumItems());
349 // Check that deletes are not purged if they are not old enough.
350 TEST_F(EphTombstoneTest, NoPurgeIfNoneOldEnough) {
351 // Delete the first item "now"
352 softDeleteOne(keys.at(0), MutationStatus::WasDirty);
353 ASSERT_EQ(2, vbucket->getNumItems());
354 ASSERT_EQ(1, vbucket->getNumInMemoryDeletes());
356 // Advance time by 5 seconds and run the EphTombstonePurger specifying a
357 // purge_age of 10s - nothing
359 TimeTraveller theTerminator(5);
360 EXPECT_EQ(0, mockEpheVB->purgeTombstones(10));
362 EXPECT_EQ(2, vbucket->getNumItems());
363 EXPECT_EQ(1, vbucket->getNumInMemoryDeletes());
366 // Check that items should be purged when they are old enough.
367 TEST_F(EphTombstoneTest, OnePurgeIfDeletedItemOld) {
368 // Delete the first item "now"
369 softDeleteOne(keys.at(0), MutationStatus::WasDirty);
370 ASSERT_EQ(2, vbucket->getNumItems());
371 ASSERT_EQ(1, vbucket->getNumInMemoryDeletes());
373 // Delete the second item at time 30.
374 TimeTraveller looper(30);
375 softDeleteOne(keys.at(1), MutationStatus::WasDirty);
376 ASSERT_EQ(1, vbucket->getNumItems());
377 ASSERT_EQ(2, vbucket->getNumInMemoryDeletes());
379 // and the third at time 60.
380 TimeTraveller looper2(30);
381 softDeleteOne(keys.at(2), MutationStatus::WasDirty);
382 ASSERT_EQ(0, vbucket->getNumItems());
383 ASSERT_EQ(3, vbucket->getNumInMemoryDeletes());
385 // Run the EphTombstonePurger specifying a purge_age of 60s - only key0
387 mockEpheVB->purgeTombstones(60);
389 EXPECT_EQ(0, vbucket->getNumItems());
390 EXPECT_EQ(2, vbucket->getNumInMemoryDeletes());
391 EXPECT_EQ(4, vbucket->getPurgeSeqno())
392 << "Should have purged up to 4th update (1st delete, after 3 sets)";
393 EXPECT_EQ(nullptr, findValue(keys.at(0)));
394 EXPECT_NE(nullptr, findValue(keys.at(1)));
395 EXPECT_NE(nullptr, findValue(keys.at(2)));
398 // Check that deleted items can be purged immediately.
399 TEST_F(EphTombstoneTest, ImmediateDeletedPurge) {
400 // Advance to non-zero time.
401 TimeTraveller jamesCole(10);
403 // Delete the first item at 10s
404 softDeleteOne(keys.at(0), MutationStatus::WasDirty);
405 ASSERT_EQ(2, vbucket->getNumItems());
406 ASSERT_EQ(1, vbucket->getNumInMemoryDeletes());
408 // Run the EphTombstonePurger specifying a purge_age of 0s - key0 should
409 // be immediately purged.
410 mockEpheVB->purgeTombstones(0);
411 EXPECT_EQ(2, vbucket->getNumItems());
412 EXPECT_EQ(0, vbucket->getNumInMemoryDeletes());
413 EXPECT_EQ(4, vbucket->getPurgeSeqno())
414 << "Should have purged up to 4th update (1st delete, after 3 sets)";
415 EXPECT_EQ(nullptr, findValue(keys.at(0)));
416 EXPECT_NE(nullptr, findValue(keys.at(1)));
417 EXPECT_NE(nullptr, findValue(keys.at(2)));
420 // Check that alive, stale items have no constraint on age.
421 TEST_F(EphTombstoneTest, ImmediatePurgeOfAliveStale) {
422 // Perform a mutation on the second element, with a (fake) Range Read in
423 // place; causing the initial OSV to be marked as stale and a new OSV to
424 // be added for that key.
425 auto& seqList = mockEpheVB->getLL()->getSeqList();
427 std::lock_guard<std::mutex> rrGuard(
428 mockEpheVB->getLL()->getRangeReadLock());
429 mockEpheVB->registerFakeReadRange(1, 2);
430 ASSERT_EQ(MutationStatus::WasClean, setOne(keys.at(1)));
432 // Sanity check - our state is as expected:
433 ASSERT_EQ(3, vbucket->getNumItems());
434 ASSERT_EQ(4, seqList.size());
435 auto staleIt = std::next(seqList.begin());
436 auto newIt = seqList.rbegin();
437 ASSERT_EQ(staleIt->getKey(), newIt->getKey());
439 std::lock_guard<std::mutex> writeGuard(
440 mockEpheVB->getLL()->getListWriteLock());
441 ASSERT_TRUE(staleIt->isStale(writeGuard));
442 ASSERT_FALSE(newIt->isStale(writeGuard));
445 // Attempt a purge - should not remove anything as read range is in
447 EXPECT_EQ(0, mockEpheVB->purgeTombstones(0));
448 EXPECT_EQ(3, vbucket->getNumItems());
449 EXPECT_EQ(4, seqList.size());
450 EXPECT_EQ(0, vbucket->getPurgeSeqno());
452 // Clear the ReadRange (so we can actually purge items) and retry the
453 // purge which should now succeed.
454 mockEpheVB->getLL()->resetReadRange();
457 EXPECT_EQ(1, mockEpheVB->purgeTombstones(0));
458 EXPECT_EQ(3, vbucket->getNumItems());
459 EXPECT_EQ(3, seqList.size());
462 // Test that deleted items purged out of order are handled correctly (and
463 // highestDeletedPurged is updated).
464 TEST_F(EphTombstoneTest, PurgeOutOfOrder) {
465 // Delete the 3rd item.
466 softDeleteOne(keys.at(2), MutationStatus::WasDirty);
468 // Run the tombstone purger.
469 mockEpheVB->getLL()->resetReadRange();
470 ASSERT_EQ(1, mockEpheVB->purgeTombstones(0));
471 ASSERT_EQ(2, vbucket->getNumItems());
472 EXPECT_EQ(4, vbucket->getPurgeSeqno());
474 // Delete the 1st item
475 softDeleteOne(keys.at(0), MutationStatus::WasDirty);
477 // Run the tombstone purger. This should succeed, but with
478 // highestDeletedPurged unchanged.
479 ASSERT_EQ(1, mockEpheVB->purgeTombstones(0));
480 ASSERT_EQ(1, vbucket->getNumItems());
481 EXPECT_EQ(5, vbucket->getPurgeSeqno());
484 // Thread-safety test (intended to run via Valgrind / ASan / TSan) -
485 // perform sets and deletes on 2 additional threads while the purger
486 // runs constantly in the main thread.
487 TEST_F(EphTombstoneTest, ConcurrentPurge) {
488 ThreadGate started(2);
489 std::atomic<size_t> completed(0);
491 auto writer = [this](
492 ThreadGate& started, std::atomic<size_t>& completed, size_t id) {
494 for (size_t ii = 0; ii < 1000; ++ii) {
495 auto key = makeStoredDocKey(std::to_string(id) + ":key_" +
497 Item item(key, /*flags*/ 0, /*expiry*/ 0, key.data(), key.size());
498 public_processSet(item, item.getCas());
499 softDeleteOne(key, MutationStatus::WasDirty);
503 std::thread fe1{writer, std::ref(started), std::ref(completed), 1};
504 std::thread fe2{writer, std::ref(started), std::ref(completed), 2};
508 purged += mockEpheVB->purgeTombstones(0);
509 std::this_thread::yield();
510 } while (completed != 2);
516 // Test that on a double-delete (delete with a different value) the deleted time
517 // is updated correctly.
518 TEST_F(EphTombstoneTest, DoubleDeleteTimeCorrect) {
519 // Delete the first item at +0s
520 auto key = keys.at(0);
521 softDeleteOne(key, MutationStatus::WasDirty);
522 auto* delOSV = findValue(key)->toOrderedStoredValue();
523 auto initialDelTime = delOSV->getDeletedTime();
524 ASSERT_EQ(2, delOSV->getRevSeqno()) << "Should be initial set + 1";
526 // Advance to non-zero time.
527 const int timeJump = 10;
528 TimeTraveller nonZero(timeJump);
529 ASSERT_GE(ep_current_time(), initialDelTime + timeJump)
530 << "Failed to advance at least " + std::to_string(timeJump) +
531 " seconds from when initial delete "
534 // Delete the same key again (delete-with-Value), checking the deleted
536 Item item(key, 0, 0, "deleted", strlen("deleted"));
538 ASSERT_EQ(MutationStatus::WasDirty, public_processSet(item, item.getCas()));
539 ASSERT_EQ(3, delOSV->getRevSeqno()) << "Should be initial set + 2";
541 auto secondDelTime = delOSV->getDeletedTime();
542 EXPECT_GE(secondDelTime, initialDelTime + timeJump);
545 TEST_F(EphemeralVBucketTest, UpdateUpdatesHighestDedupedSeqno) {
546 /* Add 3 items and then update all of them */
547 const int numItems = 3;
549 auto keys = generateKeys(numItems);
550 setMany(keys, MutationStatus::WasClean);
552 ASSERT_EQ(0, mockEpheVB->getLL()->getHighestDedupedSeqno());
554 /* Update the items */
555 setMany(keys, MutationStatus::WasDirty);
557 EXPECT_EQ(6, mockEpheVB->getLL()->getHighestDedupedSeqno());
560 TEST_F(EphemeralVBucketTest, AppendUpdatesHighestDedupedSeqno) {
561 /* Add 3 items and then update all of them */
562 const int numItems = 3;
564 auto keys = generateKeys(numItems);
565 setMany(keys, MutationStatus::WasClean);
567 ASSERT_EQ(0, mockEpheVB->getLL()->getHighestDedupedSeqno());
570 auto itr = mockEpheVB->getLL()->makeRangeIterator();
572 /* Update the items */
573 setMany(keys, MutationStatus::WasClean);
576 ASSERT_EQ(6, mockEpheVB->getLL()->getHighestDedupedSeqno());
579 TEST_F(EphemeralVBucketTest, SnapshotHasNoDuplicates) {
580 /* Add 2 items and then update all of them */
581 const int numItems = 2;
583 auto keys = generateKeys(numItems);
584 setMany(keys, MutationStatus::WasClean);
587 auto itr = mockEpheVB->getLL()->makeRangeIterator();
589 /* Update the items */
590 setMany(keys, MutationStatus::WasClean);
593 // backfill to infinity would include both the stale and updated versions,
594 // ensure we receive the right number of items
595 auto res = mockEpheVB->inMemoryBackfill(
596 1, std::numeric_limits<seqno_t>::max());
597 EXPECT_EQ(ENGINE_SUCCESS, std::get<0>(res));
598 EXPECT_EQ(numItems, std::get<1>(res).size());
599 EXPECT_EQ(numItems * 2, std::get<2>(res));
602 TEST_F(EphemeralVBucketTest, SnapshotIncludesNonDuplicateStaleItems) {
603 /* Add 2 items and then update all of them */
604 const int numItems = 2;
606 auto keys = generateKeys(numItems);
607 setMany(keys, MutationStatus::WasClean);
610 auto itr = mockEpheVB->getLL()->makeRangeIterator();
612 /* Update the items */
613 setMany(keys, MutationStatus::WasClean);
616 // backfill to numItems would /not/ include both the stale and updated
617 // versions, ensure we receive only one copy
618 auto res = mockEpheVB->inMemoryBackfill(1, numItems);
619 EXPECT_EQ(ENGINE_SUCCESS, std::get<0>(res));
620 EXPECT_EQ(numItems, std::get<1>(res).size());
621 EXPECT_EQ(numItems * 2, std::get<2>(res));
624 TEST_F(EphemeralVBucketTest, SnapshotHasNoDuplicatesWithInterveningItems) {
625 // Add 3 items, begin a rangeRead, add 1 more item, then update the first 2
626 const int numItems = 2;
628 auto keysToUpdate = generateKeys(numItems);
629 auto firstFillerKey = makeStoredDocKey(std::to_string(numItems + 1));
630 auto secondFillerKey = makeStoredDocKey(std::to_string(numItems + 2));
632 setMany(keysToUpdate, MutationStatus::WasClean);
633 EXPECT_EQ(MutationStatus::WasClean, setOne(firstFillerKey));
636 auto itr = mockEpheVB->getLL()->makeRangeIterator();
638 EXPECT_EQ(MutationStatus::WasClean, setOne(secondFillerKey));
640 /* Update the items */
641 setMany(keysToUpdate, MutationStatus::WasClean);
644 // backfill to infinity would include both the stale and updated versions,
645 // ensure we receive the right number of items
646 auto res = mockEpheVB->inMemoryBackfill(
647 1, std::numeric_limits<seqno_t>::max());
648 EXPECT_EQ(ENGINE_SUCCESS, std::get<0>(res));
649 EXPECT_EQ(numItems * 2, std::get<1>(res).size()); // how many items returned
650 EXPECT_EQ(numItems * 3, std::get<2>(res)); // extended end of readRange
653 TEST_F(EphemeralVBucketTest, SnapshotHasNoDuplicatesWithMultipleStale) {
654 /* repeatedly update two items, ensure the backfill ignores all stale
656 const int numItems = 2;
657 const int updateIterations = 10;
659 auto keys = generateKeys(numItems);
661 setMany(keys, MutationStatus::WasClean);
662 for (int i = 0; i < updateIterations; ++i) {
663 /* Set up a mock backfill, cover all items */
665 auto itr = mockEpheVB->getLL()->makeRangeIterator();
666 /* Update the items */
667 setMany(keys, MutationStatus::WasClean);
671 // backfill to infinity would include both the stale and updated versions,
672 // ensure we receive the right number of items
673 auto res = mockEpheVB->inMemoryBackfill(
674 1, std::numeric_limits<seqno_t>::max());
675 EXPECT_EQ(ENGINE_SUCCESS, std::get<0>(res));
676 EXPECT_EQ(numItems, std::get<1>(res).size()); // how many items returned
677 EXPECT_EQ(numItems * (updateIterations + 1),
678 std::get<2>(res)); // extended end of readRange