summaryrefslogtreecommitdiff
path: root/src/mongo/dbtests
diff options
context:
space:
mode:
authorLouis Williams <louis.williams@mongodb.com>2019-01-22 17:29:55 -0500
committerLouis Williams <louis.williams@mongodb.com>2019-02-06 13:04:21 -0500
commitbb69551adf200b1b050476ad01b765018d5bac80 (patch)
tree929f508105697e1d90ae84b93bbc549c0b853975 /src/mongo/dbtests
parent33b153224e08a213f505c4f9e85d087d713cae5a (diff)
downloadmongo-bb69551adf200b1b050476ad01b765018d5bac80.tar.gz
SERVER-38986 Timestamp writes while draining
Diffstat (limited to 'src/mongo/dbtests')
-rw-r--r--src/mongo/dbtests/SConscript1
-rw-r--r--src/mongo/dbtests/storage_timestamp_tests.cpp132
2 files changed, 133 insertions, 0 deletions
diff --git a/src/mongo/dbtests/SConscript b/src/mongo/dbtests/SConscript
index af471589749..927842efd57 100644
--- a/src/mongo/dbtests/SConscript
+++ b/src/mongo/dbtests/SConscript
@@ -125,6 +125,7 @@ dbtest = env.Program(
"$BUILD_DIR/mongo/db/commands/test_commands_enabled",
"$BUILD_DIR/mongo/db/concurrency/deferred_writer",
"$BUILD_DIR/mongo/db/index/index_access_methods",
+ "$BUILD_DIR/mongo/db/index/index_build_interceptor",
"$BUILD_DIR/mongo/db/logical_clock",
"$BUILD_DIR/mongo/db/logical_time_metadata_hook",
"$BUILD_DIR/mongo/db/pipeline/document_value_test_util",
diff --git a/src/mongo/dbtests/storage_timestamp_tests.cpp b/src/mongo/dbtests/storage_timestamp_tests.cpp
index 5787ef486ff..6b31f842b37 100644
--- a/src/mongo/dbtests/storage_timestamp_tests.cpp
+++ b/src/mongo/dbtests/storage_timestamp_tests.cpp
@@ -47,6 +47,7 @@
#include "mongo/db/dbdirectclient.h"
#include "mongo/db/dbhelpers.h"
#include "mongo/db/global_settings.h"
+#include "mongo/db/index/index_build_interceptor.h"
#include "mongo/db/index/index_descriptor.h"
#include "mongo/db/logical_clock.h"
#include "mongo/db/multi_key_path_tracker.h"
@@ -1876,6 +1877,135 @@ public:
}
};
+template <bool SimulatePrimary>
+class TimestampIndexBuildDrain : public StorageTimestampTest {
+public:
+ void run() {
+ const bool SimulateSecondary = !SimulatePrimary;
+ if (SimulateSecondary) {
+ // The MemberState is inspected during index builds to use a "ghost" write to timestamp
+ // index completion.
+ ASSERT_OK(_coordinatorMock->setFollowerMode({repl::MemberState::MS::RS_SECONDARY}));
+ }
+
+ NamespaceString nss("unittests.timestampIndexBuildDrain");
+ reset(nss);
+
+ AutoGetCollection autoColl(_opCtx, nss, LockMode::MODE_X, LockMode::MODE_X);
+
+ // Build an index on `{a: 1}`.
+ MultiIndexBlock indexer(_opCtx, autoColl.getCollection());
+ const LogicalTime beforeIndexBuild = _clock->reserveTicks(2);
+ BSONObj indexInfoObj;
+ {
+ // Primaries do not have a wrapping `TimestampBlock`; secondaries do.
+ const Timestamp commitTimestamp =
+ SimulatePrimary ? Timestamp::min() : beforeIndexBuild.addTicks(1).asTimestamp();
+ TimestampBlock tsBlock(_opCtx, commitTimestamp);
+
+ // Secondaries will also be in an `UnreplicatedWritesBlock` that prevents the `logOp`
+ // from making creating an entry.
+ boost::optional<repl::UnreplicatedWritesBlock> unreplicated;
+ if (SimulateSecondary) {
+ unreplicated.emplace(_opCtx);
+ }
+
+ auto swIndexInfoObj = indexer.init({BSON("v" << 2 << "unique" << true << "name"
+ << "a_1"
+ << "ns"
+ << nss.ns()
+ << "key"
+ << BSON("a" << 1))});
+ ASSERT_OK(swIndexInfoObj.getStatus());
+ indexInfoObj = std::move(swIndexInfoObj.getValue()[0]);
+ }
+
+ const LogicalTime afterIndexInit = _clock->reserveTicks(1);
+
+ // Insert a document that will be intercepted and need to be drained. This timestamp will
+ // become the lastApplied time.
+ const LogicalTime firstInsert = _clock->reserveTicks(1);
+ {
+ WriteUnitOfWork wuow(_opCtx);
+ insertDocument(autoColl.getCollection(),
+ InsertStatement(BSON("_id" << 0 << "a" << 1),
+ firstInsert.asTimestamp(),
+ presentTerm));
+ wuow.commit();
+ ASSERT_EQ(1, itCount(autoColl.getCollection()));
+ }
+
+ // Index build drain will timestamp writes from the side table into the index with the
+ // lastApplied timestamp. This is because these writes are not associated with any specific
+ // oplog entry.
+ ASSERT_EQ(repl::ReplicationCoordinator::get(getGlobalServiceContext())
+ ->getMyLastAppliedOpTime()
+ .getTimestamp(),
+ firstInsert.asTimestamp());
+
+ ASSERT_OK(indexer.drainBackgroundWrites());
+
+ auto indexCatalog = autoColl.getCollection()->getIndexCatalog();
+ const IndexCatalogEntry* buildingIndex = indexCatalog->getEntry(
+ indexCatalog->findIndexByName(_opCtx, "a_1", /* includeUnfinished */ true));
+ ASSERT(buildingIndex);
+
+ {
+ // Before the drain, there are no writes to apply.
+ OneOffRead oor(_opCtx, afterIndexInit.asTimestamp());
+ ASSERT_TRUE(buildingIndex->indexBuildInterceptor()->areAllWritesApplied(_opCtx));
+ }
+
+ // Note: In this case, we can't observe a state where all writes are not applied, because
+ // the index build drain effectively rewrites history by retroactively committing the drain
+ // at the same time as the first insert, meaning there is no point-in-time with undrained
+ // writes. This is fine, as long as the drain does not commit at a time before this insert.
+
+ {
+ // At time of the first insert, all writes are applied.
+ OneOffRead oor(_opCtx, firstInsert.asTimestamp());
+ ASSERT_TRUE(buildingIndex->indexBuildInterceptor()->areAllWritesApplied(_opCtx));
+ }
+
+ // Insert a second document that will be intercepted and need to be drained.
+ const LogicalTime secondInsert = _clock->reserveTicks(1);
+ {
+ WriteUnitOfWork wuow(_opCtx);
+ insertDocument(autoColl.getCollection(),
+ InsertStatement(BSON("_id" << 1 << "a" << 2),
+ secondInsert.asTimestamp(),
+ presentTerm));
+ wuow.commit();
+ ASSERT_EQ(2, itCount(autoColl.getCollection()));
+ }
+
+ // Advance the lastApplied optime to observe a point before the drain where there are
+ // un-drained writes.
+ const LogicalTime afterSecondInsert = _clock->reserveTicks(1);
+ setReplCoordAppliedOpTime(repl::OpTime(afterSecondInsert.asTimestamp(), presentTerm));
+
+ ASSERT_OK(indexer.drainBackgroundWrites());
+
+ {
+ // At time of the second insert, there are un-drained writes.
+ OneOffRead oor(_opCtx, secondInsert.asTimestamp());
+ ASSERT_FALSE(buildingIndex->indexBuildInterceptor()->areAllWritesApplied(_opCtx));
+ }
+
+ {
+ // After the second insert, also the lastApplied time, all writes are applied.
+ OneOffRead oor(_opCtx, afterSecondInsert.asTimestamp());
+ ASSERT_TRUE(buildingIndex->indexBuildInterceptor()->areAllWritesApplied(_opCtx));
+ }
+
+ {
+ WriteUnitOfWork wuow(_opCtx);
+ ASSERT_OK(indexer.commit());
+ wuow.commit();
+ }
+ }
+};
+
class TimestampMultiIndexBuilds : public StorageTimestampTest {
public:
void run() {
@@ -2689,6 +2819,8 @@ public:
// TimestampIndexBuilds<SimulatePrimary>
add<TimestampIndexBuilds<false>>();
add<TimestampIndexBuilds<true>>();
+ add<TimestampIndexBuildDrain<false>>();
+ add<TimestampIndexBuildDrain<true>>();
add<TimestampMultiIndexBuilds>();
add<TimestampMultiIndexBuildsDuringRename>();
add<TimestampIndexDrops>();