Make prepared statement cache size configurable

patch by Robert Stupp; reviewed by Benjamin Lerer for CASSANDRA-11555
This commit is contained in:
Robert Stupp 2016-05-02 17:03:56 +02:00
parent 3513fbcfb9
commit 9581209b35
5 changed files with 125 additions and 32 deletions

View File

@ -1,4 +1,5 @@
3.6
* Make prepared statement cache size configurable (CASSANDRA-11555)
* Integrated JMX authentication and authorization (CASSANDRA-10091)
* Add units to stress ouput (CASSANDRA-11352)
* Fix PER PARTITION LIMIT for single and multi partitions queries (CASSANDRA-11603)

View File

@ -215,6 +215,34 @@ disk_failure_policy: stop
# ignore: ignore fatal errors and let the batches fail
commit_failure_policy: stop
# Maximum size of the native protocol prepared statement cache
#
# Valid values are either "auto" (omitting the value) or a value greater 0.
#
# Note that specifying a too large value will result in long running GCs and possbily
# out-of-memory errors. Keep the value at a small fraction of the heap.
#
# If you constantly see "prepared statements discarded in the last minute because
# cache limit reached" messages, the first step is to investigate the root cause
# of these messages and check whether prepared statements are used correctly -
# i.e. use bind markers for variable parts.
#
# Do only change the default value, if you really have more prepared statements than
# fit in the cache. In most cases it is not neccessary to change this value.
# Constantly re-preparing statements is a performance penalty.
#
# Default value ("auto") is 1/256th of the heap or 10MB, whichever is greater
prepared_statements_cache_size_mb:
# Maximum size of the Thrift prepared statement cache
#
# If you do not use Thrift at all, it is safe to leave this value at "auto".
#
# See description of 'prepared_statements_cache_size_mb' above for more information.
#
# Default value ("auto") is 1/256th of the heap or 10MB, whichever is greater
thrift_prepared_statements_cache_size_mb:
# Maximum size of the key cache in memory.
#
# Each key cache hit saves 1 seek and each row cache hit saves 2 seeks at the

View File

