Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.11.9
4 changes: 4 additions & 0 deletions conf/cassandra.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2531,6 +2531,10 @@ drop_compact_storage_enabled: false
# This would also apply to system keyspaces.
# maximum_replication_factor_warn_threshold: -1
# maximum_replication_factor_fail_threshold: -1
#
# When true, allows the usage of misprepared statements, only warns in the logs.
# When false, misprepared statements will result in an error to the client. Default is true.
# misprepared_statements_enabled: true

#role_name_policy:
# # Implementation class of a validator. When not in form of FQCN, the
Expand Down
1 change: 1 addition & 0 deletions src/java/org/apache/cassandra/config/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -937,6 +937,7 @@ public static void setClientMode(boolean clientMode)
public volatile boolean user_timestamps_enabled = true;
public volatile boolean alter_table_enabled = true;
public volatile boolean group_by_enabled = true;
public volatile boolean misprepared_statements_enabled = true;
public volatile boolean bulk_load_enabled = true;
public volatile boolean drop_truncate_table_enabled = true;
public volatile boolean drop_keyspace_enabled = true;
Expand Down
9 changes: 9 additions & 0 deletions src/java/org/apache/cassandra/config/DatabaseDescriptor.java
Original file line number Diff line number Diff line change
Expand Up @@ -5483,6 +5483,15 @@ public static void setUseStatementsEnabled(boolean enabled)
}
}

public static boolean getMispreparedStatementsEnabled()
{
return conf.misprepared_statements_enabled;
}

public static void setMispreparedStatementsEnabled(boolean enabled)
{
conf.misprepared_statements_enabled = enabled;
}

public static AccordSpec getAccord()
{
Expand Down
15 changes: 15 additions & 0 deletions src/java/org/apache/cassandra/config/GuardrailsOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -1329,6 +1329,21 @@ public boolean getVectorTypeEnabled()
return config.vector_type_enabled;
}

@Override
public boolean getMispreparedStatementsEnabled()
{
return config.misprepared_statements_enabled;
}


public void setMispreparedStatementsEnabled(boolean enabled)
{
updatePropertyWithLogging("misprepare_statements_enabled",
enabled,
() -> config.misprepared_statements_enabled,
x -> config.misprepared_statements_enabled = x);
}

@Override
public void setVectorTypeEnabled(boolean enabled)
{
Expand Down
2 changes: 1 addition & 1 deletion src/java/org/apache/cassandra/cql3/QueryProcessor.java
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ public class QueryProcessor implements QueryHandler
// counters. Callers of processStatement are responsible for correctly notifying metrics
public static final CQLMetrics metrics = new CQLMetrics();


// Paging size to use when preloading prepared statements.
public static final int PRELOAD_PREPARED_STATEMENTS_FETCH_SIZE = 5000;

Expand Down Expand Up @@ -494,7 +495,6 @@ public static Prepared parseAndPrepare(String query, ClientState clientState, bo
// Note: if 2 threads prepare the same query, we'll live so don't bother synchronizing
CQLStatement statement = raw.prepare(clientState);
statement.validate(clientState);

// Set CQL string for AlterSchemaStatement as this is used to serialize the transformation
// in the cluster metadata log
if (statement instanceof AlterSchemaStatement)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,21 @@ public void validate(ClientState state) throws InvalidRequestException
checkFalse(isVirtual() && hasConditions(), "Conditional updates are not supported by virtual tables");

if (attrs.isTimestampSet())
{
Guardrails.userTimestampsEnabled.ensureEnabled(state);
}
// Misprepare guardrail shouldn't block system keyspaces
if (SchemaConstants.isSystemKeyspace(metadata.keyspace))
return;

boolean hasRestriction = restrictions != null &&
restrictions.hasPartitionKeyRestrictions() ||
restrictions.hasClusteringColumnsRestrictions() ||
restrictions.hasNonPrimaryKeyRestrictions();
if (getBindVariables().isEmpty() && hasRestriction)
{
Guardrails.onMisprepared(state);
}
}

public void validateDiskUsage(QueryOptions options, ClientState state)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -339,8 +339,19 @@ public void authorize(ClientState state) throws InvalidRequestException, Unautho

public void validate(ClientState state) throws InvalidRequestException
{
if (parameters.allowFiltering && !SchemaConstants.isSystemKeyspace(table.keyspace))
if (SchemaConstants.isSystemKeyspace(table.keyspace))
return;
if (parameters.allowFiltering)
Guardrails.allowFilteringEnabled.ensureEnabled(state);
boolean hasRestrictions = restrictions != null &&
(restrictions.hasPartitionKeyRestrictions() ||
restrictions.hasClusteringColumnsRestrictions() ||
restrictions.hasNonPrimaryKeyRestrictions());
if (getBindVariables().isEmpty()
&& hasRestrictions)
{
Guardrails.onMisprepared(state);
}
}

@Override
Expand Down
39 changes: 39 additions & 0 deletions src/java/org/apache/cassandra/db/guardrails/Guardrails.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ public final class Guardrails implements GuardrailsMBean
public static final String MBEAN_NAME = "org.apache.cassandra.db:type=Guardrails";

public static final GuardrailsConfigProvider CONFIG_PROVIDER = GuardrailsConfigProvider.instance;

public static final String MISPREPARED_STATEMENT_WARN_MESSAGE = "Performance Tip: This query contains literal values in the WHERE clause. " + "Using '?' placeholders (bind markers) is much more efficient. " + "It allows the server to cache this query once and reuse it for different values, " + "reducing memory usage and improving speed.";

private static final GuardrailsOptions DEFAULT_CONFIG = DatabaseDescriptor.getGuardrailsConfig();

@VisibleForTesting
Expand Down Expand Up @@ -613,6 +616,19 @@ public final class Guardrails implements GuardrailsMBean
format("The keyspace %s has a replication factor of %s, above the %s threshold of %s.",
what, value, isWarning ? "warning" : "failure", threshold));

