309 lines
12 KiB
C++
309 lines
12 KiB
C++
/*
|
|
* ClusterHealthIFactor.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 <algorithm>
|
|
#include <limits>
|
|
|
|
#include "fdbserver/core/RecoveryState.h"
|
|
#include "flow/Trace.h"
|
|
|
|
#include "ClusterHealthIFactor.h"
|
|
#include "ClusterHealthMonitor.h"
|
|
|
|
namespace cluster_health {
|
|
|
|
namespace {
|
|
|
|
WorkerEvents filterEmptyEvents(WorkerEvents const& events) {
|
|
WorkerEvents filteredEvents;
|
|
for (auto const& [address, traceEvent] : events) {
|
|
if (traceEvent.size() > 0) {
|
|
filteredEvents.emplace(address, traceEvent);
|
|
}
|
|
}
|
|
return filteredEvents;
|
|
}
|
|
|
|
Future<Level> fetchSpaceLevel(LatestWorkerEvents eventsAndErrors,
|
|
std::string const& availableBytesField,
|
|
std::string const& totalBytesField,
|
|
double interventionThreshold,
|
|
double criticalInterventionThreshold,
|
|
char const* failureTraceEventName) {
|
|
if (!eventsAndErrors.present()) {
|
|
co_return Level::METRICS_MISSING;
|
|
}
|
|
|
|
auto const& [events, _errors] = eventsAndErrors.get();
|
|
WorkerEvents filteredEvents = filterEmptyEvents(events);
|
|
if (filteredEvents.empty()) {
|
|
co_return Level::METRICS_MISSING;
|
|
}
|
|
|
|
double minAvailableSpaceRatio = 1.0;
|
|
|
|
try {
|
|
for (auto const& [address, traceEvent] : filteredEvents) {
|
|
(void)address;
|
|
double available = traceEvent.getDouble(availableBytesField);
|
|
double total = std::max(1.0, traceEvent.getDouble(totalBytesField));
|
|
minAvailableSpaceRatio = std::min(minAvailableSpaceRatio, available / total);
|
|
}
|
|
|
|
if (minAvailableSpaceRatio < criticalInterventionThreshold) {
|
|
co_return Level::CRITICAL_INTERVENTION_REQUIRED;
|
|
}
|
|
if (minAvailableSpaceRatio < interventionThreshold) {
|
|
co_return Level::INTERVENTION_REQUIRED;
|
|
}
|
|
co_return Level::HEALTHY;
|
|
} catch (Error& e) {
|
|
TraceEvent(SevWarnAlways, failureTraceEventName).error(e);
|
|
co_return Level::METRICS_MISSING;
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
StorageSpaceFactor::StorageSpaceFactor(double interventionThreshold, double criticalInterventionThreshold)
|
|
: interventionThreshold(interventionThreshold), criticalInterventionThreshold(criticalInterventionThreshold) {}
|
|
|
|
std::string_view StorageSpaceFactor::getName() const {
|
|
return "StorageSpace";
|
|
}
|
|
|
|
Future<Level> StorageSpaceFactor::fetchLevel(Reference<IWorkerEventProvider const> workerEventProvider,
|
|
TrackCodeProbes trackCodeProbes) {
|
|
Level level = co_await fetchSpaceLevel(co_await workerEventProvider->getLatestStorageServerEvents("StorageMetrics"),
|
|
"KvstoreBytesAvailable",
|
|
"KvstoreBytesTotal",
|
|
interventionThreshold,
|
|
criticalInterventionThreshold,
|
|
"StorageSpaceFactorFetchFailed");
|
|
CODE_PROBE(trackCodeProbes && level == Level::HEALTHY, "ClusterHealth StorageSpaceFactor returns HEALTHY");
|
|
CODE_PROBE(trackCodeProbes && level == Level::INTERVENTION_REQUIRED,
|
|
"ClusterHealth StorageSpaceFactor returns INTERVENTION_REQUIRED");
|
|
CODE_PROBE(trackCodeProbes && level == Level::CRITICAL_INTERVENTION_REQUIRED,
|
|
"ClusterHealth StorageSpaceFactor returns CRITICAL_INTERVENTION_REQUIRED");
|
|
co_return level;
|
|
}
|
|
|
|
TLogSpaceFactor::TLogSpaceFactor(double interventionThreshold, double criticalInterventionThreshold)
|
|
: interventionThreshold(interventionThreshold), criticalInterventionThreshold(criticalInterventionThreshold) {}
|
|
|
|
std::string_view TLogSpaceFactor::getName() const {
|
|
return "TLogSpace";
|
|
}
|
|
|
|
Future<Level> TLogSpaceFactor::fetchLevel(Reference<IWorkerEventProvider const> workerEventProvider,
|
|
TrackCodeProbes trackCodeProbes) {
|
|
Level level = co_await fetchSpaceLevel(co_await workerEventProvider->getLatestTLogEvents("TLogMetrics"),
|
|
"QueueDiskBytesAvailable",
|
|
"QueueDiskBytesTotal",
|
|
interventionThreshold,
|
|
criticalInterventionThreshold,
|
|
"TLogSpaceFactorFetchFailed");
|
|
CODE_PROBE(trackCodeProbes && level == Level::HEALTHY, "ClusterHealth TLogSpaceFactor returns HEALTHY");
|
|
CODE_PROBE(trackCodeProbes && level == Level::INTERVENTION_REQUIRED,
|
|
"ClusterHealth TLogSpaceFactor returns INTERVENTION_REQUIRED");
|
|
CODE_PROBE(trackCodeProbes && level == Level::CRITICAL_INTERVENTION_REQUIRED,
|
|
"ClusterHealth TLogSpaceFactor returns CRITICAL_INTERVENTION_REQUIRED");
|
|
co_return level;
|
|
}
|
|
|
|
std::string_view StorageReplicationFactor::getName() const {
|
|
return "StorageReplication";
|
|
}
|
|
|
|
Future<Level> StorageReplicationFactor::fetchLevel(Reference<IWorkerEventProvider const> workerEventProvider,
|
|
TrackCodeProbes trackCodeProbes) {
|
|
auto eventsAndErrors = co_await workerEventProvider->getLatestDataDistributorEvents("MovingData");
|
|
if (!eventsAndErrors.present()) {
|
|
co_return Level::METRICS_MISSING;
|
|
}
|
|
|
|
auto const& [events, _errors] = eventsAndErrors.get();
|
|
WorkerEvents filteredEvents = filterEmptyEvents(events);
|
|
if (filteredEvents.empty()) {
|
|
co_return Level::METRICS_MISSING;
|
|
}
|
|
|
|
try {
|
|
int64_t queuedOrInFlightRepairMoves = 0;
|
|
int64_t zeroReplicaTeams = 0;
|
|
int64_t oneReplicaTeams = 0;
|
|
for (auto const& [address, traceEvent] : filteredEvents) {
|
|
(void)address;
|
|
int64_t inQueue = traceEvent.getInt64("InQueue");
|
|
int64_t inFlight = traceEvent.getInt64("InFlight");
|
|
int64_t priorityTeamUnhealthy = traceEvent.getInt64("PriorityTeamUnhealthy");
|
|
int64_t priorityTeam2Left = traceEvent.getInt64("PriorityTeam2Left");
|
|
int64_t priorityTeam1Left = traceEvent.getInt64("PriorityTeam1Left");
|
|
int64_t priorityTeam0Left = traceEvent.getInt64("PriorityTeam0Left");
|
|
|
|
zeroReplicaTeams += priorityTeam0Left;
|
|
oneReplicaTeams += priorityTeam1Left;
|
|
if (inQueue > 0 || inFlight > 0) {
|
|
queuedOrInFlightRepairMoves +=
|
|
priorityTeamUnhealthy + priorityTeam2Left + priorityTeam1Left + priorityTeam0Left;
|
|
}
|
|
}
|
|
|
|
if (zeroReplicaTeams > 0) {
|
|
CODE_PROBE(trackCodeProbes, "ClusterHealth StorageReplicationFactor returns OUTAGE");
|
|
co_return Level::OUTAGE;
|
|
}
|
|
if (oneReplicaTeams > 0 && workerEventProvider->shouldTreatStorageTeamOneReplicaLeftAsCritical()) {
|
|
CODE_PROBE(trackCodeProbes,
|
|
"ClusterHealth StorageReplicationFactor returns CRITICAL_INTERVENTION_REQUIRED");
|
|
co_return Level::CRITICAL_INTERVENTION_REQUIRED;
|
|
}
|
|
if (queuedOrInFlightRepairMoves > 0) {
|
|
CODE_PROBE(trackCodeProbes, "ClusterHealth StorageReplicationFactor returns SELF_HEALING");
|
|
co_return Level::SELF_HEALING;
|
|
}
|
|
CODE_PROBE(trackCodeProbes, "ClusterHealth StorageReplicationFactor returns HEALTHY");
|
|
co_return Level::HEALTHY;
|
|
} catch (Error& e) {
|
|
TraceEvent(SevWarnAlways, "StorageReplicationFactorFetchFailed").error(e);
|
|
co_return Level::METRICS_MISSING;
|
|
}
|
|
}
|
|
|
|
std::string_view RecoveryStateFactor::getName() const {
|
|
return "RecoveryState";
|
|
}
|
|
|
|
Future<Level> RecoveryStateFactor::fetchLevel(Reference<IWorkerEventProvider const> workerEventProvider,
|
|
TrackCodeProbes trackCodeProbes) {
|
|
Optional<RecoveryState> recoveryState = workerEventProvider->getRecoveryState();
|
|
if (!recoveryState.present()) {
|
|
co_return Level::METRICS_MISSING;
|
|
}
|
|
|
|
Level level = Level::HEALTHY;
|
|
if (recoveryState.get() < RecoveryState::ACCEPTING_COMMITS) {
|
|
level = Level::OUTAGE;
|
|
} else if (recoveryState.get() < RecoveryState::FULLY_RECOVERED) {
|
|
level = Level::SELF_HEALING;
|
|
}
|
|
|
|
CODE_PROBE(trackCodeProbes && level == Level::OUTAGE, "ClusterHealth RecoveryStateFactor returns OUTAGE");
|
|
CODE_PROBE(trackCodeProbes && level == Level::SELF_HEALING,
|
|
"ClusterHealth RecoveryStateFactor returns SELF_HEALING");
|
|
CODE_PROBE(trackCodeProbes && level == Level::HEALTHY, "ClusterHealth RecoveryStateFactor returns HEALTHY");
|
|
co_return level;
|
|
}
|
|
|
|
std::string_view CoordinatorReachabilityFactor::getName() const {
|
|
return "CoordinatorReachability";
|
|
}
|
|
|
|
Future<Level> CoordinatorReachabilityFactor::fetchLevel(Reference<IWorkerEventProvider const> workerEventProvider,
|
|
TrackCodeProbes trackCodeProbes) {
|
|
Optional<bool> allCoordinatorsReachable = co_await workerEventProvider->areAllCoordinatorsReachable();
|
|
if (!allCoordinatorsReachable.present()) {
|
|
co_return Level::METRICS_MISSING;
|
|
}
|
|
|
|
Level level = allCoordinatorsReachable.get() ? Level::HEALTHY : Level::INTERVENTION_REQUIRED;
|
|
CODE_PROBE(trackCodeProbes && level == Level::HEALTHY,
|
|
"ClusterHealth CoordinatorReachabilityFactor returns HEALTHY");
|
|
CODE_PROBE(trackCodeProbes && level == Level::INTERVENTION_REQUIRED,
|
|
"ClusterHealth CoordinatorReachabilityFactor returns INTERVENTION_REQUIRED");
|
|
co_return level;
|
|
}
|
|
|
|
std::string_view ProcessErrorsFactor::getName() const {
|
|
return "ProcessErrors";
|
|
}
|
|
|
|
Future<Level> ProcessErrorsFactor::fetchLevel(Reference<IWorkerEventProvider const> workerEventProvider,
|
|
TrackCodeProbes trackCodeProbes) {
|
|
auto eventsAndErrors = co_await workerEventProvider->getLatestEvents("");
|
|
if (!eventsAndErrors.present()) {
|
|
co_return Level::METRICS_MISSING;
|
|
}
|
|
|
|
auto const& [events, errors] = eventsAndErrors.get();
|
|
WorkerEvents filteredEvents = filterEmptyEvents(events);
|
|
if (filteredEvents.empty()) {
|
|
bool const hadSuccessfulRequest = events.size() > errors.size();
|
|
CODE_PROBE(trackCodeProbes && hadSuccessfulRequest, "ClusterHealth ProcessErrorsFactor returns HEALTHY");
|
|
co_return hadSuccessfulRequest ? Level::HEALTHY : Level::METRICS_MISSING;
|
|
}
|
|
CODE_PROBE(trackCodeProbes,
|
|
"ClusterHealth ProcessErrorsFactor returns CRITICAL_INTERVENTION_REQUIRED",
|
|
probe::decoration::rare);
|
|
co_return Level::CRITICAL_INTERVENTION_REQUIRED;
|
|
}
|
|
|
|
RkThrottlingFactor::RkThrottlingFactor(double criticalTpsLimitToReleasedTpsRatioThreshold)
|
|
: criticalTpsLimitToReleasedTpsRatioThreshold(criticalTpsLimitToReleasedTpsRatioThreshold) {}
|
|
|
|
std::string_view RkThrottlingFactor::getName() const {
|
|
return "RkThrottling";
|
|
}
|
|
|
|
Future<Level> RkThrottlingFactor::fetchLevel(Reference<IWorkerEventProvider const> workerEventProvider,
|
|
TrackCodeProbes trackCodeProbes) {
|
|
auto eventsAndErrors = co_await workerEventProvider->getLatestRatekeeperEvents("RkUpdate");
|
|
if (!eventsAndErrors.present()) {
|
|
co_return Level::METRICS_MISSING;
|
|
}
|
|
|
|
auto const& [events, _errors] = eventsAndErrors.get();
|
|
WorkerEvents filteredEvents = filterEmptyEvents(events);
|
|
if (filteredEvents.empty()) {
|
|
co_return Level::METRICS_MISSING;
|
|
}
|
|
|
|
try {
|
|
double minTpsLimitToReleasedTpsRatio = std::numeric_limits<double>::infinity();
|
|
for (auto const& [address, traceEvent] : filteredEvents) {
|
|
(void)address;
|
|
double tpsLimit = traceEvent.getDouble("TPSLimit");
|
|
if (tpsLimit == 0.0) {
|
|
CODE_PROBE(trackCodeProbes, "ClusterHealth RkThrottlingFactor returns OUTAGE");
|
|
co_return Level::OUTAGE;
|
|
}
|
|
|
|
double releasedTps = traceEvent.getDouble("ReleasedTPS");
|
|
if (releasedTps == 0.0) {
|
|
continue;
|
|
}
|
|
|
|
minTpsLimitToReleasedTpsRatio = std::min(minTpsLimitToReleasedTpsRatio, tpsLimit / releasedTps);
|
|
}
|
|
|
|
if (minTpsLimitToReleasedTpsRatio < criticalTpsLimitToReleasedTpsRatioThreshold) {
|
|
CODE_PROBE(trackCodeProbes, "ClusterHealth RkThrottlingFactor returns CRITICAL_INTERVENTION_REQUIRED");
|
|
co_return Level::CRITICAL_INTERVENTION_REQUIRED;
|
|
}
|
|
CODE_PROBE(trackCodeProbes, "ClusterHealth RkThrottlingFactor returns HEALTHY");
|
|
co_return Level::HEALTHY;
|
|
} catch (Error& e) {
|
|
TraceEvent(SevWarnAlways, "RkThrottlingFactorFetchFailed").error(e);
|
|
co_return Level::METRICS_MISSING;
|
|
}
|
|
}
|
|
|
|
} // namespace cluster_health
|