mirror of
https://github.com/apple/foundationdb.git
synced 2025-06-02 03:12:12 +08:00
444 lines
14 KiB
C++
444 lines
14 KiB
C++
/*
|
|
* TenantCache.actor.cpp
|
|
*
|
|
* This source file is part of the FoundationDB open source project
|
|
*
|
|
* Copyright 2013-2022 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 <limits>
|
|
#include <string>
|
|
|
|
#include "fdbclient/FDBOptions.g.h"
|
|
#include "fdbclient/KeyBackedTypes.h"
|
|
#include "fdbclient/SystemData.h"
|
|
#include "fdbclient/FDBTypes.h"
|
|
#include "fdbclient/Tenant.h"
|
|
#include "fdbserver/DDTeamCollection.h"
|
|
#include "fdbserver/TenantCache.h"
|
|
#include "flow/flow.h"
|
|
#include "flow/Trace.h"
|
|
#include "flow/WipedString.h"
|
|
#include "flow/actorcompiler.h" // This must be the last #include.
|
|
|
|
class TenantCacheImpl {
|
|
|
|
ACTOR static Future<std::vector<std::pair<int64_t, TenantMapEntry>>> getTenantList(TenantCache* tenantCache,
|
|
Transaction* tr) {
|
|
tr->setOption(FDBTransactionOptions::READ_SYSTEM_KEYS);
|
|
tr->setOption(FDBTransactionOptions::READ_LOCK_AWARE);
|
|
|
|
KeyBackedRangeResult<std::pair<int64_t, TenantMapEntry>> tenantList =
|
|
wait(TenantMetadata::tenantMap().getRange(tr, {}, {}, CLIENT_KNOBS->MAX_TENANTS_PER_CLUSTER + 1));
|
|
ASSERT(tenantList.results.size() <= CLIENT_KNOBS->MAX_TENANTS_PER_CLUSTER && !tenantList.more);
|
|
|
|
return tenantList.results;
|
|
}
|
|
|
|
public:
|
|
ACTOR static Future<Void> build(TenantCache* tenantCache) {
|
|
state Transaction tr(tenantCache->dbcx());
|
|
|
|
TraceEvent(SevInfo, "BuildingTenantCache", tenantCache->id()).log();
|
|
|
|
try {
|
|
state std::vector<std::pair<int64_t, TenantMapEntry>> tenantList = wait(getTenantList(tenantCache, &tr));
|
|
|
|
for (int i = 0; i < tenantList.size(); i++) {
|
|
tenantCache->insert(tenantList[i].second);
|
|
|
|
TraceEvent(SevInfo, "TenantFound", tenantCache->id())
|
|
.detail("TenantName", tenantList[i].second.tenantName)
|
|
.detail("TenantID", tenantList[i].first)
|
|
.detail("TenantPrefix", tenantList[i].second.prefix);
|
|
}
|
|
} catch (Error& e) {
|
|
wait(tr.onError(e));
|
|
}
|
|
|
|
TraceEvent(SevInfo, "BuiltTenantCache", tenantCache->id()).log();
|
|
|
|
return Void();
|
|
}
|
|
|
|
ACTOR static Future<Void> monitorTenantMap(TenantCache* tenantCache) {
|
|
TraceEvent(SevInfo, "StartingTenantCacheMonitor", tenantCache->id()).log();
|
|
|
|
state Transaction tr(tenantCache->dbcx());
|
|
|
|
state double lastTenantListFetchTime = now();
|
|
|
|
loop {
|
|
try {
|
|
if (now() - lastTenantListFetchTime > (2 * SERVER_KNOBS->TENANT_CACHE_LIST_REFRESH_INTERVAL)) {
|
|
TraceEvent(SevWarn, "TenantListRefreshDelay", tenantCache->id()).log();
|
|
}
|
|
|
|
state std::vector<std::pair<int64_t, TenantMapEntry>> tenantList =
|
|
wait(getTenantList(tenantCache, &tr));
|
|
|
|
tenantCache->startRefresh();
|
|
bool tenantListUpdated = false;
|
|
|
|
for (int i = 0; i < tenantList.size(); i++) {
|
|
if (tenantCache->update(tenantList[i].second)) {
|
|
tenantListUpdated = true;
|
|
TenantCacheTenantCreated req(tenantList[i].second.prefix);
|
|
tenantCache->tenantCreationSignal.send(req);
|
|
}
|
|
}
|
|
|
|
if (tenantCache->cleanup()) {
|
|
tenantListUpdated = true;
|
|
}
|
|
|
|
if (tenantListUpdated) {
|
|
TraceEvent(SevInfo, "TenantCache", tenantCache->id()).detail("List", tenantCache->desc());
|
|
}
|
|
|
|
lastTenantListFetchTime = now();
|
|
tr.reset();
|
|
wait(delay(SERVER_KNOBS->TENANT_CACHE_LIST_REFRESH_INTERVAL));
|
|
} catch (Error& e) {
|
|
if (e.code() != error_code_actor_cancelled) {
|
|
TraceEvent("TenantCacheGetTenantListError", tenantCache->id())
|
|
.errorUnsuppressed(e)
|
|
.suppressFor(1.0);
|
|
}
|
|
wait(tr.onError(e));
|
|
}
|
|
}
|
|
}
|
|
|
|
ACTOR static Future<Void> monitorStorageUsage(TenantCache* tenantCache) {
|
|
TraceEvent(SevInfo, "StartingTenantCacheStorageUsageMonitor", tenantCache->id()).log();
|
|
|
|
state int refreshInterval = SERVER_KNOBS->TENANT_CACHE_STORAGE_USAGE_REFRESH_INTERVAL;
|
|
state double lastTenantListFetchTime = now();
|
|
state double lastTraceTime = 0;
|
|
|
|
loop {
|
|
state double fetchStartTime = now();
|
|
|
|
state bool toTrace = false;
|
|
if (fetchStartTime - lastTraceTime > SERVER_KNOBS->TENANT_CACHE_STORAGE_USAGE_TRACE_INTERVAL) {
|
|
toTrace = true;
|
|
lastTraceTime = fetchStartTime;
|
|
}
|
|
|
|
state std::vector<TenantGroupName> groups;
|
|
for (const auto& [group, storage] : tenantCache->tenantStorageMap) {
|
|
groups.push_back(group);
|
|
}
|
|
state int i;
|
|
for (i = 0; i < groups.size(); i++) {
|
|
state TenantGroupName group = groups[i];
|
|
state int64_t usage = 0;
|
|
// `tenants` needs to be a copy so that the erase (below) or inserts/erases from other
|
|
// functions (when this actor yields) do not interfere with the iteration
|
|
state std::unordered_set<int64_t> tenants = tenantCache->tenantStorageMap[group].tenants;
|
|
state std::unordered_set<int64_t>::iterator iter = tenants.begin();
|
|
for (; iter != tenants.end(); iter++) {
|
|
state int64_t tenantId = *iter;
|
|
state ReadYourWritesTransaction tr(tenantCache->dbcx(), makeReference<Tenant>(tenantId));
|
|
loop {
|
|
try {
|
|
state int64_t size = wait(tr.getEstimatedRangeSizeBytes(normalKeys));
|
|
usage += size;
|
|
break;
|
|
} catch (Error& e) {
|
|
if (e.code() == error_code_tenant_not_found) {
|
|
tenantCache->tenantStorageMap[group].tenants.erase(tenantId);
|
|
break;
|
|
} else {
|
|
TraceEvent("TenantCacheGetStorageUsageError", tenantCache->id()).error(e);
|
|
wait(tr.onError(e));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
tenantCache->tenantStorageMap[group].usage = usage;
|
|
|
|
if (toTrace) {
|
|
// Trace the storage used by all tenant groups for visibility.
|
|
TraceEvent(SevInfo, "StorageUsageUpdated", tenantCache->id())
|
|
.detail("TenantGroup", group)
|
|
.detail("Quota", tenantCache->tenantStorageMap[group].quota)
|
|
.detail("Usage", tenantCache->tenantStorageMap[group].usage);
|
|
}
|
|
}
|
|
|
|
lastTenantListFetchTime = now();
|
|
if (lastTenantListFetchTime - fetchStartTime > (2 * refreshInterval)) {
|
|
TraceEvent(SevWarn, "TenantCacheGetStorageUsageRefreshSlow", tenantCache->id()).log();
|
|
}
|
|
wait(delay(refreshInterval));
|
|
}
|
|
}
|
|
|
|
ACTOR static Future<Void> monitorStorageQuota(TenantCache* tenantCache) {
|
|
TraceEvent(SevInfo, "StartingTenantCacheStorageQuotaMonitor", tenantCache->id()).log();
|
|
|
|
state Reference<ReadYourWritesTransaction> tr = tenantCache->dbcx()->createTransaction();
|
|
|
|
loop {
|
|
try {
|
|
tr->setOption(FDBTransactionOptions::READ_SYSTEM_KEYS);
|
|
state KeyBackedRangeResult<std::pair<TenantGroupName, int64_t>> currentQuotas =
|
|
wait(TenantMetadata::storageQuota().getRange(tr, {}, {}, CLIENT_KNOBS->MAX_TENANTS_PER_CLUSTER));
|
|
// Reset the quota for all groups; this essentially sets the quota to `max` for groups where the
|
|
// quota might have been cleared (i.e., groups that will not be returned in `getRange` request above).
|
|
for (auto& [group, storage] : tenantCache->tenantStorageMap) {
|
|
storage.quota = std::numeric_limits<int64_t>::max();
|
|
}
|
|
for (const auto& [groupName, quota] : currentQuotas.results) {
|
|
tenantCache->tenantStorageMap[groupName].quota = quota;
|
|
}
|
|
tr->reset();
|
|
wait(delay(SERVER_KNOBS->TENANT_CACHE_STORAGE_QUOTA_REFRESH_INTERVAL));
|
|
} catch (Error& e) {
|
|
TraceEvent("TenantCacheGetStorageQuotaError", tenantCache->id()).error(e);
|
|
wait(tr->onError(e));
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
void TenantCache::insert(TenantMapEntry& tenant) {
|
|
ASSERT(tenantCache.find(tenant.prefix) == tenantCache.end());
|
|
|
|
TenantInfo tenantInfo(tenant.id, Optional<WipedString>());
|
|
tenantCache[tenantInfo.prefix.get()] = makeReference<TCTenantInfo>(tenantInfo);
|
|
tenantCache[tenantInfo.prefix.get()]->updateCacheGeneration(generation);
|
|
|
|
if (tenant.tenantGroup.present()) {
|
|
tenantStorageMap[tenant.tenantGroup.get()].tenants.insert(tenant.id);
|
|
}
|
|
}
|
|
|
|
void TenantCache::startRefresh() {
|
|
ASSERT(generation < std::numeric_limits<uint64_t>::max());
|
|
generation++;
|
|
}
|
|
|
|
void TenantCache::keep(TenantMapEntry& tenant) {
|
|
ASSERT(tenantCache.find(tenant.prefix) != tenantCache.end());
|
|
tenantCache[tenant.prefix]->updateCacheGeneration(generation);
|
|
}
|
|
|
|
bool TenantCache::update(TenantMapEntry& tenant) {
|
|
if (tenantCache.find(tenant.prefix) != tenantCache.end()) {
|
|
keep(tenant);
|
|
return false;
|
|
}
|
|
|
|
insert(tenant);
|
|
return true;
|
|
}
|
|
|
|
int TenantCache::cleanup() {
|
|
int tenantsRemoved = 0;
|
|
std::vector<Key> keysToErase;
|
|
|
|
for (auto& t : tenantCache) {
|
|
ASSERT(t.value->cacheGeneration() <= generation);
|
|
if (t.value->cacheGeneration() != generation) {
|
|
keysToErase.push_back(t.key);
|
|
}
|
|
}
|
|
|
|
for (auto& k : keysToErase) {
|
|
tenantCache.erase(k);
|
|
tenantsRemoved++;
|
|
}
|
|
|
|
return tenantsRemoved;
|
|
}
|
|
|
|
std::vector<int64_t> TenantCache::getTenantList() const {
|
|
std::vector<int64_t> tenants;
|
|
for (const auto& [prefix, entry] : tenantCache) {
|
|
tenants.push_back(entry->id());
|
|
}
|
|
return tenants;
|
|
}
|
|
|
|
std::string TenantCache::desc() const {
|
|
std::string s("@Generation: ");
|
|
s += std::to_string(generation) + " ";
|
|
int count = 0;
|
|
for (auto& [tenantPrefix, tenant] : tenantCache) {
|
|
if (count) {
|
|
s += ", ";
|
|
}
|
|
|
|
s += "ID: " + std::to_string(tenant->id()) + " Prefix: " + tenantPrefix.toString();
|
|
count++;
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
bool TenantCache::isTenantKey(KeyRef key) const {
|
|
auto it = tenantCache.lastLessOrEqual(key);
|
|
if (it == tenantCache.end()) {
|
|
return false;
|
|
}
|
|
|
|
if (!key.startsWith(it->key)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
Future<Void> TenantCache::build() {
|
|
return TenantCacheImpl::build(this);
|
|
}
|
|
|
|
Optional<Reference<TCTenantInfo>> TenantCache::tenantOwning(KeyRef key) const {
|
|
auto it = tenantCache.lastLessOrEqual(key);
|
|
if (it == tenantCache.end()) {
|
|
return {};
|
|
}
|
|
|
|
if (!key.startsWith(it->key)) {
|
|
return {};
|
|
}
|
|
|
|
return it->value;
|
|
}
|
|
|
|
std::unordered_set<int64_t> TenantCache::getTenantsOverQuota() const {
|
|
std::unordered_set<int64_t> tenantsOverQuota;
|
|
for (const auto& [tenantGroup, storage] : tenantStorageMap) {
|
|
if (storage.usage > storage.quota) {
|
|
tenantsOverQuota.insert(storage.tenants.begin(), storage.tenants.end());
|
|
}
|
|
}
|
|
return tenantsOverQuota;
|
|
}
|
|
|
|
Future<Void> TenantCache::monitorTenantMap() {
|
|
return TenantCacheImpl::monitorTenantMap(this);
|
|
}
|
|
|
|
Future<Void> TenantCache::monitorStorageUsage() {
|
|
return TenantCacheImpl::monitorStorageUsage(this);
|
|
}
|
|
|
|
Future<Void> TenantCache::monitorStorageQuota() {
|
|
return TenantCacheImpl::monitorStorageQuota(this);
|
|
}
|
|
|
|
class TenantCacheUnitTest {
|
|
public:
|
|
ACTOR static Future<Void> InsertAndTestPresence() {
|
|
wait(Future<Void>(Void()));
|
|
|
|
Database cx;
|
|
TenantCache tenantCache(cx, UID(1, 0));
|
|
|
|
constexpr static uint16_t tenantLimit = 64;
|
|
|
|
uint16_t tenantCount = deterministicRandom()->randomInt(1, tenantLimit);
|
|
uint16_t tenantNumber = deterministicRandom()->randomInt(0, std::numeric_limits<uint16_t>::max());
|
|
|
|
for (uint16_t i = 0; i < tenantCount; i++) {
|
|
TenantName tenantName(format("%s_%08d", "ddtc_test_tenant", tenantNumber + i));
|
|
TenantMapEntry tenant(tenantNumber + i, tenantName);
|
|
|
|
tenantCache.insert(tenant);
|
|
}
|
|
|
|
for (int i = 0; i < tenantLimit; i++) {
|
|
Key k(format("%d", i));
|
|
ASSERT(tenantCache.isTenantKey(k.withPrefix(TenantAPI::idToPrefix(tenantNumber + (i % tenantCount)))));
|
|
ASSERT(!tenantCache.isTenantKey(k.withPrefix(allKeys.begin)));
|
|
ASSERT(!tenantCache.isTenantKey(k));
|
|
}
|
|
|
|
return Void();
|
|
}
|
|
|
|
ACTOR static Future<Void> RefreshAndTestPresence() {
|
|
wait(Future<Void>(Void()));
|
|
|
|
Database cx;
|
|
TenantCache tenantCache(cx, UID(1, 0));
|
|
|
|
constexpr static uint16_t tenantLimit = 64;
|
|
|
|
uint16_t tenantCount = deterministicRandom()->randomInt(1, tenantLimit);
|
|
uint16_t tenantNumber = deterministicRandom()->randomInt(0, std::numeric_limits<uint16_t>::max());
|
|
|
|
for (uint16_t i = 0; i < tenantCount; i++) {
|
|
uint16_t tenantOrdinal = tenantNumber + i;
|
|
TenantName tenantName(format("%s_%08d", "ddtc_test_tenant", tenantOrdinal));
|
|
TenantMapEntry tenant(tenantOrdinal, tenantName);
|
|
|
|
tenantCache.insert(tenant);
|
|
}
|
|
|
|
uint16_t staleTenantFraction = deterministicRandom()->randomInt(1, 8);
|
|
tenantCache.startRefresh();
|
|
|
|
int keepCount = 0, removeCount = 0;
|
|
for (int i = 0; i < tenantCount; i++) {
|
|
uint16_t tenantOrdinal = tenantNumber + i;
|
|
|
|
if (tenantOrdinal % staleTenantFraction != 0) {
|
|
TenantName tenantName(format("%s_%08d", "ddtc_test_tenant", tenantOrdinal));
|
|
TenantMapEntry tenant(tenantOrdinal, tenantName);
|
|
bool newTenant = tenantCache.update(tenant);
|
|
ASSERT(!newTenant);
|
|
keepCount++;
|
|
} else {
|
|
removeCount++;
|
|
}
|
|
}
|
|
int tenantsRemoved = tenantCache.cleanup();
|
|
ASSERT(tenantsRemoved == removeCount);
|
|
|
|
int keptCount = 0, removedCount = 0;
|
|
for (int i = 0; i < tenantCount; i++) {
|
|
uint16_t tenantOrdinal = tenantNumber + i;
|
|
Key k(format("%d", i));
|
|
if (tenantOrdinal % staleTenantFraction != 0) {
|
|
ASSERT(tenantCache.isTenantKey(k.withPrefix(TenantAPI::idToPrefix(tenantOrdinal))));
|
|
keptCount++;
|
|
} else {
|
|
ASSERT(!tenantCache.isTenantKey(k.withPrefix(TenantAPI::idToPrefix(tenantOrdinal))));
|
|
removedCount++;
|
|
}
|
|
}
|
|
|
|
ASSERT(keepCount == keptCount);
|
|
ASSERT(removeCount == removedCount);
|
|
|
|
return Void();
|
|
}
|
|
};
|
|
|
|
TEST_CASE("/TenantCache/InsertAndTestPresence") {
|
|
wait(TenantCacheUnitTest::InsertAndTestPresence());
|
|
return Void();
|
|
}
|
|
|
|
TEST_CASE("/TenantCache/RefreshAndTestPresence") {
|
|
wait(TenantCacheUnitTest::RefreshAndTestPresence());
|
|
return Void();
|
|
}
|