mirror of https://github.com/apache/cassandra
Make sstableexpiredblockers support human-readable output with SSTable sizes
patch by Stefan Miklosovic; reviewed by Brad Schoening, Rishabh Saraswat for CASSANDRA-20448
This commit is contained in:
parent
cd707ba67d
commit
6bf598dcde
|
|
@ -1,4 +1,5 @@
|
|||
5.1
|
||||
* Make sstableexpiredblockers support human-readable output with SSTable sizes (CASSANDRA-20448)
|
||||
* Enhance nodetool compactionhistory to report more compaction properities (CASSANDRA-20081)
|
||||
* Fix initial auto-repairs skipped by too soon check (CASSANDRA-21115)
|
||||
* Add configuration to disk usage guardrails to stop writes across all replicas of a keyspace when any node replicating that keyspace exceeds the disk usage failure threshold. (CASSANDRA-21024)
|
||||
|
|
|
|||
|
|
@ -18,14 +18,22 @@
|
|||
package org.apache.cassandra.tools;
|
||||
|
||||
import java.io.PrintStream;
|
||||
import java.time.Instant;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.ArrayListMultimap;
|
||||
import com.google.common.collect.Multimap;
|
||||
|
||||
import org.apache.commons.cli.CommandLine;
|
||||
import org.apache.commons.cli.CommandLineParser;
|
||||
import org.apache.commons.cli.GnuParser;
|
||||
import org.apache.commons.cli.HelpFormatter;
|
||||
import org.apache.commons.cli.ParseException;
|
||||
|
||||
import org.apache.cassandra.db.ColumnFamilyStore;
|
||||
import org.apache.cassandra.db.Directories;
|
||||
import org.apache.cassandra.db.Keyspace;
|
||||
|
|
@ -33,6 +41,7 @@ import org.apache.cassandra.io.sstable.Component;
|
|||
import org.apache.cassandra.io.sstable.Descriptor;
|
||||
import org.apache.cassandra.io.sstable.format.SSTableFormat.Components;
|
||||
import org.apache.cassandra.io.sstable.format.SSTableReader;
|
||||
import org.apache.cassandra.io.util.FileUtils;
|
||||
import org.apache.cassandra.schema.Schema;
|
||||
import org.apache.cassandra.schema.TableMetadata;
|
||||
import org.apache.cassandra.tcm.ClusterMetadataService;
|
||||
|
|
@ -43,25 +52,105 @@ import static org.apache.cassandra.utils.Clock.Global.currentTimeMillis;
|
|||
* During compaction we can drop entire sstables if they only contain expired tombstones and if it is guaranteed
|
||||
* to not cover anything in other sstables. An expired sstable can be blocked from getting dropped if its newest
|
||||
* timestamp is newer than the oldest data in another sstable.
|
||||
*
|
||||
* <p>
|
||||
* This class outputs all sstables that are blocking other sstables from getting dropped so that a user can
|
||||
* figure out why certain sstables are still on disk.
|
||||
*/
|
||||
public class SSTableExpiredBlockers
|
||||
{
|
||||
public static void main(String[] args)
|
||||
private static final String TOOL_NAME = "sstableexpiredblockers";
|
||||
|
||||
private static class Options
|
||||
{
|
||||
PrintStream out = System.out;
|
||||
if (args.length < 2)
|
||||
private static final String HUMAN_READABLE_OPTION = "human-readable";
|
||||
private static final String HELP_OPTION = "help";
|
||||
|
||||
public final String keyspace;
|
||||
public final String table;
|
||||
public boolean humanReadable = false;
|
||||
|
||||
private Options(String keyspace, String table)
|
||||
{
|
||||
out.println("Usage: sstableexpiredblockers <keyspace> <table>");
|
||||
this.keyspace = keyspace;
|
||||
this.table = table;
|
||||
}
|
||||
|
||||
public static Options parseArgs(String cmdArgs[])
|
||||
{
|
||||
CommandLineParser parser = new GnuParser();
|
||||
CmdLineOptions options = getCmdLineOptions();
|
||||
|
||||
try
|
||||
{
|
||||
CommandLine cmd = parser.parse(options, cmdArgs, false);
|
||||
|
||||
if (cmd.hasOption(HELP_OPTION))
|
||||
{
|
||||
printUsage(options);
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
String[] args = cmd.getArgs();
|
||||
|
||||
if (args.length != 2)
|
||||
{
|
||||
String msg = args.length < 2 ? "Missing arguments" : "Too many arguments";
|
||||
System.err.println(msg);
|
||||
printUsage(options);
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
String keyspace = args[0];
|
||||
String table = args[1];
|
||||
|
||||
Options opts = new Options(keyspace, table);
|
||||
opts.humanReadable = cmd.hasOption(HUMAN_READABLE_OPTION);
|
||||
|
||||
return opts;
|
||||
}
|
||||
catch (ParseException e)
|
||||
{
|
||||
errorMsg(e.getMessage(), options);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void errorMsg(String msg, CmdLineOptions options)
|
||||
{
|
||||
System.err.println(msg);
|
||||
printUsage(options);
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
private static CmdLineOptions getCmdLineOptions()
|
||||
{
|
||||
CmdLineOptions options = new CmdLineOptions();
|
||||
options.addOption("H", "human-readable", "Displays values in a human-readable format");
|
||||
return options;
|
||||
}
|
||||
|
||||
private static void printUsage(CmdLineOptions options)
|
||||
{
|
||||
String usage = String.format("%s [options] <keyspace> <table>", TOOL_NAME);
|
||||
String header = "--\n" +
|
||||
"Outputs all SSTables that are blocking other SSTables from getting dropped." +
|
||||
"\n--\n" +
|
||||
"Options are:";
|
||||
new HelpFormatter().printHelp(120, usage, header, options, "");
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
Options options = Options.parseArgs(args);
|
||||
|
||||
PrintStream out = System.out;
|
||||
|
||||
Util.initDatabaseDescriptor();
|
||||
ClusterMetadataService.initializeForTools(false);
|
||||
String keyspace = args[args.length - 2];
|
||||
String columnfamily = args[args.length - 1];
|
||||
|
||||
String keyspace = options.keyspace;
|
||||
String columnfamily = options.table;
|
||||
|
||||
TableMetadata metadata = Schema.instance.validateTable(keyspace, columnfamily);
|
||||
|
||||
|
|
@ -86,7 +175,7 @@ public class SSTableExpiredBlockers
|
|||
}
|
||||
if (sstables.isEmpty())
|
||||
{
|
||||
out.println("No sstables for " + keyspace + "." + columnfamily);
|
||||
out.printf("No sstables for %s.%s", keyspace, columnfamily);
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
|
|
@ -94,10 +183,10 @@ public class SSTableExpiredBlockers
|
|||
Multimap<SSTableReader, SSTableReader> blockers = checkForExpiredSSTableBlockers(sstables, gcBefore);
|
||||
for (SSTableReader blocker : blockers.keySet())
|
||||
{
|
||||
out.println(String.format("%s blocks %d expired sstables from getting dropped: %s%n",
|
||||
formatForExpiryTracing(Collections.singleton(blocker)),
|
||||
blockers.get(blocker).size(),
|
||||
formatForExpiryTracing(blockers.get(blocker))));
|
||||
out.printf("%s blocks %d expired sstables from getting dropped: %s%n%n",
|
||||
formatForExpiryTracing(options.humanReadable, Collections.singleton(blocker)),
|
||||
blockers.get(blocker).size(),
|
||||
formatForExpiryTracing(options.humanReadable, blockers.get(blocker)));
|
||||
}
|
||||
|
||||
System.exit(0);
|
||||
|
|
@ -122,13 +211,41 @@ public class SSTableExpiredBlockers
|
|||
return blockers;
|
||||
}
|
||||
|
||||
private static String formatForExpiryTracing(Iterable<SSTableReader> sstables)
|
||||
@VisibleForTesting
|
||||
public static String formatForExpiryTracing(boolean humanReadable, Iterable<SSTableReader> sstables)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
for (SSTableReader sstable : sstables)
|
||||
sb.append(String.format("[%s (minTS = %d, maxTS = %d, maxLDT = %d)]", sstable, sstable.getMinTimestamp(), sstable.getMaxTimestamp(), sstable.getMaxLocalDeletionTime())).append(", ");
|
||||
{
|
||||
long minTimestamp = sstable.getMinTimestamp();
|
||||
long maxTimestamp = sstable.getMaxTimestamp();
|
||||
long maxLocalDeletionTime = sstable.getMaxLocalDeletionTime();
|
||||
if (humanReadable)
|
||||
{
|
||||
sb.append(logEntry(sstable,
|
||||
minTimestamp != Long.MIN_VALUE && minTimestamp != Long.MAX_VALUE ? Instant.ofEpochMilli(minTimestamp) : minTimestamp,
|
||||
maxTimestamp != Long.MIN_VALUE && minTimestamp != Long.MAX_VALUE ? Instant.ofEpochMilli(maxTimestamp) : maxTimestamp,
|
||||
maxLocalDeletionTime != Long.MAX_VALUE ? Instant.ofEpochSecond(maxLocalDeletionTime) : maxLocalDeletionTime,
|
||||
FileUtils.stringifyFileSize(sstable.onDiskLength())));
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append(logEntry(sstable,
|
||||
minTimestamp,
|
||||
maxTimestamp,
|
||||
maxLocalDeletionTime,
|
||||
sstable.onDiskLength()));
|
||||
}
|
||||
sb.append(", ");
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static String logEntry(SSTableReader sstable, Object minTs, Object maxTs, Object maxLocationDeletionTime, Object size)
|
||||
{
|
||||
return String.format("[%s (minTS = %s, maxTS = %s, maxLDT = %s, diskSize = %s)]",
|
||||
sstable, minTs, maxTs, maxLocationDeletionTime, size);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ import com.google.common.collect.Sets;
|
|||
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.apache.cassandra.SchemaLoader;
|
||||
import org.apache.cassandra.Util;
|
||||
|
|
@ -56,6 +58,8 @@ import static org.junit.Assert.assertTrue;
|
|||
|
||||
public class TTLExpiryTest
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(TTLExpiryTest.class);
|
||||
|
||||
public static final String KEYSPACE1 = "TTLExpiryTest";
|
||||
private static final String CF_STANDARD1 = "Standard1";
|
||||
|
||||
|
|
@ -296,5 +300,9 @@ public class TTLExpiryTest
|
|||
assertEquals(1, blockers.keySet().size());
|
||||
assertTrue(blockers.keySet().contains(blockingSSTable));
|
||||
assertEquals(10, blockers.get(blockingSSTable).size());
|
||||
|
||||
// with and without human friendly output
|
||||
logger.info(SSTableExpiredBlockers.formatForExpiryTracing(true, blockers.keySet()));
|
||||
logger.info(SSTableExpiredBlockers.formatForExpiryTracing(false, blockers.keySet()));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ public class SSTableExpiredBlockersTest extends OfflineToolUtils
|
|||
{
|
||||
ToolResult tool = ToolRunner.invokeClass(SSTableExpiredBlockers.class);
|
||||
assertThat(tool.getStdout(), CoreMatchers.containsStringIgnoringCase("usage:"));
|
||||
Assertions.assertThat(tool.getCleanedStderr()).isEmpty();
|
||||
assertThat(tool.getCleanedStderr(), CoreMatchers.containsStringIgnoringCase("Missing arguments"));
|
||||
assertEquals(1, tool.getExitCode());
|
||||
|
||||
assertNoUnexpectedThreadsStarted(null, false);
|
||||
|
|
@ -50,7 +50,12 @@ public class SSTableExpiredBlockersTest extends OfflineToolUtils
|
|||
{
|
||||
// If you added, modified options or help, please update docs if necessary
|
||||
ToolResult tool = ToolRunner.invokeClass(SSTableExpiredBlockers.class);
|
||||
String help = "Usage: sstableexpiredblockers <keyspace> <table>\n";
|
||||
String help = "usage: sstableexpiredblockers [options] <keyspace> <table>\n" +
|
||||
"--\n" +
|
||||
"Outputs all SSTables that are blocking other SSTables from getting dropped.\n" +
|
||||
"--\n" +
|
||||
"Options are:\n" +
|
||||
" -H,--human-readable Displays values in a human-readable format\n";
|
||||
Assertions.assertThat(tool.getStdout()).isEqualTo(help);
|
||||
}
|
||||
|
||||
|
|
@ -58,6 +63,16 @@ public class SSTableExpiredBlockersTest extends OfflineToolUtils
|
|||
public void testWrongArgsIgnored()
|
||||
{
|
||||
ToolResult tool = ToolRunner.invokeClass(SSTableExpiredBlockers.class, "--debugwrong", "system_schema", "tables");
|
||||
assertThat(tool.getStdout(), CoreMatchers.containsStringIgnoringCase("usage:"));
|
||||
assertThat(tool.getCleanedStderr(), CoreMatchers.containsStringIgnoringCase("Unrecognized option: --debugwrong"));
|
||||
assertEquals(1, tool.getExitCode());
|
||||
assertCorrectEnvPostTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHumanReadableArg()
|
||||
{
|
||||
ToolResult tool = ToolRunner.invokeClass(SSTableExpiredBlockers.class, "--human-readable", "system_schema", "tables");
|
||||
assertThat(tool.getStdout(), CoreMatchers.containsStringIgnoringCase("No sstables for"));
|
||||
Assertions.assertThat(tool.getCleanedStderr()).isEmpty();
|
||||
assertEquals(1, tool.getExitCode());
|
||||
|
|
|
|||
Loading…
Reference in New Issue