From 25c1a16f8e06badef9ab12b873b9706d74791c1f Mon Sep 17 00:00:00 2001 From: Benny Halevy Date: Thu, 21 May 2020 17:43:33 +0300 Subject: [PATCH 01/27] sstables: move column_name_helper to metadata_collector.cc It is used only for updating the metadata_collector {min,max}_column_names. Implement metadata_collector::do_update_min_max_components in sstables/metadata_collector.cc that will be used to host some other metadata_collector methods in following patches that need not be implemented in the header file. Signed-off-by: Benny Halevy --- configure.py | 1 + sstables/column_name_helper.hh | 76 ---------------------------------- sstables/mc/writer.cc | 10 +---- sstables/metadata_collector.cc | 57 +++++++++++++++++++++++++ sstables/metadata_collector.hh | 8 ++++ sstables/sstables.cc | 5 +-- sstables/types.hh | 1 - 7 files changed, 69 insertions(+), 89 deletions(-) delete mode 100644 sstables/column_name_helper.hh create mode 100644 sstables/metadata_collector.cc diff --git a/configure.py b/configure.py index 60f9316f1a..07afcd0166 100755 --- a/configure.py +++ b/configure.py @@ -560,6 +560,7 @@ scylla_core = (['database.cc', 'sstables/m_format_read_helpers.cc', 'sstables/sstable_directory.cc', 'sstables/random_access_reader.cc', + 'sstables/metadata_collector.cc', 'transport/cql_protocol_extension.cc', 'transport/event.cc', 'transport/event_notifier.cc', diff --git a/sstables/column_name_helper.hh b/sstables/column_name_helper.hh deleted file mode 100644 index 37bed0bd90..0000000000 --- a/sstables/column_name_helper.hh +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* - * Copyright (C) 2015 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 . - */ - -#pragma once - -#include -#include "schema_fwd.hh" -#include "compound_compat.hh" -#include -#include -#include - -class column_name_helper { -private: - static inline void may_grow(std::vector& v, size_t target_size) { - if (target_size > v.size()) { - v.resize(target_size); - } - } -public: - template - static void min_max_components(const schema& schema, std::vector& min_seen, std::vector& max_seen, T components) { - may_grow(min_seen, schema.clustering_key_size()); - may_grow(max_seen, schema.clustering_key_size()); - - auto& types = schema.clustering_key_type()->types(); - auto i = 0U; - for (auto& value : components) { - auto& type = types[i]; - - if (!max_seen[i] || type->compare(value, max_seen[i].value()) > 0) { - max_seen[i] = bytes(value.data(), value.size()); - } - if (!min_seen[i] || type->compare(value, min_seen[i].value()) < 0) { - min_seen[i] = bytes(value.data(), value.size()); - } - i++; - } - } -}; diff --git a/sstables/mc/writer.cc b/sstables/mc/writer.cc index cf0d07cb17..2c15a857b7 100644 --- a/sstables/mc/writer.cc +++ b/sstables/mc/writer.cc @@ -1259,10 +1259,7 @@ void writer::write_clustered(const clustering_row& clustered_row, uint64_t prev_ flush_tmp_bufs(); // Collect statistics - if (_schema.clustering_key_size()) { - column_name_helper::min_max_components(_schema, _sst.get_metadata_collector().min_column_names(), - _sst.get_metadata_collector().max_column_names(), clustered_row.key().components()); - } + _sst.get_metadata_collector().update_min_max_components(_schema, clustered_row.key()); collect_row_stats(_data_writer->offset() - current_pos, &clustered_row.key()); } @@ -1340,10 +1337,7 @@ void writer::write_clustered(const rt_marker& marker, uint64_t prev_row_size) { write_vint(*_data_writer, _tmp_bufs.size()); flush_tmp_bufs(); - if (_schema.clustering_key_size()) { - column_name_helper::min_max_components(_schema, _sst.get_metadata_collector().min_column_names(), - _sst.get_metadata_collector().max_column_names(), marker.clustering.components()); - } + _sst.get_metadata_collector().update_min_max_components(_schema, marker.clustering); } void writer::consume(rt_marker&& marker) { diff --git a/sstables/metadata_collector.cc b/sstables/metadata_collector.cc new file mode 100644 index 0000000000..3fca0a386d --- /dev/null +++ b/sstables/metadata_collector.cc @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2020 ScyllaDB + */ + +/* + * This file is part of Scylla. + * + * Scylla is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Scylla is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Scylla. If not, see . + */ + +#include "log.hh" +#include "metadata_collector.hh" + +logging::logger mdclogger("metadata_collector"); + +namespace sstables { + +void metadata_collector::do_update_min_max_components(const schema& schema, const clustering_key_prefix& key) { + auto may_grow = [] (std::vector& v, size_t target_size) { + if (target_size > v.size()) { + v.resize(target_size); + } + }; + + auto clustering_key_size = schema.clustering_key_size(); + auto& min_seen = min_column_names(); + auto& max_seen = max_column_names(); + may_grow(min_seen, clustering_key_size); + may_grow(max_seen, clustering_key_size); + + auto& types = schema.clustering_key_type()->types(); + auto i = 0U; + for (auto& value : key.components()) { + auto& type = types[i]; + + if (!max_seen[i] || type->compare(value, max_seen[i].value()) > 0) { + max_seen[i] = bytes(value.data(), value.size()); + } + if (!min_seen[i] || type->compare(value, min_seen[i].value()) < 0) { + min_seen[i] = bytes(value.data(), value.size()); + } + i++; + } +} + +} // namespace sstables diff --git a/sstables/metadata_collector.hh b/sstables/metadata_collector.hh index 2aedc0c0f8..9b04b0a8df 100644 --- a/sstables/metadata_collector.hh +++ b/sstables/metadata_collector.hh @@ -185,6 +185,8 @@ private: to.elements.push_back(std::move(s)); } } + + void do_update_min_max_components(const schema& schema, const clustering_key_prefix& key); public: void add_key(bytes_view key) { long hashed = utils::murmur_hash::hash2_64(key, 0); @@ -243,6 +245,12 @@ public: _has_legacy_counter_shards = _has_legacy_counter_shards || has_legacy_counter_shards; } + void update_min_max_components(const schema& schema, const clustering_key_prefix& key) { + if (schema.clustering_key_size()) { + do_update_min_max_components(schema, key); + } + } + void update(column_stats&& stats) { _timestamp_tracker.update(stats.timestamp_tracker); _local_deletion_time_tracker.update(stats.local_deletion_time_tracker); diff --git a/sstables/sstables.cc b/sstables/sstables.cc index 57b7255efd..c37dc6daa8 100644 --- a/sstables/sstables.cc +++ b/sstables/sstables.cc @@ -1737,10 +1737,7 @@ void sstable::write_clustered_row(file_writer& out, const schema& schema, const maybe_write_row_marker(out, schema, clustered_row.marker(), clustering_key); maybe_write_row_tombstone(out, clustering_key, clustered_row); - if (schema.clustering_key_size()) { - column_name_helper::min_max_components(schema, _collector.min_column_names(), _collector.max_column_names(), - clustered_row.key().components()); - } + _collector.update_min_max_components(schema, clustered_row.key()); // Write all cells of a partition's row. clustered_row.cells().for_each_cell([&] (column_id id, const atomic_cell_or_collection& c) { diff --git a/sstables/types.hh b/sstables/types.hh index ef09bdcde4..53fc0029fd 100644 --- a/sstables/types.hh +++ b/sstables/types.hh @@ -28,7 +28,6 @@ #include "tombstone.hh" #include "utils/streaming_histogram.hh" #include "utils/estimated_histogram.hh" -#include "column_name_helper.hh" #include "sstables/key.hh" #include "db/commitlog/replay_position.hh" #include "version.hh" From c9cade833c1b605913a6d25030beb42939eb92ac Mon Sep 17 00:00:00 2001 From: Benny Halevy Date: Tue, 14 Jul 2020 10:25:03 +0300 Subject: [PATCH 02/27] sstables: metadata_collector: make only for write path make a metadata_collector only when writing the sstable, no need to make one when reading. Signed-off-by: Benny Halevy --- sstables/compaction.cc | 4 ++++ sstables/sstables.cc | 24 ++++++++++++++++-------- sstables/sstables.hh | 17 ++++++++++++++--- test/boost/mutation_reader_test.cc | 3 +-- 4 files changed, 35 insertions(+), 13 deletions(-) diff --git a/sstables/compaction.cc b/sstables/compaction.cc index 156991b7a9..2207ac2d40 100644 --- a/sstables/compaction.cc +++ b/sstables/compaction.cc @@ -486,6 +486,7 @@ protected: void setup_new_sstable(shared_sstable& sst) { _info->new_sstables.push_back(sst); _new_unused_sstables.push_back(sst); + sst->make_metadata_collector(); sst->get_metadata_collector().set_replay_position(_rp); sst->get_metadata_collector().sstable_level(_sstable_level); for (auto ancestor : _ancestors) { @@ -1482,6 +1483,9 @@ get_fully_expired_sstables(column_family& cf, const std::vectorhas_metadata_collector()) { + return false; + } return boost::algorithm::any_of(candidate->get_metadata_collector().ancestors(), [&compacted_undeleted_gens] (auto gen) { return compacted_undeleted_gens.count(gen); }); diff --git a/sstables/sstables.cc b/sstables/sstables.cc index c37dc6daa8..60deaeb5c1 100644 --- a/sstables/sstables.cc +++ b/sstables/sstables.cc @@ -1737,7 +1737,7 @@ void sstable::write_clustered_row(file_writer& out, const schema& schema, const maybe_write_row_marker(out, schema, clustered_row.marker(), clustering_key); maybe_write_row_tombstone(out, clustering_key, clustered_row); - _collector.update_min_max_components(schema, clustered_row.key()); + get_metadata_collector().update_min_max_components(*_schema, clustered_row.key()); // Write all cells of a partition's row. clustered_row.cells().for_each_cell([&] (column_id id, const atomic_cell_or_collection& c) { @@ -2032,7 +2032,7 @@ void components_writer::consume_new_partition(const dht::decorated_key& dk) { maybe_add_summary_entry(dk.token(), bytes_view(*_partition_key)); _sst._components->filter->add(bytes_view(*_partition_key)); - _sst._collector.add_key(bytes_view(*_partition_key)); + _sst.get_metadata_collector().add_key(bytes_view(*_partition_key)); auto p_key = disk_string_view(); p_key.value = bytes_view(*_partition_key); @@ -2146,7 +2146,7 @@ stop_iteration components_writer::consume_end_of_partition() { _sst.get_large_data_handler().maybe_log_too_many_rows(_sst, *_partition_key, _sst._c_stats.rows_count); // update is about merging column_stats with the data being stored by collector. - _sst._collector.update(std::move(_sst._c_stats)); + _sst.get_metadata_collector().update(std::move(_sst._c_stats)); _sst._c_stats.reset(); if (!_first_key) { @@ -2169,11 +2169,11 @@ void components_writer::consume_end_of_stream() { _index.close(); if (_sst.has_component(component_type::CompressionInfo)) { - _sst._collector.add_compression_ratio(_sst._components->compression.compressed_file_length(), _sst._components->compression.uncompressed_file_length()); + _sst.get_metadata_collector().add_compression_ratio(_sst._components->compression.compressed_file_length(), _sst._components->compression.uncompressed_file_length()); } _sst.set_first_and_last_keys(); - seal_statistics(_sst.get_version(), _sst._components->statistics, _sst._collector, _schema.get_partitioner().name(), _schema.bloom_filter_fp_chance(), + seal_statistics(_sst.get_version(), _sst._components->statistics, _sst.get_metadata_collector(), _schema.get_partitioner().name(), _schema.bloom_filter_fp_chance(), _sst._schema, _sst.get_first_decorated_key(), _sst.get_last_decorated_key()); } @@ -2224,6 +2224,13 @@ sstable::write_scylla_metadata(const io_priority_class& pc, shard_id shard, ssta write_simple(*_components->scylla_metadata, pc); } +void +sstable::make_metadata_collector() { + if (!_collector) { + _collector.emplace(); + } +} + void sstable::update_stats_on_end_of_stream() { if (_c_stats.capped_local_deletion_time) { @@ -2353,6 +2360,7 @@ void sstable_writer_k_l::consume_end_of_stream() sstable_writer::sstable_writer(sstable& sst, const schema& s, uint64_t estimated_partitions, const sstable_writer_config& cfg, encoding_stats enc_stats, const io_priority_class& pc, shard_id shard) { + sst.make_metadata_collector(); if (sst.get_version() == sstable_version_types::mc) { _impl = mc::make_writer(sst, s, estimated_partitions, cfg, enc_stats, pc, shard); } else { @@ -2451,11 +2459,11 @@ future<> sstable::write_components( encoding_stats stats, const io_priority_class& pc) { assert_large_data_handler_is_running(); - if (cfg.replay_position) { - _collector.set_replay_position(cfg.replay_position.value()); - } return seastar::async([this, mr = std::move(mr), estimated_partitions, schema = std::move(schema), cfg, stats, &pc] () mutable { auto wr = get_writer(*schema, estimated_partitions, cfg, stats, pc); + if (cfg.replay_position) { + get_metadata_collector().set_replay_position(cfg.replay_position.value()); + } auto validator = mutation_fragment_stream_validating_filter(format("sstable writer {}", get_filename()), *schema, cfg.validate_keys); mr.consume_in_thread(std::move(wr), std::move(validator), db::no_timeout); diff --git a/sstables/sstables.hh b/sstables/sstables.hh index 162ddc3967..718be2cbde 100644 --- a/sstables/sstables.hh +++ b/sstables/sstables.hh @@ -305,7 +305,7 @@ public: } void add_ancestor(int64_t generation) { - _collector.add_ancestor(generation); + _collector->add_ancestor(generation); } // Returns true iff this sstable contains data which belongs to many shards. @@ -408,8 +408,15 @@ public: bool requires_view_building() const; + bool has_metadata_collector() const { + return _collector.has_value(); + } + metadata_collector& get_metadata_collector() { - return _collector; + if (!_collector.has_value()) { + on_internal_error(sstlog, "No metadata collector"); + } + return *_collector; } std::vector> all_components() const; @@ -468,7 +475,7 @@ private: bool _open = false; // NOTE: _collector and _c_stats are used to generation of statistics file // when writing a new sstable. - metadata_collector _collector; + std::optional _collector; column_stats _c_stats; file _index_file; file _data_file; @@ -675,6 +682,8 @@ private: serialization_header& s = *static_cast(p.get()); return s; } + + void make_metadata_collector(); public: future<> read_toc(); @@ -837,6 +846,8 @@ public: friend class sstable_writer_k_l; friend class mc::writer; friend class index_reader; + friend class sstable_writer; + friend class compaction; template friend data_consume_context data_consume_rows(const schema&, shared_sstable, typename DataConsumeRowsContext::consumer&, disk_read_range, uint64_t); diff --git a/test/boost/mutation_reader_test.cc b/test/boost/mutation_reader_test.cc index 6c532c990b..0cf65dafcc 100644 --- a/test/boost/mutation_reader_test.cc +++ b/test/boost/mutation_reader_test.cc @@ -563,8 +563,7 @@ struct sst_factory { sstables::shared_sstable operator()() { auto sst = env.make_sstable(s, path, gen, sstables::sstable::version_types::la, sstables::sstable::format_types::big); - //sst->set_sstable_level(level); - sst->get_metadata_collector().sstable_level(level); + sst->set_sstable_level(level); return sst; } From 707b098f44b7ae6692f19b9c0b117ad1380bbd9a Mon Sep 17 00:00:00 2001 From: Benny Halevy Date: Sun, 24 May 2020 13:07:53 +0300 Subject: [PATCH 03/27] sstables: metadata_collector: construct with schema Pass the sstable schema to the metadata_collector constructor. Note that the long term plan is to move metadata_collector to the sstable writer but this requires a bigger change to get rid of the dependencies on it in the legacy writer code in class sstable methods. Signed-off-by: Benny Halevy --- sstables/mc/writer.cc | 4 ++-- sstables/metadata_collector.cc | 6 +++--- sstables/metadata_collector.hh | 17 +++++++++++++---- sstables/sstables.cc | 4 ++-- 4 files changed, 20 insertions(+), 11 deletions(-) diff --git a/sstables/mc/writer.cc b/sstables/mc/writer.cc index 2c15a857b7..6bb5894b7f 100644 --- a/sstables/mc/writer.cc +++ b/sstables/mc/writer.cc @@ -1259,7 +1259,7 @@ void writer::write_clustered(const clustering_row& clustered_row, uint64_t prev_ flush_tmp_bufs(); // Collect statistics - _sst.get_metadata_collector().update_min_max_components(_schema, clustered_row.key()); + _sst.get_metadata_collector().update_min_max_components(clustered_row.key()); collect_row_stats(_data_writer->offset() - current_pos, &clustered_row.key()); } @@ -1337,7 +1337,7 @@ void writer::write_clustered(const rt_marker& marker, uint64_t prev_row_size) { write_vint(*_data_writer, _tmp_bufs.size()); flush_tmp_bufs(); - _sst.get_metadata_collector().update_min_max_components(_schema, marker.clustering); + _sst.get_metadata_collector().update_min_max_components(marker.clustering); } void writer::consume(rt_marker&& marker) { diff --git a/sstables/metadata_collector.cc b/sstables/metadata_collector.cc index 3fca0a386d..dbd49b8f1a 100644 --- a/sstables/metadata_collector.cc +++ b/sstables/metadata_collector.cc @@ -26,20 +26,20 @@ logging::logger mdclogger("metadata_collector"); namespace sstables { -void metadata_collector::do_update_min_max_components(const schema& schema, const clustering_key_prefix& key) { +void metadata_collector::do_update_min_max_components(const clustering_key_prefix& key) { auto may_grow = [] (std::vector& v, size_t target_size) { if (target_size > v.size()) { v.resize(target_size); } }; - auto clustering_key_size = schema.clustering_key_size(); + auto clustering_key_size = _schema.clustering_key_size(); auto& min_seen = min_column_names(); auto& max_seen = max_column_names(); may_grow(min_seen, clustering_key_size); may_grow(max_seen, clustering_key_size); - auto& types = schema.clustering_key_type()->types(); + auto& types = _schema.clustering_key_type()->types(); auto i = 0U; for (auto& value : key.components()) { auto& type = types[i]; diff --git a/sstables/metadata_collector.hh b/sstables/metadata_collector.hh index 9b04b0a8df..d3b4fb56dd 100644 --- a/sstables/metadata_collector.hh +++ b/sstables/metadata_collector.hh @@ -145,6 +145,7 @@ public: return hll::HyperLogLog(); } private: + const schema& _schema; // EH of 150 can track a max value of 1697806495183, i.e., > 1.5PB utils::estimated_histogram _estimated_partition_size{150}; // EH of 114 can track a max value of 2395318855, i.e., > 2B cells @@ -186,8 +187,16 @@ private: } } - void do_update_min_max_components(const schema& schema, const clustering_key_prefix& key); + void do_update_min_max_components(const clustering_key_prefix& key); public: + explicit metadata_collector(const schema& schema) + : _schema(schema) + { } + + const schema& get_schema() { + return _schema; + } + void add_key(bytes_view key) { long hashed = utils::murmur_hash::hash2_64(key, 0); _cardinality.offer_hashed(hashed); @@ -245,9 +254,9 @@ public: _has_legacy_counter_shards = _has_legacy_counter_shards || has_legacy_counter_shards; } - void update_min_max_components(const schema& schema, const clustering_key_prefix& key) { - if (schema.clustering_key_size()) { - do_update_min_max_components(schema, key); + void update_min_max_components(const clustering_key_prefix& key) { + if (_schema.clustering_key_size()) { + do_update_min_max_components(key); } } diff --git a/sstables/sstables.cc b/sstables/sstables.cc index 60deaeb5c1..b6ad90d0e6 100644 --- a/sstables/sstables.cc +++ b/sstables/sstables.cc @@ -1737,7 +1737,7 @@ void sstable::write_clustered_row(file_writer& out, const schema& schema, const maybe_write_row_marker(out, schema, clustered_row.marker(), clustering_key); maybe_write_row_tombstone(out, clustering_key, clustered_row); - get_metadata_collector().update_min_max_components(*_schema, clustered_row.key()); + get_metadata_collector().update_min_max_components(clustered_row.key()); // Write all cells of a partition's row. clustered_row.cells().for_each_cell([&] (column_id id, const atomic_cell_or_collection& c) { @@ -2227,7 +2227,7 @@ sstable::write_scylla_metadata(const io_priority_class& pc, shard_id shard, ssta void sstable::make_metadata_collector() { if (!_collector) { - _collector.emplace(); + _collector.emplace(*get_schema()); } } From 9f114d821a14a9b3cdb4db3ea6a82673b894fd58 Mon Sep 17 00:00:00 2001 From: Benny Halevy Date: Mon, 1 Jun 2020 16:40:13 +0300 Subject: [PATCH 04/27] sstables: keep whole clustering_key_prefix as min/max_column_names Currently we compare each min/max component independently. This may lead to suboptimal, inclusive clustering ranges that do not indicate any actual key we encountered. For example: ['a', 2], ['b', 1] will lead to min=['a', 1], max=['b', 2] instead of the keys themselves. This change keeps the min or max keys as a whole. It considers shorter clustering prefixes (that are possible with compact storage) as range tombstone bounds, so that a shorter key is considered less than the minimum if the latter has a common prefix, and greater than the maximum if the latter has a common prefix. Extend the min_max_clustering_key_test to test for this case. Previously {"a", "2"}, {"b", "1"} clustering keys would erronuously end up with min={"a", "1"} max={"b", "2"} while we want them to be min={"a", "2"} max={"b", "1"}. Adjust sstable_3_x_test to ignore original mc sstables that were previously computed with different min/max column names. Signed-off-by: Benny Halevy --- sstables/metadata_collector.cc | 48 ++++++++++++++----------- sstables/metadata_collector.hh | 36 ++++++------------- sstables/sstables.cc | 2 +- test/boost/sstable_3_x_test.cc | 23 +++++++----- test/boost/sstable_datafile_test.cc | 54 +++++++++++++++++++++++++++++ 5 files changed, 106 insertions(+), 57 deletions(-) diff --git a/sstables/metadata_collector.cc b/sstables/metadata_collector.cc index dbd49b8f1a..7801dad31b 100644 --- a/sstables/metadata_collector.cc +++ b/sstables/metadata_collector.cc @@ -26,31 +26,37 @@ logging::logger mdclogger("metadata_collector"); namespace sstables { +void metadata_collector::convert(disk_array>& to, const std::optional& from) { + if (!from) { + mdclogger.trace("{}: convert: empty", _name); + return; + } + mdclogger.trace("{}: convert: {}", _name, clustering_key_prefix::with_schema_wrapper(_schema, *from)); + for (auto& value : from->components()) { + to.elements.push_back(disk_string{bytes(value.data(), value.size())}); + } +} + void metadata_collector::do_update_min_max_components(const clustering_key_prefix& key) { - auto may_grow = [] (std::vector& v, size_t target_size) { - if (target_size > v.size()) { - v.resize(target_size); - } - }; + if (!_min_clustering_key) { + mdclogger.trace("{}: initializing min/max clustering keys={}", _name, clustering_key_prefix::with_schema_wrapper(_schema, key)); + _min_clustering_key.emplace(key); + _max_clustering_key.emplace(key); + return; + } - auto clustering_key_size = _schema.clustering_key_size(); - auto& min_seen = min_column_names(); - auto& max_seen = max_column_names(); - may_grow(min_seen, clustering_key_size); - may_grow(max_seen, clustering_key_size); + const bound_view::tri_compare cmp(_schema); - auto& types = _schema.clustering_key_type()->types(); - auto i = 0U; - for (auto& value : key.components()) { - auto& type = types[i]; + auto res = cmp(bound_view(key, bound_kind::incl_start), bound_view(*_min_clustering_key, bound_kind::incl_start)); + if (res < 0) { + mdclogger.trace("{}: setting min_clustering_key={}", _name, clustering_key_prefix::with_schema_wrapper(_schema, key)); + _min_clustering_key.emplace(key); + } - if (!max_seen[i] || type->compare(value, max_seen[i].value()) > 0) { - max_seen[i] = bytes(value.data(), value.size()); - } - if (!min_seen[i] || type->compare(value, min_seen[i].value()) < 0) { - min_seen[i] = bytes(value.data(), value.size()); - } - i++; + res = cmp(bound_view(key, bound_kind::incl_end), bound_view(*_max_clustering_key, bound_kind::incl_end)); + if (res > 0) { + mdclogger.trace("{}: setting max_clustering_key={}", _name, clustering_key_prefix::with_schema_wrapper(_schema, key)); + _max_clustering_key.emplace(key); } } diff --git a/sstables/metadata_collector.hh b/sstables/metadata_collector.hh index d3b4fb56dd..d3cde465a6 100644 --- a/sstables/metadata_collector.hh +++ b/sstables/metadata_collector.hh @@ -46,6 +46,8 @@ #include "utils/murmur_hash.hh" #include "hyperloglog.hh" #include "db/commitlog/replay_position.hh" +#include "clustering_bounds_comparator.hh" + #include namespace sstables { @@ -146,6 +148,7 @@ public: } private: const schema& _schema; + sstring _name; // EH of 150 can track a max value of 1697806495183, i.e., > 1.5PB utils::estimated_histogram _estimated_partition_size{150}; // EH of 114 can track a max value of 2395318855, i.e., > 2B cells @@ -159,8 +162,8 @@ private: std::set _ancestors; utils::streaming_histogram _estimated_tombstone_drop_time{TOMBSTONE_HISTOGRAM_BIN_SIZE}; int _sstable_level = 0; - std::vector _min_column_names; - std::vector _max_column_names; + std::optional _min_clustering_key; + std::optional _max_clustering_key; bool _has_legacy_counter_shards = false; uint64_t _columns_count = 0; uint64_t _rows_count = 0; @@ -173,24 +176,13 @@ private: */ hll::HyperLogLog _cardinality = hyperloglog(13, 25); private: - /* - * Convert a vector of bytes into a disk array of disk_string. - */ - static void convert(disk_array>&to, std::vector&& from) { - for (auto i = 0U; i < from.size(); i++) { - if (!from[i]) { - break; - } - disk_string s; - s.value = std::move(from[i].value()); - to.elements.push_back(std::move(s)); - } - } + void convert(disk_array>&to, const std::optional& from); void do_update_min_max_components(const clustering_key_prefix& key); public: - explicit metadata_collector(const schema& schema) + explicit metadata_collector(const schema& schema, sstring name) : _schema(schema) + , _name(name) { } const schema& get_schema() { @@ -242,14 +234,6 @@ public: _sstable_level = sstable_level; } - std::vector& min_column_names() { - return _min_column_names; - } - - std::vector& max_column_names() { - return _max_column_names; - } - void update_has_legacy_counter_shards(bool has_legacy_counter_shards) { _has_legacy_counter_shards = _has_legacy_counter_shards || has_legacy_counter_shards; } @@ -294,8 +278,8 @@ public: m.estimated_tombstone_drop_time = std::move(_estimated_tombstone_drop_time); m.sstable_level = _sstable_level; m.repaired_at = _repaired_at; - convert(m.min_column_names, std::move(_min_column_names)); - convert(m.max_column_names, std::move(_max_column_names)); + convert(m.min_column_names, _min_clustering_key); + convert(m.max_column_names, _max_clustering_key); m.has_legacy_counter_shards = _has_legacy_counter_shards; m.columns_count = _columns_count; m.rows_count = _rows_count; diff --git a/sstables/sstables.cc b/sstables/sstables.cc index b6ad90d0e6..72b8c0a813 100644 --- a/sstables/sstables.cc +++ b/sstables/sstables.cc @@ -2227,7 +2227,7 @@ sstable::write_scylla_metadata(const io_priority_class& pc, shard_id shard, ssta void sstable::make_metadata_collector() { if (!_collector) { - _collector.emplace(*get_schema()); + _collector.emplace(*get_schema(), get_filename()); } } diff --git a/test/boost/sstable_3_x_test.cc b/test/boost/sstable_3_x_test.cc index 5091fd97a0..c28d65edf6 100644 --- a/test/boost/sstable_3_x_test.cc +++ b/test/boost/sstable_3_x_test.cc @@ -3237,7 +3237,10 @@ static sstable_assertions validate_read(schema_ptr s, const std::filesystem::pat static constexpr api::timestamp_type write_timestamp = 1525385507816568; static constexpr gc_clock::time_point write_time_point = gc_clock::time_point{} + gc_clock::duration{1525385507}; -static void validate_stats_metadata(schema_ptr s, sstable_assertions written_sst, sstring table_name) { +// FIXME: base validation of min_max_column_names on sstable has_correct_min_max_column_names() +// when we generate md format sstables with correct min/max_column_names metadata. +static void validate_stats_metadata(schema_ptr s, sstable_assertions& written_sst, sstring table_name, + bool do_validate_min_max_column_names = true) { auto orig_sst = written_sst.get_env().reusable_sst(s, get_write_test_path(table_name), 1, sstable_version_types::mc).get0(); const auto& orig_stats = orig_sst->get_stats_metadata(); @@ -3256,8 +3259,10 @@ static void validate_stats_metadata(schema_ptr s, sstable_assertions written_sst BOOST_REQUIRE_EQUAL(orig_stats.max_local_deletion_time, written_stats.max_local_deletion_time); BOOST_REQUIRE_EQUAL(orig_stats.min_ttl, written_stats.min_ttl); BOOST_REQUIRE_EQUAL(orig_stats.max_ttl, written_stats.max_ttl); - BOOST_REQUIRE(orig_stats.min_column_names.elements == written_stats.min_column_names.elements); - BOOST_REQUIRE(orig_stats.max_column_names.elements == written_stats.max_column_names.elements); + if (do_validate_min_max_column_names) { + BOOST_REQUIRE(orig_stats.min_column_names.elements == written_stats.min_column_names.elements); + BOOST_REQUIRE(orig_stats.max_column_names.elements == written_stats.max_column_names.elements); + } BOOST_REQUIRE_EQUAL(orig_stats.columns_count, written_stats.columns_count); BOOST_REQUIRE_EQUAL(orig_stats.rows_count, written_stats.rows_count); check_estimated_histogram(orig_stats.estimated_partition_size, written_stats.estimated_partition_size); @@ -4112,7 +4117,7 @@ SEASTAR_THREAD_TEST_CASE(test_write_adjacent_range_tombstones) { tmpdir tmp = write_and_compare_sstables(s, mt, table_name); auto written_sst = validate_read(s, tmp.path(), {mut}); - validate_stats_metadata(s, written_sst, table_name); + validate_stats_metadata(s, written_sst, table_name, false); } // Test the case when subsequent RTs have a common clustering but those bounds are both exclusive @@ -4230,7 +4235,7 @@ SEASTAR_THREAD_TEST_CASE(test_write_mixed_rows_and_range_tombstones) { tmpdir tmp = write_and_compare_sstables(s, mt, table_name); auto written_sst = validate_read(s, tmp.path(), {mut}); - validate_stats_metadata(s, written_sst, table_name); + validate_stats_metadata(s, written_sst, table_name, false); } SEASTAR_THREAD_TEST_CASE(test_write_many_range_tombstones) { @@ -4320,7 +4325,7 @@ SEASTAR_THREAD_TEST_CASE(test_write_adjacent_range_tombstones_with_rows) { tmpdir tmp = write_and_compare_sstables(s, mt, table_name); auto written_sst = validate_read(s, tmp.path(), {mut}); - validate_stats_metadata(s, written_sst, table_name); + validate_stats_metadata(s, written_sst, table_name, false); } SEASTAR_THREAD_TEST_CASE(test_write_range_tombstone_same_start_with_row) { @@ -4359,7 +4364,7 @@ SEASTAR_THREAD_TEST_CASE(test_write_range_tombstone_same_start_with_row) { tmpdir tmp = write_and_compare_sstables(s, mt, table_name); auto written_sst = validate_read(s, tmp.path(), {mut}); - validate_stats_metadata(s, written_sst, table_name); + validate_stats_metadata(s, written_sst, table_name, false); } SEASTAR_THREAD_TEST_CASE(test_write_range_tombstone_same_end_with_row) { @@ -4398,7 +4403,7 @@ SEASTAR_THREAD_TEST_CASE(test_write_range_tombstone_same_end_with_row) { tmpdir tmp = write_and_compare_sstables(s, mt, table_name); auto written_sst = validate_read(s, tmp.path(), {mut}); - validate_stats_metadata(s, written_sst, table_name); + validate_stats_metadata(s, written_sst, table_name, false); } SEASTAR_THREAD_TEST_CASE(test_write_overlapped_start_range_tombstones) { @@ -4498,7 +4503,7 @@ SEASTAR_THREAD_TEST_CASE(test_write_two_non_adjacent_range_tombstones) { tmpdir tmp = write_and_compare_sstables(s, mt, table_name); auto written_sst = validate_read(s, tmp.path(), {mut}); - validate_stats_metadata(s, written_sst, table_name); + validate_stats_metadata(s, written_sst, table_name, false); } // The resulting files are supposed to be identical to the files diff --git a/test/boost/sstable_datafile_test.cc b/test/boost/sstable_datafile_test.cc index 45bb36ed36..35dd8f0c9a 100644 --- a/test/boost/sstable_datafile_test.cc +++ b/test/boost/sstable_datafile_test.cc @@ -3360,6 +3360,47 @@ SEASTAR_TEST_CASE(min_max_clustering_key_test) { test_min_max_clustering_key(s, {"key1"}, {{"a", "b"}, {"a", "c"}}, {"a", "b"}, {"a", "c"}, version); } + { + auto s = schema_builder("ks", "cf") + .with_column("pk", utf8_type, column_kind::partition_key) + .with_column("ck1", utf8_type, column_kind::clustering_key) + .with_column("ck2", utf8_type, column_kind::clustering_key) + .with_column("r1", int32_type) + .build(); + BOOST_TEST_MESSAGE(fmt::format("min_max_clustering_key_test: min={{\"a\", \"c\"}} max={{\"b\", \"a\"}} version={}", to_string(version))); + test_min_max_clustering_key(s, {"key1"}, {{"b", "a"}, {"a", "c"}}, {"a", "c"}, {"b", "a"}, version); + } + { + auto s = schema_builder("ks", "cf") + .with(schema_builder::compact_storage::yes) + .with_column("pk", utf8_type, column_kind::partition_key) + .with_column("ck1", utf8_type, column_kind::clustering_key) + .with_column("ck2", utf8_type, column_kind::clustering_key) + .with_column("r1", int32_type) + .build(); + BOOST_TEST_MESSAGE(fmt::format("min_max_clustering_key_test: min={{\"a\", \"c\"}} max={{\"b\", \"a\"}} with compact storage version={}", to_string(version))); + test_min_max_clustering_key(s, {"key1"}, {{"b", "a"}, {"a", "c"}}, {"a", "c"}, {"b", "a"}, version); + } + { + auto s = schema_builder("ks", "cf") + .with_column("pk", utf8_type, column_kind::partition_key) + .with_column("ck1", utf8_type, column_kind::clustering_key) + .with_column("ck2", reversed_type_impl::get_instance(utf8_type), column_kind::clustering_key) + .with_column("r1", int32_type) + .build(); + BOOST_TEST_MESSAGE(fmt::format("min_max_clustering_key_test: reversed order: min={{\"a\", \"z\"}} max={{\"a\", \"a\"}} version={}", to_string(version))); + test_min_max_clustering_key(s, {"key1"}, {{"a", "a"}, {"a", "z"}}, {"a", "z"}, {"a", "a"}, version); + } + { + auto s = schema_builder("ks", "cf") + .with_column("pk", utf8_type, column_kind::partition_key) + .with_column("ck1", utf8_type, column_kind::clustering_key) + .with_column("ck2", reversed_type_impl::get_instance(utf8_type), column_kind::clustering_key) + .with_column("r1", int32_type) + .build(); + BOOST_TEST_MESSAGE(fmt::format("min_max_clustering_key_test: reversed order: min={{\"a\", \"a\"}} max={{\"b\", \"z\"}} version={}", to_string(version))); + test_min_max_clustering_key(s, {"key1"}, {{"b", "z"}, {"a", "a"}}, {"a", "a"}, {"b", "z"}, version); + } { auto s = schema_builder("ks", "cf") .with_column("pk", utf8_type, column_kind::partition_key) @@ -3385,6 +3426,19 @@ SEASTAR_TEST_CASE(min_max_clustering_key_test) { .build(); test_min_max_clustering_key(s, {"key1"}, {}, {}, {}, version); } + if (version >= sstable_version_types::mc) { + { + auto s = schema_builder("ks", "cf") + .with(schema_builder::compact_storage::yes) + .with_column("pk", utf8_type, column_kind::partition_key) + .with_column("ck1", utf8_type, column_kind::clustering_key) + .with_column("ck2", reversed_type_impl::get_instance(utf8_type), column_kind::clustering_key) + .with_column("r1", int32_type) + .build(); + BOOST_TEST_MESSAGE(fmt::format("min_max_clustering_key_test: reversed order: min={{\"a\"}} max={{\"a\"}} with compact storage version={}", to_string(version))); + test_min_max_clustering_key(s, {"key1"}, {{"a", "z"}, {"a"}}, {"a"}, {"a"}, version); + } + } } }); } From 025b74e20e05b8f927d50e574b33938e0af21f84 Mon Sep 17 00:00:00 2001 From: Benny Halevy Date: Mon, 27 Jul 2020 18:46:07 +0300 Subject: [PATCH 05/27] sstables: metadata_collector: use empty key to represent full min/max range Instead of keeping the `_has_min_max_clustering_keys` flag, just store an empty key for `_{min,max}_clustering_key` to represent the full range. These will never be narrowed down and will be encoded as empty min/max column names as if they weren't set. Signed-off-by: Benny Halevy --- sstables/metadata_collector.cc | 2 +- sstables/metadata_collector.hh | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/sstables/metadata_collector.cc b/sstables/metadata_collector.cc index 7801dad31b..bfc3eafc89 100644 --- a/sstables/metadata_collector.cc +++ b/sstables/metadata_collector.cc @@ -37,7 +37,7 @@ void metadata_collector::convert(disk_array>& to } } -void metadata_collector::do_update_min_max_components(const clustering_key_prefix& key) { +void metadata_collector::update_min_max_components(const clustering_key_prefix& key) { if (!_min_clustering_key) { mdclogger.trace("{}: initializing min/max clustering keys={}", _name, clustering_key_prefix::with_schema_wrapper(_schema, key)); _min_clustering_key.emplace(key); diff --git a/sstables/metadata_collector.hh b/sstables/metadata_collector.hh index d3cde465a6..5839bff0ca 100644 --- a/sstables/metadata_collector.hh +++ b/sstables/metadata_collector.hh @@ -177,13 +177,17 @@ private: hll::HyperLogLog _cardinality = hyperloglog(13, 25); private: void convert(disk_array>&to, const std::optional& from); - - void do_update_min_max_components(const clustering_key_prefix& key); public: explicit metadata_collector(const schema& schema, sstring name) : _schema(schema) , _name(name) - { } + { + if (!schema.clustering_key_size()) { + // Empty min/max components represent the full range + // And so they will never be narrowed down. + update_min_max_components(clustering_key_prefix::make_empty(_schema)); + } + } const schema& get_schema() { return _schema; @@ -238,11 +242,7 @@ public: _has_legacy_counter_shards = _has_legacy_counter_shards || has_legacy_counter_shards; } - void update_min_max_components(const clustering_key_prefix& key) { - if (_schema.clustering_key_size()) { - do_update_min_max_components(key); - } - } + void update_min_max_components(const clustering_key_prefix& key); void update(column_stats&& stats) { _timestamp_tracker.update(stats.timestamp_tracker); From 7de004d42aa0e9deb3a4bedd15fc435683776446 Mon Sep 17 00:00:00 2001 From: Benny Halevy Date: Mon, 10 Aug 2020 17:10:46 +0300 Subject: [PATCH 06/27] sstables: version: delete unused is_latest_supported predicate Signed-off-by: Benny Halevy --- sstables/version.hh | 4 ---- 1 file changed, 4 deletions(-) diff --git a/sstables/version.hh b/sstables/version.hh index 9287069051..70fd5dd9cd 100644 --- a/sstables/version.hh +++ b/sstables/version.hh @@ -58,10 +58,6 @@ inline seastar::sstring to_string(sstable_version_types format) { throw std::runtime_error("Wrong sstable format"); } -inline bool is_latest_supported(sstable_version_types format) { - return format == sstable_version_types::mc; -} - inline int operator<=>(sstable_version_types a, sstable_version_types b) { auto to_int = [] (sstable_version_types x) { return static_cast>(x); From a37eaaa022544195e5b539a47a8e34440cfaff6b Mon Sep 17 00:00:00 2001 From: Pekka Enberg Date: Wed, 29 Apr 2020 08:32:25 +0300 Subject: [PATCH 07/27] sstables: Add support for the "md" format enum value Add the sstable_version_types::md enum value and logically extend sstable_version_types comparisons to cover also the > sstable_version_types::mc cases. Signed-off-by: Benny Halevy --- api/api-doc/storage_service.json | 2 +- sstables/index_reader.hh | 2 +- sstables/partition.cc | 6 +++--- sstables/sstable_version.cc | 1 + sstables/sstables.cc | 16 +++++++++------- sstables/sstables.hh | 2 +- sstables/types.hh | 3 +++ sstables/version.hh | 7 ++++++- test/boost/sstable_datafile_test.cc | 2 +- 9 files changed, 26 insertions(+), 15 deletions(-) diff --git a/api/api-doc/storage_service.json b/api/api-doc/storage_service.json index d1d8ef30da..d5d88f450a 100644 --- a/api/api-doc/storage_service.json +++ b/api/api-doc/storage_service.json @@ -2468,7 +2468,7 @@ "version":{ "type":"string", "enum":[ - "ka", "la", "mc" + "ka", "la", "mc", "md" ], "description":"SSTable version" }, diff --git a/sstables/index_reader.hh b/sstables/index_reader.hh index 3c0e0e4eef..0c893c7c83 100644 --- a/sstables/index_reader.hh +++ b/sstables/index_reader.hh @@ -376,7 +376,7 @@ class index_reader { trace_state ? sst->filename(component_type::Index) : sstring(), get_file(*sst, permit, trace_state), get_file_input_stream_options(sst, pc), begin, end - begin, - (sst->get_version() == sstable_version_types::mc + (sst->get_version() >= sstable_version_types::mc ? std::make_optional(get_clustering_values_fixed_lengths(sst->get_serialization_header())) : std::optional{}), trace_state) diff --git a/sstables/partition.cc b/sstables/partition.cc index 74f1ba6fb2..44126b0d1d 100644 --- a/sstables/partition.cc +++ b/sstables/partition.cc @@ -480,7 +480,7 @@ void mp_row_consumer_reader::on_next_partition(dht::decorated_key key, tombstone flat_mutation_reader sstable::read_rows_flat(schema_ptr schema, reader_permit permit, const io_priority_class& pc, streamed_mutation::forwarding fwd) { get_stats().on_sstable_partition_read(); - if (_version == version_types::mc) { + if (_version >= version_types::mc) { return make_flat_mutation_reader>( shared_from_this(), std::move(schema), std::move(permit), pc, tracing::trace_state_ptr(), fwd, default_read_monitor()); } @@ -499,7 +499,7 @@ sstables::sstable::read_row_flat(schema_ptr schema, read_monitor& mon) { get_stats().on_single_partition_read(); - if (_version == version_types::mc) { + if (_version >= version_types::mc) { return make_flat_mutation_reader>( shared_from_this(), std::move(schema), std::move(permit), std::move(key), slice, pc, std::move(trace_state), fwd, mutation_reader::forwarding::no, mon); @@ -519,7 +519,7 @@ sstable::read_range_rows_flat(schema_ptr schema, mutation_reader::forwarding fwd_mr, read_monitor& mon) { get_stats().on_range_partition_read(); - if (_version == version_types::mc) { + if (_version >= version_types::mc) { return make_flat_mutation_reader>( shared_from_this(), std::move(schema), std::move(permit), range, slice, pc, std::move(trace_state), fwd, fwd_mr, mon); } diff --git a/sstables/sstable_version.cc b/sstables/sstable_version.cc index 951515b423..2ccff86121 100644 --- a/sstables/sstable_version.cc +++ b/sstables/sstable_version.cc @@ -51,6 +51,7 @@ sstable_version_constants::get_component_map(sstable_version_types version) { case sstable_version_types::la: return sstable_version_constants_k_l::_component_map; case sstable_version_types::mc: + case sstable_version_types::md: return sstable_version_constants_m::_component_map; } // Should never reach this. diff --git a/sstables/sstables.cc b/sstables/sstables.cc index 72b8c0a813..b8144ea87f 100644 --- a/sstables/sstables.cc +++ b/sstables/sstables.cc @@ -197,6 +197,7 @@ std::unordered_map> sstable::_format_string = { @@ -661,7 +662,7 @@ future<> parse(const schema& schema, sstable_version_types v, random_access_read case metadata_type::Stats: return parse(schema, v, in, s.contents[type]); case metadata_type::Serialization: - if (v != sstable_version_types::mc) { + if (v < sstable_version_types::mc) { throw std::runtime_error( "Statistics is malformed: SSTable is in 2.x format but contains serialization header."); } else { @@ -1389,7 +1390,7 @@ future<> sstable::read_filter(const io_priority_class& pc) { read_simple(filter, pc).get(); auto nr_bits = filter.buckets.elements.size() * std::numeric_limits::digits; large_bitset bs(nr_bits, std::move(filter.buckets.elements)); - utils::filter_format format = (_version == sstable_version_types::mc) + utils::filter_format format = (_version >= sstable_version_types::mc) ? utils::filter_format::m_format : utils::filter_format::k_l_format; _components->filter = utils::filter::create_filter(filter.hashes, std::move(bs), format); @@ -2361,7 +2362,7 @@ void sstable_writer_k_l::consume_end_of_stream() sstable_writer::sstable_writer(sstable& sst, const schema& s, uint64_t estimated_partitions, const sstable_writer_config& cfg, encoding_stats enc_stats, const io_priority_class& pc, shard_id shard) { sst.make_metadata_collector(); - if (sst.get_version() == sstable_version_types::mc) { + if (sst.get_version() >= sstable_version_types::mc) { _impl = mc::make_writer(sst, s, estimated_partitions, cfg, enc_stats, pc, shard); } else { _impl = std::make_unique(sst, s, estimated_partitions, cfg, pc, shard); @@ -2521,7 +2522,7 @@ future<> sstable::generate_summary(const io_priority_class& pc) { auto sem = std::make_unique(reader_concurrency_semaphore::no_limits{}); auto ctx = make_lw_shared>( sem->make_permit(), s, trust_promoted_index::yes, *_schema, "", index_file, std::move(options), 0, index_size, - (_version == sstable_version_types::mc + (_version >= sstable_version_types::mc ? std::make_optional(get_clustering_values_fixed_lengths(get_serialization_header())) : std::optional{})); return ctx->consume_input().finally([ctx] { @@ -2620,6 +2621,7 @@ sstring sstable::component_basename(const sstring& ks, const sstring& cf, versio case sstable::version_types::la: return v + "-" + g + "-" + f + "-" + component; case sstable::version_types::mc: + case sstable::version_types::md: return v + "-" + g + "-" + f + "-" + component; } assert(0 && "invalid version"); @@ -2724,7 +2726,7 @@ future<> sstable::move_to_new_dir(sstring new_dir, int64_t new_generation, bool } entry_descriptor entry_descriptor::make_descriptor(sstring sstdir, sstring fname) { - static std::regex la_mc("(la|mc)-(\\d+)-(\\w+)-(.*)"); + static std::regex la_mx("(la|m[cd])-(\\d+)-(\\w+)-(.*)"); static std::regex ka("(\\w+)-(\\w+)-ka-(\\d+)-(.*)"); static std::regex dir(".*/([^/]*)/([^/]+)-[\\da-fA-F]+(?:/staging|/upload|/snapshots/[^/]+)?/?"); @@ -2741,7 +2743,7 @@ entry_descriptor entry_descriptor::make_descriptor(sstring sstdir, sstring fname sstlog.debug("Make descriptor sstdir: {}; fname: {}", sstdir, fname); std::string s(fname); - if (std::regex_match(s, match, la_mc)) { + if (std::regex_match(s, match, la_mx)) { std::string sdir(sstdir); std::smatch dirmatch; if (std::regex_match(sdir, dirmatch, dir)) { @@ -2806,7 +2808,7 @@ input_stream sstable::data_stream(uint64_t pos, size_t len, const io_prior input_stream stream; if (_components->compression) { - if (_version == sstable_version_types::mc) { + if (_version >= sstable_version_types::mc) { return make_compressed_file_m_format_input_stream(f, &_components->compression, pos, len, std::move(options)); } else { diff --git a/sstables/sstables.hh b/sstables/sstables.hh index 718be2cbde..0856ef8538 100644 --- a/sstables/sstables.hh +++ b/sstables/sstables.hh @@ -719,7 +719,7 @@ public: } bool has_correct_max_deletion_time() const { - return (_version == sstable_version_types::mc) || has_scylla_component(); + return (_version >= sstable_version_types::mc) || has_scylla_component(); } bool filter_has_key(const key& key) const { diff --git a/sstables/types.hh b/sstables/types.hh index 53fc0029fd..d10e0c19fd 100644 --- a/sstables/types.hh +++ b/sstables/types.hh @@ -277,6 +277,7 @@ struct compaction_metadata : public metadata_base { auto describe_type(sstable_version_types v, Describer f) { switch (v) { case sstable_version_types::mc: + case sstable_version_types::md: return f( cardinality ); @@ -318,6 +319,7 @@ struct stats_metadata : public metadata_base { auto describe_type(sstable_version_types v, Describer f) { switch (v) { case sstable_version_types::mc: + case sstable_version_types::md: return f( estimated_partition_size, estimated_cells_count, @@ -388,6 +390,7 @@ struct serialization_header : public metadata_base { auto describe_type(sstable_version_types v, Describer f) { switch (v) { case sstable_version_types::mc: + case sstable_version_types::md: return f( min_timestamp_base, min_local_deletion_time_base, diff --git a/sstables/version.hh b/sstables/version.hh index 70fd5dd9cd..3a176405a5 100644 --- a/sstables/version.hh +++ b/sstables/version.hh @@ -27,13 +27,14 @@ namespace sstables { -enum class sstable_version_types { ka, la, mc }; +enum class sstable_version_types { ka, la, mc, md }; enum class sstable_format_types { big }; inline std::array all_sstable_versions = { sstable_version_types::ka, sstable_version_types::la, sstable_version_types::mc, + // sstable_version_types::md, // FIXME: not yet }; inline sstable_version_types from_string(const seastar::sstring& format) { @@ -46,6 +47,9 @@ inline sstable_version_types from_string(const seastar::sstring& format) { if (format == "mc") { return sstable_version_types::mc; } + if (format == "md") { + return sstable_version_types::md; + } throw std::invalid_argument("Wrong sstable format name: " + format); } @@ -54,6 +58,7 @@ inline seastar::sstring to_string(sstable_version_types format) { case sstable_version_types::ka: return "ka"; case sstable_version_types::la: return "la"; case sstable_version_types::mc: return "mc"; + case sstable_version_types::md: return "md"; } throw std::runtime_error("Wrong sstable format"); } diff --git a/test/boost/sstable_datafile_test.cc b/test/boost/sstable_datafile_test.cc index 35dd8f0c9a..f647bba42d 100644 --- a/test/boost/sstable_datafile_test.cc +++ b/test/boost/sstable_datafile_test.cc @@ -4524,7 +4524,7 @@ SEASTAR_TEST_CASE(test_old_format_non_compound_range_tombstone_is_read) { // delete from ks.test where pk = 1 and ck = 2; return test_env::do_with_async([] (test_env& env) { for (const auto version : all_sstable_versions) { - if (version != sstables::sstable::version_types::mc) { // Does not apply to 'mc' format + if (version < sstable_version_types::mc) { // Applies only to formats older than 'm' auto s = schema_builder("ks", "test") .with_column("pk", int32_type, column_kind::partition_key) .with_column("ck", int32_type, column_kind::clustering_key) From 200d8d41d9b969da07e0dccbc246255423cc04da Mon Sep 17 00:00:00 2001 From: Benny Halevy Date: Wed, 29 Jul 2020 10:11:22 +0300 Subject: [PATCH 08/27] sstables: add may_contain_rows Move the logic from table to sstable as it will contain intimate knowledge of the sstable min/max column names validity for md format. Also, get rid of the sstable::clustering_components_ranges() method as the member is used only internally by the sstable code now. Signed-off-by: Benny Halevy --- sstables/sstables.cc | 20 ++++++++++++++++---- sstables/sstables.hh | 5 +++-- table.cc | 20 +------------------- 3 files changed, 20 insertions(+), 25 deletions(-) diff --git a/sstables/sstables.cc b/sstables/sstables.cc index b8144ea87f..28e7c2f8ee 100644 --- a/sstables/sstables.cc +++ b/sstables/sstables.cc @@ -1227,10 +1227,6 @@ void sstable::set_clustering_components_ranges() { } } -const std::vector>& sstable::clustering_components_ranges() const { - return _clustering_components_ranges; -} - double sstable::estimate_droppable_tombstone_ratio(gc_clock::time_point gc_before) const { auto& st = get_stats_metadata(); auto estimated_count = st.estimated_cells_count.mean() * st.estimated_cells_count.count(); @@ -2239,6 +2235,22 @@ void sstable::update_stats_on_end_of_stream() } } +bool sstable::may_contain_rows(const std::vector>>& ranges) { + auto clustering_components_size = _clustering_components_ranges.size(); + if (!_schema->clustering_key_size() || !clustering_components_size) { + return true; + } + + auto& clustering_key_types = _schema->clustering_key_type()->types(); + return std::ranges::any_of(ranges, [this, clustering_components_size, &clustering_key_types] (const std::vector>& range) { + auto s = std::min(range.size(), clustering_components_size); + return std::ranges::all_of(boost::irange(0, s), [this, &clustering_key_types, &range] (unsigned i) { + auto& type = clustering_key_types[i]; + return range[i].is_full() || range[i].overlaps(_clustering_components_ranges[i], type->as_tri_comparator()); + }); + }); +} + class sstable_writer_k_l : public sstable_writer::writer_impl { bool _backup; bool _leave_unsealed; diff --git a/sstables/sstables.hh b/sstables/sstables.hh index 0856ef8538..0c3b26b21a 100644 --- a/sstables/sstables.hh +++ b/sstables/sstables.hh @@ -821,8 +821,6 @@ public: return _components->summary; } - const std::vector>& clustering_components_ranges() const; - // Gets ratio of droppable tombstone. A tombstone is considered droppable here // for cells expired before gc_before and regular tombstones older than gc_before. double estimate_droppable_tombstone_ratio(gc_clock::time_point gc_before) const; @@ -837,6 +835,9 @@ public: void update_stats_on_end_of_stream(); + // Return true if this sstable possibly stores clustering row(s) specified by ranges. + bool may_contain_rows(const std::vector>>& ranges); + // Allow the test cases from sstable_test.cc to test private methods. We use // a placeholder to avoid cluttering this class too much. The sstable_test class // will then re-export as public every method it needs. diff --git a/table.cc b/table.cc index f01a495ef0..54d781855b 100644 --- a/table.cc +++ b/table.cc @@ -114,24 +114,6 @@ ranges_for_clustering_key_filter(const schema_ptr& schema, const query::clusteri return ranges; } -// Return true if this sstable possibly stores clustering row(s) specified by ranges. -static inline bool -contains_rows(const sstables::sstable& sst, const schema_ptr& schema, const ck_filter_clustering_key_ranges& ranges) { - auto& clustering_key_types = schema->clustering_key_type()->types(); - auto& clustering_components_ranges = sst.clustering_components_ranges(); - - if (!schema->clustering_key_size() || clustering_components_ranges.empty()) { - return true; - } - return boost::algorithm::any_of(ranges, [&] (const ck_filter_clustering_key_components& range) { - auto s = std::min(range.size(), clustering_components_ranges.size()); - return boost::algorithm::all_of(boost::irange(0, s), [&] (unsigned i) { - auto& type = clustering_key_types[i]; - return range[i].is_full() || range[i].overlaps(clustering_components_ranges[i], type->as_tri_comparator()); - }); - }); -} - // Filter out sstables for reader using bloom filter and sstable metadata that keeps track // of a range for each clustering component. static std::vector @@ -173,7 +155,7 @@ filter_sstable_for_reader(std::vector&& sstables, colu int64_t min_timestamp = std::numeric_limits::max(); auto sstable_has_clustering_key = [&min_timestamp, &schema, &ranges] (const sstables::shared_sstable& sst) { - if (!contains_rows(*sst, schema, ranges)) { + if (!sst->may_contain_rows(ranges)) { return false; // ordered after sstables that contain clustering rows. } else { min_timestamp = std::min(min_timestamp, sst->get_stats_metadata().min_timestamp); From 7139fb92e64b959a812372c463c2fa00ed47536e Mon Sep 17 00:00:00 2001 From: Benny Halevy Date: Wed, 29 Jul 2020 15:55:47 +0300 Subject: [PATCH 09/27] sstables: may_contain_rows: always true for old formats the min/max column names metadata can be trusted only starting the md format, so just always return `true` for older sstable formats. Note that we could achieve that by clearing the min/max metadata in set_clustering_components_ranges but we choose not to do so since it disturbs sstable unit tests Signed-off-by: Benny Halevy --- sstables/sstables.cc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sstables/sstables.cc b/sstables/sstables.cc index 28e7c2f8ee..341d28eec5 100644 --- a/sstables/sstables.cc +++ b/sstables/sstables.cc @@ -2236,6 +2236,10 @@ void sstable::update_stats_on_end_of_stream() } bool sstable::may_contain_rows(const std::vector>>& ranges) { + if (_version < sstables::sstable_version_types::md) { + return true; + } + auto clustering_components_size = _clustering_components_ranges.size(); if (!_schema->clustering_key_size() || !clustering_components_size) { return true; From 12393c5ec299aaf78f648996243b6e24bd8b839e Mon Sep 17 00:00:00 2001 From: Benny Halevy Date: Tue, 28 Jul 2020 11:08:59 +0300 Subject: [PATCH 10/27] sstables: rename mc folder to mx Prepare for supporting the md format. Signed-off-by: Benny Halevy --- configure.py | 2 +- sstables/index_entry.hh | 2 +- sstables/index_reader.hh | 2 +- sstables/m_format_read_helpers.hh | 2 +- sstables/{mc => mx}/bsearch_clustered_cursor.hh | 0 sstables/{mc => mx}/parsers.hh | 0 sstables/{mc => mx}/types.hh | 0 sstables/{mc => mx}/writer.cc | 4 ++-- sstables/{mc => mx}/writer.hh | 0 sstables/sstables.cc | 2 +- test/boost/sstable_3_x_test.cc | 2 +- 11 files changed, 8 insertions(+), 8 deletions(-) rename sstables/{mc => mx}/bsearch_clustered_cursor.hh (100%) rename sstables/{mc => mx}/parsers.hh (100%) rename sstables/{mc => mx}/types.hh (100%) rename sstables/{mc => mx}/writer.cc (99%) rename sstables/{mc => mx}/writer.hh (100%) diff --git a/configure.py b/configure.py index 07afcd0166..abf529f1d0 100755 --- a/configure.py +++ b/configure.py @@ -545,7 +545,7 @@ scylla_core = (['database.cc', 'sstables/mp_row_consumer.cc', 'sstables/sstables.cc', 'sstables/sstables_manager.cc', - 'sstables/mc/writer.cc', + 'sstables/mx/writer.cc', 'sstables/sstable_version.cc', 'sstables/compress.cc', 'sstables/partition.cc', diff --git a/sstables/index_entry.hh b/sstables/index_entry.hh index a5a947e17a..5a7f3abee6 100644 --- a/sstables/index_entry.hh +++ b/sstables/index_entry.hh @@ -28,7 +28,7 @@ #include "column_translation.hh" #include "m_format_read_helpers.hh" #include "utils/overloaded_functor.hh" -#include "sstables/mc/parsers.hh" +#include "sstables/mx/parsers.hh" namespace sstables { diff --git a/sstables/index_reader.hh b/sstables/index_reader.hh index 0c893c7c83..c3be822ab1 100644 --- a/sstables/index_reader.hh +++ b/sstables/index_reader.hh @@ -29,7 +29,7 @@ #include "sstables/prepended_input_stream.hh" #include "tracing/traced_file.hh" #include "sstables/scanning_clustered_index_cursor.hh" -#include "sstables/mc/bsearch_clustered_cursor.hh" +#include "sstables/mx/bsearch_clustered_cursor.hh" namespace sstables { diff --git a/sstables/m_format_read_helpers.hh b/sstables/m_format_read_helpers.hh index cebc08df00..674a17ec3e 100644 --- a/sstables/m_format_read_helpers.hh +++ b/sstables/m_format_read_helpers.hh @@ -29,7 +29,7 @@ #include "sstables/types.hh" #include "sstables/exceptions.hh" #include "clustering_bounds_comparator.hh" -#include "sstables/mc/types.hh" +#include "sstables/mx/types.hh" namespace sstables { diff --git a/sstables/mc/bsearch_clustered_cursor.hh b/sstables/mx/bsearch_clustered_cursor.hh similarity index 100% rename from sstables/mc/bsearch_clustered_cursor.hh rename to sstables/mx/bsearch_clustered_cursor.hh diff --git a/sstables/mc/parsers.hh b/sstables/mx/parsers.hh similarity index 100% rename from sstables/mc/parsers.hh rename to sstables/mx/parsers.hh diff --git a/sstables/mc/types.hh b/sstables/mx/types.hh similarity index 100% rename from sstables/mc/types.hh rename to sstables/mx/types.hh diff --git a/sstables/mc/writer.cc b/sstables/mx/writer.cc similarity index 99% rename from sstables/mc/writer.cc rename to sstables/mx/writer.cc index 6bb5894b7f..8722aa321e 100644 --- a/sstables/mc/writer.cc +++ b/sstables/mx/writer.cc @@ -19,14 +19,14 @@ * along with Scylla. If not, see . */ -#include "sstables/mc/writer.hh" +#include "sstables/mx/writer.hh" #include "sstables/writer.hh" #include "encoding_stats.hh" #include "schema.hh" #include "mutation_fragment.hh" #include "vint-serialization.hh" #include "sstables/types.hh" -#include "sstables/mc/types.hh" +#include "sstables/mx/types.hh" #include "db/config.hh" #include "atomic_cell.hh" #include "utils/exceptions.hh" diff --git a/sstables/mc/writer.hh b/sstables/mx/writer.hh similarity index 100% rename from sstables/mc/writer.hh rename to sstables/mx/writer.hh diff --git a/sstables/sstables.cc b/sstables/sstables.cc index 341d28eec5..057ca75fd3 100644 --- a/sstables/sstables.cc +++ b/sstables/sstables.cc @@ -38,7 +38,7 @@ #include "dht/sharder.hh" #include "types.hh" -#include "mc/writer.hh" +#include "sstables/mx/writer.hh" #include "writer.hh" #include "writer_impl.hh" #include "m_format_read_helpers.hh" diff --git a/test/boost/sstable_3_x_test.cc b/test/boost/sstable_3_x_test.cc index c28d65edf6..d0b28c29a4 100644 --- a/test/boost/sstable_3_x_test.cc +++ b/test/boost/sstable_3_x_test.cc @@ -50,7 +50,7 @@ #include "schema.hh" #include "utils/UUID_gen.hh" #include "encoding_stats.hh" -#include "sstables/mc/writer.hh" +#include "sstables/mx/writer.hh" #include "test/lib/simple_schema.hh" #include "test/lib/exception_utils.hh" #include "test/lib/reader_permit.hh" From 34fb95dacf953f26f230162b951bc8735e0da259 Mon Sep 17 00:00:00 2001 From: Benny Halevy Date: Mon, 10 Aug 2020 17:24:24 +0300 Subject: [PATCH 11/27] sstable: validate_min_max_metadata: drop outdated logic The following checks were introduced in 0a5af611761f3a9e06885cc4053c04bdb0213ed3 To deal with a bug in min max metadata generation of our own, from a time where only ka / la were supported. This is no longer relevant now that we'll consider min_max_column_names only for sstable format > mc (in sstable::may_contain_rows) We choose not to clear_incorrect_min_max_column_names from older versions here as this disturbs sstable unit tests. Signed-off-by: Benny Halevy --- sstables/sstables.cc | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/sstables/sstables.cc b/sstables/sstables.cc index 057ca75fd3..74d5ea12de 100644 --- a/sstables/sstables.cc +++ b/sstables/sstables.cc @@ -1180,28 +1180,21 @@ void sstable::validate_min_max_metadata() { } // The min/max metadata is wrong if: - // 1) it's not empty and schema defines no clustering key. - // 2) their size differ. - // 3) column name is stored instead of clustering value. - // 4) clustering component is stored as composite. - if ((!_schema->clustering_key_size() && (min_column_names.size() || max_column_names.size())) || - (min_column_names.size() != max_column_names.size())) { + // - it's not empty and schema defines no clustering key. + // + // Notes: + // - we are going to rely on min/max column names for + // clustering filtering only from md-format sstables, + // see sstable::may_contain_rows(). + // We choose not to clear_incorrect_min_max_column_names + // from older versions here as this disturbs sstable unit tests. + // + // - now that we store min/max metadata for range tombstones, + // their size may differ. + if (!_schema->clustering_key_size()) { clear_incorrect_min_max_column_names(); return; } - - for (auto i = 0U; i < min_column_names.size(); i++) { - if (_schema->get_column_definition(min_column_names[i].value) || _schema->get_column_definition(max_column_names[i].value)) { - clear_incorrect_min_max_column_names(); - break; - } - - if (_schema->is_compound() && _schema->clustering_key_size() > 1 && _schema->is_dense() && - (composite_view(min_column_names[i].value).is_valid() || composite_view(max_column_names[i].value).is_valid())) { - clear_incorrect_min_max_column_names(); - break; - } - } } void sstable::validate_max_local_deletion_time() { From 68acae5873d1d6e9e83b2b83a82cfa877a0821bc Mon Sep 17 00:00:00 2001 From: Benny Halevy Date: Mon, 13 Jul 2020 18:16:01 +0300 Subject: [PATCH 12/27] sstables: metadata_collector: support min_max_components for range tombstones We essentially treat min/max column names as range bounds with min as incl_start and max as incl_end. By generating a bound_view for min/max column names on the fly, we can correctly track and compare also short clustering key prefixes that may be used as bounds for range tombstones. Extend the sstable_tombstone_metadata_check unit test to cover these cases. Signed-off-by: Benny Halevy --- sstables/metadata_collector.cc | 21 ++ sstables/metadata_collector.hh | 4 + sstables/mx/writer.cc | 5 +- test/boost/sstable_3_x_test.cc | 19 + test/boost/sstable_datafile_test.cc | 528 +++++++++++++++++++++++++++- 5 files changed, 573 insertions(+), 4 deletions(-) diff --git a/sstables/metadata_collector.cc b/sstables/metadata_collector.cc index bfc3eafc89..5da2bdc64a 100644 --- a/sstables/metadata_collector.cc +++ b/sstables/metadata_collector.cc @@ -21,6 +21,7 @@ #include "log.hh" #include "metadata_collector.hh" +#include "range_tombstone.hh" logging::logger mdclogger("metadata_collector"); @@ -60,4 +61,24 @@ void metadata_collector::update_min_max_components(const clustering_key_prefix& } } +void metadata_collector::update_min_max_components(const range_tombstone& rt) { + const bound_view::tri_compare cmp(_schema); + + if (!_min_clustering_key) { + mdclogger.trace("{}: initializing min_clustering_key to rt.start={}", _name, clustering_key_prefix::with_schema_wrapper(_schema, rt.start)); + _min_clustering_key.emplace(rt.start); + } else if (cmp(rt.start_bound(), bound_view(*_min_clustering_key, bound_kind::incl_start)) < 0) { + mdclogger.trace("{}: updating min_clustering_key to rt.start={}", _name, clustering_key_prefix::with_schema_wrapper(_schema, rt.start)); + _min_clustering_key.emplace(rt.start); + } + + if (!_max_clustering_key) { + mdclogger.trace("{}: initializing max_clustering_key to rt.end={}", _name, clustering_key_prefix::with_schema_wrapper(_schema, rt.end)); + _max_clustering_key.emplace(rt.end); + } else if (cmp(rt.end_bound(), bound_view(*_max_clustering_key, bound_kind::incl_end)) > 0) { + mdclogger.trace("{}: updating max_clustering_key to rt.end={}", _name, clustering_key_prefix::with_schema_wrapper(_schema, rt.end)); + _max_clustering_key.emplace(rt.end); + } +} + } // namespace sstables diff --git a/sstables/metadata_collector.hh b/sstables/metadata_collector.hh index 5839bff0ca..27e2cb4e71 100644 --- a/sstables/metadata_collector.hh +++ b/sstables/metadata_collector.hh @@ -50,6 +50,8 @@ #include +class range_tombstone; + namespace sstables { static constexpr int TOMBSTONE_HISTOGRAM_BIN_SIZE = 100; @@ -244,6 +246,8 @@ public: void update_min_max_components(const clustering_key_prefix& key); + void update_min_max_components(const range_tombstone& rt); + void update(column_stats&& stats) { _timestamp_tracker.update(stats.timestamp_tracker); _local_deletion_time_tracker.update(stats.local_deletion_time_tracker); diff --git a/sstables/mx/writer.cc b/sstables/mx/writer.cc index 8722aa321e..139e5fe3a3 100644 --- a/sstables/mx/writer.cc +++ b/sstables/mx/writer.cc @@ -896,6 +896,9 @@ void writer::drain_tombstones(std::optional pos) { position_in_partition::equal_compare eq{_schema}; while (auto mfo = get_next_rt()) { range_tombstone rt {std::move(mfo)->as_range_tombstone()}; + + _sst.get_metadata_collector().update_min_max_components(rt); + bool need_write_start = true; if (_end_open_marker) { if (eq(_end_open_marker->position(), rt.position())) { @@ -1336,8 +1339,6 @@ void writer::write_clustered(const rt_marker& marker, uint64_t prev_row_size) { write_marker_body(_tmp_bufs); write_vint(*_data_writer, _tmp_bufs.size()); flush_tmp_bufs(); - - _sst.get_metadata_collector().update_min_max_components(marker.clustering); } void writer::consume(rt_marker&& marker) { diff --git a/test/boost/sstable_3_x_test.cc b/test/boost/sstable_3_x_test.cc index d0b28c29a4..e34c894e89 100644 --- a/test/boost/sstable_3_x_test.cc +++ b/test/boost/sstable_3_x_test.cc @@ -3269,6 +3269,19 @@ static void validate_stats_metadata(schema_ptr s, sstable_assertions& written_ss check_estimated_histogram(orig_stats.estimated_cells_count, written_stats.estimated_cells_count); } +static void check_min_max_column_names(sstable_assertions& written_sst, std::vector min_components, std::vector max_components) { + const auto& st = written_sst.get_stats_metadata(); + BOOST_TEST_MESSAGE(fmt::format("min {}/{} max {}/{}", st.min_column_names.elements.size(), min_components.size(), st.max_column_names.elements.size(), max_components.size())); + BOOST_REQUIRE(st.min_column_names.elements.size() == min_components.size()); + for (auto i = 0U; i < st.min_column_names.elements.size(); i++) { + BOOST_REQUIRE(min_components[i] == st.min_column_names.elements[i].value); + } + BOOST_REQUIRE(st.max_column_names.elements.size() == max_components.size()); + for (auto i = 0U; i < st.max_column_names.elements.size(); i++) { + BOOST_REQUIRE(max_components[i] == st.max_column_names.elements[i].value); + } +} + SEASTAR_THREAD_TEST_CASE(test_write_static_row) { auto abj = defer([] { await_background_jobs().get(); }); sstring table_name = "static_row"; @@ -4118,6 +4131,7 @@ SEASTAR_THREAD_TEST_CASE(test_write_adjacent_range_tombstones) { tmpdir tmp = write_and_compare_sstables(s, mt, table_name); auto written_sst = validate_read(s, tmp.path(), {mut}); validate_stats_metadata(s, written_sst, table_name, false); + check_min_max_column_names(written_sst, {"aaa"}, {"aaa"}); } // Test the case when subsequent RTs have a common clustering but those bounds are both exclusive @@ -4236,6 +4250,7 @@ SEASTAR_THREAD_TEST_CASE(test_write_mixed_rows_and_range_tombstones) { tmpdir tmp = write_and_compare_sstables(s, mt, table_name); auto written_sst = validate_read(s, tmp.path(), {mut}); validate_stats_metadata(s, written_sst, table_name, false); + check_min_max_column_names(written_sst, {"aaa"}, {"ddd"}); } SEASTAR_THREAD_TEST_CASE(test_write_many_range_tombstones) { @@ -4326,6 +4341,7 @@ SEASTAR_THREAD_TEST_CASE(test_write_adjacent_range_tombstones_with_rows) { tmpdir tmp = write_and_compare_sstables(s, mt, table_name); auto written_sst = validate_read(s, tmp.path(), {mut}); validate_stats_metadata(s, written_sst, table_name, false); + check_min_max_column_names(written_sst, {"aaa"}, {"aaa"}); } SEASTAR_THREAD_TEST_CASE(test_write_range_tombstone_same_start_with_row) { @@ -4365,6 +4381,7 @@ SEASTAR_THREAD_TEST_CASE(test_write_range_tombstone_same_start_with_row) { tmpdir tmp = write_and_compare_sstables(s, mt, table_name); auto written_sst = validate_read(s, tmp.path(), {mut}); validate_stats_metadata(s, written_sst, table_name, false); + check_min_max_column_names(written_sst, {"aaa", "bbb"}, {"aaa"}); } SEASTAR_THREAD_TEST_CASE(test_write_range_tombstone_same_end_with_row) { @@ -4404,6 +4421,7 @@ SEASTAR_THREAD_TEST_CASE(test_write_range_tombstone_same_end_with_row) { tmpdir tmp = write_and_compare_sstables(s, mt, table_name); auto written_sst = validate_read(s, tmp.path(), {mut}); validate_stats_metadata(s, written_sst, table_name, false); + check_min_max_column_names(written_sst, {"aaa"}, {"aaa", "bbb"}); } SEASTAR_THREAD_TEST_CASE(test_write_overlapped_start_range_tombstones) { @@ -4504,6 +4522,7 @@ SEASTAR_THREAD_TEST_CASE(test_write_two_non_adjacent_range_tombstones) { tmpdir tmp = write_and_compare_sstables(s, mt, table_name); auto written_sst = validate_read(s, tmp.path(), {mut}); validate_stats_metadata(s, written_sst, table_name, false); + check_min_max_column_names(written_sst, {"aaa"}, {"aaa"}); } // The resulting files are supposed to be identical to the files diff --git a/test/boost/sstable_datafile_test.cc b/test/boost/sstable_datafile_test.cc index f647bba42d..7c38cabd0e 100644 --- a/test/boost/sstable_datafile_test.cc +++ b/test/boost/sstable_datafile_test.cc @@ -3285,10 +3285,13 @@ SEASTAR_TEST_CASE(test_promoted_index_read) { static void check_min_max_column_names(const sstable_ptr& sst, std::vector min_components, std::vector max_components) { const auto& st = sst->get_stats_metadata(); + BOOST_TEST_MESSAGE(fmt::format("min {}/{} max {}/{}", st.min_column_names.elements.size(), min_components.size(), st.max_column_names.elements.size(), max_components.size())); BOOST_REQUIRE(st.min_column_names.elements.size() == min_components.size()); - BOOST_REQUIRE(st.min_column_names.elements.size() == st.max_column_names.elements.size()); for (auto i = 0U; i < st.min_column_names.elements.size(); i++) { BOOST_REQUIRE(min_components[i] == st.min_column_names.elements[i].value); + } + BOOST_REQUIRE(st.max_column_names.elements.size() == max_components.size()); + for (auto i = 0U; i < st.max_column_names.elements.size(); i++) { BOOST_REQUIRE(max_components[i] == st.max_column_names.elements[i].value); } } @@ -3506,6 +3509,8 @@ SEASTAR_TEST_CASE(sstable_tombstone_metadata_check) { auto c_key = clustering_key_prefix::from_exploded(*s, {to_bytes("c1")}); const column_definition& r1_col = *s->get_column_definition("r1"); + BOOST_TEST_MESSAGE(fmt::format("version {}", to_string(version))); + { auto mt = make_lw_shared(s); mutation m(s, key); @@ -3515,8 +3520,8 @@ SEASTAR_TEST_CASE(sstable_tombstone_metadata_check) { auto sst = env.make_sstable(s, tmp.path().string(), 1, version, big); write_memtable_to_sstable_for_test(*mt, sst).get(); sst = env.reusable_sst(s, tmp.path().string(), 1, version).get0(); - sstables::sstlog.warn("Version {}", (int)version); BOOST_REQUIRE(sst->get_stats_metadata().estimated_tombstone_drop_time.bin.size()); + check_min_max_column_names(sst, {"c1"}, {"c1"}); } { @@ -3528,6 +3533,7 @@ SEASTAR_TEST_CASE(sstable_tombstone_metadata_check) { write_memtable_to_sstable_for_test(*mt, sst).get(); sst = env.reusable_sst(s, tmp.path().string(), 2, version).get0(); BOOST_REQUIRE(sst->get_stats_metadata().estimated_tombstone_drop_time.bin.size()); + check_min_max_column_names(sst, {"c1"}, {"c1"}); } { @@ -3539,6 +3545,7 @@ SEASTAR_TEST_CASE(sstable_tombstone_metadata_check) { write_memtable_to_sstable_for_test(*mt, sst).get(); sst = env.reusable_sst(s, tmp.path().string(), 3, version).get0(); BOOST_REQUIRE(!sst->get_stats_metadata().estimated_tombstone_drop_time.bin.size()); + check_min_max_column_names(sst, {"c1"}, {"c1"}); } { @@ -3558,6 +3565,7 @@ SEASTAR_TEST_CASE(sstable_tombstone_metadata_check) { write_memtable_to_sstable_for_test(*mt, sst).get(); sst = env.reusable_sst(s, tmp.path().string(), 4, version).get0(); BOOST_REQUIRE(sst->get_stats_metadata().estimated_tombstone_drop_time.bin.size()); + check_min_max_column_names(sst, {"c1"}, {"c1"}); } { @@ -3570,6 +3578,7 @@ SEASTAR_TEST_CASE(sstable_tombstone_metadata_check) { write_memtable_to_sstable_for_test(*mt, sst).get(); sst = env.reusable_sst(s, tmp.path().string(), 5, version).get0(); BOOST_REQUIRE(sst->get_stats_metadata().estimated_tombstone_drop_time.bin.size()); + check_min_max_column_names(sst, {}, {}); } { @@ -3584,6 +3593,521 @@ SEASTAR_TEST_CASE(sstable_tombstone_metadata_check) { write_memtable_to_sstable_for_test(*mt, sst).get(); sst = env.reusable_sst(s, tmp.path().string(), 6, version).get0(); BOOST_REQUIRE(sst->get_stats_metadata().estimated_tombstone_drop_time.bin.size()); + if (version >= sstable_version_types::mc) { + check_min_max_column_names(sst, {"a"}, {"a"}); + } + } + + { + auto mt = make_lw_shared(s); + mutation m(s, key); + m.set_clustered_cell(c_key, r1_col, make_atomic_cell(int32_type, int32_type->decompose(1))); + tombstone tomb(api::new_timestamp(), gc_clock::now()); + range_tombstone rt( + clustering_key_prefix::from_single_value(*s, bytes("a")), + clustering_key_prefix::from_single_value(*s, bytes("a")), + tomb); + m.partition().apply_delete(*s, std::move(rt)); + mt->apply(std::move(m)); + auto sst = env.make_sstable(s, tmp.path().string(), 7, version, big); + write_memtable_to_sstable_for_test(*mt, sst).get(); + sst = env.reusable_sst(s, tmp.path().string(), 7, version).get0(); + BOOST_REQUIRE(sst->get_stats_metadata().estimated_tombstone_drop_time.bin.size()); + if (version >= sstable_version_types::mc) { + check_min_max_column_names(sst, {"a"}, {"c1"}); + } + } + + { + auto mt = make_lw_shared(s); + mutation m(s, key); + m.set_clustered_cell(c_key, r1_col, make_atomic_cell(int32_type, int32_type->decompose(1))); + tombstone tomb(api::new_timestamp(), gc_clock::now()); + range_tombstone rt( + clustering_key_prefix::from_single_value(*s, bytes("c")), + clustering_key_prefix::from_single_value(*s, bytes("d")), + tomb); + m.partition().apply_delete(*s, std::move(rt)); + mt->apply(std::move(m)); + auto sst = env.make_sstable(s, tmp.path().string(), 8, version, big); + write_memtable_to_sstable_for_test(*mt, sst).get(); + sst = env.reusable_sst(s, tmp.path().string(), 8, version).get0(); + BOOST_REQUIRE(sst->get_stats_metadata().estimated_tombstone_drop_time.bin.size()); + if (version >= sstable_version_types::mc) { + check_min_max_column_names(sst, {"c"}, {"d"}); + } + } + + { + auto mt = make_lw_shared(s); + mutation m(s, key); + m.set_clustered_cell(c_key, r1_col, make_atomic_cell(int32_type, int32_type->decompose(1))); + tombstone tomb(api::new_timestamp(), gc_clock::now()); + range_tombstone rt( + clustering_key_prefix::from_single_value(*s, bytes("d")), + clustering_key_prefix::from_single_value(*s, bytes("z")), + tomb); + m.partition().apply_delete(*s, std::move(rt)); + mt->apply(std::move(m)); + auto sst = env.make_sstable(s, tmp.path().string(), 9, version, big); + write_memtable_to_sstable_for_test(*mt, sst).get(); + sst = env.reusable_sst(s, tmp.path().string(), 9, version).get0(); + BOOST_REQUIRE(sst->get_stats_metadata().estimated_tombstone_drop_time.bin.size()); + if (version >= sstable_version_types::mc) { + check_min_max_column_names(sst, {"c1"}, {"z"}); + } + } + + if (version >= sstable_version_types::mc) { + { + auto mt = make_lw_shared(s); + mutation m(s, key); + m.set_clustered_cell(c_key, r1_col, make_atomic_cell(int32_type, int32_type->decompose(1))); + tombstone tomb(api::new_timestamp(), gc_clock::now()); + range_tombstone rt( + bound_view::bottom(), + bound_view(clustering_key_prefix::from_single_value(*s, bytes("z")), bound_kind::incl_end), + tomb); + m.partition().apply_delete(*s, std::move(rt)); + mt->apply(std::move(m)); + auto sst = env.make_sstable(s, tmp.path().string(), 10, version, big); + write_memtable_to_sstable_for_test(*mt, sst).get(); + sst = env.reusable_sst(s, tmp.path().string(), 10, version).get0(); + BOOST_REQUIRE(sst->get_stats_metadata().estimated_tombstone_drop_time.bin.size()); + check_min_max_column_names(sst, {}, {"z"}); + } + + { + auto mt = make_lw_shared(s); + mutation m(s, key); + m.set_clustered_cell(c_key, r1_col, make_atomic_cell(int32_type, int32_type->decompose(1))); + tombstone tomb(api::new_timestamp(), gc_clock::now()); + range_tombstone rt( + bound_view(clustering_key_prefix::from_single_value(*s, bytes("a")), bound_kind::incl_start), + bound_view::top(), + tomb); + m.partition().apply_delete(*s, std::move(rt)); + mt->apply(std::move(m)); + auto sst = env.make_sstable(s, tmp.path().string(), 11, version, big); + write_memtable_to_sstable_for_test(*mt, sst).get(); + sst = env.reusable_sst(s, tmp.path().string(), 11, version).get0(); + BOOST_REQUIRE(sst->get_stats_metadata().estimated_tombstone_drop_time.bin.size()); + check_min_max_column_names(sst, {"a"}, {}); + } + } + } + }); +} + +SEASTAR_TEST_CASE(sstable_composite_tombstone_metadata_check) { + return test_env::do_with_async([] (test_env& env) { + storage_service_for_tests ssft; + for (const auto version : all_sstable_versions) { + auto s = schema_builder("ks", "cf") + .with_column("pk", utf8_type, column_kind::partition_key) + .with_column("ck1", utf8_type, column_kind::clustering_key) + .with_column("ck2", utf8_type, column_kind::clustering_key) + .with_column("r1", int32_type) + .build(); + auto tmp = tmpdir(); + auto key = partition_key::from_exploded(*s, {to_bytes("key1")}); + auto c_key = clustering_key_prefix::from_exploded(*s, {to_bytes("c1"), to_bytes("c2")}); + const column_definition& r1_col = *s->get_column_definition("r1"); + + BOOST_TEST_MESSAGE(fmt::format("version {}", to_string(version))); + + { + auto mt = make_lw_shared(s); + mutation m(s, key); + tombstone tomb(api::new_timestamp(), gc_clock::now()); + m.partition().apply_delete(*s, c_key, tomb); + mt->apply(std::move(m)); + auto sst = env.make_sstable(s, tmp.path().string(), 1, version, big); + write_memtable_to_sstable_for_test(*mt, sst).get(); + sst = env.reusable_sst(s, tmp.path().string(), 1, version).get0(); + BOOST_REQUIRE(sst->get_stats_metadata().estimated_tombstone_drop_time.bin.size()); + check_min_max_column_names(sst, {"c1", "c2"}, {"c1", "c2"}); + } + + { + auto mt = make_lw_shared(s); + mutation m(s, key); + m.set_clustered_cell(c_key, r1_col, make_dead_atomic_cell(3600)); + mt->apply(std::move(m)); + auto sst = env.make_sstable(s, tmp.path().string(), 2, version, big); + write_memtable_to_sstable_for_test(*mt, sst).get(); + sst = env.reusable_sst(s, tmp.path().string(), 2, version).get0(); + BOOST_REQUIRE(sst->get_stats_metadata().estimated_tombstone_drop_time.bin.size()); + check_min_max_column_names(sst, {"c1", "c2"}, {"c1", "c2"}); + } + + { + auto mt = make_lw_shared(s); + mutation m(s, key); + m.set_clustered_cell(c_key, r1_col, make_atomic_cell(int32_type, int32_type->decompose(1))); + mt->apply(std::move(m)); + auto sst = env.make_sstable(s, tmp.path().string(), 3, version, big); + write_memtable_to_sstable_for_test(*mt, sst).get(); + sst = env.reusable_sst(s, tmp.path().string(), 3, version).get0(); + BOOST_REQUIRE(!sst->get_stats_metadata().estimated_tombstone_drop_time.bin.size()); + check_min_max_column_names(sst, {"c1", "c2"}, {"c1", "c2"}); + } + + { + auto mt = make_lw_shared(s); + + mutation m(s, key); + tombstone tomb(api::new_timestamp(), gc_clock::now()); + m.partition().apply_delete(*s, c_key, tomb); + mt->apply(std::move(m)); + + auto key2 = partition_key::from_exploded(*s, {to_bytes("key2")}); + mutation m2(s, key2); + m2.set_clustered_cell(c_key, r1_col, make_atomic_cell(int32_type, int32_type->decompose(1))); + mt->apply(std::move(m2)); + + auto sst = env.make_sstable(s, tmp.path().string(), 4, version, big); + write_memtable_to_sstable_for_test(*mt, sst).get(); + sst = env.reusable_sst(s, tmp.path().string(), 4, version).get0(); + BOOST_REQUIRE(sst->get_stats_metadata().estimated_tombstone_drop_time.bin.size()); + check_min_max_column_names(sst, {"c1", "c2"}, {"c1", "c2"}); + } + + { + auto mt = make_lw_shared(s); + mutation m(s, key); + tombstone tomb(api::new_timestamp(), gc_clock::now()); + m.partition().apply(tomb); + mt->apply(std::move(m)); + auto sst = env.make_sstable(s, tmp.path().string(), 5, version, big); + write_memtable_to_sstable_for_test(*mt, sst).get(); + sst = env.reusable_sst(s, tmp.path().string(), 5, version).get0(); + BOOST_REQUIRE(sst->get_stats_metadata().estimated_tombstone_drop_time.bin.size()); + check_min_max_column_names(sst, {}, {}); + } + + { + auto mt = make_lw_shared(s); + mutation m(s, key); + tombstone tomb(api::new_timestamp(), gc_clock::now()); + range_tombstone rt( + clustering_key_prefix::from_exploded(*s, {to_bytes("a"), to_bytes("aa")}), + clustering_key_prefix::from_exploded(*s, {to_bytes("z"), to_bytes("zz")}), + tomb); + m.partition().apply_delete(*s, std::move(rt)); + mt->apply(std::move(m)); + auto sst = env.make_sstable(s, tmp.path().string(), 6, version, big); + write_memtable_to_sstable_for_test(*mt, sst).get(); + sst = env.reusable_sst(s, tmp.path().string(), 6, version).get0(); + BOOST_REQUIRE(sst->get_stats_metadata().estimated_tombstone_drop_time.bin.size()); + if (version >= sstable_version_types::mc) { + check_min_max_column_names(sst, {"a", "aa"}, {"z", "zz"}); + } + } + + { + auto mt = make_lw_shared(s); + mutation m(s, key); + m.set_clustered_cell(c_key, r1_col, make_atomic_cell(int32_type, int32_type->decompose(1))); + tombstone tomb(api::new_timestamp(), gc_clock::now()); + range_tombstone rt( + clustering_key_prefix::from_exploded(*s, {to_bytes("a")}), + clustering_key_prefix::from_exploded(*s, {to_bytes("a"), to_bytes("zz")}), + tomb); + m.partition().apply_delete(*s, std::move(rt)); + mt->apply(std::move(m)); + auto sst = env.make_sstable(s, tmp.path().string(), 7, version, big); + write_memtable_to_sstable_for_test(*mt, sst).get(); + sst = env.reusable_sst(s, tmp.path().string(), 7, version).get0(); + BOOST_REQUIRE(sst->get_stats_metadata().estimated_tombstone_drop_time.bin.size()); + if (version >= sstable_version_types::mc) { + check_min_max_column_names(sst, {"a"}, {"c1", "c2"}); + } + } + + { + auto mt = make_lw_shared(s); + mutation m(s, key); + m.set_clustered_cell(c_key, r1_col, make_atomic_cell(int32_type, int32_type->decompose(1))); + tombstone tomb(api::new_timestamp(), gc_clock::now()); + range_tombstone rt( + clustering_key_prefix::from_exploded(*s, {to_bytes("c1"), to_bytes("aa")}), + clustering_key_prefix::from_exploded(*s, {to_bytes("c1"), to_bytes("zz")}), + tomb); + m.partition().apply_delete(*s, std::move(rt)); + mt->apply(std::move(m)); + auto sst = env.make_sstable(s, tmp.path().string(), 8, version, big); + write_memtable_to_sstable_for_test(*mt, sst).get(); + sst = env.reusable_sst(s, tmp.path().string(), 8, version).get0(); + BOOST_REQUIRE(sst->get_stats_metadata().estimated_tombstone_drop_time.bin.size()); + if (version >= sstable_version_types::mc) { + check_min_max_column_names(sst, {"c1", "aa"}, {"c1", "zz"}); + } + } + + { + auto mt = make_lw_shared(s); + mutation m(s, key); + m.set_clustered_cell(c_key, r1_col, make_atomic_cell(int32_type, int32_type->decompose(1))); + tombstone tomb(api::new_timestamp(), gc_clock::now()); + range_tombstone rt( + clustering_key_prefix::from_exploded(*s, {to_bytes("c1"), to_bytes("d")}), + clustering_key_prefix::from_exploded(*s, {to_bytes("z"), to_bytes("zz")}), + tomb); + m.partition().apply_delete(*s, std::move(rt)); + mt->apply(std::move(m)); + auto sst = env.make_sstable(s, tmp.path().string(), 9, version, big); + write_memtable_to_sstable_for_test(*mt, sst).get(); + sst = env.reusable_sst(s, tmp.path().string(), 9, version).get0(); + BOOST_REQUIRE(sst->get_stats_metadata().estimated_tombstone_drop_time.bin.size()); + if (version >= sstable_version_types::mc) { + check_min_max_column_names(sst, {"c1", "c2"}, {"z", "zz"}); + } + } + + if (version >= sstable_version_types::mc) { + { + auto mt = make_lw_shared(s); + mutation m(s, key); + m.set_clustered_cell(c_key, r1_col, make_atomic_cell(int32_type, int32_type->decompose(1))); + tombstone tomb(api::new_timestamp(), gc_clock::now()); + range_tombstone rt( + bound_view::bottom(), + bound_view(clustering_key_prefix::from_single_value(*s, bytes("z")), bound_kind::incl_end), + tomb); + m.partition().apply_delete(*s, std::move(rt)); + mt->apply(std::move(m)); + auto sst = env.make_sstable(s, tmp.path().string(), 10, version, big); + write_memtable_to_sstable_for_test(*mt, sst).get(); + sst = env.reusable_sst(s, tmp.path().string(), 10, version).get0(); + BOOST_REQUIRE(sst->get_stats_metadata().estimated_tombstone_drop_time.bin.size()); + check_min_max_column_names(sst, {}, {"z"}); + } + + { + auto mt = make_lw_shared(s); + mutation m(s, key); + m.set_clustered_cell(c_key, r1_col, make_atomic_cell(int32_type, int32_type->decompose(1))); + tombstone tomb(api::new_timestamp(), gc_clock::now()); + range_tombstone rt( + bound_view(clustering_key_prefix::from_single_value(*s, bytes("a")), bound_kind::incl_start), + bound_view::top(), + tomb); + m.partition().apply_delete(*s, std::move(rt)); + mt->apply(std::move(m)); + auto sst = env.make_sstable(s, tmp.path().string(), 11, version, big); + write_memtable_to_sstable_for_test(*mt, sst).get(); + sst = env.reusable_sst(s, tmp.path().string(), 11, version).get0(); + BOOST_REQUIRE(sst->get_stats_metadata().estimated_tombstone_drop_time.bin.size()); + check_min_max_column_names(sst, {"a"}, {}); + } + } + } + }); +} + +SEASTAR_TEST_CASE(sstable_composite_reverse_tombstone_metadata_check) { + return test_env::do_with_async([] (test_env& env) { + storage_service_for_tests ssft; + for (const auto version : all_sstable_versions) { + auto s = schema_builder("ks", "cf") + .with_column("pk", utf8_type, column_kind::partition_key) + .with_column("ck1", utf8_type, column_kind::clustering_key) + .with_column("ck2", reversed_type_impl::get_instance(utf8_type), column_kind::clustering_key) + .with_column("r1", int32_type) + .build(); + auto tmp = tmpdir(); + auto key = partition_key::from_exploded(*s, {to_bytes("key1")}); + auto c_key = clustering_key_prefix::from_exploded(*s, {to_bytes("c1"), to_bytes("c2")}); + const column_definition& r1_col = *s->get_column_definition("r1"); + + BOOST_TEST_MESSAGE(fmt::format("version {}", to_string(version))); + + { + auto mt = make_lw_shared(s); + mutation m(s, key); + tombstone tomb(api::new_timestamp(), gc_clock::now()); + m.partition().apply_delete(*s, c_key, tomb); + mt->apply(std::move(m)); + auto sst = env.make_sstable(s, tmp.path().string(), 1, version, big); + write_memtable_to_sstable_for_test(*mt, sst).get(); + sst = env.reusable_sst(s, tmp.path().string(), 1, version).get0(); + BOOST_REQUIRE(sst->get_stats_metadata().estimated_tombstone_drop_time.bin.size()); + check_min_max_column_names(sst, {"c1", "c2"}, {"c1", "c2"}); + } + + { + auto mt = make_lw_shared(s); + mutation m(s, key); + m.set_clustered_cell(c_key, r1_col, make_dead_atomic_cell(3600)); + mt->apply(std::move(m)); + auto sst = env.make_sstable(s, tmp.path().string(), 2, version, big); + write_memtable_to_sstable_for_test(*mt, sst).get(); + sst = env.reusable_sst(s, tmp.path().string(), 2, version).get0(); + BOOST_REQUIRE(sst->get_stats_metadata().estimated_tombstone_drop_time.bin.size()); + check_min_max_column_names(sst, {"c1", "c2"}, {"c1", "c2"}); + } + + { + auto mt = make_lw_shared(s); + mutation m(s, key); + m.set_clustered_cell(c_key, r1_col, make_atomic_cell(int32_type, int32_type->decompose(1))); + mt->apply(std::move(m)); + auto sst = env.make_sstable(s, tmp.path().string(), 3, version, big); + write_memtable_to_sstable_for_test(*mt, sst).get(); + sst = env.reusable_sst(s, tmp.path().string(), 3, version).get0(); + BOOST_REQUIRE(!sst->get_stats_metadata().estimated_tombstone_drop_time.bin.size()); + check_min_max_column_names(sst, {"c1", "c2"}, {"c1", "c2"}); + } + + { + auto mt = make_lw_shared(s); + + mutation m(s, key); + tombstone tomb(api::new_timestamp(), gc_clock::now()); + m.partition().apply_delete(*s, c_key, tomb); + mt->apply(std::move(m)); + + auto key2 = partition_key::from_exploded(*s, {to_bytes("key2")}); + mutation m2(s, key2); + m2.set_clustered_cell(c_key, r1_col, make_atomic_cell(int32_type, int32_type->decompose(1))); + mt->apply(std::move(m2)); + + auto sst = env.make_sstable(s, tmp.path().string(), 4, version, big); + write_memtable_to_sstable_for_test(*mt, sst).get(); + sst = env.reusable_sst(s, tmp.path().string(), 4, version).get0(); + BOOST_REQUIRE(sst->get_stats_metadata().estimated_tombstone_drop_time.bin.size()); + check_min_max_column_names(sst, {"c1", "c2"}, {"c1", "c2"}); + } + + { + auto mt = make_lw_shared(s); + mutation m(s, key); + tombstone tomb(api::new_timestamp(), gc_clock::now()); + m.partition().apply(tomb); + mt->apply(std::move(m)); + auto sst = env.make_sstable(s, tmp.path().string(), 5, version, big); + write_memtable_to_sstable_for_test(*mt, sst).get(); + sst = env.reusable_sst(s, tmp.path().string(), 5, version).get0(); + BOOST_REQUIRE(sst->get_stats_metadata().estimated_tombstone_drop_time.bin.size()); + check_min_max_column_names(sst, {}, {}); + } + + { + auto mt = make_lw_shared(s); + mutation m(s, key); + tombstone tomb(api::new_timestamp(), gc_clock::now()); + range_tombstone rt( + clustering_key_prefix::from_exploded(*s, {to_bytes("a"), to_bytes("zz")}), + clustering_key_prefix::from_exploded(*s, {to_bytes("a"), to_bytes("aa")}), + tomb); + m.partition().apply_delete(*s, std::move(rt)); + mt->apply(std::move(m)); + auto sst = env.make_sstable(s, tmp.path().string(), 6, version, big); + write_memtable_to_sstable_for_test(*mt, sst).get(); + sst = env.reusable_sst(s, tmp.path().string(), 6, version).get0(); + BOOST_REQUIRE(sst->get_stats_metadata().estimated_tombstone_drop_time.bin.size()); + if (version >= sstable_version_types::mc) { + check_min_max_column_names(sst, {"a", "zz"}, {"a", "aa"}); + } + } + + { + auto mt = make_lw_shared(s); + mutation m(s, key); + m.set_clustered_cell(c_key, r1_col, make_atomic_cell(int32_type, int32_type->decompose(1))); + tombstone tomb(api::new_timestamp(), gc_clock::now()); + range_tombstone rt( + clustering_key_prefix::from_exploded(*s, {to_bytes("a"), to_bytes("zz")}), + clustering_key_prefix::from_exploded(*s, {to_bytes("a")}), + tomb); + m.partition().apply_delete(*s, std::move(rt)); + mt->apply(std::move(m)); + auto sst = env.make_sstable(s, tmp.path().string(), 7, version, big); + write_memtable_to_sstable_for_test(*mt, sst).get(); + sst = env.reusable_sst(s, tmp.path().string(), 7, version).get0(); + BOOST_REQUIRE(sst->get_stats_metadata().estimated_tombstone_drop_time.bin.size()); + if (version >= sstable_version_types::mc) { + check_min_max_column_names(sst, {"a", "zz"}, {"c1", "c2"}); + } + } + + { + auto mt = make_lw_shared(s); + mutation m(s, key); + m.set_clustered_cell(c_key, r1_col, make_atomic_cell(int32_type, int32_type->decompose(1))); + tombstone tomb(api::new_timestamp(), gc_clock::now()); + range_tombstone rt( + clustering_key_prefix::from_exploded(*s, {to_bytes("c1"), to_bytes("zz")}), + clustering_key_prefix::from_exploded(*s, {to_bytes("c1")}), + tomb); + m.partition().apply_delete(*s, std::move(rt)); + mt->apply(std::move(m)); + auto sst = env.make_sstable(s, tmp.path().string(), 8, version, big); + write_memtable_to_sstable_for_test(*mt, sst).get(); + sst = env.reusable_sst(s, tmp.path().string(), 8, version).get0(); + BOOST_REQUIRE(sst->get_stats_metadata().estimated_tombstone_drop_time.bin.size()); + if (version >= sstable_version_types::mc) { + check_min_max_column_names(sst, {"c1", "zz"}, {"c1"}); + } + } + + { + auto mt = make_lw_shared(s); + mutation m(s, key); + m.set_clustered_cell(c_key, r1_col, make_atomic_cell(int32_type, int32_type->decompose(1))); + tombstone tomb(api::new_timestamp(), gc_clock::now()); + range_tombstone rt( + clustering_key_prefix::from_exploded(*s, {to_bytes("c1"), to_bytes("zz")}), + clustering_key_prefix::from_exploded(*s, {to_bytes("c1"), to_bytes("d")}), + tomb); + m.partition().apply_delete(*s, std::move(rt)); + mt->apply(std::move(m)); + auto sst = env.make_sstable(s, tmp.path().string(), 9, version, big); + write_memtable_to_sstable_for_test(*mt, sst).get(); + sst = env.reusable_sst(s, tmp.path().string(), 9, version).get0(); + BOOST_REQUIRE(sst->get_stats_metadata().estimated_tombstone_drop_time.bin.size()); + if (version >= sstable_version_types::mc) { + check_min_max_column_names(sst, {"c1", "zz"}, {"c1", "c2"}); + } + } + + if (version >= sstable_version_types::mc) { + { + auto mt = make_lw_shared(s); + mutation m(s, key); + m.set_clustered_cell(c_key, r1_col, make_atomic_cell(int32_type, int32_type->decompose(1))); + tombstone tomb(api::new_timestamp(), gc_clock::now()); + range_tombstone rt( + bound_view::bottom(), + bound_view(clustering_key_prefix::from_single_value(*s, bytes("z")), bound_kind::incl_end), + tomb); + m.partition().apply_delete(*s, std::move(rt)); + mt->apply(std::move(m)); + auto sst = env.make_sstable(s, tmp.path().string(), 10, version, big); + write_memtable_to_sstable_for_test(*mt, sst).get(); + sst = env.reusable_sst(s, tmp.path().string(), 10, version).get0(); + BOOST_REQUIRE(sst->get_stats_metadata().estimated_tombstone_drop_time.bin.size()); + check_min_max_column_names(sst, {}, {"z"}); + } + + { + auto mt = make_lw_shared(s); + mutation m(s, key); + m.set_clustered_cell(c_key, r1_col, make_atomic_cell(int32_type, int32_type->decompose(1))); + tombstone tomb(api::new_timestamp(), gc_clock::now()); + range_tombstone rt( + bound_view(clustering_key_prefix::from_single_value(*s, bytes("a")), bound_kind::incl_start), + bound_view::top(), + tomb); + m.partition().apply_delete(*s, std::move(rt)); + mt->apply(std::move(m)); + auto sst = env.make_sstable(s, tmp.path().string(), 11, version, big); + write_memtable_to_sstable_for_test(*mt, sst).get(); + sst = env.reusable_sst(s, tmp.path().string(), 11, version).get0(); + BOOST_REQUIRE(sst->get_stats_metadata().estimated_tombstone_drop_time.bin.size()); + check_min_max_column_names(sst, {"a"}, {}); + } } } }); From bd4383a8421c496cb080799164f1ff7846a6b9e8 Mon Sep 17 00:00:00 2001 From: Benny Halevy Date: Mon, 20 Jul 2020 15:33:30 +0300 Subject: [PATCH 13/27] sstables: mx/writer: update_min_max_components for partition tombstone Partition tombstones represent an implicit clustering range that is unbound on both sides, so reflect than in min/max column names metadata using empty clustering key prefixes. If we don't do that, when using the sstable for filtering, we have no other way of distinguishing range tombstones from partition tombstones given the sstable metadata and we would need to include any sstable with tombstones, even if those are range tombstone, for which we can do a better filtering job, using the sstable min/max column names metadata. Signed-off-by: Benny Halevy --- sstables/mx/writer.cc | 4 ++++ test/boost/sstable_datafile_test.cc | 14 ++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/sstables/mx/writer.cc b/sstables/mx/writer.cc index 139e5fe3a3..9c48427da4 100644 --- a/sstables/mx/writer.cc +++ b/sstables/mx/writer.cc @@ -982,6 +982,10 @@ void writer::consume(tombstone t) { _pi_write_m.tomb = t; _tombstone_written = true; + + if (t) { + _sst.get_metadata_collector().update_min_max_components(clustering_key_prefix::make_empty(_schema)); + } } void writer::write_cell(bytes_ostream& writer, const clustering_key_prefix* clustering_key, atomic_cell_view cell, diff --git a/test/boost/sstable_datafile_test.cc b/test/boost/sstable_datafile_test.cc index 7c38cabd0e..852a9e117b 100644 --- a/test/boost/sstable_datafile_test.cc +++ b/test/boost/sstable_datafile_test.cc @@ -3694,6 +3694,20 @@ SEASTAR_TEST_CASE(sstable_tombstone_metadata_check) { BOOST_REQUIRE(sst->get_stats_metadata().estimated_tombstone_drop_time.bin.size()); check_min_max_column_names(sst, {"a"}, {}); } + + { + auto mt = make_lw_shared(s); + mutation m(s, key); + m.set_clustered_cell(c_key, r1_col, make_atomic_cell(int32_type, int32_type->decompose(1))); + tombstone tomb(api::new_timestamp(), gc_clock::now()); + m.partition().apply_delete(*s, clustering_key_prefix::make_empty(), tomb); + mt->apply(std::move(m)); + auto sst = env.make_sstable(s, tmp.path().string(), 12, version, big); + write_memtable_to_sstable_for_test(*mt, sst).get(); + sst = env.reusable_sst(s, tmp.path().string(), 12, version).get0(); + BOOST_REQUIRE(sst->get_stats_metadata().estimated_tombstone_drop_time.bin.size()); + check_min_max_column_names(sst, {}, {}); + } } } }); From e44ec45ab99fa2ad82dc2d6cd5d226a4d3992059 Mon Sep 17 00:00:00 2001 From: Benny Halevy Date: Thu, 2 Jul 2020 09:48:35 +0300 Subject: [PATCH 14/27] sstables: mx/writer: use version from sstable for write calls Rather than using a constant sstable_version_types::mc. In preparation to supporting sstable_version_types::md. Signed-off-by: Benny Halevy --- sstables/mx/writer.cc | 32 ++++++++++++++++---------------- sstables/writer.hh | 10 +++++----- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/sstables/mx/writer.cc b/sstables/mx/writer.cc index 9c48427da4..50ebb1323e 100644 --- a/sstables/mx/writer.cc +++ b/sstables/mx/writer.cc @@ -192,20 +192,20 @@ public: template requires Writer -static void write(W& out, const clustering_block& block) { +static void write(sstable_version_types v, W& out, const clustering_block& block) { write_vint(out, block.header); for (const auto& [value, type]: block.values) { - write_cell_value(out, type, value); + write_cell_value(v, out, type, value); } } template requires Writer -void write_clustering_prefix(W& out, const schema& s, +void write_clustering_prefix(sstable_version_types v, W& out, const schema& s, const clustering_key_prefix& prefix, ephemerally_full_prefix is_ephemerally_full) { clustering_blocks_input_range range{s, prefix, is_ephemerally_full}; for (const auto block: range) { - write(out, block); + write(v, out, block); } } @@ -1040,14 +1040,14 @@ void writer::write_cell(bytes_ostream& writer, const clustering_key_prefix* clus 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) { + write_counter_value(ccv, writer, _sst.get_version(), [] (bytes_ostream& out, uint32_t value) { return write_vint(out, value); }); }); } } else { if (has_value) { - write_cell_value(writer, *cdef.type, cell.value()); + write_cell_value(_sst.get_version(), writer, *cdef.type, cell.value()); } } @@ -1256,7 +1256,7 @@ void writer::write_clustered(const clustering_row& clustered_row, uint64_t prev_ write(_sst.get_version(), *_data_writer, ext_flags); } - write_clustering_prefix(*_data_writer, _schema, clustered_row.key(), ephemerally_full_prefix{_schema.is_compact_table()}); + write_clustering_prefix(_sst.get_version(), *_data_writer, _schema, clustered_row.key(), ephemerally_full_prefix{_schema.is_compact_table()}); write_vint(_tmp_bufs, prev_row_size); write_row_body(_tmp_bufs, clustered_row, has_complex_deletion); @@ -1284,17 +1284,17 @@ stop_iteration writer::consume(clustering_row&& cr) { // Write clustering prefix along with its bound kind and, if not full, its size template requires Writer -static void write_clustering_prefix(W& writer, bound_kind_m kind, +static void write_clustering_prefix(sstable_version_types v, W& writer, bound_kind_m kind, const schema& s, const clustering_key_prefix& clustering) { assert(kind != bound_kind_m::static_clustering); - write(sstable_version_types::mc, writer, kind); + write(v, writer, kind); auto is_ephemerally_full = ephemerally_full_prefix{s.is_compact_table()}; if (kind != bound_kind_m::clustering) { // Don't use ephemerally full for RT bounds as they're always non-full is_ephemerally_full = ephemerally_full_prefix::no; - write(sstable_version_types::mc, writer, static_cast(clustering.size(s))); + write(v, writer, static_cast(clustering.size(s))); } - write_clustering_prefix(writer, s, clustering, is_ephemerally_full); + write_clustering_prefix(v, writer, s, clustering, is_ephemerally_full); } void writer::write_promoted_index() { @@ -1317,19 +1317,19 @@ void writer::write_pi_block(const pi_block& block) { bytes_ostream& blocks = _pi_write_m.blocks; uint32_t offset = blocks.size(); write(_sst.get_version(), _pi_write_m.offsets, offset); - write_clustering_prefix(blocks, block.first.kind, _schema, block.first.clustering); - write_clustering_prefix(blocks, block.last.kind, _schema, block.last.clustering); + write_clustering_prefix(_sst.get_version(), blocks, block.first.kind, _schema, block.first.clustering); + write_clustering_prefix(_sst.get_version(), blocks, block.last.kind, _schema, block.last.clustering); write_vint(blocks, block.offset); write_signed_vint(blocks, block.width - width_base); write(_sst.get_version(), blocks, static_cast(block.open_marker ? 1 : 0)); if (block.open_marker) { - write(sstable_version_types::mc, blocks, to_deletion_time(*block.open_marker)); + write(_sst.get_version(), blocks, to_deletion_time(*block.open_marker)); } } void writer::write_clustered(const rt_marker& marker, uint64_t prev_row_size) { - write(sstable_version_types::mc, *_data_writer, row_flags::is_marker); - write_clustering_prefix(*_data_writer, marker.kind, _schema, marker.clustering); + write(_sst.get_version(), *_data_writer, row_flags::is_marker); + write_clustering_prefix(_sst.get_version(), *_data_writer, marker.kind, _schema, marker.clustering); auto write_marker_body = [this, &marker] (bytes_ostream& writer) { write_delta_deletion_time(writer, marker.tomb); _c_stats.update(marker.tomb); diff --git a/sstables/writer.hh b/sstables/writer.hh index e6aa2fe38a..d0424871b2 100644 --- a/sstables/writer.hh +++ b/sstables/writer.hh @@ -525,26 +525,26 @@ void write_column_name(sstable_version_types v, Writer& out, const schema& s, co template requires Writer -void write_cell_value(W& out, const abstract_type& type, bytes_view value) { +void write_cell_value(sstable_version_types v, W& out, const abstract_type& type, bytes_view value) { if (!value.empty()) { if (type.value_length_if_fixed()) { - write(sstable_version_types::mc, out, value); + write(v, out, value); } else { write_vint(out, value.size()); - write(sstable_version_types::mc, out, value); + write(v, out, value); } } } template requires Writer -void write_cell_value(W& out, const abstract_type& type, atomic_cell_value_view value) { +void write_cell_value(sstable_version_types v, W& out, const abstract_type& type, atomic_cell_value_view value) { if (!value.empty()) { if (!type.value_length_if_fixed()) { write_vint(out, value.size_bytes()); } using boost::range::for_each; - for_each(value, [&] (bytes_view fragment) { write(sstable_version_types::mc, out, fragment); }); + for_each(value, [&] (bytes_view fragment) { write(v, out, fragment); }); } } From 3168be348386441a3179a50a93fab5762dbcab29 Mon Sep 17 00:00:00 2001 From: Pekka Enberg Date: Wed, 29 Apr 2020 08:32:25 +0300 Subject: [PATCH 15/27] test: Add support for the "md" format Test also the md format in all_sstable_versions. Add pre-computed md-sstable files generated using Cassandra version 3.11.7 Signed-off-by: Benny Halevy --- sstables/version.hh | 4 ++-- .../md-1-big-CompressionInfo.db | Bin 0 -> 51 bytes .../md-1-big-Data.db | Bin 0 -> 82 bytes .../md-1-big-Digest.crc32 | 1 + .../md-1-big-Filter.db | Bin 0 -> 176 bytes .../md-1-big-Index.db | Bin 0 -> 8 bytes .../md-1-big-Statistics.db | Bin 0 -> 4653 bytes .../md-1-big-Summary.db | Bin 0 -> 56 bytes .../md-1-big-TOC.txt | 8 ++++++++ .../md-5-big-CompressionInfo.db | Bin 0 -> 51 bytes .../md-5-big-Data.db | Bin 0 -> 146 bytes .../md-5-big-Digest.crc32 | 1 + .../md-5-big-Filter.db | Bin 0 -> 176 bytes .../md-5-big-Index.db | Bin 0 -> 8 bytes .../md-5-big-Statistics.db | Bin 0 -> 4712 bytes .../md-5-big-Summary.db | Bin 0 -> 56 bytes .../md-5-big-TOC.txt | 8 ++++++++ .../md-3-big-CompressionInfo.db | Bin 0 -> 75 bytes .../md-3-big-Data.db | Bin 0 -> 144236 bytes .../md-3-big-Digest.crc32 | 1 + .../md-3-big-Filter.db | Bin 0 -> 176 bytes .../md-3-big-Index.db | Bin 0 -> 106 bytes .../md-3-big-Statistics.db | Bin 0 -> 4630 bytes .../md-3-big-Summary.db | Bin 0 -> 50 bytes .../md-3-big-TOC.txt | 8 ++++++++ .../md-1-big-CRC.db | Bin 0 -> 8 bytes .../md-1-big-Data.db | Bin 0 -> 28 bytes .../md-1-big-Digest.crc32 | 1 + .../md-1-big-Filter.db | Bin 0 -> 16 bytes .../md-1-big-Index.db | Bin 0 -> 8 bytes .../md-1-big-Statistics.db | Bin 0 -> 4604 bytes .../md-1-big-Summary.db | Bin 0 -> 56 bytes .../md-1-big-TOC.txt | 8 ++++++++ .../md-1-big-CRC.db | Bin 0 -> 8 bytes .../md-1-big-Data.db | Bin 0 -> 307 bytes .../md-1-big-Digest.crc32 | 1 + .../md-1-big-Filter.db | Bin 0 -> 24 bytes .../md-1-big-Index.db | Bin 0 -> 85 bytes .../md-1-big-Statistics.db | Bin 0 -> 4638 bytes .../md-1-big-Summary.db | Bin 0 -> 56 bytes .../md-1-big-TOC.txt | 8 ++++++++ .../md-1-big-CompressionInfo.db | Bin 0 -> 43 bytes .../md-1-big-Data.db | Bin 0 -> 83 bytes .../md-1-big-Digest.crc32 | 1 + .../md-1-big-Filter.db | Bin 0 -> 16 bytes .../md-1-big-Index.db | Bin 0 -> 8 bytes .../md-1-big-Statistics.db | Bin 0 -> 4731 bytes .../md-1-big-Summary.db | Bin 0 -> 56 bytes .../md-1-big-TOC.txt | 8 ++++++++ .../md-1-big-CRC.db | Bin 0 -> 8 bytes .../md-1-big-Data.db | Bin 0 -> 190 bytes .../md-1-big-Digest.crc32 | 1 + .../md-1-big-Filter.db | Bin 0 -> 16 bytes .../md-1-big-Index.db | Bin 0 -> 16 bytes .../md-1-big-Statistics.db | Bin 0 -> 4768 bytes .../md-1-big-Summary.db | Bin 0 -> 56 bytes .../md-1-big-TOC.txt | 8 ++++++++ .../md-1-big-CompressionInfo.db | Bin 0 -> 51 bytes .../md-1-big-Data.db | Bin 0 -> 1290 bytes .../md-1-big-Digest.crc32 | 1 + .../md-1-big-Filter.db | Bin 0 -> 176 bytes .../md-1-big-Index.db | Bin 0 -> 1164 bytes .../md-1-big-Statistics.db | Bin 0 -> 4922 bytes .../md-1-big-Summary.db | Bin 0 -> 72 bytes .../md-1-big-TOC.txt | 8 ++++++++ .../md-2-big-CRC.db | Bin 0 -> 12 bytes .../md-2-big-Data.db | Bin 0 -> 4471 bytes .../md-2-big-Digest.crc32 | 1 + .../md-2-big-Filter.db | Bin 0 -> 176 bytes .../md-2-big-Index.db | Bin 0 -> 25 bytes .../md-2-big-Statistics.db | Bin 0 -> 4865 bytes .../md-2-big-Summary.db | Bin 0 -> 68 bytes .../md-2-big-TOC.txt | 8 ++++++++ .../md-1-big-CompressionInfo.db | Bin 0 -> 43 bytes .../md-1-big-Data.db | Bin 0 -> 281 bytes .../md-1-big-Digest.crc32 | 1 + .../md-1-big-Filter.db | Bin 0 -> 16 bytes .../md-1-big-Index.db | Bin 0 -> 8 bytes .../md-1-big-Statistics.db | Bin 0 -> 4817 bytes .../md-1-big-Summary.db | Bin 0 -> 56 bytes .../md-1-big-TOC.txt | 8 ++++++++ 81 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 test/resource/sstables/broken_non_compound_pi_and_range_tombstone/ks/test-1c6ace40fad111e7b9cf000000000002/md-1-big-CompressionInfo.db create mode 100644 test/resource/sstables/broken_non_compound_pi_and_range_tombstone/ks/test-1c6ace40fad111e7b9cf000000000002/md-1-big-Data.db create mode 100644 test/resource/sstables/broken_non_compound_pi_and_range_tombstone/ks/test-1c6ace40fad111e7b9cf000000000002/md-1-big-Digest.crc32 create mode 100644 test/resource/sstables/broken_non_compound_pi_and_range_tombstone/ks/test-1c6ace40fad111e7b9cf000000000002/md-1-big-Filter.db create mode 100644 test/resource/sstables/broken_non_compound_pi_and_range_tombstone/ks/test-1c6ace40fad111e7b9cf000000000002/md-1-big-Index.db create mode 100644 test/resource/sstables/broken_non_compound_pi_and_range_tombstone/ks/test-1c6ace40fad111e7b9cf000000000002/md-1-big-Statistics.db create mode 100644 test/resource/sstables/broken_non_compound_pi_and_range_tombstone/ks/test-1c6ace40fad111e7b9cf000000000002/md-1-big-Summary.db create mode 100644 test/resource/sstables/broken_non_compound_pi_and_range_tombstone/ks/test-1c6ace40fad111e7b9cf000000000002/md-1-big-TOC.txt create mode 100644 test/resource/sstables/counter_test/ks/counter_test-1c6ace40fad111e7b9cf000000000002/md-5-big-CompressionInfo.db create mode 100644 test/resource/sstables/counter_test/ks/counter_test-1c6ace40fad111e7b9cf000000000002/md-5-big-Data.db create mode 100644 test/resource/sstables/counter_test/ks/counter_test-1c6ace40fad111e7b9cf000000000002/md-5-big-Digest.crc32 create mode 100644 test/resource/sstables/counter_test/ks/counter_test-1c6ace40fad111e7b9cf000000000002/md-5-big-Filter.db create mode 100644 test/resource/sstables/counter_test/ks/counter_test-1c6ace40fad111e7b9cf000000000002/md-5-big-Index.db create mode 100644 test/resource/sstables/counter_test/ks/counter_test-1c6ace40fad111e7b9cf000000000002/md-5-big-Statistics.db create mode 100644 test/resource/sstables/counter_test/ks/counter_test-1c6ace40fad111e7b9cf000000000002/md-5-big-Summary.db create mode 100644 test/resource/sstables/counter_test/ks/counter_test-1c6ace40fad111e7b9cf000000000002/md-5-big-TOC.txt create mode 100644 test/resource/sstables/large_partition/try1/data-1c6ace40fad111e7b9cf000000000002/md-3-big-CompressionInfo.db create mode 100644 test/resource/sstables/large_partition/try1/data-1c6ace40fad111e7b9cf000000000002/md-3-big-Data.db create mode 100644 test/resource/sstables/large_partition/try1/data-1c6ace40fad111e7b9cf000000000002/md-3-big-Digest.crc32 create mode 100644 test/resource/sstables/large_partition/try1/data-1c6ace40fad111e7b9cf000000000002/md-3-big-Filter.db create mode 100644 test/resource/sstables/large_partition/try1/data-1c6ace40fad111e7b9cf000000000002/md-3-big-Index.db create mode 100644 test/resource/sstables/large_partition/try1/data-1c6ace40fad111e7b9cf000000000002/md-3-big-Statistics.db create mode 100644 test/resource/sstables/large_partition/try1/data-1c6ace40fad111e7b9cf000000000002/md-3-big-Summary.db create mode 100644 test/resource/sstables/large_partition/try1/data-1c6ace40fad111e7b9cf000000000002/md-3-big-TOC.txt create mode 100644 test/resource/sstables/multi_schema_test/test/test_multi_schema-1c6ace40fad111e7b9cf000000000002/md-1-big-CRC.db create mode 100644 test/resource/sstables/multi_schema_test/test/test_multi_schema-1c6ace40fad111e7b9cf000000000002/md-1-big-Data.db create mode 100644 test/resource/sstables/multi_schema_test/test/test_multi_schema-1c6ace40fad111e7b9cf000000000002/md-1-big-Digest.crc32 create mode 100644 test/resource/sstables/multi_schema_test/test/test_multi_schema-1c6ace40fad111e7b9cf000000000002/md-1-big-Filter.db create mode 100644 test/resource/sstables/multi_schema_test/test/test_multi_schema-1c6ace40fad111e7b9cf000000000002/md-1-big-Index.db create mode 100644 test/resource/sstables/multi_schema_test/test/test_multi_schema-1c6ace40fad111e7b9cf000000000002/md-1-big-Statistics.db create mode 100644 test/resource/sstables/multi_schema_test/test/test_multi_schema-1c6ace40fad111e7b9cf000000000002/md-1-big-Summary.db create mode 100644 test/resource/sstables/multi_schema_test/test/test_multi_schema-1c6ace40fad111e7b9cf000000000002/md-1-big-TOC.txt create mode 100644 test/resource/sstables/partition_skipping/ks/test_skipping_partitions-1c6ace40fad111e7b9cf000000000002/md-1-big-CRC.db create mode 100644 test/resource/sstables/partition_skipping/ks/test_skipping_partitions-1c6ace40fad111e7b9cf000000000002/md-1-big-Data.db create mode 100644 test/resource/sstables/partition_skipping/ks/test_skipping_partitions-1c6ace40fad111e7b9cf000000000002/md-1-big-Digest.crc32 create mode 100644 test/resource/sstables/partition_skipping/ks/test_skipping_partitions-1c6ace40fad111e7b9cf000000000002/md-1-big-Filter.db create mode 100644 test/resource/sstables/partition_skipping/ks/test_skipping_partitions-1c6ace40fad111e7b9cf000000000002/md-1-big-Index.db create mode 100644 test/resource/sstables/partition_skipping/ks/test_skipping_partitions-1c6ace40fad111e7b9cf000000000002/md-1-big-Statistics.db create mode 100644 test/resource/sstables/partition_skipping/ks/test_skipping_partitions-1c6ace40fad111e7b9cf000000000002/md-1-big-Summary.db create mode 100644 test/resource/sstables/partition_skipping/ks/test_skipping_partitions-1c6ace40fad111e7b9cf000000000002/md-1-big-TOC.txt create mode 100644 test/resource/sstables/promoted_index_read/ks/promoted_index_read-1c6ace40fad111e7b9cf000000000002/md-1-big-CompressionInfo.db create mode 100644 test/resource/sstables/promoted_index_read/ks/promoted_index_read-1c6ace40fad111e7b9cf000000000002/md-1-big-Data.db create mode 100644 test/resource/sstables/promoted_index_read/ks/promoted_index_read-1c6ace40fad111e7b9cf000000000002/md-1-big-Digest.crc32 create mode 100644 test/resource/sstables/promoted_index_read/ks/promoted_index_read-1c6ace40fad111e7b9cf000000000002/md-1-big-Filter.db create mode 100644 test/resource/sstables/promoted_index_read/ks/promoted_index_read-1c6ace40fad111e7b9cf000000000002/md-1-big-Index.db create mode 100644 test/resource/sstables/promoted_index_read/ks/promoted_index_read-1c6ace40fad111e7b9cf000000000002/md-1-big-Statistics.db create mode 100644 test/resource/sstables/promoted_index_read/ks/promoted_index_read-1c6ace40fad111e7b9cf000000000002/md-1-big-Summary.db create mode 100644 test/resource/sstables/promoted_index_read/ks/promoted_index_read-1c6ace40fad111e7b9cf000000000002/md-1-big-TOC.txt create mode 100644 test/resource/sstables/sliced_mutation_reads/ks/sliced_mutation_reads_test-1c6ace40fad111e7b9cf000000000002/md-1-big-CRC.db create mode 100644 test/resource/sstables/sliced_mutation_reads/ks/sliced_mutation_reads_test-1c6ace40fad111e7b9cf000000000002/md-1-big-Data.db create mode 100644 test/resource/sstables/sliced_mutation_reads/ks/sliced_mutation_reads_test-1c6ace40fad111e7b9cf000000000002/md-1-big-Digest.crc32 create mode 100644 test/resource/sstables/sliced_mutation_reads/ks/sliced_mutation_reads_test-1c6ace40fad111e7b9cf000000000002/md-1-big-Filter.db create mode 100644 test/resource/sstables/sliced_mutation_reads/ks/sliced_mutation_reads_test-1c6ace40fad111e7b9cf000000000002/md-1-big-Index.db create mode 100644 test/resource/sstables/sliced_mutation_reads/ks/sliced_mutation_reads_test-1c6ace40fad111e7b9cf000000000002/md-1-big-Statistics.db create mode 100644 test/resource/sstables/sliced_mutation_reads/ks/sliced_mutation_reads_test-1c6ace40fad111e7b9cf000000000002/md-1-big-Summary.db create mode 100644 test/resource/sstables/sliced_mutation_reads/ks/sliced_mutation_reads_test-1c6ace40fad111e7b9cf000000000002/md-1-big-TOC.txt create mode 100644 test/resource/sstables/summary_test/test/summary_test-1c6ace40fad111e7b9cf000000000002/md-1-big-CompressionInfo.db create mode 100644 test/resource/sstables/summary_test/test/summary_test-1c6ace40fad111e7b9cf000000000002/md-1-big-Data.db create mode 100644 test/resource/sstables/summary_test/test/summary_test-1c6ace40fad111e7b9cf000000000002/md-1-big-Digest.crc32 create mode 100644 test/resource/sstables/summary_test/test/summary_test-1c6ace40fad111e7b9cf000000000002/md-1-big-Filter.db create mode 100644 test/resource/sstables/summary_test/test/summary_test-1c6ace40fad111e7b9cf000000000002/md-1-big-Index.db create mode 100644 test/resource/sstables/summary_test/test/summary_test-1c6ace40fad111e7b9cf000000000002/md-1-big-Statistics.db create mode 100644 test/resource/sstables/summary_test/test/summary_test-1c6ace40fad111e7b9cf000000000002/md-1-big-Summary.db create mode 100644 test/resource/sstables/summary_test/test/summary_test-1c6ace40fad111e7b9cf000000000002/md-1-big-TOC.txt create mode 100644 test/resource/sstables/wrong_counter_shard_order/scylla_bench/test_counters-1c6ace40fad111e7b9cf000000000002/md-2-big-CRC.db create mode 100644 test/resource/sstables/wrong_counter_shard_order/scylla_bench/test_counters-1c6ace40fad111e7b9cf000000000002/md-2-big-Data.db create mode 100644 test/resource/sstables/wrong_counter_shard_order/scylla_bench/test_counters-1c6ace40fad111e7b9cf000000000002/md-2-big-Digest.crc32 create mode 100644 test/resource/sstables/wrong_counter_shard_order/scylla_bench/test_counters-1c6ace40fad111e7b9cf000000000002/md-2-big-Filter.db create mode 100644 test/resource/sstables/wrong_counter_shard_order/scylla_bench/test_counters-1c6ace40fad111e7b9cf000000000002/md-2-big-Index.db create mode 100644 test/resource/sstables/wrong_counter_shard_order/scylla_bench/test_counters-1c6ace40fad111e7b9cf000000000002/md-2-big-Statistics.db create mode 100644 test/resource/sstables/wrong_counter_shard_order/scylla_bench/test_counters-1c6ace40fad111e7b9cf000000000002/md-2-big-Summary.db create mode 100644 test/resource/sstables/wrong_counter_shard_order/scylla_bench/test_counters-1c6ace40fad111e7b9cf000000000002/md-2-big-TOC.txt create mode 100644 test/resource/sstables/wrong_range_tombstone_order/ks/wrong_range_tombstone_order-1c6ace40fad111e7b9cf000000000002/md-1-big-CompressionInfo.db create mode 100644 test/resource/sstables/wrong_range_tombstone_order/ks/wrong_range_tombstone_order-1c6ace40fad111e7b9cf000000000002/md-1-big-Data.db create mode 100644 test/resource/sstables/wrong_range_tombstone_order/ks/wrong_range_tombstone_order-1c6ace40fad111e7b9cf000000000002/md-1-big-Digest.crc32 create mode 100644 test/resource/sstables/wrong_range_tombstone_order/ks/wrong_range_tombstone_order-1c6ace40fad111e7b9cf000000000002/md-1-big-Filter.db create mode 100644 test/resource/sstables/wrong_range_tombstone_order/ks/wrong_range_tombstone_order-1c6ace40fad111e7b9cf000000000002/md-1-big-Index.db create mode 100644 test/resource/sstables/wrong_range_tombstone_order/ks/wrong_range_tombstone_order-1c6ace40fad111e7b9cf000000000002/md-1-big-Statistics.db create mode 100644 test/resource/sstables/wrong_range_tombstone_order/ks/wrong_range_tombstone_order-1c6ace40fad111e7b9cf000000000002/md-1-big-Summary.db create mode 100644 test/resource/sstables/wrong_range_tombstone_order/ks/wrong_range_tombstone_order-1c6ace40fad111e7b9cf000000000002/md-1-big-TOC.txt diff --git a/sstables/version.hh b/sstables/version.hh index 3a176405a5..95e54196c0 100644 --- a/sstables/version.hh +++ b/sstables/version.hh @@ -30,11 +30,11 @@ namespace sstables { enum class sstable_version_types { ka, la, mc, md }; enum class sstable_format_types { big }; -inline std::array all_sstable_versions = { +inline std::array all_sstable_versions = { sstable_version_types::ka, sstable_version_types::la, sstable_version_types::mc, - // sstable_version_types::md, // FIXME: not yet + sstable_version_types::md, }; inline sstable_version_types from_string(const seastar::sstring& format) { diff --git a/test/resource/sstables/broken_non_compound_pi_and_range_tombstone/ks/test-1c6ace40fad111e7b9cf000000000002/md-1-big-CompressionInfo.db b/test/resource/sstables/broken_non_compound_pi_and_range_tombstone/ks/test-1c6ace40fad111e7b9cf000000000002/md-1-big-CompressionInfo.db new file mode 100644 index 0000000000000000000000000000000000000000..a411d5c1698ea447838d87b0490911bd0adb1566 GIT binary patch literal 51 kcmZSJ^@%cZ&d)6;M1%-@w4gAfUj`V8F;O#LiI2z{tc16j5b(%JiR;?_rVMbE$)TPgU!M_U)T2&TY!cUp?J z8jJss7SX6^;=2hkXe6yKphitp(AHKZMyjcXCRf^M^lDQT#c=MyE&RTd{^6hf<0hHR zeD*hYJ3BMGJ6S;xbV7ehQVXW$C2AJcT%u-45X=XKx69otWyPfw7CYra#p6-Rirk7^ zRP2@CU*j%ap4o%-YGKp@Z?6VKS8Z+fCs*Qq&}f+wMg zcktvishxnNbyz#7*D9ehg0q2hBxfV%XwD|iX3lY(M{yp_c|7M7&Qmzg;G7+@Hg1c! z_)5;}Iq%?noO1`7ar42Za&a%`vuJT7=S^sd`+sQ<#&rwPdMi3&CfdMVZ)6X~jcw>? zKYHk9wB-PL#8PxZJ9$&yYX2-Zc5zG=F zqw^P__u~3w{#y_i^R#u@e^~qxE#?0L?R7d_|GFDjA+En%1CIE+0UVhZ07vyY!9#ju z!KNZyk7oN{5RYpf0UmWP0X(`R7d-wN&UZ>T&bPJp9JHU=`3HDzCiZVhf)C=W9$LT~ z=Qo0PKc5C}x`^X*w+Y6R8HYWg?U^m>Yw(6~hrvHxm`p8BJPprRai{4fjY|m?us)>b zkAlzsfuyU5${2uJ(J$|S>y0Qcsi1K~lkX{Ylzq;>)I$Ul-4yPQ6}yjC%TtFZNN}?@V4yox8s@jk+-T)O*xx zU&M4#*Ia7IqCS$hdmQ!8byZuapPp==L(JZaDQKoHoIRV~|EwYBG`;^>V7rOl|Dw}( z?+5aGvGZs~C9&jXzHP+1q-z!jb$9oAIzRenwL9qh!!RkfE0eU3toyF=1hH}c!B#hM z^d~is==Ga!oA1->H{ZxUZXxZ5E7dOgK8QbTYmrGj@k*D4&R5buS&nKFe`ldTneGSD ztPfgEByQ{Yv*k15d3zd<(*7*-Pfw%kxh&USUrX9o6*RSV67T!%?wNYxvxmNIr2X|> zNNG_~aTkR5r kN&o#3ra&%L+@50PQ`u4GwN6>^xy#93zp233+ShOW3ahJ)hX4Qo literal 0 HcmV?d00001 diff --git a/test/resource/sstables/broken_non_compound_pi_and_range_tombstone/ks/test-1c6ace40fad111e7b9cf000000000002/md-1-big-Summary.db b/test/resource/sstables/broken_non_compound_pi_and_range_tombstone/ks/test-1c6ace40fad111e7b9cf000000000002/md-1-big-Summary.db new file mode 100644 index 0000000000000000000000000000000000000000..4547a9451a6c383b420c1e8b235d7444d1e8c901 GIT binary patch literal 56 kcmZQzU}#`qU|N)?j6exe}1IoY(MAay|5K-dF)~!Sw>a{CJhFDPX=x! u#{Y~A@^d{Ic$gv?J}@xyG|XdSeQ_?;_fg7BA;z_HTGc=;Xjp literal 0 HcmV?d00001 diff --git a/test/resource/sstables/counter_test/ks/counter_test-1c6ace40fad111e7b9cf000000000002/md-5-big-Index.db b/test/resource/sstables/counter_test/ks/counter_test-1c6ace40fad111e7b9cf000000000002/md-5-big-Index.db new file mode 100644 index 0000000000000000000000000000000000000000..f9376371f57ea150ba2e6002d905fabebd52132f GIT binary patch literal 8 LcmZQzVE_XF03-kf literal 0 HcmV?d00001 diff --git a/test/resource/sstables/counter_test/ks/counter_test-1c6ace40fad111e7b9cf000000000002/md-5-big-Statistics.db b/test/resource/sstables/counter_test/ks/counter_test-1c6ace40fad111e7b9cf000000000002/md-5-big-Statistics.db new file mode 100644 index 0000000000000000000000000000000000000000..5e60a2402c2d7036b0b5e1949946103978217a43 GIT binary patch literal 4712 zcmeI$eM}Q)90%~bYs;I^*@7=%B%8vVY&n$5n2FLMHPD4c20H6rGLO;-Mp|+$%21CHt>W z^5mXRf6w)K?s=ZOdvbyx=!HniUvC0!dHV=M`i`;yOk8*Jz=RUMFhw~n^j{ASzL5%Cyp$!&v%t~}DcfE1f zF`h7hP7I>&dkJlR2R-{4blNcb{%rJvhtaYFop%Vm>NL7^7J6eU+WQo`NkO-B>vzD0 z@nAYwO#dD2Sc^V{>r)JlKwK)<)=}hV=?k>ZaSrP1^|=1^7X~42_^cTm^IHcvt~>;e zpLBs|OeTYkmAD>Fwx1xL(lZ-8cQg$=-(Ln^IE3?^HG%VOX*mP+SB_l*KT&}F+mIH3 z_~u{C;N5FF!3VBA2JZd@$LC@pt)Y76;@O+itHhxXx zy0m&&AG)6B<7r%9AD>Tcc+L;k8{?_2r}5bCz*Xva+v-25XQcPS{gmV|pQrJeI(t8L za$Y8^=Q+k}`)PdMu{SPLKd`L$ed&+L`&$ z!a{ogiyft>=>0E--ZIkr zUvdRTKPKl(W5@Fwh;=?O@FuZ7W5{f$o|xD{=g06@%Rc)4h+UjKUO?){wRSapK%B7V z-F}rg@rCAJ>Gd1GHC?9HZ@N%?(oE{lQkrV%`ylnGwO1zb^v}o5biOkFD6%(^_#^9r znRGvpYx%j~Na9xikGj;JKTcg%(?fI FZUT6ArsMzs literal 0 HcmV?d00001 diff --git a/test/resource/sstables/counter_test/ks/counter_test-1c6ace40fad111e7b9cf000000000002/md-5-big-Summary.db b/test/resource/sstables/counter_test/ks/counter_test-1c6ace40fad111e7b9cf000000000002/md-5-big-Summary.db new file mode 100644 index 0000000000000000000000000000000000000000..2044a14c2e39a55524e98a74a92e2c043fc70007 GIT binary patch literal 56 fcmZQzU}#`qU|JP7?h7)&!6Cj$V= CDhgx( literal 0 HcmV?d00001 diff --git a/test/resource/sstables/large_partition/try1/data-1c6ace40fad111e7b9cf000000000002/md-3-big-Data.db b/test/resource/sstables/large_partition/try1/data-1c6ace40fad111e7b9cf000000000002/md-3-big-Data.db new file mode 100644 index 0000000000000000000000000000000000000000..6e2725d1ca5acbd326d55ea7bd21eaab82190019 GIT binary patch literal 144236 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%0A|hh|izn-{w4PZIyE>%<${M>s5L=fcHU8 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 TZchfp>Q;p7=)bc~|84YtJ7t}f literal 0 HcmV?d00001 diff --git a/test/resource/sstables/large_partition/try1/data-1c6ace40fad111e7b9cf000000000002/md-3-big-Digest.crc32 b/test/resource/sstables/large_partition/try1/data-1c6ace40fad111e7b9cf000000000002/md-3-big-Digest.crc32 new file mode 100644 index 0000000000..937abafd61 --- /dev/null +++ b/test/resource/sstables/large_partition/try1/data-1c6ace40fad111e7b9cf000000000002/md-3-big-Digest.crc32 @@ -0,0 +1 @@ +772386724 \ No newline at end of file diff --git a/test/resource/sstables/large_partition/try1/data-1c6ace40fad111e7b9cf000000000002/md-3-big-Filter.db b/test/resource/sstables/large_partition/try1/data-1c6ace40fad111e7b9cf000000000002/md-3-big-Filter.db new file mode 100644 index 0000000000000000000000000000000000000000..0b95b17664337f6e8e0bd2568e5587af96b6107a GIT binary patch literal 176 lcmZQzU|?lnU=RgjB7p({b&ODX7G!ly$Q%$`09}AcJ^&|n08#(| literal 0 HcmV?d00001 diff --git a/test/resource/sstables/large_partition/try1/data-1c6ace40fad111e7b9cf000000000002/md-3-big-Index.db b/test/resource/sstables/large_partition/try1/data-1c6ace40fad111e7b9cf000000000002/md-3-big-Index.db new file mode 100644 index 0000000000000000000000000000000000000000..3490db63efe60713eee773b68981a6cfab42b49f GIT binary patch literal 106 zcmZQzDl=qA5vc$F|9=Am1hB9$FdHN~vM{h1nv_Hch%i7{kw9U?Jg0*UQhY!WAk!I0 ln&$@}Vo+oN37F@H07=WLsKX432aT$shA{{L8A?EG3;;ib5%&N9 literal 0 HcmV?d00001 diff --git a/test/resource/sstables/large_partition/try1/data-1c6ace40fad111e7b9cf000000000002/md-3-big-Statistics.db b/test/resource/sstables/large_partition/try1/data-1c6ace40fad111e7b9cf000000000002/md-3-big-Statistics.db new file mode 100644 index 0000000000000000000000000000000000000000..b1d3b4795a938b26d5fb1753127866b8af47e11e GIT binary patch literal 4630 zcmeI$drVVT90%}w?rnJq)fPm+$b5i0wiL30NTOD;2rL?rhoa8R3p4`CBW<17X*7z7 zVTLiMV|Y%OZce5vPLgg%7w*deW7f%%H>5CSzlCa z(=S?YDO+zbWXTp=iLIof+-xyc+wY{G4Es_!IoRcL4TeNAwsX(7TE+EBbr<)C(8AmO z;j}0>0mXa6-h*oIj%Vw~*`IR&=OE6(oHd-?&q7NWg|Z*Pc>?DtoZ~ou!g&_w49-hA zui?Cj^Ipy^oO{uX8xJ;vi`zI~M2q7%Z$nGm^`-q7SLLDA2DIO7v_JQ}0k1F~)P)Xq zqDSvQ>kgyCSD>SM&=cd)QxBo_`RL>W=+q17tg-0Tk!b4@bd8K|9Xzd>Bg!SyU&LreL;c-jviSE+FRt8QM0>#2XN1^Yd20te){z=4Bi@TkF1 zu%-y-qtOK!e}g}rhxfNU$^r4xhdS`q z#m(S-FJ^+h{L1UiM_RN^R3%q6WG4M~95~#&#t?+&oztj9m<5E-=%nzx3 zRUnP4ssiUwt3T_7=k=>7siJZJHpesSKvUYk)T5$1U_FK8>$+)tjFj0)9hw{q^Ld=+ z#rHHm{`fcdsV607oTi@k`?6Wo@!iEIsZ&;OK1gl)GhrEZ_Mx&Q>cZG_bE&_08PZQ( zd!;Fz`gqR1$<#mBSAR|Y>}*dOF`FJz&`w=Ae?DFRtSPINu7Bp*qoM0xG&^peC+CZO zC+66RB^z_>CRWAV&}C8&3~Zv~qkdkumwtcve;C?7kF*b{KV?5d9JKgIr-eB9^V&!B z`89uN@6+ek-ppvxk@jQdno9b85P8hlp(pX^tNl7UUNQfqXV#GT$9c|Jnhzuy9&~C* z+}L}!V*~NR{mm!n{j7ARB+>a?o^5KZBkijT+PeCP5B_!QLL>3TqYcgUejS(MdXh)t zOt<~|>r~>*(x zoQteyU(Pj>b1DB>43(koQO_t%S)ghVlxXR)>#9h5zXLPhg*ssOkQC#FbMz&0l z5@dIBqwJ*Ey}ue0J?Uv;w{oSS;yvYc_y=FDt!@y6>2LnOr9fXMTdc+MI(=U5f|$K;%eU6!DDWgTx>j z6%}t2Vh|*Nc%#IK8X=feG#afEK~O{8Dl7j#(Zp9{Cz;HA`%l@K*_rN8LWq?7 zOu<4yg}gw>oRCw6EFna3fQ-sCu2At=ygtRC((@)0pOI|jRmmx4)y!;TdbUv=&l}CD z=G4p#gE2BM_eJa(@3q2*-EB5okB2{{E!ErH1s4g~p6oY)iM9RaFbSPNN$jwAQ6^S; zODD$8j9nPJF}C*=COL#qS1|TwJfPR&xCS!up^QTrk7YcGaUA1$j8`yT$9NCp^NgFY zDf2w&cqVRUT#e27G2VaxQEcCZ z*a3~$gT`PFJA$p!VMiRoj=qW=?}NR}AKNq=JDBdd6~P7Ez#Xa?WIz@{-qD^qPXm40kYGFQe>Az8?tMU0l8m~7qUDVuSbRE zHHv#z`XUc#2|ym$lz=?60YC51F8sXJg*B-C@6?7tbHgvA?^kZG{1J>x0&>v$kW?;lg>h+) z>lA3&;wCg+r_9tG7EY1&kV{jcSAFqlgD$vH8Vh|Y zasLqLdqsIWpgS)%#sJe19!ZtZ`f1bP{->q!m*M`Ww%u~L|2c!Ds49 z6%XUiD?xi7K0gb-5ByI?R;fTd=vJE&KCj^RSZzLtkDhA{f#-p6_1ii*h(|WPs#*;^ z<6wCi?9U?W#BjKtGZHi^525&-&%ZO~%>1*o1cCp+j{Gega;$ErwAl69oui76f;KeK6U|)mK yR=5r=){D!&&C`48irWbp`TJjAl2qxu(UiikRB1EJ>alayWC{QGP-^)5=HCI88+n5O literal 0 HcmV?d00001 diff --git a/test/resource/sstables/multi_schema_test/test/test_multi_schema-1c6ace40fad111e7b9cf000000000002/md-1-big-Summary.db b/test/resource/sstables/multi_schema_test/test/test_multi_schema-1c6ace40fad111e7b9cf000000000002/md-1-big-Summary.db new file mode 100644 index 0000000000000000000000000000000000000000..2044a14c2e39a55524e98a74a92e2c043fc70007 GIT binary patch literal 56 fcmZQzU}#`qU|yA8rH6h`6e#Ic=}E)XRJ>=UJ8fRyZjSD>aqG*Gic24Dm7QVN=k;N;h>a!+-n zk2Dcw*BE112OSIF@9r_H4_3@h@Nk$?fYDV6J{;A m`-}#xnXTZy`OaX=Y$RWPPq1Om1rH%i;G8)X+?9_TI8_Vi9YN9n literal 0 HcmV?d00001 diff --git a/test/resource/sstables/partition_skipping/ks/test_skipping_partitions-1c6ace40fad111e7b9cf000000000002/md-1-big-Digest.crc32 b/test/resource/sstables/partition_skipping/ks/test_skipping_partitions-1c6ace40fad111e7b9cf000000000002/md-1-big-Digest.crc32 new file mode 100644 index 0000000000..2af6c722e1 --- /dev/null +++ b/test/resource/sstables/partition_skipping/ks/test_skipping_partitions-1c6ace40fad111e7b9cf000000000002/md-1-big-Digest.crc32 @@ -0,0 +1 @@ +712389452 \ No newline at end of file diff --git a/test/resource/sstables/partition_skipping/ks/test_skipping_partitions-1c6ace40fad111e7b9cf000000000002/md-1-big-Filter.db b/test/resource/sstables/partition_skipping/ks/test_skipping_partitions-1c6ace40fad111e7b9cf000000000002/md-1-big-Filter.db new file mode 100644 index 0000000000000000000000000000000000000000..dab3740e3031a89b9d20880d29595e96e1df1db4 GIT binary patch literal 24 fcmZQzU|?lnU|?!V(m2r2reXG4>5|upCJhDvJ!1yd literal 0 HcmV?d00001 diff --git a/test/resource/sstables/partition_skipping/ks/test_skipping_partitions-1c6ace40fad111e7b9cf000000000002/md-1-big-Index.db b/test/resource/sstables/partition_skipping/ks/test_skipping_partitions-1c6ace40fad111e7b9cf000000000002/md-1-big-Index.db new file mode 100644 index 0000000000000000000000000000000000000000..88324d9113c78c9e91ddddcc7e11405edd64285f GIT binary patch literal 85 zcmZQzVPIfj1!54*C=a1I>>xBlEQDsNgwQMvGawB1hV2jrTf;R7gR|i)gu&b>!T;Yec}f3oC`P_k_+X{}!4 zJR4tg?U}l#zLYrDZ&G<8${CeP@f@DZfCp>O<-x;qE<9+L=x1u$@YWi}evJJY>lgJHUnAe*?C05B8u1*rA=+U&de$-;FJ% zW5-ovPi@6c9*Dg(1lu+TyG+8aW7cnn8OL2=$Rz9ucKTfGYP>#)>jsMRsdU}H|Cm3A zEu>#U`6?A&|EjAOP+Wbc99h#-kL;i7M%MOOk^A)pA?vg7dNi2-MDgI}LC8byhawN{ zN z&nbd&zb5BPXsvnrbLf6yE$DsefR4G6eHc1^>6#j7^X)P7pi_3|#zJRC{x%u9@bAEG=<>GuMCgO5yM{qutStQj z`sJ^k(}Bszz>H?-%xTl${ZHzXkHY((xVP!y{m)yS_m6?|dB>qi#lV7{IDZ6IMO-#o zpnH2)!}(Fas@M+SAAV6m-BUomf8|fbzX0oI?`@ZX1D2FOhW*#yGCYL+H(X6>G=ltr zQdt3fAB5~rXc0j?>}$3>92M@h$nPCX!#a+W>v!>cs&bU z@v(3{=cSnIDnNc|MpK6axaRJ?);i$hwfh?2^*T>QcgB5)6XW^|Z>IuVmcMwj?t?j- zw@shHI>t1WrwwoYf8BG@ZqPj!wQtWwN8(-W+jF7e;Cy>7G#g<5wz#Z+Tc2pq7q3#&e^Bk&Xelpd vXxgrIzfE(tX`(iAT-3*ZpUDt&CE1oOtq?7FcGK9o-xVm|dk87~@9F;l;jWNO literal 0 HcmV?d00001 diff --git a/test/resource/sstables/partition_skipping/ks/test_skipping_partitions-1c6ace40fad111e7b9cf000000000002/md-1-big-Summary.db b/test/resource/sstables/partition_skipping/ks/test_skipping_partitions-1c6ace40fad111e7b9cf000000000002/md-1-big-Summary.db new file mode 100644 index 0000000000000000000000000000000000000000..ab6bd5d065001dab321da058d45d4d71968b5047 GIT binary patch literal 56 mcmZQzU}#`qU|i_@$-@wUG2xKxbGB9#71TqUTFsN_=xeNtdybnqo4LGBS#v^TEjA>ky z{V}pIwoH-4w+S(5B!j7d1{2+24;@GrQGBg5n~((_C5cYs%@UkK~iXeLmlCL^Nal?VpCru9d65_s&a&$0vy&=yr z9@>cx_o63#f;JpMPkjd+(}R9}0s4)@Xk!*S`5=1b1$5dJbWSwd^)|XnLf3Qix6_Jo zZ!DO_K0;@0L?6WY$-LJgE@mk6aP+YFBU+X9Gvuq)IRDkxu0UM#eKk1fVFNfM!w1$5 z+reSOkzidu&PToV4~R##P6bczj{(o>EpLmd{|2KQ4$mZ6)*C#!57)RD>Y zFrTOCp6;jd*G_(MmwM)+)F$dVH#RS$UeN70O}#W{=OJqAt;EgL>4yuGsPp2_zC~U3 zEMkzl`n!e{>XR9Dv#77uR(?+X3Sf^ zbgx54;^yAp+dd#(ccAeUZO<0((j+>ci_)$2H6*_>x21D{_)y>N3-!e9$BsAB_IfTS z^dyhRnPJbB7b}Tv+aEvQJ#N7@wvW%?@imxoTqNiCcLQrK?2uQjxsGS8)?CmZfi+iP%|#}}|7*>~>|NQ5j|hwA2pPw zoA2w+7I)wCKl}6L$}HChbNZFw=tq=s`K=z|=wGz*9v=Df;?I&_$Vx-jl*yJ8sFnS- zO)oMXI}?BFyR-jKi!kryA8K-qg_6_dklr`iirl7!8$T?uixW?sc&5Nu*~GK|12!$6 A`v3p{ literal 0 HcmV?d00001 diff --git a/test/resource/sstables/promoted_index_read/ks/promoted_index_read-1c6ace40fad111e7b9cf000000000002/md-1-big-Summary.db b/test/resource/sstables/promoted_index_read/ks/promoted_index_read-1c6ace40fad111e7b9cf000000000002/md-1-big-Summary.db new file mode 100644 index 0000000000000000000000000000000000000000..2044a14c2e39a55524e98a74a92e2c043fc70007 GIT binary patch literal 56 fcmZQzU}#`qU|plm@qkSRlZ+r)1}1KKCWZ$$H~$2(nIYD42tD+c ZYG&f)1M6qu;eWVufeIs#P!?ri1OQH?AejIF literal 0 HcmV?d00001 diff --git a/test/resource/sstables/sliced_mutation_reads/ks/sliced_mutation_reads_test-1c6ace40fad111e7b9cf000000000002/md-1-big-Digest.crc32 b/test/resource/sstables/sliced_mutation_reads/ks/sliced_mutation_reads_test-1c6ace40fad111e7b9cf000000000002/md-1-big-Digest.crc32 new file mode 100644 index 0000000000..5a5cc7fba4 --- /dev/null +++ b/test/resource/sstables/sliced_mutation_reads/ks/sliced_mutation_reads_test-1c6ace40fad111e7b9cf000000000002/md-1-big-Digest.crc32 @@ -0,0 +1 @@ +3809570971 \ No newline at end of file diff --git a/test/resource/sstables/sliced_mutation_reads/ks/sliced_mutation_reads_test-1c6ace40fad111e7b9cf000000000002/md-1-big-Filter.db b/test/resource/sstables/sliced_mutation_reads/ks/sliced_mutation_reads_test-1c6ace40fad111e7b9cf000000000002/md-1-big-Filter.db new file mode 100644 index 0000000000000000000000000000000000000000..ee71a0f649d57455d42fae85417fc46c9704787a GIT binary patch literal 16 XcmZQzU|?lnU|?iWQgArYz#sqs2lxTu literal 0 HcmV?d00001 diff --git a/test/resource/sstables/sliced_mutation_reads/ks/sliced_mutation_reads_test-1c6ace40fad111e7b9cf000000000002/md-1-big-Index.db b/test/resource/sstables/sliced_mutation_reads/ks/sliced_mutation_reads_test-1c6ace40fad111e7b9cf000000000002/md-1-big-Index.db new file mode 100644 index 0000000000000000000000000000000000000000..b5eead25ee8c2b16599b2625c7064de8470f5e38 GIT binary patch literal 16 TcmZQzVPIfj1Y!`);K=|00R;e4 literal 0 HcmV?d00001 diff --git a/test/resource/sstables/sliced_mutation_reads/ks/sliced_mutation_reads_test-1c6ace40fad111e7b9cf000000000002/md-1-big-Statistics.db b/test/resource/sstables/sliced_mutation_reads/ks/sliced_mutation_reads_test-1c6ace40fad111e7b9cf000000000002/md-1-big-Statistics.db new file mode 100644 index 0000000000000000000000000000000000000000..8e4df3c409ceae8485c0748ee3fb8921d6a1ea35 GIT binary patch literal 4768 zcmeI$e@q)?7zglsuPw9;DlO~>7>L_I1~X++5vLmn>jE{JA&iYVjXR(jEb=35LByHq z#F!98G$LEZHk}DsfQ(Gk0g~;HDfnZ$jhNM`Bbao|jA+qplL?{Tg&sWLSIw6ApXr-i z?)}{J^zOa)-n)Byf*@#xxso8Sn7nM07g1g+&EWwB}1rcZIv6y2|0qYHau^_gM7v@{c3^e*bJl3=@yMK5ZC0-mL7j zOl5+~@G|uZd z=LM|x+ZHaqlZ)@>;;o!}IeXBIyB_R*F7D=h5-l#}ycezE_OEHjxV8wbv!X-RqeHp# z)lXwQY#1HxL*MZV+Wanh$rI?<5%gWD=(`W2Eyd`JgXo+=bpB%Wju^D-adeY}?%>An zunptBcrc6q4PCq$eGtbd^Id?rSg4N6xy|C2Xif1qkgwI^_}894199Ew&ESy9PO!ev z4>rs?z>8+1z{WBhk0#sC5RdL(0$w&63y$*?fLETy>zy)%*W2213i8*FT?RjrjqTeO z>xKBPU(DbaH}!z`-+Tbv_bK+zr9S9S78ZG(=CiEa*T8#L90h-MdJVOhbON5Q;ycD~ zXVm_S>C~l( zA3sc8_h-a7b@OMPxzt^S`&UqZ-PZU9^^K24HWIT{5heZ9r5iTT`=52@pP=_Y^S^DR z_rK`yj($RZFOGF*HV|vv%=;FxHsP$^EaK(>ot@7#ZprZeILXe%^I+fc>EXRW_rC6e$TZxk@!7D zzC=17NViT58A&|Lb7kOJ;z!$ix@mj1`_`t@@my74>u4eQjU|1AedUT!A`1ga1AU6S=&9n{%oA(UD^)Rd^=I{=d$- z)YIOgRsw5v&ZTaqV@}!Dss;kWf7np_`qZRa6UyWo%f4_$|I+*>V<~Q6t&dlOlDp_@alw+{Ldd~N-UL<(^W1# rW3gAct!bN|t8uW~PZdP5?%vx^7wggrZqYEMDsQQ)eE$`s%}8LgB(Up%fG>+5>r_P5BRc1d3k!0eS|Cy@ODL zxbHm3XAPe{u?T5}l(im0YDcQR9BBcP{c%4uL+TGthpM39r3|!C+*3b88=>f*3!ocB zn|h9rTL>w?fYgl?7q5ioiTTw|=t3wMI0@Y(TIqHg+GzMbMye5WM-jTjRfVu|T0*Y_ zqF|^(&yn)iYfDP6hobL(rEwLFt6C_pmd34FSGk$hXU&NlJM<^C9EzIyAWp&9_ilxz$>JZ4 zOX&?_o~kiQZ-U~g0nGd5FiNu%dNyFYesc6!yrBPhNqHB$Dmz@C){jfy*TY7651=fc z>u3(Zg#W%l65|@Xi|f|Bu|o7qIjOG`Y2sg)cS~c!Wz>6V%;y|^mw3T$yhp|R;CC9? zvk-der0xV_H9x7Zm#RspxtLlgI5D4u^GI0Rt@L8?hKESt!I`LhCkgeEux7;ZcxTyH z`y9>M#&&EcU=kE|kEf_fwAusEWYM-vfYMN~av#L?8QUu_{5hGTu{Wvtsqw^Aw>g0|uC7{(NJl6HM zbr8@j)r*f&y;5dxTanUSsR^q#V)9cd*rgMywJ%cp`$+ddQF;dTY+`KBr_dNEc$<6D rOT?|?FGzE9#(uIDN{ZXUDUW~HRc)E5=iS7~9mS*k^C`98KUeoZ(L6cM literal 0 HcmV?d00001 diff --git a/test/resource/sstables/summary_test/test/summary_test-1c6ace40fad111e7b9cf000000000002/md-1-big-Digest.crc32 b/test/resource/sstables/summary_test/test/summary_test-1c6ace40fad111e7b9cf000000000002/md-1-big-Digest.crc32 new file mode 100644 index 0000000000..c1ae0dfb60 --- /dev/null +++ b/test/resource/sstables/summary_test/test/summary_test-1c6ace40fad111e7b9cf000000000002/md-1-big-Digest.crc32 @@ -0,0 +1 @@ +1423862028 \ No newline at end of file diff --git a/test/resource/sstables/summary_test/test/summary_test-1c6ace40fad111e7b9cf000000000002/md-1-big-Filter.db b/test/resource/sstables/summary_test/test/summary_test-1c6ace40fad111e7b9cf000000000002/md-1-big-Filter.db new file mode 100644 index 0000000000000000000000000000000000000000..b9e7964acbf8819e17fe22e7192e2926c39817db GIT binary patch literal 176 zcmV;h08jq_000F5000#l=6Zp$j>~|R*h-ACF%moh4`XhYBP;K$W1NG4tAm0{2?AmO zgqsK<0H!^25eix98^L0kj*!{JEF>wCfNUrTB(V@x%|;*t3lxh6$t&6vG$1BPEyyJ5 zSi?s^LA^4>WLAKqU<4D$qS;Ug2qZT|K8GD9u|fg}Kmsfr0RqsIQiioSEP-xB2nmG% eB6^A{8X^H}f+9l0>}+WH=ZPi(h%`flLf|k{+dcXK literal 0 HcmV?d00001 diff --git a/test/resource/sstables/summary_test/test/summary_test-1c6ace40fad111e7b9cf000000000002/md-1-big-Index.db b/test/resource/sstables/summary_test/test/summary_test-1c6ace40fad111e7b9cf000000000002/md-1-big-Index.db new file mode 100644 index 0000000000000000000000000000000000000000..8952f475be2f621cf06b864bf43b881c0a50ec63 GIT binary patch literal 1164 zcmXZcJ6Mik9LMn&9Z;ciD2ECaDu;5YP@!@thYA%chhB#Y6)K0~dG7l;KhK3l3kD1r zuwcM|0SgNT3|Lq&V8Fub`rgC;`hAA`x`*HJ3q^%Op$U=upNCS(g$3lnYI0kc)CR~N zZ4eyQZh=DW8PK(lV5;^9MC-|5vYrRp^h$6-Zvi=aA1Khzg6;Zs(5OEKb^1Gyum1!i zMFQ9;vO%vX14~6CC>PyeKzLw-xB|wA2cS&61~uXvNRY80O=f^jSqxUoI?ydUK&v!B zoV*Bz$h#m}J_jZ86PP3af?Oj72qPa1H>$t_qZJ%C`oU1+9GGp~0BejVAj)_Tl8j$q zzL^N-nmJ&TSq@svCQxPefIRawIAUG}$IORdhxrCnn%}`_D-MjeGC`MB0(M#TV3E}c zsx1c`wl0Ci);&;fy#!mVFJQeL4La;JFv%_ieRd64Vz+^@wg9pAd9c~O4Z^l6JkwT% z&$LzH6^<%A+fjuVJF4&*jw(FHQH8H^RN*I`oBUksJOxvn4`7+|8)UgjV6B@AMz|GV zl-msUyS?C)dj>SS*Fcf`2uyU}f^qH-aKMWPiCz}i=aqszUIS?Ny1;bL1?k>pFweUW zc6+ZtllK*r`Z1v2PX{S}5y0bb;{vEKwe*vcXpFu+qS(2#8tqjzh Z#0P3mwgzfY)&*hS3=7nx>G&@PTv7ERP6gpeeX9=VI5@ zWkCjxDJr667zslrb0XHta1(}#fE5kkri5ulg2+oiKvH%Jz47~QmhA6koaFYL-~CR@ zdG;R4t>MU!GL$@x5b9Cffy+nyQ(Ucb!S=sAgKfm|Id7 znf=JR`Jq>SomI;(eqVhPNbaB3(H%FPemcq+G(zJKekQG`P%9p&2vik`+ciw(eSx7i zs`u$;!ikHFej>H-fE^iH=wFAENeul9-@1XxRXM|Gso(aZr$I_ zZP3f|RKDcSXc@otu_U|3y}iair!J;l&lFsCE~t=}6t3;+4WHDC25!Y_E^esT`%S2P z%5^lU-#idm{6KKGnjx(!(27U{?*8Wd>q*zEI^T{#kF4?e#>wc>Ci|JcOI|y#1r3Zl zk%G~*&ZHF|Q2Dm>x@J8R*WGs;)~q~yNjg0FGKmhC@e6WAw<{z1x~yi}U-W?Add#ac z-`sN21@hY4D(WkGQ+3PZ^3k=?Ba=6;_^Dv*x{dmrtFFmDuT*I?T9Zch z?2|YC>5@GnY|ghM9WO12G!60#E=e1IT&k{h8fx_>^LdC|=475{n7AhMj}lo2D6wmX zy(wS|mR!iO6~`iu#T?skEaBLWV+V`bb~{_FvgEFooXr-p1xxP3@e+=ga~#g`29CFK zyqn{L9G~U54Vz;7Ww}i)<-{{MuED0AI96lxIiJrj#BpXlwjcmoxB}aX(_hhZ92Z}~ zw&};7yBAwpgzfkqwo41PyC3!|T5MSycF-~G&}!`H`Pf?)V5hIe&Qf5PaMte?Igaf0V@kJrP0hW2XTxE$KgkbD|?*_O|bL(Bj0-v}L}O$~%jSX{Ld`r`@P zF6f-!%fg^f#UAm5ZaSpNg&sZEvIdxX)i(YDbi(S@aQ{WdDY*;NTFj{3e{32gIW zP9N;Q~0c=A~Q8>~}C5W6f5j*@7kiubjEqW;?S5OFnz%LIXmx^KQ92 zd*(vJ!<#*Gq4AhKbHQf_k2ibf`rn+n$j?U8X?i~hJog$x4`lPq`P)x|_mGJBV)shO z9r^8G_Y)@KME2glBLr?c*`@{fgHiT|M_>Wcv-4KnVzipNEVTn5wK+4r#sC5 G-uw;9x9^VHu3%hEVU3jzkqUr zNg-+c0AjFABcu_*LXgzLc{p?3E?Y0`HFMm%;lX>qVqgyFIZx9xpm#!+g$?tpj(zwR2rQT71~}e#7tLDxaIPo1f0M7hb%- z?0zm?lD9Y|J)73blmHo)2H-^1h(jFfs!MQzCvr-7oAkDv65b%aVZF@CkYRBEg^ZMS zh|hGSOsVOqf|LpA6FDh@{IY4ijLMKvb5Q&zCEU}HA}Y8bDsoapo%Fh#6n=<$Hm#RY z89I<3E{@`GS#cr0)$vm!K57bnnxr=s{Pc*Qp7jz&Wyr8Nyk6EI>gY)Ei3DFx3Lg*& z0|hBV(udYd7?mNz;_~aI|EeRUgbSjiAf-wqR28I*NFRaqGAcuc#Q|L}{U;46d`wM` V6{K`Y@5)ILtEgwwdKs0W^9N=Nseb?f literal 0 HcmV?d00001 diff --git a/test/resource/sstables/wrong_counter_shard_order/scylla_bench/test_counters-1c6ace40fad111e7b9cf000000000002/md-2-big-Digest.crc32 b/test/resource/sstables/wrong_counter_shard_order/scylla_bench/test_counters-1c6ace40fad111e7b9cf000000000002/md-2-big-Digest.crc32 new file mode 100644 index 0000000000..bbacfe06a5 --- /dev/null +++ b/test/resource/sstables/wrong_counter_shard_order/scylla_bench/test_counters-1c6ace40fad111e7b9cf000000000002/md-2-big-Digest.crc32 @@ -0,0 +1 @@ +3116582049 \ No newline at end of file diff --git a/test/resource/sstables/wrong_counter_shard_order/scylla_bench/test_counters-1c6ace40fad111e7b9cf000000000002/md-2-big-Filter.db b/test/resource/sstables/wrong_counter_shard_order/scylla_bench/test_counters-1c6ace40fad111e7b9cf000000000002/md-2-big-Filter.db new file mode 100644 index 0000000000000000000000000000000000000000..7ab13fc1d6f23fc07a5ba0e4b5b08fa1d19eb1c8 GIT binary patch literal 176 tcmZQzU|?lnU=RgjEPw@z7*-*|3OO+K2_S@k`Wc}-7lbHG2r8lgqyZ$S0Hpu` literal 0 HcmV?d00001 diff --git a/test/resource/sstables/wrong_counter_shard_order/scylla_bench/test_counters-1c6ace40fad111e7b9cf000000000002/md-2-big-Index.db b/test/resource/sstables/wrong_counter_shard_order/scylla_bench/test_counters-1c6ace40fad111e7b9cf000000000002/md-2-big-Index.db new file mode 100644 index 0000000000000000000000000000000000000000..94cab27584dd82655666b19ef6c320ba5a584784 GIT binary patch literal 25 RcmZSJU_byEo3Udj0{{d^0aE|~ literal 0 HcmV?d00001 diff --git a/test/resource/sstables/wrong_counter_shard_order/scylla_bench/test_counters-1c6ace40fad111e7b9cf000000000002/md-2-big-Statistics.db b/test/resource/sstables/wrong_counter_shard_order/scylla_bench/test_counters-1c6ace40fad111e7b9cf000000000002/md-2-big-Statistics.db new file mode 100644 index 0000000000000000000000000000000000000000..5dee8abc20be5868ce28b312f7a91d9bb2eba65b GIT binary patch literal 4865 zcmeI$Z%h+s90%~bYYPQwM++)oBu+sYTk4;RXreNu1~4unh*mcF#l>Z-TNF_Y^~!qk`#zd%5?}bDPx7SC z=YG#~eeQXl>peY=0DM|j|<-9`9f}9<4R&ku+OYU8lRBRH4S2~Q7)NtMafg@DRnvRQbtwf?X2Um8|22(e!qV}7tae_Z3p$&`w!{lV|8*4 zq`()^#5&Lcn&gf^QaY_1)F_4EXBgvf##+XajH4Lq85T6dZuDM(T4!hp~*5>=c5d(Jc zh=CZez6jT&!SXxAW1B~TSV>|P}6RzTXr}p7|n`*Y;`rQZg7M@dqaV`xd`8x%;LYQYfPc6+g<6<&27X_K&-FjkxGJFn z)`zNjRRoQzDkV_2R?v8Oled>T!m{82^@zk4xSw=ZV>^vUt8!YYW73mh zJ&)Eu{({EG9Nl@3`t9l2C#ff0Up||9a(l^f>RGEd?WeZfn6jKY?|>tX+Lrvy`_${6 z=z6GYE;VFPAI;x0p8ChSsvXq5Uw14Z=2LWq&D6HcOnU$G4Y_CN{m=V9)6@H3uzS1C zlJ-LPvALDRDi817MXXM`YRsYT>)S}@NAsw5H+_DDzZ26lkJQ)Jov1ub9J%;Vt3(|2 zam`=!`t`pV?$PTvT+2RXB=tv%)n&@_AaFSTNJfi^#1p^kG1B=;`X?)=n#A8L@Fmmz zK$`iFRy~Plblz=QPrPVv<1yNw4}G)J=z4bMS?X&^{i?#Iwr=A6H*cM*Cq94pt47*i z@5R)P^r1L!+;-*ZeBzun5Bs+cnR5rr_#6D<9!wx@0>w9V&!zM;mg#RCuTk54ko4T)0xKgy#@0B%#@eEeJM0K zM9Ezut~D)kIg6KmQfBA>cTG+s*rrbXuk&WQ%AFp&l<8Vq?r;XaK(I|4P9yVh8ciQg JqqJ9V^bA@C;FkaZ literal 0 HcmV?d00001 diff --git a/test/resource/sstables/wrong_counter_shard_order/scylla_bench/test_counters-1c6ace40fad111e7b9cf000000000002/md-2-big-Summary.db b/test/resource/sstables/wrong_counter_shard_order/scylla_bench/test_counters-1c6ace40fad111e7b9cf000000000002/md-2-big-Summary.db new file mode 100644 index 0000000000000000000000000000000000000000..36f0accf7a9f5ce9d33ea0b6ace3ca1f0ed2f7bc GIT binary patch literal 68 icmZQzU}#`qU| literal 0 HcmV?d00001 diff --git a/test/resource/sstables/wrong_counter_shard_order/scylla_bench/test_counters-1c6ace40fad111e7b9cf000000000002/md-2-big-TOC.txt b/test/resource/sstables/wrong_counter_shard_order/scylla_bench/test_counters-1c6ace40fad111e7b9cf000000000002/md-2-big-TOC.txt new file mode 100644 index 0000000000..06b66f6fbc --- /dev/null +++ b/test/resource/sstables/wrong_counter_shard_order/scylla_bench/test_counters-1c6ace40fad111e7b9cf000000000002/md-2-big-TOC.txt @@ -0,0 +1,8 @@ +Digest.crc32 +Filter.db +Index.db +Statistics.db +Summary.db +Data.db +TOC.txt +CRC.db diff --git a/test/resource/sstables/wrong_range_tombstone_order/ks/wrong_range_tombstone_order-1c6ace40fad111e7b9cf000000000002/md-1-big-CompressionInfo.db b/test/resource/sstables/wrong_range_tombstone_order/ks/wrong_range_tombstone_order-1c6ace40fad111e7b9cf000000000002/md-1-big-CompressionInfo.db new file mode 100644 index 0000000000000000000000000000000000000000..13c639a07f6c1ab98dfd6ff719729c9284795d8a GIT binary patch literal 43 gcmZSJ^@%cZ&d)6@~ literal 0 HcmV?d00001 diff --git a/test/resource/sstables/wrong_range_tombstone_order/ks/wrong_range_tombstone_order-1c6ace40fad111e7b9cf000000000002/md-1-big-Data.db b/test/resource/sstables/wrong_range_tombstone_order/ks/wrong_range_tombstone_order-1c6ace40fad111e7b9cf000000000002/md-1-big-Data.db new file mode 100644 index 0000000000000000000000000000000000000000..e18f714c2407d15697493ce99f5f625077ab8da5 GIT binary patch literal 281 zcmXwxyGjF55QhJm-Lp4YNmdUM)?)3{GJ=vqgayIM5F1+y3$coYrFaAbmIi}ZSeOSe zh`nGF#23&~4Xn2+sGX5@K%DBE|JEen0ygA!^E@{`wgj0#KGv;B&p>AuwWaC;l*Tm&8a-UqC2CE9rcvNwpoHdK?@%`&T(`FSTiZyrw-Ym)-3U9ox4DQYO|0|ZgMrZ8Ce>1ede6;fm D|JFeu literal 0 HcmV?d00001 diff --git a/test/resource/sstables/wrong_range_tombstone_order/ks/wrong_range_tombstone_order-1c6ace40fad111e7b9cf000000000002/md-1-big-Digest.crc32 b/test/resource/sstables/wrong_range_tombstone_order/ks/wrong_range_tombstone_order-1c6ace40fad111e7b9cf000000000002/md-1-big-Digest.crc32 new file mode 100644 index 0000000000..5bd175f917 --- /dev/null +++ b/test/resource/sstables/wrong_range_tombstone_order/ks/wrong_range_tombstone_order-1c6ace40fad111e7b9cf000000000002/md-1-big-Digest.crc32 @@ -0,0 +1 @@ +2945241528 \ No newline at end of file diff --git a/test/resource/sstables/wrong_range_tombstone_order/ks/wrong_range_tombstone_order-1c6ace40fad111e7b9cf000000000002/md-1-big-Filter.db b/test/resource/sstables/wrong_range_tombstone_order/ks/wrong_range_tombstone_order-1c6ace40fad111e7b9cf000000000002/md-1-big-Filter.db new file mode 100644 index 0000000000000000000000000000000000000000..157a4de7cff17365433da137f823747e58804d15 GIT binary patch literal 16 XcmZQzU|?lnU|?imQefz4U|;|M1jqp~ literal 0 HcmV?d00001 diff --git a/test/resource/sstables/wrong_range_tombstone_order/ks/wrong_range_tombstone_order-1c6ace40fad111e7b9cf000000000002/md-1-big-Index.db b/test/resource/sstables/wrong_range_tombstone_order/ks/wrong_range_tombstone_order-1c6ace40fad111e7b9cf000000000002/md-1-big-Index.db new file mode 100644 index 0000000000000000000000000000000000000000..f9376371f57ea150ba2e6002d905fabebd52132f GIT binary patch literal 8 LcmZQzVE_XF03-kf literal 0 HcmV?d00001 diff --git a/test/resource/sstables/wrong_range_tombstone_order/ks/wrong_range_tombstone_order-1c6ace40fad111e7b9cf000000000002/md-1-big-Statistics.db b/test/resource/sstables/wrong_range_tombstone_order/ks/wrong_range_tombstone_order-1c6ace40fad111e7b9cf000000000002/md-1-big-Statistics.db new file mode 100644 index 0000000000000000000000000000000000000000..e9a1f2d163f6b8031086b2fb250b6dc6db98eea2 GIT binary patch literal 4817 zcmeI$eQc9O90%~{o~PZqx4L%YWi>PtHj!yMLYb2URtLqkMzgsw8KRMATf;_NH=Y)l za|Q$R4;M3s5hn>R3T7dLu;4(pNk$^N`GPVQL)FNfNtpx#RU^0wS$#TM`}>~8><|5~ zmt3Fw^mo_ip1XVcTpmFXG{T*fr5>1?*Qr@lbFG>sK`>kw?ypxiTI2?~s>Wrhl06>T zU9HHL>Kd=*@hwW-7R6d3E8bdfZN1y2*jk!?C_0w@g4#Ga7z|FOWH2^5@Jh1k4mF2T z=p;1p4xOAPwG)sc9gcjdjTFMiIL`5$b(|A9hx$sBo1)Os@FX6nL zvxD=KoHufA=DeG84`&~mar40*w+se$obx`?(9OD+_`X zrd;5psWh;@8rP%2eg)#`eY3%HM@`^)zEbdl^Elr*6FA@2_A^j_#n@%=qlMVNbtXT= zH(fJ=Un=ef@0olE-1`xZ&!t`%PnMW^gVtxZqF2G&=N|@tes(dnxbPJGzlv|_zoK!; z)ClWC>U$=E#x;!z1=QN7eQ>>T^|g&O9^dP~Nu6L{{Tp?XxgYMQlnUc8ji*Y^LF%;p zY*^28^pkJV_}xc#U8TNfspCEBgoVxAft|ICqWqamRf6>wM8uiVSBddwoeJPcF)K#li(fglum7JpYKMTI0r}w|; z@{fK%+KXdH3z~=}FY~`ntjRiWbW%@DG}HOf{@%WuK0o3YrHvPo`nrzeO(%#Gi{BYk zh?AdfyH2lP|Bc})y?(<5M~{)zpCz|8(C0zMVOzh2#Lb_M8|i#y{aWN~CGiK!1KD&x zkZ1jAP*37E-;e#Zbkm zpUoM`kH(pC=egS}iJhBn+}aVf;0c%UKltx^Fp;zg75}R}SLD30Jr@pe4^r@E*Ov4o-4NJA`2q6=lZuj7js?PtnN{j)Ko#c)n1o*qB(Tj<~d%r{*cbG zU45w$8A{u@Y1sVk$zs*jYL*HwUimOH5>#KJgq+K{AnK%Oak?ahU&UxtvAg7rH-Eo#x-mF Date: Tue, 21 Jul 2020 17:49:18 +0300 Subject: [PATCH 16/27] test: sstable_3_x_test: test both mc and md versions Run the test cases that write sstables using both the mc and md versions. Note that we can still compare the resulting Data, Index, Digest, and Filter components with the prepared mc sstables we have since these haven't changed in md. We take special consideration around validating min/max column names that are now calculated using a revised algorithm in the md format. Signed-off-by: Benny Halevy --- sstables/sstables.hh | 4 + test/boost/sstable_3_x_test.cc | 456 ++++++++++++++------------------- 2 files changed, 197 insertions(+), 263 deletions(-) diff --git a/sstables/sstables.hh b/sstables/sstables.hh index 0c3b26b21a..c669320b76 100644 --- a/sstables/sstables.hh +++ b/sstables/sstables.hh @@ -835,6 +835,10 @@ public: void update_stats_on_end_of_stream(); + bool has_correct_min_max_column_names() const noexcept { + return _version >= sstable_version_types::md; + } + // Return true if this sstable possibly stores clustering row(s) specified by ranges. bool may_contain_rows(const std::vector>>& ranges); diff --git a/test/boost/sstable_3_x_test.cc b/test/boost/sstable_3_x_test.cc index e34c894e89..538d0fe859 100644 --- a/test/boost/sstable_3_x_test.cc +++ b/test/boost/sstable_3_x_test.cc @@ -61,12 +61,12 @@ class sstable_assertions final { test_env _env; shared_sstable _sst; public: - sstable_assertions(schema_ptr schema, const sstring& path, int generation = 1) + sstable_assertions(schema_ptr schema, const sstring& path, sstable_version_types version = sstable_version_types::mc, int generation = 1) : _env() , _sst(_env.make_sstable(std::move(schema), path, generation, - sstable_version_types::mc, + version, sstable_format_types::big, 1)) { } @@ -101,6 +101,10 @@ public: return _sst->get_stats_metadata(); } + const shared_sstable get_sstable() const noexcept { + return _sst; + } + flat_mutation_reader read_range_rows_flat( const dht::partition_range& range, const query::partition_slice& slice, @@ -3153,6 +3157,7 @@ SEASTAR_THREAD_TEST_CASE(compact_deleted_cell) { } static void compare_files(sstring filename1, sstring filename2) { + BOOST_TEST_MESSAGE(format("comparing {} to {}", filename1, filename2)); std::ifstream ifs1; ifs1.exceptions(std::ifstream::failbit | std::ifstream::badbit); ifs1.open(filename1, std::ios_base::in | std::ios_base::binary); @@ -3171,7 +3176,7 @@ static sstring get_write_test_path(sstring table_name) { } // This method should not be called for compressed sstables because compression is not deterministic -static void compare_sstables(const std::filesystem::path& result_path, sstring table_name) { +static void compare_sstables(const std::filesystem::path& result_path, sstring table_name, sstable_version_types version) { for (auto file_type : {component_type::Data, component_type::Index, component_type::Digest, @@ -3180,15 +3185,15 @@ static void compare_sstables(const std::filesystem::path& result_path, sstring t sstable::filename(get_write_test_path(table_name), "ks", table_name, sstables::sstable_version_types::mc, 1, big, file_type); auto result_filename = - sstable::filename(result_path.string(), "ks", table_name, sstables::sstable_version_types::mc, 1, big, file_type); + sstable::filename(result_path.string(), "ks", table_name, version, 1, big, file_type); compare_files(orig_filename, result_filename); } } -static tmpdir write_sstables(test_env& env, schema_ptr s, lw_shared_ptr mt1, lw_shared_ptr mt2) { +static tmpdir write_sstables(test_env& env, schema_ptr s, lw_shared_ptr mt1, lw_shared_ptr mt2, sstable_version_types version) { storage_service_for_tests ssft; tmpdir tmp; - auto sst = env.make_sstable(s, tmp.path().string(), 1, sstables::sstable_version_types::mc, sstable::format_types::big, 4096); + auto sst = env.make_sstable(s, tmp.path().string(), 1, version, sstable::format_types::big, 4096); sst->write_components(make_combined_reader(s, mt1->make_flat_reader(s, tests::make_permit()), @@ -3199,30 +3204,30 @@ static tmpdir write_sstables(test_env& env, schema_ptr s, lw_shared_ptr mt1, lw_shared_ptr mt2, - sstring table_name) { + sstring table_name, sstable_version_types version) { test_env env; - auto tmp = write_sstables(env, std::move(s), std::move(mt1), std::move(mt2)); - compare_sstables(tmp.path(), table_name); + auto tmp = write_sstables(env, std::move(s), std::move(mt1), std::move(mt2), version); + compare_sstables(tmp.path(), table_name, version); return tmp; } -static tmpdir write_sstables(test_env& env, schema_ptr s, lw_shared_ptr mt) { +static tmpdir write_sstables(test_env& env, schema_ptr s, lw_shared_ptr mt, sstable_version_types version) { storage_service_for_tests ssft; tmpdir tmp; - auto sst = env.make_sstable(s, tmp.path().string(), 1, sstables::sstable_version_types::mc, sstable::format_types::big, 4096); + auto sst = env.make_sstable(s, tmp.path().string(), 1, version, sstable::format_types::big, 4096); write_memtable_to_sstable_for_test(*mt, sst).get(); return tmp; } -static tmpdir write_and_compare_sstables(schema_ptr s, lw_shared_ptr mt, sstring table_name) { +static tmpdir write_and_compare_sstables(schema_ptr s, lw_shared_ptr mt, sstring table_name, sstable_version_types version) { test_env env; - auto tmp = write_sstables(env, std::move(s), std::move(mt)); - compare_sstables(tmp.path(), table_name); + auto tmp = write_sstables(env, std::move(s), std::move(mt), version); + compare_sstables(tmp.path(), table_name, version); return tmp; } -static sstable_assertions validate_read(schema_ptr s, const std::filesystem::path& path, std::vector mutations) { - sstable_assertions sst(s, path.string(), 1); +static sstable_assertions validate_read(schema_ptr s, const std::filesystem::path& path, std::vector mutations, sstable_version_types version) { + sstable_assertions sst(s, path.string(), version); sst.load(); auto assertions = assert_that(sst.read_rows_flat()); @@ -3234,13 +3239,45 @@ static sstable_assertions validate_read(schema_ptr s, const std::filesystem::pat return sst; } +constexpr std::array test_sstable_versions = { + sstable_version_types::mc, + sstable_version_types::md, +}; + +static void write_mut_and_compare_sstables_version(schema_ptr s, mutation& mut, const sstring& table_name, + sstable_version_types version) { + lw_shared_ptr mt = make_lw_shared(s); + mt->apply(mut); + + (void)write_and_compare_sstables(s, mt, table_name, version); +} + +static void write_mut_and_compare_sstables(schema_ptr s, mutation& mut, const sstring& table_name) { + for (auto version : test_sstable_versions) { + write_mut_and_compare_sstables_version(s, mut, table_name, version); + } +} + +static void write_muts_and_compare_sstables_version(schema_ptr s, mutation& mut1, mutation& mut2, const sstring& table_name, + sstable_version_types version) { + lw_shared_ptr mt1 = make_lw_shared(s); + lw_shared_ptr mt2 = make_lw_shared(s); + mt1->apply(mut1); + mt2->apply(mut2); + + (void)write_and_compare_sstables(s, mt1, mt2, table_name, version); +} + +static void write_muts_and_compare_sstables(schema_ptr s, mutation& mut1, mutation& mut2, const sstring& table_name) { + for (auto version : test_sstable_versions) { + write_muts_and_compare_sstables_version(s, mut1, mut2, table_name, version); + } +} + static constexpr api::timestamp_type write_timestamp = 1525385507816568; static constexpr gc_clock::time_point write_time_point = gc_clock::time_point{} + gc_clock::duration{1525385507}; -// FIXME: base validation of min_max_column_names on sstable has_correct_min_max_column_names() -// when we generate md format sstables with correct min/max_column_names metadata. -static void validate_stats_metadata(schema_ptr s, sstable_assertions& written_sst, sstring table_name, - bool do_validate_min_max_column_names = true) { +static void do_validate_stats_metadata(schema_ptr s, sstable_assertions& written_sst, sstring table_name) { auto orig_sst = written_sst.get_env().reusable_sst(s, get_write_test_path(table_name), 1, sstable_version_types::mc).get0(); const auto& orig_stats = orig_sst->get_stats_metadata(); @@ -3259,7 +3296,7 @@ static void validate_stats_metadata(schema_ptr s, sstable_assertions& written_ss BOOST_REQUIRE_EQUAL(orig_stats.max_local_deletion_time, written_stats.max_local_deletion_time); BOOST_REQUIRE_EQUAL(orig_stats.min_ttl, written_stats.min_ttl); BOOST_REQUIRE_EQUAL(orig_stats.max_ttl, written_stats.max_ttl); - if (do_validate_min_max_column_names) { + if (orig_sst->has_correct_min_max_column_names() && written_sst.get_sstable()->has_correct_min_max_column_names()) { BOOST_REQUIRE(orig_stats.min_column_names.elements == written_stats.min_column_names.elements); BOOST_REQUIRE(orig_stats.max_column_names.elements == written_stats.max_column_names.elements); } @@ -3282,6 +3319,67 @@ static void check_min_max_column_names(sstable_assertions& written_sst, std::vec } } +struct validate_stats_metadata_tag { }; +using validate_stats_metadata = bool_class; + +static void write_mut_and_validate_version(schema_ptr s, const sstring& table_name, mutation& mut, + sstable_version_types version, validate_stats_metadata validate_flag) { + lw_shared_ptr mt = make_lw_shared(s); + mt->apply(mut); + + tmpdir tmp = write_and_compare_sstables(s, mt, table_name, version); + auto written_sst = validate_read(s, tmp.path(), {mut}, version); + if (validate_flag) { + do_validate_stats_metadata(s, written_sst, table_name); + } +} + +static void write_mut_and_validate(schema_ptr s, const sstring& table_name, mutation& mut, + validate_stats_metadata validate_flag = validate_stats_metadata::no) { + for (auto version : test_sstable_versions) { + write_mut_and_validate_version(s, table_name, mut, version, validate_flag); + } +} + +static void write_mut_and_validate_version(schema_ptr s, const sstring& table_name, mutation& mut, + sstable_version_types version, std::vector min_components, std::vector max_components) { + lw_shared_ptr mt = make_lw_shared(s); + mt->apply(mut); + + tmpdir tmp = write_and_compare_sstables(s, mt, table_name, version); + auto written_sst = validate_read(s, tmp.path(), {mut}, version); + do_validate_stats_metadata(s, written_sst, table_name); + check_min_max_column_names(written_sst, std::move(min_components), std::move(max_components)); +} + +static void write_mut_and_validate(schema_ptr s, const sstring& table_name, mutation& mut, + std::vector min_components, std::vector max_components) { + for (auto version : test_sstable_versions) { + write_mut_and_validate_version(s, table_name, mut, version, min_components, max_components); + } +} + +static void write_mut_and_validate_version(schema_ptr s, const sstring& table_name, std::vector muts, + sstable_version_types version, validate_stats_metadata validate_flag) { + lw_shared_ptr mt = make_lw_shared(s); + for (auto& mut : muts) { + mt->apply(mut); + } + + tmpdir tmp = write_and_compare_sstables(s, mt, table_name, version); + auto written_sst = validate_read(s, tmp.path(), muts, version); + if (validate_flag) { + do_validate_stats_metadata(s, written_sst, table_name); + } +} + +static void write_mut_and_validate(schema_ptr s, const sstring& table_name, std::vector muts, + validate_stats_metadata validate_flag = validate_stats_metadata::no) { + for (auto version : test_sstable_versions) { + write_mut_and_validate_version(s, table_name, muts, version, validate_flag); + } +} + SEASTAR_THREAD_TEST_CASE(test_write_static_row) { auto abj = defer([] { await_background_jobs().get(); }); sstring table_name = "static_row"; @@ -3294,18 +3392,13 @@ SEASTAR_THREAD_TEST_CASE(test_write_static_row) { builder.set_compressor_params(compression_parameters::no_compression()); schema_ptr s = builder.build(schema_builder::compact_storage::no); - lw_shared_ptr mt = make_lw_shared(s); - // INSERT INTO static_row (pk, st1, st2) values ('key1', 1135, 'hello') USING TIMESTAMP 1525385507816568; auto key = make_dkey(s, {to_bytes("key1")}); mutation mut{s, key}; mut.set_static_cell("st1", data_value{1135}, write_timestamp); mut.set_static_cell("st2", data_value{"hello"}, write_timestamp); - mt->apply(mut); - tmpdir tmp = write_and_compare_sstables(s, mt, table_name); - auto written_sst = validate_read(s, tmp.path(), {mut}); - validate_stats_metadata(s, written_sst, table_name); + write_mut_and_validate(s, table_name, mut); } SEASTAR_THREAD_TEST_CASE(test_write_composite_partition_key) { @@ -3323,8 +3416,6 @@ SEASTAR_THREAD_TEST_CASE(test_write_composite_partition_key) { builder.set_compressor_params(compression_parameters::no_compression()); schema_ptr s = builder.build(schema_builder::compact_storage::no); - lw_shared_ptr mt = make_lw_shared(s); - // INSERT INTO composite_partition_key (a,b,c,d,e,f,g) values (1, 'hello', true, 2, 'dear', 3, 'world') USING TIMESTAMP 1525385507816568; auto key = partition_key::from_deeply_exploded(*s, { data_value{1}, data_value{"hello"}, data_value{true} }); mutation mut{s, key}; @@ -3332,11 +3423,8 @@ SEASTAR_THREAD_TEST_CASE(test_write_composite_partition_key) { mut.partition().apply_insert(*s, ckey, write_timestamp); mut.set_cell(ckey, "f", data_value{3}, write_timestamp); mut.set_cell(ckey, "g", data_value{"world"}, write_timestamp); - mt->apply(mut); - tmpdir tmp = write_and_compare_sstables(s, mt, table_name); - auto written_sst = validate_read(s, tmp.path(), {mut}); - validate_stats_metadata(s, written_sst, table_name); + write_mut_and_validate(s, table_name, mut); } SEASTAR_THREAD_TEST_CASE(test_write_composite_clustering_key) { @@ -3353,8 +3441,6 @@ SEASTAR_THREAD_TEST_CASE(test_write_composite_clustering_key) { builder.set_compressor_params(compression_parameters::no_compression()); schema_ptr s = builder.build(schema_builder::compact_storage::no); - lw_shared_ptr mt = make_lw_shared(s); - // INSERT INTO composite_clustering_key (a,b,c,d,e,f) values (1, 'hello', 2, 'dear', 3, 'world') USING TIMESTAMP 1525385507816568; auto key = partition_key::from_deeply_exploded(*s, { 1 }); mutation mut{s, key}; @@ -3362,11 +3448,8 @@ SEASTAR_THREAD_TEST_CASE(test_write_composite_clustering_key) { mut.partition().apply_insert(*s, ckey, write_timestamp); mut.set_cell(ckey, "e", data_value{3}, write_timestamp); mut.set_cell(ckey, "f", data_value{"world"}, write_timestamp); - mt->apply(mut); - tmpdir tmp = write_and_compare_sstables(s, mt, table_name); - auto written_sst = validate_read(s, tmp.path(), {mut}); - validate_stats_metadata(s, written_sst, table_name); + write_mut_and_validate(s, table_name, mut); } SEASTAR_THREAD_TEST_CASE(test_write_wide_partitions) { @@ -3381,7 +3464,6 @@ SEASTAR_THREAD_TEST_CASE(test_write_wide_partitions) { builder.set_compressor_params(compression_parameters::no_compression()); schema_ptr s = builder.build(schema_builder::compact_storage::no); - lw_shared_ptr mt = make_lw_shared(s); api::timestamp_type ts = write_timestamp; sstring ck_base(1024, 'a'); sstring rc_base(1024, 'b'); @@ -3395,7 +3477,6 @@ SEASTAR_THREAD_TEST_CASE(test_write_wide_partitions) { mut1.set_cell(ckey, "rc", data_value{format("{}{}", rc_base, idx)}, ts); seastar::thread::yield(); } - mt->apply(mut1); ts += 10; } auto key2 = make_dkey(s, {to_bytes("key2")}); @@ -3408,12 +3489,9 @@ SEASTAR_THREAD_TEST_CASE(test_write_wide_partitions) { mut2.set_cell(ckey, "rc", data_value{format("{}{}", rc_base, idx)}, ts); seastar::thread::yield(); } - mt->apply(mut2); } - tmpdir tmp = write_and_compare_sstables(s, mt, table_name); - auto written_sst = validate_read(s, tmp.path(), {mut1, mut2}); - validate_stats_metadata(s, written_sst, table_name); + write_mut_and_validate(s, table_name, {mut1, mut2}); } SEASTAR_THREAD_TEST_CASE(test_write_ttled_row) { @@ -3427,8 +3505,6 @@ SEASTAR_THREAD_TEST_CASE(test_write_ttled_row) { builder.set_compressor_params(compression_parameters::no_compression()); schema_ptr s = builder.build(schema_builder::compact_storage::no); - lw_shared_ptr mt = make_lw_shared(s); - // INSERT INTO ttled_row (pk, ck, rc) VALUES ( 1, 2, 3) USING TTL 1135 AND TIMESTAMP 1525385507816568; auto key = partition_key::from_deeply_exploded(*s, { 1 }); mutation mut{s, key}; @@ -3444,11 +3520,8 @@ SEASTAR_THREAD_TEST_CASE(test_write_ttled_row) { bytes value = column_def->type->decompose(data_value{3}); auto cell = atomic_cell::make_live(*column_def->type, write_timestamp, value, tp + ttl, ttl); mut.set_clustered_cell(ckey, *column_def, std::move(cell)); - mt->apply(mut); - tmpdir tmp = write_and_compare_sstables(s, mt, table_name); - auto written_sst = validate_read(s, tmp.path(), {mut}); - validate_stats_metadata(s, written_sst, table_name); + write_mut_and_validate(s, table_name, mut); } SEASTAR_THREAD_TEST_CASE(test_write_ttled_column) { @@ -3461,8 +3534,6 @@ SEASTAR_THREAD_TEST_CASE(test_write_ttled_column) { builder.set_compressor_params(compression_parameters::no_compression()); schema_ptr s = builder.build(schema_builder::compact_storage::no); - lw_shared_ptr mt = make_lw_shared(s); - // UPDATE ttled_column USING TTL 1135 AND TIMESTAMP 1525385507816568 SET rc = 1 WHERE pk='key'; auto key = make_dkey(s, {to_bytes("key")}); mutation mut{s, key}; @@ -3476,11 +3547,8 @@ SEASTAR_THREAD_TEST_CASE(test_write_ttled_column) { bytes value = column_def->type->decompose(data_value{1}); auto cell = atomic_cell::make_live(*column_def->type, write_timestamp, value, tp + ttl, ttl); mut.set_clustered_cell(clustering_key::make_empty(), *column_def, std::move(cell)); - mt->apply(mut); - tmpdir tmp = write_and_compare_sstables(s, mt, table_name); - auto written_sst = validate_read(s, tmp.path(), {mut}); - validate_stats_metadata(s, written_sst, table_name); + write_mut_and_validate(s, table_name, mut); } SEASTAR_THREAD_TEST_CASE(test_write_deleted_column) { @@ -3493,8 +3561,6 @@ SEASTAR_THREAD_TEST_CASE(test_write_deleted_column) { builder.set_compressor_params(compression_parameters::no_compression()); schema_ptr s = builder.build(schema_builder::compact_storage::no); - lw_shared_ptr mt = make_lw_shared(s); - // DELETE rc FROM deleted_column USING TIMESTAMP 1525385507816568 WHERE pk=1; gc_clock::time_point tp = gc_clock::time_point{} + gc_clock::duration{1543905926}; auto key = partition_key::from_deeply_exploded(*s, { 1 }); @@ -3504,11 +3570,8 @@ SEASTAR_THREAD_TEST_CASE(test_write_deleted_column) { throw std::runtime_error("no column definition found"); } mut.set_cell(clustering_key::make_empty(), *column_def, atomic_cell::make_dead(write_timestamp, tp)); - mt->apply(mut); - tmpdir tmp = write_and_compare_sstables(s, mt, table_name); - auto written_sst = validate_read(s, tmp.path(), {mut}); - validate_stats_metadata(s, written_sst, table_name); + write_mut_and_validate(s, table_name, mut); } SEASTAR_THREAD_TEST_CASE(test_write_deleted_row) { @@ -3521,19 +3584,14 @@ SEASTAR_THREAD_TEST_CASE(test_write_deleted_row) { builder.set_compressor_params(compression_parameters::no_compression()); schema_ptr s = builder.build(schema_builder::compact_storage::no); - lw_shared_ptr mt = make_lw_shared(s); - // DELETE FROM deleted_row USING TIMESTAMP 1525385507816568 WHERE pk=1 and ck=2; gc_clock::time_point tp = gc_clock::time_point{} + gc_clock::duration{1543907978}; auto key = partition_key::from_deeply_exploded(*s, { 1 }); mutation mut{s, key}; clustering_key ckey = clustering_key::from_deeply_exploded(*s, { 2 }); mut.partition().apply_delete(*s, ckey, tombstone{write_timestamp, tp}); - mt->apply(mut); - tmpdir tmp = write_and_compare_sstables(s, mt, table_name); - auto written_sst = validate_read(s, tmp.path(), {mut}); - validate_stats_metadata(s, written_sst, table_name); + write_mut_and_validate(s, table_name, mut); } SEASTAR_THREAD_TEST_CASE(test_write_collection_wide_update) { @@ -3547,8 +3605,6 @@ SEASTAR_THREAD_TEST_CASE(test_write_collection_wide_update) { builder.set_compressor_params(compression_parameters::no_compression()); schema_ptr s = builder.build(schema_builder::compact_storage::no); - lw_shared_ptr mt = make_lw_shared(s); - // INSERT INTO collection_wide_update (pk, col) VALUES (1, {2, 3}) USING TIMESTAMP 1525385507816568; gc_clock::time_point tp = gc_clock::time_point{} + gc_clock::duration{1543908589}; auto key = partition_key::from_deeply_exploded(*s, { 1 }); @@ -3561,11 +3617,8 @@ SEASTAR_THREAD_TEST_CASE(test_write_collection_wide_update) { set_values.cells.emplace_back(int32_type->decompose(3), atomic_cell::make_live(*bytes_type, write_timestamp, bytes_view{})); mut.set_clustered_cell(clustering_key::make_empty(), *s->get_column_definition("col"), set_values.serialize(*set_of_ints_type)); - mt->apply(mut); - tmpdir tmp = write_and_compare_sstables(s, mt, table_name); - auto written_sst = validate_read(s, tmp.path(), {mut}); - validate_stats_metadata(s, written_sst, table_name); + write_mut_and_validate(s, table_name, mut); } SEASTAR_THREAD_TEST_CASE(test_write_collection_incremental_update) { @@ -3579,8 +3632,6 @@ SEASTAR_THREAD_TEST_CASE(test_write_collection_incremental_update) { builder.set_compressor_params(compression_parameters::no_compression()); schema_ptr s = builder.build(schema_builder::compact_storage::no); - lw_shared_ptr mt = make_lw_shared(s); - // UPDATE collection_incremental_update USING TIMESTAMP 1525385507816568 SET col = col + {2} WHERE pk = 1; auto key = partition_key::from_deeply_exploded(*s, { 1 }); mutation mut{s, key}; @@ -3589,11 +3640,8 @@ SEASTAR_THREAD_TEST_CASE(test_write_collection_incremental_update) { set_values.cells.emplace_back(int32_type->decompose(2), atomic_cell::make_live(*bytes_type, write_timestamp, bytes_view{})); mut.set_clustered_cell(clustering_key::make_empty(), *s->get_column_definition("col"), set_values.serialize(*set_of_ints_type)); - mt->apply(mut); - tmpdir tmp = write_and_compare_sstables(s, mt, table_name); - auto written_sst = validate_read(s, tmp.path(), {mut}); - validate_stats_metadata(s, written_sst, table_name); + write_mut_and_validate(s, table_name, mut); } SEASTAR_THREAD_TEST_CASE(test_write_multiple_partitions) { @@ -3608,8 +3656,6 @@ SEASTAR_THREAD_TEST_CASE(test_write_multiple_partitions) { builder.set_compressor_params(compression_parameters::no_compression()); schema_ptr s = builder.build(schema_builder::compact_storage::no); - lw_shared_ptr mt = make_lw_shared(s); - api::timestamp_type ts = write_timestamp; // INSERT INTO multiple_partitions (pk, rc1) VALUES (1, 10) USING TIMESTAMP 1525385507816568; // INSERT INTO multiple_partitions (pk, rc2) VALUES (2, 20) USING TIMESTAMP 1525385507816578; @@ -3622,13 +3668,10 @@ SEASTAR_THREAD_TEST_CASE(test_write_multiple_partitions) { clustering_key ckey = clustering_key::make_empty(); muts.back().partition().apply_insert(*s, ckey, ts); muts.back().set_cell(ckey, to_bytes(format("rc{}", i)), data_value{i * 10}, ts); - mt->apply(muts.back()); ts += 10; } - tmpdir tmp = write_and_compare_sstables(s, mt, table_name); - auto written_sst = validate_read(s, tmp.path(), muts); - validate_stats_metadata(s, written_sst, table_name); + write_mut_and_validate(s, table_name, muts); } static void test_write_many_partitions(sstring table_name, tombstone partition_tomb, compression_parameters cp) { @@ -3638,8 +3681,6 @@ static void test_write_many_partitions(sstring table_name, tombstone partition_t builder.set_compressor_params(cp); schema_ptr s = builder.build(schema_builder::compact_storage::no); - lw_shared_ptr mt = make_lw_shared(s); - std::vector muts; for (auto i : boost::irange(0, 65536)) { auto key = partition_key::from_deeply_exploded(*s, {i}); @@ -3647,14 +3688,20 @@ static void test_write_many_partitions(sstring table_name, tombstone partition_t if (partition_tomb) { muts.back().partition().apply(partition_tomb); } - mt->apply(muts.back()); } bool compressed = cp.get_compressor() != nullptr; - test_env env; - tmpdir tmp = compressed ? write_sstables(env, s, mt) : write_and_compare_sstables(s, mt, table_name); - boost::sort(muts, mutation_decorated_key_less_comparator()); - validate_read(s, tmp.path(), muts); + for (auto version : test_sstable_versions) { + lw_shared_ptr mt = make_lw_shared(s); + for (auto& mut : muts) { + mt->apply(mut); + } + + test_env env; + tmpdir tmp = compressed ? write_sstables(env, s, mt, version) : write_and_compare_sstables(s, mt, table_name, version); + boost::sort(muts, mutation_decorated_key_less_comparator()); + validate_read(s, tmp.path(), muts, version); + } } SEASTAR_THREAD_TEST_CASE(test_write_many_live_partitions) { @@ -3720,8 +3767,6 @@ SEASTAR_THREAD_TEST_CASE(test_write_multiple_rows) { builder.set_compressor_params(compression_parameters::no_compression()); schema_ptr s = builder.build(schema_builder::compact_storage::no); - lw_shared_ptr mt = make_lw_shared(s); - auto key = partition_key::from_deeply_exploded(*s, {0}); api::timestamp_type ts = write_timestamp; mutation mut{s, key}; @@ -3736,10 +3781,7 @@ SEASTAR_THREAD_TEST_CASE(test_write_multiple_rows) { ts += 10; } - mt->apply(mut); - tmpdir tmp = write_and_compare_sstables(s, mt, table_name); - auto written_sst = validate_read(s, tmp.path(), {mut}); - validate_stats_metadata(s, written_sst, table_name); + write_mut_and_validate(s, table_name, mut); } // Information on missing columns is serialized differently when the number of columns is > 64. @@ -3757,8 +3799,6 @@ SEASTAR_THREAD_TEST_CASE(test_write_missing_columns_large_set) { builder.set_compressor_params(compression_parameters::no_compression()); schema_ptr s = builder.build(schema_builder::compact_storage::no); - lw_shared_ptr mt = make_lw_shared(s); - auto key = partition_key::from_deeply_exploded(*s, {0}); api::timestamp_type ts = write_timestamp; mutation mut{s, key}; @@ -3782,11 +3822,8 @@ SEASTAR_THREAD_TEST_CASE(test_write_missing_columns_large_set) { mut.set_cell(ckey, to_bytes("rc63"), data_value{63}, ts); mut.set_cell(ckey, to_bytes("rc64"), data_value{64}, ts); } - mt->apply(mut); - tmpdir tmp = write_and_compare_sstables(s, mt, table_name); - auto written_sst = validate_read(s, tmp.path(), {mut}); - validate_stats_metadata(s, written_sst, table_name); + write_mut_and_validate(s, table_name, mut); } SEASTAR_THREAD_TEST_CASE(test_write_empty_counter) { @@ -3800,7 +3837,6 @@ SEASTAR_THREAD_TEST_CASE(test_write_empty_counter) { builder.set_compressor_params(compression_parameters::no_compression()); schema_ptr s = builder.build(schema_builder::compact_storage::no); - lw_shared_ptr mt = make_lw_shared(s); auto key = partition_key::from_exploded(*s, {to_bytes("key")}); mutation mut{s, key}; @@ -3810,9 +3846,7 @@ SEASTAR_THREAD_TEST_CASE(test_write_empty_counter) { 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}); + write_mut_and_validate(s, table_name, mut); } SEASTAR_THREAD_TEST_CASE(test_write_counter_table) { @@ -3827,7 +3861,6 @@ SEASTAR_THREAD_TEST_CASE(test_write_counter_table) { builder.set_compressor_params(compression_parameters::no_compression()); schema_ptr s = builder.build(schema_builder::compact_storage::no); - lw_shared_ptr mt = make_lw_shared(s); auto key = partition_key::from_exploded(*s, {to_bytes("key")}); mutation mut{s, key}; @@ -3858,10 +3891,7 @@ SEASTAR_THREAD_TEST_CASE(test_write_counter_table) { auto ckey2 = clustering_key::from_exploded(*s, {to_bytes("ck2")}); mut.set_clustered_cell(ckey2, cdef1, atomic_cell::make_dead(write_timestamp, write_time_point)); - mt->apply(mut); - - tmpdir tmp = write_and_compare_sstables(s, mt, table_name); - validate_read(s, tmp.path(), {mut}); + write_mut_and_validate(s, table_name, mut, validate_stats_metadata::no); } SEASTAR_THREAD_TEST_CASE(test_write_different_types) { @@ -3897,8 +3927,6 @@ SEASTAR_THREAD_TEST_CASE(test_write_different_types) { builder.set_compressor_params(compression_parameters::no_compression()); schema_ptr s = builder.build(schema_builder::compact_storage::no); - lw_shared_ptr mt = make_lw_shared(s); - // INSERT INTO different_types (pk, asciival, bigintval, blobval, boolval, // dateval, decimalval, doubleval, floatval, inetval, intval, smallintval, // timeval, tsval, timeuuidval, tinyintval, uuidval, varcharval, varintval, @@ -3930,11 +3958,8 @@ SEASTAR_THREAD_TEST_CASE(test_write_different_types) { mut.set_cell(ckey, "varcharval", data_value{"привет"}, write_timestamp); mut.set_cell(ckey, "varintval", varint_type->deserialize(varint_type->from_string("123")), write_timestamp); mut.set_cell(ckey, "durationval", duration_type->deserialize(duration_type->from_string("1h4m48s20ms")), write_timestamp); - mt->apply(mut); - tmpdir tmp = write_and_compare_sstables(s, mt, table_name); - auto written_sst = validate_read(s, tmp.path(), {mut}); - validate_stats_metadata(s, written_sst, table_name); + write_mut_and_validate(s, table_name, mut); } SEASTAR_THREAD_TEST_CASE(test_write_empty_clustering_values) { @@ -3950,8 +3975,6 @@ SEASTAR_THREAD_TEST_CASE(test_write_empty_clustering_values) { builder.set_compressor_params(compression_parameters::no_compression()); schema_ptr s = builder.build(schema_builder::compact_storage::no); - lw_shared_ptr mt = make_lw_shared(s); - auto key = partition_key::from_deeply_exploded(*s, {0}); mutation mut{s, key}; @@ -3960,10 +3983,7 @@ SEASTAR_THREAD_TEST_CASE(test_write_empty_clustering_values) { mut.partition().apply_insert(*s, ckey, write_timestamp); mut.set_cell(ckey, "rc", data_value{2}, write_timestamp); - mt->apply(mut); - tmpdir tmp = write_and_compare_sstables(s, mt, table_name); - auto written_sst = validate_read(s, tmp.path(), {mut}); - validate_stats_metadata(s, written_sst, table_name); + write_mut_and_validate(s, table_name, mut); } SEASTAR_THREAD_TEST_CASE(test_write_large_clustering_key) { @@ -3979,8 +3999,6 @@ SEASTAR_THREAD_TEST_CASE(test_write_large_clustering_key) { builder.set_compressor_params(compression_parameters::no_compression()); schema_ptr s = builder.build(schema_builder::compact_storage::no); - lw_shared_ptr mt = make_lw_shared(s); - auto key = partition_key::from_deeply_exploded(*s, {0}); mutation mut{s, key}; @@ -3996,10 +4014,7 @@ SEASTAR_THREAD_TEST_CASE(test_write_large_clustering_key) { mut.partition().apply_insert(*s, ckey, write_timestamp); mut.set_cell(ckey, "rc", data_value{1}, write_timestamp); - mt->apply(mut); - tmpdir tmp = write_and_compare_sstables(s, mt, table_name); - auto written_sst = validate_read(s, tmp.path(), {mut}); - validate_stats_metadata(s, written_sst, table_name); + write_mut_and_validate(s, table_name, mut); } SEASTAR_THREAD_TEST_CASE(test_write_compact_table) { @@ -4014,8 +4029,6 @@ SEASTAR_THREAD_TEST_CASE(test_write_compact_table) { builder.set_compressor_params(compression_parameters::no_compression()); schema_ptr s = builder.build(schema_builder::compact_storage::yes); - lw_shared_ptr mt = make_lw_shared(s); - auto key = partition_key::from_deeply_exploded(*s, {1}); mutation mut{s, key}; @@ -4023,10 +4036,7 @@ SEASTAR_THREAD_TEST_CASE(test_write_compact_table) { clustering_key ckey = clustering_key::from_deeply_exploded(*s, { 1 }); mut.set_cell(ckey, "rc", data_value{1}, write_timestamp); - mt->apply(mut); - tmpdir tmp = write_and_compare_sstables(s, mt, table_name); - auto written_sst = validate_read(s, tmp.path(), {mut}); - validate_stats_metadata(s, written_sst, table_name); + write_mut_and_validate(s, table_name, mut); } SEASTAR_THREAD_TEST_CASE(test_write_user_defined_type_table) { @@ -4044,8 +4054,6 @@ SEASTAR_THREAD_TEST_CASE(test_write_user_defined_type_table) { builder.set_compressor_params(compression_parameters::no_compression()); schema_ptr s = builder.build(schema_builder::compact_storage::no); - lw_shared_ptr mt = make_lw_shared(s); - auto key = partition_key::from_deeply_exploded(*s, {0}); mutation mut{s, key}; @@ -4054,11 +4062,8 @@ SEASTAR_THREAD_TEST_CASE(test_write_user_defined_type_table) { mut.partition().apply_insert(*s, ckey, write_timestamp); auto ut_val = make_user_value(ut, user_type_impl::native_type({int32_t(1703), true, sstring("Санкт-Петербург")})); mut.set_cell(ckey, "rc", ut_val, write_timestamp); - mt->apply(mut); - tmpdir tmp = write_and_compare_sstables(s, mt, table_name); - auto written_sst = validate_read(s, tmp.path(), {mut}); - validate_stats_metadata(s, written_sst, table_name); + write_mut_and_validate(s, table_name, mut); } SEASTAR_THREAD_TEST_CASE(test_write_simple_range_tombstone) { @@ -4073,8 +4078,6 @@ SEASTAR_THREAD_TEST_CASE(test_write_simple_range_tombstone) { builder.set_compressor_params(compression_parameters::no_compression()); schema_ptr s = builder.build(schema_builder::compact_storage::no); - lw_shared_ptr mt = make_lw_shared(s); - // DELETE FROM simple_range_tombstone USING TIMESTAMP 1525385507816568 WHERE pk = 0 and ck1 = 'aaa'; auto key = partition_key::from_deeply_exploded(*s, {0}); mutation mut{s, key}; @@ -4082,11 +4085,8 @@ SEASTAR_THREAD_TEST_CASE(test_write_simple_range_tombstone) { tombstone tomb{write_timestamp, tp}; range_tombstone rt{clustering_key_prefix::from_single_value(*s, bytes("aaa")), clustering_key_prefix::from_single_value(*s, bytes("aaa")), tomb}; mut.partition().apply_delete(*s, std::move(rt)); - mt->apply(mut); - tmpdir tmp = write_and_compare_sstables(s, mt, table_name); - auto written_sst = validate_read(s, tmp.path(), {mut}); - validate_stats_metadata(s, written_sst, table_name); + write_mut_and_validate(s, table_name, mut); } // Test the case when for RTs their adjacent bounds are written as boundary RT markers. @@ -4102,8 +4102,6 @@ SEASTAR_THREAD_TEST_CASE(test_write_adjacent_range_tombstones) { builder.set_compressor_params(compression_parameters::no_compression()); schema_ptr s = builder.build(schema_builder::compact_storage::no); - lw_shared_ptr mt = make_lw_shared(s); - auto key = make_dkey(s, {to_bytes("key")}); mutation mut{s, key}; api::timestamp_type ts = write_timestamp; @@ -4126,12 +4124,8 @@ SEASTAR_THREAD_TEST_CASE(test_write_adjacent_range_tombstones) { clustering_key::from_deeply_exploded(*s, {"aaa", "bbb"}), tomb}; mut.partition().apply_delete(*s, std::move(rt)); } - mt->apply(mut); - tmpdir tmp = write_and_compare_sstables(s, mt, table_name); - auto written_sst = validate_read(s, tmp.path(), {mut}); - validate_stats_metadata(s, written_sst, table_name, false); - check_min_max_column_names(written_sst, {"aaa"}, {"aaa"}); + write_mut_and_validate(s, table_name, mut, {"aaa"}, {"aaa"}); } // Test the case when subsequent RTs have a common clustering but those bounds are both exclusive @@ -4148,8 +4142,6 @@ SEASTAR_THREAD_TEST_CASE(test_write_non_adjacent_range_tombstones) { builder.set_compressor_params(compression_parameters::no_compression()); schema_ptr s = builder.build(schema_builder::compact_storage::no); - lw_shared_ptr mt = make_lw_shared(s); - auto key = make_dkey(s, {to_bytes("key")}); mutation mut{s, key}; api::timestamp_type ts = write_timestamp; @@ -4172,11 +4164,8 @@ SEASTAR_THREAD_TEST_CASE(test_write_non_adjacent_range_tombstones) { clustering_key_prefix::from_single_value(*s, bytes("ccc")), bound_kind::excl_end}; mut.partition().apply_delete(*s, std::move(rt)); } - mt->apply(mut); - tmpdir tmp = write_and_compare_sstables(s, mt, table_name); - auto written_sst = validate_read(s, tmp.path(), {mut}); - validate_stats_metadata(s, written_sst, table_name); + write_mut_and_validate(s, table_name, mut); } SEASTAR_THREAD_TEST_CASE(test_write_mixed_rows_and_range_tombstones) { @@ -4190,8 +4179,6 @@ SEASTAR_THREAD_TEST_CASE(test_write_mixed_rows_and_range_tombstones) { builder.set_compressor_params(compression_parameters::no_compression()); schema_ptr s = builder.build(schema_builder::compact_storage::no); - lw_shared_ptr mt = make_lw_shared(s); - auto key = make_dkey(s, {to_bytes("key")}); mutation mut{s, key}; api::timestamp_type ts = write_timestamp; @@ -4245,12 +4232,8 @@ SEASTAR_THREAD_TEST_CASE(test_write_mixed_rows_and_range_tombstones) { clustering_key ckey = clustering_key::from_deeply_exploded(*s, {"ddd", "eee"}); mut.partition().apply_insert(*s, ckey, ts); } - mt->apply(mut); - tmpdir tmp = write_and_compare_sstables(s, mt, table_name); - auto written_sst = validate_read(s, tmp.path(), {mut}); - validate_stats_metadata(s, written_sst, table_name, false); - check_min_max_column_names(written_sst, {"aaa"}, {"ddd"}); + write_mut_and_validate(s, table_name, mut, {"aaa"}, {"ddd"}); } SEASTAR_THREAD_TEST_CASE(test_write_many_range_tombstones) { @@ -4264,8 +4247,6 @@ SEASTAR_THREAD_TEST_CASE(test_write_many_range_tombstones) { builder.set_compressor_params(compression_parameters::no_compression()); schema_ptr s = builder.build(schema_builder::compact_storage::no); - lw_shared_ptr mt = make_lw_shared(s); - auto key = make_dkey(s, {to_bytes("key1")}); mutation mut{s, key}; @@ -4280,10 +4261,8 @@ SEASTAR_THREAD_TEST_CASE(test_write_many_range_tombstones) { mut.partition().apply_delete(*s, std::move(rt)); seastar::thread::yield(); } - mt->apply(mut); - tmpdir tmp = write_and_compare_sstables(s, mt, table_name); - validate_read(s, tmp.path(), {mut}); + write_mut_and_validate(s, table_name, mut, validate_stats_metadata::no); } SEASTAR_THREAD_TEST_CASE(test_write_adjacent_range_tombstones_with_rows) { @@ -4298,8 +4277,6 @@ SEASTAR_THREAD_TEST_CASE(test_write_adjacent_range_tombstones_with_rows) { builder.set_compressor_params(compression_parameters::no_compression()); schema_ptr s = builder.build(schema_builder::compact_storage::no); - lw_shared_ptr mt = make_lw_shared(s); - auto key = make_dkey(s, {to_bytes("key")}); mutation mut{s, key}; api::timestamp_type ts = write_timestamp; @@ -4336,12 +4313,8 @@ SEASTAR_THREAD_TEST_CASE(test_write_adjacent_range_tombstones_with_rows) { clustering_key ckey = clustering_key::from_deeply_exploded(*s, {"aaa", "ccc", "ccc"}); mut.partition().apply_insert(*s, ckey, ts); } - mt->apply(mut); - tmpdir tmp = write_and_compare_sstables(s, mt, table_name); - auto written_sst = validate_read(s, tmp.path(), {mut}); - validate_stats_metadata(s, written_sst, table_name, false); - check_min_max_column_names(written_sst, {"aaa"}, {"aaa"}); + write_mut_and_validate(s, table_name, mut, {"aaa"}, {"aaa"}); } SEASTAR_THREAD_TEST_CASE(test_write_range_tombstone_same_start_with_row) { @@ -4375,13 +4348,7 @@ SEASTAR_THREAD_TEST_CASE(test_write_range_tombstone_same_start_with_row) { mut.partition().apply_insert(*s, ckey, ts); } - lw_shared_ptr mt = make_lw_shared(s); - mt->apply(mut); - - tmpdir tmp = write_and_compare_sstables(s, mt, table_name); - auto written_sst = validate_read(s, tmp.path(), {mut}); - validate_stats_metadata(s, written_sst, table_name, false); - check_min_max_column_names(written_sst, {"aaa", "bbb"}, {"aaa"}); + write_mut_and_validate(s, table_name, mut, {"aaa", "bbb"}, {"aaa"}); } SEASTAR_THREAD_TEST_CASE(test_write_range_tombstone_same_end_with_row) { @@ -4415,13 +4382,7 @@ SEASTAR_THREAD_TEST_CASE(test_write_range_tombstone_same_end_with_row) { mut.partition().apply_insert(*s, ckey, ts); } - lw_shared_ptr mt = make_lw_shared(s); - mt->apply(mut); - - tmpdir tmp = write_and_compare_sstables(s, mt, table_name); - auto written_sst = validate_read(s, tmp.path(), {mut}); - validate_stats_metadata(s, written_sst, table_name, false); - check_min_max_column_names(written_sst, {"aaa"}, {"aaa", "bbb"}); + write_mut_and_validate(s, table_name, mut, {"aaa"}, {"aaa", "bbb"}); } SEASTAR_THREAD_TEST_CASE(test_write_overlapped_start_range_tombstones) { @@ -4469,13 +4430,7 @@ SEASTAR_THREAD_TEST_CASE(test_write_overlapped_start_range_tombstones) { mut2.partition().apply_delete(*s, std::move(rt)); } - lw_shared_ptr mt1 = make_lw_shared(s); - lw_shared_ptr mt2 = make_lw_shared(s); - - mt1->apply(mut1); - mt2->apply(mut2); - - write_and_compare_sstables(s, mt1, mt2, table_name); + write_muts_and_compare_sstables(s, mut1, mut2, table_name); } SEASTAR_THREAD_TEST_CASE(test_write_two_non_adjacent_range_tombstones) { @@ -4516,13 +4471,7 @@ SEASTAR_THREAD_TEST_CASE(test_write_two_non_adjacent_range_tombstones) { mut.partition().apply_delete(*s, std::move(rt)); } - lw_shared_ptr mt = make_lw_shared(s); - mt->apply(mut); - - tmpdir tmp = write_and_compare_sstables(s, mt, table_name); - auto written_sst = validate_read(s, tmp.path(), {mut}); - validate_stats_metadata(s, written_sst, table_name, false); - check_min_max_column_names(written_sst, {"aaa"}, {"aaa"}); + write_mut_and_validate(s, table_name, mut, {"aaa"}, {"aaa"}); } // The resulting files are supposed to be identical to the files @@ -4564,12 +4513,7 @@ SEASTAR_THREAD_TEST_CASE(test_write_overlapped_range_tombstones) { mut2.partition().apply_delete(*s, std::move(rt)); } - lw_shared_ptr mt1 = make_lw_shared(s); - lw_shared_ptr mt2 = make_lw_shared(s); - mt1->apply(mut1); - mt2->apply(mut2); - - write_and_compare_sstables(s, mt1, mt2, table_name); + write_muts_and_compare_sstables(s, mut1, mut2, table_name); } static sstring get_read_index_test_path(sstring table_name) { @@ -4816,8 +4760,6 @@ SEASTAR_THREAD_TEST_CASE(test_dead_row_marker) { builder.set_compressor_params(compression_parameters::no_compression()); schema_ptr s = builder.build(schema_builder::compact_storage::no); - lw_shared_ptr mt = make_lw_shared(s); - auto key = partition_key::from_deeply_exploded(*s, { 1 }); mutation mut{s, key}; mut.set_static_cell("st", data_value{1135}, ts); @@ -4828,9 +4770,7 @@ SEASTAR_THREAD_TEST_CASE(test_dead_row_marker) { mut.set_cell(ckey, "rc", data_value{7777}, ts); - mt->apply(mut); - - write_and_compare_sstables(s, mt, table_name); + write_mut_and_compare_sstables(s, mut, table_name); } SEASTAR_THREAD_TEST_CASE(test_shadowable_deletion) { @@ -4850,14 +4790,11 @@ SEASTAR_THREAD_TEST_CASE(test_shadowable_deletion) { builder.set_compressor_params(compression_parameters::no_compression()); schema_ptr s = builder.build(schema_builder::compact_storage::no); - lw_shared_ptr mt = make_lw_shared(s); - clustering_key ckey = clustering_key::from_deeply_exploded(*s, { 1 }); mutation mut1{s, partition_key::from_deeply_exploded(*s, {1})}; { auto& clustered_row = mut1.partition().clustered_row(*s, ckey); clustered_row.apply(row_marker{api::timestamp_type{1540230880370422}}); - mt->apply(mut1); } mutation mut2{s, partition_key::from_deeply_exploded(*s, {0})}; @@ -4867,11 +4804,9 @@ SEASTAR_THREAD_TEST_CASE(test_shadowable_deletion) { gc_clock::time_point tp {gc_clock::duration(1540230880)}; clustered_row.apply(row_marker{api::timestamp_type{ts}}); clustered_row.apply(shadowable_tombstone(ts, tp)); - mt->apply(mut2); } - tmpdir tmp = write_and_compare_sstables(s, mt, table_name); - validate_read(s, tmp.path(), {mut1, mut2}); + write_mut_and_validate(s, table_name, {mut1, mut2}, validate_stats_metadata::no); } SEASTAR_THREAD_TEST_CASE(test_regular_and_shadowable_deletion) { @@ -4919,8 +4854,7 @@ SEASTAR_THREAD_TEST_CASE(test_regular_and_shadowable_deletion) { mt->apply(mut2); } - tmpdir tmp = write_and_compare_sstables(s, mt, table_name); - validate_read(s, tmp.path(), {mut1, mut2}); + write_mut_and_validate(s, table_name, {mut1, mut2}, validate_stats_metadata::no); } SEASTAR_THREAD_TEST_CASE(test_write_static_row_with_missing_columns) { @@ -4936,8 +4870,6 @@ SEASTAR_THREAD_TEST_CASE(test_write_static_row_with_missing_columns) { builder.set_compressor_params(compression_parameters::no_compression()); schema_ptr s = builder.build(schema_builder::compact_storage::no); - lw_shared_ptr mt = make_lw_shared(s); - // INSERT INTO static_row (pk, ck, st1, rc) VALUES (0, 1, 2, 3); auto key = partition_key::from_deeply_exploded(*s, {0}); mutation mut{s, key}; @@ -4945,10 +4877,8 @@ SEASTAR_THREAD_TEST_CASE(test_write_static_row_with_missing_columns) { mut.partition().apply_insert(*s, ckey, write_timestamp); mut.set_static_cell("st1", data_value{2}, write_timestamp); mut.set_cell(ckey, "rc", data_value{3}, write_timestamp); - mt->apply(mut); - tmpdir tmp = write_and_compare_sstables(s, mt, table_name); - validate_read(s, tmp.path(), {mut}); + write_mut_and_validate(s, table_name, mut, validate_stats_metadata::no); } SEASTAR_THREAD_TEST_CASE(test_write_interleaved_atomic_and_collection_columns) { @@ -4969,8 +4899,6 @@ SEASTAR_THREAD_TEST_CASE(test_write_interleaved_atomic_and_collection_columns) { builder.set_compressor_params(compression_parameters::no_compression()); schema_ptr s = builder.build(schema_builder::compact_storage::no); - lw_shared_ptr mt = make_lw_shared(s); - // INSERT INTO interleaved_atomic_and_collection_columns (pk, ck, rc1, rc4, rc5) // VALUES (0, 1, 2, {3, 4}, 5) USING TIMESTAMP 1525385507816568; auto key = partition_key::from_deeply_exploded(*s, {0}); @@ -4986,10 +4914,8 @@ SEASTAR_THREAD_TEST_CASE(test_write_interleaved_atomic_and_collection_columns) { mut.set_clustered_cell(ckey, *s->get_column_definition("rc4"), set_values.serialize(*set_of_ints_type)); mut.set_cell(ckey, "rc5", data_value{5}, write_timestamp); - mt->apply(mut); - tmpdir tmp = write_and_compare_sstables(s, mt, table_name); - validate_read(s, tmp.path(), {mut}); + write_mut_and_validate(s, table_name, mut, validate_stats_metadata::no); } SEASTAR_THREAD_TEST_CASE(test_write_static_interleaved_atomic_and_collection_columns) { @@ -5011,8 +4937,6 @@ SEASTAR_THREAD_TEST_CASE(test_write_static_interleaved_atomic_and_collection_col builder.set_compressor_params(compression_parameters::no_compression()); schema_ptr s = builder.build(schema_builder::compact_storage::no); - lw_shared_ptr mt = make_lw_shared(s); - // INSERT INTO static_interleaved_atomic_and_collection_columns (pk, ck, st1, st4, st5) // VALUES (0, 1, 2, {3, 4}, 5) USING TIMESTAMP 1525385507816568; auto key = partition_key::from_deeply_exploded(*s, {0}); @@ -5028,10 +4952,8 @@ SEASTAR_THREAD_TEST_CASE(test_write_static_interleaved_atomic_and_collection_col mut.set_static_cell(*s->get_column_definition("st4"), set_values.serialize(*set_of_ints_type)); mut.set_static_cell("st5", data_value{5}, write_timestamp); - mt->apply(mut); - tmpdir tmp = write_and_compare_sstables(s, mt, table_name); - validate_read(s, tmp.path(), {mut}); + write_mut_and_validate(s, table_name, mut, validate_stats_metadata::no); } SEASTAR_THREAD_TEST_CASE(test_write_empty_static_row) { @@ -5046,7 +4968,6 @@ SEASTAR_THREAD_TEST_CASE(test_write_empty_static_row) { builder.set_compressor_params(compression_parameters::no_compression()); schema_ptr s = builder.build(schema_builder::compact_storage::no); - lw_shared_ptr mt = make_lw_shared(s); api::timestamp_type ts = write_timestamp; // INSERT INTO empty_static_row (pk, ck, rc) VALUES ( 0, 1, 2) USING TIMESTAMP 1525385507816568; @@ -5055,7 +4976,6 @@ SEASTAR_THREAD_TEST_CASE(test_write_empty_static_row) { clustering_key ckey = clustering_key::from_deeply_exploded(*s, { 1 }); mut1.partition().apply_insert(*s, ckey, ts); mut1.set_cell(ckey, "rc", data_value{2}, ts); - mt->apply(mut1); ts += 10; @@ -5065,10 +4985,15 @@ SEASTAR_THREAD_TEST_CASE(test_write_empty_static_row) { mut2.partition().apply_insert(*s, ckey, ts); mut2.set_static_cell("st", data_value{2}, ts); mut2.set_cell(ckey, "rc", data_value{3}, ts); - mt->apply(mut2); - tmpdir tmp = write_and_compare_sstables(s, mt, table_name); - validate_read(s, tmp.path(), {mut2, mut1}); // Mutations are re-ordered according to decorated_key order + for (auto version : test_sstable_versions) { + lw_shared_ptr mt = make_lw_shared(s); + mt->apply(mut1); + mt->apply(mut2); + + tmpdir tmp = write_and_compare_sstables(s, mt, table_name, version); + validate_read(s, tmp.path(), {mut2, mut1}, version); // Mutations are re-ordered according to decorated_key order + } } SEASTAR_THREAD_TEST_CASE(test_read_missing_summary) { @@ -5116,6 +5041,7 @@ SEASTAR_THREAD_TEST_CASE(test_sstable_reader_on_unknown_column) { partition.set_cell(ckey, "val1", data_value{100 + i}, write_timestamp); partition.set_cell(ckey, "val2", data_value{200 + i}, write_timestamp); }; + for (auto version : test_sstable_versions) { auto mt = make_lw_shared(write_schema); mt->apply(partition); for (auto index_block_size : {1, 128, 64*1024}) { @@ -5126,7 +5052,7 @@ SEASTAR_THREAD_TEST_CASE(test_sstable_reader_on_unknown_column) { auto sst = env.make_sstable(write_schema, dir.path().string(), 1 /* generation */, - sstable_version_types::mc, + version, sstables::sstable::format_types::big); sst->write_components(mt->make_flat_reader(write_schema, tests::make_permit()), 1, write_schema, cfg, mt->get_encoding_stats()).get(); sst->load().get(); @@ -5142,6 +5068,7 @@ SEASTAR_THREAD_TEST_CASE(test_sstable_reader_on_unknown_column) { std::exception, message_equals("Column val1 missing in current schema in sstable " + sst->get_filename())); } + } } namespace { @@ -5188,7 +5115,7 @@ struct large_row_handler : public db::large_data_handler { } static void test_sstable_write_large_row_f(schema_ptr s, memtable& mt, const partition_key& pk, - std::vector expected, uint64_t threshold) { + std::vector expected, uint64_t threshold, sstable_version_types version) { unsigned i = 0; auto f = [&i, &expected, &pk, &threshold](const schema& s, const sstables::key& partition_key, const clustering_key_prefix* clustering_key, uint64_t row_size) { @@ -5209,7 +5136,7 @@ static void test_sstable_write_large_row_f(schema_ptr s, memtable& mt, const par auto env = test_env(manager); tmpdir dir; auto sst = env.make_sstable( - s, dir.path().string(), 1 /* generation */, sstable_version_types::mc, sstables::sstable::format_types::big); + s, dir.path().string(), 1 /* generation */, version, sstables::sstable::format_types::big); // The test provides thresholds values for the large row handler. Whether the handler gets // trigger depends on the size of rows after they are written in the MC format and that size @@ -5232,15 +5159,16 @@ SEASTAR_THREAD_TEST_CASE(test_sstable_write_large_row) { auto ck2 = s.make_ckey("cv2"); s.add_row(partition, ck2, "foo bar"); - + for (auto version : test_sstable_versions) { auto mt = make_lw_shared(s.schema()); mt->apply(partition); - test_sstable_write_large_row_f(s.schema(), *mt, pk, {nullptr, &ck1, &ck2}, 21); - test_sstable_write_large_row_f(s.schema(), *mt, pk, {nullptr, &ck2}, 22); + test_sstable_write_large_row_f(s.schema(), *mt, pk, {nullptr, &ck1, &ck2}, 21, version); + test_sstable_write_large_row_f(s.schema(), *mt, pk, {nullptr, &ck2}, 22, version); + } } -static void test_sstable_log_too_many_rows_f(int rows, uint64_t threshold, bool expected) { +static void test_sstable_log_too_many_rows_f(int rows, uint64_t threshold, bool expected, sstable_version_types version) { simple_schema s; mutation p = s.new_mutation("pv"); const partition_key& pk = p.key(); @@ -5265,7 +5193,7 @@ static void test_sstable_log_too_many_rows_f(int rows, uint64_t threshold, bool sstables_manager manager(handler, test_db_config, test_feature_service); auto env = test_env(manager); tmpdir dir; - auto sst = env.make_sstable(sc, dir.path().string(), 1, sstable_version_types::mc, sstables::sstable::format_types::big); + auto sst = env.make_sstable(sc, dir.path().string(), 1, version, sstables::sstable::format_types::big); sst->write_components(mt->make_flat_reader(sc, tests::make_permit()), 1, sc, test_sstables_manager.configure_writer(), encoding_stats{}).get(); BOOST_REQUIRE_EQUAL(logged, expected); @@ -5281,11 +5209,13 @@ SEASTAR_THREAD_TEST_CASE(test_sstable_log_too_many_rows) { // This test creates a sstable with a given number of rows and test it against a // compaction_rows_count_warning_threshold. A warning is triggered when the number of rows // exceeds the threshold. - test_sstable_log_too_many_rows_f(random, 0, true); - test_sstable_log_too_many_rows_f(random, (random - 1), true); - test_sstable_log_too_many_rows_f(random, random, false); - test_sstable_log_too_many_rows_f(random, (random + 1), false); - test_sstable_log_too_many_rows_f((random + 1), random, true); + for (auto version : test_sstable_versions) { + test_sstable_log_too_many_rows_f(random, 0, true, version); + test_sstable_log_too_many_rows_f(random, (random - 1), true, version); + test_sstable_log_too_many_rows_f(random, random, false, version); + test_sstable_log_too_many_rows_f(random, (random + 1), false, version); + test_sstable_log_too_many_rows_f((random + 1), random, true, version); + } } // The following test runs on test/resource/sstables/3.x/uncompressed/legacy_udt_in_collection From 8e0e2c8a4869beabde75c3611b0ae674fb11668b Mon Sep 17 00:00:00 2001 From: Benny Halevy Date: Tue, 28 Jul 2020 08:39:14 +0300 Subject: [PATCH 17/27] database: add set_format_by_config This is required for test applications that may select a sstable format different than the default mc format, like perf_fast_forward. These apps don't use the gossip-based sstables_format_selector to set the format based on the cluster feature and so they need to rely on the db config. Call set_format_by_config in single_node_cql_env::do_with. Signed-off-by: Benny Halevy --- database.cc | 8 ++++++++ database.hh | 1 + test/lib/cql_test_env.cc | 4 ++++ 3 files changed, 13 insertions(+) diff --git a/database.cc b/database.cc index 13793fe268..a60f684e1e 100644 --- a/database.cc +++ b/database.cc @@ -545,6 +545,14 @@ void database::set_format(sstables::sstable_version_types format) { get_system_sstables_manager().set_format(format); } +void database::set_format_by_config() { + if (_cfg.enable_sstables_mc_format()) { + set_format(sstables::sstable_version_types::mc); + } else { + set_format(sstables::sstable_version_types::la); + } +} + database::~database() { _read_concurrency_sem.clear_inactive_reads(); _streaming_concurrency_sem.clear_inactive_reads(); diff --git a/database.hh b/database.hh index c8540ff5cd..3a64850843 100644 --- a/database.hh +++ b/database.hh @@ -1534,6 +1534,7 @@ public: } void set_format(sstables::sstable_version_types format); + void set_format_by_config(); future<> flush_all_memtables(); diff --git a/test/lib/cql_test_env.cc b/test/lib/cql_test_env.cc index b634dee00c..a8a881e56d 100644 --- a/test/lib/cql_test_env.cc +++ b/test/lib/cql_test_env.cc @@ -476,6 +476,10 @@ public: db.stop().get(); }); + db.invoke_on_all([] (database& db) { + db.set_format_by_config(); + }).get(); + auto stop_ms_fd_gossiper = defer([] { gms::get_gossiper().stop().get(); }); From 65239a6e5070791148c770c5bb2cc04f6bd096fa Mon Sep 17 00:00:00 2001 From: Benny Halevy Date: Mon, 20 Jul 2020 11:11:09 +0300 Subject: [PATCH 18/27] config: add enable_sstables_md_format MD format is disabled by default at this point. The option extends enable_sstables_mc_format so that both are needed to be set for supporting the md format. The MD_FORMAT cluster feature will be added in a following patch. Signed-off-by: Benny Halevy --- database.cc | 4 +++- db/config.cc | 1 + db/config.hh | 1 + gms/feature_service.cc | 5 +++++ test/lib/cql_test_env.cc | 1 + test/manual/sstable_scan_footprint_test.cc | 7 ++++++- test/perf/perf_fast_forward.cc | 7 ++++++- 7 files changed, 23 insertions(+), 3 deletions(-) diff --git a/database.cc b/database.cc index a60f684e1e..71d589542f 100644 --- a/database.cc +++ b/database.cc @@ -546,7 +546,9 @@ void database::set_format(sstables::sstable_version_types format) { } void database::set_format_by_config() { - if (_cfg.enable_sstables_mc_format()) { + if (_cfg.enable_sstables_md_format()) { + set_format(sstables::sstable_version_types::md); + } else if (_cfg.enable_sstables_mc_format()) { set_format(sstables::sstable_version_types::mc); } else { set_format(sstables::sstable_version_types::la); diff --git a/db/config.cc b/db/config.cc index b6e8229295..0ef08aaa41 100644 --- a/db/config.cc +++ b/db/config.cc @@ -715,6 +715,7 @@ db::config::config(std::shared_ptr exts) , cpu_scheduler(this, "cpu_scheduler", value_status::Used, true, "Enable cpu scheduling") , view_building(this, "view_building", value_status::Used, true, "Enable view building; should only be set to false when the node is experience issues due to view building") , enable_sstables_mc_format(this, "enable_sstables_mc_format", value_status::Used, true, "Enable SSTables 'mc' format to be used as the default file format") + , enable_sstables_md_format(this, "enable_sstables_md_format", value_status::Used, false, "Enable SSTables 'md' format to be used as the default file format (requires enable_sstables_mc_format)") , enable_dangerous_direct_import_of_cassandra_counters(this, "enable_dangerous_direct_import_of_cassandra_counters", value_status::Used, false, "Only turn this option on if you want to import tables from Cassandra containing counters, and you are SURE that no counters in that table were created in a version earlier than Cassandra 2.1." " It is not enough to have ever since upgraded to newer versions of Cassandra. If you EVER used a version earlier than 2.1 in the cluster where these SSTables come from, DO NOT TURN ON THIS OPTION! You will corrupt your data. You have been warned.") , enable_shard_aware_drivers(this, "enable_shard_aware_drivers", value_status::Used, true, "Enable native transport drivers to use connection-per-shard for better performance") diff --git a/db/config.hh b/db/config.hh index 34603bc6e2..cf6ef386f9 100644 --- a/db/config.hh +++ b/db/config.hh @@ -299,6 +299,7 @@ public: named_value cpu_scheduler; named_value view_building; named_value enable_sstables_mc_format; + named_value enable_sstables_md_format; named_value enable_dangerous_direct_import_of_cassandra_counters; named_value enable_shard_aware_drivers; named_value enable_ipv6_dns_lookup; diff --git a/gms/feature_service.cc b/gms/feature_service.cc index b4106c5b86..4397704782 100644 --- a/gms/feature_service.cc +++ b/gms/feature_service.cc @@ -101,6 +101,11 @@ feature_config feature_config_from_db_config(db::config& cfg, std::set fcfg._disabled_features = std::move(disabled); if (!cfg.enable_sstables_mc_format()) { + if (cfg.enable_sstables_md_format()) { + throw std::runtime_error( + "You must use both enable_sstables_mc_format and enable_sstables_md_format " + "to enable SSTables md format support"); + } fcfg._disabled_features.insert(sstring(gms::features::MC_SSTABLE)); } if (!cfg.enable_user_defined_functions()) { diff --git a/test/lib/cql_test_env.cc b/test/lib/cql_test_env.cc index a8a881e56d..c328a14890 100644 --- a/test/lib/cql_test_env.cc +++ b/test/lib/cql_test_env.cc @@ -59,6 +59,7 @@ #include "service/storage_service.hh" #include "db/system_keyspace.hh" #include "db/system_distributed_keyspace.hh" +#include "db/sstables-format-selector.hh" using namespace std::chrono_literals; diff --git a/test/manual/sstable_scan_footprint_test.cc b/test/manual/sstable_scan_footprint_test.cc index 34edf85145..71626e395b 100644 --- a/test/manual/sstable_scan_footprint_test.cc +++ b/test/manual/sstable_scan_footprint_test.cc @@ -229,10 +229,15 @@ int main(int argc, char** argv) { db_cfg.virtual_dirty_soft_limit(1.0); auto sstable_format_name = app.configuration()["sstable-format"].as(); - if (sstable_format_name == "mc") { + if (sstable_format_name == "md") { db_cfg.enable_sstables_mc_format(true); + db_cfg.enable_sstables_md_format(true); + } else if (sstable_format_name == "mc") { + db_cfg.enable_sstables_mc_format(true); + db_cfg.enable_sstables_md_format(false); } else if (sstable_format_name == "la") { db_cfg.enable_sstables_mc_format(false); + db_cfg.enable_sstables_md_format(false); } else { throw std::runtime_error(format("Unsupported sstable format: {}", sstable_format_name)); } diff --git a/test/perf/perf_fast_forward.cc b/test/perf/perf_fast_forward.cc index 3c57e5dd4c..7285f61f83 100644 --- a/test/perf/perf_fast_forward.cc +++ b/test/perf/perf_fast_forward.cc @@ -1815,10 +1815,15 @@ int main(int argc, char** argv) { db_cfg.virtual_dirty_soft_limit(1.0); // prevent background memtable flushes. auto sstable_format_name = app.configuration()["sstable-format"].as(); - if (sstable_format_name == "mc") { + if (sstable_format_name == "md") { db_cfg.enable_sstables_mc_format(true); + db_cfg.enable_sstables_md_format(true); + } else if (sstable_format_name == "mc") { + db_cfg.enable_sstables_mc_format(true); + db_cfg.enable_sstables_md_format(false); } else if (sstable_format_name == "la") { db_cfg.enable_sstables_mc_format(false); + db_cfg.enable_sstables_md_format(false); } else { throw std::runtime_error(format("Unsupported sstable format: {}", sstable_format_name)); } From e8d7744040a1df4df2e498f8154a25f759c4015c Mon Sep 17 00:00:00 2001 From: Benny Halevy Date: Mon, 27 Jul 2020 20:52:10 +0300 Subject: [PATCH 19/27] features: add MD_SSTABLE_FORMAT cluster feature Signed-off-by: Benny Halevy --- db/sstables-format-selector.cc | 6 ++++-- db/sstables-format-selector.hh | 1 + gms/feature.hh | 1 + gms/feature_service.cc | 7 +++++++ gms/feature_service.hh | 5 +++++ scylla-gdb.py | 1 + 6 files changed, 19 insertions(+), 2 deletions(-) diff --git a/db/sstables-format-selector.cc b/db/sstables-format-selector.cc index d6d6254fdd..ce13664c01 100644 --- a/db/sstables-format-selector.cc +++ b/db/sstables-format-selector.cc @@ -45,8 +45,9 @@ sstables_format_selector::sstables_format_selector(gms::gossiper& g, sharded sstables_format_selector::maybe_select_format(sstables::sstable_version_types new_format) { return with_gate(_sel, [this, new_format] { @@ -75,6 +76,7 @@ future<> sstables_format_selector::start() { assert(this_shard_id() == 0); return read_sstables_format().then([this] { _features.local().cluster_supports_mc_sstable().when_enabled(_mc_feature_listener); + _features.local().cluster_supports_md_sstable().when_enabled(_md_feature_listener); return make_ready_future<>(); }); } diff --git a/db/sstables-format-selector.hh b/db/sstables-format-selector.hh index 30fc3cb0a5..1680c4d9d9 100644 --- a/db/sstables-format-selector.hh +++ b/db/sstables-format-selector.hh @@ -62,6 +62,7 @@ class sstables_format_selector { seastar::gate _sel; feature_enabled_listener _mc_feature_listener; + feature_enabled_listener _md_feature_listener; sstables::sstable_version_types _selected_format = sstables::sstable_version_types::la; future<> select_format(sstables::sstable_version_types new_format); diff --git a/gms/feature.hh b/gms/feature.hh index 4c40d3f552..c4479e9907 100644 --- a/gms/feature.hh +++ b/gms/feature.hh @@ -128,6 +128,7 @@ extern const std::string_view ROLES; extern const std::string_view LA_SSTABLE; extern const std::string_view STREAM_WITH_RPC_STREAM; extern const std::string_view MC_SSTABLE; +extern const std::string_view MD_SSTABLE; extern const std::string_view ROW_LEVEL_REPAIR; extern const std::string_view TRUNCATION_TABLE; extern const std::string_view CORRECT_STATIC_COMPACT_IN_MC; diff --git a/gms/feature_service.cc b/gms/feature_service.cc index 4397704782..9d80eff988 100644 --- a/gms/feature_service.cc +++ b/gms/feature_service.cc @@ -43,6 +43,7 @@ constexpr std::string_view features::ROLES = "ROLES"; constexpr std::string_view features::LA_SSTABLE = "LA_SSTABLE_FORMAT"; constexpr std::string_view features::STREAM_WITH_RPC_STREAM = "STREAM_WITH_RPC_STREAM"; constexpr std::string_view features::MC_SSTABLE = "MC_SSTABLE_FORMAT"; +constexpr std::string_view features::MD_SSTABLE = "MD_SSTABLE_FORMAT"; constexpr std::string_view features::ROW_LEVEL_REPAIR = "ROW_LEVEL_REPAIR"; constexpr std::string_view features::TRUNCATION_TABLE = "TRUNCATION_TABLE"; constexpr std::string_view features::CORRECT_STATIC_COMPACT_IN_MC = "CORRECT_STATIC_COMPACT_IN_MC"; @@ -78,6 +79,7 @@ feature_service::feature_service(feature_config cfg) : _config(cfg) , _roles_feature(*this, features::ROLES) , _stream_with_rpc_stream_feature(*this, features::STREAM_WITH_RPC_STREAM) , _mc_sstable_feature(*this, features::MC_SSTABLE) + , _md_sstable_feature(*this, features::MD_SSTABLE) , _row_level_repair_feature(*this, features::ROW_LEVEL_REPAIR) , _truncation_table(*this, features::TRUNCATION_TABLE) , _correct_static_compact_in_mc(*this, features::CORRECT_STATIC_COMPACT_IN_MC) @@ -108,6 +110,9 @@ feature_config feature_config_from_db_config(db::config& cfg, std::set } fcfg._disabled_features.insert(sstring(gms::features::MC_SSTABLE)); } + if (!cfg.enable_sstables_md_format()) { + fcfg._disabled_features.insert(sstring(gms::features::MD_SSTABLE)); + } if (!cfg.enable_user_defined_functions()) { fcfg._disabled_features.insert(sstring(gms::features::UDF)); } else { @@ -182,6 +187,7 @@ std::set feature_service::known_feature_set() { gms::features::PER_TABLE_CACHING, gms::features::LWT, gms::features::MC_SSTABLE, + gms::features::MD_SSTABLE, gms::features::UDF, gms::features::CDC, }; @@ -261,6 +267,7 @@ void feature_service::enable(const std::set& list) { std::ref(_roles_feature), std::ref(_stream_with_rpc_stream_feature), std::ref(_mc_sstable_feature), + std::ref(_md_sstable_feature), std::ref(_row_level_repair_feature), std::ref(_truncation_table), std::ref(_correct_static_compact_in_mc), diff --git a/gms/feature_service.hh b/gms/feature_service.hh index cde2005aeb..a544de9a38 100644 --- a/gms/feature_service.hh +++ b/gms/feature_service.hh @@ -90,6 +90,7 @@ private: gms::feature _roles_feature; gms::feature _stream_with_rpc_stream_feature; gms::feature _mc_sstable_feature; + gms::feature _md_sstable_feature; gms::feature _row_level_repair_feature; gms::feature _truncation_table; gms::feature _correct_static_compact_in_mc; @@ -165,6 +166,10 @@ public: return _mc_sstable_feature; } + const feature& cluster_supports_md_sstable() const { + return _md_sstable_feature; + } + const feature& cluster_supports_cdc() const { return _cdc_feature; } diff --git a/scylla-gdb.py b/scylla-gdb.py index 7570ee95ec..7999efd4b8 100644 --- a/scylla-gdb.py +++ b/scylla-gdb.py @@ -3697,6 +3697,7 @@ class scylla_features(gdb.Command): "ROLES": true "LA_SSTABLE_FORMAT": true "MC_SSTABLE_FORMAT": true + "MD_SSTABLE_FORMAT": true "STREAM_WITH_RPC_STREAM": true "ROW_LEVEL_REPAIR": true "TRUNCATION_TABLE": true From 2fed3f472cb7d8ca12434d9e74ddf46a700c770d Mon Sep 17 00:00:00 2001 From: Benny Halevy Date: Mon, 20 Jul 2020 17:18:09 +0300 Subject: [PATCH 20/27] table: filter_sstable_for_reader: refactor clustering filtering conditional expression We're about to drop `filtering_broken` in a future patche when clustering filtering can be supported for md-format sstables. Signed-off-by: Benny Halevy --- table.cc | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/table.cc b/table.cc index 54d781855b..7a138b80b2 100644 --- a/table.cc +++ b/table.cc @@ -127,13 +127,17 @@ filter_sstable_for_reader(std::vector&& sstables, colu }; sstables.erase(boost::remove_if(sstables, sstable_has_not_key), sstables.end()); + // no clustering filtering is applied if schema defines no clustering key or + // compaction strategy thinks it will not benefit from such an optimization. + if (!schema->clustering_key_size() || !cf.get_compaction_strategy().use_clustering_key_filter()) { + return sstables; + } + // FIXME: Workaround for https://github.com/scylladb/scylla/issues/3552 // and https://github.com/scylladb/scylla/issues/3553 const bool filtering_broken = true; - // no clustering filtering is applied if schema defines no clustering key or - // compaction strategy thinks it will not benefit from such an optimization. - if (filtering_broken || !schema->clustering_key_size() || !cf.get_compaction_strategy().use_clustering_key_filter()) { + if (filtering_broken) { return sstables; } ::cf_stats* stats = cf.cf_stats(); From 2a57ec8c3db3b5a6cb456b1ba94026f8b95d35b4 Mon Sep 17 00:00:00 2001 From: Benny Halevy Date: Thu, 21 May 2020 15:06:19 +0300 Subject: [PATCH 21/27] table: filter_sstable_for_reader: do not filter if static column is requested Static rows aren't reflected in the sstable min/max clustering keys metadata. Since we don't have any indication in the metadata that the sstable stores static rows, we must read all sstables if a static column is requested. Refs #3553 Signed-off-by: Benny Halevy --- table.cc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/table.cc b/table.cc index 7a138b80b2..553098acd2 100644 --- a/table.cc +++ b/table.cc @@ -128,8 +128,9 @@ filter_sstable_for_reader(std::vector&& sstables, colu sstables.erase(boost::remove_if(sstables, sstable_has_not_key), sstables.end()); // no clustering filtering is applied if schema defines no clustering key or - // compaction strategy thinks it will not benefit from such an optimization. - if (!schema->clustering_key_size() || !cf.get_compaction_strategy().use_clustering_key_filter()) { + // compaction strategy thinks it will not benefit from such an optimization, + // or the partition_slice includes static columns. + if (!schema->clustering_key_size() || !cf.get_compaction_strategy().use_clustering_key_filter() || slice.static_columns.size()) { return sstables; } From 90d0fea7dfbc3e484885d817530ef4b97638f88f Mon Sep 17 00:00:00 2001 From: Benny Halevy Date: Tue, 21 Jul 2020 10:29:42 +0300 Subject: [PATCH 22/27] table: filter_sstable_for_reader: include non-scylla sstables with tombstones Move contains_rows from table code to sstable::may_contain_rows since its implementation now has too specific knowledge of sstable internals. Signed-off-by: Benny Halevy --- sstables/sstables.cc | 11 +++++++++++ table.cc | 23 ++++------------------- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/sstables/sstables.cc b/sstables/sstables.cc index 74d5ea12de..0fb00b4b13 100644 --- a/sstables/sstables.cc +++ b/sstables/sstables.cc @@ -2233,6 +2233,17 @@ bool sstable::may_contain_rows(const std::vectorclustering_key_size() || !clustering_components_size) { return true; diff --git a/table.cc b/table.cc index 553098acd2..7464afd0c3 100644 --- a/table.cc +++ b/table.cc @@ -40,8 +40,6 @@ #include "query-result-writer.hh" #include "db/view/view.hh" #include -#include -#include #include #include #include "utils/error_injection.hh" @@ -158,23 +156,10 @@ filter_sstable_for_reader(std::vector&& sstables, colu return {}; } - int64_t min_timestamp = std::numeric_limits::max(); - auto sstable_has_clustering_key = [&min_timestamp, &schema, &ranges] (const sstables::shared_sstable& sst) { - if (!sst->may_contain_rows(ranges)) { - return false; // ordered after sstables that contain clustering rows. - } else { - min_timestamp = std::min(min_timestamp, sst->get_stats_metadata().min_timestamp); - return true; - } - }; - auto sstable_has_relevant_tombstone = [&min_timestamp] (const sstables::shared_sstable& sst) { - const auto& stats = sst->get_stats_metadata(); - // re-add sstable as candidate if it contains a tombstone that may cover a row in an included sstable. - return (stats.max_timestamp > min_timestamp && stats.estimated_tombstone_drop_time.bin.size()); - }; - auto skipped = std::partition(sstables.begin(), sstables.end(), sstable_has_clustering_key); - auto actually_skipped = std::partition(skipped, sstables.end(), sstable_has_relevant_tombstone); - sstables.erase(actually_skipped, sstables.end()); + auto skipped = std::partition(sstables.begin(), sstables.end(), [&ranges] (const sstables::shared_sstable& sst) { + return sst->may_contain_rows(ranges); + }); + sstables.erase(skipped, sstables.end()); stats->surviving_sstables_after_clustering_filter += sstables.size(); return sstables; From a672747da3481b322682f2267546edca83ad9d2c Mon Sep 17 00:00:00 2001 From: Benny Halevy Date: Thu, 21 May 2020 16:46:39 +0300 Subject: [PATCH 23/27] table: filter_sstable_for_reader: adjust to md-format With the md sstable format, min/max column names in the metadata now track clustering rows (with or without row tombstones), range tombstones, and partition tombstones (that are reflected with empty min/max column names - indicating the full range). As such, min and max column names may be of different lengths due to range tombstones and potentially short clustering key prefixes with compact storage, so the current matching algorithm must be changed to take this into account. To determine if a slice range overlaps the min/max range we are using position_range::overlaps. sstable::clustering_components_ranges was renamed to position_range as it now holds a single position_range rather than a vector of bytes_view ranges. Signed-off-by: Benny Halevy --- sstables/sstables.cc | 46 +++++++++++++++--------------- sstables/sstables.hh | 10 +++---- table.cc | 66 +------------------------------------------- 3 files changed, 30 insertions(+), 92 deletions(-) diff --git a/sstables/sstables.cc b/sstables/sstables.cc index 0fb00b4b13..29d577f626 100644 --- a/sstables/sstables.cc +++ b/sstables/sstables.cc @@ -1205,19 +1205,29 @@ void sstable::validate_max_local_deletion_time() { } } -void sstable::set_clustering_components_ranges() { +void sstable::set_position_range() { if (!_schema->clustering_key_size()) { return; } - auto& min_column_names = get_stats_metadata().min_column_names.elements; - auto& max_column_names = get_stats_metadata().max_column_names.elements; - auto s = std::min(min_column_names.size(), max_column_names.size()); - _clustering_components_ranges.reserve(s); - for (auto i = 0U; i < s; i++) { - auto r = nonwrapping_range({{ min_column_names[i].value, true }}, {{ max_column_names[i].value, true }}); - _clustering_components_ranges.push_back(std::move(r)); + auto& min_elements = get_stats_metadata().min_column_names.elements; + auto& max_elements = get_stats_metadata().max_column_names.elements; + + if (min_elements.empty() && max_elements.empty()) { + return; } + + auto pip = [] (const utils::chunked_vector>& column_names, bound_kind kind) { + std::vector key_bytes; + key_bytes.reserve(column_names.size()); + for (auto& value : column_names) { + key_bytes.emplace_back(bytes_view(value)); + } + auto ckp = clustering_key_prefix(std::move(key_bytes)); + return position_in_partition(position_in_partition::range_tag_t(), kind, std::move(ckp)); + }; + + _position_range = position_range(pip(min_elements, bound_kind::incl_start), pip(max_elements, bound_kind::incl_end)); } double sstable::estimate_droppable_tombstone_ratio(gc_clock::time_point gc_before) const { @@ -1329,7 +1339,7 @@ future<> sstable::update_info_for_opened_data() { } return make_ready_future<>(); }).then([this] { - this->set_clustering_components_ranges(); + this->set_position_range(); this->set_first_and_last_keys(); _run_identifier = _components->scylla_metadata->get_optional_run_identifier().value_or(utils::make_random_uuid()); @@ -2228,7 +2238,7 @@ void sstable::update_stats_on_end_of_stream() } } -bool sstable::may_contain_rows(const std::vector>>& ranges) { +bool sstable::may_contain_rows(const query::clustering_row_ranges& ranges) const { if (_version < sstables::sstable_version_types::md) { return true; } @@ -2244,18 +2254,10 @@ bool sstable::may_contain_rows(const std::vectorclustering_key_size() || !clustering_components_size) { - return true; - } - - auto& clustering_key_types = _schema->clustering_key_type()->types(); - return std::ranges::any_of(ranges, [this, clustering_components_size, &clustering_key_types] (const std::vector>& range) { - auto s = std::min(range.size(), clustering_components_size); - return std::ranges::all_of(boost::irange(0, s), [this, &clustering_key_types, &range] (unsigned i) { - auto& type = clustering_key_types[i]; - return range[i].is_full() || range[i].overlaps(_clustering_components_ranges[i], type->as_tri_comparator()); - }); + return std::ranges::any_of(ranges, [this] (const query::clustering_range& range) { + return _position_range.overlaps(*_schema, + position_in_partition_view::for_range_start(range), + position_in_partition_view::for_range_end(range)); }); } diff --git a/sstables/sstables.hh b/sstables/sstables.hh index c669320b76..b793b9f5a6 100644 --- a/sstables/sstables.hh +++ b/sstables/sstables.hh @@ -61,6 +61,7 @@ #include "utils/observable.hh" #include "sstables/shareable_components.hh" #include "sstables/open_info.hh" +#include "query-request.hh" #include #include @@ -484,7 +485,7 @@ private: uint64_t _filter_file_size = 0; uint64_t _bytes_on_disk = 0; db_clock::time_point _data_file_write_time; - std::vector> _clustering_components_ranges; + position_range _position_range = position_range::all_clustered_rows(); std::vector _shards; std::optional _first; std::optional _last; @@ -615,11 +616,10 @@ private: void set_first_and_last_keys(); - // Create one range for each clustering component of this sstable. - // Each range stores min and max value for that specific component. + // Create a position range based on the min/max_column_names metadata of this sstable. // It does nothing if schema defines no clustering key, and it's supposed // to be called when loading an existing sstable or after writing a new one. - void set_clustering_components_ranges(); + void set_position_range(); future<> create_data(); @@ -840,7 +840,7 @@ public: } // Return true if this sstable possibly stores clustering row(s) specified by ranges. - bool may_contain_rows(const std::vector>>& ranges); + bool may_contain_rows(const query::clustering_row_ranges& ranges) const; // Allow the test cases from sstable_test.cc to test private methods. We use // a placeholder to avoid cluttering this class too much. The sstable_test class diff --git a/table.cc b/table.cc index 7464afd0c3..1b744f5d78 100644 --- a/table.cc +++ b/table.cc @@ -52,66 +52,6 @@ static seastar::metrics::label keyspace_label("ks"); using namespace std::chrono_literals; -// Stores ranges for all components of the same clustering key, index 0 referring to component -// range 0, and so on. -using ck_filter_clustering_key_components = std::vector>; -// Stores an entry for each clustering key range specified by the filter. -using ck_filter_clustering_key_ranges = std::vector; - -// Used to split a clustering key range into a range for each component. -// If a range in ck_filtering_all_ranges is composite, a range will be created -// for each component. If it's not composite, a single range is created. -// This split is needed to check for overlap in each component individually. -static ck_filter_clustering_key_ranges -ranges_for_clustering_key_filter(const schema_ptr& schema, const query::clustering_row_ranges& ck_filtering_all_ranges) { - ck_filter_clustering_key_ranges ranges; - - for (auto& r : ck_filtering_all_ranges) { - // this vector stores a range for each component of a key, only one if not composite. - ck_filter_clustering_key_components composite_ranges; - - if (r.is_full()) { - ranges.push_back({ nonwrapping_range::make_open_ended_both_sides() }); - continue; - } - auto start = r.start() ? r.start()->value().components() : clustering_key_prefix::make_empty().components(); - auto end = r.end() ? r.end()->value().components() : clustering_key_prefix::make_empty().components(); - auto start_it = start.begin(); - auto end_it = end.begin(); - - // This test is enough because equal bounds in nonwrapping_range are inclusive. - auto is_singular = [&schema] (const auto& type_it, const bytes_view& b1, const bytes_view& b2) { - if (type_it == schema->clustering_key_type()->types().end()) { - throw std::runtime_error(format("clustering key filter passed more components than defined in schema of {}.{}", - schema->ks_name(), schema->cf_name())); - } - return (*type_it)->compare(b1, b2) == 0; - }; - auto type_it = schema->clustering_key_type()->types().begin(); - composite_ranges.reserve(schema->clustering_key_size()); - - // the rule is to ignore any component cn if another component ck (k < n) is not if the form [v, v]. - // If we have [v1, v1], [v2, v2], ... {vl3, vr3}, .... - // then we generate [v1, v1], [v2, v2], ... {vl3, vr3}. Where { = '(' or '[', etc. - while (start_it != start.end() && end_it != end.end() && is_singular(type_it++, *start_it, *end_it)) { - composite_ranges.push_back(nonwrapping_range({{ std::move(*start_it++), true }}, - {{ std::move(*end_it++), true }})); - } - // handle a single non-singular tail element, if present - if (start_it != start.end() && end_it != end.end()) { - composite_ranges.push_back(nonwrapping_range({{ std::move(*start_it), r.start()->is_inclusive() }}, - {{ std::move(*end_it), r.end()->is_inclusive() }})); - } else if (start_it != start.end()) { - composite_ranges.push_back(nonwrapping_range({{ std::move(*start_it), r.start()->is_inclusive() }}, {})); - } else if (end_it != end.end()) { - composite_ranges.push_back(nonwrapping_range({}, {{ std::move(*end_it), r.end()->is_inclusive() }})); - } - - ranges.push_back(std::move(composite_ranges)); - } - return ranges; -} - // Filter out sstables for reader using bloom filter and sstable metadata that keeps track // of a range for each clustering component. static std::vector @@ -151,12 +91,8 @@ filter_sstable_for_reader(std::vector&& sstables, colu stats->surviving_sstables_after_clustering_filter += sstables.size(); return sstables; } - auto ranges = ranges_for_clustering_key_filter(schema, ck_filtering_all_ranges); - if (ranges.empty()) { - return {}; - } - auto skipped = std::partition(sstables.begin(), sstables.end(), [&ranges] (const sstables::shared_sstable& sst) { + auto skipped = std::partition(sstables.begin(), sstables.end(), [&ranges = ck_filtering_all_ranges] (const sstables::shared_sstable& sst) { return sst->may_contain_rows(ranges); }); sstables.erase(skipped, sstables.end()); From ab67629ea60577836d4e78683ae69406b9790f15 Mon Sep 17 00:00:00 2001 From: Benny Halevy Date: Thu, 23 Jul 2020 20:30:01 +0300 Subject: [PATCH 24/27] table: create_single_key_sstable_reader: emit partition_start/end for empty filtered results To prevent https://github.com/scylladb/scylla/issues/3552 we want to ensure that in any case that the partition exists in any sstable, we emit partition_start/end, even when returning no rows. In the first filtering pass, filter_sstable_for_reader_by_pk filters the input sstables based on the partition key, and num_sstables is set the size of the sstables list after the first filtering pass. An empty sstables list at this stage means there are indeed no sstables with the required partition so returning an empty result will leave the cache in the desired state. Otherwise, we filter again, using filter_sstable_for_reader_by_ck, and examine the list of the remaining readers. If num_readers != num_sstables, we know that some sstables were filterd by clustering key, so we append a flat_mutation_reader_from_mutations to the list of readers and return a combined reader as before. This will ensure that we will always have a partition_start/end mutations for the queried partition, even if the filtered readers emit no rows. Signed-off-by: Benny Halevy --- table.cc | 40 +++++++++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/table.cc b/table.cc index 1b744f5d78..109ac8e3bb 100644 --- a/table.cc +++ b/table.cc @@ -52,11 +52,10 @@ static seastar::metrics::label keyspace_label("ks"); using namespace std::chrono_literals; -// Filter out sstables for reader using bloom filter and sstable metadata that keeps track -// of a range for each clustering component. +// Filter out sstables for reader using bloom filter static std::vector -filter_sstable_for_reader(std::vector&& sstables, column_family& cf, const schema_ptr& schema, - const dht::partition_range& pr, const sstables::key& key, const query::partition_slice& slice) { +filter_sstable_for_reader_by_pk(std::vector&& sstables, column_family& cf, const schema_ptr& schema, + const dht::partition_range& pr, const sstables::key& key) { const dht::ring_position& pr_key = pr.start()->value(); auto sstable_has_not_key = [&, cmp = dht::ring_position_comparator(*schema)] (const sstables::shared_sstable& sst) { return cmp(pr_key, sst->get_first_decorated_key()) < 0 || @@ -64,7 +63,14 @@ filter_sstable_for_reader(std::vector&& sstables, colu !sst->filter_has_key(key); }; sstables.erase(boost::remove_if(sstables, sstable_has_not_key), sstables.end()); + return sstables; +} +// Filter out sstables for reader using sstable metadata that keeps track +// of a range for each clustering component. +static std::vector +filter_sstable_for_reader_by_ck(std::vector&& sstables, column_family& cf, const schema_ptr& schema, + const query::partition_slice& slice) { // no clustering filtering is applied if schema defines no clustering key or // compaction strategy thinks it will not benefit from such an optimization, // or the partition_slice includes static columns. @@ -195,18 +201,34 @@ create_single_key_sstable_reader(column_family* cf, streamed_mutation::forwarding fwd, mutation_reader::forwarding fwd_mr) { - auto key = sstables::key::from_partition_key(*schema, *pr.start()->value().key()); + auto& pk = pr.start()->value().key(); + auto key = sstables::key::from_partition_key(*schema, *pk); + auto selected_sstables = filter_sstable_for_reader_by_pk(sstables->select(pr), *cf, schema, pr, key); + auto num_sstables = selected_sstables.size(); + if (!num_sstables) { + return make_empty_flat_reader(schema); + } auto readers = boost::copy_range>( - filter_sstable_for_reader(sstables->select(pr), *cf, schema, pr, key, slice) + filter_sstable_for_reader_by_ck(std::move(selected_sstables), *cf, schema, slice) | boost::adaptors::transformed([&] (const sstables::shared_sstable& sstable) { tracing::trace(trace_state, "Reading key {} from sstable {}", pr, seastar::value_of([&sstable] { return sstable->get_filename(); })); return sstable->read_row_flat(schema, permit, pr.start()->value(), slice, pc, trace_state, fwd); }) ); - if (readers.empty()) { - return make_empty_flat_reader(schema); + + // If filter_sstable_for_reader_by_ck filtered any sstable that contains the partition + // we want to emit partition_start/end if no rows were found, + // to prevent https://github.com/scylladb/scylla/issues/3552. + // + // Use `flat_mutation_reader_from_mutations` with an empty mutation to emit + // the partition_start/end pair and append it to the list of readers passed + // to make_combined_reader to ensure partition_start/end are emitted even if + // all sstables actually containing the partition were filtered. + auto num_readers = readers.size(); + if (num_readers != num_sstables) { + readers.push_back(flat_mutation_reader_from_mutations({mutation(schema, *pk)}, slice, fwd)); } - sstable_histogram.add(readers.size()); + sstable_histogram.add(num_readers); return make_combined_reader(schema, std::move(readers), fwd, fwd_mr); } From 7cfca519cbd847ec125cd1bc221a8ea4df934c2e Mon Sep 17 00:00:00 2001 From: Benny Halevy Date: Mon, 20 Jul 2020 12:36:36 +0300 Subject: [PATCH 25/27] table: filter_sstable_for_reader: allow clustering filtering md-format sstables Now that it is safe to filter md format sstable by min/max column names we can remove the `filtering_broken` variable that disabled filtering in 19b76bf75bd8197f1e7aa2327323a0fa90d5d13f to fix #4442. Signed-off-by: Benny Halevy --- table.cc | 7 ------- 1 file changed, 7 deletions(-) diff --git a/table.cc b/table.cc index 109ac8e3bb..d7d41085f9 100644 --- a/table.cc +++ b/table.cc @@ -78,13 +78,6 @@ filter_sstable_for_reader_by_ck(std::vector&& sstables return sstables; } - // FIXME: Workaround for https://github.com/scylladb/scylla/issues/3552 - // and https://github.com/scylladb/scylla/issues/3553 - const bool filtering_broken = true; - - if (filtering_broken) { - return sstables; - } ::cf_stats* stats = cf.cf_stats(); stats->clustering_filter_count++; stats->sstables_checked_by_clustering_filter += sstables.size(); From 0d85ceaf379c45f8662f6e0e6ab012d12ce970a6 Mon Sep 17 00:00:00 2001 From: Benny Halevy Date: Thu, 23 Jul 2020 13:28:38 +0300 Subject: [PATCH 26/27] test: cql_query_test: add test_clustering_filtering unit tests Add unit tests reproducing https://github.com/scylladb/scylla/issues/3552 with clustering-key filtering enabled. enable_sstables_md_format option is set to true as clustering-key filtering is enabled only for md-format sstables. Signed-off-by: Benny Halevy --- test/boost/cql_query_test.cc | 81 ++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/test/boost/cql_query_test.cc b/test/boost/cql_query_test.cc index 3f9eaf5f6b..ac82675f26 100644 --- a/test/boost/cql_query_test.cc +++ b/test/boost/cql_query_test.cc @@ -4653,3 +4653,84 @@ SEASTAR_THREAD_TEST_CASE(test_query_limit) { } }, std::move(cfg)).get(); } + +// reproduces https://github.com/scylladb/scylla/issues/3552 +// when clustering-key filtering is enabled in filter_sstable_for_reader +static future<> test_clustering_filtering_with_compaction_strategy(const std::string_view& cs) { + auto db_config = make_shared(); + db_config->enable_sstables_md_format.set(true); + + return do_with_cql_env_thread([&cs] (cql_test_env& e) { + cquery_nofail(e, format("CREATE TABLE cf(pk text, ck int, v text, PRIMARY KEY(pk, ck)) WITH COMPACTION = {{'class': '{}'}}", cs)); + cquery_nofail(e, "INSERT INTO cf(pk, ck, v) VALUES ('a', 1, 'a1')"); + e.db().invoke_on_all([] (database& db) { return db.flush_all_memtables(); }).get(); + e.db().invoke_on_all([] (database& db) { db.row_cache_tracker().clear(); }).get(); + require_rows(e, "SELECT v FROM cf WHERE pk='a' AND ck=0 ALLOW FILTERING", {}); + require_rows(e, "SELECT v FROM cf", {{T("a1")}}); + }, cql_test_config(db_config)); +} + +SEASTAR_TEST_CASE(test_clustering_filtering) { + static std::array test_compaction_strategies = { + "SizeTieredCompactionStrategy", + "TimeWindowCompactionStrategy", + }; + + return do_for_each(test_compaction_strategies, + test_clustering_filtering_with_compaction_strategy); +} + +static future<> test_clustering_filtering_2_with_compaction_strategy(const std::string_view& cs) { + auto db_config = make_shared(); + db_config->enable_sstables_md_format.set(true); + + return do_with_cql_env_thread([&cs] (cql_test_env& e) { + cquery_nofail(e, format("CREATE TABLE cf(pk text, ck int, v text, PRIMARY KEY(pk, ck)) WITH COMPACTION = {{'class': '{}'}}", cs)); + cquery_nofail(e, "INSERT INTO cf(pk, ck, v) VALUES ('a', 1, 'a1')"); + cquery_nofail(e, "INSERT INTO cf(pk, ck, v) VALUES ('b', 2, 'b2')"); + e.db().invoke_on_all([] (database& db) { return db.flush_all_memtables(); }).get(); + e.db().invoke_on_all([] (database& db) { db.row_cache_tracker().clear(); }).get(); + require_rows(e, "SELECT v FROM cf WHERE pk='a' AND ck=0 ALLOW FILTERING", {}); + require_rows(e, "SELECT v FROM cf", {{T("a1")}, {T("b2")}}); + }, cql_test_config(db_config)); +} + +SEASTAR_TEST_CASE(test_clustering_filtering_2) { + static std::array test_compaction_strategies = { + "SizeTieredCompactionStrategy", + "TimeWindowCompactionStrategy", + }; + + return do_for_each(test_compaction_strategies, + test_clustering_filtering_2_with_compaction_strategy); +} + +static future<> test_clustering_filtering_3_with_compaction_strategy(const std::string_view& cs) { + auto db_config = make_shared(); + db_config->enable_sstables_md_format.set(true); + + return do_with_cql_env_thread([&cs] (cql_test_env& e) { + cquery_nofail(e, format("CREATE TABLE cf(pk text, ck int, v text, PRIMARY KEY(pk, ck)) WITH COMPACTION = {{'class': '{}'}}", cs)); + e.db().invoke_on_all([] (database& db) { + auto& table = db.find_column_family("ks", "cf"); + table.disable_auto_compaction(); + }).get(); + cquery_nofail(e, "INSERT INTO cf(pk, ck, v) VALUES ('a', 1, 'a1')"); + e.db().invoke_on_all([] (database& db) { return db.flush_all_memtables(); }).get(); + cquery_nofail(e, "INSERT INTO cf(pk, ck, v) VALUES ('b', 0, 'b0')"); + e.db().invoke_on_all([] (database& db) { return db.flush_all_memtables(); }).get(); + e.db().invoke_on_all([] (database& db) { db.row_cache_tracker().clear(); }).get(); + require_rows(e, "SELECT v FROM cf WHERE pk='a' AND ck=0 ALLOW FILTERING", {}); + require_rows(e, "SELECT v FROM cf", {{T("a1")}, {T("b0")}}); + }, cql_test_config(db_config)); +} + +SEASTAR_TEST_CASE(test_clustering_filtering_3) { + static std::array test_compaction_strategies = { + "SizeTieredCompactionStrategy", + "TimeWindowCompactionStrategy", + }; + + return do_for_each(test_compaction_strategies, + test_clustering_filtering_3_with_compaction_strategy); +} From e2340d0684c03e888d7fbbfc60ebb85e14bbebb9 Mon Sep 17 00:00:00 2001 From: Benny Halevy Date: Mon, 20 Jul 2020 11:48:55 +0300 Subject: [PATCH 27/27] config: enable_sstables_md_format by default Signed-off-by: Benny Halevy --- db/config.cc | 2 +- test/manual/sstable_scan_footprint_test.cc | 2 +- test/perf/perf_fast_forward.cc | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/db/config.cc b/db/config.cc index 0ef08aaa41..d1f3376494 100644 --- a/db/config.cc +++ b/db/config.cc @@ -715,7 +715,7 @@ db::config::config(std::shared_ptr exts) , cpu_scheduler(this, "cpu_scheduler", value_status::Used, true, "Enable cpu scheduling") , view_building(this, "view_building", value_status::Used, true, "Enable view building; should only be set to false when the node is experience issues due to view building") , enable_sstables_mc_format(this, "enable_sstables_mc_format", value_status::Used, true, "Enable SSTables 'mc' format to be used as the default file format") - , enable_sstables_md_format(this, "enable_sstables_md_format", value_status::Used, false, "Enable SSTables 'md' format to be used as the default file format (requires enable_sstables_mc_format)") + , enable_sstables_md_format(this, "enable_sstables_md_format", value_status::Used, true, "Enable SSTables 'md' format to be used as the default file format (requires enable_sstables_mc_format)") , enable_dangerous_direct_import_of_cassandra_counters(this, "enable_dangerous_direct_import_of_cassandra_counters", value_status::Used, false, "Only turn this option on if you want to import tables from Cassandra containing counters, and you are SURE that no counters in that table were created in a version earlier than Cassandra 2.1." " It is not enough to have ever since upgraded to newer versions of Cassandra. If you EVER used a version earlier than 2.1 in the cluster where these SSTables come from, DO NOT TURN ON THIS OPTION! You will corrupt your data. You have been warned.") , enable_shard_aware_drivers(this, "enable_shard_aware_drivers", value_status::Used, true, "Enable native transport drivers to use connection-per-shard for better performance") diff --git a/test/manual/sstable_scan_footprint_test.cc b/test/manual/sstable_scan_footprint_test.cc index 71626e395b..f9a5e87643 100644 --- a/test/manual/sstable_scan_footprint_test.cc +++ b/test/manual/sstable_scan_footprint_test.cc @@ -211,7 +211,7 @@ int main(int argc, char** argv) { ("read-concurrency", bpo::value()->default_value(1), "Concurrency of reads, the amount of reads to fire at once") ("sstables", bpo::value()->default_value(100), "") ("sstable-size", bpo::value()->default_value(10000000), "") - ("sstable-format", bpo::value()->default_value("mc"), "Sstable format version to use during population") + ("sstable-format", bpo::value()->default_value("md"), "Sstable format version to use during population") ("collect-stats", "Enable collecting statistics.") ("stats-file", bpo::value()->default_value("stats.csv"), "Store statistics in the specified file.") ("stats-period-ms", bpo::value()->default_value(100), "Tick period of the stats collection.") diff --git a/test/perf/perf_fast_forward.cc b/test/perf/perf_fast_forward.cc index 7285f61f83..38c5c90b6f 100644 --- a/test/perf/perf_fast_forward.cc +++ b/test/perf/perf_fast_forward.cc @@ -1774,7 +1774,7 @@ int main(int argc, char** argv) { ("test-case-duration", bpo::value()->default_value(1), "Duration in seconds of a single test case (0 for a single run).") ("data-directory", bpo::value()->default_value("./perf_large_partition_data"), "Data directory") ("output-directory", bpo::value()->default_value("./perf_fast_forward_output"), "Results output directory (for 'json')") - ("sstable-format", bpo::value()->default_value("mc"), "Sstable format version to use during population") + ("sstable-format", bpo::value()->default_value("md"), "Sstable format version to use during population") ("use-binary-search-in-promoted-index", bpo::value()->default_value(true), "Use binary search based variant of the promoted index cursor") ("dump-all-results", "Write results of all iterations of all tests to text files in the output directory") ;