1818
1919import java .util .Arrays ;
2020import java .util .HashMap ;
21+ import java .util .List ;
2122import java .util .Map ;
2223import java .util .concurrent .ScheduledExecutorService ;
2324import java .util .concurrent .ScheduledFuture ;
5253
5354import static org .apache .cassandra .config .CassandraRelevantProperties .UCS_OVERRIDE_UCS_CONFIG_FOR_VECTOR_TABLES ;
5455import static org .apache .cassandra .SchemaLoader .standardCFMD ;
56+ import static org .assertj .core .api .Assertions .assertThatThrownBy ;
5557import static org .junit .Assert .assertArrayEquals ;
5658import static org .junit .Assert .assertEquals ;
5759import static org .junit .Assert .assertFalse ;
@@ -712,4 +714,207 @@ void testValidateCompactionStrategyOptions(boolean testLogType)
712714 Map <String , String > uncheckedOptions = CompactionStrategyOptions .validateOptions (options );
713715 assertNotNull (uncheckedOptions );
714716 }
715- }
717+
718+ @ Test
719+ public void testFactorizedShardGrowth ()
720+ {
721+ // Test factorization-based growth for num_shards=1000 (5^3 * 2^3)
722+ Map <String , String > options = new HashMap <>();
723+ options .put (Controller .NUM_SHARDS_OPTION , "1000" );
724+ options .put (Controller .MIN_SSTABLE_SIZE_OPTION , "1GiB" );
725+ mockFlushSize (100 );
726+ Controller controller = Controller .fromOptions (cfs , options );
727+
728+ // Verify shard count is set
729+ assertEquals (1.0 , controller .sstableGrowthModifier , 0.0 );
730+ assertTrue (controller .useFactorizationShardCountGrowth ());
731+
732+ // Test the smooth progression sequence: 1, 5, 25, 125, 250, 500, 1000
733+ assertEquals (1 , controller .getNumShards (0 )); // 0 GiB → 1 shard
734+ assertEquals (1 , controller .getNumShards (Math .scalb (1.5 , 30 ))); // 1.5 GiB → 1 shard
735+ assertEquals (5 , controller .getNumShards (Math .scalb (6.0 , 30 ))); // 6 GiB → 5 shards
736+ assertEquals (25 , controller .getNumShards (Math .scalb (30.0 , 30 ))); // 30 GiB → 25 shards
737+ assertEquals (125 , controller .getNumShards (Math .scalb (130.0 , 30 ))); // 130 GiB → 125 shards
738+ assertEquals (250 , controller .getNumShards (Math .scalb (300.0 , 30 ))); // 300 GiB → 250 shards
739+ assertEquals (500 , controller .getNumShards (Math .scalb (600.0 , 30 ))); // 600 GiB → 500 shards
740+ assertEquals (1000 , controller .getNumShards (Math .scalb (1000.0 , 30 ))); // 1000 GiB → 1000 shards
741+
742+ // Test boundary cases
743+ assertEquals (1 , controller .getNumShards (Math .scalb (4.9 , 30 ))); // Just below 5
744+ assertEquals (5 , controller .getNumShards (Math .scalb (5.0 , 30 ))); // Exactly 5
745+ assertEquals (5 , controller .getNumShards (Math .scalb (24.9 , 30 ))); // Just below 25
746+ assertEquals (25 , controller .getNumShards (Math .scalb (25.0 , 30 ))); // Exactly 25
747+
748+ // Test very large values
749+ assertEquals (1000 , controller .getNumShards (Double .POSITIVE_INFINITY ));
750+ }
751+
752+ @ Test
753+ public void testFactorizedShardGrowthPowerOfTwo ()
754+ {
755+ // Test with a power of 2 (should behave similarly to old logic)
756+ Map <String , String > options = new HashMap <>();
757+ options .put (Controller .NUM_SHARDS_OPTION , "1024" ); // 2^10
758+ options .put (Controller .MIN_SSTABLE_SIZE_OPTION , "1GiB" );
759+ mockFlushSize (100 );
760+ Controller controller = Controller .fromOptions (cfs , options );
761+
762+ // Should give smooth power-of-2 progression: 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024
763+ assertEquals (1 , controller .getNumShards (Math .scalb (1.5 , 30 )));
764+ assertEquals (2 , controller .getNumShards (Math .scalb (2.5 , 30 )));
765+ assertEquals (4 , controller .getNumShards (Math .scalb (5.0 , 30 )));
766+ assertEquals (8 , controller .getNumShards (Math .scalb (10.0 , 30 )));
767+ assertEquals (16 , controller .getNumShards (Math .scalb (20.0 , 30 )));
768+ assertEquals (32 , controller .getNumShards (Math .scalb (40.0 , 30 )));
769+ assertEquals (64 , controller .getNumShards (Math .scalb (80.0 , 30 )));
770+ assertEquals (128 , controller .getNumShards (Math .scalb (150.0 , 30 )));
771+ assertEquals (256 , controller .getNumShards (Math .scalb (300.0 , 30 )));
772+ assertEquals (512 , controller .getNumShards (Math .scalb (600.0 , 30 )));
773+ assertEquals (1024 , controller .getNumShards (Math .scalb (1024.0 , 30 )));
774+ }
775+
776+ @ Test
777+ public void testNoFactorizedShardGrowthWithPowerOfTwo ()
778+ {
779+ // Test no factorization-based growth for num_shards=128
780+ Map <String , String > options = new HashMap <>();
781+ options .put (Controller .NUM_SHARDS_OPTION , "128" );
782+ options .put (Controller .MIN_SSTABLE_SIZE_OPTION , "1GiB" );
783+ Controller controller = Controller .fromOptions (cfs , options );
784+
785+ assertFalse (controller .useFactorizationShardCountGrowth ());
786+ }
787+
788+ @ Test
789+ public void testFactorizedShardGrowthPrimeTarget ()
790+ {
791+ // Test with a prime number (should stay at 1 until reaching the target)
792+ Map <String , String > options = new HashMap <>();
793+ options .put (Controller .NUM_SHARDS_OPTION , "17" ); // Prime number
794+ options .put (Controller .MIN_SSTABLE_SIZE_OPTION , "1GiB" );
795+ mockFlushSize (100 );
796+ Controller controller = Controller .fromOptions (cfs , options );
797+
798+ // Since 17 is prime, sequence should be just: 1, 17
799+ assertEquals (1 , controller .getNumShards (Double .NaN ));
800+ assertEquals (1 , controller .getNumShards (Math .scalb (1.5 , 30 )));
801+ assertEquals (1 , controller .getNumShards (Math .scalb (5.0 , 30 )));
802+ assertEquals (1 , controller .getNumShards (Math .scalb (10.0 , 30 )));
803+ assertEquals (1 , controller .getNumShards (Math .scalb (16.9 , 30 )));
804+ assertEquals (17 , controller .getNumShards (Math .scalb (17.0 , 30 )));
805+ assertEquals (17 , controller .getNumShards (Math .scalb (100.0 , 30 )));
806+ }
807+
808+ @ Test
809+ public void testFactorizedShardSequence ()
810+ {
811+ // Test small numbers
812+ assertArrayEquals (new int []{ 1 }, Controller .factorizedSmoothShardSequence (1 ));
813+ assertArrayEquals (new int []{ 1 , 2 }, Controller .factorizedSmoothShardSequence (2 ));
814+ assertArrayEquals (new int []{ 1 , 3 }, Controller .factorizedSmoothShardSequence (3 ));
815+ assertArrayEquals (new int []{ 1 , 2 , 4 }, Controller .factorizedSmoothShardSequence (4 ));
816+
817+ // Test perfect squares
818+ assertArrayEquals (new int []{ 1 , 3 , 9 }, Controller .factorizedSmoothShardSequence (9 ));
819+ assertArrayEquals (new int []{ 1 , 2 , 4 , 8 , 16 }, Controller .factorizedSmoothShardSequence (16 ));
820+
821+ // Test primes
822+ assertArrayEquals (new int []{ 1 , 7 }, Controller .factorizedSmoothShardSequence (7 ));
823+ assertArrayEquals (new int []{ 1 , 11 }, Controller .factorizedSmoothShardSequence (11 ));
824+
825+ // Test composite numbers
826+ assertArrayEquals (new int []{ 1 , 3 , 6 , 12 }, Controller .factorizedSmoothShardSequence (12 ));
827+
828+ // Test 1000 = 5^3 * 2^3
829+ int [] expected1000 = new int []{ 1 , 5 , 25 , 125 , 250 , 500 , 1000 };
830+ assertArrayEquals (expected1000 , Controller .factorizedSmoothShardSequence (1000 ));
831+
832+ // Test 3200 = 5^2 * 2^7
833+ int [] expected3200 = new int []{ 1 , 5 , 25 , 50 , 100 , 200 , 400 , 800 , 1600 , 3200 };
834+ assertArrayEquals (expected3200 , Controller .factorizedSmoothShardSequence (3200 ));
835+
836+ // Test Max Int
837+ assertArrayEquals (new int []{ 1 , Integer .MAX_VALUE }, Controller .factorizedSmoothShardSequence (Integer .MAX_VALUE ));
838+ }
839+
840+ @ Test
841+ public void testFactorizedShardSequenceInputValidation ()
842+ {
843+ assertThatThrownBy (() -> Controller .factorizedSmoothShardSequence (0 )).hasMessageContaining ("must be positive" );
844+ assertThatThrownBy (() -> Controller .factorizedSmoothShardSequence (-5 )).hasMessageContaining ("must be positive" );
845+ }
846+
847+ @ Test
848+ public void testPrimeFactors ()
849+ {
850+ // Test small numbers
851+ assertEquals (Arrays .asList (2 ), Controller .primeFactors (2 ));
852+ assertEquals (Arrays .asList (3 ), Controller .primeFactors (3 ));
853+ assertEquals (Arrays .asList (2 , 2 ), Controller .primeFactors (4 ));
854+
855+ // Test larger numbers
856+ assertEquals (Arrays .asList (2 , 2 , 2 , 5 , 5 , 5 ), Controller .primeFactors (1000 ));
857+ assertEquals (Arrays .asList (2 , 2 , 2 , 2 , 2 , 2 , 2 , 5 , 5 ), Controller .primeFactors (3200 ));
858+
859+ // Test primes
860+ assertEquals (Arrays .asList (7 ), Controller .primeFactors (7 ));
861+ assertEquals (Arrays .asList (97 ), Controller .primeFactors (97 ));
862+ assertEquals (Arrays .asList (Integer .MAX_VALUE ), Controller .primeFactors (Integer .MAX_VALUE ));
863+ }
864+
865+ @ Test
866+ public void testPrimeFactorsInputValidation ()
867+ {
868+ assertThatThrownBy (() -> Controller .primeFactors (0 )).hasMessageContaining ("greater than 1" );
869+ assertThatThrownBy (() -> Controller .primeFactors (1 )).hasMessageContaining ("greater than 1" );
870+ assertThatThrownBy (() -> Controller .primeFactors (-10 )).hasMessageContaining ("greater than 1" );
871+ }
872+
873+ @ Test
874+ public void testGetLargestFactorizedShardCount ()
875+ {
876+ // Test with num_shards=1000 to get sequence: [1, 5, 25, 125, 250, 500, 1000]
877+ Map <String , String > options = new HashMap <>();
878+ options .put (Controller .NUM_SHARDS_OPTION , "1000" );
879+ options .put (Controller .MIN_SSTABLE_SIZE_OPTION , "1GiB" );
880+ mockFlushSize (100 );
881+ Controller controller = Controller .fromOptions (cfs , options );
882+
883+ // Test exact matches
884+ assertEquals (1 , controller .getLargestFactorizedShardCount (1.0 ));
885+ assertEquals (5 , controller .getLargestFactorizedShardCount (5.0 ));
886+ assertEquals (25 , controller .getLargestFactorizedShardCount (25.0 ));
887+ assertEquals (125 , controller .getLargestFactorizedShardCount (125.0 ));
888+ assertEquals (250 , controller .getLargestFactorizedShardCount (250.0 ));
889+ assertEquals (500 , controller .getLargestFactorizedShardCount (500.0 ));
890+ assertEquals (1000 , controller .getLargestFactorizedShardCount (1000.0 ));
891+
892+ // Test values between sequence elements (should return largest ≤ input)
893+ assertEquals (1 , controller .getLargestFactorizedShardCount (1.5 ));
894+ assertEquals (1 , controller .getLargestFactorizedShardCount (4.9 ));
895+ assertEquals (5 , controller .getLargestFactorizedShardCount (5.1 ));
896+ assertEquals (5 , controller .getLargestFactorizedShardCount (24.9 ));
897+ assertEquals (25 , controller .getLargestFactorizedShardCount (25.1 ));
898+ assertEquals (25 , controller .getLargestFactorizedShardCount (124.9 ));
899+ assertEquals (125 , controller .getLargestFactorizedShardCount (125.1 ));
900+ assertEquals (125 , controller .getLargestFactorizedShardCount (249.9 ));
901+ assertEquals (250 , controller .getLargestFactorizedShardCount (250.1 ));
902+ assertEquals (250 , controller .getLargestFactorizedShardCount (499.9 ));
903+ assertEquals (500 , controller .getLargestFactorizedShardCount (500.1 ));
904+ assertEquals (500 , controller .getLargestFactorizedShardCount (999.9 ));
905+
906+ // Test edge cases
907+ assertEquals (1 , controller .getLargestFactorizedShardCount (0.0 ));
908+ assertEquals (1 , controller .getLargestFactorizedShardCount (0.5 ));
909+ assertEquals (1000 , controller .getLargestFactorizedShardCount (1000.1 ));
910+ assertEquals (1000 , controller .getLargestFactorizedShardCount (2000.0 ));
911+ assertEquals (1000 , controller .getLargestFactorizedShardCount (Double .POSITIVE_INFINITY ));
912+
913+ // Test NaN
914+ assertEquals (1 , controller .getLargestFactorizedShardCount (Double .NaN ));
915+
916+ // Test negative values (should return first element)
917+ assertEquals (1 , controller .getLargestFactorizedShardCount (-1.0 ));
918+ assertEquals (1 , controller .getLargestFactorizedShardCount (-100.0 ));
919+ }
920+ }
0 commit comments