@ -288,6 +288,17 @@ public class Config
public int windows_timer_interval = 0;
/**
* Size of the CQL prepared statements cache in MB.
* Defaults to 1/256th of the heap size or 10MB, whichever is greater.
*/
public Long prepared_statements_cache_size_mb = null;
/**
* Size of the Thrift prepared statements cache in MB.
* Defaults to 1/256th of the heap size or 10MB, whichever is greater.
*/
public Long thrift_prepared_statements_cache_size_mb = null;
public boolean enable_user_defined_functions = false;
public boolean enable_scripted_user_defined_functions = false;
/**

View File

@ -96,6 +96,9 @@ public class DatabaseDescriptor
private static RequestSchedulerId requestSchedulerId;
private static RequestSchedulerOptions requestSchedulerOptions;
private static long preparedStatementsCacheSizeInMB;
private static long thriftPreparedStatementsCacheSizeInMB;
private static long keyCacheSizeInMB;
private static long counterCacheSizeInMB;
private static long indexSummaryCapacityInMB;
@ -639,6 +642,38 @@ public class DatabaseDescriptor
}
try
{
// if prepared_statements_cache_size_mb option was set to "auto" then size of the cache should be "max(1/256 of Heap (in MB), 10MB)"
preparedStatementsCacheSizeInMB = (conf.prepared_statements_cache_size_mb == null)
? Math.max(10, (int) (Runtime.getRuntime().maxMemory() / 1024 / 1024 / 256))
: conf.prepared_statements_cache_size_mb;
if (preparedStatementsCacheSizeInMB <= 0)
throw new NumberFormatException(); // to escape duplicating error message
}
catch (NumberFormatException e)
{
throw new ConfigurationException("prepared_statements_cache_size_mb option was set incorrectly to '"
+ conf.prepared_statements_cache_size_mb + "', supported values are <integer> >= 0.", false);
}
try
{
// if thrift_prepared_statements_cache_size_mb option was set to "auto" then size of the cache should be "max(1/256 of Heap (in MB), 10MB)"
thriftPreparedStatementsCacheSizeInMB = (conf.thrift_prepared_statements_cache_size_mb == null)
? Math.max(10, (int) (Runtime.getRuntime().maxMemory() / 1024 / 1024 / 256))
: conf.thrift_prepared_statements_cache_size_mb;
if (thriftPreparedStatementsCacheSizeInMB <= 0)
throw new NumberFormatException(); // to escape duplicating error message
}
catch (NumberFormatException e)
{
throw new ConfigurationException("thrift_prepared_statements_cache_size_mb option was set incorrectly to '"
+ conf.thrift_prepared_statements_cache_size_mb + "', supported values are <integer> >= 0.", false);
}
try
{
// if key_cache_size_in_mb option was set to "auto" then size of the cache should be "min(5% of Heap (in MB), 100MB)
@ -1992,6 +2027,16 @@ public class DatabaseDescriptor
return conf.windows_timer_interval;
}
public static long getPreparedStatementsCacheSizeMB()
{
return preparedStatementsCacheSizeInMB;
}
public static long getThriftPreparedStatementsCacheSizeMB()
{
return thriftPreparedStatementsCacheSizeInMB;
}
public static boolean enableUserDefinedFunctions()
{
return conf.enable_user_defined_functions;

View File

@ -34,9 +34,9 @@ import org.slf4j.LoggerFactory;
import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap;
import com.googlecode.concurrentlinkedhashmap.EntryWeigher;
import com.googlecode.concurrentlinkedhashmap.EvictionListener;
import org.antlr.runtime.*;
import org.apache.cassandra.concurrent.ScheduledExecutors;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.config.Schema;
import org.apache.cassandra.cql3.functions.Function;
import org.apache.cassandra.cql3.functions.FunctionName;
@ -65,7 +65,6 @@ public class QueryProcessor implements QueryHandler
private static final Logger logger = LoggerFactory.getLogger(QueryProcessor.class);
private static final MemoryMeter meter = new MemoryMeter().withGuessing(MemoryMeter.Guess.FALLBACK_BEST).ignoreKnownSingletons();
private static final long MAX_CACHE_PREPARED_MEMORY = Runtime.getRuntime().maxMemory() / 256;
private static final EntryWeigher<MD5Digest, ParsedStatement.Prepared> cqlMemoryUsageWeigher = new EntryWeigher<MD5Digest, ParsedStatement.Prepared>()
{
@ -97,45 +96,48 @@ public class QueryProcessor implements QueryHandler
public static final CQLMetrics metrics = new CQLMetrics();
private static final AtomicInteger lastMinuteEvictionsCount = new AtomicInteger(0);
private static final AtomicInteger thriftLastMinuteEvictionsCount = new AtomicInteger(0);
static
{
preparedStatements = new ConcurrentLinkedHashMap.Builder<MD5Digest, ParsedStatement.Prepared>()
.maximumWeightedCapacity(MAX_CACHE_PREPARED_MEMORY)
.maximumWeightedCapacity(capacityToBytes(DatabaseDescriptor.getPreparedStatementsCacheSizeMB()))
.weigher(cqlMemoryUsageWeigher)
.listener(new EvictionListener<MD5Digest, ParsedStatement.Prepared>()
{
public void onEviction(MD5Digest md5Digest, ParsedStatement.Prepared prepared)
{
metrics.preparedStatementsEvicted.inc();
lastMinuteEvictionsCount.incrementAndGet();
}
.listener((md5Digest, prepared) -> {
metrics.preparedStatementsEvicted.inc();
lastMinuteEvictionsCount.incrementAndGet();
}).build();
thriftPreparedStatements = new ConcurrentLinkedHashMap.Builder<Integer, ParsedStatement.Prepared>()
.maximumWeightedCapacity(MAX_CACHE_PREPARED_MEMORY)
.maximumWeightedCapacity(capacityToBytes(DatabaseDescriptor.getThriftPreparedStatementsCacheSizeMB()))
.weigher(thriftMemoryUsageWeigher)
.listener(new EvictionListener<Integer, ParsedStatement.Prepared>()
{
public void onEviction(Integer integer, ParsedStatement.Prepared prepared)
{
metrics.preparedStatementsEvicted.inc();
lastMinuteEvictionsCount.incrementAndGet();
}
.listener((integer, prepared) -> {
metrics.preparedStatementsEvicted.inc();
thriftLastMinuteEvictionsCount.incrementAndGet();
})
.build();
ScheduledExecutors.scheduledTasks.scheduleAtFixedRate(new Runnable()
{
public void run()
{
long count = lastMinuteEvictionsCount.getAndSet(0);
if (count > 0)
logger.info("{} prepared statements discarded in the last minute because cache limit reached ({})",
count,
FBUtilities.prettyPrintMemory(MAX_CACHE_PREPARED_MEMORY));
}
ScheduledExecutors.scheduledTasks.scheduleAtFixedRate(() -> {
long count = lastMinuteEvictionsCount.getAndSet(0);
if (count > 0)
logger.warn("{} prepared statements discarded in the last minute because cache limit reached ({} MB)",
count,
DatabaseDescriptor.getPreparedStatementsCacheSizeMB());
count = thriftLastMinuteEvictionsCount.getAndSet(0);
if (count > 0)
logger.warn("{} prepared Thrift statements discarded in the last minute because cache limit reached ({} MB)",
count,
DatabaseDescriptor.getThriftPreparedStatementsCacheSizeMB());
}, 1, 1, TimeUnit.MINUTES);
logger.info("Initialized prepared statement caches with {} MB (native) and {} MB (Thrift)",
DatabaseDescriptor.getPreparedStatementsCacheSizeMB(),
DatabaseDescriptor.getThriftPreparedStatementsCacheSizeMB());
}
private static long capacityToBytes(long cacheSizeMB)
{
return cacheSizeMB * 1024 * 1024;
}
public static int preparedStatementsCount()
@ -428,18 +430,24 @@ public class QueryProcessor implements QueryHandler
// (if the keyspace is null, queryString has to have a fully-qualified keyspace so it's fine.
long statementSize = measure(prepared.statement);
// don't execute the statement if it's bigger than the allowed threshold
if (statementSize > MAX_CACHE_PREPARED_MEMORY)
throw new InvalidRequestException(String.format("Prepared statement of size %d bytes is larger than allowed maximum of %d bytes.",
statementSize,
MAX_CACHE_PREPARED_MEMORY));
if (forThrift)
{
if (statementSize > capacityToBytes(DatabaseDescriptor.getThriftPreparedStatementsCacheSizeMB()))
throw new InvalidRequestException(String.format("Prepared statement of size %d bytes is larger than allowed maximum of %d MB: %s...",
statementSize,
DatabaseDescriptor.getThriftPreparedStatementsCacheSizeMB(),
queryString.substring(0, 200)));
Integer statementId = computeThriftId(queryString, keyspace);
thriftPreparedStatements.put(statementId, prepared);
return ResultMessage.Prepared.forThrift(statementId, prepared.boundNames);
}
else
{
if (statementSize > capacityToBytes(DatabaseDescriptor.getPreparedStatementsCacheSizeMB()))
throw new InvalidRequestException(String.format("Prepared statement of size %d bytes is larger than allowed maximum of %d MB: %s...",
statementSize,
DatabaseDescriptor.getPreparedStatementsCacheSizeMB(),
queryString.substring(0, 200)));
MD5Digest statementId = computeId(queryString, keyspace);
preparedStatements.put(statementId, prepared);
return new ResultMessage.Prepared(statementId, prepared);