193 lines
6.4 KiB
C++
193 lines
6.4 KiB
C++
/*
|
|
* ServerThroughputTracker.cpp
|
|
*
|
|
* This source file is part of the FoundationDB open source project
|
|
*
|
|
* Copyright 2013-2026 Apple Inc. and the FoundationDB project authors
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include "fdbserver/ServerThroughputTracker.h"
|
|
|
|
namespace {
|
|
|
|
template <class K, class V, class H>
|
|
static Optional<V> tryGet(std::unordered_map<K, V, H> const& m, K const& k) {
|
|
auto it = m.find(k);
|
|
if (it == m.end()) {
|
|
return {};
|
|
} else {
|
|
return it->second;
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
ServerThroughputTracker::ThroughputCounters::ThroughputCounters()
|
|
: readThroughput(SERVER_KNOBS->GLOBAL_TAG_THROTTLING_COST_FOLDING_TIME),
|
|
writeThroughput(SERVER_KNOBS->GLOBAL_TAG_THROTTLING_COST_FOLDING_TIME) {}
|
|
|
|
std::vector<TransactionTag> ServerThroughputTracker::getTagsAffectingStorageServer(UID storageServerId) const {
|
|
std::vector<TransactionTag> result;
|
|
auto const tagToThroughputCounters = tryGet(throughput, storageServerId);
|
|
if (!tagToThroughputCounters.present()) {
|
|
return {};
|
|
} else {
|
|
result.reserve(tagToThroughputCounters.get().size());
|
|
for (const auto& [tag, _] : tagToThroughputCounters.get()) {
|
|
result.push_back(tag);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void ServerThroughputTracker::ThroughputCounters::updateThroughput(double newThroughput, OpType opType) {
|
|
if (opType == OpType::READ) {
|
|
readThroughput.setTotal(newThroughput);
|
|
} else {
|
|
writeThroughput.setTotal(newThroughput);
|
|
}
|
|
}
|
|
|
|
double ServerThroughputTracker::ThroughputCounters::getThroughput() const {
|
|
return readThroughput.smoothTotal() + writeThroughput.smoothTotal();
|
|
}
|
|
|
|
ServerThroughputTracker::~ServerThroughputTracker() = default;
|
|
|
|
// Exponentially decay throughput statistics for tags that were not reported.
|
|
// If reported throughput for a particular tag is too low, stop tracking this
|
|
// tag.
|
|
void ServerThroughputTracker::cleanupUnseenTags(TransactionTagMap<ThroughputCounters>& tagToThroughputCounters,
|
|
std::unordered_set<TransactionTag> const& seenReadTags,
|
|
std::unordered_set<TransactionTag> const& seenWriteTags) {
|
|
auto it = tagToThroughputCounters.begin();
|
|
while (it != tagToThroughputCounters.end()) {
|
|
auto& [tag, throughputCounters] = *it;
|
|
bool seen = false;
|
|
if (seenReadTags.contains(tag)) {
|
|
seen = true;
|
|
} else {
|
|
throughputCounters.updateThroughput(0, OpType::READ);
|
|
}
|
|
if (seenWriteTags.contains(tag)) {
|
|
seen = true;
|
|
} else {
|
|
throughputCounters.updateThroughput(0, OpType::WRITE);
|
|
}
|
|
if (!seen && throughputCounters.getThroughput() < SERVER_KNOBS->GLOBAL_TAG_THROTTLING_FORGET_SS_THRESHOLD) {
|
|
it = tagToThroughputCounters.erase(it);
|
|
} else {
|
|
++it;
|
|
}
|
|
}
|
|
}
|
|
|
|
// For unseen storage servers, exponentially decay throughput statistics for every tag.
|
|
// If the reported throughput for a particular tag is too low, stop tracking this
|
|
// tag. If no more tags are being tracked for a particular storage server,
|
|
// stop tracking this storage server altogether.
|
|
void ServerThroughputTracker::cleanupUnseenStorageServers(std::unordered_set<UID> const& seen) {
|
|
auto it1 = throughput.begin();
|
|
while (it1 != throughput.end()) {
|
|
auto& [ssId, tagToThroughputCounters] = *it1;
|
|
if (seen.contains(ssId)) {
|
|
++it1;
|
|
} else {
|
|
auto it2 = tagToThroughputCounters.begin();
|
|
while (it2 != tagToThroughputCounters.end()) {
|
|
auto& throughputCounters = it2->second;
|
|
throughputCounters.updateThroughput(0, OpType::READ);
|
|
throughputCounters.updateThroughput(0, OpType::WRITE);
|
|
if (throughputCounters.getThroughput() < SERVER_KNOBS->GLOBAL_TAG_THROTTLING_FORGET_SS_THRESHOLD) {
|
|
it2 = tagToThroughputCounters.erase(it2);
|
|
} else {
|
|
++it2;
|
|
}
|
|
}
|
|
if (tagToThroughputCounters.empty()) {
|
|
it1 = throughput.erase(it1);
|
|
} else {
|
|
++it1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ServerThroughputTracker::update(Map<UID, StorageQueueInfo> const& sqInfos) {
|
|
std::unordered_set<UID> seenStorageServerIds;
|
|
for (auto it = sqInfos.begin(); it != sqInfos.end(); ++it) {
|
|
auto const& ss = it->value;
|
|
if (!ss.valid) {
|
|
continue;
|
|
}
|
|
seenStorageServerIds.insert(ss.id);
|
|
std::unordered_set<TransactionTag> seenReadTags, seenWriteTags;
|
|
auto& tagToThroughputCounters = throughput[ss.id];
|
|
for (const auto& busyReader : ss.busiestReadTags) {
|
|
seenReadTags.insert(busyReader.tag);
|
|
tagToThroughputCounters[busyReader.tag].updateThroughput(busyReader.rate, OpType::READ);
|
|
}
|
|
for (const auto& busyWriter : ss.busiestWriteTags) {
|
|
seenWriteTags.insert(busyWriter.tag);
|
|
tagToThroughputCounters[busyWriter.tag].updateThroughput(busyWriter.rate, OpType::WRITE);
|
|
}
|
|
|
|
cleanupUnseenTags(tagToThroughputCounters, seenReadTags, seenWriteTags);
|
|
}
|
|
cleanupUnseenStorageServers(seenStorageServerIds);
|
|
}
|
|
|
|
Optional<double> ServerThroughputTracker::getThroughput(UID storageServerId, TransactionTag const& tag) const {
|
|
auto const tagToThroughputCounters = tryGet(throughput, storageServerId);
|
|
if (!tagToThroughputCounters.present()) {
|
|
return {};
|
|
}
|
|
auto const throughputCounter = tryGet(tagToThroughputCounters.get(), tag);
|
|
if (!throughputCounter.present()) {
|
|
return {};
|
|
}
|
|
return throughputCounter.get().getThroughput();
|
|
}
|
|
|
|
double ServerThroughputTracker::getThroughput(TransactionTag const& tag) const {
|
|
double result{ 0.0 };
|
|
for (auto const& [ssId, _] : throughput) {
|
|
result += getThroughput(ssId, tag).orDefault(0);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
Optional<double> ServerThroughputTracker::getThroughput(UID storageServerId) const {
|
|
auto tagToThroughputCounters = tryGet(throughput, storageServerId);
|
|
if (!tagToThroughputCounters.present()) {
|
|
return {};
|
|
}
|
|
double result = 0;
|
|
for (const auto& [_, throughputCounters] : tagToThroughputCounters.get()) {
|
|
result += throughputCounters.getThroughput();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void ServerThroughputTracker::removeTag(TransactionTag const& tag) {
|
|
for (auto& [ss, tagToCounters] : throughput) {
|
|
tagToCounters.erase(tag);
|
|
}
|
|
}
|
|
|
|
int ServerThroughputTracker::storageServersTracked() const {
|
|
return throughput.size();
|
|
}
|