Merge 'query: result_set: change row member to a chunked vector' from Benny Halevy

To prevent large memory allocations.

This series shows over 3% improvement in perf-simple-query throughput.
```
$ build/release/scylla perf-simple-query --default-log-level=error --smp=1 --random-seed=1855519715
random-seed=1855519715
enable-cache=1
Running test with config: {partitions=10000, concurrency=100, mode=read, query_single_key=no, counters=no}
Disabling auto compaction
Creating 10000 partitions...

Before:
random-seed=1775976514
enable-cache=1
enable-index-cache=1
sstable-summary-ratio=0.0005
sstable-format=me
Running test with config: {partitions=10000, concurrency=100, mode=read, query_single_key=no, counters=no}
Disabling auto compaction
Creating 10000 partitions...
336345.11 tps ( 58.1 allocs/op,   0.0 logallocs/op,  14.1 tasks/op,   32788 insns/op,   12430 cycles/op,        0 errors)
348748.14 tps ( 58.1 allocs/op,   0.0 logallocs/op,  14.1 tasks/op,   32794 insns/op,   12335 cycles/op,        0 errors)
349012.63 tps ( 58.1 allocs/op,   0.0 logallocs/op,  14.1 tasks/op,   32800 insns/op,   12326 cycles/op,        0 errors)
350629.97 tps ( 58.1 allocs/op,   0.0 logallocs/op,  14.1 tasks/op,   32770 insns/op,   12270 cycles/op,        0 errors)
348585.00 tps ( 58.1 allocs/op,   0.0 logallocs/op,  14.1 tasks/op,   32804 insns/op,   12338 cycles/op,        0 errors)
throughput:
        mean=   346664.17 standard-deviation=5825.77
        median= 348748.14 median-absolute-deviation=2348.46
        maximum=350629.97 minimum=336345.11
instructions_per_op:
        mean=   32791.35 standard-deviation=13.60
        median= 32794.47 median-absolute-deviation=8.65
        maximum=32804.45 minimum=32769.57
cpu_cycles_per_op:
        mean=   12340.05 standard-deviation=57.57
        median= 12335.05 median-absolute-deviation=13.94
        maximum=12430.42 minimum=12270.28

After:
random-seed=1775976514
enable-cache=1
enable-index-cache=1
sstable-summary-ratio=0.0005
sstable-format=me
Running test with config: {partitions=10000, concurrency=100, mode=read, query_single_key=no, counters=no}
Disabling auto compaction
Creating 10000 partitions...
353770.85 tps ( 58.1 allocs/op,   0.0 logallocs/op,  14.1 tasks/op,   32762 insns/op,   11893 cycles/op,        0 errors)
364447.98 tps ( 58.1 allocs/op,   0.0 logallocs/op,  14.1 tasks/op,   32738 insns/op,   11818 cycles/op,        0 errors)
365268.97 tps ( 58.1 allocs/op,   0.0 logallocs/op,  14.1 tasks/op,   32734 insns/op,   11788 cycles/op,        0 errors)
344304.87 tps ( 58.1 allocs/op,   0.0 logallocs/op,  14.1 tasks/op,   32746 insns/op,   12506 cycles/op,        0 errors)
362263.57 tps ( 58.1 allocs/op,   0.0 logallocs/op,  14.1 tasks/op,   32756 insns/op,   11888 cycles/op,        0 errors)
throughput:
        mean=   358011.25 standard-deviation=8916.76
        median= 362263.57 median-absolute-deviation=6436.74
        maximum=365268.97 minimum=344304.87
instructions_per_op:
        mean=   32747.06 standard-deviation=11.85
        median= 32745.80 median-absolute-deviation=9.36
        maximum=32762.18 minimum=32734.01
cpu_cycles_per_op:
        mean=   11978.65 standard-deviation=298.06
        median= 11887.96 median-absolute-deviation=160.96
        maximum=12505.72 minimum=11788.49
```

Refs #28511
(Refs rather than Fixes for the lack of a reproducer unit test)

* No backport needed as the issue is rare and not severe

Closes scylladb/scylladb#28631

* github.com:scylladb/scylladb:
  query: result_set: change row member to a chunked vector
  query: result_set_row: make noexcept
  query: non_null_data_value: assert is_nothrow_move_constructible and assignable
  types: data_value: assert is_nothrow_move_constructible and assignable
This commit is contained in:
Botond Dénes
2026-04-15 11:36:48 +03:00
6 changed files with 29 additions and 14 deletions

View File

