summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMaria van Keulen <maria.vankeulen@mongodb.com>2019-09-10 21:59:36 +0000
committerevergreen <evergreen@mongodb.com>2019-09-10 21:59:36 +0000
commitbfac8d909877600c395dccc575effbfbae76a82a (patch)
tree92a6502d4ee5e0b2168e4bbde5e0313a47df7381
parent3f6ba750fc8374968c3c06e31c67ac2839e9a736 (diff)
downloadmongo-bfac8d909877600c395dccc575effbfbae76a82a.tar.gz
SERVER-39708 Optimize Btree multikey updates
-rw-r--r--src/mongo/db/catalog/multi_index_block.cpp6
-rw-r--r--src/mongo/db/index/index_build_interceptor.cpp34
-rw-r--r--src/mongo/db/index/index_build_interceptor.h3
-rw-r--r--src/mongo/db/multi_key_path_tracker.cpp9
-rw-r--r--src/mongo/db/multi_key_path_tracker.h6
5 files changed, 56 insertions, 2 deletions
diff --git a/src/mongo/db/catalog/multi_index_block.cpp b/src/mongo/db/catalog/multi_index_block.cpp
index 1eb834bd5bb..ee75f9ed64e 100644
--- a/src/mongo/db/catalog/multi_index_block.cpp
+++ b/src/mongo/db/catalog/multi_index_block.cpp
@@ -838,6 +838,12 @@ Status MultiIndexBlock::commit(OperationContext* opCtx,
boost::optional<MultikeyPaths>(MultikeyPathTracker::get(opCtx).getMultikeyPathInfo(
collection->ns(), _indexes[i].block->getIndexName()));
if (multikeyPaths) {
+ // Upon reaching this point, multikeyPaths must either have at least one (possibly
+ // empty) element unless it is of an index with a type that doesn't support tracking
+ // multikeyPaths via the multikeyPaths array or has "special" multikey semantics.
+ invariant(!multikeyPaths.get().empty() ||
+ !IndexBuildInterceptor::typeCanFastpathMultikeyUpdates(
+ _indexes[i].block->getEntry()->descriptor()->getIndexType()));
_indexes[i].block->getEntry()->setMultikey(opCtx, *multikeyPaths);
}
}
diff --git a/src/mongo/db/index/index_build_interceptor.cpp b/src/mongo/db/index/index_build_interceptor.cpp
index c55a569442f..048163d6c6e 100644
--- a/src/mongo/db/index/index_build_interceptor.cpp
+++ b/src/mongo/db/index/index_build_interceptor.cpp
@@ -55,6 +55,18 @@ namespace mongo {
MONGO_FAIL_POINT_DEFINE(hangDuringIndexBuildDrainYield);
+bool IndexBuildInterceptor::typeCanFastpathMultikeyUpdates(IndexType indexType) {
+ // Ensure no new indexes are added without considering whether they use the multikeyPaths
+ // vector.
+ invariant(indexType == INDEX_BTREE || indexType == INDEX_2D || indexType == INDEX_HAYSTACK ||
+ indexType == INDEX_2DSPHERE || indexType == INDEX_TEXT || indexType == INDEX_HASHED ||
+ indexType == INDEX_WILDCARD);
+ // Only BTREE indexes are guaranteed to use the multikeyPaths vector. Other index types either
+ // do not track path-level multikey information or have "special" handling of multikey
+ // information.
+ return (indexType == INDEX_BTREE);
+}
+
IndexBuildInterceptor::IndexBuildInterceptor(OperationContext* opCtx, IndexCatalogEntry* entry)
: _indexCatalogEntry(entry),
_sideWritesTable(
@@ -64,6 +76,15 @@ IndexBuildInterceptor::IndexBuildInterceptor(OperationContext* opCtx, IndexCatal
if (entry->descriptor()->unique()) {
_duplicateKeyTracker = std::make_unique<DuplicateKeyTracker>(opCtx, entry);
}
+ // `mergeMultikeyPaths` is sensitive to the two inputs having the same multikey
+ // "shape". Initialize `_multikeyPaths` with the right shape from the IndexCatalogEntry.
+ auto indexType = entry->descriptor()->getIndexType();
+ if (typeCanFastpathMultikeyUpdates(indexType)) {
+ auto numFields = entry->descriptor()->getNumFields();
+ _multikeyPaths = MultikeyPaths{};
+ auto it = _multikeyPaths->begin();
+ _multikeyPaths->insert(it, numFields, {});
+ }
}
void IndexBuildInterceptor::deleteTemporaryTables(OperationContext* opCtx) {
@@ -379,12 +400,17 @@ Status IndexBuildInterceptor::sideWrite(OperationContext* opCtx,
Op op,
int64_t* const numKeysOut) {
invariant(opCtx->lockState()->inAWriteUnitOfWork());
-
// Maintain parity with IndexAccessMethods handling of key counting. Only include
// `multikeyMetadataKeys` when inserting.
*numKeysOut = keys.size() + (op == Op::kInsert ? multikeyMetadataKeys.size() : 0);
- if (op == Op::kInsert) {
+ auto indexType = _indexCatalogEntry->descriptor()->getIndexType();
+
+ // No need to take the multikeyPaths mutex if this is a trivial multikey update.
+ bool canBypassMultikeyMutex = typeCanFastpathMultikeyUpdates(indexType) &&
+ MultikeyPathTracker::isMultikeyPathsTrivial(multikeyPaths);
+
+ if (op == Op::kInsert && !canBypassMultikeyMutex) {
// SERVER-39705: It's worth noting that a document may not generate any keys, but be
// described as being multikey. This step must be done to maintain parity with `validate`s
// expectations.
@@ -392,6 +418,10 @@ Status IndexBuildInterceptor::sideWrite(OperationContext* opCtx,
if (_multikeyPaths) {
MultikeyPathTracker::mergeMultikeyPaths(&_multikeyPaths.get(), multikeyPaths);
} else {
+ // All indexes that support pre-initialization of _multikeyPaths during
+ // IndexBuildInterceptor construction time should have been initialized already.
+ invariant(!typeCanFastpathMultikeyUpdates(indexType));
+
// `mergeMultikeyPaths` is sensitive to the two inputs having the same multikey
// "shape". Initialize `_multikeyPaths` with the right shape from the first result.
_multikeyPaths = multikeyPaths;
diff --git a/src/mongo/db/index/index_build_interceptor.h b/src/mongo/db/index/index_build_interceptor.h
index 4bdaaebf6f0..97dca244576 100644
--- a/src/mongo/db/index/index_build_interceptor.h
+++ b/src/mongo/db/index/index_build_interceptor.h
@@ -48,6 +48,8 @@ class IndexBuildInterceptor {
public:
enum class Op { kInsert, kDelete };
+ static bool typeCanFastpathMultikeyUpdates(IndexType type);
+
/**
* Creates a temporary table for writes during an index build. Additionally creates a temporary
* table to store any duplicate key constraint violations found during the build, if the index
@@ -91,6 +93,7 @@ public:
*/
Status checkDuplicateKeyConstraints(OperationContext* opCtx) const;
+
/**
* Performs a resumable scan on the side writes table, and either inserts or removes each key
* from the underlying IndexAccessMethod. This will only insert as many records as are visible
diff --git a/src/mongo/db/multi_key_path_tracker.cpp b/src/mongo/db/multi_key_path_tracker.cpp
index d78271932e5..003a600e6bf 100644
--- a/src/mongo/db/multi_key_path_tracker.cpp
+++ b/src/mongo/db/multi_key_path_tracker.cpp
@@ -68,6 +68,15 @@ void MultikeyPathTracker::mergeMultikeyPaths(MultikeyPaths* toMergeInto,
}
}
+bool MultikeyPathTracker::isMultikeyPathsTrivial(const MultikeyPaths& paths) {
+ for (auto&& path : paths) {
+ if (!path.empty()) {
+ return false;
+ }
+ }
+ return true;
+}
+
void MultikeyPathTracker::addMultikeyPathInfo(MultikeyPathInfo info) {
invariant(_trackMultikeyPathInfo);
// Merge the `MultikeyPathInfo` input into the accumulated value being tracked for the
diff --git a/src/mongo/db/multi_key_path_tracker.h b/src/mongo/db/multi_key_path_tracker.h
index 1fb1ee20fac..b155bc1fe58 100644
--- a/src/mongo/db/multi_key_path_tracker.h
+++ b/src/mongo/db/multi_key_path_tracker.h
@@ -63,6 +63,12 @@ public:
static void mergeMultikeyPaths(MultikeyPaths* toMergeInto, const MultikeyPaths& newPaths);
+ /**
+ * Returns whether paths contains only empty sets, i.e., {{}, {}, {}}. This includes the case
+ * where the MultikeyPaths vector itself has no elements, e.g., {}.
+ */
+ static bool isMultikeyPathsTrivial(const MultikeyPaths& paths);
+
// Decoration requires a default constructor.
MultikeyPathTracker() = default;