Compare commits
111 Commits
scylla-3.0
...
next-3.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
afa2c1b0bf | ||
|
|
ad70fe8503 | ||
|
|
3cd9c78056 | ||
|
|
c5e5ed2775 | ||
|
|
666266c3cf | ||
|
|
19b5d70338 | ||
|
|
b3cdee7e27 | ||
|
|
4c42f18d82 | ||
|
|
ea8f8ab7a3 | ||
|
|
db6821ce8f | ||
|
|
3c91bad0dc | ||
|
|
bbe41a82be | ||
|
|
6fb42269e9 | ||
|
|
ee2255a189 | ||
|
|
3218e6cd4c | ||
|
|
1d94aac551 | ||
|
|
2e5110d063 | ||
|
|
e4bb7ce73c | ||
|
|
ecc54c1a68 | ||
|
|
71cfd108c6 | ||
|
|
d40a7a5e9e | ||
|
|
a163d245ec | ||
|
|
045831b706 | ||
|
|
148245ab6a | ||
|
|
bbe5de1403 | ||
|
|
ca0df416c0 | ||
|
|
37ed60374e | ||
|
|
7c991a276b | ||
|
|
72e039be85 | ||
|
|
a28ecc4714 | ||
|
|
584c555698 | ||
|
|
e772f11ee0 | ||
|
|
d79b6a7481 | ||
|
|
85168c500c | ||
|
|
5b9e2cd6e6 | ||
|
|
77f33ca106 | ||
|
|
93760f13ee | ||
|
|
e597ae1176 | ||
|
|
79c7015cce | ||
|
|
00a14000cd | ||
|
|
1c40a0fcd2 | ||
|
|
e10735852b | ||
|
|
42433a25a8 | ||
|
|
d04d3fa653 | ||
|
|
1bcc5a1b5c | ||
|
|
450b9ac9bf | ||
|
|
b3bfd8c08d | ||
|
|
53c10b72dc | ||
|
|
a690e20966 | ||
|
|
7172009a0d | ||
|
|
cb688ef62e | ||
|
|
ff8265dd66 | ||
|
|
a198db31dc | ||
|
|
094a2a4263 | ||
|
|
cc0b4d249b | ||
|
|
e10afc7f50 | ||
|
|
407dfe0d68 | ||
|
|
9370996a18 | ||
|
|
ac105dd2a7 | ||
|
|
1e62fc8aac | ||
|
|
c724eee649 | ||
|
|
ebb14d93c9 | ||
|
|
d77aaada86 | ||
|
|
acd05e089f | ||
|
|
f591c9c710 | ||
|
|
dea4489078 | ||
|
|
3172cc6bac | ||
|
|
840d466c4d | ||
|
|
e30c289835 | ||
|
|
f769828a68 | ||
|
|
7d743563bf | ||
|
|
23da53c4f3 | ||
|
|
d4df119735 | ||
|
|
bdcbf4aa4e | ||
|
|
e80cd9dfed | ||
|
|
87fd298a6e | ||
|
|
7dce5484c2 | ||
|
|
23df964b96 | ||
|
|
fcab0d1392 | ||
|
|
a0c4a8501e | ||
|
|
b6fa715f7b | ||
|
|
9b3ca26d7f | ||
|
|
7b8e570e6c | ||
|
|
a947f2cd84 | ||
|
|
5ce5f61b08 | ||
|
|
7b65ec866b | ||
|
|
4c16c1fe1b | ||
|
|
f2d2a9f5b8 | ||
|
|
cb3b687492 | ||
|
|
1bb84cdbcf | ||
|
|
b6307d54be | ||
|
|
a20000c1a2 | ||
|
|
b3cbc2e58a | ||
|
|
e4c1c4f052 | ||
|
|
bfe3b4cc59 | ||
|
|
6a4bc5bd71 | ||
|
|
6c818bcec0 | ||
|
|
1598d358f0 | ||
|
|
7252715c69 | ||
|
|
37e143cba5 | ||
|
|
bf68fae01b | ||
|
|
d566466fca | ||
|
|
e32e682911 | ||
|
|
3c46bbf244 | ||
|
|
5567cf4b1b | ||
|
|
733c04ad50 | ||
|
|
05913b6f58 | ||
|
|
79cf277ea2 | ||
|
|
03ada48b40 | ||
|
|
394afae3a8 | ||
|
|
69d0b1e15c |
@@ -1,6 +1,6 @@
|
||||
#!/bin/sh
|
||||
|
||||
VERSION=3.0.5
|
||||
VERSION=3.0.11
|
||||
|
||||
if test -f version
|
||||
then
|
||||
|
||||
@@ -184,7 +184,9 @@ future<> service::start() {
|
||||
return once_among_shards([this] {
|
||||
return create_keyspace_if_missing();
|
||||
}).then([this] {
|
||||
return when_all_succeed(_role_manager->start(), _authorizer->start(), _authenticator->start());
|
||||
return _role_manager->start().then([this] {
|
||||
return when_all_succeed(_authorizer->start(), _authenticator->start());
|
||||
});
|
||||
}).then([this] {
|
||||
_permissions_cache = std::make_unique<permissions_cache>(_permissions_cache_config, *this, log);
|
||||
}).then([this] {
|
||||
|
||||
@@ -61,6 +61,7 @@ class cache_flat_mutation_reader final : public flat_mutation_reader::impl {
|
||||
// - _last_row points at a direct predecessor of the next row which is going to be read.
|
||||
// Used for populating continuity.
|
||||
// - _population_range_starts_before_all_rows is set accordingly
|
||||
// - _underlying is engaged and fast-forwarded
|
||||
reading_from_underlying,
|
||||
|
||||
end_of_stream
|
||||
@@ -99,7 +100,13 @@ class cache_flat_mutation_reader final : public flat_mutation_reader::impl {
|
||||
// forward progress is not guaranteed in case iterators are getting constantly invalidated.
|
||||
bool _lower_bound_changed = false;
|
||||
|
||||
// Points to the underlying reader conforming to _schema,
|
||||
// either to *_underlying_holder or _read_context->underlying().underlying().
|
||||
flat_mutation_reader* _underlying = nullptr;
|
||||
std::optional<flat_mutation_reader> _underlying_holder;
|
||||
|
||||
future<> do_fill_buffer(db::timeout_clock::time_point);
|
||||
future<> ensure_underlying(db::timeout_clock::time_point);
|
||||
void copy_from_cache_to_buffer();
|
||||
future<> process_static_row(db::timeout_clock::time_point);
|
||||
void move_to_end();
|
||||
@@ -186,23 +193,22 @@ future<> cache_flat_mutation_reader::process_static_row(db::timeout_clock::time_
|
||||
return make_ready_future<>();
|
||||
} else {
|
||||
_read_context->cache().on_row_miss();
|
||||
return _read_context->get_next_fragment(timeout).then([this] (mutation_fragment_opt&& sr) {
|
||||
if (sr) {
|
||||
assert(sr->is_static_row());
|
||||
maybe_add_to_cache(sr->as_static_row());
|
||||
push_mutation_fragment(std::move(*sr));
|
||||
}
|
||||
maybe_set_static_row_continuous();
|
||||
return ensure_underlying(timeout).then([this, timeout] {
|
||||
return (*_underlying)(timeout).then([this] (mutation_fragment_opt&& sr) {
|
||||
if (sr) {
|
||||
assert(sr->is_static_row());
|
||||
maybe_add_to_cache(sr->as_static_row());
|
||||
push_mutation_fragment(std::move(*sr));
|
||||
}
|
||||
maybe_set_static_row_continuous();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
inline
|
||||
void cache_flat_mutation_reader::touch_partition() {
|
||||
if (_snp->at_latest_version()) {
|
||||
rows_entry& last_dummy = *_snp->version()->partition().clustered_rows().rbegin();
|
||||
_snp->tracker()->touch(last_dummy);
|
||||
}
|
||||
_snp->touch();
|
||||
}
|
||||
|
||||
inline
|
||||
@@ -232,14 +238,36 @@ future<> cache_flat_mutation_reader::fill_buffer(db::timeout_clock::time_point t
|
||||
});
|
||||
}
|
||||
|
||||
inline
|
||||
future<> cache_flat_mutation_reader::ensure_underlying(db::timeout_clock::time_point timeout) {
|
||||
if (_underlying) {
|
||||
return make_ready_future<>();
|
||||
}
|
||||
return _read_context->ensure_underlying(timeout).then([this, timeout] {
|
||||
flat_mutation_reader& ctx_underlying = _read_context->underlying().underlying();
|
||||
if (ctx_underlying.schema() != _schema) {
|
||||
_underlying_holder = make_delegating_reader(ctx_underlying);
|
||||
_underlying_holder->upgrade_schema(_schema);
|
||||
_underlying = &*_underlying_holder;
|
||||
} else {
|
||||
_underlying = &ctx_underlying;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
inline
|
||||
future<> cache_flat_mutation_reader::do_fill_buffer(db::timeout_clock::time_point timeout) {
|
||||
if (_state == state::move_to_underlying) {
|
||||
if (!_underlying) {
|
||||
return ensure_underlying(timeout).then([this, timeout] {
|
||||
return do_fill_buffer(timeout);
|
||||
});
|
||||
}
|
||||
_state = state::reading_from_underlying;
|
||||
_population_range_starts_before_all_rows = _lower_bound.is_before_all_clustered_rows(*_schema);
|
||||
auto end = _next_row_in_range ? position_in_partition(_next_row.position())
|
||||
: position_in_partition(_upper_bound);
|
||||
return _read_context->fast_forward_to(position_range{_lower_bound, std::move(end)}, timeout).then([this, timeout] {
|
||||
return _underlying->fast_forward_to(position_range{_lower_bound, std::move(end)}, timeout).then([this, timeout] {
|
||||
return read_from_underlying(timeout);
|
||||
});
|
||||
}
|
||||
@@ -280,7 +308,7 @@ future<> cache_flat_mutation_reader::do_fill_buffer(db::timeout_clock::time_poin
|
||||
|
||||
inline
|
||||
future<> cache_flat_mutation_reader::read_from_underlying(db::timeout_clock::time_point timeout) {
|
||||
return consume_mutation_fragments_until(_read_context->underlying().underlying(),
|
||||
return consume_mutation_fragments_until(*_underlying,
|
||||
[this] { return _state != state::reading_from_underlying || is_buffer_full(); },
|
||||
[this] (mutation_fragment mf) {
|
||||
_read_context->cache().on_row_miss();
|
||||
|
||||
@@ -571,6 +571,7 @@ scylla_core = (['database.cc',
|
||||
'db/consistency_level.cc',
|
||||
'db/system_keyspace.cc',
|
||||
'db/system_distributed_keyspace.cc',
|
||||
'db/size_estimates_virtual_reader.cc',
|
||||
'db/schema_tables.cc',
|
||||
'db/cql_type_parser.cc',
|
||||
'db/legacy_schema_migrator.cc',
|
||||
|
||||
@@ -130,6 +130,18 @@ query_options::query_options(std::unique_ptr<query_options> qo, ::shared_ptr<ser
|
||||
|
||||
}
|
||||
|
||||
query_options::query_options(std::unique_ptr<query_options> qo, ::shared_ptr<service::pager::paging_state> paging_state, int32_t page_size)
|
||||
: query_options(qo->_consistency,
|
||||
qo->get_timeout_config(),
|
||||
std::move(qo->_names),
|
||||
std::move(qo->_values),
|
||||
std::move(qo->_value_views),
|
||||
qo->_skip_metadata,
|
||||
std::move(query_options::specific_options{page_size, paging_state, qo->_options.serial_consistency, qo->_options.timestamp}),
|
||||
qo->_cql_serialization_format) {
|
||||
|
||||
}
|
||||
|
||||
query_options::query_options(std::vector<cql3::raw_value> values)
|
||||
: query_options(
|
||||
db::consistency_level::ONE, infinite_timeout_config, std::move(values))
|
||||
|
||||
@@ -102,7 +102,7 @@ private:
|
||||
|
||||
public:
|
||||
query_options(query_options&&) = default;
|
||||
query_options(const query_options&) = delete;
|
||||
explicit query_options(const query_options&) = default;
|
||||
|
||||
explicit query_options(db::consistency_level consistency,
|
||||
const timeout_config& timeouts,
|
||||
@@ -155,6 +155,7 @@ public:
|
||||
explicit query_options(db::consistency_level, const timeout_config& timeouts,
|
||||
std::vector<cql3::raw_value> values, specific_options options = specific_options::DEFAULT);
|
||||
explicit query_options(std::unique_ptr<query_options>, ::shared_ptr<service::pager::paging_state> paging_state);
|
||||
explicit query_options(std::unique_ptr<query_options>, ::shared_ptr<service::pager::paging_state> paging_state, int32_t page_size);
|
||||
|
||||
const timeout_config& get_timeout_config() const { return _timeout_config; }
|
||||
|
||||
|
||||
@@ -214,11 +214,9 @@ statement_restrictions::statement_restrictions(database& db,
|
||||
}
|
||||
auto& cf = db.find_column_family(schema);
|
||||
auto& sim = cf.get_index_manager();
|
||||
bool has_queriable_clustering_column_index = _clustering_columns_restrictions->has_supporting_index(sim);
|
||||
bool has_queriable_pk_index = _partition_key_restrictions->has_supporting_index(sim);
|
||||
bool has_queriable_index = has_queriable_clustering_column_index
|
||||
|| has_queriable_pk_index
|
||||
|| _nonprimary_key_restrictions->has_supporting_index(sim);
|
||||
const bool has_queriable_clustering_column_index = _clustering_columns_restrictions->has_supporting_index(sim);
|
||||
const bool has_queriable_pk_index = _partition_key_restrictions->has_supporting_index(sim);
|
||||
const bool has_queriable_regular_index = _nonprimary_key_restrictions->has_supporting_index(sim);
|
||||
|
||||
// At this point, the select statement if fully constructed, but we still have a few things to validate
|
||||
process_partition_key_restrictions(has_queriable_pk_index, for_view, allow_filtering);
|
||||
@@ -279,7 +277,7 @@ statement_restrictions::statement_restrictions(database& db,
|
||||
}
|
||||
|
||||
if (!_nonprimary_key_restrictions->empty()) {
|
||||
if (has_queriable_index) {
|
||||
if (has_queriable_regular_index) {
|
||||
_uses_secondary_indexing = true;
|
||||
} else if (!allow_filtering) {
|
||||
throw exceptions::invalid_request_exception("Cannot execute this query as it might involve data filtering and "
|
||||
@@ -365,8 +363,9 @@ std::vector<const column_definition*> statement_restrictions::get_column_defs_fo
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_clustering_columns_restrictions->needs_filtering(*_schema)) {
|
||||
column_id first_filtering_id = _schema->clustering_key_columns().begin()->id +
|
||||
const bool pk_has_unrestricted_components = _partition_key_restrictions->has_unrestricted_components(*_schema);
|
||||
if (pk_has_unrestricted_components || _clustering_columns_restrictions->needs_filtering(*_schema)) {
|
||||
column_id first_filtering_id = pk_has_unrestricted_components ? 0 : _schema->clustering_key_columns().begin()->id +
|
||||
_clustering_columns_restrictions->num_prefix_columns_that_need_not_be_filtered();
|
||||
for (auto&& cdef : _clustering_columns_restrictions->get_column_defs()) {
|
||||
if (cdef->id >= first_filtering_id && !column_uses_indexing(cdef)) {
|
||||
@@ -481,10 +480,9 @@ bool statement_restrictions::need_filtering() const {
|
||||
int number_of_filtering_restrictions = _nonprimary_key_restrictions->size();
|
||||
// If the whole partition key is restricted, it does not imply filtering
|
||||
if (_partition_key_restrictions->has_unrestricted_components(*_schema) || !_partition_key_restrictions->is_all_eq()) {
|
||||
number_of_filtering_restrictions += _partition_key_restrictions->size();
|
||||
if (_clustering_columns_restrictions->has_unrestricted_components(*_schema)) {
|
||||
number_of_filtering_restrictions += _clustering_columns_restrictions->size() - _clustering_columns_restrictions->prefix_size();
|
||||
}
|
||||
number_of_filtering_restrictions += _partition_key_restrictions->size() + _clustering_columns_restrictions->size();
|
||||
} else if (_clustering_columns_restrictions->has_unrestricted_components(*_schema)) {
|
||||
number_of_filtering_restrictions += _clustering_columns_restrictions->size() - _clustering_columns_restrictions->prefix_size();
|
||||
}
|
||||
|
||||
if (_partition_key_restrictions->is_multi_column() || _clustering_columns_restrictions->is_multi_column()) {
|
||||
|
||||
@@ -395,6 +395,14 @@ public:
|
||||
return !_nonprimary_key_restrictions->empty();
|
||||
}
|
||||
|
||||
bool pk_restrictions_need_filtering() const {
|
||||
return _partition_key_restrictions->needs_filtering(*_schema);
|
||||
}
|
||||
|
||||
bool ck_restrictions_need_filtering() const {
|
||||
return _partition_key_restrictions->has_unrestricted_components(*_schema) || _clustering_columns_restrictions->needs_filtering(*_schema);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if column is restricted by some restriction, false otherwise
|
||||
*/
|
||||
|
||||
@@ -83,6 +83,9 @@ void metadata::maybe_set_paging_state(::shared_ptr<const service::pager::paging_
|
||||
assert(paging_state);
|
||||
if (paging_state->get_remaining() > 0) {
|
||||
set_paging_state(std::move(paging_state));
|
||||
} else {
|
||||
_flags.remove<flag::HAS_MORE_PAGES>();
|
||||
_paging_state = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -142,7 +142,7 @@ shared_ptr<selector::factory>
|
||||
selectable::with_field_selection::new_selector_factory(database& db, schema_ptr s, std::vector<const column_definition*>& defs) {
|
||||
auto&& factory = _selected->new_selector_factory(db, s, defs);
|
||||
auto&& type = factory->new_instance()->get_type();
|
||||
auto&& ut = dynamic_pointer_cast<const user_type_impl>(std::move(type));
|
||||
auto&& ut = dynamic_pointer_cast<const user_type_impl>(type->underlying_type());
|
||||
if (!ut) {
|
||||
throw exceptions::invalid_request_exception(
|
||||
sprint("Invalid field selection: %s of type %s is not a user type",
|
||||
|
||||
@@ -165,7 +165,7 @@ alter_type_statement::add_or_alter::add_or_alter(const ut_name& name, bool is_ad
|
||||
user_type alter_type_statement::add_or_alter::do_add(database& db, user_type to_update) const
|
||||
{
|
||||
if (get_idx_of_field(to_update, _field_name)) {
|
||||
throw exceptions::invalid_request_exception(sprint("Cannot add new field %s to type %s: a field of the same name already exists", _field_name->name(), _name.to_string()));
|
||||
throw exceptions::invalid_request_exception(sprint("Cannot add new field %s to type %s: a field of the same name already exists", _field_name->to_string(), _name.to_string()));
|
||||
}
|
||||
|
||||
std::vector<bytes> new_names(to_update->field_names());
|
||||
@@ -173,7 +173,7 @@ user_type alter_type_statement::add_or_alter::do_add(database& db, user_type to_
|
||||
std::vector<data_type> new_types(to_update->field_types());
|
||||
auto&& add_type = _field_type->prepare(db, keyspace())->get_type();
|
||||
if (add_type->references_user_type(to_update->_keyspace, to_update->_name)) {
|
||||
throw exceptions::invalid_request_exception(sprint("Cannot add new field %s of type %s to type %s as this would create a circular reference", _field_name->name(), _field_type->to_string(), _name.to_string()));
|
||||
throw exceptions::invalid_request_exception(sprint("Cannot add new field %s of type %s to type %s as this would create a circular reference", _field_name->to_string(), _field_type->to_string(), _name.to_string()));
|
||||
}
|
||||
new_types.push_back(std::move(add_type));
|
||||
return user_type_impl::get_instance(to_update->_keyspace, to_update->_name, std::move(new_names), std::move(new_types));
|
||||
@@ -183,13 +183,13 @@ user_type alter_type_statement::add_or_alter::do_alter(database& db, user_type t
|
||||
{
|
||||
stdx::optional<uint32_t> idx = get_idx_of_field(to_update, _field_name);
|
||||
if (!idx) {
|
||||
throw exceptions::invalid_request_exception(sprint("Unknown field %s in type %s", _field_name->name(), _name.to_string()));
|
||||
throw exceptions::invalid_request_exception(sprint("Unknown field %s in type %s", _field_name->to_string(), _name.to_string()));
|
||||
}
|
||||
|
||||
auto previous = to_update->field_types()[*idx];
|
||||
auto new_type = _field_type->prepare(db, keyspace())->get_type();
|
||||
if (!new_type->is_compatible_with(*previous)) {
|
||||
throw exceptions::invalid_request_exception(sprint("Type %s in incompatible with previous type %s of field %s in user type %s", _field_type->to_string(), previous->as_cql3_type()->to_string(), _field_name->name(), _name.to_string()));
|
||||
throw exceptions::invalid_request_exception(sprint("Type %s in incompatible with previous type %s of field %s in user type %s", _field_type->to_string(), previous->as_cql3_type()->to_string(), _field_name->to_string(), _name.to_string()));
|
||||
}
|
||||
|
||||
std::vector<data_type> new_types(to_update->field_types());
|
||||
|
||||
@@ -522,8 +522,8 @@ indexed_table_select_statement::prepare_command_for_base_query(const query_optio
|
||||
return cmd;
|
||||
}
|
||||
|
||||
future<shared_ptr<cql_transport::messages::result_message>>
|
||||
indexed_table_select_statement::execute_base_query(
|
||||
future<foreign_ptr<lw_shared_ptr<query::result>>, lw_shared_ptr<query::read_command>>
|
||||
indexed_table_select_statement::do_execute_base_query(
|
||||
service::storage_proxy& proxy,
|
||||
dht::partition_range_vector&& partition_ranges,
|
||||
service::query_state& state,
|
||||
@@ -582,22 +582,27 @@ indexed_table_select_statement::execute_base_query(
|
||||
}).then([&merger]() {
|
||||
return merger.get();
|
||||
});
|
||||
}).then([this, &proxy, &state, &options, now, cmd, paging_state = std::move(paging_state)] (foreign_ptr<lw_shared_ptr<query::result>> result) mutable {
|
||||
return this->process_base_query_results(std::move(result), cmd, proxy, state, options, now, std::move(paging_state));
|
||||
}).then([cmd] (foreign_ptr<lw_shared_ptr<query::result>> result) mutable {
|
||||
return make_ready_future<foreign_ptr<lw_shared_ptr<query::result>>, lw_shared_ptr<query::read_command>>(std::move(result), std::move(cmd));
|
||||
});
|
||||
}
|
||||
|
||||
// Function for fetching the selected columns from a list of clustering rows.
|
||||
// It is currently used only in our Secondary Index implementation - ordinary
|
||||
// CQL SELECT statements do not have the syntax to request a list of rows.
|
||||
// FIXME: The current implementation is very inefficient - it requests each
|
||||
// row separately (and, incrementally, in parallel). Even multiple rows from a single
|
||||
// partition are requested separately. This last case can be easily improved,
|
||||
// but to implement the general case (multiple rows from multiple partitions)
|
||||
// efficiently, we will need more support from other layers.
|
||||
// Keys are ordered in token order (see #3423)
|
||||
future<shared_ptr<cql_transport::messages::result_message>>
|
||||
indexed_table_select_statement::execute_base_query(
|
||||
service::storage_proxy& proxy,
|
||||
dht::partition_range_vector&& partition_ranges,
|
||||
service::query_state& state,
|
||||
const query_options& options,
|
||||
gc_clock::time_point now,
|
||||
::shared_ptr<const service::pager::paging_state> paging_state) {
|
||||
return do_execute_base_query(proxy, std::move(partition_ranges), state, options, now, paging_state).then(
|
||||
[this, &proxy, &state, &options, now, paging_state = std::move(paging_state)] (foreign_ptr<lw_shared_ptr<query::result>> result, lw_shared_ptr<query::read_command> cmd) {
|
||||
return process_base_query_results(std::move(result), std::move(cmd), proxy, state, options, now, std::move(paging_state));
|
||||
});
|
||||
}
|
||||
|
||||
future<foreign_ptr<lw_shared_ptr<query::result>>, lw_shared_ptr<query::read_command>>
|
||||
indexed_table_select_statement::do_execute_base_query(
|
||||
service::storage_proxy& proxy,
|
||||
std::vector<primary_key>&& primary_keys,
|
||||
service::query_state& state,
|
||||
@@ -652,9 +657,23 @@ indexed_table_select_statement::execute_base_query(
|
||||
});
|
||||
}).then([&merger] () {
|
||||
return merger.get();
|
||||
}).then([cmd] (foreign_ptr<lw_shared_ptr<query::result>> result) mutable {
|
||||
return make_ready_future<foreign_ptr<lw_shared_ptr<query::result>>, lw_shared_ptr<query::read_command>>(std::move(result), std::move(cmd));
|
||||
});
|
||||
}).then([this, &proxy, &state, &options, now, cmd, paging_state = std::move(paging_state)] (foreign_ptr<lw_shared_ptr<query::result>> result) mutable {
|
||||
return this->process_base_query_results(std::move(result), cmd, proxy, state, options, now, std::move(paging_state));
|
||||
});
|
||||
}
|
||||
|
||||
future<shared_ptr<cql_transport::messages::result_message>>
|
||||
indexed_table_select_statement::execute_base_query(
|
||||
service::storage_proxy& proxy,
|
||||
std::vector<primary_key>&& primary_keys,
|
||||
service::query_state& state,
|
||||
const query_options& options,
|
||||
gc_clock::time_point now,
|
||||
::shared_ptr<const service::pager::paging_state> paging_state) {
|
||||
return do_execute_base_query(proxy, std::move(primary_keys), state, options, now, paging_state).then(
|
||||
[this, &proxy, &state, &options, now, paging_state = std::move(paging_state)] (foreign_ptr<lw_shared_ptr<query::result>> result, lw_shared_ptr<query::read_command> cmd) {
|
||||
return process_base_query_results(std::move(result), std::move(cmd), proxy, state, options, now, std::move(paging_state));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -929,6 +948,60 @@ indexed_table_select_statement::do_execute(service::storage_proxy& proxy,
|
||||
}
|
||||
}
|
||||
|
||||
// Aggregated and paged filtering needs to aggregate the results from all pages
|
||||
// in order to avoid returning partial per-page results (issue #4540).
|
||||
// It's a little bit more complicated than regular aggregation, because each paging state
|
||||
// needs to be translated between the base table and the underlying view.
|
||||
// The routine below keeps fetching pages from the underlying view, which are then
|
||||
// used to fetch base rows, which go straight to the result set builder.
|
||||
// A local, internal copy of query_options is kept in order to keep updating
|
||||
// the paging state between requesting data from replicas.
|
||||
const bool aggregate = _selection->is_aggregate();
|
||||
if (aggregate) {
|
||||
const bool restrictions_need_filtering = _restrictions->need_filtering();
|
||||
return do_with(cql3::selection::result_set_builder(*_selection, now, options.get_cql_serialization_format()), std::make_unique<cql3::query_options>(cql3::query_options(options)),
|
||||
[this, &options, &proxy, &state, now, whole_partitions, partition_slices, restrictions_need_filtering] (cql3::selection::result_set_builder& builder, std::unique_ptr<cql3::query_options>& internal_options) {
|
||||
// page size is set to the internal count page size, regardless of the user-provided value
|
||||
internal_options.reset(new cql3::query_options(std::move(internal_options), options.get_paging_state(), DEFAULT_COUNT_PAGE_SIZE));
|
||||
return repeat([this, &builder, &options, &internal_options, &proxy, &state, now, whole_partitions, partition_slices, restrictions_need_filtering] () {
|
||||
auto consume_results = [this, &builder, &options, &internal_options, restrictions_need_filtering] (foreign_ptr<lw_shared_ptr<query::result>> results, lw_shared_ptr<query::read_command> cmd) {
|
||||
if (restrictions_need_filtering) {
|
||||
query::result_view::consume(*results, cmd->slice, cql3::selection::result_set_builder::visitor(builder, *_schema, *_selection,
|
||||
cql3::selection::result_set_builder::restrictions_filter(_restrictions, options, cmd->row_limit)));
|
||||
} else {
|
||||
query::result_view::consume(*results, cmd->slice, cql3::selection::result_set_builder::visitor(builder, *_schema, *_selection));
|
||||
}
|
||||
};
|
||||
|
||||
if (whole_partitions || partition_slices) {
|
||||
return find_index_partition_ranges(proxy, state, *internal_options).then(
|
||||
[this, now, &state, &internal_options, &proxy, consume_results = std::move(consume_results)] (dht::partition_range_vector partition_ranges, ::shared_ptr<const service::pager::paging_state> paging_state) {
|
||||
bool has_more_pages = paging_state && paging_state->get_remaining() > 0;
|
||||
internal_options.reset(new cql3::query_options(std::move(internal_options), paging_state ? ::make_shared<service::pager::paging_state>(*paging_state) : nullptr));
|
||||
return do_execute_base_query(proxy, std::move(partition_ranges), state, *internal_options, now, std::move(paging_state)).then(consume_results).then([has_more_pages] {
|
||||
return stop_iteration(!has_more_pages);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
return find_index_clustering_rows(proxy, state, *internal_options).then(
|
||||
[this, now, &state, &internal_options, &proxy, consume_results = std::move(consume_results)] (std::vector<primary_key> primary_keys, ::shared_ptr<const service::pager::paging_state> paging_state) {
|
||||
bool has_more_pages = paging_state && paging_state->get_remaining() > 0;
|
||||
internal_options.reset(new cql3::query_options(std::move(internal_options), paging_state ? ::make_shared<service::pager::paging_state>(*paging_state) : nullptr));
|
||||
return this->do_execute_base_query(proxy, std::move(primary_keys), state, *internal_options, now, std::move(paging_state)).then(consume_results).then([has_more_pages] {
|
||||
return stop_iteration(!has_more_pages);
|
||||
});
|
||||
});
|
||||
}
|
||||
}).then([this, &builder, restrictions_need_filtering] () {
|
||||
auto rs = builder.build();
|
||||
update_stats_rows_read(rs->size());
|
||||
_stats.filtered_rows_matched_total += restrictions_need_filtering ? rs->size() : 0;
|
||||
auto msg = ::make_shared<cql_transport::messages::result_message::rows>(result(std::move(rs)));
|
||||
return make_ready_future<shared_ptr<cql_transport::messages::result_message>>(std::move(msg));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (whole_partitions || partition_slices) {
|
||||
// In this case, can use our normal query machinery, which retrieves
|
||||
// entire partitions or the same slice for many partitions.
|
||||
|
||||
@@ -67,8 +67,8 @@ class select_statement : public cql_statement {
|
||||
public:
|
||||
using parameters = raw::select_statement::parameters;
|
||||
using ordering_comparator_type = raw::select_statement::ordering_comparator_type;
|
||||
protected:
|
||||
static constexpr int DEFAULT_COUNT_PAGE_SIZE = 10000;
|
||||
protected:
|
||||
static thread_local const ::shared_ptr<parameters> _default_parameters;
|
||||
schema_ptr _schema;
|
||||
uint32_t _bound_terms;
|
||||
@@ -213,6 +213,14 @@ private:
|
||||
lw_shared_ptr<query::read_command>
|
||||
prepare_command_for_base_query(const query_options& options, service::query_state& state, gc_clock::time_point now, bool use_paging);
|
||||
|
||||
future<foreign_ptr<lw_shared_ptr<query::result>>, lw_shared_ptr<query::read_command>>
|
||||
do_execute_base_query(
|
||||
service::storage_proxy& proxy,
|
||||
dht::partition_range_vector&& partition_ranges,
|
||||
service::query_state& state,
|
||||
const query_options& options,
|
||||
gc_clock::time_point now,
|
||||
::shared_ptr<const service::pager::paging_state> paging_state);
|
||||
future<shared_ptr<cql_transport::messages::result_message>>
|
||||
execute_base_query(
|
||||
service::storage_proxy& proxy,
|
||||
@@ -222,6 +230,23 @@ private:
|
||||
gc_clock::time_point now,
|
||||
::shared_ptr<const service::pager::paging_state> paging_state);
|
||||
|
||||
// Function for fetching the selected columns from a list of clustering rows.
|
||||
// It is currently used only in our Secondary Index implementation - ordinary
|
||||
// CQL SELECT statements do not have the syntax to request a list of rows.
|
||||
// FIXME: The current implementation is very inefficient - it requests each
|
||||
// row separately (and, incrementally, in parallel). Even multiple rows from a single
|
||||
// partition are requested separately. This last case can be easily improved,
|
||||
// but to implement the general case (multiple rows from multiple partitions)
|
||||
// efficiently, we will need more support from other layers.
|
||||
// Keys are ordered in token order (see #3423)
|
||||
future<foreign_ptr<lw_shared_ptr<query::result>>, lw_shared_ptr<query::read_command>>
|
||||
do_execute_base_query(
|
||||
service::storage_proxy& proxy,
|
||||
std::vector<primary_key>&& primary_keys,
|
||||
service::query_state& state,
|
||||
const query_options& options,
|
||||
gc_clock::time_point now,
|
||||
::shared_ptr<const service::pager::paging_state> paging_state);
|
||||
future<shared_ptr<cql_transport::messages::result_message>>
|
||||
execute_base_query(
|
||||
service::storage_proxy& proxy,
|
||||
|
||||
@@ -54,7 +54,7 @@ public:
|
||||
column->ks_name,
|
||||
column->cf_name,
|
||||
::make_shared<column_identifier>(sprint("%s[%d]", column->name, component), true),
|
||||
static_pointer_cast<const tuple_type_impl>(column->type)->type(component));
|
||||
static_pointer_cast<const tuple_type_impl>(column->type->underlying_type())->type(component));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -112,7 +112,7 @@ public:
|
||||
|
||||
private:
|
||||
void validate_assignable_to(database& db, const sstring& keyspace, shared_ptr<column_specification> receiver) {
|
||||
auto tt = dynamic_pointer_cast<const tuple_type_impl>(receiver->type);
|
||||
auto tt = dynamic_pointer_cast<const tuple_type_impl>(receiver->type->underlying_type());
|
||||
if (!tt) {
|
||||
throw exceptions::invalid_request_exception(sprint("Invalid tuple type literal for %s of type %s", receiver->name, receiver->type->as_cql3_type()));
|
||||
}
|
||||
|
||||
19
database.cc
19
database.cc
@@ -1513,7 +1513,8 @@ future<> table::cleanup_sstables(sstables::compaction_descriptor descriptor) {
|
||||
return with_semaphore(sem, 1, [this, &sst] {
|
||||
// release reference to sstables cleaned up, otherwise space usage from their data and index
|
||||
// components cannot be reclaimed until all of them are cleaned.
|
||||
return this->compact_sstables(sstables::compaction_descriptor({ std::move(sst) }, sst->get_sstable_level()), true);
|
||||
auto sstable_level = sst->get_sstable_level();
|
||||
return this->compact_sstables(sstables::compaction_descriptor({ std::move(sst) }, sstable_level), true);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -2232,6 +2233,10 @@ void backlog_controller::adjust() {
|
||||
|
||||
float backlog_controller::backlog_of_shares(float shares) const {
|
||||
size_t idx = 1;
|
||||
// No control points means the controller is disabled.
|
||||
if (_control_points.size() == 0) {
|
||||
return 1.0f;
|
||||
}
|
||||
while ((idx < _control_points.size() - 1) && (_control_points[idx].output < shares)) {
|
||||
idx++;
|
||||
}
|
||||
@@ -4356,6 +4361,8 @@ future<int64_t>
|
||||
table::disable_sstable_write() {
|
||||
_sstable_writes_disabled_at = std::chrono::steady_clock::now();
|
||||
return _sstables_lock.write_lock().then([this] {
|
||||
// _sstable_deletion_sem must be acquired after _sstables_lock.write_lock
|
||||
return _sstable_deletion_sem.wait().then([this] {
|
||||
if (_sstables->all()->empty()) {
|
||||
return make_ready_future<int64_t>(0);
|
||||
}
|
||||
@@ -4364,9 +4371,19 @@ table::disable_sstable_write() {
|
||||
max = std::max(max, s->generation());
|
||||
}
|
||||
return make_ready_future<int64_t>(max);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
std::chrono::steady_clock::duration table::enable_sstable_write(int64_t new_generation) {
|
||||
if (new_generation != -1) {
|
||||
update_sstables_known_generation(new_generation);
|
||||
}
|
||||
_sstable_deletion_sem.signal();
|
||||
_sstables_lock.write_unlock();
|
||||
return std::chrono::steady_clock::now() - _sstable_writes_disabled_at;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const user_types_metadata& m) {
|
||||
os << "org.apache.cassandra.config.UTMetaData@" << &m;
|
||||
return os;
|
||||
|
||||
11
database.hh
11
database.hh
@@ -447,6 +447,7 @@ private:
|
||||
// This semaphore ensures that an operation like snapshot won't have its selected
|
||||
// sstables deleted by compaction in parallel, a race condition which could
|
||||
// easily result in failure.
|
||||
// Locking order: must be acquired either independently or after _sstables_lock
|
||||
seastar::semaphore _sstable_deletion_sem = {1};
|
||||
// There are situations in which we need to stop writing sstables. Flushers will take
|
||||
// the read lock, and the ones that wish to stop that process will take the write lock.
|
||||
@@ -737,13 +738,7 @@ public:
|
||||
|
||||
// SSTable writes are now allowed again, and generation is updated to new_generation if != -1
|
||||
// returns the amount of microseconds elapsed since we disabled writes.
|
||||
std::chrono::steady_clock::duration enable_sstable_write(int64_t new_generation) {
|
||||
if (new_generation != -1) {
|
||||
update_sstables_known_generation(new_generation);
|
||||
}
|
||||
_sstables_lock.write_unlock();
|
||||
return std::chrono::steady_clock::now() - _sstable_writes_disabled_at;
|
||||
}
|
||||
std::chrono::steady_clock::duration enable_sstable_write(int64_t new_generation);
|
||||
|
||||
// Make sure the generation numbers are sequential, starting from "start".
|
||||
// Generations before "start" are left untouched.
|
||||
@@ -897,7 +892,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
future<row_locker::lock_holder> do_push_view_replica_updates(const schema_ptr& s, mutation&& m, db::timeout_clock::time_point timeout, mutation_source&& source) const;
|
||||
future<row_locker::lock_holder> do_push_view_replica_updates(const schema_ptr& s, mutation&& m, db::timeout_clock::time_point timeout, mutation_source&& source, const io_priority_class& io_priority) const;
|
||||
std::vector<view_ptr> affected_views(const schema_ptr& base, const mutation& update) const;
|
||||
future<> generate_and_propagate_view_updates(const schema_ptr& base,
|
||||
std::vector<view_ptr>&& views,
|
||||
|
||||
@@ -395,10 +395,8 @@ std::unordered_set<gms::inet_address> db::batchlog_manager::endpoint_filter(cons
|
||||
|
||||
// grab a random member of up to two racks
|
||||
for (auto& rack : racks) {
|
||||
auto rack_members = validated.bucket(rack);
|
||||
auto n = validated.bucket_size(rack_members);
|
||||
auto cpy = boost::copy_range<std::vector<gms::inet_address>>(validated.equal_range(rack) | boost::adaptors::map_values);
|
||||
std::uniform_int_distribution<size_t> rdist(0, n - 1);
|
||||
std::uniform_int_distribution<size_t> rdist(0, cpy.size() - 1);
|
||||
result.emplace(cpy[rdist(_e1)]);
|
||||
}
|
||||
|
||||
|
||||
@@ -1189,6 +1189,34 @@ void db::commitlog::segment_manager::flush_segments(bool force) {
|
||||
}
|
||||
}
|
||||
|
||||
/// \brief Helper for ensuring a file is closed if an exception is thrown.
|
||||
///
|
||||
/// The file provided by the file_fut future is passed to func.
|
||||
/// * If func throws an exception E, the file is closed and we return
|
||||
/// a failed future with E.
|
||||
/// * If func returns a value V, the file is not closed and we return
|
||||
/// a future with V.
|
||||
/// Note that when an exception is not thrown, it is the
|
||||
/// responsibility of func to make sure the file will be closed. It
|
||||
/// can close the file itself, return it, or store it somewhere.
|
||||
///
|
||||
/// \tparam Func The type of function this wraps
|
||||
/// \param file_fut A future that produces a file
|
||||
/// \param func A function that uses a file
|
||||
/// \return A future that passes the file produced by file_fut to func
|
||||
/// and closes it if func fails
|
||||
template <typename Func>
|
||||
static auto close_on_failure(future<file> file_fut, Func func) {
|
||||
return file_fut.then([func = std::move(func)](file f) {
|
||||
return futurize_apply(func, f).handle_exception([f] (std::exception_ptr e) mutable {
|
||||
return f.close().then_wrapped([f, e = std::move(e)] (future<> x) {
|
||||
using futurator = futurize<std::result_of_t<Func(file)>>;
|
||||
return futurator::make_exception_future(e);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
future<db::commitlog::segment_manager::sseg_ptr> db::commitlog::segment_manager::allocate_segment(bool active) {
|
||||
static const auto flags = open_flags::wo | open_flags::create;
|
||||
|
||||
@@ -1219,7 +1247,7 @@ future<db::commitlog::segment_manager::sseg_ptr> db::commitlog::segment_manager:
|
||||
return fut;
|
||||
});
|
||||
|
||||
return fut.then([this, d, active, filename](file f) {
|
||||
return close_on_failure(std::move(fut), [this, d, active, filename] (file f) {
|
||||
f = make_checked_file(commit_error_handler, f);
|
||||
// xfs doesn't like files extended betond eof, so enlarge the file
|
||||
return f.truncate(max_size).then([this, d, active, f, filename] () mutable {
|
||||
@@ -1757,7 +1785,7 @@ db::commitlog::read_log_file(const sstring& filename, seastar::io_priority_class
|
||||
}
|
||||
|
||||
if (magic != segment::segment_magic) {
|
||||
throw std::invalid_argument("Not a scylla format commitlog file");
|
||||
throw invalid_segment_format();
|
||||
}
|
||||
crc32_nbo crc;
|
||||
crc.process(ver);
|
||||
@@ -1766,7 +1794,7 @@ db::commitlog::read_log_file(const sstring& filename, seastar::io_priority_class
|
||||
|
||||
auto cs = crc.checksum();
|
||||
if (cs != checksum) {
|
||||
throw std::runtime_error("Checksum error in file header");
|
||||
throw header_checksum_error();
|
||||
}
|
||||
|
||||
this->id = id;
|
||||
|
||||
@@ -342,18 +342,40 @@ public:
|
||||
|
||||
typedef std::function<future<>(temporary_buffer<char>, replay_position)> commit_load_reader_func;
|
||||
|
||||
class segment_data_corruption_error: public std::runtime_error {
|
||||
class segment_error : public std::exception {};
|
||||
|
||||
class segment_data_corruption_error: public segment_error {
|
||||
std::string _msg;
|
||||
public:
|
||||
segment_data_corruption_error(std::string msg, uint64_t s)
|
||||
: std::runtime_error(msg), _bytes(s) {
|
||||
: _msg(std::move(msg)), _bytes(s) {
|
||||
}
|
||||
uint64_t bytes() const {
|
||||
return _bytes;
|
||||
}
|
||||
virtual const char* what() const noexcept {
|
||||
return _msg.c_str();
|
||||
}
|
||||
private:
|
||||
uint64_t _bytes;
|
||||
};
|
||||
|
||||
class invalid_segment_format : public segment_error {
|
||||
static constexpr const char* _msg = "Not a scylla format commitlog file";
|
||||
public:
|
||||
virtual const char* what() const noexcept {
|
||||
return _msg;
|
||||
}
|
||||
};
|
||||
|
||||
class header_checksum_error : public segment_error {
|
||||
static constexpr const char* _msg = "Checksum error in file header";
|
||||
public:
|
||||
virtual const char* what() const noexcept {
|
||||
return _msg;
|
||||
}
|
||||
};
|
||||
|
||||
static future<std::unique_ptr<subscription<temporary_buffer<char>, replay_position>>> read_log_file(
|
||||
const sstring&, seastar::io_priority_class read_io_prio_class, commit_load_reader_func, position_type = 0, const db::extensions* = nullptr);
|
||||
private:
|
||||
|
||||
@@ -743,6 +743,7 @@ public:
|
||||
val(cpu_scheduler, bool, true, Used, "Enable cpu scheduling") \
|
||||
val(view_building, bool, true, Used, "Enable view building; should only be set to false when the node is experience issues due to view building") \
|
||||
val(enable_sstables_mc_format, bool, false, Used, "Enable SSTables 'mc' format to be used as the default file format") \
|
||||
val(abort_on_internal_error, bool, false, Used, "Abort the server instead of throwing exception when internal invariants are violated.") \
|
||||
/* done! */
|
||||
|
||||
#define _make_value_member(name, type, deflt, status, desc, ...) \
|
||||
|
||||
@@ -82,6 +82,9 @@ void manager::register_metrics(const sstring& group_name) {
|
||||
|
||||
sm::make_derive("discarded", _stats.discarded,
|
||||
sm::description("Number of hints that were discarded during sending (too old, schema changed, etc.).")),
|
||||
|
||||
sm::make_derive("corrupted_files", _stats.corrupted_files,
|
||||
sm::description("Number of hints files that were discarded during sending because the file was corrupted.")),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -114,8 +117,8 @@ future<> manager::stop() {
|
||||
|
||||
return _draining_eps_gate.close().finally([this] {
|
||||
return parallel_for_each(_ep_managers, [] (auto& pair) {
|
||||
return pair.second.stop();
|
||||
}).finally([this] {
|
||||
return pair.second.stop();
|
||||
}).finally([this] {
|
||||
_ep_managers.clear();
|
||||
manager_logger.info("Stopped");
|
||||
}).discard_result();
|
||||
@@ -236,6 +239,8 @@ future<> manager::end_point_hints_manager::stop(drain should_drain) noexcept {
|
||||
manager::end_point_hints_manager::end_point_hints_manager(const key_type& key, manager& shard_manager)
|
||||
: _key(key)
|
||||
, _shard_manager(shard_manager)
|
||||
, _file_update_mutex_ptr(make_lw_shared<seastar::shared_mutex>())
|
||||
, _file_update_mutex(*_file_update_mutex_ptr)
|
||||
, _state(state_set::of<state::stopped>())
|
||||
, _hints_dir(_shard_manager.hints_dir() / format("{}", _key).c_str())
|
||||
, _sender(*this, _shard_manager.local_storage_proxy(), _shard_manager.local_db(), _shard_manager.local_gossiper())
|
||||
@@ -244,6 +249,8 @@ manager::end_point_hints_manager::end_point_hints_manager(const key_type& key, m
|
||||
manager::end_point_hints_manager::end_point_hints_manager(end_point_hints_manager&& other)
|
||||
: _key(other._key)
|
||||
, _shard_manager(other._shard_manager)
|
||||
, _file_update_mutex_ptr(std::move(other._file_update_mutex_ptr))
|
||||
, _file_update_mutex(*_file_update_mutex_ptr)
|
||||
, _state(other._state)
|
||||
, _hints_dir(std::move(other._hints_dir))
|
||||
, _sender(other._sender, *this)
|
||||
@@ -513,28 +520,35 @@ void manager::drain_for(gms::inet_address endpoint) {
|
||||
manager_logger.trace("on_leave_cluster: {} is removed/decommissioned", endpoint);
|
||||
|
||||
with_gate(_draining_eps_gate, [this, endpoint] {
|
||||
return futurize_apply([this, endpoint] () {
|
||||
if (utils::fb_utilities::is_me(endpoint)) {
|
||||
return parallel_for_each(_ep_managers, [] (auto& pair) {
|
||||
return pair.second.stop(drain::yes).finally([&pair] {
|
||||
return remove_file(pair.second.hints_dir().c_str());
|
||||
return with_semaphore(drain_lock(), 1, [this, endpoint] {
|
||||
return futurize_apply([this, endpoint] () {
|
||||
if (utils::fb_utilities::is_me(endpoint)) {
|
||||
return parallel_for_each(_ep_managers, [] (auto& pair) {
|
||||
return pair.second.stop(drain::yes).finally([&pair] {
|
||||
return with_file_update_mutex(pair.second, [&pair] {
|
||||
return remove_file(pair.second.hints_dir().c_str());
|
||||
});
|
||||
});
|
||||
}).finally([this] {
|
||||
_ep_managers.clear();
|
||||
});
|
||||
}).finally([this] {
|
||||
_ep_managers.clear();
|
||||
});
|
||||
} else {
|
||||
ep_managers_map_type::iterator ep_manager_it = find_ep_manager(endpoint);
|
||||
if (ep_manager_it != ep_managers_end()) {
|
||||
return ep_manager_it->second.stop(drain::yes).finally([this, endpoint, hints_dir = ep_manager_it->second.hints_dir()] {
|
||||
_ep_managers.erase(endpoint);
|
||||
return remove_file(hints_dir.c_str());
|
||||
});
|
||||
}
|
||||
} else {
|
||||
ep_managers_map_type::iterator ep_manager_it = find_ep_manager(endpoint);
|
||||
if (ep_manager_it != ep_managers_end()) {
|
||||
return ep_manager_it->second.stop(drain::yes).finally([this, endpoint, &ep_man = ep_manager_it->second] {
|
||||
return with_file_update_mutex(ep_man, [&ep_man] {
|
||||
return remove_file(ep_man.hints_dir().c_str());
|
||||
}).finally([this, endpoint] {
|
||||
_ep_managers.erase(endpoint);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return make_ready_future<>();
|
||||
}
|
||||
}).handle_exception([endpoint] (auto eptr) {
|
||||
manager_logger.error("Exception when draining {}: {}", endpoint, eptr);
|
||||
return make_ready_future<>();
|
||||
}
|
||||
}).handle_exception([endpoint] (auto eptr) {
|
||||
manager_logger.error("Exception when draining {}: {}", endpoint, eptr);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -725,6 +739,10 @@ bool manager::end_point_hints_manager::sender::send_one_file(const sstring& fnam
|
||||
}, _last_not_complete_rp.pos, &_db.get_config().extensions()).get0();
|
||||
|
||||
s->done().get();
|
||||
} catch (db::commitlog::segment_error& ex) {
|
||||
manager_logger.error("{}: {}. Dropping...", fname, ex.what());
|
||||
ctx_ptr->state.remove(send_state::segment_replay_failed);
|
||||
++this->shard_stats().corrupted_files;
|
||||
} catch (...) {
|
||||
manager_logger.trace("sending of {} failed: {}", fname, std::current_exception());
|
||||
ctx_ptr->state.set(send_state::segment_replay_failed);
|
||||
|
||||
@@ -60,6 +60,7 @@ private:
|
||||
uint64_t dropped = 0;
|
||||
uint64_t sent = 0;
|
||||
uint64_t discarded = 0;
|
||||
uint64_t corrupted_files = 0;
|
||||
};
|
||||
|
||||
// map: shard -> segments
|
||||
@@ -274,7 +275,8 @@ public:
|
||||
manager& _shard_manager;
|
||||
hints_store_ptr _hints_store_anchor;
|
||||
seastar::gate _store_gate;
|
||||
seastar::shared_mutex _file_update_mutex;
|
||||
lw_shared_ptr<seastar::shared_mutex> _file_update_mutex_ptr;
|
||||
seastar::shared_mutex& _file_update_mutex;
|
||||
|
||||
enum class state {
|
||||
can_hint, // hinting is currently allowed (used by the space_watchdog)
|
||||
@@ -376,8 +378,20 @@ public:
|
||||
return _state.contains(state::stopped);
|
||||
}
|
||||
|
||||
seastar::shared_mutex& file_update_mutex() {
|
||||
return _file_update_mutex;
|
||||
/// \brief Safely runs a given functor under the file_update_mutex of \ref ep_man
|
||||
///
|
||||
/// Runs a given functor under the file_update_mutex of the given end_point_hints_manager instance.
|
||||
/// This function is safe even if \ref ep_man gets destroyed before the future this function returns resolves
|
||||
/// (as long as the \ref func call itself is safe).
|
||||
///
|
||||
/// \tparam Func Functor type.
|
||||
/// \param ep_man end_point_hints_manager instance which file_update_mutex we want to lock.
|
||||
/// \param func Functor to run under the lock.
|
||||
/// \return Whatever \ref func returns.
|
||||
template <typename Func>
|
||||
friend inline auto with_file_update_mutex(end_point_hints_manager& ep_man, Func&& func) {
|
||||
lw_shared_ptr<seastar::shared_mutex> lock_ptr = ep_man._file_update_mutex_ptr;
|
||||
return with_lock(*lock_ptr, std::forward<Func>(func)).finally([lock_ptr] {});
|
||||
}
|
||||
|
||||
const boost::filesystem::path& hints_dir() const noexcept {
|
||||
@@ -385,6 +399,10 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
seastar::shared_mutex& file_update_mutex() noexcept {
|
||||
return _file_update_mutex;
|
||||
}
|
||||
|
||||
/// \brief Creates a new hints store object.
|
||||
///
|
||||
/// - Creates a hints store directory if doesn't exist: <shard_hints_dir>/<ep_key>
|
||||
@@ -451,6 +469,7 @@ private:
|
||||
stats _stats;
|
||||
seastar::metrics::metric_groups _metrics;
|
||||
std::unordered_set<ep_key_type> _eps_with_pending_hints;
|
||||
seastar::semaphore _drain_lock = {1};
|
||||
|
||||
public:
|
||||
manager(sstring hints_directory, std::vector<sstring> hinted_dcs, int64_t max_hint_window_ms, resource_manager&res_manager, distributed<database>& db);
|
||||
@@ -529,6 +548,10 @@ public:
|
||||
return _hints_dir_device_id;
|
||||
}
|
||||
|
||||
seastar::semaphore& drain_lock() noexcept {
|
||||
return _drain_lock;
|
||||
}
|
||||
|
||||
void allow_hints();
|
||||
void forbid_hints();
|
||||
void forbid_hints_for_eps_with_pending_hints();
|
||||
|
||||
@@ -90,16 +90,27 @@ future<> space_watchdog::stop() noexcept {
|
||||
return std::move(_started);
|
||||
}
|
||||
|
||||
// Called under the end_point_hints_manager::file_update_mutex() of the corresponding end_point_hints_manager instance.
|
||||
future<> space_watchdog::scan_one_ep_dir(boost::filesystem::path path, manager& shard_manager, ep_key_type ep_key) {
|
||||
return lister::scan_dir(path, { directory_entry_type::regular }, [this, ep_key, &shard_manager] (lister::path dir, directory_entry de) {
|
||||
// Put the current end point ID to state.eps_with_pending_hints when we see the second hints file in its directory
|
||||
if (_files_count == 1) {
|
||||
shard_manager.add_ep_with_pending_hints(ep_key);
|
||||
}
|
||||
++_files_count;
|
||||
return do_with(std::move(path), [this, ep_key, &shard_manager] (boost::filesystem::path& path) {
|
||||
// It may happen that we get here and the directory has already been deleted in the context of manager::drain_for().
|
||||
// In this case simply bail out.
|
||||
return engine().file_exists(path.native()).then([this, ep_key, &shard_manager, &path] (bool exists) {
|
||||
if (!exists) {
|
||||
return make_ready_future<>();
|
||||
} else {
|
||||
return lister::scan_dir(path, { directory_entry_type::regular }, [this, ep_key, &shard_manager] (lister::path dir, directory_entry de) {
|
||||
// Put the current end point ID to state.eps_with_pending_hints when we see the second hints file in its directory
|
||||
if (_files_count == 1) {
|
||||
shard_manager.add_ep_with_pending_hints(ep_key);
|
||||
}
|
||||
++_files_count;
|
||||
|
||||
return io_check(file_size, (dir / de.name.c_str()).c_str()).then([this] (uint64_t fsize) {
|
||||
_total_size += fsize;
|
||||
return io_check(file_size, (dir / de.name.c_str()).c_str()).then([this] (uint64_t fsize) {
|
||||
_total_size += fsize;
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -137,7 +148,7 @@ void space_watchdog::on_timer() {
|
||||
// continue to enumeration - there is no one to change them.
|
||||
auto it = shard_manager.find_ep_manager(de.name);
|
||||
if (it != shard_manager.ep_managers_end()) {
|
||||
return with_lock(it->second.file_update_mutex(), [this, &shard_manager, dir = std::move(dir), ep_name = std::move(de.name)]() mutable {
|
||||
return with_file_update_mutex(it->second, [this, &shard_manager, dir = std::move(dir), ep_name = std::move(de.name)] () mutable {
|
||||
return scan_one_ep_dir(dir / ep_name.c_str(), shard_manager, ep_key_type(ep_name));
|
||||
});
|
||||
} else {
|
||||
|
||||
@@ -598,7 +598,7 @@ public:
|
||||
|
||||
future<> flush_schemas() {
|
||||
return _qp.proxy().get_db().invoke_on_all([this] (database& db) {
|
||||
return parallel_for_each(db::schema_tables::ALL, [this, &db](const sstring& cf_name) {
|
||||
return parallel_for_each(db::schema_tables::all_table_names(), [this, &db](const sstring& cf_name) {
|
||||
auto& cf = db.find_column_family(db::schema_tables::NAME, cf_name);
|
||||
return cf.flush();
|
||||
});
|
||||
|
||||
@@ -143,10 +143,10 @@ struct qualified_name {
|
||||
static future<schema_mutations> read_table_mutations(distributed<service::storage_proxy>& proxy, const qualified_name& table, schema_ptr s);
|
||||
|
||||
static void merge_tables_and_views(distributed<service::storage_proxy>& proxy,
|
||||
std::map<qualified_name, schema_mutations>&& tables_before,
|
||||
std::map<qualified_name, schema_mutations>&& tables_after,
|
||||
std::map<qualified_name, schema_mutations>&& views_before,
|
||||
std::map<qualified_name, schema_mutations>&& views_after);
|
||||
std::map<utils::UUID, schema_mutations>&& tables_before,
|
||||
std::map<utils::UUID, schema_mutations>&& tables_after,
|
||||
std::map<utils::UUID, schema_mutations>&& views_before,
|
||||
std::map<utils::UUID, schema_mutations>&& views_after);
|
||||
|
||||
struct user_types_to_drop final {
|
||||
seastar::noncopyable_function<void()> drop;
|
||||
@@ -194,8 +194,6 @@ static void prepare_builder_from_table_row(const schema_ctxt&, schema_builder&,
|
||||
|
||||
using namespace v3;
|
||||
|
||||
std::vector<const char*> ALL { KEYSPACES, TABLES, SCYLLA_TABLES, COLUMNS, DROPPED_COLUMNS, TRIGGERS, VIEWS, TYPES, FUNCTIONS, AGGREGATES, INDEXES };
|
||||
|
||||
using days = std::chrono::duration<int, std::ratio<24 * 3600>>;
|
||||
|
||||
future<> save_system_schema(const sstring & ksname) {
|
||||
@@ -203,7 +201,7 @@ future<> save_system_schema(const sstring & ksname) {
|
||||
auto ksm = ks.metadata();
|
||||
|
||||
// delete old, possibly obsolete entries in schema tables
|
||||
return parallel_for_each(ALL, [ksm] (sstring cf) {
|
||||
return parallel_for_each(all_table_names(), [ksm] (sstring cf) {
|
||||
auto deletion_timestamp = schema_creation_timestamp() - 1;
|
||||
return db::execute_cql(sprint("DELETE FROM %s.%s USING TIMESTAMP %s WHERE keyspace_name = ?", NAME, cf,
|
||||
deletion_timestamp), ksm->name()).discard_result();
|
||||
@@ -598,7 +596,7 @@ future<utils::UUID> calculate_schema_digest(distributed<service::storage_proxy>&
|
||||
}
|
||||
};
|
||||
return do_with(md5_hasher(), [map, reduce] (auto& hash) {
|
||||
return do_for_each(ALL.begin(), ALL.end(), [&hash, map, reduce] (auto& table) {
|
||||
return do_for_each(all_table_names(), [&hash, map, reduce] (auto& table) {
|
||||
return map(table).then([&hash, reduce] (auto&& mutations) {
|
||||
reduce(hash, mutations);
|
||||
});
|
||||
@@ -629,7 +627,7 @@ future<std::vector<frozen_mutation>> convert_schema_to_mutations(distributed<ser
|
||||
std::move(mutations.begin(), mutations.end(), std::back_inserter(result));
|
||||
return std::move(result);
|
||||
};
|
||||
return map_reduce(ALL.begin(), ALL.end(), map, std::vector<frozen_mutation>{}, reduce);
|
||||
return map_reduce(all_table_names(), map, std::vector<frozen_mutation>{}, reduce);
|
||||
}
|
||||
|
||||
future<schema_result>
|
||||
@@ -703,33 +701,7 @@ read_keyspace_mutation(distributed<service::storage_proxy>& proxy, const sstring
|
||||
static semaphore the_merge_lock {1};
|
||||
|
||||
future<> merge_lock() {
|
||||
// ref: #1088
|
||||
// to avoid deadlocks, we don't want long-standing calls to the shard 0
|
||||
// as they can cause a deadlock:
|
||||
//
|
||||
// fiber1 fiber2
|
||||
// merge_lock() (succeeds)
|
||||
// merge_lock() (waits)
|
||||
// invoke_on_all() (waits on merge_lock to relinquish smp::submit_to slot)
|
||||
//
|
||||
// so we issue the lock calls with a timeout; the slot will be relinquished, and invoke_on_all()
|
||||
// can complete
|
||||
return repeat([] () mutable {
|
||||
return smp::submit_to(0, [] {
|
||||
return the_merge_lock.try_wait();
|
||||
}).then([] (bool result) {
|
||||
if (result) {
|
||||
return make_ready_future<stop_iteration>(stop_iteration::yes);
|
||||
} else {
|
||||
static thread_local auto rand_engine = std::default_random_engine();
|
||||
auto dist = std::uniform_int_distribution<int>(0, 100);
|
||||
auto to = std::chrono::microseconds(dist(rand_engine));
|
||||
return sleep(to).then([] {
|
||||
return make_ready_future<stop_iteration>(stop_iteration::no);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
return smp::submit_to(0, [] { return the_merge_lock.wait(); });
|
||||
}
|
||||
|
||||
future<> merge_unlock() {
|
||||
@@ -777,16 +749,24 @@ static read_table_names_of_keyspace(distributed<service::storage_proxy>& proxy,
|
||||
});
|
||||
}
|
||||
|
||||
static utils::UUID table_id_from_mutations(const schema_mutations& sm) {
|
||||
auto table_rs = query::result_set(sm.columnfamilies_mutation());
|
||||
query::result_set_row table_row = table_rs.row(0);
|
||||
return table_row.get_nonnull<utils::UUID>("id");
|
||||
}
|
||||
|
||||
// Call inside a seastar thread
|
||||
static
|
||||
std::map<qualified_name, schema_mutations>
|
||||
std::map<utils::UUID, schema_mutations>
|
||||
read_tables_for_keyspaces(distributed<service::storage_proxy>& proxy, const std::set<sstring>& keyspace_names, schema_ptr s)
|
||||
{
|
||||
std::map<qualified_name, schema_mutations> result;
|
||||
std::map<utils::UUID, schema_mutations> result;
|
||||
for (auto&& keyspace_name : keyspace_names) {
|
||||
for (auto&& table_name : read_table_names_of_keyspace(proxy, keyspace_name, s).get0()) {
|
||||
auto qn = qualified_name(keyspace_name, table_name);
|
||||
result.emplace(qn, read_table_mutations(proxy, qn, s).get0());
|
||||
auto muts = read_table_mutations(proxy, qn, s).get0();
|
||||
auto id = table_id_from_mutations(muts);
|
||||
result.emplace(std::move(id), std::move(muts));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@@ -956,14 +936,14 @@ struct schema_diff {
|
||||
|
||||
template<typename CreateSchema>
|
||||
static schema_diff diff_table_or_view(distributed<service::storage_proxy>& proxy,
|
||||
std::map<qualified_name, schema_mutations>&& before,
|
||||
std::map<qualified_name, schema_mutations>&& after,
|
||||
std::map<utils::UUID, schema_mutations>&& before,
|
||||
std::map<utils::UUID, schema_mutations>&& after,
|
||||
CreateSchema&& create_schema)
|
||||
{
|
||||
schema_diff d;
|
||||
auto diff = difference(before, after);
|
||||
for (auto&& key : diff.entries_only_on_left) {
|
||||
auto&& s = proxy.local().get_db().local().find_schema(key.keyspace_name, key.table_name);
|
||||
auto&& s = proxy.local().get_db().local().find_schema(key);
|
||||
slogger.info("Dropping {}.{} id={} version={}", s->ks_name(), s->cf_name(), s->id(), s->version());
|
||||
d.dropped.emplace_back(schema_diff::dropped_schema{s});
|
||||
}
|
||||
@@ -986,10 +966,10 @@ static schema_diff diff_table_or_view(distributed<service::storage_proxy>& proxy
|
||||
// upon an alter table or alter type statement), then they are published together
|
||||
// as well, without any deferring in-between.
|
||||
static void merge_tables_and_views(distributed<service::storage_proxy>& proxy,
|
||||
std::map<qualified_name, schema_mutations>&& tables_before,
|
||||
std::map<qualified_name, schema_mutations>&& tables_after,
|
||||
std::map<qualified_name, schema_mutations>&& views_before,
|
||||
std::map<qualified_name, schema_mutations>&& views_after)
|
||||
std::map<utils::UUID, schema_mutations>&& tables_before,
|
||||
std::map<utils::UUID, schema_mutations>&& tables_after,
|
||||
std::map<utils::UUID, schema_mutations>&& views_before,
|
||||
std::map<utils::UUID, schema_mutations>&& views_after)
|
||||
{
|
||||
auto tables_diff = diff_table_or_view(proxy, std::move(tables_before), std::move(tables_after), [&] (auto&& sm) {
|
||||
return create_table_from_mutations(proxy, std::move(sm));
|
||||
@@ -1000,6 +980,10 @@ static void merge_tables_and_views(distributed<service::storage_proxy>& proxy,
|
||||
|
||||
proxy.local().get_db().invoke_on_all([&] (database& db) {
|
||||
return seastar::async([&] {
|
||||
parallel_for_each(boost::range::join(tables_diff.dropped, views_diff.dropped), [&] (schema_diff::dropped_schema& dt) {
|
||||
auto& s = *dt.schema.get();
|
||||
return db.drop_column_family(s.ks_name(), s.cf_name(), [&] { return dt.jp.value(); });
|
||||
}).get();
|
||||
parallel_for_each(boost::range::join(tables_diff.created, views_diff.created), [&] (global_schema_ptr& gs) {
|
||||
return db.add_column_family_and_make_directory(gs);
|
||||
}).get();
|
||||
@@ -1011,10 +995,6 @@ static void merge_tables_and_views(distributed<service::storage_proxy>& proxy,
|
||||
for (auto&& gs : boost::range::join(tables_diff.altered, views_diff.altered)) {
|
||||
columns_changed.push_back(db.update_column_family(gs));
|
||||
}
|
||||
parallel_for_each(boost::range::join(tables_diff.dropped, views_diff.dropped), [&] (schema_diff::dropped_schema& dt) {
|
||||
auto& s = *dt.schema.get();
|
||||
return db.drop_column_family(s.ks_name(), s.cf_name(), [&] { return dt.jp.value(); });
|
||||
}).get();
|
||||
|
||||
auto& mm = service::get_local_migration_manager();
|
||||
auto it = columns_changed.begin();
|
||||
@@ -2681,12 +2661,22 @@ data_type parse_type(sstring str)
|
||||
}
|
||||
|
||||
std::vector<schema_ptr> all_tables() {
|
||||
// Don't forget to update this list when new schema tables are added.
|
||||
// The listed schema tables are the ones synchronized between nodes,
|
||||
// and forgetting one of them in this list can cause bugs like #4339.
|
||||
return {
|
||||
keyspaces(), tables(), scylla_tables(), columns(), dropped_columns(), triggers(),
|
||||
views(), indexes(), types(), functions(), aggregates(), view_virtual_columns()
|
||||
};
|
||||
}
|
||||
|
||||
const std::vector<sstring>& all_table_names() {
|
||||
static thread_local std::vector<sstring> all =
|
||||
boost::copy_range<std::vector<sstring>>(all_tables() |
|
||||
boost::adaptors::transformed([] (auto schema) { return schema->cf_name(); }));
|
||||
return all;
|
||||
}
|
||||
|
||||
namespace legacy {
|
||||
|
||||
table_schema_version schema_mutations::digest() const {
|
||||
|
||||
@@ -127,9 +127,8 @@ using namespace v3;
|
||||
// Replication of schema between nodes with different version is inhibited.
|
||||
extern const sstring version;
|
||||
|
||||
extern std::vector<const char*> ALL;
|
||||
|
||||
std::vector<schema_ptr> all_tables();
|
||||
const std::vector<sstring>& all_table_names();
|
||||
|
||||
// saves/creates "ks" + all tables etc, while first deleting all old schema entries (will be rewritten)
|
||||
future<> save_system_schema(const sstring & ks);
|
||||
|
||||
329
db/size_estimates_virtual_reader.cc
Normal file
329
db/size_estimates_virtual_reader.cc
Normal file
@@ -0,0 +1,329 @@
|
||||
/*
|
||||
* Copyright (C) 2019 ScyllaDB
|
||||
*
|
||||
* Modified by 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 General Public License
|
||||
* along with Scylla. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <boost/range/adaptor/indirected.hpp>
|
||||
#include <boost/range/adaptor/map.hpp>
|
||||
#include <boost/range/adaptor/transformed.hpp>
|
||||
#include <boost/range/algorithm/find_if.hpp>
|
||||
|
||||
#include "clustering_bounds_comparator.hh"
|
||||
#include "database.hh"
|
||||
#include "db/system_keyspace.hh"
|
||||
#include "dht/i_partitioner.hh"
|
||||
#include "partition_range_compat.hh"
|
||||
#include "range.hh"
|
||||
#include "service/storage_service.hh"
|
||||
#include "stdx.hh"
|
||||
#include "mutation_fragment.hh"
|
||||
#include "sstables/sstables.hh"
|
||||
#include "db/timeout_clock.hh"
|
||||
#include "database.hh"
|
||||
|
||||
#include "db/size_estimates_virtual_reader.hh"
|
||||
|
||||
namespace db {
|
||||
|
||||
namespace size_estimates {
|
||||
|
||||
struct virtual_row {
|
||||
const bytes& cf_name;
|
||||
const token_range& tokens;
|
||||
clustering_key_prefix as_key() const {
|
||||
return clustering_key_prefix::from_exploded(std::vector<bytes_view>{cf_name, tokens.start, tokens.end});
|
||||
}
|
||||
};
|
||||
|
||||
struct virtual_row_comparator {
|
||||
schema_ptr _schema;
|
||||
virtual_row_comparator(schema_ptr schema) : _schema(schema) { }
|
||||
bool operator()(const clustering_key_prefix& key1, const clustering_key_prefix& key2) {
|
||||
return clustering_key_prefix::prefix_equality_less_compare(*_schema)(key1, key2);
|
||||
}
|
||||
bool operator()(const virtual_row& row, const clustering_key_prefix& key) {
|
||||
return operator()(row.as_key(), key);
|
||||
}
|
||||
bool operator()(const clustering_key_prefix& key, const virtual_row& row) {
|
||||
return operator()(key, row.as_key());
|
||||
}
|
||||
};
|
||||
|
||||
// Iterating over the cartesian product of cf_names and token_ranges.
|
||||
class virtual_row_iterator : public std::iterator<std::input_iterator_tag, const virtual_row> {
|
||||
std::reference_wrapper<const std::vector<bytes>> _cf_names;
|
||||
std::reference_wrapper<const std::vector<token_range>> _ranges;
|
||||
size_t _cf_names_idx = 0;
|
||||
size_t _ranges_idx = 0;
|
||||
public:
|
||||
struct end_iterator_tag {};
|
||||
virtual_row_iterator(const std::vector<bytes>& cf_names, const std::vector<token_range>& ranges)
|
||||
: _cf_names(std::ref(cf_names))
|
||||
, _ranges(std::ref(ranges))
|
||||
{ }
|
||||
virtual_row_iterator(const std::vector<bytes>& cf_names, const std::vector<token_range>& ranges, end_iterator_tag)
|
||||
: _cf_names(std::ref(cf_names))
|
||||
, _ranges(std::ref(ranges))
|
||||
, _cf_names_idx(cf_names.size())
|
||||
, _ranges_idx(ranges.size())
|
||||
{
|
||||
if (cf_names.empty() || ranges.empty()) {
|
||||
// The product of an empty range with any range is an empty range.
|
||||
// In this case we want the end iterator to be equal to the begin iterator,
|
||||
// which has_ranges_idx = _cf_names_idx = 0.
|
||||
_ranges_idx = _cf_names_idx = 0;
|
||||
}
|
||||
}
|
||||
virtual_row_iterator& operator++() {
|
||||
if (++_ranges_idx == _ranges.get().size() && ++_cf_names_idx < _cf_names.get().size()) {
|
||||
_ranges_idx = 0;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
virtual_row_iterator operator++(int) {
|
||||
virtual_row_iterator i(*this);
|
||||
++(*this);
|
||||
return i;
|
||||
}
|
||||
const value_type operator*() const {
|
||||
return { _cf_names.get()[_cf_names_idx], _ranges.get()[_ranges_idx] };
|
||||
}
|
||||
bool operator==(const virtual_row_iterator& i) const {
|
||||
return _cf_names_idx == i._cf_names_idx
|
||||
&& _ranges_idx == i._ranges_idx;
|
||||
}
|
||||
bool operator!=(const virtual_row_iterator& i) const {
|
||||
return !(*this == i);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the keyspaces, ordered by name, as selected by the partition_range.
|
||||
*/
|
||||
static std::vector<sstring> get_keyspaces(const schema& s, const database& db, dht::partition_range range) {
|
||||
struct keyspace_less_comparator {
|
||||
const schema& _s;
|
||||
keyspace_less_comparator(const schema& s) : _s(s) { }
|
||||
dht::ring_position as_ring_position(const sstring& ks) {
|
||||
auto pkey = partition_key::from_single_value(_s, utf8_type->decompose(ks));
|
||||
return dht::global_partitioner().decorate_key(_s, std::move(pkey));
|
||||
}
|
||||
bool operator()(const sstring& ks1, const sstring& ks2) {
|
||||
return as_ring_position(ks1).less_compare(_s, as_ring_position(ks2));
|
||||
}
|
||||
bool operator()(const sstring& ks, const dht::ring_position& rp) {
|
||||
return as_ring_position(ks).less_compare(_s, rp);
|
||||
}
|
||||
bool operator()(const dht::ring_position& rp, const sstring& ks) {
|
||||
return rp.less_compare(_s, as_ring_position(ks));
|
||||
}
|
||||
};
|
||||
auto keyspaces = db.get_non_system_keyspaces();
|
||||
auto cmp = keyspace_less_comparator(s);
|
||||
boost::sort(keyspaces, cmp);
|
||||
return boost::copy_range<std::vector<sstring>>(
|
||||
range.slice(keyspaces, std::move(cmp)) | boost::adaptors::filtered([&s] (const auto& ks) {
|
||||
// If this is a range query, results are divided between shards by the partition key (keyspace_name).
|
||||
return shard_of(dht::global_partitioner().get_token(s,
|
||||
partition_key::from_single_value(s, utf8_type->decompose(ks))))
|
||||
== engine().cpu_id();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a wrapping range of ring_position from a nonwrapping range of token, used to select sstables.
|
||||
*/
|
||||
static dht::partition_range as_ring_position_range(dht::token_range& r) {
|
||||
stdx::optional<range<dht::ring_position>::bound> start_bound, end_bound;
|
||||
if (r.start()) {
|
||||
start_bound = {{ dht::ring_position(r.start()->value(), dht::ring_position::token_bound::start), r.start()->is_inclusive() }};
|
||||
}
|
||||
if (r.end()) {
|
||||
end_bound = {{ dht::ring_position(r.end()->value(), dht::ring_position::token_bound::end), r.end()->is_inclusive() }};
|
||||
}
|
||||
return dht::partition_range(std::move(start_bound), std::move(end_bound), r.is_singular());
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new range_estimates for the specified range, considering the sstables associated with `cf`.
|
||||
*/
|
||||
static system_keyspace::range_estimates estimate(const column_family& cf, const token_range& r) {
|
||||
int64_t count{0};
|
||||
utils::estimated_histogram hist{0};
|
||||
auto from_bytes = [] (auto& b) {
|
||||
return dht::global_partitioner().from_sstring(utf8_type->to_string(b));
|
||||
};
|
||||
dht::token_range_vector ranges;
|
||||
::compat::unwrap_into(
|
||||
wrapping_range<dht::token>({{ from_bytes(r.start), false }}, {{ from_bytes(r.end) }}),
|
||||
dht::token_comparator(),
|
||||
[&] (auto&& rng) { ranges.push_back(std::move(rng)); });
|
||||
for (auto&& r : ranges) {
|
||||
auto rp_range = as_ring_position_range(r);
|
||||
for (auto&& sstable : cf.select_sstables(rp_range)) {
|
||||
count += sstable->estimated_keys_for_range(r);
|
||||
hist.merge(sstable->get_stats_metadata().estimated_row_size);
|
||||
}
|
||||
}
|
||||
return {cf.schema(), r.start, r.end, count, count > 0 ? hist.mean() : 0};
|
||||
}
|
||||
|
||||
future<std::vector<token_range>> get_local_ranges() {
|
||||
auto& ss = service::get_local_storage_service();
|
||||
return ss.get_local_tokens().then([&ss] (auto&& tokens) {
|
||||
auto ranges = ss.get_token_metadata().get_primary_ranges_for(std::move(tokens));
|
||||
std::vector<token_range> local_ranges;
|
||||
auto to_bytes = [](const stdx::optional<dht::token_range::bound>& b) {
|
||||
assert(b);
|
||||
return utf8_type->decompose(dht::global_partitioner().to_sstring(b->value()));
|
||||
};
|
||||
// We merge the ranges to be compatible with how Cassandra shows it's size estimates table.
|
||||
// All queries will be on that table, where all entries are text and there's no notion of
|
||||
// token ranges form the CQL point of view.
|
||||
auto left_inf = boost::find_if(ranges, [] (auto&& r) {
|
||||
return !r.start() || r.start()->value() == dht::minimum_token();
|
||||
});
|
||||
auto right_inf = boost::find_if(ranges, [] (auto&& r) {
|
||||
return !r.end() || r.start()->value() == dht::maximum_token();
|
||||
});
|
||||
if (left_inf != right_inf && left_inf != ranges.end() && right_inf != ranges.end()) {
|
||||
local_ranges.push_back(token_range{to_bytes(right_inf->start()), to_bytes(left_inf->end())});
|
||||
ranges.erase(left_inf);
|
||||
ranges.erase(right_inf);
|
||||
}
|
||||
for (auto&& r : ranges) {
|
||||
local_ranges.push_back(token_range{to_bytes(r.start()), to_bytes(r.end())});
|
||||
}
|
||||
boost::sort(local_ranges, [] (auto&& tr1, auto&& tr2) {
|
||||
return utf8_type->less(tr1.start, tr2.start);
|
||||
});
|
||||
return local_ranges;
|
||||
});
|
||||
}
|
||||
|
||||
size_estimates_mutation_reader::size_estimates_mutation_reader(schema_ptr schema, const dht::partition_range& prange, const query::partition_slice& slice, streamed_mutation::forwarding fwd)
|
||||
: impl(schema)
|
||||
, _schema(std::move(schema))
|
||||
, _prange(&prange)
|
||||
, _slice(slice)
|
||||
, _fwd(fwd)
|
||||
{ }
|
||||
|
||||
future<> size_estimates_mutation_reader::get_next_partition() {
|
||||
auto& db = service::get_local_storage_proxy().get_db().local();
|
||||
if (!_keyspaces) {
|
||||
_keyspaces = get_keyspaces(*_schema, db, *_prange);
|
||||
_current_partition = _keyspaces->begin();
|
||||
}
|
||||
if (_current_partition == _keyspaces->end()) {
|
||||
_end_of_stream = true;
|
||||
return make_ready_future<>();
|
||||
}
|
||||
return get_local_ranges().then([&db, this] (auto&& ranges) {
|
||||
auto estimates = this->estimates_for_current_keyspace(db, std::move(ranges));
|
||||
auto mutations = db::system_keyspace::make_size_estimates_mutation(*_current_partition, std::move(estimates));
|
||||
++_current_partition;
|
||||
std::vector<mutation> ms;
|
||||
ms.emplace_back(std::move(mutations));
|
||||
_partition_reader = flat_mutation_reader_from_mutations(std::move(ms), _fwd);
|
||||
});
|
||||
}
|
||||
|
||||
future<> size_estimates_mutation_reader::fill_buffer(db::timeout_clock::time_point timeout) {
|
||||
return do_until([this, timeout] { return is_end_of_stream() || is_buffer_full(); }, [this, timeout] {
|
||||
if (!_partition_reader) {
|
||||
return get_next_partition();
|
||||
}
|
||||
return _partition_reader->consume_pausable([this] (mutation_fragment mf) {
|
||||
push_mutation_fragment(std::move(mf));
|
||||
return stop_iteration(is_buffer_full());
|
||||
}, timeout).then([this] {
|
||||
if (_partition_reader->is_end_of_stream() && _partition_reader->is_buffer_empty()) {
|
||||
_partition_reader = stdx::nullopt;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void size_estimates_mutation_reader::next_partition() {
|
||||
clear_buffer_to_next_partition();
|
||||
if (is_buffer_empty()) {
|
||||
_partition_reader = stdx::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
future<> size_estimates_mutation_reader::fast_forward_to(const dht::partition_range& pr, db::timeout_clock::time_point timeout) {
|
||||
clear_buffer();
|
||||
_prange = ≺
|
||||
_keyspaces = stdx::nullopt;
|
||||
_partition_reader = stdx::nullopt;
|
||||
_end_of_stream = false;
|
||||
return make_ready_future<>();
|
||||
}
|
||||
|
||||
future<> size_estimates_mutation_reader::fast_forward_to(position_range pr, db::timeout_clock::time_point timeout) {
|
||||
forward_buffer_to(pr.start());
|
||||
_end_of_stream = false;
|
||||
if (_partition_reader) {
|
||||
return _partition_reader->fast_forward_to(std::move(pr), timeout);
|
||||
}
|
||||
return make_ready_future<>();
|
||||
}
|
||||
|
||||
size_t size_estimates_mutation_reader::buffer_size() const {
|
||||
if (_partition_reader) {
|
||||
return flat_mutation_reader::impl::buffer_size() + _partition_reader->buffer_size();
|
||||
}
|
||||
return flat_mutation_reader::impl::buffer_size();
|
||||
}
|
||||
|
||||
std::vector<db::system_keyspace::range_estimates>
|
||||
size_estimates_mutation_reader::estimates_for_current_keyspace(const database& db, std::vector<token_range> local_ranges) const {
|
||||
// For each specified range, estimate (crudely) mean partition size and partitions count.
|
||||
auto pkey = partition_key::from_single_value(*_schema, utf8_type->decompose(*_current_partition));
|
||||
auto cfs = db.find_keyspace(*_current_partition).metadata()->cf_meta_data();
|
||||
auto cf_names = boost::copy_range<std::vector<bytes>>(cfs | boost::adaptors::transformed([] (auto&& cf) {
|
||||
return utf8_type->decompose(cf.first);
|
||||
}));
|
||||
boost::sort(cf_names, [] (auto&& n1, auto&& n2) {
|
||||
return utf8_type->less(n1, n2);
|
||||
});
|
||||
std::vector<db::system_keyspace::range_estimates> estimates;
|
||||
for (auto& range : _slice.row_ranges(*_schema, pkey)) {
|
||||
auto rows = boost::make_iterator_range(
|
||||
virtual_row_iterator(cf_names, local_ranges),
|
||||
virtual_row_iterator(cf_names, local_ranges, virtual_row_iterator::end_iterator_tag()));
|
||||
auto rows_to_estimate = range.slice(rows, virtual_row_comparator(_schema));
|
||||
for (auto&& r : rows_to_estimate) {
|
||||
auto& cf = db.find_column_family(*_current_partition, utf8_type->to_string(r.cf_name));
|
||||
estimates.push_back(estimate(cf, r.tokens));
|
||||
if (estimates.size() >= _slice.partition_row_limit()) {
|
||||
return estimates;
|
||||
}
|
||||
}
|
||||
}
|
||||
return estimates;
|
||||
}
|
||||
|
||||
} // namespace size_estimates
|
||||
|
||||
} // namespace db
|
||||
@@ -21,33 +21,19 @@
|
||||
* along with Scylla. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <boost/range/adaptor/indirected.hpp>
|
||||
#include <boost/range/adaptor/map.hpp>
|
||||
#include <boost/range/adaptor/transformed.hpp>
|
||||
#include <boost/range/algorithm/find_if.hpp>
|
||||
|
||||
#include "clustering_bounds_comparator.hh"
|
||||
#include "database.hh"
|
||||
#include "db/system_keyspace.hh"
|
||||
#include "dht/i_partitioner.hh"
|
||||
#include "mutation_reader.hh"
|
||||
#include "partition_range_compat.hh"
|
||||
#include "range.hh"
|
||||
#include "service/storage_service.hh"
|
||||
#include "stdx.hh"
|
||||
#include "mutation_fragment.hh"
|
||||
#include "sstables/sstables.hh"
|
||||
#include "db/timeout_clock.hh"
|
||||
|
||||
namespace db {
|
||||
|
||||
namespace size_estimates {
|
||||
|
||||
struct token_range {
|
||||
bytes start;
|
||||
bytes end;
|
||||
};
|
||||
|
||||
class size_estimates_mutation_reader final : public flat_mutation_reader::impl {
|
||||
struct token_range {
|
||||
bytes start;
|
||||
bytes end;
|
||||
};
|
||||
schema_ptr _schema;
|
||||
const dht::partition_range* _prange;
|
||||
const query::partition_slice& _slice;
|
||||
@@ -57,267 +43,18 @@ class size_estimates_mutation_reader final : public flat_mutation_reader::impl {
|
||||
streamed_mutation::forwarding _fwd;
|
||||
flat_mutation_reader_opt _partition_reader;
|
||||
public:
|
||||
size_estimates_mutation_reader(schema_ptr schema, const dht::partition_range& prange, const query::partition_slice& slice, streamed_mutation::forwarding fwd)
|
||||
: impl(schema)
|
||||
, _schema(std::move(schema))
|
||||
, _prange(&prange)
|
||||
, _slice(slice)
|
||||
, _fwd(fwd)
|
||||
{ }
|
||||
size_estimates_mutation_reader(schema_ptr, const dht::partition_range&, const query::partition_slice&, streamed_mutation::forwarding);
|
||||
|
||||
virtual future<> fill_buffer(db::timeout_clock::time_point) override;
|
||||
virtual void next_partition() override;
|
||||
virtual future<> fast_forward_to(const dht::partition_range&, db::timeout_clock::time_point) override;
|
||||
virtual future<> fast_forward_to(position_range, db::timeout_clock::time_point) override;
|
||||
virtual size_t buffer_size() const override;
|
||||
private:
|
||||
future<> get_next_partition() {
|
||||
// For each specified range, estimate (crudely) mean partition size and partitions count.
|
||||
auto& db = service::get_local_storage_proxy().get_db().local();
|
||||
if (!_keyspaces) {
|
||||
_keyspaces = get_keyspaces(*_schema, db, *_prange);
|
||||
_current_partition = _keyspaces->begin();
|
||||
}
|
||||
if (_current_partition == _keyspaces->end()) {
|
||||
_end_of_stream = true;
|
||||
return make_ready_future<>();
|
||||
}
|
||||
return get_local_ranges().then([&db, this] (auto&& ranges) {
|
||||
auto estimates = this->estimates_for_current_keyspace(db, std::move(ranges));
|
||||
auto mutations = db::system_keyspace::make_size_estimates_mutation(*_current_partition, std::move(estimates));
|
||||
++_current_partition;
|
||||
std::vector<mutation> ms;
|
||||
ms.emplace_back(std::move(mutations));
|
||||
_partition_reader = flat_mutation_reader_from_mutations(std::move(ms), _fwd);
|
||||
});
|
||||
}
|
||||
public:
|
||||
virtual future<> fill_buffer(db::timeout_clock::time_point timeout) override {
|
||||
return do_until([this, timeout] { return is_end_of_stream() || is_buffer_full(); }, [this, timeout] {
|
||||
if (!_partition_reader) {
|
||||
return get_next_partition();
|
||||
}
|
||||
return _partition_reader->consume_pausable([this] (mutation_fragment mf) {
|
||||
push_mutation_fragment(std::move(mf));
|
||||
return stop_iteration(is_buffer_full());
|
||||
}, timeout).then([this] {
|
||||
if (_partition_reader->is_end_of_stream() && _partition_reader->is_buffer_empty()) {
|
||||
_partition_reader = stdx::nullopt;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
virtual void next_partition() override {
|
||||
clear_buffer_to_next_partition();
|
||||
if (is_buffer_empty()) {
|
||||
_partition_reader = stdx::nullopt;
|
||||
}
|
||||
}
|
||||
virtual future<> fast_forward_to(const dht::partition_range& pr, db::timeout_clock::time_point timeout) override {
|
||||
clear_buffer();
|
||||
_prange = ≺
|
||||
_keyspaces = stdx::nullopt;
|
||||
_partition_reader = stdx::nullopt;
|
||||
_end_of_stream = false;
|
||||
return make_ready_future<>();
|
||||
}
|
||||
virtual future<> fast_forward_to(position_range pr, db::timeout_clock::time_point timeout) override {
|
||||
forward_buffer_to(pr.start());
|
||||
_end_of_stream = false;
|
||||
if (_partition_reader) {
|
||||
return _partition_reader->fast_forward_to(std::move(pr), timeout);
|
||||
}
|
||||
return make_ready_future<>();
|
||||
}
|
||||
virtual size_t buffer_size() const override {
|
||||
if (_partition_reader) {
|
||||
return flat_mutation_reader::impl::buffer_size() + _partition_reader->buffer_size();
|
||||
}
|
||||
return flat_mutation_reader::impl::buffer_size();
|
||||
}
|
||||
/**
|
||||
* Returns the primary ranges for the local node.
|
||||
* Used for testing as well.
|
||||
*/
|
||||
static future<std::vector<token_range>> get_local_ranges() {
|
||||
auto& ss = service::get_local_storage_service();
|
||||
return ss.get_local_tokens().then([&ss] (auto&& tokens) {
|
||||
auto ranges = ss.get_token_metadata().get_primary_ranges_for(std::move(tokens));
|
||||
std::vector<token_range> local_ranges;
|
||||
auto to_bytes = [](const stdx::optional<dht::token_range::bound>& b) {
|
||||
assert(b);
|
||||
return utf8_type->decompose(dht::global_partitioner().to_sstring(b->value()));
|
||||
};
|
||||
// We merge the ranges to be compatible with how Cassandra shows it's size estimates table.
|
||||
// All queries will be on that table, where all entries are text and there's no notion of
|
||||
// token ranges form the CQL point of view.
|
||||
auto left_inf = boost::find_if(ranges, [] (auto&& r) {
|
||||
return !r.start() || r.start()->value() == dht::minimum_token();
|
||||
});
|
||||
auto right_inf = boost::find_if(ranges, [] (auto&& r) {
|
||||
return !r.end() || r.start()->value() == dht::maximum_token();
|
||||
});
|
||||
if (left_inf != right_inf && left_inf != ranges.end() && right_inf != ranges.end()) {
|
||||
local_ranges.push_back(token_range{to_bytes(right_inf->start()), to_bytes(left_inf->end())});
|
||||
ranges.erase(left_inf);
|
||||
ranges.erase(right_inf);
|
||||
}
|
||||
for (auto&& r : ranges) {
|
||||
local_ranges.push_back(token_range{to_bytes(r.start()), to_bytes(r.end())});
|
||||
}
|
||||
boost::sort(local_ranges, [] (auto&& tr1, auto&& tr2) {
|
||||
return utf8_type->less(tr1.start, tr2.start);
|
||||
});
|
||||
return local_ranges;
|
||||
});
|
||||
}
|
||||
private:
|
||||
struct virtual_row {
|
||||
const bytes& cf_name;
|
||||
const token_range& tokens;
|
||||
clustering_key_prefix as_key() const {
|
||||
return clustering_key_prefix::from_exploded(std::vector<bytes_view>{cf_name, tokens.start, tokens.end});
|
||||
}
|
||||
};
|
||||
struct virtual_row_comparator {
|
||||
schema_ptr _schema;
|
||||
virtual_row_comparator(schema_ptr schema) : _schema(schema) { }
|
||||
bool operator()(const clustering_key_prefix& key1, const clustering_key_prefix& key2) {
|
||||
return clustering_key_prefix::prefix_equality_less_compare(*_schema)(key1, key2);
|
||||
}
|
||||
bool operator()(const virtual_row& row, const clustering_key_prefix& key) {
|
||||
return operator()(row.as_key(), key);
|
||||
}
|
||||
bool operator()(const clustering_key_prefix& key, const virtual_row& row) {
|
||||
return operator()(key, row.as_key());
|
||||
}
|
||||
};
|
||||
class virtual_row_iterator : public std::iterator<std::input_iterator_tag, const virtual_row> {
|
||||
std::reference_wrapper<const std::vector<bytes>> _cf_names;
|
||||
std::reference_wrapper<const std::vector<token_range>> _ranges;
|
||||
size_t _cf_names_idx = 0;
|
||||
size_t _ranges_idx = 0;
|
||||
public:
|
||||
struct end_iterator_tag {};
|
||||
virtual_row_iterator(const std::vector<bytes>& cf_names, const std::vector<token_range>& ranges)
|
||||
: _cf_names(std::ref(cf_names))
|
||||
, _ranges(std::ref(ranges))
|
||||
{ }
|
||||
virtual_row_iterator(const std::vector<bytes>& cf_names, const std::vector<token_range>& ranges, end_iterator_tag)
|
||||
: _cf_names(std::ref(cf_names))
|
||||
, _ranges(std::ref(ranges))
|
||||
, _cf_names_idx(cf_names.size())
|
||||
, _ranges_idx(ranges.size())
|
||||
{ }
|
||||
virtual_row_iterator& operator++() {
|
||||
if (++_ranges_idx == _ranges.get().size() && ++_cf_names_idx < _cf_names.get().size()) {
|
||||
_ranges_idx = 0;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
virtual_row_iterator operator++(int) {
|
||||
virtual_row_iterator i(*this);
|
||||
++(*this);
|
||||
return i;
|
||||
}
|
||||
const value_type operator*() const {
|
||||
return { _cf_names.get()[_cf_names_idx], _ranges.get()[_ranges_idx] };
|
||||
}
|
||||
bool operator==(const virtual_row_iterator& i) const {
|
||||
return _cf_names_idx == i._cf_names_idx
|
||||
&& _ranges_idx == i._ranges_idx;
|
||||
}
|
||||
bool operator!=(const virtual_row_iterator& i) const {
|
||||
return !(*this == i);
|
||||
}
|
||||
};
|
||||
future<> get_next_partition();
|
||||
|
||||
std::vector<db::system_keyspace::range_estimates>
|
||||
estimates_for_current_keyspace(const database& db, std::vector<token_range> local_ranges) const {
|
||||
auto pkey = partition_key::from_single_value(*_schema, utf8_type->decompose(*_current_partition));
|
||||
auto cfs = db.find_keyspace(*_current_partition).metadata()->cf_meta_data();
|
||||
auto cf_names = boost::copy_range<std::vector<bytes>>(cfs | boost::adaptors::transformed([] (auto&& cf) {
|
||||
return utf8_type->decompose(cf.first);
|
||||
}));
|
||||
boost::sort(cf_names, [] (auto&& n1, auto&& n2) {
|
||||
return utf8_type->less(n1, n2);
|
||||
});
|
||||
std::vector<db::system_keyspace::range_estimates> estimates;
|
||||
for (auto& range : _slice.row_ranges(*_schema, pkey)) {
|
||||
auto rows = boost::make_iterator_range(
|
||||
virtual_row_iterator(cf_names, local_ranges),
|
||||
virtual_row_iterator(cf_names, local_ranges, virtual_row_iterator::end_iterator_tag()));
|
||||
auto rows_to_estimate = range.slice(rows, virtual_row_comparator(_schema));
|
||||
for (auto&& r : rows_to_estimate) {
|
||||
auto& cf = db.find_column_family(*_current_partition, utf8_type->to_string(r.cf_name));
|
||||
estimates.push_back(estimate(cf, r.tokens));
|
||||
if (estimates.size() >= _slice.partition_row_limit()) {
|
||||
return estimates;
|
||||
}
|
||||
}
|
||||
}
|
||||
return estimates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the keyspaces, ordered by name, as selected by the partition_range.
|
||||
*/
|
||||
static ks_range get_keyspaces(const schema& s, const database& db, dht::partition_range range) {
|
||||
struct keyspace_less_comparator {
|
||||
const schema& _s;
|
||||
keyspace_less_comparator(const schema& s) : _s(s) { }
|
||||
dht::ring_position as_ring_position(const sstring& ks) {
|
||||
auto pkey = partition_key::from_single_value(_s, utf8_type->decompose(ks));
|
||||
return dht::global_partitioner().decorate_key(_s, std::move(pkey));
|
||||
}
|
||||
bool operator()(const sstring& ks1, const sstring& ks2) {
|
||||
return as_ring_position(ks1).less_compare(_s, as_ring_position(ks2));
|
||||
}
|
||||
bool operator()(const sstring& ks, const dht::ring_position& rp) {
|
||||
return as_ring_position(ks).less_compare(_s, rp);
|
||||
}
|
||||
bool operator()(const dht::ring_position& rp, const sstring& ks) {
|
||||
return rp.less_compare(_s, as_ring_position(ks));
|
||||
}
|
||||
};
|
||||
auto keyspaces = db.get_non_system_keyspaces();
|
||||
auto cmp = keyspace_less_comparator(s);
|
||||
boost::sort(keyspaces, cmp);
|
||||
return boost::copy_range<ks_range>(range.slice(keyspaces, std::move(cmp)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a wrapping range of ring_position from a nonwrapping range of token, used to select sstables.
|
||||
*/
|
||||
static dht::partition_range as_ring_position_range(dht::token_range& r) {
|
||||
stdx::optional<range<dht::ring_position>::bound> start_bound, end_bound;
|
||||
if (r.start()) {
|
||||
start_bound = {{ dht::ring_position(r.start()->value(), dht::ring_position::token_bound::start), r.start()->is_inclusive() }};
|
||||
}
|
||||
if (r.end()) {
|
||||
end_bound = {{ dht::ring_position(r.end()->value(), dht::ring_position::token_bound::end), r.end()->is_inclusive() }};
|
||||
}
|
||||
return dht::partition_range(std::move(start_bound), std::move(end_bound), r.is_singular());
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new range_estimates for the specified range, considering the sstables associated with `cf`.
|
||||
*/
|
||||
static system_keyspace::range_estimates estimate(const column_family& cf, const token_range& r) {
|
||||
int64_t count{0};
|
||||
utils::estimated_histogram hist{0};
|
||||
auto from_bytes = [] (auto& b) {
|
||||
return dht::global_partitioner().from_sstring(utf8_type->to_string(b));
|
||||
};
|
||||
dht::token_range_vector ranges;
|
||||
::compat::unwrap_into(
|
||||
wrapping_range<dht::token>({{ from_bytes(r.start), false }}, {{ from_bytes(r.end) }}),
|
||||
dht::token_comparator(),
|
||||
[&] (auto&& rng) { ranges.push_back(std::move(rng)); });
|
||||
for (auto&& r : ranges) {
|
||||
auto rp_range = as_ring_position_range(r);
|
||||
for (auto&& sstable : cf.select_sstables(rp_range)) {
|
||||
count += sstable->estimated_keys_for_range(r);
|
||||
hist.merge(sstable->get_stats_metadata().estimated_row_size);
|
||||
}
|
||||
}
|
||||
return {cf.schema(), r.start, r.end, count, count > 0 ? hist.mean() : 0};
|
||||
}
|
||||
estimates_for_current_keyspace(const database&, std::vector<token_range> local_ranges) const;
|
||||
};
|
||||
|
||||
struct virtual_reader {
|
||||
@@ -332,6 +69,12 @@ struct virtual_reader {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the primary ranges for the local node.
|
||||
* Used for testing as well.
|
||||
*/
|
||||
future<std::vector<token_range>> get_local_ranges();
|
||||
|
||||
} // namespace size_estimates
|
||||
|
||||
} // namespace db
|
||||
|
||||
@@ -445,7 +445,7 @@ void create_virtual_column(schema_builder& builder, const bytes& name, const dat
|
||||
// A map has keys and values. We don't need these values,
|
||||
// and can use empty values instead.
|
||||
auto mtype = dynamic_pointer_cast<const map_type_impl>(type);
|
||||
builder.with_column(name, map_type_impl::get_instance(mtype->get_values_type(), empty_type, true), column_kind::regular_column, column_view_virtual::yes);
|
||||
builder.with_column(name, map_type_impl::get_instance(mtype->get_keys_type(), empty_type, true), column_kind::regular_column, column_view_virtual::yes);
|
||||
} else if (ctype->is_set()) {
|
||||
// A set's cell has nothing beyond the keys, so the
|
||||
// virtual version of a set is, unfortunately, a complete
|
||||
|
||||
@@ -24,7 +24,9 @@
|
||||
namespace db::view {
|
||||
|
||||
future<> view_update_from_staging_generator::start() {
|
||||
_started = seastar::async([this]() mutable {
|
||||
thread_attributes attr;
|
||||
attr.sched_group = _db.get_streaming_scheduling_group();
|
||||
_started = seastar::async(std::move(attr), [this]() mutable {
|
||||
while (!_as.abort_requested()) {
|
||||
if (_sstables_with_tables.empty()) {
|
||||
_pending_sstables.wait().get();
|
||||
|
||||
2
dist/common/scripts/scylla_util.py
vendored
2
dist/common/scripts/scylla_util.py
vendored
@@ -449,6 +449,8 @@ def create_perftune_conf(nic='eth0'):
|
||||
|
||||
|
||||
def is_valid_nic(nic):
|
||||
if len(nic) == 0:
|
||||
return False
|
||||
return os.path.exists('/sys/class/net/{}'.format(nic))
|
||||
|
||||
# Remove this when we do not support SET_NIC configuration value anymore
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
#include "flat_mutation_reader.hh"
|
||||
#include "mutation_reader.hh"
|
||||
#include "seastar/util/reference_wrapper.hh"
|
||||
#include "clustering_ranges_walker.hh"
|
||||
#include "schema_upgrader.hh"
|
||||
#include <algorithm>
|
||||
|
||||
#include <boost/range/adaptor/transformed.hpp>
|
||||
@@ -347,6 +349,7 @@ flat_mutation_reader make_empty_flat_reader(schema_ptr s) {
|
||||
|
||||
flat_mutation_reader
|
||||
flat_mutation_reader_from_mutations(std::vector<mutation> ms,
|
||||
const dht::partition_range& pr,
|
||||
const query::partition_slice& slice,
|
||||
streamed_mutation::forwarding fwd) {
|
||||
std::vector<mutation> sliced_ms;
|
||||
@@ -355,7 +358,12 @@ flat_mutation_reader_from_mutations(std::vector<mutation> ms,
|
||||
auto mp = mutation_partition(std::move(m.partition()), *m.schema(), std::move(ck_ranges));
|
||||
sliced_ms.emplace_back(m.schema(), m.decorated_key(), std::move(mp));
|
||||
}
|
||||
return flat_mutation_reader_from_mutations(sliced_ms, query::full_partition_range, fwd);
|
||||
return flat_mutation_reader_from_mutations(sliced_ms, pr, fwd);
|
||||
}
|
||||
|
||||
flat_mutation_reader
|
||||
flat_mutation_reader_from_mutations(std::vector<mutation> ms, const query::partition_slice& slice, streamed_mutation::forwarding fwd) {
|
||||
return flat_mutation_reader_from_mutations(std::move(ms), query::full_partition_range, slice, fwd);
|
||||
}
|
||||
|
||||
flat_mutation_reader
|
||||
@@ -487,11 +495,11 @@ flat_mutation_reader_from_mutations(std::vector<mutation> mutations, const dht::
|
||||
}
|
||||
public:
|
||||
reader(schema_ptr s, std::vector<mutation>&& mutations, const dht::partition_range& pr)
|
||||
: impl(std::move(s))
|
||||
: impl(s)
|
||||
, _mutations(std::move(mutations))
|
||||
, _cur(find_first_partition(_mutations, pr))
|
||||
, _end(find_last_partition(_mutations, pr))
|
||||
, _cmp(*_cur->schema())
|
||||
, _cmp(*s)
|
||||
{
|
||||
_end_of_stream = _cur == _end;
|
||||
if (!_end_of_stream) {
|
||||
@@ -509,6 +517,7 @@ flat_mutation_reader_from_mutations(std::vector<mutation> mutations, const dht::
|
||||
// clear_and_dispose() used by mutation_partition destructor won't
|
||||
// work properly.
|
||||
|
||||
_cur = _mutations.begin();
|
||||
while (_cur != _end) {
|
||||
destroy_current_mutation();
|
||||
++_cur;
|
||||
@@ -779,15 +788,32 @@ make_flat_multi_range_reader(
|
||||
|
||||
flat_mutation_reader
|
||||
make_flat_mutation_reader_from_fragments(schema_ptr schema, std::deque<mutation_fragment> fragments) {
|
||||
return make_flat_mutation_reader_from_fragments(std::move(schema), std::move(fragments), query::full_partition_range);
|
||||
}
|
||||
|
||||
flat_mutation_reader
|
||||
make_flat_mutation_reader_from_fragments(schema_ptr schema, std::deque<mutation_fragment> fragments, const dht::partition_range& pr) {
|
||||
class reader : public flat_mutation_reader::impl {
|
||||
std::deque<mutation_fragment> _fragments;
|
||||
const dht::partition_range* _pr;
|
||||
dht::ring_position_comparator _cmp;
|
||||
|
||||
private:
|
||||
bool end_of_range() const {
|
||||
return _fragments.empty() ||
|
||||
(_fragments.front().is_partition_start() && _pr->after(_fragments.front().as_partition_start().key(), _cmp));
|
||||
}
|
||||
|
||||
public:
|
||||
reader(schema_ptr schema, std::deque<mutation_fragment> fragments)
|
||||
reader(schema_ptr schema, std::deque<mutation_fragment> fragments, const dht::partition_range& pr)
|
||||
: flat_mutation_reader::impl(std::move(schema))
|
||||
, _fragments(std::move(fragments)) {
|
||||
, _fragments(std::move(fragments))
|
||||
, _pr(&pr)
|
||||
, _cmp(*_schema) {
|
||||
fast_forward_to(*_pr, db::no_timeout);
|
||||
}
|
||||
virtual future<> fill_buffer(db::timeout_clock::time_point) override {
|
||||
while (!(_end_of_stream = _fragments.empty()) && !is_buffer_full()) {
|
||||
while (!(_end_of_stream = end_of_range()) && !is_buffer_full()) {
|
||||
push_mutation_fragment(std::move(_fragments.front()));
|
||||
_fragments.pop_front();
|
||||
}
|
||||
@@ -796,7 +822,7 @@ make_flat_mutation_reader_from_fragments(schema_ptr schema, std::deque<mutation_
|
||||
virtual void next_partition() override {
|
||||
clear_buffer_to_next_partition();
|
||||
if (is_buffer_empty()) {
|
||||
while (!(_end_of_stream = _fragments.empty()) && !_fragments.front().is_partition_start()) {
|
||||
while (!(_end_of_stream = end_of_range()) && !_fragments.front().is_partition_start()) {
|
||||
_fragments.pop_front();
|
||||
}
|
||||
}
|
||||
@@ -805,8 +831,48 @@ make_flat_mutation_reader_from_fragments(schema_ptr schema, std::deque<mutation_
|
||||
throw std::runtime_error("This reader can't be fast forwarded to another range.");
|
||||
}
|
||||
virtual future<> fast_forward_to(const dht::partition_range& pr, db::timeout_clock::time_point timeout) override {
|
||||
throw std::runtime_error("This reader can't be fast forwarded to another position.");
|
||||
clear_buffer();
|
||||
_pr = ≺
|
||||
_fragments.erase(_fragments.begin(), std::find_if(_fragments.begin(), _fragments.end(), [this] (const mutation_fragment& mf) {
|
||||
return mf.is_partition_start() && !_pr->before(mf.as_partition_start().key(), _cmp);
|
||||
}));
|
||||
_end_of_stream = end_of_range();
|
||||
return make_ready_future<>();
|
||||
}
|
||||
};
|
||||
return make_flat_mutation_reader<reader>(std::move(schema), std::move(fragments));
|
||||
return make_flat_mutation_reader<reader>(std::move(schema), std::move(fragments), pr);
|
||||
}
|
||||
|
||||
flat_mutation_reader
|
||||
make_flat_mutation_reader_from_fragments(schema_ptr schema, std::deque<mutation_fragment> fragments, const dht::partition_range& pr, const query::partition_slice& slice) {
|
||||
std::optional<clustering_ranges_walker> ranges_walker;
|
||||
for (auto it = fragments.begin(); it != fragments.end();) {
|
||||
switch (it->mutation_fragment_kind()) {
|
||||
case mutation_fragment::kind::partition_start:
|
||||
ranges_walker.emplace(*schema, slice.row_ranges(*schema, it->as_partition_start().key().key()), false);
|
||||
case mutation_fragment::kind::static_row: // fall-through
|
||||
case mutation_fragment::kind::partition_end: // fall-through
|
||||
++it;
|
||||
break;
|
||||
case mutation_fragment::kind::clustering_row:
|
||||
if (ranges_walker->advance_to(it->position())) {
|
||||
++it;
|
||||
} else {
|
||||
it = fragments.erase(it);
|
||||
}
|
||||
break;
|
||||
case mutation_fragment::kind::range_tombstone:
|
||||
if (ranges_walker->advance_to(it->as_range_tombstone().position(), it->as_range_tombstone().end_position())) {
|
||||
++it;
|
||||
} else {
|
||||
it = fragments.erase(it);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return make_flat_mutation_reader_from_fragments(std::move(schema), std::move(fragments), pr);
|
||||
}
|
||||
|
||||
void flat_mutation_reader::do_upgrade_schema(const schema_ptr& s) {
|
||||
*this = transform(std::move(*this), schema_upgrader(s));
|
||||
}
|
||||
|
||||
@@ -328,6 +328,7 @@ private:
|
||||
flat_mutation_reader() = default;
|
||||
explicit operator bool() const noexcept { return bool(_impl); }
|
||||
friend class optimized_optional<flat_mutation_reader>;
|
||||
void do_upgrade_schema(const schema_ptr&);
|
||||
public:
|
||||
// Documented in mutation_reader::forwarding in mutation_reader.hh.
|
||||
class partition_range_forwarding_tag;
|
||||
@@ -466,6 +467,14 @@ public:
|
||||
void move_buffer_content_to(impl& other) {
|
||||
_impl->move_buffer_content_to(other);
|
||||
}
|
||||
|
||||
// Causes this reader to conform to s.
|
||||
// Multiple calls of upgrade_schema() compose, effects of prior calls on the stream are preserved.
|
||||
void upgrade_schema(const schema_ptr& s) {
|
||||
if (__builtin_expect(s != schema(), false)) {
|
||||
do_upgrade_schema(s);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
using flat_mutation_reader_opt = optimized_optional<flat_mutation_reader>;
|
||||
@@ -568,8 +577,12 @@ class delegating_reader : public flat_mutation_reader::impl {
|
||||
public:
|
||||
delegating_reader(Underlying&& r) : impl(to_reference(r).schema()), _underlying(std::forward<Underlying>(r)) { }
|
||||
virtual future<> fill_buffer(db::timeout_clock::time_point timeout) override {
|
||||
return fill_buffer_from(to_reference(_underlying), timeout).then([this] (bool underlying_finished) {
|
||||
_end_of_stream = underlying_finished;
|
||||
if (is_buffer_full()) {
|
||||
return make_ready_future<>();
|
||||
}
|
||||
return to_reference(_underlying).fill_buffer(timeout).then([this] {
|
||||
_end_of_stream = to_reference(_underlying).is_end_of_stream();
|
||||
to_reference(_underlying).move_buffer_content_to(*this);
|
||||
});
|
||||
}
|
||||
virtual future<> fast_forward_to(position_range pr, db::timeout_clock::time_point timeout) override {
|
||||
@@ -609,6 +622,11 @@ flat_mutation_reader
|
||||
flat_mutation_reader_from_mutations(std::vector<mutation> ms,
|
||||
const query::partition_slice& slice,
|
||||
streamed_mutation::forwarding fwd = streamed_mutation::forwarding::no);
|
||||
flat_mutation_reader
|
||||
flat_mutation_reader_from_mutations(std::vector<mutation> ms,
|
||||
const dht::partition_range& pr,
|
||||
const query::partition_slice& slice,
|
||||
streamed_mutation::forwarding fwd = streamed_mutation::forwarding::no);
|
||||
|
||||
/// Make a reader that enables the wrapped reader to work with multiple ranges.
|
||||
///
|
||||
@@ -642,6 +660,12 @@ make_flat_multi_range_reader(
|
||||
flat_mutation_reader
|
||||
make_flat_mutation_reader_from_fragments(schema_ptr, std::deque<mutation_fragment>);
|
||||
|
||||
flat_mutation_reader
|
||||
make_flat_mutation_reader_from_fragments(schema_ptr, std::deque<mutation_fragment>, const dht::partition_range& pr);
|
||||
|
||||
flat_mutation_reader
|
||||
make_flat_mutation_reader_from_fragments(schema_ptr, std::deque<mutation_fragment>, const dht::partition_range& pr, const query::partition_slice& slice);
|
||||
|
||||
// Calls the consumer for each element of the reader's stream until end of stream
|
||||
// is reached or the consumer requests iteration to stop by returning stop_iteration::yes.
|
||||
// The consumer should accept mutation as the argument and return stop_iteration.
|
||||
|
||||
@@ -483,8 +483,7 @@ future<> gossiper::apply_state_locally(std::map<inet_address, endpoint_state> ma
|
||||
int local_generation = local_ep_state_ptr.get_heart_beat_state().get_generation();
|
||||
int remote_generation = remote_state.get_heart_beat_state().get_generation();
|
||||
logger.trace("{} local generation {}, remote generation {}", ep, local_generation, remote_generation);
|
||||
// A node was removed with nodetool removenode can have a generation of 2
|
||||
if (local_generation > 2 && remote_generation > local_generation + MAX_GENERATION_DIFFERENCE) {
|
||||
if (remote_generation > service::get_generation_number() + MAX_GENERATION_DIFFERENCE) {
|
||||
// assume some peer has corrupted memory and is broadcasting an unbelievable generation about another peer (or itself)
|
||||
logger.warn("received an invalid gossip generation for peer {}; local generation = {}, received generation = {}",
|
||||
ep, local_generation, remote_generation);
|
||||
|
||||
@@ -156,7 +156,9 @@ public:
|
||||
static constexpr std::chrono::milliseconds INTERVAL{1000};
|
||||
static constexpr std::chrono::hours A_VERY_LONG_TIME{24 * 3};
|
||||
|
||||
/** Maximimum difference in generation and version values we are willing to accept about a peer */
|
||||
// Maximimum difference between remote generation value and generation
|
||||
// value this node would get if this node were restarted that we are
|
||||
// willing to accept about a peer.
|
||||
static constexpr int64_t MAX_GENERATION_DIFFERENCE = 86400 * 365;
|
||||
std::chrono::milliseconds fat_client_timeout;
|
||||
|
||||
|
||||
@@ -26,6 +26,6 @@ class partition {
|
||||
|
||||
class reconcilable_result {
|
||||
uint32_t row_count();
|
||||
std::vector<partition> partitions();
|
||||
utils::chunked_vector<partition> partitions();
|
||||
query::short_read is_short_read() [[version 1.6]] = query::short_read::no;
|
||||
};
|
||||
|
||||
@@ -51,4 +51,10 @@ enum class stream_reason : uint8_t {
|
||||
repair,
|
||||
};
|
||||
|
||||
enum class stream_mutation_fragments_cmd : uint8_t {
|
||||
error,
|
||||
mutation_fragment_data,
|
||||
end_of_stream,
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -134,6 +134,11 @@ view_ptr secondary_index_manager::create_view_for_index(const index_metadata& im
|
||||
}
|
||||
builder.with_column(col.name(), col.type, column_kind::clustering_key);
|
||||
}
|
||||
if (index_target->is_primary_key()) {
|
||||
for (auto& def : schema->regular_columns()) {
|
||||
db::view::create_virtual_column(builder, def.name(), def.type);
|
||||
}
|
||||
}
|
||||
const sstring where_clause = sprint("%s IS NOT NULL", cql3::util::maybe_quote(index_target_name));
|
||||
builder.with_view_info(*schema, false, where_clause);
|
||||
return view_ptr{builder.build()};
|
||||
|
||||
5
init.cc
5
init.cc
@@ -44,6 +44,7 @@ void init_storage_service(distributed<database>& db, sharded<auth::service>& aut
|
||||
}
|
||||
|
||||
void init_ms_fd_gossiper(sharded<gms::feature_service>& features
|
||||
, db::config& cfg
|
||||
, sstring listen_address_in
|
||||
, uint16_t storage_port
|
||||
, uint16_t ssl_storage_port
|
||||
@@ -156,6 +157,10 @@ void init_ms_fd_gossiper(sharded<gms::feature_service>& features
|
||||
to_string(seeds), listen_address_in, broadcast_address);
|
||||
throw std::runtime_error("Use broadcast_address for seeds list");
|
||||
}
|
||||
if ((!cfg.replace_address_first_boot().empty() || !cfg.replace_address().empty()) && seeds.count(broadcast_address)) {
|
||||
startlog.error("Bad configuration: replace-address and replace-address-first-boot are not allowed for seed nodes");
|
||||
throw bad_configuration_error();
|
||||
}
|
||||
gms::get_gossiper().start(std::ref(features)).get();
|
||||
auto& gossiper = gms::get_local_gossiper();
|
||||
gossiper.set_seeds(seeds);
|
||||
|
||||
1
init.hh
1
init.hh
@@ -51,6 +51,7 @@ struct init_scheduling_config {
|
||||
};
|
||||
|
||||
void init_ms_fd_gossiper(sharded<gms::feature_service>& features
|
||||
, db::config& config
|
||||
, sstring listen_address
|
||||
, uint16_t storage_port
|
||||
, uint16_t ssl_storage_port
|
||||
|
||||
28
main.cc
28
main.cc
@@ -302,15 +302,7 @@ int main(int ac, char** av) {
|
||||
auto cfg = make_lw_shared<db::config>(ext);
|
||||
auto init = app.get_options_description().add_options();
|
||||
|
||||
// If --version is requested, print it out and exit immediately to avoid
|
||||
// Seastar-specific warnings that may occur when running the app
|
||||
init("version", bpo::bool_switch(), "print version number and exit");
|
||||
bpo::variables_map vm;
|
||||
bpo::store(bpo::command_line_parser(ac, av).options(app.get_options_description()).allow_unregistered().run(), vm);
|
||||
if (vm["version"].as<bool>()) {
|
||||
print("%s\n", scylla_version());
|
||||
return 0;
|
||||
}
|
||||
|
||||
bpo::options_description deprecated("Deprecated options - ignored");
|
||||
deprecated.add_options()
|
||||
@@ -324,6 +316,15 @@ int main(int ac, char** av) {
|
||||
configurable::append_all(*cfg, init);
|
||||
cfg->add_options(init);
|
||||
|
||||
// If --version is requested, print it out and exit immediately to avoid
|
||||
// Seastar-specific warnings that may occur when running the app
|
||||
bpo::variables_map vm;
|
||||
bpo::store(bpo::command_line_parser(ac, av).options(app.get_options_description()).allow_unregistered().run(), vm);
|
||||
if (vm["version"].as<bool>()) {
|
||||
print("%s\n", scylla_version());
|
||||
return 0;
|
||||
}
|
||||
|
||||
distributed<database> db;
|
||||
seastar::sharded<service::cache_hitrate_calculator> cf_cache_hitrate_calculator;
|
||||
debug::db = &db;
|
||||
@@ -483,6 +484,9 @@ int main(int ac, char** av) {
|
||||
if (opts.count("developer-mode")) {
|
||||
smp::invoke_on_all([] { engine().set_strict_dma(false); }).get();
|
||||
}
|
||||
|
||||
set_abort_on_internal_error(cfg->abort_on_internal_error());
|
||||
|
||||
supervisor::notify("creating tracing");
|
||||
tracing::tracing::create_tracing("trace_keyspace_helper").get();
|
||||
supervisor::notify("creating snitch");
|
||||
@@ -605,6 +609,7 @@ int main(int ac, char** av) {
|
||||
scfg.streaming = dbcfg.streaming_scheduling_group;
|
||||
scfg.gossip = scheduling_group();
|
||||
init_ms_fd_gossiper(feature_service
|
||||
, *cfg
|
||||
, listen_address
|
||||
, storage_port
|
||||
, ssl_storage_port
|
||||
@@ -846,8 +851,11 @@ int main(int ac, char** av) {
|
||||
return service::get_local_storage_service().drain_on_shutdown();
|
||||
});
|
||||
|
||||
engine().at_exit([] {
|
||||
return view_builder.stop();
|
||||
engine().at_exit([cfg] {
|
||||
if (cfg->view_building()) {
|
||||
return view_builder.stop();
|
||||
}
|
||||
return make_ready_future<>();
|
||||
});
|
||||
|
||||
engine().at_exit([&db] {
|
||||
|
||||
32
memtable.cc
32
memtable.cc
@@ -24,7 +24,6 @@
|
||||
#include "frozen_mutation.hh"
|
||||
#include "stdx.hh"
|
||||
#include "partition_snapshot_reader.hh"
|
||||
#include "schema_upgrader.hh"
|
||||
#include "partition_builder.hh"
|
||||
|
||||
memtable::memtable(schema_ptr schema, dirty_memory_manager& dmm, memtable_list* memtable_list,
|
||||
@@ -343,11 +342,8 @@ public:
|
||||
bool digest_requested = _slice.options.contains<query::partition_slice::option::with_digest>();
|
||||
auto mpsr = make_partition_snapshot_flat_reader(snp_schema, std::move(key_and_snp->first), std::move(cr),
|
||||
std::move(key_and_snp->second), digest_requested, region(), read_section(), mtbl(), streamed_mutation::forwarding::no);
|
||||
if (snp_schema->version() != schema()->version()) {
|
||||
_delegate = transform(std::move(mpsr), schema_upgrader(schema()));
|
||||
} else {
|
||||
_delegate = std::move(mpsr);
|
||||
}
|
||||
mpsr.upgrade_schema(schema());
|
||||
_delegate = std::move(mpsr);
|
||||
} else {
|
||||
_end_of_stream = true;
|
||||
}
|
||||
@@ -502,11 +498,8 @@ private:
|
||||
auto snp_schema = key_and_snp->second->schema();
|
||||
auto mpsr = make_partition_snapshot_flat_reader<partition_snapshot_accounter>(snp_schema, std::move(key_and_snp->first), std::move(cr),
|
||||
std::move(key_and_snp->second), false, region(), read_section(), mtbl(), streamed_mutation::forwarding::no, *snp_schema, _flushed_memory);
|
||||
if (snp_schema->version() != schema()->version()) {
|
||||
_partition_reader = transform(std::move(mpsr), schema_upgrader(schema()));
|
||||
} else {
|
||||
_partition_reader = std::move(mpsr);
|
||||
}
|
||||
mpsr.upgrade_schema(schema());
|
||||
_partition_reader = std::move(mpsr);
|
||||
}
|
||||
}
|
||||
public:
|
||||
@@ -582,11 +575,8 @@ memtable::make_flat_reader(schema_ptr s,
|
||||
bool digest_requested = slice.options.contains<query::partition_slice::option::with_digest>();
|
||||
auto rd = make_partition_snapshot_flat_reader(snp_schema, std::move(dk), std::move(cr), std::move(snp), digest_requested,
|
||||
*this, _read_section, shared_from_this(), fwd);
|
||||
if (snp_schema->version() != s->version()) {
|
||||
return transform(std::move(rd), schema_upgrader(s));
|
||||
} else {
|
||||
return rd;
|
||||
}
|
||||
rd.upgrade_schema(s);
|
||||
return rd;
|
||||
} else {
|
||||
auto res = make_flat_mutation_reader<scanning_reader>(std::move(s), shared_from_this(), range, slice, pc, fwd_mr);
|
||||
if (fwd == streamed_mutation::forwarding::yes) {
|
||||
@@ -701,13 +691,19 @@ bool memtable::is_flushed() const {
|
||||
return bool(_underlying);
|
||||
}
|
||||
|
||||
void memtable_entry::upgrade_schema(const schema_ptr& s, mutation_cleaner& cleaner) {
|
||||
if (_schema != s) {
|
||||
partition().upgrade(_schema, s, cleaner, no_cache_tracker);
|
||||
_schema = s;
|
||||
}
|
||||
}
|
||||
|
||||
void memtable::upgrade_entry(memtable_entry& e) {
|
||||
if (e._schema != _schema) {
|
||||
assert(!reclaiming_enabled());
|
||||
with_allocator(allocator(), [this, &e] {
|
||||
with_linearized_managed_bytes([&] {
|
||||
e.partition().upgrade(e._schema, _schema, cleaner(), no_cache_tracker);
|
||||
e._schema = _schema;
|
||||
e.upgrade_schema(_schema, cleaner());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -68,6 +68,10 @@ public:
|
||||
schema_ptr& schema() { return _schema; }
|
||||
partition_snapshot_ptr snapshot(memtable& mtbl);
|
||||
|
||||
// Makes the entry conform to given schema.
|
||||
// Must be called under allocating section of the region which owns the entry.
|
||||
void upgrade_schema(const schema_ptr&, mutation_cleaner&);
|
||||
|
||||
size_t external_memory_usage_without_rows() const {
|
||||
return _key.key().external_memory_usage();
|
||||
}
|
||||
|
||||
@@ -87,6 +87,7 @@
|
||||
#include "frozen_mutation.hh"
|
||||
#include "flat_mutation_reader.hh"
|
||||
#include "streaming/stream_manager.hh"
|
||||
#include "streaming/stream_mutation_fragments_cmd.hh"
|
||||
|
||||
namespace netw {
|
||||
|
||||
@@ -281,25 +282,26 @@ void messaging_service::start_listen() {
|
||||
if (_compress_what != compress_what::none) {
|
||||
so.compressor_factory = &compressor_factory;
|
||||
}
|
||||
so.streaming_domain = rpc::streaming_domain_type(0x55AA);
|
||||
// FIXME: we don't set so.tcp_nodelay, because we can't tell at this point whether the connection will come from a
|
||||
// local or remote datacenter, and whether or not the connection will be used for gossip. We can fix
|
||||
// the first by wrapping its server_socket, but not the second.
|
||||
auto limits = rpc_resource_limits(_mcfg.rpc_memory_limit);
|
||||
if (!_server[0]) {
|
||||
auto listen = [&] (const gms::inet_address& a) {
|
||||
auto listen = [&] (const gms::inet_address& a, rpc::streaming_domain_type sdomain) {
|
||||
so.streaming_domain = sdomain;
|
||||
auto addr = ipv4_addr{a.raw_addr(), _port};
|
||||
return std::unique_ptr<rpc_protocol_server_wrapper>(new rpc_protocol_server_wrapper(*_rpc,
|
||||
so, addr, limits));
|
||||
};
|
||||
_server[0] = listen(_listen_address);
|
||||
_server[0] = listen(_listen_address, rpc::streaming_domain_type(0x55AA));
|
||||
if (listen_to_bc) {
|
||||
_server[1] = listen(utils::fb_utilities::get_broadcast_address());
|
||||
_server[1] = listen(utils::fb_utilities::get_broadcast_address(), rpc::streaming_domain_type(0x66BB));
|
||||
}
|
||||
}
|
||||
|
||||
if (!_server_tls[0]) {
|
||||
auto listen = [&] (const gms::inet_address& a) {
|
||||
auto listen = [&] (const gms::inet_address& a, rpc::streaming_domain_type sdomain) {
|
||||
so.streaming_domain = sdomain;
|
||||
return std::unique_ptr<rpc_protocol_server_wrapper>(
|
||||
[this, &so, &a, limits] () -> std::unique_ptr<rpc_protocol_server_wrapper>{
|
||||
if (_encrypt_what == encrypt_what::none) {
|
||||
@@ -312,9 +314,9 @@ void messaging_service::start_listen() {
|
||||
so, seastar::tls::listen(_credentials, addr, lo), limits);
|
||||
}());
|
||||
};
|
||||
_server_tls[0] = listen(_listen_address);
|
||||
_server_tls[0] = listen(_listen_address, rpc::streaming_domain_type(0x77CC));
|
||||
if (listen_to_bc) {
|
||||
_server_tls[1] = listen(utils::fb_utilities::get_broadcast_address());
|
||||
_server_tls[1] = listen(utils::fb_utilities::get_broadcast_address(), rpc::streaming_domain_type(0x88DD));
|
||||
}
|
||||
}
|
||||
// Do this on just cpu 0, to avoid duplicate logs.
|
||||
@@ -592,6 +594,7 @@ shared_ptr<messaging_service::rpc_protocol_client_wrapper> messaging_service::ge
|
||||
opts.compressor_factory = &compressor_factory;
|
||||
}
|
||||
opts.tcp_nodelay = must_tcp_nodelay;
|
||||
opts.reuseaddr = true;
|
||||
|
||||
auto client = must_encrypt ?
|
||||
::make_shared<rpc_protocol_client_wrapper>(*_rpc, std::move(opts),
|
||||
@@ -651,24 +654,27 @@ std::unique_ptr<messaging_service::rpc_protocol_wrapper>& messaging_service::rpc
|
||||
return _rpc;
|
||||
}
|
||||
|
||||
rpc::sink<int32_t> messaging_service::make_sink_for_stream_mutation_fragments(rpc::source<frozen_mutation_fragment>& source) {
|
||||
rpc::sink<int32_t> messaging_service::make_sink_for_stream_mutation_fragments(rpc::source<frozen_mutation_fragment, rpc::optional<streaming::stream_mutation_fragments_cmd>>& source) {
|
||||
return source.make_sink<netw::serializer, int32_t>();
|
||||
}
|
||||
|
||||
future<rpc::sink<frozen_mutation_fragment>, rpc::source<int32_t>>
|
||||
future<rpc::sink<frozen_mutation_fragment, streaming::stream_mutation_fragments_cmd>, rpc::source<int32_t>>
|
||||
messaging_service::make_sink_and_source_for_stream_mutation_fragments(utils::UUID schema_id, utils::UUID plan_id, utils::UUID cf_id, uint64_t estimated_partitions, streaming::stream_reason reason, msg_addr id) {
|
||||
if (is_stopping()) {
|
||||
return make_exception_future<rpc::sink<frozen_mutation_fragment, streaming::stream_mutation_fragments_cmd>, rpc::source<int32_t>>(rpc::closed_error());
|
||||
}
|
||||
auto rpc_client = get_rpc_client(messaging_verb::STREAM_MUTATION_FRAGMENTS, id);
|
||||
return rpc_client->make_stream_sink<netw::serializer, frozen_mutation_fragment>().then([this, plan_id, schema_id, cf_id, estimated_partitions, reason, rpc_client] (rpc::sink<frozen_mutation_fragment> sink) mutable {
|
||||
auto rpc_handler = rpc()->make_client<rpc::source<int32_t> (utils::UUID, utils::UUID, utils::UUID, uint64_t, streaming::stream_reason, rpc::sink<frozen_mutation_fragment>)>(messaging_verb::STREAM_MUTATION_FRAGMENTS);
|
||||
return rpc_client->make_stream_sink<netw::serializer, frozen_mutation_fragment, streaming::stream_mutation_fragments_cmd>().then([this, plan_id, schema_id, cf_id, estimated_partitions, reason, rpc_client] (rpc::sink<frozen_mutation_fragment, streaming::stream_mutation_fragments_cmd> sink) mutable {
|
||||
auto rpc_handler = rpc()->make_client<rpc::source<int32_t> (utils::UUID, utils::UUID, utils::UUID, uint64_t, streaming::stream_reason, rpc::sink<frozen_mutation_fragment, streaming::stream_mutation_fragments_cmd>)>(messaging_verb::STREAM_MUTATION_FRAGMENTS);
|
||||
return rpc_handler(*rpc_client , plan_id, schema_id, cf_id, estimated_partitions, reason, sink).then_wrapped([sink, rpc_client] (future<rpc::source<int32_t>> source) mutable {
|
||||
return (source.failed() ? sink.close() : make_ready_future<>()).then([sink = std::move(sink), source = std::move(source)] () mutable {
|
||||
return make_ready_future<rpc::sink<frozen_mutation_fragment>, rpc::source<int32_t>>(std::move(sink), std::move(source.get0()));
|
||||
return make_ready_future<rpc::sink<frozen_mutation_fragment, streaming::stream_mutation_fragments_cmd>, rpc::source<int32_t>>(std::move(sink), std::move(source.get0()));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void messaging_service::register_stream_mutation_fragments(std::function<future<rpc::sink<int32_t>> (const rpc::client_info& cinfo, UUID plan_id, UUID schema_id, UUID cf_id, uint64_t estimated_partitions, rpc::optional<streaming::stream_reason>, rpc::source<frozen_mutation_fragment> source)>&& func) {
|
||||
void messaging_service::register_stream_mutation_fragments(std::function<future<rpc::sink<int32_t>> (const rpc::client_info& cinfo, UUID plan_id, UUID schema_id, UUID cf_id, uint64_t estimated_partitions, rpc::optional<streaming::stream_reason>, rpc::source<frozen_mutation_fragment, rpc::optional<streaming::stream_mutation_fragments_cmd>> source)>&& func) {
|
||||
register_handler(this, messaging_verb::STREAM_MUTATION_FRAGMENTS, std::move(func));
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
#include "tracing/tracing.hh"
|
||||
#include "digest_algorithm.hh"
|
||||
#include "streaming/stream_reason.hh"
|
||||
#include "streaming/stream_mutation_fragments_cmd.hh"
|
||||
|
||||
#include <seastar/net/tls.hh>
|
||||
|
||||
@@ -256,9 +257,9 @@ public:
|
||||
|
||||
// Wrapper for STREAM_MUTATION_FRAGMENTS
|
||||
// The receiver of STREAM_MUTATION_FRAGMENTS sends status code to the sender to notify any error on the receiver side. The status code is of type int32_t. 0 means successful, -1 means error, other status code value are reserved for future use.
|
||||
void register_stream_mutation_fragments(std::function<future<rpc::sink<int32_t>> (const rpc::client_info& cinfo, UUID plan_id, UUID schema_id, UUID cf_id, uint64_t estimated_partitions, rpc::optional<streaming::stream_reason> reason_opt, rpc::source<frozen_mutation_fragment> source)>&& func);
|
||||
rpc::sink<int32_t> make_sink_for_stream_mutation_fragments(rpc::source<frozen_mutation_fragment>& source);
|
||||
future<rpc::sink<frozen_mutation_fragment>, rpc::source<int32_t>> make_sink_and_source_for_stream_mutation_fragments(utils::UUID schema_id, utils::UUID plan_id, utils::UUID cf_id, uint64_t estimated_partitions, streaming::stream_reason reason, msg_addr id);
|
||||
void register_stream_mutation_fragments(std::function<future<rpc::sink<int32_t>> (const rpc::client_info& cinfo, UUID plan_id, UUID schema_id, UUID cf_id, uint64_t estimated_partitions, rpc::optional<streaming::stream_reason> reason_opt, rpc::source<frozen_mutation_fragment, rpc::optional<streaming::stream_mutation_fragments_cmd>> source)>&& func);
|
||||
rpc::sink<int32_t> make_sink_for_stream_mutation_fragments(rpc::source<frozen_mutation_fragment, rpc::optional<streaming::stream_mutation_fragments_cmd>>& source);
|
||||
future<rpc::sink<frozen_mutation_fragment, streaming::stream_mutation_fragments_cmd>, rpc::source<int32_t>> make_sink_and_source_for_stream_mutation_fragments(utils::UUID schema_id, utils::UUID plan_id, utils::UUID cf_id, uint64_t estimated_partitions, streaming::stream_reason reason, msg_addr id);
|
||||
|
||||
void register_stream_mutation_done(std::function<future<> (const rpc::client_info& cinfo, UUID plan_id, dht::token_range_vector ranges, UUID cf_id, unsigned dst_cpu_id)>&& func);
|
||||
future<> send_stream_mutation_done(msg_addr id, UUID plan_id, dht::token_range_vector ranges, UUID cf_id, unsigned dst_cpu_id);
|
||||
|
||||
@@ -1162,6 +1162,7 @@ row::apply_monotonically(const column_definition& column, atomic_cell_or_collect
|
||||
void
|
||||
row::append_cell(column_id id, atomic_cell_or_collection value) {
|
||||
if (_type == storage_type::vector && id < max_vector_size) {
|
||||
assert(_storage.vector.v.size() <= id);
|
||||
_storage.vector.v.resize(id);
|
||||
_storage.vector.v.emplace_back(cell_and_hash{std::move(value), cell_hash_opt()});
|
||||
_storage.vector.present.set(id);
|
||||
|
||||
@@ -387,7 +387,7 @@ public:
|
||||
if (is_missing() || _ttl == dead) {
|
||||
return false;
|
||||
}
|
||||
if (_ttl != no_ttl && _expiry < now) {
|
||||
if (_ttl != no_ttl && _expiry <= now) {
|
||||
return false;
|
||||
}
|
||||
return _timestamp > t.timestamp;
|
||||
@@ -397,7 +397,7 @@ public:
|
||||
if (_ttl == dead) {
|
||||
return true;
|
||||
}
|
||||
return _ttl != no_ttl && _expiry < now;
|
||||
return _ttl != no_ttl && _expiry <= now;
|
||||
}
|
||||
// Can be called only when is_live().
|
||||
bool is_expiring() const {
|
||||
@@ -435,7 +435,7 @@ public:
|
||||
_timestamp = api::missing_timestamp;
|
||||
return false;
|
||||
}
|
||||
if (_ttl > no_ttl && _expiry < now) {
|
||||
if (_ttl > no_ttl && _expiry <= now) {
|
||||
_expiry -= _ttl;
|
||||
_ttl = dead;
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ reconcilable_result::reconcilable_result()
|
||||
: _row_count(0)
|
||||
{ }
|
||||
|
||||
reconcilable_result::reconcilable_result(uint32_t row_count, std::vector<partition> p, query::short_read short_read,
|
||||
reconcilable_result::reconcilable_result(uint32_t row_count, utils::chunked_vector<partition> p, query::short_read short_read,
|
||||
query::result_memory_tracker memory_tracker)
|
||||
: _row_count(row_count)
|
||||
, _short_read(short_read)
|
||||
@@ -39,11 +39,11 @@ reconcilable_result::reconcilable_result(uint32_t row_count, std::vector<partiti
|
||||
, _partitions(std::move(p))
|
||||
{ }
|
||||
|
||||
const std::vector<partition>& reconcilable_result::partitions() const {
|
||||
const utils::chunked_vector<partition>& reconcilable_result::partitions() const {
|
||||
return _partitions;
|
||||
}
|
||||
|
||||
std::vector<partition>& reconcilable_result::partitions() {
|
||||
utils::chunked_vector<partition>& reconcilable_result::partitions() {
|
||||
return _partitions;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "frozen_mutation.hh"
|
||||
#include "db/timeout_clock.hh"
|
||||
#include "querier.hh"
|
||||
#include "utils/chunked_vector.hh"
|
||||
#include <seastar/core/execution_stage.hh>
|
||||
|
||||
class reconcilable_result;
|
||||
@@ -72,17 +73,17 @@ class reconcilable_result {
|
||||
uint32_t _row_count;
|
||||
query::short_read _short_read;
|
||||
query::result_memory_tracker _memory_tracker;
|
||||
std::vector<partition> _partitions;
|
||||
utils::chunked_vector<partition> _partitions;
|
||||
public:
|
||||
~reconcilable_result();
|
||||
reconcilable_result();
|
||||
reconcilable_result(reconcilable_result&&) = default;
|
||||
reconcilable_result& operator=(reconcilable_result&&) = default;
|
||||
reconcilable_result(uint32_t row_count, std::vector<partition> partitions, query::short_read short_read,
|
||||
reconcilable_result(uint32_t row_count, utils::chunked_vector<partition> partitions, query::short_read short_read,
|
||||
query::result_memory_tracker memory_tracker = { });
|
||||
|
||||
const std::vector<partition>& partitions() const;
|
||||
std::vector<partition>& partitions();
|
||||
const utils::chunked_vector<partition>& partitions() const;
|
||||
utils::chunked_vector<partition>& partitions();
|
||||
|
||||
uint32_t row_count() const {
|
||||
return _row_count;
|
||||
@@ -112,7 +113,7 @@ class reconcilable_result_builder {
|
||||
const schema& _schema;
|
||||
const query::partition_slice& _slice;
|
||||
|
||||
std::vector<partition> _result;
|
||||
utils::chunked_vector<partition> _result;
|
||||
uint32_t _live_rows{};
|
||||
|
||||
bool _has_ck_selector{};
|
||||
|
||||
@@ -764,6 +764,8 @@ class foreign_reader : public flat_mutation_reader::impl {
|
||||
}
|
||||
|
||||
void update_buffer_with(foreign_unique_ptr<fragment_buffer> buffer, bool end_of_steam);
|
||||
|
||||
static future<> ensure_buffer_contains_all_fragments_for_last_pos(flat_mutation_reader& reader, fragment_buffer& buffer);
|
||||
public:
|
||||
foreign_reader(schema_ptr schema,
|
||||
foreign_unique_ptr<flat_mutation_reader> reader,
|
||||
@@ -799,6 +801,39 @@ void foreign_reader::update_buffer_with(foreign_unique_ptr<fragment_buffer> buff
|
||||
}
|
||||
}
|
||||
|
||||
future<> foreign_reader::ensure_buffer_contains_all_fragments_for_last_pos(flat_mutation_reader& reader, fragment_buffer& buffer) {
|
||||
if (buffer.empty() || !buffer.back().is_range_tombstone()) {
|
||||
return make_ready_future<>();
|
||||
}
|
||||
|
||||
auto stop = [&reader, &buffer] {
|
||||
if (reader.is_buffer_empty()) {
|
||||
return reader.is_end_of_stream();
|
||||
}
|
||||
if (!buffer.back().is_range_tombstone()) {
|
||||
return true;
|
||||
}
|
||||
const auto next_pos = reader.peek_buffer().position();
|
||||
const auto& last_key = buffer.back().key();
|
||||
|
||||
// Ending the buffer on a non-full prefix key position is
|
||||
// problematic because when recreating the reader we continue
|
||||
// from *after* the last key we saw. If this is a prefix this
|
||||
// would exclude all clustering positions that fall into the
|
||||
// prefix. Fixing this is non-trivial and has little gain over
|
||||
// just making sure we don't end the buffer on a prefix.
|
||||
return last_key.is_full(*reader.schema()) && !next_pos.key().equal(*reader.schema(), last_key);
|
||||
};
|
||||
|
||||
return do_until(stop, [&reader, &buffer] {
|
||||
if (reader.is_buffer_empty()) {
|
||||
return reader.fill_buffer(db::no_timeout);
|
||||
}
|
||||
buffer.emplace_back(reader.pop_mutation_fragment());
|
||||
return make_ready_future<>();
|
||||
});
|
||||
}
|
||||
|
||||
foreign_reader::foreign_reader(schema_ptr schema,
|
||||
foreign_unique_ptr<flat_mutation_reader> reader,
|
||||
streamed_mutation::forwarding fwd_sm)
|
||||
@@ -896,9 +931,29 @@ future<foreign_ptr<std::unique_ptr<flat_mutation_reader>>> foreign_reader::pause
|
||||
if (pending_next_partition) {
|
||||
reader->next_partition();
|
||||
}
|
||||
return make_ready_future<foreign_unique_ptr<fragment_buffer>, bool>(
|
||||
std::make_unique<fragment_buffer>(reader->detach_buffer()),
|
||||
reader->is_end_of_stream());
|
||||
auto buffer = reader->detach_buffer();
|
||||
if (buffer.empty() || !buffer.back().is_range_tombstone()) {
|
||||
return make_ready_future<foreign_unique_ptr<fragment_buffer>, bool>(
|
||||
std::make_unique<fragment_buffer>(std::move(buffer)),
|
||||
reader->is_end_of_stream());
|
||||
}
|
||||
// When the reader is recreated (after having been evicted) we
|
||||
// recreate it such that it starts reading from *after* the last
|
||||
// seen fragment's position. If the last seen fragment is a range
|
||||
// tombstone it is *not* guaranteed that the next fragments in the
|
||||
// data stream have positions strictly greater than the range
|
||||
// tombstone's. If the reader is evicted and has to be recreated,
|
||||
// these fragments would be then skipped as the read would continue
|
||||
// after their position.
|
||||
// To avoid this ensure that the buffer contains *all* fragments for
|
||||
// the last seen position.
|
||||
return do_with(std::move(buffer), [reader] (fragment_buffer& buffer) mutable {
|
||||
return ensure_buffer_contains_all_fragments_for_last_pos(*reader, buffer).then([reader, &buffer] () mutable {
|
||||
return make_ready_future<foreign_unique_ptr<fragment_buffer>, bool>(
|
||||
std::make_unique<fragment_buffer>(std::move(buffer)),
|
||||
reader->is_end_of_stream() && reader->is_buffer_empty());
|
||||
});
|
||||
});
|
||||
});
|
||||
}).then([this] (foreign_unique_ptr<fragment_buffer>&& buffer, bool end_of_stream) mutable {
|
||||
update_buffer_with(std::move(buffer), end_of_stream);
|
||||
|
||||
@@ -172,6 +172,9 @@ tombstone partition_entry::partition_tombstone() const {
|
||||
|
||||
partition_snapshot::~partition_snapshot() {
|
||||
with_allocator(region().allocator(), [this] {
|
||||
if (_locked) {
|
||||
touch();
|
||||
}
|
||||
if (_version && _version.is_unique_owner()) {
|
||||
auto v = &*_version;
|
||||
_version = {};
|
||||
@@ -268,6 +271,7 @@ partition_entry::~partition_entry() {
|
||||
return;
|
||||
}
|
||||
if (_snapshot) {
|
||||
assert(!_snapshot->is_locked());
|
||||
_snapshot->_version = std::move(_version);
|
||||
_snapshot->_version.mark_as_unique_owner();
|
||||
_snapshot->_entry = nullptr;
|
||||
@@ -284,6 +288,7 @@ stop_iteration partition_entry::clear_gently(cache_tracker* tracker) noexcept {
|
||||
}
|
||||
|
||||
if (_snapshot) {
|
||||
assert(!_snapshot->is_locked());
|
||||
_snapshot->_version = std::move(_version);
|
||||
_snapshot->_version.mark_as_unique_owner();
|
||||
_snapshot->_entry = nullptr;
|
||||
@@ -311,6 +316,7 @@ stop_iteration partition_entry::clear_gently(cache_tracker* tracker) noexcept {
|
||||
void partition_entry::set_version(partition_version* new_version)
|
||||
{
|
||||
if (_snapshot) {
|
||||
assert(!_snapshot->is_locked());
|
||||
_snapshot->_version = std::move(_version);
|
||||
_snapshot->_entry = nullptr;
|
||||
}
|
||||
@@ -459,7 +465,6 @@ public:
|
||||
|
||||
coroutine partition_entry::apply_to_incomplete(const schema& s,
|
||||
partition_entry&& pe,
|
||||
const schema& pe_schema,
|
||||
mutation_cleaner& pe_cleaner,
|
||||
logalloc::allocating_section& alloc,
|
||||
logalloc::region& reg,
|
||||
@@ -479,10 +484,6 @@ coroutine partition_entry::apply_to_incomplete(const schema& s,
|
||||
// partitions where I saw 40% slow down.
|
||||
const bool preemptible = s.clustering_key_size() > 0;
|
||||
|
||||
if (s.version() != pe_schema.version()) {
|
||||
pe.upgrade(pe_schema.shared_from_this(), s.shared_from_this(), pe_cleaner, no_cache_tracker);
|
||||
}
|
||||
|
||||
// When preemptible, later memtable reads could start using the snapshot before
|
||||
// snapshot's writes are made visible in cache, which would cause them to miss those writes.
|
||||
// So we cannot allow erasing when preemptible.
|
||||
@@ -496,6 +497,7 @@ coroutine partition_entry::apply_to_incomplete(const schema& s,
|
||||
prev_snp = read(reg, tracker.cleaner(), s.shared_from_this(), &tracker, phase - 1);
|
||||
}
|
||||
auto dst_snp = read(reg, tracker.cleaner(), s.shared_from_this(), &tracker, phase);
|
||||
dst_snp->lock();
|
||||
|
||||
// Once we start updating the partition, we must keep all snapshots until the update completes,
|
||||
// otherwise partial writes would be published. So the scope of snapshots must enclose the scope
|
||||
@@ -570,6 +572,7 @@ coroutine partition_entry::apply_to_incomplete(const schema& s,
|
||||
auto has_next = src_cur.erase_and_advance();
|
||||
acc.unpin_memory(size);
|
||||
if (!has_next) {
|
||||
dst_snp->unlock();
|
||||
return stop_iteration::yes;
|
||||
}
|
||||
} while (!preemptible || !need_preempt());
|
||||
@@ -661,6 +664,18 @@ partition_snapshot::range_tombstones()
|
||||
position_in_partition_view::after_all_clustered_rows());
|
||||
}
|
||||
|
||||
void partition_snapshot::touch() noexcept {
|
||||
// Eviction assumes that older versions are evicted before newer so only the latest snapshot
|
||||
// can be touched.
|
||||
if (_tracker && at_latest_version()) {
|
||||
auto&& rows = version()->partition().clustered_rows();
|
||||
assert(!rows.empty());
|
||||
rows_entry& last_dummy = *rows.rbegin();
|
||||
assert(last_dummy.is_last_dummy());
|
||||
_tracker->touch(last_dummy);
|
||||
}
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& out, const partition_entry& e) {
|
||||
out << "{";
|
||||
bool first = true;
|
||||
@@ -687,6 +702,7 @@ void partition_entry::evict(mutation_cleaner& cleaner) noexcept {
|
||||
return;
|
||||
}
|
||||
if (_snapshot) {
|
||||
assert(!_snapshot->is_locked());
|
||||
_snapshot->_version = std::move(_version);
|
||||
_snapshot->_version.mark_as_unique_owner();
|
||||
_snapshot->_entry = nullptr;
|
||||
@@ -706,3 +722,18 @@ partition_snapshot_ptr::~partition_snapshot_ptr() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void partition_snapshot::lock() noexcept {
|
||||
// partition_entry::is_locked() assumes that if there is a locked snapshot,
|
||||
// it can be found attached directly to it.
|
||||
assert(at_latest_version());
|
||||
_locked = true;
|
||||
}
|
||||
|
||||
void partition_snapshot::unlock() noexcept {
|
||||
// Locked snapshots must always be latest, is_locked() assumes that.
|
||||
// Also, touch() is only effective when this snapshot is latest.
|
||||
assert(at_latest_version());
|
||||
_locked = false;
|
||||
touch(); // Make the entry evictable again in case it was fully unlinked by eviction attempt.
|
||||
}
|
||||
|
||||
@@ -303,6 +303,7 @@ private:
|
||||
mutation_cleaner* _cleaner;
|
||||
cache_tracker* _tracker;
|
||||
boost::intrusive::slist_member_hook<> _cleaner_hook;
|
||||
bool _locked = false;
|
||||
friend class partition_entry;
|
||||
friend class mutation_cleaner_impl;
|
||||
public:
|
||||
@@ -318,6 +319,22 @@ public:
|
||||
partition_snapshot& operator=(const partition_snapshot&) = delete;
|
||||
partition_snapshot& operator=(partition_snapshot&&) = delete;
|
||||
|
||||
// Makes the snapshot locked.
|
||||
// See is_locked() for meaning.
|
||||
// Can be called only when at_lastest_version(). The snapshot must remain latest as long as it's locked.
|
||||
void lock() noexcept;
|
||||
|
||||
// Makes the snapshot no longer locked.
|
||||
// See is_locked() for meaning.
|
||||
void unlock() noexcept;
|
||||
|
||||
// Tells whether the snapshot is locked.
|
||||
// Locking the snapshot prevents it from getting detached from the partition entry.
|
||||
// It also prevents the partition entry from being evicted.
|
||||
bool is_locked() const {
|
||||
return _locked;
|
||||
}
|
||||
|
||||
static partition_snapshot& container_of(partition_version_ref* ref) {
|
||||
return *boost::intrusive::get_parent_from_member(ref, &partition_snapshot::_version);
|
||||
}
|
||||
@@ -344,6 +361,9 @@ public:
|
||||
// to the latest version.
|
||||
stop_iteration slide_to_oldest() noexcept;
|
||||
|
||||
// Brings the snapshot to the front of the LRU.
|
||||
void touch() noexcept;
|
||||
|
||||
// Must be called after snapshot's original region is merged into a different region
|
||||
// before the original region is destroyed, unless the snapshot is destroyed earlier.
|
||||
void migrate(logalloc::region* region, mutation_cleaner* cleaner) noexcept {
|
||||
@@ -503,9 +523,18 @@ public:
|
||||
return _version->all_elements_reversed();
|
||||
}
|
||||
|
||||
// Tells whether this entry is locked.
|
||||
// Locked entries are undergoing an update and should not have their snapshots
|
||||
// detached from the entry.
|
||||
// Certain methods can only be called when !is_locked().
|
||||
bool is_locked() const {
|
||||
return _snapshot && _snapshot->is_locked();
|
||||
}
|
||||
|
||||
// Strong exception guarantees.
|
||||
// Assumes this instance and mp are fully continuous.
|
||||
// Use only on non-evictable entries.
|
||||
// Must not be called when is_locked().
|
||||
void apply(const schema& s, const mutation_partition& mp, const schema& mp_schema);
|
||||
void apply(const schema& s, mutation_partition&& mp, const schema& mp_schema);
|
||||
|
||||
@@ -526,11 +555,14 @@ public:
|
||||
// such that if the operation is retried (possibly many times) and eventually
|
||||
// succeeds the result will be as if the first attempt didn't fail.
|
||||
//
|
||||
// The schema of pe must conform to s.
|
||||
//
|
||||
// Returns a coroutine object representing the operation.
|
||||
// The coroutine must be resumed with the region being unlocked.
|
||||
//
|
||||
// The coroutine cannot run concurrently with other apply() calls.
|
||||
coroutine apply_to_incomplete(const schema& s,
|
||||
partition_entry&& pe,
|
||||
const schema& pe_schema,
|
||||
mutation_cleaner& pe_cleaner,
|
||||
logalloc::allocating_section&,
|
||||
logalloc::region&,
|
||||
@@ -539,6 +571,7 @@ public:
|
||||
real_dirty_memory_accounter&);
|
||||
|
||||
// If this entry is evictable, cache_tracker must be provided.
|
||||
// Must not be called when is_locked().
|
||||
partition_version& add_version(const schema& s, cache_tracker*);
|
||||
|
||||
// Returns a reference to existing version with an active snapshot of given phase
|
||||
@@ -568,9 +601,11 @@ public:
|
||||
tombstone partition_tombstone() const;
|
||||
|
||||
// needs to be called with reclaiming disabled
|
||||
// Must not be called when is_locked().
|
||||
void upgrade(schema_ptr from, schema_ptr to, mutation_cleaner&, cache_tracker*);
|
||||
|
||||
// Snapshots with different values of phase will point to different partition_version objects.
|
||||
// When is_locked(), read() can only be called with a phase which is <= the phase of the current snapshot.
|
||||
partition_snapshot_ptr read(logalloc::region& region,
|
||||
mutation_cleaner&,
|
||||
schema_ptr entry_schema,
|
||||
|
||||
@@ -151,6 +151,7 @@ public:
|
||||
return {partition_region::clustered, 1, &ck};
|
||||
}
|
||||
|
||||
partition_region region() const { return _type; }
|
||||
bool is_partition_start() const { return _type == partition_region::partition_start; }
|
||||
bool is_partition_end() const { return _type == partition_region::partition_end; }
|
||||
bool is_static_row() const { return _type == partition_region::static_row; }
|
||||
|
||||
@@ -288,11 +288,11 @@ static void insert_querier(
|
||||
|
||||
auto& e = entries.emplace_back(key, std::move(q), expires);
|
||||
e.set_pos(--entries.end());
|
||||
++stats.population;
|
||||
|
||||
if (auto irh = sem.register_inactive_read(std::make_unique<querier_inactive_read>(entries, e.pos(), stats))) {
|
||||
e.set_inactive_handle(irh);
|
||||
index.insert(e);
|
||||
++stats.population;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -191,7 +191,7 @@ public:
|
||||
const dht::decorated_key& key() const { return *_key; }
|
||||
void on_underlying_created() { ++_underlying_created; }
|
||||
bool digest_requested() const { return _slice.options.contains<query::partition_slice::option::with_digest>(); }
|
||||
private:
|
||||
public:
|
||||
future<> ensure_underlying(db::timeout_clock::time_point timeout) {
|
||||
if (_underlying_snapshot) {
|
||||
return create_underlying(true, timeout);
|
||||
@@ -210,18 +210,6 @@ public:
|
||||
_underlying_snapshot = {};
|
||||
_key = dk;
|
||||
}
|
||||
// Fast forwards the underlying streamed_mutation to given range.
|
||||
future<> fast_forward_to(position_range range, db::timeout_clock::time_point timeout) {
|
||||
return ensure_underlying(timeout).then([this, range = std::move(range), timeout] {
|
||||
return _underlying.underlying().fast_forward_to(std::move(range), timeout);
|
||||
});
|
||||
}
|
||||
// Gets the next fragment from the underlying reader
|
||||
future<mutation_fragment_opt> get_next_fragment(db::timeout_clock::time_point timeout) {
|
||||
return ensure_underlying(timeout).then([this, timeout] {
|
||||
return _underlying.underlying()(timeout);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -814,8 +814,10 @@ static future<> repair_cf_range(repair_info& ri,
|
||||
// still do our best to repair available replicas.
|
||||
std::vector<gms::inet_address> live_neighbors;
|
||||
std::vector<partition_checksum> live_neighbors_checksum;
|
||||
bool local_checksum_failed = false;
|
||||
for (unsigned i = 0; i < checksums.size(); i++) {
|
||||
if (checksums[i].failed()) {
|
||||
local_checksum_failed |= (i == 0);
|
||||
rlogger.warn(
|
||||
"Checksum of range {} on {} failed: {}",
|
||||
range,
|
||||
@@ -831,7 +833,7 @@ static future<> repair_cf_range(repair_info& ri,
|
||||
live_neighbors_checksum.push_back(checksums[i].get0());
|
||||
}
|
||||
}
|
||||
if (checksums[0].failed() || live_neighbors.empty()) {
|
||||
if (local_checksum_failed || live_neighbors.empty()) {
|
||||
return make_ready_future<>();
|
||||
}
|
||||
// If one of the available checksums is different, repair
|
||||
|
||||
36
row_cache.cc
36
row_cache.cc
@@ -32,7 +32,6 @@
|
||||
#include <sys/sdt.h>
|
||||
#include "stdx.hh"
|
||||
#include "read_context.hh"
|
||||
#include "schema_upgrader.hh"
|
||||
#include "dirty_memory_manager.hh"
|
||||
#include "cache_flat_mutation_reader.hh"
|
||||
#include "real_dirty_memory_accounter.hh"
|
||||
@@ -350,13 +349,11 @@ future<> read_context::create_underlying(bool skip_first_fragment, db::timeout_c
|
||||
|
||||
static flat_mutation_reader read_directly_from_underlying(read_context& reader) {
|
||||
flat_mutation_reader res = make_delegating_reader(reader.underlying().underlying());
|
||||
if (reader.schema()->version() != reader.underlying().underlying().schema()->version()) {
|
||||
res = transform(std::move(res), schema_upgrader(reader.schema()));
|
||||
}
|
||||
if (reader.fwd() == streamed_mutation::forwarding::no) {
|
||||
res = make_nonforwardable(std::move(res), true);
|
||||
}
|
||||
return std::move(res);
|
||||
res.upgrade_schema(reader.schema());
|
||||
return res;
|
||||
}
|
||||
|
||||
// Reader which populates the cache using data from the delegate.
|
||||
@@ -947,7 +944,6 @@ future<> row_cache::do_update(external_updater eu, memtable& m, Updater updater)
|
||||
});
|
||||
|
||||
return seastar::async([this, &m, updater = std::move(updater), real_dirty_acc = std::move(real_dirty_acc)] () mutable {
|
||||
coroutine update;
|
||||
size_t size_entry;
|
||||
// In case updater fails, we must bring the cache to consistency without deferring.
|
||||
auto cleanup = defer([&m, this] {
|
||||
@@ -955,6 +951,7 @@ future<> row_cache::do_update(external_updater eu, memtable& m, Updater updater)
|
||||
_prev_snapshot_pos = {};
|
||||
_prev_snapshot = {};
|
||||
});
|
||||
coroutine update; // Destroy before cleanup to release snapshots before invalidating.
|
||||
partition_presence_checker is_present = _prev_snapshot->make_partition_presence_checker();
|
||||
while (!m.partitions.empty()) {
|
||||
with_allocator(_tracker.allocator(), [&] () {
|
||||
@@ -1026,8 +1023,10 @@ future<> row_cache::update(external_updater eu, memtable& m) {
|
||||
if (cache_i != partitions_end() && cache_i->key().equal(*_schema, mem_e.key())) {
|
||||
cache_entry& entry = *cache_i;
|
||||
upgrade_entry(entry);
|
||||
assert(entry._schema == _schema);
|
||||
_tracker.on_partition_merge();
|
||||
return entry.partition().apply_to_incomplete(*_schema, std::move(mem_e.partition()), *mem_e.schema(), _tracker.memtable_cleaner(),
|
||||
mem_e.upgrade_schema(_schema, _tracker.memtable_cleaner());
|
||||
return entry.partition().apply_to_incomplete(*_schema, std::move(mem_e.partition()), _tracker.memtable_cleaner(),
|
||||
alloc, _tracker.region(), _tracker, _underlying_phase, acc);
|
||||
} else if (cache_i->continuous()
|
||||
|| with_allocator(standard_allocator(), [&] { return is_present(mem_e.key()); })
|
||||
@@ -1039,7 +1038,8 @@ future<> row_cache::update(external_updater eu, memtable& m) {
|
||||
entry->set_continuous(cache_i->continuous());
|
||||
_tracker.insert(*entry);
|
||||
_partitions.insert_before(cache_i, *entry);
|
||||
return entry->partition().apply_to_incomplete(*_schema, std::move(mem_e.partition()), *mem_e.schema(), _tracker.memtable_cleaner(),
|
||||
mem_e.upgrade_schema(_schema, _tracker.memtable_cleaner());
|
||||
return entry->partition().apply_to_incomplete(*_schema, std::move(mem_e.partition()), _tracker.memtable_cleaner(),
|
||||
alloc, _tracker.region(), _tracker, _underlying_phase, acc);
|
||||
} else {
|
||||
return make_empty_coroutine();
|
||||
@@ -1136,8 +1136,8 @@ future<> row_cache::invalidate(external_updater eu, dht::partition_range_vector&
|
||||
});
|
||||
}
|
||||
|
||||
void row_cache::evict(const dht::partition_range& range) {
|
||||
invalidate_unwrapped(range);
|
||||
void row_cache::evict() {
|
||||
while (_tracker.region().evict_some() == memory::reclaiming_result::reclaimed_something) {}
|
||||
}
|
||||
|
||||
void row_cache::invalidate_unwrapped(const dht::partition_range& range) {
|
||||
@@ -1224,8 +1224,11 @@ void rows_entry::on_evicted(cache_tracker& tracker) noexcept {
|
||||
partition_version& pv = partition_version::container_of(mutation_partition::container_of(
|
||||
mutation_partition::rows_type::container_of_only_member(*it)));
|
||||
if (pv.is_referenced_from_entry()) {
|
||||
cache_entry& ce = cache_entry::container_of(partition_entry::container_of(pv));
|
||||
ce.on_evicted(tracker);
|
||||
partition_entry& pe = partition_entry::container_of(pv);
|
||||
if (!pe.is_locked()) {
|
||||
cache_entry& ce = cache_entry::container_of(pe);
|
||||
ce.on_evicted(tracker);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1246,13 +1249,12 @@ flat_mutation_reader cache_entry::do_read(row_cache& rc, read_context& reader) {
|
||||
auto snp = _pe.read(rc._tracker.region(), rc._tracker.cleaner(), _schema, &rc._tracker, reader.phase());
|
||||
auto ckr = query::clustering_key_filter_ranges::get_ranges(*_schema, reader.slice(), _key.key());
|
||||
auto r = make_cache_flat_mutation_reader(_schema, _key, std::move(ckr), rc, reader.shared_from_this(), std::move(snp));
|
||||
if (reader.schema()->version() != _schema->version()) {
|
||||
r = transform(std::move(r), schema_upgrader(reader.schema()));
|
||||
}
|
||||
if (reader.fwd() == streamed_mutation::forwarding::yes) {
|
||||
r = make_forwardable(std::move(r));
|
||||
}
|
||||
return std::move(r);
|
||||
r.upgrade_schema(rc.schema());
|
||||
r.upgrade_schema(reader.schema());
|
||||
return r;
|
||||
}
|
||||
|
||||
const schema_ptr& row_cache::schema() const {
|
||||
@@ -1260,7 +1262,7 @@ const schema_ptr& row_cache::schema() const {
|
||||
}
|
||||
|
||||
void row_cache::upgrade_entry(cache_entry& e) {
|
||||
if (e._schema != _schema) {
|
||||
if (e._schema != _schema && !e.partition().is_locked()) {
|
||||
auto& r = _tracker.region();
|
||||
assert(!r.reclaiming_enabled());
|
||||
with_allocator(r.allocator(), [this, &e] {
|
||||
|
||||
@@ -549,12 +549,12 @@ public:
|
||||
future<> invalidate(external_updater, const dht::partition_range& = query::full_partition_range);
|
||||
future<> invalidate(external_updater, dht::partition_range_vector&&);
|
||||
|
||||
// Evicts entries from given range in cache.
|
||||
// Evicts entries from cache.
|
||||
//
|
||||
// Note that this does not synchronize with the underlying source,
|
||||
// it is assumed that the underlying source didn't change.
|
||||
// If it did, use invalidate() instead.
|
||||
void evict(const dht::partition_range& = query::full_partition_range);
|
||||
void evict();
|
||||
|
||||
size_t partitions() const {
|
||||
return _partitions.size();
|
||||
|
||||
2
seastar
2
seastar
Submodule seastar updated: f541231a30...083dc0875e
@@ -28,11 +28,25 @@
|
||||
namespace service {
|
||||
|
||||
class cache_hitrate_calculator : public seastar::async_sharded_service<cache_hitrate_calculator> {
|
||||
struct stat {
|
||||
float h = 0;
|
||||
float m = 0;
|
||||
stat& operator+=(stat& o) {
|
||||
h += o.h;
|
||||
m += o.m;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
seastar::sharded<database>& _db;
|
||||
seastar::sharded<cache_hitrate_calculator>& _me;
|
||||
timer<lowres_clock> _timer;
|
||||
bool _stopped = false;
|
||||
float _diff = 0;
|
||||
std::unordered_map<utils::UUID, stat> _rates;
|
||||
size_t _slen = 0;
|
||||
std::string _gstate;
|
||||
future<> _done = make_ready_future();
|
||||
|
||||
future<lowres_clock::duration> recalculate_hitrates();
|
||||
void recalculate_timer();
|
||||
|
||||
@@ -181,7 +181,7 @@ future<> service::client_state::has_access(const sstring& ks, auth::permission p
|
||||
for (auto cf : { db::system_keyspace::LOCAL, db::system_keyspace::PEERS }) {
|
||||
tmp.insert(auth::make_data_resource(db::system_keyspace::NAME, cf));
|
||||
}
|
||||
for (auto cf : db::schema_tables::ALL) {
|
||||
for (auto cf : db::schema_tables::all_table_names()) {
|
||||
tmp.insert(auth::make_data_resource(db::schema_tables::NAME, cf));
|
||||
}
|
||||
return tmp;
|
||||
|
||||
@@ -533,6 +533,10 @@ future<> migration_manager::announce_new_column_family(schema_ptr cfm, api::time
|
||||
if (db.has_schema(cfm->ks_name(), cfm->cf_name())) {
|
||||
throw exceptions::already_exists_exception(cfm->ks_name(), cfm->cf_name());
|
||||
}
|
||||
if (db.column_family_exists(cfm->id())) {
|
||||
throw exceptions::invalid_request_exception(sprint("Table with ID %s already exists: %s", cfm->id(), db.find_schema(cfm->id())));
|
||||
}
|
||||
|
||||
mlogger.info("Create new ColumnFamily: {}", cfm);
|
||||
return db::schema_tables::make_create_table_mutations(keyspace.metadata(), cfm, timestamp)
|
||||
.then([announce_locally, this] (auto&& mutations) {
|
||||
|
||||
@@ -92,7 +92,7 @@ cache_hitrate_calculator::cache_hitrate_calculator(seastar::sharded<database>& d
|
||||
{}
|
||||
|
||||
void cache_hitrate_calculator::recalculate_timer() {
|
||||
recalculate_hitrates().then_wrapped([p = shared_from_this()] (future<lowres_clock::duration> f) {
|
||||
_done = recalculate_hitrates().then_wrapped([p = shared_from_this()] (future<lowres_clock::duration> f) {
|
||||
lowres_clock::duration d;
|
||||
if (f.failed()) {
|
||||
d = std::chrono::milliseconds(2000);
|
||||
@@ -112,21 +112,11 @@ void cache_hitrate_calculator::run_on(size_t master, lowres_clock::duration d) {
|
||||
}
|
||||
|
||||
future<lowres_clock::duration> cache_hitrate_calculator::recalculate_hitrates() {
|
||||
struct stat {
|
||||
float h = 0;
|
||||
float m = 0;
|
||||
stat& operator+=(stat& o) {
|
||||
h += o.h;
|
||||
m += o.m;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
static auto non_system_filter = [&] (const std::pair<utils::UUID, lw_shared_ptr<column_family>>& cf) {
|
||||
auto non_system_filter = [&] (const std::pair<utils::UUID, lw_shared_ptr<column_family>>& cf) {
|
||||
return _db.local().find_keyspace(cf.second->schema()->ks_name()).get_replication_strategy().get_type() != locator::replication_strategy_type::local;
|
||||
};
|
||||
|
||||
auto cf_to_cache_hit_stats = [] (database& db) {
|
||||
auto cf_to_cache_hit_stats = [non_system_filter] (database& db) {
|
||||
return boost::copy_range<std::unordered_map<utils::UUID, stat>>(db.get_column_families() | boost::adaptors::filtered(non_system_filter) |
|
||||
boost::adaptors::transformed([] (const std::pair<utils::UUID, lw_shared_ptr<column_family>>& cf) {
|
||||
auto& stats = cf.second->get_row_cache().stats();
|
||||
@@ -141,17 +131,20 @@ future<lowres_clock::duration> cache_hitrate_calculator::recalculate_hitrates()
|
||||
return std::move(a);
|
||||
};
|
||||
|
||||
return _db.map_reduce0(cf_to_cache_hit_stats, std::unordered_map<utils::UUID, stat>(), sum_stats_per_cf).then([this] (std::unordered_map<utils::UUID, stat> rates) mutable {
|
||||
return _db.map_reduce0(cf_to_cache_hit_stats, std::unordered_map<utils::UUID, stat>(), sum_stats_per_cf).then([this, non_system_filter] (std::unordered_map<utils::UUID, stat> rates) mutable {
|
||||
_diff = 0;
|
||||
_gstate.reserve(_slen); // assume length did not change from previous iteration
|
||||
_slen = 0;
|
||||
_rates = std::move(rates);
|
||||
// set calculated rates on all shards
|
||||
return _db.invoke_on_all([this, rates = std::move(rates), cpuid = engine().cpu_id()] (database& db) {
|
||||
sstring gstate;
|
||||
for (auto& cf : db.get_column_families() | boost::adaptors::filtered(non_system_filter)) {
|
||||
auto it = rates.find(cf.first);
|
||||
if (it == rates.end()) { // a table may be added before map/reduce compltes and this code runs
|
||||
continue;
|
||||
return _db.invoke_on_all([this, cpuid = engine().cpu_id(), non_system_filter] (database& db) {
|
||||
return do_for_each(_rates, [this, cpuid, &db] (auto&& r) mutable {
|
||||
auto it = db.get_column_families().find(r.first);
|
||||
if (it == db.get_column_families().end()) { // a table may be added before map/reduce completes and this code runs
|
||||
return;
|
||||
}
|
||||
stat s = it->second;
|
||||
auto& cf = *it;
|
||||
stat& s = r.second;
|
||||
float rate = 0;
|
||||
if (s.h) {
|
||||
rate = s.h / (s.h + s.m);
|
||||
@@ -159,31 +152,33 @@ future<lowres_clock::duration> cache_hitrate_calculator::recalculate_hitrates()
|
||||
if (engine().cpu_id() == cpuid) {
|
||||
// calculate max difference between old rate and new one for all cfs
|
||||
_diff = std::max(_diff, std::abs(float(cf.second->get_global_cache_hit_rate()) - rate));
|
||||
gstate += sprint("%s.%s:%f;", cf.second->schema()->ks_name(), cf.second->schema()->cf_name(), rate);
|
||||
_gstate += sprint("%s.%s:%.6f;", cf.second->schema()->ks_name(), cf.second->schema()->cf_name(), rate);
|
||||
}
|
||||
cf.second->set_global_cache_hit_rate(cache_temperature(rate));
|
||||
}
|
||||
if (gstate.size()) {
|
||||
auto& g = gms::get_local_gossiper();
|
||||
auto& ss = get_local_storage_service();
|
||||
return g.add_local_application_state(gms::application_state::CACHE_HITRATES, ss.value_factory.cache_hitrates(std::move(gstate)));
|
||||
}
|
||||
return make_ready_future<>();
|
||||
});
|
||||
});
|
||||
}).then([this] {
|
||||
// if max difference during this round is big schedule next recalculate earlier
|
||||
if (_diff < 0.01) {
|
||||
return std::chrono::milliseconds(2000);
|
||||
} else {
|
||||
return std::chrono::milliseconds(500);
|
||||
}
|
||||
auto& g = gms::get_local_gossiper();
|
||||
auto& ss = get_local_storage_service();
|
||||
_slen = _gstate.size();
|
||||
return g.add_local_application_state(gms::application_state::CACHE_HITRATES, ss.value_factory.cache_hitrates(_gstate)).then([this] {
|
||||
// if max difference during this round is big schedule next recalculate earlier
|
||||
if (_diff < 0.01) {
|
||||
return std::chrono::milliseconds(2000);
|
||||
} else {
|
||||
return std::chrono::milliseconds(500);
|
||||
}
|
||||
});
|
||||
}).finally([this] {
|
||||
_gstate = std::string(); // free memory, do not trust clear() to do that for string
|
||||
_rates.clear();
|
||||
});
|
||||
}
|
||||
|
||||
future<> cache_hitrate_calculator::stop() {
|
||||
_timer.cancel();
|
||||
_stopped = true;
|
||||
return make_ready_future<>();
|
||||
return std::move(_done);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1447,6 +1447,22 @@ future<> storage_proxy::mutate_begin(std::vector<unique_response_handler> ids, d
|
||||
stdx::optional<clock_type::time_point> timeout_opt) {
|
||||
return parallel_for_each(ids, [this, cl, timeout_opt] (unique_response_handler& protected_response) {
|
||||
auto response_id = protected_response.id;
|
||||
// This function, mutate_begin(), is called after a preemption point
|
||||
// so it's possible that other code besides our caller just ran. In
|
||||
// particular, Scylla may have noticed that a remote node went down,
|
||||
// called storage_proxy::on_down(), and removed some of the ongoing
|
||||
// handlers, including this id. If this happens, we need to ignore
|
||||
// this id - not try to look it up or start a send.
|
||||
if (_response_handlers.find(response_id) == _response_handlers.end()) {
|
||||
protected_response.release(); // Don't try to remove this id again
|
||||
// Requests that time-out normally below after response_wait()
|
||||
// result in an exception (see ~abstract_write_response_handler())
|
||||
// However, here we no longer have the handler or its information
|
||||
// to put in the exception. The exception is not needed for
|
||||
// correctness (e.g., hints are written by timeout_cb(), not
|
||||
// because of an exception here).
|
||||
return make_exception_future<>(std::runtime_error("unstarted write cancelled"));
|
||||
}
|
||||
// it is better to send first and hint afterwards to reduce latency
|
||||
// but request may complete before hint_to_dead_endpoints() is called and
|
||||
// response_id handler will be removed, so we will have to do hint with separate
|
||||
@@ -2735,8 +2751,8 @@ public:
|
||||
|
||||
// build reconcilable_result from reconciled data
|
||||
// traverse backwards since large keys are at the start
|
||||
std::vector<partition> vec;
|
||||
auto r = boost::accumulate(reconciled_partitions | boost::adaptors::reversed, std::ref(vec), [] (std::vector<partition>& a, const mutation_and_live_row_count& m_a_rc) {
|
||||
utils::chunked_vector<partition> vec;
|
||||
auto r = boost::accumulate(reconciled_partitions | boost::adaptors::reversed, std::ref(vec), [] (utils::chunked_vector<partition>& a, const mutation_and_live_row_count& m_a_rc) {
|
||||
a.emplace_back(partition(m_a_rc.live_row_count, freeze(m_a_rc.mut)));
|
||||
return std::ref(a);
|
||||
});
|
||||
|
||||
@@ -409,6 +409,14 @@ void storage_service::prepare_to_join(std::vector<inet_address> loaded_endpoints
|
||||
}
|
||||
}
|
||||
|
||||
// If this is a restarting node, we should update tokens before gossip starts
|
||||
auto my_tokens = db::system_keyspace::get_saved_tokens().get0();
|
||||
bool restarting_normal_node = db::system_keyspace::bootstrap_complete() && !db().local().is_replacing() && !my_tokens.empty();
|
||||
if (restarting_normal_node) {
|
||||
slogger.info("Restarting a node in NORMAL status");
|
||||
_token_metadata.update_normal_tokens(my_tokens, get_broadcast_address());
|
||||
}
|
||||
|
||||
// have to start the gossip service before we can see any info on other nodes. this is necessary
|
||||
// for bootstrap to get the load info it needs.
|
||||
// (we won't be part of the storage ring though until we add a counterId to our state, below.)
|
||||
@@ -419,6 +427,12 @@ void storage_service::prepare_to_join(std::vector<inet_address> loaded_endpoints
|
||||
}).get();
|
||||
auto features = get_config_supported_features();
|
||||
_token_metadata.update_host_id(local_host_id, get_broadcast_address());
|
||||
|
||||
// Replicate the tokens early because once gossip runs other nodes
|
||||
// might send reads/writes to this node. Replicate it early to make
|
||||
// sure the tokens are valid on all the shards.
|
||||
replicate_to_all_cores().get();
|
||||
|
||||
auto broadcast_rpc_address = utils::fb_utilities::get_broadcast_rpc_address();
|
||||
app_states.emplace(gms::application_state::NET_VERSION, value_factory.network_version());
|
||||
app_states.emplace(gms::application_state::HOST_ID, value_factory.host_id(local_host_id));
|
||||
@@ -429,6 +443,10 @@ void storage_service::prepare_to_join(std::vector<inet_address> loaded_endpoints
|
||||
app_states.emplace(gms::application_state::SCHEMA_TABLES_VERSION, versioned_value(db::schema_tables::version));
|
||||
app_states.emplace(gms::application_state::RPC_READY, value_factory.cql_ready(false));
|
||||
app_states.emplace(gms::application_state::VIEW_BACKLOG, versioned_value(""));
|
||||
if (restarting_normal_node) {
|
||||
app_states.emplace(gms::application_state::TOKENS, value_factory.tokens(my_tokens));
|
||||
app_states.emplace(gms::application_state::STATUS, value_factory.normal(my_tokens));
|
||||
}
|
||||
slogger.info("Starting up server gossip");
|
||||
|
||||
auto& gossiper = gms::get_local_gossiper();
|
||||
@@ -697,6 +715,7 @@ void storage_service::bootstrap(std::unordered_set<token> tokens) {
|
||||
} else {
|
||||
// Dont set any state for the node which is bootstrapping the existing token...
|
||||
_token_metadata.update_normal_tokens(tokens, get_broadcast_address());
|
||||
replicate_to_all_cores().get();
|
||||
auto replace_addr = db().local().get_replace_address();
|
||||
if (replace_addr) {
|
||||
slogger.debug("Removing replaced endpoint {} from system.peers", *replace_addr);
|
||||
@@ -1473,6 +1492,7 @@ future<> storage_service::init_server(int delay, bind_messaging_port do_bind) {
|
||||
auto tokens = db::system_keyspace::get_saved_tokens().get0();
|
||||
if (!tokens.empty()) {
|
||||
_token_metadata.update_normal_tokens(tokens, get_broadcast_address());
|
||||
replicate_to_all_cores().get();
|
||||
// order is important here, the gossiper can fire in between adding these two states. It's ok to send TOKENS without STATUS, but *not* vice versa.
|
||||
gossiper.add_local_application_state({
|
||||
{ gms::application_state::TOKENS, value_factory.tokens(tokens) },
|
||||
|
||||
@@ -66,6 +66,14 @@ public:
|
||||
_cm->deregister_compacting_sstables(_compacting);
|
||||
}
|
||||
}
|
||||
|
||||
// Explicitly release compacting sstables
|
||||
void release_compacting(const std::vector<sstables::shared_sstable>& sstables) {
|
||||
_cm->deregister_compacting_sstables(sstables);
|
||||
for (auto& sst : sstables) {
|
||||
_compacting.erase(boost::remove(_compacting, sst), _compacting.end());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
compaction_weight_registration::compaction_weight_registration(compaction_manager* cm, int weight)
|
||||
@@ -564,18 +572,24 @@ future<> compaction_manager::perform_cleanup(column_family* cf) {
|
||||
return make_ready_future<stop_iteration>(stop_iteration::yes);
|
||||
}
|
||||
column_family& cf = *task->compacting_cf;
|
||||
sstables::compaction_descriptor descriptor = sstables::compaction_descriptor(get_candidates(cf));
|
||||
auto compacting = compacting_sstable_registration(this, descriptor.sstables);
|
||||
auto sstables = get_candidates(cf);
|
||||
auto compacting = make_lw_shared<compacting_sstable_registration>(this, sstables);
|
||||
|
||||
_stats.pending_tasks--;
|
||||
_stats.active_tasks++;
|
||||
task->compaction_running = true;
|
||||
compaction_backlog_tracker user_initiated(std::make_unique<user_initiated_backlog_tracker>(_compaction_controller.backlog_of_shares(200), _available_memory));
|
||||
return do_with(std::move(user_initiated), [this, &cf, descriptor = std::move(descriptor)] (compaction_backlog_tracker& bt) mutable {
|
||||
return with_scheduling_group(_scheduling_group, [this, &cf, descriptor = std::move(descriptor)] () mutable {
|
||||
return cf.cleanup_sstables(std::move(descriptor));
|
||||
return do_with(std::move(user_initiated), std::move(sstables), [this, &cf, compacting] (compaction_backlog_tracker& bt,
|
||||
std::vector<sstables::shared_sstable>& sstables) mutable {
|
||||
return with_scheduling_group(_scheduling_group, [this, &cf, &sstables, compacting] () mutable {
|
||||
return do_for_each(sstables, [this, &cf, compacting] (auto& sst) {
|
||||
return cf.cleanup_sstables(sstables::compaction_descriptor({sst})).then([&sst, compacting] {
|
||||
// Releases reference to cleaned sstable such that respective used disk space can be freed.
|
||||
compacting->release_compacting({std::move(sst)});
|
||||
});
|
||||
});
|
||||
});
|
||||
}).then_wrapped([this, task, compacting = std::move(compacting)] (future<> f) mutable {
|
||||
}).then_wrapped([this, task, compacting] (future<> f) mutable {
|
||||
task->compaction_running = false;
|
||||
_stats.active_tasks--;
|
||||
if (!can_proceed(task)) {
|
||||
|
||||
@@ -170,7 +170,10 @@ public:
|
||||
_sstables.push_back(std::move(sst));
|
||||
}
|
||||
virtual void erase(shared_sstable sst) override {
|
||||
_sstables.erase(boost::range::find(_sstables, sst));
|
||||
auto it = boost::range::find(_sstables, sst);
|
||||
if (it != _sstables.end()){
|
||||
_sstables.erase(it);
|
||||
}
|
||||
}
|
||||
virtual std::unique_ptr<incremental_selector_impl> make_incremental_selector() const override;
|
||||
class incremental_selector;
|
||||
@@ -420,11 +423,6 @@ public:
|
||||
auto itw = writes_per_window.find(bound);
|
||||
if (itw != writes_per_window.end()) {
|
||||
ow_this_window = &itw->second;
|
||||
// We will erase here so we can keep track of which
|
||||
// writes belong to existing windows. Writes that don't belong to any window
|
||||
// are writes in progress to new windows and will be accounted in the final
|
||||
// loop before we return
|
||||
writes_per_window.erase(itw);
|
||||
}
|
||||
auto* oc_this_window = &no_oc;
|
||||
auto itc = compactions_per_window.find(bound);
|
||||
@@ -432,6 +430,13 @@ public:
|
||||
oc_this_window = &itc->second;
|
||||
}
|
||||
b += windows.second.backlog(*ow_this_window, *oc_this_window);
|
||||
if (itw != writes_per_window.end()) {
|
||||
// We will erase here so we can keep track of which
|
||||
// writes belong to existing windows. Writes that don't belong to any window
|
||||
// are writes in progress to new windows and will be accounted in the final
|
||||
// loop before we return
|
||||
writes_per_window.erase(itw);
|
||||
}
|
||||
}
|
||||
|
||||
// Partial writes that don't belong to any window are accounted here.
|
||||
|
||||
@@ -390,9 +390,17 @@ private:
|
||||
}
|
||||
|
||||
return do_with(std::make_unique<reader>(_sstable, _pc, position, end, quantity), [this, summary_idx] (auto& entries_reader) {
|
||||
return entries_reader->_context.consume_input().then([this, summary_idx, &entries_reader] {
|
||||
return entries_reader->_context.consume_input().then_wrapped([this, summary_idx, &entries_reader] (future<> f) {
|
||||
std::exception_ptr ex;
|
||||
if (f.failed()) {
|
||||
ex = f.get_exception();
|
||||
sstlog.error("failed reading index for {}: {}", _sstable->get_filename(), ex);
|
||||
}
|
||||
auto indexes = std::move(entries_reader->_consumer.indexes);
|
||||
return entries_reader->_context.close().then([indexes = std::move(indexes)] () mutable {
|
||||
return entries_reader->_context.close().then([indexes = std::move(indexes), ex = std::move(ex)] () mutable {
|
||||
if (ex) {
|
||||
std::rethrow_exception(std::move(ex));
|
||||
}
|
||||
return std::move(indexes);
|
||||
});
|
||||
|
||||
|
||||
@@ -72,8 +72,11 @@ inline gc_clock::duration parse_ttl(int32_t value) {
|
||||
|
||||
inline gc_clock::duration parse_ttl(const serialization_header& header,
|
||||
uint64_t delta) {
|
||||
int32_t _delta = static_cast<int32_t>(delta);
|
||||
return parse_ttl(header.get_min_ttl() + _delta);
|
||||
// sign-extend min_ttl back to 64 bits and
|
||||
// add the delta using unsigned arithmetic
|
||||
// to prevent signed integer overflow
|
||||
uint64_t min_ttl = static_cast<uint64_t>(static_cast<int64_t>(header.get_min_ttl()));
|
||||
return parse_ttl(static_cast<int32_t>(min_ttl + delta));
|
||||
}
|
||||
|
||||
inline gc_clock::time_point parse_expiry(int32_t value) {
|
||||
@@ -85,8 +88,11 @@ inline gc_clock::time_point parse_expiry(int32_t value) {
|
||||
|
||||
inline gc_clock::time_point parse_expiry(const serialization_header& header,
|
||||
uint64_t delta) {
|
||||
int32_t _delta = static_cast<int32_t>(delta);
|
||||
return parse_expiry(header.get_min_local_deletion_time() + _delta);
|
||||
// sign-extend min_local_deletion_time back to 64 bits and
|
||||
// add the delta using unsigned arithmetic
|
||||
// to prevent signed integer overflow
|
||||
uint64_t min_local_deletion_time = static_cast<uint64_t>(static_cast<int64_t>(header.get_min_local_deletion_time()));
|
||||
return parse_expiry(static_cast<int32_t>(min_local_deletion_time + delta));
|
||||
}
|
||||
|
||||
}; // namespace sstables
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include "sstables/mc/types.hh"
|
||||
#include "db/config.hh"
|
||||
#include "atomic_cell.hh"
|
||||
#include "utils/exceptions.hh"
|
||||
|
||||
#include <functional>
|
||||
#include <boost/iterator/iterator_facade.hpp>
|
||||
@@ -308,9 +309,11 @@ void write_missing_columns(W& out, const indexed_columns& columns, const row& ro
|
||||
template <typename T, typename W>
|
||||
GCC6_CONCEPT(requires Writer<W>())
|
||||
void write_unsigned_delta_vint(W& out, T value, T base) {
|
||||
using unsigned_type = std::make_unsigned_t<T>;
|
||||
unsigned_type unsigned_delta = static_cast<unsigned_type>(value) - static_cast<unsigned_type>(base);
|
||||
// sign-extend to 64-bits
|
||||
using signed_type = std::make_signed_t<T>;
|
||||
int64_t delta = static_cast<signed_type>(value) - static_cast<signed_type>(base);
|
||||
int64_t delta = static_cast<int64_t>(static_cast<signed_type>(unsigned_delta));
|
||||
// write as unsigned 64-bit varint
|
||||
write_vint(out, static_cast<uint64_t>(delta));
|
||||
}
|
||||
@@ -380,9 +383,11 @@ sstable_schema make_sstable_schema(const schema& s, const encoding_stats& enc_st
|
||||
sstable_schema sst_sch;
|
||||
serialization_header& header = sst_sch.header;
|
||||
// mc serialization header minimum values are delta-encoded based on the default timestamp epoch times
|
||||
header.min_timestamp_base.value = static_cast<uint64_t>(enc_stats.min_timestamp - encoding_stats::timestamp_epoch);
|
||||
header.min_local_deletion_time_base.value = static_cast<uint64_t>(enc_stats.min_local_deletion_time - encoding_stats::deletion_time_epoch);
|
||||
header.min_ttl_base.value = static_cast<uint64_t>(enc_stats.min_ttl - encoding_stats::ttl_epoch);
|
||||
// Note: We rely on implicit conversion to uint64_t when subtracting the signed epoch values below
|
||||
// for preventing signed integer overflow.
|
||||
header.min_timestamp_base.value = static_cast<uint64_t>(enc_stats.min_timestamp) - encoding_stats::timestamp_epoch;
|
||||
header.min_local_deletion_time_base.value = static_cast<uint64_t>(enc_stats.min_local_deletion_time) - encoding_stats::deletion_time_epoch;
|
||||
header.min_ttl_base.value = static_cast<uint64_t>(enc_stats.min_ttl) - encoding_stats::ttl_epoch;
|
||||
|
||||
header.pk_type_name = to_bytes_array_vint_size(pk_type_to_string(s));
|
||||
|
||||
@@ -534,7 +539,7 @@ private:
|
||||
shard_id _shard; // Specifies which shard the new SStable will belong to.
|
||||
bool _compression_enabled = false;
|
||||
std::unique_ptr<file_writer> _data_writer;
|
||||
std::optional<file_writer> _index_writer;
|
||||
std::unique_ptr<file_writer> _index_writer;
|
||||
bool _tombstone_written = false;
|
||||
bool _static_row_written = false;
|
||||
// The length of partition header (partition key, partition deletion and static row, if present)
|
||||
@@ -592,6 +597,10 @@ private:
|
||||
bool _write_regular_as_static; // See #4139
|
||||
|
||||
void init_file_writers();
|
||||
|
||||
// Returns the closed writer
|
||||
std::unique_ptr<file_writer> close_writer(std::unique_ptr<file_writer>& w);
|
||||
|
||||
void close_data_writer();
|
||||
void ensure_tombstone_is_written() {
|
||||
if (!_tombstone_written) {
|
||||
@@ -654,7 +663,7 @@ private:
|
||||
|
||||
// Writes single atomic cell
|
||||
void write_cell(bytes_ostream& writer, atomic_cell_view cell, const column_definition& cdef,
|
||||
const row_time_properties& properties, bytes_view cell_path = {});
|
||||
const row_time_properties& properties, std::optional<bytes_view> cell_path = {});
|
||||
|
||||
// Writes information about row liveness (formerly 'row marker')
|
||||
void write_liveness_info(bytes_ostream& writer, const row_marker& marker);
|
||||
@@ -821,13 +830,17 @@ void writer::init_file_writers() {
|
||||
&_sst._components->compression,
|
||||
_schema.get_compressor_params()));
|
||||
}
|
||||
_index_writer.emplace(std::move(_sst._index_file), options);
|
||||
_index_writer = std::make_unique<file_writer>(std::move(_sst._index_file), options);
|
||||
}
|
||||
|
||||
std::unique_ptr<file_writer> writer::close_writer(std::unique_ptr<file_writer>& w) {
|
||||
auto writer = std::move(w);
|
||||
writer->close();
|
||||
return writer;
|
||||
}
|
||||
|
||||
void writer::close_data_writer() {
|
||||
auto writer = std::move(_data_writer);
|
||||
writer->close();
|
||||
|
||||
auto writer = close_writer(_data_writer);
|
||||
if (!_compression_enabled) {
|
||||
auto chksum_wr = static_cast<crc32_checksummed_file_writer*>(writer.get());
|
||||
_sst.write_digest(chksum_wr->full_checksum());
|
||||
@@ -957,7 +970,7 @@ void writer::consume(tombstone t) {
|
||||
}
|
||||
|
||||
void writer::write_cell(bytes_ostream& writer, atomic_cell_view cell, const column_definition& cdef,
|
||||
const row_time_properties& properties, bytes_view cell_path) {
|
||||
const row_time_properties& properties, std::optional<bytes_view> cell_path) {
|
||||
|
||||
bool is_deleted = !cell.is_live();
|
||||
bool has_value = !is_deleted && !cell.value().empty();
|
||||
@@ -969,7 +982,7 @@ void writer::write_cell(bytes_ostream& writer, atomic_cell_view cell, const colu
|
||||
properties.local_deletion_time == cell.deletion_time().time_since_epoch().count();
|
||||
|
||||
cell_flags flags = cell_flags::none;
|
||||
if (!has_value) {
|
||||
if ((!has_value && !cdef.is_counter()) || is_deleted) {
|
||||
flags |= cell_flags::has_empty_value_mask;
|
||||
}
|
||||
if (is_deleted) {
|
||||
@@ -998,20 +1011,22 @@ void writer::write_cell(bytes_ostream& writer, atomic_cell_view cell, const colu
|
||||
}
|
||||
}
|
||||
|
||||
if (!cell_path.empty()) {
|
||||
write_vint(writer, cell_path.size());
|
||||
write(_sst.get_version(), writer, cell_path);
|
||||
if (bool(cell_path)) {
|
||||
write_vint(writer, cell_path->size());
|
||||
write(_sst.get_version(), writer, *cell_path);
|
||||
}
|
||||
|
||||
if (has_value) {
|
||||
if (cdef.is_counter()) {
|
||||
if (cdef.is_counter()) {
|
||||
if (!is_deleted) {
|
||||
assert(!cell.is_counter_update());
|
||||
counter_cell_view::with_linearized(cell, [&] (counter_cell_view ccv) {
|
||||
write_counter_value(ccv, writer, sstable_version_types::mc, [] (bytes_ostream& out, uint32_t value) {
|
||||
return write_vint(out, value);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
}
|
||||
} else {
|
||||
if (has_value) {
|
||||
write_cell_value(writer, *cdef.type, cell.value());
|
||||
}
|
||||
}
|
||||
@@ -1352,10 +1367,15 @@ stop_iteration writer::consume_end_of_partition() {
|
||||
_first_key = *_partition_key;
|
||||
}
|
||||
_last_key = std::move(*_partition_key);
|
||||
_partition_key = std::nullopt;
|
||||
return get_data_offset() < _cfg.max_sstable_size ? stop_iteration::no : stop_iteration::yes;
|
||||
}
|
||||
|
||||
void writer::consume_end_of_stream() {
|
||||
if (_partition_key) {
|
||||
on_internal_error(sstlog, "Mutation stream ends with unclosed partition during write");
|
||||
}
|
||||
|
||||
_cfg.monitor->on_data_write_completed();
|
||||
|
||||
seal_summary(_sst._components->summary, std::move(_first_key), std::move(_last_key), _index_sampling_state);
|
||||
@@ -1364,8 +1384,7 @@ void writer::consume_end_of_stream() {
|
||||
_sst.get_metadata_collector().add_compression_ratio(_sst._components->compression.compressed_file_length(), _sst._components->compression.uncompressed_file_length());
|
||||
}
|
||||
|
||||
_index_writer->close();
|
||||
_index_writer.reset();
|
||||
close_writer(_index_writer);
|
||||
_sst.set_first_and_last_keys();
|
||||
|
||||
_sst._components->statistics.contents[metadata_type::Serialization] = std::make_unique<serialization_header>(std::move(_sst_schema.header));
|
||||
|
||||
@@ -44,6 +44,14 @@ namespace sstables {
|
||||
atomic_cell make_counter_cell(api::timestamp_type timestamp, bytes_view value) {
|
||||
static constexpr size_t shard_size = 32;
|
||||
|
||||
if (value.empty()) {
|
||||
// This will never happen in a correct MC sstable but
|
||||
// we had a bug #4363 that caused empty counters
|
||||
// to be incorrectly stored inside sstables.
|
||||
counter_cell_builder ccb;
|
||||
return ccb.build(timestamp);
|
||||
}
|
||||
|
||||
data_input in(value);
|
||||
|
||||
auto header_size = in.read<int16_t>();
|
||||
@@ -59,8 +67,6 @@ atomic_cell make_counter_cell(api::timestamp_type timestamp, bytes_view value) {
|
||||
throw marshal_exception("encountered remote shards in a counter cell");
|
||||
}
|
||||
|
||||
std::vector<counter_shard> shards;
|
||||
shards.reserve(shard_count);
|
||||
counter_cell_builder ccb(shard_count);
|
||||
for (auto i = 0u; i < shard_count; i++) {
|
||||
auto id_hi = in.read<int64_t>();
|
||||
|
||||
@@ -702,9 +702,12 @@ public:
|
||||
// Sets streamed_mutation::_end_of_range when there are no more fragments for the query range.
|
||||
// Returns information whether the parser should continue to parse more
|
||||
// input and produce more fragments or we have collected enough and should yield.
|
||||
// Returns proceed:yes only when all pending fragments have been pushed.
|
||||
proceed push_ready_fragments() {
|
||||
if (_ready) {
|
||||
return push_ready_fragments_with_ready_set();
|
||||
if (push_ready_fragments_with_ready_set() == proceed::no) {
|
||||
return proceed::no;
|
||||
}
|
||||
}
|
||||
|
||||
if (_out_of_range) {
|
||||
|
||||
@@ -1023,9 +1023,26 @@ void sstable::write_simple(const T& component, const io_priority_class& pc) {
|
||||
options.buffer_size = sstable_buffer_size;
|
||||
options.io_priority_class = pc;
|
||||
auto w = file_writer(std::move(f), std::move(options));
|
||||
write(_version, w, component);
|
||||
w.flush();
|
||||
w.close();
|
||||
std::exception_ptr eptr;
|
||||
try {
|
||||
write(_version, w, component);
|
||||
w.flush();
|
||||
} catch (...) {
|
||||
eptr = std::current_exception();
|
||||
}
|
||||
try {
|
||||
w.close();
|
||||
} catch (...) {
|
||||
std::exception_ptr close_eptr = std::current_exception();
|
||||
sstlog.warn("failed to close file_writer: {}", close_eptr);
|
||||
// If write succeeded but close failed, we rethrow close's exception.
|
||||
if (!eptr) {
|
||||
eptr = close_eptr;
|
||||
}
|
||||
}
|
||||
if (eptr) {
|
||||
std::rethrow_exception(eptr);
|
||||
}
|
||||
}
|
||||
|
||||
template future<> sstable::read_simple<component_type::Filter>(sstables::filter& f, const io_priority_class& pc);
|
||||
@@ -2077,11 +2094,15 @@ stop_iteration components_writer::consume_end_of_partition() {
|
||||
_first_key = *_partition_key;
|
||||
}
|
||||
_last_key = std::move(*_partition_key);
|
||||
_partition_key = stdx::nullopt;
|
||||
|
||||
return get_offset() < _max_sstable_size ? stop_iteration::no : stop_iteration::yes;
|
||||
}
|
||||
|
||||
void components_writer::consume_end_of_stream() {
|
||||
if (_partition_key) {
|
||||
on_internal_error(sstlog, "Mutation stream ends with unclosed partition during write");
|
||||
}
|
||||
// what if there is only one partition? what if it is empty?
|
||||
seal_summary(_sst._components->summary, std::move(_first_key), std::move(_last_key), _index_sampling_state);
|
||||
|
||||
|
||||
@@ -410,16 +410,17 @@ struct serialization_header : public metadata_base<serialization_header> {
|
||||
}
|
||||
|
||||
// mc serialization header minimum values are delta-encoded based on the default timestamp epoch times
|
||||
// Note: following conversions rely on min_*_base.value being unsigned to prevent signed integer overflow
|
||||
api::timestamp_type get_min_timestamp() const {
|
||||
return static_cast<api::timestamp_type>(min_timestamp_base.value + encoding_stats::timestamp_epoch);
|
||||
}
|
||||
|
||||
int32_t get_min_ttl() const {
|
||||
return static_cast<int32_t>(min_ttl_base.value) + encoding_stats::ttl_epoch;
|
||||
return static_cast<int32_t>(min_ttl_base.value + encoding_stats::ttl_epoch);
|
||||
}
|
||||
|
||||
int32_t get_min_local_deletion_time() const {
|
||||
return static_cast<int32_t>(min_local_deletion_time_base.value) + encoding_stats::deletion_time_epoch;
|
||||
return static_cast<int32_t>(min_local_deletion_time_base.value + encoding_stats::deletion_time_epoch);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -456,7 +457,8 @@ enum sstable_feature : uint8_t {
|
||||
NonCompoundRangeTombstones = 1, // See #2986
|
||||
ShadowableTombstones = 2, // See #3885
|
||||
CorrectStaticCompact = 3, // See #4139
|
||||
End = 4,
|
||||
CorrectEmptyCounters = 4, // See #4363
|
||||
End = 5,
|
||||
};
|
||||
|
||||
// Scylla-specific features enabled for a particular sstable.
|
||||
|
||||
33
streaming/stream_mutation_fragments_cmd.hh
Normal file
33
streaming/stream_mutation_fragments_cmd.hh
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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 General Public License
|
||||
* along with Scylla. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace streaming {
|
||||
|
||||
enum class stream_mutation_fragments_cmd : uint8_t {
|
||||
error,
|
||||
mutation_fragment_data,
|
||||
end_of_stream,
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
@@ -63,6 +63,7 @@
|
||||
#include "db/system_keyspace.hh"
|
||||
#include <boost/algorithm/cxx11/any_of.hpp>
|
||||
#include <boost/range/adaptor/map.hpp>
|
||||
#include "streaming/stream_mutation_fragments_cmd.hh"
|
||||
|
||||
namespace streaming {
|
||||
|
||||
@@ -214,7 +215,7 @@ void stream_session::init_messaging_service_handler() {
|
||||
});
|
||||
});
|
||||
});
|
||||
ms().register_stream_mutation_fragments([] (const rpc::client_info& cinfo, UUID plan_id, UUID schema_id, UUID cf_id, uint64_t estimated_partitions, rpc::optional<stream_reason> reason_opt, rpc::source<frozen_mutation_fragment> source) {
|
||||
ms().register_stream_mutation_fragments([] (const rpc::client_info& cinfo, UUID plan_id, UUID schema_id, UUID cf_id, uint64_t estimated_partitions, rpc::optional<stream_reason> reason_opt, rpc::source<frozen_mutation_fragment, rpc::optional<stream_mutation_fragments_cmd>> source) {
|
||||
auto from = netw::messaging_service::get_source(cinfo);
|
||||
auto reason = reason_opt ? *reason_opt: stream_reason::unspecified;
|
||||
sslog.trace("Got stream_mutation_fragments from {} reason {}", from, int(reason));
|
||||
@@ -225,15 +226,41 @@ void stream_session::init_messaging_service_handler() {
|
||||
return with_scheduling_group(service::get_local_storage_service().db().local().get_streaming_scheduling_group(), [from, estimated_partitions, plan_id, schema_id, cf_id, source, reason] () mutable {
|
||||
return service::get_schema_for_write(schema_id, from).then([from, estimated_partitions, plan_id, schema_id, cf_id, source, reason] (schema_ptr s) mutable {
|
||||
auto sink = ms().make_sink_for_stream_mutation_fragments(source);
|
||||
auto get_next_mutation_fragment = [source, plan_id, from, s] () mutable {
|
||||
return source().then([plan_id, from, s] (stdx::optional<std::tuple<frozen_mutation_fragment>> fmf_opt) mutable {
|
||||
if (fmf_opt) {
|
||||
frozen_mutation_fragment& fmf = std::get<0>(fmf_opt.value());
|
||||
struct stream_mutation_fragments_cmd_status {
|
||||
bool got_cmd = false;
|
||||
bool got_end_of_stream = false;
|
||||
};
|
||||
auto cmd_status = make_lw_shared<stream_mutation_fragments_cmd_status>();
|
||||
auto get_next_mutation_fragment = [source, plan_id, from, s, cmd_status] () mutable {
|
||||
return source().then([plan_id, from, s, cmd_status] (stdx::optional<std::tuple<frozen_mutation_fragment, rpc::optional<stream_mutation_fragments_cmd>>> opt) mutable {
|
||||
if (opt) {
|
||||
auto cmd = std::get<1>(*opt);
|
||||
if (cmd) {
|
||||
cmd_status->got_cmd = true;
|
||||
switch (*cmd) {
|
||||
case stream_mutation_fragments_cmd::mutation_fragment_data:
|
||||
break;
|
||||
case stream_mutation_fragments_cmd::error:
|
||||
return make_exception_future<mutation_fragment_opt>(std::runtime_error("Sender failed"));
|
||||
case stream_mutation_fragments_cmd::end_of_stream:
|
||||
cmd_status->got_end_of_stream = true;
|
||||
return make_ready_future<mutation_fragment_opt>();
|
||||
default:
|
||||
return make_exception_future<mutation_fragment_opt>(std::runtime_error("Sender sent wrong cmd"));
|
||||
}
|
||||
}
|
||||
frozen_mutation_fragment& fmf = std::get<0>(*opt);
|
||||
auto sz = fmf.representation().size();
|
||||
auto mf = fmf.unfreeze(*s);
|
||||
streaming::get_local_stream_manager().update_progress(plan_id, from.addr, progress_info::direction::IN, sz);
|
||||
return make_ready_future<mutation_fragment_opt>(std::move(mf));
|
||||
} else {
|
||||
// If the sender has sent stream_mutation_fragments_cmd it means it is
|
||||
// a node that understands the new protocol. It must send end_of_stream
|
||||
// before close the stream.
|
||||
if (cmd_status->got_cmd && !cmd_status->got_end_of_stream) {
|
||||
return make_exception_future<mutation_fragment_opt>(std::runtime_error("Sender did not sent end_of_stream"));
|
||||
}
|
||||
return make_ready_future<mutation_fragment_opt>();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
#include "streaming/stream_session.hh"
|
||||
#include "streaming/stream_manager.hh"
|
||||
#include "streaming/stream_reason.hh"
|
||||
#include "streaming/stream_mutation_fragments_cmd.hh"
|
||||
#include "mutation_reader.hh"
|
||||
#include "frozen_mutation.hh"
|
||||
#include "mutation.hh"
|
||||
@@ -104,6 +105,21 @@ struct send_info {
|
||||
, prs(to_partition_ranges(ranges))
|
||||
, reader(cf.make_streaming_reader(cf.schema(), prs)) {
|
||||
}
|
||||
future<bool> has_relevant_range_on_this_shard() {
|
||||
return do_with(false, [this] (bool& found_relevant_range) {
|
||||
return do_for_each(ranges, [this, &found_relevant_range] (dht::token_range range) {
|
||||
if (!found_relevant_range) {
|
||||
auto sharder = dht::selective_token_range_sharder(range, engine().cpu_id());
|
||||
auto range_shard = sharder.next();
|
||||
if (range_shard) {
|
||||
found_relevant_range = true;
|
||||
}
|
||||
}
|
||||
}).then([&found_relevant_range] {
|
||||
return found_relevant_range;
|
||||
});
|
||||
});
|
||||
}
|
||||
future<size_t> estimate_partitions() {
|
||||
return do_with(cf.get_sstables(), size_t(0), [this] (auto& sstables, size_t& partition_count) {
|
||||
return do_for_each(*sstables, [this, &partition_count] (auto& sst) {
|
||||
@@ -160,7 +176,7 @@ future<> send_mutations(lw_shared_ptr<send_info> si) {
|
||||
future<> send_mutation_fragments(lw_shared_ptr<send_info> si) {
|
||||
return si->estimate_partitions().then([si] (size_t estimated_partitions) {
|
||||
sslog.info("[Stream #{}] Start sending ks={}, cf={}, estimated_partitions={}, with new rpc streaming", si->plan_id, si->cf.schema()->ks_name(), si->cf.schema()->cf_name(), estimated_partitions);
|
||||
return netw::get_local_messaging_service().make_sink_and_source_for_stream_mutation_fragments(si->reader.schema()->version(), si->plan_id, si->cf_id, estimated_partitions, si->reason, si->id).then([si] (rpc::sink<frozen_mutation_fragment> sink, rpc::source<int32_t> source) mutable {
|
||||
return netw::get_local_messaging_service().make_sink_and_source_for_stream_mutation_fragments(si->reader.schema()->version(), si->plan_id, si->cf_id, estimated_partitions, si->reason, si->id).then([si] (rpc::sink<frozen_mutation_fragment, stream_mutation_fragments_cmd> sink, rpc::source<int32_t> source) mutable {
|
||||
auto got_error_from_peer = make_lw_shared<bool>(false);
|
||||
|
||||
auto source_op = [source, got_error_from_peer, si] () mutable -> future<> {
|
||||
@@ -183,18 +199,25 @@ future<> send_mutation_fragments(lw_shared_ptr<send_info> si) {
|
||||
}();
|
||||
|
||||
auto sink_op = [sink, si, got_error_from_peer] () mutable -> future<> {
|
||||
return do_with(std::move(sink), [si, got_error_from_peer] (rpc::sink<frozen_mutation_fragment>& sink) {
|
||||
return do_with(std::move(sink), [si, got_error_from_peer] (rpc::sink<frozen_mutation_fragment, stream_mutation_fragments_cmd>& sink) {
|
||||
return repeat([&sink, si, got_error_from_peer] () mutable {
|
||||
return si->reader(db::no_timeout).then([&sink, si, s = si->reader.schema(), got_error_from_peer] (mutation_fragment_opt mf) mutable {
|
||||
if (mf && !(*got_error_from_peer)) {
|
||||
frozen_mutation_fragment fmf = freeze(*s, *mf);
|
||||
auto size = fmf.representation().size();
|
||||
streaming::get_local_stream_manager().update_progress(si->plan_id, si->id.addr, streaming::progress_info::direction::OUT, size);
|
||||
return sink(fmf).then([] { return stop_iteration::no; });
|
||||
return sink(fmf, stream_mutation_fragments_cmd::mutation_fragment_data).then([] { return stop_iteration::no; });
|
||||
} else {
|
||||
return make_ready_future<stop_iteration>(stop_iteration::yes);
|
||||
}
|
||||
});
|
||||
}).then([&sink] () mutable {
|
||||
return sink(frozen_mutation_fragment(bytes_ostream()), stream_mutation_fragments_cmd::end_of_stream);
|
||||
}).handle_exception([&sink] (std::exception_ptr ep) mutable {
|
||||
// Notify the receiver the sender has failed
|
||||
return sink(frozen_mutation_fragment(bytes_ostream()), stream_mutation_fragments_cmd::error).then([ep = std::move(ep)] () mutable {
|
||||
return make_exception_future<>(std::move(ep));
|
||||
});
|
||||
}).finally([&sink] () mutable {
|
||||
return sink.close();
|
||||
});
|
||||
@@ -221,11 +244,18 @@ future<> stream_transfer_task::execute() {
|
||||
auto reason = session->get_reason();
|
||||
return session->get_db().invoke_on_all([plan_id, cf_id, id, dst_cpu_id, ranges=this->_ranges, streaming_with_rpc_stream, reason] (database& db) {
|
||||
auto si = make_lw_shared<send_info>(db, plan_id, cf_id, std::move(ranges), id, dst_cpu_id, reason);
|
||||
if (streaming_with_rpc_stream) {
|
||||
return send_mutation_fragments(std::move(si));
|
||||
} else {
|
||||
return send_mutations(std::move(si));
|
||||
}
|
||||
return si->has_relevant_range_on_this_shard().then([si, plan_id, cf_id, streaming_with_rpc_stream] (bool has_relevant_range_on_this_shard) {
|
||||
if (!has_relevant_range_on_this_shard) {
|
||||
sslog.debug("[Stream #{}] stream_transfer_task: cf_id={}: ignore ranges on shard={}",
|
||||
plan_id, cf_id, engine().cpu_id());
|
||||
return make_ready_future<>();
|
||||
}
|
||||
if (streaming_with_rpc_stream) {
|
||||
return send_mutation_fragments(std::move(si));
|
||||
} else {
|
||||
return send_mutations(std::move(si));
|
||||
}
|
||||
});
|
||||
}).then([this, plan_id, cf_id, id, streaming_with_rpc_stream] {
|
||||
sslog.debug("[Stream #{}] SEND STREAM_MUTATION_DONE to {}, cf_id={}", plan_id, id, cf_id);
|
||||
return session->ms().send_stream_mutation_done(id, plan_id, _ranges,
|
||||
|
||||
12
table.cc
12
table.cc
@@ -70,7 +70,7 @@ future<row_locker::lock_holder> table::push_view_replica_updates(const schema_pt
|
||||
return push_view_replica_updates(s, std::move(m), timeout);
|
||||
}
|
||||
|
||||
future<row_locker::lock_holder> table::do_push_view_replica_updates(const schema_ptr& s, mutation&& m, db::timeout_clock::time_point timeout, mutation_source&& source) const {
|
||||
future<row_locker::lock_holder> table::do_push_view_replica_updates(const schema_ptr& s, mutation&& m, db::timeout_clock::time_point timeout, mutation_source&& source, const io_priority_class& io_priority) const {
|
||||
if (!_config.view_update_concurrency_semaphore->current()) {
|
||||
// We don't have resources to generate view updates for this write. If we reached this point, we failed to
|
||||
// throttle the client. The memory queue is already full, waiting on the semaphore would cause this node to
|
||||
@@ -110,13 +110,13 @@ future<row_locker::lock_holder> table::do_push_view_replica_updates(const schema
|
||||
// We'll return this lock to the caller, which will release it after
|
||||
// writing the base-table update.
|
||||
future<row_locker::lock_holder> lockf = local_base_lock(base, m.decorated_key(), slice.default_row_ranges(), timeout);
|
||||
return lockf.then([m = std::move(m), slice = std::move(slice), views = std::move(views), base, this, timeout, source = std::move(source)] (row_locker::lock_holder lock) {
|
||||
return lockf.then([m = std::move(m), slice = std::move(slice), views = std::move(views), base, this, timeout, source = std::move(source), &io_priority] (row_locker::lock_holder lock) {
|
||||
return do_with(
|
||||
dht::partition_range::make_singular(m.decorated_key()),
|
||||
std::move(slice),
|
||||
std::move(m),
|
||||
[base, views = std::move(views), lock = std::move(lock), this, timeout, source = std::move(source)] (auto& pk, auto& slice, auto& m) mutable {
|
||||
auto reader = source.make_reader(base, pk, slice, service::get_local_sstable_query_read_priority());
|
||||
[base, views = std::move(views), lock = std::move(lock), this, timeout, source = std::move(source), &io_priority] (auto& pk, auto& slice, auto& m) mutable {
|
||||
auto reader = source.make_reader(base, pk, slice, io_priority, nullptr, streamed_mutation::forwarding::no, mutation_reader::forwarding::no);
|
||||
return this->generate_and_propagate_view_updates(base, std::move(views), std::move(m), std::move(reader)).then([lock = std::move(lock)] () mutable {
|
||||
// return the local partition/row lock we have taken so it
|
||||
// remains locked until the caller is done modifying this
|
||||
@@ -128,11 +128,11 @@ future<row_locker::lock_holder> table::do_push_view_replica_updates(const schema
|
||||
}
|
||||
|
||||
future<row_locker::lock_holder> table::push_view_replica_updates(const schema_ptr& s, mutation&& m, db::timeout_clock::time_point timeout) const {
|
||||
return do_push_view_replica_updates(s, std::move(m), timeout, as_mutation_source());
|
||||
return do_push_view_replica_updates(s, std::move(m), timeout, as_mutation_source(), service::get_local_sstable_query_read_priority());
|
||||
}
|
||||
|
||||
future<row_locker::lock_holder> table::stream_view_replica_updates(const schema_ptr& s, mutation&& m, db::timeout_clock::time_point timeout, sstables::shared_sstable excluded_sstable) const {
|
||||
return do_push_view_replica_updates(s, std::move(m), timeout, as_mutation_source_excluding(std::move(excluded_sstable)));
|
||||
return do_push_view_replica_updates(s, std::move(m), timeout, as_mutation_source_excluding(std::move(excluded_sstable)), service::get_local_streaming_write_priority());
|
||||
}
|
||||
|
||||
mutation_source
|
||||
|
||||
@@ -151,3 +151,14 @@ rows_assertions rows_assertions::with_serialized_columns_count(size_t columns_co
|
||||
}
|
||||
return {*this};
|
||||
}
|
||||
|
||||
shared_ptr<cql_transport::messages::result_message> cquery_nofail(
|
||||
cql_test_env& env, const char* query, const std::experimental::source_location& loc) {
|
||||
try {
|
||||
return env.execute_cql(query).get0();
|
||||
} catch (...) {
|
||||
BOOST_FAIL(format("query '{}' failed: {}\n{}:{}: originally from here",
|
||||
query, std::current_exception(), loc.file_name(), loc.line()));
|
||||
}
|
||||
return shared_ptr<cql_transport::messages::result_message>(nullptr);
|
||||
}
|
||||
|
||||
@@ -22,10 +22,12 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "tests/cql_test_env.hh"
|
||||
#include "transport/messages/result_message_base.hh"
|
||||
#include "bytes.hh"
|
||||
#include "core/shared_ptr.hh"
|
||||
#include "core/future.hh"
|
||||
#include <experimental/source_location>
|
||||
|
||||
class rows_assertions {
|
||||
shared_ptr<cql_transport::messages::result_message::rows> _rows;
|
||||
@@ -73,3 +75,12 @@ void assert_that_failed(future<T...>&& f)
|
||||
catch (...) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Invokes env.execute_cql(query), awaits its result, and returns it. If an exception is thrown,
|
||||
/// invokes BOOST_FAIL with useful diagnostics.
|
||||
///
|
||||
/// \note Should be called from a seastar::thread context, as it awaits the CQL result.
|
||||
shared_ptr<cql_transport::messages::result_message> cquery_nofail(
|
||||
cql_test_env& env,
|
||||
const char* query,
|
||||
const std::experimental::source_location& loc = std::experimental::source_location::current());
|
||||
|
||||
@@ -72,7 +72,7 @@ SEASTAR_TEST_CASE(test_create_table_with_id_statement) {
|
||||
.is_rows().with_size(0);
|
||||
BOOST_REQUIRE_THROW(
|
||||
e.execute_cql(sprint("CREATE TABLE tbl2 (a int, b int, PRIMARY KEY (a)) WITH id='%s'", id)).get(),
|
||||
std::invalid_argument);
|
||||
exceptions::invalid_request_exception);
|
||||
BOOST_REQUIRE_THROW(
|
||||
e.execute_cql("CREATE TABLE tbl2 (a int, b int, PRIMARY KEY (a)) WITH id='55'").get(),
|
||||
exceptions::configuration_exception);
|
||||
@@ -1182,6 +1182,18 @@ SEASTAR_TEST_CASE(test_tuples) {
|
||||
});
|
||||
}
|
||||
|
||||
SEASTAR_TEST_CASE(test_user_type_reversed) {
|
||||
return do_with_cql_env_thread([](cql_test_env& e) {
|
||||
e.execute_cql("create type my_type (a int);").get();
|
||||
e.execute_cql("create table tbl (a int, b frozen<my_type>, primary key ((a), b)) with clustering order by (b desc);").get();
|
||||
e.execute_cql("insert into tbl (a, b) values (1, (2));").get();
|
||||
assert_that(e.execute_cql("select a,b.a from tbl;").get0())
|
||||
.is_rows()
|
||||
.with_size(1)
|
||||
.with_row({int32_type->decompose(1), int32_type->decompose(2)});
|
||||
});
|
||||
}
|
||||
|
||||
SEASTAR_TEST_CASE(test_user_type) {
|
||||
return do_with_cql_env([] (cql_test_env& e) {
|
||||
return e.execute_cql("create type ut1 (my_int int, my_bigint bigint, my_text text);").discard_result().then([&e] {
|
||||
@@ -2978,3 +2990,11 @@ SEASTAR_TEST_CASE(test_select_with_mixed_order_table) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
SEASTAR_TEST_CASE(test_alter_type_on_compact_storage_with_no_regular_columns_does_not_crash) {
|
||||
return do_with_cql_env_thread([] (cql_test_env& e) {
|
||||
cquery_nofail(e, "CREATE TYPE my_udf (first text);");
|
||||
cquery_nofail(e, "create table z (pk int, ck frozen<my_udf>, primary key(pk, ck)) with compact storage;");
|
||||
cquery_nofail(e, "alter type my_udf add test_int int;");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -337,6 +337,7 @@ public:
|
||||
boost::filesystem::create_directories((cfg->view_hints_directory() + "/" + std::to_string(i)).c_str());
|
||||
}
|
||||
|
||||
set_abort_on_internal_error(true);
|
||||
const gms::inet_address listen("127.0.0.1");
|
||||
auto& ms = netw::get_messaging_service();
|
||||
// don't start listening so tests can be run in parallel
|
||||
|
||||
@@ -634,6 +634,13 @@ SEASTAR_TEST_CASE(test_allow_filtering_with_secondary_index) {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
eventually([&] {
|
||||
auto msg = e.execute_cql("SELECT SUM(e) FROM t WHERE c = 5 AND b = 911 ALLOW FILTERING;").get0();
|
||||
assert_that(msg).is_rows().with_rows({{ int32_type->decompose(0), {} }});
|
||||
msg = e.execute_cql("SELECT e FROM t WHERE c = 5 AND b = 3 ALLOW FILTERING;").get0();
|
||||
assert_that(msg).is_rows().with_rows({{ int32_type->decompose(9), int32_type->decompose(3) }});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -725,3 +725,76 @@ SEASTAR_TEST_CASE(test_abandoned_flat_mutation_reader_from_mutation) {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
static std::vector<mutation> squash_mutations(std::vector<mutation> mutations) {
|
||||
if (mutations.empty()) {
|
||||
return {};
|
||||
}
|
||||
std::map<dht::decorated_key, mutation, dht::ring_position_less_comparator> merged_muts{
|
||||
dht::ring_position_less_comparator{*mutations.front().schema()}};
|
||||
for (const auto& mut : mutations) {
|
||||
auto [it, inserted] = merged_muts.try_emplace(mut.decorated_key(), mut);
|
||||
if (!inserted) {
|
||||
it->second.apply(mut);
|
||||
}
|
||||
}
|
||||
return boost::copy_range<std::vector<mutation>>(merged_muts | boost::adaptors::map_values);
|
||||
}
|
||||
|
||||
SEASTAR_THREAD_TEST_CASE(test_mutation_reader_from_mutations_as_mutation_source) {
|
||||
auto populate = [] (schema_ptr, const std::vector<mutation>& muts) {
|
||||
return mutation_source([=] (
|
||||
schema_ptr schema,
|
||||
const dht::partition_range& range,
|
||||
const query::partition_slice& slice,
|
||||
const io_priority_class&,
|
||||
tracing::trace_state_ptr,
|
||||
streamed_mutation::forwarding fwd_sm,
|
||||
mutation_reader::forwarding) mutable {
|
||||
auto squashed_muts = squash_mutations(muts);
|
||||
if (schema->version() != muts.front().schema()->version()) {
|
||||
for (auto& mut : squashed_muts) {
|
||||
auto part = std::move(mut.partition());
|
||||
part.upgrade(*mut.schema(), *schema);
|
||||
mut = mutation(schema, std::move(mut.decorated_key()), std::move(part));
|
||||
}
|
||||
}
|
||||
return flat_mutation_reader_from_mutations(std::move(squashed_muts), range, slice, fwd_sm);
|
||||
});
|
||||
};
|
||||
run_mutation_source_tests(populate);
|
||||
}
|
||||
|
||||
SEASTAR_THREAD_TEST_CASE(test_mutation_reader_from_fragments_as_mutation_source) {
|
||||
auto populate = [] (schema_ptr, const std::vector<mutation>& muts) {
|
||||
return mutation_source([=] (
|
||||
schema_ptr schema,
|
||||
const dht::partition_range& range,
|
||||
const query::partition_slice& slice,
|
||||
const io_priority_class&,
|
||||
tracing::trace_state_ptr,
|
||||
streamed_mutation::forwarding fwd_sm,
|
||||
mutation_reader::forwarding) mutable {
|
||||
auto squashed_muts = squash_mutations(muts);
|
||||
if (schema->version() != muts.front().schema()->version()) {
|
||||
for (auto& mut : squashed_muts) {
|
||||
auto part = std::move(mut.partition());
|
||||
part.upgrade(*mut.schema(), *schema);
|
||||
mut = mutation(schema, std::move(mut.decorated_key()), std::move(part));
|
||||
}
|
||||
}
|
||||
std::deque<mutation_fragment> fragments;
|
||||
flat_mutation_reader_from_mutations(std::move(squashed_muts)).consume_pausable([&fragments] (mutation_fragment mf) {
|
||||
fragments.emplace_back(std::move(mf));
|
||||
return stop_iteration::no;
|
||||
}, db::no_timeout).get();
|
||||
|
||||
auto rd = make_flat_mutation_reader_from_fragments(schema, std::move(fragments), range, slice);
|
||||
if (fwd_sm) {
|
||||
return make_forwardable(std::move(rd));
|
||||
}
|
||||
return rd;
|
||||
});
|
||||
};
|
||||
run_mutation_source_tests(populate);
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ private:
|
||||
}
|
||||
auto count = _memtables.size();
|
||||
auto op = _apply.start();
|
||||
auto new_mt = make_lw_shared<memtable>(_memtables.back()->schema());
|
||||
auto new_mt = make_lw_shared<memtable>(_s);
|
||||
std::vector<flat_mutation_reader> readers;
|
||||
for (auto&& mt : _memtables) {
|
||||
readers.push_back(mt->make_flat_reader(new_mt->schema(),
|
||||
@@ -101,6 +101,13 @@ public:
|
||||
_should_compact.broadcast();
|
||||
_compactor.get();
|
||||
}
|
||||
// Will cause subsequent apply() calls to accept writes conforming to given schema (or older).
|
||||
// Without this, the writes will be upgraded to the old schema and snapshots will not reflect
|
||||
// parts of writes which depend on the new schema.
|
||||
void set_schema(schema_ptr s) {
|
||||
pending()->set_schema(s);
|
||||
_s = s;
|
||||
}
|
||||
// Must run in a seastar thread
|
||||
void clear() {
|
||||
_memtables.erase(_memtables.begin(), _memtables.end());
|
||||
|
||||
@@ -1874,6 +1874,168 @@ SEASTAR_THREAD_TEST_CASE(test_multishard_combining_reader_destroyed_with_pending
|
||||
}).get();
|
||||
}
|
||||
|
||||
// Test the multishard reader correctly handling non-full prefix keys
|
||||
//
|
||||
// Check that the presence of non-full prefix keys in the mutation
|
||||
// stream will not cause shard reader recreation skipping clustering rows
|
||||
// that fall into the prefix.
|
||||
//
|
||||
// Theory of operation:
|
||||
// 1) Prepare a bunch of partitions, each with a bunch of clustering
|
||||
// rows with full clustering keys.
|
||||
// 2) Use as the shard reader a special reader, which, if the last mutation
|
||||
// fragment in the buffer is a clustering row, injects a range tombstone
|
||||
// which starts with a non-full prefix covering the next clustering row.
|
||||
// 4) Create range tombstones such that they don't shadow any of the rows
|
||||
// and they are already expired, hence can be filtered out by compaction.
|
||||
// 3) Read back all the mutations and check that no clustering row is missing.
|
||||
//
|
||||
// Note that the multishard reader recreates shard readers based on the last
|
||||
// fragment seen by that shard reader. In this test we check that recreating
|
||||
// the reader doesn't skip any rows if that last seen fragment is a range
|
||||
// tombstone with a non-full prefix position.
|
||||
//
|
||||
// Has to be run with smp >= 3
|
||||
SEASTAR_THREAD_TEST_CASE(test_multishard_combining_reader_non_full_prefix_keys) {
|
||||
class reader : public flat_mutation_reader::impl {
|
||||
flat_mutation_reader _reader;
|
||||
public:
|
||||
reader(schema_ptr schema, const dht::partition_range& range, const query::partition_slice& slice, std::vector<mutation> mutations)
|
||||
: impl(std::move(schema)), _reader(flat_mutation_reader_from_mutations(std::move(mutations), range, slice)) {
|
||||
}
|
||||
virtual future<> fill_buffer(db::timeout_clock::time_point timeout) override {
|
||||
return _reader.fill_buffer(timeout).then([this] {
|
||||
_reader.move_buffer_content_to(*this);
|
||||
if (is_buffer_empty()) {
|
||||
_end_of_stream = _reader.is_end_of_stream();
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& mf = buffer().back();
|
||||
if (!mf.is_clustering_row()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& ck = mf.key();
|
||||
auto ck_val = value_cast<int32_t>(int32_type->deserialize_value(ck.explode().front()));
|
||||
|
||||
// The last fragment is a cr with pos {ck_val, ck_val}.
|
||||
// The next (if any) will be {ck_val + 1, ck_val + 1}.
|
||||
// We want to cover: [{ck_val + 1}, {ck_val + 2, 0}), so that
|
||||
// the prefix covers the next row, but not the one after it.
|
||||
auto start = clustering_key_prefix::from_exploded(*_schema, {int32_type->decompose(data_value(++ck_val))});
|
||||
auto end = clustering_key_prefix::from_exploded(*_schema, {int32_type->decompose(data_value(++ck_val)), int32_type->decompose(data_value(0))});
|
||||
|
||||
// We want all the range tombstones to be gc-able.
|
||||
const auto deletion_time = gc_clock::now() - _schema->gc_grace_seconds() - std::chrono::hours(8);
|
||||
|
||||
// Make expired tombstones so we can just compact them away
|
||||
// when comparing the read data to the original ones. We are
|
||||
// only interested in the rows anyway.
|
||||
auto rt = range_tombstone(start, bound_kind::incl_start, end, bound_kind::excl_end, tombstone(-100, deletion_time));
|
||||
push_mutation_fragment(std::move(rt));
|
||||
});
|
||||
}
|
||||
virtual void next_partition() override { }
|
||||
virtual future<> fast_forward_to(const dht::partition_range&, db::timeout_clock::time_point) override { throw std::bad_function_call(); }
|
||||
virtual future<> fast_forward_to(position_range, db::timeout_clock::time_point) override { throw std::bad_function_call(); }
|
||||
};
|
||||
|
||||
struct mutation_less_comparator {
|
||||
dht::decorated_key::less_comparator _cmp;
|
||||
explicit mutation_less_comparator(schema_ptr s) : _cmp(s) { }
|
||||
bool operator()(const mutation& a, const mutation& b) const {
|
||||
return _cmp(a.decorated_key(), b.decorated_key());
|
||||
}
|
||||
};
|
||||
|
||||
if (smp::count < 2) {
|
||||
std::cerr << "Cannot run test " << get_name() << " with smp::count < 2" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
do_with_cql_env([] (cql_test_env& env) -> future<> {
|
||||
auto schema = schema_builder("ks", "cf")
|
||||
.with_column(to_bytes("pk"), int32_type, column_kind::partition_key)
|
||||
.with_column(to_bytes(format("ck{}", 0)), int32_type, column_kind::clustering_key)
|
||||
.with_column(to_bytes(format("ck{}", 1)), int32_type, column_kind::clustering_key)
|
||||
.with_column(to_bytes("v"), int32_type, column_kind::regular_column)
|
||||
.build();
|
||||
|
||||
auto expected_mutations = std::set<mutation, mutation_less_comparator>{mutation_less_comparator{schema}};
|
||||
std::unordered_map<shard_id, std::vector<frozen_mutation>> shard_mutations;
|
||||
auto& partitioner = dht::global_partitioner();
|
||||
auto val_cdef = schema->regular_column_at(0);
|
||||
api::timestamp_type ts = 0;
|
||||
|
||||
for (auto pk = 0; pk < 10 * static_cast<int>(smp::count); ++pk) {
|
||||
auto dkey = partitioner.decorate_key(*schema, partition_key::from_single_value(*schema, int32_type->decompose(data_value(pk))));
|
||||
auto mut = mutation(schema, dkey);
|
||||
|
||||
for (auto ck = 0; ck < 100; ++ck) {
|
||||
auto ck_val = int32_type->decompose(data_value(ck));
|
||||
auto ckey = clustering_key::from_exploded(*schema, {ck_val, ck_val});
|
||||
mut.set_clustered_cell(ckey, val_cdef, atomic_cell::make_live(*val_cdef.type, ts++, int32_type->decompose(data_value(0))));
|
||||
}
|
||||
|
||||
expected_mutations.emplace(std::move(mut));
|
||||
}
|
||||
|
||||
for (const auto& mut : expected_mutations) {
|
||||
shard_mutations[partitioner.shard_of(mut.token())].emplace_back(freeze(mut));
|
||||
}
|
||||
|
||||
auto factory = [&shard_mutations] (
|
||||
shard_id shard,
|
||||
schema_ptr schema,
|
||||
const dht::partition_range& range,
|
||||
const query::partition_slice& slice,
|
||||
const io_priority_class& pc,
|
||||
tracing::trace_state_ptr trace_state,
|
||||
mutation_reader::forwarding fwd_mr) {
|
||||
auto& frozen_muts = shard_mutations[shard];
|
||||
return smp::submit_to(shard, [gs = global_schema_ptr(schema), &range, &slice, &frozen_muts] () mutable {
|
||||
auto schema = gs.get();
|
||||
auto rd = make_flat_mutation_reader<reader>(schema, range, slice, boost::copy_range<std::vector<mutation>>(
|
||||
frozen_muts | boost::adaptors::transformed([schema] (const frozen_mutation& fm) { return fm.unfreeze(schema); })));
|
||||
|
||||
using foreign_reader_ptr = foreign_ptr<std::unique_ptr<flat_mutation_reader>>;
|
||||
return make_ready_future<foreign_reader_ptr>(make_foreign(std::make_unique<flat_mutation_reader>(std::move(rd))));
|
||||
});
|
||||
};
|
||||
|
||||
std::vector<mutation> actual_mutations;
|
||||
{
|
||||
auto reader = make_multishard_combining_reader(
|
||||
seastar::make_shared<test_reader_lifecycle_policy>(std::move(factory), test_reader_lifecycle_policy::no_delay, true),
|
||||
partitioner,
|
||||
schema,
|
||||
query::full_partition_range,
|
||||
schema->full_slice(),
|
||||
service::get_local_sstable_query_read_priority());
|
||||
|
||||
const auto now = gc_clock::now();
|
||||
while (auto mut_opt = read_mutation_from_flat_mutation_reader(reader, db::no_timeout).get0()) {
|
||||
// We expect the range tombstones to be purged.
|
||||
mut_opt->partition().compact_for_query(*schema, now, {query::clustering_range::make_open_ended_both_sides()}, false,
|
||||
std::numeric_limits<uint32_t>::max());
|
||||
actual_mutations.emplace_back(std::move(*mut_opt));
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_REQUIRE_EQUAL(actual_mutations.size(), expected_mutations.size());
|
||||
|
||||
|
||||
auto ita = actual_mutations.begin();
|
||||
auto ite = expected_mutations.begin();
|
||||
for (;ita != actual_mutations.end(), ite != expected_mutations.end(); ++ita, ++ite) {
|
||||
assert_that(*ita).is_equal_to(*ite);
|
||||
}
|
||||
|
||||
return make_ready_future<>();
|
||||
}).get();
|
||||
}
|
||||
|
||||
// A reader that can controlled by it's "creator" after it's created.
|
||||
//
|
||||
// It can execute one of a set of actions on it's fill_buffer() call:
|
||||
@@ -2133,3 +2295,119 @@ SEASTAR_THREAD_TEST_CASE(test_multishard_combining_reader_destroyed_with_pending
|
||||
return make_ready_future<>();
|
||||
}).get();
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
std::deque<mutation_fragment> make_fragments_with_non_monotonic_positions(simple_schema& s, dht::decorated_key pkey, size_t max_buffer_size) {
|
||||
std::deque<mutation_fragment> fragments;
|
||||
|
||||
fragments.emplace_back(partition_start{std::move(pkey), {}});
|
||||
|
||||
int i = 0;
|
||||
size_t mem_usage = fragments.back().memory_usage(*s.schema());
|
||||
while (mem_usage <= max_buffer_size * 2) {
|
||||
fragments.emplace_back(s.make_range_tombstone(query::clustering_range::make(s.make_ckey(0), s.make_ckey(i + 1))));
|
||||
mem_usage += fragments.back().memory_usage(*s.schema());
|
||||
++i;
|
||||
}
|
||||
|
||||
fragments.emplace_back(s.make_row(s.make_ckey(0), "v"));
|
||||
|
||||
return fragments;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
// Test that the multishard reader will not skip any rows when the position of
|
||||
// the mutation fragments in the stream is not strictly monotonous.
|
||||
// See the explanation in `shard_reader::remote_reader::fill_buffer()`.
|
||||
// To test this we need to craft a mutation such that after the first
|
||||
// `fill_buffer()` call, as well as after the second one, the last fragment is a
|
||||
// range tombstone, with the very same position-in-partition.
|
||||
// This is to check that the reader will not skip the row after the
|
||||
// range-tombstones and that it can make progress (doesn't assume that an additional
|
||||
// fill buffer call will bring in a fragment with a higher position).
|
||||
SEASTAR_THREAD_TEST_CASE(test_multishard_combining_reader_non_strictly_monotonic_positions) {
|
||||
const size_t max_buffer_size = 512;
|
||||
const int pk = 0;
|
||||
simple_schema s;
|
||||
|
||||
// Validate that the generated fragments are fit for the very strict
|
||||
// requirement of this test.
|
||||
// The test is meaningless if these requirements are not met.
|
||||
{
|
||||
auto fragments = make_fragments_with_non_monotonic_positions(s, s.make_pkey(pk), max_buffer_size);
|
||||
auto rd = make_flat_mutation_reader_from_fragments(s.schema(), std::move(fragments));
|
||||
rd.set_max_buffer_size(max_buffer_size);
|
||||
|
||||
rd.fill_buffer(db::no_timeout).get();
|
||||
|
||||
auto mf = rd.pop_mutation_fragment();
|
||||
BOOST_REQUIRE_EQUAL(mf.mutation_fragment_kind(), mutation_fragment::kind::partition_start);
|
||||
|
||||
mf = rd.pop_mutation_fragment();
|
||||
BOOST_REQUIRE_EQUAL(mf.mutation_fragment_kind(), mutation_fragment::kind::range_tombstone);
|
||||
const auto ckey = mf.as_range_tombstone().start;
|
||||
|
||||
while (!rd.is_buffer_empty()) {
|
||||
mf = rd.pop_mutation_fragment();
|
||||
BOOST_REQUIRE_EQUAL(mf.mutation_fragment_kind(), mutation_fragment::kind::range_tombstone);
|
||||
BOOST_REQUIRE(mf.as_range_tombstone().start.equal(*s.schema(), ckey));
|
||||
}
|
||||
|
||||
rd.fill_buffer(db::no_timeout).get();
|
||||
|
||||
while (!rd.is_buffer_empty()) {
|
||||
mf = rd.pop_mutation_fragment();
|
||||
BOOST_REQUIRE_EQUAL(mf.mutation_fragment_kind(), mutation_fragment::kind::range_tombstone);
|
||||
BOOST_REQUIRE(mf.as_range_tombstone().start.equal(*s.schema(), ckey));
|
||||
}
|
||||
|
||||
rd.fill_buffer(db::no_timeout).get();
|
||||
|
||||
BOOST_REQUIRE(!rd.is_buffer_empty());
|
||||
|
||||
mf = rd.pop_mutation_fragment();
|
||||
BOOST_REQUIRE_EQUAL(mf.mutation_fragment_kind(), mutation_fragment::kind::clustering_row);
|
||||
BOOST_REQUIRE(mf.as_clustering_row().key().equal(*s.schema(), ckey));
|
||||
}
|
||||
|
||||
do_with_cql_env([=, s = std::move(s)] (cql_test_env& env) mutable -> future<> {
|
||||
auto factory = [=, gs = global_simple_schema(s)] (
|
||||
shard_id shard,
|
||||
schema_ptr,
|
||||
const dht::partition_range& range,
|
||||
const query::partition_slice& slice,
|
||||
const io_priority_class& pc,
|
||||
tracing::trace_state_ptr trace_state,
|
||||
mutation_reader::forwarding fwd_mr) {
|
||||
return smp::submit_to(shard, [gs = std::move(gs), &range, &slice] {
|
||||
auto s = gs.get();
|
||||
auto pkey = s.make_pkey(pk);
|
||||
if (dht::global_partitioner().shard_of(pkey.token()) != engine().cpu_id()) {
|
||||
return make_foreign(std::make_unique<flat_mutation_reader>(make_empty_flat_reader(s.schema())));
|
||||
}
|
||||
auto fragments = make_fragments_with_non_monotonic_positions(s, std::move(pkey), max_buffer_size);
|
||||
auto rd = make_flat_mutation_reader_from_fragments(s.schema(), std::move(fragments), range, slice);
|
||||
rd.set_max_buffer_size(max_buffer_size);
|
||||
return make_foreign(std::make_unique<flat_mutation_reader>(std::move(rd)));
|
||||
});
|
||||
};
|
||||
|
||||
auto fragments = make_fragments_with_non_monotonic_positions(s, s.make_pkey(pk), max_buffer_size);
|
||||
auto rd = make_flat_mutation_reader_from_fragments(s.schema(), std::move(fragments));
|
||||
auto mut_opt = read_mutation_from_flat_mutation_reader(rd, db::no_timeout).get0();
|
||||
BOOST_REQUIRE(mut_opt);
|
||||
|
||||
assert_that(make_multishard_combining_reader(
|
||||
seastar::make_shared<test_reader_lifecycle_policy>(std::move(factory), test_reader_lifecycle_policy::no_delay, true),
|
||||
dht::global_partitioner(),
|
||||
s.schema(),
|
||||
query::full_partition_range,
|
||||
s.schema()->full_slice(),
|
||||
service::get_local_sstable_query_read_priority()))
|
||||
.produces_partition(*mut_opt);
|
||||
|
||||
return make_ready_future<>();
|
||||
}).get();
|
||||
}
|
||||
|
||||
@@ -1253,6 +1253,104 @@ SEASTAR_THREAD_TEST_CASE(test_mutation_upgrade_type_change) {
|
||||
assert_that(m).is_equal_to(m2);
|
||||
}
|
||||
|
||||
// This test checks the behavior of row_marker::{is_live, is_dead, compact_and_expire}. Those functions have some
|
||||
// duplicated logic that decides if a row is expired, and this test verifies that they behave the same with respect
|
||||
// to TTL.
|
||||
SEASTAR_THREAD_TEST_CASE(test_row_marker_expiry) {
|
||||
can_gc_fn never_gc = [] (tombstone) { return false; };
|
||||
|
||||
auto must_be_alive = [&] (row_marker mark, gc_clock::time_point t) {
|
||||
BOOST_TEST_MESSAGE(format("must_be_alive({}, {})", mark, t));
|
||||
BOOST_REQUIRE(mark.is_live(tombstone(), t));
|
||||
BOOST_REQUIRE(mark.is_missing() || !mark.is_dead(t));
|
||||
BOOST_REQUIRE(mark.compact_and_expire(tombstone(), t, never_gc, gc_clock::time_point()));
|
||||
};
|
||||
|
||||
auto must_be_dead = [&] (row_marker mark, gc_clock::time_point t) {
|
||||
BOOST_TEST_MESSAGE(format("must_be_dead({}, {})", mark, t));
|
||||
BOOST_REQUIRE(!mark.is_live(tombstone(), t));
|
||||
BOOST_REQUIRE(mark.is_missing() || mark.is_dead(t));
|
||||
BOOST_REQUIRE(!mark.compact_and_expire(tombstone(), t, never_gc, gc_clock::time_point()));
|
||||
};
|
||||
|
||||
const auto timestamp = api::timestamp_type(1);
|
||||
const auto t0 = gc_clock::now();
|
||||
const auto t1 = t0 + 1s;
|
||||
const auto t2 = t0 + 2s;
|
||||
const auto t3 = t0 + 3s;
|
||||
|
||||
// Without timestamp the marker is missing (doesn't exist)
|
||||
const row_marker m1;
|
||||
must_be_dead(m1, t0);
|
||||
must_be_dead(m1, t1);
|
||||
must_be_dead(m1, t2);
|
||||
must_be_dead(m1, t3);
|
||||
|
||||
// With timestamp and without ttl, a row_marker is always alive
|
||||
const row_marker m2(timestamp);
|
||||
must_be_alive(m2, t0);
|
||||
must_be_alive(m2, t1);
|
||||
must_be_alive(m2, t2);
|
||||
must_be_alive(m2, t3);
|
||||
|
||||
// A row_marker becomes dead exactly at the moment of expiry
|
||||
// Reproduces #4263, #5290
|
||||
const auto ttl = 1s;
|
||||
const row_marker m3(timestamp, ttl, t2);
|
||||
must_be_alive(m3, t0);
|
||||
must_be_alive(m3, t1);
|
||||
must_be_dead(m3, t2);
|
||||
must_be_dead(m3, t3);
|
||||
}
|
||||
|
||||
SEASTAR_THREAD_TEST_CASE(test_querying_expired_rows) {
|
||||
auto s = schema_builder("ks", "cf")
|
||||
.with_column("pk", bytes_type, column_kind::partition_key)
|
||||
.with_column("ck", bytes_type, column_kind::clustering_key)
|
||||
.build();
|
||||
|
||||
auto pk = partition_key::from_singular(*s, data_value(bytes("key1")));
|
||||
auto ckey1 = clustering_key::from_singular(*s, data_value(bytes("A")));
|
||||
auto ckey2 = clustering_key::from_singular(*s, data_value(bytes("B")));
|
||||
auto ckey3 = clustering_key::from_singular(*s, data_value(bytes("C")));
|
||||
|
||||
auto ttl = 1s;
|
||||
auto t0 = gc_clock::now();
|
||||
auto t1 = t0 + 1s;
|
||||
auto t2 = t0 + 2s;
|
||||
auto t3 = t0 + 3s;
|
||||
|
||||
auto results_at_time = [s] (const mutation& m, gc_clock::time_point t) {
|
||||
auto slice = partition_slice_builder(*s)
|
||||
.without_partition_key_columns()
|
||||
.build();
|
||||
auto opts = query::result_options{query::result_request::result_and_digest, query::digest_algorithm::xxHash};
|
||||
return query::result_set::from_raw_result(s, slice, m.query(slice, opts, t));
|
||||
};
|
||||
|
||||
mutation m(s, pk);
|
||||
m.partition().clustered_row(*m.schema(), ckey1).apply(row_marker(api::new_timestamp(), ttl, t1));
|
||||
m.partition().clustered_row(*m.schema(), ckey2).apply(row_marker(api::new_timestamp(), ttl, t2));
|
||||
m.partition().clustered_row(*m.schema(), ckey3).apply(row_marker(api::new_timestamp(), ttl, t3));
|
||||
|
||||
assert_that(results_at_time(m, t0))
|
||||
.has_size(3)
|
||||
.has(a_row().with_column("ck", data_value(bytes("A"))))
|
||||
.has(a_row().with_column("ck", data_value(bytes("B"))))
|
||||
.has(a_row().with_column("ck", data_value(bytes("C"))));
|
||||
|
||||
assert_that(results_at_time(m, t1))
|
||||
.has_size(2)
|
||||
.has(a_row().with_column("ck", data_value(bytes("B"))))
|
||||
.has(a_row().with_column("ck", data_value(bytes("C"))));
|
||||
|
||||
assert_that(results_at_time(m, t2))
|
||||
.has_size(1)
|
||||
.has(a_row().with_column("ck", data_value(bytes("C"))));
|
||||
|
||||
assert_that(results_at_time(m, t3)).is_empty();
|
||||
}
|
||||
|
||||
SEASTAR_TEST_CASE(test_querying_expired_cells) {
|
||||
return seastar::async([] {
|
||||
auto s = schema_builder("ks", "cf")
|
||||
|
||||
@@ -269,7 +269,10 @@ void mvcc_partition::apply_to_evictable(partition_entry&& src, schema_ptr src_sc
|
||||
logalloc::allocating_section as;
|
||||
mutation_cleaner src_cleaner(region(), no_cache_tracker);
|
||||
auto c = as(region(), [&] {
|
||||
return _e.apply_to_incomplete(*schema(), std::move(src), *src_schema, src_cleaner, as, region(),
|
||||
if (_s != src_schema) {
|
||||
src.upgrade(src_schema, _s, src_cleaner, no_cache_tracker);
|
||||
}
|
||||
return _e.apply_to_incomplete(*schema(), std::move(src), src_cleaner, as, region(),
|
||||
*_container.tracker(), _container.next_phase(), _container.accounter());
|
||||
});
|
||||
repeat([&] {
|
||||
|
||||
@@ -65,6 +65,16 @@ struct table {
|
||||
c_keys = s.make_ckeys(rows);
|
||||
}
|
||||
|
||||
void set_schema(schema_ptr new_s) {
|
||||
s.set_schema(new_s);
|
||||
mt->set_schema(new_s);
|
||||
if (prev_mt) {
|
||||
prev_mt->set_schema(new_s);
|
||||
}
|
||||
cache.set_schema(new_s);
|
||||
underlying.set_schema(new_s);
|
||||
}
|
||||
|
||||
size_t index_of_key(const dht::decorated_key& dk) {
|
||||
for (auto i : boost::irange<size_t>(0, p_keys.size())) {
|
||||
if (p_keys[i].equal(*s.schema(), dk)) {
|
||||
@@ -125,16 +135,28 @@ struct table {
|
||||
flat_mutation_reader rd;
|
||||
};
|
||||
|
||||
void alter_schema() {
|
||||
static thread_local int col_id = 0;
|
||||
auto new_s = schema_builder(s.schema())
|
||||
.with_column(to_bytes(format("_a{}", col_id++)), byte_type)
|
||||
.build();
|
||||
test_log.trace("changing schema to {}", *new_s);
|
||||
set_schema(new_s);
|
||||
}
|
||||
|
||||
std::unique_ptr<reader> make_reader(dht::partition_range pr, query::partition_slice slice) {
|
||||
test_log.trace("making reader, pk={} ck={}", pr, slice);
|
||||
auto r = std::make_unique<reader>(reader{std::move(pr), std::move(slice), make_empty_flat_reader(s.schema())});
|
||||
std::vector<flat_mutation_reader> rd;
|
||||
if (prev_mt) {
|
||||
rd.push_back(prev_mt->make_flat_reader(s.schema(), r->pr, r->slice));
|
||||
rd.push_back(prev_mt->make_flat_reader(s.schema(), r->pr, r->slice, default_priority_class(), nullptr,
|
||||
streamed_mutation::forwarding::no, mutation_reader::forwarding::no));
|
||||
}
|
||||
rd.push_back(mt->make_flat_reader(s.schema(), r->pr, r->slice));
|
||||
rd.push_back(cache.make_reader(s.schema(), r->pr, r->slice));
|
||||
r->rd = make_combined_reader(s.schema(), std::move(rd), streamed_mutation::forwarding::yes, mutation_reader::forwarding::no);
|
||||
rd.push_back(mt->make_flat_reader(s.schema(), r->pr, r->slice, default_priority_class(), nullptr,
|
||||
streamed_mutation::forwarding::no, mutation_reader::forwarding::no));
|
||||
rd.push_back(cache.make_reader(s.schema(), r->pr, r->slice, default_priority_class(), nullptr,
|
||||
streamed_mutation::forwarding::no, mutation_reader::forwarding::no));
|
||||
r->rd = make_combined_reader(s.schema(), std::move(rd), streamed_mutation::forwarding::no, mutation_reader::forwarding::no);
|
||||
return r;
|
||||
}
|
||||
|
||||
@@ -168,11 +190,13 @@ class validating_consumer {
|
||||
size_t _row_count = 0;
|
||||
size_t _key = 0;
|
||||
std::vector<api::timestamp_type> _writetimes;
|
||||
schema_ptr _s;
|
||||
public:
|
||||
validating_consumer(table& t, reader_id id)
|
||||
validating_consumer(table& t, reader_id id, schema_ptr s)
|
||||
: _t(t)
|
||||
, _id(id)
|
||||
, _writetimes(t.p_writetime)
|
||||
, _s(s)
|
||||
{ }
|
||||
|
||||
void consume_new_partition(const dht::decorated_key& key) {
|
||||
@@ -190,7 +214,7 @@ public:
|
||||
++_row_count;
|
||||
sstring value;
|
||||
api::timestamp_type t;
|
||||
std::tie(value, t) = _t.s.get_value(row);
|
||||
std::tie(value, t) = _t.s.get_value(*_s, row);
|
||||
test_log.trace("reader {}: {} @{}, {}", _id, value, t, row);
|
||||
if (_value && value != _value) {
|
||||
throw std::runtime_error(sprint("Saw values from two different writes in partition %d: %s and %s", _key, _value, value));
|
||||
@@ -305,7 +329,7 @@ int main(int argc, char** argv) {
|
||||
while (!cancelled) {
|
||||
test_log.trace("{}: starting read", id);
|
||||
auto rd = t.make_single_key_reader(pk, ck_range);
|
||||
auto row_count = rd->rd.consume(validating_consumer(t, id), db::no_timeout).get0();
|
||||
auto row_count = rd->rd.consume(validating_consumer(t, id, t.s.schema()), db::no_timeout).get0();
|
||||
if (row_count != len) {
|
||||
throw std::runtime_error(sprint("Expected %d fragments, got %d", len, row_count));
|
||||
}
|
||||
@@ -317,7 +341,7 @@ int main(int argc, char** argv) {
|
||||
while (!cancelled) {
|
||||
test_log.trace("{}: starting read", id);
|
||||
auto rd = t.make_scanning_reader();
|
||||
auto row_count = rd->rd.consume(validating_consumer(t, id), db::no_timeout).get0();
|
||||
auto row_count = rd->rd.consume(validating_consumer(t, id, t.s.schema()), db::no_timeout).get0();
|
||||
if (row_count != expected_row_count) {
|
||||
throw std::runtime_error(sprint("Expected %d fragments, got %d", expected_row_count, row_count));
|
||||
}
|
||||
@@ -352,6 +376,12 @@ int main(int argc, char** argv) {
|
||||
});
|
||||
evictor.arm_periodic(3s);
|
||||
|
||||
timer<> schema_changer;
|
||||
schema_changer.set_callback([&] {
|
||||
t.alter_schema();
|
||||
});
|
||||
schema_changer.arm_periodic(1s);
|
||||
|
||||
// Mutator
|
||||
while (!cancelled) {
|
||||
t.mutate_next_phase();
|
||||
@@ -362,6 +392,13 @@ int main(int argc, char** argv) {
|
||||
evictor.cancel();
|
||||
readers.get();
|
||||
scanning_readers.get();
|
||||
|
||||
t.cache.evict();
|
||||
t.tracker.cleaner().drain().get();
|
||||
t.tracker.memtable_cleaner().drain().get();
|
||||
|
||||
assert(t.tracker.get_stats().partitions == 0);
|
||||
assert(t.tracker.get_stats().rows == 0);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
#include <boost/test/unit_test.hpp>
|
||||
#include <seastar/core/sleep.hh>
|
||||
#include <seastar/util/defer.hh>
|
||||
#include <seastar/util/backtrace.hh>
|
||||
#include <seastar/util/alloc_failure_injector.hh>
|
||||
#include <boost/algorithm/cxx11/any_of.hpp>
|
||||
@@ -3196,6 +3197,66 @@ SEASTAR_TEST_CASE(test_concurrent_reads_and_eviction) {
|
||||
});
|
||||
}
|
||||
|
||||
SEASTAR_TEST_CASE(test_alter_then_preempted_update_then_memtable_read) {
|
||||
return seastar::async([] {
|
||||
simple_schema ss;
|
||||
memtable_snapshot_source underlying(ss.schema());
|
||||
schema_ptr s = ss.schema();
|
||||
|
||||
auto pk = ss.make_pkey("pk");
|
||||
mutation m(s, pk);
|
||||
mutation m2(s, pk);
|
||||
const int c_keys = 10000; // enough for update to be preempted
|
||||
for (auto ck : ss.make_ckeys(c_keys)) {
|
||||
ss.add_row(m, ck, "tag1");
|
||||
ss.add_row(m2, ck, "tag2");
|
||||
}
|
||||
|
||||
underlying.apply(m);
|
||||
|
||||
cache_tracker tracker;
|
||||
row_cache cache(s, snapshot_source([&] { return underlying(); }), tracker);
|
||||
|
||||
auto pr = dht::partition_range::make_singular(m.decorated_key());
|
||||
|
||||
// Populate the cache so that update has an entry to update.
|
||||
assert_that(cache.make_reader(s, pr)).produces(m);
|
||||
|
||||
auto mt2 = make_lw_shared<memtable>(s);
|
||||
mt2->apply(m2);
|
||||
|
||||
// Alter the schema
|
||||
auto s2 = schema_builder(s)
|
||||
.with_column(to_bytes("_a"), byte_type)
|
||||
.build();
|
||||
cache.set_schema(s2);
|
||||
mt2->set_schema(s2);
|
||||
|
||||
auto update_f = cache.update([&] () noexcept {
|
||||
underlying.apply(m2);
|
||||
}, *mt2);
|
||||
auto wait_for_update = defer([&] { update_f.get(); });
|
||||
|
||||
// Wait for cache update to enter the partition
|
||||
while (tracker.get_stats().partition_merges == 0) {
|
||||
later().get();
|
||||
}
|
||||
|
||||
auto mt2_reader = mt2->make_flat_reader(s, pr, s->full_slice(), default_priority_class(),
|
||||
nullptr, streamed_mutation::forwarding::no, mutation_reader::forwarding::no);
|
||||
auto cache_reader = cache.make_reader(s, pr, s->full_slice(), default_priority_class(),
|
||||
nullptr, streamed_mutation::forwarding::no, mutation_reader::forwarding::no);
|
||||
|
||||
assert_that(std::move(mt2_reader)).produces(m2);
|
||||
assert_that(std::move(cache_reader)).produces(m);
|
||||
|
||||
wait_for_update.cancel();
|
||||
update_f.get();
|
||||
|
||||
assert_that(cache.make_reader(s)).produces(m + m2);
|
||||
});
|
||||
}
|
||||
|
||||
SEASTAR_TEST_CASE(test_cache_update_and_eviction_preserves_monotonicity_of_memtable_readers) {
|
||||
// Verifies that memtable readers created before memtable is moved to cache
|
||||
// are not affected by eviction in cache after their partition entries were moved to cache.
|
||||
|
||||
@@ -22,6 +22,9 @@
|
||||
#include "tests/test-utils.hh"
|
||||
#include "tests/cql_test_env.hh"
|
||||
#include "tests/cql_assertions.hh"
|
||||
#include "service/pager/paging_state.hh"
|
||||
#include "transport/messages/result_message.hh"
|
||||
#include "cql3/statements/select_statement.hh"
|
||||
|
||||
|
||||
SEASTAR_TEST_CASE(test_secondary_index_regular_column_query) {
|
||||
@@ -462,6 +465,96 @@ SEASTAR_TEST_CASE(test_index_on_pk_ck_with_paging) {
|
||||
});
|
||||
}
|
||||
|
||||
SEASTAR_TEST_CASE(test_simple_index_paging) {
|
||||
return do_with_cql_env_thread([] (auto& e) {
|
||||
e.execute_cql("CREATE TABLE tab (p int, c int, v int, PRIMARY KEY (p, c))").get();
|
||||
e.execute_cql("CREATE INDEX ON tab (v)").get();
|
||||
e.execute_cql("CREATE INDEX ON tab (c)").get();
|
||||
|
||||
e.execute_cql("INSERT INTO tab (p, c, v) VALUES (1, 2, 1)").get();
|
||||
e.execute_cql("INSERT INTO tab (p, c, v) VALUES (1, 1, 1)").get();
|
||||
e.execute_cql("INSERT INTO tab (p, c, v) VALUES (3, 2, 1)").get();
|
||||
|
||||
auto extract_paging_state = [] (::shared_ptr<cql_transport::messages::result_message> res) {
|
||||
auto rows = dynamic_pointer_cast<cql_transport::messages::result_message::rows>(res);
|
||||
auto paging_state = rows->rs().get_metadata().paging_state();
|
||||
assert(paging_state);
|
||||
return ::make_shared<service::pager::paging_state>(*paging_state);
|
||||
};
|
||||
|
||||
auto expect_more_pages = [] (::shared_ptr<cql_transport::messages::result_message> res, bool more_pages_expected) {
|
||||
auto rows = dynamic_pointer_cast<cql_transport::messages::result_message::rows>(res);
|
||||
if(more_pages_expected != rows->rs().get_metadata().flags().contains(cql3::metadata::flag::HAS_MORE_PAGES)) {
|
||||
throw std::runtime_error(format("Expected {}more pages", more_pages_expected ? "" : "no "));
|
||||
}
|
||||
};
|
||||
|
||||
eventually([&] {
|
||||
auto qo = std::make_unique<cql3::query_options>(db::consistency_level::LOCAL_ONE, infinite_timeout_config, std::vector<cql3::raw_value>{},
|
||||
cql3::query_options::specific_options{1, nullptr, {}, api::new_timestamp()});
|
||||
auto res = e.execute_cql("SELECT * FROM tab WHERE v = 1", std::move(qo)).get0();
|
||||
auto paging_state = extract_paging_state(res);
|
||||
expect_more_pages(res, true);
|
||||
|
||||
assert_that(res).is_rows().with_rows({{
|
||||
{int32_type->decompose(3)}, {int32_type->decompose(2)}, {int32_type->decompose(1)},
|
||||
}});
|
||||
|
||||
qo = std::make_unique<cql3::query_options>(db::consistency_level::LOCAL_ONE, infinite_timeout_config, std::vector<cql3::raw_value>{},
|
||||
cql3::query_options::specific_options{1, paging_state, {}, api::new_timestamp()});
|
||||
res = e.execute_cql("SELECT * FROM tab WHERE v = 1", std::move(qo)).get0();
|
||||
expect_more_pages(res, true);
|
||||
paging_state = extract_paging_state(res);
|
||||
|
||||
assert_that(res).is_rows().with_rows({{
|
||||
{int32_type->decompose(1)}, {int32_type->decompose(1)}, {int32_type->decompose(1)},
|
||||
}});
|
||||
|
||||
qo = std::make_unique<cql3::query_options>(db::consistency_level::LOCAL_ONE, infinite_timeout_config, std::vector<cql3::raw_value>{},
|
||||
cql3::query_options::specific_options{1, paging_state, {}, api::new_timestamp()});
|
||||
res = e.execute_cql("SELECT * FROM tab WHERE v = 1", std::move(qo)).get0();
|
||||
paging_state = extract_paging_state(res);
|
||||
|
||||
assert_that(res).is_rows().with_rows({{
|
||||
{int32_type->decompose(1)}, {int32_type->decompose(2)}, {int32_type->decompose(1)},
|
||||
}});
|
||||
|
||||
// Due to implementation differences from origin (Scylla is allowed to return empty pages with has_more_pages == true
|
||||
// and it's a legal operation), paging indexes may result in finding an additional empty page at the end of the results,
|
||||
// but never more than one. This case used to be buggy (see #4569).
|
||||
try {
|
||||
expect_more_pages(res, false);
|
||||
} catch (...) {
|
||||
qo = std::make_unique<cql3::query_options>(db::consistency_level::LOCAL_ONE, infinite_timeout_config, std::vector<cql3::raw_value>{},
|
||||
cql3::query_options::specific_options{1, paging_state, {}, api::new_timestamp()});
|
||||
res = e.execute_cql("SELECT * FROM tab WHERE v = 1", std::move(qo)).get0();
|
||||
assert_that(res).is_rows().with_size(0);
|
||||
expect_more_pages(res, false);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
eventually([&] {
|
||||
auto qo = std::make_unique<cql3::query_options>(db::consistency_level::LOCAL_ONE, infinite_timeout_config, std::vector<cql3::raw_value>{},
|
||||
cql3::query_options::specific_options{1, nullptr, {}, api::new_timestamp()});
|
||||
auto res = e.execute_cql("SELECT * FROM tab WHERE c = 2", std::move(qo)).get0();
|
||||
auto paging_state = extract_paging_state(res);
|
||||
|
||||
assert_that(res).is_rows().with_rows({{
|
||||
{int32_type->decompose(3)}, {int32_type->decompose(2)}, {int32_type->decompose(1)},
|
||||
}});
|
||||
|
||||
qo = std::make_unique<cql3::query_options>(db::consistency_level::LOCAL_ONE, infinite_timeout_config, std::vector<cql3::raw_value>{},
|
||||
cql3::query_options::specific_options{1, paging_state, {}, api::new_timestamp()});
|
||||
res = e.execute_cql("SELECT * FROM tab WHERE c = 2", std::move(qo)).get0();
|
||||
|
||||
assert_that(res).is_rows().with_rows({{
|
||||
{int32_type->decompose(1)}, {int32_type->decompose(2)}, {int32_type->decompose(1)},
|
||||
}});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
SEASTAR_TEST_CASE(test_secondary_index_collections) {
|
||||
return do_with_cql_env_thread([] (cql_test_env& e) {
|
||||
e.execute_cql("create table t (p int primary key, s1 set<int>, m1 map<int, text>, l1 list<int>, s2 frozen<set<int>>, m2 frozen<map<int, text>>, l2 frozen<list<int>>)").get();
|
||||
@@ -534,3 +627,92 @@ SEASTAR_TEST_CASE(test_secondary_index_create_custom_index) {
|
||||
assert_that_failed(e.execute_cql("create index on cf (a) using 'org.apache.cassandra.index.sasi.SASIIndex'"));
|
||||
});
|
||||
}
|
||||
|
||||
// Reproducer for #4144
|
||||
SEASTAR_TEST_CASE(test_secondary_index_contains_virtual_columns) {
|
||||
return do_with_cql_env_thread([] (cql_test_env& e) {
|
||||
e.execute_cql("create table cf (p int, c int, v int, primary key(p, c))").get();
|
||||
e.execute_cql("create index on cf (c)").get();
|
||||
e.execute_cql("update cf set v = 1 where p = 1 and c = 1").get();
|
||||
eventually([&] {
|
||||
auto res = e.execute_cql("select * from cf where c = 1").get0();
|
||||
assert_that(res).is_rows().with_rows({{{int32_type->decompose(1)}, {int32_type->decompose(1)}, {int32_type->decompose(1)}}});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Reproduces issue #4539 - a partition key index should not influence a filtering decision for regular columns.
|
||||
// Previously, given sequence resulted in a "No index found" error.
|
||||
SEASTAR_TEST_CASE(test_secondary_index_on_partition_key_with_filtering) {
|
||||
return do_with_cql_env_thread([] (cql_test_env& e) {
|
||||
e.execute_cql("CREATE TABLE test_a(a int, b int, c int, PRIMARY KEY ((a, b)));").get();
|
||||
e.execute_cql("CREATE INDEX ON test_a(a);").get();
|
||||
e.execute_cql("INSERT INTO test_a (a, b, c) VALUES (1, 2, 3);").get();
|
||||
eventually([&] {
|
||||
auto res = e.execute_cql("SELECT * FROM test_a WHERE a = 1 AND b = 2 AND c = 3 ALLOW FILTERING;").get0();
|
||||
assert_that(res).is_rows().with_rows({
|
||||
{{int32_type->decompose(1)}, {int32_type->decompose(2)}, {int32_type->decompose(3)}}});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
SEASTAR_TEST_CASE(test_indexing_paging_and_aggregation) {
|
||||
static constexpr int row_count = 2 * cql3::statements::select_statement::DEFAULT_COUNT_PAGE_SIZE + 120;
|
||||
|
||||
return do_with_cql_env_thread([] (cql_test_env& e) {
|
||||
e.execute_cql("CREATE TABLE fpa (id int primary key, v int)").get();
|
||||
e.execute_cql("CREATE INDEX ON fpa(v)").get();
|
||||
for (int i = 0; i < row_count; ++i) {
|
||||
e.execute_cql(format("INSERT INTO fpa (id, v) VALUES ({}, {})", i + 1, i % 2).c_str()).get();
|
||||
}
|
||||
|
||||
eventually([&] {
|
||||
auto qo = std::make_unique<cql3::query_options>(db::consistency_level::LOCAL_ONE, infinite_timeout_config, std::vector<cql3::raw_value>{},
|
||||
cql3::query_options::specific_options{2, nullptr, {}, api::new_timestamp()});
|
||||
auto msg = e.execute_cql("SELECT sum(id) FROM fpa WHERE v = 0;", std::move(qo)).get0();
|
||||
// Even though we set up paging, we still expect a single result from an aggregation function.
|
||||
// Also, instead of the user-provided page size, internal DEFAULT_COUNT_PAGE_SIZE is expected to be used.
|
||||
assert_that(msg).is_rows().with_rows({
|
||||
{ int32_type->decompose(row_count * row_count / 4)},
|
||||
});
|
||||
|
||||
// Even if paging is not explicitly used, the query will be internally paged to avoid OOM.
|
||||
msg = e.execute_cql("SELECT sum(id) FROM fpa WHERE v = 1;").get0();
|
||||
assert_that(msg).is_rows().with_rows({
|
||||
{ int32_type->decompose(row_count * row_count / 4 + row_count / 2)},
|
||||
});
|
||||
|
||||
qo = std::make_unique<cql3::query_options>(db::consistency_level::LOCAL_ONE, infinite_timeout_config, std::vector<cql3::raw_value>{},
|
||||
cql3::query_options::specific_options{3, nullptr, {}, api::new_timestamp()});
|
||||
msg = e.execute_cql("SELECT avg(id) FROM fpa WHERE v = 1;", std::move(qo)).get0();
|
||||
assert_that(msg).is_rows().with_rows({
|
||||
{ int32_type->decompose(row_count / 2 + 1)},
|
||||
});
|
||||
});
|
||||
|
||||
// Similar, but this time a non-prefix clustering key part is indexed (wrt. issue 3405, after which we have
|
||||
// a special code path for indexing composite non-prefix clustering keys).
|
||||
e.execute_cql("CREATE TABLE fpa2 (id int, c1 int, c2 int, primary key (id, c1, c2))").get();
|
||||
e.execute_cql("CREATE INDEX ON fpa2(c2)").get();
|
||||
eventually([&] {
|
||||
for (int i = 0; i < row_count; ++i) {
|
||||
e.execute_cql(format("INSERT INTO fpa2 (id, c1, c2) VALUES ({}, {}, {})", i + 1, i + 1, i % 2).c_str()).get();
|
||||
}
|
||||
|
||||
auto qo = std::make_unique<cql3::query_options>(db::consistency_level::LOCAL_ONE, infinite_timeout_config, std::vector<cql3::raw_value>{},
|
||||
cql3::query_options::specific_options{2, nullptr, {}, api::new_timestamp()});
|
||||
auto msg = e.execute_cql("SELECT sum(id) FROM fpa2 WHERE c2 = 0;", std::move(qo)).get0();
|
||||
// Even though we set up paging, we still expect a single result from an aggregation function
|
||||
assert_that(msg).is_rows().with_rows({
|
||||
{ int32_type->decompose(row_count * row_count / 4)},
|
||||
});
|
||||
|
||||
qo = std::make_unique<cql3::query_options>(db::consistency_level::LOCAL_ONE, infinite_timeout_config, std::vector<cql3::raw_value>{},
|
||||
cql3::query_options::specific_options{3, nullptr, {}, api::new_timestamp()});
|
||||
msg = e.execute_cql("SELECT avg(id) FROM fpa2 WHERE c2 = 1;", std::move(qo)).get0();
|
||||
assert_that(msg).is_rows().with_rows({
|
||||
{ int32_type->decompose(row_count / 2 + 1)},
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -40,12 +40,21 @@ class simple_schema {
|
||||
|
||||
schema_ptr _s;
|
||||
api::timestamp_type _timestamp = api::min_timestamp;
|
||||
const column_definition& _v_def;
|
||||
const column_definition* _v_def = nullptr;
|
||||
table_schema_version _v_def_version;
|
||||
|
||||
simple_schema(schema_ptr s, api::timestamp_type timestamp)
|
||||
: _s(s)
|
||||
, _timestamp(timestamp)
|
||||
, _v_def(*_s->get_column_definition(to_bytes("v"))) {
|
||||
{}
|
||||
private:
|
||||
const column_definition& get_v_def(const schema& s) {
|
||||
if (_v_def_version == s.version() && _v_def) {
|
||||
return *_v_def;
|
||||
}
|
||||
_v_def = s.get_column_definition(to_bytes("v"));
|
||||
_v_def_version = s.version();
|
||||
return *_v_def;
|
||||
}
|
||||
public:
|
||||
api::timestamp_type current_timestamp() {
|
||||
@@ -66,7 +75,6 @@ public:
|
||||
.with_column("s1", utf8_type, ws ? column_kind::static_column : column_kind::regular_column)
|
||||
.with_column("v", utf8_type)
|
||||
.build())
|
||||
, _v_def(*_s->get_column_definition(to_bytes("v")))
|
||||
{ }
|
||||
|
||||
sstring cql() const {
|
||||
@@ -101,16 +109,18 @@ public:
|
||||
if (t == api::missing_timestamp) {
|
||||
t = new_timestamp();
|
||||
}
|
||||
m.set_clustered_cell(key, _v_def, atomic_cell::make_live(*_v_def.type, t, data_value(v).serialize()));
|
||||
const column_definition& v_def = get_v_def(*_s);
|
||||
m.set_clustered_cell(key, v_def, atomic_cell::make_live(*v_def.type, t, data_value(v).serialize()));
|
||||
return t;
|
||||
}
|
||||
|
||||
std::pair<sstring, api::timestamp_type> get_value(const clustering_row& row) {
|
||||
auto cell = row.cells().find_cell(_v_def.id);
|
||||
std::pair<sstring, api::timestamp_type> get_value(const schema& s, const clustering_row& row) {
|
||||
const column_definition& v_def = get_v_def(s);
|
||||
auto cell = row.cells().find_cell(v_def.id);
|
||||
if (!cell) {
|
||||
throw std::runtime_error("cell not found");
|
||||
}
|
||||
atomic_cell_view ac = cell->as_atomic_cell(_v_def);
|
||||
atomic_cell_view ac = cell->as_atomic_cell(v_def);
|
||||
if (!ac.is_live()) {
|
||||
throw std::runtime_error("cell is dead");
|
||||
}
|
||||
@@ -119,17 +129,22 @@ public:
|
||||
|
||||
mutation_fragment make_row(const clustering_key& key, sstring v) {
|
||||
auto row = clustering_row(key);
|
||||
row.cells().apply(*_s->get_column_definition(to_bytes(sstring("v"))),
|
||||
atomic_cell::make_live(*_v_def.type, new_timestamp(), data_value(v).serialize()));
|
||||
const column_definition& v_def = get_v_def(*_s);
|
||||
row.cells().apply(v_def, atomic_cell::make_live(*v_def.type, new_timestamp(), data_value(v).serialize()));
|
||||
return mutation_fragment(std::move(row));
|
||||
}
|
||||
|
||||
mutation_fragment make_row_from_serialized_value(const clustering_key& key, bytes_view v) {
|
||||
auto row = clustering_row(key);
|
||||
row.cells().apply(_v_def, atomic_cell::make_live(*_v_def.type, new_timestamp(), v));
|
||||
const column_definition& v_def = get_v_def(*_s);
|
||||
row.cells().apply(v_def, atomic_cell::make_live(*v_def.type, new_timestamp(), v));
|
||||
return mutation_fragment(std::move(row));
|
||||
}
|
||||
|
||||
void set_schema(schema_ptr s) {
|
||||
_s = s;
|
||||
}
|
||||
|
||||
api::timestamp_type add_static_row(mutation& m, sstring s1, api::timestamp_type t = api::missing_timestamp) {
|
||||
if (t == api::missing_timestamp) {
|
||||
t = new_timestamp();
|
||||
|
||||
@@ -3639,6 +3639,32 @@ SEASTAR_THREAD_TEST_CASE(test_write_missing_columns_large_set) {
|
||||
validate_stats_metadata(s, written_sst, table_name);
|
||||
}
|
||||
|
||||
SEASTAR_THREAD_TEST_CASE(test_write_empty_counter) {
|
||||
auto abj = defer([] { await_background_jobs().get(); });
|
||||
sstring table_name = "empty_counter";
|
||||
// CREATE TABLE empty_counter (pk text, ck text, val counter, PRIMARY KEY (pk, ck)) WITH compression = {'sstable_compression': ''};
|
||||
schema_builder builder("sst3", table_name);
|
||||
builder.with_column("pk", utf8_type, column_kind::partition_key);
|
||||
builder.with_column("ck", utf8_type, column_kind::clustering_key);
|
||||
builder.with_column("val", counter_type);
|
||||
builder.set_compressor_params(compression_parameters::no_compression());
|
||||
schema_ptr s = builder.build(schema_builder::compact_storage::no);
|
||||
|
||||
lw_shared_ptr<memtable> mt = make_lw_shared<memtable>(s);
|
||||
auto key = partition_key::from_exploded(*s, {to_bytes("key")});
|
||||
mutation mut{s, key};
|
||||
|
||||
const column_definition& cdef = *s->get_column_definition("val");
|
||||
auto ckey = clustering_key::from_exploded(*s, {to_bytes("ck")});
|
||||
|
||||
counter_cell_builder b;
|
||||
mut.set_clustered_cell(ckey, cdef, b.build(write_timestamp));
|
||||
|
||||
mt->apply(mut);
|
||||
tmpdir tmp = write_and_compare_sstables(s, mt, table_name);
|
||||
validate_read(s, tmp.path, {mut});
|
||||
}
|
||||
|
||||
SEASTAR_THREAD_TEST_CASE(test_write_counter_table) {
|
||||
auto abj = defer([] { await_background_jobs().get(); });
|
||||
sstring table_name = "counter_table";
|
||||
|
||||
@@ -3991,6 +3991,45 @@ SEASTAR_TEST_CASE(sstable_set_incremental_selector) {
|
||||
return make_ready_future<>();
|
||||
}
|
||||
|
||||
SEASTAR_TEST_CASE(sstable_set_erase) {
|
||||
auto s = make_lw_shared(schema({}, some_keyspace, some_column_family,
|
||||
{{"p1", utf8_type}}, {}, {}, {}, utf8_type));
|
||||
auto key_and_token_pair = token_generation_for_current_shard(1);
|
||||
|
||||
// check that sstable_set::erase is capable of working properly when a non-existing element is given.
|
||||
{
|
||||
auto cs = sstables::make_compaction_strategy(sstables::compaction_strategy_type::leveled, s->compaction_strategy_options());
|
||||
sstable_set set = cs.make_sstable_set(s);
|
||||
|
||||
auto sst = sstable_for_overlapping_test(s, 0, key_and_token_pair[0].first, key_and_token_pair[0].first, 0);
|
||||
set.insert(sst);
|
||||
BOOST_REQUIRE(set.all()->size() == 1);
|
||||
|
||||
auto unleveled_sst = sstable_for_overlapping_test(s, 1, key_and_token_pair[0].first, key_and_token_pair[0].first, 0);
|
||||
auto leveled_sst = sstable_for_overlapping_test(s, 2, key_and_token_pair[0].first, key_and_token_pair[0].first, 1);
|
||||
set.erase(unleveled_sst);
|
||||
set.erase(leveled_sst);
|
||||
BOOST_REQUIRE(set.all()->size() == 1);
|
||||
BOOST_REQUIRE(set.all()->count(sst));
|
||||
}
|
||||
|
||||
{
|
||||
auto cs = sstables::make_compaction_strategy(sstables::compaction_strategy_type::size_tiered, s->compaction_strategy_options());
|
||||
sstable_set set = cs.make_sstable_set(s);
|
||||
|
||||
auto sst = sstable_for_overlapping_test(s, 0, key_and_token_pair[0].first, key_and_token_pair[0].first, 0);
|
||||
set.insert(sst);
|
||||
BOOST_REQUIRE(set.all()->size() == 1);
|
||||
|
||||
auto sst2 = sstable_for_overlapping_test(s, 1, key_and_token_pair[0].first, key_and_token_pair[0].first, 0);
|
||||
set.erase(sst2);
|
||||
BOOST_REQUIRE(set.all()->size() == 1);
|
||||
BOOST_REQUIRE(set.all()->count(sst));
|
||||
}
|
||||
|
||||
return make_ready_future<>();
|
||||
}
|
||||
|
||||
SEASTAR_TEST_CASE(sstable_resharding_strategy_tests) {
|
||||
// TODO: move it to sstable_resharding_test.cc. Unable to do so now because of linking issues
|
||||
// when using sstables::stats_metadata at sstable_resharding_test.cc.
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
||||
4022779186
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user