From 3027ec2fac00ba5c6780f1d7aed4cb9b24778a15 Mon Sep 17 00:00:00 2001 From: Alan Wang Date: Fri, 14 Nov 2025 11:02:53 -0800 Subject: [PATCH 01/11] additional info level info for tablestats for UCS --- .../cassandra/db/ColumnFamilyStore.java | 36 ++++ .../cassandra/db/ColumnFamilyStoreMBean.java | 42 +++++ .../compaction/CompactionStrategyManager.java | 170 ++++++++++++++++++ .../compaction/UnifiedCompactionStrategy.java | 26 ++- .../tools/nodetool/stats/StatsTable.java | 7 + .../nodetool/stats/TableStatsHolder.java | 69 +++++++ .../nodetool/stats/TableStatsPrinter.java | 16 ++ .../test/ColumnFamilyStoreMBeansTest.java | 126 +++++++++++++ 8 files changed, 491 insertions(+), 1 deletion(-) create mode 100644 test/distributed/org/apache/cassandra/distributed/test/ColumnFamilyStoreMBeansTest.java diff --git a/src/java/org/apache/cassandra/db/ColumnFamilyStore.java b/src/java/org/apache/cassandra/db/ColumnFamilyStore.java index 839a21d55d67..8bec2b9225a3 100644 --- a/src/java/org/apache/cassandra/db/ColumnFamilyStore.java +++ b/src/java/org/apache/cassandra/db/ColumnFamilyStore.java @@ -2994,6 +2994,42 @@ public long[] getPerLevelSizeBytes() return compactionStrategyManager.getPerLevelSizeBytes(); } + @Override + public double[] getPerLevelAvgTokenSpace() + { + return compactionStrategyManager.getPerLevelAvgTokenSpace(); + } + + @Override + public double[] getPerLevelMaxDensityThreshold() + { + return compactionStrategyManager.getPerLevelMaxDensityThreshold(); + } + + @Override + public double[] getPerLevelAvgSize() + { + return compactionStrategyManager.getPerLevelAvgSize(); + } + + @Override + public double[] getPerLevelAvgDensity() + { + return compactionStrategyManager.getPerLevelAvgDensity(); + } + + @Override + public double[] getPerLevelAvgDensityMaxDensityThresholdRatio() + { + return compactionStrategyManager.getPerLevelAvgDensityMaxDensityThresholdRatio(); + } + + @Override + public double[] getPerLevelMaxDensityMaxDensityThresholdRatio() + { + return compactionStrategyManager.getPerLevelMaxDensityMaxDensityThresholdRatio(); + } + @Override public boolean isLeveledCompaction() { diff --git a/src/java/org/apache/cassandra/db/ColumnFamilyStoreMBean.java b/src/java/org/apache/cassandra/db/ColumnFamilyStoreMBean.java index 33cd6ebbc25b..91bd7f07e14a 100644 --- a/src/java/org/apache/cassandra/db/ColumnFamilyStoreMBean.java +++ b/src/java/org/apache/cassandra/db/ColumnFamilyStoreMBean.java @@ -277,6 +277,48 @@ public List importNewSSTables(Set srcPaths, */ public long[] getPerLevelSizeBytes(); + /** + * @return average of sstable covered token spaces in each level. + * null unless unified compaction strategy is used. + * array index corresponds to level(int[0] is for level 0, ...). + */ + public double[] getPerLevelAvgTokenSpace(); + + /** + * @return the maximum density each level is allowed to hold. + * null unless unified compaction strategy is used. + * array index corresponds to level(int[0] is for level 0, ...). + */ + public double[] getPerLevelMaxDensityThreshold(); + + /** + * @return the average size of sstables in each level. + * null unless unified compaction strategy is used. + * array index corresponds to level(int[0] is for level 0, ...). + */ + public double[] getPerLevelAvgSize(); + + /** + * @return the average density of sstables in each level. + * null unless unified compaction strategy is used. + * array index corresponds to level(int[0] is for level 0, ...). + */ + public double[] getPerLevelAvgDensity(); + + /** + * @return the ratio of avg density to the maximum density threshold of that level + * in each level. null unless unified compaction strategy is used. + * array index corresponds to level(int[0] is for level 0, ...). + */ + public double[] getPerLevelAvgDensityMaxDensityThresholdRatio(); + + /** + * @return the ratio of maximum density to the maximum density threshold of that level + * in each level. null unless unified compaction strategy is used. + * array index corresponds to level(int[0] is for level 0, ...). + */ + public double[] getPerLevelMaxDensityMaxDensityThresholdRatio(); + /** * @return true if the table is using LeveledCompactionStrategy. false otherwise. */ diff --git a/src/java/org/apache/cassandra/db/compaction/CompactionStrategyManager.java b/src/java/org/apache/cassandra/db/compaction/CompactionStrategyManager.java index b8eaa5bd812c..4c7277e866f6 100644 --- a/src/java/org/apache/cassandra/db/compaction/CompactionStrategyManager.java +++ b/src/java/org/apache/cassandra/db/compaction/CompactionStrategyManager.java @@ -32,6 +32,7 @@ import java.util.Set; import java.util.TreeMap; import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.function.BiFunction; import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -80,6 +81,8 @@ import org.apache.cassandra.schema.CompactionParams; import org.apache.cassandra.service.ActiveRepairService; import org.apache.cassandra.utils.TimeUUID; +import org.apache.cassandra.utils.TriFunction; +import org.apache.cassandra.db.compaction.UnifiedCompactionStrategy.Level; import static org.apache.cassandra.db.compaction.AbstractStrategyHolder.GroupedSSTableContainer; @@ -690,6 +693,94 @@ public long[] getPerLevelSizeBytes() } } + public double[] getPerLevelAvgTokenSpace() + { + return avgUCSHelper((ucs, sstable) -> + sstable.tokenSpaceCoverage()); + } + + public double[] getPerLevelMaxDensityThreshold() + { + return perLevelUCSHelper((level, sstable, acc) -> Math.max(acc, level.max)); + } + + public double[] getPerLevelAvgSize() + { + return avgUCSHelper((ucs, sstable) -> (double) sstable.onDiskLength()); + } + + public double[] getPerLevelAvgDensity() + { + return avgUCSHelper(UnifiedCompactionStrategy::getDensity); + } + + public double[] getPerLevelAvgDensityMaxDensityThresholdRatio() + { + readLock.lock(); + try + { + if (repaired.first() instanceof UnifiedCompactionStrategy) + { + double[] avgDensityPerLevel = getPerLevelAvgDensity(); + double[] maxDensityThresholdPerLevel = getPerLevelMaxDensityThreshold(); + + double[] res = new double[avgDensityPerLevel.length]; + + for (int i = 0; i < avgDensityPerLevel.length; i++) + { + res[i] = avgDensityPerLevel[i] / maxDensityThresholdPerLevel[i]; + } + + return res; + } + return null; + } + finally + { + readLock.unlock(); + } + } + + public double[] getPerLevelMaxDensityMaxDensityThresholdRatio() + { + readLock.lock(); + try + { + if (repaired.first() instanceof UnifiedCompactionStrategy) + { + + double[] maxDensityThresholdPerLevel = getPerLevelMaxDensityThreshold(); + double[] maxDensityPerLevel = new double[maxDensityThresholdPerLevel.length]; + double[] res = new double[maxDensityThresholdPerLevel.length]; + + for (AbstractCompactionStrategy strategy : getAllStrategies()) + { + UnifiedCompactionStrategy ucsStrategy = (UnifiedCompactionStrategy) strategy; + List levels = ucsStrategy.getLevelsSnapshot(); + for (int i = 0; i < levels.size(); i++) + { + for (SSTableReader sstable : levels.get(i).getSSTables()) + { + maxDensityPerLevel[i] = Math.max(maxDensityPerLevel[i], ucsStrategy.getDensity(sstable)); + } + } + } + + for (int i = 0; i < maxDensityThresholdPerLevel.length; i++) + { + res[i] = maxDensityPerLevel[i] / maxDensityThresholdPerLevel[i]; + } + + return res; + } + return null; + } + finally + { + readLock.unlock(); + } + } + public boolean isLeveledCompaction() { readLock.lock(); @@ -702,6 +793,85 @@ public boolean isLeveledCompaction() } } + double[] avgUCSHelper(BiFunction fn) + { + readLock.lock(); + try + { + if (repaired.first() instanceof UnifiedCompactionStrategy) + { + int numberOfLevels = 0; + + double[] sum = new double[UnifiedCompactionStrategy.MAX_LEVELS]; + int[] count = new int[UnifiedCompactionStrategy.MAX_LEVELS]; + + for (AbstractCompactionStrategy strategy : getAllStrategies()) + { + UnifiedCompactionStrategy ucsStrategy = (UnifiedCompactionStrategy) strategy; + List levels = ucsStrategy.getLevelsSnapshot(); + numberOfLevels = Math.max(numberOfLevels, levels.size()); + for (int i = 0; i < levels.size(); i++) + { + for (SSTableReader sstable : levels.get(i).getSSTables()) + { + sum[i] += fn.apply(ucsStrategy, sstable); + count[i] += 1; + } + } + } + + double[] res = new double[numberOfLevels]; + for (int i = 0; i < numberOfLevels; i++) + res[i] = count[i] == 0 ? 0 : sum[i] / count[i]; + + return res; + } + return null; + } + finally + { + readLock.unlock(); + } + } + + double[] perLevelUCSHelper(TriFunction fn) + { + readLock.lock(); + try + { + if (repaired.first() instanceof UnifiedCompactionStrategy) + { + int numberOfLevels = 0; + + double[] tmp = new double[UnifiedCompactionStrategy.MAX_LEVELS]; + + for (AbstractCompactionStrategy strategy : getAllStrategies()) + { + UnifiedCompactionStrategy ucsStrategy = (UnifiedCompactionStrategy) strategy; + List levels = ucsStrategy.getLevelsSnapshot(); + numberOfLevels = Math.max(numberOfLevels, levels.size()); + for (int i = 0; i < levels.size(); i++) + { + for (SSTableReader sstable : levels.get(i).getSSTables()) + { + tmp[i] = fn.apply(levels.get(i), sstable, tmp[i]); + } + } + } + + double[] res = new double[numberOfLevels]; + System.arraycopy(tmp, 0, res, 0, numberOfLevels); + + return res; + } + return null; + } + finally + { + readLock.unlock(); + } + } + public int[] getSSTableCountPerTWCSBucket() { readLock.lock(); diff --git a/src/java/org/apache/cassandra/db/compaction/UnifiedCompactionStrategy.java b/src/java/org/apache/cassandra/db/compaction/UnifiedCompactionStrategy.java index d44a9f3288c2..6f9332f52f25 100644 --- a/src/java/org/apache/cassandra/db/compaction/UnifiedCompactionStrategy.java +++ b/src/java/org/apache/cassandra/db/compaction/UnifiedCompactionStrategy.java @@ -529,6 +529,25 @@ List getLevels() return getLevels(getSSTables(), UnifiedCompactionStrategy::isSuitableForCompaction); } + /** + * @return a list of the levels in the compaction hierarchy, that also includes SSTables that + * are currently undergoing compaction. This is used only for table stats so we can have a consistent + * snapshot of the levels. + */ + @VisibleForTesting + List getLevelsSnapshot() + { + Set sstables = getSSTables(); + List suitable = new ArrayList<>(sstables.size()); + for (SSTableReader rdr : sstables) + { + if (isSuitableForCompaction(rdr)) + suitable.add(rdr); + } + + return formLevels(suitable); + } + /** * Groups the sstables passed in into levels. This is used by the strategy to determine * new compactions, and by external tools to analyze the strategy decisions. @@ -546,6 +565,11 @@ public List getLevels(Collection sstables, return formLevels(suitable); } + public double getDensity(SSTableReader sstable) + { + return shardManager.density(sstable); + } + private List formLevels(List suitable) { maybeUpdateShardManager(); @@ -557,7 +581,7 @@ private List formLevels(List suitable) Level level = new Level(controller, index, 0, maxDensity); for (SSTableReader candidate : suitable) { - final double density = shardManager.density(candidate); + final double density = getDensity(candidate); if (density < level.max) { level.add(candidate); diff --git a/src/java/org/apache/cassandra/tools/nodetool/stats/StatsTable.java b/src/java/org/apache/cassandra/tools/nodetool/stats/StatsTable.java index ec10f18be77d..d8b18c5bee37 100644 --- a/src/java/org/apache/cassandra/tools/nodetool/stats/StatsTable.java +++ b/src/java/org/apache/cassandra/tools/nodetool/stats/StatsTable.java @@ -29,6 +29,7 @@ public class StatsTable public String tableName; public boolean isIndex; public boolean isLeveledSstable = false; + public boolean isUCSSstable = false; public Object sstableCount; public Object oldSSTableCount; public Long maxSSTableSize; @@ -72,6 +73,12 @@ public class StatsTable public long maximumTombstonesPerSliceLastFiveMinutes; public List sstablesInEachLevel = new ArrayList<>(); public List sstableBytesInEachLevel = new ArrayList<>(); + public List sstableAvgTokenSpaceInEachLevel = new ArrayList<>(); + public List sstableMaxDensityThresholdInEachLevel = new ArrayList<>(); + public List sstableAvgSizeInEachLevel = new ArrayList<>(); + public List sstableAvgDensityInEachLevel = new ArrayList<>(); + public List sstableAvgDensityMaxDensityThresholdRatioInEachLevel = new ArrayList<>(); + public List sstableMaxDensityMaxDensityThresholdRatioInEachLevel = new ArrayList<>(); public int[] sstableCountPerTWCSBucket = null; public Boolean isInCorrectLocation = null; // null: option not active public double droppableTombstoneRatio; diff --git a/src/java/org/apache/cassandra/tools/nodetool/stats/TableStatsHolder.java b/src/java/org/apache/cassandra/tools/nodetool/stats/TableStatsHolder.java index a2005f4b3236..580ac1ae5834 100644 --- a/src/java/org/apache/cassandra/tools/nodetool/stats/TableStatsHolder.java +++ b/src/java/org/apache/cassandra/tools/nodetool/stats/TableStatsHolder.java @@ -124,6 +124,15 @@ private Map convertStatsTableToMap(StatsTable table) mpTable.put("old_sstable_count", table.oldSSTableCount); mpTable.put("sstables_in_each_level", table.sstablesInEachLevel); mpTable.put("sstable_bytes_in_each_level", table.sstableBytesInEachLevel); + if (table.isUCSSstable) + { + mpTable.put("sstable_avg_token_space_in_each_level", table.sstableAvgTokenSpaceInEachLevel); + mpTable.put("sstable_max_density_threshold_in_each_level", table.sstableMaxDensityThresholdInEachLevel); + mpTable.put("sstable_avg_size_in_each_level", table.sstableAvgSizeInEachLevel); + mpTable.put("sstable_avg_density_in_each_level", table.sstableAvgDensityInEachLevel); + mpTable.put("sstable_avg_density_max_density_threshold_ratio_in_each_level", table.sstableAvgDensityMaxDensityThresholdRatioInEachLevel); + mpTable.put("sstable_max_density_max_density_threshold_ratio_in_each_level", table.sstableMaxDensityMaxDensityThresholdRatioInEachLevel); + } mpTable.put("max_sstable_size", table.maxSSTableSize); mpTable.put("twcs", table.twcs); mpTable.put("space_used_live", table.spaceUsedLive); @@ -275,6 +284,66 @@ private void initializeKeyspaces(NodeProbe probe, boolean ignore, List t } } + double[] ucsSSTableTokenSpace = table.getPerLevelAvgTokenSpace(); + if (ucsSSTableTokenSpace != null) + { + statsTable.isUCSSstable = true; + for (int level = 0; level < ucsSSTableTokenSpace.length; level++) + { + statsTable.sstableAvgTokenSpaceInEachLevel.add(String.format("%.03f", ucsSSTableTokenSpace[level])); + } + } + + double[] ucsMaxDensityThreshold = table.getPerLevelMaxDensityThreshold(); + if (ucsMaxDensityThreshold != null) + { + statsTable.isUCSSstable = true; + for (int level = 0; level < ucsMaxDensityThreshold.length; level++) + { + statsTable.sstableMaxDensityThresholdInEachLevel.add(String.format("%.03f", ucsMaxDensityThreshold[level])); + } + } + + double[] ucsSsTableAvgSize = table.getPerLevelAvgSize(); + if (ucsSsTableAvgSize != null) + { + statsTable.isUCSSstable = true; + for (int level = 0; level < ucsSsTableAvgSize.length; level++) + { + statsTable.sstableAvgSizeInEachLevel.add(String.format("%.03f", ucsSsTableAvgSize[level])); + } + } + + double[] ucsSStableAvgDensity = table.getPerLevelAvgDensity(); + if (ucsSStableAvgDensity != null) + { + statsTable.isUCSSstable = true; + for (int level = 0; level < ucsSStableAvgDensity.length; level++) + { + statsTable.sstableAvgDensityInEachLevel.add(String.format("%.03f", ucsSStableAvgDensity[level])); + } + } + + double[] ucsSStableAvgDensityMaxDensityThresholdRatio = table.getPerLevelAvgDensityMaxDensityThresholdRatio(); + if (ucsSStableAvgDensityMaxDensityThresholdRatio != null) + { + statsTable.isUCSSstable = true; + for (int level = 0; level < ucsSStableAvgDensityMaxDensityThresholdRatio.length; level++) + { + statsTable.sstableAvgDensityMaxDensityThresholdRatioInEachLevel.add(String.format("%.03f", ucsSStableAvgDensityMaxDensityThresholdRatio[level])); + } + } + + double[] ucsSStableMaxDensityMaxDensityThresholdRatioInEachLevel = table.getPerLevelMaxDensityMaxDensityThresholdRatio(); + if (ucsSStableMaxDensityMaxDensityThresholdRatioInEachLevel != null) + { + statsTable.isUCSSstable = true; + for (int level = 0; level < ucsSStableMaxDensityMaxDensityThresholdRatioInEachLevel.length; level++) + { + statsTable.sstableMaxDensityMaxDensityThresholdRatioInEachLevel.add(String.format("%.03f", ucsSStableMaxDensityMaxDensityThresholdRatioInEachLevel[level])); + } + } + if (locationCheck) statsTable.isInCorrectLocation = !table.hasMisplacedSSTables(); diff --git a/src/java/org/apache/cassandra/tools/nodetool/stats/TableStatsPrinter.java b/src/java/org/apache/cassandra/tools/nodetool/stats/TableStatsPrinter.java index 187ca05b8edb..24ccd94bf6cd 100644 --- a/src/java/org/apache/cassandra/tools/nodetool/stats/TableStatsPrinter.java +++ b/src/java/org/apache/cassandra/tools/nodetool/stats/TableStatsPrinter.java @@ -100,6 +100,22 @@ protected void printStatsTable(StatsTable table, String tableDisplayName, String table.sstableBytesInEachLevel) + "]"); } + if (table.isUCSSstable) + { + out.println(indent + "Average token space for SSTables in each level: [" + String.join(", ", + table.sstableAvgTokenSpaceInEachLevel) + "]"); + out.println(indent + "Maximum density for SSTables in each level: [" + String.join(", ", + table.sstableMaxDensityThresholdInEachLevel) + "]"); + out.println(indent + "Average SSTable size in each level: [" + String.join(", ", + table.sstableAvgSizeInEachLevel) + "]"); + out.println(indent + "Average SSTable density in each level: [" + String.join(", ", + table.sstableAvgDensityInEachLevel) + "]"); + out.println(indent + "Ratio of average SSTable density and maximum density threshold in each level: [" + String.join(", ", + table.sstableAvgDensityMaxDensityThresholdRatioInEachLevel) + "]"); + out.println(indent + "Ratio of maximum SSTable density and maximum density threshold in each level: [" + String.join(", ", + table.sstableMaxDensityMaxDensityThresholdRatioInEachLevel) + "]"); + } + out.println(indent + "Space used (live): " + table.spaceUsedLive); out.println(indent + "Space used (total): " + table.spaceUsedTotal); out.println(indent + "Space used by snapshots (total): " + table.spaceUsedBySnapshotsTotal); diff --git a/test/distributed/org/apache/cassandra/distributed/test/ColumnFamilyStoreMBeansTest.java b/test/distributed/org/apache/cassandra/distributed/test/ColumnFamilyStoreMBeansTest.java new file mode 100644 index 000000000000..59e79ca33d14 --- /dev/null +++ b/test/distributed/org/apache/cassandra/distributed/test/ColumnFamilyStoreMBeansTest.java @@ -0,0 +1,126 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.cassandra.distributed.test; + +import org.junit.*; + +import org.apache.cassandra.db.ColumnFamilyStore; +import org.apache.cassandra.db.Keyspace; +import org.apache.cassandra.distributed.Cluster; + +import java.io.IOException; + +import static org.apache.cassandra.distributed.api.Feature.GOSSIP; +import static org.apache.cassandra.distributed.api.Feature.NETWORK; + +public class ColumnFamilyStoreMBeansTest extends TestBaseImpl +{ + private static Cluster CLUSTER; + + @BeforeClass + public static void setup() throws IOException + { + CLUSTER = init(Cluster.build(1).withConfig(c -> + c.with(GOSSIP, NETWORK)) + .start()); + + CLUSTER.schemaChange(withKeyspace("DROP KEYSPACE %s")); + CLUSTER.schemaChange(withKeyspace("CREATE KEYSPACE %s WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}")); + CLUSTER.schemaChange(withKeyspace("CREATE TABLE %s.cf (k text, c1 text, c2 text, PRIMARY KEY (k)) WITH compaction = {'class': 'UnifiedCompactionStrategy', 'scaling_parameters': 'L10'}")); + + for (int i = 0; i < 10000; i++) + CLUSTER.get(1).executeInternal(withKeyspace("INSERT INTO %s.cf (k, c1, c2) VALUES (?, 'value1', 'value2');"), Integer.toString(i)); + + CLUSTER.get(1).nodetool("flush"); + } + + @Test + public void testPerLevelAverageTokenSpace() throws Throwable + { + CLUSTER.get(1).runOnInstance(() -> { + ColumnFamilyStore cfs = Keyspace.open(KEYSPACE).getColumnFamilyStore("cf"); + double[] perLevelAvgTokenSpace = cfs.getPerLevelAvgTokenSpace(); + assert(perLevelAvgTokenSpace.length > 0); + for (int i = 0; i < perLevelAvgTokenSpace.length; i++) + Assert.assertTrue(perLevelAvgTokenSpace[i] > 0); + }); + } + + @Test + public void testGetPerLevelMaxDensityThreshold() throws Throwable + { + CLUSTER.get(1).runOnInstance(() -> { + ColumnFamilyStore cfs = Keyspace.open(KEYSPACE).getColumnFamilyStore("cf"); + Assert.assertTrue(cfs.getPerLevelMaxDensityThreshold().length > 0); + }); + } + + @Test + public void testGetPerLevelAvgSize() throws Throwable + { + CLUSTER.get(1).runOnInstance(() -> { + ColumnFamilyStore cfs = Keyspace.open(KEYSPACE).getColumnFamilyStore("cf"); + double[] perLevelAvgSize = cfs.getPerLevelAvgSize(); + assert(perLevelAvgSize.length > 0); + for (int i = 0; i < perLevelAvgSize.length; i++) + Assert.assertTrue(perLevelAvgSize[i] > 0); + }); + } + + @Test + public void testGetPerLevelAvgDensity() throws Throwable + { + CLUSTER.get(1).runOnInstance(() -> { + ColumnFamilyStore cfs = Keyspace.open(KEYSPACE).getColumnFamilyStore("cf"); + double[] perLevelAvgDensity = cfs.getPerLevelAvgDensity(); + assert(perLevelAvgDensity.length > 0); + for (int i = 0; i < perLevelAvgDensity.length; i++) + Assert.assertTrue(perLevelAvgDensity[i] > 0); + }); + } + + @Test + public void testGetPerLevelAvgDensityMaxDensityThresholdRatio() throws Throwable + { + CLUSTER.get(1).runOnInstance(() -> { + ColumnFamilyStore cfs = Keyspace.open(KEYSPACE).getColumnFamilyStore("cf"); + double[] perLevelAvgDensityMaxDensityThresholdRatio = cfs.getPerLevelAvgDensityMaxDensityThresholdRatio(); + assert(perLevelAvgDensityMaxDensityThresholdRatio.length > 0); + for (int i = 0; i < perLevelAvgDensityMaxDensityThresholdRatio.length; i++) + { + Assert.assertTrue(0 <= perLevelAvgDensityMaxDensityThresholdRatio[i]); + Assert.assertTrue(perLevelAvgDensityMaxDensityThresholdRatio[i] < 1); + } + }); + } + + @Test + public void testGetPerLevelMaxDensityMaxDensityThresholdRatio() throws Throwable + { + CLUSTER.get(1).runOnInstance(() -> { + ColumnFamilyStore cfs = Keyspace.open(KEYSPACE).getColumnFamilyStore("cf"); + double[] perLevelMaxDensityMaxDensityThresholdRatio = cfs.getPerLevelMaxDensityMaxDensityThresholdRatio(); + assert(perLevelMaxDensityMaxDensityThresholdRatio.length > 0); + for (int i = 0; i < perLevelMaxDensityMaxDensityThresholdRatio.length; i++) { + Assert.assertTrue(0 <= perLevelMaxDensityMaxDensityThresholdRatio[i]); + Assert.assertTrue(perLevelMaxDensityMaxDensityThresholdRatio[i] < 1); + } + }); + } +} \ No newline at end of file From d13df2a3f49d1eb3fd812e40d808844fe8332ea8 Mon Sep 17 00:00:00 2001 From: Alan Wang Date: Thu, 18 Dec 2025 11:40:30 -0800 Subject: [PATCH 02/11] formatting --- .../compaction/UnifiedCompactionStrategy.java | 4 ++- .../nodetool/stats/TableStatsHolder.java | 8 ++--- .../nodetool/stats/TableStatsPrinter.java | 6 ++-- .../test/ColumnFamilyStoreMBeansTest.java | 29 ++++++++++--------- 4 files changed, 25 insertions(+), 22 deletions(-) diff --git a/src/java/org/apache/cassandra/db/compaction/UnifiedCompactionStrategy.java b/src/java/org/apache/cassandra/db/compaction/UnifiedCompactionStrategy.java index 6f9332f52f25..8f6792590e88 100644 --- a/src/java/org/apache/cassandra/db/compaction/UnifiedCompactionStrategy.java +++ b/src/java/org/apache/cassandra/db/compaction/UnifiedCompactionStrategy.java @@ -534,7 +534,6 @@ List getLevels() * are currently undergoing compaction. This is used only for table stats so we can have a consistent * snapshot of the levels. */ - @VisibleForTesting List getLevelsSnapshot() { Set sstables = getSSTables(); @@ -565,6 +564,9 @@ public List getLevels(Collection sstables, return formLevels(suitable); } + /** + * Returns the density of the SSTable + */ public double getDensity(SSTableReader sstable) { return shardManager.density(sstable); diff --git a/src/java/org/apache/cassandra/tools/nodetool/stats/TableStatsHolder.java b/src/java/org/apache/cassandra/tools/nodetool/stats/TableStatsHolder.java index 580ac1ae5834..4191bc5d33f7 100644 --- a/src/java/org/apache/cassandra/tools/nodetool/stats/TableStatsHolder.java +++ b/src/java/org/apache/cassandra/tools/nodetool/stats/TableStatsHolder.java @@ -304,13 +304,13 @@ private void initializeKeyspaces(NodeProbe probe, boolean ignore, List t } } - double[] ucsSsTableAvgSize = table.getPerLevelAvgSize(); - if (ucsSsTableAvgSize != null) + double[] ucsSSTableAvgSize = table.getPerLevelAvgSize(); + if (ucsSSTableAvgSize != null) { statsTable.isUCSSstable = true; - for (int level = 0; level < ucsSsTableAvgSize.length; level++) + for (int level = 0; level < ucsSSTableAvgSize.length; level++) { - statsTable.sstableAvgSizeInEachLevel.add(String.format("%.03f", ucsSsTableAvgSize[level])); + statsTable.sstableAvgSizeInEachLevel.add(String.format("%.03f", ucsSSTableAvgSize[level])); } } diff --git a/src/java/org/apache/cassandra/tools/nodetool/stats/TableStatsPrinter.java b/src/java/org/apache/cassandra/tools/nodetool/stats/TableStatsPrinter.java index 24ccd94bf6cd..8f378d0530dc 100644 --- a/src/java/org/apache/cassandra/tools/nodetool/stats/TableStatsPrinter.java +++ b/src/java/org/apache/cassandra/tools/nodetool/stats/TableStatsPrinter.java @@ -104,15 +104,15 @@ protected void printStatsTable(StatsTable table, String tableDisplayName, String { out.println(indent + "Average token space for SSTables in each level: [" + String.join(", ", table.sstableAvgTokenSpaceInEachLevel) + "]"); - out.println(indent + "Maximum density for SSTables in each level: [" + String.join(", ", + out.println(indent + "Maximum density threshold for SSTables in each level: [" + String.join(", ", table.sstableMaxDensityThresholdInEachLevel) + "]"); out.println(indent + "Average SSTable size in each level: [" + String.join(", ", table.sstableAvgSizeInEachLevel) + "]"); out.println(indent + "Average SSTable density in each level: [" + String.join(", ", table.sstableAvgDensityInEachLevel) + "]"); - out.println(indent + "Ratio of average SSTable density and maximum density threshold in each level: [" + String.join(", ", + out.println(indent + "Average SSTable density to max threshold ratio in each level: [" + String.join(", ", table.sstableAvgDensityMaxDensityThresholdRatioInEachLevel) + "]"); - out.println(indent + "Ratio of maximum SSTable density and maximum density threshold in each level: [" + String.join(", ", + out.println(indent + "Maximum SSTable density to max threshold ratio in each level: [" + String.join(", ", table.sstableMaxDensityMaxDensityThresholdRatioInEachLevel) + "]"); } diff --git a/test/distributed/org/apache/cassandra/distributed/test/ColumnFamilyStoreMBeansTest.java b/test/distributed/org/apache/cassandra/distributed/test/ColumnFamilyStoreMBeansTest.java index 59e79ca33d14..d580f44857fe 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/ColumnFamilyStoreMBeansTest.java +++ b/test/distributed/org/apache/cassandra/distributed/test/ColumnFamilyStoreMBeansTest.java @@ -28,6 +28,7 @@ import static org.apache.cassandra.distributed.api.Feature.GOSSIP; import static org.apache.cassandra.distributed.api.Feature.NETWORK; +import static org.junit.Assert.assertTrue; public class ColumnFamilyStoreMBeansTest extends TestBaseImpl { @@ -56,9 +57,9 @@ public void testPerLevelAverageTokenSpace() throws Throwable CLUSTER.get(1).runOnInstance(() -> { ColumnFamilyStore cfs = Keyspace.open(KEYSPACE).getColumnFamilyStore("cf"); double[] perLevelAvgTokenSpace = cfs.getPerLevelAvgTokenSpace(); - assert(perLevelAvgTokenSpace.length > 0); + assertTrue(perLevelAvgTokenSpace.length > 0); for (int i = 0; i < perLevelAvgTokenSpace.length; i++) - Assert.assertTrue(perLevelAvgTokenSpace[i] > 0); + assertTrue(perLevelAvgTokenSpace[i] > 0); }); } @@ -67,7 +68,7 @@ public void testGetPerLevelMaxDensityThreshold() throws Throwable { CLUSTER.get(1).runOnInstance(() -> { ColumnFamilyStore cfs = Keyspace.open(KEYSPACE).getColumnFamilyStore("cf"); - Assert.assertTrue(cfs.getPerLevelMaxDensityThreshold().length > 0); + assertTrue(cfs.getPerLevelMaxDensityThreshold().length > 0); }); } @@ -77,9 +78,9 @@ public void testGetPerLevelAvgSize() throws Throwable CLUSTER.get(1).runOnInstance(() -> { ColumnFamilyStore cfs = Keyspace.open(KEYSPACE).getColumnFamilyStore("cf"); double[] perLevelAvgSize = cfs.getPerLevelAvgSize(); - assert(perLevelAvgSize.length > 0); + assertTrue(perLevelAvgSize.length > 0); for (int i = 0; i < perLevelAvgSize.length; i++) - Assert.assertTrue(perLevelAvgSize[i] > 0); + assertTrue(perLevelAvgSize[i] > 0); }); } @@ -89,9 +90,9 @@ public void testGetPerLevelAvgDensity() throws Throwable CLUSTER.get(1).runOnInstance(() -> { ColumnFamilyStore cfs = Keyspace.open(KEYSPACE).getColumnFamilyStore("cf"); double[] perLevelAvgDensity = cfs.getPerLevelAvgDensity(); - assert(perLevelAvgDensity.length > 0); + assertTrue(perLevelAvgDensity.length > 0); for (int i = 0; i < perLevelAvgDensity.length; i++) - Assert.assertTrue(perLevelAvgDensity[i] > 0); + assertTrue(perLevelAvgDensity[i] > 0); }); } @@ -101,11 +102,11 @@ public void testGetPerLevelAvgDensityMaxDensityThresholdRatio() throws Throwable CLUSTER.get(1).runOnInstance(() -> { ColumnFamilyStore cfs = Keyspace.open(KEYSPACE).getColumnFamilyStore("cf"); double[] perLevelAvgDensityMaxDensityThresholdRatio = cfs.getPerLevelAvgDensityMaxDensityThresholdRatio(); - assert(perLevelAvgDensityMaxDensityThresholdRatio.length > 0); + assertTrue(perLevelAvgDensityMaxDensityThresholdRatio.length > 0); for (int i = 0; i < perLevelAvgDensityMaxDensityThresholdRatio.length; i++) { - Assert.assertTrue(0 <= perLevelAvgDensityMaxDensityThresholdRatio[i]); - Assert.assertTrue(perLevelAvgDensityMaxDensityThresholdRatio[i] < 1); + assertTrue(0 <= perLevelAvgDensityMaxDensityThresholdRatio[i]); + assertTrue(perLevelAvgDensityMaxDensityThresholdRatio[i] < 1); } }); } @@ -116,11 +117,11 @@ public void testGetPerLevelMaxDensityMaxDensityThresholdRatio() throws Throwable CLUSTER.get(1).runOnInstance(() -> { ColumnFamilyStore cfs = Keyspace.open(KEYSPACE).getColumnFamilyStore("cf"); double[] perLevelMaxDensityMaxDensityThresholdRatio = cfs.getPerLevelMaxDensityMaxDensityThresholdRatio(); - assert(perLevelMaxDensityMaxDensityThresholdRatio.length > 0); + assertTrue(perLevelMaxDensityMaxDensityThresholdRatio.length > 0); for (int i = 0; i < perLevelMaxDensityMaxDensityThresholdRatio.length; i++) { - Assert.assertTrue(0 <= perLevelMaxDensityMaxDensityThresholdRatio[i]); - Assert.assertTrue(perLevelMaxDensityMaxDensityThresholdRatio[i] < 1); + assertTrue(0 <= perLevelMaxDensityMaxDensityThresholdRatio[i]); + assertTrue(perLevelMaxDensityMaxDensityThresholdRatio[i] < 1); } }); } -} \ No newline at end of file +} From 822b075f1a0b8841beb2e5b71346e51b04593f53 Mon Sep 17 00:00:00 2001 From: Alan Wang Date: Thu, 18 Dec 2025 14:26:18 -0800 Subject: [PATCH 03/11] formatting --- .../nodetool/stats/TableStatsHolder.java | 72 +++++-------------- 1 file changed, 18 insertions(+), 54 deletions(-) diff --git a/src/java/org/apache/cassandra/tools/nodetool/stats/TableStatsHolder.java b/src/java/org/apache/cassandra/tools/nodetool/stats/TableStatsHolder.java index 4191bc5d33f7..0772a25272d4 100644 --- a/src/java/org/apache/cassandra/tools/nodetool/stats/TableStatsHolder.java +++ b/src/java/org/apache/cassandra/tools/nodetool/stats/TableStatsHolder.java @@ -284,65 +284,17 @@ private void initializeKeyspaces(NodeProbe probe, boolean ignore, List t } } - double[] ucsSSTableTokenSpace = table.getPerLevelAvgTokenSpace(); - if (ucsSSTableTokenSpace != null) - { - statsTable.isUCSSstable = true; - for (int level = 0; level < ucsSSTableTokenSpace.length; level++) - { - statsTable.sstableAvgTokenSpaceInEachLevel.add(String.format("%.03f", ucsSSTableTokenSpace[level])); - } - } + addUCSMetric(statsTable, statsTable.sstableAvgTokenSpaceInEachLevel, table.getPerLevelAvgTokenSpace()); - double[] ucsMaxDensityThreshold = table.getPerLevelMaxDensityThreshold(); - if (ucsMaxDensityThreshold != null) - { - statsTable.isUCSSstable = true; - for (int level = 0; level < ucsMaxDensityThreshold.length; level++) - { - statsTable.sstableMaxDensityThresholdInEachLevel.add(String.format("%.03f", ucsMaxDensityThreshold[level])); - } - } + addUCSMetric(statsTable, statsTable.sstableMaxDensityThresholdInEachLevel, table.getPerLevelMaxDensityThreshold()); - double[] ucsSSTableAvgSize = table.getPerLevelAvgSize(); - if (ucsSSTableAvgSize != null) - { - statsTable.isUCSSstable = true; - for (int level = 0; level < ucsSSTableAvgSize.length; level++) - { - statsTable.sstableAvgSizeInEachLevel.add(String.format("%.03f", ucsSSTableAvgSize[level])); - } - } + addUCSMetric(statsTable, statsTable.sstableAvgSizeInEachLevel, table.getPerLevelAvgSize()); - double[] ucsSStableAvgDensity = table.getPerLevelAvgDensity(); - if (ucsSStableAvgDensity != null) - { - statsTable.isUCSSstable = true; - for (int level = 0; level < ucsSStableAvgDensity.length; level++) - { - statsTable.sstableAvgDensityInEachLevel.add(String.format("%.03f", ucsSStableAvgDensity[level])); - } - } + addUCSMetric(statsTable, statsTable.sstableAvgDensityInEachLevel, table.getPerLevelAvgDensity()); - double[] ucsSStableAvgDensityMaxDensityThresholdRatio = table.getPerLevelAvgDensityMaxDensityThresholdRatio(); - if (ucsSStableAvgDensityMaxDensityThresholdRatio != null) - { - statsTable.isUCSSstable = true; - for (int level = 0; level < ucsSStableAvgDensityMaxDensityThresholdRatio.length; level++) - { - statsTable.sstableAvgDensityMaxDensityThresholdRatioInEachLevel.add(String.format("%.03f", ucsSStableAvgDensityMaxDensityThresholdRatio[level])); - } - } + addUCSMetric(statsTable, statsTable.sstableAvgDensityMaxDensityThresholdRatioInEachLevel, table.getPerLevelAvgDensityMaxDensityThresholdRatio()); - double[] ucsSStableMaxDensityMaxDensityThresholdRatioInEachLevel = table.getPerLevelMaxDensityMaxDensityThresholdRatio(); - if (ucsSStableMaxDensityMaxDensityThresholdRatioInEachLevel != null) - { - statsTable.isUCSSstable = true; - for (int level = 0; level < ucsSStableMaxDensityMaxDensityThresholdRatioInEachLevel.length; level++) - { - statsTable.sstableMaxDensityMaxDensityThresholdRatioInEachLevel.add(String.format("%.03f", ucsSStableMaxDensityMaxDensityThresholdRatioInEachLevel[level])); - } - } + addUCSMetric(statsTable, statsTable.sstableMaxDensityMaxDensityThresholdRatioInEachLevel, table.getPerLevelMaxDensityMaxDensityThresholdRatio()); if (locationCheck) statsTable.isInCorrectLocation = !table.hasMisplacedSSTables(); @@ -516,6 +468,18 @@ private void initializeKeyspaces(NodeProbe probe, boolean ignore, List t } } + private void addUCSMetric(StatsTable statsTable, List acc, double[] values) + { + if (values != null) + { + statsTable.isUCSSstable = true; + for (int level = 0; level < values.length; level++) + { + acc.add(String.format("%.03f", values[level])); + } + } + } + private double getMetricMean(Object metricObject) { if (metricObject instanceof CassandraMetricsRegistry.JmxTimerMBean) { return ((CassandraMetricsRegistry.JmxTimerMBean) metricObject).getMean() / 1000; From 15f1755bc3deca8c8526f9786949d2c3febee16c Mon Sep 17 00:00:00 2001 From: Alan Wang Date: Thu, 18 Dec 2025 16:02:55 -0800 Subject: [PATCH 04/11] refactor to be more readable --- .../compaction/CompactionStrategyManager.java | 209 ++++++++---------- 1 file changed, 96 insertions(+), 113 deletions(-) diff --git a/src/java/org/apache/cassandra/db/compaction/CompactionStrategyManager.java b/src/java/org/apache/cassandra/db/compaction/CompactionStrategyManager.java index 4c7277e866f6..c1bdb7ecb270 100644 --- a/src/java/org/apache/cassandra/db/compaction/CompactionStrategyManager.java +++ b/src/java/org/apache/cassandra/db/compaction/CompactionStrategyManager.java @@ -32,7 +32,8 @@ import java.util.Set; import java.util.TreeMap; import java.util.concurrent.locks.ReentrantReadWriteLock; -import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -81,7 +82,6 @@ import org.apache.cassandra.schema.CompactionParams; import org.apache.cassandra.service.ActiveRepairService; import org.apache.cassandra.utils.TimeUUID; -import org.apache.cassandra.utils.TriFunction; import org.apache.cassandra.db.compaction.UnifiedCompactionStrategy.Level; import static org.apache.cassandra.db.compaction.AbstractStrategyHolder.GroupedSSTableContainer; @@ -695,90 +695,69 @@ public long[] getPerLevelSizeBytes() public double[] getPerLevelAvgTokenSpace() { - return avgUCSHelper((ucs, sstable) -> - sstable.tokenSpaceCoverage()); + return computeUCSMetric( + data -> { + data.sum[data.levelIndex] += data.sstable.tokenSpaceCoverage(); + data.count[data.levelIndex]++; + }, + CompactionStrategyManager::averageFinalizer + ); } public double[] getPerLevelMaxDensityThreshold() { - return perLevelUCSHelper((level, sstable, acc) -> Math.max(acc, level.max)); + return computeUCSMetric( + data -> { + data.max[data.levelIndex] = Math.max(data.max[data.levelIndex], data.level.max); + }, + CompactionStrategyManager::maxArrayFinalizer + ); } public double[] getPerLevelAvgSize() { - return avgUCSHelper((ucs, sstable) -> (double) sstable.onDiskLength()); + return computeUCSMetric( + data -> { + data.sum[data.levelIndex] += data.sstable.onDiskLength(); + data.count[data.levelIndex]++; + }, + CompactionStrategyManager::averageFinalizer + ); } public double[] getPerLevelAvgDensity() { - return avgUCSHelper(UnifiedCompactionStrategy::getDensity); + return computeUCSMetric( + data -> { + data.sum[data.levelIndex] += data.strategy.getDensity(data.sstable); + data.count[data.levelIndex]++; + }, + CompactionStrategyManager::averageFinalizer + ); } public double[] getPerLevelAvgDensityMaxDensityThresholdRatio() { - readLock.lock(); - try - { - if (repaired.first() instanceof UnifiedCompactionStrategy) - { - double[] avgDensityPerLevel = getPerLevelAvgDensity(); - double[] maxDensityThresholdPerLevel = getPerLevelMaxDensityThreshold(); - - double[] res = new double[avgDensityPerLevel.length]; - - for (int i = 0; i < avgDensityPerLevel.length; i++) - { - res[i] = avgDensityPerLevel[i] / maxDensityThresholdPerLevel[i]; - } - - return res; - } + double[] avgDensity = getPerLevelAvgDensity(); + if (avgDensity == null) return null; - } - finally - { - readLock.unlock(); - } + + double[] maxThreshold = getPerLevelMaxDensityThreshold(); + double[] res = new double[avgDensity.length]; + for (int i = 0; i < avgDensity.length; i++) + res[i] = avgDensity[i] / maxThreshold[i]; + return res; } public double[] getPerLevelMaxDensityMaxDensityThresholdRatio() { - readLock.lock(); - try - { - if (repaired.first() instanceof UnifiedCompactionStrategy) - { - - double[] maxDensityThresholdPerLevel = getPerLevelMaxDensityThreshold(); - double[] maxDensityPerLevel = new double[maxDensityThresholdPerLevel.length]; - double[] res = new double[maxDensityThresholdPerLevel.length]; - - for (AbstractCompactionStrategy strategy : getAllStrategies()) - { - UnifiedCompactionStrategy ucsStrategy = (UnifiedCompactionStrategy) strategy; - List levels = ucsStrategy.getLevelsSnapshot(); - for (int i = 0; i < levels.size(); i++) - { - for (SSTableReader sstable : levels.get(i).getSSTables()) - { - maxDensityPerLevel[i] = Math.max(maxDensityPerLevel[i], ucsStrategy.getDensity(sstable)); - } - } - } - - for (int i = 0; i < maxDensityThresholdPerLevel.length; i++) - { - res[i] = maxDensityPerLevel[i] / maxDensityThresholdPerLevel[i]; - } - - return res; - } - return null; - } - finally - { - readLock.unlock(); - } + return computeUCSMetric( + data -> { + data.sum[data.levelIndex] = Math.max(data.sum[data.levelIndex], data.strategy.getDensity(data.sstable)); + data.max[data.levelIndex] = Math.max(data.max[data.levelIndex], data.level.max); + }, + CompactionStrategyManager::ratioArrayFinalizer + ); } public boolean isLeveledCompaction() @@ -793,83 +772,87 @@ public boolean isLeveledCompaction() } } - double[] avgUCSHelper(BiFunction fn) + /** + * Data class for accumulating UCS metrics computation state. + * Holds intermediate values during metric calculation across all strategies and levels. + */ + private static class CompactionStatsMetricsData + { + final double[] sum = new double[UnifiedCompactionStrategy.MAX_LEVELS]; + final int[] count = new int[UnifiedCompactionStrategy.MAX_LEVELS]; + final double[] max = new double[UnifiedCompactionStrategy.MAX_LEVELS]; + int numberOfLevels = 0; + + int levelIndex; + Level level; + SSTableReader sstable; + UnifiedCompactionStrategy strategy; + } + + /** + * Generic helper to compute UCS metrics across all strategies and levels. + * Reduces code duplication for per-level metric calculations. + * + * @param accumulator processes each sstable and updates the metrics data state + * @param finalizer computes the final result array from the accumulated metrics data + * @return computed metric array, one value per level, or null if not using UCS + */ + private double[] computeUCSMetric(Consumer accumulator, Function finalizer) { readLock.lock(); try { if (repaired.first() instanceof UnifiedCompactionStrategy) { - int numberOfLevels = 0; - - double[] sum = new double[UnifiedCompactionStrategy.MAX_LEVELS]; - int[] count = new int[UnifiedCompactionStrategy.MAX_LEVELS]; + CompactionStatsMetricsData data = new CompactionStatsMetricsData(); for (AbstractCompactionStrategy strategy : getAllStrategies()) { UnifiedCompactionStrategy ucsStrategy = (UnifiedCompactionStrategy) strategy; List levels = ucsStrategy.getLevelsSnapshot(); - numberOfLevels = Math.max(numberOfLevels, levels.size()); + + data.numberOfLevels = Math.max(data.numberOfLevels, levels.size()); + data.strategy = ucsStrategy; + for (int i = 0; i < levels.size(); i++) { + data.levelIndex = i; + data.level = levels.get(i); for (SSTableReader sstable : levels.get(i).getSSTables()) { - sum[i] += fn.apply(ucsStrategy, sstable); - count[i] += 1; + data.sstable = sstable; + accumulator.accept(data); } } } - double[] res = new double[numberOfLevels]; - for (int i = 0; i < numberOfLevels; i++) - res[i] = count[i] == 0 ? 0 : sum[i] / count[i]; - - return res; + return finalizer.apply(data); } return null; } - finally - { + finally { readLock.unlock(); } } - double[] perLevelUCSHelper(TriFunction fn) + private static double[] averageFinalizer(CompactionStatsMetricsData data) { - readLock.lock(); - try - { - if (repaired.first() instanceof UnifiedCompactionStrategy) - { - int numberOfLevels = 0; - - double[] tmp = new double[UnifiedCompactionStrategy.MAX_LEVELS]; - - for (AbstractCompactionStrategy strategy : getAllStrategies()) - { - UnifiedCompactionStrategy ucsStrategy = (UnifiedCompactionStrategy) strategy; - List levels = ucsStrategy.getLevelsSnapshot(); - numberOfLevels = Math.max(numberOfLevels, levels.size()); - for (int i = 0; i < levels.size(); i++) - { - for (SSTableReader sstable : levels.get(i).getSSTables()) - { - tmp[i] = fn.apply(levels.get(i), sstable, tmp[i]); - } - } - } + double[] res = new double[data.numberOfLevels]; + for (int i = 0; i < data.numberOfLevels; i++) + res[i] = data.count[i] == 0 ? 0 : data.sum[i] / data.numberOfLevels; + return res; + } - double[] res = new double[numberOfLevels]; - System.arraycopy(tmp, 0, res, 0, numberOfLevels); + private static double[] maxArrayFinalizer(CompactionStatsMetricsData data) + { + return Arrays.copyOf(data.max, data.numberOfLevels); + } - return res; - } - return null; - } - finally - { - readLock.unlock(); - } + private static double[] ratioArrayFinalizer(CompactionStatsMetricsData data) { + double[] res = new double[data.numberOfLevels]; + for (int i = 0; i < data.numberOfLevels; i++) + res[i] = data.sum[i] / data.max[i]; + return res; } public int[] getSSTableCountPerTWCSBucket() From 36c00e24c164b77ae07f1480725e29b6ce04adc6 Mon Sep 17 00:00:00 2001 From: Alan Wang Date: Thu, 18 Dec 2025 16:22:09 -0800 Subject: [PATCH 05/11] added cluster teardown --- .../distributed/test/ColumnFamilyStoreMBeansTest.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/distributed/org/apache/cassandra/distributed/test/ColumnFamilyStoreMBeansTest.java b/test/distributed/org/apache/cassandra/distributed/test/ColumnFamilyStoreMBeansTest.java index d580f44857fe..2a4f4e0114b5 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/ColumnFamilyStoreMBeansTest.java +++ b/test/distributed/org/apache/cassandra/distributed/test/ColumnFamilyStoreMBeansTest.java @@ -51,6 +51,13 @@ public static void setup() throws IOException CLUSTER.get(1).nodetool("flush"); } + @AfterClass + public static void teardownCluster() throws Exception + { + if (CLUSTER != null) + CLUSTER.close(); + } + @Test public void testPerLevelAverageTokenSpace() throws Throwable { From 1eeb9ad9fdcf7137fdf4a7c0b658726efcc86f9a Mon Sep 17 00:00:00 2001 From: Alan Wang Date: Fri, 19 Dec 2025 10:33:51 -0800 Subject: [PATCH 06/11] fixed bug --- .../cassandra/db/compaction/CompactionStrategyManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/java/org/apache/cassandra/db/compaction/CompactionStrategyManager.java b/src/java/org/apache/cassandra/db/compaction/CompactionStrategyManager.java index c1bdb7ecb270..53be680cdfe4 100644 --- a/src/java/org/apache/cassandra/db/compaction/CompactionStrategyManager.java +++ b/src/java/org/apache/cassandra/db/compaction/CompactionStrategyManager.java @@ -839,7 +839,7 @@ private static double[] averageFinalizer(CompactionStatsMetricsData data) { double[] res = new double[data.numberOfLevels]; for (int i = 0; i < data.numberOfLevels; i++) - res[i] = data.count[i] == 0 ? 0 : data.sum[i] / data.numberOfLevels; + res[i] = data.count[i] == 0 ? 0 : data.sum[i] / data.count[i]; return res; } From 59b4b042c190bc4ceeaa3875b822cad5fbce5fed Mon Sep 17 00:00:00 2001 From: Alan Wang Date: Fri, 19 Dec 2025 12:29:38 -0800 Subject: [PATCH 07/11] extra tests --- .../compaction/CompactionStrategyManager.java | 12 ++-- .../CompactionStrategyManagerTest.java | 62 +++++++++++++++++-- 2 files changed, 65 insertions(+), 9 deletions(-) diff --git a/src/java/org/apache/cassandra/db/compaction/CompactionStrategyManager.java b/src/java/org/apache/cassandra/db/compaction/CompactionStrategyManager.java index 53be680cdfe4..1b0f0c97f4f5 100644 --- a/src/java/org/apache/cassandra/db/compaction/CompactionStrategyManager.java +++ b/src/java/org/apache/cassandra/db/compaction/CompactionStrategyManager.java @@ -776,7 +776,8 @@ public boolean isLeveledCompaction() * Data class for accumulating UCS metrics computation state. * Holds intermediate values during metric calculation across all strategies and levels. */ - private static class CompactionStatsMetricsData + @VisibleForTesting + static class CompactionStatsMetricsData { final double[] sum = new double[UnifiedCompactionStrategy.MAX_LEVELS]; final int[] count = new int[UnifiedCompactionStrategy.MAX_LEVELS]; @@ -835,7 +836,8 @@ private double[] computeUCSMetric(Consumer accumulat } } - private static double[] averageFinalizer(CompactionStatsMetricsData data) + @VisibleForTesting + static double[] averageFinalizer(CompactionStatsMetricsData data) { double[] res = new double[data.numberOfLevels]; for (int i = 0; i < data.numberOfLevels; i++) @@ -843,12 +845,14 @@ private static double[] averageFinalizer(CompactionStatsMetricsData data) return res; } - private static double[] maxArrayFinalizer(CompactionStatsMetricsData data) + @VisibleForTesting + static double[] maxArrayFinalizer(CompactionStatsMetricsData data) { return Arrays.copyOf(data.max, data.numberOfLevels); } - private static double[] ratioArrayFinalizer(CompactionStatsMetricsData data) { + @VisibleForTesting + static double[] ratioArrayFinalizer(CompactionStatsMetricsData data) { double[] res = new double[data.numberOfLevels]; for (int i = 0; i < data.numberOfLevels; i++) res[i] = data.sum[i] / data.max[i]; diff --git a/test/unit/org/apache/cassandra/db/compaction/CompactionStrategyManagerTest.java b/test/unit/org/apache/cassandra/db/compaction/CompactionStrategyManagerTest.java index 20420aeef3f5..e62f214c68fd 100644 --- a/test/unit/org/apache/cassandra/db/compaction/CompactionStrategyManagerTest.java +++ b/test/unit/org/apache/cassandra/db/compaction/CompactionStrategyManagerTest.java @@ -19,11 +19,7 @@ package org.apache.cassandra.db.compaction; import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Set; +import java.util.*; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; @@ -433,6 +429,62 @@ public void testCountsByBuckets() Collections.emptyMap()), CompactionStrategyManager.TWCS_BUCKET_COUNT_MAX)); } + @Test + public void testAverageFinalizer() + { + CompactionStrategyManager.CompactionStatsMetricsData data = new CompactionStrategyManager.CompactionStatsMetricsData(); + Random random = new Random(); + data.numberOfLevels = random.nextInt(32); + + for (int i = 0; i < data.numberOfLevels; i++) { + data.sum[i] = random.nextInt(250); + data.count[i] = random.nextInt(10); + } + + double[] res = CompactionStrategyManager.averageFinalizer(data); + assertEquals(res.length, data.numberOfLevels); + for (int i = 0; i < data.numberOfLevels; i++) + if (data.count[i] == 0) + assertEquals(0, res[i], 0.0); + else + assertEquals(data.sum[i] / data.count[i], res[i], 0.1); + } + + @Test + public void testMaxArrayFinalizer() + { + CompactionStrategyManager.CompactionStatsMetricsData data = new CompactionStrategyManager.CompactionStatsMetricsData(); + Random random = new Random(); + data.numberOfLevels = random.nextInt(32); + + for (int i = 0; i < data.numberOfLevels; i++) { + data.max[i] = random.nextInt(250); + } + + double[] res = CompactionStrategyManager.maxArrayFinalizer(data); + assertEquals(res.length, data.numberOfLevels); + for (int i = 0; i < data.numberOfLevels; i++) + assertEquals(data.max[i], res[i], 0.0); + } + + @Test + public void testRatioArrayFinalizer() + { + CompactionStrategyManager.CompactionStatsMetricsData data = new CompactionStrategyManager.CompactionStatsMetricsData(); + Random random = new Random(); + data.numberOfLevels = random.nextInt(32); + + for (int i = 0; i < data.numberOfLevels; i++) { + data.sum[i] = random.nextInt(250); + data.max[i] = 250 + random.nextInt(500); + } + + double[] res = CompactionStrategyManager.ratioArrayFinalizer(data); + assertEquals(res.length, data.numberOfLevels); + for (int i = 0; i < data.numberOfLevels; i++) + assertEquals(data.sum[i] / data.max[i], res[i], 0.1); + } + private MockCFS createJBODMockCFS(int disks) { // Create #disks data directories to simulate JBOD From 80ef8cae87bb9ee426be8b8983fd3903eed5fffc Mon Sep 17 00:00:00 2001 From: Alan Wang Date: Fri, 19 Dec 2025 12:43:32 -0800 Subject: [PATCH 08/11] formating fix --- .../cassandra/db/compaction/CompactionStrategyManagerTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/unit/org/apache/cassandra/db/compaction/CompactionStrategyManagerTest.java b/test/unit/org/apache/cassandra/db/compaction/CompactionStrategyManagerTest.java index e62f214c68fd..201c5409dda5 100644 --- a/test/unit/org/apache/cassandra/db/compaction/CompactionStrategyManagerTest.java +++ b/test/unit/org/apache/cassandra/db/compaction/CompactionStrategyManagerTest.java @@ -457,9 +457,8 @@ public void testMaxArrayFinalizer() Random random = new Random(); data.numberOfLevels = random.nextInt(32); - for (int i = 0; i < data.numberOfLevels; i++) { + for (int i = 0; i < data.numberOfLevels; i++) data.max[i] = random.nextInt(250); - } double[] res = CompactionStrategyManager.maxArrayFinalizer(data); assertEquals(res.length, data.numberOfLevels); From 77c6b4bb3ec06530b994e681c9e267088e0a5d3d Mon Sep 17 00:00:00 2001 From: Alan Wang Date: Fri, 19 Dec 2025 13:25:36 -0800 Subject: [PATCH 09/11] Remove extra imports --- .../distributed/test/ColumnFamilyStoreMBeansTest.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/test/distributed/org/apache/cassandra/distributed/test/ColumnFamilyStoreMBeansTest.java b/test/distributed/org/apache/cassandra/distributed/test/ColumnFamilyStoreMBeansTest.java index 2a4f4e0114b5..bb5c02686c6f 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/ColumnFamilyStoreMBeansTest.java +++ b/test/distributed/org/apache/cassandra/distributed/test/ColumnFamilyStoreMBeansTest.java @@ -25,9 +25,6 @@ import org.apache.cassandra.distributed.Cluster; import java.io.IOException; - -import static org.apache.cassandra.distributed.api.Feature.GOSSIP; -import static org.apache.cassandra.distributed.api.Feature.NETWORK; import static org.junit.Assert.assertTrue; public class ColumnFamilyStoreMBeansTest extends TestBaseImpl @@ -37,9 +34,8 @@ public class ColumnFamilyStoreMBeansTest extends TestBaseImpl @BeforeClass public static void setup() throws IOException { - CLUSTER = init(Cluster.build(1).withConfig(c -> - c.with(GOSSIP, NETWORK)) - .start()); + CLUSTER = init(Cluster.build(1) + .start()); CLUSTER.schemaChange(withKeyspace("DROP KEYSPACE %s")); CLUSTER.schemaChange(withKeyspace("CREATE KEYSPACE %s WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}")); From 9182ed71d4612c86e0fa33e184556aeb03dc39c7 Mon Sep 17 00:00:00 2001 From: Alan Wang Date: Fri, 19 Dec 2025 14:28:50 -0800 Subject: [PATCH 10/11] standardized naming --- .../db/compaction/CompactionStrategyManager.java | 8 ++++---- .../db/compaction/CompactionStrategyManagerTest.java | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/java/org/apache/cassandra/db/compaction/CompactionStrategyManager.java b/src/java/org/apache/cassandra/db/compaction/CompactionStrategyManager.java index 1b0f0c97f4f5..4882fca7558b 100644 --- a/src/java/org/apache/cassandra/db/compaction/CompactionStrategyManager.java +++ b/src/java/org/apache/cassandra/db/compaction/CompactionStrategyManager.java @@ -700,7 +700,7 @@ public double[] getPerLevelAvgTokenSpace() data.sum[data.levelIndex] += data.sstable.tokenSpaceCoverage(); data.count[data.levelIndex]++; }, - CompactionStrategyManager::averageFinalizer + CompactionStrategyManager::averageArrayFinalizer ); } @@ -721,7 +721,7 @@ public double[] getPerLevelAvgSize() data.sum[data.levelIndex] += data.sstable.onDiskLength(); data.count[data.levelIndex]++; }, - CompactionStrategyManager::averageFinalizer + CompactionStrategyManager::averageArrayFinalizer ); } @@ -732,7 +732,7 @@ public double[] getPerLevelAvgDensity() data.sum[data.levelIndex] += data.strategy.getDensity(data.sstable); data.count[data.levelIndex]++; }, - CompactionStrategyManager::averageFinalizer + CompactionStrategyManager::averageArrayFinalizer ); } @@ -837,7 +837,7 @@ private double[] computeUCSMetric(Consumer accumulat } @VisibleForTesting - static double[] averageFinalizer(CompactionStatsMetricsData data) + static double[] averageArrayFinalizer(CompactionStatsMetricsData data) { double[] res = new double[data.numberOfLevels]; for (int i = 0; i < data.numberOfLevels; i++) diff --git a/test/unit/org/apache/cassandra/db/compaction/CompactionStrategyManagerTest.java b/test/unit/org/apache/cassandra/db/compaction/CompactionStrategyManagerTest.java index 201c5409dda5..a823ccca0234 100644 --- a/test/unit/org/apache/cassandra/db/compaction/CompactionStrategyManagerTest.java +++ b/test/unit/org/apache/cassandra/db/compaction/CompactionStrategyManagerTest.java @@ -430,7 +430,7 @@ public void testCountsByBuckets() } @Test - public void testAverageFinalizer() + public void testAverageArrayFinalizer() { CompactionStrategyManager.CompactionStatsMetricsData data = new CompactionStrategyManager.CompactionStatsMetricsData(); Random random = new Random(); From 48fe90e39489c78f0e2940c5ed2dcdc78506e926 Mon Sep 17 00:00:00 2001 From: Alan Wang Date: Tue, 23 Dec 2025 12:01:31 -0800 Subject: [PATCH 11/11] typo --- .../cassandra/db/compaction/CompactionStrategyManagerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/org/apache/cassandra/db/compaction/CompactionStrategyManagerTest.java b/test/unit/org/apache/cassandra/db/compaction/CompactionStrategyManagerTest.java index a823ccca0234..6e2ad9f1dea5 100644 --- a/test/unit/org/apache/cassandra/db/compaction/CompactionStrategyManagerTest.java +++ b/test/unit/org/apache/cassandra/db/compaction/CompactionStrategyManagerTest.java @@ -441,7 +441,7 @@ public void testAverageArrayFinalizer() data.count[i] = random.nextInt(10); } - double[] res = CompactionStrategyManager.averageFinalizer(data); + double[] res = CompactionStrategyManager.averageArrayFinalizer(data); assertEquals(res.length, data.numberOfLevels); for (int i = 0; i < data.numberOfLevels; i++) if (data.count[i] == 0)