From ed62b9a6672817dcffdbd5f1c22220354c330ede Mon Sep 17 00:00:00 2001 From: Vladimir Krivopalov Date: Thu, 19 Apr 2018 12:33:15 -0700 Subject: [PATCH 1/7] Add mutation_partition::apply_insert() overload that accepts TTL and expiry for row marker. For #1969. Signed-off-by: Vladimir Krivopalov --- mutation_partition.cc | 5 ++++- mutation_partition.hh | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/mutation_partition.cc b/mutation_partition.cc index 0c5e65c5ef..b436b05bf1 100644 --- a/mutation_partition.cc +++ b/mutation_partition.cc @@ -443,7 +443,10 @@ void mutation_partition::apply_insert(const schema& s, clustering_key_view key, api::timestamp_type created_at) { clustered_row(s, key).apply(row_marker(created_at)); } - +void mutation_partition::apply_insert(const schema& s, clustering_key_view key, api::timestamp_type created_at, + gc_clock::duration ttl, gc_clock::time_point expiry) { + clustered_row(s, key).apply(row_marker(created_at, ttl, expiry)); +} void mutation_partition::insert_row(const schema& s, const clustering_key& key, deletable_row&& row) { auto e = current_allocator().construct(key, std::move(row)); _rows.insert(_rows.end(), *e, rows_entry::compare(s)); diff --git a/mutation_partition.hh b/mutation_partition.hh index d8a6798923..e3d010a3bc 100644 --- a/mutation_partition.hh +++ b/mutation_partition.hh @@ -953,6 +953,8 @@ public: void apply_delete(const schema& schema, clustering_key_prefix_view prefix, tombstone t); // Equivalent to applying a mutation with an empty row, created with given timestamp void apply_insert(const schema& s, clustering_key_view, api::timestamp_type created_at); + void apply_insert(const schema& s, clustering_key_view, api::timestamp_type created_at, + gc_clock::duration ttl, gc_clock::time_point expiry); // prefix must not be full void apply_row_tombstone(const schema& schema, clustering_key_prefix prefix, tombstone t); void apply_row_tombstone(const schema& schema, range_tombstone rt); From 54bd74fda0f99248ebc2ab1148030ba3d69a5e7c Mon Sep 17 00:00:00 2001 From: Vladimir Krivopalov Date: Thu, 29 Mar 2018 14:46:01 -0700 Subject: [PATCH 2/7] Add is_fixed_length() to data types. For any given CQL data type, this member returns whether its values are of fixed or variable length. This is used by SSTables 3.0 format to only store the length value for variable-length cells. For #1969. Signed-off-by: Vladimir Krivopalov --- types.cc | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ types.hh | 7 +++++++ 2 files changed, 58 insertions(+) diff --git a/types.cc b/types.cc index 32a1f1f0f1..79f8a5cdfd 100644 --- a/types.cc +++ b/types.cc @@ -239,6 +239,9 @@ struct integer_type_impl : simple_type_impl { virtual bytes from_json_object(const Json::Value& value, cql_serialization_format sf) const override { return this->decompose(T(json::to_int64_t(value))); } + virtual bool is_fixed_length() const override { + return true; + } }; struct byte_type_impl : integer_type_impl { @@ -350,6 +353,9 @@ struct string_type_impl : public concrete_type { virtual bytes from_json_object(const Json::Value& value, cql_serialization_format sf) const override { return from_string(value.asString()); } + virtual bool is_fixed_length() const override { + return false; + } }; struct ascii_type_impl final : public string_type_impl { @@ -436,6 +442,9 @@ struct bytes_type_impl final : public concrete_type { // bytesType validate everything, so it is compatible with the former. return this == &other || &other == ascii_type.get() || &other == utf8_type.get(); } + virtual bool is_fixed_length() const override { + return false; + } }; struct boolean_type_impl : public simple_type_impl { @@ -512,6 +521,9 @@ struct boolean_type_impl : public simple_type_impl { virtual ::shared_ptr as_cql3_type() const override { return cql3::cql3_type::boolean; } + virtual bool is_fixed_length() const override { + return true; + } }; class date_type_impl : public concrete_type { @@ -591,6 +603,9 @@ public: } return false; } + virtual bool is_fixed_length() const override { + return true; + } }; logging::logger date_type_impl::_logger(date_type_name); @@ -689,6 +704,9 @@ struct timeuuid_type_impl : public concrete_type { virtual ::shared_ptr as_cql3_type() const override { return cql3::cql3_type::timeuuid; } + virtual bool is_fixed_length() const override { + return true; + } private: static int compare_bytes(bytes_view o1, bytes_view o2) { auto compare_pos = [&] (unsigned pos, int mask, int ifequal) { @@ -880,6 +898,9 @@ public: } return false; } + virtual bool is_fixed_length() const override { + return true; + } }; logging::logger timestamp_type_impl::_logger(timestamp_type_name); @@ -975,6 +996,9 @@ struct simple_date_type_impl : public simple_type_impl { virtual ::shared_ptr as_cql3_type() const override { return cql3::cql3_type::date; } + virtual bool is_fixed_length() const override { + return true; + } }; struct time_type_impl : public simple_type_impl { @@ -1076,6 +1100,9 @@ struct time_type_impl : public simple_type_impl { virtual ::shared_ptr as_cql3_type() const override { return cql3::cql3_type::time; } + virtual bool is_fixed_length() const override { + return true; + } }; struct uuid_type_impl : concrete_type { @@ -1167,6 +1194,9 @@ struct uuid_type_impl : concrete_type { virtual bool is_value_compatible_with_internal(const abstract_type& other) const override { return &other == this || &other == timeuuid_type.get(); } + virtual bool is_fixed_length() const override { + return true; + } }; using inet_address = seastar::net::inet_address; @@ -1272,6 +1302,9 @@ struct inet_addr_type_impl : concrete_type { virtual ::shared_ptr as_cql3_type() const override { return cql3::cql3_type::inet; } + virtual bool is_fixed_length() const override { + return true; + } }; // Integer of same length of a given type. This is useful because our @@ -1422,6 +1455,9 @@ struct floating_type_impl : public simple_type_impl { throw marshal_exception("Only float/double types can be parsed from JSON floating point object"); } } + virtual bool is_fixed_length() const override { + return true; + } }; struct double_type_impl : floating_type_impl { @@ -1559,6 +1595,9 @@ public: virtual bool is_value_compatible_with_internal(const abstract_type& other) const override { return &other == this || int32_type->is_value_compatible_with(other) || long_type->is_value_compatible_with(other); } + virtual bool is_fixed_length() const override { + return false; + } friend class decimal_type_impl; }; @@ -1666,6 +1705,9 @@ public: virtual ::shared_ptr as_cql3_type() const override { return cql3::cql3_type::decimal; } + virtual bool is_fixed_length() const override { + return false; + } }; class counter_type_impl : public abstract_type { @@ -1741,6 +1783,9 @@ public: virtual std::experimental::optional update_user_type(const shared_ptr updated) const { return std::experimental::nullopt; } + virtual bool is_fixed_length() const override { + fail(unimplemented::cause::COUNTERS); + } }; // TODO(jhaberku): Move this to Seastar. @@ -1886,6 +1931,9 @@ public: virtual bool references_duration() const override { return true; } + virtual bool is_fixed_length() const override { + return false; + } private: using counter_type = cql_duration::common_counter_type; @@ -1975,6 +2023,9 @@ struct empty_type_impl : abstract_type { // Can't happen abort(); } + virtual bool is_fixed_length() const override { + return true; + } }; diff --git a/types.hh b/types.hh index 5ed2e94603..d93d3164db 100644 --- a/types.hh +++ b/types.hh @@ -499,6 +499,7 @@ public: virtual bool references_duration() const { return false; } + virtual bool is_fixed_length() const = 0; protected: virtual bool equals(const abstract_type& other) const { return this == &other; @@ -845,6 +846,7 @@ public: return deserialize(v, sf); } bytes_opt reserialize(cql_serialization_format from, cql_serialization_format to, bytes_view_opt v) const; + virtual bool is_fixed_length() const override { return false; } }; using collection_type = shared_ptr; @@ -1009,6 +1011,10 @@ public: static shared_ptr get_instance(data_type type) { return intern::get_instance(std::move(type)); } + + virtual bool is_fixed_length() const override { + return _underlying_type->is_fixed_length(); + } protected: virtual size_t native_value_size() const override; virtual size_t native_value_alignment() const override; @@ -1640,6 +1646,7 @@ public: virtual bool references_user_type(const sstring& keyspace, const bytes& name) const override; virtual std::experimental::optional update_user_type(const shared_ptr updated) const override; virtual bool references_duration() const override; + virtual bool is_fixed_length() const override { return false; } private: bool check_compatibility(const abstract_type& previous, bool (abstract_type::*predicate)(const abstract_type&) const) const; static sstring make_name(const std::vector& types); From bb2bea928a049f59338e9748d682a73eaffb3854 Mon Sep 17 00:00:00 2001 From: Vladimir Krivopalov Date: Tue, 10 Apr 2018 17:09:37 -0700 Subject: [PATCH 3/7] Refactor sstable_writer to support various internal implementations. This is preparatory work for supporting writing SSTables in multiple formats. For #1969. Signed-off-by: Vladimir Krivopalov --- sstables/sstables.cc | 108 +++++++++++++++++++++++++++++++++++++------ sstables/sstables.hh | 44 ++++++++---------- 2 files changed, 114 insertions(+), 38 deletions(-) diff --git a/sstables/sstables.cc b/sstables/sstables.cc index 77eb7791ce..40e28bd9c5 100644 --- a/sstables/sstables.cc +++ b/sstables/sstables.cc @@ -2163,7 +2163,7 @@ void components_writer::consume_new_partition(const dht::decorated_key& dk) { // Write an index entry minus the "promoted index" (sample of columns) // part. We can only write that after processing the entire partition // and collecting the sample of columns. - write_index_header(_sst._version, _index, p_key, _out.offset()); + write_index_header(_sst.get_version(), _index, p_key, _out.offset()); _sst._pi_write.data = {}; _sst._pi_write.numblocks = 0; _sst._pi_write.deltime.local_deletion_time = std::numeric_limits::max(); @@ -2173,7 +2173,7 @@ void components_writer::consume_new_partition(const dht::decorated_key& dk) { _sst._pi_write.schemap = &_schema; // sadly we need this // Write partition key into data file. - write(_sst._version, _out, p_key); + write(_sst.get_version(), _out, p_key); _tombstone_written = false; } @@ -2194,7 +2194,7 @@ void components_writer::consume(tombstone t) { d.local_deletion_time = std::numeric_limits::max(); d.marked_for_delete_at = std::numeric_limits::min(); } - write(_sst._version, _out, d); + write(_sst.get_version(), _out, d); _tombstone_written = true; // TODO: need to verify we don't do this twice? _sst._pi_write.deltime = d; @@ -2263,13 +2263,13 @@ stop_iteration components_writer::consume_end_of_partition() { _out.offset() - _sst._pi_write.block_start_offset); _sst._pi_write.numblocks++; } - write_index_promoted(_sst._version, _index, _sst._pi_write.data, _sst._pi_write.deltime, + write_index_promoted(_sst.get_version(), _index, _sst._pi_write.data, _sst._pi_write.deltime, _sst._pi_write.numblocks); _sst._pi_write.data = {}; _sst._pi_write.block_first_colname = {}; int16_t end_of_row = 0; - write(_sst._version, _out, end_of_row); + write(_sst.get_version(), _out, end_of_row); // compute size of the current row. _sst._c_stats.row_size = _out.offset() - _sst._c_stats.start_offset; @@ -2300,7 +2300,7 @@ void components_writer::consume_end_of_stream() { } _sst.set_first_and_last_keys(); - seal_statistics(_sst._version, _sst._components->statistics, _sst._collector, dht::global_partitioner().name(), _schema.bloom_filter_fp_chance(), + seal_statistics(_sst.get_version(), _sst._components->statistics, _sst._collector, dht::global_partitioner().name(), _schema.bloom_filter_fp_chance(), _sst._schema, _sst.get_first_decorated_key(), _sst.get_last_decorated_key()); } @@ -2350,7 +2350,50 @@ sstable::write_scylla_metadata(const io_priority_class& pc, shard_id shard, ssta write_simple(*_components->scylla_metadata, pc); } -void sstable_writer::prepare_file_writer() +struct sstable_writer::writer_impl { + virtual void consume_new_partition(const dht::decorated_key& dk) = 0; + virtual void consume(tombstone t) = 0; + virtual stop_iteration consume(static_row&& sr) = 0; + virtual stop_iteration consume(clustering_row&& cr) = 0; + virtual stop_iteration consume(range_tombstone&& rt) = 0; + virtual stop_iteration consume_end_of_partition() = 0; + virtual void consume_end_of_stream() = 0; + virtual ~writer_impl() {} +}; + +class sstable_writer_k_l : public sstable_writer::writer_impl { + sstable& _sst; + const schema& _schema; + const io_priority_class& _pc; + bool _backup; + bool _leave_unsealed; + bool _compression_enabled; + std::unique_ptr _writer; + stdx::optional _components_writer; + shard_id _shard; // Specifies which shard new sstable will belong to. + write_monitor* _monitor; + bool _correctly_serialize_non_compound_range_tombstones; +private: + void prepare_file_writer(); + void finish_file_writer(); +public: + sstable_writer_k_l(sstable& sst, const schema& s, uint64_t estimated_partitions, + const sstable_writer_config&, const io_priority_class& pc, shard_id shard = engine().cpu_id()); + ~sstable_writer_k_l(); + sstable_writer_k_l(sstable_writer_k_l&& o) : _sst(o._sst), _schema(o._schema), _pc(o._pc), _backup(o._backup), + _leave_unsealed(o._leave_unsealed), _compression_enabled(o._compression_enabled), _writer(std::move(o._writer)), + _components_writer(std::move(o._components_writer)), _shard(o._shard), _monitor(o._monitor), + _correctly_serialize_non_compound_range_tombstones(o._correctly_serialize_non_compound_range_tombstones) { } + void consume_new_partition(const dht::decorated_key& dk) override { return _components_writer->consume_new_partition(dk); } + void consume(tombstone t) override { _components_writer->consume(t); } + stop_iteration consume(static_row&& sr) override { return _components_writer->consume(std::move(sr)); } + stop_iteration consume(clustering_row&& cr) override { return _components_writer->consume(std::move(cr)); } + stop_iteration consume(range_tombstone&& rt) override { return _components_writer->consume(std::move(rt)); } + stop_iteration consume_end_of_partition() override { return _components_writer->consume_end_of_partition(); } + void consume_end_of_stream() override; +}; + +void sstable_writer_k_l::prepare_file_writer() { file_output_stream_options options; options.io_priority_class = _pc; @@ -2364,21 +2407,21 @@ void sstable_writer::prepare_file_writer() } } -void sstable_writer::finish_file_writer() +void sstable_writer_k_l::finish_file_writer() { auto writer = std::move(_writer); writer->close().get(); if (!_compression_enabled) { auto chksum_wr = static_cast(writer.get()); - write_digest(_sst._version, _sst._write_error_handler, _sst.filename(component_type::Digest), chksum_wr->full_checksum()); - write_crc(_sst._version, _sst._write_error_handler, _sst.filename(component_type::CRC), chksum_wr->finalize_checksum()); + write_digest(_sst.get_version(), _sst._write_error_handler, _sst.filename(component_type::Digest), chksum_wr->full_checksum()); + write_crc(_sst.get_version(), _sst._write_error_handler, _sst.filename(component_type::CRC), chksum_wr->finalize_checksum()); } else { - write_digest(_sst._version, _sst._write_error_handler, _sst.filename(component_type::Digest), _sst._components->compression.full_checksum()); + write_digest(_sst.get_version(), _sst._write_error_handler, _sst.filename(component_type::Digest), _sst._components->compression.full_checksum()); } } -sstable_writer::~sstable_writer() { +sstable_writer_k_l::~sstable_writer_k_l() { if (_writer) { try { _writer->close().get(); @@ -2388,7 +2431,7 @@ sstable_writer::~sstable_writer() { } } -sstable_writer::sstable_writer(sstable& sst, const schema& s, uint64_t estimated_partitions, +sstable_writer_k_l::sstable_writer_k_l(sstable& sst, const schema& s, uint64_t estimated_partitions, const sstable_writer_config& cfg, const io_priority_class& pc, shard_id shard) : _sst(sst) , _schema(s) @@ -2414,7 +2457,7 @@ static sstable_enabled_features all_features() { return sstable_enabled_features{(1 << sstable_feature::End) - 1}; } -void sstable_writer::consume_end_of_stream() +void sstable_writer_k_l::consume_end_of_stream() { _components_writer->consume_end_of_stream(); _components_writer = stdx::nullopt; @@ -2439,6 +2482,43 @@ void sstable_writer::consume_end_of_stream() _monitor->on_flush_completed(); } +sstable_writer::sstable_writer(sstable& sst, const schema& s, uint64_t estimated_partitions, + const sstable_writer_config& cfg, const io_priority_class& pc, shard_id shard) + : _impl(std::make_unique(sst, s, estimated_partitions, cfg, pc, shard)) + {} + +void sstable_writer::consume_new_partition(const dht::decorated_key& dk) { + return _impl->consume_new_partition(dk); +} + +void sstable_writer::consume(tombstone t) { + return _impl->consume(t); +} + +stop_iteration sstable_writer::consume(static_row&& sr) { + return _impl->consume(std::move(sr)); +} + +stop_iteration sstable_writer::consume(clustering_row&& cr) { + return _impl->consume(std::move(cr)); +} + +stop_iteration sstable_writer::consume(range_tombstone&& rt) { + return _impl->consume(std::move(rt)); +} + +stop_iteration sstable_writer::consume_end_of_partition() { + return _impl->consume_end_of_partition(); +} + +void sstable_writer::consume_end_of_stream() { + return _impl->consume_end_of_stream(); +} + +sstable_writer::sstable_writer(sstable_writer&& o) = default; +sstable_writer& sstable_writer::operator=(sstable_writer&& o) = default; +sstable_writer::~sstable_writer() = default; + future<> sstable::seal_sstable(bool backup) { return seal_sstable().then([this, backup] { diff --git a/sstables/sstables.hh b/sstables/sstables.hh index f6d4d58920..0b265b3842 100644 --- a/sstables/sstables.hh +++ b/sstables/sstables.hh @@ -70,6 +70,7 @@ namespace sstables { extern logging::logger sstlog; class key; class sstable_writer; +class sstable_writer_k_l; struct foreign_sstable_open_info; struct sstable_open_info; @@ -329,6 +330,10 @@ public: return _components->filter->memory_size(); } + version_types get_version() const { + return _version; + } + // Returns the total bytes of all components. uint64_t bytes_on_disk(); @@ -696,7 +701,7 @@ public: friend class test; friend class components_writer; - friend class sstable_writer; + friend class sstable_writer_k_l; friend class index_reader; template friend data_consume_context @@ -810,34 +815,25 @@ public: }; class sstable_writer { - sstable& _sst; - const schema& _schema; - const io_priority_class& _pc; - bool _backup; - bool _leave_unsealed; - bool _compression_enabled; - std::unique_ptr _writer; - stdx::optional _components_writer; - shard_id _shard; // Specifies which shard new sstable will belong to. - write_monitor* _monitor; - bool _correctly_serialize_non_compound_range_tombstones; +public: + class writer_impl; private: - void prepare_file_writer(); - void finish_file_writer(); + std::unique_ptr _impl; public: sstable_writer(sstable& sst, const schema& s, uint64_t estimated_partitions, const sstable_writer_config&, const io_priority_class& pc, shard_id shard = engine().cpu_id()); + + sstable_writer(sstable_writer&& o); + sstable_writer& operator=(sstable_writer&& o); + ~sstable_writer(); - sstable_writer(sstable_writer&& o) : _sst(o._sst), _schema(o._schema), _pc(o._pc), _backup(o._backup), - _leave_unsealed(o._leave_unsealed), _compression_enabled(o._compression_enabled), _writer(std::move(o._writer)), - _components_writer(std::move(o._components_writer)), _shard(o._shard), _monitor(o._monitor), - _correctly_serialize_non_compound_range_tombstones(o._correctly_serialize_non_compound_range_tombstones) { } - void consume_new_partition(const dht::decorated_key& dk) { return _components_writer->consume_new_partition(dk); } - void consume(tombstone t) { _components_writer->consume(t); } - stop_iteration consume(static_row&& sr) { return _components_writer->consume(std::move(sr)); } - stop_iteration consume(clustering_row&& cr) { return _components_writer->consume(std::move(cr)); } - stop_iteration consume(range_tombstone&& rt) { return _components_writer->consume(std::move(rt)); } - stop_iteration consume_end_of_partition() { return _components_writer->consume_end_of_partition(); } + + void consume_new_partition(const dht::decorated_key& dk); + void consume(tombstone t); + stop_iteration consume(static_row&& sr); + stop_iteration consume(clustering_row&& cr); + stop_iteration consume(range_tombstone&& rt); + stop_iteration consume_end_of_partition(); void consume_end_of_stream(); }; From a95664be08c01cd42afb77a2bb85bf3cb3a16c48 Mon Sep 17 00:00:00 2001 From: Vladimir Krivopalov Date: Thu, 29 Mar 2018 13:33:09 -0700 Subject: [PATCH 4/7] Add building blocks for writing data in SSTables 3.0 format. For #1969. Signed-off-by: Vladimir Krivopalov --- configure.py | 1 + sstables/m_format_write_helpers.cc | 315 +++++++++++++++++++++++++++++ sstables/m_format_write_helpers.hh | 70 +++++++ sstables/sstables.cc | 12 ++ vint-serialization.hh | 2 + 5 files changed, 400 insertions(+) create mode 100644 sstables/m_format_write_helpers.cc create mode 100644 sstables/m_format_write_helpers.hh diff --git a/configure.py b/configure.py index 9bf948c85c..3f6e52d71d 100755 --- a/configure.py +++ b/configure.py @@ -412,6 +412,7 @@ scylla_core = (['database.cc', 'sstables/compaction_manager.cc', 'sstables/integrity_checked_file_impl.cc', 'sstables/prepended_input_stream.cc', + 'sstables/m_format_write_helpers.cc', 'transport/event.cc', 'transport/event_notifier.cc', 'transport/server.cc', diff --git a/sstables/m_format_write_helpers.cc b/sstables/m_format_write_helpers.cc new file mode 100644 index 0000000000..bb9583516e --- /dev/null +++ b/sstables/m_format_write_helpers.cc @@ -0,0 +1,315 @@ +/* + * Copyright (C) 2018 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 +#include +#include + +#include "encoding_stats.hh" +#include "schema.hh" +#include "mutation_fragment.hh" +#include "vint-serialization.hh" +#include "sstables/types.hh" +#include "sstables/m_format_write_helpers.hh" +#include "sstables/writer.hh" + +namespace sstables { + +template +inline void write_vint_impl(file_writer& out, T value) { + using vint_type = std::conditional_t, unsigned_vint, signed_vint>; + std::array encoding_buffer; + const auto size = vint_type::serialize(value, encoding_buffer.begin()); + out.write(reinterpret_cast(encoding_buffer.data()), size); +} + +void write_unsigned_vint(file_writer& out, uint64_t value) { + return write_vint_impl(out, value); +} + +void write_signed_vint(file_writer& out, int64_t value) { + return write_vint_impl(out, value); +} + +// A helper CRTP base class for input ranges. +// Derived classes should implement the following functions: +// bool next() const; +// generates the next value, if possible; +// returns true if the next value has been evaluated, false otherwise +// explicit operator bool() const; +// tells whether the range can produce more items +// TODO: turn description into a concept +template +struct input_range_base { +private: + + InputRange& self() { + return static_cast(*this); + } + + const InputRange& self() const { + return static_cast(*this); + } + +public: + // Use the same type for iterator and const_iterator + using const_iterator = class iterator + : public boost::iterator_facade< + iterator, + const ValueType, + std::input_iterator_tag, + const ValueType + > + { + private: + const InputRange* _range; + + friend class input_range_base; + friend class boost::iterator_core_access; + + explicit iterator(const InputRange& range) + : _range(range.next() ? &range : nullptr) + {} + + void increment() { + assert(_range); + if (!_range->next()) { + _range = nullptr; + } + } + + bool equal(iterator that) const { + return (_range == that._range); + } + + const ValueType dereference() const { + assert(_range); + return _range->get_value(); + } + + public: + iterator() : _range{} {} + + }; + + iterator begin() const { return iterator{self()}; } + iterator end() const { return iterator{}; } +}; + +struct clustering_block { + constexpr static uint32_t max_block_size = 32; + uint64_t header = 0; + struct described_value { + bytes_view value; + std::reference_wrapper type; + }; + boost::container::static_vector values; +}; + +class clustering_blocks_input_range + : public input_range_base { +private: + const schema& _schema; + const clustering_key_prefix& _prefix; + mutable clustering_block _current_block; + mutable uint32_t _offset = 0; + +public: + clustering_blocks_input_range(const schema& s, const clustering_key_prefix& prefix) + : _schema(s), _prefix(prefix) {} + + bool next() const { + if (_offset == _schema.clustering_key_size()) { + // No more values to encode + return false; + } + + // Each block contains up to max_block_size values + auto limit = std::min(_schema.clustering_key_size(), _offset + clustering_block::max_block_size); + + _current_block = {}; + assert (_offset % clustering_block::max_block_size == 0); + while (_offset < limit) { + auto shift = _offset & clustering_block::max_block_size; + if (_offset < _prefix.size(_schema)) { + bytes_view value = _prefix.get_component(_schema, _offset); + if (value.empty()) { + _current_block.header |= (uint64_t(1) << (shift * 2)); + } else { + _current_block.values.push_back({value, *_prefix.get_compound_type(_schema)->types()[_offset]}); + } + } else { + // This (and all subsequent) values of the prefix are missing (null) + _current_block.header |= (uint64_t(1) << ((shift * 2) + 1)); + } + ++_offset; + } + return true; + } + + clustering_block get_value() const { return _current_block; }; + + explicit operator bool() const { + return (_offset < _schema.clustering_key_size()); + } +}; + +// Writes cell value according to its data type traits +// NOTE: this function is defined in sstables/sstables.cc +void write_cell_value(file_writer& out, const abstract_type& type, bytes_view value); + +static void write(file_writer& out, const clustering_block& block) { + write_vint(out, block.header); + for (const auto& [value, type]: block.values) { + write_cell_value(out, type, value); + } +} + +void write_clustering_prefix(file_writer& out, const schema& s, const clustering_key_prefix& prefix) { + clustering_blocks_input_range range{s, prefix}; + for (const auto block: range) { + write(out, block); + } +} + +// This range generates a sequence of values that represent information +// about missing columns for SSTables 3.0 format. +class missing_columns_input_range + : public input_range_base { +private: + const schema& _schema; + const row& _row; + mutable uint64_t _current_value = 0; + mutable column_id _current_id = 0; + mutable bool _large_mode_produced_size = false; + + enum class encoding_mode { + small, + large_encode_present, + large_encode_missing, + } _mode; + +public: + missing_columns_input_range(const schema& s, const row& row) + : _schema(s) + , _row(row) { + + auto row_size = _row.size(); + auto total_size = _schema.regular_columns_count(); + + _current_id = row_size < total_size ? 0 : total_size; + _mode = (total_size < 64) ? encoding_mode::small : + (row_size < total_size / 2) ? encoding_mode::large_encode_present : + encoding_mode::large_encode_missing; + } + + bool next() const { + auto total_size = _schema.regular_columns_count(); + if (_current_id == total_size) { + // No more values to encode + return false; + } + + if (_mode == encoding_mode::small) { + // Set bit for every missing column + for (column_id id = 0; id < total_size; ++id) { + auto cell = _row.find_cell(id); + if (!cell) { + _current_value |= (uint64_t(1) << id); + } + } + _current_id = total_size; + return true; + } else { + // For either of large modes, output the difference between total size and row size first + if (!_large_mode_produced_size) { + _current_value = total_size - _row.size(); + _large_mode_produced_size = true; + return true; + } + + if (_mode == encoding_mode::large_encode_present) { + while (_current_id < total_size) { + auto cell = _row.find_cell(_current_id); + if (cell) { + _current_value = _current_id; + ++_current_id; + return true; + } + ++_current_id; + } + } else { + assert(_mode == encoding_mode::large_encode_missing); + while (_current_id < total_size) { + auto cell = _row.find_cell(_current_id); + if (!cell) { + _current_value = _current_id; + ++_current_id; + return true; + } + ++_current_id; + } + } + } + + return false; + } + + uint64_t get_value() const { return _current_value; } + + explicit operator bool() const + { + return (_current_id < _schema.regular_columns_count()); + } +}; + +void write_missing_columns(file_writer& out, const schema& s, const row& row) { + for (const auto value: missing_columns_input_range{s, row}) { + write_vint(out, value); + } +} + +template +void write_unsigned_delta_vint(file_writer& out, T value, T base) { + using unsigned_type = std::make_unsigned_t; + unsigned_type delta = value - base; + write_vint(out, delta); +} + +void write_delta_timestamp(file_writer& out, api::timestamp_type timestamp, const encoding_stats& enc_stats) { + write_unsigned_delta_vint(out, timestamp, enc_stats.min_timestamp); +} + +void write_delta_ttl(file_writer& out, uint32_t ttl, const encoding_stats& enc_stats) { + write_unsigned_delta_vint(out, ttl, enc_stats.min_ttl); +} + +void write_delta_local_deletion_time(file_writer& out, uint32_t local_deletion_time, const encoding_stats& enc_stats) { + write_unsigned_delta_vint(out, local_deletion_time, enc_stats.min_local_deletion_time); +} + +void write_delta_deletion_time(file_writer& out, deletion_time dt, const encoding_stats& enc_stats) { + write_delta_timestamp(out, dt.marked_for_delete_at, enc_stats); + write_delta_local_deletion_time(out, dt.local_deletion_time, enc_stats); +} + +}; // namespace sstables + diff --git a/sstables/m_format_write_helpers.hh b/sstables/m_format_write_helpers.hh new file mode 100644 index 0000000000..ac74350706 --- /dev/null +++ b/sstables/m_format_write_helpers.hh @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2018 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 "bytes.hh" +#include "types.hh" +#include "timestamp.hh" + +class schema; +class row; +class clustering_key_prefix; +class encoding_stats; + +namespace sstables { + +class file_writer; + +// Utilities for writing integral values in variable-length format +// See vint-serialization.hh for more details +void write_unsigned_vint(file_writer& out, uint64_t value); +void write_signed_vint(file_writer& out, int64_t value); + +template +typename std::enable_if_t> +write_vint(file_writer& out, T t) = delete; + +template +inline void write_vint(file_writer& out, T value) { + static_assert(std::is_integral_v, "Non-integral values can't be written using write_vint"); + return std::is_unsigned_v ? write_unsigned_vint(out, value) : write_signed_vint(out, value); +} + + +// Writes clustering prefix, full or not, encoded in SSTables 3.0 format +void write_clustering_prefix(file_writer& out, const schema& s, const clustering_key_prefix& prefix); + +// Writes encoded information about missing columns in the given row +void write_missing_columns(file_writer& out, const schema& s, const row& row); + +// Helper functions for writing delta-encoded time-related values +void write_delta_timestamp(file_writer& out, api::timestamp_type timestamp, const encoding_stats& enc_stats); + +void write_delta_ttl(file_writer& out, uint32_t ttl, const encoding_stats& enc_stats); + +void write_delta_local_deletion_time(file_writer& out, uint32_t local_deletion_time, const encoding_stats& enc_stats); + +void write_delta_deletion_time(file_writer& out, deletion_time dt, const encoding_stats& enc_stats); + +}; // namespace sstables diff --git a/sstables/sstables.cc b/sstables/sstables.cc index 40e28bd9c5..dfc572e302 100644 --- a/sstables/sstables.cc +++ b/sstables/sstables.cc @@ -35,6 +35,7 @@ #include #include "types.hh" +#include "v3_write_helpers.hh" #include "sstables.hh" #include "progress_monitor.hh" #include "compress.hh" @@ -1708,6 +1709,17 @@ void sstable::maybe_flush_pi_block(file_writer& out, } } +void write_cell_value(file_writer& out, const data_type& type, bytes_view value) { + if (!value.empty()) { + if (type->is_fixed_length()) { + write(out, value); + } else { + write_vint(out, value.size()); + write(out, value); + } + } +} + static inline void update_cell_stats(column_stats& c_stats, api::timestamp_type timestamp) { c_stats.update_min_timestamp(timestamp); c_stats.update_max_timestamp(timestamp); diff --git a/vint-serialization.hh b/vint-serialization.hh index d47b70a34a..d7e01065d6 100644 --- a/vint-serialization.hh +++ b/vint-serialization.hh @@ -31,6 +31,8 @@ using vint_size_type = bytes::size_type; +static constexpr size_t max_vint_length = 9; + struct unsigned_vint final { using value_type = uint64_t; From 3ecc9e9ce40955179b45b9bdc15db6f5c0468cbb Mon Sep 17 00:00:00 2001 From: Vladimir Krivopalov Date: Mon, 16 Apr 2018 15:09:41 -0700 Subject: [PATCH 5/7] Add missing enum values to bound_kind. bound_kind::clustering, bound_kind::excl_end_incl_start and bound_kind::incl_end_excl_start are used during SSTables 3.0 writing. bound_kind::static_clustering is not used yet but added for completeness and parity with the Origin. For #1969. Signed-off-by: Vladimir Krivopalov --- clustering_bounds_comparator.hh | 5 ++++- keys.cc | 14 ++++++++++++++ position_in_partition.hh | 5 +++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/clustering_bounds_comparator.hh b/clustering_bounds_comparator.hh index b4f0545601..c5d425db5e 100644 --- a/clustering_bounds_comparator.hh +++ b/clustering_bounds_comparator.hh @@ -32,7 +32,10 @@ enum class bound_kind : uint8_t { excl_end = 0, incl_start = 1, - // values 2 to 5 are reserved for forward Origin compatibility + excl_end_incl_start = 2, + static_clustering = 3, + clustering = 4, + incl_end_excl_start = 5, incl_end = 6, excl_start = 7, }; diff --git a/keys.cc b/keys.cc index 6f9af7c88b..8da170dba3 100644 --- a/keys.cc +++ b/keys.cc @@ -63,6 +63,14 @@ std::ostream& operator<<(std::ostream& out, const bound_kind k) { return out << "excl end"; case bound_kind::incl_start: return out << "incl start"; + case bound_kind::excl_end_incl_start: + return out << "excl end + incl start"; + case bound_kind::static_clustering: + return out << "static clustering"; + case bound_kind ::clustering: + return out << "clustering"; + case bound_kind::incl_end_excl_start: + return out << "incl end + excl start"; case bound_kind::incl_end: return out << "incl end"; case bound_kind::excl_start: @@ -77,6 +85,10 @@ bound_kind invert_kind(bound_kind k) { case bound_kind::incl_start: return bound_kind::excl_end; case bound_kind::excl_end: return bound_kind::incl_start; case bound_kind::incl_end: return bound_kind::excl_start; + case bound_kind::excl_end_incl_start: return bound_kind::incl_end_excl_start; + case bound_kind::incl_end_excl_start: return bound_kind::excl_end_incl_start; + case bound_kind::static_clustering: return bound_kind::static_clustering; + case bound_kind::clustering: return bound_kind::clustering; } abort(); } @@ -91,6 +103,8 @@ int32_t weight(bound_kind k) { return 1; case bound_kind::excl_start: return 2; + default: + throw std::invalid_argument(sprint("weight() is not defined for bound_kind {}", k)); } abort(); } diff --git a/position_in_partition.hh b/position_in_partition.hh index f4b13e5792..f7084b98e8 100644 --- a/position_in_partition.hh +++ b/position_in_partition.hh @@ -58,10 +58,15 @@ int position_weight(bound_kind k) { switch(k) { case bound_kind::excl_end: case bound_kind::incl_start: + case bound_kind::excl_end_incl_start: return -1; case bound_kind::incl_end: case bound_kind::excl_start: + case bound_kind::incl_end_excl_start: return 1; + case bound_kind::clustering: + case bound_kind::static_clustering: + return 0; } abort(); } From 15ef4ca73ca1b09a17f4661161c48343e736c36a Mon Sep 17 00:00:00 2001 From: Vladimir Krivopalov Date: Fri, 13 Apr 2018 11:34:17 -0700 Subject: [PATCH 6/7] Support for writing SSTables 3.0 ('mc') Data.db and Index.db files - rows only. This fix adds functionality for writing data in 'mc' format to Data.db file according to the SSTables 3.0 data format as described at https://github.com/scylladb/scylla/wiki/SSTables-3.0-Data-File-Format and Index.db file according to the specification at https://github.com/scylladb/scylla/wiki/SSTables-3.0-Index-File-Format The following cases are not supported yet: - writing counter cells - range tombstones In Index.db, end open markers are not written since range tombstones are not supported for data files yet. For #1969. Signed-off-by: Vladimir Krivopalov --- sstables/compaction.cc | 6 +- sstables/sstables.cc | 650 ++++++++++++++++++++++++++++++++++++++++- sstables/sstables.hh | 6 +- 3 files changed, 646 insertions(+), 16 deletions(-) diff --git a/sstables/compaction.cc b/sstables/compaction.cc index 1215dd0deb..7cc6ae3e99 100644 --- a/sstables/compaction.cc +++ b/sstables/compaction.cc @@ -549,7 +549,8 @@ public: cfg.max_sstable_size = _max_sstable_size; cfg.monitor = &_active_write_monitors.back(); cfg.large_partition_warning_threshold_bytes = _cf.large_partition_warning_threshold_bytes(); - _writer.emplace(_sst->get_writer(*_cf.schema(), partitions_per_sstable(), cfg, priority)); + // TODO: calculate encoding_stats based on statistics of compacted sstables + _writer.emplace(_sst->get_writer(*_cf.schema(), partitions_per_sstable(), cfg, encoding_stats{}, priority)); } return &*_writer; } @@ -696,7 +697,8 @@ public: sstable_writer_config cfg; cfg.max_sstable_size = _max_sstable_size; auto&& priority = service::get_local_compaction_priority(); - writer.emplace(sst->get_writer(*_cf.schema(), partitions_per_sstable(_shard), cfg, priority, _shard)); + // TODO: calculate encoding_stats based on statistics of compacted sstables + writer.emplace(sst->get_writer(*_cf.schema(), partitions_per_sstable(_shard), cfg, encoding_stats{}, priority, _shard)); } return &*writer; } diff --git a/sstables/sstables.cc b/sstables/sstables.cc index dfc572e302..e388d92349 100644 --- a/sstables/sstables.cc +++ b/sstables/sstables.cc @@ -35,7 +35,7 @@ #include #include "types.hh" -#include "v3_write_helpers.hh" +#include "m_format_write_helpers.hh" #include "sstables.hh" #include "progress_monitor.hh" #include "compress.hh" @@ -65,6 +65,7 @@ #include "service/storage_service.hh" #include "db/extensions.hh" #include "unimplemented.hh" +#include "vint-serialization.hh" thread_local disk_error_signal_type sstable_read_error; thread_local disk_error_signal_type sstable_write_error; @@ -1709,13 +1710,13 @@ void sstable::maybe_flush_pi_block(file_writer& out, } } -void write_cell_value(file_writer& out, const data_type& type, bytes_view value) { +void write_cell_value(file_writer& out, const abstract_type& type, bytes_view value) { if (!value.empty()) { - if (type->is_fixed_length()) { - write(out, value); + if (type.is_fixed_length()) { + write(sstable_version_types::mc, out, value); } else { write_vint(out, value.size()); - write(out, value); + write(sstable_version_types::mc, out, value); } } } @@ -2171,7 +2172,7 @@ void components_writer::consume_new_partition(const dht::decorated_key& dk) { auto p_key = disk_string_view(); p_key.value = bytes_view(*_partition_key); - // Write index file entry from partition key into index file. + // Write index file entry for partition key into index file. // Write an index entry minus the "promoted index" (sample of columns) // part. We can only write that after processing the entire partition // and collecting the sample of columns. @@ -2494,10 +2495,632 @@ void sstable_writer_k_l::consume_end_of_stream() _monitor->on_flush_completed(); } +enum class cell_flags : uint8_t { + none = 0x00, + is_deleted_mask = 0x01, // Whether the cell is a tombstone or not. + is_expiring_mask = 0x02, // Whether the cell is expiring. + has_empty_value_mask = 0x04, // Whether the cell has an empty value. This will be the case for a tombstone in particular. + use_row_timestamp_mask = 0x08, // Whether the cell has the same timestamp as the row this is a cell of. + use_row_ttl_mask = 0x10, // Whether the cell has the same TTL as the row this is a cell of. +}; + +inline cell_flags operator& (cell_flags lhs, cell_flags rhs) { + return cell_flags(static_cast(lhs) & static_cast(rhs)); +} + +inline cell_flags& operator |= (cell_flags& lhs, cell_flags rhs) { + lhs = cell_flags(static_cast(lhs) | static_cast(rhs)); + return lhs; +} + +enum class row_flags : uint8_t { + none = 0x00, + // Signal the end of the partition. Nothing follows a field with that flag. + end_of_partition = 0x01, + // Whether the encoded unfiltered is a marker or a row. All following flags apply only to rows. + is_marker = 0x02, + // Whether the encoded row has a timestamp (i.e. its liveness_info is not empty). + has_timestamp = 0x04, + // Whether the encoded row has some expiration info (i.e. if its liveness_info contains TTL and local_deletion). + has_ttl = 0x08, + // Whether the encoded row has some deletion info. + has_deletion = 0x10, + // Whether the encoded row has all of the columns from the header present. + has_all_columns = 0x20, + // Whether the encoded row has some complex deletion for at least one of its complex columns. + has_complex_deletion = 0x40, + // If present, another byte is read containing the "extended flags" below. + extension_flag = 0x80 +}; + +inline row_flags operator& (row_flags lhs, row_flags rhs) { + return row_flags(static_cast(lhs) & static_cast(rhs)); +} + +inline row_flags& operator |= (row_flags& lhs, row_flags rhs) { + lhs = row_flags(static_cast(lhs) | static_cast(rhs)); + return lhs; +} + +enum class row_extended_flags : uint8_t { + none = 0x00, + // Whether the encoded row is a static. If there is no extended flag, the row is assumed not static. + is_static = 0x01, + // Whether the row deletion is shadowable. If there is no extended flag (or no row deletion) + // the deletion is assumed not shadowable. + // This flag is deprecated in Origin - see CASSANDRA-11500. + has_shadowable_deletion = 0x02, +}; + +// Used for writing SSTables in 'mc' format. +class sstable_writer_m : public sstable_writer::writer_impl { +private: + sstable& _sst; + const schema& _schema; + const io_priority_class& _pc; + sstable_writer_config _cfg; + encoding_stats _enc_stats; + shard_id _shard; // Specifies which shard the new SStable will belong to. + std::unique_ptr _data_writer; + std::optional _index_writer; + bool _tombstone_written = false; + bool _row_deletion_written = false; + uint64_t _current_partition_offset = 0; + uint64_t _prev_row_start = 0; + std::optional _partition_key; + stdx::optional _first_key, _last_key; + index_sampling_state _index_sampling_state; + struct pi_block { + clustering_key_prefix first; + clustering_key_prefix last; + uint64_t offset; + uint64_t width; + }; + // _pi_write_m is used temporarily for building the promoted + // index (column sample) of one partition when writing a new sstable. + struct { + // Unfortunately we cannot output the promoted index directly to the + // index file because it needs to be prepended by its size. + seastar::circular_buffer promoted_index; + tombstone tomb; + uint64_t block_start_offset; + uint64_t block_next_start_offset; + std::optional first_clustering; + std::optional last_clustering; + size_t desired_block_size; + } _pi_write_m; + + void init_file_writers(); + void close_data_writer(); + void ensure_tombstone_is_written() { + if (!_tombstone_written) { + consume(tombstone()); + } + } + + void write_delta_timestamp(file_writer& writer, api::timestamp_type timestamp) { + sstables::write_delta_timestamp(writer, timestamp, _enc_stats); + } + void write_delta_ttl(file_writer& writer, uint32_t ttl) { + sstables::write_delta_ttl(writer, ttl, _enc_stats); + } + void write_delta_local_deletion_time(file_writer& writer, uint32_t ldt) { + sstables::write_delta_local_deletion_time(writer, ldt, _enc_stats); + } + void write_delta_deletion_time(file_writer& writer, deletion_time dt) { + sstables::write_delta_deletion_time(writer, dt, _enc_stats); + } + + struct row_time_properties { + std::optional timestamp; + std::optional ttl; + std::optional local_deletion_time; + }; + + // Writes single atomic cell + void write_cell(file_writer& writer, atomic_cell_view cell, const column_definition& cdef, + const row_time_properties& properties, bytes_view cell_path = {}); + + // Writes information about row liveness (formerly 'row marker') + void write_liveness_info(file_writer& writer, const row_marker& marker); + + // Writes a CQL collection (list, set or map) + void write_collection(file_writer& writer, const column_definition& cdef, collection_mutation_view collection, + const row_time_properties& properties, bool has_complex_deletion); + + void write_cells(file_writer& writer, column_kind kind, const row& row_body, const row_time_properties& properties, bool has_complex_deletion = false); + void write_row_body(file_writer& writer, const clustering_row& row, bool has_complex_deletion); + void write_static_row(const row& static_row); + void write_clustered_row(const clustering_row& clustered_row, uint64_t prev_row_size); + std::vector write_promoted_index(file_writer& writer); +public: + + sstable_writer_m(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 = engine().cpu_id()) + : _sst(sst) + , _schema(s) + , _pc(pc) + , _cfg(cfg) + , _enc_stats(enc_stats) + , _shard(shard) + { + _index_sampling_state.summary_byte_cost = summary_byte_cost(); + _sst.generate_toc(_schema.get_compressor_params().get_compressor(), _schema.bloom_filter_fp_chance()); + _sst.write_toc(_pc); + _sst.create_data().get(); + if (!_sst.has_component(component_type::CRC)) { + throw std::runtime_error("Compression is not yet implemented for SSTables 3.0 yet"); + } + init_file_writers(); + _sst._shards = { shard }; + + _cfg.monitor->on_write_started(_data_writer->offset_tracker()); + _sst._components->filter = utils::i_filter::get_filter(estimated_partitions, _schema.bloom_filter_fp_chance()); + _pi_write_m.desired_block_size = cfg.promoted_index_block_size.value_or(get_config().column_index_size_in_kb() * 1024); + _sst._correctly_serialize_non_compound_range_tombstones = _cfg.correctly_serialize_non_compound_range_tombstones; + _index_sampling_state.summary_byte_cost = summary_byte_cost(); + prepare_summary(_sst._components->summary, estimated_partitions, _schema.min_index_interval()); + + } + + ~sstable_writer_m(); + sstable_writer_m(sstable_writer_m&& o) = default; + void consume_new_partition(const dht::decorated_key& dk) override; + void consume(tombstone t) override; + stop_iteration consume(static_row&& sr) override; + stop_iteration consume(clustering_row&& cr) override; + stop_iteration consume(range_tombstone&& rt) override { + throw std::runtime_error("consume(range_tombstone) is not yet implemented for SSTables v3"); + } + stop_iteration consume_end_of_partition(); + void consume_end_of_stream() override; +}; + +sstable_writer_m::~sstable_writer_m() { + auto close_writer = [](auto& writer) { + if (writer) { + try { + writer->close().get(); + } catch (...) { + sstlog.error("sstable_writer_m failed to close file: {}", std::current_exception()); + } + } + }; + close_writer(_index_writer); + close_writer(_data_writer); +} + +void sstable_writer_m::init_file_writers() { + file_output_stream_options options; + options.io_priority_class = _pc; + options.buffer_size = _sst.sstable_buffer_size; + options.write_behind = 10; + + _data_writer = std::make_unique(std::move(_sst._data_file), options, true); + _index_writer.emplace(std::move(_sst._index_file), options); +} + +void sstable_writer_m::close_data_writer() { + auto writer = std::move(_data_writer); + writer->close().get(); + auto chksum_wr = static_cast(writer.get()); + write_digest(_sst.get_version(), _sst._write_error_handler, _sst.filename(component_type::Digest), chksum_wr->full_checksum()); + write_crc(_sst.get_version(), _sst._write_error_handler, _sst.filename(component_type::CRC), chksum_wr->finalize_checksum()); +} + +void sstable_writer_m::consume_new_partition(const dht::decorated_key& dk) { + _current_partition_offset = _data_writer->offset(); + _prev_row_start = 0; + + _partition_key = key::from_partition_key(_schema, dk.key()); + _sst._components->filter->add(bytes_view(*_partition_key)); + _sst._collector.add_key(bytes_view(*_partition_key)); + + auto p_key = disk_string_view(); + p_key.value = bytes_view(*_partition_key); + + // Write index file entry from partition key into index file. + // Write an index entry minus the "promoted index" (sample of columns) + // part. We can only write that after processing the entire partition + // and collecting the sample of columns. + write(_sst.get_version(), *_index_writer, p_key); + write_vint(*_index_writer, _data_writer->offset()); + + _pi_write_m.promoted_index = {}; + _pi_write_m.tomb = {}; + _pi_write_m.first_clustering.reset(); + _pi_write_m.last_clustering.reset(); + + write(_sst.get_version(), *_data_writer, p_key); + + _tombstone_written = false; +} + +deletion_time to_deletion_time(tombstone t) { + deletion_time dt; + if (t) { + dt.local_deletion_time = t.deletion_time.time_since_epoch().count(); + dt.marked_for_delete_at = t.timestamp; + } else { + // Default values for live, non-deleted rows. + dt.local_deletion_time = std::numeric_limits::max(); + dt.marked_for_delete_at = std::numeric_limits::min(); + } + return dt; +} + +void sstable_writer_m::consume(tombstone t) { + write(_sst.get_version(), *_data_writer, to_deletion_time(t)); + _pi_write_m.tomb = t; + _tombstone_written = true; +} + +void sstable_writer_m::write_cell(file_writer& writer, atomic_cell_view cell, const column_definition& cdef, + const row_time_properties& properties, bytes_view cell_path) { + + bytes_view cell_value = cell.value(); + bool has_value = !cell_value.empty(); + bool is_deleted = cell.is_dead(_sst._now); + if (is_deleted) { + has_value = false; + } + bool use_row_timestamp = (properties.timestamp == cell.timestamp()); + bool is_row_expiring = properties.ttl.has_value(); + bool is_cell_expiring = cell.is_live_and_has_ttl() || properties.ttl; + bool is_expiring = is_row_expiring || is_cell_expiring; + bool use_row_ttl = is_row_expiring || (is_cell_expiring && + (properties.ttl == cell.ttl().count()) && + (properties.local_deletion_time == cell.deletion_time().time_since_epoch().count())); + + cell_flags flags = cell_flags::none; + if (!has_value) { + flags |= cell_flags::has_empty_value_mask; + } + if (is_deleted) { + flags |= cell_flags::is_deleted_mask; + } else if (is_expiring) { + flags |= cell_flags::is_expiring_mask; + } + if (use_row_timestamp) { + flags |= cell_flags::use_row_timestamp_mask; + } + if (use_row_ttl) { + flags |= cell_flags::use_row_ttl_mask; + } + write(_sst.get_version(), writer, flags); + + if (!use_row_timestamp) { + write_delta_timestamp(writer, cell.timestamp()); + } + + if (!use_row_ttl) { + if (is_deleted) { + write_delta_local_deletion_time(writer, cell.deletion_time().time_since_epoch().count()); + } else if (is_expiring) { + write_delta_local_deletion_time(writer, cell.expiry().time_since_epoch().count()); + write_delta_ttl(writer, cell.ttl().count()); + } + } + + if (!cell_path.empty()) { + write_vint(writer, cell_path.size()); + write(_sst.get_version(), writer, cell_path); + } + + if (has_value) { + write_cell_value(writer, *cdef.type, cell_value); + } +} + +void sstable_writer_m::write_liveness_info(file_writer& writer, const row_marker& marker) { + if (marker.is_missing()) { + return; + } + + uint64_t timestamp = marker.timestamp(); + if (marker.is_dead(_sst._now)) { + // the row has expired by the time of flush + // write deletion info instead of liveness info + deletion_time dt; + dt.local_deletion_time = marker.deletion_time().time_since_epoch().count(); + dt.marked_for_delete_at = timestamp; + write_delta_deletion_time(writer, dt); + _row_deletion_written = true; + } else { // marker.is_live() + write_delta_timestamp(writer, timestamp); + if (marker.is_expiring()) { + write_delta_ttl(writer, marker.ttl().count()); + write_delta_local_deletion_time(writer, marker.expiry().time_since_epoch().count()); + } + } +} + +void sstable_writer_m::write_collection(file_writer& writer, const column_definition& cdef, + collection_mutation_view collection, const row_time_properties& properties, bool has_complex_deletion) { + auto t = static_pointer_cast(cdef.type); + auto mview = t->deserialize_mutation_form(collection); + if (has_complex_deletion) { + write_delta_deletion_time(writer, to_deletion_time(mview.tomb)); + } + + write_vint(writer, mview.cells.size()); + for (const auto& [cell_path, cell]: mview.cells) { + write_cell(writer, cell, cdef, properties, cell_path); + } +} + +void sstable_writer_m::write_cells(file_writer& writer, column_kind kind, const row& row_body, + const row_time_properties& properties, bool has_complex_deletion) { + // Note that missing columns are written based on the whole set of regular columns as defined by schema. + // This differs from Origin where all updated columns are tracked and the set of filled columns of a row + // is compared with the set of all columns filled in the memtable. So our encoding may be less optimal in some cases + // but still valid. + write_missing_columns(writer, _schema, row_body); + row_body.for_each_cell([this, &writer, kind, &properties, has_complex_deletion] (column_id id, const atomic_cell_or_collection& c) { + auto&& column_definition = _schema.column_at(kind, id); + if (!column_definition.is_atomic()) { + write_collection(writer, column_definition, c.as_collection_mutation(), properties, has_complex_deletion); + return; + } + atomic_cell_view cell = c.as_atomic_cell(); + write_cell(writer, cell, column_definition, properties); + }); +} + +void sstable_writer_m::write_row_body(file_writer& writer, const clustering_row& row, bool has_complex_deletion) { + _row_deletion_written = false; + // write_liveness_info may end up writing deletion info for the row if the row + // has expired by the time of writing. If this happens, _row_deletion_writen is set + write_liveness_info(writer, row.marker()); + if (row.tomb() && !_row_deletion_written) { + write_delta_deletion_time(writer, to_deletion_time(row.tomb().tomb())); + } + row_time_properties properties; + if (!row.marker().is_missing()) { + properties.timestamp = row.marker().timestamp(); + if (row.marker().is_expiring()) { + properties.ttl = row.marker().ttl().count(); + properties.local_deletion_time = row.marker().deletion_time().time_since_epoch().count(); + } + } + + return write_cells(writer, column_kind::regular_column, row.cells(), properties, has_complex_deletion); +} + +template +uint64_t calculate_write_size(Func&& func) { + uint64_t written_size = 0; + { + auto counting_writer = file_writer(make_sizing_output_stream(written_size)); + func(counting_writer); + counting_writer.flush().get(); + counting_writer.close().get(); + } + return written_size; +} + +void sstable_writer_m::write_static_row(const row& static_row) { + assert(_schema.is_compound()); + + // Static row flag is stored in extended flags so extension_flag is always set for static rows + row_flags flags = row_flags::extension_flag; + if (static_row.size() == _schema.static_columns_count()) { + flags |= row_flags::has_all_columns; + } + + write(_sst.get_version(), *_data_writer, flags); + write(_sst.get_version(), *_data_writer, row_extended_flags::is_static); + + // Calculate the size of the row body + auto write_row = [this, &static_row] (file_writer& writer) { + write_cells(writer, column_kind::static_column, static_row, row_time_properties{}); + }; + + uint64_t row_body_size = calculate_write_size(write_row) + unsigned_vint::serialized_size(0); + write_vint(*_data_writer, row_body_size); + write_vint(*_data_writer, 0); // as the static row always comes first, the previous row size is always zero + + write_row(*_data_writer); +} + +stop_iteration sstable_writer_m::consume(static_row&& sr) { + ensure_tombstone_is_written(); + write_static_row(sr.cells()); + return stop_iteration::no; +} + +// Find if any collection in the row contains a collection-wide tombstone +static bool row_has_complex_deletion(const schema& s, const row& r) { + bool result = false; + r.for_each_cell_until([&] (column_id id, const atomic_cell_or_collection& c) { + auto&& cdef = s.column_at(column_kind::regular_column, id); + if (cdef.is_atomic()) { + return stop_iteration::no; + } + auto t = static_pointer_cast(cdef.type); + auto mview = t->deserialize_mutation_form(c.as_collection_mutation()); + if (mview.tomb) { + result = true; + } + return stop_iteration(static_cast(mview.tomb)); + }); + + return result; +} + +void sstable_writer_m::write_clustered_row(const clustering_row& clustered_row, uint64_t prev_row_size) { + row_flags flags = row_flags::none; + row_extended_flags ext_flags = row_extended_flags::none; + if (clustered_row.marker().is_live()) { + flags |= row_flags::has_timestamp; + if (clustered_row.marker().is_expiring()) { + flags |= row_flags::has_ttl; + } + } + + if ((!clustered_row.marker().is_missing() && clustered_row.marker().is_dead(_sst._now)) || clustered_row.tomb().tomb()) { + flags |= row_flags::has_deletion; + if (clustered_row.tomb().tomb() && clustered_row.tomb().is_shadowable()) { + ext_flags = row_extended_flags::has_shadowable_deletion; + } + } + + if (clustered_row.cells().size() == _schema.regular_columns_count()) { + flags |= row_flags::has_all_columns; + } + bool has_complex_deletion = row_has_complex_deletion(_schema, clustered_row.cells()); + if (has_complex_deletion) { + flags |= row_flags::has_complex_deletion; + } + write(_sst.get_version(), *_data_writer, flags); + if (ext_flags != row_extended_flags::none) { + write(_sst.get_version(), *_data_writer, ext_flags); + } + + write_clustering_prefix(*_data_writer, _schema, clustered_row.key()); + + + auto write_row = [this, &clustered_row, has_complex_deletion] (file_writer& writer) { + write_row_body(writer, clustered_row, has_complex_deletion); + }; + + uint64_t row_body_size = calculate_write_size(write_row) + unsigned_vint::serialized_size(prev_row_size); + + write_vint(*_data_writer, row_body_size); + write_vint(*_data_writer, prev_row_size); + + write_row(*_data_writer); +} + +stop_iteration sstable_writer_m::consume(clustering_row&& cr) { + ensure_tombstone_is_written(); + uint64_t pos = _data_writer->offset(); + if (!_pi_write_m.first_clustering) { + _pi_write_m.first_clustering = cr.key(); + _pi_write_m.block_start_offset = pos; + _pi_write_m.block_next_start_offset = pos + _pi_write_m.desired_block_size; + } + write_clustered_row(cr, pos - _prev_row_start); + + _pi_write_m.last_clustering = cr.key(); + + pos = _data_writer->offset(); + _prev_row_start = pos; + if (pos >= _pi_write_m.block_next_start_offset) { + _pi_write_m.promoted_index.push_back({ + *_pi_write_m.first_clustering, + *_pi_write_m.last_clustering, + _pi_write_m.block_start_offset - _current_partition_offset, + pos - _pi_write_m.block_start_offset}); + _pi_write_m.first_clustering.reset(); + _pi_write_m.block_next_start_offset = pos + _pi_write_m.desired_block_size; + } + return stop_iteration::no; +} + +// Write clustering prefix along with its bound kind and, if not full, its size +static void write_clustering_prefix(file_writer& writer, bound_kind kind, + const schema& s, const clustering_key_prefix& clustering) { + assert(kind != bound_kind::static_clustering); + write(sstable_version_types::mc, writer, kind); + if (kind != bound_kind::clustering) { + write(sstable_version_types::mc, writer, static_cast(clustering.size(s))); + } + write_clustering_prefix(writer, s, clustering); +} + +std::vector sstable_writer_m::write_promoted_index(file_writer& writer) { + static constexpr size_t width_base = 65536; + if (_pi_write_m.promoted_index.empty()) { + return {}; + } + write(_sst.get_version(), writer, to_deletion_time(_pi_write_m.tomb)); + write_vint(writer, _pi_write_m.promoted_index.size()); + std::vector offsets; + offsets.reserve(_pi_write_m.promoted_index.size()); + uint64_t start = writer.offset(); + for (const pi_block& block: _pi_write_m.promoted_index) { + offsets.push_back(writer.offset() - start); + write_clustering_prefix(writer, bound_kind::clustering, _schema, block.first); + write_clustering_prefix(writer, bound_kind::clustering, _schema, block.last); + write_vint(writer, block.offset); + write_signed_vint(writer, block.width - width_base); + // TODO: serialize end open marker here later, for now always write "false" + // to indicate there is no end open marker + write(_sst.get_version(), writer, (std::byte)0); + } + + return offsets; +} + +stop_iteration sstable_writer_m::consume_end_of_partition() { + if (!_pi_write_m.promoted_index.empty() && _pi_write_m.first_clustering) { + _pi_write_m.promoted_index.push_back({ + *_pi_write_m.first_clustering, + *_pi_write_m.last_clustering, + _pi_write_m.block_start_offset - _current_partition_offset, + _data_writer->offset() - _pi_write_m.block_start_offset}); + } + + auto write_pi = [this] (file_writer& writer) { + return write_promoted_index(writer); + }; + + uint64_t pi_size = calculate_write_size(write_pi); + write_vint(*_index_writer, pi_size); + auto offsets = write_pi(*_index_writer); + for (uint32_t offset: offsets) { + write(_sst.get_version(), *_index_writer, offset); + } + + write(_sst.get_version(), *_data_writer, row_flags::end_of_partition); + if (!_first_key) { + _first_key = *_partition_key; + } + _last_key = std::move(*_partition_key); + return stop_iteration::no; +} + +void sstable_writer_m::consume_end_of_stream() { + seal_summary(_sst._components->summary, std::move(_first_key), std::move(_last_key), _index_sampling_state); + + if (_sst.has_component(component_type::CompressionInfo)) { + _sst._collector.add_compression_ratio(_sst._components->compression.compressed_file_length(), _sst._components->compression.uncompressed_file_length()); + } + + _index_writer->close().get(); + _index_writer.reset(); + _sst.set_first_and_last_keys(); + seal_statistics(_sst.get_version(), _sst._components->statistics, _sst._collector, + dht::global_partitioner().name(), _schema.bloom_filter_fp_chance(), + _sst._schema, _sst.get_first_decorated_key(), _sst.get_last_decorated_key()); + _cfg.monitor->on_data_write_completed(); + close_data_writer(); + _sst.write_summary(_pc); + _sst.write_filter(_pc); + _sst.write_statistics(_pc); + _sst.write_compression(_pc); + auto features = all_features(); + if (!_cfg.correctly_serialize_non_compound_range_tombstones) { + features.disable(sstable_feature::NonCompoundRangeTombstones); + } + _sst.write_scylla_metadata(_pc, _shard, std::move(features)); + _cfg.monitor->on_write_completed(); + if (!_cfg.leave_unsealed) { + _sst.seal_sstable(_cfg.backup).get(); + } + _cfg.monitor->on_flush_completed(); +} + sstable_writer::sstable_writer(sstable& sst, const schema& s, uint64_t estimated_partitions, - const sstable_writer_config& cfg, const io_priority_class& pc, shard_id shard) - : _impl(std::make_unique(sst, s, estimated_partitions, cfg, pc, shard)) - {} + const sstable_writer_config& cfg, encoding_stats enc_stats, const io_priority_class& pc, shard_id shard) { + if (sst.get_version() == sstable_version_types::mc) { + _impl = std::make_unique(sst, s, estimated_partitions, cfg, enc_stats, pc, shard); + } else { + _impl = std::make_unique(sst, s, estimated_partitions, cfg, pc, shard); + } +} void sstable_writer::consume_new_partition(const dht::decorated_key& dk) { return _impl->consume_new_partition(dk); @@ -2544,9 +3167,10 @@ future<> sstable::seal_sstable(bool backup) }); } -sstable_writer sstable::get_writer(const schema& s, uint64_t estimated_partitions, const sstable_writer_config& cfg, const io_priority_class& pc, shard_id shard) +sstable_writer sstable::get_writer(const schema& s, uint64_t estimated_partitions, + const sstable_writer_config& cfg, encoding_stats enc_stats, const io_priority_class& pc, shard_id shard) { - return sstable_writer(*this, s, estimated_partitions, cfg, pc, shard); + return sstable_writer(*this, s, estimated_partitions, cfg, enc_stats, pc, shard); } future<> sstable::write_components( @@ -2559,8 +3183,8 @@ future<> sstable::write_components( 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, &pc] () mutable { - auto wr = get_writer(*schema, estimated_partitions, cfg, pc); + 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); mr.consume_in_thread(std::move(wr)); }); } diff --git a/sstables/sstables.hh b/sstables/sstables.hh index 0b265b3842..bd060b9088 100644 --- a/sstables/sstables.hh +++ b/sstables/sstables.hh @@ -71,6 +71,7 @@ extern logging::logger sstlog; class key; class sstable_writer; class sstable_writer_k_l; +class sstable_writer_m; struct foreign_sstable_open_info; struct sstable_open_info; @@ -261,6 +262,7 @@ public: sstable_writer get_writer(const schema& s, uint64_t estimated_partitions, const sstable_writer_config&, + encoding_stats enc_stats, const io_priority_class& pc = default_priority_class(), shard_id shard = engine().cpu_id()); @@ -702,6 +704,7 @@ public: friend class components_writer; friend class sstable_writer_k_l; + friend class sstable_writer_m; friend class index_reader; template friend data_consume_context @@ -821,7 +824,8 @@ private: std::unique_ptr _impl; public: sstable_writer(sstable& sst, const schema& s, uint64_t estimated_partitions, - const sstable_writer_config&, const io_priority_class& pc, shard_id shard = engine().cpu_id()); + const sstable_writer_config&, encoding_stats enc_stats, + const io_priority_class& pc, shard_id shard = engine().cpu_id()); sstable_writer(sstable_writer&& o); sstable_writer& operator=(sstable_writer&& o); From 77fdfa3e7aeac700d9452bd1326fe6693813d6ba Mon Sep 17 00:00:00 2001 From: Vladimir Krivopalov Date: Fri, 20 Apr 2018 17:07:44 -0700 Subject: [PATCH 7/7] Add tests for writing data and index files in SSTables 3.0 ('mc') format. Signed-off-by: Vladimir Krivopalov --- tests/sstable_3_x_test.cc | 520 +++++++++++++++++- .../mc-1-big-Data.db | Bin 0 -> 30 bytes .../mc-1-big-Index.db | Bin 0 -> 8 bytes .../mc-1-big-Data.db | Bin 0 -> 38 bytes .../mc-1-big-Index.db | Bin 0 -> 8 bytes .../mc-1-big-Data.db | Bin 0 -> 51 bytes .../mc-1-big-Index.db | Bin 0 -> 8 bytes .../write_deleted_column/mc-1-big-Data.db | Bin 0 -> 25 bytes .../write_deleted_column/mc-1-big-Index.db | Bin 0 -> 8 bytes .../write_deleted_row/mc-1-big-Data.db | Bin 0 -> 29 bytes .../write_deleted_row/mc-1-big-Index.db | Bin 0 -> 8 bytes .../mc-1-big-Data.db | Bin 0 -> 364 bytes .../mc-1-big-Index.db | Bin 0 -> 8 bytes .../mc-1-big-Data.db | Bin 0 -> 87 bytes .../mc-1-big-Index.db | Bin 0 -> 24 bytes .../write_multiple_rows/mc-1-big-Data.db | Bin 0 -> 64 bytes .../write_multiple_rows/mc-1-big-Index.db | Bin 0 -> 8 bytes .../write_static_row/mc-1-big-Data.db | Bin 0 -> 37 bytes .../write_static_row/mc-1-big-Index.db | Bin 0 -> 8 bytes .../write_ttled_column/mc-1-big-Data.db | Bin 0 -> 29 bytes .../write_ttled_column/mc-1-big-Index.db | Bin 0 -> 7 bytes .../write_ttled_row/mc-1-big-Data.db | Bin 0 -> 35 bytes .../write_ttled_row/mc-1-big-Index.db | Bin 0 -> 8 bytes .../write_wide_partition/mc-1-big-Data.db | Bin 0 -> 2114406 bytes .../write_wide_partition/mc-1-big-Index.db | Bin 0 -> 66316 bytes 25 files changed, 519 insertions(+), 1 deletion(-) create mode 100644 tests/sstables/3.x/uncompressed/write_collection_incremental_update/mc-1-big-Data.db create mode 100644 tests/sstables/3.x/uncompressed/write_collection_incremental_update/mc-1-big-Index.db create mode 100644 tests/sstables/3.x/uncompressed/write_collection_wide_update/mc-1-big-Data.db create mode 100644 tests/sstables/3.x/uncompressed/write_collection_wide_update/mc-1-big-Index.db create mode 100644 tests/sstables/3.x/uncompressed/write_composite_clustering_key/mc-1-big-Data.db create mode 100644 tests/sstables/3.x/uncompressed/write_composite_clustering_key/mc-1-big-Index.db create mode 100644 tests/sstables/3.x/uncompressed/write_deleted_column/mc-1-big-Data.db create mode 100644 tests/sstables/3.x/uncompressed/write_deleted_column/mc-1-big-Index.db create mode 100644 tests/sstables/3.x/uncompressed/write_deleted_row/mc-1-big-Data.db create mode 100644 tests/sstables/3.x/uncompressed/write_deleted_row/mc-1-big-Index.db create mode 100644 tests/sstables/3.x/uncompressed/write_missing_columns_large_set/mc-1-big-Data.db create mode 100644 tests/sstables/3.x/uncompressed/write_missing_columns_large_set/mc-1-big-Index.db create mode 100644 tests/sstables/3.x/uncompressed/write_multiple_partitions/mc-1-big-Data.db create mode 100644 tests/sstables/3.x/uncompressed/write_multiple_partitions/mc-1-big-Index.db create mode 100644 tests/sstables/3.x/uncompressed/write_multiple_rows/mc-1-big-Data.db create mode 100644 tests/sstables/3.x/uncompressed/write_multiple_rows/mc-1-big-Index.db create mode 100644 tests/sstables/3.x/uncompressed/write_static_row/mc-1-big-Data.db create mode 100644 tests/sstables/3.x/uncompressed/write_static_row/mc-1-big-Index.db create mode 100644 tests/sstables/3.x/uncompressed/write_ttled_column/mc-1-big-Data.db create mode 100644 tests/sstables/3.x/uncompressed/write_ttled_column/mc-1-big-Index.db create mode 100644 tests/sstables/3.x/uncompressed/write_ttled_row/mc-1-big-Data.db create mode 100644 tests/sstables/3.x/uncompressed/write_ttled_row/mc-1-big-Index.db create mode 100644 tests/sstables/3.x/uncompressed/write_wide_partition/mc-1-big-Data.db create mode 100644 tests/sstables/3.x/uncompressed/write_wide_partition/mc-1-big-Index.db diff --git a/tests/sstable_3_x_test.cc b/tests/sstable_3_x_test.cc index da57eab885..7f04cdae1e 100644 --- a/tests/sstable_3_x_test.cc +++ b/tests/sstable_3_x_test.cc @@ -20,17 +20,23 @@ */ #include +#include +#include #include #include +#include #include "sstables/sstables.hh" #include "compress.hh" #include "schema_builder.hh" -#include "tests/test-utils.hh" #include "sstable_test.hh" #include "flat_mutation_reader_assertions.hh" +#include "memtable-sstable.hh" +#include "sstable_test.hh" +#include "tests/test_services.hh" +#include "tests/tmpdir.hh" using namespace sstables; @@ -217,3 +223,515 @@ SEASTAR_TEST_CASE(test_uncompressed_simple_read_index) { BOOST_REQUIRE_EQUAL(1, vec.size()); }); } + +static void compare_files(sstring filename1, sstring filename2) { + std::ifstream ifs1(filename1); + std::ifstream ifs2(filename2); + + std::istream_iterator b1(ifs1), e1; + std::istream_iterator b2(ifs2), e2; + BOOST_CHECK_EQUAL_COLLECTIONS(b1, e1, b2, e2); +} + +static void write_and_compare_sstables(schema_ptr s, lw_shared_ptr mt, sstring table_name) { + storage_service_for_tests ssft; + tmpdir tmp; + auto sst = sstables::test::make_test_sstable(4096, s, tmp.path, 1, sstables::sstable_version_types::mc, sstable::format_types::big); + write_memtable_to_sstable(*mt, sst).get(); + + for (auto file_type : {component_type::Data, component_type::Index}) { + auto orig_filename = + sstable::filename("tests/sstables/3.x/uncompressed/write_" + table_name, "ks", + table_name, sstables::sstable_version_types::mc, 1, big, file_type); + auto result_filename = + sstable::filename(tmp.path, "ks", table_name, sstables::sstable_version_types::mc, 1, big, file_type); + compare_files(orig_filename, result_filename); + } +} + +SEASTAR_TEST_CASE(test_write_static_row) { + return seastar::async([] { + sstring table_name = "static_row"; + // CREATE TABLE static_row (pk int, ck int, st1 int static, st2 text static, PRIMARY KEY (pk, ck)) WITH compression = {'sstable_compression': ''}; + schema_builder builder(make_lw_shared(schema(generate_legacy_id("ks", table_name), "sst3", table_name, + // partition key + {{"pk", utf8_type}}, + // clustering key + {{"ck", int32_type}}, + // regular columns + {}, + // static columns + {{"st1", int32_type}, {"st2", utf8_type}}, + // regular column name type + utf8_type, + // comment + "SSTable 3.0 format write path - static row test" + ))); + builder.set_compressor_params(compression_parameters()); + schema_ptr s = builder.build(schema_builder::compact_storage::no); + + lw_shared_ptr mt = make_lw_shared(s); + api::timestamp_type ts = api::new_timestamp(); + + // INSERT INTO static_row (pk, st1, st2) values ('key1', 1135, 'hello'); + auto key = make_dkey(s, {to_bytes("key1")}); + mutation mut{s, key}; + mut.set_static_cell("st1", data_value{1135}, ts); + mut.set_static_cell("st2", data_value{"hello"}, ts); + mt->apply(std::move(mut)); + + write_and_compare_sstables(s, mt, table_name); + }); +} + +SEASTAR_TEST_CASE(test_write_composite_clustering_key) { + return seastar::async([] { + sstring table_name = "composite_clustering_key"; + // CREATE TABLE composite_clustering_key (a int , b text, c int, d text, e int, f text, PRIMARY KEY (a, b, c, d)) WITH compression = {'sstable_compression': ''}; + schema_builder builder(make_lw_shared(schema(generate_legacy_id("ks", table_name), "sst3", table_name, + // partition key + {{"a", int32_type}}, + // clustering key + {{"b", utf8_type}, {"c", int32_type}, {"d", utf8_type}}, + // regular columns + {{"e", int32_type}, {"f", utf8_type}}, + // static columns + {}, + // regular column name type + utf8_type, + // comment + "SSTable 3.0 format write path - composite clustering key test" + ))); + builder.set_compressor_params(compression_parameters()); + schema_ptr s = builder.build(schema_builder::compact_storage::no); + + lw_shared_ptr mt = make_lw_shared(s); + api::timestamp_type ts = api::new_timestamp(); + + // INSERT INTO composite_clustering_key (a,b,c,d,e,f) values (1, 'hello', 2, 'dear', 3, 'world'); + auto key = partition_key::from_deeply_exploded(*s, { 1 }); + mutation mut{s, key}; + clustering_key ckey = clustering_key::from_deeply_exploded(*s, { "hello", 2, "dear" }); + mut.partition().apply_insert(*s, ckey, ts); + mut.set_cell(ckey, "e", data_value{3}, ts); + mut.set_cell(ckey, "f", data_value{"world"}, ts); + mt->apply(std::move(mut)); + + write_and_compare_sstables(s, mt, table_name); + }); +} + +SEASTAR_TEST_CASE(test_write_wide_partition) { + return seastar::async([] { + sstring table_name = "wide_partition"; + // CREATE TABLE wide_partition (pk text , ck text, rc text, PRIMARY KEY (pk, ck) WITH compression = {'sstable_compression': ''}; + schema_builder builder(make_lw_shared(schema(generate_legacy_id("ks", table_name), "sst3", table_name, + // partition key + {{"pk", utf8_type}}, + // clustering key + {{"ck", utf8_type}}, + // regular columns + {{"rc", utf8_type}}, + // static columns + {}, + // regular column name type + utf8_type, + // comment + "SSTable 3.0 format write path - wide partition test" + ))); + builder.set_compressor_params(compression_parameters()); + 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}; + sstring ck_base(1024, 'a'); + sstring rc_base(1024, 'b'); + api::timestamp_type ts = api::new_timestamp(); + for (auto idx: boost::irange(0, 1024)) { + clustering_key ckey = clustering_key::from_deeply_exploded(*s, {format("{}{}", ck_base, idx)}); + mut.partition().apply_insert(*s, ckey, ts); + mut.set_cell(ckey, "rc", data_value{format("{}{}", rc_base, idx)}, ts); + mt->apply(std::move(mut)); + seastar::thread::yield(); + } + + write_and_compare_sstables(s, mt, table_name); + }); +} + +SEASTAR_TEST_CASE(test_write_ttled_row) { + return seastar::async([] { + sstring table_name = "ttled_row"; + // CREATE TABLE ttled_row (pk int, ck int, rc int, PRIMARY KEY (pk)); + schema_builder builder(make_lw_shared(schema(generate_legacy_id("ks", table_name), "sst3", table_name, + // partition key + {{"pk", int32_type}}, + // clustering key + {{"ck", int32_type}}, + // regular columns + {{"rc", int32_type}}, + // static columns + {}, + // regular column name type + utf8_type, + // comment + "SSTable 3.0 format write path - TTL-ed row test" + ))); + builder.set_compressor_params(compression_parameters()); + 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; + auto key = partition_key::from_deeply_exploded(*s, { 1 }); + mutation mut{s, key}; + clustering_key ckey = clustering_key::from_deeply_exploded(*s, { 2 }); + api::timestamp_type ts = api::new_timestamp(); + gc_clock::time_point tp = gc_clock::now(); + gc_clock::duration ttl{1135}; + mut.partition().apply_insert(*s, ckey, ts, ttl, tp + ttl); + mut.set_cell(ckey, "rc", data_value{3}, ts); + mt->apply(std::move(mut)); + + write_and_compare_sstables(s, mt, table_name); + }); +} + +SEASTAR_TEST_CASE(test_write_ttled_column) { + return seastar::async([] { + sstring table_name = "ttled_column"; + // CREATE TABLE ttled_column (pk text, rc int, PRIMARY KEY (pk)); + schema_builder builder(make_lw_shared(schema(generate_legacy_id("ks", table_name), "sst3", table_name, + // partition key + {{"pk", utf8_type}}, + // clustering key + {}, + // regular columns + {{"rc", int32_type}}, + // static columns + {}, + // regular column name type + utf8_type, + // comment + "SSTable 3.0 format write path - TTL-ed column test" + ))); + builder.set_compressor_params(compression_parameters()); + schema_ptr s = builder.build(schema_builder::compact_storage::no); + + lw_shared_ptr mt = make_lw_shared(s); + + // UPDATE ttled_column USING TTL 1135 SET rc = 1 WHERE pk='key'; + auto key = make_dkey(s, {to_bytes("key")}); + mutation mut{s, key}; + api::timestamp_type ts = api::new_timestamp(); + gc_clock::duration ttl{1135}; + mut.set_clustered_cell(clustering_key::make_empty(), "rc", data_value{1}, ts, ttl); + mt->apply(std::move(mut)); + + write_and_compare_sstables(s, mt, table_name); + }); +} + +SEASTAR_TEST_CASE(test_write_deleted_column) { + return seastar::async([] { + sstring table_name = "deleted_column"; + // CREATE TABLE deleted_cell (int pk, int rc, PRIMARY KEY (pk)) WITH compression = {'sstable_compression': ''}; + schema_builder builder(make_lw_shared(schema(generate_legacy_id("ks", table_name), "sst3", table_name, + // partition key + {{"pk", int32_type}}, + // clustering key + {}, + // regular columns + {{"rc", int32_type}}, + // static columns + {}, + // regular column name type + utf8_type, + // comment + "SSTable 3.0 format write path - deleted column test" + ))); + builder.set_compressor_params(compression_parameters()); + schema_ptr s = builder.build(schema_builder::compact_storage::no); + + lw_shared_ptr mt = make_lw_shared(s); + + // DELETE rc FROM deleted_column WHERE pk=1; + auto key = partition_key::from_deeply_exploded(*s, { 1 }); + mutation mut{s, key}; + //mut.partition().apply_delete(*s, clustering_key::make_empty(), tombstone{api::new_timestamp(), gc_clock::now()}); + auto column_def = s->get_column_definition("rc"); + if (!column_def) { + throw std::runtime_error("no column definition found"); + } + mut.set_cell(clustering_key::make_empty(), *column_def, atomic_cell::make_dead(api::new_timestamp(), gc_clock::now())); + mt->apply(std::move(mut)); + + write_and_compare_sstables(s, mt, table_name); + }); +} + +SEASTAR_TEST_CASE(test_write_deleted_row) { + return seastar::async([] { + sstring table_name = "deleted_row"; + // CREATE TABLE deleted_row (int pk, int ck, PRIMARY KEY (pk, ck)) WITH compression = {'sstable_compression': ''}; + schema_builder builder(make_lw_shared(schema(generate_legacy_id("ks", table_name), "sst3", table_name, + // partition key + {{"pk", int32_type}}, + // clustering key + {{"ck", int32_type}}, + // regular columns + {}, + // static columns + {}, + // regular column name type + utf8_type, + // comment + "SSTable 3.0 format write path - deleted row test" + ))); + builder.set_compressor_params(compression_parameters()); + schema_ptr s = builder.build(schema_builder::compact_storage::no); + + lw_shared_ptr mt = make_lw_shared(s); + + // DELETE FROM deleted_row WHERE pk=1 and ck=2; + 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{api::new_timestamp(), gc_clock::now()}); + mt->apply(std::move(mut)); + + write_and_compare_sstables(s, mt, table_name); + }); +} + +SEASTAR_TEST_CASE(test_write_collection_wide_update) { + return seastar::async([] { + sstring table_name = "collection_wide_update"; + auto set_of_ints_type = set_type_impl::get_instance(int32_type, true); + // CREATE TABLE collection_wide_update (pk int, col set, PRIMARY KEY (pk)) with compression = {'sstable_compression': ''}; + schema_builder builder(make_lw_shared(schema(generate_legacy_id("ks", table_name), "sst3", table_name, + // partition key + {{"pk", int32_type}}, + // clustering key + {}, + // regular columns + {{"col", set_of_ints_type}}, + // static columns + {}, + // regular column name type + utf8_type, + // comment + "SSTable 3.0 format write path - collection wide update test" + ))); + builder.set_compressor_params(compression_parameters()); + 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}); + auto key = partition_key::from_deeply_exploded(*s, { 1 }); + mutation mut{s, key}; + + api::timestamp_type ts = api::new_timestamp(); + gc_clock::time_point tp = gc_clock::now(); + mut.partition().apply_insert(*s, clustering_key::make_empty(), ts); + set_type_impl::mutation set_values { + {ts - 1, tp}, // tombstone + { + {int32_type->decompose(2), atomic_cell::make_live(ts, bytes_view{})}, + {int32_type->decompose(3), atomic_cell::make_live(ts, bytes_view{})}, + } + }; + + mut.set_clustered_cell(clustering_key::make_empty(), *s->get_column_definition("col"), set_of_ints_type->serialize_mutation_form(set_values)); + mt->apply(std::move(mut)); + + write_and_compare_sstables(s, mt, table_name); + }); +} + +SEASTAR_TEST_CASE(test_write_collection_incremental_update) { + return seastar::async([] { + sstring table_name = "collection_incremental_update"; + auto set_of_ints_type = set_type_impl::get_instance(int32_type, true); + // CREATE TABLE collection_incremental_update (pk int, col set, PRIMARY KEY (pk)) with compression = {'sstable_compression': ''}; + schema_builder builder(make_lw_shared(schema(generate_legacy_id("ks", table_name), "sst3", table_name, + // partition key + {{"pk", int32_type}}, + // clustering key + {}, + // regular columns + {{"col", set_of_ints_type}}, + // static columns + {}, + // regular column name type + utf8_type, + // comment + "SSTable 3.0 format write path - collection incremental update test" + ))); + builder.set_compressor_params(compression_parameters()); + schema_ptr s = builder.build(schema_builder::compact_storage::no); + + lw_shared_ptr mt = make_lw_shared(s); + + // UPDATE collection_incremental_update SET col = col + {2} WHERE pk = 1; + auto key = partition_key::from_deeply_exploded(*s, { 1 }); + mutation mut{s, key}; + + api::timestamp_type ts = api::new_timestamp(); + set_type_impl::mutation set_values { + {}, // tombstone + { + {int32_type->decompose(2), atomic_cell::make_live(ts, bytes_view{})}, + } + }; + + mut.set_clustered_cell(clustering_key::make_empty(), *s->get_column_definition("col"), set_of_ints_type->serialize_mutation_form(set_values)); + mt->apply(std::move(mut)); + + write_and_compare_sstables(s, mt, table_name); + }); +} + +SEASTAR_TEST_CASE(test_write_multiple_partitions) { + return seastar::async([] { + sstring table_name = "multiple_partitions"; + // CREATE TABLE multiple_partitions (pk int, rc1 int, rc2 int, rc3 int, PRIMARY KEY (pk)) WITH compression = {'sstable_compression': ''}; + schema_builder builder(make_lw_shared(schema(generate_legacy_id("ks", table_name), "sst3", table_name, + // partition key + {{"pk", int32_type}}, + // clustering key + {}, + // regular columns + {{"rc1", int32_type}, {"rc2", int32_type}, {"rc3", int32_type}}, + // static columns + {}, + // regular column name type + utf8_type, + // comment + "SSTable 3.0 format write path - multiple partitions test" + ))); + builder.set_compressor_params(compression_parameters()); + schema_ptr s = builder.build(schema_builder::compact_storage::no); + + lw_shared_ptr mt = make_lw_shared(s); + + api::timestamp_type ts = api::new_timestamp(); + // INSERT INTO multiple_partitions (pk, rc1) VALUES (1, 10); + // INSERT INTO multiple_partitions (pk, rc2) VALUES (2, 20); + // INSERT INTO multiple_partitions (pk, rc3) VALUES (3, 30); + for (auto i : boost::irange(1, 4)) { + auto key = partition_key::from_deeply_exploded(*s, {i}); + mutation mut{s, key}; + + mut.set_cell(clustering_key::make_empty(), to_bytes(format("rc{}", i)), data_value{i * 10}, ts); + mt->apply(std::move(mut)); + ts += 10; + } + + write_and_compare_sstables(s, mt, table_name); + }); +} + +SEASTAR_TEST_CASE(test_write_multiple_rows) { + return seastar::async([] { + sstring table_name = "multiple_rows"; + // CREATE TABLE multiple_rows (pk int, ck int, rc1 int, rc2 int, rc3 int, PRIMARY KEY (pk, ck)) WITH compression = {'sstable_compression': ''}; + schema_builder builder(make_lw_shared(schema(generate_legacy_id("ks", table_name), "sst3", table_name, + // partition key + {{"pk", int32_type}}, + // clustering key + {{"ck", int32_type}}, + // regular columns + {{"rc1", int32_type}, {"rc2", int32_type}, {"rc3", int32_type}}, + // static columns + {}, + // regular column name type + utf8_type, + // comment + "SSTable 3.0 format write path - multiple rows test" + ))); + builder.set_compressor_params(compression_parameters()); + 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 = api::new_timestamp(); + mutation mut{s, key}; + + // INSERT INTO multiple_rows (pk, ck, rc1) VALUES (0, 1, 10); + // INSERT INTO multiple_rows (pk, ck, rc2) VALUES (0, 2, 20); + // INSERT INTO multiple_rows (pk, ck, rc3) VALUES (0, 3, 30); + for (auto i : boost::irange(1, 4)) { + clustering_key ckey = clustering_key::from_deeply_exploded(*s, { i }); + mut.partition().apply_insert(*s, ckey, ts); + mut.set_cell(ckey, to_bytes(format("rc{}", i)), data_value{i * 10}, ts); + ts += 10; + } + + mt->apply(std::move(mut)); + write_and_compare_sstables(s, mt, table_name); + }); +} + +// Information on missing columns is serialized differently when the number of columns is > 64. +// This test checks that this information is encoded correctly. +SEASTAR_TEST_CASE(test_write_missing_columns_large_set) { + return seastar::async([] { + sstring table_name = "missing_columns_large_set"; + // CREATE TABLE missing_columns_large_set (pk int, ck int, rc1 int, ..., rc64, PRIMARY KEY (pk, ck)) WITH compression = {'sstable_compression': ''}; + std::vector regular_columns; + regular_columns.reserve(64); + for (auto idx: boost::irange(1, 65)) { + regular_columns.push_back({to_bytes(format("rc{}", idx)), int32_type}); + } + schema_builder builder(make_lw_shared(schema(generate_legacy_id("ks", table_name), "sst3", table_name, + // partition key + {{"pk", int32_type}}, + // clustering key + {{"ck", int32_type}}, + regular_columns, + // static columns + {}, + // regular column name type + utf8_type, + // comment + "SSTable 3.0 format write path - missing columns large set test" + ))); + builder.set_compressor_params(compression_parameters()); + 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 = api::new_timestamp(); + mutation mut{s, key}; + + // INSERT INTO missing_columns_large_set (pk, ck, rc1, ..., rc62) VALUES (0, 0, 1, ..., 62); + // For missing columns, the missing ones will be written as majority are present. + { + clustering_key ckey = clustering_key::from_deeply_exploded(*s, {0}); + mut.partition().apply_insert(*s, ckey, ts); + for (auto idx: boost::irange(1, 63)) { + mut.set_cell(ckey, to_bytes(format("rc{}", idx)), data_value{idx}, ts); + } + mt->apply(std::move(mut)); + } + ts += 10; + // INSERT INTO missing_columns_large_set (pk, ck, rc63, rc64) VALUES (0, 1, 63, 64); + // For missing columns, the present ones will be written as majority are missing. + { + clustering_key ckey = clustering_key::from_deeply_exploded(*s, {1}); + mut.partition().apply_insert(*s, ckey, ts); + mut.set_cell(ckey, to_bytes(format("rc63", 63)), data_value{63}, ts); + mut.set_cell(ckey, to_bytes(format("rc64", 63)), data_value{64}, ts); + mt->apply(std::move(mut)); + } + + write_and_compare_sstables(s, mt, table_name); + }); +} + diff --git a/tests/sstables/3.x/uncompressed/write_collection_incremental_update/mc-1-big-Data.db b/tests/sstables/3.x/uncompressed/write_collection_incremental_update/mc-1-big-Data.db new file mode 100644 index 0000000000000000000000000000000000000000..54a179910545d52f7579d01e30753454fef33fcf GIT binary patch literal 30 fcmZQzVPIfjtpET2e*=&K0t%c$j4TWwVJ1cZTP_4i literal 0 HcmV?d00001 diff --git a/tests/sstables/3.x/uncompressed/write_collection_incremental_update/mc-1-big-Index.db b/tests/sstables/3.x/uncompressed/write_collection_incremental_update/mc-1-big-Index.db new file mode 100644 index 0000000000000000000000000000000000000000..b077026fd87b3cdd609bc60c41ad771d3edb460d GIT binary patch literal 8 PcmZQzVPIfjWMBXQ04D$j literal 0 HcmV?d00001 diff --git a/tests/sstables/3.x/uncompressed/write_collection_wide_update/mc-1-big-Data.db b/tests/sstables/3.x/uncompressed/write_collection_wide_update/mc-1-big-Data.db new file mode 100644 index 0000000000000000000000000000000000000000..48bd694aa050cc19c07458af077e47a9672c916e GIT binary patch literal 38 jcmZQzVPIfjtpET2e*=&K0x5z*j0_A+JRk`O$;=1fUCJqKJRuESN%w^_a5CLKa202Cm?-B&Q literal 0 HcmV?d00001 diff --git a/tests/sstables/3.x/uncompressed/write_multiple_rows/mc-1-big-Index.db b/tests/sstables/3.x/uncompressed/write_multiple_rows/mc-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/tests/sstables/3.x/uncompressed/write_static_row/mc-1-big-Data.db b/tests/sstables/3.x/uncompressed/write_static_row/mc-1-big-Data.db new file mode 100644 index 0000000000000000000000000000000000000000..cd36e089259e079b98c5f8d06805ec81cd1302f8 GIT binary patch literal 37 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 literal 0 HcmV?d00001 diff --git a/tests/sstables/3.x/uncompressed/write_wide_partition/mc-1-big-Index.db b/tests/sstables/3.x/uncompressed/write_wide_partition/mc-1-big-Index.db new file mode 100644 index 0000000000000000000000000000000000000000..3598902428574dadfc97407212d3c222cf38f93d GIT binary patch literal 66316 zcmeI*zpE8T7{>9}do__D;zc9|2`WY-L3C$kXLd#rW0ycg8%a7HK@hA&EUXgoBp|V| z)DXnN!bVIHLa@4OOpHHoes~jqLcm5VEiF9R@@OXD z_knmBh_5Z+Ibo<>0Ma8+eV4B;nB~m@RX{*qz;}hy1oABaKwKa+>DPtNd37LO1L7l4 zYiK%V0*Ziuwm=B+x2d;zPN3Za(j(9dgpimCXaWM-0wJ~67O!L`&~5?g5$FX%;yeLG zKtNm|l<@nKP0R%1H6T6${Y+5USVRvH5Ep2@yZ-J@&J&2&fcOa1+WOIU0{+(lT|hus zppDIq_xAIgK)3=#M_??_M%T{-e348a5D*q<^UY;nGZF|lfanN}1=`HUBD#QpxIk?3 zt@lq48nzgU7lHT)#D?_*v;YBdff%~m%YWiIfp`swk3g+4WR4S11O&7NVoG;b&M*^b zw}A8r^a3%lvWOxeATAI~oLTjZnLxY-#7Cf?2?{HV=m7%a0?CIz*39EmMDZFBAAwqv zXFUNuKtNm|MgQm8WjrSkuL1E9=x2f$I8Q(k5YQG#+5PoVWG2vV0qGIw1yUZZEMh$Y zWk5h!pmTq(+s>;4;RX;Lfw4g6SWiF~5D*vWLb$ybQ$G7U(!lpj`mc zBT&1PXPYCuIiLy%$P09djYaY$06<-!E8Rc-J7xm)9*`e_+7&hyQ3C|T1+tI-wm)*5 zK)eRTN1)d1*-k(Y5D*v0vAvhB^PE7u2E<38p9x~*OGJu*fVM!+;eLL?OrYHY(j(9d z