diff --git a/CHANGES.txt b/CHANGES.txt index 3b7c96d57f..0e29bca84f 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ 4.0-beta4 + * Send back client warnings when creating too many tables/keyspaces (CASSANDRA-16309) * Add dedicated tcp user timeout for streaming connection (CASSANDRA-16143) * Add generatetokens script for offline token allocation strategy generation (CASSANDRA-16205) * Remove Windows scripts (CASSANDRA-16171) diff --git a/conf/cassandra.yaml b/conf/cassandra.yaml index db7cbb231a..abfc063d60 100644 --- a/conf/cassandra.yaml +++ b/conf/cassandra.yaml @@ -1404,6 +1404,12 @@ repaired_data_tracking_for_partition_reads_enabled: false # mismatches are less actionable than confirmed ones. report_unconfirmed_repaired_data_mismatches: false +# Having many tables and/or keyspaces negatively affects performance of many operations in the +# cluster. When the number of tables/keyspaces in the cluster exceeds the following thresholds +# a client warning will be sent back to the user when creating a table or keyspace. +# table_count_warn_threshold: 150 +# keyspace_count_warn_threshold: 40 + ######################### # EXPERIMENTAL FEATURES # ######################### diff --git a/src/java/org/apache/cassandra/config/Config.java b/src/java/org/apache/cassandra/config/Config.java index 60c6945e42..dec37efe83 100644 --- a/src/java/org/apache/cassandra/config/Config.java +++ b/src/java/org/apache/cassandra/config/Config.java @@ -553,6 +553,9 @@ public class Config isClientMode = clientMode; } + public volatile int table_count_warn_threshold = 150; + public volatile int keyspace_count_warn_threshold = 40; + public static Supplier getOverrideLoadConfig() { return overrideLoadConfig; diff --git a/src/java/org/apache/cassandra/config/DatabaseDescriptor.java b/src/java/org/apache/cassandra/config/DatabaseDescriptor.java index 35c19300c2..304a0044a4 100644 --- a/src/java/org/apache/cassandra/config/DatabaseDescriptor.java +++ b/src/java/org/apache/cassandra/config/DatabaseDescriptor.java @@ -3273,5 +3273,25 @@ public class DatabaseDescriptor conf.auto_optimise_preview_repair_streams = enabled; } + public static int tableCountWarnThreshold() + { + return conf.table_count_warn_threshold; + } + + public static void setTableCountWarnThreshold(int value) + { + conf.table_count_warn_threshold = value; + } + + public static int keyspaceCountWarnThreshold() + { + return conf.keyspace_count_warn_threshold; + } + + public static void setKeyspaceCountWarnThreshold(int value) + { + conf.keyspace_count_warn_threshold = value; + } + } diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/CreateKeyspaceStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/CreateKeyspaceStatement.java index f85a4e94b9..094806c841 100644 --- a/src/java/org/apache/cassandra/cql3/statements/schema/CreateKeyspaceStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/schema/CreateKeyspaceStatement.java @@ -21,9 +21,13 @@ import java.util.Set; import com.google.common.collect.ImmutableSet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import org.apache.cassandra.audit.AuditLogContext; import org.apache.cassandra.audit.AuditLogEntryType; import org.apache.cassandra.auth.*; +import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.cql3.CQLStatement; import org.apache.cassandra.exceptions.AlreadyExistsException; import org.apache.cassandra.locator.LocalStrategy; @@ -31,12 +35,15 @@ import org.apache.cassandra.schema.KeyspaceMetadata; import org.apache.cassandra.schema.KeyspaceParams.Option; import org.apache.cassandra.schema.Keyspaces; import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff; +import org.apache.cassandra.schema.Schema; import org.apache.cassandra.service.ClientState; import org.apache.cassandra.transport.Event.SchemaChange; import org.apache.cassandra.transport.Event.SchemaChange.Change; public final class CreateKeyspaceStatement extends AlterSchemaStatement { + private static final Logger logger = LoggerFactory.getLogger(CreateKeyspaceStatement.class); + private final KeyspaceAttributes attrs; private final boolean ifNotExists; @@ -99,6 +106,20 @@ public final class CreateKeyspaceStatement extends AlterSchemaStatement return String.format("%s (%s)", getClass().getSimpleName(), keyspaceName); } + @Override + Set clientWarnings(KeyspacesDiff diff) + { + int keyspaceCount = Schema.instance.getKeyspaces().size(); + if (keyspaceCount > DatabaseDescriptor.keyspaceCountWarnThreshold()) + { + String msg = String.format("Cluster already contains %d keyspaces. Having a large number of keyspaces will significantly slow down schema dependent cluster operations.", + keyspaceCount); + logger.warn(msg); + return ImmutableSet.of(msg); + } + return ImmutableSet.of(); + } + public static final class Raw extends CQLStatement.Raw { public final String keyspaceName; diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/CreateTableStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/CreateTableStatement.java index b71351ad27..19884a7647 100644 --- a/src/java/org/apache/cassandra/cql3/statements/schema/CreateTableStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/schema/CreateTableStatement.java @@ -23,11 +23,15 @@ import com.google.common.collect.ImmutableSet; import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import org.apache.cassandra.audit.AuditLogContext; import org.apache.cassandra.audit.AuditLogEntryType; import org.apache.cassandra.auth.DataResource; import org.apache.cassandra.auth.IResource; import org.apache.cassandra.auth.Permission; +import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.cql3.*; import org.apache.cassandra.db.marshal.*; import org.apache.cassandra.exceptions.AlreadyExistsException; @@ -45,6 +49,7 @@ import static com.google.common.collect.Iterables.concat; public final class CreateTableStatement extends AlterSchemaStatement { + private static final Logger logger = LoggerFactory.getLogger(CreateTableStatement.class); private final String tableName; private final Map rawColumns; @@ -364,6 +369,21 @@ public final class CreateTableStatement extends AlterSchemaStatement } } + @Override + public Set clientWarnings(KeyspacesDiff diff) + { + int tableCount = Schema.instance.getNumberOfTables(); + if (tableCount > DatabaseDescriptor.tableCountWarnThreshold()) + { + String msg = String.format("Cluster already contains %d tables in %d keyspaces. Having a large number of tables will significantly slow down schema dependent cluster operations.", + tableCount, + Schema.instance.getKeyspaces().size()); + logger.warn(msg); + return ImmutableSet.of(msg); + } + return ImmutableSet.of(); + } + private static class DefaultNames { private static final String DEFAULT_CLUSTERING_NAME = "column"; diff --git a/src/java/org/apache/cassandra/service/StorageService.java b/src/java/org/apache/cassandra/service/StorageService.java index 26625c1ccb..90a0b89923 100644 --- a/src/java/org/apache/cassandra/service/StorageService.java +++ b/src/java/org/apache/cassandra/service/StorageService.java @@ -5810,4 +5810,30 @@ public class StorageService extends NotificationBroadcasterSupport implements IE { DatabaseDescriptor.setAutoOptimisePreviewRepairStreams(enabled); } + + public int getTableCountWarnThreshold() + { + return DatabaseDescriptor.tableCountWarnThreshold(); + } + + public void setTableCountWarnThreshold(int value) + { + if (value < 0) + throw new IllegalStateException("Table count warn threshold should be positive, not "+value); + logger.info("Changing table count warn threshold from {} to {}", getTableCountWarnThreshold(), value); + DatabaseDescriptor.setTableCountWarnThreshold(value); + } + + public int getKeyspaceCountWarnThreshold() + { + return DatabaseDescriptor.keyspaceCountWarnThreshold(); + } + + public void setKeyspaceCountWarnThreshold(int value) + { + if (value < 0) + throw new IllegalStateException("Keyspace count warn threshold should be positive, not "+value); + logger.info("Changing keyspace count warn threshold from {} to {}", getKeyspaceCountWarnThreshold(), value); + DatabaseDescriptor.setKeyspaceCountWarnThreshold(value); + } } diff --git a/src/java/org/apache/cassandra/service/StorageServiceMBean.java b/src/java/org/apache/cassandra/service/StorageServiceMBean.java index 7100ed6f72..63c2d96037 100644 --- a/src/java/org/apache/cassandra/service/StorageServiceMBean.java +++ b/src/java/org/apache/cassandra/service/StorageServiceMBean.java @@ -837,4 +837,9 @@ public interface StorageServiceMBean extends NotificationEmitter public void setAutoOptimiseFullRepairStreams(boolean enabled); public boolean autoOptimisePreviewRepairStreams(); public void setAutoOptimisePreviewRepairStreams(boolean enabled); + + int getTableCountWarnThreshold(); + void setTableCountWarnThreshold(int value); + int getKeyspaceCountWarnThreshold(); + void setKeyspaceCountWarnThreshold(int value); } diff --git a/test/unit/org/apache/cassandra/schema/CreateTableValidationTest.java b/test/unit/org/apache/cassandra/schema/CreateTableValidationTest.java index 9708552382..c9a13568c8 100644 --- a/test/unit/org/apache/cassandra/schema/CreateTableValidationTest.java +++ b/test/unit/org/apache/cassandra/schema/CreateTableValidationTest.java @@ -18,10 +18,22 @@ */ package org.apache.cassandra.schema; +import java.io.IOException; + +import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.cql3.CQLTester; +import org.apache.cassandra.cql3.QueryOptions; import org.apache.cassandra.exceptions.ConfigurationException; +import org.apache.cassandra.transport.Message; +import org.apache.cassandra.transport.ProtocolVersion; +import org.apache.cassandra.transport.Server; +import org.apache.cassandra.transport.SimpleClient; +import org.apache.cassandra.transport.messages.QueryMessage; + import org.junit.Test; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; public class CreateTableValidationTest extends CQLTester @@ -48,4 +60,43 @@ public class CreateTableValidationTest extends CQLTester // sanity check createTable("CREATE TABLE %s (a int PRIMARY KEY, b int) WITH bloom_filter_fp_chance = 0.1"); } + + @Test + public void testCreateKeyspaceTableWarning() throws IOException + { + requireNetwork(); + int tableCountWarn = DatabaseDescriptor.tableCountWarnThreshold(); + int keyspaceCountWarn = DatabaseDescriptor.keyspaceCountWarnThreshold(); + DatabaseDescriptor.setTableCountWarnThreshold(Schema.instance.getNumberOfTables()); + DatabaseDescriptor.setKeyspaceCountWarnThreshold(Schema.instance.getKeyspaces().size()); + + try (SimpleClient client = newSimpleClient(ProtocolVersion.CURRENT).connect(false)) + { + String createKeyspace = "CREATE KEYSPACE createkswarning%d WITH REPLICATION={'class':'org.apache.cassandra.locator.NetworkTopologyStrategy','datacenter1':'2'}"; + QueryMessage query = new QueryMessage(String.format(createKeyspace, 1), QueryOptions.DEFAULT); + Message.Response resp = client.execute(query); + assertTrue(resp.getWarnings().size() > 0); + assertTrue(resp.getWarnings().get(0).contains("Having a large number of keyspaces will significantly")); + + DatabaseDescriptor.setKeyspaceCountWarnThreshold(Schema.instance.getKeyspaces().size() + 1); + query = new QueryMessage(String.format(createKeyspace, 2), QueryOptions.DEFAULT); + resp = client.execute(query); + assertTrue(resp.getWarnings() == null || resp.getWarnings().isEmpty()); + + query = new QueryMessage(String.format("CREATE TABLE %s.%s (id int primary key, x int)", KEYSPACE, "test1"), QueryOptions.DEFAULT); + resp = client.execute(query); + assertTrue(resp.getWarnings().size() > 0); + assertTrue(resp.getWarnings().get(0).contains("Having a large number of tables")); + + DatabaseDescriptor.setTableCountWarnThreshold(Schema.instance.getNumberOfTables() + 1); + query = new QueryMessage(String.format("CREATE TABLE %s.%s (id int primary key, x int)", KEYSPACE, "test2"), QueryOptions.DEFAULT); + resp = client.execute(query); + assertTrue(resp.getWarnings() == null || resp.getWarnings().isEmpty()); + } + finally + { + DatabaseDescriptor.setTableCountWarnThreshold(tableCountWarn); + DatabaseDescriptor.setKeyspaceCountWarnThreshold(keyspaceCountWarn); + } + } }