-
Type: Improvement
-
Resolution: Fixed
-
Priority: Major - P3
-
Affects Version/s: None
-
Component/s: Internal Code
-
Service Arch
-
Fully Compatible
-
v8.0
-
Service Arch 2024-04-29
At a high-level, TenantMigrationAccessBlockerRegistry is a hash-map, mapping a TenantId to an instance of DonorRecipientAccessBlockerPair, which holds two shared pointers. Currently, all accesses to this hash-map is protected by a single mutex that doesn't support shared access. We can replace the mutex with a shared mutex (e.g. RWMutex from SERVER-86656) and make DonorRecipientAccessBlockerPair a synchronized type, using a cheap synchronization primitive like a SpinLock.
I imagine the implementation to be similar to the following, and can use std::shared_mutex in the meantime and before SERVER-86656 lands.
void add(const TenantId&, std::shared_ptr<TenantMigrationAccessBlocker>) { std::shared_lock sLock(_mutex); if (canFindAnEntryForTenant) { // Use the spin-lock on the entry and update it. } sLock.unlock(); std::unique_lock xLock(_mutex); // Add a new entry unless it's already added by someone else. } void add(const std::vector<TenantId>&, std::shared_ptr<TenantMigrationAccessBlocker>) { // Similar to adding a single access blocker, but we'd want to acquire the shared-lock // once, find what can be updated and what needs to be added, and then add new // entries when exclusively acquiring the mutex. ... } boost::optional<MtabPair> getAccessBlockersForDbName(const DatabaseName&) { std::shared_lock sLock(_mutex); ... } auto getTenantMigrationAccessBlockerForTenantId(const TenantId&, MtabType) { std::shared_lock sLock(_mutex); ... } template <class Container, class BlockerType > auto getBlockers(WithLock, Container& container, const UUID& migrationId) { std::vector<std::shared_ptr<BlockerType>> blockers; for (const auto& pair : container) { auto mTab = [&] { if constexpr (std::is_same(BlockerType, TenantMigrationDonorAccessBlocker)) { return pair.second.getDonorAccessBlocker(); } else { static_assert(std::is_same(BlockerType, TenantMigrationRecipientAccessBlocker)); return pair.second.getRecipientAccessBlocker(); } }(); if (mTab && mTab->getMigrationId() == migrationId) { blockers.push_back(mTab); } } return blockers; } auto getDonorAccessBlockersForMigration(const UUID& migrationId) { std::shared_lock sLock(_mutex); return getBlockers<decltype(_tenantMigrationAccessBlockers), TenantMigrationDonorAccessBlocker>(_tenantMigrationAccessBlockers, migrationId); } auto getRecipientAccessBlockersForMigration(const UUID&) { std::shared_lock sLock(_mutex); return getBlockers<decltype(_tenantMigrationAccessBlockers), TenantMigrationRecipientAccessBlocker>(_tenantMigrationAccessBlockers, migrationId); } void applyAll(BlockerType, ApplyAllCallback&& callback) { std::shared_lock sLock(_mutex); ... } void appendInfoForServerStatus(BSONObjBuilder*) const { std::shared_lock sLock(_mutex); ... } void onMajorityCommitPointUpdate(repl::OpTime opTime) { std::shared_lock sLock(_mutex); ... } // We'd need to acquire the mutex exclusively for the following, since they are // not expected to be on the happy path. void clear() { ... } void shutDown() { ... } void removeAll(MtabType) { ... } void removeAccessBlockersForMigration(const UUID&, BlockerType) { ... } void addGlobalDonorAccessBlocker(...){ ... } std::shared_ptr<TaskExecutor> getAsyncBlockingOperationsExecutor() const { // Can just be inlined in the header. }
- is related to
-
SERVER-87004 Optimize hash-tables with infrequent updates
- Closed