diff options
author | Bernard Gorman <bernard.gorman@gmail.com> | 2020-08-29 23:48:59 +0100 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2020-11-20 01:54:55 +0000 |
commit | 9870937b91b88348e619580f1050965b1006e33d (patch) | |
tree | 317b8564e315f8290a01477e4190ac56ea7d9722 /src/mongo | |
parent | 6f7f808583de3c432458d0adc2413d3a4022c126 (diff) | |
download | mongo-9870937b91b88348e619580f1050965b1006e33d.tar.gz |
SERVER-47812 Secondaries persist wildcard multikeypaths out of order
(cherry picked from commit bd320bc2d10cff75756a2c95986cc81ec8a5e7c7)
(cherry picked from commit 48089c01bcccc193b1d8dd3c50ae5cb3e072ebed)
Diffstat (limited to 'src/mongo')
21 files changed, 408 insertions, 124 deletions
diff --git a/src/mongo/db/catalog/index_catalog.h b/src/mongo/db/catalog/index_catalog.h index 9cc22948090..eb2dfc2dc41 100644 --- a/src/mongo/db/catalog/index_catalog.h +++ b/src/mongo/db/catalog/index_catalog.h @@ -443,6 +443,7 @@ public: */ virtual void setMultikeyPaths(OperationContext* const opCtx, const IndexDescriptor* const desc, + const std::vector<BSONObj>& multikeyMetadataKeys, const MultikeyPaths& multikeyPaths) = 0; // ----- data modifiers ------ diff --git a/src/mongo/db/catalog/index_catalog_entry.h b/src/mongo/db/catalog/index_catalog_entry.h index 822957dcc61..8c22cfb53b5 100644 --- a/src/mongo/db/catalog/index_catalog_entry.h +++ b/src/mongo/db/catalog/index_catalog_entry.h @@ -124,7 +124,9 @@ public: * namespace, index name, and multikey paths on the OperationContext rather than set the index * as multikey here. */ - virtual void setMultikey(OperationContext* const opCtx, const MultikeyPaths& multikeyPaths) = 0; + virtual void setMultikey(OperationContext* const opCtx, + const std::vector<BSONObj>& multikeyMetadataKeys, + const MultikeyPaths& multikeyPaths) = 0; // if this ready is ready for queries virtual bool isReady(OperationContext* const opCtx) const = 0; diff --git a/src/mongo/db/catalog/index_catalog_entry_impl.cpp b/src/mongo/db/catalog/index_catalog_entry_impl.cpp index 2c51dbae331..325689f9060 100644 --- a/src/mongo/db/catalog/index_catalog_entry_impl.cpp +++ b/src/mongo/db/catalog/index_catalog_entry_impl.cpp @@ -75,7 +75,7 @@ IndexCatalogEntryImpl::IndexCatalogEntryImpl(OperationContext* const opCtx, { stdx::lock_guard<Latch> lk(_indexMultikeyPathsMutex); _isMultikey.store(_catalogIsMultikey(opCtx, &_indexMultikeyPaths)); - _indexTracksPathLevelMultikeyInfo = !_indexMultikeyPaths.empty(); + _indexTracksMultikeyPathsInCatalog = !_indexMultikeyPaths.empty(); } const BSONObj& collation = _descriptor->collation(); @@ -199,14 +199,19 @@ void IndexCatalogEntryImpl::setIsReady(bool newIsReady) { } void IndexCatalogEntryImpl::setMultikey(OperationContext* opCtx, + const std::vector<BSONObj>& multikeyMetadataKeys, const MultikeyPaths& multikeyPaths) { - if (!_indexTracksPathLevelMultikeyInfo && isMultikey(opCtx)) { - // If the index is already set as multikey and we don't have any path-level information to - // update, then there's nothing more for us to do. + // An index can either track path-level multikey information in the catalog or as metadata keys + // in the index itself, but not both. + invariant(!(_indexTracksMultikeyPathsInCatalog && multikeyMetadataKeys.size() > 0)); + // If the index is already set as multikey and we don't have any path-level information to + // update, then there's nothing more for us to do. + bool hasNoPathLevelInfo = (!_indexTracksMultikeyPathsInCatalog && multikeyMetadataKeys.empty()); + if (hasNoPathLevelInfo && _isMultikey.load()) { return; } - if (_indexTracksPathLevelMultikeyInfo) { + if (_indexTracksMultikeyPathsInCatalog) { stdx::lock_guard<Latch> lk(_indexMultikeyPathsMutex); invariant(multikeyPaths.size() == _indexMultikeyPaths.size()); @@ -230,7 +235,7 @@ void IndexCatalogEntryImpl::setMultikey(OperationContext* opCtx, } } - MultikeyPaths paths = _indexTracksPathLevelMultikeyInfo ? multikeyPaths : MultikeyPaths{}; + MultikeyPaths paths = _indexTracksMultikeyPathsInCatalog ? multikeyPaths : MultikeyPaths{}; // On a primary, we can simply assign this write the same timestamp as the index creation, // insert, or update that caused this index to become multikey. This is because if two @@ -253,14 +258,21 @@ void IndexCatalogEntryImpl::setMultikey(OperationContext* opCtx, // it multikey, that write will be marked as "isTrackingMultikeyPathInfo" on the applier's // OperationContext and we can safely defer that write to the end of the batch. if (MultikeyPathTracker::get(opCtx).isTrackingMultikeyPathInfo()) { - MultikeyPathInfo info; - info.nss = _ns; - info.indexName = _descriptor->indexName(); - info.multikeyPaths = paths; - MultikeyPathTracker::get(opCtx).addMultikeyPathInfo(info); + MultikeyPathTracker::get(opCtx).addMultikeyPathInfo( + {_ns, _descriptor->indexName(), multikeyMetadataKeys, std::move(paths)}); return; } + // If multikeyMetadataKeys is non-empty, we must insert these keys into the index itself. We do + // not have to account for potential dupes, since all metadata keys are indexed against a single + // RecordId. An attempt to write a duplicate key will therefore be ignored. + if (!multikeyMetadataKeys.empty()) { + static const auto kMultikeyMetadataKeyId = + RecordId{RecordId::ReservedId::kWildcardMultikeyMetadataId}; + uassertStatusOK(accessMethod()->insertKeys( + opCtx, multikeyMetadataKeys, kMultikeyMetadataKeyId, {}, {}, nullptr)); + } + // It's possible that the index type (e.g. ascending/descending index) supports tracking // path-level multikey information, but this particular index doesn't. // CollectionCatalogEntry::setIndexIsMultikey() requires that we discard the path-level @@ -274,7 +286,7 @@ void IndexCatalogEntryImpl::setMultikey(OperationContext* opCtx, auto onMultikeyCommitFn = [this, multikeyPaths](bool indexMetadataHasChanged) { _isMultikey.store(true); - if (_indexTracksPathLevelMultikeyInfo) { + if (_indexTracksMultikeyPathsInCatalog) { stdx::lock_guard<Latch> lk(_indexMultikeyPathsMutex); for (size_t i = 0; i < multikeyPaths.size(); ++i) { _indexMultikeyPaths[i].insert(multikeyPaths[i].begin(), multikeyPaths[i].end()); @@ -345,8 +357,8 @@ void IndexCatalogEntryImpl::setMultikey(OperationContext* opCtx, // previous write in the transaction. if (opCtx->inMultiDocumentTransaction()) { invariant(txnParticipant); - txnParticipant.addUncommittedMultikeyPathInfo( - MultikeyPathInfo{_ns, _descriptor->indexName(), std::move(paths)}); + txnParticipant.addUncommittedMultikeyPathInfo(MultikeyPathInfo{ + _ns, _descriptor->indexName(), multikeyMetadataKeys, std::move(paths)}); } } diff --git a/src/mongo/db/catalog/index_catalog_entry_impl.h b/src/mongo/db/catalog/index_catalog_entry_impl.h index 9698975bc8b..88a5d666888 100644 --- a/src/mongo/db/catalog/index_catalog_entry_impl.h +++ b/src/mongo/db/catalog/index_catalog_entry_impl.h @@ -152,7 +152,9 @@ public: * namespace, index name, and multikey paths on the OperationContext rather than set the index * as multikey here. */ - void setMultikey(OperationContext* opCtx, const MultikeyPaths& multikeyPaths) final; + void setMultikey(OperationContext* opCtx, + const std::vector<BSONObj>& multikeyMetadataKeys, + const MultikeyPaths& multikeyPaths) final; // if this ready is ready for queries bool isReady(OperationContext* opCtx) const final; @@ -210,10 +212,9 @@ private: Ordering _ordering; // TODO: this might be b-tree specific bool _isReady; // cache of NamespaceDetails info - // Set to true if this index supports path-level multikey tracking. - // '_indexTracksPathLevelMultikeyInfo' is effectively const after IndexCatalogEntry::init() is - // called. - bool _indexTracksPathLevelMultikeyInfo = false; + // Set to true if this index can track path-level multikey information in the catalog. This + // member is effectively const after IndexCatalogEntry::init() is called. + bool _indexTracksMultikeyPathsInCatalog = false; // Set to true if this index is multikey. '_isMultikey' serves as a cache of the information // stored in the NamespaceDetails or DurableCatalog. diff --git a/src/mongo/db/catalog/index_catalog_impl.cpp b/src/mongo/db/catalog/index_catalog_impl.cpp index 266fac5b46b..96eb1ddd835 100644 --- a/src/mongo/db/catalog/index_catalog_impl.cpp +++ b/src/mongo/db/catalog/index_catalog_impl.cpp @@ -1059,13 +1059,14 @@ MultikeyPaths IndexCatalogImpl::getMultikeyPaths(OperationContext* opCtx, void IndexCatalogImpl::setMultikeyPaths(OperationContext* const opCtx, const IndexDescriptor* desc, + const std::vector<BSONObj>& multikeyMetadataKeys, const MultikeyPaths& multikeyPaths) { IndexCatalogEntry* entry = _readyIndexes.find(desc); if (!entry) { entry = _buildingIndexes.find(desc); } invariant(entry); - entry->setMultikey(opCtx, multikeyPaths); + entry->setMultikey(opCtx, multikeyMetadataKeys, multikeyPaths); }; // --------------------------- @@ -1313,7 +1314,7 @@ Status IndexCatalogImpl::_indexKeys(OperationContext* opCtx, } } else { int64_t numInserted; - status = index->accessMethod()->insertKeys( + status = index->accessMethod()->insertKeysAndUpdateMultikeyPaths( opCtx, keys, {multikeyMetadataKeys.begin(), multikeyMetadataKeys.end()}, diff --git a/src/mongo/db/catalog/index_catalog_impl.h b/src/mongo/db/catalog/index_catalog_impl.h index 1439832ab96..be525335b16 100644 --- a/src/mongo/db/catalog/index_catalog_impl.h +++ b/src/mongo/db/catalog/index_catalog_impl.h @@ -250,6 +250,7 @@ public: void setMultikeyPaths(OperationContext* const opCtx, const IndexDescriptor* desc, + const std::vector<BSONObj>& multikeyMetadataKeys, const MultikeyPaths& multikeyPaths) override; // --- these probably become private? diff --git a/src/mongo/db/catalog/index_catalog_noop.h b/src/mongo/db/catalog/index_catalog_noop.h index 9f9bb23c27b..95414148792 100644 --- a/src/mongo/db/catalog/index_catalog_noop.h +++ b/src/mongo/db/catalog/index_catalog_noop.h @@ -184,6 +184,7 @@ public: void setMultikeyPaths(OperationContext* const opCtx, const IndexDescriptor* const desc, + const std::vector<BSONObj>& multikeyMetadataKeys, const MultikeyPaths& multikeyPaths) override {} Status indexRecords(OperationContext* const opCtx, diff --git a/src/mongo/db/catalog/multi_index_block.cpp b/src/mongo/db/catalog/multi_index_block.cpp index cb902dec15b..27545c0c070 100644 --- a/src/mongo/db/catalog/multi_index_block.cpp +++ b/src/mongo/db/catalog/multi_index_block.cpp @@ -846,37 +846,47 @@ Status MultiIndexBlock::commit(OperationContext* opCtx, onCreateEach(_indexes[i].block->getSpec()); // Do this before calling success(), which unsets the interceptor pointer on the index - // catalog entry. + // catalog entry. The interceptor will write multikey metadata keys into the index during + // IndexBuildInterceptor::sideWrite, so we only need to pass the cached MultikeyPaths into + // IndexCatalogEntry::setMultikey here. auto interceptor = _indexes[i].block->getEntry()->indexBuildInterceptor(); if (interceptor) { auto multikeyPaths = interceptor->getMultikeyPaths(); if (multikeyPaths) { - _indexes[i].block->getEntry()->setMultikey(opCtx, multikeyPaths.get()); + _indexes[i].block->getEntry()->setMultikey(opCtx, {}, multikeyPaths.get()); } } _indexes[i].block->success(opCtx, collection); - // 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`. + // The bulk builder will track multikey information itself, and will write cached multikey + // metadata keys into the index just before committing. We therefore only need to pass the + // MultikeyPaths into IndexCatalogEntry::setMultikey here. if (_indexes[i].bulk) { const auto& bulkBuilder = _indexes[i].bulk; if (bulkBuilder->isMultikey()) { - _indexes[i].block->getEntry()->setMultikey(opCtx, bulkBuilder->getMultikeyPaths()); + _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) { + // 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. + auto multikeyPaths = MultikeyPathTracker::get(opCtx).getMultikeyPathInfo( + collection->ns(), _indexes[i].block->getIndexName()); + auto multikeyMetadataKeys = MultikeyPathTracker::get(opCtx).getMultikeyMetadataKeys( + collection->ns(), _indexes[i].block->getIndexName()); + if (multikeyPaths || multikeyMetadataKeys) { // 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() || + invariant(multikeyMetadataKeys || !multikeyPaths.get().empty() || !IndexBuildInterceptor::typeCanFastpathMultikeyUpdates( _indexes[i].block->getEntry()->descriptor()->getIndexType())); - _indexes[i].block->getEntry()->setMultikey(opCtx, *multikeyPaths); + _indexes[i].block->getEntry()->setMultikey( + opCtx, + multikeyMetadataKeys.value_or(std::vector<BSONObj>{}), + multikeyPaths.value_or(MultikeyPaths{{}})); } } } diff --git a/src/mongo/db/index/index_access_method.cpp b/src/mongo/db/index/index_access_method.cpp index 706218c164d..b9024a5fd94 100644 --- a/src/mongo/db/index/index_access_method.cpp +++ b/src/mongo/db/index/index_access_method.cpp @@ -200,20 +200,45 @@ Status AbstractIndexAccessMethod::insert(OperationContext* opCtx, &multikeyMetadataKeys, &multikeyPaths); - return insertKeys(opCtx, - {keys.begin(), keys.end()}, - {multikeyMetadataKeys.begin(), multikeyMetadataKeys.end()}, - multikeyPaths, - loc, - options, - std::move(onDuplicateKey), - numInserted); + return insertKeysAndUpdateMultikeyPaths( + opCtx, + {keys.begin(), keys.end()}, + {multikeyMetadataKeys.begin(), multikeyMetadataKeys.end()}, + multikeyPaths, + loc, + options, + std::move(onDuplicateKey), + numInserted); +} + +Status AbstractIndexAccessMethod::insertKeysAndUpdateMultikeyPaths( + OperationContext* opCtx, + const std::vector<BSONObj>& keys, + const std::vector<BSONObj>& multikeyMetadataKeys, + const MultikeyPaths& multikeyPaths, + const RecordId& loc, + const InsertDeleteOptions& options, + KeyHandlerFn&& onDuplicateKey, + int64_t* numInserted) { + // Insert the specified data keys into the index. + auto status = insertKeys(opCtx, keys, loc, options, std::move(onDuplicateKey), numInserted); + if (!status.isOK()) { + return status; + } + // If these keys should cause the index to become multikey, pass them into the catalog. + if (shouldMarkIndexAsMultikey(keys, multikeyMetadataKeys, multikeyPaths)) { + _btreeState->setMultikey(opCtx, multikeyMetadataKeys, multikeyPaths); + } + // If we have some multikey metadata keys, they should have been added while marking the index + // as multikey in the catalog. Add them to the count of keys inserted for completeness. + if (numInserted && !multikeyMetadataKeys.empty()) { + *numInserted += multikeyMetadataKeys.size(); + } + return Status::OK(); } Status AbstractIndexAccessMethod::insertKeys(OperationContext* opCtx, const vector<BSONObj>& keys, - const vector<BSONObj>& multikeyMetadataKeys, - const MultikeyPaths& multikeyPaths, const RecordId& loc, const InsertDeleteOptions& options, KeyHandlerFn&& onDuplicateKey, @@ -222,47 +247,36 @@ Status AbstractIndexAccessMethod::insertKeys(OperationContext* opCtx, if (numInserted) { *numInserted = 0; } - // Add all new data keys, and all new multikey metadata keys, into the index. When iterating - // over the data keys, each of them should point to the doc's RecordId. When iterating over - // the multikey metadata keys, they should point to the reserved 'kMultikeyMetadataKeyId'. + // Add all new data keys into the index with the specified RecordId. bool checkIndexKeySize = shouldCheckIndexKeySize(opCtx); - for (const auto keyVec : {&keys, &multikeyMetadataKeys}) { - const auto& recordId = (keyVec == &keys ? loc : kMultikeyMetadataKeyId); - for (const auto& key : *keyVec) { - Status status = checkIndexKeySize ? checkKeySize(key) : Status::OK(); - if (status.isOK()) { - bool unique = _descriptor->unique(); - StatusWith<SpecialFormatInserted> ret = - _newInterface->insert(opCtx, key, recordId, !unique /* dupsAllowed */); - status = ret.getStatus(); - - // When duplicates are encountered and allowed, retry with dupsAllowed. Call - // onDuplicateKey() with the inserted duplicate key. - if (ErrorCodes::DuplicateKey == status.code() && options.dupsAllowed) { - invariant(unique); - ret = _newInterface->insert(opCtx, key, recordId, true /* dupsAllowed */); - status = ret.getStatus(); + for (const auto& key : keys) { + Status status = checkIndexKeySize ? checkKeySize(key) : Status::OK(); + if (status.isOK()) { + bool unique = _descriptor->unique(); + StatusWith<SpecialFormatInserted> ret = + _newInterface->insert(opCtx, key, loc, !unique /* dupsAllowed */); + status = ret.getStatus(); - if (status.isOK() && onDuplicateKey) - status = onDuplicateKey(key); - } + // When duplicates are encountered and allowed, retry with dupsAllowed. Call + // onDuplicateKey() with the inserted duplicate key. + if (ErrorCodes::DuplicateKey == status.code() && options.dupsAllowed) { + invariant(unique); + ret = _newInterface->insert(opCtx, key, loc, true /* dupsAllowed */); + status = ret.getStatus(); - if (status.isOK() && ret.getValue() == SpecialFormatInserted::LongTypeBitsInserted) - DurableCatalog::get(opCtx)->setIndexKeyStringWithLongTypeBitsExistsOnDisk( - opCtx); - } - if (isFatalError(opCtx, status, key)) { - return status; + if (status.isOK() && onDuplicateKey) + status = onDuplicateKey(key); } + + if (status.isOK() && ret.getValue() == SpecialFormatInserted::LongTypeBitsInserted) + DurableCatalog::get(opCtx)->setIndexKeyStringWithLongTypeBitsExistsOnDisk(opCtx); + } + if (isFatalError(opCtx, status, key)) { + return status; } } - if (numInserted) { - *numInserted = keys.size() + multikeyMetadataKeys.size(); - } - - if (shouldMarkIndexAsMultikey(keys, multikeyMetadataKeys, multikeyPaths)) { - _btreeState->setMultikey(opCtx, multikeyPaths); + *numInserted = keys.size(); } return Status::OK(); } @@ -488,37 +502,36 @@ Status AbstractIndexAccessMethod::update(OperationContext* opCtx, bool checkIndexKeySize = shouldCheckIndexKeySize(opCtx); - // Add all new data keys, and all new multikey metadata keys, into the index. When iterating - // over the data keys, each of them should point to the doc's RecordId. When iterating over - // the multikey metadata keys, they should point to the reserved 'kMultikeyMetadataKeyId'. - const auto newMultikeyMetadataKeys = asVector(ticket.newMultikeyMetadataKeys); - for (const auto keySet : {&ticket.added, &newMultikeyMetadataKeys}) { - const auto& recordId = (keySet == &ticket.added ? ticket.loc : kMultikeyMetadataKeyId); - for (const auto& key : *keySet) { - Status status = checkIndexKeySize ? checkKeySize(key) : Status::OK(); - if (status.isOK()) { - StatusWith<SpecialFormatInserted> ret = - _newInterface->insert(opCtx, key, recordId, ticket.dupsAllowed); - status = ret.getStatus(); - if (status.isOK() && ret.getValue() == SpecialFormatInserted::LongTypeBitsInserted) - DurableCatalog::get(opCtx)->setIndexKeyStringWithLongTypeBitsExistsOnDisk( - opCtx); - } - if (isFatalError(opCtx, status, key)) { - return status; - } + // Add all new data keys into the index with the specified RecordId. + for (const auto& key : ticket.added) { + Status status = checkIndexKeySize ? checkKeySize(key) : Status::OK(); + if (status.isOK()) { + StatusWith<SpecialFormatInserted> ret = + _newInterface->insert(opCtx, key, ticket.loc, ticket.dupsAllowed); + status = ret.getStatus(); + if (status.isOK() && ret.getValue() == SpecialFormatInserted::LongTypeBitsInserted) + DurableCatalog::get(opCtx)->setIndexKeyStringWithLongTypeBitsExistsOnDisk(opCtx); + } + if (isFatalError(opCtx, status, key)) { + return status; } } + // If these keys should cause the index to become multikey, pass them into the catalog. if (shouldMarkIndexAsMultikey( {ticket.newKeys.begin(), ticket.newKeys.end()}, {ticket.newMultikeyMetadataKeys.begin(), ticket.newMultikeyMetadataKeys.end()}, ticket.newMultikeyPaths)) { - _btreeState->setMultikey(opCtx, ticket.newMultikeyPaths); + _btreeState->setMultikey( + opCtx, + {ticket.newMultikeyMetadataKeys.begin(), ticket.newMultikeyMetadataKeys.end()}, + ticket.newMultikeyPaths); } + // If we have some multikey metadata keys, they should have been added while marking the index + // as multikey in the catalog. Add them to the count of keys inserted for completeness. + *numInserted = ticket.added.size() + ticket.newMultikeyMetadataKeys.size(); *numDeleted = ticket.removed.size(); - *numInserted = ticket.added.size(); return Status::OK(); } @@ -750,8 +763,10 @@ Status AbstractIndexAccessMethod::commitBulk(OperationContext* opCtx, return Status::OK(); } -void AbstractIndexAccessMethod::setIndexIsMultikey(OperationContext* opCtx, MultikeyPaths paths) { - _btreeState->setMultikey(opCtx, paths); +void AbstractIndexAccessMethod::setIndexIsMultikey(OperationContext* opCtx, + std::vector<BSONObj> multikeyMetadataKeys, + MultikeyPaths paths) { + _btreeState->setMultikey(opCtx, multikeyMetadataKeys, paths); } void AbstractIndexAccessMethod::getKeys(const BSONObj& obj, diff --git a/src/mongo/db/index/index_access_method.h b/src/mongo/db/index/index_access_method.h index 5171c7ffef4..e4d150ab60d 100644 --- a/src/mongo/db/index/index_access_method.h +++ b/src/mongo/db/index/index_access_method.h @@ -97,15 +97,26 @@ public: /** * Inserts the specified keys into the index, and determines whether these keys should cause the * index to become multikey. If so, this method also handles the task of marking the index as - * multikey in the catalog, and sets the path-level multikey information if applicable. The - * 'numInserted' output parameter, if non-nullptr, will be reset to the number of keys inserted - * by this function call, or to zero in the case of either a non-OK return Status or an empty - * 'keys' argument. + * multikey in the catalog, and sets the path-level multikey information if applicable. + */ + virtual Status insertKeysAndUpdateMultikeyPaths( + OperationContext* opCtx, + const std::vector<BSONObj>& keys, + const std::vector<BSONObj>& multikeyMetadataKeys, + const MultikeyPaths& multikeyPaths, + const RecordId& loc, + const InsertDeleteOptions& options, + KeyHandlerFn&& onDuplicateKey, + int64_t* numInserted) = 0; + + /** + * Inserts the specified keys into the index. Does not attempt to determine whether the + * insertion of these keys should cause the index to become multikey. The 'numInserted' output + * parameter, if non-nullptr, will be reset to the number of keys inserted by this function + * call, or to zero in the case of either a non-OK return Status or an empty 'keys' argument. */ virtual Status insertKeys(OperationContext* opCtx, const std::vector<BSONObj>& keys, - const std::vector<BSONObj>& multikeyMetadataKeys, - const MultikeyPaths& multikeyPaths, const RecordId& loc, const InsertDeleteOptions& options, KeyHandlerFn&& onDuplicateKey, @@ -216,7 +227,9 @@ public: /** * Sets this index as multikey with the provided paths. */ - virtual void setIndexIsMultikey(OperationContext* opCtx, MultikeyPaths paths) = 0; + virtual void setIndexIsMultikey(OperationContext* opCtx, + std::vector<BSONObj> multikeyMetadataKeys, + MultikeyPaths paths) = 0; // // Bulk operations support @@ -461,13 +474,20 @@ public: Status insertKeys(OperationContext* opCtx, const std::vector<BSONObj>& keys, - const std::vector<BSONObj>& multikeyMetadataKeys, - const MultikeyPaths& multikeyPaths, const RecordId& loc, const InsertDeleteOptions& options, KeyHandlerFn&& onDuplicateKey, int64_t* numInserted) final; + Status insertKeysAndUpdateMultikeyPaths(OperationContext* opCtx, + const std::vector<BSONObj>& keys, + const std::vector<BSONObj>& multikeyMetadataKeys, + const MultikeyPaths& multikeyPaths, + const RecordId& loc, + const InsertDeleteOptions& options, + KeyHandlerFn&& onDuplicateKey, + int64_t* numInserted) final; + Status removeKeys(OperationContext* opCtx, const std::vector<BSONObj>& keys, const RecordId& loc, @@ -511,7 +531,9 @@ public: Status compact(OperationContext* opCtx) final; - void setIndexIsMultikey(OperationContext* opCtx, MultikeyPaths paths) final; + void setIndexIsMultikey(OperationContext* opCtx, + std::vector<BSONObj> multikeyMetadataKeys, + MultikeyPaths paths) final; std::unique_ptr<BulkBuilder> initiateBulk(size_t maxMemoryUsageBytes) final; diff --git a/src/mongo/db/index/index_build_interceptor.cpp b/src/mongo/db/index/index_build_interceptor.cpp index b7eb5b336b5..8b9b1c504d3 100644 --- a/src/mongo/db/index/index_build_interceptor.cpp +++ b/src/mongo/db/index/index_build_interceptor.cpp @@ -276,7 +276,7 @@ Status IndexBuildInterceptor::_applyWrite(OperationContext* opCtx, auto accessMethod = _indexCatalogEntry->accessMethod(); if (opType == Op::kInsert) { int64_t numInserted; - auto status = accessMethod->insertKeys( + auto status = accessMethod->insertKeysAndUpdateMultikeyPaths( opCtx, {keySet.begin(), keySet.end()}, {}, diff --git a/src/mongo/db/multi_key_path_tracker.cpp b/src/mongo/db/multi_key_path_tracker.cpp index 003a600e6bf..a80a5e48194 100644 --- a/src/mongo/db/multi_key_path_tracker.cpp +++ b/src/mongo/db/multi_key_path_tracker.cpp @@ -87,6 +87,9 @@ void MultikeyPathTracker::addMultikeyPathInfo(MultikeyPathInfo info) { } mergeMultikeyPaths(&existingChanges.multikeyPaths, info.multikeyPaths); + existingChanges.multikeyMetadataKeys.insert(existingChanges.multikeyMetadataKeys.end(), + info.multikeyMetadataKeys.begin(), + info.multikeyMetadataKeys.end()); return; } @@ -109,6 +112,17 @@ const boost::optional<MultikeyPaths> MultikeyPathTracker::getMultikeyPathInfo( return boost::none; } +const boost::optional<std::vector<BSONObj>> MultikeyPathTracker::getMultikeyMetadataKeys( + const NamespaceString& nss, const std::string& indexName) { + for (const auto& multikeyPathInfo : _multikeyPathInfo) { + if (multikeyPathInfo.nss == nss && multikeyPathInfo.indexName == indexName) { + return multikeyPathInfo.multikeyMetadataKeys; + } + } + + 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 b155bc1fe58..0849a812c8a 100644 --- a/src/mongo/db/multi_key_path_tracker.h +++ b/src/mongo/db/multi_key_path_tracker.h @@ -41,6 +41,7 @@ namespace mongo { struct MultikeyPathInfo { NamespaceString nss; std::string indexName; + std::vector<BSONObj> multikeyMetadataKeys; MultikeyPaths multikeyPaths; }; @@ -91,6 +92,12 @@ public: const std::string& indexName); /** + * Returns the multikey metadata keys for the given inputs, or boost::none if none exist. + */ + const boost::optional<std::vector<BSONObj>> getMultikeyMetadataKeys( + 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/repl/storage_interface.h b/src/mongo/db/repl/storage_interface.h index ca6354a9a7a..426c6fbcb03 100644 --- a/src/mongo/db/repl/storage_interface.h +++ b/src/mongo/db/repl/storage_interface.h @@ -186,6 +186,7 @@ public: virtual Status setIndexIsMultikey(OperationContext* opCtx, const NamespaceString& nss, const std::string& indexName, + const std::vector<BSONObj>& multikeyMetadataKeys, const MultikeyPaths& paths, Timestamp ts) = 0; /** diff --git a/src/mongo/db/repl/storage_interface_impl.cpp b/src/mongo/db/repl/storage_interface_impl.cpp index 693f2199c70..e0527d461bb 100644 --- a/src/mongo/db/repl/storage_interface_impl.cpp +++ b/src/mongo/db/repl/storage_interface_impl.cpp @@ -522,6 +522,7 @@ Status StorageInterfaceImpl::renameCollection(OperationContext* opCtx, Status StorageInterfaceImpl::setIndexIsMultikey(OperationContext* opCtx, const NamespaceString& nss, const std::string& indexName, + const std::vector<BSONObj>& multikeyMetadataKeys, const MultikeyPaths& paths, Timestamp ts) { if (ts.isNull()) { @@ -552,7 +553,7 @@ Status StorageInterfaceImpl::setIndexIsMultikey(OperationContext* opCtx, str::stream() << "Could not find index " << indexName << " in " << nss.ns() << " to set to multikey."); } - collection->getIndexCatalog()->setMultikeyPaths(opCtx, idx, paths); + collection->getIndexCatalog()->setMultikeyPaths(opCtx, idx, multikeyMetadataKeys, paths); wunit.commit(); return Status::OK(); }); diff --git a/src/mongo/db/repl/storage_interface_impl.h b/src/mongo/db/repl/storage_interface_impl.h index 61eee68dea1..21827a8b0e8 100644 --- a/src/mongo/db/repl/storage_interface_impl.h +++ b/src/mongo/db/repl/storage_interface_impl.h @@ -96,6 +96,7 @@ public: Status setIndexIsMultikey(OperationContext* opCtx, const NamespaceString& nss, const std::string& indexName, + const std::vector<BSONObj>& multikeyMetadataKeys, const MultikeyPaths& paths, Timestamp ts) override; diff --git a/src/mongo/db/repl/storage_interface_impl_test.cpp b/src/mongo/db/repl/storage_interface_impl_test.cpp index 8ade17d6efa..f4ef3dc1bd7 100644 --- a/src/mongo/db/repl/storage_interface_impl_test.cpp +++ b/src/mongo/db/repl/storage_interface_impl_test.cpp @@ -2607,7 +2607,7 @@ TEST_F(StorageInterfaceImplTest, SetIndexIsMultikeyReturnsNamespaceNotFoundForMi StorageInterfaceImpl storage; auto nss = makeNamespace(_agent); ASSERT_EQUALS(ErrorCodes::NamespaceNotFound, - storage.setIndexIsMultikey(opCtx, nss, "foo", {}, Timestamp(3, 3))); + storage.setIndexIsMultikey(opCtx, nss, "foo", {}, {}, Timestamp(3, 3))); } TEST_F(StorageInterfaceImplTest, SetIndexIsMultikeyReturnsNamespaceNotFoundForMissingCollection) { @@ -2617,7 +2617,7 @@ TEST_F(StorageInterfaceImplTest, SetIndexIsMultikeyReturnsNamespaceNotFoundForMi NamespaceString wrongColl(nss.db(), "wrongColl"_sd); ASSERT_OK(storage.createCollection(opCtx, nss, CollectionOptions())); ASSERT_EQUALS(ErrorCodes::NamespaceNotFound, - storage.setIndexIsMultikey(opCtx, wrongColl, "foo", {}, Timestamp(3, 3))); + storage.setIndexIsMultikey(opCtx, wrongColl, "foo", {}, {}, Timestamp(3, 3))); } TEST_F(StorageInterfaceImplTest, SetIndexIsMultikeyReturnsIndexNotFoundForMissingIndex) { @@ -2626,7 +2626,7 @@ TEST_F(StorageInterfaceImplTest, SetIndexIsMultikeyReturnsIndexNotFoundForMissin auto nss = makeNamespace(_agent); ASSERT_OK(storage.createCollection(opCtx, nss, CollectionOptions())); ASSERT_EQUALS(ErrorCodes::IndexNotFound, - storage.setIndexIsMultikey(opCtx, nss, "foo", {}, Timestamp(3, 3))); + storage.setIndexIsMultikey(opCtx, nss, "foo", {}, {}, Timestamp(3, 3))); } TEST_F(StorageInterfaceImplTest, SetIndexIsMultikeyReturnsInvalidOptionsForNullTimestamp) { @@ -2635,7 +2635,7 @@ TEST_F(StorageInterfaceImplTest, SetIndexIsMultikeyReturnsInvalidOptionsForNullT auto nss = makeNamespace(_agent); ASSERT_OK(storage.createCollection(opCtx, nss, CollectionOptions())); ASSERT_EQUALS(ErrorCodes::InvalidOptions, - storage.setIndexIsMultikey(opCtx, nss, "foo", {}, Timestamp())); + storage.setIndexIsMultikey(opCtx, nss, "foo", {}, {}, Timestamp())); } TEST_F(StorageInterfaceImplTest, SetIndexIsMultikeySucceeds) { @@ -2650,7 +2650,7 @@ TEST_F(StorageInterfaceImplTest, SetIndexIsMultikeySucceeds) { ASSERT_EQUALS(_createIndexOnEmptyCollection(opCtx, nss, indexSpec), 2); MultikeyPaths paths = {{1}}; - ASSERT_OK(storage.setIndexIsMultikey(opCtx, nss, indexName, paths, Timestamp(3, 3))); + ASSERT_OK(storage.setIndexIsMultikey(opCtx, nss, indexName, {}, paths, Timestamp(3, 3))); AutoGetCollectionForReadCommand autoColl(opCtx, nss); ASSERT_TRUE(autoColl.getCollection()); auto indexCatalog = autoColl.getCollection()->getIndexCatalog(); diff --git a/src/mongo/db/repl/storage_interface_mock.h b/src/mongo/db/repl/storage_interface_mock.h index 68811f01bab..6b48bfcbb15 100644 --- a/src/mongo/db/repl/storage_interface_mock.h +++ b/src/mongo/db/repl/storage_interface_mock.h @@ -193,6 +193,7 @@ public: Status setIndexIsMultikey(OperationContext* opCtx, const NamespaceString& nss, const std::string& indexName, + const std::vector<BSONObj>& multikeyMetadataKeys, const MultikeyPaths& paths, Timestamp ts) override { diff --git a/src/mongo/db/repl/sync_tail.cpp b/src/mongo/db/repl/sync_tail.cpp index bdcccdfd384..1a72e087b1e 100644 --- a/src/mongo/db/repl/sync_tail.cpp +++ b/src/mongo/db/repl/sync_tail.cpp @@ -1438,8 +1438,12 @@ StatusWith<OpTime> SyncTail::multiApply(OperationContext* opCtx, MultiApplier::O // the first timestamp in the batch since we do not have enough information to find out // the timestamp of the first write that set the given multikey path. fassert(50686, - _storageInterface->setIndexIsMultikey( - opCtx, info.nss, info.indexName, info.multikeyPaths, firstTimeInBatch)); + _storageInterface->setIndexIsMultikey(opCtx, + info.nss, + info.indexName, + info.multikeyMetadataKeys, + info.multikeyPaths, + firstTimeInBatch)); } } diff --git a/src/mongo/dbtests/storage_timestamp_tests.cpp b/src/mongo/dbtests/storage_timestamp_tests.cpp index e59ea345044..218547ceb1e 100644 --- a/src/mongo/dbtests/storage_timestamp_tests.cpp +++ b/src/mongo/dbtests/storage_timestamp_tests.cpp @@ -48,6 +48,7 @@ #include "mongo/db/global_settings.h" #include "mongo/db/index/index_build_interceptor.h" #include "mongo/db/index/index_descriptor.h" +#include "mongo/db/index/wildcard_access_method.h" #include "mongo/db/index_builds_coordinator.h" #include "mongo/db/logical_clock.h" #include "mongo/db/multi_key_path_tracker.h" @@ -1323,6 +1324,196 @@ public: } }; +class SecondarySetWildcardIndexMultikeyOnInsert : public StorageTimestampTest { + +public: + void run() { + // Pretend to be a secondary. + repl::UnreplicatedWritesBlock uwb(_opCtx); + + NamespaceString nss("unittests.SecondarySetWildcardIndexMultikeyOnInsert"); + reset(nss); + UUID uuid = UUID::gen(); + { + AutoGetCollection autoColl(_opCtx, nss, LockMode::MODE_IX); + uuid = *autoColl.getCollection()->uuid(); + } + auto indexName = "a_1"; + auto indexSpec = BSON("name" << indexName << "key" << BSON("$**" << 1) << "v" + << static_cast<int>(kIndexVersion)); + ASSERT_OK(dbtests::createIndexFromSpec(_opCtx, nss.ns(), indexSpec)); + + _coordinatorMock->alwaysAllowWrites(false); + + const LogicalTime pastTime = _clock->reserveTicks(1); + const LogicalTime insertTime0 = _clock->reserveTicks(1); + const LogicalTime insertTime1 = _clock->reserveTicks(1); + const LogicalTime insertTime2 = _clock->reserveTicks(1); + + BSONObj doc0 = BSON("_id" << 0 << "a" << 3); + BSONObj doc1 = BSON("_id" << 1 << "a" << BSON_ARRAY(1 << 2)); + BSONObj doc2 = BSON("_id" << 2 << "a" << BSON_ARRAY(1 << 2)); + auto op0 = repl::OplogEntry( + BSON("ts" << insertTime0.asTimestamp() << "t" << 1LL << "v" << 2 << "op" + << "i" + << "ns" << nss.ns() << "ui" << uuid << "wall" << Date_t() << "o" << doc0)); + auto op1 = repl::OplogEntry( + BSON("ts" << insertTime1.asTimestamp() << "t" << 1LL << "v" << 2 << "op" + << "i" + << "ns" << nss.ns() << "ui" << uuid << "wall" << Date_t() << "o" << doc1)); + auto op2 = repl::OplogEntry( + BSON("ts" << insertTime2.asTimestamp() << "t" << 1LL << "v" << 2 << "op" + << "i" + << "ns" << nss.ns() << "ui" << uuid << "wall" << Date_t() << "o" << doc2)); + + std::vector<repl::OplogEntry> ops = {op0, op1, op2}; + + DoNothingOplogApplierObserver observer; + auto storageInterface = repl::StorageInterface::get(_opCtx); + auto writerPool = repl::OplogApplier::makeWriterPool(); + repl::OplogApplierImpl oplogApplier( + nullptr, // task executor. not required for multiApply(). + nullptr, // oplog buffer. not required for multiApply(). + &observer, + _coordinatorMock, + _consistencyMarkers, + storageInterface, + repl::OplogApplier::Options(repl::OplogApplication::Mode::kRecovering), + writerPool.get()); + + uassertStatusOK(oplogApplier.multiApply(_opCtx, ops)); + + AutoGetCollectionForRead autoColl(_opCtx, nss); + auto wildcardIndexDescriptor = + autoColl.getCollection()->getIndexCatalog()->findIndexByName(_opCtx, indexName); + const IndexAccessMethod* wildcardIndexAccessMethod = autoColl.getCollection() + ->getIndexCatalog() + ->getEntry(wildcardIndexDescriptor) + ->accessMethod(); + { + // Verify that the index is not multikey prior to the earliest insert timestamp. + OneOffRead oor(_opCtx, pastTime.asTimestamp()); + const WildcardAccessMethod* wam = + dynamic_cast<const WildcardAccessMethod*>(wildcardIndexAccessMethod); + MultikeyMetadataAccessStats stats; + std::set<FieldRef> paths = wam->getMultikeyPathSet(_opCtx, &stats); + ASSERT_EQUALS(0UL, paths.size()); + } + { + // Oplog application conservatively uses the first optime in the batch, insertTime0, as + // the point at which the index became multikey, despite the fact that the earliest op + // which caused the index to become multikey did not occur until insertTime1. This works + // because if we construct a query plan that incorrectly believes a particular path to + // be multikey, the plan will still be correct (if possibly sub-optimal). Conversely, if + // we were to construct a query plan that incorrectly believes a path is NOT multikey, + // it could produce incorrect results. + OneOffRead oor(_opCtx, insertTime0.asTimestamp()); + const WildcardAccessMethod* wam = + dynamic_cast<const WildcardAccessMethod*>(wildcardIndexAccessMethod); + MultikeyMetadataAccessStats stats; + std::set<FieldRef> paths = wam->getMultikeyPathSet(_opCtx, &stats); + ASSERT_EQUALS(1UL, paths.size()); + ASSERT_EQUALS("a", paths.begin()->dottedField()); + } + } +}; + +class SecondarySetWildcardIndexMultikeyOnUpdate : public StorageTimestampTest { + +public: + void run() { + // Pretend to be a secondary. + repl::UnreplicatedWritesBlock uwb(_opCtx); + + NamespaceString nss("unittests.SecondarySetWildcardIndexMultikeyOnUpdate"); + reset(nss); + UUID uuid = UUID::gen(); + { + AutoGetCollection autoColl(_opCtx, nss, LockMode::MODE_IX); + uuid = *autoColl.getCollection()->uuid(); + } + auto indexName = "a_1"; + auto indexSpec = BSON("name" << indexName << "key" << BSON("$**" << 1) << "v" + << static_cast<int>(kIndexVersion)); + ASSERT_OK(dbtests::createIndexFromSpec(_opCtx, nss.ns(), indexSpec)); + + _coordinatorMock->alwaysAllowWrites(false); + + const LogicalTime pastTime = _clock->reserveTicks(1); + const LogicalTime insertTime0 = _clock->reserveTicks(1); + const LogicalTime updateTime1 = _clock->reserveTicks(1); + const LogicalTime updateTime2 = _clock->reserveTicks(1); + + BSONObj doc0 = BSON("_id" << 0 << "a" << 3); + BSONObj doc1 = BSON("$v" << 1 << "$set" << BSON("a" << BSON_ARRAY(1 << 2))); + BSONObj doc2 = BSON("$v" << 1 << "$set" << BSON("a" << BSON_ARRAY(1 << 2))); + auto op0 = repl::OplogEntry( + BSON("ts" << insertTime0.asTimestamp() << "t" << 1LL << "v" << 2 << "op" + << "i" + << "ns" << nss.ns() << "ui" << uuid << "wall" << Date_t() << "o" << doc0)); + auto op1 = repl::OplogEntry( + BSON("ts" << updateTime1.asTimestamp() << "t" << 1LL << "v" << 2 << "op" + << "u" + << "ns" << nss.ns() << "ui" << uuid << "wall" << Date_t() << "o" << doc1 + << "o2" << BSON("_id" << 0))); + auto op2 = repl::OplogEntry( + BSON("ts" << updateTime2.asTimestamp() << "t" << 1LL << "v" << 2 << "op" + << "u" + << "ns" << nss.ns() << "ui" << uuid << "wall" << Date_t() << "o" << doc2 + << "o2" << BSON("_id" << 0))); + + std::vector<repl::OplogEntry> ops = {op0, op1, op2}; + + DoNothingOplogApplierObserver observer; + auto storageInterface = repl::StorageInterface::get(_opCtx); + auto writerPool = repl::OplogApplier::makeWriterPool(); + repl::OplogApplierImpl oplogApplier( + nullptr, // task executor. not required for multiApply(). + nullptr, // oplog buffer. not required for multiApply(). + &observer, + _coordinatorMock, + _consistencyMarkers, + storageInterface, + repl::OplogApplier::Options(repl::OplogApplication::Mode::kRecovering), + writerPool.get()); + + uassertStatusOK(oplogApplier.multiApply(_opCtx, ops)); + + AutoGetCollectionForRead autoColl(_opCtx, nss); + auto wildcardIndexDescriptor = + autoColl.getCollection()->getIndexCatalog()->findIndexByName(_opCtx, indexName); + const IndexAccessMethod* wildcardIndexAccessMethod = autoColl.getCollection() + ->getIndexCatalog() + ->getEntry(wildcardIndexDescriptor) + ->accessMethod(); + { + // Verify that the index is not multikey prior to the earliest insert timestamp. + OneOffRead oor(_opCtx, pastTime.asTimestamp()); + const WildcardAccessMethod* wam = + dynamic_cast<const WildcardAccessMethod*>(wildcardIndexAccessMethod); + MultikeyMetadataAccessStats stats; + std::set<FieldRef> paths = wam->getMultikeyPathSet(_opCtx, &stats); + ASSERT_EQUALS(0UL, paths.size()); + } + { + // Oplog application conservatively uses the first optime in the batch, insertTime0, as + // the point at which the index became multikey, despite the fact that the earliest op + // which caused the index to become multikey did not occur until updateTime1. This works + // because if we construct a query plan that incorrectly believes a particular path to + // be multikey, the plan will still be correct (if possibly sub-optimal). Conversely, if + // we were to construct a query plan that incorrectly believes a path is NOT multikey, + // it could produce incorrect results. + OneOffRead oor(_opCtx, insertTime0.asTimestamp()); + const WildcardAccessMethod* wam = + dynamic_cast<const WildcardAccessMethod*>(wildcardIndexAccessMethod); + MultikeyMetadataAccessStats stats; + std::set<FieldRef> paths = wam->getMultikeyPathSet(_opCtx, &stats); + ASSERT_EQUALS(1UL, paths.size()); + ASSERT_EQUALS("a", paths.begin()->dottedField()); + } + } +}; + class InitialSyncSetIndexMultikeyOnInsert : public StorageTimestampTest { public: @@ -3432,6 +3623,8 @@ public: add<SecondaryCreateCollectionBetweenInserts>(); add<PrimaryCreateCollectionInApplyOps>(); add<SecondarySetIndexMultikeyOnInsert>(); + add<SecondarySetWildcardIndexMultikeyOnInsert>(); + add<SecondarySetWildcardIndexMultikeyOnUpdate>(); add<InitialSyncSetIndexMultikeyOnInsert>(); add<PrimarySetIndexMultikeyOnInsert>(); add<PrimarySetIndexMultikeyOnInsertUnreplicated>(); diff --git a/src/mongo/dbtests/validate_tests.cpp b/src/mongo/dbtests/validate_tests.cpp index 8968eb7ec71..d27fe28a291 100644 --- a/src/mongo/dbtests/validate_tests.cpp +++ b/src/mongo/dbtests/validate_tests.cpp @@ -1609,8 +1609,6 @@ public: auto insertStatus = iam->insertKeys( &_opCtx, {keys.begin(), keys.end()}, - {}, - MultikeyPaths{}, swRecordId.getValue(), options, [this, &interceptor](const BSONObj& duplicateKey) { @@ -1648,8 +1646,6 @@ public: auto insertStatus = iam->insertKeys( &_opCtx, {keys.begin(), keys.end()}, - {}, - MultikeyPaths{}, swRecordId.getValue(), options, [this, &interceptor](const BSONObj& duplicateKey) { |