/**
* Prevents heap exhaustion caused by the anti-pattern of preparing queries with
* hardcoded literals instead of bind markers. This prevents filling the statement cache with non-reusable entries.
*
* @see <a href="https://issues.apache.org/jira/browse/CASSANDRA-21139">CASSANDRA-21139</a>
*/

public static final EnableFlag mispreparedStatementsEnabled =
new EnableFlag("misprepared_statements_enabled",
"Mis-prepared statements cause server-side memory exhaustion and high GC pressure by flooding the statement cache with non-reusable query entries",
state -> CONFIG_PROVIDER.getOrCreate(state).getMispreparedStatementsEnabled(),
"Mis-prepared statements");

public static final MaxThreshold maximumAllowableTimestamp =
new MaxThreshold("maximum_timestamp",
"Timestamps too far in the future can lead to data that can't be easily overwritten",
Expand Down Expand Up @@ -705,6 +721,18 @@ public void setKeyspacesThreshold(int warn, int fail)
DEFAULT_CONFIG.setKeyspacesThreshold(warn, fail);
}

@Override
public boolean getMispreparedStatementsEnabled()
{
return DEFAULT_CONFIG.getMispreparedStatementsEnabled();
}

@Override
public void setMispreparedStatementsEnabled(boolean enabled)
{
DEFAULT_CONFIG.setMispreparedStatementsEnabled(enabled);
}

@Override
public int getTablesWarnThreshold()
{
Expand Down Expand Up @@ -1886,4 +1914,15 @@ private static long minimumTimestampAsRelativeMicros(@Nullable DurationSpec.Long
? Long.MIN_VALUE
: (ClientState.getLastTimestampMicros() - duration.toMicroseconds());
}

public static void onMisprepared(ClientState state)
{
mispreparedStatementsEnabled.ensureEnabled(state);

// only warn if user ins't super user or a external call
if (!state.isInternal && !state.isSuper())
{
mispreparedStatementsEnabled.warn(MISPREPARED_STATEMENT_WARN_MESSAGE);
}
}
}
36 changes: 36 additions & 0 deletions src/java/org/apache/cassandra/db/guardrails/GuardrailsConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,40 @@ public interface GuardrailsConfig
*/
boolean getVectorTypeEnabled();