@@ -335,7 +335,7 @@ static std::vector<bytes> get_primary_key(const std::vector<column_definition>&
// Build a map from primary keys to rows.
static std::map<std::vector<bytes>, const query::result_set_row*> build_row_map(const query::result_set& result) {
const std::vector<query::result_set_row>& rows = result.rows();
const auto& rows = result.rows();
auto primary_key = get_primary_key_definition(result.schema());
std::map<std::vector<bytes>, const query::result_set_row*> ret;
for (const auto& row: rows) {

View File

@@ -17,6 +17,13 @@
namespace query {
static_assert(std::is_nothrow_move_constructible_v<non_null_data_value>);
static_assert(std::is_nothrow_move_assignable_v<non_null_data_value>);
static_assert(std::is_nothrow_move_constructible_v<result_set_row>);
static_assert(std::is_nothrow_move_assignable_v<result_set_row>);
static_assert(std::is_nothrow_move_constructible_v<result_set>);
static_assert(std::is_nothrow_move_assignable_v<result_set>);
class deserialization_error : public std::runtime_error {
public:
using runtime_error::runtime_error;
@@ -28,7 +35,7 @@ public:
class result_set_builder {
schema_ptr _schema;
const partition_slice& _slice;
std::vector<result_set_row> _rows;
result_set::rows_type _rows;
std::unordered_map<sstring, non_null_data_value> _pkey_cells;
uint64_t _row_count;
public:
@@ -47,7 +54,7 @@ private:
};
std::ostream& operator<<(std::ostream& out, const result_set_row& row) {
for (auto&& cell : row._cells) {
for (auto&& cell : row.cells()) {
auto&& type = static_cast<const data_value&>(cell.second).type();
auto&& value = cell.second;
out << cell.first << "=\"" << type->to_string(type->decompose(value)) << "\" ";

View File

@@ -46,7 +46,7 @@ inline bool operator==(const non_null_data_value& x, const non_null_data_value&
// including regular column cells, partition keys, as well as static values.
class result_set_row {
schema_ptr _schema;
const std::unordered_map<sstring, non_null_data_value> _cells;
std::unordered_map<sstring, non_null_data_value> _cells;
public:
result_set_row(schema_ptr schema, std::unordered_map<sstring, non_null_data_value>&& cells)
: _schema{schema}
@@ -54,15 +54,16 @@ public:
{ }
result_set_row(result_set_row&&) = default;
result_set_row(const result_set_row&) = delete;
result_set_row& operator=(result_set_row&&) = default;
result_set_row& operator=(const result_set_row&) = delete;
result_set_row copy() const {
return {_schema, std::unordered_map{_cells}};
return {_schema, std::unordered_map{cells()}};
}
// Look up a deserialized row cell value by column name
const data_value*
get_data_value(const sstring& column_name) const {
auto it = _cells.find(column_name);
if (it == _cells.end()) {
auto it = cells().find(column_name);
if (it == cells().end()) {
return nullptr;
}
return &static_cast<const data_value&>(it->second);
@@ -103,11 +104,14 @@ public:
// deserialized format. To obtain a result set, use the result_set_builder
// class as a visitor to query_result::consume() function.
class result_set {
public:
using rows_type = utils::chunked_vector<result_set_row>;
private:
schema_ptr _schema;
std::vector<result_set_row> _rows;
rows_type _rows;
public:
static result_set from_raw_result(schema_ptr, const partition_slice&, const result&);
result_set(schema_ptr s, std::vector<result_set_row>&& rows)
result_set(schema_ptr s, rows_type&& rows)
: _schema(std::move(s)), _rows{std::move(rows)}
{ }
explicit result_set(const mutation&);
@@ -121,7 +125,7 @@ public:
}
return _rows[idx];
}
const std::vector<result_set_row>& rows() const {
const rows_type& rows() const {
return _rows;
}
const schema_ptr& schema() const {

View File

@@ -450,7 +450,7 @@ private:
schema_ptr _s;
const query::partition_slice& _slice;
uint64_t _page_size = 0;
std::vector<query::result_set_row> _rows;
query::result_set::rows_type _rows;
std::optional<dht::decorated_key> _last_pkey;
std::optional<clustering_key> _last_ckey;
uint64_t _last_pkey_rows = 0;
@@ -816,7 +816,7 @@ SEASTAR_THREAD_TEST_CASE(test_read_reversed) {
auto [data_results, _np2] = read_partitions_with_generic_paged_scan<data_result_builder>(db, s, page_size, std::numeric_limits<uint64_t>::max(), stateful,
query::full_partition_range, slice);
std::vector<query::result_set_row> expected_rows;
query::result_set::rows_type expected_rows;
for (const auto& mut : expected_results) {
auto rs = query::result_set(mut);
std::ranges::copy(rs.rows() | std::views::transform([](const auto& row) { return row.copy(); }), std::back_inserter(expected_rows));

View File

@@ -37,6 +37,7 @@
#include <boost/locale/encoding_utf.hpp>
#include <boost/multiprecision/cpp_int.hpp>
#include <seastar/net/inet_address.hh>
#include <type_traits>
#include <unordered_set>
#include "utils/big_decimal.hh"
#include "utils/date.h"
@@ -55,6 +56,9 @@
#include "types/set.hh"
#include "types/listlike_partial_deserializing_iterator.hh"
static_assert(std::is_nothrow_move_constructible_v<data_value>);
static_assert(std::is_nothrow_move_assignable_v<data_value>);
static logging::logger tlogger("types");
bytes_view_opt read_collection_value(bytes_view& in);
@@ -3839,7 +3843,7 @@ data_value::data_value(const data_value& v) : _value(nullptr), _type(v._type) {
}
data_value&
data_value::operator=(data_value&& x) {
data_value::operator=(data_value&& x) noexcept {
auto tmp = std::move(x);
std::swap(tmp._value, this->_value);
std::swap(tmp._type, this->_type);

View File

@@ -261,7 +261,7 @@ public:
data_value(bool_class<Tag>);
data_value& operator=(const data_value&);
data_value& operator=(data_value&&);
data_value& operator=(data_value&&) noexcept;
const data_type& type() const {
return _type;
}