The patch introduces two new features to aid with negotiating
protocol extensions for the CQL protocol:
- `cql_protocol_extensions` enum, which holds all supported
extensions for the CQL protocol (currently contains only
`LWT_ADD_METADATA_MARK` extension, which will be mentioned
below).
- An additional mechainsm of negotiating cql protocol extensions
to be used in a client connection between a scylla server
and a client driver.
These extensions are propagated in SUPPORTED message sent from the
server side with "SCYLLA_" prefix and received back as a response
from the client driver in order to determine intersection between
the cql extensions that are both supported by the server and
acknowledged by a client driver.
This intersection of features is later determined to be a working
set of cql protocol extensions in use for the current `client_state`,
which is associated with a particular client connection.
This way we can easily settle on the used extensions set on
both sides of the connection.
Currently there is only one value: `LWT_ADD_METADATA_MARK`, which
regulates whether to set a designated bit in prepared statement
metadata indicating if the statement at hand is an lwt statement
or not (actual implementation for the feature will be in a later
patch).
Each extension can also propagate some custom parameters to the
corresponding key. CQL protocol specification allows to send
a list of values with each key in the SUPPORTED message, we use
that to pass parameters to extensions as `PARAM=VALUE` strings.
In case of `LWT_ADD_METADATA_MARK` it's
`SCYLLA_LWT_OPTIMIZATION_META_BIT_MASK` which designates the
bitmask for LWT flag in prepared statement metadata in order to be
used for lookup in a client library. The associated bits of code in
`cql3::prepared_metadata` are adjusted to accomodate the feature.
Also extend the underlying type of `flag` enum in
`cql3::prepared_metadata` to be `uint32_t` instead of `uint8_t`
because in either case flags mask is serialized as 32-bit integer.
In theory, shard-awareness extension support also should be
reworked in terms of provided minimal infrastructure, but for the
sake of simplicity, this is left to be done in a follow-up some
time later.
This solution eliminates the need to assume that all the client
drivers follow the CQL spec carefully because scylla-specific
features and protocol extensions could be enabled only in case both
server and client driver negotiate the supported feature set.
Tests: unit(dev, debug)
Signed-off-by: Pavel Solodovnikov <
pa.solo...@scylladb.com>
---
configure.py | 1 +
cql3/result_set.hh | 10 ++++-
docs/protocol-extensions.md | 61 ++++++++++++++++++++++++--
service/client_state.hh | 17 ++++++++
transport/cql_protocol_extension.cc | 51 ++++++++++++++++++++++
transport/cql_protocol_extension.hh | 66 +++++++++++++++++++++++++++++
transport/server.cc | 22 +++++++++-
7 files changed, 223 insertions(+), 5 deletions(-)
create mode 100644 transport/cql_protocol_extension.cc
create mode 100644 transport/cql_protocol_extension.hh
diff --git a/configure.py b/configure.py
index 5a75d00bf..2106e8bf4 100755
--- a/configure.py
+++ b/configure.py
@@ -547,6 +547,7 @@ scylla_core = (['database.cc',
'sstables/prepended_input_stream.cc',
'sstables/m_format_read_helpers.cc',
'sstables/sstable_directory.cc',
+ 'transport/cql_protocol_extension.cc',
'transport/event.cc',
'transport/event_notifier.cc',
'transport/server.cc',
diff --git a/cql3/result_set.hh b/cql3/result_set.hh
index 2e11b1ac5..42b2a5ff5 100644
--- a/cql3/result_set.hh
+++ b/cql3/result_set.hh
@@ -128,14 +128,22 @@ ::shared_ptr<const cql3::metadata> make_empty_metadata();
class prepared_metadata {
public:
- enum class flag : uint8_t {
+ enum class flag : uint32_t {
GLOBAL_TABLES_SPEC = 0,
+ // Denotes whether the prepared statement at hand is an LWT statement.
+ //
+ // Use the last available bit in the flags since we don't want to clash
+ // with C* in case they add some other flag in one the next versions of binary protocol.
+ LWT = 31
};
using flag_enum = super_enum<flag,
flag::GLOBAL_TABLES_SPEC>;
using flag_enum_set = enum_set<flag_enum>;
+
+ static constexpr flag_enum_set::mask_type LWT_FLAG_MASK = flag_enum_set::mask_for<flag::LWT>();
+
private:
flag_enum_set _flags;
std::vector<lw_shared_ptr<column_specification>> _names;
diff --git a/docs/protocol-extensions.md b/docs/protocol-extensions.md
index 56572c5b7..49d6f77c4 100644
--- a/docs/protocol-extensions.md
+++ b/docs/protocol-extensions.md
@@ -8,12 +8,38 @@ continue to interoperate with Cassandra and other compatible servers
with no configuration needed; the driver can discover the extensions
and enable them conditionally.
-An extension can be discovered by using the OPTIONS request; the
-returned SUPPORTED response will have zero or more options beginning
-with SCYLLA indicating extensions defined in this documented, in
+An extension can be discovered by the client driver by using the OPTIONS
+request; the returned SUPPORTED response will have zero or more options
+beginning with `SCYLLA` indicating extensions defined in this document, in
addition to options documented by Cassandra. How to use the extension
is further explained in this document.
+# Extending protocol extensions support
+
+As mentioned above, in order to use a protocol extension feature by both
+server and client, they need to negotiate the used feature set when establishing
+a connection.
+
+The negotiation procedure has the following steps:
+ - Client sends the OPTIONS request to the Scylla instance to get a list of
+ protocol extensions that the server understands.
+ - Server sends the SUPPORTED message in reply to the OPTIONS request. The
+ message body is a string multimap, in which keys describe different
+ extensions and possibly one or more additional values specific to a
+ particular extension (specified as distinct values under a feature key in
+ the following form: `ARG_NAME=VALUE`).
+ - The client determines the set of compatible extensions which it is going
+ to use in the current connection by intersecting known capabilities list
+ with what it has received in SUPPORTED response.
+ - Client driver sends the STARTUP request with additional payload consisting
+ of key-value pairs, each describing a negotiated extension.
+ - Server determines the set of compatible extensions by intersecting known
+ list of protocol extensions with what it has received in STARTUP request.
+
+Both client and server use the same string identifiers for the keys to determine
+negotiated extension set, judging by the presence of a particular key in the
+SUPPORTED/STARTUP messages.
+
# Intranode sharding
This extension allows the driver to discover how Scylla internally
@@ -80,3 +106,32 @@ is for Java):
It is recommended that drivers open connections until they have at
least one connection per shard, then close excess connections.
+
+# LWT prepared statements metadata mark
+
+This extension allows the driver to discover whether LWT statements have a
+special bit set in prepared statement metadata flags, which indicates that
+the driver currently deals with an LWT statement.
+
+Having a designated flag gives the ability to reliably detect LWT statements
+and remove the need to execute custom parsing logic for each query, which is not
+only costly but also error-prone (e.g. parsing the prepared query with regular
+expressions).
+
+The feature is meant to be further utilized by client drivers to use primary
+replicas consistently when dealing with conditional statements.
+
+Choosing primary replicas in a predefined order ensures that in case of multiple
+LWT queries that contend on a single key, these queries will queue up at the
+replica rather than compete: choose the primary replica first, then, if the
+primary is known to be down, the first secondary, then the second secondary, and
+so on.
+This will reduce contention over hot keys and thus increase LWT performance.
+
+The feature is identified by the `SCYLLA_LWT_ADD_METADATA_MARK` key that is
+meant to be sent in the SUPPORTED message along with the following additional
+parameters:
+ - `SCYLLA_LWT_OPTIMIZATION_META_BIT_MASK` is a 32-bit integer that represents
+ the bit mask that should be used by the client to test against when checking
+ prepared statement metadata flags to see if the current query is conditional
+ or not.
diff --git a/service/client_state.hh b/service/client_state.hh
index 7a0f0236b..eab04b9c3 100644
--- a/service/client_state.hh
+++ b/service/client_state.hh
@@ -53,6 +53,9 @@
#include "tracing/tracing.hh"
#include "tracing/trace_state.hh"
+#include "enum_set.hh"
+#include "transport/cql_protocol_extension.hh"
+
namespace auth {
class resource;
}
@@ -354,6 +357,20 @@ class client_state {
}
}
#endif
+
+private:
+
+ cql_transport::cql_protocol_extension_enum_set _enabled_protocol_extensions;
+
+public:
+
+ bool is_protocol_extension_set(cql_transport::cql_protocol_extension ext) const {
+ return _enabled_protocol_extensions.contains(ext);
+ }
+
+ void set_protocol_extensions(cql_transport::cql_protocol_extension_enum_set exts) {
+ _enabled_protocol_extensions = std::move(exts);
+ }
};
}
diff --git a/transport/cql_protocol_extension.cc b/transport/cql_protocol_extension.cc
new file mode 100644
index 000000000..d1634b569
--- /dev/null
+++ b/transport/cql_protocol_extension.cc
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2020 ScyllaDB
+ */
+
+/*
+ * This file is part of Scylla.
+ *
+ * Scylla is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Scylla is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Scylla. If not, see <
http://www.gnu.org/licenses/>.
+ */
+
+#include <seastar/core/print.hh>
+#include "transport/cql_protocol_extension.hh"
+#include "cql3/result_set.hh"
+
+#include <map>
+
+namespace cql_transport {
+
+static const std::map<cql_protocol_extension, seastar::sstring> EXTENSION_NAMES = {
+ {cql_protocol_extension::LWT_ADD_METADATA_MARK, "SCYLLA_LWT_ADD_METADATA_MARK"}
+};
+
+cql_protocol_extension_enum_set supported_cql_protocol_extensions() {
+ return cql_protocol_extension_enum_set::full();
+}
+
+const seastar::sstring& protocol_extension_name(cql_protocol_extension ext) {
+ return EXTENSION_NAMES.at(ext);
+}
+
+std::vector<seastar::sstring> additional_options_for_proto_ext(cql_protocol_extension ext) {
+ switch (ext) {
+ case cql_protocol_extension::LWT_ADD_METADATA_MARK:
+ return {format("LWT_OPTIMIZATION_META_BIT_MASK={:d}", cql3::prepared_metadata::LWT_FLAG_MASK)};
+ default:
+ return {};
+ }
+}
+
+} // namespace cql_transport
diff --git a/transport/cql_protocol_extension.hh b/transport/cql_protocol_extension.hh
new file mode 100644
index 000000000..689b447a9
--- /dev/null
+++ b/transport/cql_protocol_extension.hh
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2020 ScyllaDB
+ */
+
+/*
+ * This file is part of Scylla.
+ *
+ * Scylla is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Scylla is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Scylla. If not, see <
http://www.gnu.org/licenses/>.
+ */
+#pragma once
+
+#include <seastar/core/sstring.hh>
+#include "enum_set.hh"
+
+#include <map>
+
+namespace cql_transport {
+
+/**
+ * CQL Protocol extensions. They can be viewed as an opportunity to provide
+ * some vendor-specific extensions to the CQL protocol without changing
+ * the version of the protocol itself (i.e. when the changes introduced by
+ * extensions are binary-compatible with the current version of the protocol).
+ *
+ * Extensions are meant to be passed between client and server in terms of
+ * SUPPORTED/STARTUP messages in order to negotiate compatible set of features
+ * to be used in a connection.
+ *
+ * The negotiation procedure and extensions themselves are documented in the
+ * `docs/protocol-extensions.md`.
+ */
+enum class cql_protocol_extension {
+ LWT_ADD_METADATA_MARK
+};
+
+using cql_protocol_extension_enum = super_enum<cql_protocol_extension,
+ cql_protocol_extension::LWT_ADD_METADATA_MARK>;
+
+using cql_protocol_extension_enum_set = enum_set<cql_protocol_extension_enum>;
+
+cql_protocol_extension_enum_set supported_cql_protocol_extensions();
+
+/**
+ * Returns the name of extension to be used in SUPPORTED/STARTUP feature negotiation.
+ */
+const seastar::sstring& protocol_extension_name(cql_protocol_extension ext);
+
+/**
+ * Returns a list of additional key-value pairs (in the form of "ARG=VALUE" string)
+ * that belong to a particular extension and provide some additional capabilities
+ * to be used by the client driver in order to support this extension.
+ */
+std::vector<seastar::sstring> additional_options_for_proto_ext(cql_protocol_extension ext);
+
+} // namespace cql_transport
diff --git a/transport/server.cc b/transport/server.cc
index 87c0f0796..9f2e9900a 100644
--- a/transport/server.cc
+++ b/transport/server.cc
@@ -65,6 +65,8 @@
#include "types/user.hh"
+#include "transport/cql_protocol_extension.hh"
+
namespace cql_transport {
static logging::logger clogger("cql_server");
@@ -728,6 +730,13 @@ future<std::unique_ptr<cql_server::response>> cql_server::connection::process_st
throw exceptions::protocol_exception(format("Unknown compression algorithm: {}", compression));
}
}
+ cql_protocol_extension_enum_set cql_proto_exts;
+ for (cql_protocol_extension ext : supported_cql_protocol_extensions()) {
+ if (options.find(protocol_extension_name(ext)) != options.cend()) {
+ cql_proto_exts.set(ext);
+ }
+ }
+ _client_state.set_protocol_extensions(std::move(cql_proto_exts));
auto& a = client_state.get_auth_service()->underlying_authenticator();
if (a.require_authentication()) {
return make_ready_future<std::unique_ptr<cql_server::response>>(make_autheticate(stream, a.qualified_java_name(), trace_state));
@@ -1213,8 +1222,19 @@ std::unique_ptr<cql_server::response> cql_server::connection::make_supported(int
opts.insert({"SCYLLA_SHARDING_IGNORE_MSB", format("{:d}", _server._config.sharding_ignore_msb)});
opts.insert({"SCYLLA_PARTITIONER", _server._config.partitioner_name});
}
+ for (cql_protocol_extension ext : supported_cql_protocol_extensions()) {
+ const sstring ext_key_name = protocol_extension_name(ext);
+ std::vector<sstring> params = additional_options_for_proto_ext(ext);
+ if (params.empty()) {
+ opts.emplace(ext_key_name, "");
+ } else {
+ for (sstring val : params) {
+ opts.emplace(ext_key_name, std::move(val));
+ }
+ }
+ }
auto response = std::make_unique<cql_server::response>(stream, cql_binary_opcode::SUPPORTED, tr_state);
- response->write_string_multimap(opts);
+ response->write_string_multimap(std::move(opts));
return response;
}
--
2.26.2