Skip to content

Commit f057e5a

Browse files
daverigbypaolococchi
authored andcommitted
MB-32734 [SR]: Add SyncDelete support to HashTable
Update HashTable::unlocked_softDelete() to take a SyncDelete argument which specifies if the delete should be performed durably. Similar to SyncWrites, this will stage an additional pending Item in the HashTable with the proposed deleted state. Upon commit() this will replace the existing item. Expand HashTablePerspectiveTest and VBucketDurabilityTest to cover the basic use-cases of this. Change-Id: Iab4bb4c384bce6e7680cb523de96ed8cd71cdc00 Reviewed-on: http://review.couchbase.org/104152 Tested-by: Build Bot <[email protected]> Reviewed-by: Paolo Cocchi <[email protected]>
1 parent 620f79b commit f057e5a

13 files changed

+280
-62
lines changed

engines/ep/src/ep_vb.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -562,7 +562,7 @@ std::tuple<StoredValue*, VBNotifyCtx> EPVBucket::softDeleteStoredValue(
562562
const VBQueueItemCtx& queueItmCtx,
563563
uint64_t bySeqno,
564564
DeleteSource deleteSource) {
565-
ht.unlocked_softDelete(hbl.getHTLock(), v, onlyMarkDeleted, deleteSource);
565+
ht.unlocked_softDelete(hbl, v, onlyMarkDeleted, deleteSource);
566566

567567
if (queueItmCtx.genBySeqno == GenerateBySeqno::No) {
568568
v.setBySeqno(bySeqno);

engines/ep/src/ephemeral_vb.cc

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -542,8 +542,7 @@ std::tuple<StoredValue*, VBNotifyCtx> EphemeralVBucket::softDeleteStoredValue(
542542
}
543543

544544
/* Delete the storedvalue */
545-
ht.unlocked_softDelete(
546-
hbl.getHTLock(), *newSv, onlyMarkDeleted, deleteSource);
545+
ht.unlocked_softDelete(hbl, *newSv, onlyMarkDeleted, deleteSource);
547546

548547
if (queueItmCtx.genBySeqno == GenerateBySeqno::No) {
549548
newSv->setBySeqno(bySeqno);

engines/ep/src/hash_table.cc

Lines changed: 49 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "stats.h"
2222
#include "stored_value_factories.h"
2323

24+
#include <memcached/3rd_party/folly/lang/Assume.h>
2425
#include <phosphor/phosphor.h>
2526
#include <platform/compress.h>
2627

@@ -323,7 +324,7 @@ void HashTable::commit(const HashTable::HashBucketLock& hbl, StoredValue& v) {
323324
}
324325

325326
// Change the pending item to Committed.
326-
v.setCommitted();
327+
v.setCommitted(CommittedState::CommittedViaPrepare);
327328

328329
// Update stats for pending -> Committed item
329330
valueStats.epilogue(pendingPreProps, &v);
@@ -372,9 +373,7 @@ HashTable::UpdateResult HashTable::unlocked_updateStoredValue(
372373

373374
return {status, &v};
374375
}
375-
376-
throw std::logic_error(
377-
"HashTable::unlocked_updateStoredValue: unreachable");
376+
folly::assume_unreachable();
378377
}
379378

380379
StoredValue* HashTable::unlocked_addNewStoredValue(const HashBucketLock& hbl,
@@ -529,19 +528,54 @@ HashTable::unlocked_replaceByCopy(const HashBucketLock& hbl,
529528
return {values[hbl.getBucketNum()].get().get(), std::move(releasedSv)};
530529
}
531530

532-
void HashTable::unlocked_softDelete(const std::unique_lock<std::mutex>& htLock,
533-
StoredValue& v,
534-
bool onlyMarkDeleted,
535-
DeleteSource delSource) {
536-
const auto preProps = valueStats.prologue(&v);
531+
HashTable::DeleteResult HashTable::unlocked_softDelete(
532+
const HashBucketLock& hbl,
533+
StoredValue& v,
534+
bool onlyMarkDeleted,
535+
DeleteSource delSource,
536+
HashTable::SyncDelete syncDelete) {
537+
switch (v.getCommitted()) {
538+
case CommittedState::Pending:
539+
// Cannot update a SV if it's a Pending item.
540+
return {DeletionStatus::IsPendingSyncWrite, nullptr};
537541

538-
if (onlyMarkDeleted) {
539-
v.markDeleted(delSource);
540-
} else {
541-
v.del(delSource);
542-
}
542+
case CommittedState::CommittedViaMutation:
543+
case CommittedState::CommittedViaPrepare:
544+
if (syncDelete == SyncDelete::Yes) {
545+
// Logically we /can/ delete a non-Pending StoredValue with a
546+
// SyncDelete, however internally this is implemented as a separate
547+
// (new) StoredValue object for the Pending delete.
548+
549+
// Clone the existing StoredValue (for it's key, CAS, other
550+
// metadata), set the opcode to pending and call del() to prepare it
551+
// as a deleted item. The existing StoredValue keeps the same state
552+
// until we Commit the pending one.
553+
auto pendingDel = valFact->copyStoredValue(
554+
v, std::move(values[hbl.getBucketNum()]));
555+
pendingDel->setCommitted(CommittedState::Pending);
556+
pendingDel->del(delSource);
557+
558+
// Adding a new item into the HashTable; update stats.
559+
const auto emptyProperties = valueStats.prologue(nullptr);
560+
valueStats.epilogue(emptyProperties, pendingDel.get().get());
561+
values[hbl.getBucketNum()] = std::move(pendingDel);
562+
563+
return {DeletionStatus::Success,
564+
values[hbl.getBucketNum()].get().get()};
565+
}
566+
// non-syncDelete requests, can directly update existing SV.
567+
const auto preProps = valueStats.prologue(&v);
543568

544-
valueStats.epilogue(preProps, &v);
569+
if (onlyMarkDeleted) {
570+
v.markDeleted(delSource);
571+
} else {
572+
v.del(delSource);
573+
}
574+
575+
valueStats.epilogue(preProps, &v);
576+
return {DeletionStatus::Success, &v};
577+
}
578+
folly::assume_unreachable();
545579
}
546580

547581
StoredValue* HashTable::unlocked_find(const DocKey& key,

engines/ep/src/hash_table.h

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,12 @@ enum class TempAddStatus : uint8_t {
6767
BgFetch //Schedule a background fetch
6868
};
6969

70+
/// Result from delete options.
71+
enum class DeletionStatus : uint8_t {
72+
Success, // Item was successfully marked as deleted.
73+
IsPendingSyncWrite, //!< The item a pending SyncWrite and can't be deleted.
74+
};
75+
7076
/**
7177
* A container of StoredValue instances.
7278
*
@@ -747,21 +753,44 @@ class HashTable {
747753
*/
748754
std::pair<StoredValue*, StoredValue::UniquePtr> unlocked_replaceByCopy(
749755
const HashBucketLock& hbl, const StoredValue& vToCopy);
756+
757+
enum class SyncDelete {
758+
// A normal, non-synchronous, instant delete.
759+
No,
760+
/// A SyncDelete which not complete until it's durability requirements
761+
// have been met.
762+
Yes,
763+
};
764+
765+
/**
766+
* Result of a Delete operation.
767+
*/
768+
struct DeleteResult {
769+
/// Status of the operation.
770+
DeletionStatus status;
771+
// If the update was successful (Success); points to the delete value;
772+
// otherwise nullptr.
773+
StoredValue* deletedValue;
774+
};
775+
750776
/**
751777
* Logically (soft) delete the item in ht
752778
* Assumes that HT bucket lock is grabbed.
753779
* Also assumes that v is in the hash table.
754780
*
755-
* @param htLock Hash table lock that must be held
781+
* @param hbl Hash table bucket lock that must be held.
756782
* @param v Reference to the StoredValue to be soft deleted
757783
* @param onlyMarkDeleted indicates if we must reset the StoredValue or
758784
* just mark deleted
759785
* @param delSource The source of the deletion (explicit or expiry)
786+
* @param syncDelete Is this a regular or durable delete?
787+
* @return the outcome of the deletion attempt.
760788
*/
761-
void unlocked_softDelete(const std::unique_lock<std::mutex>& htLock,
762-
StoredValue& v,
763-
bool onlyMarkDeleted,
764-
DeleteSource delSource);
789+
DeleteResult unlocked_softDelete(const HashBucketLock& hbl,
790+
StoredValue& v,
791+
bool onlyMarkDeleted,
792+
DeleteSource delSource,
793+
SyncDelete syncDelete = SyncDelete::No);
765794

766795
/**
767796
* Find an item within a specific bucket assuming you already

engines/ep/src/stored-value.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,10 @@ bool StoredValue::operator==(const StoredValue& other) const {
269269
getCommitted() == other.getCommitted());
270270
}
271271

272+
bool StoredValue::operator!=(const StoredValue& other) const {
273+
return !(*this == other);
274+
}
275+
272276
bool StoredValue::deleteImpl(DeleteSource delSource) {
273277
if (isDeleted() && !getValue()) {
274278
// SV is already marked as deleted and has no value - no further

engines/ep/src/stored-value.h

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -734,6 +734,8 @@ class StoredValue {
734734
*/
735735
bool operator==(const StoredValue& other) const;
736736

737+
bool operator!=(const StoredValue& other) const;
738+
737739
/// Return how many bytes are need to store item given key as a StoredValue
738740
static size_t getRequiredStorage(const DocKey& key);
739741

@@ -754,9 +756,9 @@ class StoredValue {
754756
return static_cast<CommittedState>(committed);
755757
}
756758

757-
/// Sets the Committed state of the SV to "Committed"
758-
void setCommitted() {
759-
committed = static_cast<uint8_t>(CommittedState::CommittedViaPrepare);
759+
/// Sets the Committed state of the SV to the specified value.
760+
void setCommitted(CommittedState value) {
761+
committed = static_cast<uint8_t>(value);
760762
}
761763

762764
protected:
@@ -868,11 +870,6 @@ class StoredValue {
868870
deletionSource = static_cast<uint8_t>(delSource);
869871
}
870872

871-
/// Sets the commited state to the specified value.
872-
void setCommitted(CommittedState value) {
873-
committed = static_cast<uint8_t>(value);
874-
}
875-
876873
friend class StoredValueFactory;
877874

878875
/**

engines/ep/src/stored_value_factories.cc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,15 @@ StoredValue::UniquePtr StoredValueFactory::operator()(
3131
/*isOrdered*/ false));
3232
}
3333

34+
StoredValue::UniquePtr StoredValueFactory::copyStoredValue(
35+
const StoredValue& other, StoredValue::UniquePtr next) {
36+
// Allocate a buffer to store the copy of StoredValue and any
37+
// trailing bytes required for the key.
38+
return StoredValue::UniquePtr(
39+
new (::operator new(other.getObjectSize()))
40+
StoredValue(other, std::move(next), *stats));
41+
}
42+
3443
StoredValue::UniquePtr OrderedStoredValueFactory::operator()(
3544
const Item& itm, StoredValue::UniquePtr next) {
3645
// Allocate a buffer to store the OrderStoredValue and any trailing

engines/ep/src/stored_value_factories.h

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,8 @@ class StoredValueFactory : public AbstractStoredValueFactory {
7272
StoredValue::UniquePtr operator()(const Item& itm,
7373
StoredValue::UniquePtr next) override;
7474

75-
StoredValue::UniquePtr copyStoredValue(const StoredValue& other,
76-
StoredValue::UniquePtr next) override {
77-
throw std::logic_error("Copy of StoredValue is not supported");
78-
}
75+
StoredValue::UniquePtr copyStoredValue(
76+
const StoredValue& other, StoredValue::UniquePtr next) override;
7977

8078
private:
8179
EPStats* stats;

engines/ep/tests/module_tests/basic_ll_test.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ class BasicLinkedListTest : public ::testing::Test {
217217
{ /* hbl lock scope */
218218
auto result = ht.findForWrite(makeStoredDocKey(key));
219219

220-
ht.unlocked_softDelete(result.lock.getHTLock(),
220+
ht.unlocked_softDelete(result.lock,
221221
*result.storedValue,
222222
/* onlyMarkDeleted */ false,
223223
DeleteSource::Explicit);

0 commit comments

Comments
 (0)