diff --git a/src/java/org/apache/cassandra/cql3/QueryHandler.java b/src/java/org/apache/cassandra/cql3/QueryHandler.java index 8be7184d44..e0480a6e4b 100644 --- a/src/java/org/apache/cassandra/cql3/QueryHandler.java +++ b/src/java/org/apache/cassandra/cql3/QueryHandler.java @@ -58,6 +58,9 @@ public interface QueryHandler public static class Prepared { + // CASSANDRA-19986 Precomputed size might be available + public volatile int pstmntSize = -1; + public final CQLStatement statement; public final MD5Digest resultMetadataId; diff --git a/src/java/org/apache/cassandra/cql3/QueryProcessor.java b/src/java/org/apache/cassandra/cql3/QueryProcessor.java index 66ce2ef5a6..910f8c79f8 100644 --- a/src/java/org/apache/cassandra/cql3/QueryProcessor.java +++ b/src/java/org/apache/cassandra/cql3/QueryProcessor.java @@ -93,7 +93,7 @@ public class QueryProcessor implements QueryHandler preparedStatements = Caffeine.newBuilder() .executor(MoreExecutors.directExecutor()) .maximumWeight(capacityToBytes(DatabaseDescriptor.getPreparedStatementsCacheSizeMB())) - .weigher(QueryProcessor::measure) + .weigher(QueryProcessor::getSizeOfPreparedStatementForCache) .removalListener((key, prepared, cause) -> { MD5Digest md5Digest = (MD5Digest) key; if (cause.wasEvicted()) @@ -411,10 +411,14 @@ public class QueryProcessor implements QueryHandler CQLStatement statement = raw.prepare(clientState); statement.validate(clientState); + Prepared res; if (isInternal) - return new Prepared(statement, "", fullyQualified, keyspace); + res = new Prepared(statement, "", fullyQualified, keyspace); else - return new Prepared(statement, query, fullyQualified, keyspace); + res = new Prepared(statement, query, fullyQualified, keyspace); + res.pstmntSize = measurePstmnt(res); + + return res; } public static UntypedResultSet executeInternal(String query, Object... values) @@ -674,14 +678,14 @@ public class QueryProcessor implements QueryHandler { // Concatenate the current keyspace so we don't mix prepared statements between keyspace (#5352). // (if the keyspace is null, queryString has to have a fully-qualified keyspace so it's fine. - long statementSize = ObjectSizes.measureDeep(prepared.statement); + MD5Digest statementId = computeId(queryString, keyspace); // don't execute the statement if it's bigger than the allowed threshold - if (statementSize > capacityToBytes(DatabaseDescriptor.getPreparedStatementsCacheSizeMB())) + if (getSizeOfPreparedStatementForCache(statementId, prepared) > capacityToBytes(DatabaseDescriptor.getPreparedStatementsCacheSizeMB())) throw new InvalidRequestException(String.format("Prepared statement of size %d bytes is larger than allowed maximum of %d MB: %s...", - statementSize, + prepared.pstmntSize, DatabaseDescriptor.getPreparedStatementsCacheSizeMB(), queryString.substring(0, 200))); - MD5Digest statementId = computeId(queryString, keyspace); + Prepared previous = preparedStatements.get(statementId, (ignored_) -> prepared); if (previous == prepared) SystemKeyspace.writePreparedStatement(keyspace, statementId, queryString); @@ -797,9 +801,17 @@ public class QueryProcessor implements QueryHandler } } - private static int measure(Object key, Prepared value) + private static int measurePstmnt(Prepared value) { - return Ints.checkedCast(ObjectSizes.measureDeep(key) + ObjectSizes.measureDeep(value)); + return Ints.checkedCast(ObjectSizes.measureDeep(value)); + } + + private static int getSizeOfPreparedStatementForCache(MD5Digest key, Prepared value) + { + if (value.pstmntSize < 0) + throw new IllegalStateException("Precomputed prepared statement size not available"); + + return Ints.checkedCast(key.size() + value.pstmntSize); } /** diff --git a/src/java/org/apache/cassandra/utils/MD5Digest.java b/src/java/org/apache/cassandra/utils/MD5Digest.java index d5429918d5..1f3d0a52d8 100644 --- a/src/java/org/apache/cassandra/utils/MD5Digest.java +++ b/src/java/org/apache/cassandra/utils/MD5Digest.java @@ -113,4 +113,10 @@ public class MD5Digest { return localMD5Digest.get(); } + + public int size() + { + return bytes.length + //bytes + 4; // int hashCode + } } diff --git a/test/unit/org/apache/cassandra/cql3/PstmtPersistenceTest.java b/test/unit/org/apache/cassandra/cql3/PstmtPersistenceTest.java index 8e3f7f4c3e..c0d4994262 100644 --- a/test/unit/org/apache/cassandra/cql3/PstmtPersistenceTest.java +++ b/test/unit/org/apache/cassandra/cql3/PstmtPersistenceTest.java @@ -21,7 +21,9 @@ import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.concurrent.TimeUnit; +import org.apache.cassandra.Util; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -153,7 +155,7 @@ public class PstmtPersistenceTest extends CQLTester prepareStatement("INSERT INTO %s (key, val) VALUES (?, ?) USING TIMESTAMP " + cnt2, clientState); // each new prepared statement should have caused an eviction - assertEquals("eviction count didn't increase by the expected number", numberOfEvictedStatements(), 10); + Util.spinAssertEquals("eviction count didn't increase by the expected number", 10L, this::numberOfEvictedStatements, 5, TimeUnit.SECONDS); assertEquals("Number of statements in table and in cache don't match", numberOfStatementsInMemory(), numberOfStatementsOnDisk()); return;