Currently, most of the failures that occur during CQL reads or writes are reported using C++ exceptions. Although the seastar framework avoids most of the cost of unwinding by keeping exceptions in futures as `std::exception_ptr`s, the exceptions need to be inspected at various points for the purposes of accounting metrics or converting them to a CQL error response. Analyzing the value and type of an exception held by `std::exception_ptr`'s cannot be done without rethrowing the exception, and that can be very costly even if the exception is immediately caught. Because of that, exceptions are not a good fit for reporting failures which happen frequently during overload, especially if the CPU is the bottleneck.
This PR introduces facilities for reporting exceptions as values using the boost::outcome library. As a first step, the need to use exceptions for reporting timeouts was eliminated for regular and batch writes, and no exceptions are thrown between creation of a `mutation_write_timeout_exception` and its serialization as a CQL response in the `cql_server`.
The types and helpers introduced here can be reused in order to migrate more exceptions and exception paths in a similar fashion.
Results of `perf_simple_query --smp 1 --operations-per-shard 1000000`:
Master (00a9326ae7)
128789.53 tps ( 82.2 allocs/op, 12.2 tasks/op, 49245 insns/op)
This PR
127072.93 tps ( 82.2 allocs/op, 12.2 tasks/op, 49356 insns/op)
The new version seems to be slower by about 100 insns/op, fortunately not by much (about 0.2%).
Tests: unit(dev), unit(result_utils_test, debug)
Closes #10014
* github.com:scylladb/scylla:
cql_test_env: optimize handling result_message::exception
transport/server: handle exceptions from coordinator_result without throwing
transport/server: propagate coordinator_result to the error handling code
transport/server: unwrap the exception result_message in process_xyz_internal
query_processor: add exception-returning variants of execute_ methods
modification_statement: propagate failed result through result_message::exception
batch_statement: propagate failed result through result_message::exception
cql_statement: add `execute_without_checking_exception_message`
result_message: add result_message::exception
storage_proxy: change mutate_with_triggers to return future<result<>>
storage_proxy: add mutate_atomically_result
storage_proxy: return result<> from mutate_result
storage_proxy: return result<> from mutate_internal
storage_proxy: properly propagate future from mutate_begin to mutate_end
storage_proxy: handle exceptions as values in mutate_end
storage_proxy: let mutate_end take a future<result<>>
storage_proxy: resultify mutate_begin
storage_proxy: use result in the _ready future of write handlers
storage_proxy: introduce helpers for dealing with results
exceptions: add coordinator_exception_container and coordinator_result
utils: add result utils
utils: add exception_container
240 lines
8.3 KiB
C++
240 lines
8.3 KiB
C++
/*
|
|
* Copyright (C) 2019-present ScyllaDB
|
|
*/
|
|
|
|
/*
|
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
*/
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
// use boost::regex instead of std::regex due
|
|
// to stack overflow in debug mode
|
|
#include <boost/regex.hpp>
|
|
|
|
#include "test/lib/cql_test_env.hh"
|
|
#include "test/lib/cql_assertions.hh"
|
|
|
|
#include <seastar/core/future-util.hh>
|
|
#include <seastar/core/sleep.hh>
|
|
#include <seastar/core/app-template.hh>
|
|
#include <seastar/core/seastar.hh>
|
|
#include "transport/messages/result_message.hh"
|
|
#include "types/user.hh"
|
|
#include "types/map.hh"
|
|
#include "types/list.hh"
|
|
#include "types/set.hh"
|
|
#include "db/config.hh"
|
|
#include "db/paxos_grace_seconds_extension.hh"
|
|
#include "cql3/cql_config.hh"
|
|
#include "cql3/type_json.hh"
|
|
#include "cql3/column_identifier.hh"
|
|
#include "test/lib/exception_utils.hh"
|
|
#include "alternator/tags_extension.hh"
|
|
#include "cdc/cdc_extension.hh"
|
|
#include <json/json.h>
|
|
|
|
//
|
|
// A helper class to serialize result set output to a formatted JSON
|
|
//
|
|
class json_visitor final : public cql_transport::messages::result_message::visitor {
|
|
Json::Value& _root;
|
|
public:
|
|
json_visitor(Json::Value& root)
|
|
: _root(root)
|
|
{
|
|
}
|
|
|
|
virtual void visit(const cql_transport::messages::result_message::void_message&) override {
|
|
_root["status"] = "ok";
|
|
}
|
|
|
|
virtual void visit(const cql_transport::messages::result_message::set_keyspace& m) override {
|
|
_root["status"] = "ok";
|
|
}
|
|
|
|
virtual void visit(const cql_transport::messages::result_message::prepared::cql& m) override {
|
|
_root["status"] = "ok";
|
|
}
|
|
|
|
virtual void visit(const cql_transport::messages::result_message::prepared::thrift& m) override {
|
|
assert(false);
|
|
}
|
|
|
|
virtual void visit(const cql_transport::messages::result_message::schema_change& m) override {
|
|
_root["status"] = "ok";
|
|
}
|
|
|
|
virtual void visit(const cql_transport::messages::result_message::bounce_to_shard& m) override {
|
|
assert(false);
|
|
}
|
|
|
|
virtual void visit(const cql_transport::messages::result_message::exception& m) override {
|
|
m.throw_me();
|
|
}
|
|
|
|
virtual void visit(const cql_transport::messages::result_message::rows& m) override {
|
|
Json::Value& output_rows = _root["rows"];
|
|
const auto input_rows = m.rs().result_set().rows();
|
|
const auto& meta = m.rs().result_set().get_metadata().get_names();
|
|
for (auto&& in_row: input_rows) {
|
|
Json::Value out_row;
|
|
for (unsigned i = 0; i < meta.size(); ++i) {
|
|
const cql3::column_specification& col = *meta[i];
|
|
const bytes_opt& cell = in_row[i];
|
|
if (cell.has_value()) {
|
|
out_row[col.name->text()] = fmt::format("{}", to_json_string(*col.type, cell));
|
|
}
|
|
}
|
|
output_rows.append(out_row);
|
|
}
|
|
}
|
|
};
|
|
|
|
// Prepare query_options with serial consistency
|
|
std::unique_ptr<cql3::query_options> repl_options() {
|
|
const auto& so = cql3::query_options::specific_options::DEFAULT;
|
|
auto qo = std::make_unique<cql3::query_options>(
|
|
db::consistency_level::ONE,
|
|
std::vector<cql3::raw_value>{},
|
|
// Ensure (optional) serial consistency is always specified.
|
|
cql3::query_options::specific_options{
|
|
so.page_size,
|
|
so.state,
|
|
db::consistency_level::SERIAL,
|
|
so.timestamp,
|
|
}
|
|
);
|
|
return qo;
|
|
}
|
|
|
|
// Read-evaluate-print-loop for CQL
|
|
void repl(seastar::app_template& app) {
|
|
auto ext = std::make_shared<db::extensions>();
|
|
ext->add_schema_extension<alternator::tags_extension>(alternator::tags_extension::NAME);
|
|
ext->add_schema_extension<cdc::cdc_extension>(cdc::cdc_extension::NAME);
|
|
ext->add_schema_extension<db::paxos_grace_seconds_extension>(db::paxos_grace_seconds_extension::NAME);
|
|
auto db_cfg = ::make_shared<db::config>(std::move(ext));
|
|
db_cfg->enable_user_defined_functions({true}, db::config::config_source::CommandLine);
|
|
db_cfg->experimental_features(db::experimental_features_t::all(), db::config::config_source::CommandLine);
|
|
do_with_cql_env_thread([] (cql_test_env& e) {
|
|
|
|
// Comments allowed by CQL - -- and //
|
|
const boost::regex comment_re("^[[:space:]]*((--|//).*)?$");
|
|
// A comment is not a delimiter even if ends with one
|
|
const boost::regex delimiter_re("^(?![[:space:]]*(--|//)).*;[[:space:]]*$");
|
|
|
|
while (std::cin) {
|
|
std::string line;
|
|
std::ostringstream stmt;
|
|
if (!std::getline(std::cin, line)) {
|
|
break;
|
|
}
|
|
// Handle multiline input and comments
|
|
if (boost::regex_match(line.begin(), line.end(), comment_re)) {
|
|
std::cout << line << std::endl;
|
|
continue;
|
|
}
|
|
stmt << line << std::endl;
|
|
while (!boost::regex_match(line.begin(), line.end(), delimiter_re)) {
|
|
// Read the rest of input until delimiter or EOF
|
|
if (!std::getline(std::cin, line)) {
|
|
break;
|
|
}
|
|
stmt << line << std::endl;
|
|
}
|
|
// Print the statement
|
|
std::cout << stmt.str();
|
|
Json::Value json;
|
|
|
|
auto execute = [&json, &stmt, &e] () mutable {
|
|
return seastar::async([&] () mutable -> std::optional<unsigned> {
|
|
auto qo = repl_options();
|
|
auto msg = e.execute_cql(stmt.str(), std::move(qo)).get0();
|
|
if (msg->move_to_shard()) {
|
|
return *msg->move_to_shard();
|
|
}
|
|
json_visitor visitor(json);
|
|
msg->accept(visitor);
|
|
return std::nullopt;
|
|
});
|
|
};
|
|
|
|
try {
|
|
auto shard = execute().get0();
|
|
if (shard) {
|
|
smp::submit_to(*shard, std::move(execute)).get();
|
|
}
|
|
} catch (std::exception& e) {
|
|
json["status"] = "error";
|
|
json["message"] = fmt::format("{}", e);
|
|
}
|
|
std::cout << json << std::endl;
|
|
}
|
|
}, db_cfg).get0();
|
|
}
|
|
|
|
// Reset stdin/stdout/log streams to locations pointed
|
|
// on the command line.
|
|
void apply_configuration(const boost::program_options::variables_map& cfg) {
|
|
|
|
if (cfg.contains("input")) {
|
|
static std::ifstream input(cfg["input"].as<std::string>());
|
|
std::cin.rdbuf(input.rdbuf());
|
|
}
|
|
FILE *redirect_cerr = fopen(cfg["log"].as<std::string>().c_str(), "w");
|
|
if (redirect_cerr == NULL) {
|
|
throw std::system_error(errno, std::iostream_category(), "Failed to open the log file");
|
|
}
|
|
dup2(fileno(redirect_cerr), STDERR_FILENO);
|
|
fclose(redirect_cerr);
|
|
if (cfg.contains("output")) {
|
|
FILE *redirect_cout = fopen(cfg["output"].as<std::string>().c_str(), "w");
|
|
if (redirect_cout == NULL) {
|
|
throw std::system_error(errno, std::iostream_category(), "Failed to open the output file");
|
|
}
|
|
dup2(fileno(redirect_cout), STDOUT_FILENO);
|
|
fclose(redirect_cout);
|
|
}
|
|
}
|
|
|
|
int main(int argc, char* argv[]) {
|
|
|
|
namespace bpo = boost::program_options;
|
|
namespace fs = std::filesystem;
|
|
|
|
seastar::app_template::config cfg;
|
|
cfg.name = fmt::format(R"({} - An embedded single-node version of Scylla.
|
|
|
|
Runs read-evaluate-print loop, reading commands from stdin,
|
|
evaluating them and printing output, formatted as JSON, to stdout.
|
|
Creates a temporary database in /tmp and deletes it at exit.
|
|
Pre-configures a default keyspace, naturally, with replication
|
|
factor 1.
|
|
|
|
Used in unit tests as a test driver for .test.cql files.
|
|
|
|
Available )", argv[0]);
|
|
|
|
seastar::app_template app(cfg);
|
|
|
|
/* Define options for input, output and log file. */
|
|
app.add_options()
|
|
("input", bpo::value<std::string>(),
|
|
"Input file with CQL, defaults to stdin")
|
|
("output", bpo::value<std::string>(),
|
|
"Output file for data, defaults to stdout")
|
|
("log", bpo::value<std::string>()->default_value(
|
|
fmt::format("{}.log", fs::path(argv[0]).stem().string())),
|
|
"Output file for Scylla log");
|
|
|
|
return app.run(argc, argv, [&app] {
|
|
|
|
apply_configuration(app.configuration());
|
|
|
|
return seastar::async([&app] {
|
|
return repl(app);
|
|
});
|
|
});
|
|
}
|
|
|