This is an automated email from the ASF dual-hosted git repository.

dcapwell pushed a commit to branch cassandra-4.0
in repository https://gitbox.apache.org/repos/asf/cassandra.git


The following commit(s) were added to refs/heads/cassandra-4.0 by this push:
     new 61014f2ae7 Leveled Compaction doesn't validate maxBytesForLevel when 
the table is altered/created
61014f2ae7 is described below

commit 61014f2ae7cd2c3126042275e627f6a90560b5ef
Author: Nikhil Kumawat <[email protected]>
AuthorDate: Wed Nov 5 15:23:42 2025 -0800

    Leveled Compaction doesn't validate maxBytesForLevel when the table is 
altered/created
    
    patch by Nikhil Kumawat, Nikhil; reviewed by David Capwell, guo Maxwell for 
CASSANDRA-20570
---
 CHANGES.txt                                        |  1 +
 .../db/compaction/LeveledCompactionStrategy.java   | 29 +++++++++++++++++++---
 .../compaction/LeveledCompactionStrategyTest.java  | 18 ++++++++++++++
 .../schema/CreateTableValidationTest.java          | 16 +++++++++++-
 4 files changed, 60 insertions(+), 4 deletions(-)

diff --git a/CHANGES.txt b/CHANGES.txt
index ddbee6860e..8364602a1a 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,5 @@
 4.0.20
+ * Leveled Compaction doesn't validate maxBytesForLevel when the table is 
altered/created (CASSANDRA-20570)
  * Updated dtest-api to 0.0.18 and removed JMX-related classes that now live 
in the dtest-api (CASSANDRA-20884)
 
 4.0.19
diff --git 
a/src/java/org/apache/cassandra/db/compaction/LeveledCompactionStrategy.java 
b/src/java/org/apache/cassandra/db/compaction/LeveledCompactionStrategy.java
index dd7c9dfcff..a39e14bf25 100644
--- a/src/java/org/apache/cassandra/db/compaction/LeveledCompactionStrategy.java
+++ b/src/java/org/apache/cassandra/db/compaction/LeveledCompactionStrategy.java
@@ -18,6 +18,7 @@
 package org.apache.cassandra.db.compaction;
 
 import java.util.*;
+import java.math.BigInteger;
 
 
 import com.google.common.annotations.VisibleForTesting;
@@ -43,6 +44,8 @@ import org.apache.cassandra.exceptions.ConfigurationException;
 import org.apache.cassandra.io.sstable.ISSTableScanner;
 import org.apache.cassandra.io.sstable.format.SSTableReader;
 
