Merge remote-tracking branch 'origin/master' into config-db

This commit is contained in:
sfc-gh-tclinkenbeard 2021-06-10 22:31:23 -07:00
commit 41c790b299
111 changed files with 5027 additions and 2242 deletions

1
.gitignore vendored
View File

@ -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

View File

@ -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)

View File

@ -0,0 +1,7 @@
{
global:
workloadFactory;
local:
*;
};

View File

@ -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))

View File

@ -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;

View File

@ -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 */

View File

@ -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 */

View File

@ -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

View File

@ -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 };
}

View File

@ -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;
}

View File

@ -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)

View File

@ -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()

View File

@ -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()

View File

@ -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>`.

View File

@ -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``.

View File

@ -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,

View File

@ -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
---------------------

View File

@ -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++) {

View File

@ -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:

View File

@ -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);
}
};

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -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);
}
}
}

View File

@ -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>>());
}
}
}

View File

@ -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();
}

View File

@ -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

View File

@ -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.

View File

@ -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.

View File

@ -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
},

View File

@ -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 );

View File

@ -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

View File

@ -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 "

View File

@ -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;

View File

@ -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"));

View File

@ -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

View File

@ -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,

View File

@ -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();

View File

@ -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();

View File

@ -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;

View File

@ -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());

View File

@ -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 {

View File

@ -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();
}

View File

@ -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;
}

View File

@ -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);

View File

@ -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) {

View File

@ -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

View File

@ -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 {}

View File

@ -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);
}

View File

@ -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

View File

@ -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,

View File

@ -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

View File

@ -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,

View File

@ -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) {

View File

@ -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);
}

View File

@ -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;
}
};

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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

View File

@ -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);
}

View File

@ -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",

View File

@ -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());
}
}

View File

@ -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());

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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);

View File

@ -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));

View File

@ -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 {

View File

@ -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()

View File

@ -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;

View File

@ -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;
}

View File

@ -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;

View File

@ -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();
}

View File

@ -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

View File

@ -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

View File

@ -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++) {

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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());

View File

@ -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())

View File

@ -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;

View File

@ -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();
}
};

View File

@ -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;

View File

@ -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

View File

@ -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,

View File

@ -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) {

View File

@ -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);

View File

@ -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) {

View File

@ -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