diff --git a/cql3/statements/cf_prop_defs.cc b/cql3/statements/cf_prop_defs.cc index fb055de7d9..5d336c51a9 100644 --- a/cql3/statements/cf_prop_defs.cc +++ b/cql3/statements/cf_prop_defs.cc @@ -60,6 +60,7 @@ const sstring cf_prop_defs::COMPACTION_ENABLED_KEY = "enabled"; const sstring cf_prop_defs::KW_TABLETS = "tablets"; const sstring cf_prop_defs::KW_STORAGE_ENGINE = "storage_engine"; +const sstring cf_prop_defs::KW_LARGE_DATA_GUARDRAILS_ENABLED = "large_data_guardrails_enabled"; schema::extensions_map cf_prop_defs::make_schema_extensions(const db::extensions& exts) const { schema::extensions_map er; @@ -109,6 +110,7 @@ void cf_prop_defs::validate(const data_dictionary::database db, sstring ks_name, KW_COMPRESSION, KW_CRC_CHECK_CHANCE, KW_ID, KW_PAXOSGRACESECONDS, KW_SYNCHRONOUS_UPDATES, KW_TABLETS, KW_STORAGE_ENGINE, + KW_LARGE_DATA_GUARDRAILS_ENABLED, }); static std::set obsolete_keywords({ sstring("index_interval"), @@ -210,6 +212,12 @@ void cf_prop_defs::validate(const data_dictionary::database db, sstring ks_name, throw exceptions::configuration_exception(format("Illegal value for '{}'", KW_STORAGE_ENGINE)); } } + + if (has_property(KW_LARGE_DATA_GUARDRAILS_ENABLED) && get_boolean(KW_LARGE_DATA_GUARDRAILS_ENABLED, false)) { + if (!db.features().large_data_guardrails) { + throw exceptions::configuration_exception("large_data_guardrails_enabled cannot be used until all nodes in the cluster enable this feature"); + } + } } std::map cf_prop_defs::get_compaction_type_options() const { @@ -417,6 +425,9 @@ void cf_prop_defs::apply_to_builder(schema_builder& builder, schema::extensions_ builder.set_logstor(); } } + if (has_property(KW_LARGE_DATA_GUARDRAILS_ENABLED)) { + builder.set_large_data_guardrails_enabled(get_boolean(KW_LARGE_DATA_GUARDRAILS_ENABLED, false)); + } } void cf_prop_defs::validate_minimum_int(const sstring& field, int32_t minimum_value, int32_t default_value) const diff --git a/cql3/statements/cf_prop_defs.hh b/cql3/statements/cf_prop_defs.hh index b1d236b6d9..9157f755f0 100644 --- a/cql3/statements/cf_prop_defs.hh +++ b/cql3/statements/cf_prop_defs.hh @@ -65,6 +65,7 @@ public: static const sstring KW_TABLETS; static const sstring KW_STORAGE_ENGINE; + static const sstring KW_LARGE_DATA_GUARDRAILS_ENABLED; // FIXME: In origin the following consts are in CFMetaData. static constexpr int32_t DEFAULT_DEFAULT_TIME_TO_LIVE = 0; diff --git a/db/schema_tables.cc b/db/schema_tables.cc index e3d2dccc4b..acc310ed1a 100644 --- a/db/schema_tables.cc +++ b/db/schema_tables.cc @@ -338,6 +338,7 @@ schema_ptr scylla_tables(schema_features features) { sb.with_column("tablets", map_type_impl::get_instance(utf8_type, utf8_type, false)); sb.with_column("storage_engine", utf8_type); + sb.with_column("large_data_guardrails_enabled", boolean_type); sb.with_hash_version(); s = sb.build(); @@ -1702,6 +1703,17 @@ mutation make_scylla_tables_mutation(schema_ptr table, api::timestamp_type times if (table->logstor_enabled()) { m.set_clustered_cell(ckey, "storage_engine", "logstor", timestamp); } + // Write the large_data_guardrails_enabled column only when enabled. + // When disabled (the default), omit the cell entirely so that old nodes + // that don't know this column can still read the SSTable during rolling + // upgrade or rollback. The CQL validation gate ensures that 'true' can + // only be set once all nodes support the column, so it is safe to write + // the live cell at that point. + if (table->large_data_guardrails_enabled()) { + auto& guardrails_cdef = *scylla_tables()->get_column_definition("large_data_guardrails_enabled"); + m.set_clustered_cell(ckey, guardrails_cdef, + atomic_cell::make_live(*boolean_type, timestamp, boolean_type->decompose(true))); + } // In-memory tables are deprecated since scylla-2024.1.0 // FIXME: delete the column when there's no live version supporting it anymore. // Writing it here breaks upgrade rollback to versions that do not support the in_memory schema_feature @@ -1935,6 +1947,23 @@ utils::chunked_vector make_update_table_mutations(service::storage_pro utils::chunked_vector mutations; add_table_or_view_to_schema_mutation(new_table, timestamp, false, mutations); make_update_indices_mutations(sp, old_table, new_table, timestamp, mutations); + + // When large_data_guardrails_enabled transitions from true to false, + // write a tombstone to override the previous live cell in scylla_tables. + // make_scylla_tables_mutation only writes a live cell when enabled, so + // the tombstone must be added here where we have the old schema. + // This is safe because the CQL feature gate ensures all nodes are + // upgraded before the property can be set to true. + if (old_table->large_data_guardrails_enabled() && !new_table->large_data_guardrails_enabled()) { + schema_ptr s = tables(); + auto pkey = partition_key::from_singular(*s, new_table->ks_name()); + auto ckey = clustering_key::from_singular(*s, new_table->cf_name()); + mutation m(scylla_tables(), pkey); + auto& guardrails_cdef = *scylla_tables()->get_column_definition("large_data_guardrails_enabled"); + m.set_clustered_cell(ckey, guardrails_cdef, atomic_cell::make_dead(timestamp, gc_clock::now())); + mutations.emplace_back(std::move(m)); + } + make_update_columns_mutations(std::move(old_table), std::move(new_table), timestamp, mutations); warn(unimplemented::cause::TRIGGERS); @@ -2194,6 +2223,8 @@ static void prepare_builder_from_scylla_tables_row(const schema_ctxt& ctxt, sche throw std::invalid_argument(format("Invalid value for storage_engine: {}", *storage_engine)); } } + auto guardrails_enabled = table_row.get("large_data_guardrails_enabled"); + builder.set_large_data_guardrails_enabled(guardrails_enabled.value_or(false)); } schema_ptr create_table_from_mutations(const schema_ctxt& ctxt, schema_mutations sm, const data_dictionary::user_types_storage& user_types, schema_ptr cdc_schema, std::optional version) diff --git a/gms/feature_service.hh b/gms/feature_service.hh index 0a7c1be7ad..8fb2ac024b 100644 --- a/gms/feature_service.hh +++ b/gms/feature_service.hh @@ -181,6 +181,7 @@ public: gms::feature writetime_ttl_individual_element { *this, "WRITETIME_TTL_INDIVIDUAL_ELEMENT"sv }; gms::feature arbitrary_tablet_boundaries { *this, "ARBITRARY_TABLET_BOUNDARIES"sv }; gms::feature large_data_virtual_tables { *this, "LARGE_DATA_VIRTUAL_TABLES"sv }; + gms::feature large_data_guardrails { *this, "LARGE_DATA_GUARDRAILS"sv }; gms::feature keyspace_multi_rf_change { *this, "KEYSPACE_MULTI_RF_CHANGE"sv }; public: diff --git a/schema/schema.cc b/schema/schema.cc index e21d318ee5..6bd426d258 100644 --- a/schema/schema.cc +++ b/schema/schema.cc @@ -711,6 +711,7 @@ table_schema_version schema::calculate_digest(const schema::raw_schema& r) { } feed_hash(h, r._props.tablet_options); + feed_hash(h, r._large_data_guardrails_enabled); return table_schema_version(utils::UUID_gen::get_name_UUID(h.finalize())); } diff --git a/schema/schema.hh b/schema/schema.hh index 1f127ba681..63d50f24ab 100644 --- a/schema/schema.hh +++ b/schema/schema.hh @@ -612,6 +612,7 @@ private: // schema digest. It is also not set locally on a schema tables. std::reference_wrapper _sharder; bool _in_memory = false; + bool _large_data_guardrails_enabled = false; std::optional _view_info; user_properties _props; @@ -814,6 +815,10 @@ public: return _raw._per_partition_rate_limit_options; } + bool large_data_guardrails_enabled() const { + return _raw._large_data_guardrails_enabled; + } + const ::speculative_retry& speculative_retry() const { return _raw._props.speculative_retry; } diff --git a/schema/schema_builder.hh b/schema/schema_builder.hh index 7ee1eb8c22..6464a23e2a 100644 --- a/schema/schema_builder.hh +++ b/schema/schema_builder.hh @@ -251,6 +251,10 @@ public: _raw._in_memory = in_memory; return *this; } + schema_builder& set_large_data_guardrails_enabled(bool enabled) { + _raw._large_data_guardrails_enabled = enabled; + return *this; + } schema_builder& set_tablet_options(std::map&& hints); diff --git a/test/boost/schema_change_test.cc b/test/boost/schema_change_test.cc index ca6d739076..4c963c1e50 100644 --- a/test/boost/schema_change_test.cc +++ b/test/boost/schema_change_test.cc @@ -676,7 +676,7 @@ SEASTAR_TEST_CASE(test_system_schema_version_is_stable) { // If you changed the schema of system.batchlog then this is expected to fail. // Just replace expected version with the new version. - BOOST_REQUIRE_EQUAL(s->version(), table_schema_version(utils::UUID("c3f984e4-f886-3616-bb80-f8c68ed93595"))); + BOOST_REQUIRE_EQUAL(s->version(), table_schema_version(utils::UUID("95ec5e20-12f2-361a-90b3-662f8999400b"))); }); }