1510 lines
66 KiB
C++
1510 lines
66 KiB
C++
/*
|
|
* DatabaseContext.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 "fdbclient/NativeAPI.actor.h"
|
|
|
|
#include <functional>
|
|
#include <iterator>
|
|
#include <limits>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "fdbclient/Knobs.h"
|
|
#include "flow/CodeProbe.h"
|
|
|
|
#include "fdbclient/FDBOptions.g.h"
|
|
#include "fdbclient/FDBTypes.h"
|
|
#include "fdbrpc/MultiInterface.h"
|
|
|
|
#include "fdbclient/AnnotateActor.h"
|
|
#include "fdbclient/ClusterInterface.h"
|
|
#include "fdbclient/CoordinationInterface.h"
|
|
#include "fdbclient/CommitTransaction.h"
|
|
#include "fdbclient/DatabaseContext.h"
|
|
#include "fdbclient/GlobalConfig.h"
|
|
#include "fdbclient/KeyBackedTypes.h"
|
|
#include "fdbclient/KeyRangeMap.h"
|
|
#include "fdbclient/CommitProxyInterface.h"
|
|
#include "ProxyLoadBalance.h"
|
|
#include "fdbclient/ReadYourWrites.h"
|
|
#include "fdbclient/SpecialKeySpace.h"
|
|
#include "fdbclient/StorageServerInterface.h"
|
|
#include "fdbclient/SystemData.h"
|
|
#include "flow/Arena.h"
|
|
#include "flow/ActorCollection.h"
|
|
#include "flow/Error.h"
|
|
#include "flow/FastRef.h"
|
|
#include "flow/IRandom.h"
|
|
#include "flow/Trace.h"
|
|
#include "flow/flow.h"
|
|
#include "flow/genericactors.actor.h"
|
|
#include "flow/Platform.h"
|
|
#include "fdbclient/Tracing.h"
|
|
#include "flow/network.h"
|
|
#include "flow/serialize.h"
|
|
|
|
#ifdef ADDRESS_SANITIZER
|
|
#include <sanitizer/lsan_interface.h>
|
|
#endif
|
|
|
|
#ifdef WIN32
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#include <Windows.h>
|
|
#undef min
|
|
#undef max
|
|
#else
|
|
#endif
|
|
#include "flow/CoroUtils.h"
|
|
|
|
Reference<WatchMetadata> DatabaseContext::getWatchMetadata(KeyRef key) const {
|
|
const auto it = watchMap.find(WatchMapKey(key));
|
|
if (it == watchMap.end())
|
|
return Reference<WatchMetadata>();
|
|
return it->second;
|
|
}
|
|
|
|
void DatabaseContext::setWatchMetadata(Reference<WatchMetadata> metadata) {
|
|
const WatchMapKey key(metadata->parameters->key);
|
|
watchMap[key] = metadata;
|
|
// NOTE Here we do *NOT* update/reset the reference count for the key, see the source code in getWatchFuture.
|
|
// Basically the reference count could be increased, or the same watch is refreshed, or the watch might be cancelled
|
|
}
|
|
|
|
int32_t DatabaseContext::increaseWatchRefCount(KeyRef key, const Version& version) {
|
|
const WatchMapKey mapKey(key);
|
|
watchCounterMap[mapKey].insert(version);
|
|
return watchCounterMap[mapKey].size();
|
|
}
|
|
|
|
int32_t DatabaseContext::decreaseWatchRefCount(KeyRef key, const Version& version) {
|
|
const WatchMapKey mapKey(key);
|
|
auto mapKeyIter = watchCounterMap.find(mapKey);
|
|
if (mapKeyIter == std::end(watchCounterMap)) {
|
|
// Key does not exist. The metadata might be removed by deleteWatchMetadata already.
|
|
return 0;
|
|
}
|
|
|
|
auto& versionSet = mapKeyIter->second;
|
|
auto versionIter = versionSet.find(version);
|
|
|
|
if (versionIter == std::end(versionSet)) {
|
|
// Version not found, the watch might be cleared before.
|
|
return versionSet.size();
|
|
}
|
|
versionSet.erase(versionIter);
|
|
|
|
const auto count = versionSet.size();
|
|
// The metadata might be deleted somewhere else, before calling this decreaseWatchRefCount
|
|
if (auto metadata = getWatchMetadata(key); metadata.isValid() && versionSet.empty()) {
|
|
// It is a *must* to cancel the watchFutureSS manually. watchFutureSS waits for watchStorageServerResp, which
|
|
// holds a reference to the metadata. If the ACTOR is not cancelled, it indirectly holds a Future waiting for
|
|
// itself.
|
|
metadata->watchFutureSS.cancel();
|
|
deleteWatchMetadata(key);
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
void DatabaseContext::deleteWatchMetadata(KeyRef key, bool removeReferenceCount) {
|
|
const WatchMapKey mapKey(key);
|
|
watchMap.erase(mapKey);
|
|
if (removeReferenceCount) {
|
|
watchCounterMap.erase(mapKey);
|
|
}
|
|
}
|
|
|
|
void DatabaseContext::addTssMapping(StorageServerInterface const& ssi, StorageServerInterface const& tssi) {
|
|
auto result = tssMapping.find(ssi.id());
|
|
// Update tss endpoint mapping if ss isn't in mapping, or the interface it mapped to changed
|
|
if (result == tssMapping.end() ||
|
|
result->second.getValue.getEndpoint().token.first() != tssi.getValue.getEndpoint().token.first()) {
|
|
Reference<TSSMetrics> metrics;
|
|
if (result == tssMapping.end()) {
|
|
// new TSS pairing
|
|
metrics = makeReference<TSSMetrics>();
|
|
tssMetrics[tssi.id()] = metrics;
|
|
tssMapping[ssi.id()] = tssi;
|
|
} else {
|
|
ASSERT(result->second.id() == tssi.id());
|
|
metrics = tssMetrics[tssi.id()];
|
|
result->second = tssi;
|
|
}
|
|
|
|
// data requests duplicated for load and data comparison
|
|
queueModel.updateTssEndpoint(ssi.getValue.getEndpoint().token.first(),
|
|
TSSEndpointData(tssi.id(), tssi.getValue.getEndpoint(), metrics));
|
|
queueModel.updateTssEndpoint(ssi.getKey.getEndpoint().token.first(),
|
|
TSSEndpointData(tssi.id(), tssi.getKey.getEndpoint(), metrics));
|
|
queueModel.updateTssEndpoint(ssi.getKeyValues.getEndpoint().token.first(),
|
|
TSSEndpointData(tssi.id(), tssi.getKeyValues.getEndpoint(), metrics));
|
|
queueModel.updateTssEndpoint(ssi.getMappedKeyValues.getEndpoint().token.first(),
|
|
TSSEndpointData(tssi.id(), tssi.getMappedKeyValues.getEndpoint(), metrics));
|
|
queueModel.updateTssEndpoint(ssi.getKeyValuesStream.getEndpoint().token.first(),
|
|
TSSEndpointData(tssi.id(), tssi.getKeyValuesStream.getEndpoint(), metrics));
|
|
|
|
// non-data requests duplicated for load
|
|
queueModel.updateTssEndpoint(ssi.watchValue.getEndpoint().token.first(),
|
|
TSSEndpointData(tssi.id(), tssi.watchValue.getEndpoint(), metrics));
|
|
queueModel.updateTssEndpoint(ssi.splitMetrics.getEndpoint().token.first(),
|
|
TSSEndpointData(tssi.id(), tssi.splitMetrics.getEndpoint(), metrics));
|
|
queueModel.updateTssEndpoint(ssi.getReadHotRanges.getEndpoint().token.first(),
|
|
TSSEndpointData(tssi.id(), tssi.getReadHotRanges.getEndpoint(), metrics));
|
|
queueModel.updateTssEndpoint(ssi.getRangeSplitPoints.getEndpoint().token.first(),
|
|
TSSEndpointData(tssi.id(), tssi.getRangeSplitPoints.getEndpoint(), metrics));
|
|
}
|
|
}
|
|
|
|
void DatabaseContext::removeTssMapping(StorageServerInterface const& ssi) {
|
|
auto result = tssMapping.find(ssi.id());
|
|
if (result != tssMapping.end()) {
|
|
tssMetrics.erase(ssi.id());
|
|
tssMapping.erase(result);
|
|
queueModel.removeTssEndpoint(ssi.getValue.getEndpoint().token.first());
|
|
queueModel.removeTssEndpoint(ssi.getKey.getEndpoint().token.first());
|
|
queueModel.removeTssEndpoint(ssi.getKeyValues.getEndpoint().token.first());
|
|
queueModel.removeTssEndpoint(ssi.getMappedKeyValues.getEndpoint().token.first());
|
|
queueModel.removeTssEndpoint(ssi.getKeyValuesStream.getEndpoint().token.first());
|
|
|
|
queueModel.removeTssEndpoint(ssi.watchValue.getEndpoint().token.first());
|
|
queueModel.removeTssEndpoint(ssi.splitMetrics.getEndpoint().token.first());
|
|
queueModel.removeTssEndpoint(ssi.getReadHotRanges.getEndpoint().token.first());
|
|
queueModel.removeTssEndpoint(ssi.getRangeSplitPoints.getEndpoint().token.first());
|
|
}
|
|
}
|
|
|
|
void DatabaseContext::addSSIdTagMapping(const UID& uid, const Tag& tag) {
|
|
ssidTagMapping[uid] = tag;
|
|
}
|
|
|
|
void DatabaseContext::getLatestCommitVersionForSSID(const UID& ssid, Tag& tag, Version& commitVersion) {
|
|
tag = invalidTag;
|
|
commitVersion = invalidVersion;
|
|
|
|
auto iter = ssidTagMapping.find(ssid);
|
|
if (iter != ssidTagMapping.end()) {
|
|
tag = iter->second;
|
|
|
|
if (ssVersionVectorCache.hasVersion(tag)) {
|
|
commitVersion = ssVersionVectorCache.getVersion(tag);
|
|
}
|
|
}
|
|
}
|
|
|
|
void DatabaseContext::getLatestCommitVersion(const StorageServerInterface& ssi,
|
|
Version readVersion,
|
|
VersionVector& latestCommitVersion) {
|
|
latestCommitVersion.clear();
|
|
|
|
if (ssVersionVectorCache.getMaxVersion() == invalidVersion) {
|
|
return;
|
|
}
|
|
|
|
// Error checking (based on the assumption that the read version was not obtained
|
|
// from the client's grv cache).
|
|
if (readVersion > ssVersionVectorCache.getMaxVersion()) {
|
|
TraceEvent(SevError, "ReadVersionExceedsVersionVectorMax")
|
|
.detail("ReadVersion", readVersion)
|
|
.setMaxFieldLength(495)
|
|
.detail("VersionVector", ssVersionVectorCache.toString());
|
|
if (g_network->isSimulated()) {
|
|
ASSERT(false);
|
|
} else {
|
|
return; // Do not return a stale commit version in production.
|
|
}
|
|
}
|
|
|
|
Tag tag = invalidTag;
|
|
Version commitVersion = invalidVersion;
|
|
getLatestCommitVersionForSSID(ssi.id(), tag, commitVersion);
|
|
|
|
if (tag != invalidTag && commitVersion != invalidVersion && commitVersion < readVersion) {
|
|
latestCommitVersion.setVersion(tag, commitVersion);
|
|
}
|
|
}
|
|
|
|
void DatabaseContext::getLatestCommitVersions(const Reference<LocationInfo>& locationInfo,
|
|
Reference<TransactionState> info,
|
|
VersionVector& latestCommitVersions) {
|
|
latestCommitVersions.clear();
|
|
|
|
if (info->readOptions.present() && info->readOptions.get().debugID.present()) {
|
|
g_traceBatch.addEvent(
|
|
"TransactionDebug", info->readOptions.get().debugID.get().first(), "NativeAPI.getLatestCommitVersions");
|
|
}
|
|
|
|
if (!info->readVersionObtainedFromGrvProxy) {
|
|
return;
|
|
}
|
|
|
|
if (ssVersionVectorCache.getMaxVersion() == invalidVersion) {
|
|
return;
|
|
}
|
|
|
|
if (info->readVersion() > ssVersionVectorCache.getMaxVersion()) {
|
|
if (!CLIENT_KNOBS->FORCE_GRV_CACHE_OFF && !info->options.skipGrvCache && info->options.useGrvCache) {
|
|
return;
|
|
} else {
|
|
TraceEvent(SevError, "GetLatestCommitVersions")
|
|
.detail("ReadVersion", info->readVersion())
|
|
.setMaxFieldLength(495)
|
|
.detail("VersionVector", ssVersionVectorCache.toString());
|
|
ASSERT(false);
|
|
}
|
|
}
|
|
|
|
std::map<Version, std::set<Tag>> versionMap; // order the versions to be returned
|
|
for (int i = 0; i < locationInfo->locations()->size(); i++) {
|
|
Tag tag = invalidTag;
|
|
Version commitVersion = invalidVersion; // latest commit version
|
|
getLatestCommitVersionForSSID(locationInfo->locations()->getId(i), tag, commitVersion);
|
|
|
|
bool updatedVersionMap = false;
|
|
if (tag != invalidTag && commitVersion != invalidVersion && commitVersion < info->readVersion()) {
|
|
updatedVersionMap = true;
|
|
versionMap[commitVersion].insert(tag);
|
|
}
|
|
|
|
// Do not log if commitVersion >= readVersion.
|
|
if (!updatedVersionMap && commitVersion == invalidVersion) {
|
|
TraceEvent(SevDebug, "CommitVersionNotFoundForSS")
|
|
.detail("InSSIDMap", tag != invalidTag ? 1 : 0)
|
|
.detail("Tag", tag)
|
|
.detail("CommitVersion", commitVersion)
|
|
.detail("ReadVersion", info->readVersion())
|
|
.detail("VersionVector", ssVersionVectorCache.toString())
|
|
.setMaxEventLength(11000)
|
|
.setMaxFieldLength(10000);
|
|
++transactionCommitVersionNotFoundForSS;
|
|
}
|
|
}
|
|
|
|
// insert the commit versions in the version vector.
|
|
for (auto& iter : versionMap) {
|
|
latestCommitVersions.setVersion(iter.second, iter.first);
|
|
}
|
|
}
|
|
|
|
void updateCachedReadVersionShared(double t, Version v, DatabaseSharedState* p) {
|
|
MutexHolder mutex(p->mutexLock);
|
|
if (v >= p->grvCacheSpace.cachedReadVersion) {
|
|
//TraceEvent(SevDebug, "CacheReadVersionUpdate")
|
|
// .detail("Version", v)
|
|
// .detail("CurTime", t)
|
|
// .detail("LastVersion", p->grvCacheSpace.cachedReadVersion)
|
|
// .detail("LastTime", p->grvCacheSpace.lastGrvTime);
|
|
p->grvCacheSpace.cachedReadVersion = v;
|
|
if (t > p->grvCacheSpace.lastGrvTime) {
|
|
p->grvCacheSpace.lastGrvTime = t;
|
|
}
|
|
}
|
|
}
|
|
|
|
void DatabaseContext::updateCachedReadVersion(double t, Version v) {
|
|
if (sharedStatePtr) {
|
|
return updateCachedReadVersionShared(t, v, sharedStatePtr);
|
|
}
|
|
if (v >= cachedReadVersion) {
|
|
//TraceEvent(SevDebug, "CachedReadVersionUpdate")
|
|
// .detail("Version", v)
|
|
// .detail("GrvStartTime", t)
|
|
// .detail("LastVersion", cachedReadVersion)
|
|
// .detail("LastTime", lastGrvTime);
|
|
cachedReadVersion = v;
|
|
// Since the time is based on the start of the request, it's possible that we
|
|
// get a newer version with an older time.
|
|
// (Request started earlier, but was latest to reach the proxy)
|
|
// Only update time when strictly increasing (?)
|
|
if (t > lastGrvTime) {
|
|
lastGrvTime = t;
|
|
}
|
|
}
|
|
}
|
|
|
|
Version DatabaseContext::getCachedReadVersion() {
|
|
if (sharedStatePtr) {
|
|
MutexHolder mutex(sharedStatePtr->mutexLock);
|
|
return sharedStatePtr->grvCacheSpace.cachedReadVersion;
|
|
}
|
|
return cachedReadVersion;
|
|
}
|
|
|
|
double DatabaseContext::getLastGrvTime() {
|
|
if (sharedStatePtr) {
|
|
MutexHolder mutex(sharedStatePtr->mutexLock);
|
|
return sharedStatePtr->grvCacheSpace.lastGrvTime;
|
|
}
|
|
return lastGrvTime;
|
|
}
|
|
|
|
Reference<StorageServerInfo> StorageServerInfo::getInterface(DatabaseContext* cx,
|
|
StorageServerInterface const& ssi,
|
|
LocalityData const& locality) {
|
|
auto it = cx->server_interf.find(ssi.id());
|
|
if (it != cx->server_interf.end()) {
|
|
if (it->second->interf.getValue.getEndpoint().token != ssi.getValue.getEndpoint().token) {
|
|
if (it->second->interf.locality == ssi.locality) {
|
|
// FIXME: load balance holds pointers to individual members of the interface, and this assignment will
|
|
// swap out the object they are
|
|
// pointing to. This is technically correct, but is very unnatural. We may want to refactor load
|
|
// balance to take an AsyncVar<Reference<Interface>> so that it is notified when the interface
|
|
// changes.
|
|
|
|
it->second->interf = ssi;
|
|
} else {
|
|
it->second->notifyContextDestroyed();
|
|
Reference<StorageServerInfo> loc(new StorageServerInfo(cx, ssi, locality));
|
|
cx->server_interf[ssi.id()] = loc.getPtr();
|
|
return loc;
|
|
}
|
|
}
|
|
|
|
return Reference<StorageServerInfo>::addRef(it->second);
|
|
}
|
|
|
|
Reference<StorageServerInfo> loc(new StorageServerInfo(cx, ssi, locality));
|
|
cx->server_interf[ssi.id()] = loc.getPtr();
|
|
return loc;
|
|
}
|
|
|
|
void StorageServerInfo::notifyContextDestroyed() {
|
|
cx = nullptr;
|
|
}
|
|
|
|
StorageServerInfo::~StorageServerInfo() {
|
|
if (cx) {
|
|
auto it = cx->server_interf.find(interf.id());
|
|
if (it != cx->server_interf.end())
|
|
cx->server_interf.erase(it);
|
|
cx = nullptr;
|
|
}
|
|
}
|
|
|
|
void DatabaseContext::validateVersion(Version version) const {
|
|
// Version could be 0 if the INITIALIZE_NEW_DATABASE option is set. In that case, it is illegal to perform any
|
|
// reads. We throw client_invalid_operation because the caller didn't directly set the version, so the
|
|
// version_invalid error might be confusing.
|
|
if (version == 0) {
|
|
throw client_invalid_operation();
|
|
}
|
|
if (switchable && version < minAcceptableReadVersion) {
|
|
CODE_PROBE(true, "Attempted to read a version lower than any this client has seen from the current cluster");
|
|
throw transaction_too_old();
|
|
}
|
|
|
|
ASSERT(version > 0 || version == latestVersion);
|
|
}
|
|
|
|
inline HealthMetrics populateHealthMetrics(const HealthMetrics& detailedMetrics, bool detailedOutput) {
|
|
if (detailedOutput) {
|
|
return detailedMetrics;
|
|
} else {
|
|
HealthMetrics result;
|
|
result.update(detailedMetrics, false, false);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
static Future<HealthMetrics> getHealthMetricsActor(DatabaseContext* cx, bool detailed, bool sendDetailedRequest) {
|
|
GetHealthMetricsReply rep =
|
|
co_await grvProxyLoadBalance(Database(Reference<DatabaseContext>::addRef(cx)),
|
|
makeReqBuilder<GetHealthMetricsRequest>(sendDetailedRequest),
|
|
&GrvProxyInterface::getHealthMetrics);
|
|
cx->healthMetrics.update(rep.healthMetrics, sendDetailedRequest, true);
|
|
cx->healthMetricsLastUpdated = now();
|
|
if (sendDetailedRequest) {
|
|
cx->detailedHealthMetricsLastUpdated = now();
|
|
}
|
|
co_return populateHealthMetrics(cx->healthMetrics, detailed);
|
|
}
|
|
|
|
Future<HealthMetrics> DatabaseContext::getHealthMetrics(bool detailed = false) {
|
|
if (now() - healthMetricsLastUpdated < CLIENT_KNOBS->AGGREGATE_HEALTH_METRICS_MAX_STALENESS) {
|
|
return populateHealthMetrics(healthMetrics, detailed);
|
|
}
|
|
bool sendDetailedRequest =
|
|
detailed && now() - detailedHealthMetricsLastUpdated > CLIENT_KNOBS->DETAILED_HEALTH_METRICS_MAX_STALENESS;
|
|
return getHealthMetricsActor(this, detailed, sendDetailedRequest);
|
|
}
|
|
|
|
Future<Optional<HealthMetrics::StorageStats>> DatabaseContext::getStorageStats(UID id, double maxStaleness) {
|
|
if (now() - detailedHealthMetricsLastUpdated < maxStaleness) {
|
|
auto it = healthMetrics.storageStats.find(id);
|
|
return it == healthMetrics.storageStats.end() ? Optional<HealthMetrics::StorageStats>() : it->second;
|
|
}
|
|
|
|
return map(getHealthMetricsActor(this, true, true), [id](auto metrics) -> Optional<HealthMetrics::StorageStats> {
|
|
auto it = metrics.storageStats.find(id);
|
|
return it == metrics.storageStats.end() ? Optional<HealthMetrics::StorageStats>() : it->second;
|
|
});
|
|
}
|
|
|
|
// register a special key(s) implementation under the specified module
|
|
void DatabaseContext::registerSpecialKeysImpl(SpecialKeySpace::MODULE module,
|
|
SpecialKeySpace::IMPLTYPE type,
|
|
std::unique_ptr<SpecialKeyRangeReadImpl>&& impl,
|
|
int deprecatedVersion) {
|
|
// if deprecated, add the implementation when the api version is less than the deprecated version
|
|
if (deprecatedVersion == -1 || apiVersion.version() < deprecatedVersion) {
|
|
specialKeySpace->registerKeyRange(module, type, impl->getKeyRange(), impl.get());
|
|
specialKeySpaceModules.push_back(std::move(impl));
|
|
}
|
|
}
|
|
|
|
void traceTSSErrors(const char* name, UID tssId, const std::unordered_map<int, uint64_t>& errorsByCode) {
|
|
TraceEvent ev(name, tssId);
|
|
for (auto& it : errorsByCode) {
|
|
ev.detail("E" + std::to_string(it.first), it.second);
|
|
}
|
|
}
|
|
|
|
/*
|
|
For each request type, this will produce
|
|
<Type>Count
|
|
<Type>{SS,TSS}{Mean,P50,P90,P99}
|
|
Example:
|
|
GetValueLatencySSMean
|
|
*/
|
|
void traceSSOrTSSPercentiles(TraceEvent& ev, const std::string name, DDSketch<double>& sample) {
|
|
ev.detail(name + "Mean", sample.mean());
|
|
// don't log the larger percentiles unless we actually have enough samples to log the accurate percentile instead of
|
|
// the largest sample in this window
|
|
if (sample.getPopulationSize() >= 3) {
|
|
ev.detail(name + "P50", sample.median());
|
|
}
|
|
if (sample.getPopulationSize() >= 10) {
|
|
ev.detail(name + "P90", sample.percentile(0.90));
|
|
}
|
|
if (sample.getPopulationSize() >= 100) {
|
|
ev.detail(name + "P99", sample.percentile(0.99));
|
|
}
|
|
}
|
|
|
|
void traceTSSPercentiles(TraceEvent& ev,
|
|
const std::string name,
|
|
DDSketch<double>& ssSample,
|
|
DDSketch<double>& tssSample) {
|
|
ASSERT(ssSample.getPopulationSize() == tssSample.getPopulationSize());
|
|
ev.detail(name + "Count", ssSample.getPopulationSize());
|
|
if (ssSample.getPopulationSize() > 0) {
|
|
traceSSOrTSSPercentiles(ev, name + "SS", ssSample);
|
|
traceSSOrTSSPercentiles(ev, name + "TSS", tssSample);
|
|
}
|
|
}
|
|
|
|
Future<Void> tssLogger(DatabaseContext* cx) {
|
|
double lastLogged = 0;
|
|
while (true) {
|
|
co_await delay(CLIENT_KNOBS->TSS_METRICS_LOGGING_INTERVAL, TaskPriority::FlushTrace);
|
|
|
|
// Log each TSS pair separately
|
|
for (const auto& it : cx->tssMetrics) {
|
|
if (!it.second->detailedMismatches.empty()) {
|
|
cx->tssMismatchStream.send(
|
|
std::pair<UID, std::vector<DetailedTSSMismatch>>(it.first, it.second->detailedMismatches));
|
|
}
|
|
|
|
// Do error histograms as separate event
|
|
if (!it.second->ssErrorsByCode.empty()) {
|
|
traceTSSErrors("TSS_SSErrors", it.first, it.second->ssErrorsByCode);
|
|
}
|
|
|
|
if (!it.second->tssErrorsByCode.empty()) {
|
|
traceTSSErrors("TSS_TSSErrors", it.first, it.second->tssErrorsByCode);
|
|
}
|
|
|
|
TraceEvent tssEv("TSSClientMetrics", cx->dbId);
|
|
tssEv.detail("TSSID", it.first)
|
|
.detail("Elapsed", (lastLogged == 0) ? 0 : now() - lastLogged)
|
|
.detail("Internal", cx->internal);
|
|
|
|
it.second->cc.logToTraceEvent(tssEv);
|
|
|
|
traceTSSPercentiles(tssEv, "GetValueLatency", it.second->SSgetValueLatency, it.second->TSSgetValueLatency);
|
|
traceTSSPercentiles(
|
|
tssEv, "GetKeyValuesLatency", it.second->SSgetKeyValuesLatency, it.second->TSSgetKeyValuesLatency);
|
|
traceTSSPercentiles(tssEv, "GetKeyLatency", it.second->SSgetKeyLatency, it.second->TSSgetKeyLatency);
|
|
traceTSSPercentiles(tssEv,
|
|
"GetMappedKeyValuesLatency",
|
|
it.second->SSgetMappedKeyValuesLatency,
|
|
it.second->TSSgetMappedKeyValuesLatency);
|
|
|
|
it.second->clear();
|
|
}
|
|
|
|
lastLogged = now();
|
|
}
|
|
}
|
|
|
|
Future<Void> databaseLogger(DatabaseContext* cx) {
|
|
double lastLogged = 0;
|
|
while (true) {
|
|
co_await delay(CLIENT_KNOBS->SYSTEM_MONITOR_INTERVAL, TaskPriority::FlushTrace);
|
|
|
|
bool logMetrics = !g_network->isSimulated() || buggify(0.01);
|
|
if (logMetrics) {
|
|
TraceEvent ev("TransactionMetrics", cx->dbId);
|
|
|
|
ev.detail("Elapsed", (lastLogged == 0) ? 0 : now() - lastLogged)
|
|
.detail("Cluster",
|
|
cx->getConnectionRecord()
|
|
? cx->getConnectionRecord()->getConnectionString().clusterKeyName().toString()
|
|
: "")
|
|
.detail("Internal", cx->internal);
|
|
|
|
cx->cc.logToTraceEvent(ev);
|
|
|
|
ev.detail("LocationCacheEntryCount", cx->locationCache.size());
|
|
ev.detail("MeanLatency", cx->latencies.mean())
|
|
.detail("MedianLatency", cx->latencies.median())
|
|
.detail("Latency90", cx->latencies.percentile(0.90))
|
|
.detail("Latency98", cx->latencies.percentile(0.98))
|
|
.detail("MaxLatency", cx->latencies.max())
|
|
.detail("MeanRowReadLatency", cx->readLatencies.mean())
|
|
.detail("MedianRowReadLatency", cx->readLatencies.median())
|
|
.detail("MaxRowReadLatency", cx->readLatencies.max())
|
|
.detail("MeanGRVLatency", cx->GRVLatencies.mean())
|
|
.detail("MedianGRVLatency", cx->GRVLatencies.median())
|
|
.detail("MaxGRVLatency", cx->GRVLatencies.max())
|
|
.detail("MeanCommitLatency", cx->commitLatencies.mean())
|
|
.detail("MedianCommitLatency", cx->commitLatencies.median())
|
|
.detail("MaxCommitLatency", cx->commitLatencies.max())
|
|
.detail("MeanMutationsPerCommit", cx->mutationsPerCommit.mean())
|
|
.detail("MedianMutationsPerCommit", cx->mutationsPerCommit.median())
|
|
.detail("MaxMutationsPerCommit", cx->mutationsPerCommit.max())
|
|
.detail("MeanBytesPerCommit", cx->bytesPerCommit.mean())
|
|
.detail("MedianBytesPerCommit", cx->bytesPerCommit.median())
|
|
.detail("MaxBytesPerCommit", cx->bytesPerCommit.max())
|
|
.detail("NumLocalityCacheEntries", cx->locationCache.size());
|
|
}
|
|
|
|
cx->latencies.clear();
|
|
cx->readLatencies.clear();
|
|
cx->GRVLatencies.clear();
|
|
cx->commitLatencies.clear();
|
|
cx->mutationsPerCommit.clear();
|
|
cx->bytesPerCommit.clear();
|
|
|
|
lastLogged = now();
|
|
}
|
|
}
|
|
|
|
struct TrInfoChunk {
|
|
ValueRef value;
|
|
Key key;
|
|
};
|
|
|
|
static const Key CLIENT_LATENCY_INFO_PREFIX = "client_latency/"_sr;
|
|
static const Key CLIENT_LATENCY_INFO_CTR_PREFIX = "client_latency_counter/"_sr;
|
|
|
|
static void resetClientStatusTransaction(Transaction* tr) {
|
|
tr->reset();
|
|
// Profiling maintenance must not generate more records for itself to persist.
|
|
tr->trState->trLogInfo.clear();
|
|
}
|
|
|
|
static Future<Void> transactionInfoCommitActor(Transaction* tr, std::vector<TrInfoChunk>* chunks) {
|
|
const Key clientLatencyAtomicCtr = CLIENT_LATENCY_INFO_CTR_PREFIX.withPrefix(fdbClientInfoPrefixRange.begin);
|
|
int retryCount = 0;
|
|
while (true) {
|
|
Error err;
|
|
try {
|
|
resetClientStatusTransaction(tr);
|
|
tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS);
|
|
tr->setOption(FDBTransactionOptions::LOCK_AWARE);
|
|
[[maybe_unused]] Future<Standalone<StringRef>> vstamp = tr->getVersionstamp();
|
|
int64_t numCommitBytes = 0;
|
|
for (auto& chunk : *chunks) {
|
|
tr->atomicOp(chunk.key, chunk.value, MutationRef::SetVersionstampedKey);
|
|
numCommitBytes += chunk.key.size() + chunk.value.size() -
|
|
4; // subtract number of bytes of key that denotes version stamp index
|
|
}
|
|
tr->atomicOp(clientLatencyAtomicCtr, StringRef((uint8_t*)&numCommitBytes, 8), MutationRef::AddValue);
|
|
co_await tr->commit();
|
|
co_return;
|
|
} catch (Error& e) {
|
|
err = e;
|
|
}
|
|
retryCount++;
|
|
if (retryCount == 10)
|
|
throw err;
|
|
co_await tr->onError(err);
|
|
}
|
|
}
|
|
|
|
static Future<Void> delExcessClntTxnEntriesActor(Transaction* tr, int64_t clientTxInfoSizeLimit) {
|
|
const Key clientLatencyName = CLIENT_LATENCY_INFO_PREFIX.withPrefix(fdbClientInfoPrefixRange.begin);
|
|
const Key clientLatencyAtomicCtr = CLIENT_LATENCY_INFO_CTR_PREFIX.withPrefix(fdbClientInfoPrefixRange.begin);
|
|
TraceEvent(SevInfo, "DelExcessClntTxnEntriesCalled").log();
|
|
|
|
// If we don't limit it with retries, the DatabaseContext will never cleanup as Transaction
|
|
// object will be alive and hold reference to DatabaseContext.
|
|
int retries = 0;
|
|
while (true) {
|
|
Error err;
|
|
try {
|
|
resetClientStatusTransaction(tr);
|
|
tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS);
|
|
tr->setOption(FDBTransactionOptions::LOCK_AWARE);
|
|
Optional<Value> ctrValue = co_await tr->get(KeyRef(clientLatencyAtomicCtr), Snapshot::True);
|
|
if (!ctrValue.present()) {
|
|
TraceEvent(SevInfo, "NumClntTxnEntriesNotFound").log();
|
|
co_return;
|
|
}
|
|
int64_t txInfoSize = 0;
|
|
ASSERT(ctrValue.get().size() == sizeof(int64_t));
|
|
memcpy(&txInfoSize, ctrValue.get().begin(), ctrValue.get().size());
|
|
if (txInfoSize < clientTxInfoSizeLimit)
|
|
co_return;
|
|
int getRangeByteLimit = (txInfoSize - clientTxInfoSizeLimit) < CLIENT_KNOBS->TRANSACTION_SIZE_LIMIT
|
|
? (txInfoSize - clientTxInfoSizeLimit)
|
|
: CLIENT_KNOBS->TRANSACTION_SIZE_LIMIT;
|
|
GetRangeLimits limit(GetRangeLimits::ROW_LIMIT_UNLIMITED, getRangeByteLimit);
|
|
RangeResult txEntries =
|
|
co_await tr->getRange(KeyRangeRef(clientLatencyName, strinc(clientLatencyName)), limit);
|
|
int64_t numBytesToDel = 0;
|
|
KeyRef endKey;
|
|
for (auto& kv : txEntries) {
|
|
endKey = kv.key;
|
|
numBytesToDel += kv.key.size() + kv.value.size();
|
|
if (txInfoSize - numBytesToDel <= clientTxInfoSizeLimit)
|
|
break;
|
|
}
|
|
if (numBytesToDel) {
|
|
tr->clear(KeyRangeRef(txEntries[0].key, strinc(endKey)));
|
|
TraceEvent(SevInfo, "DeletingExcessCntTxnEntries").detail("BytesToBeDeleted", numBytesToDel);
|
|
int64_t bytesDel = -numBytesToDel;
|
|
|
|
tr->atomicOp(clientLatencyAtomicCtr, StringRef((uint8_t*)&bytesDel, 8), MutationRef::AddValue);
|
|
co_await tr->commit();
|
|
}
|
|
if (txInfoSize - numBytesToDel <= clientTxInfoSizeLimit)
|
|
co_return;
|
|
continue;
|
|
} catch (Error& e) {
|
|
err = e;
|
|
}
|
|
if (err.code() == error_code_actor_cancelled || retries++ >= 10) {
|
|
throw err;
|
|
}
|
|
|
|
co_await tr->onError(err);
|
|
}
|
|
}
|
|
|
|
// Client status is the client-side transaction profiling data persisted under
|
|
// fdbClientInfoPrefixRange for status reporting.
|
|
// The reason for getting a pointer to DatabaseContext instead of a reference counted object is because reference
|
|
// counting will increment reference count for DatabaseContext which holds the future of this actor. This creates a
|
|
// cyclic reference and hence this actor and Database object will not be destroyed at all.
|
|
static Future<Void> clientStatusUpdateActor(DatabaseContext* cx) {
|
|
const std::string clientLatencyName =
|
|
CLIENT_LATENCY_INFO_PREFIX.withPrefix(fdbClientInfoPrefixRange.begin).toString();
|
|
Transaction tr;
|
|
std::vector<TrInfoChunk> commitQ;
|
|
int txBytes = 0;
|
|
|
|
while (true) {
|
|
// Make sure we are connected to the server. Otherwise we may just try to keep reconnecting
|
|
// with incompatible clusters.
|
|
co_await cx->onConnected();
|
|
|
|
// Need to make sure that we eventually destroy tr. We can't rely on getting cancelled to do
|
|
// this because of the cyclic reference to self.
|
|
co_await refreshTransaction(cx, &tr);
|
|
Error err;
|
|
try {
|
|
ASSERT(cx->clientStatusUpdater.outStatusQ.empty());
|
|
cx->clientStatusUpdater.inStatusQ.swap(cx->clientStatusUpdater.outStatusQ);
|
|
// Split Transaction Info into chunks
|
|
std::vector<TrInfoChunk> trChunksQ;
|
|
for (auto& entry : cx->clientStatusUpdater.outStatusQ) {
|
|
auto& bw = entry.second;
|
|
int64_t value_size_limit = buggify()
|
|
? deterministicRandom()->randomInt(1e3, CLIENT_KNOBS->VALUE_SIZE_LIMIT)
|
|
: CLIENT_KNOBS->VALUE_SIZE_LIMIT;
|
|
int num_chunks = (bw.getLength() + value_size_limit - 1) / value_size_limit;
|
|
std::string random_id = deterministicRandom()->randomAlphaNumeric(16);
|
|
std::string user_provided_id = !entry.first.empty() ? entry.first + "/" : "";
|
|
for (int i = 0; i < num_chunks; i++) {
|
|
TrInfoChunk chunk;
|
|
BinaryWriter chunkBW(Unversioned());
|
|
chunkBW << bigEndian32(i + 1) << bigEndian32(num_chunks);
|
|
chunk.key = KeyRef(clientLatencyName + std::string(10, '\x00') + "/" + random_id + "/" +
|
|
chunkBW.toValue().toString() + "/" + user_provided_id + std::string(4, '\x00'));
|
|
int32_t pos = littleEndian32(clientLatencyName.size());
|
|
memcpy(mutateString(chunk.key) + chunk.key.size() - sizeof(int32_t), &pos, sizeof(int32_t));
|
|
if (i == num_chunks - 1) {
|
|
chunk.value = ValueRef(static_cast<uint8_t*>(bw.getData()) + (i * value_size_limit),
|
|
bw.getLength() - (i * value_size_limit));
|
|
} else {
|
|
chunk.value =
|
|
ValueRef(static_cast<uint8_t*>(bw.getData()) + (i * value_size_limit), value_size_limit);
|
|
}
|
|
trChunksQ.push_back(std::move(chunk));
|
|
}
|
|
}
|
|
|
|
// Commit the chunks splitting into different transactions if needed
|
|
int64_t dataSizeLimit =
|
|
buggify() ? deterministicRandom()->randomInt(200e3, 1.5 * CLIENT_KNOBS->TRANSACTION_SIZE_LIMIT)
|
|
: 0.8 * CLIENT_KNOBS->TRANSACTION_SIZE_LIMIT;
|
|
auto tracking_iter = trChunksQ.begin();
|
|
ASSERT(commitQ.empty() && (txBytes == 0));
|
|
while (true) {
|
|
auto iter = tracking_iter;
|
|
txBytes = 0;
|
|
commitQ.clear();
|
|
try {
|
|
while (iter != trChunksQ.end()) {
|
|
if (iter->value.size() + iter->key.size() + txBytes > dataSizeLimit) {
|
|
co_await transactionInfoCommitActor(&tr, &commitQ);
|
|
tracking_iter = iter;
|
|
commitQ.clear();
|
|
txBytes = 0;
|
|
}
|
|
commitQ.push_back(*iter);
|
|
txBytes += iter->value.size() + iter->key.size();
|
|
++iter;
|
|
}
|
|
if (!commitQ.empty()) {
|
|
co_await transactionInfoCommitActor(&tr, &commitQ);
|
|
commitQ.clear();
|
|
txBytes = 0;
|
|
}
|
|
break;
|
|
} catch (Error& e) {
|
|
if (e.code() == error_code_transaction_too_large) {
|
|
dataSizeLimit /= 2;
|
|
ASSERT(dataSizeLimit >= CLIENT_KNOBS->VALUE_SIZE_LIMIT + CLIENT_KNOBS->KEY_SIZE_LIMIT);
|
|
} else {
|
|
TraceEvent(SevWarnAlways, "ClientTrInfoErrorCommit").error(e).detail("TxBytes", txBytes);
|
|
commitQ.clear();
|
|
txBytes = 0;
|
|
throw;
|
|
}
|
|
}
|
|
}
|
|
cx->clientStatusUpdater.outStatusQ.clear();
|
|
co_await cx->globalConfig->onInitialized();
|
|
double sampleRate =
|
|
cx->globalConfig->get<double>(fdbClientInfoTxnSampleRate, std::numeric_limits<double>::infinity());
|
|
double clientSamplingProbability =
|
|
std::isinf(sampleRate) ? CLIENT_KNOBS->CSI_SAMPLING_PROBABILITY : sampleRate;
|
|
int64_t sizeLimit = cx->globalConfig->get<int64_t>(fdbClientInfoTxnSizeLimit, -1);
|
|
int64_t clientTxnInfoSizeLimit = sizeLimit == -1 ? CLIENT_KNOBS->CSI_SIZE_LIMIT : sizeLimit;
|
|
if (!trChunksQ.empty() && deterministicRandom()->random01() < clientSamplingProbability)
|
|
co_await delExcessClntTxnEntriesActor(&tr, clientTxnInfoSizeLimit);
|
|
|
|
// Cleanup Transaction sooner than later, so that we don't hold reference to context.
|
|
tr = Transaction();
|
|
co_await delay(CLIENT_KNOBS->CSI_STATUS_DELAY);
|
|
continue;
|
|
} catch (Error& e) {
|
|
err = e;
|
|
}
|
|
TraceEvent(SevWarnAlways, "UnableToWriteClientStatus").error(err);
|
|
if (err.code() == error_code_actor_cancelled) {
|
|
throw err;
|
|
}
|
|
cx->clientStatusUpdater.outStatusQ.clear();
|
|
|
|
// Cleanup Transaction sooner than later, so that we don't hold reference to context.
|
|
tr = Transaction();
|
|
co_await delay(10.0);
|
|
}
|
|
}
|
|
|
|
Future<Void> assertFailure(GrvProxyInterface remote, Future<ErrorOr<GetReadVersionReply>> reply) {
|
|
try {
|
|
ErrorOr<GetReadVersionReply> res = co_await reply;
|
|
if (!res.isError()) {
|
|
TraceEvent(SevError, "GotStaleReadVersion")
|
|
.detail("Remote", remote.getConsistentReadVersion.getEndpoint().addresses.address.toString())
|
|
.detail("Provisional", remote.provisional)
|
|
.detail("ReadVersion", res.get().version);
|
|
ASSERT_WE_THINK(false);
|
|
}
|
|
} catch (Error& e) {
|
|
if (e.code() == error_code_actor_cancelled) {
|
|
throw;
|
|
}
|
|
// we want this to fail -- so getting here is good, we'll just ignore the error.
|
|
}
|
|
}
|
|
|
|
Future<Void> attemptGRVFromOldProxies(std::vector<GrvProxyInterface> oldProxies,
|
|
std::vector<GrvProxyInterface> newProxies) {
|
|
auto debugID = nondeterministicRandom()->randomUniqueID();
|
|
g_traceBatch.addEvent("AttemptGRVFromOldProxyDebug", debugID.first(), "NativeAPI.attemptGRVFromOldProxies.Start");
|
|
Span span("NAPI:VerifyCausalReadRisky"_loc);
|
|
std::vector<Future<Void>> replies;
|
|
replies.reserve(oldProxies.size());
|
|
GetReadVersionRequest req(
|
|
span.context, 1, TransactionPriority::IMMEDIATE, invalidVersion, GetReadVersionRequest::FLAG_CAUSAL_READ_RISKY);
|
|
TraceEvent evt("AttemptGRVFromOldProxies");
|
|
evt.detail("NumOldProxies", oldProxies.size()).detail("NumNewProxies", newProxies.size());
|
|
auto traceProxies = [&](std::vector<GrvProxyInterface>& proxies, std::string const& key) {
|
|
for (int i = 0; i < proxies.size(); ++i) {
|
|
auto k = key + std::to_string(i);
|
|
evt.detail(k.c_str(), proxies[i].id());
|
|
}
|
|
};
|
|
traceProxies(oldProxies, "OldProxy"s);
|
|
traceProxies(newProxies, "NewProxy"s);
|
|
evt.log();
|
|
for (auto& i : oldProxies) {
|
|
req.reply = ReplyPromise<GetReadVersionReply>();
|
|
replies.push_back(assertFailure(i, i.getConsistentReadVersion.tryGetReply(req)));
|
|
}
|
|
return waitForAll(replies);
|
|
}
|
|
|
|
static Future<Void> monitorClientDBInfoChange(DatabaseContext* cx,
|
|
Reference<AsyncVar<ClientDBInfo> const> clientDBInfo,
|
|
AsyncTrigger* proxiesChangeTrigger) {
|
|
std::vector<CommitProxyInterface> curCommitProxies;
|
|
std::vector<GrvProxyInterface> curGrvProxies;
|
|
ActorCollection actors(false);
|
|
Future<Void> clientDBInfoOnChange = clientDBInfo->onChange();
|
|
curCommitProxies = clientDBInfo->get().commitProxies;
|
|
curGrvProxies = clientDBInfo->get().grvProxies;
|
|
|
|
while (true) {
|
|
auto res = co_await race(clientDBInfoOnChange, actors.getResult());
|
|
if (res.index() == 0) {
|
|
clientDBInfoOnChange = clientDBInfo->onChange();
|
|
if (clientDBInfo->get().commitProxies != curCommitProxies ||
|
|
clientDBInfo->get().grvProxies != curGrvProxies) {
|
|
// This condition is a bit complicated. Here we want to verify that we're unable to receive a read
|
|
// version from a proxy of an old generation after a successful recovery. The conditions are:
|
|
// 1. We only do this with a configured probability.
|
|
// 2. If the old set of Grv proxies is empty, there's nothing to do
|
|
// 3. If the new set of Grv proxies is empty, it means the recovery is not complete. So if an old
|
|
// Grv proxy still gives out read versions, this would be correct behavior.
|
|
// 4. If we see a provisional proxy, it means the recovery didn't complete yet, so the same as (3)
|
|
// applies.
|
|
if (deterministicRandom()->random01() < cx->verifyCausalReadsProp && !curGrvProxies.empty() &&
|
|
!clientDBInfo->get().grvProxies.empty() && !clientDBInfo->get().grvProxies[0].provisional) {
|
|
actors.add(attemptGRVFromOldProxies(curGrvProxies, clientDBInfo->get().grvProxies));
|
|
}
|
|
curCommitProxies = clientDBInfo->get().commitProxies;
|
|
curGrvProxies = clientDBInfo->get().grvProxies;
|
|
// Commits in the previous epoch may have been recovered but not included in the version vector.
|
|
// Clear the version vector to ensure the latest commit versions are received.
|
|
cx->ssVersionVectorCache.clear();
|
|
proxiesChangeTrigger->trigger();
|
|
}
|
|
} else if (res.index() == 1) {
|
|
UNSTOPPABLE_ASSERT(false);
|
|
} else {
|
|
UNREACHABLE();
|
|
}
|
|
}
|
|
}
|
|
|
|
static Future<Void> handleTssMismatches(DatabaseContext* cx) {
|
|
Reference<ReadYourWritesTransaction> tr;
|
|
KeyBackedMap<UID, UID> tssMapDB = KeyBackedMap<UID, UID>(tssMappingKeys.begin);
|
|
KeyBackedMap<Tuple, std::string> tssMismatchDB = KeyBackedMap<Tuple, std::string>(tssMismatchKeys.begin);
|
|
while (true) {
|
|
// return to calling actor, cx might be destroyed already with the tr reset below.
|
|
// This gives ~DatabaseContext a chance to cancel this actor.
|
|
co_await delay(0);
|
|
|
|
// <tssid, list of detailed mismatch data>
|
|
std::pair<UID, std::vector<DetailedTSSMismatch>> data = co_await cx->tssMismatchStream.getFuture();
|
|
// find ss pair id so we can remove it from the mapping
|
|
UID tssPairID;
|
|
bool found = false;
|
|
for (const auto& it : cx->tssMapping) {
|
|
if (it.second.id() == data.first) {
|
|
tssPairID = it.first;
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (found) {
|
|
bool quarantine = CLIENT_KNOBS->QUARANTINE_TSS_ON_MISMATCH;
|
|
TraceEvent(SevWarnAlways, quarantine ? "TSS_QuarantineMismatch" : "TSS_KillMismatch")
|
|
.detail("TSSID", data.first.toString());
|
|
CODE_PROBE(quarantine, "Quarantining TSS because it got mismatch");
|
|
CODE_PROBE(!quarantine, "Killing TSS because it got mismatch");
|
|
|
|
tr = makeReference<ReadYourWritesTransaction>(Database(Reference<DatabaseContext>::addRef(cx)));
|
|
int tries = 0;
|
|
while (true) {
|
|
Error err;
|
|
try {
|
|
tr->setOption(FDBTransactionOptions::PRIORITY_SYSTEM_IMMEDIATE);
|
|
tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS);
|
|
if (quarantine) {
|
|
tr->set(tssQuarantineKeyFor(data.first), ""_sr);
|
|
} else {
|
|
tr->clear(serverTagKeyFor(data.first));
|
|
}
|
|
tssMapDB.erase(tr, tssPairID);
|
|
|
|
for (const DetailedTSSMismatch& d : data.second) {
|
|
// <tssid, time, mismatchid> -> mismatch data
|
|
tssMismatchDB.set(tr,
|
|
Tuple::makeTuple(data.first.toString(), d.timestamp, d.mismatchId.toString()),
|
|
d.traceString);
|
|
}
|
|
|
|
co_await tr->commit();
|
|
|
|
break;
|
|
} catch (Error& e) {
|
|
err = e;
|
|
}
|
|
co_await tr->onError(err);
|
|
tries++;
|
|
if (tries > 10) {
|
|
// Give up, it'll get another mismatch or a human will investigate eventually
|
|
TraceEvent("TSS_MismatchGaveUp").detail("TSSID", data.first.toString());
|
|
break;
|
|
}
|
|
}
|
|
// clear out txn so that the extra DatabaseContext ref gets decref'd and we can free cx
|
|
tr = makeReference<ReadYourWritesTransaction>();
|
|
} else {
|
|
CODE_PROBE(true, "Not handling TSS with mismatch because it's already gone");
|
|
}
|
|
}
|
|
}
|
|
|
|
struct SingleSpecialKeyImpl : SpecialKeyRangeReadImpl {
|
|
Future<RangeResult> getRange(ReadYourWritesTransaction* ryw,
|
|
KeyRangeRef kr,
|
|
GetRangeLimits limitsHint) const override {
|
|
ASSERT(kr.contains(k));
|
|
return map(f(ryw), [k = k](Optional<Value> v) {
|
|
RangeResult result;
|
|
if (v.present()) {
|
|
result.push_back_deep(result.arena(), KeyValueRef(k, v.get()));
|
|
}
|
|
return result;
|
|
});
|
|
}
|
|
|
|
SingleSpecialKeyImpl(KeyRef k, const std::function<Future<Optional<Value>>(ReadYourWritesTransaction*)>& f)
|
|
: SpecialKeyRangeReadImpl(singleKeyRange(k)), k(k), f(f) {}
|
|
|
|
private:
|
|
Key k;
|
|
std::function<Future<Optional<Value>>(ReadYourWritesTransaction*)> f;
|
|
};
|
|
|
|
class HealthMetricsRangeImpl : public SpecialKeyRangeAsyncImpl {
|
|
public:
|
|
explicit HealthMetricsRangeImpl(KeyRangeRef kr);
|
|
Future<RangeResult> getRange(ReadYourWritesTransaction* ryw,
|
|
KeyRangeRef kr,
|
|
GetRangeLimits limitsHint) const override;
|
|
};
|
|
|
|
static RangeResult healthMetricsToKVPairs(const HealthMetrics& metrics, KeyRangeRef kr) {
|
|
RangeResult result;
|
|
if (CLIENT_BUGGIFY)
|
|
return result;
|
|
if (kr.contains("\xff\xff/metrics/health/aggregate"_sr) && metrics.worstStorageDurabilityLag != 0) {
|
|
json_spirit::mObject statsObj;
|
|
statsObj["batch_limited"] = metrics.batchLimited;
|
|
statsObj["tps_limit"] = metrics.tpsLimit;
|
|
statsObj["worst_storage_durability_lag"] = metrics.worstStorageDurabilityLag;
|
|
statsObj["limiting_storage_durability_lag"] = metrics.limitingStorageDurabilityLag;
|
|
statsObj["worst_storage_queue"] = metrics.worstStorageQueue;
|
|
statsObj["limiting_storage_queue"] = metrics.limitingStorageQueue;
|
|
statsObj["worst_log_queue"] = metrics.worstTLogQueue;
|
|
std::string statsString =
|
|
json_spirit::write_string(json_spirit::mValue(statsObj), json_spirit::Output_options::raw_utf8);
|
|
ValueRef bytes(result.arena(), statsString);
|
|
result.push_back(result.arena(), KeyValueRef("\xff\xff/metrics/health/aggregate"_sr, bytes));
|
|
}
|
|
// tlog stats
|
|
{
|
|
int phase = 0; // Avoid comparing twice per loop iteration
|
|
for (const auto& [uid, logStats] : metrics.tLogQueue) {
|
|
StringRef k{ StringRef(uid.toString()).withPrefix("\xff\xff/metrics/health/log/"_sr, result.arena()) };
|
|
if (phase == 0 && k >= kr.begin) {
|
|
phase = 1;
|
|
}
|
|
if (phase == 1) {
|
|
if (k < kr.end) {
|
|
json_spirit::mObject statsObj;
|
|
statsObj["log_queue"] = logStats;
|
|
std::string statsString =
|
|
json_spirit::write_string(json_spirit::mValue(statsObj), json_spirit::Output_options::raw_utf8);
|
|
ValueRef bytes(result.arena(), statsString);
|
|
result.push_back(result.arena(), KeyValueRef(k, bytes));
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Storage stats
|
|
{
|
|
int phase = 0; // Avoid comparing twice per loop iteration
|
|
for (const auto& [uid, storageStats] : metrics.storageStats) {
|
|
StringRef k{ StringRef(uid.toString()).withPrefix("\xff\xff/metrics/health/storage/"_sr, result.arena()) };
|
|
if (phase == 0 && k >= kr.begin) {
|
|
phase = 1;
|
|
}
|
|
if (phase == 1) {
|
|
if (k < kr.end) {
|
|
json_spirit::mObject statsObj;
|
|
statsObj["storage_durability_lag"] = storageStats.storageDurabilityLag;
|
|
statsObj["storage_queue"] = storageStats.storageQueue;
|
|
statsObj["cpu_usage"] = storageStats.cpuUsage;
|
|
statsObj["disk_usage"] = storageStats.diskUsage;
|
|
std::string statsString =
|
|
json_spirit::write_string(json_spirit::mValue(statsObj), json_spirit::Output_options::raw_utf8);
|
|
ValueRef bytes(result.arena(), statsString);
|
|
result.push_back(result.arena(), KeyValueRef(k, bytes));
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static Future<RangeResult> healthMetricsGetRangeActor(ReadYourWritesTransaction* ryw, KeyRangeRef kr) {
|
|
HealthMetrics metrics = co_await ryw->getDatabase()->getHealthMetrics(
|
|
/*detailed ("per process")*/ kr.intersects(
|
|
KeyRangeRef("\xff\xff/metrics/health/storage/"_sr, "\xff\xff/metrics/health/storage0"_sr)) ||
|
|
kr.intersects(KeyRangeRef("\xff\xff/metrics/health/log/"_sr, "\xff\xff/metrics/health/log0"_sr)));
|
|
co_return healthMetricsToKVPairs(metrics, kr);
|
|
}
|
|
|
|
HealthMetricsRangeImpl::HealthMetricsRangeImpl(KeyRangeRef kr) : SpecialKeyRangeAsyncImpl(kr) {}
|
|
|
|
Future<RangeResult> HealthMetricsRangeImpl::getRange(ReadYourWritesTransaction* ryw,
|
|
KeyRangeRef kr,
|
|
GetRangeLimits limitsHint) const {
|
|
return healthMetricsGetRangeActor(ryw, kr);
|
|
}
|
|
|
|
Future<UID> getClusterId(Database db) {
|
|
while (!db->clientInfo->get().clusterId.isValid()) {
|
|
co_await db->clientInfo->onChange();
|
|
}
|
|
co_return db->clientInfo->get().clusterId;
|
|
}
|
|
|
|
void DatabaseContext::initializeSpecialCounters() {
|
|
specialCounter(cc, "OutstandingWatches", [this] { return outstandingWatches; });
|
|
specialCounter(cc, "WatchMapSize", [this] { return watchMap.size(); });
|
|
}
|
|
|
|
DatabaseContext::DatabaseContext(Reference<AsyncVar<Reference<IClusterConnectionRecord>>> connectionRecord,
|
|
Reference<AsyncVar<ClientDBInfo>> clientInfo,
|
|
Reference<AsyncVar<Optional<ClientLeaderRegInterface>> const> coordinator,
|
|
Future<Void> clientInfoMonitor,
|
|
TaskPriority taskID,
|
|
LocalityData const& clientLocality,
|
|
EnableLocalityLoadBalance enableLocalityLoadBalance,
|
|
LockAware lockAware,
|
|
IsInternal internal,
|
|
int _apiVersion,
|
|
IsSwitchable switchable)
|
|
: dbId(deterministicRandom()->randomUniqueID()), lockAware(lockAware), switchable(switchable),
|
|
connectionRecord(connectionRecord), proxyProvisional(false), clientLocality(clientLocality),
|
|
enableLocalityLoadBalance(enableLocalityLoadBalance), internal(internal), cc("TransactionMetrics", dbId.toString()),
|
|
transactionReadVersions("ReadVersions", cc), transactionReadVersionsThrottled("ReadVersionsThrottled", cc),
|
|
transactionReadVersionsCompleted("ReadVersionsCompleted", cc),
|
|
transactionReadVersionBatches("ReadVersionBatches", cc),
|
|
transactionBatchReadVersions("BatchPriorityReadVersions", cc),
|
|
transactionDefaultReadVersions("DefaultPriorityReadVersions", cc),
|
|
transactionImmediateReadVersions("ImmediatePriorityReadVersions", cc),
|
|
transactionBatchReadVersionsCompleted("BatchPriorityReadVersionsCompleted", cc),
|
|
transactionDefaultReadVersionsCompleted("DefaultPriorityReadVersionsCompleted", cc),
|
|
transactionImmediateReadVersionsCompleted("ImmediatePriorityReadVersionsCompleted", cc),
|
|
transactionLogicalReads("LogicalUncachedReads", cc), transactionPhysicalReads("PhysicalReadRequests", cc),
|
|
transactionPhysicalReadsCompleted("PhysicalReadRequestsCompleted", cc),
|
|
transactionGetKeyRequests("GetKeyRequests", cc), transactionGetValueRequests("GetValueRequests", cc),
|
|
transactionGetRangeRequests("GetRangeRequests", cc),
|
|
transactionGetMappedRangeRequests("GetMappedRangeRequests", cc),
|
|
transactionGetRangeStreamRequests("GetRangeStreamRequests", cc), transactionWatchRequests("WatchRequests", cc),
|
|
transactionGetAddressesForKeyRequests("GetAddressesForKeyRequests", cc), transactionBytesRead("BytesRead", cc),
|
|
transactionKeysRead("KeysRead", cc), transactionMetadataVersionReads("MetadataVersionReads", cc),
|
|
transactionCommittedMutations("CommittedMutations", cc),
|
|
transactionCommittedMutationBytes("CommittedMutationBytes", cc), transactionSetMutations("SetMutations", cc),
|
|
transactionClearMutations("ClearMutations", cc), transactionAtomicMutations("AtomicMutations", cc),
|
|
transactionsCommitStarted("CommitStarted", cc), transactionsCommitCompleted("CommitCompleted", cc),
|
|
transactionKeyServerLocationRequests("KeyServerLocationRequests", cc),
|
|
transactionKeyServerLocationRequestsCompleted("KeyServerLocationRequestsCompleted", cc),
|
|
transactionStatusRequests("StatusRequests", cc), transactionsTooOld("TooOld", cc),
|
|
transactionsFutureVersions("FutureVersions", cc), transactionsNotCommitted("NotCommitted", cc),
|
|
transactionsMaybeCommitted("MaybeCommitted", cc), transactionsResourceConstrained("ResourceConstrained", cc),
|
|
transactionsProcessBehind("ProcessBehind", cc), transactionsThrottled("Throttled", cc),
|
|
transactionsLockRejected("LockRejected", cc),
|
|
transactionsExpensiveClearCostEstCount("ExpensiveClearCostEstCount", cc),
|
|
transactionGrvFullBatches("NumGrvFullBatches", cc), transactionGrvTimedOutBatches("NumGrvTimedOutBatches", cc),
|
|
transactionCommitVersionNotFoundForSS("CommitVersionNotFoundForSS", cc),
|
|
|
|
latencies(), readLatencies(), commitLatencies(), GRVLatencies(), mutationsPerCommit(), bytesPerCommit(),
|
|
outstandingWatches(0), sharedStatePtr(nullptr), lastGrvTime(0.0), cachedReadVersion(0),
|
|
lastRkBatchThrottleTime(0.0), lastRkDefaultThrottleTime(0.0), lastProxyRequestTime(0.0),
|
|
transactionTracingSample(false), taskID(taskID), clientInfo(clientInfo), clientInfoMonitor(clientInfoMonitor),
|
|
coordinator(coordinator), apiVersion(_apiVersion), mvCacheInsertLocation(0), healthMetricsLastUpdated(0),
|
|
detailedHealthMetricsLastUpdated(0), smoothMidShardSize(CLIENT_KNOBS->SHARD_STAT_SMOOTH_AMOUNT),
|
|
specialKeySpace(std::make_unique<SpecialKeySpace>(specialKeys.begin, specialKeys.end, /* test */ false)),
|
|
connectToDatabaseEventCacheHolder(format("ConnectToDatabase/%s", dbId.toString().c_str())) {
|
|
|
|
DisabledTraceEvent("DatabaseContextCreated", dbId).backtrace();
|
|
|
|
connected = (!clientInfo->get().commitProxies.empty() && !clientInfo->get().grvProxies.empty())
|
|
? Void()
|
|
: clientInfo->onChange();
|
|
|
|
metadataVersionCache.resize(CLIENT_KNOBS->METADATA_VERSION_CACHE_SIZE);
|
|
maxOutstandingWatches = CLIENT_KNOBS->DEFAULT_MAX_OUTSTANDING_WATCHES;
|
|
|
|
snapshotRywEnabled = apiVersion.hasSnapshotRYW() ? 1 : 0;
|
|
|
|
logger = databaseLogger(this) && tssLogger(this);
|
|
locationCacheSize = g_network->isSimulated() ? CLIENT_KNOBS->LOCATION_CACHE_EVICTION_SIZE_SIM
|
|
: CLIENT_KNOBS->LOCATION_CACHE_EVICTION_SIZE;
|
|
|
|
getValueSubmitted.init("NativeAPI.GetValueSubmitted"_sr);
|
|
getValueCompleted.init("NativeAPI.GetValueCompleted"_sr);
|
|
|
|
clientDBInfoMonitor = monitorClientDBInfoChange(this, clientInfo, &proxiesChangeTrigger);
|
|
tssMismatchHandler = handleTssMismatches(this);
|
|
clientStatusUpdater.actor = clientStatusUpdateActor(this);
|
|
|
|
smoothMidShardSize.reset(CLIENT_KNOBS->INIT_MID_SHARD_BYTES);
|
|
globalConfig = std::make_unique<GlobalConfig>(this);
|
|
|
|
if (apiVersion.version() >= 740) {
|
|
registerSpecialKeysImpl(
|
|
SpecialKeySpace::MODULE::METRICS,
|
|
SpecialKeySpace::IMPLTYPE::READONLY,
|
|
std::make_unique<FaultToleranceMetricsImpl>(
|
|
singleKeyRange("fault_tolerance_metrics_json"_sr)
|
|
.withPrefix(SpecialKeySpace::getModuleRange(SpecialKeySpace::MODULE::METRICS).begin)));
|
|
}
|
|
|
|
if (apiVersion.version() >= 700) {
|
|
registerSpecialKeysImpl(SpecialKeySpace::MODULE::ERRORMSG,
|
|
SpecialKeySpace::IMPLTYPE::READONLY,
|
|
std::make_unique<SingleSpecialKeyImpl>(
|
|
SpecialKeySpace::getModuleRange(SpecialKeySpace::MODULE::ERRORMSG).begin,
|
|
[](ReadYourWritesTransaction* ryw) -> Future<Optional<Value>> {
|
|
if (ryw->getSpecialKeySpaceErrorMsg().present())
|
|
return Optional<Value>(ryw->getSpecialKeySpaceErrorMsg().get());
|
|
else
|
|
return Optional<Value>();
|
|
}));
|
|
registerSpecialKeysImpl(
|
|
SpecialKeySpace::MODULE::MANAGEMENT,
|
|
SpecialKeySpace::IMPLTYPE::READWRITE,
|
|
std::make_unique<ManagementCommandsOptionsImpl>(
|
|
KeyRangeRef("options/"_sr, "options0"_sr)
|
|
.withPrefix(SpecialKeySpace::getModuleRange(SpecialKeySpace::MODULE::MANAGEMENT).begin)));
|
|
registerSpecialKeysImpl(
|
|
SpecialKeySpace::MODULE::MANAGEMENT,
|
|
SpecialKeySpace::IMPLTYPE::READWRITE,
|
|
std::make_unique<ExcludeServersRangeImpl>(SpecialKeySpace::getManagementApiCommandRange("exclude")));
|
|
registerSpecialKeysImpl(
|
|
SpecialKeySpace::MODULE::MANAGEMENT,
|
|
SpecialKeySpace::IMPLTYPE::READWRITE,
|
|
std::make_unique<FailedServersRangeImpl>(SpecialKeySpace::getManagementApiCommandRange("failed")));
|
|
registerSpecialKeysImpl(SpecialKeySpace::MODULE::MANAGEMENT,
|
|
SpecialKeySpace::IMPLTYPE::READWRITE,
|
|
std::make_unique<ExcludedLocalitiesRangeImpl>(
|
|
SpecialKeySpace::getManagementApiCommandRange("excludedlocality")));
|
|
registerSpecialKeysImpl(SpecialKeySpace::MODULE::MANAGEMENT,
|
|
SpecialKeySpace::IMPLTYPE::READWRITE,
|
|
std::make_unique<FailedLocalitiesRangeImpl>(
|
|
SpecialKeySpace::getManagementApiCommandRange("failedlocality")));
|
|
registerSpecialKeysImpl(
|
|
SpecialKeySpace::MODULE::MANAGEMENT,
|
|
SpecialKeySpace::IMPLTYPE::READONLY,
|
|
std::make_unique<ExclusionInProgressRangeImpl>(
|
|
KeyRangeRef("in_progress_exclusion/"_sr, "in_progress_exclusion0"_sr)
|
|
.withPrefix(SpecialKeySpace::getModuleRange(SpecialKeySpace::MODULE::MANAGEMENT).begin)));
|
|
registerSpecialKeysImpl(
|
|
SpecialKeySpace::MODULE::CONFIGURATION,
|
|
SpecialKeySpace::IMPLTYPE::READWRITE,
|
|
std::make_unique<ProcessClassRangeImpl>(
|
|
KeyRangeRef("process/class_type/"_sr, "process/class_type0"_sr)
|
|
.withPrefix(SpecialKeySpace::getModuleRange(SpecialKeySpace::MODULE::CONFIGURATION).begin)));
|
|
registerSpecialKeysImpl(
|
|
SpecialKeySpace::MODULE::CONFIGURATION,
|
|
SpecialKeySpace::IMPLTYPE::READONLY,
|
|
std::make_unique<ProcessClassSourceRangeImpl>(
|
|
KeyRangeRef("process/class_source/"_sr, "process/class_source0"_sr)
|
|
.withPrefix(SpecialKeySpace::getModuleRange(SpecialKeySpace::MODULE::CONFIGURATION).begin)));
|
|
registerSpecialKeysImpl(
|
|
SpecialKeySpace::MODULE::MANAGEMENT,
|
|
SpecialKeySpace::IMPLTYPE::READWRITE,
|
|
std::make_unique<LockDatabaseImpl>(
|
|
singleKeyRange("db_locked"_sr)
|
|
.withPrefix(SpecialKeySpace::getModuleRange(SpecialKeySpace::MODULE::MANAGEMENT).begin)));
|
|
registerSpecialKeysImpl(
|
|
SpecialKeySpace::MODULE::MANAGEMENT,
|
|
SpecialKeySpace::IMPLTYPE::READWRITE,
|
|
std::make_unique<ConsistencyCheckImpl>(
|
|
singleKeyRange("consistency_check_suspended"_sr)
|
|
.withPrefix(SpecialKeySpace::getModuleRange(SpecialKeySpace::MODULE::MANAGEMENT).begin)));
|
|
registerSpecialKeysImpl(
|
|
SpecialKeySpace::MODULE::GLOBALCONFIG,
|
|
SpecialKeySpace::IMPLTYPE::READWRITE,
|
|
std::make_unique<GlobalConfigImpl>(SpecialKeySpace::getModuleRange(SpecialKeySpace::MODULE::GLOBALCONFIG)));
|
|
registerSpecialKeysImpl(
|
|
SpecialKeySpace::MODULE::TRACING,
|
|
SpecialKeySpace::IMPLTYPE::READWRITE,
|
|
std::make_unique<TracingOptionsImpl>(SpecialKeySpace::getModuleRange(SpecialKeySpace::MODULE::TRACING)));
|
|
registerSpecialKeysImpl(
|
|
SpecialKeySpace::MODULE::CONFIGURATION,
|
|
SpecialKeySpace::IMPLTYPE::READWRITE,
|
|
std::make_unique<CoordinatorsImpl>(
|
|
KeyRangeRef("coordinators/"_sr, "coordinators0"_sr)
|
|
.withPrefix(SpecialKeySpace::getModuleRange(SpecialKeySpace::MODULE::CONFIGURATION).begin)));
|
|
registerSpecialKeysImpl(
|
|
SpecialKeySpace::MODULE::MANAGEMENT,
|
|
SpecialKeySpace::IMPLTYPE::READONLY,
|
|
std::make_unique<CoordinatorsAutoImpl>(
|
|
singleKeyRange("auto_coordinators"_sr)
|
|
.withPrefix(SpecialKeySpace::getModuleRange(SpecialKeySpace::MODULE::MANAGEMENT).begin)));
|
|
registerSpecialKeysImpl(
|
|
SpecialKeySpace::MODULE::MANAGEMENT,
|
|
SpecialKeySpace::IMPLTYPE::READWRITE,
|
|
std::make_unique<AdvanceVersionImpl>(
|
|
singleKeyRange("min_required_commit_version"_sr)
|
|
.withPrefix(SpecialKeySpace::getModuleRange(SpecialKeySpace::MODULE::MANAGEMENT).begin)));
|
|
registerSpecialKeysImpl(
|
|
SpecialKeySpace::MODULE::MANAGEMENT,
|
|
SpecialKeySpace::IMPLTYPE::READWRITE,
|
|
std::make_unique<VersionEpochImpl>(
|
|
singleKeyRange("version_epoch"_sr)
|
|
.withPrefix(SpecialKeySpace::getModuleRange(SpecialKeySpace::MODULE::MANAGEMENT).begin)));
|
|
registerSpecialKeysImpl(
|
|
SpecialKeySpace::MODULE::MANAGEMENT,
|
|
SpecialKeySpace::IMPLTYPE::READWRITE,
|
|
std::make_unique<ClientProfilingImpl>(
|
|
KeyRangeRef("profiling/"_sr, "profiling0"_sr)
|
|
.withPrefix(SpecialKeySpace::getModuleRange(SpecialKeySpace::MODULE::MANAGEMENT).begin)),
|
|
/* deprecated */ ApiVersion::withClientProfilingDeprecated().version());
|
|
registerSpecialKeysImpl(
|
|
SpecialKeySpace::MODULE::MANAGEMENT,
|
|
SpecialKeySpace::IMPLTYPE::READWRITE,
|
|
std::make_unique<MaintenanceImpl>(
|
|
KeyRangeRef("maintenance/"_sr, "maintenance0"_sr)
|
|
.withPrefix(SpecialKeySpace::getModuleRange(SpecialKeySpace::MODULE::MANAGEMENT).begin)));
|
|
registerSpecialKeysImpl(
|
|
SpecialKeySpace::MODULE::MANAGEMENT,
|
|
SpecialKeySpace::IMPLTYPE::READWRITE,
|
|
std::make_unique<DataDistributionImpl>(
|
|
KeyRangeRef("data_distribution/"_sr, "data_distribution0"_sr)
|
|
.withPrefix(SpecialKeySpace::getModuleRange(SpecialKeySpace::MODULE::MANAGEMENT).begin)));
|
|
registerSpecialKeysImpl(
|
|
SpecialKeySpace::MODULE::ACTORLINEAGE,
|
|
SpecialKeySpace::IMPLTYPE::READONLY,
|
|
std::make_unique<ActorLineageImpl>(SpecialKeySpace::getModuleRange(SpecialKeySpace::MODULE::ACTORLINEAGE)));
|
|
registerSpecialKeysImpl(SpecialKeySpace::MODULE::ACTOR_PROFILER_CONF,
|
|
SpecialKeySpace::IMPLTYPE::READWRITE,
|
|
std::make_unique<ActorProfilerConf>(
|
|
SpecialKeySpace::getModuleRange(SpecialKeySpace::MODULE::ACTOR_PROFILER_CONF)));
|
|
}
|
|
if (apiVersion.version() >= 630) {
|
|
registerSpecialKeysImpl(SpecialKeySpace::MODULE::TRANSACTION,
|
|
SpecialKeySpace::IMPLTYPE::READONLY,
|
|
std::make_unique<ConflictingKeysImpl>(conflictingKeysRange));
|
|
registerSpecialKeysImpl(SpecialKeySpace::MODULE::TRANSACTION,
|
|
SpecialKeySpace::IMPLTYPE::READONLY,
|
|
std::make_unique<ReadConflictRangeImpl>(readConflictRangeKeysRange));
|
|
registerSpecialKeysImpl(SpecialKeySpace::MODULE::TRANSACTION,
|
|
SpecialKeySpace::IMPLTYPE::READONLY,
|
|
std::make_unique<WriteConflictRangeImpl>(writeConflictRangeKeysRange));
|
|
registerSpecialKeysImpl(SpecialKeySpace::MODULE::METRICS,
|
|
SpecialKeySpace::IMPLTYPE::READONLY,
|
|
std::make_unique<DDStatsRangeImpl>(ddStatsRange));
|
|
registerSpecialKeysImpl(SpecialKeySpace::MODULE::METRICS,
|
|
SpecialKeySpace::IMPLTYPE::READONLY,
|
|
std::make_unique<HealthMetricsRangeImpl>(
|
|
KeyRangeRef("\xff\xff/metrics/health/"_sr, "\xff\xff/metrics/health0"_sr)));
|
|
registerSpecialKeysImpl(SpecialKeySpace::MODULE::WORKERINTERFACE,
|
|
SpecialKeySpace::IMPLTYPE::READONLY,
|
|
std::make_unique<WorkerInterfacesSpecialKeyImpl>(
|
|
KeyRangeRef("\xff\xff/worker_interfaces/"_sr, "\xff\xff/worker_interfaces0"_sr)));
|
|
registerSpecialKeysImpl(
|
|
SpecialKeySpace::MODULE::STATUSJSON,
|
|
SpecialKeySpace::IMPLTYPE::READONLY,
|
|
std::make_unique<SingleSpecialKeyImpl>(
|
|
"\xff\xff/status/json"_sr, [](ReadYourWritesTransaction* ryw) -> Future<Optional<Value>> {
|
|
if (ryw->getDatabase().getPtr() && ryw->getDatabase()->getConnectionRecord()) {
|
|
++ryw->getDatabase()->transactionStatusRequests;
|
|
return getJSON(ryw->getDatabase());
|
|
} else {
|
|
return Optional<Value>();
|
|
}
|
|
}));
|
|
registerSpecialKeysImpl(
|
|
SpecialKeySpace::MODULE::CLUSTERFILEPATH,
|
|
SpecialKeySpace::IMPLTYPE::READONLY,
|
|
std::make_unique<SingleSpecialKeyImpl>(
|
|
"\xff\xff/cluster_file_path"_sr, [](ReadYourWritesTransaction* ryw) -> Future<Optional<Value>> {
|
|
try {
|
|
if (ryw->getDatabase().getPtr() && ryw->getDatabase()->getConnectionRecord()) {
|
|
Optional<Value> output =
|
|
StringRef(ryw->getDatabase()->getConnectionRecord()->getLocation());
|
|
return output;
|
|
}
|
|
} catch (Error& e) {
|
|
return e;
|
|
}
|
|
return Optional<Value>();
|
|
}));
|
|
|
|
registerSpecialKeysImpl(
|
|
SpecialKeySpace::MODULE::CONNECTIONSTRING,
|
|
SpecialKeySpace::IMPLTYPE::READONLY,
|
|
std::make_unique<SingleSpecialKeyImpl>(
|
|
"\xff\xff/connection_string"_sr, [](ReadYourWritesTransaction* ryw) -> Future<Optional<Value>> {
|
|
try {
|
|
if (ryw->getDatabase().getPtr() && ryw->getDatabase()->getConnectionRecord()) {
|
|
Reference<IClusterConnectionRecord> f = ryw->getDatabase()->getConnectionRecord();
|
|
Optional<Value> output = StringRef(f->getConnectionString().toString());
|
|
return output;
|
|
}
|
|
} catch (Error& e) {
|
|
return e;
|
|
}
|
|
return Optional<Value>();
|
|
}));
|
|
registerSpecialKeysImpl(
|
|
SpecialKeySpace::MODULE::CLUSTERID,
|
|
SpecialKeySpace::IMPLTYPE::READONLY,
|
|
std::make_unique<SingleSpecialKeyImpl>(
|
|
"\xff\xff/cluster_id"_sr, [](ReadYourWritesTransaction* ryw) -> Future<Optional<Value>> {
|
|
try {
|
|
if (ryw->getDatabase().getPtr()) {
|
|
return map(getClusterId(ryw->getDatabase()),
|
|
[](UID id) { return Optional<Value>(StringRef(id.toString())); });
|
|
}
|
|
} catch (Error& e) {
|
|
return e;
|
|
}
|
|
return Optional<Value>();
|
|
}));
|
|
}
|
|
throttleExpirer = recurring(std::bind_front(&DatabaseContext::expireThrottles, this),
|
|
CLIENT_KNOBS->TAG_THROTTLE_EXPIRATION_INTERVAL);
|
|
|
|
if (buggify()) {
|
|
DatabaseContext::debugUseTags = true;
|
|
}
|
|
|
|
initializeSpecialCounters();
|
|
}
|
|
|
|
DatabaseContext::DatabaseContext(const Error& err)
|
|
: deferredError(err), internal(IsInternal::False), cc("TransactionMetrics"),
|
|
transactionReadVersions("ReadVersions", cc), transactionReadVersionsThrottled("ReadVersionsThrottled", cc),
|
|
transactionReadVersionsCompleted("ReadVersionsCompleted", cc),
|
|
transactionReadVersionBatches("ReadVersionBatches", cc),
|
|
transactionBatchReadVersions("BatchPriorityReadVersions", cc),
|
|
transactionDefaultReadVersions("DefaultPriorityReadVersions", cc),
|
|
transactionImmediateReadVersions("ImmediatePriorityReadVersions", cc),
|
|
transactionBatchReadVersionsCompleted("BatchPriorityReadVersionsCompleted", cc),
|
|
transactionDefaultReadVersionsCompleted("DefaultPriorityReadVersionsCompleted", cc),
|
|
transactionImmediateReadVersionsCompleted("ImmediatePriorityReadVersionsCompleted", cc),
|
|
transactionLogicalReads("LogicalUncachedReads", cc), transactionPhysicalReads("PhysicalReadRequests", cc),
|
|
transactionPhysicalReadsCompleted("PhysicalReadRequestsCompleted", cc),
|
|
transactionGetKeyRequests("GetKeyRequests", cc), transactionGetValueRequests("GetValueRequests", cc),
|
|
transactionGetRangeRequests("GetRangeRequests", cc),
|
|
transactionGetMappedRangeRequests("GetMappedRangeRequests", cc),
|
|
transactionGetRangeStreamRequests("GetRangeStreamRequests", cc), transactionWatchRequests("WatchRequests", cc),
|
|
transactionGetAddressesForKeyRequests("GetAddressesForKeyRequests", cc), transactionBytesRead("BytesRead", cc),
|
|
transactionKeysRead("KeysRead", cc), transactionMetadataVersionReads("MetadataVersionReads", cc),
|
|
transactionCommittedMutations("CommittedMutations", cc),
|
|
transactionCommittedMutationBytes("CommittedMutationBytes", cc), transactionSetMutations("SetMutations", cc),
|
|
transactionClearMutations("ClearMutations", cc), transactionAtomicMutations("AtomicMutations", cc),
|
|
transactionsCommitStarted("CommitStarted", cc), transactionsCommitCompleted("CommitCompleted", cc),
|
|
transactionKeyServerLocationRequests("KeyServerLocationRequests", cc),
|
|
transactionKeyServerLocationRequestsCompleted("KeyServerLocationRequestsCompleted", cc),
|
|
transactionStatusRequests("StatusRequests", cc), transactionsTooOld("TooOld", cc),
|
|
transactionsFutureVersions("FutureVersions", cc), transactionsNotCommitted("NotCommitted", cc),
|
|
transactionsMaybeCommitted("MaybeCommitted", cc), transactionsResourceConstrained("ResourceConstrained", cc),
|
|
transactionsProcessBehind("ProcessBehind", cc), transactionsThrottled("Throttled", cc),
|
|
transactionsLockRejected("LockRejected", cc),
|
|
transactionsExpensiveClearCostEstCount("ExpensiveClearCostEstCount", cc),
|
|
transactionGrvFullBatches("NumGrvFullBatches", cc), transactionGrvTimedOutBatches("NumGrvTimedOutBatches", cc),
|
|
transactionCommitVersionNotFoundForSS("CommitVersionNotFoundForSS", cc), latencies(), readLatencies(),
|
|
commitLatencies(), GRVLatencies(), mutationsPerCommit(), bytesPerCommit(), sharedStatePtr(nullptr),
|
|
transactionTracingSample(false), smoothMidShardSize(CLIENT_KNOBS->SHARD_STAT_SMOOTH_AMOUNT),
|
|
connectToDatabaseEventCacheHolder(format("ConnectToDatabase/%s", dbId.toString().c_str())), outstandingWatches(0) {
|
|
initializeSpecialCounters();
|
|
}
|
|
|
|
// Static constructor used by server processes to create a DatabaseContext
|
|
// For internal (fdbserver) use only
|
|
Database DatabaseContext::create(Reference<AsyncVar<ClientDBInfo>> clientInfo,
|
|
Future<Void> clientInfoMonitor,
|
|
LocalityData clientLocality,
|
|
EnableLocalityLoadBalance enableLocalityLoadBalance,
|
|
TaskPriority taskID,
|
|
LockAware lockAware,
|
|
int apiVersion,
|
|
IsSwitchable switchable) {
|
|
return Database(new DatabaseContext(Reference<AsyncVar<Reference<IClusterConnectionRecord>>>(),
|
|
clientInfo,
|
|
makeReference<AsyncVar<Optional<ClientLeaderRegInterface>>>(),
|
|
clientInfoMonitor,
|
|
taskID,
|
|
clientLocality,
|
|
enableLocalityLoadBalance,
|
|
lockAware,
|
|
IsInternal::True,
|
|
apiVersion,
|
|
switchable));
|
|
}
|
|
|
|
DatabaseContext::~DatabaseContext() {
|
|
clientDBInfoMonitor.cancel();
|
|
monitorTssInfoChange.cancel();
|
|
tssMismatchHandler.cancel();
|
|
logger.cancel();
|
|
clientStatusUpdater.actor.cancel();
|
|
throttleExpirer.cancel();
|
|
statusLeaderMon.cancel();
|
|
|
|
if (grvUpdateHandler.isValid()) {
|
|
grvUpdateHandler.cancel();
|
|
}
|
|
if (sharedStatePtr) {
|
|
sharedStatePtr->delRef(sharedStatePtr);
|
|
}
|
|
for (auto it = server_interf.begin(); it != server_interf.end(); it = server_interf.erase(it))
|
|
it->second->notifyContextDestroyed();
|
|
ASSERT_ABORT(server_interf.empty());
|
|
locationCache.insert(allKeys, Reference<LocationInfo>());
|
|
|
|
DisabledTraceEvent("DatabaseContextDestructed", dbId).backtrace();
|
|
}
|