Merge remote-tracking branch 'origin/master' into config-db
This commit is contained in:
commit
41c790b299
|
|
@ -8,6 +8,7 @@ bindings/java/foundationdb-tests*.jar
|
|||
bindings/java/fdb-java-*-sources.jar
|
||||
packaging/msi/FDBInstaller.msi
|
||||
builds/
|
||||
cmake-build-debug/
|
||||
# Generated source, build, and packaging files
|
||||
*.g.cpp
|
||||
*.g.h
|
||||
|
|
|
|||
|
|
@ -78,6 +78,8 @@ if(NOT WIN32)
|
|||
test/unit/fdb_api.cpp
|
||||
test/unit/fdb_api.hpp)
|
||||
|
||||
set(UNIT_TEST_VERSION_510_SRCS test/unit/unit_tests_version_510.cpp)
|
||||
|
||||
if(OPEN_FOR_IDE)
|
||||
add_library(fdb_c_performance_test OBJECT test/performance_test.c test/test.h)
|
||||
add_library(fdb_c_ryw_benchmark OBJECT test/ryw_benchmark.c test/test.h)
|
||||
|
|
@ -85,6 +87,7 @@ if(NOT WIN32)
|
|||
add_library(mako OBJECT ${MAKO_SRCS})
|
||||
add_library(fdb_c_setup_tests OBJECT test/unit/setup_tests.cpp)
|
||||
add_library(fdb_c_unit_tests OBJECT ${UNIT_TEST_SRCS})
|
||||
add_library(fdb_c_unit_tests_version_510 OBJECT ${UNIT_TEST_VERSION_510_SRCS})
|
||||
else()
|
||||
add_executable(fdb_c_performance_test test/performance_test.c test/test.h)
|
||||
add_executable(fdb_c_ryw_benchmark test/ryw_benchmark.c test/test.h)
|
||||
|
|
@ -92,6 +95,7 @@ if(NOT WIN32)
|
|||
add_executable(mako ${MAKO_SRCS})
|
||||
add_executable(fdb_c_setup_tests test/unit/setup_tests.cpp)
|
||||
add_executable(fdb_c_unit_tests ${UNIT_TEST_SRCS})
|
||||
add_executable(fdb_c_unit_tests_version_510 ${UNIT_TEST_VERSION_510_SRCS})
|
||||
strip_debug_symbols(fdb_c_performance_test)
|
||||
strip_debug_symbols(fdb_c_ryw_benchmark)
|
||||
strip_debug_symbols(fdb_c_txn_size_test)
|
||||
|
|
@ -104,8 +108,10 @@ if(NOT WIN32)
|
|||
add_dependencies(fdb_c_unit_tests doctest)
|
||||
target_include_directories(fdb_c_setup_tests PUBLIC ${DOCTEST_INCLUDE_DIR})
|
||||
target_include_directories(fdb_c_unit_tests PUBLIC ${DOCTEST_INCLUDE_DIR})
|
||||
target_include_directories(fdb_c_unit_tests_version_510 PUBLIC ${DOCTEST_INCLUDE_DIR})
|
||||
target_link_libraries(fdb_c_setup_tests PRIVATE fdb_c Threads::Threads)
|
||||
target_link_libraries(fdb_c_unit_tests PRIVATE fdb_c Threads::Threads)
|
||||
target_link_libraries(fdb_c_unit_tests_version_510 PRIVATE fdb_c Threads::Threads)
|
||||
|
||||
# do not set RPATH for mako
|
||||
set_property(TARGET mako PROPERTY SKIP_BUILD_RPATH TRUE)
|
||||
|
|
@ -135,6 +141,11 @@ if(NOT WIN32)
|
|||
COMMAND $<TARGET_FILE:fdb_c_unit_tests>
|
||||
@CLUSTER_FILE@
|
||||
fdb)
|
||||
add_fdbclient_test(
|
||||
NAME fdb_c_unit_tests_version_510
|
||||
COMMAND $<TARGET_FILE:fdb_c_unit_tests_version_510>
|
||||
@CLUSTER_FILE@
|
||||
fdb)
|
||||
add_fdbclient_test(
|
||||
NAME fdb_c_external_client_unit_tests
|
||||
COMMAND $<TARGET_FILE:fdb_c_unit_tests>
|
||||
|
|
@ -158,6 +169,10 @@ set_target_properties(c_workloads PROPERTIES
|
|||
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/share/foundationdb")
|
||||
target_link_libraries(c_workloads PUBLIC fdb_c)
|
||||
|
||||
if (NOT WIN32 AND NOT APPLE AND NOT OPEN_FOR_IDE)
|
||||
target_link_options(c_workloads PRIVATE "LINKER:--version-script=${CMAKE_CURRENT_SOURCE_DIR}/external_workload.map,-z,nodelete")
|
||||
endif()
|
||||
|
||||
# TODO: re-enable once the old vcxproj-based build system is removed.
|
||||
#generate_export_header(fdb_c EXPORT_MACRO_NAME "DLLEXPORT"
|
||||
# EXPORT_FILE_NAME ${CMAKE_CURRENT_BINARY_DIR}/foundationdb/fdb_c_export.h)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
global:
|
||||
workloadFactory;
|
||||
local:
|
||||
*;
|
||||
};
|
||||
|
||||
|
|
@ -74,10 +74,41 @@ def write_unix_asm(asmfile, functions, prefix):
|
|||
for f in functions:
|
||||
asmfile.write("\n.globl %s%s\n" % (prefix, f))
|
||||
asmfile.write("%s%s:\n" % (prefix, f))
|
||||
|
||||
# These assembly implementations of versioned fdb c api functions must have the following properties.
|
||||
#
|
||||
# 1. Don't require dynamic relocation.
|
||||
#
|
||||
# 2. Perform a tail-call to the function pointer that works for a
|
||||
# function with any number of arguments. For example, since registers x0-x7 are used
|
||||
# to pass arguments in the Arm calling convention we must not use x0-x7
|
||||
# here.
|
||||
#
|
||||
# You can compile this example c program to get a rough idea of how to
|
||||
# load the extern symbol and make a tail call.
|
||||
#
|
||||
# $ cat test.c
|
||||
# typedef int (*function)();
|
||||
# extern function f;
|
||||
# int g() { return f(); }
|
||||
# $ cc -S -O3 -fPIC test.c && grep -A 10 '^g:' test.[sS]
|
||||
# g:
|
||||
# .LFB0:
|
||||
# .cfi_startproc
|
||||
# adrp x0, :got:f
|
||||
# ldr x0, [x0, #:got_lo12:f]
|
||||
# ldr x0, [x0]
|
||||
# br x0
|
||||
# .cfi_endproc
|
||||
# .LFE0:
|
||||
# .size g, .-g
|
||||
# .ident "GCC: (GNU) 8.3.1 20190311 (Red Hat 8.3.1-3)"
|
||||
|
||||
if platform == "linux-aarch64":
|
||||
asmfile.write("\tldr x16, =fdb_api_ptr_%s\n" % (f))
|
||||
asmfile.write("\tldr x16, [x16]\n")
|
||||
asmfile.write("\tbr x16\n")
|
||||
asmfile.write("\tadrp x8, :got:fdb_api_ptr_%s\n" % (f))
|
||||
asmfile.write("\tldr x8, [x8, #:got_lo12:fdb_api_ptr_%s]\n" % (f))
|
||||
asmfile.write("\tldr x8, [x8]\n")
|
||||
asmfile.write("\tbr x8\n")
|
||||
else:
|
||||
asmfile.write(
|
||||
"\tmov r11, qword ptr [%sfdb_api_ptr_%s@GOTPCREL+rip]\n" % (prefix, f))
|
||||
|
|
|
|||
|
|
@ -151,18 +151,46 @@ void* fdb_network_thread(void* args) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
int genprefix(char* str, char* prefix, int prefixlen, int prefixpadding, int rows, int len) {
|
||||
const int rowdigit = digits(rows);
|
||||
const int paddinglen = len - (prefixlen + rowdigit) - 1;
|
||||
int offset = 0;
|
||||
if (prefixpadding) {
|
||||
memset(str, 'x', paddinglen);
|
||||
offset += paddinglen;
|
||||
}
|
||||
memcpy(str + offset, prefix, prefixlen);
|
||||
str[len - 1] = '\0';
|
||||
return offset + prefixlen;
|
||||
}
|
||||
|
||||
|
||||
/* cleanup database */
|
||||
int cleanup(FDBTransaction* transaction, mako_args_t* args) {
|
||||
struct timespec timer_start, timer_end;
|
||||
char beginstr[7];
|
||||
char endstr[7];
|
||||
char* prefixstr = (char*)malloc(sizeof(char) * args->key_length + 1);
|
||||
if (!prefixstr)
|
||||
return -1;
|
||||
char* beginstr = (char*)malloc(sizeof(char) * args->key_length + 1);
|
||||
if (!beginstr) {
|
||||
free(prefixstr);
|
||||
return -1;
|
||||
}
|
||||
char* endstr = (char*)malloc(sizeof(char) * args->key_length + 1);
|
||||
if (!endstr) {
|
||||
free(prefixstr);
|
||||
free(beginstr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int len = genprefix(prefixstr, KEYPREFIX, KEYPREFIXLEN, args->prefixpadding, args->rows, args->key_length + 1);
|
||||
snprintf(beginstr, len + 2, "%s%c", prefixstr, 0x00);
|
||||
snprintf(endstr, len + 2, "%s%c", prefixstr, 0xff);
|
||||
free(prefixstr);
|
||||
len += 1;
|
||||
|
||||
strncpy(beginstr, "mako", 4);
|
||||
beginstr[4] = 0x00;
|
||||
strncpy(endstr, "mako", 4);
|
||||
endstr[4] = 0xff;
|
||||
clock_gettime(CLOCK_MONOTONIC_COARSE, &timer_start);
|
||||
fdb_transaction_clear_range(transaction, (uint8_t*)beginstr, 5, (uint8_t*)endstr, 5);
|
||||
fdb_transaction_clear_range(transaction, (uint8_t*)beginstr, len + 1, (uint8_t*)endstr, len + 1);
|
||||
if (commit_transaction(transaction) != FDB_SUCCESS)
|
||||
goto failExit;
|
||||
|
||||
|
|
@ -172,9 +200,16 @@ int cleanup(FDBTransaction* transaction, mako_args_t* args) {
|
|||
"INFO: Clear range: %6.3f sec\n",
|
||||
((timer_end.tv_sec - timer_start.tv_sec) * 1000000000.0 + timer_end.tv_nsec - timer_start.tv_nsec) /
|
||||
1000000000);
|
||||
|
||||
free(beginstr);
|
||||
free(endstr);
|
||||
|
||||
return 0;
|
||||
|
||||
failExit:
|
||||
free(beginstr);
|
||||
free(endstr);
|
||||
|
||||
fprintf(stderr, "ERROR: FDB failure in cleanup()\n");
|
||||
return -1;
|
||||
}
|
||||
|
|
@ -220,7 +255,7 @@ int populate(FDBTransaction* transaction,
|
|||
for (i = begin; i <= end; i++) {
|
||||
|
||||
/* sequential keys */
|
||||
genkey(keystr, i, args->rows, args->key_length + 1);
|
||||
genkey(keystr, KEYPREFIX, KEYPREFIXLEN, args->prefixpadding, i, args->rows, args->key_length + 1);
|
||||
/* random values */
|
||||
randstr(valstr, args->value_length + 1);
|
||||
|
||||
|
|
@ -512,7 +547,7 @@ retryTxn:
|
|||
} else {
|
||||
keynum = urand(0, args->rows - 1);
|
||||
}
|
||||
genkey(keystr, keynum, args->rows, args->key_length + 1);
|
||||
genkey(keystr, KEYPREFIX, KEYPREFIXLEN, args->prefixpadding, keynum, args->rows, args->key_length + 1);
|
||||
|
||||
/* range */
|
||||
if (args->txnspec.ops[i][OP_RANGE] > 0) {
|
||||
|
|
@ -520,7 +555,7 @@ retryTxn:
|
|||
if (keyend > args->rows - 1) {
|
||||
keyend = args->rows - 1;
|
||||
}
|
||||
genkey(keystr2, keyend, args->rows, args->key_length + 1);
|
||||
genkey(keystr2, KEYPREFIX, KEYPREFIXLEN, args->prefixpadding, keyend, args->rows, args->key_length + 1);
|
||||
}
|
||||
|
||||
if (stats->xacts % args->sampling == 0) {
|
||||
|
|
@ -1354,6 +1389,7 @@ int init_args(mako_args_t* args) {
|
|||
args->flatbuffers = 0; /* internal */
|
||||
args->knobs[0] = '\0';
|
||||
args->log_group[0] = '\0';
|
||||
args->prefixpadding = 0;
|
||||
args->trace = 0;
|
||||
args->tracepath[0] = '\0';
|
||||
args->traceformat = 0; /* default to client's default (XML) */
|
||||
|
|
@ -1515,6 +1551,7 @@ void usage() {
|
|||
printf("%-24s %s\n", "-z, --zipf", "Use zipfian distribution instead of uniform distribution");
|
||||
printf("%-24s %s\n", " --commitget", "Commit GETs");
|
||||
printf("%-24s %s\n", " --loggroup=LOGGROUP", "Set client log group");
|
||||
printf("%-24s %s\n", " --prefix_padding", "Pad key by prefixing data (Default: postfix padding)");
|
||||
printf("%-24s %s\n", " --trace", "Enable tracing");
|
||||
printf("%-24s %s\n", " --tracepath=PATH", "Set trace file path");
|
||||
printf("%-24s %s\n", " --trace_format <xml|json>", "Set trace format (Default: json)");
|
||||
|
|
@ -1567,6 +1604,7 @@ int parse_args(int argc, char* argv[], mako_args_t* args) {
|
|||
{ "zipf", no_argument, NULL, 'z' },
|
||||
{ "commitget", no_argument, NULL, ARG_COMMITGET },
|
||||
{ "flatbuffers", no_argument, NULL, ARG_FLATBUFFERS },
|
||||
{ "prefix_padding", no_argument, NULL, ARG_PREFIXPADDING },
|
||||
{ "trace", no_argument, NULL, ARG_TRACE },
|
||||
{ "txntagging", required_argument, NULL, ARG_TXNTAGGING },
|
||||
{ "txntagging_prefix", required_argument, NULL, ARG_TXNTAGGINGPREFIX },
|
||||
|
|
@ -1670,6 +1708,9 @@ int parse_args(int argc, char* argv[], mako_args_t* args) {
|
|||
case ARG_LOGGROUP:
|
||||
memcpy(args->log_group, optarg, strlen(optarg) + 1);
|
||||
break;
|
||||
case ARG_PREFIXPADDING:
|
||||
args->prefixpadding = 1;
|
||||
break;
|
||||
case ARG_TRACE:
|
||||
args->trace = 1;
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ enum Arguments {
|
|||
ARG_KNOBS,
|
||||
ARG_FLATBUFFERS,
|
||||
ARG_LOGGROUP,
|
||||
ARG_PREFIXPADDING,
|
||||
ARG_TRACE,
|
||||
ARG_TRACEPATH,
|
||||
ARG_TRACEFORMAT,
|
||||
|
|
@ -125,6 +126,7 @@ typedef struct {
|
|||
mako_txnspec_t txnspec;
|
||||
char cluster_file[PATH_MAX];
|
||||
char log_group[LOGGROUP_MAX];
|
||||
int prefixpadding;
|
||||
int trace;
|
||||
char tracepath[PATH_MAX];
|
||||
int traceformat; /* 0 - XML, 1 - JSON */
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* uniform-distribution random */
|
||||
int urand(int low, int high) {
|
||||
|
|
@ -67,15 +68,16 @@ int digits(int num) {
|
|||
}
|
||||
|
||||
/* generate a key for a given key number */
|
||||
/* prefix is "mako" by default, prefixpadding = 1 means 'x' will be in front rather than trailing the keyname */
|
||||
/* len is the buffer size, key length + null */
|
||||
void genkey(char* str, int num, int rows, int len) {
|
||||
int i;
|
||||
int rowdigit = digits(rows);
|
||||
sprintf(str, KEYPREFIX "%0.*d", rowdigit, num);
|
||||
for (i = (KEYPREFIXLEN + rowdigit); i < len - 1; i++) {
|
||||
str[i] = 'x';
|
||||
}
|
||||
str[len - 1] = '\0';
|
||||
void genkey(char* str, char* prefix, int prefixlen, int prefixpadding, int num, int rows, int len) {
|
||||
const int rowdigit = digits(rows);
|
||||
const int prefixoffset = prefixpadding ? len - (prefixlen + rowdigit) - 1 : 0;
|
||||
char* prefixstr = (char*)alloca(sizeof(char) * (prefixlen + rowdigit + 1));
|
||||
snprintf(prefixstr, prefixlen + rowdigit + 1, "%s%0.*d", prefix, rowdigit, num);
|
||||
memset(str, 'x', len);
|
||||
memcpy(str + prefixoffset, prefixstr, prefixlen + rowdigit);
|
||||
str[len - 1] = '\0';
|
||||
}
|
||||
|
||||
/* This is another sorting algorithm used to calculate latency parameters */
|
||||
|
|
|
|||
|
|
@ -47,8 +47,9 @@ int compute_thread_portion(int val, int p_idx, int t_idx, int total_p, int total
|
|||
int digits(int num);
|
||||
|
||||
/* generate a key for a given key number */
|
||||
/* prefix is "mako" by default, prefixpadding = 1 means 'x' will be in front rather than trailing the keyname */
|
||||
/* len is the buffer size, key length + null */
|
||||
void genkey(char* str, int num, int rows, int len);
|
||||
void genkey(char* str, char* prefix, int prefixlen, int prefixpadding, int num, int rows, int len);
|
||||
|
||||
#if 0
|
||||
// The main function is to sort arr[] of size n using Radix Sort
|
||||
|
|
|
|||
|
|
@ -219,7 +219,7 @@ GetRangeResult get_range(fdb::Transaction& tr,
|
|||
for (int i = 0; i < out_count; ++i) {
|
||||
std::string key((const char*)out_kv[i].key, out_kv[i].key_length);
|
||||
std::string value((const char*)out_kv[i].value, out_kv[i].value_length);
|
||||
results.push_back(std::make_pair(key, value));
|
||||
results.emplace_back(key, value);
|
||||
}
|
||||
return GetRangeResult{ results, out_more != 0, 0 };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
* unit_tests_header_510.cpp
|
||||
*
|
||||
* This source file is part of the FoundationDB open source project
|
||||
*
|
||||
* Copyright 2013-2021 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.
|
||||
*/
|
||||
|
||||
// Unit tests for the FoundationDB C API, at api header version 510
|
||||
|
||||
#include "fdb_c_options.g.h"
|
||||
#include <thread>
|
||||
|
||||
#define FDB_API_VERSION 510
|
||||
static_assert(FDB_API_VERSION == 510, "Don't change this! This test intentionally tests an old api header version");
|
||||
|
||||
#include <foundationdb/fdb_c.h>
|
||||
|
||||
#define DOCTEST_CONFIG_IMPLEMENT
|
||||
#include "doctest.h"
|
||||
|
||||
#include "flow/config.h"
|
||||
|
||||
void fdb_check(fdb_error_t e) {
|
||||
if (e) {
|
||||
std::cerr << fdb_get_error(e) << std::endl;
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
std::string clusterFilePath;
|
||||
std::string prefix;
|
||||
|
||||
FDBDatabase* db;
|
||||
|
||||
struct Future {
|
||||
FDBFuture* f = nullptr;
|
||||
Future() = default;
|
||||
explicit Future(FDBFuture* f) : f(f) {}
|
||||
~Future() {
|
||||
if (f)
|
||||
fdb_future_destroy(f);
|
||||
}
|
||||
};
|
||||
|
||||
struct Transaction {
|
||||
FDBTransaction* tr = nullptr;
|
||||
Transaction() = default;
|
||||
explicit Transaction(FDBTransaction* tr) : tr(tr) {}
|
||||
~Transaction() {
|
||||
if (tr)
|
||||
fdb_transaction_destroy(tr);
|
||||
}
|
||||
};
|
||||
|
||||
// TODO add more tests. The motivation for this test for now is to test the
|
||||
// assembly code that handles emulating older api versions, but there's no
|
||||
// reason why this shouldn't also test api version 510 specific behavior.
|
||||
|
||||
TEST_CASE("GRV") {
|
||||
Transaction tr;
|
||||
fdb_check(fdb_database_create_transaction(db, &tr.tr));
|
||||
Future grv{ fdb_transaction_get_read_version(tr.tr) };
|
||||
fdb_check(fdb_future_block_until_ready(grv.f));
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if (argc < 3) {
|
||||
std::cout << "Unit tests for the FoundationDB C API.\n"
|
||||
<< "Usage: " << argv[0] << " /path/to/cluster_file key_prefix [doctest args]" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
fdb_check(fdb_select_api_version(FDB_API_VERSION));
|
||||
|
||||
doctest::Context context;
|
||||
context.applyCommandLine(argc, argv);
|
||||
|
||||
fdb_check(fdb_setup_network());
|
||||
std::thread network_thread{ &fdb_run_network };
|
||||
|
||||
{
|
||||
FDBCluster* cluster;
|
||||
Future clusterFuture{ fdb_create_cluster(argv[1]) };
|
||||
fdb_check(fdb_future_block_until_ready(clusterFuture.f));
|
||||
fdb_check(fdb_future_get_cluster(clusterFuture.f, &cluster));
|
||||
Future databaseFuture{ fdb_cluster_create_database(cluster, (const uint8_t*)"DB", 2) };
|
||||
fdb_check(fdb_future_block_until_ready(databaseFuture.f));
|
||||
fdb_check(fdb_future_get_database(databaseFuture.f, &db));
|
||||
fdb_cluster_destroy(cluster);
|
||||
}
|
||||
|
||||
clusterFilePath = std::string(argv[1]);
|
||||
prefix = argv[2];
|
||||
int res = context.run();
|
||||
fdb_database_destroy(db);
|
||||
|
||||
if (context.shouldExit()) {
|
||||
fdb_check(fdb_stop_network());
|
||||
network_thread.join();
|
||||
return res;
|
||||
}
|
||||
fdb_check(fdb_stop_network());
|
||||
network_thread.join();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
|
@ -138,6 +138,11 @@ else()
|
|||
add_library(fdb_java SHARED fdbJNI.cpp)
|
||||
add_library(java_workloads SHARED JavaWorkload.cpp)
|
||||
endif()
|
||||
|
||||
if (NOT WIN32 AND NOT APPLE AND NOT OPEN_FOR_IDE)
|
||||
target_link_options(java_workloads PRIVATE "LINKER:--version-script=${CMAKE_SOURCE_DIR}/bindings/c/external_workload.map,-z,nodelete")
|
||||
endif()
|
||||
|
||||
target_include_directories(fdb_java PRIVATE ${JNI_INCLUDE_DIRS})
|
||||
# libfdb_java.so is loaded by fdb-java.jar and doesn't need to depened on jvm shared libraries.
|
||||
target_link_libraries(fdb_java PRIVATE fdb_c)
|
||||
|
|
|
|||
|
|
@ -74,3 +74,12 @@ add_custom_command(OUTPUT ${package_file}
|
|||
add_custom_target(python_package DEPENDS ${package_file})
|
||||
add_dependencies(python_package python_binding)
|
||||
add_dependencies(packages python_package)
|
||||
|
||||
if (NOT WIN32 AND NOT OPEN_FOR_IDE)
|
||||
add_fdbclient_test(
|
||||
NAME fdbcli_tests
|
||||
COMMAND ${CMAKE_SOURCE_DIR}/bindings/python/tests/fdbcli_tests.py
|
||||
${CMAKE_BINARY_DIR}/bin/fdbcli
|
||||
@CLUSTER_FILE@
|
||||
)
|
||||
endif()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,93 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import sys
|
||||
import subprocess
|
||||
import logging
|
||||
import functools
|
||||
|
||||
def enable_logging(level=logging.ERROR):
|
||||
"""Enable logging in the function with the specified logging level
|
||||
|
||||
Args:
|
||||
level (logging.<level>, optional): logging level for the decorated function. Defaults to logging.ERROR.
|
||||
"""
|
||||
def func_decorator(func):
|
||||
@functools.wraps(func)
|
||||
def wrapper(*args,**kwargs):
|
||||
# initialize logger
|
||||
logger = logging.getLogger(func.__name__)
|
||||
logger.setLevel(level)
|
||||
# set logging format
|
||||
handler = logging.StreamHandler()
|
||||
handler_format = logging.Formatter('[%(asctime)s] - %(filename)s:%(lineno)d - %(levelname)s - %(name)s - %(message)s')
|
||||
handler.setFormatter(handler_format)
|
||||
handler.setLevel(level)
|
||||
logger.addHandler(handler)
|
||||
# pass the logger to the decorated function
|
||||
result = func(logger, *args,**kwargs)
|
||||
return result
|
||||
return wrapper
|
||||
return func_decorator
|
||||
|
||||
def run_fdbcli_command(*args):
|
||||
"""run the fdbcli statement: fdbcli --exec '<arg1> <arg2> ... <argN>'.
|
||||
|
||||
Returns:
|
||||
string: Console output from fdbcli
|
||||
"""
|
||||
commands = command_template + ["{}".format(' '.join(args))]
|
||||
return subprocess.run(commands, stdout=subprocess.PIPE).stdout.decode('utf-8').strip()
|
||||
|
||||
@enable_logging()
|
||||
def advanceversion(logger):
|
||||
# get current read version
|
||||
version1 = int(run_fdbcli_command('getversion'))
|
||||
logger.debug("Read version: {}".format(version1))
|
||||
# advance version to a much larger value compared to the current version
|
||||
version2 = version1 * 10000
|
||||
logger.debug("Advanced to version: " + str(version2))
|
||||
run_fdbcli_command('advanceversion', str(version2))
|
||||
# after running the advanceversion command,
|
||||
# check the read version is advanced to the specified value
|
||||
version3 = int(run_fdbcli_command('getversion'))
|
||||
logger.debug("Read version: {}".format(version3))
|
||||
assert version3 >= version2
|
||||
# advance version to a smaller value compared to the current version
|
||||
# this should be a no-op
|
||||
run_fdbcli_command('advanceversion', str(version1))
|
||||
# get the current version to make sure the version did not decrease
|
||||
version4 = int(run_fdbcli_command('getversion'))
|
||||
logger.debug("Read version: {}".format(version4))
|
||||
assert version4 >= version3
|
||||
|
||||
@enable_logging()
|
||||
def maintenance(logger):
|
||||
# expected fdbcli output when running 'maintenance' while there's no ongoing maintenance
|
||||
no_maintenance_output = 'No ongoing maintenance.'
|
||||
output1 = run_fdbcli_command('maintenance')
|
||||
assert output1 == no_maintenance_output
|
||||
# set maintenance on a fake zone id for 10 seconds
|
||||
run_fdbcli_command('maintenance', 'on', 'fake_zone_id', '10')
|
||||
# show current maintenance status
|
||||
output2 = run_fdbcli_command('maintenance')
|
||||
logger.debug("Maintenance status: " + output2)
|
||||
items = output2.split(' ')
|
||||
# make sure this specific zone id is under maintenance
|
||||
assert 'fake_zone_id' in items
|
||||
logger.debug("Remaining time(seconds): " + items[-2])
|
||||
assert 0 < int(items[-2]) < 10
|
||||
# turn off maintenance
|
||||
run_fdbcli_command('maintenance', 'off')
|
||||
# check maintenance status
|
||||
output3 = run_fdbcli_command('maintenance')
|
||||
assert output3 == no_maintenance_output
|
||||
|
||||
if __name__ == '__main__':
|
||||
# fdbcli_tests.py <path_to_fdbcli_binary> <path_to_fdb_cluster_file>
|
||||
assert len(sys.argv) == 3, "Please pass arguments: <path_to_fdbcli_binary> <path_to_fdb_cluster_file>"
|
||||
# shell command template
|
||||
command_template = [sys.argv[1], '-C', sys.argv[2], '--exec']
|
||||
# tests for fdbcli commands
|
||||
# assertions will fail if fdbcli does not work as expected
|
||||
advanceversion()
|
||||
maintenance()
|
||||
|
|
@ -59,7 +59,7 @@ It can be stopped and prevented from starting at boot as follows::
|
|||
Start, stop and restart behavior
|
||||
=================================
|
||||
|
||||
These commands above start and stop the master ``fdbmonitor`` process, which in turn starts ``fdbserver`` and ``backup-agent`` processes. See :ref:`administration_fdbmonitor` for details.
|
||||
These commands above start and stop the ``fdbmonitor`` process, which in turn starts ``fdbserver`` and ``backup-agent`` processes. See :ref:`administration_fdbmonitor` for details.
|
||||
|
||||
After any child process has terminated by any reason, ``fdbmonitor`` tries to restart it. See :ref:`restarting parameters <configuration-restarting>`.
|
||||
|
||||
|
|
|
|||
|
|
@ -971,7 +971,7 @@ For example, you can change a process type or update coordinators by manipulatin
|
|||
|
||||
#. ``\xff\xff/configuration/process/class_type/<address> := <class_type>`` Read/write. Reading keys in the range will retrieve processes' class types. Setting keys in the range will update processes' class types. The process matching ``<address>`` will be assigned to the given class type if the commit is successful. The valid class types are ``storage``, ``transaction``, ``resolution``, etc. A full list of class type can be found via ``fdbcli`` command ``help setclass``. Clearing keys is forbidden in the range. Instead, you can set the type as ``default``, which will clear the assigned class type if existing. For more details, see help text of ``fdbcli`` command ``setclass``.
|
||||
#. ``\xff\xff/configuration/process/class_source/<address> := <class_source>`` Read-only. Reading keys in the range will retrieve processes' class source. The class source is one of ``command_line``, ``configure_auto``, ``set_class`` and ``invalid``, indicating the source that the process's class type comes from.
|
||||
#. ``\xff\xff/configuration/coordinators/processes := <ip:port>,<ip:port>,...,<ip:port>`` Read/write. A single key, if read, will return a comma delimited string of coordinators's network addresses. Thus to provide a new set of cooridinators, set the key with a correct formatted string of new coordinators' network addresses. As there's always the need to have coordinators, clear on the key is forbidden and a transaction will fail with the ``special_keys_api_failure`` error if the clear is committed. For more details, see help text of ``fdbcli`` command ``coordinators``.
|
||||
#. ``\xff\xff/configuration/coordinators/processes := <ip:port>,<ip:port>,...,<ip:port>`` Read/write. A single key, if read, will return a comma delimited string of coordinators' network addresses. Thus to provide a new set of cooridinators, set the key with a correct formatted string of new coordinators' network addresses. As there's always the need to have coordinators, clear on the key is forbidden and a transaction will fail with the ``special_keys_api_failure`` error if the clear is committed. For more details, see help text of ``fdbcli`` command ``coordinators``.
|
||||
#. ``\xff\xff/configuration/coordinators/cluster_description := <new_description>`` Read/write. A single key, if read, will return the cluster description. Thus modifying the key will update the cluster decription. The new description needs to match ``[A-Za-z0-9_]+``, otherwise, the ``special_keys_api_failure`` error will be thrown. In addition, clear on the key is meaningless thus forbidden. For more details, see help text of ``fdbcli`` command ``coordinators``.
|
||||
|
||||
The ``<address>`` here is the network address of the corresponding process. Thus the general form is ``ip:port``.
|
||||
|
|
|
|||
|
|
@ -530,7 +530,7 @@
|
|||
"hz":0.0,
|
||||
"counter":0,
|
||||
"roughness":0.0
|
||||
},
|
||||
},
|
||||
"low_priority_reads":{ // measures number of incoming low priority read requests
|
||||
"hz":0.0,
|
||||
"counter":0,
|
||||
|
|
@ -702,7 +702,8 @@
|
|||
"auto_resolvers":1,
|
||||
"auto_logs":3,
|
||||
"backup_worker_enabled":1,
|
||||
"commit_proxies":5 // this field will be absent if a value has not been explicitly set
|
||||
"commit_proxies":5, // this field will be absent if a value has not been explicitly set
|
||||
"proxies":6 // this field will be absent if a value has not been explicitly set
|
||||
},
|
||||
"data":{
|
||||
"least_operating_space_bytes_log_server":0,
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ Performance
|
|||
|
||||
* Increased performance of dr_agent when copying the mutation log. The ``COPY_LOG_BLOCK_SIZE``, ``COPY_LOG_BLOCKS_PER_TASK``, ``COPY_LOG_PREFETCH_BLOCKS``, ``COPY_LOG_READ_AHEAD_BYTES`` and ``COPY_LOG_TASK_DURATION_NANOS`` knobs can be set. `(PR #3436) <https://github.com/apple/foundationdb/pull/3436>`_
|
||||
* Reduced the number of connections required by the multi-version client when loading external clients. When connecting to 7.0 clusters, only one connection with version 6.2 or larger will be used. With older clusters, at most two connections with version 6.2 or larger will be used. Clients older than version 6.2 will continue to create an additional connection each. `(PR #4667) <https://github.com/apple/foundationdb/pull/4667>`_
|
||||
* Reduce CPU overhead of load balancing on client processes. `(PR #4561) <https://github.com/apple/foundationdb/pull/4561>`_
|
||||
|
||||
Reliability
|
||||
-----------
|
||||
|
|
@ -34,16 +35,21 @@ Status
|
|||
* Added ``cluster.bounce_impact`` section to status to report if there will be any extra effects when bouncing the cluster, and if so, the reason for those effects. `(PR #4770) <https://github.com/apple/foundationdb/pull/4770>`_
|
||||
* Added ``fetched_versions`` to the storage metrics section of status to report how fast a storage server is catching up in versions. `(PR #4770) <https://github.com/apple/foundationdb/pull/4770>`_
|
||||
* Added ``fetches_from_logs`` to the storage metrics section of status to report how frequently a storage server fetches updates from transaction logs. `(PR #4770) <https://github.com/apple/foundationdb/pull/4770>`_
|
||||
* Added ``seconds_since_last_recovered`` to the ``cluster.recovery_state`` section to report how long it has been since the cluster recovered to the point where it is able to accept requests. `(PR #3759) <https://github.com/apple/foundationdb/pull/3759>`_
|
||||
|
||||
Bindings
|
||||
--------
|
||||
* Python: The function ``get_estimated_range_size_bytes`` will now throw an error if the ``begin_key`` or ``end_key`` is ``None``. `(PR #3394) <https://github.com/apple/foundationdb/pull/3394>`_
|
||||
* C: Added a function, ``fdb_database_reboot_worker``, to reboot or suspend the specified process. `(PR #4094) <https://github.com/apple/foundationdb/pull/4094>`_
|
||||
* C: Added a function, ``fdb_database_force_recovery_with_data_loss``, to force the database to recover into the given datacenter. `(PR #4420) <https://github.com/apple/foundationdb/pull/4220>`_
|
||||
* C: Added a function, ``fdb_database_create_snapshot``, to create a snapshot of the database. `(PR #) <https://github.com/apple/foundationdb/pull/4241/files>`_
|
||||
* C: Added a function, ``fdb_database_create_snapshot``, to create a snapshot of the database. `(PR #4241) <https://github.com/apple/foundationdb/pull/4241>`_
|
||||
* C: Added ``fdb_database_get_main_thread_busyness`` function to report how busy a client's main thread is. `(PR #4504) <https://github.com/apple/foundationdb/pull/4504>`_
|
||||
* Java: Added ``Database.getMainThreadBusyness`` function to report how busy a client's main thread is. `(PR #4564) <https://github.com/apple/foundationdb/pull/4564>`_
|
||||
|
||||
Other Changes
|
||||
-------------
|
||||
* When ``fdbmonitor`` dies, all of its child processes are now killed. `(PR #3841) <https://github.com/apple/foundationdb/pull/3841>`_
|
||||
* The ``foundationdb`` service installed by the RPM packages will now automatically restart ``fdbmonitor`` after 60 seconds when it fails. `(PR #3841) <https://github.com/apple/foundationdb/pull/3841>`_
|
||||
|
||||
Earlier release notes
|
||||
---------------------
|
||||
|
|
|
|||
|
|
@ -3358,7 +3358,7 @@ int main(int argc, char* argv[]) {
|
|||
deleteData = true;
|
||||
break;
|
||||
case OPT_MIN_CLEANUP_SECONDS:
|
||||
knobs.push_back(std::make_pair("min_cleanup_seconds", args->OptionArg()));
|
||||
knobs.emplace_back("min_cleanup_seconds", args->OptionArg());
|
||||
break;
|
||||
case OPT_FORCE:
|
||||
forceAction = true;
|
||||
|
|
@ -3453,7 +3453,7 @@ int main(int argc, char* argv[]) {
|
|||
return FDB_EXIT_ERROR;
|
||||
}
|
||||
syn = syn.substr(7);
|
||||
knobs.push_back(std::make_pair(syn, args->OptionArg()));
|
||||
knobs.emplace_back(syn, args->OptionArg());
|
||||
break;
|
||||
}
|
||||
case OPT_BACKUPKEYS:
|
||||
|
|
@ -4211,7 +4211,7 @@ int main(int argc, char* argv[]) {
|
|||
s = s.substr(LiteralStringRef("struct ").size());
|
||||
#endif
|
||||
|
||||
typeNames.push_back(std::make_pair(s, i->first));
|
||||
typeNames.emplace_back(s, i->first);
|
||||
}
|
||||
std::sort(typeNames.begin(), typeNames.end());
|
||||
for (int i = 0; i < typeNames.size(); i++) {
|
||||
|
|
|
|||
|
|
@ -3110,7 +3110,7 @@ struct CLIOptions {
|
|||
return FDB_EXIT_ERROR;
|
||||
}
|
||||
syn = syn.substr(7);
|
||||
knobs.push_back(std::make_pair(syn, args.OptionArg()));
|
||||
knobs.emplace_back(syn, args.OptionArg());
|
||||
break;
|
||||
}
|
||||
case OPT_DEBUG_TLS:
|
||||
|
|
|
|||
|
|
@ -114,30 +114,18 @@ struct ClientDBInfo {
|
|||
firstCommitProxy; // not serialized, used for commitOnFirstProxy when the commit proxies vector has been shrunk
|
||||
Optional<Value> forward;
|
||||
vector<VersionHistory> history;
|
||||
vector<std::pair<UID, StorageServerInterface>>
|
||||
tssMapping; // logically map<ssid, tss interface> for all active TSS pairs
|
||||
|
||||
ClientDBInfo() {}
|
||||
|
||||
bool operator==(ClientDBInfo const& r) const { return id == r.id; }
|
||||
bool operator!=(ClientDBInfo const& r) const { return id != r.id; }
|
||||
|
||||
// convenience method to treat tss mapping like a map
|
||||
Optional<StorageServerInterface> getTssPair(UID storageServerID) const {
|
||||
for (auto& it : tssMapping) {
|
||||
if (it.first == storageServerID) {
|
||||
return Optional<StorageServerInterface>(it.second);
|
||||
}
|
||||
}
|
||||
return Optional<StorageServerInterface>();
|
||||
}
|
||||
|
||||
template <class Archive>
|
||||
void serialize(Archive& ar) {
|
||||
if constexpr (!is_fb_function<Archive>) {
|
||||
ASSERT(ar.protocolVersion().isValid());
|
||||
}
|
||||
serializer(ar, grvProxies, commitProxies, id, forward, history, tssMapping);
|
||||
serializer(ar, grvProxies, commitProxies, id, forward, history);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -162,40 +150,6 @@ struct CommitID {
|
|||
conflictingKRIndices(conflictingKRIndices) {}
|
||||
};
|
||||
|
||||
struct ClientTagThrottleLimits {
|
||||
double tpsRate;
|
||||
double expiration;
|
||||
|
||||
ClientTagThrottleLimits() : tpsRate(0), expiration(0) {}
|
||||
ClientTagThrottleLimits(double tpsRate, double expiration) : tpsRate(tpsRate), expiration(expiration) {}
|
||||
|
||||
template <class Archive>
|
||||
void serialize(Archive& ar) {
|
||||
// Convert expiration time to a duration to avoid clock differences
|
||||
double duration = 0;
|
||||
if (!ar.isDeserializing) {
|
||||
duration = expiration - now();
|
||||
}
|
||||
|
||||
serializer(ar, tpsRate, duration);
|
||||
|
||||
if (ar.isDeserializing) {
|
||||
expiration = now() + duration;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct ClientTrCommitCostEstimation {
|
||||
int opsCount = 0;
|
||||
uint64_t writeCosts = 0;
|
||||
std::deque<std::pair<int, uint64_t>> clearIdxCosts;
|
||||
uint32_t expensiveCostEstCount = 0;
|
||||
template <class Ar>
|
||||
void serialize(Ar& ar) {
|
||||
serializer(ar, opsCount, writeCosts, clearIdxCosts, expensiveCostEstCount);
|
||||
}
|
||||
};
|
||||
|
||||
struct CommitTransactionRequest : TimedRequest {
|
||||
constexpr static FileIdentifier file_identifier = 93948;
|
||||
enum { FLAG_IS_LOCK_AWARE = 0x1, FLAG_FIRST_IN_BATCH = 0x2 };
|
||||
|
|
@ -332,9 +286,12 @@ struct GetKeyServerLocationsReply {
|
|||
Arena arena;
|
||||
std::vector<std::pair<KeyRangeRef, vector<StorageServerInterface>>> results;
|
||||
|
||||
// if any storage servers in results have a TSS pair, that mapping is in here
|
||||
std::vector<std::pair<UID, StorageServerInterface>> resultsTssMapping;
|
||||
|
||||
template <class Ar>
|
||||
void serialize(Ar& ar) {
|
||||
serializer(ar, results, arena);
|
||||
serializer(ar, results, resultsTssMapping, arena);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -338,14 +338,26 @@ StatusObject DatabaseConfiguration::toJSON(bool noPolicies) const {
|
|||
result["regions"] = getRegionJSON();
|
||||
}
|
||||
|
||||
// Add to the `proxies` count for backwards compatibility with tools built before 7.0.
|
||||
int32_t proxyCount = -1;
|
||||
if (desiredTLogCount != -1 || isOverridden("logs")) {
|
||||
result["logs"] = desiredTLogCount;
|
||||
}
|
||||
if (commitProxyCount != -1 || isOverridden("commit_proxies")) {
|
||||
result["commit_proxies"] = commitProxyCount;
|
||||
if (proxyCount != -1) {
|
||||
proxyCount += commitProxyCount;
|
||||
} else {
|
||||
proxyCount = commitProxyCount;
|
||||
}
|
||||
}
|
||||
if (grvProxyCount != -1 || isOverridden("grv_proxies")) {
|
||||
result["grv_proxies"] = grvProxyCount;
|
||||
if (proxyCount != -1) {
|
||||
proxyCount += grvProxyCount;
|
||||
} else {
|
||||
proxyCount = grvProxyCount;
|
||||
}
|
||||
}
|
||||
if (resolverCount != -1 || isOverridden("resolvers")) {
|
||||
result["resolvers"] = resolverCount;
|
||||
|
|
@ -371,6 +383,9 @@ StatusObject DatabaseConfiguration::toJSON(bool noPolicies) const {
|
|||
if (autoDesiredTLogCount != CLIENT_KNOBS->DEFAULT_AUTO_LOGS || isOverridden("auto_logs")) {
|
||||
result["auto_logs"] = autoDesiredTLogCount;
|
||||
}
|
||||
if (proxyCount != -1) {
|
||||
result["proxies"] = proxyCount;
|
||||
}
|
||||
|
||||
result["backup_worker_enabled"] = (int32_t)backupWorkerEnabled;
|
||||
result["perpetual_storage_wiggle"] = perpetualStorageWiggleSpeed;
|
||||
|
|
|
|||
|
|
@ -323,7 +323,10 @@ public:
|
|||
|
||||
std::map<UID, StorageServerInfo*> server_interf;
|
||||
|
||||
std::map<UID, Reference<TSSMetrics>> tssMetrics;
|
||||
// map from ssid -> tss interface
|
||||
std::unordered_map<UID, StorageServerInterface> tssMapping;
|
||||
// map from tssid -> metrics for that tss pair
|
||||
std::unordered_map<UID, Reference<TSSMetrics>> tssMetrics;
|
||||
|
||||
UID dbId;
|
||||
bool internal; // Only contexts created through the C client and fdbcli are non-internal
|
||||
|
|
@ -426,8 +429,13 @@ public:
|
|||
static const std::vector<std::string> debugTransactionTagChoices;
|
||||
std::unordered_map<KeyRef, Reference<WatchMetadata>> watchMap;
|
||||
|
||||
void maybeAddTssMapping(StorageServerInterface const& ssi);
|
||||
// Adds or updates the specified (SS, TSS) pair in the TSS mapping (if not already present).
|
||||
// Requests to the storage server will be duplicated to the TSS.
|
||||
void addTssMapping(StorageServerInterface const& ssi, StorageServerInterface const& tssi);
|
||||
|
||||
// Removes the storage server and its TSS pair from the TSS mapping (if present).
|
||||
// Requests to the storage server will no longer be duplicated to its pair TSS.
|
||||
void removeTssMapping(StorageServerInterface const& ssi);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ public:
|
|||
if (itr != optionsIndexMap.end()) {
|
||||
options.erase(itr->second);
|
||||
}
|
||||
options.push_back(std::make_pair(option, value));
|
||||
options.emplace_back(option, value);
|
||||
optionsIndexMap[option] = --options.end();
|
||||
}
|
||||
|
||||
|
|
@ -107,4 +107,4 @@ public:
|
|||
type::optionInfo.insert( \
|
||||
var, FDBOptionInfo(name, comment, parameterComment, hasParameter, hidden, persistent, defaultFor));
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -482,7 +482,9 @@ inline Key keyAfter(const KeyRef& key) {
|
|||
|
||||
Standalone<StringRef> r;
|
||||
uint8_t* s = new (r.arena()) uint8_t[key.size() + 1];
|
||||
memcpy(s, key.begin(), key.size());
|
||||
if (key.size() > 0) {
|
||||
memcpy(s, key.begin(), key.size());
|
||||
}
|
||||
s[key.size()] = 0;
|
||||
((StringRef&)r) = StringRef(s, key.size() + 1);
|
||||
return r;
|
||||
|
|
|
|||
|
|
@ -34,16 +34,7 @@ const KeyRef fdbClientInfoTxnSizeLimit = LiteralStringRef("config/fdb_client_inf
|
|||
const KeyRef transactionTagSampleRate = LiteralStringRef("config/transaction_tag_sample_rate");
|
||||
const KeyRef transactionTagSampleCost = LiteralStringRef("config/transaction_tag_sample_cost");
|
||||
|
||||
GlobalConfig::GlobalConfig() : lastUpdate(0) {}
|
||||
|
||||
void GlobalConfig::create(DatabaseContext* cx, Reference<AsyncVar<ClientDBInfo>> dbInfo) {
|
||||
if (g_network->global(INetwork::enGlobalConfig) == nullptr) {
|
||||
auto config = new GlobalConfig{};
|
||||
config->cx = Database(cx);
|
||||
g_network->setGlobal(INetwork::enGlobalConfig, config);
|
||||
config->_updater = updater(config, dbInfo);
|
||||
}
|
||||
}
|
||||
GlobalConfig::GlobalConfig(Database& cx) : cx(cx), lastUpdate(0) {}
|
||||
|
||||
GlobalConfig& GlobalConfig::globalConfig() {
|
||||
void* res = g_network->global(INetwork::enGlobalConfig);
|
||||
|
|
@ -77,6 +68,14 @@ Future<Void> GlobalConfig::onInitialized() {
|
|||
return initialized.getFuture();
|
||||
}
|
||||
|
||||
Future<Void> GlobalConfig::onChange() {
|
||||
return configChanged.onTrigger();
|
||||
}
|
||||
|
||||
void GlobalConfig::trigger(KeyRef key, std::function<void(std::optional<std::any>)> fn) {
|
||||
callbacks.emplace(key, std::move(fn));
|
||||
}
|
||||
|
||||
void GlobalConfig::insert(KeyRef key, ValueRef value) {
|
||||
data.erase(key);
|
||||
|
||||
|
|
@ -89,6 +88,8 @@ void GlobalConfig::insert(KeyRef key, ValueRef value) {
|
|||
any = StringRef(arena, t.getString(0).contents());
|
||||
} else if (t.getType(0) == Tuple::ElementType::INT) {
|
||||
any = t.getInt(0);
|
||||
} else if (t.getType(0) == Tuple::ElementType::BOOL) {
|
||||
any = t.getBool(0);
|
||||
} else if (t.getType(0) == Tuple::ElementType::FLOAT) {
|
||||
any = t.getFloat(0);
|
||||
} else if (t.getType(0) == Tuple::ElementType::DOUBLE) {
|
||||
|
|
@ -97,19 +98,26 @@ void GlobalConfig::insert(KeyRef key, ValueRef value) {
|
|||
ASSERT(false);
|
||||
}
|
||||
data[stableKey] = makeReference<ConfigValue>(std::move(arena), std::move(any));
|
||||
|
||||
if (callbacks.find(stableKey) != callbacks.end()) {
|
||||
callbacks[stableKey](data[stableKey]->value);
|
||||
}
|
||||
} catch (Error& e) {
|
||||
TraceEvent("GlobalConfigTupleParseError").detail("What", e.what());
|
||||
TraceEvent(SevWarn, "GlobalConfigTupleParseError").detail("What", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void GlobalConfig::erase(KeyRef key) {
|
||||
data.erase(key);
|
||||
void GlobalConfig::erase(Key key) {
|
||||
erase(KeyRangeRef(key, keyAfter(key)));
|
||||
}
|
||||
|
||||
void GlobalConfig::erase(KeyRangeRef range) {
|
||||
auto it = data.begin();
|
||||
while (it != data.end()) {
|
||||
if (range.contains(it->first)) {
|
||||
if (callbacks.find(it->first) != callbacks.end()) {
|
||||
callbacks[it->first](std::nullopt);
|
||||
}
|
||||
it = data.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
|
|
@ -134,36 +142,39 @@ ACTOR Future<Void> GlobalConfig::migrate(GlobalConfig* self) {
|
|||
state Optional<Value> sampleRate = wait(tr->get(Key("\xff\x02/fdbClientInfo/client_txn_sample_rate/"_sr)));
|
||||
state Optional<Value> sizeLimit = wait(tr->get(Key("\xff\x02/fdbClientInfo/client_txn_size_limit/"_sr)));
|
||||
|
||||
loop {
|
||||
try {
|
||||
tr->setOption(FDBTransactionOptions::SPECIAL_KEY_SPACE_ENABLE_WRITES);
|
||||
// The value doesn't matter too much, as long as the key is set.
|
||||
tr->set(migratedKey.contents(), "1"_sr);
|
||||
if (sampleRate.present()) {
|
||||
const double sampleRateDbl =
|
||||
BinaryReader::fromStringRef<double>(sampleRate.get().contents(), Unversioned());
|
||||
Tuple rate = Tuple().appendDouble(sampleRateDbl);
|
||||
tr->set(GlobalConfig::prefixedKey(fdbClientInfoTxnSampleRate), rate.pack());
|
||||
}
|
||||
if (sizeLimit.present()) {
|
||||
const int64_t sizeLimitInt =
|
||||
BinaryReader::fromStringRef<int64_t>(sizeLimit.get().contents(), Unversioned());
|
||||
Tuple size = Tuple().append(sizeLimitInt);
|
||||
tr->set(GlobalConfig::prefixedKey(fdbClientInfoTxnSizeLimit), size.pack());
|
||||
}
|
||||
|
||||
wait(tr->commit());
|
||||
return Void();
|
||||
} catch (Error& e) {
|
||||
throw;
|
||||
try {
|
||||
tr->setOption(FDBTransactionOptions::SPECIAL_KEY_SPACE_ENABLE_WRITES);
|
||||
// The value doesn't matter too much, as long as the key is set.
|
||||
tr->set(migratedKey.contents(), "1"_sr);
|
||||
if (sampleRate.present()) {
|
||||
const double sampleRateDbl =
|
||||
BinaryReader::fromStringRef<double>(sampleRate.get().contents(), Unversioned());
|
||||
Tuple rate = Tuple().appendDouble(sampleRateDbl);
|
||||
tr->set(GlobalConfig::prefixedKey(fdbClientInfoTxnSampleRate), rate.pack());
|
||||
}
|
||||
if (sizeLimit.present()) {
|
||||
const int64_t sizeLimitInt =
|
||||
BinaryReader::fromStringRef<int64_t>(sizeLimit.get().contents(), Unversioned());
|
||||
Tuple size = Tuple().append(sizeLimitInt);
|
||||
tr->set(GlobalConfig::prefixedKey(fdbClientInfoTxnSizeLimit), size.pack());
|
||||
}
|
||||
|
||||
wait(tr->commit());
|
||||
} catch (Error& e) {
|
||||
// If multiple fdbserver processes are started at once, they will all
|
||||
// attempt this migration at the same time, sometimes resulting in
|
||||
// aborts due to conflicts. Purposefully avoid retrying, making this
|
||||
// migration best-effort.
|
||||
TraceEvent(SevInfo, "GlobalConfigMigrationError").detail("What", e.what());
|
||||
}
|
||||
|
||||
return Void();
|
||||
}
|
||||
|
||||
// Updates local copy of global configuration by reading the entire key-range
|
||||
// from storage.
|
||||
ACTOR Future<Void> GlobalConfig::refresh(GlobalConfig* self) {
|
||||
self->data.clear();
|
||||
self->erase(KeyRangeRef(""_sr, "\xff"_sr));
|
||||
|
||||
Transaction tr(self->cx);
|
||||
RangeResult result = wait(tr.getRange(globalConfigDataKeys, CLIENT_KNOBS->TOO_MANY));
|
||||
|
|
@ -176,7 +187,8 @@ ACTOR Future<Void> GlobalConfig::refresh(GlobalConfig* self) {
|
|||
|
||||
// Applies updates to the local copy of the global configuration when this
|
||||
// process receives an updated history.
|
||||
ACTOR Future<Void> GlobalConfig::updater(GlobalConfig* self, Reference<AsyncVar<ClientDBInfo>> dbInfo) {
|
||||
ACTOR Future<Void> GlobalConfig::updater(GlobalConfig* self, const ClientDBInfo* dbInfo) {
|
||||
wait(self->cx->onConnected());
|
||||
wait(self->migrate(self));
|
||||
|
||||
wait(self->refresh(self));
|
||||
|
|
@ -184,9 +196,9 @@ ACTOR Future<Void> GlobalConfig::updater(GlobalConfig* self, Reference<AsyncVar<
|
|||
|
||||
loop {
|
||||
try {
|
||||
wait(dbInfo->onChange());
|
||||
wait(self->dbInfoChanged.onTrigger());
|
||||
|
||||
auto& history = dbInfo->get().history;
|
||||
auto& history = dbInfo->history;
|
||||
if (history.size() == 0) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -196,8 +208,8 @@ ACTOR Future<Void> GlobalConfig::updater(GlobalConfig* self, Reference<AsyncVar<
|
|||
// history updates or the protocol version changed, so it
|
||||
// must re-read the entire configuration range.
|
||||
wait(self->refresh(self));
|
||||
if (dbInfo->get().history.size() > 0) {
|
||||
self->lastUpdate = dbInfo->get().history.back().version;
|
||||
if (dbInfo->history.size() > 0) {
|
||||
self->lastUpdate = dbInfo->history.back().version;
|
||||
}
|
||||
} else {
|
||||
// Apply history in order, from lowest version to highest
|
||||
|
|
@ -222,6 +234,8 @@ ACTOR Future<Void> GlobalConfig::updater(GlobalConfig* self, Reference<AsyncVar<
|
|||
self->lastUpdate = vh.version;
|
||||
}
|
||||
}
|
||||
|
||||
self->configChanged.trigger();
|
||||
} catch (Error& e) {
|
||||
throw;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,10 +62,28 @@ struct ConfigValue : ReferenceCounted<ConfigValue> {
|
|||
|
||||
class GlobalConfig : NonCopyable {
|
||||
public:
|
||||
// Creates a GlobalConfig singleton, accessed by calling GlobalConfig().
|
||||
// This function should only be called once by each process (however, it is
|
||||
// idempotent and calling it multiple times will have no effect).
|
||||
static void create(DatabaseContext* cx, Reference<AsyncVar<ClientDBInfo>> dbInfo);
|
||||
// Creates a GlobalConfig singleton, accessed by calling
|
||||
// GlobalConfig::globalConfig(). This function requires a database object
|
||||
// to allow global configuration to run transactions on the database, and
|
||||
// an AsyncVar object to watch for changes on. The ClientDBInfo pointer
|
||||
// should point to a ClientDBInfo object which will contain the updated
|
||||
// global configuration history when the given AsyncVar changes. This
|
||||
// function should be called whenever the database object changes, in order
|
||||
// to allow global configuration to run transactions on the latest
|
||||
// database.
|
||||
template <class T>
|
||||
static void create(Database& cx, Reference<AsyncVar<T>> db, const ClientDBInfo* dbInfo) {
|
||||
if (g_network->global(INetwork::enGlobalConfig) == nullptr) {
|
||||
auto config = new GlobalConfig{ cx };
|
||||
g_network->setGlobal(INetwork::enGlobalConfig, config);
|
||||
config->_updater = updater(config, dbInfo);
|
||||
// Bind changes in `db` to the `dbInfoChanged` AsyncTrigger.
|
||||
forward(db, std::addressof(config->dbInfoChanged));
|
||||
} else {
|
||||
GlobalConfig* config = reinterpret_cast<GlobalConfig*>(g_network->global(INetwork::enGlobalConfig));
|
||||
config->cx = cx;
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a reference to the global GlobalConfig object. Clients should
|
||||
// call this function whenever they need to read a value out of the global
|
||||
|
|
@ -114,8 +132,18 @@ public:
|
|||
// been created and is ready.
|
||||
Future<Void> onInitialized();
|
||||
|
||||
// Triggers the returned future when any key-value pair in the global
|
||||
// configuration changes.
|
||||
Future<Void> onChange();
|
||||
|
||||
// Calls \ref fn when the value associated with \ref key is changed. \ref
|
||||
// fn is passed the updated value for the key, or an empty optional if the
|
||||
// key has been cleared. If the value is an allocated object, its memory
|
||||
// remains in the control of the global configuration.
|
||||
void trigger(KeyRef key, std::function<void(std::optional<std::any>)> fn);
|
||||
|
||||
private:
|
||||
GlobalConfig();
|
||||
GlobalConfig(Database& cx);
|
||||
|
||||
// The functions below only affect the local copy of the global
|
||||
// configuration keyspace! To insert or remove values across all nodes you
|
||||
|
|
@ -127,20 +155,23 @@ private:
|
|||
void insert(KeyRef key, ValueRef value);
|
||||
// Removes the given key (and associated value) from the local copy of the
|
||||
// global configuration keyspace.
|
||||
void erase(KeyRef key);
|
||||
void erase(Key key);
|
||||
// Removes the given key range (and associated values) from the local copy
|
||||
// of the global configuration keyspace.
|
||||
void erase(KeyRangeRef range);
|
||||
|
||||
ACTOR static Future<Void> migrate(GlobalConfig* self);
|
||||
ACTOR static Future<Void> refresh(GlobalConfig* self);
|
||||
ACTOR static Future<Void> updater(GlobalConfig* self, Reference<AsyncVar<ClientDBInfo>> dbInfo);
|
||||
ACTOR static Future<Void> updater(GlobalConfig* self, const ClientDBInfo* dbInfo);
|
||||
|
||||
Database cx;
|
||||
AsyncTrigger dbInfoChanged;
|
||||
Future<Void> _updater;
|
||||
Promise<Void> initialized;
|
||||
AsyncTrigger configChanged;
|
||||
std::unordered_map<StringRef, Reference<ConfigValue>> data;
|
||||
Version lastUpdate;
|
||||
std::unordered_map<KeyRef, std::function<void(std::optional<std::any>)>> callbacks;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -171,7 +171,6 @@ void ClientKnobs::initialize(Randomize _randomize) {
|
|||
init( BACKUP_STATUS_DELAY, 40.0 );
|
||||
init( BACKUP_STATUS_JITTER, 0.05 );
|
||||
init( MIN_CLEANUP_SECONDS, 3600.0 );
|
||||
init( RESTORE_IGNORE_LOG_FILES, false );
|
||||
init( FASTRESTORE_ATOMICOP_WEIGHT, 1 ); if( randomize && BUGGIFY ) { FASTRESTORE_ATOMICOP_WEIGHT = deterministicRandom()->random01() * 200 + 1; }
|
||||
|
||||
// Configuration
|
||||
|
|
|
|||
|
|
@ -168,8 +168,6 @@ public:
|
|||
double BACKUP_STATUS_DELAY;
|
||||
double BACKUP_STATUS_JITTER;
|
||||
double MIN_CLEANUP_SECONDS;
|
||||
bool RESTORE_IGNORE_LOG_FILES; // Default is false. When set to true, the log files will be ignored during the
|
||||
// restore, which can produce inconsistent restored data.
|
||||
int64_t FASTRESTORE_ATOMICOP_WEIGHT; // workload amplication factor for atomic op
|
||||
|
||||
// Configuration
|
||||
|
|
|
|||
|
|
@ -785,7 +785,7 @@ ConfigureAutoResult parseConfig(StatusObject const& status) {
|
|||
}
|
||||
|
||||
if (processClass.classType() != ProcessClass::TesterClass) {
|
||||
machine_processes[machineId].push_back(std::make_pair(addr, processClass));
|
||||
machine_processes[machineId].emplace_back(addr, processClass);
|
||||
processCount++;
|
||||
}
|
||||
}
|
||||
|
|
@ -1315,7 +1315,7 @@ struct AutoQuorumChange final : IQuorumChange {
|
|||
vector<NetworkAddress> oldCoordinators,
|
||||
Reference<ClusterConnectionFile> ccf,
|
||||
CoordinatorsResult& err) override {
|
||||
return getDesired(this, tr, oldCoordinators, ccf, &err);
|
||||
return getDesired(Reference<AutoQuorumChange>::addRef(this), tr, oldCoordinators, ccf, &err);
|
||||
}
|
||||
|
||||
ACTOR static Future<int> getRedundancy(AutoQuorumChange* self, Transaction* tr) {
|
||||
|
|
@ -1378,7 +1378,7 @@ struct AutoQuorumChange final : IQuorumChange {
|
|||
return true; // The status quo seems fine
|
||||
}
|
||||
|
||||
ACTOR static Future<vector<NetworkAddress>> getDesired(AutoQuorumChange* self,
|
||||
ACTOR static Future<vector<NetworkAddress>> getDesired(Reference<AutoQuorumChange> self,
|
||||
Transaction* tr,
|
||||
vector<NetworkAddress> oldCoordinators,
|
||||
Reference<ClusterConnectionFile> ccf,
|
||||
|
|
@ -1386,7 +1386,7 @@ struct AutoQuorumChange final : IQuorumChange {
|
|||
state int desiredCount = self->desired;
|
||||
|
||||
if (desiredCount == -1) {
|
||||
int redundancy = wait(getRedundancy(self, tr));
|
||||
int redundancy = wait(getRedundancy(self.getPtr(), tr));
|
||||
desiredCount = redundancy * 2 - 1;
|
||||
}
|
||||
|
||||
|
|
@ -1415,7 +1415,7 @@ struct AutoQuorumChange final : IQuorumChange {
|
|||
}
|
||||
|
||||
if (checkAcceptable) {
|
||||
bool ok = wait(isAcceptable(self, tr, oldCoordinators, ccf, desiredCount, &excluded));
|
||||
bool ok = wait(isAcceptable(self.getPtr(), tr, oldCoordinators, ccf, desiredCount, &excluded));
|
||||
if (ok)
|
||||
return oldCoordinators;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -434,9 +434,9 @@ Optional<std::pair<LeaderInfo, bool>> getLeader(const vector<Optional<LeaderInfo
|
|||
maskedNominees.reserve(nominees.size());
|
||||
for (int i = 0; i < nominees.size(); i++) {
|
||||
if (nominees[i].present()) {
|
||||
maskedNominees.push_back(std::make_pair(
|
||||
maskedNominees.emplace_back(
|
||||
UID(nominees[i].get().changeID.first() & LeaderInfo::changeIDMask, nominees[i].get().changeID.second()),
|
||||
i));
|
||||
i);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -586,7 +586,7 @@ OpenDatabaseRequest ClientData::getRequest() {
|
|||
auto& entry = issueMap[it];
|
||||
entry.count++;
|
||||
if (entry.examples.size() < CLIENT_KNOBS->CLIENT_EXAMPLE_AMOUNT) {
|
||||
entry.examples.push_back(std::make_pair(ci.first, ci.second.traceLogGroup));
|
||||
entry.examples.emplace_back(ci.first, ci.second.traceLogGroup);
|
||||
}
|
||||
}
|
||||
if (ci.second.versions.size()) {
|
||||
|
|
@ -597,19 +597,19 @@ OpenDatabaseRequest ClientData::getRequest() {
|
|||
auto& entry = versionMap[it];
|
||||
entry.count++;
|
||||
if (entry.examples.size() < CLIENT_KNOBS->CLIENT_EXAMPLE_AMOUNT) {
|
||||
entry.examples.push_back(std::make_pair(ci.first, ci.second.traceLogGroup));
|
||||
entry.examples.emplace_back(ci.first, ci.second.traceLogGroup);
|
||||
}
|
||||
}
|
||||
auto& maxEntry = maxProtocolMap[maxProtocol];
|
||||
maxEntry.count++;
|
||||
if (maxEntry.examples.size() < CLIENT_KNOBS->CLIENT_EXAMPLE_AMOUNT) {
|
||||
maxEntry.examples.push_back(std::make_pair(ci.first, ci.second.traceLogGroup));
|
||||
maxEntry.examples.emplace_back(ci.first, ci.second.traceLogGroup);
|
||||
}
|
||||
} else {
|
||||
auto& entry = versionMap[ClientVersionRef()];
|
||||
entry.count++;
|
||||
if (entry.examples.size() < CLIENT_KNOBS->CLIENT_EXAMPLE_AMOUNT) {
|
||||
entry.examples.push_back(std::make_pair(ci.first, ci.second.traceLogGroup));
|
||||
entry.examples.emplace_back(ci.first, ci.second.traceLogGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -595,7 +595,7 @@ Reference<IDatabase> DLApi::createDatabase(const char* clusterFilePath) {
|
|||
|
||||
void DLApi::addNetworkThreadCompletionHook(void (*hook)(void*), void* hookParameter) {
|
||||
MutexHolder holder(lock);
|
||||
threadCompletionHooks.push_back(std::make_pair(hook, hookParameter));
|
||||
threadCompletionHooks.emplace_back(hook, hookParameter);
|
||||
}
|
||||
|
||||
// MultiVersionTransaction
|
||||
|
|
@ -947,7 +947,7 @@ void MultiVersionDatabase::setOption(FDBDatabaseOptions::Option option, Optional
|
|||
value.castTo<Standalone<StringRef>>());
|
||||
}
|
||||
|
||||
dbState->options.push_back(std::make_pair(option, value.castTo<Standalone<StringRef>>()));
|
||||
dbState->options.emplace_back(option, value.castTo<Standalone<StringRef>>());
|
||||
|
||||
if (dbState->db) {
|
||||
dbState->db->setOption(option, value);
|
||||
|
|
@ -1559,7 +1559,7 @@ void MultiVersionApi::setNetworkOptionInternal(FDBNetworkOptions::Option option,
|
|||
runOnExternalClientsAllThreads(
|
||||
[option, value](Reference<ClientInfo> client) { client->api->setNetworkOption(option, value); });
|
||||
} else {
|
||||
options.push_back(std::make_pair(option, value.castTo<Standalone<StringRef>>()));
|
||||
options.emplace_back(option, value.castTo<Standalone<StringRef>>());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -122,38 +122,50 @@ NetworkOptions::NetworkOptions()
|
|||
static const Key CLIENT_LATENCY_INFO_PREFIX = LiteralStringRef("client_latency/");
|
||||
static const Key CLIENT_LATENCY_INFO_CTR_PREFIX = LiteralStringRef("client_latency_counter/");
|
||||
|
||||
void DatabaseContext::maybeAddTssMapping(StorageServerInterface const& ssi) {
|
||||
// add tss mapping if server is new
|
||||
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 {
|
||||
if (result->second.id() == tssi.id()) {
|
||||
metrics = tssMetrics[tssi.id()];
|
||||
} else {
|
||||
TEST(true); // SS now maps to new TSS! This will probably never happen in practice
|
||||
tssMetrics.erase(result->second.id());
|
||||
metrics = makeReference<TSSMetrics>();
|
||||
tssMetrics[tssi.id()] = metrics;
|
||||
}
|
||||
result->second = tssi;
|
||||
}
|
||||
|
||||
Optional<StorageServerInterface> tssPair = clientInfo->get().getTssPair(ssi.id());
|
||||
if (tssPair.present()) {
|
||||
addTssMapping(ssi, tssPair.get());
|
||||
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.watchValue.getEndpoint().token.first(),
|
||||
TSSEndpointData(tssi.id(), tssi.watchValue.getEndpoint(), metrics));
|
||||
}
|
||||
}
|
||||
|
||||
// calling getInterface potentially recursively is weird, but since this function is only called when an entry is
|
||||
// created/changed, the recursive call should never recurse itself.
|
||||
void DatabaseContext::addTssMapping(StorageServerInterface const& ssi, StorageServerInterface const& tssi) {
|
||||
Reference<StorageServerInfo> tssInfo = StorageServerInfo::getInterface(this, tssi, clientLocality);
|
||||
Reference<StorageServerInfo> ssInfo = StorageServerInfo::getInterface(this, ssi, clientLocality);
|
||||
|
||||
Reference<TSSMetrics> metrics = makeReference<TSSMetrics>();
|
||||
tssMetrics[tssi.id()] = metrics;
|
||||
|
||||
// Add each read data request we want to duplicate to TSS to endpoint mapping (getValue, getKey, getKeyValues,
|
||||
// watchValue)
|
||||
queueModel.updateTssEndpoint(
|
||||
ssInfo->interf.getValue.getEndpoint().token.first(),
|
||||
TSSEndpointData(tssi.id(), tssInfo->interf.getValue.getEndpoint(), metrics, clientInfo->get().id));
|
||||
queueModel.updateTssEndpoint(
|
||||
ssInfo->interf.getKey.getEndpoint().token.first(),
|
||||
TSSEndpointData(tssi.id(), tssInfo->interf.getKey.getEndpoint(), metrics, clientInfo->get().id));
|
||||
queueModel.updateTssEndpoint(
|
||||
ssInfo->interf.getKeyValues.getEndpoint().token.first(),
|
||||
TSSEndpointData(tssi.id(), tssInfo->interf.getKeyValues.getEndpoint(), metrics, clientInfo->get().id));
|
||||
queueModel.updateTssEndpoint(
|
||||
ssInfo->interf.watchValue.getEndpoint().token.first(),
|
||||
TSSEndpointData(tssi.id(), tssInfo->interf.watchValue.getEndpoint(), metrics, clientInfo->get().id));
|
||||
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.watchValue.getEndpoint().token.first());
|
||||
}
|
||||
}
|
||||
|
||||
Reference<StorageServerInfo> StorageServerInfo::getInterface(DatabaseContext* cx,
|
||||
|
|
@ -170,12 +182,10 @@ Reference<StorageServerInfo> StorageServerInfo::getInterface(DatabaseContext* cx
|
|||
// changes.
|
||||
|
||||
it->second->interf = ssi;
|
||||
cx->maybeAddTssMapping(ssi);
|
||||
} else {
|
||||
it->second->notifyContextDestroyed();
|
||||
Reference<StorageServerInfo> loc(new StorageServerInfo(cx, ssi, locality));
|
||||
cx->server_interf[ssi.id()] = loc.getPtr();
|
||||
cx->maybeAddTssMapping(ssi);
|
||||
return loc;
|
||||
}
|
||||
}
|
||||
|
|
@ -185,7 +195,6 @@ Reference<StorageServerInfo> StorageServerInfo::getInterface(DatabaseContext* cx
|
|||
|
||||
Reference<StorageServerInfo> loc(new StorageServerInfo(cx, ssi, locality));
|
||||
cx->server_interf[ssi.id()] = loc.getPtr();
|
||||
cx->maybeAddTssMapping(ssi);
|
||||
return loc;
|
||||
}
|
||||
|
||||
|
|
@ -813,45 +822,6 @@ ACTOR Future<Void> monitorCacheList(DatabaseContext* self) {
|
|||
}
|
||||
}
|
||||
|
||||
// updates tss mapping when set of tss servers changes
|
||||
ACTOR static Future<Void> monitorTssChange(DatabaseContext* cx) {
|
||||
state vector<std::pair<UID, StorageServerInterface>> curTssMapping;
|
||||
curTssMapping = cx->clientInfo->get().tssMapping;
|
||||
|
||||
loop {
|
||||
wait(cx->clientInfo->onChange());
|
||||
if (cx->clientInfo->get().tssMapping != curTssMapping) {
|
||||
// To optimize size of the ClientDBInfo payload, we could eventually change CC to just send a tss change
|
||||
// id/generation, and have client reread the mapping here if it changed. It's a very minor optimization
|
||||
// though, and would cause extra read load.
|
||||
ClientDBInfo clientInfo = cx->clientInfo->get();
|
||||
curTssMapping = clientInfo.tssMapping;
|
||||
|
||||
std::unordered_set<UID> seenTssIds;
|
||||
|
||||
if (curTssMapping.size()) {
|
||||
for (const auto& it : curTssMapping) {
|
||||
seenTssIds.insert(it.second.id());
|
||||
|
||||
if (cx->server_interf.count(it.first)) {
|
||||
cx->addTssMapping(cx->server_interf[it.first]->interf, it.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto it = cx->tssMetrics.begin(); it != cx->tssMetrics.end();) {
|
||||
if (seenTssIds.count(it->first)) {
|
||||
it++;
|
||||
} else {
|
||||
it = cx->tssMetrics.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
cx->queueModel.removeOldTssData(clientInfo.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ACTOR static Future<Void> handleTssMismatches(DatabaseContext* cx) {
|
||||
state Reference<ReadYourWritesTransaction> tr;
|
||||
state KeyBackedMap<UID, UID> tssMapDB = KeyBackedMap<UID, UID>(tssMappingKeys.begin);
|
||||
|
|
@ -860,7 +830,7 @@ ACTOR static Future<Void> handleTssMismatches(DatabaseContext* cx) {
|
|||
// find ss pair id so we can remove it from the mapping
|
||||
state UID tssPairID;
|
||||
bool found = false;
|
||||
for (const auto& it : cx->clientInfo->get().tssMapping) {
|
||||
for (const auto& it : cx->tssMapping) {
|
||||
if (it.second.id() == tssID) {
|
||||
tssPairID = it.first;
|
||||
found = true;
|
||||
|
|
@ -870,7 +840,7 @@ ACTOR static Future<Void> handleTssMismatches(DatabaseContext* cx) {
|
|||
if (found) {
|
||||
TraceEvent(SevWarnAlways, "TSS_KillMismatch").detail("TSSID", tssID.toString());
|
||||
TEST(true); // killing TSS because it got mismatch
|
||||
|
||||
|
||||
// TODO we could write something to the system keyspace and then have DD listen to that keyspace and then DD
|
||||
// do exactly this, so why not just cut out the middle man (or the middle system keys, as it were)
|
||||
tr = makeReference<ReadYourWritesTransaction>(Database(Reference<DatabaseContext>::addRef(cx)));
|
||||
|
|
@ -883,7 +853,6 @@ ACTOR static Future<Void> handleTssMismatches(DatabaseContext* cx) {
|
|||
tr->clear(serverTagKeyFor(tssID));
|
||||
tssMapDB.erase(tr, tssPairID);
|
||||
|
||||
tr->set(tssMappingChangeKey, deterministicRandom()->randomUniqueID().toString());
|
||||
wait(tr->commit());
|
||||
|
||||
break;
|
||||
|
|
@ -1152,10 +1121,7 @@ DatabaseContext::DatabaseContext(Reference<AsyncVar<Reference<ClusterConnectionF
|
|||
getValueSubmitted.init(LiteralStringRef("NativeAPI.GetValueSubmitted"));
|
||||
getValueCompleted.init(LiteralStringRef("NativeAPI.GetValueCompleted"));
|
||||
|
||||
GlobalConfig::create(this, clientInfo);
|
||||
|
||||
monitorProxiesInfoChange = monitorProxiesChange(clientInfo, &proxiesChangeTrigger);
|
||||
monitorTssInfoChange = monitorTssChange(this);
|
||||
tssMismatchHandler = handleTssMismatches(this);
|
||||
clientStatusUpdater.actor = clientStatusUpdateActor(this);
|
||||
cacheListMonitor = monitorCacheList(this);
|
||||
|
|
@ -1758,7 +1724,9 @@ Database Database::createDatabase(Reference<ClusterConnectionFile> connFile,
|
|||
/*switchable*/ true);
|
||||
}
|
||||
|
||||
return Database(db);
|
||||
auto database = Database(db);
|
||||
GlobalConfig::create(database, clientInfo, std::addressof(clientInfo->get()));
|
||||
return database;
|
||||
}
|
||||
|
||||
Database Database::createDatabase(std::string connFileName,
|
||||
|
|
@ -2218,6 +2186,29 @@ ACTOR Future<Optional<vector<StorageServerInterface>>> transactionalGetServerInt
|
|||
return serverInterfaces;
|
||||
}
|
||||
|
||||
void updateTssMappings(Database cx, const GetKeyServerLocationsReply& reply) {
|
||||
// Since a ss -> tss mapping is included in resultsTssMapping iff that SS is in results and has a tss pair,
|
||||
// all SS in results that do not have a mapping present must not have a tss pair.
|
||||
std::unordered_map<UID, const StorageServerInterface*> ssiById;
|
||||
for (const auto& [_, shard] : reply.results) {
|
||||
for (auto& ssi : shard) {
|
||||
ssiById[ssi.id()] = &ssi;
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& mapping : reply.resultsTssMapping) {
|
||||
auto ssi = ssiById.find(mapping.first);
|
||||
ASSERT(ssi != ssiById.end());
|
||||
cx->addTssMapping(*ssi->second, mapping.second);
|
||||
ssiById.erase(mapping.first);
|
||||
}
|
||||
|
||||
// if SS didn't have a mapping above, it's still in the ssiById map, so remove its tss mapping
|
||||
for (const auto& it : ssiById) {
|
||||
cx->removeTssMapping(*it.second);
|
||||
}
|
||||
}
|
||||
|
||||
// If isBackward == true, returns the shard containing the key before 'key' (an infinitely long, inexpressible key).
|
||||
// Otherwise returns the shard containing key
|
||||
ACTOR Future<pair<KeyRange, Reference<LocationInfo>>> getKeyLocation_internal(Database cx,
|
||||
|
|
@ -2250,6 +2241,7 @@ ACTOR Future<pair<KeyRange, Reference<LocationInfo>>> getKeyLocation_internal(Da
|
|||
ASSERT(rep.results.size() == 1);
|
||||
|
||||
auto locationInfo = cx->setCachedLocation(rep.results[0].first, rep.results[0].second);
|
||||
updateTssMappings(cx, rep);
|
||||
return std::make_pair(KeyRange(rep.results[0].first, rep.arena), locationInfo);
|
||||
}
|
||||
}
|
||||
|
|
@ -2313,6 +2305,7 @@ ACTOR Future<vector<pair<KeyRange, Reference<LocationInfo>>>> getKeyRangeLocatio
|
|||
cx->setCachedLocation(rep.results[shard].first, rep.results[shard].second));
|
||||
wait(yield());
|
||||
}
|
||||
updateTssMappings(cx, rep);
|
||||
|
||||
return results;
|
||||
}
|
||||
|
|
@ -5885,3 +5878,23 @@ Future<Void> DatabaseContext::createSnapshot(StringRef uid, StringRef snapshot_c
|
|||
}
|
||||
return createSnapshotActor(this, UID::fromString(uid_str), snapshot_command);
|
||||
}
|
||||
|
||||
ACTOR Future<Void> setPerpetualStorageWiggle(Database cx, bool enable, bool lock_aware) {
|
||||
state ReadYourWritesTransaction tr(cx);
|
||||
loop {
|
||||
try {
|
||||
tr.setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS);
|
||||
if(lock_aware) {
|
||||
tr.setOption(FDBTransactionOptions::LOCK_AWARE);
|
||||
}
|
||||
|
||||
tr.set(perpetualStorageWiggleKey, enable ? LiteralStringRef("1") : LiteralStringRef("0"));
|
||||
wait(tr.commit());
|
||||
break;
|
||||
}
|
||||
catch (Error& e) {
|
||||
wait(tr.onError(e));
|
||||
}
|
||||
}
|
||||
return Void();
|
||||
}
|
||||
|
|
@ -409,5 +409,10 @@ ACTOR Future<bool> checkSafeExclusions(Database cx, vector<AddressExclusion> exc
|
|||
inline uint64_t getWriteOperationCost(uint64_t bytes) {
|
||||
return bytes / std::max(1, CLIENT_KNOBS->WRITE_COST_BYTE_FACTOR) + 1;
|
||||
}
|
||||
|
||||
// Create a transaction to set the value of system key \xff/conf/perpetual_storage_wiggle. If enable == true, the value
|
||||
// will be 1. Otherwise, the value will be 0.
|
||||
ACTOR Future<Void> setPerpetualStorageWiggle(Database cx, bool enable, bool lock_aware = false);
|
||||
|
||||
#include "flow/unactorcompiler.h"
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* This source file is part of the FoundationDB open source project
|
||||
*
|
||||
* Copyright 2013-2018 Apple Inc. and the FoundationDB project authors
|
||||
* Copyright 2013-2021 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.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* This source file is part of the FoundationDB open source project
|
||||
*
|
||||
* Copyright 2013-2018 Apple Inc. and the FoundationDB project authors
|
||||
* Copyright 2013-2021 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.
|
||||
|
|
|
|||
|
|
@ -755,6 +755,7 @@ const KeyRef JSONSchemas::statusSchema = LiteralStringRef(R"statusSchema(
|
|||
"auto_logs":3,
|
||||
"commit_proxies":5,
|
||||
"grv_proxies":1,
|
||||
"proxies":6,
|
||||
"backup_worker_enabled":1,
|
||||
"perpetual_storage_wiggle":0
|
||||
},
|
||||
|
|
|
|||
|
|
@ -94,7 +94,9 @@ void ServerKnobs::initialize(Randomize _randomize, ClientKnobs* clientKnobs, IsS
|
|||
init( PEEK_STATS_INTERVAL, 10.0 );
|
||||
init( PEEK_STATS_SLOW_AMOUNT, 2 );
|
||||
init( PEEK_STATS_SLOW_RATIO, 0.5 );
|
||||
init( PUSH_RESET_INTERVAL, 300.0 ); if ( randomize && BUGGIFY ) PUSH_RESET_INTERVAL = 20.0;
|
||||
// Buggified value must be larger than the amount of simulated time taken by snapshots, to prevent repeatedly failing
|
||||
// snapshots due to closed commit proxy connections
|
||||
init( PUSH_RESET_INTERVAL, 300.0 ); if ( randomize && BUGGIFY ) PUSH_RESET_INTERVAL = 40.0;
|
||||
init( PUSH_MAX_LATENCY, 0.5 ); if ( randomize && BUGGIFY ) PUSH_MAX_LATENCY = 0.0;
|
||||
init( PUSH_STATS_INTERVAL, 10.0 );
|
||||
init( PUSH_STATS_SLOW_AMOUNT, 2 );
|
||||
|
|
@ -128,6 +130,7 @@ void ServerKnobs::initialize(Randomize _randomize, ClientKnobs* clientKnobs, IsS
|
|||
init( PRIORITY_RECOVER_MOVE, 110 );
|
||||
init( PRIORITY_REBALANCE_UNDERUTILIZED_TEAM, 120 );
|
||||
init( PRIORITY_REBALANCE_OVERUTILIZED_TEAM, 121 );
|
||||
init( PRIORITY_PERPETUAL_STORAGE_WIGGLE, 139 );
|
||||
init( PRIORITY_TEAM_HEALTHY, 140 );
|
||||
init( PRIORITY_TEAM_CONTAINS_UNDESIRED_SERVER, 150 );
|
||||
init( PRIORITY_TEAM_REDUNDANT, 200 );
|
||||
|
|
@ -216,7 +219,7 @@ void ServerKnobs::initialize(Randomize _randomize, ClientKnobs* clientKnobs, IsS
|
|||
init( STORAGE_RECRUITMENT_DELAY, 10.0 );
|
||||
init( TSS_HACK_IDENTITY_MAPPING, false ); // THIS SHOULD NEVER BE SET IN PROD. Only for performance testing
|
||||
init( TSS_RECRUITMENT_TIMEOUT, 3*STORAGE_RECRUITMENT_DELAY ); if (randomize && BUGGIFY ) TSS_RECRUITMENT_TIMEOUT = 1.0; // Super low timeout should cause tss recruitments to fail
|
||||
init( TSS_DD_KILL_INTERVAL, 60.0 ); if (randomize && BUGGIFY ) TSS_DD_KILL_INTERVAL = 1.0; // May kill all TSS quickly
|
||||
init( TSS_DD_CHECK_INTERVAL, 60.0 ); if (randomize && BUGGIFY ) TSS_DD_CHECK_INTERVAL = 1.0; // May kill all TSS quickly
|
||||
init( DATA_DISTRIBUTION_LOGGING_INTERVAL, 5.0 );
|
||||
init( DD_ENABLED_CHECK_DELAY, 1.0 );
|
||||
init( DD_STALL_CHECK_DELAY, 0.4 ); //Must be larger than 2*MAX_BUGGIFIED_DELAY
|
||||
|
|
@ -250,6 +253,7 @@ void ServerKnobs::initialize(Randomize _randomize, ClientKnobs* clientKnobs, IsS
|
|||
init( DD_TEAMS_INFO_PRINT_INTERVAL, 60 ); if( randomize && BUGGIFY ) DD_TEAMS_INFO_PRINT_INTERVAL = 10;
|
||||
init( DD_TEAMS_INFO_PRINT_YIELD_COUNT, 100 ); if( randomize && BUGGIFY ) DD_TEAMS_INFO_PRINT_YIELD_COUNT = deterministicRandom()->random01() * 1000 + 1;
|
||||
init( DD_TEAM_ZERO_SERVER_LEFT_LOG_DELAY, 120 ); if( randomize && BUGGIFY ) DD_TEAM_ZERO_SERVER_LEFT_LOG_DELAY = 5;
|
||||
init( DD_STORAGE_WIGGLE_PAUSE_THRESHOLD, 1 ); if( randomize && BUGGIFY ) DD_STORAGE_WIGGLE_PAUSE_THRESHOLD = 10;
|
||||
|
||||
// TeamRemover
|
||||
init( TR_FLAG_DISABLE_MACHINE_TEAM_REMOVER, false ); if( randomize && BUGGIFY ) TR_FLAG_DISABLE_MACHINE_TEAM_REMOVER = deterministicRandom()->random01() < 0.1 ? true : false; // false by default. disable the consistency check when it's true
|
||||
|
|
@ -261,10 +265,6 @@ void ServerKnobs::initialize(Randomize _randomize, ClientKnobs* clientKnobs, IsS
|
|||
|
||||
init( DD_REMOVE_STORE_ENGINE_DELAY, 60.0 ); if( randomize && BUGGIFY ) DD_REMOVE_STORE_ENGINE_DELAY = deterministicRandom()->random01() * 60.0;
|
||||
|
||||
// Redwood Storage Engine
|
||||
init( PREFIX_TREE_IMMEDIATE_KEY_SIZE_LIMIT, 30 );
|
||||
init( PREFIX_TREE_IMMEDIATE_KEY_SIZE_MIN, 0 );
|
||||
|
||||
// KeyValueStore SQLITE
|
||||
init( CLEAR_BUFFER_SIZE, 20000 );
|
||||
init( READ_VALUE_TIME_ESTIMATE, .00005 );
|
||||
|
|
@ -705,8 +705,10 @@ void ServerKnobs::initialize(Randomize _randomize, ClientKnobs* clientKnobs, IsS
|
|||
init( FASTRESTORE_RATE_UPDATE_SECONDS, 1.0 ); if( randomize && BUGGIFY ) { FASTRESTORE_RATE_UPDATE_SECONDS = deterministicRandom()->random01() < 0.5 ? 0.1 : 2;}
|
||||
|
||||
init( REDWOOD_DEFAULT_PAGE_SIZE, 4096 );
|
||||
init( REDWOOD_DEFAULT_EXTENT_SIZE, 32 * 1024 * 1024 );
|
||||
init( REDWOOD_DEFAULT_EXTENT_READ_SIZE, 1024 * 1024 );
|
||||
init( REDWOOD_EXTENT_CONCURRENT_READS, 4 );
|
||||
init( REDWOOD_KVSTORE_CONCURRENT_READS, 64 );
|
||||
init( REDWOOD_COMMIT_CONCURRENT_READS, 64 );
|
||||
init( REDWOOD_PAGE_REBUILD_MAX_SLACK, 0.33 );
|
||||
init( REDWOOD_LAZY_CLEAR_BATCH_SIZE_PAGES, 10 );
|
||||
init( REDWOOD_LAZY_CLEAR_MIN_PAGES, 0 );
|
||||
|
|
|
|||
|
|
@ -132,6 +132,7 @@ public:
|
|||
int PRIORITY_RECOVER_MOVE;
|
||||
int PRIORITY_REBALANCE_UNDERUTILIZED_TEAM;
|
||||
int PRIORITY_REBALANCE_OVERUTILIZED_TEAM;
|
||||
int PRIORITY_PERPETUAL_STORAGE_WIGGLE;
|
||||
int PRIORITY_TEAM_HEALTHY;
|
||||
int PRIORITY_TEAM_CONTAINS_UNDESIRED_SERVER;
|
||||
int PRIORITY_TEAM_REDUNDANT;
|
||||
|
|
@ -168,7 +169,7 @@ public:
|
|||
double STORAGE_RECRUITMENT_DELAY;
|
||||
bool TSS_HACK_IDENTITY_MAPPING;
|
||||
double TSS_RECRUITMENT_TIMEOUT;
|
||||
double TSS_DD_KILL_INTERVAL;
|
||||
double TSS_DD_CHECK_INTERVAL;
|
||||
double DATA_DISTRIBUTION_LOGGING_INTERVAL;
|
||||
double DD_ENABLED_CHECK_DELAY;
|
||||
double DD_STALL_CHECK_DELAY;
|
||||
|
|
@ -202,6 +203,7 @@ public:
|
|||
int DD_TEAMS_INFO_PRINT_INTERVAL;
|
||||
int DD_TEAMS_INFO_PRINT_YIELD_COUNT;
|
||||
int DD_TEAM_ZERO_SERVER_LEFT_LOG_DELAY;
|
||||
int DD_STORAGE_WIGGLE_PAUSE_THRESHOLD; // How many unhealthy relocations are ongoing will pause storage wiggle
|
||||
|
||||
// TeamRemover to remove redundant teams
|
||||
bool TR_FLAG_DISABLE_MACHINE_TEAM_REMOVER; // disable the machineTeamRemover actor
|
||||
|
|
@ -641,6 +643,9 @@ public:
|
|||
double FASTRESTORE_RATE_UPDATE_SECONDS; // how long to update appliers target write rate
|
||||
|
||||
int REDWOOD_DEFAULT_PAGE_SIZE; // Page size for new Redwood files
|
||||
int REDWOOD_DEFAULT_EXTENT_SIZE; // Extent size for new Redwood files
|
||||
int REDWOOD_DEFAULT_EXTENT_READ_SIZE; // Extent read size for Redwood files
|
||||
int REDWOOD_EXTENT_CONCURRENT_READS; // Max number of simultaneous extent disk reads in progress.
|
||||
int REDWOOD_KVSTORE_CONCURRENT_READS; // Max number of simultaneous point or range reads in progress.
|
||||
int REDWOOD_COMMIT_CONCURRENT_READS; // Max number of concurrent reads done to support commit operations
|
||||
double REDWOOD_PAGE_REBUILD_MAX_SLACK; // When rebuilding pages, max slack to allow in page
|
||||
|
|
|
|||
|
|
@ -1384,6 +1384,9 @@ Future<RangeResult> GlobalConfigImpl::getRange(ReadYourWritesTransaction* ryw, K
|
|||
} else if (config->value.type() == typeid(int64_t)) {
|
||||
result.push_back_deep(result.arena(),
|
||||
KeyValueRef(prefixedKey, std::to_string(std::any_cast<int64_t>(config->value))));
|
||||
} else if (config->value.type() == typeid(bool)) {
|
||||
result.push_back_deep(result.arena(),
|
||||
KeyValueRef(prefixedKey, std::to_string(std::any_cast<bool>(config->value))));
|
||||
} else if (config->value.type() == typeid(float)) {
|
||||
result.push_back_deep(result.arena(),
|
||||
KeyValueRef(prefixedKey, std::to_string(std::any_cast<float>(config->value))));
|
||||
|
|
@ -2058,9 +2061,20 @@ Future<Optional<std::string>> DataDistributionImpl::commit(ReadYourWritesTransac
|
|||
try {
|
||||
int mode = boost::lexical_cast<int>(iter->value().second.get().toString());
|
||||
Value modeVal = BinaryWriter::toValue(mode, Unversioned());
|
||||
if (mode == 0 || mode == 1)
|
||||
if (mode == 0 || mode == 1) {
|
||||
// Whenever configuration changes or DD related system keyspace is changed,
|
||||
// actor must grab the moveKeysLockOwnerKey and update moveKeysLockWriteKey.
|
||||
// This prevents concurrent write to the same system keyspace.
|
||||
// When the owner of the DD related system keyspace changes, DD will reboot
|
||||
BinaryWriter wrMyOwner(Unversioned());
|
||||
wrMyOwner << dataDistributionModeLock;
|
||||
ryw->getTransaction().set(moveKeysLockOwnerKey, wrMyOwner.toValue());
|
||||
BinaryWriter wrLastWrite(Unversioned());
|
||||
wrLastWrite << deterministicRandom()->randomUniqueID();
|
||||
ryw->getTransaction().set(moveKeysLockWriteKey, wrLastWrite.toValue());
|
||||
// set mode
|
||||
ryw->getTransaction().set(dataDistributionModeKey, modeVal);
|
||||
else
|
||||
} else
|
||||
msg = ManagementAPIError::toJsonString(false,
|
||||
"datadistribution",
|
||||
"Please set the value of the data_distribution/mode to "
|
||||
|
|
|
|||
|
|
@ -263,6 +263,8 @@ TEST_CASE("/StorageServerInterface/TSSCompare/TestComparison") {
|
|||
gkvReq.begin = firstGreaterOrEqual(StringRef(a, s_a));
|
||||
gkvReq.end = firstGreaterOrEqual(StringRef(a, s_b));
|
||||
gkvReq.version = 5;
|
||||
gkvReq.limit = 100;
|
||||
gkvReq.limitBytes = 1000;
|
||||
|
||||
GetKeyValuesReply gkvReplyEmpty;
|
||||
GetKeyValuesReply gkvReplyOne;
|
||||
|
|
|
|||
|
|
@ -346,7 +346,6 @@ uint16_t cacheChangeKeyDecodeIndex(const KeyRef& key) {
|
|||
return idx;
|
||||
}
|
||||
|
||||
const KeyRef tssMappingChangeKey = LiteralStringRef("\xff\x02/tssMappingChangeKey");
|
||||
const KeyRangeRef tssMappingKeys(LiteralStringRef("\xff/tss/"), LiteralStringRef("\xff/tss0"));
|
||||
|
||||
const KeyRangeRef serverTagKeys(LiteralStringRef("\xff/serverTag/"), LiteralStringRef("\xff/serverTag0"));
|
||||
|
|
|
|||
|
|
@ -115,9 +115,7 @@ const Key cacheChangeKeyFor(uint16_t idx);
|
|||
uint16_t cacheChangeKeyDecodeIndex(const KeyRef& key);
|
||||
|
||||
// "\xff/tss/[[serverId]]" := "[[tssId]]"
|
||||
extern const KeyRef tssMappingChangeKey;
|
||||
extern const KeyRangeRef tssMappingKeys;
|
||||
extern const KeyRef tssMappingPrefix;
|
||||
|
||||
// "\xff/serverTag/[[serverID]]" = "[[Tag]]"
|
||||
// Provides the Tag for the given serverID. Used to access a
|
||||
|
|
|
|||
|
|
@ -194,6 +194,40 @@ struct TagThrottleInfo {
|
|||
}
|
||||
};
|
||||
|
||||
struct ClientTagThrottleLimits {
|
||||
double tpsRate;
|
||||
double expiration;
|
||||
|
||||
ClientTagThrottleLimits() : tpsRate(0), expiration(0) {}
|
||||
ClientTagThrottleLimits(double tpsRate, double expiration) : tpsRate(tpsRate), expiration(expiration) {}
|
||||
|
||||
template <class Archive>
|
||||
void serialize(Archive& ar) {
|
||||
// Convert expiration time to a duration to avoid clock differences
|
||||
double duration = 0;
|
||||
if (!ar.isDeserializing) {
|
||||
duration = expiration - now();
|
||||
}
|
||||
|
||||
serializer(ar, tpsRate, duration);
|
||||
|
||||
if (ar.isDeserializing) {
|
||||
expiration = now() + duration;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct ClientTrCommitCostEstimation {
|
||||
int opsCount = 0;
|
||||
uint64_t writeCosts = 0;
|
||||
std::deque<std::pair<int, uint64_t>> clearIdxCosts;
|
||||
uint32_t expensiveCostEstCount = 0;
|
||||
template <class Ar>
|
||||
void serialize(Ar& ar) {
|
||||
serializer(ar, opsCount, writeCosts, clearIdxCosts, expensiveCostEstCount);
|
||||
}
|
||||
};
|
||||
|
||||
namespace ThrottleApi {
|
||||
Future<std::vector<TagThrottleInfo>> getThrottledTags(Database const& db,
|
||||
int const& limit,
|
||||
|
|
|
|||
|
|
@ -477,7 +477,7 @@ void ThreadSafeApi::addNetworkThreadCompletionHook(void (*hook)(void*), void* ho
|
|||
|
||||
MutexHolder holder(lock); // We could use the network thread to protect this action, but then we can't guarantee
|
||||
// upon return that the hook is set.
|
||||
threadCompletionHooks.push_back(std::make_pair(hook, hookParameter));
|
||||
threadCompletionHooks.emplace_back(hook, hookParameter);
|
||||
}
|
||||
|
||||
IClientApi* ThreadSafeApi::api = new ThreadSafeApi();
|
||||
|
|
|
|||
|
|
@ -71,6 +71,8 @@ Tuple::Tuple(StringRef const& str, bool exclude_incomplete) {
|
|||
i += sizeof(float) + 1;
|
||||
} else if (data[i] == 0x21) {
|
||||
i += sizeof(double) + 1;
|
||||
} else if (data[i] == 0x26 || data[i] == 0x27) {
|
||||
i += 1;
|
||||
} else if (data[i] == '\x00') {
|
||||
i += 1;
|
||||
} else {
|
||||
|
|
@ -144,6 +146,16 @@ Tuple& Tuple::append(int64_t value) {
|
|||
return *this;
|
||||
}
|
||||
|
||||
Tuple& Tuple::appendBool(bool value) {
|
||||
offsets.push_back(data.size());
|
||||
if (value) {
|
||||
data.push_back(data.arena(), 0x27);
|
||||
} else {
|
||||
data.push_back(data.arena(), 0x26);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Tuple& Tuple::appendFloat(float value) {
|
||||
offsets.push_back(data.size());
|
||||
float swap = bigEndianFloat(value);
|
||||
|
|
@ -192,6 +204,8 @@ Tuple::ElementType Tuple::getType(size_t index) const {
|
|||
return ElementType::FLOAT;
|
||||
} else if (code == 0x21) {
|
||||
return ElementType::DOUBLE;
|
||||
} else if (code == 0x26 || code == 0x27) {
|
||||
return ElementType::BOOL;
|
||||
} else {
|
||||
throw invalid_tuple_data_type();
|
||||
}
|
||||
|
|
@ -287,6 +301,21 @@ int64_t Tuple::getInt(size_t index, bool allow_incomplete) const {
|
|||
}
|
||||
|
||||
// TODO: Combine with bindings/flow/Tuple.*. This code is copied from there.
|
||||
bool Tuple::getBool(size_t index) const {
|
||||
if (index >= offsets.size()) {
|
||||
throw invalid_tuple_index();
|
||||
}
|
||||
ASSERT_LT(offsets[index], data.size());
|
||||
uint8_t code = data[offsets[index]];
|
||||
if (code == 0x26) {
|
||||
return false;
|
||||
} else if (code == 0x27) {
|
||||
return true;
|
||||
} else {
|
||||
throw invalid_tuple_data_type();
|
||||
}
|
||||
}
|
||||
|
||||
float Tuple::getFloat(size_t index) const {
|
||||
if (index >= offsets.size()) {
|
||||
throw invalid_tuple_index();
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ struct Tuple {
|
|||
Tuple& append(int64_t);
|
||||
// There are some ambiguous append calls in fdbclient, so to make it easier
|
||||
// to add append for floats and doubles, name them differently for now.
|
||||
Tuple& appendBool(bool);
|
||||
Tuple& appendFloat(float);
|
||||
Tuple& appendDouble(double);
|
||||
Tuple& appendNull();
|
||||
|
|
@ -51,7 +52,7 @@ struct Tuple {
|
|||
return append(t);
|
||||
}
|
||||
|
||||
enum ElementType { NULL_TYPE, INT, BYTES, UTF8, FLOAT, DOUBLE };
|
||||
enum ElementType { NULL_TYPE, INT, BYTES, UTF8, BOOL, FLOAT, DOUBLE };
|
||||
|
||||
// this is number of elements, not length of data
|
||||
size_t size() const { return offsets.size(); }
|
||||
|
|
@ -59,6 +60,7 @@ struct Tuple {
|
|||
ElementType getType(size_t index) const;
|
||||
Standalone<StringRef> getString(size_t index) const;
|
||||
int64_t getInt(size_t index, bool allow_incomplete = false) const;
|
||||
bool getBool(size_t index) const;
|
||||
float getFloat(size_t index) const;
|
||||
double getDouble(size_t index) const;
|
||||
|
||||
|
|
|
|||
|
|
@ -856,7 +856,7 @@ void load_conf(const char* confpath, uid_t& uid, gid_t& gid, sigset_t* mask, fdb
|
|||
|
||||
if (id_command[i.first]->kill_on_configuration_change) {
|
||||
kill_ids.push_back(i.first);
|
||||
start_ids.push_back(std::make_pair(i.first, cmd));
|
||||
start_ids.emplace_back(i.first, cmd);
|
||||
}
|
||||
} else {
|
||||
log_msg(SevInfo, "Updated configuration for %s\n", id_command[i.first]->ssection.c_str());
|
||||
|
|
|
|||
|
|
@ -141,16 +141,23 @@ public:
|
|||
// Opens a file that uses the FDB in-memory page cache
|
||||
static Future<Reference<IAsyncFile>> open(std::string filename, int flags, int mode) {
|
||||
//TraceEvent("AsyncFileCachedOpen").detail("Filename", filename);
|
||||
if (openFiles.find(filename) == openFiles.end()) {
|
||||
auto itr = openFiles.find(filename);
|
||||
if (itr == openFiles.end()) {
|
||||
auto f = open_impl(filename, flags, mode);
|
||||
if (f.isReady() && f.isError())
|
||||
return f;
|
||||
if (!f.isReady())
|
||||
openFiles[filename] = UnsafeWeakFutureReference<IAsyncFile>(f);
|
||||
else
|
||||
return f.get();
|
||||
|
||||
auto result = openFiles.try_emplace(filename, f);
|
||||
|
||||
// This should be inserting a new entry
|
||||
ASSERT(result.second);
|
||||
itr = result.first;
|
||||
|
||||
// We return here instead of falling through to the outer scope so that we don't delete all references to
|
||||
// the underlying file before returning
|
||||
return itr->second.get();
|
||||
}
|
||||
return openFiles[filename].get();
|
||||
return itr->second.get();
|
||||
}
|
||||
|
||||
Future<int> read(void* data, int length, int64_t offset) override {
|
||||
|
|
|
|||
|
|
@ -1484,3 +1484,133 @@ TEST_CASE("/flow/flow/PromiseStream/move2") {
|
|||
ASSERT(movedTracker.copied == 0);
|
||||
return Void();
|
||||
}
|
||||
|
||||
constexpr double mutexTestDelay = 0.00001;
|
||||
|
||||
ACTOR Future<Void> mutexTest(int id, FlowMutex* mutex, int n, bool allowError, bool* verbose) {
|
||||
while (n-- > 0) {
|
||||
state double d = deterministicRandom()->random01() * mutexTestDelay;
|
||||
if (*verbose) {
|
||||
printf("%d:%d wait %f while unlocked\n", id, n, d);
|
||||
}
|
||||
wait(delay(d));
|
||||
|
||||
if (*verbose) {
|
||||
printf("%d:%d locking\n", id, n);
|
||||
}
|
||||
state FlowMutex::Lock lock = wait(mutex->take());
|
||||
if (*verbose) {
|
||||
printf("%d:%d locked\n", id, n);
|
||||
}
|
||||
|
||||
d = deterministicRandom()->random01() * mutexTestDelay;
|
||||
if (*verbose) {
|
||||
printf("%d:%d wait %f while locked\n", id, n, d);
|
||||
}
|
||||
wait(delay(d));
|
||||
|
||||
// On the last iteration, send an error or drop the lock if allowError is true
|
||||
if (n == 0 && allowError) {
|
||||
if (deterministicRandom()->coinflip()) {
|
||||
// Send explicit error
|
||||
if (*verbose) {
|
||||
printf("%d:%d sending error\n", id, n);
|
||||
}
|
||||
lock.error(end_of_stream());
|
||||
} else {
|
||||
// Do nothing
|
||||
if (*verbose) {
|
||||
printf("%d:%d dropping promise, returning without unlock\n", id, n);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (*verbose) {
|
||||
printf("%d:%d unlocking\n", id, n);
|
||||
}
|
||||
lock.release();
|
||||
}
|
||||
}
|
||||
|
||||
if (*verbose) {
|
||||
printf("%d Returning\n", id);
|
||||
}
|
||||
return Void();
|
||||
}
|
||||
|
||||
TEST_CASE("/flow/flow/FlowMutex") {
|
||||
state int count = 100000;
|
||||
|
||||
// Default verboseness
|
||||
state bool verboseSetting = false;
|
||||
// Useful for debugging, enable verbose mode for this iteration number
|
||||
state int verboseTestIteration = -1;
|
||||
|
||||
try {
|
||||
state bool verbose = verboseSetting || count == verboseTestIteration;
|
||||
|
||||
while (--count > 0) {
|
||||
if (count % 1000 == 0) {
|
||||
printf("%d tests left\n", count);
|
||||
}
|
||||
|
||||
state FlowMutex mutex;
|
||||
state std::vector<Future<Void>> tests;
|
||||
|
||||
state bool allowErrors = deterministicRandom()->coinflip();
|
||||
if (verbose) {
|
||||
printf("\nTesting allowErrors=%d\n", allowErrors);
|
||||
}
|
||||
|
||||
state Optional<Error> error;
|
||||
|
||||
try {
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
tests.push_back(mutexTest(i, &mutex, 10, allowErrors, &verbose));
|
||||
}
|
||||
wait(waitForAll(tests));
|
||||
|
||||
if (allowErrors) {
|
||||
if (verbose) {
|
||||
printf("Final wait in case error was injected by the last actor to finish\n");
|
||||
}
|
||||
wait(success(mutex.take()));
|
||||
}
|
||||
} catch (Error& e) {
|
||||
if (verbose) {
|
||||
printf("Caught error %s\n", e.what());
|
||||
}
|
||||
error = e;
|
||||
|
||||
// Wait for all actors still running to finish their waits and try to take the mutex
|
||||
if (verbose) {
|
||||
printf("Waiting for completions\n");
|
||||
}
|
||||
wait(delay(2 * mutexTestDelay));
|
||||
|
||||
if (verbose) {
|
||||
printf("Future end states:\n");
|
||||
}
|
||||
// All futures should be ready, some with errors.
|
||||
bool allReady = true;
|
||||
for (int i = 0; i < tests.size(); ++i) {
|
||||
auto f = tests[i];
|
||||
if (verbose) {
|
||||
printf(
|
||||
" %d: %s\n", i, f.isReady() ? (f.isError() ? f.getError().what() : "done") : "not ready");
|
||||
}
|
||||
allReady = allReady && f.isReady();
|
||||
}
|
||||
ASSERT(allReady);
|
||||
}
|
||||
|
||||
// If an error was caused, one should have been detected.
|
||||
// Otherwise, no errors should be detected.
|
||||
ASSERT(error.present() == allowErrors);
|
||||
}
|
||||
} catch (Error& e) {
|
||||
printf("Error at count=%d\n", count + 1);
|
||||
ASSERT(false);
|
||||
}
|
||||
|
||||
return Void();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@
|
|||
|
||||
void HealthMonitor::reportPeerClosed(const NetworkAddress& peerAddress) {
|
||||
purgeOutdatedHistory();
|
||||
peerClosedHistory.push_back(std::make_pair(now(), peerAddress));
|
||||
peerClosedHistory.emplace_back(now(), peerAddress);
|
||||
peerClosedNum[peerAddress] += 1;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -144,6 +144,7 @@ Future<Void> tssComparison(Req req,
|
|||
: SevError;
|
||||
|
||||
if (!TSS_doCompare(req, src.get(), tss.get().get(), traceSeverity, tssData.tssId)) {
|
||||
TEST(true); // TSS Mismatch
|
||||
++tssData.metrics->mismatches;
|
||||
}
|
||||
} else if (tssLB.present() && tssLB.get().error.present()) {
|
||||
|
|
@ -192,6 +193,7 @@ struct RequestData : NonCopyable {
|
|||
Optional<TSSEndpointData> tssData = model->getTssData(stream->getEndpoint().token.first());
|
||||
|
||||
if (tssData.present()) {
|
||||
TEST(true); // duplicating request to TSS
|
||||
resetReply(request);
|
||||
// FIXME: optimize to avoid creating new netNotifiedQueue for each message
|
||||
RequestStream<Request> tssRequestStream(tssData.get().endpoint);
|
||||
|
|
|
|||
|
|
@ -62,24 +62,12 @@ double QueueModel::addRequest(uint64_t id) {
|
|||
|
||||
void QueueModel::updateTssEndpoint(uint64_t endpointId, const TSSEndpointData& tssData) {
|
||||
auto& d = data[endpointId];
|
||||
if (!d.tssData.present()) {
|
||||
tssCount++;
|
||||
d.tssData = Optional<TSSEndpointData>(tssData);
|
||||
} else {
|
||||
d.tssData.get().generation = tssData.generation;
|
||||
}
|
||||
d.tssData = tssData;
|
||||
}
|
||||
|
||||
void QueueModel::removeOldTssData(UID currentGeneration) {
|
||||
if (tssCount > 0) {
|
||||
// expire old tss mappings that aren't present in new mapping
|
||||
for (auto& it : data) {
|
||||
if (it.second.tssData.present() && it.second.tssData.get().generation != currentGeneration) {
|
||||
it.second.tssData = Optional<TSSEndpointData>();
|
||||
tssCount--;
|
||||
}
|
||||
}
|
||||
}
|
||||
void QueueModel::removeTssEndpoint(uint64_t endpointId) {
|
||||
auto& d = data[endpointId];
|
||||
d.tssData = Optional<TSSEndpointData>();
|
||||
}
|
||||
|
||||
Optional<TSSEndpointData> QueueModel::getTssData(uint64_t id) {
|
||||
|
|
|
|||
|
|
@ -33,10 +33,9 @@ struct TSSEndpointData {
|
|||
UID tssId;
|
||||
Endpoint endpoint;
|
||||
Reference<TSSMetrics> metrics;
|
||||
UID generation;
|
||||
|
||||
TSSEndpointData(UID tssId, Endpoint endpoint, Reference<TSSMetrics> metrics, UID generation)
|
||||
: tssId(tssId), endpoint(endpoint), metrics(metrics), generation(generation) {}
|
||||
TSSEndpointData(UID tssId, Endpoint endpoint, Reference<TSSMetrics> metrics)
|
||||
: tssId(tssId), endpoint(endpoint), metrics(metrics) {}
|
||||
};
|
||||
|
||||
// The data structure used for the client-side load balancing algorithm to
|
||||
|
|
@ -110,11 +109,16 @@ public:
|
|||
int laggingRequestCount;
|
||||
int laggingTSSCompareCount;
|
||||
|
||||
// Updates this endpoint data to duplicate requests to the specified TSS endpoint
|
||||
void updateTssEndpoint(uint64_t endpointId, const TSSEndpointData& endpointData);
|
||||
void removeOldTssData(UID currentGeneration);
|
||||
|
||||
// Removes the TSS mapping from this endpoint to stop duplicating requests to a TSS endpoint
|
||||
void removeTssEndpoint(uint64_t endpointId);
|
||||
|
||||
// Retrieves the data for this endpoint's pair TSS endpoint, if present
|
||||
Optional<TSSEndpointData> getTssData(uint64_t endpointId);
|
||||
|
||||
QueueModel() : secondMultiplier(1.0), secondBudget(0), laggingRequestCount(0), tssCount(0) {
|
||||
QueueModel() : secondMultiplier(1.0), secondBudget(0), laggingRequestCount(0) {
|
||||
laggingRequests = actorCollection(addActor.getFuture(), &laggingRequestCount);
|
||||
tssComparisons = actorCollection(addTSSActor.getFuture(), &laggingTSSCompareCount);
|
||||
}
|
||||
|
|
@ -126,7 +130,6 @@ public:
|
|||
|
||||
private:
|
||||
std::unordered_map<uint64_t, QueueData> data;
|
||||
uint32_t tssCount;
|
||||
};
|
||||
|
||||
/* old queue model
|
||||
|
|
@ -149,4 +152,4 @@ private:
|
|||
};
|
||||
*/
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
|
||||
#ifndef FDBRPC_STATS_H
|
||||
#define FDBRPC_STATS_H
|
||||
#include <type_traits>
|
||||
#pragma once
|
||||
|
||||
// Yet another performance statistics interface
|
||||
|
|
@ -136,7 +137,15 @@ struct SpecialCounter final : ICounter, FastAllocated<SpecialCounter<F>>, NonCop
|
|||
void remove() override { delete this; }
|
||||
|
||||
std::string const& getName() const override { return name; }
|
||||
int64_t getValue() const override { return f(); }
|
||||
int64_t getValue() const override {
|
||||
auto result = f();
|
||||
// Disallow conversion from floating point to int64_t, since this has
|
||||
// been a source of confusion - e.g. a percentage represented as a
|
||||
// fraction between 0 and 1 is not meaningful after conversion to
|
||||
// int64_t.
|
||||
static_assert(!std::is_floating_point_v<decltype(result)>);
|
||||
return result;
|
||||
}
|
||||
|
||||
void resetInterval() override {}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
*/
|
||||
|
||||
#include "fdbclient/MutationList.h"
|
||||
#include "fdbclient/KeyBackedTypes.h" // for key backed map codecs for tss mapping
|
||||
#include "fdbclient/SystemData.h"
|
||||
#include "fdbclient/BackupAgent.actor.h"
|
||||
#include "fdbclient/Notified.h"
|
||||
|
|
@ -64,6 +65,7 @@ void applyMetadataMutations(SpanID const& spanContext,
|
|||
NotifiedVersion* commitVersion,
|
||||
std::map<UID, Reference<StorageInfo>>* storageCache,
|
||||
std::map<Tag, Version>* tag_popped,
|
||||
std::unordered_map<UID, StorageServerInterface>* tssMapping,
|
||||
bool initialCommit) {
|
||||
// std::map<keyRef, vector<uint16_t>> cacheRangeInfo;
|
||||
std::map<KeyRef, MutationRef> cachedRangeInfo;
|
||||
|
|
@ -72,7 +74,9 @@ void applyMetadataMutations(SpanID const& spanContext,
|
|||
// tss + find partner's tag to send the private mutation. Since the removeStorageServer transaction clears both the
|
||||
// storage list and server tag, we have to enforce ordering, proccessing the server tag first, and postpone the
|
||||
// server list clear until the end;
|
||||
// Similarly, the TSS mapping change key needs to read the server list at the end of the commit
|
||||
std::vector<KeyRangeRef> tssServerListToRemove;
|
||||
std::vector<std::pair<UID, UID>> tssMappingToAdd;
|
||||
|
||||
for (auto const& m : mutations) {
|
||||
//TraceEvent("MetadataMutation", dbgid).detail("M", m.toString());
|
||||
|
|
@ -240,6 +244,29 @@ void applyMetadataMutations(SpanID const& spanContext,
|
|||
}
|
||||
}
|
||||
}
|
||||
} else if (m.param1.startsWith(tssMappingKeys.begin)) {
|
||||
if (!initialCommit) {
|
||||
txnStateStore->set(KeyValueRef(m.param1, m.param2));
|
||||
if (tssMapping) {
|
||||
// Normally uses key backed map, so have to use same unpacking code here.
|
||||
UID ssId = Codec<UID>::unpack(Tuple::unpack(m.param1.removePrefix(tssMappingKeys.begin)));
|
||||
UID tssId = Codec<UID>::unpack(Tuple::unpack(m.param2));
|
||||
|
||||
tssMappingToAdd.push_back(std::pair(ssId, tssId));
|
||||
|
||||
// send private mutation to SS that it now has a TSS pair
|
||||
if (toCommit) {
|
||||
MutationRef privatized = m;
|
||||
privatized.param1 = m.param1.withPrefix(systemKeys.begin, arena);
|
||||
|
||||
Optional<Value> tagV = txnStateStore->readValue(serverTagKeyFor(ssId)).get();
|
||||
if (tagV.present()) {
|
||||
toCommit->addTag(decodeServerTagValue(tagV.get()));
|
||||
toCommit->writeTypedMessage(privatized);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (m.param1 == databaseLockedKey || m.param1 == metadataVersionKey ||
|
||||
m.param1 == mustContainSystemMutationsKey ||
|
||||
m.param1.startsWith(applyMutationsBeginRange.begin) ||
|
||||
|
|
@ -430,7 +457,7 @@ void applyMetadataMutations(SpanID const& spanContext,
|
|||
}
|
||||
// Might be a tss removal, which doesn't store a tag there.
|
||||
// Chained if is a little verbose, but avoids unecessary work
|
||||
if (!initialCommit && !serverKeysCleared.size()) {
|
||||
if (toCommit && !initialCommit && !serverKeysCleared.size()) {
|
||||
KeyRangeRef maybeTssRange = range & serverTagKeys;
|
||||
if (maybeTssRange.singleKeyRange()) {
|
||||
UID id = decodeServerTagKey(maybeTssRange.begin);
|
||||
|
|
@ -482,6 +509,19 @@ void applyMetadataMutations(SpanID const& spanContext,
|
|||
if (!initialCommit)
|
||||
txnStateStore->clear(range & serverTagHistoryKeys);
|
||||
}
|
||||
if (tssMappingKeys.intersects(range)) {
|
||||
if (!initialCommit) {
|
||||
KeyRangeRef rangeToClear = range & tssMappingKeys;
|
||||
ASSERT(rangeToClear.singleKeyRange());
|
||||
txnStateStore->clear(rangeToClear);
|
||||
if (tssMapping) {
|
||||
// Normally uses key backed map, so have to use same unpacking code here.
|
||||
UID ssId =
|
||||
Codec<UID>::unpack(Tuple::unpack(rangeToClear.begin.removePrefix(tssMappingKeys.begin)));
|
||||
tssMapping->erase(ssId);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (range.contains(coordinatorsKey)) {
|
||||
if (!initialCommit)
|
||||
txnStateStore->clear(singleKeyRange(coordinatorsKey));
|
||||
|
|
@ -615,6 +655,13 @@ void applyMetadataMutations(SpanID const& spanContext,
|
|||
txnStateStore->clear(range);
|
||||
}
|
||||
|
||||
for (auto& tssPair : tssMappingToAdd) {
|
||||
// read tss server list from txn state store and add it to tss mapping
|
||||
StorageServerInterface tssi =
|
||||
decodeServerListValue(txnStateStore->readValue(serverListKeyFor(tssPair.second)).get().get());
|
||||
(*tssMapping)[tssPair.first] = tssi;
|
||||
}
|
||||
|
||||
// If we accumulated private mutations for cached key-ranges, we also need to
|
||||
// tag them with the relevant storage servers. This is done to make the storage
|
||||
// servers aware of the cached key-ranges
|
||||
|
|
@ -713,6 +760,7 @@ void applyMetadataMutations(SpanID const& spanContext,
|
|||
&proxyCommitData.committedVersion,
|
||||
&proxyCommitData.storageCache,
|
||||
&proxyCommitData.tag_popped,
|
||||
&proxyCommitData.tssMapping,
|
||||
initialCommit);
|
||||
}
|
||||
|
||||
|
|
@ -742,5 +790,6 @@ void applyMetadataMutations(SpanID const& spanContext,
|
|||
/* commitVersion= */ nullptr,
|
||||
/* storageCache= */ nullptr,
|
||||
/* tag_popped= */ nullptr,
|
||||
/* tssMapping= */ nullptr,
|
||||
/* initialCommit= */ false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,6 @@ set(FDBSERVER_SRCS
|
|||
IKeyValueContainer.h
|
||||
IKeyValueStore.h
|
||||
IPager.h
|
||||
IVersionedStore.h
|
||||
KeyValueStoreCompressTestData.actor.cpp
|
||||
KeyValueStoreMemory.actor.cpp
|
||||
KeyValueStoreRocksDB.actor.cpp
|
||||
|
|
@ -124,6 +123,8 @@ set(FDBSERVER_SRCS
|
|||
TesterInterface.actor.h
|
||||
TLogInterface.h
|
||||
TLogServer.actor.cpp
|
||||
TSSMappingUtil.actor.h
|
||||
TSSMappingUtil.actor.cpp
|
||||
VersionedBTree.actor.cpp
|
||||
VFSAsync.h
|
||||
VFSAsync.cpp
|
||||
|
|
|
|||
|
|
@ -600,8 +600,8 @@ public:
|
|||
std::vector<std::tuple<ProcessClass::Fitness, int, bool, int, Field>> orderedFields;
|
||||
for (auto& it : fieldsWithMin) {
|
||||
auto& fitness = field_fitness[it];
|
||||
orderedFields.push_back(std::make_tuple(
|
||||
std::get<0>(fitness), std::get<1>(fitness), std::get<2>(fitness), field_count[it], it));
|
||||
orderedFields.emplace_back(
|
||||
std::get<0>(fitness), std::get<1>(fitness), std::get<2>(fitness), field_count[it], it);
|
||||
}
|
||||
std::sort(orderedFields.begin(), orderedFields.end());
|
||||
int totalFields = desired / minPerField;
|
||||
|
|
@ -1693,20 +1693,37 @@ public:
|
|||
if (req.configuration.regions.size() > 1) {
|
||||
std::vector<RegionInfo> regions = req.configuration.regions;
|
||||
if (regions[0].priority == regions[1].priority && regions[1].dcId == clusterControllerDcId.get()) {
|
||||
TraceEvent("CCSwitchPrimaryDc", id)
|
||||
.detail("CCDcId", clusterControllerDcId.get())
|
||||
.detail("OldPrimaryDcId", regions[0].dcId)
|
||||
.detail("NewPrimaryDcId", regions[1].dcId);
|
||||
std::swap(regions[0], regions[1]);
|
||||
}
|
||||
|
||||
if (regions[1].dcId == clusterControllerDcId.get() &&
|
||||
(!versionDifferenceUpdated || datacenterVersionDifference >= SERVER_KNOBS->MAX_VERSION_DIFFERENCE)) {
|
||||
if (regions[1].priority >= 0) {
|
||||
TraceEvent("CCSwitchPrimaryDcVersionDifference", id)
|
||||
.detail("CCDcId", clusterControllerDcId.get())
|
||||
.detail("OldPrimaryDcId", regions[0].dcId)
|
||||
.detail("NewPrimaryDcId", regions[1].dcId);
|
||||
std::swap(regions[0], regions[1]);
|
||||
} else {
|
||||
TraceEvent(SevWarnAlways, "CCDcPriorityNegative")
|
||||
.detail("DcId", regions[1].dcId)
|
||||
.detail("Priority", regions[1].priority);
|
||||
.detail("Priority", regions[1].priority)
|
||||
.detail("FindWorkersInDc", regions[0].dcId)
|
||||
.detail("Warning", "Failover did not happen but CC is in remote DC");
|
||||
}
|
||||
}
|
||||
|
||||
TraceEvent("CCFindWorkersForConfiguration", id)
|
||||
.detail("CCDcId", clusterControllerDcId.get())
|
||||
.detail("Region0DcId", regions[0].dcId)
|
||||
.detail("Region1DcId", regions[1].dcId)
|
||||
.detail("DatacenterVersionDifference", datacenterVersionDifference)
|
||||
.detail("VersionDifferenceUpdated", versionDifferenceUpdated);
|
||||
|
||||
bool setPrimaryDesired = false;
|
||||
try {
|
||||
auto reply = findWorkersForConfigurationFromDC(req, regions[0].dcId);
|
||||
|
|
@ -1720,6 +1737,10 @@ public:
|
|||
} else if (regions[0].dcId == clusterControllerDcId.get()) {
|
||||
return reply.get();
|
||||
}
|
||||
TraceEvent(SevWarn, "CCRecruitmentFailed", id)
|
||||
.detail("Reason", "Recruited Txn system and CC are in different DCs")
|
||||
.detail("CCDcId", clusterControllerDcId.get())
|
||||
.detail("RecruitedTxnSystemDcId", regions[0].dcId);
|
||||
throw no_more_servers();
|
||||
} catch (Error& e) {
|
||||
if (!goodRemoteRecruitmentTime.isReady() && regions[1].dcId != clusterControllerDcId.get()) {
|
||||
|
|
@ -1729,7 +1750,9 @@ public:
|
|||
if (e.code() != error_code_no_more_servers || regions[1].priority < 0) {
|
||||
throw;
|
||||
}
|
||||
TraceEvent(SevWarn, "AttemptingRecruitmentInRemoteDC", id).error(e);
|
||||
TraceEvent(SevWarn, "AttemptingRecruitmentInRemoteDc", id)
|
||||
.detail("SetPrimaryDesired", setPrimaryDesired)
|
||||
.error(e);
|
||||
auto reply = findWorkersForConfigurationFromDC(req, regions[1].dcId);
|
||||
if (!setPrimaryDesired) {
|
||||
vector<Optional<Key>> dcPriority;
|
||||
|
|
@ -3391,7 +3414,6 @@ void clusterRegisterMaster(ClusterControllerData* self, RegisterMasterRequest co
|
|||
clientInfo.id = deterministicRandom()->randomUniqueID();
|
||||
clientInfo.commitProxies = req.commitProxies;
|
||||
clientInfo.grvProxies = req.grvProxies;
|
||||
clientInfo.tssMapping = db->clientInfo->get().tssMapping;
|
||||
db->clientInfo->set(clientInfo);
|
||||
dbInfo.client = db->clientInfo->get();
|
||||
}
|
||||
|
|
@ -3869,118 +3891,6 @@ ACTOR Future<Void> monitorServerInfoConfig(ClusterControllerData::DBInfo* db) {
|
|||
}
|
||||
}
|
||||
|
||||
// Monitors the tss mapping change key for changes,
|
||||
// and broadcasts the new tss mapping to the rest of the cluster in ClientDBInfo.
|
||||
ACTOR Future<Void> monitorTSSMapping(ClusterControllerData* self) {
|
||||
state KeyBackedMap<UID, UID> tssMapDB = KeyBackedMap<UID, UID>(tssMappingKeys.begin);
|
||||
loop {
|
||||
state Reference<ReadYourWritesTransaction> tr =
|
||||
Reference<ReadYourWritesTransaction>(new ReadYourWritesTransaction(self->db.db));
|
||||
loop {
|
||||
try {
|
||||
tr->setOption(FDBTransactionOptions::READ_SYSTEM_KEYS);
|
||||
tr->setOption(FDBTransactionOptions::PRIORITY_SYSTEM_IMMEDIATE);
|
||||
tr->setOption(FDBTransactionOptions::READ_LOCK_AWARE);
|
||||
|
||||
std::vector<std::pair<UID, UID>> tssResults =
|
||||
wait(tssMapDB.getRange(tr, UID(), Optional<UID>(), CLIENT_KNOBS->TOO_MANY));
|
||||
ASSERT(tssResults.size() < CLIENT_KNOBS->TOO_MANY);
|
||||
|
||||
state std::unordered_map<UID, UID> tssIdMap;
|
||||
std::set<UID> seenTssIds;
|
||||
|
||||
for (auto& it : tssResults) {
|
||||
tssIdMap[it.first] = it.second;
|
||||
// ensure two storage servers don't map to same TSS
|
||||
ASSERT(seenTssIds.insert(it.second).second);
|
||||
// ensure a storage server doesn't accidentally map to itself (unless we're in HACK_IDENTITY_MAPPING
|
||||
// mode)
|
||||
ASSERT(SERVER_KNOBS->TSS_HACK_IDENTITY_MAPPING || it.first != it.second);
|
||||
}
|
||||
|
||||
state std::vector<std::pair<UID, StorageServerInterface>> newMapping;
|
||||
state std::map<UID, StorageServerInterface> oldMapping;
|
||||
state bool mappingChanged = false;
|
||||
|
||||
state ClientDBInfo clientInfo = self->db.clientInfo->get();
|
||||
|
||||
for (auto& it : clientInfo.tssMapping) {
|
||||
oldMapping[it.first] = it.second;
|
||||
if (!tssIdMap.count(it.first)) {
|
||||
TraceEvent("TSS_MappingRemoved", self->id)
|
||||
.detail("SSID", it.first)
|
||||
.detail("TSSID", it.second.id());
|
||||
mappingChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& it : tssIdMap) {
|
||||
bool ssAlreadyPaired = oldMapping.count(it.first);
|
||||
|
||||
state Optional<UID> oldTssId;
|
||||
state Optional<UID> oldGetValueEndpoint;
|
||||
|
||||
if (ssAlreadyPaired) {
|
||||
auto interf = oldMapping[it.first];
|
||||
// check if this SS maps to a new TSS
|
||||
oldTssId = Optional<UID>(interf.id());
|
||||
oldGetValueEndpoint = Optional<UID>(interf.getValue.getEndpoint().token);
|
||||
if (interf.id() != it.second) {
|
||||
TraceEvent("TSS_MappingChanged", self->id)
|
||||
.detail("SSID", it.first)
|
||||
.detail("TSSID", it.second)
|
||||
.detail("OldTSSID", interf.id());
|
||||
mappingChanged = true;
|
||||
}
|
||||
} else {
|
||||
TraceEvent("TSS_MappingAdded", self->id).detail("SSID", it.first).detail("TSSID", it.second);
|
||||
mappingChanged = true;
|
||||
}
|
||||
|
||||
state UID ssid = it.first;
|
||||
state UID tssid = it.second;
|
||||
// request storage server interface for tssid, add it to results
|
||||
Optional<Value> tssiVal = wait(tr->get(serverListKeyFor(it.second)));
|
||||
|
||||
// because we read the tss mapping in the same transaction, there can be no races with tss removal
|
||||
// and the tss interface must exist
|
||||
ASSERT(tssiVal.present());
|
||||
|
||||
StorageServerInterface tssi = decodeServerListValue(tssiVal.get());
|
||||
if (oldTssId.present() && tssi.id() == oldTssId.get() && oldGetValueEndpoint.present() &&
|
||||
oldGetValueEndpoint.get() != tssi.getValue.getEndpoint().token) {
|
||||
mappingChanged = true;
|
||||
}
|
||||
newMapping.push_back(std::pair<UID, StorageServerInterface>(ssid, tssi));
|
||||
}
|
||||
|
||||
// if nothing changed, skip updating
|
||||
if (mappingChanged) {
|
||||
clientInfo.id = deterministicRandom()->randomUniqueID();
|
||||
clientInfo.tssMapping = newMapping;
|
||||
self->db.clientInfo->set(clientInfo);
|
||||
|
||||
ServerDBInfo serverInfo = self->db.serverInfo->get();
|
||||
// also change server db info so workers get new mapping
|
||||
serverInfo.id = deterministicRandom()->randomUniqueID();
|
||||
serverInfo.infoGeneration = ++self->db.dbInfoCount;
|
||||
serverInfo.client = clientInfo;
|
||||
self->db.serverInfo->set(serverInfo);
|
||||
}
|
||||
|
||||
state Future<Void> tssChangeFuture = tr->watch(tssMappingChangeKey);
|
||||
|
||||
wait(tr->commit());
|
||||
wait(tssChangeFuture);
|
||||
|
||||
break;
|
||||
} catch (Error& e) {
|
||||
wait(tr->onError(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Monitors the global configuration version key for changes. When changes are
|
||||
// made, the global configuration history is read and any updates are sent to
|
||||
// all processes in the system by updating the ClientDBInfo object. The
|
||||
|
|
@ -3994,7 +3904,7 @@ ACTOR Future<Void> monitorGlobalConfig(ClusterControllerData::DBInfo* db) {
|
|||
tr.setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS);
|
||||
tr.setOption(FDBTransactionOptions::PRIORITY_SYSTEM_IMMEDIATE);
|
||||
state Optional<Value> globalConfigVersion = wait(tr.get(globalConfigVersionKey));
|
||||
state ClientDBInfo clientInfo = db->clientInfo->get();
|
||||
state ClientDBInfo clientInfo = db->serverInfo->get().client;
|
||||
|
||||
if (globalConfigVersion.present()) {
|
||||
// Since the history keys end with versionstamps, they
|
||||
|
|
@ -4052,6 +3962,14 @@ ACTOR Future<Void> monitorGlobalConfig(ClusterControllerData::DBInfo* db) {
|
|||
}
|
||||
|
||||
clientInfo.id = deterministicRandom()->randomUniqueID();
|
||||
// Update ServerDBInfo so fdbserver processes receive updated history.
|
||||
ServerDBInfo serverInfo = db->serverInfo->get();
|
||||
serverInfo.id = deterministicRandom()->randomUniqueID();
|
||||
serverInfo.infoGeneration = ++db->dbInfoCount;
|
||||
serverInfo.client = clientInfo;
|
||||
db->serverInfo->set(serverInfo);
|
||||
|
||||
// Update ClientDBInfo so client processes receive updated history.
|
||||
db->clientInfo->set(clientInfo);
|
||||
}
|
||||
|
||||
|
|
@ -4539,7 +4457,7 @@ ACTOR Future<Void> clusterControllerCore(ClusterControllerFullInterface interf,
|
|||
self.addActor.send(handleForcedRecoveries(&self, interf));
|
||||
self.addActor.send(monitorDataDistributor(&self));
|
||||
self.addActor.send(monitorRatekeeper(&self));
|
||||
self.addActor.send(monitorTSSMapping(&self));
|
||||
// self.addActor.send(monitorTSSMapping(&self));
|
||||
self.addActor.send(dbInfoUpdater(&self));
|
||||
self.addActor.send(traceCounters("ClusterControllerMetrics",
|
||||
self.id,
|
||||
|
|
|
|||
|
|
@ -1431,11 +1431,26 @@ ACTOR Future<Void> commitBatch(ProxyCommitData* self,
|
|||
return Void();
|
||||
}
|
||||
|
||||
// Add tss mapping data to the reply, if any of the included storage servers have a TSS pair
|
||||
void maybeAddTssMapping(GetKeyServerLocationsReply& reply,
|
||||
ProxyCommitData* commitData,
|
||||
std::unordered_set<UID>& included,
|
||||
UID ssId) {
|
||||
if (!included.count(ssId)) {
|
||||
auto mappingItr = commitData->tssMapping.find(ssId);
|
||||
if (mappingItr != commitData->tssMapping.end()) {
|
||||
included.insert(ssId);
|
||||
reply.resultsTssMapping.push_back(*mappingItr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ACTOR static Future<Void> doKeyServerLocationRequest(GetKeyServerLocationsRequest req, ProxyCommitData* commitData) {
|
||||
// We can't respond to these requests until we have valid txnStateStore
|
||||
wait(commitData->validState.getFuture());
|
||||
wait(delay(0, TaskPriority::DefaultEndpoint));
|
||||
|
||||
std::unordered_set<UID> tssMappingsIncluded;
|
||||
GetKeyServerLocationsReply rep;
|
||||
if (!req.end.present()) {
|
||||
auto r = req.reverse ? commitData->keyInfo.rangeContainingKeyBefore(req.begin)
|
||||
|
|
@ -1444,8 +1459,9 @@ ACTOR static Future<Void> doKeyServerLocationRequest(GetKeyServerLocationsReques
|
|||
ssis.reserve(r.value().src_info.size());
|
||||
for (auto& it : r.value().src_info) {
|
||||
ssis.push_back(it->interf);
|
||||
maybeAddTssMapping(rep, commitData, tssMappingsIncluded, it->interf.id());
|
||||
}
|
||||
rep.results.push_back(std::make_pair(r.range(), ssis));
|
||||
rep.results.emplace_back(r.range(), ssis);
|
||||
} else if (!req.reverse) {
|
||||
int count = 0;
|
||||
for (auto r = commitData->keyInfo.rangeContaining(req.begin);
|
||||
|
|
@ -1455,8 +1471,9 @@ ACTOR static Future<Void> doKeyServerLocationRequest(GetKeyServerLocationsReques
|
|||
ssis.reserve(r.value().src_info.size());
|
||||
for (auto& it : r.value().src_info) {
|
||||
ssis.push_back(it->interf);
|
||||
maybeAddTssMapping(rep, commitData, tssMappingsIncluded, it->interf.id());
|
||||
}
|
||||
rep.results.push_back(std::make_pair(r.range(), ssis));
|
||||
rep.results.emplace_back(r.range(), ssis);
|
||||
count++;
|
||||
}
|
||||
} else {
|
||||
|
|
@ -1467,8 +1484,9 @@ ACTOR static Future<Void> doKeyServerLocationRequest(GetKeyServerLocationsReques
|
|||
ssis.reserve(r.value().src_info.size());
|
||||
for (auto& it : r.value().src_info) {
|
||||
ssis.push_back(it->interf);
|
||||
maybeAddTssMapping(rep, commitData, tssMappingsIncluded, it->interf.id());
|
||||
}
|
||||
rep.results.push_back(std::make_pair(r.range(), ssis));
|
||||
rep.results.emplace_back(r.range(), ssis);
|
||||
if (r == commitData->keyInfo.ranges().begin()) {
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -263,6 +263,7 @@ ACTOR Future<Void> dataDistributionQueue(Database cx,
|
|||
Reference<ShardsAffectedByTeamFailure> shardsAffectedByTeamFailure,
|
||||
MoveKeysLock lock,
|
||||
PromiseStream<Promise<int64_t>> getAverageShardBytes,
|
||||
PromiseStream<Promise<int>> getUnhealthyRelocationCount,
|
||||
UID distributorId,
|
||||
int teamSize,
|
||||
int singleRegionTeamSize,
|
||||
|
|
|
|||
|
|
@ -993,7 +993,7 @@ ACTOR Future<Void> dataDistributionRelocator(DDQueueData* self, RelocateData rd,
|
|||
allHealthy = true;
|
||||
anyWithSource = false;
|
||||
bestTeams.clear();
|
||||
// Get team from teamCollections in diffrent DCs and find the best one
|
||||
// Get team from teamCollections in different DCs and find the best one
|
||||
while (tciIndex < self->teamCollections.size()) {
|
||||
double inflightPenalty = SERVER_KNOBS->INFLIGHT_PENALTY_HEALTHY;
|
||||
if (rd.healthPriority == SERVER_KNOBS->PRIORITY_TEAM_UNHEALTHY ||
|
||||
|
|
@ -1032,7 +1032,7 @@ ACTOR Future<Void> dataDistributionRelocator(DDQueueData* self, RelocateData rd,
|
|||
anyWithSource = true;
|
||||
}
|
||||
|
||||
bestTeams.push_back(std::make_pair(bestTeam.first.get(), bestTeam.second));
|
||||
bestTeams.emplace_back(bestTeam.first.get(), bestTeam.second);
|
||||
tciIndex++;
|
||||
}
|
||||
if (foundTeams && anyHealthy) {
|
||||
|
|
@ -1550,6 +1550,7 @@ ACTOR Future<Void> dataDistributionQueue(Database cx,
|
|||
Reference<ShardsAffectedByTeamFailure> shardsAffectedByTeamFailure,
|
||||
MoveKeysLock lock,
|
||||
PromiseStream<Promise<int64_t>> getAverageShardBytes,
|
||||
PromiseStream<Promise<int>> getUnhealthyRelocationCount,
|
||||
UID distributorId,
|
||||
int teamSize,
|
||||
int singleRegionTeamSize,
|
||||
|
|
@ -1679,6 +1680,9 @@ ACTOR Future<Void> dataDistributionQueue(Database cx,
|
|||
}
|
||||
when(wait(self.error.getFuture())) {} // Propagate errors from dataDistributionRelocator
|
||||
when(wait(waitForAll(balancingFutures))) {}
|
||||
when(Promise<int> r = waitNext(getUnhealthyRelocationCount.getFuture())) {
|
||||
r.send(self.unhealthyRelocations);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Error& e) {
|
||||
|
|
|
|||
|
|
@ -176,8 +176,8 @@ ShardSizeBounds getShardSizeBounds(KeyRangeRef shard, int64_t maxShardSize) {
|
|||
}
|
||||
|
||||
int64_t getMaxShardSize(double dbSizeEstimate) {
|
||||
return std::min((SERVER_KNOBS->MIN_SHARD_BYTES +
|
||||
(int64_t)std::sqrt(dbSizeEstimate) * SERVER_KNOBS->SHARD_BYTES_PER_SQRT_BYTES) *
|
||||
return std::min((SERVER_KNOBS->MIN_SHARD_BYTES + (int64_t)std::sqrt(std::max<double>(dbSizeEstimate, 0)) *
|
||||
SERVER_KNOBS->SHARD_BYTES_PER_SQRT_BYTES) *
|
||||
SERVER_KNOBS->SHARD_BYTES_RATIO,
|
||||
(int64_t)SERVER_KNOBS->MAX_SHARD_BYTES);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,14 @@
|
|||
#include "fdbserver/Knobs.h"
|
||||
#include <string.h>
|
||||
|
||||
#define DELTATREE_DEBUG 0
|
||||
|
||||
#if DELTATREE_DEBUG
|
||||
#define deltatree_printf(...) printf(__VA_ARGS__)
|
||||
#else
|
||||
#define deltatree_printf(...)
|
||||
#endif
|
||||
|
||||
typedef uint64_t Word;
|
||||
// Get the number of prefix bytes that are the same between a and b, up to their common length of cl
|
||||
static inline int commonPrefixLength(uint8_t const* ap, uint8_t const* bp, int cl) {
|
||||
|
|
@ -198,10 +206,6 @@ struct DeltaTree {
|
|||
smallOffsets.left = offset;
|
||||
}
|
||||
}
|
||||
|
||||
int size(bool large) const {
|
||||
return delta(large).size() + (large ? sizeof(smallOffsets) : sizeof(largeOffsets));
|
||||
}
|
||||
};
|
||||
|
||||
static constexpr int SmallSizeLimit = std::numeric_limits<uint16_t>::max();
|
||||
|
|
@ -356,8 +360,6 @@ public:
|
|||
|
||||
Mirror(const void* treePtr = nullptr, const T* lowerBound = nullptr, const T* upperBound = nullptr)
|
||||
: tree((DeltaTree*)treePtr), lower(lowerBound), upper(upperBound) {
|
||||
// TODO: Remove these copies into arena and require users of Mirror to keep prev and next alive during its
|
||||
// lifetime
|
||||
lower = new (arena) T(arena, *lower);
|
||||
upper = new (arena) T(arena, *upper);
|
||||
|
||||
|
|
@ -832,7 +834,7 @@ public:
|
|||
int count = end - begin;
|
||||
numItems = count;
|
||||
nodeBytesDeleted = 0;
|
||||
initialHeight = (uint8_t)log2(count) + 1;
|
||||
initialHeight = count ? (uint8_t)log2(count) + 1 : 0;
|
||||
maxHeight = 0;
|
||||
|
||||
// The boundary leading to the new page acts as the last time we branched right
|
||||
|
|
@ -875,7 +877,10 @@ private:
|
|||
|
||||
int deltaSize = item.writeDelta(node.delta(largeNodes), *base, commonPrefix);
|
||||
node.delta(largeNodes).setPrefixSource(prefixSourcePrev);
|
||||
// printf("Serialized %s to %p\n", item.toString().c_str(), &root.delta(largeNodes));
|
||||
deltatree_printf("Serialized %s to offset %d data: %s\n",
|
||||
item.toString().c_str(),
|
||||
(uint8_t*)&node - (uint8_t*)this,
|
||||
StringRef((uint8_t*)&node.delta(largeNodes), deltaSize).toHexString().c_str());
|
||||
|
||||
// Continue writing after the serialized Delta.
|
||||
uint8_t* wptr = (uint8_t*)&node.delta(largeNodes) + deltaSize;
|
||||
|
|
@ -899,3 +904,823 @@ private:
|
|||
return wptr - (uint8_t*)&node;
|
||||
}
|
||||
};
|
||||
|
||||
// DeltaTree2 is a memory mappable binary tree of T objects such that each node's item is
|
||||
// stored as a Delta which can reproduce the node's T item given either
|
||||
// - The node's greatest lesser ancestor, called the "left parent"
|
||||
// - The node's least greater ancestor, called the "right parent"
|
||||
// One of these ancestors will also happen to be the node's direct parent.
|
||||
//
|
||||
// The Delta type is intended to make use of ordered prefix compression and borrow all
|
||||
// available prefix bytes from the ancestor T which shares the most prefix bytes with
|
||||
// the item T being encoded. If T is implemented properly, this results in perfect
|
||||
// prefix compression while performing O(log n) comparisons for a seek.
|
||||
//
|
||||
// T requirements
|
||||
//
|
||||
// Must be compatible with Standalone<T> and must implement the following additional things:
|
||||
//
|
||||
// // Return the common prefix length between *this and T
|
||||
// // skipLen is a hint, representing the length that is already known to be common.
|
||||
// int getCommonPrefixLen(const T& other, int skipLen) const;
|
||||
//
|
||||
// // Compare *this to rhs, returns < 0 for less than, 0 for equal, > 0 for greater than
|
||||
// // skipLen is a hint, representing the length that is already known to be common.
|
||||
// int compare(const T &rhs, int skipLen) const;
|
||||
//
|
||||
// // Writes to d a delta which can create *this from base
|
||||
// // commonPrefix is a hint, representing the length that is already known to be common.
|
||||
// // DeltaT's size need not be static, for more details see below.
|
||||
// void writeDelta(DeltaT &d, const T &base, int commonPrefix) const;
|
||||
//
|
||||
// // Returns the size in bytes of the DeltaT required to recreate *this from base
|
||||
// int deltaSize(const T &base) const;
|
||||
//
|
||||
// // A type which represents the parts of T that either borrowed from the base T
|
||||
// // or can be borrowed by other T's using the first T as a base
|
||||
// // Partials must allocate any heap storage in the provided Arena for any operation.
|
||||
// typedef Partial;
|
||||
//
|
||||
// // Update cache with the Partial for *this, storing any heap memory for the Partial in arena
|
||||
// void updateCache(Optional<Partial> cache, Arena& arena) const;
|
||||
//
|
||||
// // For debugging, return a useful human-readable string representation of *this
|
||||
// std::string toString() const;
|
||||
//
|
||||
// DeltaT requirements
|
||||
//
|
||||
// DeltaT can be variable sized, larger than sizeof(DeltaT), and implement the following:
|
||||
//
|
||||
// // Returns the size in bytes of this specific DeltaT instance
|
||||
// int size();
|
||||
//
|
||||
// // Apply *this to base and return the resulting T
|
||||
// // Store the Partial for T into cache, allocating any heap memory for the Partial in arena
|
||||
// T apply(Arena& arena, const T& base, Optional<T::Partial>& cache);
|
||||
//
|
||||
// // Recreate T from *this and the Partial for T
|
||||
// T apply(const T::Partial& cache);
|
||||
//
|
||||
// // Set or retrieve a boolean flag representing which base ancestor the DeltaT is to be applied to
|
||||
// void setPrefixSource(bool val);
|
||||
// bool getPrefixSource() const;
|
||||
//
|
||||
// // Set of retrieve a boolean flag representing that a DeltaTree node has been erased
|
||||
// void setDeleted(bool val);
|
||||
// bool getDeleted() const;
|
||||
//
|
||||
// // For debugging, return a useful human-readable string representation of *this
|
||||
// std::string toString() const;
|
||||
//
|
||||
#pragma pack(push, 1)
|
||||
template <typename T, typename DeltaT = typename T::Delta>
|
||||
struct DeltaTree2 {
|
||||
typedef typename T::Partial Partial;
|
||||
|
||||
struct {
|
||||
uint16_t numItems; // Number of items in the tree.
|
||||
uint32_t nodeBytesUsed; // Bytes used by nodes (everything after the tree header)
|
||||
uint32_t nodeBytesFree; // Bytes left at end of tree to expand into
|
||||
uint32_t nodeBytesDeleted; // Delta bytes deleted from tree. Note that some of these bytes could be borrowed by
|
||||
// descendents.
|
||||
uint8_t initialHeight; // Height of tree as originally built
|
||||
uint8_t maxHeight; // Maximum height of tree after any insertion. Value of 0 means no insertions done.
|
||||
bool largeNodes; // Node size, can be calculated as capacity > SmallSizeLimit but it will be used a lot
|
||||
};
|
||||
|
||||
// Node is not fixed size. Most node methods require the context of whether the node is in small or large
|
||||
// offset mode, passed as a boolean
|
||||
struct Node {
|
||||
// Offsets are relative to the start of the DeltaTree
|
||||
union {
|
||||
struct {
|
||||
uint32_t leftChild;
|
||||
uint32_t rightChild;
|
||||
|
||||
} largeOffsets;
|
||||
struct {
|
||||
uint16_t leftChild;
|
||||
uint16_t rightChild;
|
||||
} smallOffsets;
|
||||
};
|
||||
|
||||
static int headerSize(bool large) { return large ? sizeof(largeOffsets) : sizeof(smallOffsets); }
|
||||
|
||||
// Delta is located after the offsets, which differs by node size
|
||||
DeltaT& delta(bool large) { return large ? *(DeltaT*)(&largeOffsets + 1) : *(DeltaT*)(&smallOffsets + 1); };
|
||||
|
||||
// Delta is located after the offsets, which differs by node size
|
||||
const DeltaT& delta(bool large) const {
|
||||
return large ? *(DeltaT*)(&largeOffsets + 1) : *(DeltaT*)(&smallOffsets + 1);
|
||||
};
|
||||
|
||||
std::string toString(DeltaTree2* tree) const {
|
||||
return format("Node{offset=%d leftChild=%d rightChild=%d delta=%s}",
|
||||
tree->nodeOffset(this),
|
||||
getLeftChildOffset(tree->largeNodes),
|
||||
getRightChildOffset(tree->largeNodes),
|
||||
delta(tree->largeNodes).toString().c_str());
|
||||
}
|
||||
|
||||
#define getMember(m) (large ? largeOffsets.m : smallOffsets.m)
|
||||
#define setMember(m, v) \
|
||||
if (large) { \
|
||||
largeOffsets.m = v; \
|
||||
} else { \
|
||||
smallOffsets.m = v; \
|
||||
}
|
||||
|
||||
void setRightChildOffset(bool large, int offset) { setMember(rightChild, offset); }
|
||||
void setLeftChildOffset(bool large, int offset) { setMember(leftChild, offset); }
|
||||
|
||||
int getRightChildOffset(bool large) const { return getMember(rightChild); }
|
||||
int getLeftChildOffset(bool large) const { return getMember(leftChild); }
|
||||
|
||||
int size(bool large) const { return delta(large).size() + headerSize(large); }
|
||||
#undef getMember
|
||||
#undef setMember
|
||||
};
|
||||
|
||||
static constexpr int SmallSizeLimit = std::numeric_limits<uint16_t>::max();
|
||||
static constexpr int LargeTreePerNodeExtraOverhead = sizeof(Node::largeOffsets) - sizeof(Node::smallOffsets);
|
||||
|
||||
int nodeOffset(const Node* n) const { return (uint8_t*)n - (uint8_t*)this; }
|
||||
Node* nodeAt(int offset) { return offset == 0 ? nullptr : (Node*)((uint8_t*)this + offset); }
|
||||
Node* root() { return numItems == 0 ? nullptr : (Node*)(this + 1); }
|
||||
int rootOffset() { return sizeof(DeltaTree2); }
|
||||
|
||||
int size() const { return sizeof(DeltaTree2) + nodeBytesUsed; }
|
||||
int capacity() const { return size() + nodeBytesFree; }
|
||||
|
||||
public:
|
||||
// DecodedNode represents a Node of a DeltaTree and its T::Partial.
|
||||
// DecodedNodes are created on-demand, as DeltaTree Nodes are visited by a Cursor.
|
||||
// DecodedNodes link together to form a binary tree with the same Node relationships as their
|
||||
// corresponding DeltaTree Nodes. Additionally, DecodedNodes store links to their left and
|
||||
// right ancestors which correspond to possible base Nodes on which the Node's Delta is based.
|
||||
//
|
||||
// DecodedNode links are not pointers, but rather indices to be looked up in the DecodeCache
|
||||
// defined below. An index value of -1 is uninitialized, meaning it is not yet known whether
|
||||
// the corresponding DeltaTree Node link is non-null in any version of the DeltaTree which is
|
||||
// using or has used the DecodeCache.
|
||||
struct DecodedNode {
|
||||
DecodedNode(int nodeOffset, int leftParentIndex, int rightParentIndex)
|
||||
: nodeOffset(nodeOffset), leftParentIndex(leftParentIndex), rightParentIndex(rightParentIndex),
|
||||
leftChildIndex(-1), rightChildIndex(-1) {}
|
||||
int nodeOffset;
|
||||
int16_t leftParentIndex;
|
||||
int16_t rightParentIndex;
|
||||
int16_t leftChildIndex;
|
||||
int16_t rightChildIndex;
|
||||
Optional<Partial> partial;
|
||||
|
||||
Node* node(DeltaTree2* tree) const { return tree->nodeAt(nodeOffset); }
|
||||
|
||||
std::string toString() {
|
||||
return format("DecodedNode{nodeOffset=%d leftChildIndex=%d rightChildIndex=%d leftParentIndex=%d "
|
||||
"rightParentIndex=%d}",
|
||||
(int)nodeOffset,
|
||||
(int)leftChildIndex,
|
||||
(int)rightChildIndex,
|
||||
(int)leftParentIndex,
|
||||
(int)rightParentIndex);
|
||||
}
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
// The DecodeCache is a reference counted structure that stores DecodedNodes by an integer index
|
||||
// and can be shared across a series of updated copies of a DeltaTree.
|
||||
//
|
||||
// DecodedNodes are stored in a contiguous vector, which sometimes must be expanded, so care
|
||||
// must be taken to resolve DecodedNode pointers again after the DecodeCache has new entries added.
|
||||
struct DecodeCache : FastAllocated<DecodeCache>, ReferenceCounted<DecodeCache> {
|
||||
DecodeCache(const T& lowerBound = T(), const T& upperBound = T())
|
||||
: lowerBound(arena, lowerBound), upperBound(arena, upperBound) {
|
||||
decodedNodes.reserve(10);
|
||||
deltatree_printf("DecodedNode size: %d\n", sizeof(DecodedNode));
|
||||
}
|
||||
|
||||
Arena arena;
|
||||
T lowerBound;
|
||||
T upperBound;
|
||||
|
||||
// Index 0 is always the root
|
||||
std::vector<DecodedNode> decodedNodes;
|
||||
|
||||
DecodedNode& get(int index) { return decodedNodes[index]; }
|
||||
|
||||
template <class... Args>
|
||||
int emplace_new(Args&&... args) {
|
||||
int index = decodedNodes.size();
|
||||
decodedNodes.emplace_back(args...);
|
||||
return index;
|
||||
}
|
||||
|
||||
bool empty() const { return decodedNodes.empty(); }
|
||||
|
||||
void clear() {
|
||||
decodedNodes.clear();
|
||||
Arena a;
|
||||
lowerBound = T(a, lowerBound);
|
||||
upperBound = T(a, upperBound);
|
||||
arena = a;
|
||||
}
|
||||
};
|
||||
|
||||
// Cursor provides a way to seek into a DeltaTree and iterate over its contents
|
||||
// The cursor needs a DeltaTree pointer and a DecodeCache, which can be shared
|
||||
// with other DeltaTrees which were incrementally modified to produce the the
|
||||
// tree that this cursor is referencing.
|
||||
struct Cursor {
|
||||
Cursor() : cache(nullptr), nodeIndex(-1) {}
|
||||
|
||||
Cursor(DecodeCache* cache, DeltaTree2* tree) : cache(cache), tree(tree), nodeIndex(-1) {}
|
||||
|
||||
Cursor(DecodeCache* cache, DeltaTree2* tree, int nodeIndex) : cache(cache), tree(tree), nodeIndex(nodeIndex) {}
|
||||
|
||||
// Copy constructor does not copy item because normally a copied cursor will be immediately moved.
|
||||
Cursor(const Cursor& c) : cache(c.cache), tree(c.tree), nodeIndex(c.nodeIndex) {}
|
||||
|
||||
Cursor next() const {
|
||||
Cursor c = *this;
|
||||
c.moveNext();
|
||||
return c;
|
||||
}
|
||||
|
||||
Cursor previous() const {
|
||||
Cursor c = *this;
|
||||
c.movePrev();
|
||||
return c;
|
||||
}
|
||||
|
||||
int rootIndex() {
|
||||
if (!cache->empty()) {
|
||||
return 0;
|
||||
} else if (tree->numItems != 0) {
|
||||
return cache->emplace_new(tree->rootOffset(), -1, -1);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
DeltaTree2* tree;
|
||||
DecodeCache* cache;
|
||||
int nodeIndex;
|
||||
mutable Optional<T> item;
|
||||
|
||||
Node* node() const { return tree->nodeAt(cache->get(nodeIndex).nodeOffset); }
|
||||
|
||||
std::string toString() const {
|
||||
if (nodeIndex == -1) {
|
||||
return format("Cursor{nodeIndex=-1}");
|
||||
}
|
||||
return format("Cursor{item=%s indexItem=%s nodeIndex=%d decodedNode=%s node=%s ",
|
||||
item.present() ? item.get().toString().c_str() : "<absent>",
|
||||
get(cache->get(nodeIndex)).toString().c_str(),
|
||||
nodeIndex,
|
||||
cache->get(nodeIndex).toString().c_str(),
|
||||
node()->toString(tree).c_str());
|
||||
}
|
||||
|
||||
bool valid() const { return nodeIndex != -1; }
|
||||
|
||||
// Get T for Node n, and provide to n's delta the base and local decode cache entries to use/modify
|
||||
const T get(DecodedNode& decoded) const {
|
||||
DeltaT& delta = decoded.node(tree)->delta(tree->largeNodes);
|
||||
|
||||
// If this node's cached partial is populated, then the delta can create T from that alone
|
||||
if (decoded.partial.present()) {
|
||||
return delta.apply(decoded.partial.get());
|
||||
}
|
||||
|
||||
// Otherwise, get the base T
|
||||
bool basePrev = delta.getPrefixSource();
|
||||
int baseIndex = basePrev ? decoded.leftParentIndex : decoded.rightParentIndex;
|
||||
|
||||
// If baseOffset is -1, then base T is DecodeCache's lower or upper bound
|
||||
if (baseIndex == -1) {
|
||||
return delta.apply(cache->arena, basePrev ? cache->lowerBound : cache->upperBound, decoded.partial);
|
||||
}
|
||||
|
||||
// Otherwise, get the base's decoded node
|
||||
DecodedNode& baseDecoded = cache->get(baseIndex);
|
||||
|
||||
// If the base's partial is present, apply delta to it to get result
|
||||
if (baseDecoded.partial.present()) {
|
||||
return delta.apply(cache->arena, baseDecoded.partial.get(), decoded.partial);
|
||||
}
|
||||
|
||||
// Otherwise apply delta to base T
|
||||
return delta.apply(cache->arena, get(baseDecoded), decoded.partial);
|
||||
}
|
||||
|
||||
public:
|
||||
// Get the item at the cursor
|
||||
// Behavior is undefined if the cursor is not valid.
|
||||
// If the cursor is moved, the reference object returned will be modified to
|
||||
// the cursor's new current item.
|
||||
const T& get() const {
|
||||
if (!item.present()) {
|
||||
item = get(cache->get(nodeIndex));
|
||||
}
|
||||
return item.get();
|
||||
}
|
||||
|
||||
void switchTree(DeltaTree2* newTree) { tree = newTree; }
|
||||
|
||||
// If the cursor is valid, return a reference to the cursor's internal T.
|
||||
// Otherwise, returns a reference to the cache's upper boundary.
|
||||
const T& getOrUpperBound() const { return valid() ? get() : cache->upperBound; }
|
||||
|
||||
bool operator==(const Cursor& rhs) const { return nodeIndex == rhs.nodeIndex; }
|
||||
bool operator!=(const Cursor& rhs) const { return nodeIndex != rhs.nodeIndex; }
|
||||
|
||||
// The seek methods, of the form seek[Less|Greater][orEqual](...) are very similar.
|
||||
// They attempt move the cursor to the [Greatest|Least] item, based on the name of the function.
|
||||
// Then will not "see" erased records.
|
||||
// If successful, they return true, and if not then false a while making the cursor invalid.
|
||||
// These methods forward arguments to the seek() overloads, see those for argument descriptions.
|
||||
template <typename... Args>
|
||||
bool seekLessThan(Args... args) {
|
||||
int cmp = seek(args...);
|
||||
if (cmp < 0 || (cmp == 0 && nodeIndex != -1)) {
|
||||
movePrev();
|
||||
}
|
||||
return _hideDeletedBackward();
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
bool seekLessThanOrEqual(Args... args) {
|
||||
int cmp = seek(args...);
|
||||
if (cmp < 0) {
|
||||
movePrev();
|
||||
}
|
||||
return _hideDeletedBackward();
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
bool seekGreaterThan(Args... args) {
|
||||
int cmp = seek(args...);
|
||||
if (cmp > 0 || (cmp == 0 && nodeIndex != -1)) {
|
||||
moveNext();
|
||||
}
|
||||
return _hideDeletedForward();
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
bool seekGreaterThanOrEqual(Args... args) {
|
||||
int cmp = seek(args...);
|
||||
if (cmp > 0) {
|
||||
moveNext();
|
||||
}
|
||||
return _hideDeletedForward();
|
||||
}
|
||||
|
||||
// Get the right child index for parentIndex
|
||||
int getRightChildIndex(int parentIndex) {
|
||||
DecodedNode* parent = &cache->get(parentIndex);
|
||||
|
||||
// The cache may have a child index, but since cache covers multiple versions of a DeltaTree
|
||||
// it can't be used unless the node in the tree has a child.
|
||||
int childOffset = parent->node(tree)->getRightChildOffset(tree->largeNodes);
|
||||
|
||||
if (childOffset == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// parent has this child so return the index if it is in DecodedNode
|
||||
if (parent->rightChildIndex != -1) {
|
||||
return parent->rightChildIndex;
|
||||
}
|
||||
|
||||
// Create the child's DecodedNode and get its index
|
||||
int childIndex = cache->emplace_new(childOffset, parentIndex, parent->rightParentIndex);
|
||||
|
||||
// Set the index in the parent. The cache lookup is repeated because the cache has changed.
|
||||
cache->get(parentIndex).rightChildIndex = childIndex;
|
||||
return childIndex;
|
||||
}
|
||||
|
||||
// Get the left child index for parentIndex
|
||||
int getLeftChildIndex(int parentIndex) {
|
||||
DecodedNode* parent = &cache->get(parentIndex);
|
||||
|
||||
// The cache may have a child index, but since cache covers multiple versions of a DeltaTree
|
||||
// it can't be used unless the node in the tree has a child.
|
||||
int childOffset = parent->node(tree)->getLeftChildOffset(tree->largeNodes);
|
||||
|
||||
if (childOffset == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// parent has this child so return the index if it is in DecodedNode
|
||||
if (parent->leftChildIndex != -1) {
|
||||
return parent->leftChildIndex;
|
||||
}
|
||||
|
||||
// Create the child's DecodedNode and get its index
|
||||
int childIndex = cache->emplace_new(childOffset, parent->leftParentIndex, parentIndex);
|
||||
|
||||
// Set the index in the parent. The cache lookup is repeated because the cache has changed.
|
||||
cache->get(parentIndex).leftChildIndex = childIndex;
|
||||
return childIndex;
|
||||
}
|
||||
|
||||
// seek() moves the cursor to a node containing s or the node that would be the parent of s if s were to be
|
||||
// added to the tree. If the tree was empty, the cursor will be invalid and the return value will be 0.
|
||||
// Otherwise, returns the result of s.compare(item at cursor position)
|
||||
// Does not skip/avoid deleted nodes.
|
||||
int seek(const T& s, int skipLen = 0) {
|
||||
nodeIndex = -1;
|
||||
item.reset();
|
||||
deltatree_printf("seek(%s) start %s\n", s.toString().c_str(), toString().c_str());
|
||||
int nIndex = rootIndex();
|
||||
int cmp = 0;
|
||||
|
||||
while (nIndex != -1) {
|
||||
nodeIndex = nIndex;
|
||||
item.reset();
|
||||
cmp = s.compare(get(), skipLen);
|
||||
deltatree_printf("seek(%s) loop cmp=%d %s\n", s.toString().c_str(), cmp, toString().c_str());
|
||||
if (cmp == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (cmp > 0) {
|
||||
nIndex = getRightChildIndex(nIndex);
|
||||
} else {
|
||||
nIndex = getLeftChildIndex(nIndex);
|
||||
}
|
||||
}
|
||||
|
||||
return cmp;
|
||||
}
|
||||
|
||||
bool moveFirst() {
|
||||
nodeIndex = -1;
|
||||
item.reset();
|
||||
int nIndex = rootIndex();
|
||||
deltatree_printf("moveFirst start %s\n", toString().c_str());
|
||||
while (nIndex != -1) {
|
||||
nodeIndex = nIndex;
|
||||
deltatree_printf("moveFirst moved %s\n", toString().c_str());
|
||||
nIndex = getLeftChildIndex(nIndex);
|
||||
}
|
||||
return _hideDeletedForward();
|
||||
}
|
||||
|
||||
bool moveLast() {
|
||||
nodeIndex = -1;
|
||||
item.reset();
|
||||
int nIndex = rootIndex();
|
||||
deltatree_printf("moveLast start %s\n", toString().c_str());
|
||||
while (nIndex != -1) {
|
||||
nodeIndex = nIndex;
|
||||
deltatree_printf("moveLast moved %s\n", toString().c_str());
|
||||
nIndex = getRightChildIndex(nIndex);
|
||||
}
|
||||
return _hideDeletedBackward();
|
||||
}
|
||||
|
||||
// Try to move to next node, sees deleted nodes.
|
||||
void _moveNext() {
|
||||
deltatree_printf("_moveNext start %s\n", toString().c_str());
|
||||
item.reset();
|
||||
// Try to go right
|
||||
int nIndex = getRightChildIndex(nodeIndex);
|
||||
|
||||
// If we couldn't go right, then the answer is our next ancestor
|
||||
if (nIndex == -1) {
|
||||
nodeIndex = cache->get(nodeIndex).rightParentIndex;
|
||||
deltatree_printf("_moveNext move1 %s\n", toString().c_str());
|
||||
} else {
|
||||
// Go left as far as possible
|
||||
do {
|
||||
nodeIndex = nIndex;
|
||||
deltatree_printf("_moveNext move2 %s\n", toString().c_str());
|
||||
nIndex = getLeftChildIndex(nodeIndex);
|
||||
} while (nIndex != -1);
|
||||
}
|
||||
}
|
||||
|
||||
// Try to move to previous node, sees deleted nodes.
|
||||
void _movePrev() {
|
||||
deltatree_printf("_movePrev start %s\n", toString().c_str());
|
||||
item.reset();
|
||||
// Try to go left
|
||||
int nIndex = getLeftChildIndex(nodeIndex);
|
||||
// If we couldn't go left, then the answer is our prev ancestor
|
||||
if (nIndex == -1) {
|
||||
nodeIndex = cache->get(nodeIndex).leftParentIndex;
|
||||
deltatree_printf("_movePrev move1 %s\n", toString().c_str());
|
||||
} else {
|
||||
// Go right as far as possible
|
||||
do {
|
||||
nodeIndex = nIndex;
|
||||
deltatree_printf("_movePrev move2 %s\n", toString().c_str());
|
||||
nIndex = getRightChildIndex(nodeIndex);
|
||||
} while (nIndex != -1);
|
||||
}
|
||||
}
|
||||
|
||||
bool moveNext() {
|
||||
_moveNext();
|
||||
return _hideDeletedForward();
|
||||
}
|
||||
|
||||
bool movePrev() {
|
||||
_movePrev();
|
||||
return _hideDeletedBackward();
|
||||
}
|
||||
|
||||
DeltaT& getDelta() const { return cache->get(nodeIndex).node(tree)->delta(tree->largeNodes); }
|
||||
|
||||
bool isErased() const { return getDelta().getDeleted(); }
|
||||
|
||||
// Erase current item by setting its deleted flag to true.
|
||||
// Tree header is updated if a change is made.
|
||||
// Cursor is then moved forward to the next non-deleted node.
|
||||
void erase() {
|
||||
auto& delta = getDelta();
|
||||
if (!delta.getDeleted()) {
|
||||
delta.setDeleted(true);
|
||||
--tree->numItems;
|
||||
tree->nodeBytesDeleted += (delta.size() + Node::headerSize(tree->largeNodes));
|
||||
}
|
||||
moveNext();
|
||||
}
|
||||
|
||||
// Erase k by setting its deleted flag to true. Returns true only if k existed
|
||||
bool erase(const T& k, int skipLen = 0) {
|
||||
Cursor c(cache, tree);
|
||||
if (c.seek(k, skipLen) == 0 && !c.isErased()) {
|
||||
c.erase();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Try to insert k into the DeltaTree, updating byte counts and initialHeight if they
|
||||
// have changed (they won't if k already exists in the tree but was deleted).
|
||||
// Returns true if successful, false if k does not fit in the space available
|
||||
// or if k is already in the tree (and was not already deleted).
|
||||
// Insertion on an empty tree returns false as well.
|
||||
// Insert does NOT change the cursor position.
|
||||
bool insert(const T& k, int skipLen = 0, int maxHeightAllowed = std::numeric_limits<int>::max()) {
|
||||
deltatree_printf("insert %s\n", k.toString().c_str());
|
||||
|
||||
int nIndex = rootIndex();
|
||||
int parentIndex = nIndex;
|
||||
DecodedNode* parentDecoded;
|
||||
// Result of comparing node at parentIndex
|
||||
int cmp = 0;
|
||||
// Height of the inserted node
|
||||
int height = 0;
|
||||
|
||||
// Find the parent to add the node to
|
||||
// This is just seek but modifies parentIndex instead of nodeIndex and tracks the insertion height
|
||||
deltatree_printf(
|
||||
"insert(%s) start %s\n", k.toString().c_str(), Cursor(cache, tree, parentIndex).toString().c_str());
|
||||
while (nIndex != -1) {
|
||||
++height;
|
||||
parentIndex = nIndex;
|
||||
parentDecoded = &cache->get(parentIndex);
|
||||
cmp = k.compare(get(*parentDecoded), skipLen);
|
||||
deltatree_printf("insert(%s) moved cmp=%d %s\n",
|
||||
k.toString().c_str(),
|
||||
cmp,
|
||||
Cursor(cache, tree, parentIndex).toString().c_str());
|
||||
|
||||
if (cmp == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (cmp > 0) {
|
||||
deltatree_printf("insert(%s) move right\n", k.toString().c_str());
|
||||
nIndex = getRightChildIndex(nIndex);
|
||||
} else {
|
||||
deltatree_printf("insert(%s) move left\n", k.toString().c_str());
|
||||
nIndex = getLeftChildIndex(nIndex);
|
||||
}
|
||||
}
|
||||
|
||||
// If the item is found, mark it erased if it isn't already
|
||||
if (cmp == 0) {
|
||||
DeltaT& delta = tree->nodeAt(parentDecoded->nodeOffset)->delta(tree->largeNodes);
|
||||
if (delta.getDeleted()) {
|
||||
delta.setDeleted(false);
|
||||
++tree->numItems;
|
||||
tree->nodeBytesDeleted -= (delta.size() + Node::headerSize(tree->largeNodes));
|
||||
deltatree_printf("insert(%s) deleted item restored %s\n",
|
||||
k.toString().c_str(),
|
||||
Cursor(cache, tree, parentIndex).toString().c_str());
|
||||
return true;
|
||||
}
|
||||
deltatree_printf("insert(%s) item exists %s\n",
|
||||
k.toString().c_str(),
|
||||
Cursor(cache, tree, parentIndex).toString().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the tree was empty or the max insertion height is exceeded then fail
|
||||
if (parentIndex == -1 || height > maxHeightAllowed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find the base base to borrow from, see if the resulting delta fits into the tree
|
||||
int leftBaseIndex, rightBaseIndex;
|
||||
bool addingRight = cmp > 0;
|
||||
if (addingRight) {
|
||||
leftBaseIndex = parentIndex;
|
||||
rightBaseIndex = parentDecoded->rightParentIndex;
|
||||
} else {
|
||||
leftBaseIndex = parentDecoded->leftParentIndex;
|
||||
rightBaseIndex = parentIndex;
|
||||
}
|
||||
|
||||
T leftBase = leftBaseIndex == -1 ? cache->lowerBound : get(cache->get(leftBaseIndex));
|
||||
T rightBase = rightBaseIndex == -1 ? cache->upperBound : get(cache->get(rightBaseIndex));
|
||||
|
||||
int common = leftBase.getCommonPrefixLen(rightBase, skipLen);
|
||||
int commonWithLeftParent = k.getCommonPrefixLen(leftBase, common);
|
||||
int commonWithRightParent = k.getCommonPrefixLen(rightBase, common);
|
||||
bool borrowFromLeft = commonWithLeftParent >= commonWithRightParent;
|
||||
|
||||
const T* base;
|
||||
int commonPrefix;
|
||||
if (borrowFromLeft) {
|
||||
base = &leftBase;
|
||||
commonPrefix = commonWithLeftParent;
|
||||
} else {
|
||||
base = &rightBase;
|
||||
commonPrefix = commonWithRightParent;
|
||||
}
|
||||
|
||||
int deltaSize = k.deltaSize(*base, commonPrefix, false);
|
||||
int nodeSpace = deltaSize + Node::headerSize(tree->largeNodes);
|
||||
|
||||
if (nodeSpace > tree->nodeBytesFree) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int childOffset = tree->size();
|
||||
Node* childNode = tree->nodeAt(childOffset);
|
||||
childNode->setLeftChildOffset(tree->largeNodes, 0);
|
||||
childNode->setRightChildOffset(tree->largeNodes, 0);
|
||||
|
||||
// Create the decoded node and link it to the parent
|
||||
// Link the parent's decodednode to the child's decodednode
|
||||
// Link the parent node in the tree to the new child node
|
||||
// true if node is being added to right child
|
||||
int childIndex = cache->emplace_new(childOffset, leftBaseIndex, rightBaseIndex);
|
||||
|
||||
// Get a new parentDecoded pointer as the cache may have changed allocations
|
||||
parentDecoded = &cache->get(parentIndex);
|
||||
|
||||
if (addingRight) {
|
||||
// Adding child to right of parent
|
||||
parentDecoded->rightChildIndex = childIndex;
|
||||
parentDecoded->node(tree)->setRightChildOffset(tree->largeNodes, childOffset);
|
||||
} else {
|
||||
// Adding child to left of parent
|
||||
parentDecoded->leftChildIndex = childIndex;
|
||||
parentDecoded->node(tree)->setLeftChildOffset(tree->largeNodes, childOffset);
|
||||
}
|
||||
|
||||
// Give k opportunity to populate its cache partial record
|
||||
k.updateCache(cache->get(childIndex).partial, cache->arena);
|
||||
|
||||
DeltaT& childDelta = childNode->delta(tree->largeNodes);
|
||||
deltatree_printf("insert(%s) writing delta from %s\n", k.toString().c_str(), base->toString().c_str());
|
||||
int written = k.writeDelta(childDelta, *base, commonPrefix);
|
||||
ASSERT(deltaSize == written);
|
||||
childDelta.setPrefixSource(borrowFromLeft);
|
||||
|
||||
tree->nodeBytesUsed += nodeSpace;
|
||||
tree->nodeBytesFree -= nodeSpace;
|
||||
++tree->numItems;
|
||||
|
||||
// Update max height of the tree if necessary
|
||||
if (height > tree->maxHeight) {
|
||||
tree->maxHeight = height;
|
||||
}
|
||||
|
||||
deltatree_printf("insert(%s) done parent=%s\n",
|
||||
k.toString().c_str(),
|
||||
Cursor(cache, tree, parentIndex).toString().c_str());
|
||||
deltatree_printf("insert(%s) done child=%s\n",
|
||||
k.toString().c_str(),
|
||||
Cursor(cache, tree, childIndex).toString().c_str());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
bool _hideDeletedBackward() {
|
||||
while (nodeIndex != -1 && getDelta().getDeleted()) {
|
||||
_movePrev();
|
||||
}
|
||||
return nodeIndex != -1;
|
||||
}
|
||||
|
||||
bool _hideDeletedForward() {
|
||||
while (nodeIndex != -1 && getDelta().getDeleted()) {
|
||||
_moveNext();
|
||||
}
|
||||
return nodeIndex != -1;
|
||||
}
|
||||
};
|
||||
|
||||
// Returns number of bytes written
|
||||
int build(int spaceAvailable, const T* begin, const T* end, const T* lowerBound, const T* upperBound) {
|
||||
largeNodes = spaceAvailable > SmallSizeLimit;
|
||||
int count = end - begin;
|
||||
numItems = count;
|
||||
nodeBytesDeleted = 0;
|
||||
initialHeight = (uint8_t)log2(count) + 1;
|
||||
maxHeight = 0;
|
||||
|
||||
// The boundary leading to the new page acts as the last time we branched right
|
||||
if (count > 0) {
|
||||
nodeBytesUsed = buildSubtree(
|
||||
*root(), begin, end, lowerBound, upperBound, lowerBound->getCommonPrefixLen(*upperBound, 0));
|
||||
} else {
|
||||
nodeBytesUsed = 0;
|
||||
}
|
||||
nodeBytesFree = spaceAvailable - size();
|
||||
return size();
|
||||
}
|
||||
|
||||
private:
|
||||
int buildSubtree(Node& node,
|
||||
const T* begin,
|
||||
const T* end,
|
||||
const T* leftParent,
|
||||
const T* rightParent,
|
||||
int subtreeCommon) {
|
||||
|
||||
int count = end - begin;
|
||||
|
||||
// Find key to be stored in root
|
||||
int mid = perfectSubtreeSplitPointCached(count);
|
||||
const T& item = begin[mid];
|
||||
|
||||
int commonWithPrev = item.getCommonPrefixLen(*leftParent, subtreeCommon);
|
||||
int commonWithNext = item.getCommonPrefixLen(*rightParent, subtreeCommon);
|
||||
|
||||
bool prefixSourcePrev;
|
||||
int commonPrefix;
|
||||
const T* base;
|
||||
if (commonWithPrev >= commonWithNext) {
|
||||
prefixSourcePrev = true;
|
||||
commonPrefix = commonWithPrev;
|
||||
base = leftParent;
|
||||
} else {
|
||||
prefixSourcePrev = false;
|
||||
commonPrefix = commonWithNext;
|
||||
base = rightParent;
|
||||
}
|
||||
|
||||
int deltaSize = item.writeDelta(node.delta(largeNodes), *base, commonPrefix);
|
||||
node.delta(largeNodes).setPrefixSource(prefixSourcePrev);
|
||||
|
||||
// Continue writing after the serialized Delta.
|
||||
uint8_t* wptr = (uint8_t*)&node.delta(largeNodes) + deltaSize;
|
||||
|
||||
int leftChildOffset;
|
||||
// Serialize left subtree
|
||||
if (count > 1) {
|
||||
leftChildOffset = wptr - (uint8_t*)this;
|
||||
deltatree_printf("%p: offset=%d count=%d serialize left subtree leftChildOffset=%d\n",
|
||||
this,
|
||||
nodeOffset(&node),
|
||||
count,
|
||||
leftChildOffset);
|
||||
|
||||
wptr += buildSubtree(*(Node*)wptr, begin, begin + mid, leftParent, &item, commonWithPrev);
|
||||
} else {
|
||||
leftChildOffset = 0;
|
||||
}
|
||||
|
||||
int rightChildOffset;
|
||||
// Serialize right subtree
|
||||
if (count > 2) {
|
||||
rightChildOffset = wptr - (uint8_t*)this;
|
||||
deltatree_printf("%p: offset=%d count=%d serialize right subtree rightChildOffset=%d\n",
|
||||
this,
|
||||
nodeOffset(&node),
|
||||
count,
|
||||
rightChildOffset);
|
||||
|
||||
wptr += buildSubtree(*(Node*)wptr, begin + mid + 1, end, &item, rightParent, commonWithNext);
|
||||
} else {
|
||||
rightChildOffset = 0;
|
||||
}
|
||||
|
||||
node.setLeftChildOffset(largeNodes, leftChildOffset);
|
||||
node.setRightChildOffset(largeNodes, rightChildOffset);
|
||||
|
||||
deltatree_printf("%p: Serialized %s as %s\n", this, item.toString().c_str(), node.toString(this).c_str());
|
||||
|
||||
return wptr - (uint8_t*)&node;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -109,15 +109,18 @@ struct GrvProxyStats {
|
|||
SERVER_KNOBS->LATENCY_SAMPLE_SIZE),
|
||||
grvLatencyBands("GRVLatencyMetrics", id, SERVER_KNOBS->STORAGE_LOGGING_DELAY) {
|
||||
// The rate at which the limit(budget) is allowed to grow.
|
||||
specialCounter(cc, "SystemAndDefaultTxnRateAllowed", [this]() { return this->transactionRateAllowed; });
|
||||
specialCounter(cc, "BatchTransactionRateAllowed", [this]() { return this->batchTransactionRateAllowed; });
|
||||
specialCounter(cc, "SystemAndDefaultTxnLimit", [this]() { return this->transactionLimit; });
|
||||
specialCounter(cc, "BatchTransactionLimit", [this]() { return this->batchTransactionLimit; });
|
||||
specialCounter(cc, "PercentageOfDefaultGRVQueueProcessed", [this]() {
|
||||
return this->percentageOfDefaultGRVQueueProcessed;
|
||||
});
|
||||
specialCounter(
|
||||
cc, "PercentageOfBatchGRVQueueProcessed", [this]() { return this->percentageOfBatchGRVQueueProcessed; });
|
||||
cc, "SystemAndDefaultTxnRateAllowed", [this]() { return int64_t(this->transactionRateAllowed); });
|
||||
specialCounter(
|
||||
cc, "BatchTransactionRateAllowed", [this]() { return int64_t(this->batchTransactionRateAllowed); });
|
||||
specialCounter(cc, "SystemAndDefaultTxnLimit", [this]() { return int64_t(this->transactionLimit); });
|
||||
specialCounter(cc, "BatchTransactionLimit", [this]() { return int64_t(this->batchTransactionLimit); });
|
||||
specialCounter(cc, "PercentageOfDefaultGRVQueueProcessed", [this]() {
|
||||
return int64_t(100 * this->percentageOfDefaultGRVQueueProcessed);
|
||||
});
|
||||
specialCounter(cc, "PercentageOfBatchGRVQueueProcessed", [this]() {
|
||||
return int64_t(100 * this->percentageOfBatchGRVQueueProcessed);
|
||||
});
|
||||
|
||||
logger = traceCounters("GrvProxyMetrics", id, SERVER_KNOBS->WORKER_LOGGING_INTERVAL, &cc, "GrvProxyMetrics");
|
||||
for (int i = 0; i < FLOW_KNOBS->BASIC_LOAD_BALANCE_BUCKETS; i++) {
|
||||
|
|
@ -186,8 +189,8 @@ struct GrvTransactionRateInfo {
|
|||
|
||||
void disable() {
|
||||
disabled = true;
|
||||
rate = 0;
|
||||
smoothRate.reset(0);
|
||||
// Use smoothRate.setTotal(0) instead of setting rate to 0 so txns will not be throttled immediately.
|
||||
smoothRate.setTotal(0);
|
||||
}
|
||||
|
||||
void setRate(double rate) {
|
||||
|
|
@ -386,13 +389,15 @@ ACTOR Future<Void> queueGetReadVersionRequests(Reference<AsyncVar<ServerDBInfo>>
|
|||
TaskPriority::ProxyGRVTimer));
|
||||
}
|
||||
|
||||
++stats->txnRequestIn;
|
||||
stats->txnStartIn += req.transactionCount;
|
||||
if (req.priority >= TransactionPriority::IMMEDIATE) {
|
||||
++stats->txnRequestIn;
|
||||
stats->txnStartIn += req.transactionCount;
|
||||
stats->txnSystemPriorityStartIn += req.transactionCount;
|
||||
systemQueue->push_back(req);
|
||||
systemQueue->span.addParent(req.spanContext);
|
||||
} else if (req.priority >= TransactionPriority::DEFAULT) {
|
||||
++stats->txnRequestIn;
|
||||
stats->txnStartIn += req.transactionCount;
|
||||
stats->txnDefaultPriorityStartIn += req.transactionCount;
|
||||
defaultQueue->push_back(req);
|
||||
defaultQueue->span.addParent(req.spanContext);
|
||||
|
|
@ -402,12 +407,13 @@ ACTOR Future<Void> queueGetReadVersionRequests(Reference<AsyncVar<ServerDBInfo>>
|
|||
if (batchRateInfo->rate <= (1.0 / proxiesCount)) {
|
||||
req.reply.sendError(batch_transaction_throttled());
|
||||
stats->txnThrottled += req.transactionCount;
|
||||
continue;
|
||||
} else {
|
||||
++stats->txnRequestIn;
|
||||
stats->txnStartIn += req.transactionCount;
|
||||
stats->txnBatchPriorityStartIn += req.transactionCount;
|
||||
batchQueue->push_back(req);
|
||||
batchQueue->span.addParent(req.spanContext);
|
||||
}
|
||||
|
||||
stats->txnBatchPriorityStartIn += req.transactionCount;
|
||||
batchQueue->push_back(req);
|
||||
batchQueue->span.addParent(req.spanContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -831,8 +837,10 @@ ACTOR static Future<Void> transactionStarter(GrvProxyInterface proxy,
|
|||
}
|
||||
span = Span(span.location);
|
||||
|
||||
grvProxyData->stats.percentageOfDefaultGRVQueueProcessed = (double)defaultGRVProcessed / defaultQueueSize;
|
||||
grvProxyData->stats.percentageOfBatchGRVQueueProcessed = (double)batchGRVProcessed / batchQueueSize;
|
||||
grvProxyData->stats.percentageOfDefaultGRVQueueProcessed =
|
||||
defaultQueueSize ? (double)defaultGRVProcessed / defaultQueueSize : 1;
|
||||
grvProxyData->stats.percentageOfBatchGRVQueueProcessed =
|
||||
batchQueueSize ? (double)batchGRVProcessed / batchQueueSize : 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -37,6 +37,9 @@ typedef uint32_t LogicalPageID;
|
|||
typedef uint32_t PhysicalPageID;
|
||||
#define invalidLogicalPageID std::numeric_limits<LogicalPageID>::max()
|
||||
|
||||
typedef uint32_t QueueID;
|
||||
#define invalidQueueID std::numeric_limits<QueueID>::max()
|
||||
|
||||
// Represents a block of memory in a 4096-byte aligned location held by an Arena.
|
||||
class ArenaPage : public ReferenceCounted<ArenaPage>, public FastAllocated<ArenaPage> {
|
||||
public:
|
||||
|
|
@ -56,9 +59,6 @@ public:
|
|||
if (userData != nullptr && userDataDestructor != nullptr) {
|
||||
userDataDestructor(userData);
|
||||
}
|
||||
if (buffer != nullptr) {
|
||||
VALGRIND_MAKE_MEM_UNDEFINED(buffer, bufferSize);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t const* begin() const { return (uint8_t*)buffer; }
|
||||
|
|
@ -128,10 +128,7 @@ public:
|
|||
|
||||
class IPagerSnapshot {
|
||||
public:
|
||||
virtual Future<Reference<const ArenaPage>> getPhysicalPage(LogicalPageID pageID,
|
||||
bool cacheable,
|
||||
bool nohit,
|
||||
bool* fromCache = nullptr) = 0;
|
||||
virtual Future<Reference<const ArenaPage>> getPhysicalPage(LogicalPageID pageID, bool cacheable, bool nohit) = 0;
|
||||
virtual bool tryEvictPage(LogicalPageID id) = 0;
|
||||
virtual Version getVersion() const = 0;
|
||||
|
||||
|
|
@ -153,11 +150,17 @@ public:
|
|||
// For a given pager instance, separate calls to this function must return the same value.
|
||||
// Only valid to call after recovery is complete.
|
||||
virtual int getUsablePageSize() const = 0;
|
||||
virtual int getPhysicalPageSize() const = 0;
|
||||
virtual int getLogicalPageSize() const = 0;
|
||||
virtual int getPagesPerExtent() const = 0;
|
||||
|
||||
// Allocate a new page ID for a subsequent write. The page will be considered in-use after the next commit
|
||||
// regardless of whether or not it was written to.
|
||||
virtual Future<LogicalPageID> newPageID() = 0;
|
||||
|
||||
virtual Future<LogicalPageID> newExtentPageID(QueueID queueID) = 0;
|
||||
virtual QueueID newLastQueueID() = 0;
|
||||
|
||||
// Replace the contents of a page with new data across *all* versions.
|
||||
// Existing holders of a page reference for pageID, read from any version,
|
||||
// may see the effects of this write.
|
||||
|
|
@ -172,6 +175,8 @@ public:
|
|||
// Free pageID to be used again after the commit that moves oldestVersion past v
|
||||
virtual void freePage(LogicalPageID pageID, Version v) = 0;
|
||||
|
||||
virtual void freeExtent(LogicalPageID pageID) = 0;
|
||||
|
||||
// If id is remapped, delete the original as of version v and return the page it was remapped to. The caller
|
||||
// is then responsible for referencing and deleting the returned page ID.
|
||||
virtual LogicalPageID detachRemappedPage(LogicalPageID id, Version v) = 0;
|
||||
|
|
@ -183,10 +188,16 @@ public:
|
|||
// Cacheable indicates that the page should be added to the page cache (if applicable?) as a result of this read.
|
||||
// NoHit indicates that the read should not be considered a cache hit, such as when preloading pages that are
|
||||
// considered likely to be needed soon.
|
||||
virtual Future<Reference<ArenaPage>> readPage(LogicalPageID pageID,
|
||||
bool cacheable = true,
|
||||
bool noHit = false,
|
||||
bool* fromCache = nullptr) = 0;
|
||||
virtual Future<Reference<ArenaPage>> readPage(LogicalPageID pageID, bool cacheable = true, bool noHit = false) = 0;
|
||||
virtual Future<Reference<ArenaPage>> readExtent(LogicalPageID pageID) = 0;
|
||||
virtual void releaseExtentReadLock() = 0;
|
||||
|
||||
// Temporary methods for testing
|
||||
virtual Future<Standalone<VectorRef<LogicalPageID>>> getUsedExtents(QueueID queueID) = 0;
|
||||
virtual void pushExtentUsedList(QueueID queueID, LogicalPageID extID) = 0;
|
||||
virtual void extentCacheClear() = 0;
|
||||
virtual int64_t getPageCacheCount() = 0;
|
||||
virtual int64_t getExtentCacheCount() = 0;
|
||||
|
||||
// Get a snapshot of the metakey and all pages as of the version v which must be >= getOldestVersion()
|
||||
// Note that snapshots at any version may still see the results of updatePage() calls.
|
||||
|
|
@ -207,6 +218,8 @@ public:
|
|||
|
||||
virtual StorageBytes getStorageBytes() const = 0;
|
||||
|
||||
virtual int64_t getPageCount() = 0;
|
||||
|
||||
// Count of pages in use by the pager client (including retained old page versions)
|
||||
virtual Future<int64_t> getUserPageCount() = 0;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,80 +0,0 @@
|
|||
/*
|
||||
* IVersionedStore.h
|
||||
*
|
||||
* This source file is part of the FoundationDB open source project
|
||||
*
|
||||
* Copyright 2013-2018 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.
|
||||
*/
|
||||
|
||||
#ifndef FDBSERVER_IVERSIONEDSTORE_H
|
||||
#define FDBSERVER_IVERSIONEDSTORE_H
|
||||
#pragma once
|
||||
|
||||
#include "fdbserver/IKeyValueStore.h"
|
||||
|
||||
#include "flow/flow.h"
|
||||
#include "fdbclient/FDBTypes.h"
|
||||
|
||||
class IStoreCursor {
|
||||
public:
|
||||
virtual Future<Void> findEqual(KeyRef key) = 0;
|
||||
virtual Future<Void> findFirstEqualOrGreater(KeyRef key, int prefetchBytes = 0) = 0;
|
||||
virtual Future<Void> findLastLessOrEqual(KeyRef key, int prefetchBytes = 0) = 0;
|
||||
virtual Future<Void> next() = 0;
|
||||
virtual Future<Void> prev() = 0;
|
||||
|
||||
virtual bool isValid() = 0;
|
||||
virtual KeyRef getKey() = 0;
|
||||
virtual ValueRef getValue() = 0;
|
||||
|
||||
virtual void addref() = 0;
|
||||
virtual void delref() = 0;
|
||||
};
|
||||
|
||||
class IVersionedStore : public IClosable {
|
||||
public:
|
||||
virtual KeyValueStoreType getType() const = 0;
|
||||
virtual bool supportsMutation(int op) const = 0; // If this returns true, then mutate(op, ...) may be called
|
||||
virtual StorageBytes getStorageBytes() const = 0;
|
||||
|
||||
// Writes are provided in an ordered stream.
|
||||
// A write is considered part of (a change leading to) the version determined by the previous call to
|
||||
// setWriteVersion() A write shall not become durable until the following call to commit() begins, and shall be
|
||||
// durable once the following call to commit() returns
|
||||
virtual void set(KeyValueRef keyValue) = 0;
|
||||
virtual void clear(KeyRangeRef range) = 0;
|
||||
virtual void mutate(int op, StringRef param1, StringRef param2) = 0;
|
||||
virtual void setWriteVersion(Version) = 0; // The write version must be nondecreasing
|
||||
virtual void setOldestVersion(Version v) = 0; // Set oldest readable version to be used in next commit
|
||||
virtual Version getOldestVersion() const = 0; // Get oldest readable version
|
||||
virtual Future<Void> commit() = 0;
|
||||
|
||||
virtual Future<Void> init() = 0;
|
||||
virtual Version getLatestVersion() const = 0;
|
||||
|
||||
// readAtVersion() may only be called on a version which has previously been passed to setWriteVersion() and never
|
||||
// previously passed
|
||||
// to forgetVersion. The returned results when violating this precondition are unspecified; the store is not
|
||||
// required to be able to detect violations.
|
||||
// The returned read cursor provides a consistent snapshot of the versioned store, corresponding to all the writes
|
||||
// done with write versions less
|
||||
// than or equal to the given version.
|
||||
// If readAtVersion() is called on the *current* write version, the given read cursor MAY reflect subsequent writes
|
||||
// at the same
|
||||
// write version, OR it may represent a snapshot as of the call to readAtVersion().
|
||||
virtual Reference<IStoreCursor> readAtVersion(Version) = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -402,7 +402,7 @@ private:
|
|||
if (o->op == OpSet) {
|
||||
if (sequential) {
|
||||
KeyValueMapPair pair(o->p1, o->p2);
|
||||
dataSets.push_back(std::make_pair(pair, pair.arena.getSize() + data.getElementBytes()));
|
||||
dataSets.emplace_back(pair, pair.arena.getSize() + data.getElementBytes());
|
||||
} else {
|
||||
data.insert(o->p1, o->p2);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -175,22 +175,22 @@ struct LogRouterData {
|
|||
specialCounter(cc, "WaitForVersionMS", [this]() {
|
||||
double val = this->waitForVersionTime;
|
||||
this->waitForVersionTime = 0;
|
||||
return 1000 * val;
|
||||
return int64_t(1000 * val);
|
||||
});
|
||||
specialCounter(cc, "WaitForVersionMaxMS", [this]() {
|
||||
double val = this->maxWaitForVersionTime;
|
||||
this->maxWaitForVersionTime = 0;
|
||||
return 1000 * val;
|
||||
return int64_t(1000 * val);
|
||||
});
|
||||
specialCounter(cc, "GetMoreMS", [this]() {
|
||||
double val = this->getMoreTime;
|
||||
this->getMoreTime = 0;
|
||||
return 1000 * val;
|
||||
return int64_t(1000 * val);
|
||||
});
|
||||
specialCounter(cc, "GetMoreMaxMS", [this]() {
|
||||
double val = this->maxGetMoreTime;
|
||||
this->maxGetMoreTime = 0;
|
||||
return 1000 * val;
|
||||
return int64_t(1000 * val);
|
||||
});
|
||||
specialCounter(cc, "Generation", [this]() { return this->generation; });
|
||||
logger = traceCounters("LogRouterMetrics",
|
||||
|
|
|
|||
|
|
@ -20,11 +20,11 @@
|
|||
|
||||
#include "flow/Util.h"
|
||||
#include "fdbrpc/FailureMonitor.h"
|
||||
#include "fdbclient/DatabaseContext.h" // for tss mapping
|
||||
#include "fdbclient/KeyBackedTypes.h"
|
||||
#include "fdbclient/SystemData.h"
|
||||
#include "fdbserver/MoveKeys.actor.h"
|
||||
#include "fdbserver/Knobs.h"
|
||||
#include "fdbserver/TSSMappingUtil.actor.h"
|
||||
#include "flow/actorcompiler.h" // This must be the last #include.
|
||||
|
||||
using std::max;
|
||||
|
|
@ -322,6 +322,7 @@ ACTOR static Future<Void> startMoveKeys(Database occ,
|
|||
MoveKeysLock lock,
|
||||
FlowLock* startMoveKeysLock,
|
||||
UID relocationIntervalId,
|
||||
std::map<UID, StorageServerInterface>* tssMapping,
|
||||
const DDEnabledState* ddEnabledState) {
|
||||
state TraceInterval interval("RelocateShard_StartMoveKeys");
|
||||
state Future<Void> warningLogger = logWarningAfter("StartMoveKeysTooLong", 600, servers);
|
||||
|
|
@ -329,6 +330,7 @@ ACTOR static Future<Void> startMoveKeys(Database occ,
|
|||
|
||||
wait(startMoveKeysLock->take(TaskPriority::DataDistributionLaunch));
|
||||
state FlowLock::Releaser releaser(*startMoveKeysLock);
|
||||
state bool loadedTssMapping = false;
|
||||
|
||||
TraceEvent(SevDebug, interval.begin(), relocationIntervalId);
|
||||
|
||||
|
|
@ -365,6 +367,12 @@ ACTOR static Future<Void> startMoveKeys(Database occ,
|
|||
|
||||
wait(checkMoveKeysLock(&(tr->getTransaction()), lock, ddEnabledState));
|
||||
|
||||
if (!loadedTssMapping) {
|
||||
// share transaction for loading tss mapping with the rest of start move keys
|
||||
wait(readTSSMappingRYW(tr, tssMapping));
|
||||
loadedTssMapping = true;
|
||||
}
|
||||
|
||||
vector<Future<Optional<Value>>> serverListEntries;
|
||||
serverListEntries.reserve(servers.size());
|
||||
for (int s = 0; s < servers.size(); s++)
|
||||
|
|
@ -547,7 +555,8 @@ ACTOR Future<Void> checkFetchingState(Database cx,
|
|||
vector<UID> dest,
|
||||
KeyRange keys,
|
||||
Promise<Void> dataMovementComplete,
|
||||
UID relocationIntervalId) {
|
||||
UID relocationIntervalId,
|
||||
std::map<UID, StorageServerInterface> tssMapping) {
|
||||
state Transaction tr(cx);
|
||||
|
||||
loop {
|
||||
|
|
@ -565,7 +574,6 @@ ACTOR Future<Void> checkFetchingState(Database cx,
|
|||
state vector<Optional<Value>> serverListValues = wait(getAll(serverListEntries));
|
||||
vector<Future<Void>> requests;
|
||||
state vector<Future<Void>> tssRequests;
|
||||
ClientDBInfo clientInfo = cx->clientInfo->get();
|
||||
for (int s = 0; s < serverListValues.size(); s++) {
|
||||
if (!serverListValues[s].present()) {
|
||||
// FIXME: Is this the right behavior? dataMovementComplete will never be sent!
|
||||
|
|
@ -577,10 +585,10 @@ ACTOR Future<Void> checkFetchingState(Database cx,
|
|||
requests.push_back(
|
||||
waitForShardReady(si, keys, tr.getReadVersion().get(), GetShardStateRequest::FETCHING));
|
||||
|
||||
Optional<StorageServerInterface> tssPair = clientInfo.getTssPair(si.id());
|
||||
if (tssPair.present()) {
|
||||
auto tssPair = tssMapping.find(si.id());
|
||||
if (tssPair != tssMapping.end()) {
|
||||
tssRequests.push_back(waitForShardReady(
|
||||
tssPair.get(), keys, tr.getReadVersion().get(), GetShardStateRequest::FETCHING));
|
||||
tssPair->second, keys, tr.getReadVersion().get(), GetShardStateRequest::FETCHING));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -617,6 +625,7 @@ ACTOR static Future<Void> finishMoveKeys(Database occ,
|
|||
FlowLock* finishMoveKeysParallelismLock,
|
||||
bool hasRemote,
|
||||
UID relocationIntervalId,
|
||||
std::map<UID, StorageServerInterface> tssMapping,
|
||||
const DDEnabledState* ddEnabledState) {
|
||||
state TraceInterval interval("RelocateShard_FinishMoveKeys");
|
||||
state TraceInterval waitInterval("");
|
||||
|
|
@ -626,9 +635,7 @@ ACTOR static Future<Void> finishMoveKeys(Database occ,
|
|||
state int retries = 0;
|
||||
state FlowLock::Releaser releaser;
|
||||
|
||||
// for killing tss if any get stuck during movekeys
|
||||
state KeyBackedMap<UID, UID> tssMapDB = KeyBackedMap<UID, UID>(tssMappingKeys.begin);
|
||||
state std::vector<StorageServerInterface> tssToKill;
|
||||
state std::vector<std::pair<UID, UID>> tssToKill;
|
||||
state std::unordered_set<UID> tssToIgnore;
|
||||
// try waiting for tss for a 2 loops, give up if they're stuck to not affect the rest of the cluster
|
||||
state int waitForTSSCounter = 2;
|
||||
|
|
@ -658,33 +665,13 @@ ACTOR static Future<Void> finishMoveKeys(Database occ,
|
|||
// (and don't want to add bugs) by changing whole method to RYW. Also, using a different
|
||||
// transaction makes it commit earlier which we may need to guarantee causality of tss getting
|
||||
// removed before client sends a request to this key range on the new SS
|
||||
state Reference<ReadYourWritesTransaction> tssTr =
|
||||
makeReference<ReadYourWritesTransaction>(occ);
|
||||
loop {
|
||||
try {
|
||||
tssTr->setOption(FDBTransactionOptions::PRIORITY_SYSTEM_IMMEDIATE);
|
||||
tssTr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS);
|
||||
for (auto& tss : tssToKill) {
|
||||
// DO NOT remove server list key - that'll break a bunch of stuff. DD will
|
||||
// eventually call removeStorageServer
|
||||
wait(removeTSSPairsFromCluster(occ, tssToKill));
|
||||
|
||||
tssTr->clear(serverTagKeyFor(tss.id()));
|
||||
tssMapDB.erase(tssTr, tss.tssPairID.get());
|
||||
}
|
||||
tssTr->set(tssMappingChangeKey, deterministicRandom()->randomUniqueID().toString());
|
||||
wait(tssTr->commit());
|
||||
|
||||
for (auto& tss : tssToKill) {
|
||||
TraceEvent(SevWarnAlways, "TSS_KillMoveKeys").detail("TSSID", tss.id().toString());
|
||||
tssToIgnore.insert(tss.id());
|
||||
}
|
||||
tssToKill.clear();
|
||||
|
||||
break;
|
||||
} catch (Error& e) {
|
||||
wait(tssTr->onError(e));
|
||||
}
|
||||
for (auto& tssPair : tssToKill) {
|
||||
TraceEvent(SevWarnAlways, "TSS_KillMoveKeys").detail("TSSID", tssPair.second);
|
||||
tssToIgnore.insert(tssPair.second);
|
||||
}
|
||||
tssToKill.clear();
|
||||
}
|
||||
|
||||
tr.info.taskID = TaskPriority::MoveKeys;
|
||||
|
|
@ -861,9 +848,6 @@ ACTOR static Future<Void> finishMoveKeys(Database occ,
|
|||
|
||||
// update client info in case tss mapping changed or server got updated
|
||||
|
||||
// Use most up to date version of tss mapping
|
||||
ClientDBInfo clientInfo = occ->clientInfo->get();
|
||||
|
||||
// Wait for new destination servers to fetch the keys
|
||||
|
||||
serverReady.reserve(storageServerInterfaces.size());
|
||||
|
|
@ -875,13 +859,13 @@ ACTOR static Future<Void> finishMoveKeys(Database occ,
|
|||
tr.getReadVersion().get(),
|
||||
GetShardStateRequest::READABLE));
|
||||
|
||||
Optional<StorageServerInterface> tssPair =
|
||||
clientInfo.getTssPair(storageServerInterfaces[s].id());
|
||||
auto tssPair = tssMapping.find(storageServerInterfaces[s].id());
|
||||
|
||||
if (tssPair.present() && waitForTSSCounter > 0 && !tssToIgnore.count(tssPair.get().id())) {
|
||||
tssReadyInterfs.push_back(tssPair.get());
|
||||
if (tssPair != tssMapping.end() && waitForTSSCounter > 0 &&
|
||||
!tssToIgnore.count(tssPair->second.id())) {
|
||||
tssReadyInterfs.push_back(tssPair->second);
|
||||
tssReady.push_back(waitForShardReady(
|
||||
tssPair.get(), keys, tr.getReadVersion().get(), GetShardStateRequest::READABLE));
|
||||
tssPair->second, keys, tr.getReadVersion().get(), GetShardStateRequest::READABLE));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -918,7 +902,8 @@ ACTOR static Future<Void> finishMoveKeys(Database occ,
|
|||
if (anyTssNotDone && waitForTSSCounter == 0) {
|
||||
for (int i = 0; i < tssReady.size(); i++) {
|
||||
if (!tssReady[i].isReady() || tssReady[i].isError()) {
|
||||
tssToKill.push_back(tssReadyInterfs[i]);
|
||||
tssToKill.push_back(
|
||||
std::pair(tssReadyInterfs[i].tssPairID.get(), tssReadyInterfs[i].id()));
|
||||
}
|
||||
}
|
||||
// repeat loop and go back to start to kill tss' before continuing on
|
||||
|
|
@ -1080,7 +1065,6 @@ ACTOR Future<std::pair<Version, Tag>> addStorageServer(Database cx, StorageServe
|
|||
}
|
||||
|
||||
tssMapDB.set(tr, server.tssPairID.get(), server.id());
|
||||
tr->set(tssMappingChangeKey, deterministicRandom()->randomUniqueID().toString());
|
||||
|
||||
} else {
|
||||
int8_t maxTagLocality = 0;
|
||||
|
|
@ -1143,7 +1127,6 @@ ACTOR Future<std::pair<Version, Tag>> addStorageServer(Database cx, StorageServe
|
|||
// THIS SHOULD NEVER BE ENABLED IN ANY NON-TESTING ENVIRONMENT
|
||||
TraceEvent(SevError, "TSSIdentityMappingEnabled");
|
||||
tssMapDB.set(tr, server.id(), server.id());
|
||||
tr->set(tssMappingChangeKey, deterministicRandom()->randomUniqueID().toString());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1269,10 +1252,8 @@ ACTOR Future<Void> removeStorageServer(Database cx,
|
|||
// THIS SHOULD NEVER BE ENABLED IN ANY NON-TESTING ENVIRONMENT
|
||||
TraceEvent(SevError, "TSSIdentityMappingEnabled");
|
||||
tssMapDB.erase(tr, serverID);
|
||||
tr->set(tssMappingChangeKey, deterministicRandom()->randomUniqueID().toString());
|
||||
} else if (tssPairID.present()) {
|
||||
tssMapDB.erase(tr, tssPairID.get());
|
||||
tr->set(tssMappingChangeKey, deterministicRandom()->randomUniqueID().toString());
|
||||
}
|
||||
|
||||
retry = true;
|
||||
|
|
@ -1374,11 +1355,20 @@ ACTOR Future<Void> moveKeys(Database cx,
|
|||
const DDEnabledState* ddEnabledState) {
|
||||
ASSERT(destinationTeam.size());
|
||||
std::sort(destinationTeam.begin(), destinationTeam.end());
|
||||
wait(startMoveKeys(
|
||||
cx, keys, destinationTeam, lock, startMoveKeysParallelismLock, relocationIntervalId, ddEnabledState));
|
||||
|
||||
state std::map<UID, StorageServerInterface> tssMapping;
|
||||
|
||||
wait(startMoveKeys(cx,
|
||||
keys,
|
||||
destinationTeam,
|
||||
lock,
|
||||
startMoveKeysParallelismLock,
|
||||
relocationIntervalId,
|
||||
&tssMapping,
|
||||
ddEnabledState));
|
||||
|
||||
state Future<Void> completionSignaller =
|
||||
checkFetchingState(cx, healthyDestinations, keys, dataMovementComplete, relocationIntervalId);
|
||||
checkFetchingState(cx, healthyDestinations, keys, dataMovementComplete, relocationIntervalId, tssMapping);
|
||||
|
||||
wait(finishMoveKeys(cx,
|
||||
keys,
|
||||
|
|
@ -1387,6 +1377,7 @@ ACTOR Future<Void> moveKeys(Database cx,
|
|||
finishMoveKeysParallelismLock,
|
||||
hasRemote,
|
||||
relocationIntervalId,
|
||||
tssMapping,
|
||||
ddEnabledState));
|
||||
|
||||
// This is defensive, but make sure that we always say that the movement is complete before moveKeys completes
|
||||
|
|
@ -1428,8 +1419,6 @@ void seedShardServers(Arena& arena, CommitTransactionRef& tr, vector<StorageServ
|
|||
// hack key-backed map here since we can't really change CommitTransactionRef to a RYW transaction
|
||||
Key uidRef = Codec<UID>::pack(s.id()).pack();
|
||||
tr.set(arena, uidRef.withPrefix(tssMappingKeys.begin), uidRef);
|
||||
// tssMapDB.set(tr, server.id(), server.id());
|
||||
tr.set(arena, tssMappingChangeKey, deterministicRandom()->randomUniqueID().toString());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -838,7 +838,7 @@ void commitMessages(Reference<LogData> self,
|
|||
TEST(true); // Splitting commit messages across multiple blocks
|
||||
messages1 = StringRef(block.end(), bytes);
|
||||
block.append(block.arena(), messages.begin(), bytes);
|
||||
self->messageBlocks.push_back(std::make_pair(version, block));
|
||||
self->messageBlocks.emplace_back(version, block);
|
||||
addedBytes += int64_t(block.size()) * SERVER_KNOBS->TLOG_MESSAGE_BLOCK_OVERHEAD_FACTOR;
|
||||
messages = messages.substr(bytes);
|
||||
}
|
||||
|
|
@ -851,7 +851,7 @@ void commitMessages(Reference<LogData> self,
|
|||
// Copy messages into block
|
||||
ASSERT(messages.size() <= block.capacity() - block.size());
|
||||
block.append(block.arena(), messages.begin(), messages.size());
|
||||
self->messageBlocks.push_back(std::make_pair(version, block));
|
||||
self->messageBlocks.emplace_back(version, block);
|
||||
addedBytes += int64_t(block.size()) * SERVER_KNOBS->TLOG_MESSAGE_BLOCK_OVERHEAD_FACTOR;
|
||||
messages = StringRef(block.end() - messages.size(), messages.size());
|
||||
|
||||
|
|
@ -869,7 +869,7 @@ void commitMessages(Reference<LogData> self,
|
|||
int offs = tag->messageOffsets[m];
|
||||
uint8_t const* p =
|
||||
offs < messages1.size() ? messages1.begin() + offs : messages.begin() + offs - messages1.size();
|
||||
tsm->value.version_messages.push_back(std::make_pair(version, LengthPrefixedStringRef((uint32_t*)p)));
|
||||
tsm->value.version_messages.emplace_back(version, LengthPrefixedStringRef((uint32_t*)p));
|
||||
if (tsm->value.version_messages.back().second.expectedSize() > SERVER_KNOBS->MAX_MESSAGE_SIZE) {
|
||||
TraceEvent(SevWarnAlways, "LargeMessage")
|
||||
.detail("Size", tsm->value.version_messages.back().second.expectedSize());
|
||||
|
|
|
|||
|
|
@ -2813,7 +2813,10 @@ ACTOR Future<Void> restorePersistentState(TLogData* self,
|
|||
removed.push_back(errorOr(logData->removed));
|
||||
logsByVersion.emplace_back(ver, id1);
|
||||
|
||||
TraceEvent("TLogPersistentStateRestore", self->dbgid).detail("LogId", logData->logId).detail("Ver", ver);
|
||||
TraceEvent("TLogPersistentStateRestore", self->dbgid)
|
||||
.detail("LogId", logData->logId)
|
||||
.detail("Ver", ver)
|
||||
.detail("RecoveryCount", logData->recoveryCount);
|
||||
// Restore popped keys. Pop operations that took place after the last (committed) updatePersistentDataVersion
|
||||
// might be lost, but that is fine because we will get the corresponding data back, too.
|
||||
tagKeys = prefixRange(rawId.withPrefix(persistTagPoppedKeys.begin));
|
||||
|
|
@ -3050,7 +3053,7 @@ ACTOR Future<Void> tLogStart(TLogData* self, InitializeTLogRequest req, Locality
|
|||
self->popOrder.push_back(recruited.id());
|
||||
self->spillOrder.push_back(recruited.id());
|
||||
|
||||
TraceEvent("TLogStart", logData->logId);
|
||||
TraceEvent("TLogStart", logData->logId).detail("RecoveryCount", logData->recoveryCount);
|
||||
|
||||
state Future<Void> updater;
|
||||
state bool pulledRecoveryVersions = false;
|
||||
|
|
|
|||
|
|
@ -158,6 +158,7 @@ struct ProxyCommitData {
|
|||
EventMetricHandle<SingleKeyMutation> singleKeyMutationEvent;
|
||||
|
||||
std::map<UID, Reference<StorageInfo>> storageCache;
|
||||
std::unordered_map<UID, StorageServerInterface> tssMapping;
|
||||
std::map<Tag, Version> tag_popped;
|
||||
Deque<std::pair<Version, Version>> txsPopVersions;
|
||||
Version lastTxsPop;
|
||||
|
|
|
|||
|
|
@ -309,9 +309,13 @@ ACTOR Future<int64_t> getMaxStorageServerQueueSize(Database cx, Reference<AsyncV
|
|||
.detail("SS", servers[i].id());
|
||||
throw attribute_not_found();
|
||||
}
|
||||
messages.push_back(timeoutError(itr->second.eventLogRequest.getReply(
|
||||
EventLogRequest(StringRef(servers[i].id().toString() + "/StorageMetrics"))),
|
||||
1.0));
|
||||
// Ignore TSS in add delay mode since it can purposefully freeze forever
|
||||
if (!servers[i].isTss() || !g_network->isSimulated() ||
|
||||
g_simulator.tssMode != ISimulator::TSSMode::EnabledAddDelay) {
|
||||
messages.push_back(timeoutError(itr->second.eventLogRequest.getReply(EventLogRequest(
|
||||
StringRef(servers[i].id().toString() + "/StorageMetrics"))),
|
||||
1.0));
|
||||
}
|
||||
}
|
||||
|
||||
wait(waitForAll(messages));
|
||||
|
|
@ -595,6 +599,10 @@ ACTOR Future<Void> waitForQuietDatabase(Database cx,
|
|||
if (g_network->isSimulated())
|
||||
wait(delay(5.0));
|
||||
|
||||
// The quiet database check (which runs at the end of every test) will always time out due to active data movement.
|
||||
// To get around this, quiet Database will disable the perpetual wiggle in the setup phase.
|
||||
wait(setPerpetualStorageWiggle(cx, false, true));
|
||||
|
||||
// Require 3 consecutive successful quiet database checks spaced 2 second apart
|
||||
state int numSuccesses = 0;
|
||||
|
||||
|
|
|
|||
|
|
@ -54,7 +54,9 @@ StringRef radix_join(const StringRef& key1, const StringRef& key2, Arena& arena)
|
|||
uint8_t* s = new (arena) uint8_t[rsize];
|
||||
|
||||
memcpy(s, key1.begin(), key1.size());
|
||||
memcpy(s + key1.size(), key2.begin(), key2.size());
|
||||
if (key2.size() > 0) {
|
||||
memcpy(s + key1.size(), key2.begin(), key2.size());
|
||||
}
|
||||
|
||||
return StringRef(s, rsize);
|
||||
}
|
||||
|
|
@ -591,7 +593,9 @@ StringRef radix_tree::iterator::getKey(uint8_t* content) const {
|
|||
auto node = m_pointee;
|
||||
uint32_t pos = m_pointee->m_depth;
|
||||
while (true) {
|
||||
memcpy(content + pos, node->getKey().begin(), node->getKeySize());
|
||||
if (node->getKeySize() > 0) {
|
||||
memcpy(content + pos, node->getKey().begin(), node->getKeySize());
|
||||
}
|
||||
node = node->m_parent;
|
||||
if (node == nullptr || pos <= 0)
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -233,7 +233,7 @@ ACTOR Future<Void> resolveBatch(Reference<Resolver> self, ResolveTransactionBatc
|
|||
self->resolvedStateBytes += stateBytes;
|
||||
|
||||
if (stateBytes > 0)
|
||||
self->recentStateTransactionSizes.push_back(std::make_pair(req.version, stateBytes));
|
||||
self->recentStateTransactionSizes.emplace_back(req.version, stateBytes);
|
||||
|
||||
ASSERT(req.version >= firstUnseenVersion);
|
||||
ASSERT(firstUnseenVersion >= self->debugMinRecentStateVersion);
|
||||
|
|
|
|||
|
|
@ -189,7 +189,7 @@ ACTOR Future<Void> monitorWorkerLiveness(Reference<RestoreWorkerData> self) {
|
|||
loop {
|
||||
std::vector<std::pair<UID, RestoreSimpleRequest>> requests;
|
||||
for (auto& worker : self->workerInterfaces) {
|
||||
requests.push_back(std::make_pair(worker.first, RestoreSimpleRequest()));
|
||||
requests.emplace_back(worker.first, RestoreSimpleRequest());
|
||||
}
|
||||
wait(sendBatchRequests(&RestoreWorkerInterface::heartbeat, self->workerInterfaces, requests));
|
||||
wait(delay(60.0));
|
||||
|
|
|
|||
|
|
@ -170,7 +170,10 @@ class TestConfig {
|
|||
if (attrib == "maxTLogVersion") {
|
||||
sscanf(value.c_str(), "%d", &maxTLogVersion);
|
||||
}
|
||||
if (attrib == "restartInfoLocation") {
|
||||
if (attrib == "disableTss") {
|
||||
sscanf(value.c_str(), "%d", &disableTss);
|
||||
}
|
||||
if (attrib == "restartInfoLocation") {
|
||||
isFirstTestInRestart = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -186,6 +189,8 @@ public:
|
|||
bool startIncompatibleProcess = false;
|
||||
int logAntiQuorum = -1;
|
||||
bool isFirstTestInRestart = false;
|
||||
// 7.0 cannot be downgraded to 6.3 after enabling TSS, so disable TSS for 6.3 downgrade tests
|
||||
bool disableTss = false;
|
||||
// Storage Engine Types: Verify match with SimulationConfig::generateNormalConfig
|
||||
// 0 = "ssd"
|
||||
// 1 = "memory"
|
||||
|
|
@ -203,6 +208,23 @@ public:
|
|||
stderrSeverity, machineCount, processesPerMachine, coordinators;
|
||||
Optional<std::string> config;
|
||||
|
||||
bool tomlKeyPresent(const toml::value& data, std::string key) {
|
||||
if (data.is_table()) {
|
||||
for (const auto& [k, v] : data.as_table()) {
|
||||
if (k == key || tomlKeyPresent(v, key)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else if (data.is_array()) {
|
||||
for (const auto& v : data.as_array()) {
|
||||
if (tomlKeyPresent(v, key)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void readFromConfig(const char* testFile) {
|
||||
if (isIniFile(testFile)) {
|
||||
loadIniFile(testFile);
|
||||
|
|
@ -217,6 +239,7 @@ public:
|
|||
.add("logAntiQuorum", &logAntiQuorum)
|
||||
.add("storageEngineExcludeTypes", &storageEngineExcludeTypes)
|
||||
.add("maxTLogVersion", &maxTLogVersion)
|
||||
.add("disableTss", &disableTss)
|
||||
.add("simpleConfig", &simpleConfig)
|
||||
.add("generateFearless", &generateFearless)
|
||||
.add("datacenters", &datacenters)
|
||||
|
|
@ -248,6 +271,10 @@ public:
|
|||
TraceEvent("StderrSeverity").detail("NewSeverity", stderrSeverity.get());
|
||||
}
|
||||
}
|
||||
// look for restartInfoLocation to mark isFirstTestInRestart
|
||||
if (!isFirstTestInRestart) {
|
||||
isFirstTestInRestart = tomlKeyPresent(file, "restartInfoLocation");
|
||||
}
|
||||
} catch (std::exception& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
TraceEvent("TOMLParseError").detail("Error", printable(e.what()));
|
||||
|
|
@ -1173,7 +1200,7 @@ void SimulationConfig::generateNormalConfig(const TestConfig& testConfig) {
|
|||
}
|
||||
|
||||
int tssCount = 0;
|
||||
if (!testConfig.simpleConfig && deterministicRandom()->random01() < 0.25) {
|
||||
if (!testConfig.simpleConfig && !testConfig.disableTss && deterministicRandom()->random01() < 0.25) {
|
||||
// 1 or 2 tss
|
||||
tssCount = deterministicRandom()->randomInt(1, 3);
|
||||
}
|
||||
|
|
@ -1185,13 +1212,24 @@ void SimulationConfig::generateNormalConfig(const TestConfig& testConfig) {
|
|||
// }
|
||||
// set_config("memory");
|
||||
// set_config("memory-radixtree-beta");
|
||||
|
||||
if (deterministicRandom()->random01() < 0.5) {
|
||||
set_config("perpetual_storage_wiggle=0");
|
||||
} else {
|
||||
set_config("perpetual_storage_wiggle=1");
|
||||
}
|
||||
// set_config("perpetual_storage_wiggle=1");
|
||||
if (testConfig.simpleConfig) {
|
||||
db.desiredTLogCount = 1;
|
||||
db.commitProxyCount = 1;
|
||||
db.grvProxyCount = 1;
|
||||
db.resolverCount = 1;
|
||||
}
|
||||
int replication_type = testConfig.simpleConfig ? 1 : (std::max(testConfig.minimumReplication, datacenters > 4 ? deterministicRandom()->randomInt(1, 3) : std::min(deterministicRandom()->randomInt(0, 6), 3)));
|
||||
int replication_type = testConfig.simpleConfig
|
||||
? 1
|
||||
: (std::max(testConfig.minimumReplication,
|
||||
datacenters > 4 ? deterministicRandom()->randomInt(1, 3)
|
||||
: std::min(deterministicRandom()->randomInt(0, 6), 3)));
|
||||
if (testConfig.config.present()) {
|
||||
set_config(testConfig.config.get());
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -355,8 +355,10 @@ public:
|
|||
// pre: !finished()
|
||||
force_inline void prefetch() {
|
||||
Node* next = x->getNext(level - 1);
|
||||
_mm_prefetch((const char*)next, _MM_HINT_T0);
|
||||
_mm_prefetch((const char*)next + 64, _MM_HINT_T0);
|
||||
if (next) {
|
||||
_mm_prefetch((const char*)next, _MM_HINT_T0);
|
||||
_mm_prefetch((const char*)next + 64, _MM_HINT_T0);
|
||||
}
|
||||
}
|
||||
|
||||
// pre: !finished()
|
||||
|
|
|
|||
|
|
@ -1807,7 +1807,7 @@ static Future<vector<std::pair<iface, EventMap>>> getServerMetrics(
|
|||
++futureItr;
|
||||
}
|
||||
|
||||
results.push_back(std::make_pair(servers[i], serverResults));
|
||||
results.emplace_back(servers[i], serverResults);
|
||||
}
|
||||
|
||||
return results;
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ struct TransientStorageMetricSample : StorageMetricSample {
|
|||
int64_t addAndExpire(KeyRef key, int64_t metric, double expiration) {
|
||||
int64_t x = add(key, metric);
|
||||
if (x)
|
||||
queue.push_back(std::make_pair(expiration, std::make_pair(*sample.find(key), -x)));
|
||||
queue.emplace_back(expiration, std::make_pair(*sample.find(key), -x));
|
||||
return x;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2884,7 +2884,10 @@ ACTOR Future<Void> restorePersistentState(TLogData* self,
|
|||
removed.push_back(errorOr(logData->removed));
|
||||
logsByVersion.emplace_back(ver, id1);
|
||||
|
||||
TraceEvent("TLogPersistentStateRestore", self->dbgid).detail("LogId", logData->logId).detail("Ver", ver);
|
||||
TraceEvent("TLogPersistentStateRestore", self->dbgid)
|
||||
.detail("LogId", logData->logId)
|
||||
.detail("Ver", ver)
|
||||
.detail("RecoveryCount", logData->recoveryCount);
|
||||
// Restore popped keys. Pop operations that took place after the last (committed) updatePersistentDataVersion
|
||||
// might be lost, but that is fine because we will get the corresponding data back, too.
|
||||
tagKeys = prefixRange(rawId.withPrefix(persistTagPoppedKeys.begin));
|
||||
|
|
@ -3129,7 +3132,7 @@ ACTOR Future<Void> tLogStart(TLogData* self, InitializeTLogRequest req, Locality
|
|||
self->popOrder.push_back(recruited.id());
|
||||
self->spillOrder.push_back(recruited.id());
|
||||
|
||||
TraceEvent("TLogStart", logData->logId);
|
||||
TraceEvent("TLogStart", logData->logId).detail("RecoveryCount", logData->recoveryCount);
|
||||
|
||||
state Future<Void> updater;
|
||||
state bool pulledRecoveryVersions = false;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* TSSMappingUtil.actor.cpp
|
||||
*
|
||||
* This source file is part of the FoundationDB open source project
|
||||
*
|
||||
* Copyright 2013-2018 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/SystemData.h"
|
||||
#include "fdbclient/KeyBackedTypes.h"
|
||||
#include "fdbserver/TSSMappingUtil.actor.h"
|
||||
#include "flow/actorcompiler.h" // This must be the last #include.
|
||||
|
||||
ACTOR Future<Void> readTSSMappingRYW(Reference<ReadYourWritesTransaction> tr, std::map<UID, StorageServerInterface>* tssMapping) {
|
||||
KeyBackedMap<UID, UID> tssMapDB = KeyBackedMap<UID, UID>(tssMappingKeys.begin);
|
||||
state std::vector<std::pair<UID, UID>> uidMapping = wait(tssMapDB.getRange(tr, UID(), Optional<UID>(), CLIENT_KNOBS->TOO_MANY));
|
||||
ASSERT(uidMapping.size() < CLIENT_KNOBS->TOO_MANY);
|
||||
|
||||
state std::map<UID, StorageServerInterface> mapping;
|
||||
for (auto& it : uidMapping) {
|
||||
state UID ssId = it.first;
|
||||
Optional<Value> v = wait(tr->get(serverListKeyFor(it.second)));
|
||||
(*tssMapping)[ssId] = decodeServerListValue(v.get());
|
||||
}
|
||||
return Void();
|
||||
}
|
||||
|
||||
ACTOR Future<Void> readTSSMapping(Transaction* tr, std::map<UID, StorageServerInterface>* tssMapping) {
|
||||
state RangeResult mappingList = wait(tr->getRange(tssMappingKeys, CLIENT_KNOBS->TOO_MANY));
|
||||
ASSERT(!mappingList.more && mappingList.size() < CLIENT_KNOBS->TOO_MANY);
|
||||
|
||||
for (auto& it : mappingList) {
|
||||
state UID ssId = Codec<UID>::unpack(Tuple::unpack(it.key.removePrefix(tssMappingKeys.begin)));
|
||||
UID tssId = Codec<UID>::unpack(Tuple::unpack(it.value));
|
||||
Optional<Value> v = wait(tr->get(serverListKeyFor(tssId)));
|
||||
(*tssMapping)[ssId] = decodeServerListValue(v.get());
|
||||
}
|
||||
return Void();
|
||||
}
|
||||
|
||||
ACTOR Future<Void> removeTSSPairsFromCluster(Database cx, vector<std::pair<UID, UID>> pairsToRemove) {
|
||||
state Reference<ReadYourWritesTransaction> tr = makeReference<ReadYourWritesTransaction>(cx);
|
||||
state KeyBackedMap<UID, UID> tssMapDB = KeyBackedMap<UID, UID>(tssMappingKeys.begin);
|
||||
loop {
|
||||
try {
|
||||
tr->setOption(FDBTransactionOptions::PRIORITY_SYSTEM_IMMEDIATE);
|
||||
tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS);
|
||||
for (auto& tssPair : pairsToRemove) {
|
||||
// DO NOT remove server list key - that'll break a bunch of stuff. DD will eventually call removeStorageServer
|
||||
tr->clear(serverTagKeyFor(tssPair.second));
|
||||
tssMapDB.erase(tr, tssPair.first);
|
||||
}
|
||||
wait(tr->commit());
|
||||
break;
|
||||
} catch (Error& e) {
|
||||
wait(tr->onError(e));
|
||||
}
|
||||
}
|
||||
return Void();
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* TSSMappingUtil.actor.h
|
||||
*
|
||||
* This source file is part of the FoundationDB open source project
|
||||
*
|
||||
* Copyright 2013-2018 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// When actually compiled (NO_INTELLISENSE), include the generated version of this file. In intellisense use the source
|
||||
// version.
|
||||
#if defined(NO_INTELLISENSE) && !defined(TSS_MAPPING_UTIL_SERVER_G_H)
|
||||
#define TSS_MAPPING_UTIL_SERVER_G_H
|
||||
#include "fdbserver/TSSMappingUtil.actor.g.h"
|
||||
#elif !defined(TSS_MAPPING_UTIL_SERVER_H)
|
||||
#define TSS_MAPPING_UTIL_SERVER_H
|
||||
|
||||
#include "fdbclient/StorageServerInterface.h"
|
||||
#include "flow/actorcompiler.h" // This must be the last #include.
|
||||
|
||||
/*
|
||||
* Collection of utility functions for dealing with the TSS mapping
|
||||
*/
|
||||
|
||||
// Reads the current cluster TSS mapping as part of the RYW transaction
|
||||
ACTOR Future<Void> readTSSMappingRYW(Reference<ReadYourWritesTransaction> tr, std::map<UID, StorageServerInterface>* tssMapping);
|
||||
|
||||
// Reads the current cluster TSS mapping as part of the given Transaction
|
||||
ACTOR Future<Void> readTSSMapping(Transaction* tr, std::map<UID, StorageServerInterface>* tssMapping);
|
||||
|
||||
// Removes the TSS pairs from the cluster
|
||||
ACTOR Future<Void> removeTSSPairsFromCluster(Database cx, vector<std::pair<UID, UID>> pairsToRemove);
|
||||
|
||||
#include "flow/unactorcompiler.h"
|
||||
#endif
|
||||
|
|
@ -2118,7 +2118,7 @@ struct TagPartitionedLogSystem : ILogSystem, ReferenceCounted<TagPartitionedLogS
|
|||
std::vector<Reference<AsyncVar<bool>>> failed;
|
||||
|
||||
for (const auto& logVar : logServers.back()->logServers) {
|
||||
allLogServers.push_back(std::make_pair(logVar, coreSet.tLogPolicy));
|
||||
allLogServers.emplace_back(logVar, coreSet.tLogPolicy);
|
||||
failed.push_back(makeReference<AsyncVar<bool>>());
|
||||
failureTrackers.push_back(monitorLog(logVar, failed.back()));
|
||||
}
|
||||
|
|
@ -2130,7 +2130,7 @@ struct TagPartitionedLogSystem : ILogSystem, ReferenceCounted<TagPartitionedLogS
|
|||
|
||||
for (const auto& logSet : oldLogData.back().tLogs) {
|
||||
for (const auto& logVar : logSet->logServers) {
|
||||
allLogServers.push_back(std::make_pair(logVar, logSet->tLogPolicy));
|
||||
allLogServers.emplace_back(logVar, logSet->tLogPolicy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -1057,7 +1057,7 @@ private:
|
|||
flushAndExit(FDB_EXIT_ERROR);
|
||||
}
|
||||
syn = syn.substr(7);
|
||||
knobs.push_back(std::make_pair(syn, args.OptionArg()));
|
||||
knobs.emplace_back(syn, args.OptionArg());
|
||||
manualKnobOverrides[syn] = args.OptionArg();
|
||||
break;
|
||||
}
|
||||
|
|
@ -1367,10 +1367,10 @@ private:
|
|||
}
|
||||
// SOMEDAY: ideally we'd have some better way to express that a knob should be elevated to formal
|
||||
// parameter
|
||||
knobs.push_back(std::make_pair(
|
||||
knobs.emplace_back(
|
||||
"page_cache_4k",
|
||||
format("%ld", ti.get() / 4096 * 4096))); // The cache holds 4K pages, so we can truncate this to the
|
||||
// next smaller multiple of 4K.
|
||||
format("%ld", ti.get() / 4096 * 4096)); // The cache holds 4K pages, so we can truncate this to the
|
||||
// next smaller multiple of 4K.
|
||||
break;
|
||||
case OPT_BUGGIFY:
|
||||
if (!strcmp(args.OptionArg(), "on"))
|
||||
|
|
@ -2143,7 +2143,7 @@ int main(int argc, char* argv[]) {
|
|||
s = s.substr(LiteralStringRef("struct ").size());
|
||||
#endif
|
||||
|
||||
typeNames.push_back(std::make_pair(s, i->first));
|
||||
typeNames.emplace_back(s, i->first);
|
||||
}
|
||||
std::sort(typeNames.begin(), typeNames.end());
|
||||
for (int i = 0; i < typeNames.size(); i++) {
|
||||
|
|
|
|||
|
|
@ -711,15 +711,10 @@ ACTOR Future<vector<Standalone<CommitTransactionRef>>> recruitEverything(Referen
|
|||
TraceEvent("MasterRecoveryState", self->dbgid)
|
||||
.detail("StatusCode", RecoveryStatus::recruiting_transaction_servers)
|
||||
.detail("Status", RecoveryStatus::names[RecoveryStatus::recruiting_transaction_servers])
|
||||
.detail("RequiredTLogs", self->configuration.tLogReplicationFactor)
|
||||
.detail("DesiredTLogs", self->configuration.getDesiredLogs())
|
||||
.detail("Conf", self->configuration.toString())
|
||||
.detail("RequiredCommitProxies", 1)
|
||||
.detail("DesiredCommitProxies", self->configuration.getDesiredCommitProxies())
|
||||
.detail("RequiredGrvProxies", 1)
|
||||
.detail("DesiredGrvProxies", self->configuration.getDesiredGrvProxies())
|
||||
.detail("RequiredResolvers", 1)
|
||||
.detail("DesiredResolvers", self->configuration.getDesiredResolvers())
|
||||
.detail("StoreType", self->configuration.storageServerStoreType)
|
||||
.trackLatest("MasterRecoveryState");
|
||||
|
||||
// FIXME: we only need log routers for the same locality as the master
|
||||
|
|
@ -732,14 +727,25 @@ ACTOR Future<vector<Standalone<CommitTransactionRef>>> recruitEverything(Referen
|
|||
wait(brokenPromiseToNever(self->clusterController.recruitFromConfiguration.getReply(
|
||||
RecruitFromConfigurationRequest(self->configuration, self->lastEpochEnd == 0, maxLogRouters))));
|
||||
|
||||
std::string primaryDcIds, remoteDcIds;
|
||||
|
||||
self->primaryDcId.clear();
|
||||
self->remoteDcIds.clear();
|
||||
if (recruits.dcId.present()) {
|
||||
self->primaryDcId.push_back(recruits.dcId);
|
||||
if (!primaryDcIds.empty()) {
|
||||
primaryDcIds += ',';
|
||||
}
|
||||
primaryDcIds += printable(recruits.dcId);
|
||||
if (self->configuration.regions.size() > 1) {
|
||||
self->remoteDcIds.push_back(recruits.dcId.get() == self->configuration.regions[0].dcId
|
||||
? self->configuration.regions[1].dcId
|
||||
: self->configuration.regions[0].dcId);
|
||||
Key remoteDcId = recruits.dcId.get() == self->configuration.regions[0].dcId
|
||||
? self->configuration.regions[1].dcId
|
||||
: self->configuration.regions[0].dcId;
|
||||
self->remoteDcIds.push_back(remoteDcId);
|
||||
if (!remoteDcIds.empty()) {
|
||||
remoteDcIds += ',';
|
||||
}
|
||||
remoteDcIds += printable(remoteDcId);
|
||||
}
|
||||
}
|
||||
self->backupWorkers.swap(recruits.backupWorkers);
|
||||
|
|
@ -755,6 +761,8 @@ ACTOR Future<vector<Standalone<CommitTransactionRef>>> recruitEverything(Referen
|
|||
.detail("OldLogRouters", recruits.oldLogRouters.size())
|
||||
.detail("StorageServers", recruits.storageServers.size())
|
||||
.detail("BackupWorkers", self->backupWorkers.size())
|
||||
.detail("PrimaryDcIds", primaryDcIds)
|
||||
.detail("RemoteDcIds", remoteDcIds)
|
||||
.trackLatest("MasterRecoveryState");
|
||||
|
||||
// Actually, newSeedServers does both the recruiting and initialization of the seed servers; so if this is a brand
|
||||
|
|
|
|||
|
|
@ -43143,7 +43143,7 @@ SQLITE_PRIVATE void sqlite3VdbeMakeReady(
|
|||
p->pFree = sqlite3DbMallocZero(db, nByte);
|
||||
}
|
||||
zCsr = p->pFree;
|
||||
zEnd = &zCsr[nByte];
|
||||
zEnd = zCsr ? &zCsr[nByte] : NULL;
|
||||
}while( nByte && !db->mallocFailed );
|
||||
|
||||
p->nCursor = (u16)nCursor;
|
||||
|
|
|
|||
|
|
@ -95,13 +95,25 @@ struct AddingShard : NonCopyable {
|
|||
Promise<Void> fetchComplete;
|
||||
Promise<Void> readWrite;
|
||||
|
||||
std::deque<Standalone<VerUpdateRef>>
|
||||
updates; // during the Fetching phase, mutations with key in keys and version>=(fetchClient's) fetchVersion;
|
||||
// During the Fetching phase, it saves newer mutations whose version is greater or equal to fetchClient's
|
||||
// fetchVersion, while the shard is still busy catching up with fetchClient. It applies these updates after fetching
|
||||
// completes.
|
||||
std::deque<Standalone<VerUpdateRef>> updates;
|
||||
|
||||
struct StorageServer* server;
|
||||
Version transferredVersion;
|
||||
|
||||
enum Phase { WaitPrevious, Fetching, Waiting };
|
||||
// To learn more details of the phase transitions, see function fetchKeys(). The phases below are sorted in
|
||||
// chronological order and do not go back.
|
||||
enum Phase {
|
||||
WaitPrevious,
|
||||
// During Fetching phase, it fetches data before fetchVersion and write it to storage, then let updater know it
|
||||
// is ready to update the deferred updates` (see the comment of member variable `updates` above).
|
||||
Fetching,
|
||||
// During Waiting phase, it sends updater the deferred updates, and wait until they are durable.
|
||||
Waiting
|
||||
// The shard's state is changed from adding to readWrite then.
|
||||
};
|
||||
|
||||
Phase phase;
|
||||
|
||||
|
|
@ -128,6 +140,7 @@ class ShardInfo : public ReferenceCounted<ShardInfo>, NonCopyable {
|
|||
: adding(std::move(adding)), readWrite(readWrite), keys(keys) {}
|
||||
|
||||
public:
|
||||
// A shard has 3 mutual exclusive states: adding, readWrite and notAssigned.
|
||||
std::unique_ptr<AddingShard> adding;
|
||||
struct StorageServer* readWrite;
|
||||
KeyRange keys;
|
||||
|
|
@ -284,6 +297,7 @@ const int VERSION_OVERHEAD =
|
|||
sizeof(Reference<VersionedMap<KeyRef, ValueOrClearToRef>::PTreeT>)); // versioned map [ x2 for
|
||||
// createNewVersion(version+1) ], 64b
|
||||
// overhead for map
|
||||
// For both the mutation log and the versioned map.
|
||||
static int mvccStorageBytes(MutationRef const& m) {
|
||||
return VersionedMap<KeyRef, ValueOrClearToRef>::overheadPerItem * 2 +
|
||||
(MutationRef::OVERHEAD_BYTES + m.param1.size() + m.param2.size()) * 2;
|
||||
|
|
@ -690,9 +704,24 @@ public:
|
|||
CounterCollection cc;
|
||||
Counter allQueries, getKeyQueries, getValueQueries, getRangeQueries, finishedQueries, lowPriorityQueries,
|
||||
rowsQueried, bytesQueried, watchQueries, emptyQueries;
|
||||
Counter bytesInput, bytesDurable, bytesFetched,
|
||||
mutationBytes; // Like bytesInput but without MVCC accounting
|
||||
|
||||
// Bytes of the mutations that have been added to the memory of the storage server. When the data is durable
|
||||
// and cleared from the memory, we do not subtract it but add it to bytesDurable.
|
||||
Counter bytesInput;
|
||||
// Bytes of the mutations that have been removed from memory because they durable. The counting is same as
|
||||
// bytesInput, instead of the actual bytes taken in the storages, so that (bytesInput - bytesDurable) can
|
||||
// reflect the current memory footprint of MVCC.
|
||||
Counter bytesDurable;
|
||||
// Bytes fetched by fetchKeys() for data movements. The size is counted as a collection of KeyValueRef.
|
||||
Counter bytesFetched;
|
||||
// Like bytesInput but without MVCC accounting. The size is counted as how much it takes when serialized. It
|
||||
// is basically the size of both parameters of the mutation and a 12 bytes overhead that keeps mutation type
|
||||
// and the lengths of both parameters.
|
||||
Counter mutationBytes;
|
||||
|
||||
Counter sampledBytesCleared;
|
||||
// The number of key-value pairs fetched by fetchKeys()
|
||||
Counter kvFetched;
|
||||
Counter mutations, setMutations, clearRangeMutations, atomicMutations;
|
||||
Counter updateBatches, updateVersions;
|
||||
Counter loops;
|
||||
|
|
@ -712,16 +741,16 @@ public:
|
|||
bytesQueried("BytesQueried", cc), watchQueries("WatchQueries", cc), emptyQueries("EmptyQueries", cc),
|
||||
bytesInput("BytesInput", cc), bytesDurable("BytesDurable", cc), bytesFetched("BytesFetched", cc),
|
||||
mutationBytes("MutationBytes", cc), sampledBytesCleared("SampledBytesCleared", cc),
|
||||
mutations("Mutations", cc), setMutations("SetMutations", cc),
|
||||
kvFetched("KVFetched", cc), mutations("Mutations", cc), setMutations("SetMutations", cc),
|
||||
clearRangeMutations("ClearRangeMutations", cc), atomicMutations("AtomicMutations", cc),
|
||||
updateBatches("UpdateBatches", cc), updateVersions("UpdateVersions", cc), loops("Loops", cc),
|
||||
fetchWaitingMS("FetchWaitingMS", cc), fetchWaitingCount("FetchWaitingCount", cc),
|
||||
fetchExecutingMS("FetchExecutingMS", cc), fetchExecutingCount("FetchExecutingCount", cc),
|
||||
readsRejected("ReadsRejected", cc), fetchedVersions("FetchedVersions", cc),
|
||||
fetchesFromLogs("FetchesFromLogs", cc), readLatencySample("ReadLatencyMetrics",
|
||||
self->thisServerID,
|
||||
SERVER_KNOBS->LATENCY_METRICS_LOGGING_INTERVAL,
|
||||
SERVER_KNOBS->LATENCY_SAMPLE_SIZE),
|
||||
self->thisServerID,
|
||||
SERVER_KNOBS->LATENCY_METRICS_LOGGING_INTERVAL,
|
||||
SERVER_KNOBS->LATENCY_SAMPLE_SIZE),
|
||||
readLatencyBands("ReadLatencyBands", self->thisServerID, SERVER_KNOBS->STORAGE_LOGGING_DELAY) {
|
||||
specialCounter(cc, "LastTLogVersion", [self]() { return self->lastTLogVersion; });
|
||||
specialCounter(cc, "Version", [self]() { return self->version.get(); });
|
||||
|
|
@ -729,7 +758,7 @@ public:
|
|||
specialCounter(cc, "DurableVersion", [self]() { return self->durableVersion.get(); });
|
||||
specialCounter(cc, "DesiredOldestVersion", [self]() { return self->desiredOldestVersion.get(); });
|
||||
specialCounter(cc, "VersionLag", [self]() { return self->versionLag; });
|
||||
specialCounter(cc, "LocalRate", [self] { return self->currentRate() * 100; });
|
||||
specialCounter(cc, "LocalRate", [self] { return int64_t(self->currentRate() * 100); });
|
||||
|
||||
specialCounter(cc, "BytesReadSampleCount", [self]() { return self->metrics.bytesReadSample.queue.size(); });
|
||||
|
||||
|
|
@ -2209,6 +2238,8 @@ Optional<MutationRef> clipMutation(MutationRef const& m, KeyRangeRef range) {
|
|||
return Optional<MutationRef>();
|
||||
}
|
||||
|
||||
// Return true if the mutation need to be applied, otherwise (it's a CompareAndClear mutation and failed the comparison)
|
||||
// false.
|
||||
bool expandMutation(MutationRef& m,
|
||||
StorageServer::VersionedData const& data,
|
||||
UpdateEagerReadInfo* eager,
|
||||
|
|
@ -2312,6 +2343,8 @@ void applyMutation(StorageServer* self, MutationRef const& m, Arena& arena, Stor
|
|||
self->metrics.notify(m.param1, metrics);
|
||||
|
||||
if (m.type == MutationRef::SetValue) {
|
||||
// VersionedMap (data) is bookkeeping all empty ranges. If the key to be set is new, it is supposed to be in a
|
||||
// range what was empty. Break the empty range into halves.
|
||||
auto prev = data.atLatest().lastLessOrEqual(m.param1);
|
||||
if (prev && prev->isClearTo() && prev->getEndKey() > m.param1) {
|
||||
ASSERT(prev.key() <= m.param1);
|
||||
|
|
@ -2542,19 +2575,28 @@ class FetchKeysMetricReporter {
|
|||
int fetchedBytes;
|
||||
StorageServer::FetchKeysHistograms& histograms;
|
||||
StorageServer::CurrentRunningFetchKeys& currentRunning;
|
||||
Counter& bytesFetchedCounter;
|
||||
Counter& kvFetchedCounter;
|
||||
|
||||
public:
|
||||
FetchKeysMetricReporter(const UID& uid_,
|
||||
const double startTime_,
|
||||
const KeyRange& keyRange,
|
||||
StorageServer::FetchKeysHistograms& histograms_,
|
||||
StorageServer::CurrentRunningFetchKeys& currentRunning_)
|
||||
: uid(uid_), startTime(startTime_), fetchedBytes(0), histograms(histograms_), currentRunning(currentRunning_) {
|
||||
StorageServer::CurrentRunningFetchKeys& currentRunning_,
|
||||
Counter& bytesFetchedCounter,
|
||||
Counter& kvFetchedCounter)
|
||||
: uid(uid_), startTime(startTime_), fetchedBytes(0), histograms(histograms_), currentRunning(currentRunning_),
|
||||
bytesFetchedCounter(bytesFetchedCounter), kvFetchedCounter(kvFetchedCounter) {
|
||||
|
||||
currentRunning.recordStart(uid, keyRange);
|
||||
}
|
||||
|
||||
void addFetchedBytes(const int bytes) { fetchedBytes += bytes; }
|
||||
void addFetchedBytes(const int bytes, const int kvCount) {
|
||||
fetchedBytes += bytes;
|
||||
bytesFetchedCounter += bytes;
|
||||
kvFetchedCounter += kvCount;
|
||||
}
|
||||
|
||||
~FetchKeysMetricReporter() {
|
||||
double latency = now() - startTime;
|
||||
|
|
@ -2580,8 +2622,13 @@ ACTOR Future<Void> fetchKeys(StorageServer* data, AddingShard* shard) {
|
|||
state Future<Void> warningLogger = logFetchKeysWarning(shard);
|
||||
state const double startTime = now();
|
||||
state int fetchBlockBytes = BUGGIFY ? SERVER_KNOBS->BUGGIFY_BLOCK_BYTES : SERVER_KNOBS->FETCH_BLOCK_BYTES;
|
||||
state FetchKeysMetricReporter metricReporter(
|
||||
fetchKeysID, startTime, keys, data->fetchKeysHistograms, data->currentRunningFetchKeys);
|
||||
state FetchKeysMetricReporter metricReporter(fetchKeysID,
|
||||
startTime,
|
||||
keys,
|
||||
data->fetchKeysHistograms,
|
||||
data->currentRunningFetchKeys,
|
||||
data->counters.bytesFetched,
|
||||
data->counters.kvFetched);
|
||||
|
||||
// delay(0) to force a return to the run loop before the work of fetchKeys is started.
|
||||
// This allows adding->start() to be called inline with CSK.
|
||||
|
|
@ -2673,8 +2720,8 @@ ACTOR Future<Void> fetchKeys(StorageServer* data, AddingShard* shard) {
|
|||
for (auto k = this_block.begin(); k != this_block.end(); ++k)
|
||||
DEBUG_MUTATION("fetch", fetchVersion, MutationRef(MutationRef::SetValue, k->key, k->value));
|
||||
|
||||
metricReporter.addFetchedBytes(expectedSize);
|
||||
data->counters.bytesFetched += expectedSize;
|
||||
metricReporter.addFetchedBytes(expectedSize, this_block.size());
|
||||
|
||||
if (fetchBlockBytes > expectedSize) {
|
||||
holdingFKPL.release(fetchBlockBytes - expectedSize);
|
||||
}
|
||||
|
|
@ -2683,7 +2730,7 @@ ACTOR Future<Void> fetchKeys(StorageServer* data, AddingShard* shard) {
|
|||
// wait( data->fetchKeysStorageWriteLock.take() );
|
||||
// state FlowLock::Releaser holdingFKSWL( data->fetchKeysStorageWriteLock );
|
||||
|
||||
// Write this_block to storage
|
||||
// Write this_block directly to storage, bypassing update() which write to MVCC in memory.
|
||||
state KeyValueRef* kvItr = this_block.begin();
|
||||
for (; kvItr != this_block.end(); ++kvItr) {
|
||||
data->storage.writeKeyValue(*kvItr);
|
||||
|
|
@ -2805,6 +2852,8 @@ ACTOR Future<Void> fetchKeys(StorageServer* data, AddingShard* shard) {
|
|||
Promise<FetchInjectionInfo*> p;
|
||||
data->readyFetchKeys.push_back(p);
|
||||
|
||||
// After we add to the promise readyFetchKeys, update() would provide a pointer to FetchInjectionInfo that we
|
||||
// can put mutation in.
|
||||
FetchInjectionInfo* batch = wait(p.getFuture());
|
||||
TraceEvent(SevDebug, "FKUpdateBatch", data->thisServerID).detail("FKID", interval.pairID);
|
||||
|
||||
|
|
@ -2859,6 +2908,9 @@ ACTOR Future<Void> fetchKeys(StorageServer* data, AddingShard* shard) {
|
|||
keys,
|
||||
true); // keys will be available when getLatestVersion()==transferredVersion is durable
|
||||
|
||||
// Note that since it receives a pointer to FetchInjectionInfo, the thread does not leave this actor until this
|
||||
// point.
|
||||
|
||||
// Wait for the transferredVersion (and therefore the shard data) to be committed and durable.
|
||||
wait(data->durableVersion.whenAtLeast(shard->transferredVersion));
|
||||
|
||||
|
|
@ -2922,6 +2974,9 @@ void AddingShard::addMutation(Version version, MutationRef const& mutation) {
|
|||
if (phase == WaitPrevious) {
|
||||
// Updates can be discarded
|
||||
} else if (phase == Fetching) {
|
||||
// Save incoming mutations (See the comments of member variable `updates`).
|
||||
|
||||
// Create a new VerUpdateRef in updates queue if it is a new version.
|
||||
if (!updates.size() || version > updates.end()[-1].version) {
|
||||
VerUpdateRef v;
|
||||
v.version = version;
|
||||
|
|
@ -2930,6 +2985,7 @@ void AddingShard::addMutation(Version version, MutationRef const& mutation) {
|
|||
} else {
|
||||
ASSERT(version == updates.end()[-1].version);
|
||||
}
|
||||
// Add the mutation to the version.
|
||||
updates.back().mutations.push_back_deep(updates.back().arena(), mutation);
|
||||
} else if (phase == Waiting) {
|
||||
server->addMutation(version, mutation, keys, server->updateEagerReads);
|
||||
|
|
@ -3254,6 +3310,10 @@ private:
|
|||
(m.type == MutationRef::ClearRange && (matchesThisServer || (data->isTss() && matchesTssPair)))) {
|
||||
throw worker_removed();
|
||||
}
|
||||
if (!data->isTss() && m.type == MutationRef::ClearRange && data->ssPairID.present() &&
|
||||
serverTagKey == data->ssPairID.get()) {
|
||||
data->clearSSWithTssPair();
|
||||
}
|
||||
} else if (m.type == MutationRef::SetValue && m.param1 == rebootWhenDurablePrivateKey) {
|
||||
data->rebootAfterDurableVersion = currentVersion;
|
||||
TraceEvent("RebootWhenDurableSet", data->thisServerID)
|
||||
|
|
@ -3263,6 +3323,13 @@ private:
|
|||
data->primaryLocality = BinaryReader::fromStringRef<int8_t>(m.param2, Unversioned());
|
||||
auto& mLV = data->addVersionToMutationLog(data->data().getLatestVersion());
|
||||
data->addMutationToMutationLog(mLV, MutationRef(MutationRef::SetValue, persistPrimaryLocality, m.param2));
|
||||
} else if (m.type == MutationRef::SetValue && m.param1.substr(1).startsWith(tssMappingKeys.begin)) {
|
||||
if (!data->isTss()) {
|
||||
UID ssId = Codec<UID>::unpack(Tuple::unpack(m.param1.substr(1).removePrefix(tssMappingKeys.begin)));
|
||||
UID tssId = Codec<UID>::unpack(Tuple::unpack(m.param2));
|
||||
ASSERT(ssId == data->thisServerID);
|
||||
data->setSSWithTssPair(tssId);
|
||||
}
|
||||
} else {
|
||||
ASSERT(false); // Unknown private mutation
|
||||
}
|
||||
|
|
@ -3423,6 +3490,8 @@ ACTOR Future<Void> update(StorageServer* data, bool* pReceivedUpdate) {
|
|||
auto fk = data->readyFetchKeys.back();
|
||||
data->readyFetchKeys.pop_back();
|
||||
fk.send(&fii);
|
||||
// fetchKeys() would put the data it fetched into the fii. The thread will not return back to this actor
|
||||
// until it was completed.
|
||||
}
|
||||
|
||||
for (auto& c : fii.changes)
|
||||
|
|
@ -3461,6 +3530,8 @@ ACTOR Future<Void> update(StorageServer* data, bool* pReceivedUpdate) {
|
|||
for (; mutationNum < pUpdate->mutations.size(); mutationNum++) {
|
||||
updater.applyMutation(data, pUpdate->mutations[mutationNum], pUpdate->version);
|
||||
mutationBytes += pUpdate->mutations[mutationNum].totalSize();
|
||||
// data->counters.mutationBytes or data->counters.mutations should not be updated because they should
|
||||
// have counted when the mutations arrive from cursor initially.
|
||||
injectedChanges = true;
|
||||
if (mutationBytes > SERVER_KNOBS->DESIRED_UPDATE_BYTES) {
|
||||
mutationBytes = 0;
|
||||
|
|
@ -3588,8 +3659,9 @@ ACTOR Future<Void> update(StorageServer* data, bool* pReceivedUpdate) {
|
|||
data->sourceTLogID = curSourceTLogID;
|
||||
|
||||
TraceEvent("StorageServerSourceTLogID", data->thisServerID)
|
||||
.detail("SourceTLogID", data->sourceTLogID.present() ? data->sourceTLogID.get().toString() : "unknown")
|
||||
.trackLatest(data->thisServerID.toString() + "/StorageServerSourceTLogID");
|
||||
.detail("SourceTLogID",
|
||||
data->sourceTLogID.present() ? data->sourceTLogID.get().toString() : "unknown")
|
||||
.trackLatest(data->thisServerID.toString() + "/StorageServerSourceTLogID");
|
||||
}
|
||||
|
||||
data->noRecentUpdates.set(false);
|
||||
|
|
@ -4319,6 +4391,7 @@ ACTOR Future<Void> metricsCore(StorageServer* self, StorageServerInterface ssi)
|
|||
|
||||
wait(self->byteSampleRecovery);
|
||||
|
||||
// Logs all counters in `counters.cc` and reset the interval.
|
||||
self->actors.add(traceCounters("StorageMetrics",
|
||||
self->thisServerID,
|
||||
SERVER_KNOBS->STORAGE_LOGGING_DELAY,
|
||||
|
|
@ -4678,18 +4751,6 @@ ACTOR Future<Void> storageServerCore(StorageServer* self, StorageServerInterface
|
|||
}
|
||||
}
|
||||
}
|
||||
// SS monitors tss mapping here to see if it has a tss pair.
|
||||
// This information is only used for ss/tss pair metrics reporting so it's ok to be eventually
|
||||
// consistent.
|
||||
if (!self->isTss()) {
|
||||
ClientDBInfo clientInfo = self->db->get().client;
|
||||
Optional<StorageServerInterface> myTssPair = clientInfo.getTssPair(self->thisServerID);
|
||||
if (myTssPair.present()) {
|
||||
self->setSSWithTssPair(myTssPair.get().id());
|
||||
} else {
|
||||
self->clearSSWithTssPair();
|
||||
}
|
||||
}
|
||||
}
|
||||
when(GetShardStateRequest req = waitNext(ssi.getShardState.getFuture())) {
|
||||
if (req.mode == GetShardStateRequest::NO_WAIT) {
|
||||
|
|
@ -4831,6 +4892,7 @@ ACTOR Future<Void> storageServer(IKeyValueStore* persistentData,
|
|||
rep.addedVersion = self.version.get();
|
||||
recruitReply.send(rep);
|
||||
self.byteSampleRecovery = Void();
|
||||
|
||||
wait(storageServerCore(&self, ssi));
|
||||
|
||||
throw internal_error();
|
||||
|
|
@ -4964,9 +5026,7 @@ ACTOR Future<Void> replaceTSSInterface(StorageServer* self, StorageServerInterfa
|
|||
tr->set(serverListKeyFor(ssi.id()), serverListValue(ssi));
|
||||
|
||||
// add itself back to tss mapping
|
||||
// tr->set(tssMappingKeyFor(self->tssPairID.get()), tssMappingValueFor(ssi.id()));
|
||||
tssMapDB.set(tr, self->tssPairID.get(), ssi.id());
|
||||
tr->set(tssMappingChangeKey, deterministicRandom()->randomUniqueID().toString());
|
||||
|
||||
wait(tr->commit());
|
||||
self->tag = myTag;
|
||||
|
|
|
|||
|
|
@ -890,6 +890,7 @@ ACTOR Future<Void> checkConsistency(Database cx,
|
|||
StringRef performTSSCheck = LiteralStringRef("false");
|
||||
if (doQuiescentCheck) {
|
||||
performQuiescent = LiteralStringRef("true");
|
||||
spec.restorePerpetualWiggleSetting = false;
|
||||
}
|
||||
if (doCacheCheck) {
|
||||
performCacheCheck = LiteralStringRef("true");
|
||||
|
|
@ -1047,7 +1048,9 @@ std::map<std::string, std::function<void(const std::string&)>> testSpecGlobalKey
|
|||
{ "storageEngineExcludeTypes",
|
||||
[](const std::string& value) { TraceEvent("TestParserTest").detail("ParsedStorageEngineExcludeTypes", ""); } },
|
||||
{ "maxTLogVersion",
|
||||
[](const std::string& value) { TraceEvent("TestParserTest").detail("ParsedMaxTLogVersion", ""); } }
|
||||
[](const std::string& value) { TraceEvent("TestParserTest").detail("ParsedMaxTLogVersion", ""); } },
|
||||
{ "disableTss",
|
||||
[](const std::string& value) { TraceEvent("TestParserTest").detail("ParsedDisableTSS", ""); } }
|
||||
};
|
||||
|
||||
std::map<std::string, std::function<void(const std::string& value, TestSpec* spec)>> testSpecTestKeys = {
|
||||
|
|
@ -1383,6 +1386,8 @@ ACTOR Future<Void> runTests(Reference<AsyncVar<Optional<struct ClusterController
|
|||
state bool useDB = false;
|
||||
state bool waitForQuiescenceBegin = false;
|
||||
state bool waitForQuiescenceEnd = false;
|
||||
state bool restorePerpetualWiggleSetting = false;
|
||||
state bool perpetualWiggleEnabled = false;
|
||||
state double startDelay = 0.0;
|
||||
state double databasePingDelay = 1e9;
|
||||
state ISimulator::BackupAgentType simBackupAgents = ISimulator::BackupAgentType::NoBackupAgents;
|
||||
|
|
@ -1397,6 +1402,8 @@ ACTOR Future<Void> runTests(Reference<AsyncVar<Optional<struct ClusterController
|
|||
waitForQuiescenceBegin = true;
|
||||
if (iter->waitForQuiescenceEnd)
|
||||
waitForQuiescenceEnd = true;
|
||||
if (iter->restorePerpetualWiggleSetting)
|
||||
restorePerpetualWiggleSetting = true;
|
||||
startDelay = std::max(startDelay, iter->startDelay);
|
||||
databasePingDelay = std::min(databasePingDelay, iter->databasePingDelay);
|
||||
if (iter->simBackupAgents != ISimulator::BackupAgentType::NoBackupAgents)
|
||||
|
|
@ -1435,6 +1442,15 @@ ACTOR Future<Void> runTests(Reference<AsyncVar<Optional<struct ClusterController
|
|||
} catch (Error& e) {
|
||||
TraceEvent(SevError, "TestFailure").error(e).detail("Reason", "Unable to set starting configuration");
|
||||
}
|
||||
if (restorePerpetualWiggleSetting) {
|
||||
std::string_view confView(reinterpret_cast<const char*>(startingConfiguration.begin()),
|
||||
startingConfiguration.size());
|
||||
const std::string setting = "perpetual_storage_wiggle:=";
|
||||
auto pos = confView.find(setting);
|
||||
if (pos != confView.npos && confView.at(pos + setting.size()) == '1') {
|
||||
perpetualWiggleEnabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (useDB && waitForQuiescenceBegin) {
|
||||
|
|
@ -1450,6 +1466,10 @@ ACTOR Future<Void> runTests(Reference<AsyncVar<Optional<struct ClusterController
|
|||
TraceEvent("QuietDatabaseStartExternalError").error(e);
|
||||
throw;
|
||||
}
|
||||
|
||||
if (perpetualWiggleEnabled) { // restore the enabled perpetual storage wiggle setting
|
||||
wait(setPerpetualStorageWiggle(cx, true, true));
|
||||
}
|
||||
}
|
||||
|
||||
TraceEvent("TestsExpectedToPass").detail("Count", tests.size());
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
#include <boost/lexical_cast.hpp>
|
||||
|
||||
#include "fdbrpc/Locality.h"
|
||||
#include "fdbclient/GlobalConfig.actor.h"
|
||||
#include "fdbclient/StorageServerInterface.h"
|
||||
#include "fdbserver/Knobs.h"
|
||||
#include "flow/ActorCollection.h"
|
||||
|
|
@ -140,12 +141,14 @@ Database openDBOnServer(Reference<AsyncVar<ServerDBInfo>> const& db,
|
|||
bool enableLocalityLoadBalance,
|
||||
bool lockAware) {
|
||||
auto info = makeReference<AsyncVar<ClientDBInfo>>();
|
||||
return DatabaseContext::create(info,
|
||||
extractClientInfo(db, info),
|
||||
enableLocalityLoadBalance ? db->get().myLocality : LocalityData(),
|
||||
enableLocalityLoadBalance,
|
||||
taskID,
|
||||
lockAware);
|
||||
auto cx = DatabaseContext::create(info,
|
||||
extractClientInfo(db, info),
|
||||
enableLocalityLoadBalance ? db->get().myLocality : LocalityData(),
|
||||
enableLocalityLoadBalance,
|
||||
taskID,
|
||||
lockAware);
|
||||
GlobalConfig::create(cx, db, std::addressof(db->get().client));
|
||||
return cx;
|
||||
}
|
||||
|
||||
struct ErrorInfo {
|
||||
|
|
@ -1294,7 +1297,6 @@ ACTOR Future<Void> workerServer(Reference<ClusterConnectionFile> connFile,
|
|||
notUpdated = interf.updateServerDBInfo.getEndpoint();
|
||||
} else if (localInfo.infoGeneration > dbInfo->get().infoGeneration ||
|
||||
dbInfo->get().clusterInterface != ccInterface->get().get()) {
|
||||
|
||||
TraceEvent("GotServerDBInfoChange")
|
||||
.detail("ChangeID", localInfo.id)
|
||||
.detail("MasterID", localInfo.master.id())
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@
|
|||
#include "fdbserver/StorageMetrics.h"
|
||||
#include "fdbserver/DataDistribution.actor.h"
|
||||
#include "fdbserver/QuietDatabase.h"
|
||||
#include "fdbserver/TSSMappingUtil.actor.h"
|
||||
#include "flow/DeterministicRandom.h"
|
||||
#include "fdbclient/ManagementAPI.actor.h"
|
||||
#include "fdbclient/StorageServerInterface.h"
|
||||
|
|
@ -209,11 +210,16 @@ struct ConsistencyCheckWorkload : TestWorkload {
|
|||
if (self->firstClient || self->distributed) {
|
||||
try {
|
||||
state DatabaseConfiguration configuration;
|
||||
state std::map<UID, StorageServerInterface> tssMapping;
|
||||
|
||||
state Transaction tr(cx);
|
||||
tr.setOption(FDBTransactionOptions::LOCK_AWARE);
|
||||
loop {
|
||||
try {
|
||||
if (self->performTSSCheck) {
|
||||
tssMapping.clear();
|
||||
wait(readTSSMapping(&tr, &tssMapping));
|
||||
}
|
||||
RangeResult res = wait(tr.getRange(configKeys, 1000));
|
||||
if (res.size() == 1000) {
|
||||
TraceEvent("ConsistencyCheck_TooManyConfigOptions");
|
||||
|
|
@ -286,7 +292,7 @@ struct ConsistencyCheckWorkload : TestWorkload {
|
|||
throw;
|
||||
}
|
||||
|
||||
wait(::success(self->checkForStorage(cx, configuration, self)));
|
||||
wait(::success(self->checkForStorage(cx, configuration, tssMapping, self)));
|
||||
wait(::success(self->checkForExtraDataStores(cx, self)));
|
||||
|
||||
// Check that each machine is operating as its desired class
|
||||
|
|
@ -317,7 +323,7 @@ struct ConsistencyCheckWorkload : TestWorkload {
|
|||
state Standalone<VectorRef<KeyValueRef>> keyLocations = keyLocationPromise.getFuture().get();
|
||||
|
||||
// Check that each shard has the same data on all storage servers that it resides on
|
||||
wait(::success(self->checkDataConsistency(cx, keyLocations, configuration, self)));
|
||||
wait(::success(self->checkDataConsistency(cx, keyLocations, configuration, tssMapping, self)));
|
||||
|
||||
// Cache consistency check
|
||||
if (self->performCacheCheck)
|
||||
|
|
@ -1124,6 +1130,7 @@ struct ConsistencyCheckWorkload : TestWorkload {
|
|||
ACTOR Future<bool> checkDataConsistency(Database cx,
|
||||
VectorRef<KeyValueRef> keyLocations,
|
||||
DatabaseConfiguration configuration,
|
||||
std::map<UID, StorageServerInterface> tssMapping,
|
||||
ConsistencyCheckWorkload* self) {
|
||||
// Stores the total number of bytes on each storage server
|
||||
// In a distributed test, this will be an estimated size
|
||||
|
|
@ -1250,10 +1257,11 @@ struct ConsistencyCheckWorkload : TestWorkload {
|
|||
if (!isRelocating && self->performTSSCheck) {
|
||||
int initialSize = storageServers.size();
|
||||
for (int i = 0; i < initialSize; i++) {
|
||||
Optional<StorageServerInterface> tssPair = cx->clientInfo->get().getTssPair(storageServers[i]);
|
||||
if (tssPair.present()) {
|
||||
storageServers.push_back(tssPair.get().id());
|
||||
storageServerInterfaces.push_back(tssPair.get());
|
||||
auto tssPair = tssMapping.find(storageServers[i]);
|
||||
if (tssPair != tssMapping.end()) {
|
||||
TEST(true); // TSS checked in consistency check
|
||||
storageServers.push_back(tssPair->second.id());
|
||||
storageServerInterfaces.push_back(tssPair->second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1491,7 +1499,9 @@ struct ConsistencyCheckWorkload : TestWorkload {
|
|||
|
||||
// All shards should be available in quiscence
|
||||
if (self->performQuiescentChecks &&
|
||||
(g_network->isSimulated() || !storageServerInterfaces[j].isTss())) {
|
||||
((g_network->isSimulated() &&
|
||||
g_simulator.tssMode != ISimulator::TSSMode::EnabledAddDelay) ||
|
||||
!storageServerInterfaces[j].isTss())) {
|
||||
self->testFailure("Storage server unavailable");
|
||||
return false;
|
||||
}
|
||||
|
|
@ -1746,6 +1756,7 @@ struct ConsistencyCheckWorkload : TestWorkload {
|
|||
// Returns false if any worker that should have a storage server does not have one
|
||||
ACTOR Future<bool> checkForStorage(Database cx,
|
||||
DatabaseConfiguration configuration,
|
||||
std::map<UID, StorageServerInterface> tssMapping,
|
||||
ConsistencyCheckWorkload* self) {
|
||||
state vector<WorkerDetails> workers = wait(getWorkers(self->dbInfo));
|
||||
state vector<StorageServerInterface> storageServers = wait(getStorageServers(cx));
|
||||
|
|
@ -1766,6 +1777,7 @@ struct ConsistencyCheckWorkload : TestWorkload {
|
|||
if (!found) {
|
||||
TraceEvent("ConsistencyCheck_NoStorage")
|
||||
.detail("Address", addr)
|
||||
.detail("ProcessId", workers[i].interf.locality.processId())
|
||||
.detail("ProcessClassEqualToStorageClass",
|
||||
(int)(workers[i].processClass == ProcessClass::StorageClass));
|
||||
missingStorage.push_back(workers[i].interf.locality.dcId());
|
||||
|
|
@ -1786,8 +1798,7 @@ struct ConsistencyCheckWorkload : TestWorkload {
|
|||
(configuration.regions.size() == 2 && configuration.usableRegions > 1 && (missingDc0 || missingDc1))) {
|
||||
|
||||
// TODO could improve this check by also ensuring DD is currently recruiting a TSS by using quietdb?
|
||||
bool couldExpectMissingTss =
|
||||
(configuration.desiredTSSCount - self->dbInfo->get().client.tssMapping.size()) > 0;
|
||||
bool couldExpectMissingTss = (configuration.desiredTSSCount - tssMapping.size()) > 0;
|
||||
|
||||
int countMissing = missingStorage.size();
|
||||
int acceptableTssMissing = 1;
|
||||
|
|
|
|||
|
|
@ -625,7 +625,7 @@ struct SpecialKeySpaceCorrectnessWorkload : TestWorkload {
|
|||
|
||||
ACTOR Future<Void> managementApiCorrectnessActor(Database cx_, SpecialKeySpaceCorrectnessWorkload* self) {
|
||||
// All management api related tests
|
||||
Database cx = cx_->clone();
|
||||
state Database cx = cx_->clone();
|
||||
state Reference<ReadYourWritesTransaction> tx = makeReference<ReadYourWritesTransaction>(cx);
|
||||
// test ordered option keys
|
||||
{
|
||||
|
|
@ -1430,6 +1430,40 @@ struct SpecialKeySpaceCorrectnessWorkload : TestWorkload {
|
|||
}
|
||||
}
|
||||
}
|
||||
// make sure when we change dd related special keys, we grab the two system keys,
|
||||
// i.e. moveKeysLockOwnerKey and moveKeysLockWriteKey
|
||||
{
|
||||
state Reference<ReadYourWritesTransaction> tr1(new ReadYourWritesTransaction(cx));
|
||||
state Reference<ReadYourWritesTransaction> tr2(new ReadYourWritesTransaction(cx));
|
||||
loop {
|
||||
try {
|
||||
Version readVersion = wait(tr1->getReadVersion());
|
||||
tr2->setVersion(readVersion);
|
||||
tr1->setOption(FDBTransactionOptions::SPECIAL_KEY_SPACE_ENABLE_WRITES);
|
||||
tr2->setOption(FDBTransactionOptions::READ_SYSTEM_KEYS);
|
||||
KeyRef ddPrefix = SpecialKeySpace::getManagementApiCommandPrefix("datadistribution");
|
||||
tr1->set(LiteralStringRef("mode").withPrefix(ddPrefix), LiteralStringRef("1"));
|
||||
wait(tr1->commit());
|
||||
// randomly read the moveKeysLockOwnerKey/moveKeysLockWriteKey
|
||||
// both of them should be grabbed when changing dd mode
|
||||
wait(success(
|
||||
tr2->get(deterministicRandom()->coinflip() ? moveKeysLockOwnerKey : moveKeysLockWriteKey)));
|
||||
// tr2 shoulde never succeed, just write to a key to make it not a read-only transaction
|
||||
tr2->set(LiteralStringRef("unused_key"), LiteralStringRef(""));
|
||||
wait(tr2->commit());
|
||||
ASSERT(false); // commit should always fail due to conflict
|
||||
} catch (Error& e) {
|
||||
if (e.code() != error_code_not_committed) {
|
||||
// when buggify is enabled, it's possible we get other retriable errors
|
||||
wait(tr2->onError(e));
|
||||
tr1->reset();
|
||||
} else {
|
||||
// loop until we get conflict error
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Void();
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -734,7 +734,9 @@ ACTOR Future<Void> randomTransaction(Database cx, WriteDuringReadWorkload* self,
|
|||
state bool readAheadDisabled = deterministicRandom()->random01() < 0.5;
|
||||
state bool snapshotRYWDisabled = deterministicRandom()->random01() < 0.5;
|
||||
state bool useBatchPriority = deterministicRandom()->random01() < 0.5;
|
||||
state int64_t timebomb = deterministicRandom()->random01() < 0.01 ? deterministicRandom()->randomInt64(1, 6000) : 0;
|
||||
state int64_t timebomb = (FLOW_KNOBS->MAX_BUGGIFIED_DELAY == 0.0 && deterministicRandom()->random01() < 0.01)
|
||||
? deterministicRandom()->randomInt64(1, 6000)
|
||||
: 0; // timebomb check can fail incorrectly if simulation injects delay longer than the timebomb
|
||||
state std::vector<Future<Void>> operations;
|
||||
state ActorCollection commits(false);
|
||||
state std::vector<Future<Void>> watches;
|
||||
|
|
|
|||
|
|
@ -159,6 +159,7 @@ public:
|
|||
simConnectionFailuresDisableDuration = 0;
|
||||
simBackupAgents = ISimulator::BackupAgentType::NoBackupAgents;
|
||||
simDrAgents = ISimulator::BackupAgentType::NoBackupAgents;
|
||||
restorePerpetualWiggleSetting = true;
|
||||
}
|
||||
TestSpec(StringRef title,
|
||||
bool dump,
|
||||
|
|
@ -169,8 +170,8 @@ public:
|
|||
: title(title), dumpAfterTest(dump), clearAfterTest(clear), startDelay(startDelay), useDB(useDB), timeout(600),
|
||||
databasePingDelay(databasePingDelay), runConsistencyCheck(g_network->isSimulated()),
|
||||
runConsistencyCheckOnCache(false), runConsistencyCheckOnTSS(false), waitForQuiescenceBegin(true),
|
||||
waitForQuiescenceEnd(true), simCheckRelocationDuration(false), simConnectionFailuresDisableDuration(0),
|
||||
simBackupAgents(ISimulator::BackupAgentType::NoBackupAgents),
|
||||
waitForQuiescenceEnd(true), restorePerpetualWiggleSetting(true), simCheckRelocationDuration(false),
|
||||
simConnectionFailuresDisableDuration(0), simBackupAgents(ISimulator::BackupAgentType::NoBackupAgents),
|
||||
simDrAgents(ISimulator::BackupAgentType::NoBackupAgents) {
|
||||
phases = TestWorkload::SETUP | TestWorkload::EXECUTION | TestWorkload::CHECK | TestWorkload::METRICS;
|
||||
if (databasePingDelay < 0)
|
||||
|
|
@ -191,6 +192,11 @@ public:
|
|||
bool runConsistencyCheckOnTSS;
|
||||
bool waitForQuiescenceBegin;
|
||||
bool waitForQuiescenceEnd;
|
||||
bool restorePerpetualWiggleSetting; // whether set perpetual_storage_wiggle as the value after run
|
||||
// QuietDatabase. QuietDatabase always disables perpetual storage wiggle on
|
||||
// purpose. If waitForQuiescenceBegin == true and we want to keep perpetual
|
||||
// storage wiggle the same setting as before during testing, this value should
|
||||
// be set true.
|
||||
|
||||
bool simCheckRelocationDuration; // If set to true, then long duration relocations generate SevWarnAlways messages.
|
||||
// Once any workload sets this to true, it will be true for the duration of the
|
||||
|
|
|
|||
|
|
@ -556,7 +556,7 @@ private:
|
|||
LogEvent(EVENTLOG_INFORMATION_TYPE,
|
||||
format("Found new configuration for process (ID %d)", sp->id));
|
||||
stop_processes.push_back(sp);
|
||||
start_ids.push_back(std::make_pair(sp->id, cmd));
|
||||
start_ids.emplace_back(sp->id, cmd);
|
||||
} else if (cmd.quiet != sp->command.quiet || cmd.restartDelay != sp->command.restartDelay) {
|
||||
// Update restartDelay and quiet but do not restart running processes
|
||||
if (!cmd.quiet || !sp->command.quiet)
|
||||
|
|
@ -585,7 +585,7 @@ private:
|
|||
std::string section(it->pItem, dot - it->pItem);
|
||||
Command cmd = makeCommand(ini, section, id);
|
||||
if (cmd.valid) {
|
||||
start_ids.push_back(std::make_pair(id, cmd));
|
||||
start_ids.emplace_back(id, cmd);
|
||||
} else {
|
||||
LogEvent(
|
||||
EVENTLOG_ERROR_TYPE,
|
||||
|
|
|
|||
|
|
@ -241,10 +241,11 @@ void* ArenaBlock::make4kAlignedBuffer(uint32_t size) {
|
|||
r->aligned4kBuffer = allocateFast4kAligned(size);
|
||||
// printf("Arena::aligned4kBuffer alloc size=%u ptr=%p\n", size, r->aligned4kBuffer);
|
||||
r->nextBlockOffset = nextBlockOffset;
|
||||
auto result = r->aligned4kBuffer;
|
||||
makeNoAccess(r, sizeof(ArenaBlockRef));
|
||||
nextBlockOffset = bigUsed;
|
||||
bigUsed += sizeof(ArenaBlockRef);
|
||||
return r->aligned4kBuffer;
|
||||
return result;
|
||||
}
|
||||
|
||||
void ArenaBlock::dependOn(Reference<ArenaBlock>& self, ArenaBlock* other) {
|
||||
|
|
|
|||
19
flow/Arena.h
19
flow/Arena.h
|
|
@ -444,8 +444,18 @@ public:
|
|||
|
||||
StringRef substr(int start) const { return StringRef(data + start, length - start); }
|
||||
StringRef substr(int start, int size) const { return StringRef(data + start, size); }
|
||||
bool startsWith(const StringRef& s) const { return size() >= s.size() && !memcmp(begin(), s.begin(), s.size()); }
|
||||
bool startsWith(const StringRef& s) const {
|
||||
// Avoid UB - can't pass nullptr to memcmp
|
||||
if (s.size() == 0) {
|
||||
return true;
|
||||
}
|
||||
return size() >= s.size() && !memcmp(begin(), s.begin(), s.size());
|
||||
}
|
||||
bool endsWith(const StringRef& s) const {
|
||||
// Avoid UB - can't pass nullptr to memcmp
|
||||
if (s.size() == 0) {
|
||||
return true;
|
||||
}
|
||||
return size() >= s.size() && !memcmp(end() - s.size(), s.begin(), s.size());
|
||||
}
|
||||
|
||||
|
|
@ -782,6 +792,7 @@ struct VectorRefPreserializer {
|
|||
void invalidate() {}
|
||||
void add(const T& item) {}
|
||||
void remove(const T& item) {}
|
||||
void reset() {}
|
||||
};
|
||||
|
||||
template <class T>
|
||||
|
|
@ -813,6 +824,7 @@ struct VectorRefPreserializer<T, VecSerStrategy::String> {
|
|||
_cached_size -= _string_traits.getSize(item);
|
||||
}
|
||||
}
|
||||
void reset() { _cached_size = 0; }
|
||||
};
|
||||
|
||||
template <class T, VecSerStrategy SerStrategy = VecSerStrategy::FlatBuffers>
|
||||
|
|
@ -1031,6 +1043,11 @@ public:
|
|||
m_size = size;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
VPS::reset();
|
||||
m_size = 0;
|
||||
}
|
||||
|
||||
void reserve(Arena& p, int size) {
|
||||
if (size > m_capacity)
|
||||
reallocate(p, size);
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ void setFastAllocatorThreadInitFunction(ThreadInitFunction f) {
|
|||
std::atomic<int64_t> g_hugeArenaMemory(0);
|
||||
|
||||
double hugeArenaLastLogged = 0;
|
||||
std::map<std::string, std::pair<int, int>> hugeArenaTraces;
|
||||
std::map<std::string, std::pair<int, int64_t>> hugeArenaTraces;
|
||||
|
||||
void hugeArenaSample(int size) {
|
||||
if (TraceEvent::isNetworkThread()) {
|
||||
|
|
@ -564,7 +564,7 @@ void FastAllocator<Size>::releaseThreadMagazines() {
|
|||
if (thr.freelist || thr.alternate) {
|
||||
if (thr.freelist) {
|
||||
ASSERT(thr.count > 0 && thr.count <= magazine_size);
|
||||
globalData()->partial_magazines.push_back(std::make_pair(thr.count, thr.freelist));
|
||||
globalData()->partial_magazines.emplace_back(thr.count, thr.freelist);
|
||||
globalData()->partialMagazineUnallocatedMemory += thr.count * Size;
|
||||
}
|
||||
if (thr.alternate) {
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@
|
|||
// either we pull g_simulator into flow, or flow (and the I/O path) will be unable to log performance
|
||||
// metrics.
|
||||
#include <fdbrpc/simulator.h>
|
||||
#include <limits>
|
||||
|
||||
// pull in some global pointers too: These types are implemented in fdbrpc/sim2.actor.cpp, which is not available here.
|
||||
// Yuck. If you're not using the simulator, these will remain null, and all should be well.
|
||||
|
|
@ -117,7 +118,7 @@ void Histogram::writeToLog() {
|
|||
e.detail("Group", group).detail("Op", op).detail("Unit", UnitToStringMapper.at(unit));
|
||||
|
||||
for (uint32_t i = 0; i < 32; i++) {
|
||||
uint32_t value = ((uint32_t)1) << (i + 1);
|
||||
uint64_t value = uint64_t(1) << (i + 1);
|
||||
|
||||
if (buckets[i]) {
|
||||
switch (unit) {
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue