| /* |
| * 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.tools; |
| |
| import java.io.PrintStream; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import com.google.common.collect.ArrayListMultimap; |
| import com.google.common.collect.Multimap; |
| |
| import org.apache.cassandra.schema.TableMetadata; |
| import org.apache.cassandra.schema.Schema; |
| import org.apache.cassandra.db.ColumnFamilyStore; |
| import org.apache.cassandra.db.Directories; |
| import org.apache.cassandra.db.Keyspace; |
| import org.apache.cassandra.io.sstable.Component; |
| import org.apache.cassandra.io.sstable.Descriptor; |
| import org.apache.cassandra.io.sstable.format.SSTableReader; |
| |
| /** |
| * 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. |
| * |
| * 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) |
| { |
| PrintStream out = System.out; |
| if (args.length < 2) |
| { |
| out.println("Usage: sstableexpiredblockers <keyspace> <table>"); |
| System.exit(1); |
| } |
| |
| Util.initDatabaseDescriptor(); |
| |
| String keyspace = args[args.length - 2]; |
| String columnfamily = args[args.length - 1]; |
| Schema.instance.loadFromDisk(false); |
| |
| TableMetadata metadata = Schema.instance.validateTable(keyspace, columnfamily); |
| |
| Keyspace ks = Keyspace.openWithoutSSTables(keyspace); |
| ColumnFamilyStore cfs = ks.getColumnFamilyStore(columnfamily); |
| Directories.SSTableLister lister = cfs.getDirectories().sstableLister(Directories.OnTxnErr.THROW).skipTemporary(true); |
| Set<SSTableReader> sstables = new HashSet<>(); |
| for (Map.Entry<Descriptor, Set<Component>> sstable : lister.list().entrySet()) |
| { |
| if (sstable.getKey() != null) |
| { |
| try |
| { |
| SSTableReader reader = SSTableReader.open(sstable.getKey()); |
| sstables.add(reader); |
| } |
| catch (Throwable t) |
| { |
| out.println("Couldn't open sstable: " + sstable.getKey().filenameFor(Component.DATA)+" ("+t.getMessage()+")"); |
| } |
| } |
| } |
| if (sstables.isEmpty()) |
| { |
| out.println("No sstables for " + keyspace + "." + columnfamily); |
| System.exit(1); |
| } |
| |
| int gcBefore = (int)(System.currentTimeMillis()/1000) - metadata.params.gcGraceSeconds; |
| 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)))); |
| } |
| |
| System.exit(0); |
| } |
| |
| public static Multimap<SSTableReader, SSTableReader> checkForExpiredSSTableBlockers(Iterable<SSTableReader> sstables, int gcBefore) |
| { |
| Multimap<SSTableReader, SSTableReader> blockers = ArrayListMultimap.create(); |
| for (SSTableReader sstable : sstables) |
| { |
| if (sstable.getSSTableMetadata().maxLocalDeletionTime < gcBefore) |
| { |
| for (SSTableReader potentialBlocker : sstables) |
| { |
| if (!potentialBlocker.equals(sstable) && |
| potentialBlocker.getMinTimestamp() <= sstable.getMaxTimestamp() && |
| potentialBlocker.getSSTableMetadata().maxLocalDeletionTime > gcBefore) |
| blockers.put(potentialBlocker, sstable); |
| } |
| } |
| } |
| return blockers; |
| } |
| |
| private static String formatForExpiryTracing(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.getSSTableMetadata().maxLocalDeletionTime)).append(", "); |
| |
| return sb.toString(); |
| } |
| } |