/**
* <p>
* A statement is considered "mis-prepared" if it contains hardcoded literal values
* instead of bind markers. This is a performance anti-pattern because it prevents
* query plan reuse and floods the server-side Prepared Statement Cache with
* unique entries, leading to heap exhaustion and high GC pressure.
* <p>
* <b>LWT and Batch Considerations:</b>
* This check applies to both the {@code WHERE} clause and the {@code IF} conditions
* in Lightweight Transactions (LWT). For example, the following is considered
* mis-prepared because of the hardcoded literals:
* <pre>{@code
* UPDATE users SET description = 'v2' WHERE id = 1 AND name = 'v1' IF description = 'v0'
* }</pre>
* To be compliant, it should use bind markers ({@code ?}):
* <pre>{@code
* UPDATE users SET description = ? WHERE id = ? AND name = ? IF description = ?
* }</pre>
* For {@code BATCH} statements, if any individual statement within the batch is
* identified as mis-prepared, the entire batch triggers this guardrail.

*
* @return true if the usage of mis-prepared statements is enabled, false otherwise. Returns true by default.
* {@code false} if mis-prepared statements should be strictly rejected, causing the query to fail.
* @see <a href="https://issues.apache.org/jira/browse/CASSANDRA-21139">CASSANDRA-21139</a>
*/

boolean getMispreparedStatementsEnabled();

/**
* sets whether misprepared statements is enabled
*/
void setMispreparedStatementsEnabled(boolean enabled);

/**
* Sets whether new columns can be created with vector type
*
Expand Down Expand Up @@ -509,6 +543,8 @@ public interface GuardrailsConfig

void setIntersectFilteringQueryEnabled(boolean value);



/**
* @return A timestamp that if a user supplied timestamp is after will trigger a warning
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@ public interface GuardrailsMBean
*/
void setKeyspacesThreshold(int warn, int fail);

/**
* @return the use of misprepared statement is enabled
*/
boolean getMispreparedStatementsEnabled();

void setMispreparedStatementsEnabled(boolean enabled);

/**
* @return The threshold to warn when creating more tables than threshold.
* -1 means disabled.
Expand Down
13 changes: 13 additions & 0 deletions src/java/org/apache/cassandra/service/StorageService.java
Original file line number Diff line number Diff line change
Expand Up @@ -4836,6 +4836,19 @@ public void setColumnIndexCacheSize(int cacheSizeInKB)
logger.info("Updated column_index_cache_size to {}", cacheSizeInKB);
}

@Override
public boolean getMispreparedStatementsEnabled()
{
return DatabaseDescriptor.getMispreparedStatementsEnabled();
}

@Override
public void setMispreparedStatementsEnabled(boolean enabled)
{
DatabaseDescriptor.setMispreparedStatementsEnabled(enabled);
logger.info("Updated misprepared_statements_enabled to {}", enabled);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why logging

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this logging be kept for auditing (as we removed logging from DatabaseDescriptor).

}

/*
* In CASSANDRA-17668, JMX setters that did not throw standard exceptions were deprecated in favor of ones that do.
* For consistency purposes, the respective getter "getColumnIndexCacheSize" was also deprecated and replaced by
Expand Down
10 changes: 10 additions & 0 deletions src/java/org/apache/cassandra/service/StorageServiceMBean.java
Original file line number Diff line number Diff line change
Expand Up @@ -1097,6 +1097,16 @@ default int upgradeSSTables(String keyspaceName, boolean excludeCurrentVersion,
@Deprecated(since = "5.0")
public void setColumnIndexCacheSize(int cacheSizeInKB);

/**
* Returns whether use of misprepared statements is enabled
*/
public boolean getMispreparedStatementsEnabled();

/**
* Sets whether use of misprepared statements is enabled
*/
public void setMispreparedStatementsEnabled(boolean enabled);

/** Returns the threshold for skipping the column index when caching partition info **/
public int getColumnIndexCacheSizeInKiB();

Expand Down
21 changes: 21 additions & 0 deletions test/unit/org/apache/cassandra/config/DatabaseDescriptorTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -781,6 +781,27 @@ public void testRowIndexSizeWarnEqAbort()
DatabaseDescriptor.applyReadThresholdsValidations(conf);
}

@Test
public void testMispreparedStatementsEnabled() {
boolean originalValue = DatabaseDescriptor.getMispreparedStatementsEnabled();
try
{
DatabaseDescriptor.setMispreparedStatementsEnabled(true);
Assert.assertTrue("Value should be true after setting to true",
DatabaseDescriptor.getMispreparedStatementsEnabled());

DatabaseDescriptor.setMispreparedStatementsEnabled(false);
Assert.assertFalse("Value should be false after setting to false",
DatabaseDescriptor.getMispreparedStatementsEnabled());

Assert.assertTrue("Default value of misprepared_statement_enabled must be true", originalValue);
}
finally
{
DatabaseDescriptor.setMispreparedStatementsEnabled(originalValue);
}
}

@Test
public void testRowIndexSizeWarnEnabledAbortDisabled()
{
Expand Down
Loading