Reduce EphTombstoneTest/ConcurrentPurge runtime
[ep-engine.git] / tests / module_tests / ephemeral_vb_test.cc
1 /* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3  *     Copyright 2017 Couchbase, Inc
4  *
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
8  *
9  *       http://www.apache.org/licenses/LICENSE-2.0
10  *
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.
16  */
17
18 /**
19  * Tests specific to Ephemeral VBuckets.
20  */
21
22 #include "config.h"
23
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"
29
30 #include <thread>
31
32 class EphemeralVBucketTest : public VBucketTest {
33 protected:
34     void SetUp() {
35         /* to test ephemeral vbucket specific stuff */
36         mockEpheVB = new MockEphemeralVBucket(0,
37                                               vbucket_state_active,
38                                               global_stats,
39                                               checkpoint_config,
40                                               /*kvshard*/ nullptr,
41                                               /*lastSeqno*/ 0,
42                                               /*lastSnapStart*/ 0,
43                                               /*lastSnapEnd*/ 0,
44                                               /*table*/ nullptr,
45                                               /*newSeqnoCb*/ nullptr,
46                                               config,
47                                               VALUE_ONLY);
48         /* vbucket manages the life time of mockEpheVB and is a base test class
49            ptr of owning type */
50         vbucket.reset(mockEpheVB);
51     }
52
53     void TearDown() {
54         vbucket.reset();
55     }
56
57     /* We want a ptr to MockEphemeralVBucket as we test ephemeral vbucket
58        specific stuff in this class */
59     MockEphemeralVBucket* mockEpheVB;
60
61     EPStats global_stats;
62     CheckpointConfig checkpoint_config;
63     Configuration config;
64 };
65
66 // Verify that attempting to pageOut an item twice has no effect the second
67 // time.
68 TEST_F(EphemeralVBucketTest, DoublePageOut) {
69     auto key = makeStoredDocKey("key");
70     ASSERT_EQ(AddStatus::Success, addOne(key));
71     ASSERT_EQ(1, vbucket->getNumItems());
72
73     auto lock_sv = lockAndFind(key);
74     auto* storedVal = lock_sv.second;
75     ASSERT_FALSE(storedVal->isDeleted());
76
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());
81
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());
86 }
87
88
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
93     // XATTR).
94     auto key = makeStoredDocKey("key");
95     std::string value = "deleted value";
96     Item item(key, 0, /*expiry*/0, value.data(), value.size());
97     item.setDeleted();
98     ASSERT_EQ(AddStatus::Success, public_processAdd(item));
99     ASSERT_EQ(0, vbucket->getNumItems());
100
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());
106
107     // Page it out.
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());
112 }
113
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");
118
119     // Add a key, then page out.
120     ASSERT_EQ(AddStatus::Success, addOne(key));
121     {
122         auto lock_sv = lockAndFind(key);
123         EXPECT_TRUE(vbucket->pageOut(lock_sv.first, lock_sv.second));
124     }
125     // Sanity check - should have just the one deleted item.
126     ASSERT_EQ(0, vbucket->getNumItems());
127     ASSERT_EQ(1, mockEpheVB->getLL()->getNumDeletedItems());
128
129     // Test: Set the key again.
130     ASSERT_EQ(MutationStatus::WasDirty, setOne(key));
131
132     EXPECT_EQ(1, vbucket->getNumItems());
133     EXPECT_EQ(0, mockEpheVB->getLL()->getNumDeletedItems());
134
135     // Finally for good measure, delete again and check the numbers are correct.
136     {
137         auto lock_sv = lockAndFind(key);
138         EXPECT_TRUE(vbucket->pageOut(lock_sv.first, lock_sv.second));
139     }
140     EXPECT_EQ(0, vbucket->getNumItems());
141     EXPECT_EQ(1, mockEpheVB->getLL()->getNumDeletedItems());
142 }
143
144 TEST_F(EphemeralVBucketTest, SetItems) {
145     const int numItems = 3;
146
147     auto keys = generateKeys(numItems);
148     setMany(keys, MutationStatus::WasClean);
149
150     EXPECT_EQ(numItems, vbucket->getNumItems());
151     EXPECT_EQ(numItems, vbucket->getHighSeqno());
152 }
153
154 TEST_F(EphemeralVBucketTest, UpdateItems) {
155     /* Add 3 items and then update all of them */
156     const int numItems = 3;
157
158     auto keys = generateKeys(numItems);
159     setMany(keys, MutationStatus::WasClean);
160
161     /* Update the items */
162     setMany(keys, MutationStatus::WasDirty);
163
164     EXPECT_EQ(numItems * 2, vbucket->getHighSeqno());
165     EXPECT_EQ(numItems, vbucket->getNumItems());
166 }
167
168 TEST_F(EphemeralVBucketTest, SoftDelete) {
169     /* Add 3 items and then delete all of them */
170     const int numItems = 3;
171
172     auto keys = generateKeys(numItems);
173     setMany(keys, MutationStatus::WasClean);
174
175     /* soft delete all */
176     softDeleteMany(keys, MutationStatus::WasDirty);
177
178     EXPECT_EQ(numItems * 2, vbucket->getHighSeqno());
179     EXPECT_EQ(0, vbucket->getNumItems());
180 }
181
182 TEST_F(EphemeralVBucketTest, Backfill) {
183     /* Add 3 items and get them by backfill */
184     const int numItems = 3;
185
186     auto keys = generateKeys(numItems);
187     setMany(keys, MutationStatus::WasClean);
188
189     auto res = mockEpheVB->inMemoryBackfill(1, numItems);
190     EXPECT_EQ(ENGINE_SUCCESS, res.first);
191     EXPECT_EQ(numItems, res.second.size());
192 }
193
194 TEST_F(EphemeralVBucketTest, UpdateDuringBackfill) {
195     /* Add 5 items and then update all of them */
196     const int numItems = 5;
197
198     auto keys = generateKeys(numItems);
199     setMany(keys, MutationStatus::WasClean);
200
201     /* Set up a mock backfill by setting the range of the backfill */
202     mockEpheVB->registerFakeReadRange(2, numItems - 1);
203
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]));
209     }
210     ASSERT_EQ(MutationStatus::WasDirty, setOne(keys[numItems - 1]));
211
212     /* Hash table must have only recent (updated) items */
213     EXPECT_EQ(numItems, vbucket->getNumItems());
214
215     /* High Seqno must be 2 * numItems */
216     EXPECT_EQ(numItems * 2, vbucket->getHighSeqno());
217
218     /* LinkedList must have 3 stale items */
219     EXPECT_EQ(3, mockEpheVB->public_getNumStaleItems());
220
221     EXPECT_EQ(numItems * 2 - /* since 2 items are deduped*/ 2,
222               mockEpheVB->public_getNumListItems());
223 }
224
225 TEST_F(EphemeralVBucketTest, SoftDeleteDuringBackfill) {
226     /* Add 5 items and then soft delete all of them */
227     const int numItems = 5;
228
229     auto keys = generateKeys(numItems);
230     setMany(keys, MutationStatus::WasClean);
231
232     /* Set up a mock backfill by setting the range of the backfill */
233     mockEpheVB->registerFakeReadRange(2, numItems - 1);
234
235     /* Update the first, middle and last item in the range read and 2 items
236        that are outside (before and after) range read */
237     softDeleteMany(keys, MutationStatus::WasDirty);
238
239     /* Hash table must have only recent (updated) items */
240     EXPECT_EQ(0, vbucket->getNumItems());
241
242     /* High Seqno must be 2 * numItems */
243     EXPECT_EQ(numItems * 2, vbucket->getHighSeqno());
244
245     /* LinkedList must have 3 stale items */
246     EXPECT_EQ(3, mockEpheVB->public_getNumStaleItems());
247
248     EXPECT_EQ(numItems * 2 - /* since 2 items are deduped*/ 2,
249               mockEpheVB->public_getNumListItems());
250 }
251
252 // EphemeralVB Tombstone Purging //////////////////////////////////////////////
253
254 class EphTombstoneTest : public EphemeralVBucketTest {
255 protected:
256     void SetUp() override {
257         EphemeralVBucketTest::SetUp();
258
259         // Store three items to work with.
260         keys = generateKeys(3);
261         setMany(keys, MutationStatus::WasClean);
262         ASSERT_EQ(3, vbucket->getNumItems());
263     }
264     std::vector<StoredDocKey> keys;
265 };
266
267 // Check an empty seqList is handled correctly.
268 TEST_F(EphTombstoneTest, ZeroElementPurge) {
269     // Create a new empty VB (using parent class SetUp).
270     EphemeralVBucketTest::SetUp();
271     ASSERT_EQ(0, mockEpheVB->public_getNumListItems());
272
273     EXPECT_EQ(0, mockEpheVB->purgeTombstones(0));
274 }
275
276 // Check a seqList with one element is handled correctly.
277 TEST_F(EphTombstoneTest, OneElementPurge) {
278     // Create a new empty VB (using parent class SetUp).
279     EphemeralVBucketTest::SetUp();
280     ASSERT_EQ(MutationStatus::WasClean, setOne(makeStoredDocKey("one")));
281     ASSERT_EQ(1, mockEpheVB->public_getNumListItems());
282
283     EXPECT_EQ(0, mockEpheVB->purgeTombstones(0));
284 }
285
286 // Check that nothing is purged if no items are stale.
287 TEST_F(EphTombstoneTest, NoPurgeIfNoneStale) {
288     // Run purger - nothing should be removed.
289     EXPECT_EQ(0, mockEpheVB->purgeTombstones(0));
290     EXPECT_EQ(keys.size(), vbucket->getNumItems());
291 }
292
293 // Check that deletes are not purged if they are not old enough.
294 TEST_F(EphTombstoneTest, NoPurgeIfNoneOldEnough) {
295     // Delete the first item "now"
296     softDeleteOne(keys.at(0), MutationStatus::WasDirty);
297     ASSERT_EQ(2, vbucket->getNumItems());
298     ASSERT_EQ(1, vbucket->getNumInMemoryDeletes());
299
300     // Advance time by 5 seconds and run the EphTombstonePurger specifying a
301     // purge_age of 10s - nothing
302     // should be purged.
303     TimeTraveller theTerminator(5);
304     EXPECT_EQ(0, mockEpheVB->purgeTombstones(10));
305
306     EXPECT_EQ(2, vbucket->getNumItems());
307     EXPECT_EQ(1, vbucket->getNumInMemoryDeletes());
308 }
309
310 // Check that items should be purged when they are old enough.
311 TEST_F(EphTombstoneTest, OnePurgeIfDeletedItemOld) {
312     // Delete the first item "now"
313     softDeleteOne(keys.at(0), MutationStatus::WasDirty);
314     ASSERT_EQ(2, vbucket->getNumItems());
315     ASSERT_EQ(1, vbucket->getNumInMemoryDeletes());
316
317     // Delete the second item at time 30.
318     TimeTraveller looper(30);
319     softDeleteOne(keys.at(1), MutationStatus::WasDirty);
320     ASSERT_EQ(1, vbucket->getNumItems());
321     ASSERT_EQ(2, vbucket->getNumInMemoryDeletes());
322
323     // and the third at time 60.
324     TimeTraveller looper2(30);
325     softDeleteOne(keys.at(2), MutationStatus::WasDirty);
326     ASSERT_EQ(0, vbucket->getNumItems());
327     ASSERT_EQ(3, vbucket->getNumInMemoryDeletes());
328
329     // Run the EphTombstonePurger specifying a purge_age of 60s - only key0
330     // should be purged.
331     mockEpheVB->purgeTombstones(60);
332
333     EXPECT_EQ(0, vbucket->getNumItems());
334     EXPECT_EQ(2, vbucket->getNumInMemoryDeletes());
335     EXPECT_EQ(4, vbucket->getPurgeSeqno())
336             << "Should have purged up to 4th update (1st delete, after 3 sets)";
337     EXPECT_EQ(nullptr, findValue(keys.at(0)));
338     EXPECT_NE(nullptr, findValue(keys.at(1)));
339     EXPECT_NE(nullptr, findValue(keys.at(2)));
340 }
341
342 // Check that deleted items can be purged immediately.
343 TEST_F(EphTombstoneTest, ImmediateDeletedPurge) {
344     // Advance to non-zero time.
345     TimeTraveller jamesCole(10);
346
347     // Delete the first item at 10s
348     softDeleteOne(keys.at(0), MutationStatus::WasDirty);
349     ASSERT_EQ(2, vbucket->getNumItems());
350     ASSERT_EQ(1, vbucket->getNumInMemoryDeletes());
351
352     // Run the EphTombstonePurger specifying a purge_age of 0s - key0 should
353     // be immediately purged.
354     mockEpheVB->purgeTombstones(0);
355     EXPECT_EQ(2, vbucket->getNumItems());
356     EXPECT_EQ(0, vbucket->getNumInMemoryDeletes());
357     EXPECT_EQ(4, vbucket->getPurgeSeqno())
358             << "Should have purged up to 4th update (1st delete, after 3 sets)";
359     EXPECT_EQ(nullptr, findValue(keys.at(0)));
360     EXPECT_NE(nullptr, findValue(keys.at(1)));
361     EXPECT_NE(nullptr, findValue(keys.at(2)));
362 }
363
364 // Check that alive, stale items have no constraint on age.
365 TEST_F(EphTombstoneTest, ImmediatePurgeOfAliveStale) {
366     // Perform a mutation on the second element, with a (fake) Range Read in
367     // place; causing the initial OSV to be marked as stale and a new OSV to
368     // be added for that key.
369     auto& seqList = mockEpheVB->getLL()->getSeqList();
370     {
371         std::lock_guard<std::mutex> rrGuard(
372                 mockEpheVB->getLL()->getRangeReadLock());
373         mockEpheVB->registerFakeReadRange(1, 2);
374         ASSERT_EQ(MutationStatus::WasClean, setOne(keys.at(1)));
375
376         // Sanity check - our state is as expected:
377         ASSERT_EQ(3, vbucket->getNumItems());
378         ASSERT_EQ(4, seqList.size());
379         auto staleIt = std::next(seqList.begin());
380         auto newIt = seqList.rbegin();
381         ASSERT_EQ(staleIt->getKey(), newIt->getKey());
382         {
383             std::lock_guard<std::mutex> writeGuard(
384                     mockEpheVB->getLL()->getWriteLock());
385             ASSERT_TRUE(staleIt->isStale(writeGuard));
386             ASSERT_FALSE(newIt->isStale(writeGuard));
387         }
388
389         // Attempt a purge - should not remove anything as read range is in
390         // place.
391         EXPECT_EQ(0, mockEpheVB->purgeTombstones(0));
392         EXPECT_EQ(3, vbucket->getNumItems());
393         EXPECT_EQ(4, seqList.size());
394         EXPECT_EQ(0, vbucket->getPurgeSeqno());
395
396         // Clear the ReadRange (so we can actually purge items) and retry the
397         // purge which should now succeed.
398         mockEpheVB->getLL()->resetReadRange();
399     } // END rrGuard.
400
401     EXPECT_EQ(1, mockEpheVB->purgeTombstones(0));
402     EXPECT_EQ(3, vbucket->getNumItems());
403     EXPECT_EQ(3, seqList.size());
404 }
405
406 // Test that deleted items purged out of order are handled correctly (and
407 // highestDeletedPurged is updated).
408 TEST_F(EphTombstoneTest, PurgeOutOfOrder) {
409     // Delete the 3rd item.
410     softDeleteOne(keys.at(2), MutationStatus::WasDirty);
411
412     // Run the tombstone purger.
413     mockEpheVB->getLL()->resetReadRange();
414     ASSERT_EQ(1, mockEpheVB->purgeTombstones(0));
415     ASSERT_EQ(2, vbucket->getNumItems());
416     EXPECT_EQ(4, vbucket->getPurgeSeqno());
417
418     // Delete the 1st item
419     softDeleteOne(keys.at(0), MutationStatus::WasDirty);
420
421     // Run the tombstone purger. This should succeed, but with
422     // highestDeletedPurged unchanged.
423     ASSERT_EQ(1, mockEpheVB->purgeTombstones(0));
424     ASSERT_EQ(1, vbucket->getNumItems());
425     EXPECT_EQ(5, vbucket->getPurgeSeqno());
426 }
427
428 // Thread-safety test (intended to run via Valgrind / ASan / TSan) -
429 // perform sets and deletes on 2 additional threads while the purger
430 // runs constantly in the main thread.
431 TEST_F(EphTombstoneTest, ConcurrentPurge) {
432     ThreadGate started(2);
433     std::atomic<size_t> completed(0);
434
435     auto writer = [this](
436             ThreadGate& started, std::atomic<size_t>& completed, size_t id) {
437         started.threadUp();
438         for (size_t ii = 0; ii < 1000; ++ii) {
439             auto key = makeStoredDocKey(std::to_string(id) + ":key_" +
440                                         std::to_string(ii));
441             Item item(key, /*flags*/ 0, /*expiry*/ 0, key.data(), key.size());
442             public_processSet(item, item.getCas());
443             softDeleteOne(key, MutationStatus::WasDirty);
444         }
445         ++completed;
446     };
447     std::thread fe1{writer, std::ref(started), std::ref(completed), 1};
448     std::thread fe2{writer, std::ref(started), std::ref(completed), 2};
449
450     size_t purged = 0;
451     do {
452         purged += mockEpheVB->purgeTombstones(0);
453         std::this_thread::yield();
454     } while (completed != 2);
455
456     fe1.join();
457     fe2.join();
458 }
459
460 // Test that on a double-delete (delete with a different value) the deleted time
461 // is updated correctly.
462 TEST_F(EphTombstoneTest, DoubleDeleteTimeCorrect) {
463     // Delete the first item at +0s
464     auto key = keys.at(0);
465     softDeleteOne(key, MutationStatus::WasDirty);
466     auto* delOSV = findValue(key)->toOrderedStoredValue();
467     auto initialDelTime = delOSV->getDeletedTime();
468     ASSERT_EQ(2, delOSV->getRevSeqno()) << "Should be initial set + 1";
469
470     // Advance to non-zero time.
471     const int timeJump = 10;
472     TimeTraveller nonZero(timeJump);
473     ASSERT_GE(ep_current_time(), initialDelTime + timeJump)
474             << "Failed to advance at least " + std::to_string(timeJump) +
475                        " seconds from when initial delete "
476                        "occcured";
477
478     // Delete the same key again (delete-with-Value), checking the deleted
479     // time has changed.
480     Item item(key, 0, 0, "deleted", strlen("deleted"));
481     item.setDeleted();
482     ASSERT_EQ(MutationStatus::WasDirty, public_processSet(item, item.getCas()));
483     ASSERT_EQ(3, delOSV->getRevSeqno()) << "Should be initial set + 2";
484
485     auto secondDelTime = delOSV->getDeletedTime();
486     EXPECT_GE(secondDelTime, initialDelTime + timeJump);
487 }