Send back client warning if creating too many tables/keyspaces

Patch by marcuse; reviewed by Alex Petrov, Brandon Williams, Yifan Cai for CASSANDRA-16309
This commit is contained in:
Marcus Eriksson 2020-12-03 10:17:11 +01:00
parent 1515a6e60c
commit 786cb11e18
9 changed files with 153 additions and 0 deletions

View File

@ -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)

View File

@ -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 #
#########################

View File

@ -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<Config> getOverrideLoadConfig()
{
return overrideLoadConfig;

View File

@ -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;
}
}

View File

@ -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<String> 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;

View File

@ -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<ColumnIdentifier, CQL3Type.Raw> rawColumns;
@ -364,6 +369,21 @@ public final class CreateTableStatement extends AlterSchemaStatement
}
}
@Override
public Set<String> 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";

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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);
}
}
}