-
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
The data-structure that backs APIVersionMetrics can be optimized for frequent reads:
- Although users are not required to reuse appNames, it is expected for the set of appNames connected to a server to rarely change after warmup.
- Currently, we only track 3 API versions: "default" (no API version is specified), "1", and "2" (test only).
- For each appName and version combo, APIVersionMetrics records the latest timestamp.
- To generate a report of API version usage, APIVersionMetrics first prunes the entires that are older than a day, and then report the appName and the versions used by each appName.
Considering the above, we can change the implementation to favor readers:
class APIVersionMetrics { private: struct VersionMetrics { // [0]: Default // [1]: Version 1 // [2]: Version 2 (test-only). std::array<Atomic<Date_t>, 3> timestamps(Date_t::min()); }; mutable std::shared_mutex _mutex; // Before SERVER-86656 lands. StringMap<VersionMetrics> _apiVersionMetrics; };
The above enables us to update individual timestamps with atomic operations, rather than exclusively acquire the mutex that protects _apiVersionMetrics. Furthermore, we only need to exclusively lock _mutex when updating the hash-table, which should be infrequent (hopefully very rare).
void APIVersionMetrics::update(StringData appName, const APIParameters& apiParams) { auto now = getGlobalServiceContext()->getFastClockSource()->now(); std::shared_lock sLock(_mutex); ... if (MONGO_likely(appNameIter != _apiVersionMetrics.end()) { appNameIter->second.timestamps[versionIndex].storeRelaxed(now); } else { sLock.unlock(); stdx::lock_guard xLock(_mutex); [ appNameIter, _ ] = _apiVersionMetrics.emplace(appName, {}); appNameIter->second.timestamps[versionIndex].storeRelaxed(now); } } void APIVersionMetrics::_appendAPIVersionData(BSONObjBuilder* bob) { std::shared_lock sLock(_mutex); for (const auto& [appName, versionTimestamps] : _apiVersionMetrics) { ... } }
Finally, we can optimize removing stale entries by only acquiring the exclusive mutex if the hash-map needs to be changed:
void APIVersionMetrics::_removeStaleTimestamps() { auto now = getGlobalServiceContext()->getFastClockSource()->now(); // Check if we have done this less than a day ago, and return if that's the case. auto appNamesToRemove = [&] { std::vector<std::string> toRemove; std::shared_lock sLock(_mutex); for (const auto& [appName, versionTimestamps] : _apiVersionMetrics) { for (const auto& ts : versionTimestamps.timestamps) { if (ts.loadRelaxed() + Days(1) >= now) { continue; } toRemove.push_back(appName); } } return toRemove; }(); if (MONGO_unlikely(!appNamesToRemove.empty())) { stdx::lock_guard xLock(_mutex); // Remove entries that are too stale and marked for removal. } }
- is related to
-
SERVER-87004 Optimize hash-tables with infrequent updates
- Closed