From e6a61be8c857106d5d99a270b2d17de9f84c4d67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andre=CC=81s=20de=20la=20Pen=CC=83a?= Date: Tue, 6 Nov 2018 19:01:47 +0000 Subject: [PATCH] Add flag to disable SASI indexes, and warning on creation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit patch by Andres de la Peña; reviewed by Robert Stupp for CASSANDRA-14866 --- CHANGES.txt | 5 +- NEWS.txt | 8 +++ conf/cassandra.yaml | 19 +++++-- .../org/apache/cassandra/config/Config.java | 2 + .../cassandra/config/DatabaseDescriptor.java | 17 +++++- .../cql3/statements/CreateIndexStatement.java | 15 ++++- .../cql3/statements/CreateViewStatement.java | 9 ++- .../org/apache/cassandra/db/view/View.java | 3 +- .../cassandra/index/sasi/SASIIndex.java | 2 + .../org/apache/cassandra/cql3/ViewTest.java | 55 +++++++++++++++++-- .../cassandra/index/sasi/SASICQLTest.java | 48 ++++++++++++++++ 11 files changed, 164 insertions(+), 19 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 2f9813e8d0..f520aeda7a 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,10 +1,11 @@ 3.11.5 + * Add flag to disable SASI indexes, and warnings on creation (CASSANDRA-14866) Merged from 3.0: * Improve `nodetool status -r` speed (CASSANDRA-14847) * Improve merkle tree size and time on heap (CASSANDRA-14096) * Add missing commands to nodetool_completion (CASSANDRA-14916) * Anti-compaction temporarily corrupts sstable state for readers (CASSANDRA-15004) - Merged from 2.2: +Merged from 2.2: * Multi-version in-JVM dtests (CASSANDRA-14937) @@ -43,7 +44,7 @@ Merged from 3.0: * Fix static column order for SELECT * wildcard queries (CASSANDRA-14638) * sstableloader should use discovered broadcast address to connect intra-cluster (CASSANDRA-14522) * Fix reading columns with non-UTF names from schema (CASSANDRA-14468) - Merged from 2.2: +Merged from 2.2: * CircleCI docker image should bake in more dependencies (CASSANDRA-14985) * Don't enable client transports when bootstrap is pending (CASSANDRA-14525) * MigrationManager attempts to pull schema from different major version nodes (CASSANDRA-14928) diff --git a/NEWS.txt b/NEWS.txt index d5a91284ef..2feac814ea 100644 --- a/NEWS.txt +++ b/NEWS.txt @@ -50,6 +50,14 @@ Upgrading - repair_session_max_tree_depth setting has been added to cassandra.yaml to allow operators to reduce merkle tree size if repair is creating too much heap pressure. See CASSANDRA-14096 for details. +Experimental features +--------------------- + - An 'enable_sasi_indexes' flag, true by default, has been added to cassandra.yaml to allow operators to prevent + the creation of new SASI indexes, which are considered experimental and are not recommended for production use. + (See https://www.mail-archive.com/dev@cassandra.apache.org/msg13582.html) + - The flags 'enable_sasi_indexes' and 'enable_materialized_views' have been grouped under an experimental features + section in cassandra.yaml. + 3.11.4 ====== diff --git a/conf/cassandra.yaml b/conf/cassandra.yaml index a263d8aaca..918200889c 100644 --- a/conf/cassandra.yaml +++ b/conf/cassandra.yaml @@ -1112,10 +1112,6 @@ enable_user_defined_functions: false # This option has no effect, if enable_user_defined_functions is false. enable_scripted_user_defined_functions: false -# Enables materialized view creation on this node. -# Materialized views are considered experimental and are not recommended for production use. -enable_materialized_views: true - # The default Windows kernel timer and scheduling resolution is 15.6ms for power conservation. # Lowering this value on Windows can provide much tighter latency and better throughput, however # some virtualized environments may see a negative performance impact from changing this setting @@ -1247,4 +1243,17 @@ back_pressure_strategy: # time and queue contention while iterating the backlog of messages. # An interval of 0 disables any wait time, which is the behavior of former Cassandra versions. # -# otc_backlog_expiration_interval_ms: 200 \ No newline at end of file +# otc_backlog_expiration_interval_ms: 200 + + +######################### +# EXPERIMENTAL FEATURES # +######################### + +# Enables materialized view creation on this node. +# Materialized views are considered experimental and are not recommended for production use. +enable_materialized_views: true + +# Enables SASI index creation on this node. +# SASI indexes are considered experimental and are not recommended for production use. +enable_sasi_indexes: true \ No newline at end of file diff --git a/src/java/org/apache/cassandra/config/Config.java b/src/java/org/apache/cassandra/config/Config.java index 528cf4f74d..1976b95bfc 100644 --- a/src/java/org/apache/cassandra/config/Config.java +++ b/src/java/org/apache/cassandra/config/Config.java @@ -348,6 +348,8 @@ public class Config public boolean enable_materialized_views = true; + public boolean enable_sasi_indexes = true; + /** * Optionally disable asynchronous UDF execution. * Disabling asynchronous UDF execution also implicitly disables the security-manager! diff --git a/src/java/org/apache/cassandra/config/DatabaseDescriptor.java b/src/java/org/apache/cassandra/config/DatabaseDescriptor.java index 069a17e3d0..99f8575a3f 100644 --- a/src/java/org/apache/cassandra/config/DatabaseDescriptor.java +++ b/src/java/org/apache/cassandra/config/DatabaseDescriptor.java @@ -2407,11 +2407,26 @@ public class DatabaseDescriptor conf.user_defined_function_warn_timeout = userDefinedFunctionWarnTimeout; } - public static boolean enableMaterializedViews() + public static boolean getEnableMaterializedViews() { return conf.enable_materialized_views; } + public static void setEnableMaterializedViews(boolean enableMaterializedViews) + { + conf.enable_materialized_views = enableMaterializedViews; + } + + public static boolean getEnableSASIIndexes() + { + return conf.enable_sasi_indexes; + } + + public static void setEnableSASIIndexes(boolean enableSASIIndexes) + { + conf.enable_sasi_indexes = enableSASIIndexes; + } + public static long getUserDefinedFunctionFailTimeout() { return conf.user_defined_function_fail_timeout; diff --git a/src/java/org/apache/cassandra/cql3/statements/CreateIndexStatement.java b/src/java/org/apache/cassandra/cql3/statements/CreateIndexStatement.java index 88afc6b72a..d615917e88 100644 --- a/src/java/org/apache/cassandra/cql3/statements/CreateIndexStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/CreateIndexStatement.java @@ -29,18 +29,20 @@ import org.slf4j.LoggerFactory; import org.apache.cassandra.auth.Permission; import org.apache.cassandra.config.CFMetaData; import org.apache.cassandra.config.ColumnDefinition; +import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.config.Schema; import org.apache.cassandra.cql3.CFName; import org.apache.cassandra.cql3.ColumnIdentifier; import org.apache.cassandra.cql3.IndexName; -import org.apache.cassandra.db.marshal.DurationType; import org.apache.cassandra.db.marshal.MapType; import org.apache.cassandra.exceptions.InvalidRequestException; import org.apache.cassandra.exceptions.RequestValidationException; import org.apache.cassandra.exceptions.UnauthorizedException; +import org.apache.cassandra.index.sasi.SASIIndex; import org.apache.cassandra.schema.IndexMetadata; import org.apache.cassandra.schema.Indexes; import org.apache.cassandra.service.ClientState; +import org.apache.cassandra.service.ClientWarn; import org.apache.cassandra.service.MigrationManager; import org.apache.cassandra.service.QueryState; import org.apache.cassandra.thrift.ThriftValidation; @@ -229,6 +231,17 @@ public class CreateIndexStatement extends SchemaAlteringStatement { kind = IndexMetadata.Kind.CUSTOM; indexOptions = properties.getOptions(); + + if (properties.customClass.equals(SASIIndex.class.getName())) + { + if (!DatabaseDescriptor.getEnableSASIIndexes()) + throw new InvalidRequestException("SASI indexes are disabled. Enable in cassandra.yaml to use."); + + logger.warn("Creating SASI index {} for {}.{}. {}", + acceptedName, cfm.ksName, cfm.cfName, SASIIndex.USAGE_WARNING); + + ClientWarn.instance.warn(SASIIndex.USAGE_WARNING); + } } else { diff --git a/src/java/org/apache/cassandra/cql3/statements/CreateViewStatement.java b/src/java/org/apache/cassandra/cql3/statements/CreateViewStatement.java index c191fa108f..51e0aaf227 100644 --- a/src/java/org/apache/cassandra/cql3/statements/CreateViewStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/CreateViewStatement.java @@ -123,7 +123,7 @@ public class CreateViewStatement extends SchemaAlteringStatement public Event.SchemaChange announceMigration(QueryState queryState, boolean isLocalOnly) throws RequestValidationException { - if (!DatabaseDescriptor.enableMaterializedViews()) + if (!DatabaseDescriptor.getEnableMaterializedViews()) { throw new InvalidRequestException("Materialized views are disabled. Enable in cassandra.yaml to use."); } @@ -327,13 +327,12 @@ public class CreateViewStatement extends SchemaAlteringStatement whereClauseText, viewCfm); - logger.warn("Creating materialized view {} for {}.{}. " + - "Materialized views are experimental and are not recommended for production use.", - definition.viewName, cfm.ksName, cfm.cfName); + logger.warn("Creating materialized view {} for {}.{}. {}", + definition.viewName, cfm.ksName, cfm.cfName, View.USAGE_WARNING); try { - ClientWarn.instance.warn("Materialized views are experimental and are not recommended for production use."); + ClientWarn.instance.warn(View.USAGE_WARNING); MigrationManager.announceNewView(definition, isLocalOnly); return new Event.SchemaChange(Event.SchemaChange.Change.CREATED, Event.SchemaChange.Target.TABLE, keyspace(), columnFamily()); } diff --git a/src/java/org/apache/cassandra/db/view/View.java b/src/java/org/apache/cassandra/db/view/View.java index 9a00b137a5..d7cd827cfc 100644 --- a/src/java/org/apache/cassandra/db/view/View.java +++ b/src/java/org/apache/cassandra/db/view/View.java @@ -33,7 +33,6 @@ import org.apache.cassandra.config.CFMetaData; import org.apache.cassandra.config.ColumnDefinition; import org.apache.cassandra.config.Schema; import org.apache.cassandra.config.ViewDefinition; -import org.apache.cassandra.cql3.ColumnIdentifier; import org.apache.cassandra.cql3.MultiColumnRelation; import org.apache.cassandra.cql3.QueryOptions; import org.apache.cassandra.cql3.Relation; @@ -57,6 +56,8 @@ import org.apache.cassandra.utils.FBUtilities; */ public class View { + public final static String USAGE_WARNING = "Materialized views are experimental and are not recommended for production use."; + private static final Logger logger = LoggerFactory.getLogger(View.class); public final String name; diff --git a/src/java/org/apache/cassandra/index/sasi/SASIIndex.java b/src/java/org/apache/cassandra/index/sasi/SASIIndex.java index 2c1d088b2d..4bf94ef545 100644 --- a/src/java/org/apache/cassandra/index/sasi/SASIIndex.java +++ b/src/java/org/apache/cassandra/index/sasi/SASIIndex.java @@ -59,6 +59,8 @@ import org.apache.cassandra.utils.concurrent.OpOrder; public class SASIIndex implements Index, INotificationConsumer { + public final static String USAGE_WARNING = "SASI indexes are experimental and are not recommended for production use."; + private static class SASIIndexBuildingSupport implements IndexBuildingSupport { public SecondaryIndexBuilder getIndexBuildTask(ColumnFamilyStore cfs, diff --git a/test/unit/org/apache/cassandra/cql3/ViewTest.java b/test/unit/org/apache/cassandra/cql3/ViewTest.java index eb9c855883..8a98a8e355 100644 --- a/test/unit/org/apache/cassandra/cql3/ViewTest.java +++ b/test/unit/org/apache/cassandra/cql3/ViewTest.java @@ -18,12 +18,9 @@ package org.apache.cassandra.cql3; -import static org.junit.Assert.*; - import java.util.ArrayList; import java.util.HashSet; import java.util.List; -import java.util.UUID; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import com.google.common.util.concurrent.Uninterruptibles; @@ -43,14 +40,16 @@ import org.apache.cassandra.concurrent.Stage; import org.apache.cassandra.concurrent.StageManager; import org.apache.cassandra.config.CFMetaData; import org.apache.cassandra.config.ColumnDefinition; +import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.db.ColumnFamilyStore; import org.apache.cassandra.db.Keyspace; import org.apache.cassandra.db.SystemKeyspace; import org.apache.cassandra.db.compaction.CompactionManager; import org.apache.cassandra.db.marshal.AsciiType; -import org.apache.cassandra.exceptions.InvalidRequestException; +import org.apache.cassandra.db.view.View; import org.apache.cassandra.exceptions.SyntaxException; import org.apache.cassandra.schema.KeyspaceParams; +import org.apache.cassandra.service.ClientWarn; import org.apache.cassandra.transport.ProtocolVersion; import org.apache.cassandra.utils.FBUtilities; @@ -1421,4 +1420,52 @@ public class ViewTest extends CQLTester { execute("CREATE MATERIALIZED VIEW myview AS SELECT a, b FROM \"\" WHERE b IS NOT NULL PRIMARY KEY (b, a)"); } + + /** + * Tests that a client warning is issued on materialized view creation. + */ + @Test + public void testClientWarningOnCreate() throws Throwable + { + createTable("CREATE TABLE %s (k int PRIMARY KEY, v int)"); + + ClientWarn.instance.captureWarnings(); + String viewName = keyspace() + ".warning_view"; + execute("CREATE MATERIALIZED VIEW " + viewName + + " AS SELECT v FROM %s WHERE k IS NOT NULL AND v IS NOT NULL PRIMARY KEY (v, k)"); + views.add(viewName); + List warnings = ClientWarn.instance.getWarnings(); + + Assert.assertNotNull(warnings); + Assert.assertEquals(1, warnings.size()); + Assert.assertEquals(View.USAGE_WARNING, warnings.get(0)); + } + + /** + * Tests the configuration flag to disable materialized views. + */ + @Test + public void testDisableMaterializedViews() throws Throwable + { + createTable("CREATE TABLE %s (k int PRIMARY KEY, v int)"); + + executeNet(protocolVersion, "USE " + keyspace()); + + boolean enableMaterializedViews = DatabaseDescriptor.getEnableMaterializedViews(); + try + { + DatabaseDescriptor.setEnableMaterializedViews(false); + createView("view1", "CREATE MATERIALIZED VIEW %s AS SELECT v FROM %%s WHERE k IS NOT NULL AND v IS NOT NULL PRIMARY KEY (v, k)"); + Assert.fail("Should not be able to create a materialized view if they are disabled"); + } + catch (Throwable e) + { + Assert.assertTrue(e instanceof InvalidQueryException); + Assert.assertTrue(e.getMessage().contains("Materialized views are disabled")); + } + finally + { + DatabaseDescriptor.setEnableMaterializedViews(enableMaterializedViews); + } + } } \ No newline at end of file diff --git a/test/unit/org/apache/cassandra/index/sasi/SASICQLTest.java b/test/unit/org/apache/cassandra/index/sasi/SASICQLTest.java index efef880987..17bd1967b1 100644 --- a/test/unit/org/apache/cassandra/index/sasi/SASICQLTest.java +++ b/test/unit/org/apache/cassandra/index/sasi/SASICQLTest.java @@ -28,7 +28,10 @@ import com.datastax.driver.core.Row; import com.datastax.driver.core.Session; import com.datastax.driver.core.SimpleStatement; import junit.framework.Assert; +import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.cql3.CQLTester; +import org.apache.cassandra.exceptions.InvalidRequestException; +import org.apache.cassandra.service.ClientWarn; public class SASICQLTest extends CQLTester { @@ -78,4 +81,49 @@ public class SASICQLTest extends CQLTester Assert.assertEquals(20, rs.size()); } } + + /** + * Tests that a client warning is issued on SASI index creation. + */ + @Test + public void testClientWarningOnCreate() + { + createTable("CREATE TABLE %s (k int PRIMARY KEY, v int)"); + + ClientWarn.instance.captureWarnings(); + createIndex("CREATE CUSTOM INDEX ON %s (v) USING 'org.apache.cassandra.index.sasi.SASIIndex'"); + List warnings = ClientWarn.instance.getWarnings(); + + Assert.assertNotNull(warnings); + Assert.assertEquals(1, warnings.size()); + Assert.assertEquals(SASIIndex.USAGE_WARNING, warnings.get(0)); + } + + /** + * Tests the configuration flag to disable SASI indexes. + */ + @Test + public void testDisableSASIIndexes() + { + createTable("CREATE TABLE %s (k int PRIMARY KEY, v int)"); + + boolean enableSASIIndexes = DatabaseDescriptor.getEnableSASIIndexes(); + try + { + DatabaseDescriptor.setEnableSASIIndexes(false); + createIndex("CREATE CUSTOM INDEX ON %s (v) USING 'org.apache.cassandra.index.sasi.SASIIndex'"); + Assert.fail("Should not be able to create a SASI index if they are disabled"); + } + catch (RuntimeException e) + { + Throwable cause = e.getCause(); + Assert.assertNotNull(cause); + Assert.assertTrue(cause instanceof InvalidRequestException); + Assert.assertTrue(cause.getMessage().contains("SASI indexes are disabled")); + } + finally + { + DatabaseDescriptor.setEnableSASIIndexes(enableSASIIndexes); + } + } }