Skip to content

Commit 8d301c5

Browse files
committed
MB-55199: Sort magma pendingRequests into key and ascending seqno order
Before handing over the batch of writes to magma, order by key and seqno, so that when duplicates occur they are in seqno ascending order. Change-Id: Ida6bdb0c6caa52a05d10d6167e27d2768c97222a Reviewed-on: https://review.couchbase.org/c/kv_engine/+/185329 Reviewed-by: Paolo Cocchi <[email protected]> Tested-by: Jim Walker <[email protected]>
1 parent 5629c4e commit 8d301c5

File tree

3 files changed

+81
-20
lines changed

3 files changed

+81
-20
lines changed

engines/ep/src/kvstore/magma-kvstore/magma-kvstore.cc

Lines changed: 31 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -293,22 +293,28 @@ bool MagmaKVStore::MagmaCompactionCB::canPurge(CollectionID collection) {
293293
return onlyThisCollection.value() == collection;
294294
}
295295

296-
struct MagmaKVStoreTransactionContext : public TransactionContext {
297-
MagmaKVStoreTransactionContext(KVStore& kvstore,
298-
Vbid vbid,
299-
std::unique_ptr<PersistenceCallback> cb)
300-
: TransactionContext(kvstore, vbid, std::move(cb)) {
301-
}
302-
/**
303-
* Container for pending Magma requests.
304-
*
305-
* Using deque as as the expansion behaviour is less aggressive compared to
306-
* std::vector (MagmaRequest objects are ~176 bytes in size).
307-
*/
308-
using PendingRequestQueue = std::deque<MagmaRequest>;
309-
310-
PendingRequestQueue pendingReqs;
311-
};
296+
MagmaKVStoreTransactionContext::MagmaKVStoreTransactionContext(
297+
KVStore& kvstore, Vbid vbid, std::unique_ptr<PersistenceCallback> cb)
298+
: TransactionContext(kvstore, vbid, std::move(cb)) {
299+
}
300+
301+
void MagmaKVStoreTransactionContext::preparePendingRequests() {
302+
// MB-55199: Magma requires key/seqno order, but ascending seqno.
303+
// KV-engine flusher writes out the batch in key/seqno, but descending seqno
304+
// order.
305+
std::sort(pendingReqs.begin(),
306+
pendingReqs.end(),
307+
[](const auto& lhs, const auto& rhs) {
308+
const auto comp = lhs->getItem().getKey().compare(
309+
rhs->getItem().getKey());
310+
// When keys are equal, sort by seqno.
311+
if (comp == 0) {
312+
return lhs->getItem().getBySeqno() <
313+
rhs->getItem().getBySeqno();
314+
}
315+
return comp < 0;
316+
});
317+
}
312318

