summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/mongo/db/SConscript10
-rw-r--r--src/mongo/db/catalog/index_catalog_impl.h4
-rw-r--r--src/mongo/db/catalog/index_create_impl.cpp21
-rw-r--r--src/mongo/db/multi_key_path_tracker.cpp31
-rw-r--r--src/mongo/db/multi_key_path_tracker.h10
-rw-r--r--src/mongo/db/multi_key_path_tracker_test.cpp95
6 files changed, 170 insertions, 1 deletions
diff --git a/src/mongo/db/SConscript b/src/mongo/db/SConscript
index c64b5dccf97..e91bb4d42a8 100644
--- a/src/mongo/db/SConscript
+++ b/src/mongo/db/SConscript
@@ -1730,3 +1730,13 @@ env.CppUnitTest(
'write_ops',
],
)
+
+env.CppUnitTest(
+ target='multi_key_path_tracker_test',
+ source=[
+ 'multi_key_path_tracker_test.cpp',
+ ],
+ LIBDEPS_PRIVATE=[
+ 'service_context',
+ ],
+)
diff --git a/src/mongo/db/catalog/index_catalog_impl.h b/src/mongo/db/catalog/index_catalog_impl.h
index e181825523f..52e4568e3eb 100644
--- a/src/mongo/db/catalog/index_catalog_impl.h
+++ b/src/mongo/db/catalog/index_catalog_impl.h
@@ -306,6 +306,10 @@ public:
return _entry;
}
+ const std::string& getIndexName() {
+ return _indexName;
+ }
+
private:
Collection* const _collection;
IndexCatalog* const _catalog;
diff --git a/src/mongo/db/catalog/index_create_impl.cpp b/src/mongo/db/catalog/index_create_impl.cpp
index 81eb4678c03..15210df625c 100644
--- a/src/mongo/db/catalog/index_create_impl.cpp
+++ b/src/mongo/db/catalog/index_create_impl.cpp
@@ -341,6 +341,16 @@ StatusWith<std::vector<BSONObj>> MultiIndexBlockImpl::init(const std::vector<BSO
Status MultiIndexBlockImpl::insertAllDocumentsInCollection(std::set<RecordId>* dupsOut) {
invariant(!_opCtx->lockState()->inAWriteUnitOfWork());
+ // Refrain from persisting any multikey updates as a result from building the index. Instead,
+ // accumulate them in the `MultikeyPathTracker` and do the write as part of the update that
+ // commits the index.
+ auto stopTracker =
+ MakeGuard([this] { MultikeyPathTracker::get(_opCtx).stopTrackingMultikeyPathInfo(); });
+ if (MultikeyPathTracker::get(_opCtx).isTrackingMultikeyPathInfo()) {
+ stopTracker.Dismiss();
+ }
+ MultikeyPathTracker::get(_opCtx).startTrackingMultikeyPathInfo();
+
const char* curopMessage = _buildInBackground ? "Index Build (background)" : "Index Build";
const auto numRecords = _collection->numRecords(_opCtx);
stdx::unique_lock<Client> lk(*_opCtx->getClient());
@@ -530,11 +540,21 @@ void MultiIndexBlockImpl::commit() {
for (size_t i = 0; i < _indexes.size(); i++) {
_indexes[i].block->success();
+ // The bulk builder will track multikey information itself. Non-bulk builders re-use the
+ // code path that a typical insert/update uses. State is altered on the non-bulk build
+ // path to accumulate the multikey information on the `MultikeyPathTracker`.
if (_indexes[i].bulk) {
const auto& bulkBuilder = _indexes[i].bulk;
if (bulkBuilder->isMultikey()) {
_indexes[i].block->getEntry()->setMultikey(_opCtx, bulkBuilder->getMultikeyPaths());
}
+ } else {
+ auto multikeyPaths =
+ boost::optional<MultikeyPaths>(MultikeyPathTracker::get(_opCtx).getMultikeyPathInfo(
+ _collection->ns(), _indexes[i].block->getIndexName()));
+ if (multikeyPaths) {
+ _indexes[i].block->getEntry()->setMultikey(_opCtx, *multikeyPaths);
+ }
}
}
@@ -547,5 +567,4 @@ void MultiIndexBlockImpl::commit() {
_opCtx->recoveryUnit()->registerChange(new SetNeedToCleanupOnRollback(this));
_needToCleanup = false;
}
-
} // namespace mongo
diff --git a/src/mongo/db/multi_key_path_tracker.cpp b/src/mongo/db/multi_key_path_tracker.cpp
index 43b5fd0567b..9bed1f3080a 100644
--- a/src/mongo/db/multi_key_path_tracker.cpp
+++ b/src/mongo/db/multi_key_path_tracker.cpp
@@ -35,8 +35,28 @@ namespace mongo {
const OperationContext::Decoration<MultikeyPathTracker> MultikeyPathTracker::get =
OperationContext::declareDecoration<MultikeyPathTracker>();
+void MultikeyPathTracker::mergeMultikeyPaths(MultikeyPaths* toMergeInto,
+ const MultikeyPaths& newPaths) {
+ invariant(toMergeInto->size() == newPaths.size());
+ for (auto idx = std::size_t(0); idx < toMergeInto->size(); ++idx) {
+ toMergeInto->at(idx).insert(newPaths[idx].begin(), newPaths[idx].end());
+ }
+}
+
void MultikeyPathTracker::addMultikeyPathInfo(MultikeyPathInfo info) {
invariant(_trackMultikeyPathInfo);
+ // Merge the `MultikeyPathInfo` input into the accumulated value being tracked for the
+ // (collection, index) key.
+ for (auto& existingChanges : _multikeyPathInfo) {
+ if (existingChanges.nss != info.nss || existingChanges.indexName != info.indexName) {
+ continue;
+ }
+
+ mergeMultikeyPaths(&existingChanges.multikeyPaths, info.multikeyPaths);
+ return;
+ }
+
+ // If an existing entry wasn't found for the (collection, index) input, create a new entry.
_multikeyPathInfo.emplace_back(info);
}
@@ -44,6 +64,17 @@ const WorkerMultikeyPathInfo& MultikeyPathTracker::getMultikeyPathInfo() const {
return _multikeyPathInfo;
}
+const boost::optional<MultikeyPaths> MultikeyPathTracker::getMultikeyPathInfo(
+ const NamespaceString& nss, const std::string& indexName) {
+ for (const auto& multikeyPathInfo : _multikeyPathInfo) {
+ if (multikeyPathInfo.nss == nss && multikeyPathInfo.indexName == indexName) {
+ return multikeyPathInfo.multikeyPaths;
+ }
+ }
+
+ return boost::none;
+}
+
void MultikeyPathTracker::startTrackingMultikeyPathInfo() {
_trackMultikeyPathInfo = true;
}
diff --git a/src/mongo/db/multi_key_path_tracker.h b/src/mongo/db/multi_key_path_tracker.h
index 3f1d4800bab..02034aae6cd 100644
--- a/src/mongo/db/multi_key_path_tracker.h
+++ b/src/mongo/db/multi_key_path_tracker.h
@@ -30,6 +30,8 @@
#include <string>
+#include <boost/optional.hpp>
+
#include "mongo/db/index/multikey_paths.h"
#include "mongo/db/operation_context.h"
@@ -53,6 +55,8 @@ class MultikeyPathTracker {
public:
static const OperationContext::Decoration<MultikeyPathTracker> get;
+ static void mergeMultikeyPaths(MultikeyPaths* toMergeInto, const MultikeyPaths& newPaths);
+
// Decoration requires a default constructor.
MultikeyPathTracker() = default;
@@ -69,6 +73,12 @@ public:
const WorkerMultikeyPathInfo& getMultikeyPathInfo() const;
/**
+ * Returns the multikey path information for the given inputs, or boost::none if none exist.
+ */
+ const boost::optional<MultikeyPaths> getMultikeyPathInfo(const NamespaceString& nss,
+ const std::string& indexName);
+
+ /**
* Specifies that we should track multikey path information on this MultikeyPathTracker. This is
* only expected to be called during oplog application on secondaries. We cannot simply check
* 'canAcceptWritesFor' because background index builds use their own OperationContext and
diff --git a/src/mongo/db/multi_key_path_tracker_test.cpp b/src/mongo/db/multi_key_path_tracker_test.cpp
new file mode 100644
index 00000000000..ecbddb1e6ef
--- /dev/null
+++ b/src/mongo/db/multi_key_path_tracker_test.cpp
@@ -0,0 +1,95 @@
+/**
+ * Copyright (C) 2018 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects
+ * for all of the code used other than as permitted herein. If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so. If you do not
+ * wish to do so, delete this exception statement from your version. If you
+ * delete this exception statement from all source files in the program,
+ * then also delete it in the license file.
+ */
+
+/**
+ * Unittest for MultikeyPathTracker operations.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include <sstream>
+
+#include "mongo/db/multi_key_path_tracker.h"
+#include "mongo/unittest/unittest.h"
+#include "mongo/util/mongoutils/str.h"
+
+namespace mongo {
+namespace {
+
+std::string dumpMultikeyPaths(const MultikeyPaths& multikeyPaths) {
+ std::stringstream ss;
+
+ ss << "[ ";
+ for (const auto multikeyComponents : multikeyPaths) {
+ ss << "[ ";
+ for (const auto multikeyComponent : multikeyComponents) {
+ ss << multikeyComponent << " ";
+ }
+ ss << "] ";
+ }
+ ss << "]";
+
+ return ss.str();
+}
+
+void assertMultikeyPathsAreEqual(const MultikeyPaths& actual, const MultikeyPaths& expected) {
+ bool match = (expected == actual);
+ if (!match) {
+ FAIL(str::stream() << "Expected: " << dumpMultikeyPaths(expected) << ", "
+ << "Actual: "
+ << dumpMultikeyPaths(actual));
+ }
+ ASSERT(match);
+}
+
+TEST(MultikeyPathTracker, TestMergeMultikeyPaths) {
+ // Suppose the index key is {"a.c": 1, "a.b": 1, "c.d.b.e": 1}.
+ MultikeyPaths mutablePaths = {{}, {}, {}};
+ {
+ // `foundPaths` finds `a` to be multikey.
+ MultikeyPaths foundPaths = {{0}, {0}, {}};
+ MultikeyPathTracker::mergeMultikeyPaths(&mutablePaths, foundPaths);
+ assertMultikeyPathsAreEqual(mutablePaths, foundPaths);
+ }
+
+ {
+ // `foundPaths` finds `c` and `d` to be multikey.
+ MultikeyPaths foundPaths = {{1}, {}, {0, 1}};
+ MultikeyPathTracker::mergeMultikeyPaths(&mutablePaths, foundPaths);
+ assertMultikeyPathsAreEqual(mutablePaths, {{0, 1}, {0}, {0, 1}});
+ }
+
+ {
+ // `foundPaths` finds `b` to be multikey.
+ MultikeyPaths foundPaths = {{}, {1}, {2}};
+ MultikeyPathTracker::mergeMultikeyPaths(&mutablePaths, foundPaths);
+ assertMultikeyPathsAreEqual(mutablePaths, {{0, 1}, {0, 1}, {0, 1, 2}});
+ }
+}
+} // namespace
+} // namespace mongo