Uploaded image for project: 'Core Server'
  1. Core Server
  2. SERVER-89009

Optimize `TenantMigrationAccessBlockerRegistry`

    • 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.
      }
      

            Assignee:
            didier.nadeau@mongodb.com Didier Nadeau
            Reporter:
            amirsaman.memaripour@mongodb.com Amirsaman Memaripour
            Votes:
            0 Vote for this issue
            Watchers:
            6 Start watching this issue

              Created:
              Updated:
              Resolved: