mirror of
https://github.com/scylladb/scylladb.git
synced 2026-04-26 03:20:37 +00:00
Schema digest is calculated by querying for mutations of all schema tables, then compacting them so that all tombstones in them are dropped. However, even if the mutation becomes empty after compaction, we still feed its partition key. If the same mutations were compacted prior to the query, because the tombstones expire, we won't get any mutation at all and won't feed the partition key. So schema digest will change once an empty partition of some schema table is compacted away. Tombstones expire 7 days after schema change which introduces them. If one of the nodes is restarted after that, it will compute a different table schema digest on boot. This may cause performance problems. When sending a request from coordinator to replica, the replica needs schema_ptr of exact schema version request by the coordinator. If it doesn't know that version, it will request it from the coordinator and perform a full schema merge. This adds latency to every such request. Schema versions which are not referenced are currently kept in cache for only 1 second, so if request flow has low-enough rate, this situation results in perpetual schema pulls. Afterae8d2a550d(5.2.0), it is more liekly to run into this situation, because table creation generates tombstones for all schema tables relevant to the table, even the ones which will be otherwise empty for the new table (e.g. computed_columns). This change inroduces a cluster feature which when enabled will change digest calculation to be insensitive to expiry by ignoring empty partitions in digest calculation. When the feature is enabled, schema_ptrs are reloaded so that the window of discrepancy during transition is short and no rolling restart is required. A similar problem was fixed for per-node digest calculation in c2ba94dc39e4add9db213751295fb17b95e6b962. Per-table digest calculation was not fixed at that time because we didn't persist enabled features and they were not enabled early-enough on boot for us to depend on them in digest calculation. Now they are enabled before non-system tables are loaded so digest calculation can rely on cluster features. Fixes #4485. Manually tested using ccm on cluster upgrade scenarios and node restarts. Closes #14441 * github.com:scylladb/scylladb: test: schema_change_test: Verify digests also with TABLE_DIGEST_INSENSITIVE_TO_EXPIRY enabled schema_mutations, migration_manager: Ignore empty partitions in per-table digest migration_manager, schema_tables: Implement migration_manager::reload_schema() schema_tables: Avoid crashing when table selector has only one kind of tables (cherry picked from commitcf81eef370)
168 lines
7.1 KiB
C++
168 lines
7.1 KiB
C++
/*
|
|
* Copyright 2015-present ScyllaDB
|
|
*/
|
|
|
|
/*
|
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
*/
|
|
|
|
#include "schema_mutations.hh"
|
|
#include "canonical_mutation.hh"
|
|
#include "db/schema_tables.hh"
|
|
#include "hashers.hh"
|
|
#include "utils/UUID_gen.hh"
|
|
|
|
schema_mutations::schema_mutations(canonical_mutation columnfamilies,
|
|
canonical_mutation columns,
|
|
bool is_view,
|
|
std::optional<canonical_mutation> indices,
|
|
std::optional<canonical_mutation> dropped_columns,
|
|
std::optional<canonical_mutation> scylla_tables,
|
|
std::optional<canonical_mutation> view_virtual_columns,
|
|
std::optional<canonical_mutation> computed_columns)
|
|
: _columnfamilies(columnfamilies.to_mutation(is_view ? db::schema_tables::views() : db::schema_tables::tables()))
|
|
, _columns(columns.to_mutation(db::schema_tables::columns()))
|
|
, _view_virtual_columns(view_virtual_columns ? mutation_opt{view_virtual_columns.value().to_mutation(db::schema_tables::view_virtual_columns())} : std::nullopt)
|
|
, _computed_columns(computed_columns ? mutation_opt{computed_columns.value().to_mutation(db::schema_tables::computed_columns())} : std::nullopt)
|
|
, _indices(indices ? mutation_opt{indices.value().to_mutation(db::schema_tables::indexes())} : std::nullopt)
|
|
, _dropped_columns(dropped_columns ? mutation_opt{dropped_columns.value().to_mutation(db::schema_tables::dropped_columns())} : std::nullopt)
|
|
, _scylla_tables(scylla_tables ? mutation_opt{scylla_tables.value().to_mutation(db::schema_tables::scylla_tables())} : std::nullopt)
|
|
{}
|
|
|
|
void schema_mutations::copy_to(std::vector<mutation>& dst) const {
|
|
dst.push_back(_columnfamilies);
|
|
dst.push_back(_columns);
|
|
if (_view_virtual_columns) {
|
|
dst.push_back(*_view_virtual_columns);
|
|
}
|
|
if (_computed_columns) {
|
|
dst.push_back(*_computed_columns);
|
|
}
|
|
if (_indices) {
|
|
dst.push_back(*_indices);
|
|
}
|
|
if (_dropped_columns) {
|
|
dst.push_back(*_dropped_columns);
|
|
}
|
|
if (_scylla_tables) {
|
|
dst.push_back(*_scylla_tables);
|
|
}
|
|
}
|
|
|
|
table_schema_version schema_mutations::digest(db::schema_features sf) const {
|
|
if (_scylla_tables) {
|
|
auto rs = query::result_set(*_scylla_tables);
|
|
if (!rs.empty()) {
|
|
auto&& row = rs.row(0);
|
|
auto val = row.get<utils::UUID>("version");
|
|
if (val) {
|
|
return table_schema_version(*val);
|
|
}
|
|
}
|
|
}
|
|
|
|
md5_hasher h;
|
|
|
|
if (!sf.contains<db::schema_feature::TABLE_DIGEST_INSENSITIVE_TO_EXPIRY>()) {
|
|
// Disable this feature so that the digest remains compactible with Scylla
|
|
// versions prior to this feature.
|
|
// This digest affects the table schema version calculation and it's important
|
|
// that all nodes arrive at the same table schema version to avoid needless schema version
|
|
// pulls. It used to be the case that when table schema versions were calculated on boot we
|
|
// didn't yet know all the cluster features, so we could get different table versions after reboot
|
|
// in an already upgraded cluster. However, they are now available, and if
|
|
// TABLE_DIGEST_INSENSITIVE_TO_EXPIRY is enabled, we can compute with DIGEST_INSENSITIVE_TO_EXPIRY
|
|
// enabled.
|
|
sf.remove<db::schema_feature::DIGEST_INSENSITIVE_TO_EXPIRY>();
|
|
}
|
|
|
|
db::schema_tables::feed_hash_for_schema_digest(h, _columnfamilies, sf);
|
|
db::schema_tables::feed_hash_for_schema_digest(h, _columns, sf);
|
|
if (_view_virtual_columns && !_view_virtual_columns->partition().empty()) {
|
|
db::schema_tables::feed_hash_for_schema_digest(h, *_view_virtual_columns, sf);
|
|
}
|
|
if (_computed_columns && !_computed_columns->partition().empty()) {
|
|
db::schema_tables::feed_hash_for_schema_digest(h, *_computed_columns, sf);
|
|
}
|
|
if (_indices && !_indices->partition().empty()) {
|
|
db::schema_tables::feed_hash_for_schema_digest(h, *_indices, sf);
|
|
}
|
|
if (_dropped_columns && !_dropped_columns->partition().empty()) {
|
|
db::schema_tables::feed_hash_for_schema_digest(h, *_dropped_columns, sf);
|
|
}
|
|
if (_scylla_tables) {
|
|
db::schema_tables::feed_hash_for_schema_digest(h, *_scylla_tables, sf);
|
|
}
|
|
return table_schema_version(utils::UUID_gen::get_name_UUID(h.finalize()));
|
|
}
|
|
|
|
std::optional<sstring> schema_mutations::partitioner() const {
|
|
if (_scylla_tables) {
|
|
auto rs = query::result_set(*_scylla_tables);
|
|
if (!rs.empty()) {
|
|
return rs.row(0).get<sstring>("partitioner");
|
|
}
|
|
}
|
|
return { };
|
|
}
|
|
|
|
static mutation_opt compact(const mutation_opt& m) {
|
|
if (!m) {
|
|
return m;
|
|
}
|
|
return db::schema_tables::compact_for_schema_digest(*m);
|
|
}
|
|
|
|
static mutation_opt compact(const mutation& m) {
|
|
return db::schema_tables::compact_for_schema_digest(m);
|
|
}
|
|
|
|
bool schema_mutations::operator==(const schema_mutations& other) const {
|
|
return compact(_columnfamilies) == compact(other._columnfamilies)
|
|
&& compact(_columns) == compact(other._columns)
|
|
&& compact(_view_virtual_columns) == compact(other._view_virtual_columns)
|
|
&& compact(_computed_columns) == compact(other._computed_columns)
|
|
&& compact(_indices) == compact(other._indices)
|
|
&& compact(_dropped_columns) == compact(other._dropped_columns)
|
|
&& compact(_scylla_tables) == compact(other._scylla_tables)
|
|
;
|
|
}
|
|
|
|
bool schema_mutations::operator!=(const schema_mutations& other) const {
|
|
return !(*this == other);
|
|
}
|
|
|
|
bool schema_mutations::live() const {
|
|
return _columnfamilies.live_row_count() > 0 || _columns.live_row_count() > 0 ||
|
|
(_view_virtual_columns && _view_virtual_columns->live_row_count() > 0) ||
|
|
(_computed_columns && _computed_columns->live_row_count() > 0);
|
|
}
|
|
|
|
bool schema_mutations::is_view() const {
|
|
return _columnfamilies.schema() == db::schema_tables::views();
|
|
}
|
|
|
|
std::ostream& operator<<(std::ostream& out, const schema_mutations& sm) {
|
|
out << "schema_mutations{\n";
|
|
out << " tables=" << sm.columnfamilies_mutation() << ",\n";
|
|
out << " scylla_tables=" << sm.scylla_tables() << ",\n";
|
|
out << " columns=" << sm.columns_mutation() << ",\n";
|
|
out << " dropped_columns=" << sm.dropped_columns_mutation() << ",\n";
|
|
out << " indices=" << sm.indices_mutation() << ",\n";
|
|
out << " computed_columns=" << sm.computed_columns_mutation() << ",\n";
|
|
out << " view_virtual_columns=" << sm.view_virtual_columns_mutation() << "\n";
|
|
out << "}";
|
|
return out;
|
|
}
|
|
|
|
schema_mutations& schema_mutations::operator+=(schema_mutations&& sm) {
|
|
_columnfamilies += std::move(sm._columnfamilies);
|
|
_columns += std::move(sm._columns);
|
|
apply(_computed_columns, std::move(sm._computed_columns));
|
|
apply(_view_virtual_columns, std::move(sm._view_virtual_columns));
|
|
apply(_indices, std::move(sm._indices));
|
|
apply(_dropped_columns, std::move(sm._dropped_columns));
|
|
apply(_scylla_tables, std::move(sm._scylla_tables));
|
|
return *this;
|
|
}
|