+import static 
org.apache.cassandra.db.compaction.LeveledGenerations.MAX_LEVEL_COUNT;
+
 public class LeveledCompactionStrategy extends AbstractCompactionStrategy
 {
     private static final Logger logger = 
LoggerFactory.getLogger(LeveledCompactionStrategy.class);
@@ -560,10 +563,14 @@ public class LeveledCompactionStrategy extends 
AbstractCompactionStrategy
     {
         Map<String, String> uncheckedOptions = 
AbstractCompactionStrategy.validateOptions(options);
 
+        int ssSize;
+        int fanoutSize;
+
+        // Validate the sstable_size option
         String size = options.containsKey(SSTABLE_SIZE_OPTION) ? 
options.get(SSTABLE_SIZE_OPTION) : "1";
         try
         {
-            int ssSize = Integer.parseInt(size);
+            ssSize = Integer.parseInt(size);
             if (ssSize < 1)
             {
                 throw new ConfigurationException(String.format("%s must be 
larger than 0, but was %s", SSTABLE_SIZE_OPTION, ssSize));
@@ -580,7 +587,7 @@ public class LeveledCompactionStrategy extends 
AbstractCompactionStrategy
         String levelFanoutSize = options.containsKey(LEVEL_FANOUT_SIZE_OPTION) 
? options.get(LEVEL_FANOUT_SIZE_OPTION) : 
String.valueOf(DEFAULT_LEVEL_FANOUT_SIZE);
         try
         {
-            int fanoutSize = Integer.parseInt(levelFanoutSize);
+            fanoutSize = Integer.parseInt(levelFanoutSize);
             if (fanoutSize < 1)
             {
                 throw new ConfigurationException(String.format("%s must be 
larger than 0, but was %s", LEVEL_FANOUT_SIZE_OPTION, fanoutSize));
@@ -588,7 +595,23 @@ public class LeveledCompactionStrategy extends 
AbstractCompactionStrategy
         }
         catch (NumberFormatException ex)
         {
-            throw new ConfigurationException(String.format("%s is not a 
parsable int (base10) for %s", size, LEVEL_FANOUT_SIZE_OPTION), ex);
+            throw new ConfigurationException(String.format("%s is not a 
parsable int (base10) for %s", levelFanoutSize, LEVEL_FANOUT_SIZE_OPTION), ex);
+        }
+
+        // Validate max Bytes for a level
+        try
+        {
+            long maxSSTableSizeInBytes = Math.multiplyExact(ssSize, 1024L * 
1024L); // Convert MB to Bytes
+            BigInteger fanoutPower = 
BigInteger.valueOf(fanoutSize).pow(MAX_LEVEL_COUNT - 1);
+            BigInteger maxBytes = 
fanoutPower.multiply(BigInteger.valueOf(maxSSTableSizeInBytes));
+            BigInteger longMaxValue = BigInteger.valueOf(Long.MAX_VALUE);
+            if (maxBytes.compareTo(longMaxValue) > 0)
+                throw new ConfigurationException(String.format("At most %s 
bytes may be in a compaction level; " +
+                        "your maxSSTableSize must be absurdly high to compute 
%s", Long.MAX_VALUE, maxBytes));
+        }
+        catch (ArithmeticException ex)
+        {
+            throw new 
ConfigurationException(String.format("sstable_size_in_mb=%d is too large; 
resulting bytes exceed Long.MAX_VALUE (%d)", ssSize, Long.MAX_VALUE), ex);
         }
 
         uncheckedOptions.remove(LEVEL_FANOUT_SIZE_OPTION);
diff --git 
a/test/unit/org/apache/cassandra/db/compaction/LeveledCompactionStrategyTest.java
 
b/test/unit/org/apache/cassandra/db/compaction/LeveledCompactionStrategyTest.java
index 88ddb0b0c7..f741e1e997 100644
--- 
a/test/unit/org/apache/cassandra/db/compaction/LeveledCompactionStrategyTest.java
+++ 
b/test/unit/org/apache/cassandra/db/compaction/LeveledCompactionStrategyTest.java
@@ -906,6 +906,24 @@ public class LeveledCompactionStrategyTest
         }
     }
 
+    @Test()
+    public void testInvalidFanoutAndSSTableSize()
+    {
+        try
+        {
+            Map<String, String> options = new HashMap<>();
+            options.put("class", "LeveledCompactionStrategy");
+            options.put("fanout_size", "90");
+            options.put("sstable_size_in_mb", "1089");
+            LeveledCompactionStrategy.validateOptions(options);
+            Assert.fail("fanout_sizeed and sstable_size_in_mb are invalid, but 
did not throw ConfigurationException");
+        }
+        catch (ConfigurationException e)
+        {
+            assertTrue(e.getMessage().contains("your maxSSTableSize must be 
absurdly high to compute"));
+        }
+    }
+
     @Test
     public void testReduceScopeL0()
     {
diff --git 
a/test/unit/org/apache/cassandra/schema/CreateTableValidationTest.java 
b/test/unit/org/apache/cassandra/schema/CreateTableValidationTest.java
index b76177cebf..ce6ccb8a6a 100644
--- a/test/unit/org/apache/cassandra/schema/CreateTableValidationTest.java
+++ b/test/unit/org/apache/cassandra/schema/CreateTableValidationTest.java
@@ -28,6 +28,7 @@ import org.apache.cassandra.cql3.CQLTester;
 import org.apache.cassandra.cql3.QueryOptions;
 import org.apache.cassandra.exceptions.ConfigurationException;
 import org.apache.cassandra.exceptions.InvalidRequestException;
+import org.apache.cassandra.exceptions.RequestValidationException;
 import org.apache.cassandra.transport.Message;
 import org.apache.cassandra.transport.ProtocolVersion;
 import org.apache.cassandra.transport.SimpleClient;
@@ -202,9 +203,22 @@ public class CreateTableValidationTest extends CQLTester
                              String.format("CREATE TABLE %s.\"    \" (key int 
PRIMARY KEY, val int)", KEYSPACE));
     }
 
-    private void expectedFailure(String statement, String errorMsg)
+    @Test
+    public void testInvalidCompactionOptions()
+    {
+        expectedFailure(ConfigurationException.class, "CREATE TABLE %s (k int 
PRIMARY KEY, v int) WITH compaction = {'class': 'LeveledCompactionStrategy', 
'fanout_size': '90', 'sstable_size_in_mb': '1089'}",
+                        "your maxSSTableSize must be absurdly high to 
compute");
+    }
+
+    private void expectedFailure(final Class<? extends 
RequestValidationException> exceptionType, String statement, String errorMsg)
     {
 
+        assertThatExceptionOfType(exceptionType)
+        .isThrownBy(() -> createTableMayThrow(statement)) 
.withMessageContaining(errorMsg);
+    }
+
+    private void expectedFailure(String statement, String errorMsg)
+    {
         assertThatExceptionOfType(InvalidRequestException.class)
         .isThrownBy(() -> createTableMayThrow(statement)) 
.withMessageContaining(errorMsg);
     }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to