Files
scylladb/test/tools/cql_repl.cc
Avi Kivity 5099b1e272 Merge 'Propagate coordinator timeouts for regular writes and batches without throwing' from Piotr Dulikowski
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
2022-02-08 14:27:09 +02:00

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