summaryrefslogtreecommitdiff
path: root/src/mongo/db/catalog/historical_catalogid_tracker.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/db/catalog/historical_catalogid_tracker.cpp')
-rw-r--r--src/mongo/db/catalog/historical_catalogid_tracker.cpp695
1 files changed, 695 insertions, 0 deletions
diff --git a/src/mongo/db/catalog/historical_catalogid_tracker.cpp b/src/mongo/db/catalog/historical_catalogid_tracker.cpp
new file mode 100644
index 00000000000..901f9704906
--- /dev/null
+++ b/src/mongo/db/catalog/historical_catalogid_tracker.cpp
@@ -0,0 +1,695 @@
+/**
+ * Copyright (C) 2023-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * 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
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * 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 Server Side 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.
+ */
+
+#include "mongo/db/catalog/historical_catalogid_tracker.h"
+#include "mongo/db/storage/storage_options.h"
+
+namespace mongo {
+namespace {
+// Sentinel id for marking a catalogId mapping range as unknown. Must use an invalid RecordId.
+static RecordId kUnknownRangeMarkerId = RecordId::minLong();
+// Maximum number of entries in catalogId mapping when inserting catalogId missing at timestamp.
+// Used to avoid quadratic behavior when inserting entries at the beginning. When threshold is
+// reached we will fall back to more durable catalog scans.
+static constexpr int kMaxCatalogIdMappingLengthForMissingInsert = 1000;
+
+// Copy existing value from immutable data structure or default-construct if not existing
+template <class Container, class Key>
+auto copyIfExists(const Container& container, const Key& key) {
+ const auto* value = container.find(key);
+ if (value) {
+ return *value;
+ }
+ return typename Container::mapped_type();
+}
+
+// Returns true if cleanup is needed for a catalogId range
+bool needsCleanup(const std::vector<HistoricalCatalogIdTracker::TimestampedCatalogId>& ids) {
+ // Cleanup may occur if we have more than one entry for the namespace.
+ return ids.size() > 1;
+}
+
+// Returns the lowest time a catalogId range may be cleaned up. needsCleanup() needs to have been
+// checked prior to calling this function
+Timestamp cleanupTime(const std::vector<HistoricalCatalogIdTracker::TimestampedCatalogId>& ids) {
+ // When we have multiple entries, use the time at the second entry as the cleanup time,
+ // when the oldest timestamp advances past this we no longer need the first entry.
+ return ids.at(1).ts;
+}
+
+// Converts a not found lookup timestamp to a LookupResult based on the oldest maintained timestamp
+HistoricalCatalogIdTracker::LookupResult resultForNotFound(boost::optional<Timestamp> ts,
+ Timestamp oldestMaintained) {
+ // If the request was with a time prior to the oldest maintained time it is unknown, otherwise
+ // we know it is not existing.
+ return {RecordId{},
+ ts && *ts < oldestMaintained
+ ? HistoricalCatalogIdTracker::LookupResult::Existence::kUnknown
+ : HistoricalCatalogIdTracker::LookupResult::Existence::kNotExists};
+}
+
+// Converts a catalogId range into a lookup result that represents the latest state
+HistoricalCatalogIdTracker::LookupResult latestInRange(
+ const std::vector<HistoricalCatalogIdTracker::TimestampedCatalogId>& range) {
+ auto catalogId = range.back().id;
+ if (catalogId) {
+ return {*catalogId, HistoricalCatalogIdTracker::LookupResult::Existence::kExists};
+ }
+ return {RecordId{}, HistoricalCatalogIdTracker::LookupResult::Existence::kNotExists};
+}
+
+HistoricalCatalogIdTracker::LookupResult findInRange(
+ Timestamp ts,
+ const std::vector<HistoricalCatalogIdTracker::TimestampedCatalogId>& range,
+ Timestamp oldestMaintained) {
+ // The algorithm is as follows for an input range of the following format that is sorted on
+ // timestamp: (ts1, id1), (ts2, id2), ..., (tsN, idN).
+ //
+ // We use upper_bound to perform binary search to the timestamp that is strictly larger than our
+ // query timestamp ts. The iterator can then be decremented to get the entry where the time is
+ // less or equal, this is the entry we are looking for. If upper_bound returns begin() or the
+ // 'id' in our found entry is the unknown marker the lookup result is unknown.
+ auto rangeIt =
+ std::upper_bound(range.begin(), range.end(), ts, [](const auto& ts, const auto& entry) {
+ return ts < entry.ts;
+ });
+ if (rangeIt == range.begin()) {
+ return resultForNotFound(ts, oldestMaintained);
+ }
+ // Upper bound returns an iterator to the first entry with a larger timestamp. Decrement the
+ // iterator to get the last entry where the time is less or equal.
+ auto catalogId = (--rangeIt)->id;
+ if (catalogId) {
+ if (*catalogId != kUnknownRangeMarkerId) {
+ return {*catalogId, HistoricalCatalogIdTracker::LookupResult::Existence::kExists};
+ } else {
+ return {RecordId{}, HistoricalCatalogIdTracker::LookupResult::Existence::kUnknown};
+ }
+ }
+ return {RecordId{}, HistoricalCatalogIdTracker::LookupResult::Existence::kNotExists};
+}
+} // namespace
+
+HistoricalCatalogIdTracker::LookupResult HistoricalCatalogIdTracker::lookup(
+ const NamespaceString& nss, boost::optional<Timestamp> ts) const {
+ if (const std::vector<TimestampedCatalogId>* mapping = _nss.find(nss)) {
+ // Mapping found for namespace, get result depending on timestamp.
+ if (ts) {
+ return findInRange(*ts, *mapping, _oldestTimestampMaintained);
+ }
+ return latestInRange(*mapping);
+ }
+ // No mapping found for namespace, result is either not found or unknown depending on timestamp
+ return resultForNotFound(ts, _oldestTimestampMaintained);
+}
+
+
+HistoricalCatalogIdTracker::LookupResult HistoricalCatalogIdTracker::lookup(
+ const UUID& uuid, boost::optional<Timestamp> ts) const {
+ if (const std::vector<TimestampedCatalogId>* mapping = _uuid.find(uuid)) {
+ // Mapping found for namespace, get result depending on timestamp.
+ if (ts) {
+ return findInRange(*ts, *mapping, _oldestTimestampMaintained);
+ }
+ return latestInRange(*mapping);
+ }
+
+ // No mapping found for namespace, result is either not found or unknown depending on timestamp
+ return resultForNotFound(ts, _oldestTimestampMaintained);
+}
+
+void HistoricalCatalogIdTracker::create(const NamespaceString& nss,
+ const UUID& uuid,
+ RecordId catalogId,
+ boost::optional<Timestamp> ts) {
+
+ if (!ts) {
+ _createNoTimestamp(nss, uuid, catalogId);
+ return;
+ }
+
+ _createTimestamp(nss, uuid, catalogId, *ts);
+}
+
+void HistoricalCatalogIdTracker::drop(const NamespaceString& nss,
+ const UUID& uuid,
+ boost::optional<Timestamp> ts) {
+ if (!ts) {
+ _dropNoTimestamp(nss, uuid);
+ return;
+ }
+
+ _dropTimestamp(nss, uuid, *ts);
+}
+
+void HistoricalCatalogIdTracker::rename(const NamespaceString& from,
+ const NamespaceString& to,
+ boost::optional<Timestamp> ts) {
+ if (!ts) {
+ _renameNoTimestamp(from, to);
+ return;
+ }
+
+ _renameTimestamp(from, to, *ts);
+}
+
+bool HistoricalCatalogIdTracker::canRecordNonExisting(const NamespaceString& nss) const {
+ // recordNonExistingAtTime can use a lot of entries because of the unknown marker that is
+ // needed. Constrain the memory usage.
+ if (const std::vector<TimestampedCatalogId>* ids = _nss.find(nss)) {
+ return ids->size() < kMaxCatalogIdMappingLengthForMissingInsert;
+ }
+ return true;
+}
+
+bool HistoricalCatalogIdTracker::canRecordNonExisting(const UUID& uuid) const {
+ // recordNonExistingAtTime can use a lot of entries because of the unknown marker that is
+ // needed. Constrain the memory usage.
+ if (const std::vector<TimestampedCatalogId>* ids = _uuid.find(uuid)) {
+ return ids->size() < kMaxCatalogIdMappingLengthForMissingInsert;
+ }
+ return true;
+}
+
+void HistoricalCatalogIdTracker::recordExistingAtTime(const NamespaceString& nss,
+ const UUID& uuid,
+ RecordId catalogId,
+ Timestamp ts) {
+
+ // Helper lambda to perform the operation on both namespace and UUID
+ auto doRecord =
+ [this, &catalogId, &ts](auto& idsContainer, auto& changesContainer, const auto& key) {
+ // Helper to update the cleanup time after we've performed an insert.
+ auto markForCleanupIfNeeded = [&](const auto& ids) {
+ if (!needsCleanup(ids)) {
+ return;
+ }
+
+ changesContainer = changesContainer.insert(key);
+ _recordCleanupTime(cleanupTime(ids));
+ };
+
+ // Get copy of existing mapping, or default-construct new.
+ auto ids = copyIfExists(idsContainer, key);
+ // Helper to write updated id mapping back into container at scope exit. This allows us
+ // to write to 'ids' as if we were doing inplace updates to the container.
+ ScopeGuard scopedGuard([&] { idsContainer = idsContainer.set(key, std::move(ids)); });
+
+ // Binary search to the entry with same or larger timestamp. This represents the insert
+ // position in the container.
+ auto it = std::lower_bound(
+ ids.begin(), ids.end(), ts, [](const auto& entry, const Timestamp& ts) {
+ return entry.ts < ts;
+ });
+
+ if (it != ids.end()) {
+ // An entry could exist already if concurrent writes are performed, keep the latest
+ // change in that case.
+ if (it->ts == ts) {
+ it->id = catalogId;
+ return;
+ }
+
+ // If next element has same catalogId, we can adjust its timestamp to cover a longer
+ // range
+ if (it->id == catalogId) {
+ it->ts = ts;
+
+ markForCleanupIfNeeded(ids);
+ return;
+ }
+ }
+
+ // Otherwise insert new entry at timestamp
+ ids.insert(it, {{catalogId, ts}});
+ markForCleanupIfNeeded(ids);
+ };
+
+ // Apply the insert to both namespace and uuid.
+ doRecord(_nss, _nssChanges, nss);
+ doRecord(_uuid, _uuidChanges, uuid);
+}
+
+void HistoricalCatalogIdTracker::recordNonExistingAtTime(const NamespaceString& nss, Timestamp ts) {
+ // Get copy of existing mapping, or default-construct new.
+ auto ids = copyIfExists(_nss, nss);
+
+ // Avoid inserting missing mapping when the list has grown past the threshold. Will cause
+ // the system to fall back to scanning the durable catalog.
+ if (ids.size() >= kMaxCatalogIdMappingLengthForMissingInsert) {
+ return;
+ }
+
+ // Helper to write updated id mapping back into container at scope exit
+ ScopeGuard scopedGuard([&] { _nss = _nss.set(nss, std::move(ids)); });
+
+ // Binary search to the entry with same or larger timestamp. This represents the insert position
+ // in the container.
+ auto it =
+ std::lower_bound(ids.begin(), ids.end(), ts, [](const auto& entry, const Timestamp& ts) {
+ return entry.ts < ts;
+ });
+
+ if (it != ids.end() && it->ts == ts) {
+ // An entry could exist already if concurrent writes are performed, keep the latest
+ // change in that case.
+ it->id = boost::none;
+ } else {
+ // Otherwise insert new entry
+ it = ids.insert(it, {boost::none, ts});
+ }
+
+ // The iterator is positioned on the added/modified element above, reposition it to the next
+ // entry
+ ++it;
+
+ // We don't want to assume that the namespace remains not existing until the next entry, as
+ // there can be times where the namespace actually does exist. To make sure we trigger the
+ // scanning of the durable catalog in this range we will insert a bogus entry using an invalid
+ // RecordId at the next timestamp. This will treat the range forward as unknown.
+ auto nextTs = ts + 1;
+
+ // If the next entry is on the next timestamp already, we can skip adding the bogus entry.
+ // If this function is called for a previously unknown namespace or UUID, we may not have
+ // any future valid entries and the iterator would be positioned at and at this point.
+ if (it == ids.end() || it->ts != nextTs) {
+ ids.insert(it, {kUnknownRangeMarkerId, nextTs});
+ }
+
+ // Update cleanup time if needed
+ if (!needsCleanup(ids)) {
+ return;
+ }
+
+ _nssChanges = _nssChanges.insert(nss);
+ _recordCleanupTime(cleanupTime(ids));
+}
+
+void HistoricalCatalogIdTracker::recordNonExistingAtTime(const UUID& uuid, Timestamp ts) {
+ auto ids = copyIfExists(_uuid, uuid);
+
+ // Avoid inserting missing mapping when the list has grown past the threshold. Will cause
+ // the system to fall back to scanning the durable catalog.
+ if (ids.size() >= kMaxCatalogIdMappingLengthForMissingInsert) {
+ return;
+ }
+
+ // Helper to write updated id mapping back into container at scope exit
+ ScopeGuard scopedGuard([&] { _uuid = _uuid.set(uuid, std::move(ids)); });
+
+ // Binary search to the entry with same or larger timestamp. This represents the insert position
+ // in the container.
+ auto it =
+ std::lower_bound(ids.begin(), ids.end(), ts, [](const auto& entry, const Timestamp& ts) {
+ return entry.ts < ts;
+ });
+
+ if (it != ids.end() && it->ts == ts) {
+ // An entry could exist already if concurrent writes are performed, keep the latest
+ // change in that case.
+ it->id = boost::none;
+ } else {
+ // Otherwise insert new entry
+ it = ids.insert(it, {boost::none, ts});
+ }
+
+ // The iterator is positioned on the added/modified element above, reposition it to the next
+ // entry
+ ++it;
+
+ // We don't want to assume that the namespace remains not existing until the next entry, as
+ // there can be times where the namespace actually does exist. To make sure we trigger the
+ // scanning of the durable catalog in this range we will insert a bogus entry using an invalid
+ // RecordId at the next timestamp. This will treat the range forward as unknown.
+ auto nextTs = ts + 1;
+
+ // If the next entry is on the next timestamp already, we can skip adding the bogus entry.
+ // If this function is called for a previously unknown namespace or UUID, we may not have
+ // any future valid entries and the iterator would be positioned at and at this point.
+ if (it == ids.end() || it->ts != nextTs) {
+ ids.insert(it, {kUnknownRangeMarkerId, nextTs});
+ }
+
+ // Update cleanup time if needed
+ if (!needsCleanup(ids)) {
+ return;
+ }
+
+ _uuidChanges = _uuidChanges.insert(uuid);
+ _recordCleanupTime(cleanupTime(ids));
+}
+
+bool HistoricalCatalogIdTracker::dirty(Timestamp oldest) const {
+ return _lowestTimestampForCleanup <= oldest;
+}
+
+void HistoricalCatalogIdTracker::cleanup(Timestamp oldest) {
+ Timestamp nextLowestCleanupTimestamp = Timestamp::max();
+
+ // Helper lambda to perform the operation on both namespace and UUID
+ auto doCleanup = [this, &oldest, &nextLowestCleanupTimestamp](auto& idsContainer,
+ auto& changesContainer) {
+ // Batch all changes together
+ auto ids = idsContainer.transient();
+ auto changes = changesContainer.transient();
+
+ for (auto&& key : changesContainer) {
+ //
+ auto range = ids.at(key);
+
+ // Binary search for next larger timestamp
+ auto rangeIt = std::upper_bound(
+ range.begin(), range.end(), oldest, [](const auto& ts, const auto& entry) {
+ return ts < entry.ts;
+ });
+
+ // Continue if there is nothing to cleanup for this timestamp yet
+ if (rangeIt == range.begin()) {
+ // There should always be at least two entries in the range when we hit this
+ // branch. For the namespace to be put in '_nssChanges' we need at least two
+ // entries.
+ invariant(range.size() > 1);
+ nextLowestCleanupTimestamp =
+ std::min(nextLowestCleanupTimestamp, cleanupTime(range));
+ continue;
+ }
+
+ // The iterator is positioned to the closest entry that has a larger timestamp,
+ // decrement to get a lower or equal timestamp. This represents the first entry that we
+ // may not cleanup.
+ --rangeIt;
+
+ // Erase range, we will leave at least one element due to the decrement above
+ range.erase(range.begin(), rangeIt);
+
+ // If more changes are needed for this namespace, keep it in the set and keep track
+ // of lowest timestamp.
+ if (range.size() > 1) {
+ nextLowestCleanupTimestamp =
+ std::min(nextLowestCleanupTimestamp, cleanupTime(range));
+ ids.set(key, std::move(range));
+ continue;
+ }
+ // If the last remaining element is a drop earlier than the oldest timestamp, we can
+ // remove tracking this namespace
+ if (range.back().id == boost::none) {
+ ids.erase(key);
+ } else {
+ ids.set(key, std::move(range));
+ }
+
+ // Unmark this namespace or UUID for needing changes.
+ changes.erase(key);
+ }
+
+ // Write back all changes to main container
+ changesContainer = changes.persistent();
+ idsContainer = ids.persistent();
+ };
+
+ // Iterate over all namespaces and UUIDs that is marked that they need cleanup
+ doCleanup(_nss, _nssChanges);
+ doCleanup(_uuid, _uuidChanges);
+
+ _lowestTimestampForCleanup = nextLowestCleanupTimestamp;
+ _oldestTimestampMaintained = std::max(_oldestTimestampMaintained, oldest);
+}
+
+void HistoricalCatalogIdTracker::rollback(Timestamp stable) {
+ _nssChanges = {};
+ _uuidChanges = {};
+ _lowestTimestampForCleanup = Timestamp::max();
+ _oldestTimestampMaintained = std::min(_oldestTimestampMaintained, stable);
+
+ // Helper lambda to perform the operation on both namespace and UUID
+ auto removeLargerTimestamps = [this, &stable](auto& idsContainer, auto& changesContainer) {
+ // Batch all changes together
+ auto idsWriter = idsContainer.transient();
+ auto changesWriter = changesContainer.transient();
+
+ // Go through all known mappings and remove entries larger than input stable timestamp
+ for (const auto& [key, ids] : idsContainer) {
+ // Binary search to the first entry with a too large timestamp
+ auto end = std::upper_bound(
+ ids.begin(), ids.end(), stable, [](Timestamp ts, const auto& entry) {
+ return ts < entry.ts;
+ });
+
+ // Create a new range without the timestamps that are too large
+ std::vector<TimestampedCatalogId> removed(ids.begin(), end);
+
+ // If the resulting range is empty, remove the key from the container
+ if (removed.empty()) {
+ idsWriter.erase(key);
+ continue;
+ }
+
+ // Calculate when this namespace needs to be cleaned up next
+ if (needsCleanup(removed)) {
+ Timestamp cleanTime = cleanupTime(removed);
+ changesWriter.insert(key);
+ _recordCleanupTime(cleanTime);
+ }
+ idsWriter.set(key, std::move(removed));
+ }
+
+ // Write back all changes to main container
+ changesContainer = changesWriter.persistent();
+ idsContainer = idsWriter.persistent();
+ };
+
+ // Rollback on both namespace and uuid containers.
+ removeLargerTimestamps(_nss, _nssChanges);
+ removeLargerTimestamps(_uuid, _uuidChanges);
+}
+
+void HistoricalCatalogIdTracker::_recordCleanupTime(Timestamp ts) {
+ if (ts < _lowestTimestampForCleanup) {
+ _lowestTimestampForCleanup = ts;
+ }
+}
+
+void HistoricalCatalogIdTracker::_createTimestamp(const NamespaceString& nss,
+ const UUID& uuid,
+ RecordId catalogId,
+ Timestamp ts) {
+ // Helper lambda to perform the operation on both namespace and UUID
+ auto doCreate = [&catalogId, &ts](auto& idsContainer, const auto& key) {
+ // Make a copy of the vector stored at 'key'
+ auto ids = copyIfExists(idsContainer, key);
+
+ // An entry could exist already if concurrent writes are performed, keep the latest
+ // change in that case.
+ if (!ids.empty() && ids.back().ts == ts) {
+ ids.back().id = catalogId;
+ idsContainer = idsContainer.set(key, std::move(ids));
+ return;
+ }
+
+ // Otherwise, push new entry at the end. Timestamp is always increasing
+ invariant(ids.empty() || ids.back().ts < ts);
+ // If the catalogId is the same as last entry, there's nothing we need to do. This can
+ // happen when the catalog is reopened.
+ if (!ids.empty() && ids.back().id == catalogId) {
+ return;
+ }
+
+ // Push new mapping to the end and write back to the container. As this is a create, we do
+ // not need to update the cleanup time as a create can never yield an updated (lower)
+ // cleanup time for this namespace/uuid.
+ ids.push_back({catalogId, ts});
+ idsContainer = idsContainer.set(key, std::move(ids));
+ };
+
+ // Create on both namespace and uuid containers.
+ doCreate(_nss, nss);
+ doCreate(_uuid, uuid);
+}
+
+void HistoricalCatalogIdTracker::_createNoTimestamp(const NamespaceString& nss,
+ const UUID& uuid,
+ RecordId catalogId) {
+ // Make sure untimestamped writes have a single entry in mapping. If we're mixing
+ // timestamped with untimestamped (such as repair). Ignore the untimestamped writes
+ // as an untimestamped deregister will correspond with an untimestamped register. We
+ // should leave the mapping as-is in this case.
+
+ auto doCreate = [&catalogId](auto& idsContainer, const auto& key) {
+ const std::vector<TimestampedCatalogId>* ids = idsContainer.find(key);
+ if (!ids) {
+ // This namespace or UUID was added due to an untimestamped write, add an entry
+ // with min timestamp
+ idsContainer = idsContainer.set(key, {{catalogId, Timestamp::min()}});
+ return;
+ }
+
+ if (ids->size() > 1 && !storageGlobalParams.repair) {
+ // This namespace or UUID was added due to an untimestamped write. But this
+ // namespace or UUID already had some timestamped writes performed. In this
+ // case, we re-write the history. The only known area that does this today is
+ // when profiling is enabled (untimestamped collection creation), followed by
+ // dropping the database (timestamped collection drop).
+ // TODO SERVER-75740: Remove this branch.
+ invariant(!ids->back().ts.isNull());
+
+ idsContainer = idsContainer.set(key, {{catalogId, Timestamp::min()}});
+ }
+ };
+
+ // Create on both namespace and uuid containers.
+ doCreate(_nss, nss);
+ doCreate(_uuid, uuid);
+}
+
+void HistoricalCatalogIdTracker::_dropTimestamp(const NamespaceString& nss,
+ const UUID& uuid,
+ Timestamp ts) {
+ // Helper lambda to perform the operation on both namespace and UUID
+ auto doDrop = [this, &ts](auto& idsContainer, auto& changesContainer, const auto& key) {
+ // Make a copy of the vector stored at 'key'
+ auto ids = copyIfExists(idsContainer, key);
+ // An entry could exist already if concurrent writes are performed, keep the latest change
+ // in that case.
+ if (!ids.empty() && ids.back().ts == ts) {
+ ids.back().id = boost::none;
+ idsContainer = idsContainer.set(key, std::move(ids));
+ return;
+ }
+
+ // Otherwise, push new entry at the end. Timestamp is always increasing
+ invariant(ids.empty() || ids.back().ts < ts);
+ // If the catalogId is the same as last entry, there's nothing we need to do. This can
+ // happen when the catalog is reopened.
+ if (!ids.empty() && !ids.back().id.has_value()) {
+ return;
+ }
+
+ // A drop entry can't be pushed in the container if it's empty. This is because we cannot
+ // initialize the namespace or UUID with a single drop.
+ invariant(!ids.empty());
+
+ // Push the drop at the end our or mapping
+ ids.push_back({boost::none, ts});
+
+ // This drop may result in the possibility of cleanup in the future
+ if (needsCleanup(ids)) {
+ Timestamp cleanTime = cleanupTime(ids);
+ changesContainer = changesContainer.insert(key);
+ _recordCleanupTime(cleanTime);
+ }
+
+ // Write back the updated mapping into our container
+ idsContainer = idsContainer.set(key, std::move(ids));
+ };
+
+ // Drop on both namespace and uuid containers
+ doDrop(_nss, _nssChanges, nss);
+ doDrop(_uuid, _uuidChanges, uuid);
+}
+
+void HistoricalCatalogIdTracker::_dropNoTimestamp(const NamespaceString& nss, const UUID& uuid) {
+ // Make sure untimestamped writes have a single entry in mapping. If we're mixing
+ // timestamped with untimestamped (such as repair). Ignore the untimestamped writes as
+ // an untimestamped deregister will correspond with an untimestamped register. We should
+ // leave the mapping as-is in this case.
+
+ auto doDrop = [](auto& idsContainer, const auto& key) {
+ const std::vector<TimestampedCatalogId>* ids = idsContainer.find(key);
+ if (ids && ids->size() == 1) {
+ // This namespace or UUID was removed due to an untimestamped write, clear entries.
+ idsContainer = idsContainer.erase(key);
+ }
+ };
+
+ // Drop on both namespace and uuid containers
+ doDrop(_nss, nss);
+ doDrop(_uuid, uuid);
+}
+
+void HistoricalCatalogIdTracker::_renameTimestamp(const NamespaceString& from,
+ const NamespaceString& to,
+ Timestamp ts) {
+ // Make copies of existing mappings on these namespaces.
+ auto toIds = copyIfExists(_nss, to);
+ auto fromIds = copyIfExists(_nss, from);
+
+ // First update 'to' mapping. This is similar to a 'create'.
+ if (!toIds.empty() && toIds.back().ts == ts) {
+ // An entry could exist already if concurrent writes are performed, keep the latest change
+ // in that case.
+ toIds.back().id = fromIds.back().id;
+ } else {
+ // Timestamps should always be increasing.
+ invariant(toIds.empty() || toIds.back().ts < ts);
+
+ // Push to end, we can take the catalogId from 'from'. We don't need to check if timestamp
+ // needs to be cleaned up as this is equivalent of a 'create'.
+ toIds.push_back({fromIds.back().id, ts});
+ }
+
+ // Then, update 'from' mapping. This is similar to a 'drop'.
+ if (!fromIds.empty() && fromIds.back().ts == ts) {
+ // Re-write latest entry if timestamp match (multiple changes occured in this transaction),
+ // otherwise push at end.
+ fromIds.back().id = boost::none;
+ } else {
+ // Timestamps should always be increasing.
+ invariant(fromIds.empty() || fromIds.back().ts < ts);
+ // Push to end and calculate cleanup timestamp.
+ fromIds.push_back({boost::none, ts});
+ if (needsCleanup(fromIds)) {
+ Timestamp cleanTime = cleanupTime(fromIds);
+ _nssChanges = std::move(_nssChanges).insert(from);
+ _recordCleanupTime(cleanTime);
+ }
+ }
+
+ // Store updates mappings back into container.
+ auto writer = _nss.transient();
+ writer.set(from, std::move(fromIds));
+ writer.set(to, std::move(toIds));
+ _nss = writer.persistent();
+}
+
+void HistoricalCatalogIdTracker::_renameNoTimestamp(const NamespaceString& from,
+ const NamespaceString& to) {
+ // We should never perform rename in a mixed-mode environment. 'from' should contain a
+ // single entry and there should be nothing in 'to' .
+ const std::vector<TimestampedCatalogId>* fromIds = _nss.find(from);
+ invariant(fromIds && fromIds->size() == 1);
+ invariant(!_nss.find(to));
+
+ auto writer = _nss.transient();
+ // Take the last known catalogId from 'from'.
+ writer.set(to, {{fromIds->back().id, Timestamp::min()}});
+ writer.erase(from);
+ _nss = writer.persistent();
+}
+
+} // namespace mongo