313319
std::pair<Status, bool> MagmaKVStore::compactionCallBack(
314320
MagmaKVStore::MagmaCompactionCB& cbCtx,
@@ -778,7 +784,8 @@ void MagmaKVStore::commitCallback(MagmaKVStoreTransactionContext& txnCtx,
778784
int errCode,
779785
kvstats_ctx&) {
780786
const auto flushSuccess = (errCode == Status::Code::Ok);
781-
for (const auto& req : txnCtx.pendingReqs) {
787+
for (const auto& reqPtr : txnCtx.pendingReqs) {
788+
const auto& req = *reqPtr;
782789
size_t mutationSize = req.getRawKeyLen() + req.getBodySize() +
783790
req.getDocMeta().size();
784791
st.io_num_write++;
@@ -900,7 +907,8 @@ void MagmaKVStore::set(TransactionContext& txnCtx, queued_item item) {
900907
}
901908

902909
auto& ctx = static_cast<MagmaKVStoreTransactionContext&>(txnCtx);
903-
ctx.pendingReqs.emplace_back(std::move(item));
910+
ctx.pendingReqs.emplace_back(
911+
std::make_unique<MagmaRequest>(std::move(item)));
904912
}
905913

906914
GetValue MagmaKVStore::get(const DiskDocKey& key,
@@ -1111,7 +1119,8 @@ void MagmaKVStore::del(TransactionContext& txnCtx, queued_item item) {
11111119
}
11121120

11131121
auto& ctx = dynamic_cast<MagmaKVStoreTransactionContext&>(txnCtx);
1114-
ctx.pendingReqs.emplace_back(std::move(item));
1122+
ctx.pendingReqs.emplace_back(
1123+
std::make_unique<MagmaRequest>(std::move(item)));
11151124
}
11161125

11171126
void MagmaKVStore::delVBucket(Vbid vbid,
@@ -1487,6 +1496,7 @@ int MagmaKVStore::saveDocs(MagmaKVStoreTransactionContext& txnCtx,
14871496
};
14881497

14891498
auto& ctx = dynamic_cast<MagmaKVStoreTransactionContext&>(txnCtx);
1499+
ctx.preparePendingRequests();
14901500

14911501
// Vector of updates to be written to the data store.
14921502
WriteOps writeOps;
@@ -1495,7 +1505,8 @@ int MagmaKVStore::saveDocs(MagmaKVStoreTransactionContext& txnCtx,
14951505
// TODO: Replace writeOps with Magma::WriteOperations when it
14961506
// becomes available. This will allow us to pass pendingReqs
14971507
// in and create the WriteOperation from the pendingReqs queue.
1498-
for (auto& req : ctx.pendingReqs) {
1508+
for (auto& reqPtr : ctx.pendingReqs) {
1509+
auto& req = *reqPtr;
14991510
Slice valSlice{req.getBodyData(), req.getBodySize()};
15001511

15011512
auto docMeta = req.getDocMeta();

engines/ep/src/kvstore/magma-kvstore/magma-kvstore.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ class Status;
3636

3737
class MagmaKVStoreConfig;
3838
class MagmaMemoryTrackingProxy;
39+
class MagmaRequest;
3940
struct kvstats_ctx;
4041
struct MagmaKVStoreTransactionContext;
4142
struct MagmaScanResult;
@@ -829,3 +830,24 @@ class MagmaKVStore : public KVStore {
829830
private:
830831
EventuallyPersistentEngine* currEngine;
831832
};
833+
834+
struct MagmaKVStoreTransactionContext : public TransactionContext {
835+
MagmaKVStoreTransactionContext(KVStore& kvstore,
836+
Vbid vbid,
837+
std::unique_ptr<PersistenceCallback> cb);
838+
839+
/**
840+
* Prepare the requests before handing them over to Magma. This currently
841+
* orders by seqno (ascending) see MB-55199
842+
*/
843+
void preparePendingRequests();
844+
845+
/**
846+
* Container for pending Magma requests.
847+
*
848+
* Storing the pointer as preparePendingRequests will sort this container.
849+
*/
850+
using PendingRequestQueue = std::deque<std::unique_ptr<MagmaRequest>>;
851+
852+
PendingRequestQueue pendingReqs;
853+
};

engines/ep/tests/module_tests/magma-kvstore_test.cc

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "configuration.h"
1414
#include "kvstore/magma-kvstore/kv_magma_common/magma-kvstore_metadata.h"
1515
#include "kvstore/magma-kvstore/magma-kvstore_config.h"
16+
#include "kvstore/magma-kvstore/magma-kvstore_iorequest.h"
1617
#include "kvstore/storage_common/storage_common/local_doc_constants.h"
1718
#include "kvstore_test.h"
1819
#include "programs/engine_testapp/mock_server.h"
@@ -742,4 +743,31 @@ TEST_F(MagmaKVStoreTest, scanAllVersions) {
742743
auto& cb =
743744
static_cast<CustomCallback<GetValue>&>(bySeq->getValueCallback());
744745
EXPECT_EQ(2, cb.getProcessedCount());
746+
}
747+
748+
// Validate the ordering by seqno
749+
TEST_F(MagmaKVStoreTest, preparePendingRequests) {
750+
MagmaKVStoreTransactionContext ctx(*kvstore, vbid, nullptr);
751+
752+
// seqnos 5, 4, 3, 2, 1
753+
std::array<std::string, 5> keys = {{"bbb", "c", "bbb", "aaa", "bbb"}};
754+
uint64_t seqno = 5;
755+
for (const auto& k : keys) {
756+
auto qi = makeCommittedItem(makeStoredDocKey(k), "value");
757+
qi->setBySeqno(seqno--);
758+
ctx.pendingReqs.push_back(
759+
std::make_unique<MagmaRequest>(std::move(qi)));
760+
}
761+
762+
ctx.preparePendingRequests();
763+
764+
std::array<std::pair<uint64_t, std::string>, 5> expected = {
765+
{{2, "aaa"}, {1, "bbb"}, {3, "bbb"}, {5, "bbb"}, {4, "c"}}};
766+
ASSERT_EQ(expected.size(), ctx.pendingReqs.size());
767+
auto itr = expected.begin();
768+
for (const auto& req : ctx.pendingReqs) {
769+
EXPECT_EQ(itr->first, req->getItem().getBySeqno()) << req->getItem();
770+
EXPECT_EQ(itr->second, req->getItem().getKey().c_str());
771+
++itr;
772+
}
745773
}

0 commit comments

Comments
 (0)