Consolidate all snapshot management to SnapshotManager

patch by Stefan Miklosovic; reviewed by Francisco Guerrero for CASSANDRA-18111
This commit is contained in:
Stefan Miklosovic 2024-06-20 10:29:51 +02:00
parent 4f49ca5e29
commit f410b0fa0b
No known key found for this signature in database
GPG Key ID: 32F35CB2F546D93E
79 changed files with 5142 additions and 1523 deletions

View File

@ -1,4 +1,5 @@
5.1 5.1
* Consolidate all snapshot management to SnapshotManager and introduce SnapshotManagerMBean (CASSANDRA-18111)
* Fix RequestFailureReason constants codes (CASSANDRA-20126) * Fix RequestFailureReason constants codes (CASSANDRA-20126)
* Introduce SSTableSimpleScanner for compaction (CASSANDRA-20092) * Introduce SSTableSimpleScanner for compaction (CASSANDRA-20092)
* Include column drop timestamp in alter table transformation (CASSANDRA-18961) * Include column drop timestamp in alter table transformation (CASSANDRA-18961)

View File

@ -89,6 +89,9 @@ New features
when 'GENERATED PASSWORD' clause is used. Character sets supported are: English, Cyrillic, modern Cyrillic, when 'GENERATED PASSWORD' clause is used. Character sets supported are: English, Cyrillic, modern Cyrillic,
German, Polish and Czech. German, Polish and Czech.
- JMX SSL configuration can be now done in cassandra.yaml via jmx_encryption_options section instead of cassandra-env.sh - JMX SSL configuration can be now done in cassandra.yaml via jmx_encryption_options section instead of cassandra-env.sh
- There is new MBean of name org.apache.cassandra.service.snapshot:type=SnapshotManager which exposes user-facing
snapshot operations. Snapshot-related methods on StorageServiceMBean are still present and functional
but marked as deprecated.
Upgrading Upgrading

View File

@ -18,11 +18,9 @@
package org.apache.cassandra.db; package org.apache.cassandra.db;
import java.io.IOException; import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
@ -32,7 +30,6 @@ import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
@ -68,7 +65,6 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import com.google.common.util.concurrent.RateLimiter;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -79,7 +75,6 @@ import org.apache.cassandra.cache.RowCacheSentinel;
import org.apache.cassandra.concurrent.ExecutorPlus; import org.apache.cassandra.concurrent.ExecutorPlus;
import org.apache.cassandra.concurrent.FutureTask; import org.apache.cassandra.concurrent.FutureTask;
import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.config.DurationSpec;
import org.apache.cassandra.db.commitlog.CommitLog; import org.apache.cassandra.db.commitlog.CommitLog;
import org.apache.cassandra.db.commitlog.CommitLogPosition; import org.apache.cassandra.db.commitlog.CommitLogPosition;
import org.apache.cassandra.db.commitlog.IntervalSet; import org.apache.cassandra.db.commitlog.IntervalSet;
@ -116,8 +111,6 @@ import org.apache.cassandra.exceptions.StartupException;
import org.apache.cassandra.index.SecondaryIndexManager; import org.apache.cassandra.index.SecondaryIndexManager;
import org.apache.cassandra.index.internal.CassandraIndex; import org.apache.cassandra.index.internal.CassandraIndex;
import org.apache.cassandra.index.transactions.UpdateTransaction; import org.apache.cassandra.index.transactions.UpdateTransaction;
import org.apache.cassandra.io.FSReadError;
import org.apache.cassandra.io.FSWriteError;
import org.apache.cassandra.io.sstable.Component; import org.apache.cassandra.io.sstable.Component;
import org.apache.cassandra.io.sstable.Descriptor; import org.apache.cassandra.io.sstable.Descriptor;
import org.apache.cassandra.io.sstable.IScrubber; import org.apache.cassandra.io.sstable.IScrubber;
@ -131,7 +124,6 @@ import org.apache.cassandra.io.sstable.format.SSTableFormat.Components;
import org.apache.cassandra.io.sstable.format.SSTableReader; import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.io.sstable.format.Version; import org.apache.cassandra.io.sstable.format.Version;
import org.apache.cassandra.io.util.File; import org.apache.cassandra.io.util.File;
import org.apache.cassandra.io.util.FileOutputStreamPlus;
import org.apache.cassandra.metrics.Sampler; import org.apache.cassandra.metrics.Sampler;
import org.apache.cassandra.metrics.Sampler.Sample; import org.apache.cassandra.metrics.Sampler.Sample;
import org.apache.cassandra.metrics.Sampler.SamplerType; import org.apache.cassandra.metrics.Sampler.SamplerType;
@ -157,9 +149,7 @@ import org.apache.cassandra.service.StorageService;
import org.apache.cassandra.service.paxos.Ballot; import org.apache.cassandra.service.paxos.Ballot;
import org.apache.cassandra.service.paxos.PaxosRepairHistory; import org.apache.cassandra.service.paxos.PaxosRepairHistory;
import org.apache.cassandra.service.paxos.TablePaxosRepairHistory; import org.apache.cassandra.service.paxos.TablePaxosRepairHistory;
import org.apache.cassandra.service.snapshot.SnapshotLoader; import org.apache.cassandra.service.snapshot.SnapshotManager;
import org.apache.cassandra.service.snapshot.SnapshotManifest;
import org.apache.cassandra.service.snapshot.TableSnapshot;
import org.apache.cassandra.streaming.TableStreamManager; import org.apache.cassandra.streaming.TableStreamManager;
import org.apache.cassandra.tcm.ClusterMetadata; import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.tcm.Epoch; import org.apache.cassandra.tcm.Epoch;
@ -185,7 +175,6 @@ import static org.apache.cassandra.config.DatabaseDescriptor.getFlushWriters;
import static org.apache.cassandra.db.commitlog.CommitLogPosition.NONE; import static org.apache.cassandra.db.commitlog.CommitLogPosition.NONE;
import static org.apache.cassandra.utils.Clock.Global.currentTimeMillis; import static org.apache.cassandra.utils.Clock.Global.currentTimeMillis;
import static org.apache.cassandra.utils.Clock.Global.nanoTime; import static org.apache.cassandra.utils.Clock.Global.nanoTime;
import static org.apache.cassandra.utils.FBUtilities.now;
import static org.apache.cassandra.utils.Throwables.maybeFail; import static org.apache.cassandra.utils.Throwables.maybeFail;
import static org.apache.cassandra.utils.Throwables.merge; import static org.apache.cassandra.utils.Throwables.merge;
import static org.apache.cassandra.utils.concurrent.CountDownLatch.newCountDownLatch; import static org.apache.cassandra.utils.concurrent.CountDownLatch.newCountDownLatch;
@ -260,8 +249,6 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner
private static final String SAMPLING_RESULTS_NAME = "SAMPLING_RESULTS"; private static final String SAMPLING_RESULTS_NAME = "SAMPLING_RESULTS";
public static final String SNAPSHOT_TRUNCATE_PREFIX = "truncated";
public static final String SNAPSHOT_DROP_PREFIX = "dropped";
static final String TOKEN_DELIMITER = ":"; static final String TOKEN_DELIMITER = ":";
/** Special values used when the local ranges are not changed with ring changes (e.g. local tables). */ /** Special values used when the local ranges are not changed with ring changes (e.g. local tables). */
@ -517,6 +504,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner
// Note that this needs to happen before we load the first sstables, or the global sstable tracker will not // Note that this needs to happen before we load the first sstables, or the global sstable tracker will not
// be notified on the initial loading. // be notified on the initial loading.
data.subscribe(StorageService.instance.sstablesTracker); data.subscribe(StorageService.instance.sstablesTracker);
data.subscribe(SnapshotManager.instance);
Collection<SSTableReader> sstables = null; Collection<SSTableReader> sstables = null;
// scan for sstables corresponding to this cf and load them // scan for sstables corresponding to this cf and load them
@ -785,14 +773,11 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner
* Removes unnecessary files from the cf directory at startup: these include temp files, orphans, zero-length files * Removes unnecessary files from the cf directory at startup: these include temp files, orphans, zero-length files
* and compacted sstables. Files that cannot be recognized will be ignored. * and compacted sstables. Files that cannot be recognized will be ignored.
*/ */
public static void scrubDataDirectories(TableMetadata metadata) throws StartupException public static void scrubDataDirectories(TableMetadata metadata) throws StartupException
{ {
Directories directories = new Directories(metadata); Directories directories = new Directories(metadata);
Set<File> cleanedDirectories = new HashSet<>(); Set<File> cleanedDirectories = new HashSet<>();
// clear ephemeral snapshots that were not properly cleared last session (CASSANDRA-7357)
clearEphemeralSnapshots(directories);
directories.removeTemporaryDirectories(); directories.removeTemporaryDirectories();
logger.trace("Removing temporary or obsoleted files from unfinished operations for table {}", metadata.name); logger.trace("Removing temporary or obsoleted files from unfinished operations for table {}", metadata.name);
@ -977,6 +962,11 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner
return keyspace.getName(); return keyspace.getName();
} }
public String getKeyspaceTableName()
{
return getKeyspaceName() + '.' + getTableName();
}
public Descriptor newSSTableDescriptor(File directory) public Descriptor newSSTableDescriptor(File directory)
{ {
return newSSTableDescriptor(directory, DatabaseDescriptor.getSelectedSSTableFormat().getLatestVersion()); return newSSTableDescriptor(directory, DatabaseDescriptor.getSelectedSSTableFormat().getLatestVersion());
@ -1757,12 +1747,8 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner
public CompactionManager.AllSSTableOpStatus scrub(boolean disableSnapshot, boolean alwaysFail, IScrubber.Options options, int jobs) throws ExecutionException, InterruptedException public CompactionManager.AllSSTableOpStatus scrub(boolean disableSnapshot, boolean alwaysFail, IScrubber.Options options, int jobs) throws ExecutionException, InterruptedException
{ {
// skip snapshot creation during scrub, SEE JIRA 5891 // skip snapshot creation during scrub, SEE JIRA 5891
if(!disableSnapshot) if (!disableSnapshot)
{ data.notifyPreScrubbed();
Instant creationTime = now();
String snapshotName = "pre-scrub-" + creationTime.toEpochMilli();
snapshotWithoutMemtable(snapshotName, creationTime);
}
try try
{ {
@ -2127,262 +2113,6 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner
return metadata().comparator; return metadata().comparator;
} }
public TableSnapshot snapshotWithoutMemtable(String snapshotName)
{
return snapshotWithoutMemtable(snapshotName, now());
}
public TableSnapshot snapshotWithoutMemtable(String snapshotName, Instant creationTime)
{
return snapshotWithoutMemtable(snapshotName, null, false, null, null, creationTime);
}
/**
* @param ephemeral If this flag is set to true, the snapshot will be cleaned during next startup
*/
public TableSnapshot snapshotWithoutMemtable(String snapshotName, Predicate<SSTableReader> predicate, boolean ephemeral, DurationSpec.IntSecondsBound ttl, RateLimiter rateLimiter, Instant creationTime)
{
if (ephemeral && ttl != null)
{
throw new IllegalStateException(String.format("can not take ephemeral snapshot (%s) while ttl is specified too", snapshotName));
}
if (rateLimiter == null)
rateLimiter = DatabaseDescriptor.getSnapshotRateLimiter();
Set<SSTableReader> snapshottedSSTables = new LinkedHashSet<>();
for (ColumnFamilyStore cfs : concatWithIndexes())
{
try (RefViewFragment currentView = cfs.selectAndReference(View.select(SSTableSet.CANONICAL, (x) -> predicate == null || predicate.apply(x))))
{
for (SSTableReader ssTable : currentView.sstables)
{
File snapshotDirectory = Directories.getSnapshotDirectory(ssTable.descriptor, snapshotName);
ssTable.createLinks(snapshotDirectory.path(), rateLimiter); // hard links
if (logger.isTraceEnabled())
logger.trace("Snapshot for {} keyspace data file {} created in {}", keyspace, ssTable.getFilename(), snapshotDirectory);
snapshottedSSTables.add(ssTable);
}
}
}
return createSnapshot(snapshotName, ephemeral, ttl, snapshottedSSTables, creationTime);
}
protected TableSnapshot createSnapshot(String tag, boolean ephemeral, DurationSpec.IntSecondsBound ttl, Set<SSTableReader> sstables, Instant creationTime) {
Set<File> snapshotDirs = sstables.stream()
.map(s -> Directories.getSnapshotDirectory(s.descriptor, tag).toAbsolute())
.filter(dir -> !Directories.isSecondaryIndexFolder(dir)) // Remove secondary index subdirectory
.collect(Collectors.toCollection(HashSet::new));
// Create and write snapshot manifest
SnapshotManifest manifest = new SnapshotManifest(mapToDataFilenames(sstables), ttl, creationTime, ephemeral);
File manifestFile = getDirectories().getSnapshotManifestFile(tag);
writeSnapshotManifest(manifest, manifestFile);
snapshotDirs.add(manifestFile.parent().toAbsolute()); // manifest may create empty snapshot dir
// Write snapshot schema
if (!SchemaConstants.isLocalSystemKeyspace(metadata.keyspace) && !SchemaConstants.isReplicatedSystemKeyspace(metadata.keyspace))
{
File schemaFile = getDirectories().getSnapshotSchemaFile(tag);
writeSnapshotSchema(schemaFile);
snapshotDirs.add(schemaFile.parent().toAbsolute()); // schema may create empty snapshot dir
}
TableSnapshot snapshot = new TableSnapshot(metadata.keyspace, metadata.name, metadata.id.asUUID(),
tag, manifest.createdAt, manifest.expiresAt, snapshotDirs,
manifest.ephemeral);
StorageService.instance.addSnapshot(snapshot);
return snapshot;
}
private SnapshotManifest writeSnapshotManifest(SnapshotManifest manifest, File manifestFile)
{
try
{
manifestFile.parent().tryCreateDirectories();
manifest.serializeToJsonFile(manifestFile);
return manifest;
}
catch (IOException e)
{
throw new FSWriteError(e, manifestFile);
}
}
private List<String> mapToDataFilenames(Collection<SSTableReader> sstables)
{
return sstables.stream().map(s -> s.descriptor.relativeFilenameFor(Components.DATA)).collect(Collectors.toList());
}
private void writeSnapshotSchema(File schemaFile)
{
try
{
if (!schemaFile.parent().exists())
schemaFile.parent().tryCreateDirectories();
try (PrintStream out = new PrintStream(new FileOutputStreamPlus(schemaFile)))
{
SchemaCQLHelper.reCreateStatementsForSchemaCql(metadata(),
keyspace.getMetadata())
.forEach(out::println);
}
}
catch (IOException e)
{
throw new FSWriteError(e, schemaFile);
}
}
protected static void clearEphemeralSnapshots(Directories directories)
{
RateLimiter clearSnapshotRateLimiter = DatabaseDescriptor.getSnapshotRateLimiter();
List<TableSnapshot> ephemeralSnapshots = new SnapshotLoader(directories).loadSnapshots()
.stream()
.filter(TableSnapshot::isEphemeral)
.collect(Collectors.toList());
for (TableSnapshot ephemeralSnapshot : ephemeralSnapshots)
{
logger.trace("Clearing ephemeral snapshot {} leftover from previous session.", ephemeralSnapshot.getId());
Directories.clearSnapshot(ephemeralSnapshot.getTag(), directories.getCFDirectories(), clearSnapshotRateLimiter);
}
}
public Refs<SSTableReader> getSnapshotSSTableReaders(String tag) throws IOException
{
Map<SSTableId, SSTableReader> active = new HashMap<>();
for (SSTableReader sstable : getSSTables(SSTableSet.CANONICAL))
active.put(sstable.descriptor.id, sstable);
Map<Descriptor, Set<Component>> snapshots = getDirectories().sstableLister(Directories.OnTxnErr.IGNORE).snapshots(tag).list();
Refs<SSTableReader> refs = new Refs<>();
try
{
for (Map.Entry<Descriptor, Set<Component>> entries : snapshots.entrySet())
{
// Try acquire reference to an active sstable instead of snapshot if it exists,
// to avoid opening new sstables. If it fails, use the snapshot reference instead.
SSTableReader sstable = active.get(entries.getKey().id);
if (sstable == null || !refs.tryRef(sstable))
{
if (logger.isTraceEnabled())
logger.trace("using snapshot sstable {}", entries.getKey());
// open offline so we don't modify components or track hotness.
sstable = SSTableReader.open(this, entries.getKey(), entries.getValue(), metadata, true, true);
refs.tryRef(sstable);
// release the self ref as we never add the snapshot sstable to DataTracker where it is otherwise released
sstable.selfRef().release();
}
else if (logger.isTraceEnabled())
{
logger.trace("using active sstable {}", entries.getKey());
}
}
}
catch (FSReadError | RuntimeException e)
{
// In case one of the snapshot sstables fails to open,
// we must release the references to the ones we opened so far
refs.release();
throw e;
}
return refs;
}
/**
* Take a snap shot of this columnfamily store.
*
* @param snapshotName the name of the associated with the snapshot
*/
public TableSnapshot snapshot(String snapshotName)
{
return snapshot(snapshotName, null);
}
public TableSnapshot snapshot(String snapshotName, DurationSpec.IntSecondsBound ttl)
{
return snapshot(snapshotName, false, ttl, null, now());
}
/**
* Take a snap shot of this columnfamily store.
*
* @param snapshotName the name of the associated with the snapshot
* @param skipMemtable Skip flushing the memtable
* @param ttl duration after which the taken snapshot is removed automatically, if supplied with null, it will never be automatically removed
* @param rateLimiter Rate limiter for hardlinks-per-second
* @param creationTime time when this snapshot was taken
*/
public TableSnapshot snapshot(String snapshotName, boolean skipMemtable, DurationSpec.IntSecondsBound ttl, RateLimiter rateLimiter, Instant creationTime)
{
return snapshot(snapshotName, null, false, skipMemtable, ttl, rateLimiter, creationTime);
}
/**
* @param ephemeral If this flag is set to true, the snapshot will be cleaned up during next startup
* @param skipMemtable Skip flushing the memtable
*/
public TableSnapshot snapshot(String snapshotName, Predicate<SSTableReader> predicate, boolean ephemeral, boolean skipMemtable)
{
return snapshot(snapshotName, predicate, ephemeral, skipMemtable, null, null, now());
}
/**
* @param ephemeral If this flag is set to true, the snapshot will be cleaned up during next startup
* @param skipMemtable Skip flushing the memtable
* @param ttl duration after which the taken snapshot is removed automatically, if supplied with null, it will never be automatically removed
* @param rateLimiter Rate limiter for hardlinks-per-second
* @param creationTime time when this snapshot was taken
*/
public TableSnapshot snapshot(String snapshotName, Predicate<SSTableReader> predicate, boolean ephemeral, boolean skipMemtable, DurationSpec.IntSecondsBound ttl, RateLimiter rateLimiter, Instant creationTime)
{
if (!skipMemtable)
{
Memtable current = getTracker().getView().getCurrentMemtable();
if (!current.isClean())
{
if (current.shouldSwitch(FlushReason.SNAPSHOT))
FBUtilities.waitOnFuture(switchMemtableIfCurrent(current, FlushReason.SNAPSHOT));
else
current.performSnapshot(snapshotName);
}
}
return snapshotWithoutMemtable(snapshotName, predicate, ephemeral, ttl, rateLimiter, creationTime);
}
public boolean snapshotExists(String snapshotName)
{
return getDirectories().snapshotExists(snapshotName);
}
/**
* Clear all the snapshots for a given column family.
*
* @param snapshotName the user supplied snapshot name. If left empty,
* all the snapshots will be cleaned.
*/
public void clearSnapshot(String snapshotName)
{
RateLimiter clearSnapshotRateLimiter = DatabaseDescriptor.getSnapshotRateLimiter();
List<File> snapshotDirs = getDirectories().getCFDirectories();
Directories.clearSnapshot(snapshotName, snapshotDirs, clearSnapshotRateLimiter);
}
/**
*
* @return Return a map of all snapshots to space being used
* The pair for a snapshot has true size and size on disk.
*/
public Map<String, TableSnapshot> listSnapshots()
{
return getDirectories().listSnapshots();
}
/** /**
* @return the cached partition for @param key if it is already present in the cache. * @return the cached partition for @param key if it is already present in the cache.
* Not that this will not readAndCache the parition if it is not present, nor * Not that this will not readAndCache the parition if it is not present, nor
@ -2771,15 +2501,12 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner
// stream in data that is actually supposed to have been deleted // stream in data that is actually supposed to have been deleted
ActiveRepairService.instance().abort((prs) -> prs.getTableIds().contains(metadata.id), ActiveRepairService.instance().abort((prs) -> prs.getTableIds().contains(metadata.id),
"Stopping parent sessions {} due to truncation of tableId="+metadata.id); "Stopping parent sessions {} due to truncation of tableId="+metadata.id);
data.notifyTruncated(truncatedAt); data.notifyTruncated(noSnapshot, truncatedAt, DatabaseDescriptor.getAutoSnapshotTtl());
if (!noSnapshot && isAutoSnapshotEnabled()) discardSSTables(truncatedAt);
snapshot(Keyspace.getTimestampedSnapshotNameWithPrefix(name, SNAPSHOT_TRUNCATE_PREFIX), DatabaseDescriptor.getAutoSnapshotTtl());
discardSSTables(truncatedAt); indexManager.truncateAllIndexesBlocking(truncatedAt);
viewManager.truncateBlocking(replayAfter, truncatedAt);
indexManager.truncateAllIndexesBlocking(truncatedAt);
viewManager.truncateBlocking(replayAfter, truncatedAt);
SystemKeyspace.saveTruncationRecord(ColumnFamilyStore.this, truncatedAt, replayAfter); SystemKeyspace.saveTruncationRecord(ColumnFamilyStore.this, truncatedAt, replayAfter);
logger.trace("cleaning out row cache"); logger.trace("cleaning out row cache");
@ -3303,10 +3030,20 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner
return allColumns > 0 ? allDroppable / allColumns : 0; return allColumns > 0 ? allDroppable / allColumns : 0;
} }
public Set<String> getFilesOfCfs()
{
Set<String> files = new HashSet<>();
for (ColumnFamilyStore cfs : concatWithIndexes())
cfs.getTracker().getView().liveSSTables().forEach(s -> files.addAll(s.getAllFilePaths()));
return files;
}
@Override @Override
public long trueSnapshotsSize() public long trueSnapshotsSize()
{ {
return getDirectories().trueSnapshotsSize(); return SnapshotManager.instance.getTrueSnapshotsSize(getKeyspaceName(), getTableName());
} }
/** /**
@ -3426,8 +3163,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner
CompactionManager.instance.interruptCompactionForCFs(concatWithIndexes(), (sstable) -> true, true); CompactionManager.instance.interruptCompactionForCFs(concatWithIndexes(), (sstable) -> true, true);
if (isAutoSnapshotEnabled()) data.notifyDropped(DatabaseDescriptor.getAutoSnapshotTtl());
snapshot(Keyspace.getTimestampedSnapshotNameWithPrefix(name, ColumnFamilyStore.SNAPSHOT_DROP_PREFIX), DatabaseDescriptor.getAutoSnapshotTtl());
CommitLog.instance.forceRecycleAllSegments(Collections.singleton(metadata.id)); CommitLog.instance.forceRecycleAllSegments(Collections.singleton(metadata.id));

View File

@ -22,7 +22,6 @@ import java.io.IOException;
import java.nio.file.FileStore; import java.nio.file.FileStore;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
@ -49,8 +48,6 @@ import java.util.stream.StreamSupport;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.util.concurrent.RateLimiter;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -74,8 +71,6 @@ import org.apache.cassandra.io.util.PathUtils;
import org.apache.cassandra.schema.SchemaConstants; import org.apache.cassandra.schema.SchemaConstants;
import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.snapshot.SnapshotManifest; import org.apache.cassandra.service.snapshot.SnapshotManifest;
import org.apache.cassandra.service.snapshot.TableSnapshot;
import org.apache.cassandra.utils.DirectorySizeCalculator;
import org.apache.cassandra.utils.JVMStabilityInspector; import org.apache.cassandra.utils.JVMStabilityInspector;
import org.apache.cassandra.utils.Pair; import org.apache.cassandra.utils.Pair;
@ -306,6 +301,11 @@ public class Directories
canonicalPathToDD = canonicalPathsBuilder.build(); canonicalPathToDD = canonicalPathsBuilder.build();
} }
public File[] getDataPaths()
{
return dataPaths;
}
/** /**
* Returns SSTable location which is inside given data directory. * Returns SSTable location which is inside given data directory.
* *
@ -673,6 +673,16 @@ public class Directories
} }
} }
public Set<File> getSnapshotDirs(String tag)
{
Set<File> snapshotDirs = new HashSet<>();
for (File cfDir : getCFDirectories())
snapshotDirs.add(Directories.getSnapshotDirectory(cfDir, tag).toAbsolute());
return snapshotDirs;
}
public File getSnapshotManifestFile(String snapshotName) public File getSnapshotManifestFile(String snapshotName)
{ {
File snapshotDir = getSnapshotDirectory(getDirectoryForNewSSTables(), snapshotName); File snapshotDir = getSnapshotDirectory(getDirectoryForNewSSTables(), snapshotName);
@ -1171,37 +1181,6 @@ public class Directories
} }
} }
public Map<String, TableSnapshot> listSnapshots()
{
Map<String, Set<File>> snapshotDirsByTag = listSnapshotDirsByTag();
Map<String, TableSnapshot> snapshots = Maps.newHashMapWithExpectedSize(snapshotDirsByTag.size());
for (Map.Entry<String, Set<File>> entry : snapshotDirsByTag.entrySet())
{
String tag = entry.getKey();
Set<File> snapshotDirs = entry.getValue();
SnapshotManifest manifest = maybeLoadManifest(metadata.keyspace, metadata.name, tag, snapshotDirs);
snapshots.put(tag, buildSnapshot(tag, manifest, snapshotDirs));
}
return snapshots;
}
private TableSnapshot buildSnapshot(String tag, SnapshotManifest manifest, Set<File> snapshotDirs)
{
boolean ephemeral = manifest != null ? manifest.isEphemeral() : isLegacyEphemeralSnapshot(snapshotDirs);
Instant createdAt = manifest == null ? null : manifest.createdAt;
Instant expiresAt = manifest == null ? null : manifest.expiresAt;
return new TableSnapshot(metadata.keyspace, metadata.name, metadata.id.asUUID(), tag, createdAt, expiresAt,
snapshotDirs, ephemeral);
}
private static boolean isLegacyEphemeralSnapshot(Set<File> snapshotDirs)
{
return snapshotDirs.stream().map(d -> new File(d, "ephemeral.snapshot")).anyMatch(File::exists);
}
@VisibleForTesting @VisibleForTesting
protected static SnapshotManifest maybeLoadManifest(String keyspace, String table, String tag, Set<File> snapshotDirs) protected static SnapshotManifest maybeLoadManifest(String keyspace, String table, String tag, Set<File> snapshotDirs)
{ {
@ -1230,100 +1209,10 @@ public class Directories
return null; return null;
} }
@VisibleForTesting
protected Map<String, Set<File>> listSnapshotDirsByTag()
{
Map<String, Set<File>> snapshotDirsByTag = new HashMap<>();
for (final File dir : dataPaths)
{
File snapshotDir = isSecondaryIndexFolder(dir)
? new File(dir.parentPath(), SNAPSHOT_SUBDIR)
: new File(dir, SNAPSHOT_SUBDIR);
if (snapshotDir.exists() && snapshotDir.isDirectory())
{
final File[] snapshotDirs = snapshotDir.tryList();
if (snapshotDirs != null)
{
for (final File snapshot : snapshotDirs)
{
if (snapshot.isDirectory()) {
snapshotDirsByTag.computeIfAbsent(snapshot.name(), k -> new LinkedHashSet<>()).add(snapshot.toAbsolute());
}
}
}
}
}
return snapshotDirsByTag;
}
public boolean snapshotExists(String snapshotName)
{
for (File dir : dataPaths)
{
File snapshotDir;
if (isSecondaryIndexFolder(dir))
{
snapshotDir = new File(dir.parent(), join(SNAPSHOT_SUBDIR, snapshotName, dir.name()));
}
else
{
snapshotDir = new File(dir, join(SNAPSHOT_SUBDIR, snapshotName));
}
if (snapshotDir.exists())
return true;
}
return false;
}
public static void clearSnapshot(String snapshotName, List<File> tableDirectories, RateLimiter snapshotRateLimiter)
{
// If snapshotName is empty or null, we will delete the entire snapshot directory
String tag = snapshotName == null ? "" : snapshotName;
for (File tableDir : tableDirectories)
{
File snapshotDir = new File(tableDir, join(SNAPSHOT_SUBDIR, tag));
removeSnapshotDirectory(snapshotRateLimiter, snapshotDir);
}
}
public static void removeSnapshotDirectory(RateLimiter snapshotRateLimiter, File snapshotDir)
{
if (snapshotDir.exists())
{
logger.trace("Removing snapshot directory {}", snapshotDir);
try
{
FileUtils.deleteRecursiveWithThrottle(snapshotDir, snapshotRateLimiter);
}
catch (RuntimeException ex)
{
if (!snapshotDir.exists())
return; // ignore
throw ex;
}
}
}
/**
* @return total snapshot size in byte for all snapshots.
*/
public long trueSnapshotsSize()
{
long result = 0L;
for (File dir : dataPaths)
{
File snapshotDir = isSecondaryIndexFolder(dir)
? new File(dir.parentPath(), SNAPSHOT_SUBDIR)
: new File(dir, SNAPSHOT_SUBDIR);
result += getTrueAllocatedSizeIn(snapshotDir);
}
return result;
}
/** /**
* @return Raw size on disk for all directories * @return Raw size on disk for all directories
*/ */
public long getRawDiretoriesSize() public long getRawDirectoriesSize()
{ {
long totalAllocatedSize = 0L; long totalAllocatedSize = 0L;
@ -1333,25 +1222,7 @@ public class Directories
return totalAllocatedSize; return totalAllocatedSize;
} }
public long getTrueAllocatedSizeIn(File snapshotDir) // Recursively finds all the subdirectories in the KS directory.
{
if (!snapshotDir.isDirectory())
return 0;
SSTableSizeSummer visitor = new SSTableSizeSummer(sstableLister(OnTxnErr.THROW).listFiles());
try
{
Files.walkFileTree(snapshotDir.toPath(), visitor);
}
catch (IOException e)
{
logger.error("Could not calculate the size of {}. {}", snapshotDir, e.getMessage());
}
return visitor.getAllocatedSize();
}
// Recursively finds all the sub directories in the KS directory.
public static List<File> getKSChildDirectories(String ksName) public static List<File> getKSChildDirectories(String ksName)
{ {
List<File> result = new ArrayList<>(); List<File> result = new ArrayList<>();
@ -1434,25 +1305,4 @@ public class Directories
{ {
return StringUtils.join(s, File.pathSeparator()); return StringUtils.join(s, File.pathSeparator());
} }
private class SSTableSizeSummer extends DirectorySizeCalculator
{
private final Set<String> toSkip;
SSTableSizeSummer(List<File> files)
{
toSkip = files.stream().map(File::name).collect(Collectors.toSet());
}
@Override
public boolean isAcceptable(Path path)
{
File file = new File(path);
Descriptor desc = SSTable.tryDescriptorFromFile(file);
return desc != null
&& desc.ksname.equals(metadata.keyspace)
&& desc.cfname.equals(metadata.name)
&& !toSkip.contains(file.name());
}
}
} }

View File

@ -17,8 +17,6 @@
*/ */
package org.apache.cassandra.db; package org.apache.cassandra.db;
import java.io.IOException;
import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
@ -35,18 +33,17 @@ import java.util.stream.Stream;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.util.concurrent.RateLimiter;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.apache.cassandra.concurrent.Stage; import org.apache.cassandra.concurrent.Stage;
import org.apache.cassandra.config.CassandraRelevantProperties; import org.apache.cassandra.config.CassandraRelevantProperties;
import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.config.DurationSpec;
import org.apache.cassandra.db.lifecycle.SSTableSet; import org.apache.cassandra.db.lifecycle.SSTableSet;
import org.apache.cassandra.db.partitions.PartitionUpdate; import org.apache.cassandra.db.partitions.PartitionUpdate;
import org.apache.cassandra.db.repair.CassandraKeyspaceRepairManager; import org.apache.cassandra.db.repair.CassandraKeyspaceRepairManager;
import org.apache.cassandra.db.view.ViewManager; import org.apache.cassandra.db.view.ViewManager;
import org.apache.cassandra.db.virtual.VirtualKeyspaceRegistry;
import org.apache.cassandra.exceptions.WriteTimeoutException; import org.apache.cassandra.exceptions.WriteTimeoutException;
import org.apache.cassandra.index.Index; import org.apache.cassandra.index.Index;
import org.apache.cassandra.index.SecondaryIndexManager; import org.apache.cassandra.index.SecondaryIndexManager;
@ -61,7 +58,6 @@ import org.apache.cassandra.schema.SchemaConstants;
import org.apache.cassandra.schema.SchemaProvider; import org.apache.cassandra.schema.SchemaProvider;
import org.apache.cassandra.schema.TableId; import org.apache.cassandra.schema.TableId;
import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.snapshot.TableSnapshot;
import org.apache.cassandra.tracing.Tracing; import org.apache.cassandra.tracing.Tracing;
import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.JVMStabilityInspector; import org.apache.cassandra.utils.JVMStabilityInspector;
@ -74,7 +70,6 @@ import org.apache.cassandra.utils.concurrent.Promise;
import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.NANOSECONDS; import static java.util.concurrent.TimeUnit.NANOSECONDS;
import static org.apache.cassandra.utils.Clock.Global.currentTimeMillis; import static org.apache.cassandra.utils.Clock.Global.currentTimeMillis;
import static org.apache.cassandra.utils.FBUtilities.now;
import static org.apache.cassandra.utils.MonotonicClock.Global.approxTime; import static org.apache.cassandra.utils.MonotonicClock.Global.approxTime;
/** /**
@ -226,80 +221,19 @@ public class Keyspace
return columnFamilyStores.containsKey(id); return columnFamilyStores.containsKey(id);
} }
/** public static void verifyKeyspaceIsValid(String keyspaceName)
* Take a snapshot of the specific column family, or the entire set of column families
* if columnFamily is null with a given timestamp
*
* @param snapshotName the tag associated with the name of the snapshot. This value may not be null
* @param columnFamilyName the column family to snapshot or all on null
* @param skipFlush Skip blocking flush of memtable
* @param rateLimiter Rate limiter for hardlinks-per-second
* @throws IOException if the column family doesn't exist
*/
public void snapshot(String snapshotName, String columnFamilyName, boolean skipFlush, DurationSpec.IntSecondsBound ttl, RateLimiter rateLimiter, Instant creationTime) throws IOException
{ {
assert snapshotName != null; if (null != VirtualKeyspaceRegistry.instance.getKeyspaceNullable(keyspaceName))
boolean tookSnapShot = false; throw new IllegalArgumentException("Cannot perform any operations against virtual keyspace " + keyspaceName);
for (ColumnFamilyStore cfStore : columnFamilyStores.values())
{
if (columnFamilyName == null || cfStore.name.equals(columnFamilyName))
{
tookSnapShot = true;
cfStore.snapshot(snapshotName, skipFlush, ttl, rateLimiter, creationTime);
}
}
if ((columnFamilyName != null) && !tookSnapShot) if (!Schema.instance.getKeyspaces().contains(keyspaceName))
throw new IOException("Failed taking snapshot. Table " + columnFamilyName + " does not exist."); throw new IllegalArgumentException("Keyspace " + keyspaceName + " does not exist");
} }
/** public static Keyspace getValidKeyspace(String keyspaceName)
* Take a snapshot of the specific column family, or the entire set of column families
* if columnFamily is null with a given timestamp
*
* @param snapshotName the tag associated with the name of the snapshot. This value may not be null
* @param columnFamilyName the column family to snapshot or all on null
* @throws IOException if the column family doesn't exist
*/
public void snapshot(String snapshotName, String columnFamilyName) throws IOException
{ {
snapshot(snapshotName, columnFamilyName, false, null, null, now()); verifyKeyspaceIsValid(keyspaceName);
} return Keyspace.open(keyspaceName);
/**
* @param clientSuppliedName may be null.
* @return the name of the snapshot
*/
public static String getTimestampedSnapshotName(String clientSuppliedName)
{
String snapshotName = Long.toString(currentTimeMillis());
if (clientSuppliedName != null && !clientSuppliedName.equals(""))
{
snapshotName = snapshotName + "-" + clientSuppliedName;
}
return snapshotName;
}
public static String getTimestampedSnapshotNameWithPrefix(String clientSuppliedName, String prefix)
{
return prefix + "-" + getTimestampedSnapshotName(clientSuppliedName);
}
/**
* Check whether snapshots already exists for a given name.
*
* @param snapshotName the user supplied snapshot name
* @return true if the snapshot exists
*/
public boolean snapshotExists(String snapshotName)
{
assert snapshotName != null;
for (ColumnFamilyStore cfStore : columnFamilyStores.values())
{
if (cfStore.snapshotExists(snapshotName))
return true;
}
return false;
} }
/** /**
@ -313,11 +247,6 @@ public class Keyspace
return list; return list;
} }
public Stream<TableSnapshot> getAllSnapshots()
{
return getColumnFamilyStores().stream().flatMap(cfs -> cfs.listSnapshots().values().stream());
}
public static Keyspace forSchema(String keyspaceName, SchemaProvider schema) public static Keyspace forSchema(String keyspaceName, SchemaProvider schema)
{ {
return new Keyspace(keyspaceName, schema, true); return new Keyspace(keyspaceName, schema, true);

View File

@ -109,6 +109,9 @@ import org.apache.cassandra.service.paxos.PaxosRepairHistory;
import org.apache.cassandra.service.paxos.PaxosState; import org.apache.cassandra.service.paxos.PaxosState;
import org.apache.cassandra.service.paxos.uncommitted.PaxosRows; import org.apache.cassandra.service.paxos.uncommitted.PaxosRows;
import org.apache.cassandra.service.paxos.uncommitted.PaxosUncommittedIndex; import org.apache.cassandra.service.paxos.uncommitted.PaxosUncommittedIndex;
import org.apache.cassandra.service.snapshot.SnapshotOptions;
import org.apache.cassandra.service.snapshot.SnapshotManager;
import org.apache.cassandra.service.snapshot.SnapshotType;
import org.apache.cassandra.streaming.StreamOperation; import org.apache.cassandra.streaming.StreamOperation;
import org.apache.cassandra.tcm.ClusterMetadata; import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.tcm.Epoch; import org.apache.cassandra.tcm.Epoch;
@ -141,10 +144,10 @@ import static org.apache.cassandra.gms.ApplicationState.RELEASE_VERSION;
import static org.apache.cassandra.gms.ApplicationState.STATUS_WITH_PORT; import static org.apache.cassandra.gms.ApplicationState.STATUS_WITH_PORT;
import static org.apache.cassandra.gms.ApplicationState.TOKENS; import static org.apache.cassandra.gms.ApplicationState.TOKENS;
import static org.apache.cassandra.service.paxos.Commit.latest; import static org.apache.cassandra.service.paxos.Commit.latest;
import static org.apache.cassandra.service.snapshot.SnapshotOptions.systemSnapshot;
import static org.apache.cassandra.utils.CassandraVersion.NULL_VERSION; import static org.apache.cassandra.utils.CassandraVersion.NULL_VERSION;
import static org.apache.cassandra.utils.CassandraVersion.UNREADABLE_VERSION; import static org.apache.cassandra.utils.CassandraVersion.UNREADABLE_VERSION;
import static org.apache.cassandra.utils.Clock.Global.currentTimeMillis; import static org.apache.cassandra.utils.Clock.Global.currentTimeMillis;
import static org.apache.cassandra.utils.FBUtilities.now;
public final class SystemKeyspace public final class SystemKeyspace
{ {
@ -1808,10 +1811,8 @@ public final class SystemKeyspace
* Compare the release version in the system.local table with the one included in the distro. * Compare the release version in the system.local table with the one included in the distro.
* If they don't match, snapshot all tables in the system and schema keyspaces. This is intended * If they don't match, snapshot all tables in the system and schema keyspaces. This is intended
* to be called at startup to create a backup of the system tables during an upgrade * to be called at startup to create a backup of the system tables during an upgrade
*
* @throws IOException
*/ */
public static void snapshotOnVersionChange() throws IOException public static void snapshotOnVersionChange()
{ {
String previous = getPreviousVersionString(); String previous = getPreviousVersionString();
String next = FBUtilities.getReleaseVersionString(); String next = FBUtilities.getReleaseVersionString();
@ -1820,16 +1821,18 @@ public final class SystemKeyspace
// if we're restarting after an upgrade, snapshot the system and schema keyspaces // if we're restarting after an upgrade, snapshot the system and schema keyspaces
if (!previous.equals(NULL_VERSION.toString()) && !previous.equals(next)) if (!previous.equals(NULL_VERSION.toString()) && !previous.equals(next))
{ {
logger.info("Detected version upgrade from {} to {}, snapshotting system keyspaces", previous, next); List<String> entities = new ArrayList<>();
String snapshotName = Keyspace.getTimestampedSnapshotName(format("upgrade-%s-%s",
previous,
next));
Instant creationTime = now();
for (String keyspace : SchemaConstants.LOCAL_SYSTEM_KEYSPACE_NAMES) for (String keyspace : SchemaConstants.LOCAL_SYSTEM_KEYSPACE_NAMES)
Keyspace.open(keyspace).snapshot(snapshotName, null, false, null, null, creationTime); {
for (ColumnFamilyStore cfs : Keyspace.open(keyspace).getColumnFamilyStores())
entities.add(cfs.getKeyspaceTableName());
}
logger.info("Detected version upgrade from {} to {}, snapshotting system keyspaces", previous, next);
SnapshotOptions options = systemSnapshot(format("%s-%s", previous, next), SnapshotType.UPGRADE, entities.toArray(new String[0])).build();
SnapshotManager.instance.takeSnapshot(options);
} }
} }

View File

@ -17,7 +17,6 @@
*/ */
package org.apache.cassandra.db.compaction; package org.apache.cassandra.db.compaction;
import java.time.Instant;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
@ -46,6 +45,9 @@ import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.io.sstable.metadata.MetadataCollector; import org.apache.cassandra.io.sstable.metadata.MetadataCollector;
import org.apache.cassandra.io.util.File; import org.apache.cassandra.io.util.File;
import org.apache.cassandra.service.ActiveRepairService; import org.apache.cassandra.service.ActiveRepairService;
import org.apache.cassandra.service.snapshot.SnapshotManager;
import org.apache.cassandra.service.snapshot.SnapshotOptions;
import org.apache.cassandra.service.snapshot.SnapshotType;
import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.TimeUUID; import org.apache.cassandra.utils.TimeUUID;
import org.apache.cassandra.utils.concurrent.Refs; import org.apache.cassandra.utils.concurrent.Refs;
@ -53,7 +55,6 @@ import org.apache.cassandra.utils.concurrent.Refs;
import static org.apache.cassandra.db.compaction.CompactionHistoryTabularData.COMPACTION_TYPE_PROPERTY; import static org.apache.cassandra.db.compaction.CompactionHistoryTabularData.COMPACTION_TYPE_PROPERTY;
import static org.apache.cassandra.utils.Clock.Global.currentTimeMillis; import static org.apache.cassandra.utils.Clock.Global.currentTimeMillis;
import static org.apache.cassandra.utils.Clock.Global.nanoTime; import static org.apache.cassandra.utils.Clock.Global.nanoTime;
import static org.apache.cassandra.utils.FBUtilities.now;
public class CompactionTask extends AbstractCompactionTask public class CompactionTask extends AbstractCompactionTask
{ {
@ -126,8 +127,8 @@ public class CompactionTask extends AbstractCompactionTask
if (DatabaseDescriptor.isSnapshotBeforeCompaction()) if (DatabaseDescriptor.isSnapshotBeforeCompaction())
{ {
Instant creationTime = now(); SnapshotOptions options = SnapshotOptions.systemSnapshot(cfs.name, SnapshotType.COMPACT, cfs.getKeyspaceTableName()).skipFlush().build();
cfs.snapshotWithoutMemtable(creationTime.toEpochMilli() + "-compact-" + cfs.name, creationTime); SnapshotManager.instance.takeSnapshot(options);
} }
try (CompactionController controller = getCompactionController(transaction.originals())) try (CompactionController controller = getCompactionController(transaction.originals()))

View File

@ -35,6 +35,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.config.DurationSpec;
import org.apache.cassandra.db.ColumnFamilyStore; import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.Directories; import org.apache.cassandra.db.Directories;
import org.apache.cassandra.db.commitlog.CommitLogPosition; import org.apache.cassandra.db.commitlog.CommitLogPosition;
@ -56,6 +57,8 @@ import org.apache.cassandra.notifications.SSTableDeletingNotification;
import org.apache.cassandra.notifications.SSTableListChangedNotification; import org.apache.cassandra.notifications.SSTableListChangedNotification;
import org.apache.cassandra.notifications.SSTableMetadataChanged; import org.apache.cassandra.notifications.SSTableMetadataChanged;
import org.apache.cassandra.notifications.SSTableRepairStatusChanged; import org.apache.cassandra.notifications.SSTableRepairStatusChanged;
import org.apache.cassandra.notifications.TableDroppedNotification;
import org.apache.cassandra.notifications.TablePreScrubNotification;
import org.apache.cassandra.notifications.TruncationNotification; import org.apache.cassandra.notifications.TruncationNotification;
import org.apache.cassandra.utils.Pair; import org.apache.cassandra.utils.Pair;
import org.apache.cassandra.utils.Throwables; import org.apache.cassandra.utils.Throwables;
@ -510,31 +513,34 @@ public class Tracker
{ {
if (repairStatusesChanged.isEmpty()) if (repairStatusesChanged.isEmpty())
return; return;
INotification notification = new SSTableRepairStatusChanged(repairStatusesChanged); notify(new SSTableRepairStatusChanged(repairStatusesChanged));
for (INotificationConsumer subscriber : subscribers)
subscriber.handleNotification(notification, this);
} }
public void notifySSTableMetadataChanged(SSTableReader levelChanged, StatsMetadata oldMetadata) public void notifySSTableMetadataChanged(SSTableReader levelChanged, StatsMetadata oldMetadata)
{ {
INotification notification = new SSTableMetadataChanged(levelChanged, oldMetadata); notify(new SSTableMetadataChanged(levelChanged, oldMetadata));
for (INotificationConsumer subscriber : subscribers)
subscriber.handleNotification(notification, this);
} }
public void notifyDeleting(SSTableReader deleting) public void notifyDeleting(SSTableReader deleting)
{ {
INotification notification = new SSTableDeletingNotification(deleting); notify(new SSTableDeletingNotification(deleting));
for (INotificationConsumer subscriber : subscribers)
subscriber.handleNotification(notification, this);
} }
public void notifyTruncated(long truncatedAt) public void notifyTruncated(boolean disableSnapshot,
long truncatedAt,
DurationSpec.IntSecondsBound ttl)
{ {
INotification notification = new TruncationNotification(truncatedAt); notify(new TruncationNotification(cfstore, disableSnapshot, truncatedAt, ttl));
for (INotificationConsumer subscriber : subscribers) }
subscriber.handleNotification(notification, this);
public void notifyDropped(DurationSpec.IntSecondsBound ttl)
{
notify(new TableDroppedNotification(cfstore, ttl));
}
public void notifyPreScrubbed()
{
notify(new TablePreScrubNotification(cfstore));
} }
public void notifyRenewed(Memtable renewed) public void notifyRenewed(Memtable renewed)

View File

@ -22,8 +22,7 @@ import java.io.IOException;
import java.util.Collection; import java.util.Collection;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.function.Predicate;
import com.google.common.base.Predicate;
import org.apache.cassandra.db.ColumnFamilyStore; import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.compaction.CompactionManager; import org.apache.cassandra.db.compaction.CompactionManager;
@ -36,6 +35,9 @@ import org.apache.cassandra.repair.SharedContext;
import org.apache.cassandra.repair.TableRepairManager; import org.apache.cassandra.repair.TableRepairManager;
import org.apache.cassandra.repair.ValidationPartitionIterator; import org.apache.cassandra.repair.ValidationPartitionIterator;
import org.apache.cassandra.repair.NoSuchRepairSessionException; import org.apache.cassandra.repair.NoSuchRepairSessionException;
import org.apache.cassandra.service.snapshot.SnapshotOptions;
import org.apache.cassandra.service.snapshot.SnapshotManager;
import org.apache.cassandra.service.snapshot.SnapshotType;
import org.apache.cassandra.utils.TimeUUID; import org.apache.cassandra.utils.TimeUUID;
import org.apache.cassandra.service.ActiveRepairService; import org.apache.cassandra.service.ActiveRepairService;
@ -79,17 +81,14 @@ public class CassandraTableRepairManager implements TableRepairManager
try try
{ {
ActiveRepairService.instance().snapshotExecutor.submit(() -> { ActiveRepairService.instance().snapshotExecutor.submit(() -> {
if (force || !cfs.snapshotExists(name)) if (force || !SnapshotManager.instance.exists(cfs.getKeyspaceName(), cfs.getTableName(), name))
{ {
cfs.snapshot(name, new Predicate<SSTableReader>() Predicate<SSTableReader> predicate = sstable -> sstable != null &&
{ !sstable.metadata().isIndex() && // exclude SSTables from 2i
public boolean apply(SSTableReader sstable) new Bounds<>(sstable.getFirst().getToken(), sstable.getLast().getToken()).intersects(ranges);
{
return sstable != null && SnapshotOptions options = SnapshotOptions.systemSnapshot(name, SnapshotType.REPAIR, predicate, cfs.getKeyspaceTableName()).ephemeral().build();
!sstable.metadata().isIndex() && // exclude SSTables from 2i SnapshotManager.instance.takeSnapshot(options);
new Bounds<>(sstable.getFirst().getToken(), sstable.getLast().getToken()).intersects(ranges);
}
}, true, false); //ephemeral snapshot, if repair fails, it will be cleaned next startup
} }
}).get(); }).get();
} }

View File

@ -57,6 +57,8 @@ import org.apache.cassandra.repair.ValidationPartitionIterator;
import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.ActiveRepairService; import org.apache.cassandra.service.ActiveRepairService;
import org.apache.cassandra.repair.NoSuchRepairSessionException; import org.apache.cassandra.repair.NoSuchRepairSessionException;
import org.apache.cassandra.service.snapshot.SnapshotManager;
import org.apache.cassandra.service.snapshot.TableSnapshot;
import org.apache.cassandra.utils.TimeUUID; import org.apache.cassandra.utils.TimeUUID;
import org.apache.cassandra.utils.concurrent.Refs; import org.apache.cassandra.utils.concurrent.Refs;
@ -179,19 +181,19 @@ public class CassandraValidationIterator extends ValidationPartitionIterator
this.cfs = cfs; this.cfs = cfs;
this.ctx = ctx; this.ctx = ctx;
isGlobalSnapshotValidation = cfs.snapshotExists(parentId.toString()); isGlobalSnapshotValidation = SnapshotManager.instance.exists(cfs.getKeyspaceName(), cfs.getTableName(), parentId.toString());
if (isGlobalSnapshotValidation) if (isGlobalSnapshotValidation)
snapshotName = parentId.toString(); snapshotName = parentId.toString();
else else
snapshotName = sessionID.toString(); snapshotName = sessionID.toString();
isSnapshotValidation = cfs.snapshotExists(snapshotName); isSnapshotValidation = SnapshotManager.instance.exists(cfs.getKeyspaceName(), cfs.getTableName(), snapshotName);
if (isSnapshotValidation) if (isSnapshotValidation)
{ {
// If there is a snapshot created for the session then read from there. // If there is a snapshot created for the session then read from there.
// note that we populate the parent repair session when creating the snapshot, meaning the sstables in the snapshot are the ones we // note that we populate the parent repair session when creating the snapshot, meaning the sstables in the snapshot are the ones we
// are supposed to validate. // are supposed to validate.
sstables = cfs.getSnapshotSSTableReaders(snapshotName); sstables = TableSnapshot.getSnapshotSSTableReaders(cfs, snapshotName);
} }
else else
{ {
@ -270,7 +272,7 @@ public class CassandraValidationIterator extends ValidationPartitionIterator
{ {
// we can only clear the snapshot if we are not doing a global snapshot validation (we then clear it once anticompaction // we can only clear the snapshot if we are not doing a global snapshot validation (we then clear it once anticompaction
// is done). // is done).
cfs.clearSnapshot(snapshotName); SnapshotManager.instance.clearSnapshot(cfs.getKeyspaceName(), cfs.getTableName(), snapshotName);
} }
if (sstables != null) if (sstables != null)

View File

@ -26,7 +26,7 @@ import org.apache.cassandra.db.marshal.TimestampType;
import org.apache.cassandra.db.marshal.UTF8Type; import org.apache.cassandra.db.marshal.UTF8Type;
import org.apache.cassandra.dht.LocalPartitioner; import org.apache.cassandra.dht.LocalPartitioner;
import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.StorageService; import org.apache.cassandra.service.snapshot.SnapshotManager;
import org.apache.cassandra.service.snapshot.TableSnapshot; import org.apache.cassandra.service.snapshot.TableSnapshot;
public class SnapshotsTable extends AbstractVirtualTable public class SnapshotsTable extends AbstractVirtualTable
@ -62,7 +62,7 @@ public class SnapshotsTable extends AbstractVirtualTable
{ {
SimpleDataSet result = new SimpleDataSet(metadata()); SimpleDataSet result = new SimpleDataSet(metadata());
for (TableSnapshot tableSnapshot : StorageService.instance.snapshotManager.loadSnapshots()) for (TableSnapshot tableSnapshot : SnapshotManager.instance.getSnapshots(false, true))
{ {
SimpleDataSet row = result.row(tableSnapshot.getTag(), SimpleDataSet row = result.row(tableSnapshot.getTag(),
tableSnapshot.getKeyspaceName(), tableSnapshot.getKeyspaceName(),

View File

@ -0,0 +1,34 @@
/*
* 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.notifications;
import org.apache.cassandra.config.DurationSpec;
import org.apache.cassandra.db.ColumnFamilyStore;
public class TableDroppedNotification implements INotification
{
public final ColumnFamilyStore cfs;
public final DurationSpec.IntSecondsBound ttl;
public TableDroppedNotification(ColumnFamilyStore cfs, DurationSpec.IntSecondsBound ttl)
{
this.cfs = cfs;
this.ttl = ttl;
}
}

View File

@ -0,0 +1,31 @@
/*
* 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.notifications;
import org.apache.cassandra.db.ColumnFamilyStore;
public class TablePreScrubNotification implements INotification
{
public final ColumnFamilyStore cfs;
public TablePreScrubNotification(ColumnFamilyStore cfs)
{
this.cfs = cfs;
}
}

View File

@ -17,16 +17,28 @@
*/ */
package org.apache.cassandra.notifications; package org.apache.cassandra.notifications;
import org.apache.cassandra.config.DurationSpec;
import org.apache.cassandra.db.ColumnFamilyStore;
/** /**
* Fired during truncate, after the memtable has been flushed but before any * Fired during truncate, after the memtable has been flushed but before any
* snapshot is taken and SSTables are discarded * snapshot is taken and SSTables are discarded
*/ */
public class TruncationNotification implements INotification public class TruncationNotification implements INotification
{ {
public final ColumnFamilyStore cfs;
public final boolean disableSnapshot;
public final long truncatedAt; public final long truncatedAt;
public final DurationSpec.IntSecondsBound ttl;
public TruncationNotification(long truncatedAt) public TruncationNotification(ColumnFamilyStore cfs,
boolean disableSnapshot,
long truncatedAt,
DurationSpec.IntSecondsBound ttl)
{ {
this.cfs = cfs;
this.disableSnapshot = disableSnapshot;
this.truncatedAt = truncatedAt; this.truncatedAt = truncatedAt;
this.ttl = ttl;
} }
} }

View File

@ -32,6 +32,7 @@ import org.apache.cassandra.dht.Token;
import org.apache.cassandra.locator.InetAddressAndPort; import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.metrics.RepairMetrics; import org.apache.cassandra.metrics.RepairMetrics;
import org.apache.cassandra.repair.consistent.SyncStatSummary; import org.apache.cassandra.repair.consistent.SyncStatSummary;
import org.apache.cassandra.service.snapshot.SnapshotManager;
import org.apache.cassandra.streaming.PreviewKind; import org.apache.cassandra.streaming.PreviewKind;
import org.apache.cassandra.utils.DiagnosticSnapshotService; import org.apache.cassandra.utils.DiagnosticSnapshotService;
import org.apache.cassandra.utils.TimeUUID; import org.apache.cassandra.utils.TimeUUID;
@ -128,7 +129,7 @@ public class PreviewRepairTask extends AbstractRepairTask
for (String table : mismatchingTables) for (String table : mismatchingTables)
{ {
// we can just check snapshot existence locally since the repair coordinator is always a replica (unlike in the read case) // we can just check snapshot existence locally since the repair coordinator is always a replica (unlike in the read case)
if (!Keyspace.open(keyspace).getColumnFamilyStore(table).snapshotExists(snapshotName)) if (!SnapshotManager.instance.exists(keyspace, table, snapshotName))
{ {
List<Range<Token>> normalizedRanges = Range.normalize(ranges); List<Range<Token>> normalizedRanges = Range.normalize(ranges);
logger.info("{} Snapshotting {}.{} for preview repair mismatch for ranges {} with tag {} on instances {}", logger.info("{} Snapshotting {}.{} for preview repair mismatch for ranges {} with tag {} on instances {}",

View File

@ -106,6 +106,7 @@ import org.apache.cassandra.schema.TableId;
import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.paxos.PaxosRepair; import org.apache.cassandra.service.paxos.PaxosRepair;
import org.apache.cassandra.service.paxos.cleanup.PaxosCleanup; import org.apache.cassandra.service.paxos.cleanup.PaxosCleanup;
import org.apache.cassandra.service.snapshot.SnapshotManager;
import org.apache.cassandra.tcm.ClusterMetadata; import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.streaming.PreviewKind; import org.apache.cassandra.streaming.PreviewKind;
import org.apache.cassandra.utils.ExecutorUtils; import org.apache.cassandra.utils.ExecutorUtils;
@ -884,10 +885,8 @@ public class ActiveRepairService implements IEndpointStateChangeSubscriber, IFai
.map(cfs -> cfs.metadata().toString()).collect(Collectors.joining(", "))); .map(cfs -> cfs.metadata().toString()).collect(Collectors.joining(", ")));
long startNanos = ctx.clock().nanoTime(); long startNanos = ctx.clock().nanoTime();
for (ColumnFamilyStore cfs : session.columnFamilyStores.values()) for (ColumnFamilyStore cfs : session.columnFamilyStores.values())
{ SnapshotManager.instance.clearSnapshot(cfs.keyspace.getName(), cfs.name, snapshotName);
if (cfs.snapshotExists(snapshotName))
cfs.clearSnapshot(snapshotName);
}
logger.info("[repair #{}] Cleared snapshots in {}ms", parentSessionId, TimeUnit.NANOSECONDS.toMillis(ctx.clock().nanoTime() - startNanos)); logger.info("[repair #{}] Cleared snapshots in {}ms", parentSessionId, TimeUnit.NANOSECONDS.toMillis(ctx.clock().nanoTime() - startNanos));
}); });
} }

View File

@ -71,6 +71,7 @@ import org.apache.cassandra.schema.Schema;
import org.apache.cassandra.schema.SchemaConstants; import org.apache.cassandra.schema.SchemaConstants;
import org.apache.cassandra.security.ThreadAwareSecurityManager; import org.apache.cassandra.security.ThreadAwareSecurityManager;
import org.apache.cassandra.service.paxos.PaxosState; import org.apache.cassandra.service.paxos.PaxosState;
import org.apache.cassandra.service.snapshot.SnapshotManager;
import org.apache.cassandra.streaming.StreamManager; import org.apache.cassandra.streaming.StreamManager;
import org.apache.cassandra.tcm.CMSOperations; import org.apache.cassandra.tcm.CMSOperations;
import org.apache.cassandra.tcm.ClusterMetadata; import org.apache.cassandra.tcm.ClusterMetadata;
@ -266,8 +267,16 @@ public class CassandraDaemon
DatabaseDescriptor.createAllDirectories(); DatabaseDescriptor.createAllDirectories();
Keyspace.setInitialized(); Keyspace.setInitialized();
CommitLog.instance.start(); CommitLog.instance.start();
runStartupChecks();
SnapshotManager.instance.start(false);
SnapshotManager.instance.clearExpiredSnapshots();
SnapshotManager.instance.clearEphemeralSnapshots();
SnapshotManager.instance.resumeSnapshotCleanup();
SnapshotManager.instance.registerMBean();
// clearing of snapshots above here will in fact clear all ephemeral snapshots
// which were cleared as part of startup checks before CASSANDRA-18111
runStartupChecks();
try try
{ {
@ -291,7 +300,7 @@ public class CassandraDaemon
{ {
SystemKeyspace.snapshotOnVersionChange(); SystemKeyspace.snapshotOnVersionChange();
} }
catch (IOException e) catch (Throwable e)
{ {
exitOrFail(StartupException.ERR_WRONG_DISK_STATE, e.getMessage(), e.getCause()); exitOrFail(StartupException.ERR_WRONG_DISK_STATE, e.getMessage(), e.getCause());
} }

View File

@ -20,11 +20,13 @@ package org.apache.cassandra.service;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.apache.cassandra.db.Keyspace;
import org.apache.cassandra.db.SnapshotCommand; import org.apache.cassandra.db.SnapshotCommand;
import org.apache.cassandra.net.IVerbHandler; import org.apache.cassandra.net.IVerbHandler;
import org.apache.cassandra.net.Message; import org.apache.cassandra.net.Message;
import org.apache.cassandra.net.MessagingService; import org.apache.cassandra.net.MessagingService;
import org.apache.cassandra.service.snapshot.SnapshotManager;
import org.apache.cassandra.service.snapshot.SnapshotOptions;
import org.apache.cassandra.service.snapshot.SnapshotType;
import org.apache.cassandra.utils.DiagnosticSnapshotService; import org.apache.cassandra.utils.DiagnosticSnapshotService;
@ -38,7 +40,7 @@ public class SnapshotVerbHandler implements IVerbHandler<SnapshotCommand>
SnapshotCommand command = message.payload; SnapshotCommand command = message.payload;
if (command.clear_snapshot) if (command.clear_snapshot)
{ {
StorageService.instance.clearSnapshot(command.snapshot_name, command.keyspace); SnapshotManager.instance.clearSnapshots(command.snapshot_name, command.keyspace);
} }
else if (DiagnosticSnapshotService.isDiagnosticSnapshotRequest(command)) else if (DiagnosticSnapshotService.isDiagnosticSnapshotRequest(command))
{ {
@ -46,7 +48,15 @@ public class SnapshotVerbHandler implements IVerbHandler<SnapshotCommand>
} }
else else
{ {
Keyspace.open(command.keyspace).getColumnFamilyStore(command.column_family).snapshot(command.snapshot_name); try
{
SnapshotOptions options = SnapshotOptions.systemSnapshot(command.snapshot_name, SnapshotType.DIAGNOSTICS, command.keyspace + '.' + command.column_family).build();
SnapshotManager.instance.takeSnapshot(options);
}
catch (Exception ex)
{
throw new RuntimeException(ex);
}
} }
logger.debug("Enqueuing response to snapshot request {} to {}", command.snapshot_name, message.from()); logger.debug("Enqueuing response to snapshot request {} to {}", command.snapshot_name, message.from());

View File

@ -22,8 +22,6 @@ import java.io.IOException;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.time.Instant;
import java.time.format.DateTimeParseException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
@ -59,7 +57,6 @@ import javax.management.NotificationListener;
import javax.management.openmbean.CompositeData; import javax.management.openmbean.CompositeData;
import javax.management.openmbean.OpenDataException; import javax.management.openmbean.OpenDataException;
import javax.management.openmbean.TabularData; import javax.management.openmbean.TabularData;
import javax.management.openmbean.TabularDataSupport;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner; import com.google.common.base.Joiner;
@ -72,7 +69,6 @@ import com.google.common.collect.Lists;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import com.google.common.collect.Ordering; import com.google.common.collect.Ordering;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import com.google.common.util.concurrent.RateLimiter;
import com.google.common.util.concurrent.Uninterruptibles; import com.google.common.util.concurrent.Uninterruptibles;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -96,21 +92,18 @@ import org.apache.cassandra.config.Config.PaxosStatePurging;
import org.apache.cassandra.config.Converters; import org.apache.cassandra.config.Converters;
import org.apache.cassandra.config.DataStorageSpec; import org.apache.cassandra.config.DataStorageSpec;
import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.config.DurationSpec;
import org.apache.cassandra.cql3.QueryHandler; import org.apache.cassandra.cql3.QueryHandler;
import org.apache.cassandra.cql3.QueryProcessor; import org.apache.cassandra.cql3.QueryProcessor;
import org.apache.cassandra.db.ColumnFamilyStore; import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.DecoratedKey; import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.Keyspace; import org.apache.cassandra.db.Keyspace;
import org.apache.cassandra.db.SizeEstimatesRecorder; import org.apache.cassandra.db.SizeEstimatesRecorder;
import org.apache.cassandra.db.SnapshotDetailsTabularData;
import org.apache.cassandra.db.SystemKeyspace; import org.apache.cassandra.db.SystemKeyspace;
import org.apache.cassandra.db.commitlog.CommitLog; import org.apache.cassandra.db.commitlog.CommitLog;
import org.apache.cassandra.db.compaction.CompactionManager; import org.apache.cassandra.db.compaction.CompactionManager;
import org.apache.cassandra.db.compaction.OperationType; import org.apache.cassandra.db.compaction.OperationType;
import org.apache.cassandra.db.guardrails.Guardrails; import org.apache.cassandra.db.guardrails.Guardrails;
import org.apache.cassandra.db.lifecycle.LifecycleTransaction; import org.apache.cassandra.db.lifecycle.LifecycleTransaction;
import org.apache.cassandra.db.virtual.VirtualKeyspaceRegistry;
import org.apache.cassandra.dht.BootStrapper; import org.apache.cassandra.dht.BootStrapper;
import org.apache.cassandra.dht.IPartitioner; import org.apache.cassandra.dht.IPartitioner;
import org.apache.cassandra.dht.OwnedRanges; import org.apache.cassandra.dht.OwnedRanges;
@ -179,7 +172,6 @@ import org.apache.cassandra.service.paxos.PaxosState;
import org.apache.cassandra.service.paxos.cleanup.PaxosCleanupLocalCoordinator; import org.apache.cassandra.service.paxos.cleanup.PaxosCleanupLocalCoordinator;
import org.apache.cassandra.service.paxos.cleanup.PaxosRepairState; import org.apache.cassandra.service.paxos.cleanup.PaxosRepairState;
import org.apache.cassandra.service.snapshot.SnapshotManager; import org.apache.cassandra.service.snapshot.SnapshotManager;
import org.apache.cassandra.service.snapshot.TableSnapshot;
import org.apache.cassandra.streaming.StreamManager; import org.apache.cassandra.streaming.StreamManager;
import org.apache.cassandra.streaming.StreamResultFuture; import org.apache.cassandra.streaming.StreamResultFuture;
import org.apache.cassandra.streaming.StreamState; import org.apache.cassandra.streaming.StreamState;
@ -208,7 +200,6 @@ import org.apache.cassandra.tcm.transformations.Startup;
import org.apache.cassandra.tcm.transformations.Unregister; import org.apache.cassandra.tcm.transformations.Unregister;
import org.apache.cassandra.transport.ClientResourceLimits; import org.apache.cassandra.transport.ClientResourceLimits;
import org.apache.cassandra.transport.ProtocolVersion; import org.apache.cassandra.transport.ProtocolVersion;
import org.apache.cassandra.utils.Clock;
import org.apache.cassandra.utils.ExecutorUtils; import org.apache.cassandra.utils.ExecutorUtils;
import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.JVMStabilityInspector; import org.apache.cassandra.utils.JVMStabilityInspector;
@ -263,7 +254,6 @@ import static org.apache.cassandra.tcm.membership.NodeState.MOVING;
import static org.apache.cassandra.tcm.membership.NodeState.REGISTERED; import static org.apache.cassandra.tcm.membership.NodeState.REGISTERED;
import static org.apache.cassandra.utils.Clock.Global.currentTimeMillis; import static org.apache.cassandra.utils.Clock.Global.currentTimeMillis;
import static org.apache.cassandra.utils.FBUtilities.getBroadcastAddressAndPort; import static org.apache.cassandra.utils.FBUtilities.getBroadcastAddressAndPort;
import static org.apache.cassandra.utils.FBUtilities.now;
/** /**
* This abstraction contains the token/identifier of this node * This abstraction contains the token/identifier of this node
@ -312,8 +302,6 @@ public class StorageService extends NotificationBroadcasterSupport implements IE
private final List<Runnable> preShutdownHooks = new ArrayList<>(); private final List<Runnable> preShutdownHooks = new ArrayList<>();
private final List<Runnable> postShutdownHooks = new ArrayList<>(); private final List<Runnable> postShutdownHooks = new ArrayList<>();
public final SnapshotManager snapshotManager = new SnapshotManager();
public static final StorageService instance = new StorageService(); public static final StorageService instance = new StorageService();
private final SamplingManager samplingManager = new SamplingManager(); private final SamplingManager samplingManager = new SamplingManager();
@ -884,7 +872,6 @@ public class StorageService extends NotificationBroadcasterSupport implements IE
DiskUsageBroadcaster.instance.startBroadcasting(); DiskUsageBroadcaster.instance.startBroadcasting();
HintsService.instance.startDispatch(); HintsService.instance.startDispatch();
BatchlogManager.instance.start(); BatchlogManager.instance.start();
startSnapshotManager();
servicesInitialized = true; servicesInitialized = true;
} }
@ -925,12 +912,6 @@ public class StorageService extends NotificationBroadcasterSupport implements IE
return DatabaseDescriptor.getSeeds().contains(getBroadcastAddressAndPort()); return DatabaseDescriptor.getSeeds().contains(getBroadcastAddressAndPort());
} }
@VisibleForTesting
public void startSnapshotManager()
{
snapshotManager.start();
}
public static boolean isReplacingSameAddress() public static boolean isReplacingSameAddress()
{ {
InetAddressAndPort replaceAddress = DatabaseDescriptor.getReplaceAddress(); InetAddressAndPort replaceAddress = DatabaseDescriptor.getReplaceAddress();
@ -2742,55 +2723,6 @@ public class StorageService extends NotificationBroadcasterSupport implements IE
return status.statusCode; return status.statusCode;
} }
/**
* Takes the snapshot of a multiple column family from different keyspaces. A snapshot name must be specified.
*
* @param tag
* the tag given to the snapshot; may not be null or empty
* @param options
* Map of options (skipFlush is the only supported option for now)
* @param entities
* list of keyspaces / tables in the form of empty | ks1 ks2 ... | ks1.cf1,ks2.cf2,...
*/
@Override
public void takeSnapshot(String tag, Map<String, String> options, String... entities) throws IOException
{
DurationSpec.IntSecondsBound ttl = options.containsKey("ttl") ? new DurationSpec.IntSecondsBound(options.get("ttl")) : null;
if (ttl != null)
{
int minAllowedTtlSecs = CassandraRelevantProperties.SNAPSHOT_MIN_ALLOWED_TTL_SECONDS.getInt();
if (ttl.toSeconds() < minAllowedTtlSecs)
throw new IllegalArgumentException(String.format("ttl for snapshot must be at least %d seconds", minAllowedTtlSecs));
}
boolean skipFlush = Boolean.parseBoolean(options.getOrDefault("skipFlush", "false"));
if (entities != null && entities.length > 0 && entities[0].contains("."))
{
takeMultipleTableSnapshot(tag, skipFlush, ttl, entities);
}
else
{
takeSnapshot(tag, skipFlush, ttl, entities);
}
}
/**
* Takes the snapshot of a specific table. A snapshot name must be
* specified.
*
* @param keyspaceName
* the keyspace which holds the specified table
* @param tableName
* the table to snapshot
* @param tag
* the tag given to the snapshot; may not be null or empty
*/
public void takeTableSnapshot(String keyspaceName, String tableName, String tag)
throws IOException
{
takeMultipleTableSnapshot(tag, false, null, keyspaceName + "." + tableName);
}
@Override @Override
public void forceKeyspaceCompactionForTokenRange(String keyspaceName, String startToken, String endToken, String... tableNames) throws IOException, ExecutionException, InterruptedException public void forceKeyspaceCompactionForTokenRange(String keyspaceName, String startToken, String endToken, String... tableNames) throws IOException, ExecutionException, InterruptedException
{ {
@ -2840,296 +2772,126 @@ public class StorageService extends NotificationBroadcasterSupport implements IE
public void forceCompactionKeysIgnoringGcGrace(String keyspaceName, public void forceCompactionKeysIgnoringGcGrace(String keyspaceName,
String tableName, String... partitionKeysIgnoreGcGrace) throws IOException, ExecutionException, InterruptedException String tableName, String... partitionKeysIgnoreGcGrace) throws IOException, ExecutionException, InterruptedException
{ {
ColumnFamilyStore cfStore = getValidKeyspace(keyspaceName).getColumnFamilyStore(tableName); ColumnFamilyStore cfStore = Keyspace.getValidKeyspace(keyspaceName).getColumnFamilyStore(tableName);
cfStore.forceCompactionKeysIgnoringGcGrace(partitionKeysIgnoreGcGrace); cfStore.forceCompactionKeysIgnoringGcGrace(partitionKeysIgnoreGcGrace);
} }
/**
* Takes the snapshot of a multiple column family from different keyspaces. A snapshot name must be specified.
*
* @param tag the tag given to the snapshot; may not be null or empty
* @param options Map of options (skipFlush is the only supported option for now)
* @param entities list of keyspaces / tables in the form of empty | ks1 ks2 ... | ks1.cf1,ks2.cf2,...
*
* @deprecated See CASSANDRA-18111
*/
@Override
@Deprecated(since = "5.1")
public void takeSnapshot(String tag, Map<String, String> options, String... entities) throws IOException
{
SnapshotManager.instance.takeSnapshot(tag, options, entities);
}
/**
* Takes the snapshot of a specific table. A snapshot name must be
* specified.
*
* @param keyspaceName the keyspace which holds the specified table
* @param tableName the table to snapshot
* @param tag the tag given to the snapshot; may not be null or empty
*
* @deprecated use {@link #takeSnapshot(String tag, Map options, String... entities)} instead. See CASSANDRA-10907
*/
@Override
@Deprecated(since = "3.4")
public void takeTableSnapshot(String keyspaceName, String tableName, String tag) throws IOException
{
SnapshotManager.instance.takeSnapshot(tag, Collections.emptyMap(), keyspaceName + '.' + tableName);
}
/** /**
* Takes the snapshot for the given keyspaces. A snapshot name must be specified. * Takes the snapshot for the given keyspaces. A snapshot name must be specified.
* *
* @param tag the tag given to the snapshot; may not be null or empty * @param tag the tag given to the snapshot; may not be null or empty
* @param keyspaceNames the names of the keyspaces to snapshot; empty means "all." * @param keyspaceNames the names of the keyspaces to snapshot; empty means "all."
*
* @deprecated use {@link #takeSnapshot(String tag, Map options, String... entities)} instead. See CASSANDRA-10907
*/ */
@Override
@Deprecated(since = "3.4")
public void takeSnapshot(String tag, String... keyspaceNames) throws IOException public void takeSnapshot(String tag, String... keyspaceNames) throws IOException
{ {
takeSnapshot(tag, false, null, keyspaceNames); SnapshotManager.instance.takeSnapshot(tag, Collections.emptyMap(), keyspaceNames);
} }
/** /**
* Takes the snapshot of a multiple column family from different keyspaces. A snapshot name must be specified. * Takes the snapshot of a multiple column family from different keyspaces. A snapshot name must be specified.
* *
* @param tag
* the tag given to the snapshot; may not be null or empty
* @param tableList
* list of tables from different keyspace in the form of ks1.cf1 ks2.cf2
*/
public void takeMultipleTableSnapshot(String tag, String... tableList)
throws IOException
{
takeMultipleTableSnapshot(tag, false, null, tableList);
}
/**
* Takes the snapshot for the given keyspaces. A snapshot name must be specified.
*
* @param tag the tag given to the snapshot; may not be null or empty * @param tag the tag given to the snapshot; may not be null or empty
* @param skipFlush Skip blocking flush of memtable * @param tableList list of tables from different keyspace in the form of ks1.cf1 ks2.cf2
* @param keyspaceNames the names of the keyspaces to snapshot; empty means "all."
*/
private void takeSnapshot(String tag, boolean skipFlush, DurationSpec.IntSecondsBound ttl, String... keyspaceNames) throws IOException
{
if (operationMode() == Mode.JOINING)
throw new IOException("Cannot snapshot until bootstrap completes");
if (tag == null || tag.equals(""))
throw new IOException("You must supply a snapshot name.");
Iterable<Keyspace> keyspaces;
if (keyspaceNames.length == 0)
{
keyspaces = Keyspace.all();
}
else
{
ArrayList<Keyspace> t = new ArrayList<>(keyspaceNames.length);
for (String keyspaceName : keyspaceNames)
t.add(getValidKeyspace(keyspaceName));
keyspaces = t;
}
// Do a check to see if this snapshot exists before we actually snapshot
for (Keyspace keyspace : keyspaces)
if (keyspace.snapshotExists(tag))
throw new IOException("Snapshot " + tag + " already exists.");
RateLimiter snapshotRateLimiter = DatabaseDescriptor.getSnapshotRateLimiter();
Instant creationTime = now();
for (Keyspace keyspace : keyspaces)
{
keyspace.snapshot(tag, null, skipFlush, ttl, snapshotRateLimiter, creationTime);
}
}
/**
* Takes the snapshot of a multiple column family from different keyspaces. A snapshot name must be specified.
* *
* * @deprecated See CASSANDRA-10907
* @param tag
* the tag given to the snapshot; may not be null or empty
* @param skipFlush
* Skip blocking flush of memtable
* @param tableList
* list of tables from different keyspace in the form of ks1.cf1 ks2.cf2
*/ */
private void takeMultipleTableSnapshot(String tag, boolean skipFlush, DurationSpec.IntSecondsBound ttl, String... tableList) @Override
throws IOException @Deprecated(since = "3.4")
public void takeMultipleTableSnapshot(String tag, String... tableList) throws IOException
{ {
Map<Keyspace, List<String>> keyspaceColumnfamily = new HashMap<Keyspace, List<String>>(); SnapshotManager.instance.takeSnapshot(tag, Collections.emptyMap(), tableList);
for (String table : tableList)
{
String splittedString[] = StringUtils.split(table, '.');
if (splittedString.length == 2)
{
String keyspaceName = splittedString[0];
String tableName = splittedString[1];
if (keyspaceName == null)
throw new IOException("You must supply a keyspace name");
if (operationMode() == Mode.JOINING)
throw new IOException("Cannot snapshot until bootstrap completes");
if (tableName == null)
throw new IOException("You must supply a table name");
if (tag == null || tag.equals(""))
throw new IOException("You must supply a snapshot name.");
Keyspace keyspace = getValidKeyspace(keyspaceName);
ColumnFamilyStore columnFamilyStore = keyspace.getColumnFamilyStore(tableName);
// As there can be multiple column family from same keyspace check if snapshot exist for that specific
// columnfamily and not for whole keyspace
if (columnFamilyStore.snapshotExists(tag))
throw new IOException("Snapshot " + tag + " already exists.");
if (!keyspaceColumnfamily.containsKey(keyspace))
{
keyspaceColumnfamily.put(keyspace, new ArrayList<String>());
}
// Add Keyspace columnfamily to map in order to support atomicity for snapshot process.
// So no snapshot should happen if any one of the above conditions fail for any keyspace or columnfamily
keyspaceColumnfamily.get(keyspace).add(tableName);
}
else
{
throw new IllegalArgumentException(
"Cannot take a snapshot on secondary index or invalid column family name. You must supply a column family name in the form of keyspace.columnfamily");
}
}
RateLimiter snapshotRateLimiter = DatabaseDescriptor.getSnapshotRateLimiter();
Instant creationTime = now();
for (Entry<Keyspace, List<String>> entry : keyspaceColumnfamily.entrySet())
{
for (String table : entry.getValue())
entry.getKey().snapshot(tag, table, skipFlush, ttl, snapshotRateLimiter, creationTime);
}
}
private void verifyKeyspaceIsValid(String keyspaceName)
{
if (null != VirtualKeyspaceRegistry.instance.getKeyspaceNullable(keyspaceName))
throw new IllegalArgumentException("Cannot perform any operations against virtual keyspace " + keyspaceName);
if (!Schema.instance.getKeyspaces().contains(keyspaceName))
throw new IllegalArgumentException("Keyspace " + keyspaceName + " does not exist");
}
private Keyspace getValidKeyspace(String keyspaceName)
{
verifyKeyspaceIsValid(keyspaceName);
return Keyspace.open(keyspaceName);
} }
/** /**
* Remove the snapshot with the given name from the given keyspaces. * Remove the snapshot with the given name from the given keyspaces.
* If no tag is specified we will remove all snapshots. * If no tag is specified we will remove all snapshots.
*
* @deprecated See CASSANDRA-16860
*/ */
public void clearSnapshot(String tag, String... keyspaceNames) @Override
@Deprecated(since = "5.0")
public void clearSnapshot(String tag, String... keyspaceNames) throws IOException
{ {
clearSnapshot(Collections.emptyMap(), tag, keyspaceNames); SnapshotManager.instance.clearSnapshot(tag, Collections.emptyMap(), keyspaceNames);
} }
@Override
@Deprecated(since = "5.1")
public void clearSnapshot(Map<String, Object> options, String tag, String... keyspaceNames) public void clearSnapshot(Map<String, Object> options, String tag, String... keyspaceNames)
{ {
if (tag == null) SnapshotManager.instance.clearSnapshot(tag, options, keyspaceNames);
tag = "";
if (options == null)
options = Collections.emptyMap();
Set<String> keyspaces = new HashSet<>();
for (String dataDir : DatabaseDescriptor.getAllDataFileLocations())
{
for (String keyspaceDir : new File(dataDir).tryListNames())
{
// Only add a ks if it has been specified as a param, assuming params were actually provided.
if (keyspaceNames.length > 0 && !Arrays.asList(keyspaceNames).contains(keyspaceDir))
continue;
keyspaces.add(keyspaceDir);
}
}
Object olderThan = options.get("older_than");
Object olderThanTimestamp = options.get("older_than_timestamp");
final long clearOlderThanTimestamp;
if (olderThan != null)
{
assert olderThan instanceof String : "it is expected that older_than is an instance of java.lang.String";
clearOlderThanTimestamp = Clock.Global.currentTimeMillis() - new DurationSpec.LongSecondsBound((String) olderThan).toMilliseconds();
}
else if (olderThanTimestamp != null)
{
assert olderThanTimestamp instanceof String : "it is expected that older_than_timestamp is an instance of java.lang.String";
try
{
clearOlderThanTimestamp = Instant.parse((String) olderThanTimestamp).toEpochMilli();
}
catch (DateTimeParseException ex)
{
throw new RuntimeException("Parameter older_than_timestamp has to be a valid instant in ISO format.");
}
}
else
clearOlderThanTimestamp = 0L;
for (String keyspace : keyspaces)
clearKeyspaceSnapshot(keyspace, tag, clearOlderThanTimestamp);
if (logger.isDebugEnabled())
logger.debug("Cleared out snapshot directories");
}
/**
* Clear snapshots for a given keyspace.
* @param keyspace keyspace to remove snapshots for
* @param tag the user supplied snapshot name. If empty or null, all the snapshots will be cleaned
* @param olderThanTimestamp if a snapshot was created before this timestamp, it will be cleared,
* if its value is 0, this parameter is effectively ignored.
*/
private void clearKeyspaceSnapshot(String keyspace, String tag, long olderThanTimestamp)
{
Set<TableSnapshot> snapshotsToClear = snapshotManager.loadSnapshots(keyspace)
.stream()
.filter(TableSnapshot.shouldClearSnapshot(tag, olderThanTimestamp))
.collect(Collectors.toSet());
for (TableSnapshot snapshot : snapshotsToClear)
snapshotManager.clearSnapshot(snapshot);
} }
@Override
@Deprecated(since = "5.1")
public Map<String, TabularData> getSnapshotDetails(Map<String, String> options) public Map<String, TabularData> getSnapshotDetails(Map<String, String> options)
{ {
boolean skipExpiring = options != null && Boolean.parseBoolean(options.getOrDefault("no_ttl", "false")); return SnapshotManager.instance.listSnapshots(options);
boolean includeEphemeral = options != null && Boolean.parseBoolean(options.getOrDefault("include_ephemeral", "false"));
Map<String, TabularData> snapshotMap = new HashMap<>();
for (TableSnapshot snapshot : snapshotManager.loadSnapshots())
{
if (skipExpiring && snapshot.isExpiring())
continue;
if (!includeEphemeral && snapshot.isEphemeral())
continue;
TabularDataSupport data = (TabularDataSupport) snapshotMap.get(snapshot.getTag());
if (data == null)
{
data = new TabularDataSupport(SnapshotDetailsTabularData.TABULAR_TYPE);
snapshotMap.put(snapshot.getTag(), data);
}
SnapshotDetailsTabularData.from(snapshot, data);
}
return snapshotMap;
} }
/** @deprecated See CASSANDRA-16789 */ @Override
@Deprecated(since = "4.1") @Deprecated(since = "4.1")
public Map<String, TabularData> getSnapshotDetails() public Map<String, TabularData> getSnapshotDetails()
{ {
return getSnapshotDetails(ImmutableMap.of()); return SnapshotManager.instance.listSnapshots(ImmutableMap.of());
} }
@Override
@Deprecated(since = "5.1")
public long trueSnapshotsSize() public long trueSnapshotsSize()
{ {
long total = 0; return SnapshotManager.instance.getTrueSnapshotSize();
for (Keyspace keyspace : Keyspace.all())
{
if (isLocalSystemKeyspace(keyspace.getName()))
continue;
for (ColumnFamilyStore cfStore : keyspace.getColumnFamilyStores())
{
total += cfStore.trueSnapshotsSize();
}
}
return total;
} }
@Override
@Deprecated(since = "5.1")
public void setSnapshotLinksPerSecond(long throttle) public void setSnapshotLinksPerSecond(long throttle)
{ {
logger.info("Setting snapshot throttle to {}", throttle); SnapshotManager.instance.setSnapshotLinksPerSecond(throttle);
DatabaseDescriptor.setSnapshotLinksPerSecond(throttle);
} }
@Override
@Deprecated(since = "5.1")
public long getSnapshotLinksPerSecond() public long getSnapshotLinksPerSecond()
{ {
return DatabaseDescriptor.getSnapshotLinksPerSecond(); return SnapshotManager.instance.getSnapshotLinksPerSecond();
} }
public void refreshSizeEstimates() throws ExecutionException public void refreshSizeEstimates() throws ExecutionException
@ -3152,7 +2914,7 @@ public class StorageService extends NotificationBroadcasterSupport implements IE
*/ */
public Iterable<ColumnFamilyStore> getValidColumnFamilies(boolean allowIndexes, boolean autoAddIndexes, String keyspaceName, String... cfNames) public Iterable<ColumnFamilyStore> getValidColumnFamilies(boolean allowIndexes, boolean autoAddIndexes, String keyspaceName, String... cfNames)
{ {
Keyspace keyspace = getValidKeyspace(keyspaceName); Keyspace keyspace = Keyspace.getValidKeyspace(keyspaceName);
return keyspace.getValidColumnFamilies(allowIndexes, autoAddIndexes, cfNames); return keyspace.getValidColumnFamilies(allowIndexes, autoAddIndexes, cfNames);
} }
@ -3944,7 +3706,7 @@ public class StorageService extends NotificationBroadcasterSupport implements IE
logger.error("Batchlog manager timed out shutting down", t); logger.error("Batchlog manager timed out shutting down", t);
} }
snapshotManager.stop(); SnapshotManager.instance.close();
HintsService.instance.pauseDispatch(); HintsService.instance.pauseDispatch();
if (daemon != null) if (daemon != null)
@ -4038,7 +3800,7 @@ public class StorageService extends NotificationBroadcasterSupport implements IE
} }
FBUtilities.waitOnFutures(flushes); FBUtilities.waitOnFutures(flushes);
SnapshotManager.shutdownAndWait(1L, MINUTES); SnapshotManager.instance.shutdownAndWait(1L, MINUTES);
HintsService.instance.shutdownBlocking(); HintsService.instance.shutdownBlocking();
// Interrupt ongoing compactions and shutdown CM to prevent further compactions. // Interrupt ongoing compactions and shutdown CM to prevent further compactions.
@ -4167,7 +3929,7 @@ public class StorageService extends NotificationBroadcasterSupport implements IE
public void truncate(String keyspace, String table) throws TimeoutException, IOException public void truncate(String keyspace, String table) throws TimeoutException, IOException
{ {
verifyKeyspaceIsValid(keyspace); Keyspace.verifyKeyspaceIsValid(keyspace);
try try
{ {
@ -4537,7 +4299,7 @@ public class StorageService extends NotificationBroadcasterSupport implements IE
{ {
if (!isInitialized()) if (!isInitialized())
throw new RuntimeException("Not yet initialized, can't load new sstables"); throw new RuntimeException("Not yet initialized, can't load new sstables");
verifyKeyspaceIsValid(ksName); Keyspace.verifyKeyspaceIsValid(ksName);
ColumnFamilyStore.loadNewSSTables(ksName, cfName); ColumnFamilyStore.loadNewSSTables(ksName, cfName);
} }
@ -5240,10 +5002,6 @@ public class StorageService extends NotificationBroadcasterSupport implements IE
return Math.toIntExact(Guardrails.instance.getPartitionTombstonesWarnThreshold()); return Math.toIntExact(Guardrails.instance.getPartitionTombstonesWarnThreshold());
} }
public void addSnapshot(TableSnapshot snapshot) {
snapshotManager.addSnapshot(snapshot);
}
@Override @Override
public boolean getReadThresholdsEnabled() public boolean getReadThresholdsEnabled()
{ {

View File

@ -273,7 +273,9 @@ public interface StorageServiceMBean extends NotificationEmitter
public void takeTableSnapshot(String keyspaceName, String tableName, String tag) throws IOException; public void takeTableSnapshot(String keyspaceName, String tableName, String tag) throws IOException;
/** /**
* @deprecated use {@link #takeSnapshot(String tag, Map options, String... entities)} instead. See CASSANDRA-10907 * Use {@link #takeSnapshot(String tag, Map options, String... entities)} instead.
*
* @deprecated See CASSANDRA-10907
*/ */
@Deprecated(since = "3.4") @Deprecated(since = "3.4")
public void takeMultipleTableSnapshot(String tag, String... tableList) throws IOException; public void takeMultipleTableSnapshot(String tag, String... tableList) throws IOException;
@ -281,13 +283,12 @@ public interface StorageServiceMBean extends NotificationEmitter
/** /**
* Takes the snapshot of a multiple column family from different keyspaces. A snapshot name must be specified. * Takes the snapshot of a multiple column family from different keyspaces. A snapshot name must be specified.
* *
* @param tag * @param tag the tag given to the snapshot; may not be null or empty
* the tag given to the snapshot; may not be null or empty * @param options Map of options (skipFlush is the only supported option for now)
* @param options * @param entities list of keyspaces / tables in the form of empty | ks1 ks2 ... | ks1.cf1,ks2.cf2,...
* Map of options (skipFlush is the only supported option for now) * @deprecated See CASSANDRA-18111
* @param entities
* list of keyspaces / tables in the form of empty | ks1 ks2 ... | ks1.cf1,ks2.cf2,...
*/ */
@Deprecated(since = "5.1")
public void takeSnapshot(String tag, Map<String, String> options, String... entities) throws IOException; public void takeSnapshot(String tag, Map<String, String> options, String... entities) throws IOException;
/** /**
@ -308,7 +309,9 @@ public interface StorageServiceMBean extends NotificationEmitter
* @param options map of options for cleanup operation, consult nodetool's ClearSnapshot * @param options map of options for cleanup operation, consult nodetool's ClearSnapshot
* @param tag name of snapshot to clear, if null or empty string, all snapshots of given keyspace will be cleared * @param tag name of snapshot to clear, if null or empty string, all snapshots of given keyspace will be cleared
* @param keyspaceNames name of keyspaces to clear snapshots for * @param keyspaceNames name of keyspaces to clear snapshots for
* @deprecated See CASSANDRA-18111
*/ */
@Deprecated(since = "5.1")
public void clearSnapshot(Map<String, Object> options, String tag, String... keyspaceNames) throws IOException; public void clearSnapshot(Map<String, Object> options, String tag, String... keyspaceNames) throws IOException;
/** /**
@ -324,13 +327,17 @@ public interface StorageServiceMBean extends NotificationEmitter
* *
* @param options map of options used for filtering of snapshots * @param options map of options used for filtering of snapshots
* @return A map of snapshotName to all its details in Tabular form. * @return A map of snapshotName to all its details in Tabular form.
* @deprecated See CASSANDRA-18111
*/ */
@Deprecated(since = "5.1")
public Map<String, TabularData> getSnapshotDetails(Map<String, String> options); public Map<String, TabularData> getSnapshotDetails(Map<String, String> options);
/** /**
* Get the true size taken by all snapshots across all keyspaces. * Get the true size taken by all snapshots across all keyspaces.
* @return True size taken by all the snapshots. * @return True size taken by all the snapshots.
* @deprecated See CASSANDRA-18111
*/ */
@Deprecated(since = "5.1")
public long trueSnapshotsSize(); public long trueSnapshotsSize();
/** /**
@ -338,7 +345,9 @@ public interface StorageServiceMBean extends NotificationEmitter
* A setting of zero indicates no throttling * A setting of zero indicates no throttling
* *
* @param throttle * @param throttle
* @deprecated See CASSANDRA-18111
*/ */
@Deprecated(since = "5.1")
public void setSnapshotLinksPerSecond(long throttle); public void setSnapshotLinksPerSecond(long throttle);
/** /**
@ -346,7 +355,9 @@ public interface StorageServiceMBean extends NotificationEmitter
* A setting of zero indicates no throttling. * A setting of zero indicates no throttling.
* *
* @return snapshot links-per-second throttle * @return snapshot links-per-second throttle
* @deprecated See CASSANDRA-18111
*/ */
@Deprecated(since = "5.1")
public long getSnapshotLinksPerSecond(); public long getSnapshotLinksPerSecond();
/** /**

View File

@ -0,0 +1,42 @@
/*
* 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.service.snapshot;
import java.util.concurrent.Callable;
public abstract class AbstractSnapshotTask<T> implements Callable<T>
{
public enum SnapshotTaskType
{
SNAPSHOT,
CLEAR,
RELOAD,
LIST,
SIZE
}
protected final SnapshotOptions options;
public AbstractSnapshotTask(SnapshotOptions options)
{
this.options = options;
}
public abstract SnapshotTaskType getTaskType();
}

View File

@ -0,0 +1,196 @@
/*
* 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.service.snapshot;
import java.time.Instant;
import java.time.format.DateTimeParseException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.config.DurationSpec;
import org.apache.cassandra.io.util.File;
import org.apache.cassandra.utils.Clock;
public class ClearSnapshotTask extends AbstractSnapshotTask<Void>
{
private static final Logger logger = LoggerFactory.getLogger(ClearSnapshotTask.class);
private final SnapshotManager manager;
private final Predicate<TableSnapshot> predicateForToBeCleanedSnapshots;
private final boolean deleteData;
public ClearSnapshotTask(SnapshotManager manager,
Predicate<TableSnapshot> predicateForToBeCleanedSnapshots,
boolean deleteData)
{
super(null);
this.manager = manager;
this.predicateForToBeCleanedSnapshots = predicateForToBeCleanedSnapshots;
this.deleteData = deleteData;
}
@Override
public SnapshotTaskType getTaskType()
{
return SnapshotTaskType.CLEAR;
}
@Override
public Void call()
{
Set<TableSnapshot> toRemove = new HashSet<>();
for (TableSnapshot snapshot : new GetSnapshotsTask(manager, predicateForToBeCleanedSnapshots, false).call())
{
logger.debug("Removing snapshot {}{}", snapshot, deleteData ? ", deleting data" : "");
toRemove.add(snapshot);
if (deleteData)
{
for (File snapshotDir : snapshot.getDirectories())
{
try
{
removeSnapshotDirectory(snapshotDir);
}
catch (Throwable ex)
{
logger.warn("Unable to remove snapshot directory {}", snapshotDir, ex);
}
}
}
}
manager.getSnapshots().removeAll(toRemove);
return null;
}
/**
* Returns predicate which will pass the test when arguments match.
*
* @param tag name of snapshot
* @param options options for filtering
* @param keyspaceNames names of keyspaces a snapshot is supposed to be from
* @return predicate which will pass the test when arguments match.
*/
static Predicate<TableSnapshot> getPredicateForCleanedSnapshots(String tag, Map<String, Object> options, String... keyspaceNames)
{
if (options == null)
options = Collections.emptyMap();
Object olderThan = options.get("older_than");
Object olderThanTimestamp = options.get("older_than_timestamp");
long maxCreatedAt = Clock.Global.currentTimeMillis();
if (olderThan != null)
{
assert olderThan instanceof String : "it is expected that older_than is an instance of java.lang.String";
maxCreatedAt -= new DurationSpec.LongSecondsBound((String) olderThan).toMilliseconds();
}
else if (olderThanTimestamp != null)
{
assert olderThanTimestamp instanceof String : "it is expected that older_than_timestamp is an instance of java.lang.String";
try
{
maxCreatedAt = Instant.parse((String) olderThanTimestamp).toEpochMilli();
}
catch (DateTimeParseException ex)
{
throw new RuntimeException("Parameter older_than_timestamp has to be a valid instant in ISO format.");
}
}
return getClearSnapshotPredicate(tag, Set.of(keyspaceNames), maxCreatedAt, false);
}
/**
* Returns a predicate based on which a snapshot will be included for deletion or not.
*
* @param tag name of snapshot to remove
* @param keyspaces keyspaces this snapshot belongs to
* @param olderThanTimestamp clear the snapshot if it is older than given timestamp
* @param includeEphemeral whether to include ephemeral snapshots as well
* @return predicate which filters snapshots on given parameters
*/
static Predicate<TableSnapshot> getClearSnapshotPredicate(String tag,
Set<String> keyspaces,
long olderThanTimestamp,
boolean includeEphemeral)
{
return ts ->
{
// When no tag is supplied, all snapshots must be cleared
boolean clearAll = tag == null || tag.isEmpty();
if (!clearAll && ts.isEphemeral() && !includeEphemeral)
logger.info("Skipping deletion of ephemeral snapshot '{}' in keyspace {}. " +
"Ephemeral snapshots are not removable by a user.",
tag, ts.getKeyspaceName());
boolean passedEphemeralTest = !ts.isEphemeral() || (ts.isEphemeral() && includeEphemeral);
boolean shouldClearTag = clearAll || ts.getTag().equals(tag);
boolean byTimestamp = true;
if (olderThanTimestamp > 0L)
{
Instant createdAt = ts.getCreatedAt();
if (createdAt != null)
byTimestamp = createdAt.isBefore(Instant.ofEpochMilli(olderThanTimestamp));
}
boolean byKeyspace = (keyspaces.isEmpty() || keyspaces.contains(ts.getKeyspaceName()));
return passedEphemeralTest && shouldClearTag && byTimestamp && byKeyspace;
};
}
private void removeSnapshotDirectory(File snapshotDir)
{
if (snapshotDir.exists())
{
logger.trace("Removing snapshot directory {}", snapshotDir);
try
{
snapshotDir.deleteRecursive(DatabaseDescriptor.getSnapshotRateLimiter());
}
catch (RuntimeException ex)
{
if (!snapshotDir.exists())
return; // ignore
throw ex;
}
}
}
@Override
public String toString()
{
return "ClearSnapshotTask{" +
"deleteData=" + deleteData +
'}';
}
}

View File

@ -0,0 +1,99 @@
/*
* 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.service.snapshot;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.function.Predicate;
public class GetSnapshotsTask implements Callable<List<TableSnapshot>>
{
private final SnapshotManager snapshotManager;
private final Predicate<TableSnapshot> predicate;
private final boolean shouldRemoveIfNotExists;
public GetSnapshotsTask(SnapshotManager snapshotManager,
Predicate<TableSnapshot> predicate,
boolean shouldRemoveIfNotExists)
{
this.snapshotManager = snapshotManager;
this.predicate = predicate;
this.shouldRemoveIfNotExists = shouldRemoveIfNotExists;
}
@Override
public List<TableSnapshot> call()
{
if (shouldRemoveIfNotExists)
return getWithRemoval();
else
return getWithoutRemoval();
}
private List<TableSnapshot> getWithRemoval()
{
List<TableSnapshot> notExistingAnymore = new ArrayList<>();
List<TableSnapshot> snapshots = new ArrayList<>();
for (TableSnapshot snapshot : snapshotManager.getSnapshots())
{
if (snapshot.isCompleted() && predicate.test(snapshot))
{
if (!snapshot.hasManifest())
notExistingAnymore.add(snapshot);
else
snapshots.add(snapshot);
}
}
// we do not want to clear snapshots which do not exist periodically
// because that would beat the purpose of caching (we would need to go to the disk
// to see if manifests still exists every time), hence, we will clean on listing,
// we do not need to have cache clean of non-existing snapshots when nobody is looking
if (!notExistingAnymore.isEmpty())
{
snapshotManager.getSnapshots().removeAll(notExistingAnymore);
}
return snapshots;
}
private List<TableSnapshot> getWithoutRemoval()
{
List<TableSnapshot> snapshots = new ArrayList<>();
for (TableSnapshot snapshot : snapshotManager.getSnapshots())
{
if (snapshot.isCompleted() && predicate.test(snapshot))
{
if (snapshot.hasManifest())
snapshots.add(snapshot);
}
}
return snapshots;
}
@Override
public String toString()
{
return "GetSnapshotsTask{" +
"shouldRemoveIfNotExists=" + shouldRemoveIfNotExists +
'}';
}
}

View File

@ -0,0 +1,148 @@
/*
* 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.service.snapshot;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.function.Predicate;
import javax.management.openmbean.TabularData;
import javax.management.openmbean.TabularDataSupport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.db.ColumnFamilyStore;
public class ListSnapshotsTask implements Callable<Map<String, TabularData>>
{
private static final Logger logger = LoggerFactory.getLogger(ListSnapshotsTask.class);
private final SnapshotManager snapshotManager;
private final Predicate<TableSnapshot> predicate;
private final Map<String, String> options;
private final boolean shouldRemoveIfNotExists;
public ListSnapshotsTask(SnapshotManager snapshotManager,
Map<String, String> options,
boolean shouldRemoveIfNotExists)
{
this.snapshotManager = snapshotManager;
this.options = options;
this.predicate = getListingSnapshotsPredicate(options);
this.shouldRemoveIfNotExists = shouldRemoveIfNotExists;
}
private static Predicate<TableSnapshot> getListingSnapshotsPredicate(Map<String, String> options)
{
boolean skipExpiring = options != null && Boolean.parseBoolean(options.getOrDefault("no_ttl", "false"));
boolean includeEphemeral = options != null && Boolean.parseBoolean(options.getOrDefault("include_ephemeral", "false"));
String selectedKeyspace = options != null ? options.get("keyspace") : null;
String selectedTable = options != null ? options.get("table") : null;
String selectedSnapshotName = options != null ? options.get("snapshot") : null;
return s -> {
if (selectedSnapshotName != null && !s.getTag().equals(selectedSnapshotName))
return false;
if (skipExpiring && s.isExpiring())
return false;
if (!includeEphemeral && s.isEphemeral())
return false;
if (selectedKeyspace != null && !s.getKeyspaceName().equals(selectedKeyspace))
return false;
return selectedTable == null || s.getTableName().equals(selectedTable);
};
}
@Override
public Map<String, TabularData> call()
{
List<TableSnapshot> filteredSnapshots;
try
{
filteredSnapshots = new GetSnapshotsTask(snapshotManager, predicate, shouldRemoveIfNotExists).call();
}
catch (Exception e)
{
logger.trace("Unable to get snapshots for listing purposes", e);
return Map.of();
}
Map<String, TabularData> snapshotMap = new HashMap<>();
Set<String> tags = new HashSet<>();
for (TableSnapshot t : filteredSnapshots)
tags.add(t.getTag());
for (String tag : tags)
snapshotMap.put(tag, new TabularDataSupport(SnapshotDetailsTabularData.TABULAR_TYPE));
Map<String, Set<String>> keyspaceTables = new HashMap<>();
for (TableSnapshot s : filteredSnapshots)
{
keyspaceTables.computeIfAbsent(s.getKeyspaceName(), ignore -> new HashSet<>());
keyspaceTables.get(s.getKeyspaceName()).add(s.getTableName());
}
Map<String, Set<String>> cfsFiles = new HashMap<>();
for (Map.Entry<String, Set<String>> entry : keyspaceTables.entrySet())
{
for (String table : entry.getValue())
{
try
{
ColumnFamilyStore cfs = ColumnFamilyStore.getIfExists(entry.getKey(), table);
if (cfs == null)
continue;
cfsFiles.put(cfs.getKeyspaceName() + '.' + cfs.name, cfs.getFilesOfCfs());
}
catch (Throwable t)
{
logger.debug("Unable to get all files of live SSTables for {}.{}", entry.getKey(), entry.getValue());
}
}
}
for (TableSnapshot snapshot : filteredSnapshots)
{
TabularDataSupport data = (TabularDataSupport) snapshotMap.get(snapshot.getTag());
SnapshotDetailsTabularData.from(snapshot, data, cfsFiles.get(snapshot.getKeyspaceTable()));
}
return snapshotMap;
}
@Override
public String toString()
{
return "ListSnapshotsTask{" +
"options=" + options +
'}';
}
}

View File

@ -15,33 +15,33 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.apache.cassandra.db; package org.apache.cassandra.service.snapshot;
import java.util.Set;
import javax.management.openmbean.*; import javax.management.openmbean.*;
import org.apache.cassandra.io.util.FileUtils; import org.apache.cassandra.io.util.FileUtils;
import org.apache.cassandra.service.snapshot.TableSnapshot;
public class SnapshotDetailsTabularData public class SnapshotDetailsTabularData
{ {
private static final String[] ITEM_NAMES = new String[]{"Snapshot name", private static final String[] ITEM_NAMES = new String[]{"Snapshot name",
"Keyspace name", "Keyspace name",
"Column family name", "Column family name",
"True size", "True size",
"Size on disk", "Size on disk",
"Creation time", "Creation time",
"Expiration time", "Expiration time",
"Ephemeral"}; "Ephemeral"};
private static final String[] ITEM_DESCS = new String[]{"snapshot_name", private static final String[] ITEM_DESCS = new String[]{"snapshot_name",
"keyspace_name", "keyspace_name",
"columnfamily_name", "columnfamily_name",
"TrueDiskSpaceUsed", "TrueDiskSpaceUsed",
"TotalDiskSpaceUsed", "TotalDiskSpaceUsed",
"created_at", "created_at",
"expires_at", "expires_at",
"ephemeral"}; "ephemeral"};
private static final String TYPE_NAME = "SnapshotDetails"; private static final String TYPE_NAME = "SnapshotDetails";
@ -70,17 +70,18 @@ public class SnapshotDetailsTabularData
} }
public static void from(TableSnapshot details, TabularDataSupport result) public static void from(TableSnapshot details, TabularDataSupport result, Set<String> files)
{ {
try try
{ {
final String totalSize = FileUtils.stringifyFileSize(details.computeSizeOnDiskBytes()); final String totalSize = FileUtils.stringifyFileSize(details.computeSizeOnDiskBytes());
long trueSizeBytes = details.computeTrueSizeBytes(files);
final String liveSize = FileUtils.stringifyFileSize(details.computeTrueSizeBytes()); final String liveSize = FileUtils.stringifyFileSize(details.computeTrueSizeBytes());
String createdAt = safeToString(details.getCreatedAt()); String createdAt = safeToString(details.getCreatedAt());
String expiresAt = safeToString(details.getExpiresAt()); String expiresAt = safeToString(details.getExpiresAt());
String ephemeral = Boolean.toString(details.isEphemeral()); String ephemeral = Boolean.toString(details.isEphemeral());
result.put(new CompositeDataSupport(COMPOSITE_TYPE, ITEM_NAMES, result.put(new CompositeDataSupport(COMPOSITE_TYPE, ITEM_NAMES,
new Object[]{ details.getTag(), details.getKeyspaceName(), details.getTableName(), liveSize, totalSize, createdAt, expiresAt, ephemeral })); new Object[]{ details.getTag(), details.getKeyspaceName(), details.getTableName(), liveSize, totalSize, createdAt, expiresAt, ephemeral }));
} }
catch (OpenDataException e) catch (OpenDataException e)
{ {
@ -92,4 +93,4 @@ public class SnapshotDetailsTabularData
{ {
return object == null ? null : object.toString(); return object == null ? null : object.toString();
} }
} }

View File

@ -0,0 +1,32 @@
/*
* 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.service.snapshot;
public class SnapshotException extends RuntimeException
{
public SnapshotException(String message)
{
super(message);
}
public SnapshotException(String message, Throwable cause)
{
super(message, cause);
}
}

View File

@ -29,6 +29,7 @@ import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
@ -153,7 +154,7 @@ public class SnapshotLoader
String tag = snapshotDirMatcher.group("tag"); String tag = snapshotDirMatcher.group("tag");
String snapshotId = buildSnapshotId(keyspaceName, tableName, tableId, tag); String snapshotId = buildSnapshotId(keyspaceName, tableName, tableId, tag);
TableSnapshot.Builder builder = snapshots.computeIfAbsent(snapshotId, k -> new TableSnapshot.Builder(keyspaceName, tableName, tableId, tag)); TableSnapshot.Builder builder = snapshots.computeIfAbsent(snapshotId, k -> new TableSnapshot.Builder(keyspaceName, tableName, tableId, tag));
builder.addSnapshotDir(new File(snapshotDir)); builder.addSnapshotDir(new File(snapshotDir).toAbsolute());
} }
} }
@ -184,7 +185,11 @@ public class SnapshotLoader
} }
} }
return snapshots.values().stream().map(TableSnapshot.Builder::build).collect(Collectors.toSet()); Set<TableSnapshot> tableSnapshots = new HashSet<>();
for (TableSnapshot.Builder snapshotBuilder : snapshots.values())
tableSnapshots.add(snapshotBuilder.build());
return tableSnapshots;
} }
public Set<TableSnapshot> loadSnapshots() public Set<TableSnapshot> loadSnapshots()

View File

@ -17,81 +17,213 @@
*/ */
package org.apache.cassandra.service.snapshot; package org.apache.cassandra.service.snapshot;
import java.io.IOException;
import java.util.Collection; import java.time.Instant;
import java.util.PriorityQueue; import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Predicate;
import javax.management.openmbean.TabularData;
import com.google.common.annotations.VisibleForTesting;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.apache.cassandra.concurrent.ScheduledExecutorPlus; import org.apache.cassandra.concurrent.ScheduledExecutorPlus;
import org.apache.cassandra.config.CassandraRelevantProperties; import org.apache.cassandra.config.CassandraRelevantProperties;
import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.db.Directories; import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.notifications.INotification;
import java.util.concurrent.TimeoutException; import org.apache.cassandra.notifications.INotificationConsumer;
import org.apache.cassandra.notifications.TableDroppedNotification;
import com.google.common.annotations.VisibleForTesting; import org.apache.cassandra.notifications.TablePreScrubNotification;
import com.google.common.base.Joiner; import org.apache.cassandra.notifications.TruncationNotification;
import org.apache.cassandra.utils.Clock;
import org.apache.cassandra.io.util.File;
import org.apache.cassandra.utils.ExecutorUtils; import org.apache.cassandra.utils.ExecutorUtils;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.MBeanWrapper;
import static java.util.Comparator.comparing; import static java.lang.String.format;
import static java.util.stream.Collectors.toList; import static java.util.concurrent.TimeUnit.SECONDS;
import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory;
import static org.apache.cassandra.utils.FBUtilities.now; import static org.apache.cassandra.service.snapshot.ClearSnapshotTask.getClearSnapshotPredicate;
import static org.apache.cassandra.service.snapshot.ClearSnapshotTask.getPredicateForCleanedSnapshots;
public class SnapshotManager {
private static final ScheduledExecutorPlus executor = executorFactory().scheduled(false, "SnapshotCleanup");
public class SnapshotManager implements SnapshotManagerMBean, INotificationConsumer, AutoCloseable
{
private static final Logger logger = LoggerFactory.getLogger(SnapshotManager.class); private static final Logger logger = LoggerFactory.getLogger(SnapshotManager.class);
private ScheduledExecutorPlus snapshotCleanupExecutor;
public static final SnapshotManager instance = new SnapshotManager();
private final long initialDelaySeconds; private final long initialDelaySeconds;
private final long cleanupPeriodSeconds; private final long cleanupPeriodSeconds;
private final SnapshotLoader snapshotLoader;
@VisibleForTesting private volatile ScheduledFuture<?> cleanupTaskFuture;
protected volatile ScheduledFuture<?> cleanupTaskFuture;
private final String[] dataDirs;
private volatile boolean started = false;
/** /**
* Expiring snapshots ordered by expiration date, to allow only iterating over snapshots * We read / list snapshots way more often than write / create them so COW is ideal to use here.
* that need to be removed on {@link this#clearExpiredSnapshots()} * This enables us to not submit listing tasks or tasks computing snapshot sizes to any executor's queue as they
* can be just run concurrently which gives way better throughput in case
* of excessive listing from clients (dashboards and similar) where snapshot metrics are gathered.
*/ */
private final PriorityQueue<TableSnapshot> expiringSnapshots = new PriorityQueue<>(comparing(TableSnapshot::getExpiresAt)); private final List<TableSnapshot> snapshots = new CopyOnWriteArrayList<>();
public SnapshotManager() private SnapshotManager()
{ {
this(CassandraRelevantProperties.SNAPSHOT_CLEANUP_INITIAL_DELAY_SECONDS.getInt(), this(CassandraRelevantProperties.SNAPSHOT_CLEANUP_INITIAL_DELAY_SECONDS.getInt(),
CassandraRelevantProperties.SNAPSHOT_CLEANUP_PERIOD_SECONDS.getInt()); CassandraRelevantProperties.SNAPSHOT_CLEANUP_PERIOD_SECONDS.getInt(),
DatabaseDescriptor.getAllDataFileLocations());
} }
@VisibleForTesting @VisibleForTesting
protected SnapshotManager(long initialDelaySeconds, long cleanupPeriodSeconds) SnapshotManager(long initialDelaySeconds, long cleanupPeriodSeconds, String[] dataDirs)
{ {
this.initialDelaySeconds = initialDelaySeconds; this.initialDelaySeconds = initialDelaySeconds;
this.cleanupPeriodSeconds = cleanupPeriodSeconds; this.cleanupPeriodSeconds = cleanupPeriodSeconds;
snapshotLoader = new SnapshotLoader(DatabaseDescriptor.getAllDataFileLocations()); this.dataDirs = dataDirs;
this.snapshotCleanupExecutor = createSnapshotCleanupExecutor();
} }
public Collection<TableSnapshot> getExpiringSnapshots() public void registerMBean()
{ {
return expiringSnapshots; MBeanWrapper.instance.registerMBean(this, MBEAN_NAME);
} }
public synchronized void start() public void unregisterMBean()
{ {
addSnapshots(loadSnapshots()); MBeanWrapper.instance.unregisterMBean(MBEAN_NAME);
resumeSnapshotCleanup();
} }
public synchronized void stop() throws InterruptedException, TimeoutException public synchronized SnapshotManager start(boolean runPeriodicSnapshotCleaner)
{
if (started)
return this;
if (snapshotCleanupExecutor == null)
snapshotCleanupExecutor = createSnapshotCleanupExecutor();
executeTask(new ReloadSnapshotsTask(dataDirs));
if (runPeriodicSnapshotCleaner)
resumeSnapshotCleanup();
started = true;
return this;
}
@Override
public synchronized void close()
{
if (!started)
return;
pauseSnapshotCleanup();
shutdownAndWait(1, TimeUnit.MINUTES);
snapshots.clear();
started = false;
}
public synchronized void shutdownAndWait(long timeout, TimeUnit unit)
{
try
{
ExecutorUtils.shutdownNowAndWait(timeout, unit, snapshotCleanupExecutor);
}
catch (InterruptedException | TimeoutException ex)
{
throw new RuntimeException(ex);
}
finally
{
snapshotCleanupExecutor = null;
}
}
public synchronized void restart(boolean runPeriodicSnapshotCleaner)
{
if (!started)
return;
logger.debug("Restarting SnapshotManager");
close();
start(runPeriodicSnapshotCleaner);
logger.debug("SnapshotManager restarted");
}
public synchronized void restart()
{
restart(true);
}
private static class ReloadSnapshotsTask extends AbstractSnapshotTask<Set<TableSnapshot>>
{
private final String[] dataDirs;
public ReloadSnapshotsTask(String[] dataDirs)
{
super(null);
this.dataDirs = dataDirs;
}
@Override
public Set<TableSnapshot> call()
{
Set<TableSnapshot> tableSnapshots = new SnapshotLoader(dataDirs).loadSnapshots();
new ClearSnapshotTask(SnapshotManager.instance, snapshot -> true, false).call();
for (TableSnapshot snapshot : tableSnapshots)
SnapshotManager.instance.addSnapshot(snapshot);
return tableSnapshots;
}
@Override
public SnapshotTaskType getTaskType()
{
return SnapshotTaskType.RELOAD;
}
}
void addSnapshot(TableSnapshot snapshot)
{
logger.debug("Adding snapshot {}", snapshot);
snapshots.add(snapshot);
}
List<TableSnapshot> getSnapshots()
{
return snapshots;
}
public void resumeSnapshotCleanup()
{
if (cleanupTaskFuture == null)
{
logger.info("Scheduling expired snapshots cleanup with initialDelaySeconds={} and cleanupPeriodSeconds={}",
initialDelaySeconds, cleanupPeriodSeconds);
cleanupTaskFuture = snapshotCleanupExecutor.scheduleWithFixedDelay(SnapshotManager.instance::clearExpiredSnapshots,
initialDelaySeconds,
cleanupPeriodSeconds,
SECONDS);
}
}
private void pauseSnapshotCleanup()
{ {
expiringSnapshots.clear();
if (cleanupTaskFuture != null) if (cleanupTaskFuture != null)
{ {
cleanupTaskFuture.cancel(false); cleanupTaskFuture.cancel(false);
@ -99,74 +231,350 @@ public class SnapshotManager {
} }
} }
public synchronized void addSnapshot(TableSnapshot snapshot) /**
* Deletes snapshot and removes it from manager.
*
* @param snapshot snapshot to clear
*/
void clearSnapshot(TableSnapshot snapshot)
{ {
// We currently only care about expiring snapshots executeTask(new ClearSnapshotTask(this, s -> s.equals(snapshot), true));
if (snapshot.isExpiring()) }
/**
* Returns list of snapshots of given keyspace
*
* @param keyspace keyspace of a snapshot
* @return list of snapshots of given keyspace.
*/
public List<TableSnapshot> getSnapshots(String keyspace)
{
return getSnapshots(snapshot -> snapshot.getKeyspaceName().equals(keyspace));
}
/**
* Return snapshots based on given parameters.
*
* @param skipExpiring if expiring snapshots should be skipped
* @param includeEphemeral if ephemeral snapshots should be included
* @return snapshots based on given parameters
*/
public List<TableSnapshot> getSnapshots(boolean skipExpiring, boolean includeEphemeral)
{
return getSnapshots(s -> (!skipExpiring || !s.isExpiring()) && (includeEphemeral || !s.isEphemeral()));
}
/**
* Returns all snapshots passing the given predicate.
*
* @param predicate predicate to filter all snapshots of
* @return list of snapshots passing the predicate
*/
public List<TableSnapshot> getSnapshots(Predicate<TableSnapshot> predicate)
{
return new GetSnapshotsTask(this, predicate, true).call();
}
/**
* Returns a snapshot or empty optional based on the given parameters.
*
* @param keyspace keyspace of a snapshot
* @param table table of a snapshot
* @param tag name of a snapshot
* @return empty optional if there is not such snapshot, non-empty otherwise
*/
public Optional<TableSnapshot> getSnapshot(String keyspace, String table, String tag)
{
List<TableSnapshot> foundSnapshots = new GetSnapshotsTask(this,
snapshot -> snapshot.getKeyspaceName().equals(keyspace) &&
snapshot.getTableName().equals(table) &&
snapshot.getTag().equals(tag) || (tag != null && tag.isEmpty()),
true).call();
if (foundSnapshots.isEmpty())
return Optional.empty();
else
return Optional.of(foundSnapshots.get(0));
}
/**
* Checks whether a snapshot for given keyspace and table exists of a given name exists.
*
* @param keyspace keyspace to get a snapshot of
* @param table table to get a snapshot of
* @param tag name of a snapshot
* @return true of a snapshot of given properties exist, false otherwise
*/
public boolean exists(String keyspace, String table, String tag)
{
return getSnapshot(keyspace, table, tag).isPresent();
}
/**
* Checks whether a snapshot which satisfies given predicate exists.
*
* @param predicate predicate to check the existence of a snapshot
* @return true if a snapshot which satisfies a predicate exists, false otherwise
*/
public boolean exists(Predicate<TableSnapshot> predicate)
{
return !getSnapshots(predicate).isEmpty();
}
/**
* Clear snapshots of given tag from given keyspace. Does not remove ephemeral snapshots.
* <p>
*
* @param tag snapshot name
* @param keyspace keyspace to clear all snapshots of a given tag of
*/
public void clearSnapshots(String tag, String keyspace)
{
clearSnapshots(tag, Set.of(keyspace), Clock.Global.currentTimeMillis());
}
/**
* Removes a snapshot.
* <p>
*
* @param keyspace keyspace of a snapshot to remove
* @param table table of a snapshot to remove
* @param tag name of a snapshot to remove.
*/
public void clearSnapshot(String keyspace, String table, String tag)
{
executeTask(new ClearSnapshotTask(this,
snapshot -> snapshot.getKeyspaceName().equals(keyspace)
&& snapshot.getTableName().equals(table)
&& snapshot.getTag().equals(tag),
true));
}
/**
* Removes all snapshots for given keyspace and table.
*
* @param keyspace keyspace to remove snapshots for
* @param table table in a given keyspace to remove snapshots for
*/
public void clearAllSnapshots(String keyspace, String table)
{
executeTask(new ClearSnapshotTask(this,
snapshot -> snapshot.getKeyspaceName().equals(keyspace)
&& snapshot.getTableName().equals(table),
true));
}
/**
* Clears all snapshots, expiring and ephemeral as well.
*/
public void clearAllSnapshots()
{
executeTask(new ClearSnapshotTask(this, snapshot -> true, true));
}
/**
* Clear snapshots based on a given predicate
*
* @param predicate predicate to filter snapshots on
*/
public void clearSnapshot(Predicate<TableSnapshot> predicate)
{
executeTask(new ClearSnapshotTask(this, predicate, true));
}
/**
* Clears all ephemeral snapshots in a node.
*/
public void clearEphemeralSnapshots()
{
executeTask(new ClearSnapshotTask(this, TableSnapshot::isEphemeral, true));
}
/**
* Clears all expired snapshots in a node.
*/
public void clearExpiredSnapshots()
{
Instant now = FBUtilities.now();
executeTask(new ClearSnapshotTask(this, s -> s.isExpired(now), true));
}
/**
* Clear snapshots of given tag from given keyspaces.
* <p>
* If tag is not present / is empty, all snapshots are considered to be cleared.
* If keyspaces are empty, all snapshots of given tag and older than maxCreatedAt are removed.
*
* @param tag optional tag of snapshot to clear
* @param keyspaces keyspaces to remove snapshots for
* @param maxCreatedAt clear all such snapshots which were created before this timestamp
*/
private void clearSnapshots(String tag, Set<String> keyspaces, long maxCreatedAt)
{
executeTask(new ClearSnapshotTask(this, getClearSnapshotPredicate(tag, keyspaces, maxCreatedAt, false), true));
}
public List<TableSnapshot> takeSnapshot(SnapshotOptions options)
{
return executeTask(new TakeSnapshotTask(this, options));
}
// Super methods
@Override
public void takeSnapshot(String tag, String... entities)
{
takeSnapshot(SnapshotOptions.userSnapshot(tag, Map.of(), entities));
}
@Override
public void takeSnapshot(String tag, Map<String, String> optMap, String... entities) throws IOException
{
try
{ {
logger.debug("Adding expiring snapshot {}", snapshot); takeSnapshot(SnapshotOptions.userSnapshot(tag, optMap, entities));
expiringSnapshots.add(snapshot); }
catch (SnapshotException ex)
{
// to be compatible with deprecated methods in StorageService
throw new IOException(ex);
} }
} }
public synchronized Set<TableSnapshot> loadSnapshots(String keyspace) @Override
public void clearSnapshot(String tag, Map<String, Object> options, String... keyspaceNames)
{ {
return snapshotLoader.loadSnapshots(keyspace); executeTask(new ClearSnapshotTask(this, getPredicateForCleanedSnapshots(tag, options, keyspaceNames), true));
} }
public synchronized Set<TableSnapshot> loadSnapshots() @Override
public Map<String, TabularData> listSnapshots(Map<String, String> options)
{ {
return snapshotLoader.loadSnapshots(); return new ListSnapshotsTask(this, options, true).call();
} }
@VisibleForTesting @Override
protected synchronized void addSnapshots(Collection<TableSnapshot> snapshots) public long getTrueSnapshotSize()
{ {
logger.debug("Adding snapshots: {}.", Joiner.on(", ").join(snapshots.stream().map(TableSnapshot::getId).collect(toList()))); return new TrueSnapshotSizeTask(this, s -> true).call();
snapshots.forEach(this::addSnapshot);
} }
// TODO: Support pausing snapshot cleanup @Override
@VisibleForTesting public long getTrueSnapshotsSize(String keyspace)
synchronized void resumeSnapshotCleanup()
{ {
if (cleanupTaskFuture == null) return new TrueSnapshotSizeTask(this, s -> s.getKeyspaceName().equals(keyspace)).call();
}
@Override
public long getTrueSnapshotsSize(String keyspace, String table)
{
return new TrueSnapshotSizeTask(this, s -> s.getKeyspaceName().equals(keyspace) && s.getTableName().equals(table)).call();
}
@Override
public void setSnapshotLinksPerSecond(long throttle)
{
logger.info("Setting snapshot throttle to {}", throttle);
DatabaseDescriptor.setSnapshotLinksPerSecond(throttle);
}
@Override
public long getSnapshotLinksPerSecond()
{
return DatabaseDescriptor.getSnapshotLinksPerSecond();
}
@Override
public void handleNotification(INotification notification, Object sender)
{
if (notification instanceof TruncationNotification)
{ {
logger.info("Scheduling expired snapshot cleanup with initialDelaySeconds={} and cleanupPeriodSeconds={}", TruncationNotification truncationNotification = (TruncationNotification) notification;
initialDelaySeconds, cleanupPeriodSeconds); ColumnFamilyStore cfs = truncationNotification.cfs;
cleanupTaskFuture = executor.scheduleWithFixedDelay(this::clearExpiredSnapshots, initialDelaySeconds, if (!truncationNotification.disableSnapshot && cfs.isAutoSnapshotEnabled())
cleanupPeriodSeconds, TimeUnit.SECONDS); {
SnapshotOptions opts = SnapshotOptions.systemSnapshot(cfs.name, SnapshotType.TRUNCATE, cfs.getKeyspaceTableName())
.ttl(truncationNotification.ttl).build();
takeSnapshot(opts);
}
}
else if (notification instanceof TableDroppedNotification)
{
TableDroppedNotification tableDroppedNotification = (TableDroppedNotification) notification;
ColumnFamilyStore cfs = tableDroppedNotification.cfs;
if (cfs.isAutoSnapshotEnabled())
{
SnapshotOptions opts = SnapshotOptions.systemSnapshot(cfs.name, SnapshotType.DROP, cfs.getKeyspaceTableName())
.cfs(cfs).ttl(tableDroppedNotification.ttl).build();
takeSnapshot(opts);
}
}
else if (notification instanceof TablePreScrubNotification)
{
ColumnFamilyStore cfs = ((TablePreScrubNotification) notification).cfs;
SnapshotOptions opts = SnapshotOptions.systemSnapshot(cfs.name, SnapshotType.PRE_SCRUB, cfs.getKeyspaceTableName()).build();
takeSnapshot(opts);
} }
} }
@VisibleForTesting @VisibleForTesting
protected synchronized void clearExpiredSnapshots() List<TableSnapshot> executeTask(TakeSnapshotTask task)
{ {
TableSnapshot expiredSnapshot; try
while ((expiredSnapshot = expiringSnapshots.peek()) != null)
{ {
if (!expiredSnapshot.isExpired(now())) prePopulateSnapshots(task);
break; // the earliest expiring snapshot is not expired yet, so there is no more expired snapshots to remove return task.call();
}
catch (Throwable t)
{
throw new SnapshotException(String.format("Exception occured while executing %s: %s", task.toString(), t.getMessage()), t);
}
}
logger.debug("Removing expired snapshot {}.", expiredSnapshot); @VisibleForTesting
clearSnapshot(expiredSnapshot); <T> T executeTask(AbstractSnapshotTask<T> task)
{
try
{
return task.call();
}
catch (Throwable t)
{
throw new SnapshotException(String.format("Exception occured while executing %s", task.toString()), t);
} }
} }
/** /**
* Deletes snapshot and remove it from manager * Add table snapshots to snaphots cow list in advance in order to be sure that the snapshots to be created
* are not existing already. "executeTask" method can be invoked by multiple threads, and they hit
* this synchronized method which populates snapshots so next thread will fail to advance (exception is thrown)
* if table snapshots of some other tasks are already present in snapshots cow list.
*
* Added snapshots to snapshot list are at this point "incomplete", they will not appear in listing output
* until they are indeed taken and exist on disk, otherwise we would see "phantom" snapshots in listings,
* they would be present but in fact they are just going through the process of being taken.
*
* @param task task to process
*/ */
public synchronized void clearSnapshot(TableSnapshot snapshot) private synchronized void prePopulateSnapshots(TakeSnapshotTask task)
{ {
for (File snapshotDir : snapshot.getDirectories()) Map<ColumnFamilyStore, TableSnapshot> snapshotsToCreate = task.getSnapshotsToCreate();
Directories.removeSnapshotDirectory(DatabaseDescriptor.getSnapshotRateLimiter(), snapshotDir); for (Map.Entry<ColumnFamilyStore, TableSnapshot> toCreateEntry : snapshotsToCreate.entrySet())
{
if (snapshots.contains(toCreateEntry.getValue()))
{
throw new RuntimeException(format("Snapshot %s for %s.%s already exists.",
toCreateEntry.getValue().getTag(),
toCreateEntry.getValue().getKeyspaceName(),
toCreateEntry.getValue().getTableName()));
}
}
expiringSnapshots.remove(snapshot); snapshots.addAll(snapshotsToCreate.values());
} }
@VisibleForTesting private static ScheduledExecutorPlus createSnapshotCleanupExecutor()
public static void shutdownAndWait(long timeout, TimeUnit unit) throws InterruptedException, TimeoutException
{ {
ExecutorUtils.shutdownNowAndWait(timeout, unit, executor); return executorFactory().scheduled(false, "SnapshotCleanup");
} }
} }

View File

@ -0,0 +1,109 @@
/*
* 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.service.snapshot;
import java.io.IOException;
import java.util.Map;
import javax.management.openmbean.TabularData;
public interface SnapshotManagerMBean
{
String MBEAN_NAME = "org.apache.cassandra.service.snapshot:type=SnapshotManager";
/**
* Takes the snapshot of a multiple column family from different keyspaces. A snapshot name must be specified.
*
* @param tag the tag given to the snapshot; may not be null or empty
* @param options Map of options (skipFlush is the only supported option for now)
* @param entities list of keyspaces / tables in the form of empty | ks1 ks2 ... | ks1.cf1,ks2.cf2,...
*/
void takeSnapshot(String tag, Map<String, String> options, String... entities) throws IOException;
/**
* Takes the snapshot of a multiple column family from different keyspaces. A snapshot name must be specified.
*
* @param tag the tag given to the snapshot; may not be null or empty
* @param entities list of keyspaces / tables in the form of empty | ks1 ks2 ... | ks1.cf1,ks2.cf2,...
*/
void takeSnapshot(String tag, String... entities) throws IOException;
/**
* Remove the snapshot with the given name from the given keyspaces.
* If no tag is specified we will remove all snapshots.
*
* @param tag name of snapshot to clear, if null or empty string,
* all snapshots of given keyspace will be cleared
* @param options map of options for cleanup operation, consult nodetool's ClearSnapshot
* @param keyspaceNames name of keyspaces to clear snapshots for
*/
void clearSnapshot(String tag, Map<String, Object> options, String... keyspaceNames) throws IOException;
/**
* Get the details of all the snapshots
*
* @param options map of options used for filtering of snapshots
* @return A map of snapshotName to all its details in Tabular form.
*/
Map<String, TabularData> listSnapshots(Map<String, String> options);
/**
* Get the true size taken by all snapshots across all keyspaces.
*
* @return True size taken by all the snapshots.
*/
long getTrueSnapshotSize();
/**
* Get the true size take by all snapshots in given keyspace.
*
* @param keyspace keyspace to get true size of all snapshots of
* @return true size of all snapshots in given keyspace
*/
long getTrueSnapshotsSize(String keyspace);
/**
* Get the true size take by all snapshots in given keyspace and table.
*
* @param keyspace keyspace to get true size of all snapshots of
* @param table table in a keyspace to get true size of all snapshots of
* @return true size of all snapshots in given keyspace and table
*/
long getTrueSnapshotsSize(String keyspace, String table);
/**
* Set the current hardlink-per-second throttle for snapshots
* A setting of zero indicates no throttling
*
* @param throttle hard-links-per-second
*/
void setSnapshotLinksPerSecond(long throttle);
/**
* Get the current hardlink-per-second throttle for snapshots
* A setting of zero indicates no throttling.
*
* @return snapshot links-per-second throttle
*/
long getSnapshotLinksPerSecond();
/**
* Restarting means that snapshots will be reloaded from disk.
*/
void restart();
}

View File

@ -63,7 +63,7 @@ public class SnapshotManifest
{ {
this.files = files; this.files = files;
this.createdAt = creationTime; this.createdAt = creationTime;
this.expiresAt = ttl == null ? null : createdAt.plusSeconds(ttl.toSeconds()); this.expiresAt = computeExpiration(ttl, creationTime);
this.ephemeral = ephemeral; this.ephemeral = ephemeral;
} }
@ -97,6 +97,11 @@ public class SnapshotManifest
return JsonUtils.deserializeFromJsonFile(SnapshotManifest.class, file); return JsonUtils.deserializeFromJsonFile(SnapshotManifest.class, file);
} }
public static Instant computeExpiration(DurationSpec.IntSecondsBound ttl, Instant createdAt)
{
return ttl == null ? null : createdAt.plusSeconds(ttl.toSeconds());
}
@Override @Override
public boolean equals(Object o) public boolean equals(Object o)
{ {

View File

@ -0,0 +1,235 @@
/*
* 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.service.snapshot;
import java.time.Instant;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import java.util.function.Predicate;
import com.google.common.util.concurrent.RateLimiter;
import org.apache.commons.lang3.StringUtils;
import org.apache.cassandra.config.CassandraRelevantProperties;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.config.DurationSpec;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import static java.lang.String.format;
public class SnapshotOptions
{
public static final String SKIP_FLUSH = "skipFlush";
public static final String TTL = "ttl";
public final SnapshotType type;
public final String tag;
public final DurationSpec.IntSecondsBound ttl;
public final Instant creationTime;
public final boolean skipFlush;
public final boolean ephemeral;
public final String[] entities;
public final RateLimiter rateLimiter;
public final Predicate<SSTableReader> sstableFilter;
public final ColumnFamilyStore cfs;
private SnapshotOptions(SnapshotType type,
String tag,
DurationSpec.IntSecondsBound ttl,
Instant creationTime,
boolean skipFlush,
boolean ephemeral,
String[] entities,
RateLimiter rateLimiter,
Predicate<SSTableReader> sstableFilter,
ColumnFamilyStore cfs)
{
this.type = type;
this.tag = tag;
this.ttl = ttl;
this.creationTime = creationTime;
this.skipFlush = skipFlush;
this.ephemeral = ephemeral;
this.entities = entities;
this.rateLimiter = rateLimiter;
this.sstableFilter = sstableFilter;
this.cfs = cfs;
}
public static Builder systemSnapshot(String tag, SnapshotType type, String... entities)
{
return new Builder(tag, type, ssTableReader -> true, entities);
}
public static Builder systemSnapshot(String tag, SnapshotType type, Predicate<SSTableReader> sstableFilter, String... entities)
{
return new Builder(tag, type, sstableFilter, entities);
}
public static SnapshotOptions userSnapshot(String tag, String... entities)
{
return userSnapshot(tag, Collections.emptyMap(), entities);
}
public static SnapshotOptions userSnapshot(String tag, Map<String, String> options, String... entities)
{
Builder builder = new Builder(tag, SnapshotType.USER, ssTableReader -> true, entities).ttl(options.get(TTL));
if (Boolean.parseBoolean(options.getOrDefault(SKIP_FLUSH, Boolean.FALSE.toString())))
builder.skipFlush();
return builder.build();
}
public String getSnapshotName(Instant creationTime)
{
// Diagnostic snapshots have very specific naming convention hence we are keeping it.
// Repair snapshots rely on snapshots having name of their repair session ids
if (type == SnapshotType.USER || type == SnapshotType.DIAGNOSTICS || type == SnapshotType.REPAIR)
return tag;
// System snapshots have the creation timestamp on the name
String snapshotName = format("%d-%s", creationTime.toEpochMilli(), type.label);
if (StringUtils.isNotBlank(tag))
snapshotName = snapshotName + '-' + tag;
return snapshotName;
}
public static class Builder
{
private final String tag;
private final String[] entities;
private DurationSpec.IntSecondsBound ttl;
private Instant creationTime;
private boolean skipFlush = false;
private boolean ephemeral = false;
private ColumnFamilyStore cfs;
private final Predicate<SSTableReader> sstableFilter;
private final SnapshotType type;
private RateLimiter rateLimiter;
public Builder(String tag, SnapshotType type, Predicate<SSTableReader> sstableFilter, String... entities)
{
this.tag = tag;
this.type = type;
this.entities = entities;
this.sstableFilter = sstableFilter;
}
public Builder ttl(String ttl)
{
if (ttl != null)
this.ttl = new DurationSpec.IntSecondsBound(ttl);
return this;
}
public Builder ttl(DurationSpec.IntSecondsBound ttl)
{
this.ttl = ttl;
return this;
}
public Builder creationTime(String creationTime)
{
if (creationTime != null)
{
try
{
return creationTime(Long.parseLong(creationTime));
}
catch (Exception ex)
{
throw new RuntimeException("Unable to parse creation time from " + creationTime);
}
}
return this;
}
public Builder creationTime(Instant creationTime)
{
this.creationTime = creationTime;
return this;
}
public Builder creationTime(long creationTime)
{
return creationTime(Instant.ofEpochMilli(creationTime));
}
public Builder skipFlush()
{
skipFlush = true;
return this;
}
public Builder ephemeral()
{
ephemeral = true;
return this;
}
public Builder cfs(ColumnFamilyStore cfs)
{
this.cfs = cfs;
return this;
}
public Builder rateLimiter(RateLimiter rateLimiter)
{
this.rateLimiter = rateLimiter;
return this;
}
public SnapshotOptions build()
{
if (tag == null || tag.isEmpty())
throw new RuntimeException("You must supply a snapshot name.");
if (ttl != null)
{
int minAllowedTtlSecs = CassandraRelevantProperties.SNAPSHOT_MIN_ALLOWED_TTL_SECONDS.getInt();
if (ttl.toSeconds() < minAllowedTtlSecs)
throw new IllegalArgumentException(format("ttl for snapshot must be at least %d seconds", minAllowedTtlSecs));
}
if (ephemeral && ttl != null)
throw new IllegalStateException(format("can not take ephemeral snapshot (%s) while ttl is specified too", tag));
if (rateLimiter == null)
rateLimiter = DatabaseDescriptor.getSnapshotRateLimiter();
return new SnapshotOptions(type, tag, ttl, creationTime, skipFlush, ephemeral, entities, rateLimiter,
sstableFilter, cfs);
}
}
@Override
public String toString()
{
return "CreateSnapshotOptions{" +
"type=" + type +
", tag='" + tag + '\'' +
", ttl=" + ttl +
", creationTime=" + creationTime +
", skipFlush=" + skipFlush +
", ephemeral=" + ephemeral +
", entities=" + Arrays.toString(entities) +
'}';
}
}

View File

@ -0,0 +1,42 @@
/*
* 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.service.snapshot;
public enum SnapshotType
{
USER("user"),
TRUNCATE("truncated"),
DROP("dropped"),
PRE_SCRUB("pre-scrub"),
COMPACT("compact"),
UPGRADE("upgrade"),
DIAGNOSTICS("diagnostics"),
REPAIR("repair"),
MISC("misc");
public final String label;
SnapshotType(String label)
{
this.label = label;
}
}

View File

@ -21,21 +21,33 @@ import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.time.Instant; import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.function.Predicate; import java.util.stream.Stream;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.Directories; import org.apache.cassandra.db.Directories;
import org.apache.cassandra.db.Keyspace;
import org.apache.cassandra.db.lifecycle.SSTableSet;
import org.apache.cassandra.io.FSReadError;
import org.apache.cassandra.io.sstable.Component;
import org.apache.cassandra.io.sstable.Descriptor;
import org.apache.cassandra.io.sstable.SSTableId;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.io.util.File; import org.apache.cassandra.io.util.File;
import org.apache.cassandra.io.util.FileUtils; import org.apache.cassandra.io.util.FileUtils;
import org.apache.cassandra.utils.DirectorySizeCalculator; import org.apache.cassandra.utils.Throwables;
import org.apache.cassandra.utils.concurrent.Refs;
public class TableSnapshot public class TableSnapshot
{ {
@ -43,6 +55,7 @@ public class TableSnapshot
private final String keyspaceName; private final String keyspaceName;
private final String tableName; private final String tableName;
private final String keyspaceTable;
private final UUID tableId; private final UUID tableId;
private final String tag; private final String tag;
private final boolean ephemeral; private final boolean ephemeral;
@ -52,25 +65,51 @@ public class TableSnapshot
private final Set<File> snapshotDirs; private final Set<File> snapshotDirs;
private volatile long sizeOnDisk = 0;
private volatile long manifestsSize;
private volatile long schemasSize;
private volatile boolean inProgress = false;
public TableSnapshot(String keyspaceName, String tableName, UUID tableId, public TableSnapshot(String keyspaceName, String tableName, UUID tableId,
String tag, Instant createdAt, Instant expiresAt, String tag, Instant createdAt, Instant expiresAt,
Set<File> snapshotDirs, boolean ephemeral) Set<File> snapshotDirs, boolean ephemeral)
{ {
this.keyspaceName = keyspaceName; this.keyspaceName = keyspaceName;
this.tableName = tableName; this.tableName = tableName;
this.keyspaceTable = keyspaceName + '.' + tableName;
this.tableId = tableId; this.tableId = tableId;
this.tag = tag; this.tag = tag;
this.createdAt = createdAt; this.createdAt = createdAt;
this.expiresAt = expiresAt; this.expiresAt = expiresAt;
this.snapshotDirs = snapshotDirs; this.snapshotDirs = snapshotDirs;
this.ephemeral = ephemeral; this.ephemeral = ephemeral;
manifestsSize = getManifestsSize();
schemasSize = getSchemasSize();
}
public boolean isCompleted()
{
return !inProgress;
}
public void incomplete()
{
inProgress = true;
}
public void complete()
{
inProgress = false;
} }
/** /**
* Unique identifier of a snapshot. Used * Unique identifier of a snapshot. Used
* only to deduplicate snapshots internally, * only to deduplicate snapshots internally,
* not exposed externally. * not exposed externally.
* * <p>
* Format: "$ks:$table_name:$table_id:$tag" * Format: "$ks:$table_name:$table_id:$tag"
*/ */
public String getId() public String getId()
@ -88,6 +127,11 @@ public class TableSnapshot
return tableName; return tableName;
} }
public String getKeyspaceTable()
{
return keyspaceTable;
}
public String getTag() public String getTag()
{ {
return tag; return tag;
@ -95,13 +139,22 @@ public class TableSnapshot
public Instant getCreatedAt() public Instant getCreatedAt()
{ {
if (createdAt == null) if (createdAt == null)
{ {
long minCreation = snapshotDirs.stream().mapToLong(File::lastModified).min().orElse(0); long minCreation = 0;
if (minCreation != 0) for (File snapshotDir : snapshotDirs)
{ {
return Instant.ofEpochMilli(minCreation); long lastModified = snapshotDir.lastModified();
if (lastModified == 0)
continue;
if (minCreation == 0 || minCreation > lastModified)
minCreation = lastModified;
} }
if (minCreation != 0)
return Instant.ofEpochMilli(minCreation);
} }
return createdAt; return createdAt;
} }
@ -123,7 +176,11 @@ public class TableSnapshot
public boolean exists() public boolean exists()
{ {
return snapshotDirs.stream().anyMatch(File::exists); for (File snapshotDir : snapshotDirs)
if (snapshotDir.exists())
return true;
return false;
} }
public boolean isEphemeral() public boolean isEphemeral()
@ -138,26 +195,106 @@ public class TableSnapshot
public long computeSizeOnDiskBytes() public long computeSizeOnDiskBytes()
{ {
return snapshotDirs.stream().mapToLong(FileUtils::folderSize).sum(); long sum = sizeOnDisk;
if (sum == 0)
{
for (File snapshotDir : snapshotDirs)
sum += FileUtils.folderSize(snapshotDir);
sizeOnDisk = sum;
}
return sum;
} }
public long computeTrueSizeBytes() public long computeTrueSizeBytes()
{ {
DirectorySizeCalculator visitor = new SnapshotTrueSizeCalculator(); ColumnFamilyStore cfs = ColumnFamilyStore.getIfExists(keyspaceName, tableName);
if (cfs == null)
return 0;
for (File snapshotDir : snapshotDirs) return computeTrueSizeBytes(cfs.getFilesOfCfs());
}
public long computeTrueSizeBytes(Set<String> files)
{
long size = manifestsSize + schemasSize;
for (File dataPath : getDirectories())
{ {
try List<Path> snapshotFiles = listDir(dataPath.toPath());
for (Path snapshotFile : snapshotFiles)
{ {
Files.walkFileTree(snapshotDir.toPath(), visitor); if (!snapshotFile.endsWith("manifest.json") && !snapshotFile.endsWith("schema.cql"))
} {
catch (IOException e) // files == null means that the underlying table was most probably dropped
{ // so in that case we indeed go to count snapshots file in for true size
logger.error("Could not calculate the size of {}.", snapshotDir, e); if (files == null || (!files.contains(getLiveFileFromSnapshotFile(snapshotFile))))
size += getFileSize(snapshotFile);
}
} }
} }
return visitor.getAllocatedSize(); return size;
}
private List<Path> listDir(Path dir)
{
List<Path> paths = new ArrayList<>();
try (Stream<Path> stream = Files.list(dir))
{
stream.forEach(p -> {
if (p.getFileName().toString().startsWith("."))
{
paths.addAll(listDir(p));
}
else
{
paths.add(p);
}
});
}
catch (Throwable t)
{
logger.error("Could not list directory content {}", dir, t);
}
return paths;
}
private long getFileSize(Path file)
{
try
{
return Files.size(file);
}
catch (Throwable t)
{
return 0;
}
}
/**
* Returns the corresponding live file for a given snapshot file.
* <p>
* Example:
* - Base table:
* - Snapshot file: ~/.ccm/test/node1/data0/test_ks/tbl-e03faca0813211eca100c705ea09b5ef/snapshots/1643481737850/me-1-big-Data.db
* - Live file: ~/.ccm/test/node1/data0/test_ks/tbl-e03faca0813211eca100c705ea09b5ef/me-1-big-Data.db
* - Secondary index:
* - Snapshot file: ~/.ccm/test/node1/data0/test_ks/tbl-e03faca0813211eca100c705ea09b5ef/snapshots/1643481737850/.tbl_val_idx/me-1-big-Summary.db
* - Live file: ~/.ccm/test/node1/data0/test_ks/tbl-e03faca0813211eca100c705ea09b5ef/.tbl_val_idx/me-1-big-Summary.db
*/
static String getLiveFileFromSnapshotFile(Path snapshotFilePath)
{
// Snapshot directory structure format is {data_dir}/snapshots/{snapshot_name}/{snapshot_file}
Path liveDir = snapshotFilePath.getParent().getParent().getParent();
if (Directories.isSecondaryIndexFolder(snapshotFilePath.getParent()))
{
// Snapshot file structure format is {data_dir}/snapshots/{snapshot_name}/.{index}/{sstable-component}.db
liveDir = File.getPath(liveDir.getParent().toString(), snapshotFilePath.getParent().getFileName().toString());
}
return liveDir.resolve(snapshotFilePath.getFileName().toString()).toAbsolutePath().toString();
} }
public Collection<File> getDirectories() public Collection<File> getDirectories()
@ -165,48 +302,88 @@ public class TableSnapshot
return snapshotDirs; return snapshotDirs;
} }
public Optional<File> getManifestFile() /**
* Returns all manifest files of a snapshot.
* <p>
* In practice, there might be multiple manifest files, as many as we have snapshot dirs.
* Each snapshot dir will hold its view of a snapshot, containing only sstables located in such snapshot dir.
*
* @return all manifest files
*/
public Set<File> getManifestFiles()
{ {
Set<File> manifestFiles = new HashSet<>();
for (File snapshotDir : snapshotDirs) for (File snapshotDir : snapshotDirs)
{ {
File manifestFile = Directories.getSnapshotManifestFile(snapshotDir); File manifestFile = Directories.getSnapshotManifestFile(snapshotDir);
if (manifestFile.exists()) if (manifestFile.exists())
{ manifestFiles.add(manifestFile);
return Optional.of(manifestFile);
}
} }
return Optional.empty(); return manifestFiles;
} }
public Optional<File> getSchemaFile() public boolean hasManifest()
{ {
for (File snapshotDir : snapshotDirs)
{
if (Directories.getSnapshotManifestFile(snapshotDir).exists())
return true;
}
return false;
}
/**
* Returns all schemas files of a snapshot.
*
* @return all schema files
*/
public Set<File> getSchemaFiles()
{
Set<File> schemaFiles = new HashSet<>();
for (File snapshotDir : snapshotDirs) for (File snapshotDir : snapshotDirs)
{ {
File schemaFile = Directories.getSnapshotSchemaFile(snapshotDir); File schemaFile = Directories.getSnapshotSchemaFile(snapshotDir);
if (schemaFile.exists()) if (schemaFile.exists())
{ schemaFiles.add(schemaFile);
return Optional.of(schemaFile);
}
} }
return Optional.empty(); return schemaFiles;
} }
public long getManifestsSize()
{
long size = 0;
for (File manifestFile : getManifestFiles())
size += manifestFile.length();
return size;
}
public long getSchemasSize()
{
long size = 0;
for (File schemaFile : getSchemaFiles())
size += schemaFile.length();
return size;
}
@Override @Override
public boolean equals(Object o) public boolean equals(Object o)
{ {
if (this == o) return true; if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false; if (o == null || getClass() != o.getClass()) return false;
TableSnapshot snapshot = (TableSnapshot) o; TableSnapshot snapshot = (TableSnapshot) o;
return Objects.equals(keyspaceName, snapshot.keyspaceName) && Objects.equals(tableName, snapshot.tableName) && return Objects.equals(keyspaceName, snapshot.keyspaceName) &&
Objects.equals(tableId, snapshot.tableId) && Objects.equals(tag, snapshot.tag) && Objects.equals(tableName, snapshot.tableName) &&
Objects.equals(createdAt, snapshot.createdAt) && Objects.equals(expiresAt, snapshot.expiresAt) && Objects.equals(tableId, snapshot.tableId) &&
Objects.equals(snapshotDirs, snapshot.snapshotDirs) && Objects.equals(ephemeral, snapshot.ephemeral); Objects.equals(tag, snapshot.tag);
} }
@Override @Override
public int hashCode() public int hashCode()
{ {
return Objects.hash(keyspaceName, tableName, tableId, tag, createdAt, expiresAt, snapshotDirs, ephemeral); return Objects.hash(keyspaceName, tableName, tableId, tag);
} }
@Override @Override
@ -224,7 +401,16 @@ public class TableSnapshot
'}'; '}';
} }
static class Builder { public void updateMetadataSize()
{
if (manifestsSize == 0)
manifestsSize = getManifestsSize();
if (schemasSize == 0)
schemasSize = getSchemasSize();
}
static class Builder
{
private final String keyspaceName; private final String keyspaceName;
private final String tableName; private final String tableName;
private final UUID tableId; private final UUID tableId;
@ -287,66 +473,82 @@ public class TableSnapshot
return String.format("%s:%s:%s:%s", keyspaceName, tableName, tableId, tag); return String.format("%s:%s:%s:%s", keyspaceName, tableName, tableId, tag);
} }
public static class SnapshotTrueSizeCalculator extends DirectorySizeCalculator public static Set<Descriptor> getSnapshotDescriptors(String keyspace, String table, String tag)
{ {
/** try
* Snapshots are composed of hard-linked sstables. The true snapshot size should only include
* snapshot files which do not contain a corresponding "live" sstable file.
*/
@Override
public boolean isAcceptable(Path snapshotFilePath)
{ {
return !getLiveFileFromSnapshotFile(snapshotFilePath).exists(); Refs<SSTableReader> snapshotSSTableReaders = getSnapshotSSTableReaders(keyspace, table, tag);
}
}
/** Set<Descriptor> descriptors = new HashSet<>();
* Returns the corresponding live file for a given snapshot file. for (SSTableReader ssTableReader : snapshotSSTableReaders)
*
* Example:
* - Base table:
* - Snapshot file: ~/.ccm/test/node1/data0/test_ks/tbl-e03faca0813211eca100c705ea09b5ef/snapshots/1643481737850/me-1-big-Data.db
* - Live file: ~/.ccm/test/node1/data0/test_ks/tbl-e03faca0813211eca100c705ea09b5ef/me-1-big-Data.db
* - Secondary index:
* - Snapshot file: ~/.ccm/test/node1/data0/test_ks/tbl-e03faca0813211eca100c705ea09b5ef/snapshots/1643481737850/.tbl_val_idx/me-1-big-Summary.db
* - Live file: ~/.ccm/test/node1/data0/test_ks/tbl-e03faca0813211eca100c705ea09b5ef/.tbl_val_idx/me-1-big-Summary.db
*
*/
static File getLiveFileFromSnapshotFile(Path snapshotFilePath)
{
// Snapshot directory structure format is {data_dir}/snapshots/{snapshot_name}/{snapshot_file}
Path liveDir = snapshotFilePath.getParent().getParent().getParent();
if (Directories.isSecondaryIndexFolder(snapshotFilePath.getParent()))
{
// Snapshot file structure format is {data_dir}/snapshots/{snapshot_name}/.{index}/{sstable-component}.db
liveDir = File.getPath(liveDir.getParent().toString(), snapshotFilePath.getParent().getFileName().toString());
}
return new File(liveDir.toString(), snapshotFilePath.getFileName().toString());
}
public static Predicate<TableSnapshot> shouldClearSnapshot(String tag, long olderThanTimestamp)
{
return ts ->
{
// When no tag is supplied, all snapshots must be cleared
boolean clearAll = tag == null || tag.isEmpty();
if (!clearAll && ts.isEphemeral())
logger.info("Skipping deletion of ephemeral snapshot '{}' in keyspace {}. " +
"Ephemeral snapshots are not removable by a user.",
tag, ts.keyspaceName);
boolean notEphemeral = !ts.isEphemeral();
boolean shouldClearTag = clearAll || ts.tag.equals(tag);
boolean byTimestamp = true;
if (olderThanTimestamp > 0L)
{ {
Instant createdAt = ts.getCreatedAt(); descriptors.add(ssTableReader.descriptor);
if (createdAt != null)
byTimestamp = createdAt.isBefore(Instant.ofEpochMilli(olderThanTimestamp));
} }
return notEphemeral && shouldClearTag && byTimestamp; return descriptors;
}; }
catch (IOException e)
{
throw Throwables.unchecked(e);
}
} }
public static Refs<SSTableReader> getSnapshotSSTableReaders(String keyspace, String table, String tag) throws IOException
{
return getSnapshotSSTableReaders(Keyspace.open(keyspace).getColumnFamilyStore(table), tag);
}
public static Refs<SSTableReader> getSnapshotSSTableReaders(ColumnFamilyStore cfs, String tag)
{
Map<SSTableId, SSTableReader> active = new HashMap<>();
for (SSTableReader sstable : cfs.getSSTables(SSTableSet.CANONICAL))
active.put(sstable.descriptor.id, sstable);
Map<Descriptor, Set<Component>> snapshots = cfs.getDirectories().sstableLister(Directories.OnTxnErr.IGNORE).snapshots(tag).list();
Refs<SSTableReader> refs = new Refs<>();
try
{
for (Map.Entry<Descriptor, Set<Component>> entries : snapshots.entrySet())
{
// Try to acquire reference to an active sstable instead of snapshot if it exists,
// to avoid opening new sstables. If it fails, use the snapshot reference instead.
SSTableReader sstable = active.get(entries.getKey().id);
if (sstable == null || !refs.tryRef(sstable))
{
if (logger.isTraceEnabled())
logger.trace("using snapshot sstable {}", entries.getKey());
// open offline so we don't modify components or track hotness.
sstable = SSTableReader.open(cfs, entries.getKey(), entries.getValue(), cfs.metadata, true, true);
refs.tryRef(sstable);
// release the self ref as we never add the snapshot sstable to DataTracker where it is otherwise released
sstable.selfRef().release();
}
else if (logger.isTraceEnabled())
{
logger.trace("using active sstable {}", entries.getKey());
}
}
}
catch (FSReadError | RuntimeException e)
{
// In case one of the snapshot sstables fails to open,
// we must release the references to the ones we opened so far
refs.release();
throw e;
}
return refs;
}
public static String getTimestampedSnapshotName(String clientSuppliedName, long timestamp)
{
String snapshotName = Long.toString(timestamp);
if (clientSuppliedName != null && !clientSuppliedName.isEmpty())
snapshotName = snapshotName + '-' + clientSuppliedName;
return snapshotName;
}
public static String getTimestampedSnapshotNameWithPrefix(String clientSuppliedName, long timestamp, String prefix)
{
return prefix + '-' + getTimestampedSnapshotName(clientSuppliedName, timestamp);
}
} }

View File

@ -0,0 +1,310 @@
/*
* 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.service.snapshot;
import java.io.IOException;
import java.io.PrintStream;
import java.time.Instant;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.Directories;
import org.apache.cassandra.db.Keyspace;
import org.apache.cassandra.db.SchemaCQLHelper;
import org.apache.cassandra.db.lifecycle.SSTableSet;
import org.apache.cassandra.db.lifecycle.View;
import org.apache.cassandra.db.memtable.Memtable;
import org.apache.cassandra.index.Index;
import org.apache.cassandra.index.internal.CassandraIndex;
import org.apache.cassandra.io.FSWriteError;
import org.apache.cassandra.io.sstable.format.SSTableFormat;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.io.util.File;
import org.apache.cassandra.io.util.FileOutputStreamPlus;
import org.apache.cassandra.schema.SchemaConstants;
import org.apache.cassandra.service.StorageService;
import org.apache.cassandra.utils.Clock;
import org.apache.cassandra.utils.FBUtilities;
public class TakeSnapshotTask extends AbstractSnapshotTask<List<TableSnapshot>>
{
private static final Logger logger = LoggerFactory.getLogger(TakeSnapshotTask.class);
private final SnapshotManager manager;
private Instant creationTime;
private String snapshotName;
private final Map<ColumnFamilyStore, TableSnapshot> snapshotsToCreate = new HashMap<>();
public TakeSnapshotTask(SnapshotManager manager, SnapshotOptions options)
{
super(options);
this.manager = manager;
}
@Override
public SnapshotTaskType getTaskType()
{
return SnapshotTaskType.SNAPSHOT;
}
public Map<ColumnFamilyStore, TableSnapshot> getSnapshotsToCreate()
{
if (StorageService.instance.operationMode() == StorageService.Mode.JOINING)
throw new RuntimeException("Cannot snapshot until bootstrap completes");
// This is not in builder's build method on purpose in order to postpone the timestamp for as long as possible
// until the actual snapshot is taken. If we constructed a task and have not done anything with it for 5 minutes
// then by the time a snapshot would be taken the creation time would be quite off
Instant creationTimeInOptions = options.creationTime;
if (creationTimeInOptions == null)
creationTime = Instant.ofEpochMilli(Clock.Global.currentTimeMillis());
else
creationTime = options.creationTime;
snapshotName = options.getSnapshotName(creationTime);
Set<ColumnFamilyStore> entitiesForSnapshot = options.cfs == null ? parseEntitiesForSnapshot(options.entities) : Set.of(options.cfs);
for (ColumnFamilyStore cfs : entitiesForSnapshot)
{
Set<File> snapshotDirs = cfs.getDirectories().getSnapshotDirs(snapshotName);
TableSnapshot tableSnapshot = new TableSnapshot(cfs.metadata.keyspace,
cfs.metadata.name,
cfs.metadata.id.asUUID(),
snapshotName,
creationTime,
SnapshotManifest.computeExpiration(options.ttl, creationTime),
snapshotDirs,
options.ephemeral);
// this snapshot does not have any actual representation on disk
// because that snapshot was technically not taken yet
tableSnapshot.incomplete();
snapshotsToCreate.put(cfs, tableSnapshot);
}
return snapshotsToCreate;
}
@Override
public List<TableSnapshot> call()
{
assert snapshotName != null : "You need to call getSnapshotsToCreate() first";
List<TableSnapshot> createdSnapshots = new ArrayList<>();
for (Map.Entry<ColumnFamilyStore, TableSnapshot> entry : snapshotsToCreate.entrySet())
{
try
{
ColumnFamilyStore cfs = entry.getKey();
if (!options.skipFlush)
{
Memtable current = cfs.getTracker().getView().getCurrentMemtable();
if (!current.isClean())
{
if (current.shouldSwitch(ColumnFamilyStore.FlushReason.SNAPSHOT))
FBUtilities.waitOnFuture(cfs.switchMemtableIfCurrent(current, ColumnFamilyStore.FlushReason.SNAPSHOT));
else
current.performSnapshot(snapshotName);
}
}
createSnapshot(cfs, entry.getValue(), snapshotName, creationTime);
createdSnapshots.add(entry.getValue());
}
catch (Throwable t)
{
logger.warn(String.format("Unable to create snapshot %s for %s", entry.getValue().getTag(), entry.getKey().getKeyspaceTableName()), t);
// if we fail to take a snapshot, there is its phantom / in-progress representation among manager's taken snapshots,
// so we need to remove it from there to not appear in the outputs / not leak
manager.getSnapshots().remove(entry.getValue());
}
}
return createdSnapshots;
}
private void createSnapshot(ColumnFamilyStore cfs, TableSnapshot snapshotToCreate, String snapshotName, Instant creationTime)
{
Predicate<SSTableReader> predicate = options.sstableFilter;
Set<SSTableReader> sstables = new LinkedHashSet<>();
for (ColumnFamilyStore aCfs : cfs.concatWithIndexes())
{
try (ColumnFamilyStore.RefViewFragment currentView = aCfs.selectAndReference(View.select(SSTableSet.CANONICAL, (x) -> predicate == null || predicate.test(x))))
{
for (SSTableReader ssTable : currentView.sstables)
{
File snapshotDirectory = Directories.getSnapshotDirectory(ssTable.descriptor, snapshotName);
ssTable.createLinks(snapshotDirectory.path(), options.rateLimiter); // hard links
if (logger.isTraceEnabled())
logger.trace("Snapshot for {} keyspace data file {} created in {}", cfs.keyspace, ssTable.getFilename(), snapshotDirectory);
sstables.add(ssTable);
}
}
}
List<String> dataComponents = new ArrayList<>();
for (SSTableReader sstable : sstables)
dataComponents.add(sstable.descriptor.relativeFilenameFor(SSTableFormat.Components.DATA));
SnapshotManifest manifest = new SnapshotManifest(dataComponents, options.ttl, creationTime, options.ephemeral);
Set<File> snapshotDirs = cfs.getDirectories().getSnapshotDirs(snapshotName);
for (File snapshotDir : snapshotDirs)
{
writeSnapshotManifest(manifest, Directories.getSnapshotManifestFile(snapshotDir));
if (!SchemaConstants.isLocalSystemKeyspace(cfs.metadata.keyspace)
&& !SchemaConstants.isReplicatedSystemKeyspace(cfs.metadata.keyspace))
{
writeSnapshotSchema(Directories.getSnapshotSchemaFile(snapshotDir), cfs);
}
}
snapshotToCreate.updateMetadataSize();
snapshotToCreate.complete();
}
private Set<ColumnFamilyStore> parseEntitiesForSnapshot(String... entities)
{
Set<ColumnFamilyStore> entitiesForSnapshot = new HashSet<>();
if (entities != null && entities.length > 0 && entities[0].contains("."))
{
for (String entity : entities)
{
String[] splitted = StringUtils.split(entity, '.');
if (splitted.length == 2)
{
String keyspaceName = splitted[0];
String tableName = splitted[1];
if (keyspaceName == null)
throw new RuntimeException("You must supply a keyspace name");
if (tableName == null)
throw new RuntimeException("You must supply a table name");
Keyspace validKeyspace = Keyspace.getValidKeyspace(keyspaceName);
ColumnFamilyStore existingTable = validKeyspace.getColumnFamilyStore(tableName);
entitiesForSnapshot.add(existingTable);
}
// special case for index which we can not normally create a snapshot for
// but a snapshot is apparently taken before a secondary index is scrubbed,
// so we preserve this behavior
else if (splitted.length == 3)
{
String keyspaceName = splitted[0];
String tableName = splitted[1];
Keyspace validKeyspace = Keyspace.getValidKeyspace(keyspaceName);
ColumnFamilyStore existingTable = validKeyspace.getColumnFamilyStore(tableName);
Index indexByName = existingTable.indexManager.getIndexByName(splitted[2]);
if (indexByName instanceof CassandraIndex)
{
ColumnFamilyStore indexCfs = ((CassandraIndex) indexByName).getIndexCfs();
entitiesForSnapshot.add(indexCfs);
}
else
{
throw new IllegalArgumentException("Unknown index " + entity);
}
}
else
{
throw new IllegalArgumentException("Cannot take a snapshot on secondary index or invalid column " +
"family name. You must supply a column family name in the " +
"form of keyspace.columnfamily");
}
}
}
else
{
if (entities != null && entities.length == 0)
{
for (Keyspace keyspace : Keyspace.all())
{
entitiesForSnapshot.addAll(keyspace.getColumnFamilyStores());
}
}
else if (entities != null)
{
for (String keyspace : entities)
{
Keyspace validKeyspace = Keyspace.getValidKeyspace(keyspace);
entitiesForSnapshot.addAll(validKeyspace.getColumnFamilyStores());
}
}
}
return entitiesForSnapshot;
}
private void writeSnapshotManifest(SnapshotManifest manifest, File manifestFile)
{
try
{
manifestFile.parent().tryCreateDirectories();
manifest.serializeToJsonFile(manifestFile);
}
catch (IOException e)
{
throw new FSWriteError(e, manifestFile);
}
}
private void writeSnapshotSchema(File schemaFile, ColumnFamilyStore cfs)
{
try
{
if (!schemaFile.parent().exists())
schemaFile.parent().tryCreateDirectories();
try (PrintStream out = new PrintStream(new FileOutputStreamPlus(schemaFile)))
{
SchemaCQLHelper.reCreateStatementsForSchemaCql(cfs.metadata(), cfs.keyspace.getMetadata())
.forEach(out::println);
}
}
catch (IOException e)
{
throw new FSWriteError(e, schemaFile);
}
}
@Override
public String toString()
{
return "TakeSnapshotTask{" +
"options=" + options +
'}';
}
}

View File

@ -0,0 +1,76 @@
/*
* 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.service.snapshot;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.function.Predicate;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.Keyspace;
public class TrueSnapshotSizeTask implements Callable<Long>
{
private final SnapshotManager snapshotManager;
private final Predicate<TableSnapshot> predicate;
public TrueSnapshotSizeTask(SnapshotManager snapshotManager, Predicate<TableSnapshot> predicate)
{
this.snapshotManager = snapshotManager;
this.predicate = predicate;
}
@Override
public Long call()
{
long size = 0;
for (TableSnapshot snapshot : snapshotManager.getSnapshots())
{
if (predicate.test(snapshot))
try
{
size += snapshot.computeTrueSizeBytes(getTablesFiles(snapshot.getKeyspaceName(), snapshot.getTableName()));
}
catch (Throwable ex)
{
// if any error happens while computing size, we don't include
return 0L;
}
}
return size;
}
private Set<String> getTablesFiles(String keyspaceName, String tableName)
{
try
{
Keyspace keyspace = Keyspace.getValidKeyspace(keyspaceName);
ColumnFamilyStore table = keyspace.getColumnFamilyStore(tableName);
return table.getFilesOfCfs();
}
catch (IllegalArgumentException ex)
{
// keyspace / table not found
return null;
}
}
}

View File

@ -102,6 +102,7 @@ import org.apache.cassandra.net.MessagingServiceMBean;
import org.apache.cassandra.service.ActiveRepairServiceMBean; import org.apache.cassandra.service.ActiveRepairServiceMBean;
import org.apache.cassandra.service.CacheService; import org.apache.cassandra.service.CacheService;
import org.apache.cassandra.service.CacheServiceMBean; import org.apache.cassandra.service.CacheServiceMBean;
import org.apache.cassandra.service.snapshot.SnapshotManagerMBean;
import org.apache.cassandra.tcm.CMSOperationsMBean; import org.apache.cassandra.tcm.CMSOperationsMBean;
import org.apache.cassandra.service.GCInspector; import org.apache.cassandra.service.GCInspector;
import org.apache.cassandra.service.GCInspectorMXBean; import org.apache.cassandra.service.GCInspectorMXBean;
@ -150,6 +151,7 @@ public class NodeProbe implements AutoCloseable
protected MBeanServerConnection mbeanServerConn; protected MBeanServerConnection mbeanServerConn;
protected CompactionManagerMBean compactionProxy; protected CompactionManagerMBean compactionProxy;
protected StorageServiceMBean ssProxy; protected StorageServiceMBean ssProxy;
protected SnapshotManagerMBean snapshotProxy;
protected CMSOperationsMBean cmsProxy; protected CMSOperationsMBean cmsProxy;
protected GossiperMBean gossProxy; protected GossiperMBean gossProxy;
protected MemoryMXBean memProxy; protected MemoryMXBean memProxy;
@ -263,6 +265,8 @@ public class NodeProbe implements AutoCloseable
{ {
ObjectName name = new ObjectName(ssObjName); ObjectName name = new ObjectName(ssObjName);
ssProxy = JMX.newMBeanProxy(mbeanServerConn, name, StorageServiceMBean.class); ssProxy = JMX.newMBeanProxy(mbeanServerConn, name, StorageServiceMBean.class);
name = new ObjectName(SnapshotManagerMBean.MBEAN_NAME);
snapshotProxy = JMX.newMBeanProxy(mbeanServerConn, name, SnapshotManagerMBean.class);
name = new ObjectName(CMSOperations.MBEAN_OBJECT_NAME); name = new ObjectName(CMSOperations.MBEAN_OBJECT_NAME);
cmsProxy = JMX.newMBeanProxy(mbeanServerConn, name, CMSOperationsMBean.class); cmsProxy = JMX.newMBeanProxy(mbeanServerConn, name, CMSOperationsMBean.class);
name = new ObjectName(MessagingService.MBEAN_NAME); name = new ObjectName(MessagingService.MBEAN_NAME);
@ -875,12 +879,12 @@ public class NodeProbe implements AutoCloseable
public long getSnapshotLinksPerSecond() public long getSnapshotLinksPerSecond()
{ {
return ssProxy.getSnapshotLinksPerSecond(); return snapshotProxy.getSnapshotLinksPerSecond();
} }
public void setSnapshotLinksPerSecond(long throttle) public void setSnapshotLinksPerSecond(long throttle)
{ {
ssProxy.setSnapshotLinksPerSecond(throttle); snapshotProxy.setSnapshotLinksPerSecond(throttle);
} }
/** /**
@ -900,10 +904,10 @@ public class NodeProbe implements AutoCloseable
throw new IOException("When specifying the table for a snapshot, you must specify one and only one keyspace"); throw new IOException("When specifying the table for a snapshot, you must specify one and only one keyspace");
} }
ssProxy.takeSnapshot(snapshotName, options, keyspaces[0] + "." + table); snapshotProxy.takeSnapshot(snapshotName, options, keyspaces[0] + "." + table);
} }
else else
ssProxy.takeSnapshot(snapshotName, options, keyspaces); snapshotProxy.takeSnapshot(snapshotName, options, keyspaces);
} }
/** /**
@ -921,11 +925,11 @@ public class NodeProbe implements AutoCloseable
{ {
if (null != tableList && tableList.length != 0) if (null != tableList && tableList.length != 0)
{ {
ssProxy.takeSnapshot(snapshotName, options, tableList); snapshotProxy.takeSnapshot(snapshotName, options, tableList);
} }
else else
{ {
throw new IOException("The column family List for a snapshot should not be empty or null"); throw new IOException("The column family list for a snapshot should not be empty or null");
} }
} }
@ -955,12 +959,20 @@ public class NodeProbe implements AutoCloseable
*/ */
public void clearSnapshot(Map<String, Object> options, String tag, String... keyspaces) throws IOException public void clearSnapshot(Map<String, Object> options, String tag, String... keyspaces) throws IOException
{ {
ssProxy.clearSnapshot(options, tag, keyspaces); snapshotProxy.clearSnapshot(tag, options, keyspaces);
} }
/**
* Gets all snapshots' details.
*
* @param options options to use upon listing of snapshots
* @return details of snapshots
*/
/** @deprecated See CASSANDRA-18111 */
@Deprecated(since = "5.1")
public Map<String, TabularData> getSnapshotDetails(Map<String, String> options) public Map<String, TabularData> getSnapshotDetails(Map<String, String> options)
{ {
return ssProxy.getSnapshotDetails(options); return snapshotProxy.listSnapshots(options);
} }
/** @deprecated See CASSANDRA-16789 */ /** @deprecated See CASSANDRA-16789 */
@ -972,7 +984,7 @@ public class NodeProbe implements AutoCloseable
public long trueSnapshotsSize() public long trueSnapshotsSize()
{ {
return ssProxy.trueSnapshotsSize(); return snapshotProxy.getTrueSnapshotSize();
} }
public boolean isJoined() public boolean isJoined()

View File

@ -95,4 +95,4 @@ public class ListSnapshots extends NodeToolCmd
throw new RuntimeException("Error during list snapshot", e); throw new RuntimeException("Error during list snapshot", e);
} }
} }
} }

View File

@ -29,6 +29,7 @@ import io.airlift.airline.Command;
import io.airlift.airline.Option; import io.airlift.airline.Option;
import org.apache.cassandra.config.DurationSpec; import org.apache.cassandra.config.DurationSpec;
import org.apache.cassandra.io.util.File; import org.apache.cassandra.io.util.File;
import org.apache.cassandra.service.snapshot.SnapshotOptions;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.NodeTool.NodeToolCmd; import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
@ -68,10 +69,10 @@ public class Snapshot extends NodeToolCmd
sb.append("Requested creating snapshot(s) for "); sb.append("Requested creating snapshot(s) for ");
Map<String, String> options = new HashMap<String,String>(); Map<String, String> options = new HashMap<String,String>();
options.put("skipFlush", Boolean.toString(skipFlush)); options.put(SnapshotOptions.SKIP_FLUSH, Boolean.toString(skipFlush));
if (null != ttl) { if (null != ttl) {
DurationSpec.LongNanosecondsBound d = new DurationSpec.LongNanosecondsBound(ttl); DurationSpec.LongNanosecondsBound d = new DurationSpec.LongNanosecondsBound(ttl);
options.put("ttl", d.toString()); options.put(SnapshotOptions.TTL, d.toString());
} }
if (!snapshotName.isEmpty() && snapshotName.contains(File.pathSeparator())) if (!snapshotName.isEmpty() && snapshotName.contains(File.pathSeparator()))
@ -83,16 +84,16 @@ public class Snapshot extends NodeToolCmd
{ {
ktList = ktList.replace(" ", ""); ktList = ktList.replace(" ", "");
if (keyspaces.isEmpty() && null == table) if (keyspaces.isEmpty() && null == table)
sb.append("[").append(ktList).append("]"); sb.append('[').append(ktList).append(']');
else else
{ {
throw new IOException( throw new IOException(
"When specifying the Keyspace table list (using -kt,--kt-list,-kc,--kc.list), you must not also specify keyspaces to snapshot"); "When specifying the Keyspace table list (using -kt,--kt-list,-kc,--kc.list), you must not also specify keyspaces to snapshot");
} }
if (!snapshotName.isEmpty()) if (!snapshotName.isEmpty())
sb.append(" with snapshot name [").append(snapshotName).append("]"); sb.append(" with snapshot name [").append(snapshotName).append(']');
sb.append(" and options ").append(options.toString()); sb.append(" and options ").append(options);
out.println(sb.toString()); out.println(sb);
probe.takeMultipleTableSnapshot(snapshotName, options, ktList.split(",")); probe.takeMultipleTableSnapshot(snapshotName, options, ktList.split(","));
out.println("Snapshot directory: " + snapshotName); out.println("Snapshot directory: " + snapshotName);
} }
@ -101,12 +102,12 @@ public class Snapshot extends NodeToolCmd
if (keyspaces.isEmpty()) if (keyspaces.isEmpty())
sb.append("[all keyspaces]"); sb.append("[all keyspaces]");
else else
sb.append("[").append(join(keyspaces, ", ")).append("]"); sb.append('[').append(join(keyspaces, ", ")).append(']');
if (!snapshotName.isEmpty()) if (!snapshotName.isEmpty())
sb.append(" with snapshot name [").append(snapshotName).append("]"); sb.append(" with snapshot name [").append(snapshotName).append(']');
sb.append(" and options ").append(options.toString()); sb.append(" and options ").append(options);
out.println(sb.toString()); out.println(sb);
probe.takeSnapshot(snapshotName, table, options, toArray(keyspaces, String.class)); probe.takeSnapshot(snapshotName, table, options, toArray(keyspaces, String.class));
out.println("Snapshot directory: " + snapshotName); out.println("Snapshot directory: " + snapshotName);

View File

@ -27,6 +27,7 @@ import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Predicate;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
@ -39,12 +40,16 @@ import org.apache.cassandra.db.SnapshotCommand;
import org.apache.cassandra.dht.Bounds; import org.apache.cassandra.dht.Bounds;
import org.apache.cassandra.dht.Range; import org.apache.cassandra.dht.Range;
import org.apache.cassandra.dht.Token; import org.apache.cassandra.dht.Token;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.locator.InetAddressAndPort; import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.net.Message; import org.apache.cassandra.net.Message;
import org.apache.cassandra.net.MessagingService; import org.apache.cassandra.net.MessagingService;
import org.apache.cassandra.net.Verb; import org.apache.cassandra.net.Verb;
import org.apache.cassandra.schema.TableId; import org.apache.cassandra.schema.TableId;
import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.snapshot.SnapshotManager;
import org.apache.cassandra.service.snapshot.SnapshotOptions;
import org.apache.cassandra.service.snapshot.SnapshotType;
import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory;
import static org.apache.cassandra.config.CassandraRelevantProperties.DIAGNOSTIC_SNAPSHOT_INTERVAL_NANOS; import static org.apache.cassandra.config.CassandraRelevantProperties.DIAGNOSTIC_SNAPSHOT_INTERVAL_NANOS;
@ -194,7 +199,8 @@ public class DiagnosticSnapshotService
} }
ColumnFamilyStore cfs = ks.getColumnFamilyStore(command.column_family); ColumnFamilyStore cfs = ks.getColumnFamilyStore(command.column_family);
if (cfs.snapshotExists(command.snapshot_name))
if (SnapshotManager.instance.exists(command.keyspace, command.column_family, command.snapshot_name))
{ {
logger.info("Received diagnostic snapshot request from {} for {}.{}, " + logger.info("Received diagnostic snapshot request from {} for {}.{}, " +
"but snapshot with tag {} already exists", "but snapshot with tag {} already exists",
@ -210,16 +216,14 @@ public class DiagnosticSnapshotService
command.column_family, command.column_family,
command.snapshot_name); command.snapshot_name);
if (ranges.isEmpty()) Predicate<SSTableReader> predicate = null;
cfs.snapshot(command.snapshot_name); if (!ranges.isEmpty())
else predicate = (sstable) -> checkIntersection(ranges,
{ sstable.getFirst().getToken(),
cfs.snapshot(command.snapshot_name, sstable.getLast().getToken());
(sstable) -> checkIntersection(ranges,
sstable.getFirst().getToken(), SnapshotOptions options = SnapshotOptions.systemSnapshot(command.snapshot_name, SnapshotType.DIAGNOSTICS, predicate, cfs.getKeyspaceTableName()).build();
sstable.getLast().getToken()), SnapshotManager.instance.takeSnapshot(options);
false, false);
}
} }
catch (IllegalArgumentException e) catch (IllegalArgumentException e)
{ {

View File

@ -42,7 +42,7 @@ public class DirectorySizeCalculator extends SimpleFileVisitor<Path>
} }
@Override @Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
{ {
if (isAcceptable(file)) if (isAcceptable(file))
size += attrs.size(); size += attrs.size();
@ -63,7 +63,7 @@ public class DirectorySizeCalculator extends SimpleFileVisitor<Path>
/** /**
* Reset the size to 0 in case that the size calculator is used multiple times * Reset the size to 0 in case that the size calculator is used multiple times
*/ */
protected void resetSize() public void resetSize()
{ {
size = 0; size = 0;
} }

View File

@ -632,7 +632,6 @@ public class Instance extends IsolatedExecutor implements IInvokableInstance
{ {
partialStartup(cluster); partialStartup(cluster);
} }
StorageService.instance.startSnapshotManager();
} }
catch (Throwable t) catch (Throwable t)
{ {
@ -719,6 +718,12 @@ public class Instance extends IsolatedExecutor implements IInvokableInstance
CassandraDaemon.getInstanceForTesting().migrateSystemDataIfNeeded(); CassandraDaemon.getInstanceForTesting().migrateSystemDataIfNeeded();
CommitLog.instance.start(); CommitLog.instance.start();
SnapshotManager.instance.start(false);
SnapshotManager.instance.clearExpiredSnapshots();
SnapshotManager.instance.clearEphemeralSnapshots();
SnapshotManager.instance.resumeSnapshotCleanup();
CassandraDaemon.getInstanceForTesting().runStartupChecks(); CassandraDaemon.getInstanceForTesting().runStartupChecks();
Keyspace.setInitialized(); // TODO: this seems to be superfluous by now Keyspace.setInitialized(); // TODO: this seems to be superfluous by now
@ -891,6 +896,8 @@ public class Instance extends IsolatedExecutor implements IInvokableInstance
Future<?> future = async((ExecutorService executor) -> { Future<?> future = async((ExecutorService executor) -> {
Throwable error = null; Throwable error = null;
error = parallelRun(error, executor, SnapshotManager.instance::close);
CompactionManager.instance.forceShutdown(); CompactionManager.instance.forceShutdown();
error = parallelRun(error, executor, error = parallelRun(error, executor,
@ -945,7 +952,7 @@ public class Instance extends IsolatedExecutor implements IInvokableInstance
() -> shutdownAndWait(Collections.singletonList(ActiveRepairService.repairCommandExecutor())), () -> shutdownAndWait(Collections.singletonList(ActiveRepairService.repairCommandExecutor())),
() -> ActiveRepairService.instance().shutdownNowAndWait(1L, MINUTES), () -> ActiveRepairService.instance().shutdownNowAndWait(1L, MINUTES),
() -> EpochAwareDebounce.instance.close(), () -> EpochAwareDebounce.instance.close(),
() -> SnapshotManager.shutdownAndWait(1L, MINUTES) SnapshotManager.instance::close
); );
internodeMessagingStarted = false; internodeMessagingStarted = false;

View File

@ -44,6 +44,7 @@ import org.apache.cassandra.service.CacheServiceMBean;
import org.apache.cassandra.service.GCInspector; import org.apache.cassandra.service.GCInspector;
import org.apache.cassandra.service.StorageProxy; import org.apache.cassandra.service.StorageProxy;
import org.apache.cassandra.service.StorageService; import org.apache.cassandra.service.StorageService;
import org.apache.cassandra.service.snapshot.SnapshotManager;
import org.apache.cassandra.streaming.StreamManager; import org.apache.cassandra.streaming.StreamManager;
import org.apache.cassandra.tcm.CMSOperations; import org.apache.cassandra.tcm.CMSOperations;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
@ -69,6 +70,7 @@ public class InternalNodeProbe extends NodeProbe
StorageService.instance.skipNotificationListeners = !withNotifications; StorageService.instance.skipNotificationListeners = !withNotifications;
ssProxy = StorageService.instance; ssProxy = StorageService.instance;
snapshotProxy = SnapshotManager.instance;
cmsProxy = CMSOperations.instance; cmsProxy = CMSOperations.instance;
msProxy = MessagingService.instance(); msProxy = MessagingService.instance();
streamProxy = StreamManager.instance; streamProxy = StreamManager.instance;

View File

@ -27,7 +27,7 @@ import org.junit.Test;
import org.apache.cassandra.distributed.Cluster; import org.apache.cassandra.distributed.Cluster;
import org.apache.cassandra.distributed.api.IIsolatedExecutor; import org.apache.cassandra.distributed.api.IIsolatedExecutor;
import org.apache.cassandra.service.StorageService; import org.apache.cassandra.service.snapshot.SnapshotManager;
import static org.apache.cassandra.distributed.Cluster.build; import static org.apache.cassandra.distributed.Cluster.build;
import static org.apache.cassandra.distributed.api.ConsistencyLevel.ALL; import static org.apache.cassandra.distributed.api.ConsistencyLevel.ALL;
@ -147,8 +147,8 @@ public class AllowAutoSnapshotTest extends TestBaseImpl
{ {
final int node = i; // has to be effectively final for the usage in "until" method final int node = i; // has to be effectively final for the usage in "until" method
await().until(() -> cluster.get(node).appliesOnInstance((IIsolatedExecutor.SerializableTriFunction<Boolean, String, String, Boolean>) (shouldContainSnapshot, tableName, prefix) -> { await().until(() -> cluster.get(node).appliesOnInstance((IIsolatedExecutor.SerializableTriFunction<Boolean, String, String, Boolean>) (shouldContainSnapshot, tableName, prefix) -> {
Stream<String> stream = StorageService.instance.getSnapshotDetails(Collections.emptyMap()).keySet().stream(); Stream<String> stream = SnapshotManager.instance.listSnapshots(Collections.emptyMap()).keySet().stream();
Predicate<String> predicate = tag -> tag.startsWith(prefix + '-') && tag.endsWith('-' + tableName); Predicate<String> predicate = tag -> tag.contains(prefix + '-' + table);
return shouldContainSnapshot ? stream.anyMatch(predicate) : stream.noneMatch(predicate); return shouldContainSnapshot ? stream.anyMatch(predicate) : stream.noneMatch(predicate);
}).apply(shouldContain, table, snapshotPrefix)); }).apply(shouldContain, table, snapshotPrefix));
} }

View File

@ -25,16 +25,14 @@ import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
import org.apache.cassandra.config.CassandraRelevantProperties; import org.apache.cassandra.config.CassandraRelevantProperties;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.distributed.Cluster; import org.apache.cassandra.distributed.Cluster;
import org.apache.cassandra.distributed.api.ConsistencyLevel; import org.apache.cassandra.distributed.api.ConsistencyLevel;
import org.apache.cassandra.distributed.api.Feature; import org.apache.cassandra.distributed.api.Feature;
import org.apache.cassandra.distributed.api.IInvokableInstance; import org.apache.cassandra.distributed.api.IInvokableInstance;
import org.apache.cassandra.distributed.shared.WithProperties; import org.apache.cassandra.distributed.shared.WithProperties;
import org.apache.cassandra.service.snapshot.SnapshotType;
import static java.util.concurrent.TimeUnit.SECONDS; import static java.util.concurrent.TimeUnit.SECONDS;
import static org.apache.cassandra.db.ColumnFamilyStore.SNAPSHOT_DROP_PREFIX;
import static org.apache.cassandra.db.ColumnFamilyStore.SNAPSHOT_TRUNCATE_PREFIX;
import static org.apache.cassandra.distributed.Cluster.build; import static org.apache.cassandra.distributed.Cluster.build;
import static org.apache.cassandra.distributed.shared.ClusterUtils.stopUnchecked; import static org.apache.cassandra.distributed.shared.ClusterUtils.stopUnchecked;
import static org.awaitility.Awaitility.await; import static org.awaitility.Awaitility.await;
@ -81,12 +79,12 @@ public class AutoSnapshotTtlTest extends TestBaseImpl
cluster.schemaChange(withKeyspace("TRUNCATE %s.tbl;")); cluster.schemaChange(withKeyspace("TRUNCATE %s.tbl;"));
// Check snapshot is listed after table is truncated // Check snapshot is listed after table is truncated
instance.nodetoolResult("listsnapshots").asserts().success().stdoutContains(SNAPSHOT_TRUNCATE_PREFIX); instance.nodetoolResult("listsnapshots").asserts().success().stdoutContains(SnapshotType.TRUNCATE.label);
// Check snapshot is removed after 10s // Check snapshot is removed after 10s
await().timeout(10, SECONDS) await().timeout(10, SECONDS)
.pollInterval(1, SECONDS) .pollInterval(1, SECONDS)
.until(() -> !instance.nodetoolResult("listsnapshots").getStdout().contains(SNAPSHOT_DROP_PREFIX)); .until(() -> !instance.nodetoolResult("listsnapshots").getStdout().contains(SnapshotType.DROP.label));
} }
} }
@ -111,12 +109,12 @@ public class AutoSnapshotTtlTest extends TestBaseImpl
cluster.schemaChange(withKeyspace("DROP TABLE %s.tbl;")); cluster.schemaChange(withKeyspace("DROP TABLE %s.tbl;"));
// Check snapshot is listed after table is dropped // Check snapshot is listed after table is dropped
instance.nodetoolResult("listsnapshots").asserts().success().stdoutContains(SNAPSHOT_DROP_PREFIX); instance.nodetoolResult("listsnapshots").asserts().success().stdoutContains(SnapshotType.DROP.label);
// Check snapshot is removed after 10s // Check snapshot is removed after 10s
await().timeout(10, SECONDS) await().timeout(10, SECONDS)
.pollInterval(1, SECONDS) .pollInterval(1, SECONDS)
.until(() -> !instance.nodetoolResult("listsnapshots").getStdout().contains(SNAPSHOT_DROP_PREFIX)); .until(() -> !instance.nodetoolResult("listsnapshots").getStdout().contains(SnapshotType.DROP.label));
} }
} }
@ -146,12 +144,12 @@ public class AutoSnapshotTtlTest extends TestBaseImpl
instance.startup(); instance.startup();
// Check snapshot is listed after restart // Check snapshot is listed after restart
instance.nodetoolResult("listsnapshots").asserts().success().stdoutContains(SNAPSHOT_DROP_PREFIX); instance.nodetoolResult("listsnapshots").asserts().success().stdoutContains(SnapshotType.DROP.label);
// Check snapshot is removed after at most auto_snapshot_ttl + 1s // Check snapshot is removed after at most auto_snapshot_ttl + 1s
await().timeout(ONE_MINUTE + 1, SECONDS) await().timeout(ONE_MINUTE + 1, SECONDS)
.pollInterval(1, SECONDS) .pollInterval(1, SECONDS)
.until(() -> !instance.nodetoolResult("listsnapshots").getStdout().contains(SNAPSHOT_DROP_PREFIX)); .until(() -> !instance.nodetoolResult("listsnapshots").getStdout().contains(SnapshotType.DROP.label));
} }
} }
@ -178,13 +176,13 @@ public class AutoSnapshotTtlTest extends TestBaseImpl
cluster.schemaChange(withKeyspace("DROP TABLE %s.tbl;")); cluster.schemaChange(withKeyspace("DROP TABLE %s.tbl;"));
// Check snapshots are created after table is truncated and dropped // Check snapshots are created after table is truncated and dropped
instance.nodetoolResult("listsnapshots").asserts().success().stdoutContains(SNAPSHOT_TRUNCATE_PREFIX); instance.nodetoolResult("listsnapshots").asserts().success().stdoutContains(SnapshotType.TRUNCATE.label);
instance.nodetoolResult("listsnapshots").asserts().success().stdoutContains(SNAPSHOT_DROP_PREFIX); instance.nodetoolResult("listsnapshots").asserts().success().stdoutContains(SnapshotType.DROP.label);
// Check snapshot are *NOT* expired after 10s // Check snapshot are *NOT* expired after 10s
Thread.sleep(2 * FIVE_SECONDS * 1000L); Thread.sleep(2 * FIVE_SECONDS * 1000L);
instance.nodetoolResult("listsnapshots").asserts().success().stdoutContains(ColumnFamilyStore.SNAPSHOT_TRUNCATE_PREFIX); instance.nodetoolResult("listsnapshots").asserts().success().stdoutContains(SnapshotType.TRUNCATE.label);
instance.nodetoolResult("listsnapshots").asserts().success().stdoutContains(ColumnFamilyStore.SNAPSHOT_DROP_PREFIX); instance.nodetoolResult("listsnapshots").asserts().success().stdoutContains(SnapshotType.DROP.label);
} }
} }

View File

@ -30,6 +30,7 @@ import org.apache.cassandra.distributed.Cluster;
import org.apache.cassandra.distributed.api.IInvokableInstance; import org.apache.cassandra.distributed.api.IInvokableInstance;
import org.apache.cassandra.distributed.api.IIsolatedExecutor; import org.apache.cassandra.distributed.api.IIsolatedExecutor;
import org.apache.cassandra.io.util.File; import org.apache.cassandra.io.util.File;
import org.apache.cassandra.service.snapshot.SnapshotManager;
import org.apache.cassandra.service.snapshot.SnapshotManifest; import org.apache.cassandra.service.snapshot.SnapshotManifest;
import org.apache.cassandra.utils.Pair; import org.apache.cassandra.utils.Pair;
@ -61,6 +62,8 @@ public class EphemeralSnapshotTest extends TestBaseImpl
rewriteManifestToEphemeral(initialisationData.left, initialisationData.right); rewriteManifestToEphemeral(initialisationData.left, initialisationData.right);
c.get(1).runOnInstance((IIsolatedExecutor.SerializableRunnable) () -> SnapshotManager.instance.restart(true));
verify(c.get(1)); verify(c.get(1));
} }
} }
@ -88,6 +91,8 @@ public class EphemeralSnapshotTest extends TestBaseImpl
Files.createFile(ephemeralMarkerFile); Files.createFile(ephemeralMarkerFile);
c.get(1).runOnInstance((IIsolatedExecutor.SerializableRunnable) () -> SnapshotManager.instance.restart(true));
verify(c.get(1)); verify(c.get(1));
} }
} }
@ -104,6 +109,8 @@ public class EphemeralSnapshotTest extends TestBaseImpl
Pair<String, String[]> initialisationData = initialise(c); Pair<String, String[]> initialisationData = initialise(c);
rewriteManifestToEphemeral(initialisationData.left, initialisationData.right); rewriteManifestToEphemeral(initialisationData.left, initialisationData.right);
c.get(1).runOnInstance((IIsolatedExecutor.SerializableRunnable) () -> SnapshotManager.instance.restart(true));
assertTrue(instance.nodetoolResult("listsnapshots", "-e").getStdout().contains(snapshotName)); assertTrue(instance.nodetoolResult("listsnapshots", "-e").getStdout().contains(snapshotName));
instance.nodetoolResult("clearsnapshot", "-t", snapshotName).asserts().success(); instance.nodetoolResult("clearsnapshot", "-t", snapshotName).asserts().success();
// ephemeral snapshot was not removed as it can not be (from nodetool / user operation) // ephemeral snapshot was not removed as it can not be (from nodetool / user operation)
@ -127,6 +134,8 @@ public class EphemeralSnapshotTest extends TestBaseImpl
rewriteManifestToEphemeral(initialisationData.left, initialisationData.right); rewriteManifestToEphemeral(initialisationData.left, initialisationData.right);
c.get(1).runOnInstance((IIsolatedExecutor.SerializableRunnable) () -> SnapshotManager.instance.restart(true));
instance.nodetoolResult("clearsnapshot", "--all").asserts().success(); instance.nodetoolResult("clearsnapshot", "--all").asserts().success();
assertTrue(instance.nodetoolResult("listsnapshots", "-e").getStdout().contains(snapshotName)); assertTrue(instance.nodetoolResult("listsnapshots", "-e").getStdout().contains(snapshotName));
assertFalse(instance.nodetoolResult("listsnapshots", "-e").getStdout().contains(snapshotName2)); assertFalse(instance.nodetoolResult("listsnapshots", "-e").getStdout().contains(snapshotName2));

View File

@ -36,6 +36,7 @@ import org.apache.cassandra.distributed.Cluster;
import org.apache.cassandra.distributed.api.ConsistencyLevel; import org.apache.cassandra.distributed.api.ConsistencyLevel;
import org.apache.cassandra.distributed.api.IIsolatedExecutor; import org.apache.cassandra.distributed.api.IIsolatedExecutor;
import org.apache.cassandra.io.sstable.format.SSTableReader; import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.service.snapshot.TableSnapshot;
import org.apache.cassandra.utils.concurrent.Refs; import org.apache.cassandra.utils.concurrent.Refs;
import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MILLISECONDS;
@ -126,7 +127,7 @@ public class PreviewRepairSnapshotTest extends TestBaseImpl
String snapshotTag = await().atMost(1, MINUTES) String snapshotTag = await().atMost(1, MINUTES)
.pollInterval(100, MILLISECONDS) .pollInterval(100, MILLISECONDS)
.until(() -> { .until(() -> {
for (String tag : cfs.listSnapshots().keySet()) for (String tag : Util.listSnapshots(cfs).keySet())
{ {
// we create the snapshot schema file last, so when this exists we know the snapshot is complete; // we create the snapshot schema file last, so when this exists we know the snapshot is complete;
if (cfs.getDirectories().getSnapshotSchemaFile(tag).exists()) if (cfs.getDirectories().getSnapshotSchemaFile(tag).exists())
@ -138,7 +139,7 @@ public class PreviewRepairSnapshotTest extends TestBaseImpl
Set<SSTableReader> inSnapshot = new HashSet<>(); Set<SSTableReader> inSnapshot = new HashSet<>();
try (Refs<SSTableReader> sstables = cfs.getSnapshotSSTableReaders(snapshotTag)) try (Refs<SSTableReader> sstables = TableSnapshot.getSnapshotSSTableReaders(cfs.getKeyspaceName(), cfs.name, snapshotTag))
{ {
inSnapshot.addAll(sstables); inSnapshot.addAll(sstables);
} }

View File

@ -35,6 +35,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import com.google.common.util.concurrent.Uninterruptibles; import com.google.common.util.concurrent.Uninterruptibles;
import org.apache.cassandra.Util;
import org.apache.cassandra.service.StorageService; import org.apache.cassandra.service.StorageService;
import org.apache.cassandra.utils.concurrent.Condition; import org.apache.cassandra.utils.concurrent.Condition;
import org.junit.BeforeClass; import org.junit.BeforeClass;
@ -434,11 +435,11 @@ public class PreviewRepairTest extends TestBaseImpl
ColumnFamilyStore cfs = Keyspace.open(KEYSPACE).getColumnFamilyStore(table); ColumnFamilyStore cfs = Keyspace.open(KEYSPACE).getColumnFamilyStore(table);
if(shouldBeEmpty) if(shouldBeEmpty)
{ {
assertTrue(cfs.listSnapshots().isEmpty()); assertTrue(Util.listSnapshots(cfs).isEmpty());
} }
else else
{ {
while (cfs.listSnapshots().isEmpty()) while (Util.listSnapshots(cfs).isEmpty())
Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS); Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS);
} }
})); }));

View File

@ -30,6 +30,8 @@ import java.util.stream.Stream;
import com.google.common.util.concurrent.Uninterruptibles; import com.google.common.util.concurrent.Uninterruptibles;
import org.junit.Assert; import org.junit.Assert;
import org.apache.cassandra.Util;
import org.apache.cassandra.concurrent.SEPExecutor; import org.apache.cassandra.concurrent.SEPExecutor;
import org.apache.cassandra.config.CassandraRelevantProperties; import org.apache.cassandra.config.CassandraRelevantProperties;
import org.apache.cassandra.dht.Token; import org.apache.cassandra.dht.Token;
@ -39,6 +41,7 @@ import org.apache.cassandra.locator.EndpointsForToken;
import org.apache.cassandra.locator.InetAddressAndPort; import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.locator.ReplicaLayout; import org.apache.cassandra.locator.ReplicaLayout;
import org.apache.cassandra.locator.ReplicaUtils; import org.apache.cassandra.locator.ReplicaUtils;
import org.apache.cassandra.service.snapshot.SnapshotManager;
import org.apache.cassandra.tcm.ClusterMetadata; import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.utils.Throwables; import org.apache.cassandra.utils.Throwables;
import org.junit.Test; import org.junit.Test;
@ -591,7 +594,7 @@ public class RepairDigestTrackingTest extends TestBaseImpl
int attempts = 100; int attempts = 100;
ColumnFamilyStore cfs = Keyspace.open(KEYSPACE).getColumnFamilyStore(TABLE); ColumnFamilyStore cfs = Keyspace.open(KEYSPACE).getColumnFamilyStore(TABLE);
while (cfs.listSnapshots().isEmpty()) while (Util.listSnapshots(cfs).isEmpty())
{ {
Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS); Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS);
if (attempts-- < 0) if (attempts-- < 0)
@ -602,11 +605,7 @@ public class RepairDigestTrackingTest extends TestBaseImpl
private IInvokableInstance.SerializableRunnable assertSnapshotNotPresent(String snapshotName) private IInvokableInstance.SerializableRunnable assertSnapshotNotPresent(String snapshotName)
{ {
return () -> return () -> Assert.assertFalse(SnapshotManager.instance.exists(KEYSPACE, TABLE, snapshotName));
{
ColumnFamilyStore cfs = Keyspace.open(KEYSPACE).getColumnFamilyStore(TABLE);
Assert.assertFalse(cfs.snapshotExists(snapshotName));
};
} }
private long getConfirmedInconsistencies(IInvokableInstance instance) private long getConfirmedInconsistencies(IInvokableInstance instance)

View File

@ -21,6 +21,7 @@ package org.apache.cassandra.distributed.test;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.function.Function; import java.util.function.Function;
@ -51,6 +52,9 @@ import org.apache.cassandra.io.sstable.SequenceBasedSSTableId;
import org.apache.cassandra.io.sstable.UUIDBasedSSTableId; import org.apache.cassandra.io.sstable.UUIDBasedSSTableId;
import org.apache.cassandra.io.util.File; import org.apache.cassandra.io.util.File;
import org.apache.cassandra.metrics.RestorableMeter; import org.apache.cassandra.metrics.RestorableMeter;
import org.apache.cassandra.service.snapshot.SnapshotManager;
import org.apache.cassandra.service.snapshot.SnapshotOptions;
import org.apache.cassandra.service.snapshot.TableSnapshot;
import org.apache.cassandra.tools.SystemExitException; import org.apache.cassandra.tools.SystemExitException;
import org.apache.cassandra.utils.TimeUUID; import org.apache.cassandra.utils.TimeUUID;
import org.assertj.core.api.Assertions; import org.assertj.core.api.Assertions;
@ -60,7 +64,6 @@ import static java.lang.String.format;
import static org.apache.cassandra.Util.bulkLoadSSTables; import static org.apache.cassandra.Util.bulkLoadSSTables;
import static org.apache.cassandra.Util.getBackups; import static org.apache.cassandra.Util.getBackups;
import static org.apache.cassandra.Util.getSSTables; import static org.apache.cassandra.Util.getSSTables;
import static org.apache.cassandra.Util.getSnapshots;
import static org.apache.cassandra.Util.relativizePath; import static org.apache.cassandra.Util.relativizePath;
import static org.apache.cassandra.cql3.QueryProcessor.executeInternal; import static org.apache.cassandra.cql3.QueryProcessor.executeInternal;
import static org.apache.cassandra.db.SystemKeyspace.LEGACY_SSTABLE_ACTIVITY; import static org.apache.cassandra.db.SystemKeyspace.LEGACY_SSTABLE_ACTIVITY;
@ -404,12 +407,20 @@ public class SSTableIdGenerationTest extends TestBaseImpl
private static Set<String> snapshot(IInvokableInstance instance, String ks, String tableName) private static Set<String> snapshot(IInvokableInstance instance, String ks, String tableName)
{ {
Set<String> snapshotDirs = instance.callOnInstance(() -> ColumnFamilyStore.getIfExists(ks, tableName) Set<String> snapshotDirs = instance.callOnInstance(() -> {
.snapshot(SNAPSHOT_TAG)
.getDirectories() ColumnFamilyStore cfs = ColumnFamilyStore.getIfExists(ks, tableName);
.stream()
.map(File::toString) if (cfs == null)
.collect(Collectors.toSet())); return Set.of();
TableSnapshot tableSnapshot = SnapshotManager.instance.takeSnapshot(SnapshotOptions.userSnapshot(SNAPSHOT_TAG, cfs.getKeyspaceTableName())).iterator().next();
Set<String> dirs = new HashSet<>();
for (File dir : tableSnapshot.getDirectories())
dirs.add(dir.toString());
return dirs;
});
assertThat(snapshotDirs).isNotEmpty(); assertThat(snapshotDirs).isNotEmpty();
return snapshotDirs; return snapshotDirs;
} }
@ -458,7 +469,7 @@ public class SSTableIdGenerationTest extends TestBaseImpl
private static void assertSnapshotSSTablesCount(IInvokableInstance instance, int expectedSeqGenIds, int expectedUUIDGenIds, String ks, String... tableNames) private static void assertSnapshotSSTablesCount(IInvokableInstance instance, int expectedSeqGenIds, int expectedUUIDGenIds, String ks, String... tableNames)
{ {
instance.runOnInstance(rethrow(() -> Arrays.stream(tableNames).forEach(tableName -> assertSSTablesCount(getSnapshots(ks, tableName, SNAPSHOT_TAG), tableName, expectedSeqGenIds, expectedUUIDGenIds)))); instance.runOnInstance(rethrow(() -> Arrays.stream(tableNames).forEach(tableName -> assertSSTablesCount(TableSnapshot.getSnapshotDescriptors(ks, tableName, SNAPSHOT_TAG), tableName, expectedSeqGenIds, expectedUUIDGenIds))));
} }
private static void assertBackupSSTablesCount(IInvokableInstance instance, int expectedSeqGenIds, int expectedUUIDGenIds, String ks, String... tableNames) private static void assertBackupSSTablesCount(IInvokableInstance instance, int expectedSeqGenIds, int expectedUUIDGenIds, String ks, String... tableNames)

View File

@ -19,6 +19,9 @@
package org.apache.cassandra.distributed.test; package org.apache.cassandra.distributed.test;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -29,9 +32,11 @@ import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
import org.apache.cassandra.config.CassandraRelevantProperties; import org.apache.cassandra.config.CassandraRelevantProperties;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.distributed.Cluster; import org.apache.cassandra.distributed.Cluster;
import org.apache.cassandra.distributed.api.ConsistencyLevel; import org.apache.cassandra.distributed.api.ConsistencyLevel;
import org.apache.cassandra.distributed.api.IInvokableInstance; import org.apache.cassandra.distributed.api.IInvokableInstance;
import org.apache.cassandra.distributed.api.IIsolatedExecutor.SerializableCallable;
import org.apache.cassandra.distributed.api.NodeToolResult; import org.apache.cassandra.distributed.api.NodeToolResult;
import org.apache.cassandra.distributed.shared.WithProperties; import org.apache.cassandra.distributed.shared.WithProperties;
import org.apache.cassandra.utils.Clock; import org.apache.cassandra.utils.Clock;
@ -40,8 +45,10 @@ import static java.lang.String.format;
import static java.util.concurrent.TimeUnit.SECONDS; import static java.util.concurrent.TimeUnit.SECONDS;
import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toList;
import static org.apache.cassandra.distributed.shared.ClusterUtils.stopUnchecked; import static org.apache.cassandra.distributed.shared.ClusterUtils.stopUnchecked;
import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await; import static org.awaitility.Awaitility.await;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class SnapshotsTest extends TestBaseImpl public class SnapshotsTest extends TestBaseImpl
{ {
@ -51,9 +58,9 @@ public class SnapshotsTest extends TestBaseImpl
private static final WithProperties properties = new WithProperties(); private static final WithProperties properties = new WithProperties();
private static Cluster cluster; private static Cluster cluster;
private final String[] exoticSnapshotNames = new String[] { "snapshot", "snapshots", "backup", "backups", private final String[] exoticSnapshotNames = new String[]{ "snapshot", "snapshots", "backup", "backups",
"Snapshot", "Snapshots", "Backups", "Backup", "Snapshot", "Snapshots", "Backups", "Backup",
"snapshot.with.dots-and-dashes"}; "snapshot.with.dots-and-dashes" };
@BeforeClass @BeforeClass
public static void before() throws IOException public static void before() throws IOException
@ -61,15 +68,18 @@ public class SnapshotsTest extends TestBaseImpl
properties.set(CassandraRelevantProperties.SNAPSHOT_CLEANUP_INITIAL_DELAY_SECONDS, 0); properties.set(CassandraRelevantProperties.SNAPSHOT_CLEANUP_INITIAL_DELAY_SECONDS, 0);
properties.set(CassandraRelevantProperties.SNAPSHOT_CLEANUP_PERIOD_SECONDS, SNAPSHOT_CLEANUP_PERIOD_SECONDS); properties.set(CassandraRelevantProperties.SNAPSHOT_CLEANUP_PERIOD_SECONDS, SNAPSHOT_CLEANUP_PERIOD_SECONDS);
properties.set(CassandraRelevantProperties.SNAPSHOT_MIN_ALLOWED_TTL_SECONDS, FIVE_SECONDS); properties.set(CassandraRelevantProperties.SNAPSHOT_MIN_ALLOWED_TTL_SECONDS, FIVE_SECONDS);
cluster = init(Cluster.build(1).start()); cluster = init(Cluster.build(1)
.withDataDirCount(3)
.start());
} }
@After @After
public void clearAllSnapshots() public void clearAllSnapshots()
{ {
cluster.schemaChange(withKeyspace("DROP TABLE IF EXISTS %s.tbl;")); cluster.schemaChange(withKeyspace("DROP TABLE IF EXISTS %s.tbl;"));
cluster.schemaChange(withKeyspace("DROP TABLE IF EXISTS %s.tbl2;"));
cluster.get(1).nodetoolResult("clearsnapshot", "--all").asserts().success(); cluster.get(1).nodetoolResult("clearsnapshot", "--all").asserts().success();
for (String tag : new String[] {"basic", "first", "second", "tag1"}) for (String tag : new String[]{ "basic", "first", "second", "tag1" })
waitForSnapshotCleared(tag); waitForSnapshotCleared(tag);
for (String tag : exoticSnapshotNames) for (String tag : exoticSnapshotNames)
waitForSnapshot(tag, false, true); waitForSnapshot(tag, false, true);
@ -83,6 +93,34 @@ public class SnapshotsTest extends TestBaseImpl
cluster.close(); cluster.close();
} }
@Test
public void testEverySnapshotDirHasManifestAndSchema()
{
cluster.schemaChange(withKeyspace("CREATE TABLE %s.tbl (key int, value text, PRIMARY KEY (key))"));
String[] dataDirs = (String[]) cluster.get(1).config().get("data_file_directories");
String tableId = cluster.get(1).callOnInstance((SerializableCallable<String>) () -> {
return ColumnFamilyStore.getIfExists("distributed_test_keyspace", "tbl").metadata().id.toHexString();
});
cluster.get(1)
.nodetoolResult("snapshot", "-t", "mysnapshot", "-kt", format("%s.tbl", KEYSPACE))
.asserts()
.success();
for (String dataDir : dataDirs)
{
Path snapshotDir = Paths.get(dataDir)
.resolve(KEYSPACE)
.resolve("tbl-" + tableId)
.resolve("snapshots")
.resolve("mysnapshot");
assertTrue(snapshotDir.toFile().exists());
assertTrue(snapshotDir.resolve("manifest.json").toFile().exists());
assertTrue(snapshotDir.resolve("schema.cql").toFile().exists());
}
}
@Test @Test
public void testSnapshotsCleanupByTTL() public void testSnapshotsCleanupByTTL()
{ {
@ -161,7 +199,8 @@ public class SnapshotsTest extends TestBaseImpl
} }
@Test @Test
public void testManualSnapshotCleanup() { public void testManualSnapshotCleanup()
{
// take snapshots with ttl // take snapshots with ttl
cluster.get(1).nodetoolResult("snapshot", "--ttl", cluster.get(1).nodetoolResult("snapshot", "--ttl",
format("%ds", TEN_SECONDS), format("%ds", TEN_SECONDS),
@ -210,8 +249,8 @@ public class SnapshotsTest extends TestBaseImpl
populate(cluster); populate(cluster);
instance.nodetoolResult("snapshot", instance.nodetoolResult("snapshot",
"-t", "tag1", "-t", "tag1",
"-kt", withKeyspace("%s.tbl")).asserts().success(); "-kt", withKeyspace("%s.tbl")).asserts().success();
// Check snapshot is listed when table is not dropped // Check snapshot is listed when table is not dropped
waitForSnapshotPresent("tag1"); waitForSnapshotPresent("tag1");
@ -314,21 +353,47 @@ public class SnapshotsTest extends TestBaseImpl
Pattern COMPILE = Pattern.compile(" +"); Pattern COMPILE = Pattern.compile(" +");
long distinctTimestamps = Arrays.stream(result.getStdout().split("\n")) long distinctTimestamps = Arrays.stream(result.getStdout().split("\n"))
.filter(line -> line.startsWith("sametimestamp")) .filter(line -> line.startsWith("sametimestamp"))
.map(line -> COMPILE.matcher(line).replaceAll(" ").split(" ")[7]) .map(line -> COMPILE.matcher(line).replaceAll(" ").split(" ")[7])
.distinct() .distinct()
.count(); .count();
// assert all dates are same so there is just one value accross all individual tables // assert all dates are same so there is just one value accross all individual tables
assertEquals(1, distinctTimestamps); assertEquals(1, distinctTimestamps);
} }
@Test
public void testFailureToSnapshotTwiceOnSameEntityWithSameSnapshotName()
{
cluster.get(1).nodetoolResult("snapshot", "-t", "somename").asserts().success();
NodeToolResult failedSnapshotResult = cluster.get(1).nodetoolResult("snapshot", "-t", "somename");
failedSnapshotResult.asserts().failure();
Throwable error = failedSnapshotResult.getError();
assertThat(error.getMessage()).contains("already exists");
}
@Test
public void testTakingSnapshoWithSameNameOnDifferentTablesDoesNotFail()
{
cluster.schemaChange(withKeyspace("CREATE TABLE %s.tbl (key int, value text, PRIMARY KEY (key))"));
cluster.schemaChange(withKeyspace("CREATE TABLE %s.tbl2 (key int, value text, PRIMARY KEY (key))"));
cluster.get(1).nodetoolResult("snapshot", "-t", "somename", "-kt", String.format("%s.tbl", KEYSPACE)).asserts().success();
cluster.get(1).nodetoolResult("snapshot", "-t", "somename", "-kt", String.format("%s.tbl2", KEYSPACE)).asserts().success();
}
private void populate(Cluster cluster) private void populate(Cluster cluster)
{ {
for (int i = 0; i < 100; i++) for (int i = 0; i < 100; i++)
cluster.coordinator(1).execute(withKeyspace("INSERT INTO %s.tbl (key, value) VALUES (?, 'txt')"), ConsistencyLevel.ONE, i); cluster.coordinator(1).execute(withKeyspace("INSERT INTO %s.tbl (key, value) VALUES (?, 'txt')"), ConsistencyLevel.ONE, i);
} }
private void populate(Cluster cluster, String keyspace, String table)
{
for (int i = 0; i < 100; i++)
cluster.coordinator(1).execute(format("INSERT INTO %s.%s (key, value) VALUES (?, 'txt')", keyspace, table), ConsistencyLevel.ONE, i);
}
private void waitForSnapshotPresent(String snapshotName) private void waitForSnapshotPresent(String snapshotName)
{ {
waitForSnapshot(snapshotName, true, false); waitForSnapshot(snapshotName, true, false);
@ -339,20 +404,28 @@ public class SnapshotsTest extends TestBaseImpl
waitForSnapshot(snapshotName, false, false); waitForSnapshot(snapshotName, false, false);
} }
private void waitForSnapshot(String snapshotName, boolean expectPresent, boolean noTTL) private void waitForSnapshot(String keyspaceName, String tableName, String snapshotName, boolean expectPresent, boolean noTTL)
{ {
await().timeout(20, SECONDS) await().timeout(20, SECONDS)
.pollDelay(0, SECONDS) .pollDelay(0, SECONDS)
.pollInterval(1, SECONDS) .pollInterval(1, SECONDS)
.until(() -> waitForSnapshotInternal(snapshotName, expectPresent, noTTL)); .until(() -> waitForSnapshotInternal(keyspaceName, tableName, snapshotName, expectPresent, noTTL));
} }
private boolean waitForSnapshotInternal(String snapshotName, boolean expectPresent, boolean noTTL) { private void waitForSnapshot(String snapshotName, boolean expectPresent, boolean noTTL)
{
waitForSnapshot(null, null, snapshotName, expectPresent, noTTL);
}
private boolean waitForSnapshotInternal(String keyspaceName, String tableName, String snapshotName, boolean expectPresent, boolean noTTL)
{
List<String> args = new ArrayList<>();
args.add("listsnapshots");
NodeToolResult listsnapshots; NodeToolResult listsnapshots;
if (noTTL) if (noTTL)
listsnapshots = cluster.get(1).nodetoolResult("listsnapshots", "-nt"); args.add("-nt");
else
listsnapshots = cluster.get(1).nodetoolResult("listsnapshots"); listsnapshots = cluster.get(1).nodetoolResult(args.toArray(new String[0]));
List<String> lines = Arrays.stream(listsnapshots.getStdout().split("\n")) List<String> lines = Arrays.stream(listsnapshots.getStdout().split("\n"))
.filter(line -> !line.isEmpty()) .filter(line -> !line.isEmpty())

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,167 @@
/*
* 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.test.microbench;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.UUID;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.cassandra.SchemaLoader;
import org.apache.cassandra.Util;
import org.apache.cassandra.config.CassandraRelevantProperties;
import org.apache.cassandra.config.Config;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.cql3.CQLTester;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.Keyspace;
import org.apache.cassandra.db.RowUpdateBuilder;
import org.apache.cassandra.db.commitlog.CommitLog;
import org.apache.cassandra.schema.KeyspaceParams;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.snapshot.SnapshotManager;
import org.apache.cassandra.service.snapshot.TableSnapshot;
import org.apache.cassandra.tcm.ClusterMetadataService;
import static org.apache.cassandra.service.snapshot.SnapshotOptions.userSnapshot;
import static org.junit.Assert.assertEquals;
public abstract class AbstractSnapshotManagerBase
{
static final String KEYSPACE = "KEYSPACE";
private final Random random = new Random();
static int NUM_SSTABLES = 10;
static int NUM_KEYSPACES = 5;
static int NUM_TABLES_PER_KEYSPACE = 10;
static int NUM_SNAPSHOTS_PER_TABLE = 10;
public void teardown()
{
SnapshotManager.instance.clearAllSnapshots();
CQLTester.tearDownClass();
CommitLog.instance.stopUnsafe(true);
// TODO CASSANDRA-20119
ClusterMetadataService.instance().log().close();
CQLTester.cleanup();
}
public void setup(boolean takeSnapshotAfterTablePopulation)
{
CassandraRelevantProperties.SNAPSHOT_CLEANUP_INITIAL_DELAY_SECONDS.getInt(1000);
CassandraRelevantProperties.SNAPSHOT_CLEANUP_PERIOD_SECONDS.setInt(60);
DatabaseDescriptor.daemonInitialization(() -> {
Config config = DatabaseDescriptor.loadConfig();
config.dump_heap_on_uncaught_exception = false;
return config;
});
SchemaLoader.prepareServer();
SnapshotManager.instance.start(true);
populateTables(takeSnapshotAfterTablePopulation);
}
public void populateTables(boolean takeSnapshotAfterTablePopulation)
{
for (int i = 0; i < NUM_KEYSPACES; i++)
{
String keyspaceName = KEYSPACE + '_' + i;
// Create Schema
TableMetadata[] tables = new TableMetadata[NUM_TABLES_PER_KEYSPACE];
for (int j = 0; j < NUM_TABLES_PER_KEYSPACE; j++)
{
tables[j] = SchemaLoader.standardCFMD(keyspaceName, String.format("table%d", i + '_' + j)).build();
}
SchemaLoader.createKeyspace(keyspaceName,
KeyspaceParams.simple(1),
tables);
for (int j = 0; j < NUM_TABLES_PER_KEYSPACE; j++)
{
String tableName = String.format("table%d", i + '_' + j);
ColumnFamilyStore cfs = Keyspace.open(keyspaceName).getColumnFamilyStore(tableName);
cfs.disableAutoCompaction();
for (int k = 0; k < NUM_SSTABLES; k++)
{
new RowUpdateBuilder(cfs.metadata(), 0, "key1")
.clustering("Column1")
.add("val", "asdf")
.build()
.applyUnsafe();
Util.flush(cfs);
}
if (takeSnapshotAfterTablePopulation)
{
for (int snapId = 0; snapId < NUM_SNAPSHOTS_PER_TABLE; snapId++)
SnapshotManager.instance.takeSnapshot(userSnapshot(String.format("snap_%d_%d_%d", i, j, snapId), cfs.getKeyspaceTableName()));
}
}
}
if (takeSnapshotAfterTablePopulation)
{
List<TableSnapshot> snapshots = SnapshotManager.instance.getSnapshots(t -> true);
assertEquals(NUM_KEYSPACES * NUM_TABLES_PER_KEYSPACE * NUM_SNAPSHOTS_PER_TABLE, snapshots.size());
}
}
public String getRandomKeyspaceTable()
{
List<Keyspace> keyspaces = new ArrayList<>();
Keyspace.nonSystem().forEach(keyspaces::add);
Keyspace randomKeyspace = keyspaces.get(random.nextInt(keyspaces.size()));
List<ColumnFamilyStore> cfss = new ArrayList<>(randomKeyspace.getColumnFamilyStores());
ColumnFamilyStore cfs = cfss.get(random.nextInt(cfss.size()));
return cfs.getKeyspaceTableName();
}
public String getRandomKeyspace()
{
List<Keyspace> keyspaces = new ArrayList<>();
Keyspace.nonSystem().forEach(keyspaces::add);
Keyspace randomKeyspace = keyspaces.get(random.nextInt(keyspaces.size()));
return randomKeyspace.getName();
}
public Pair<String, String> getRandomKeyspaceTablePair()
{
List<Keyspace> keyspaces = new ArrayList<>();
Keyspace.nonSystem().forEach(keyspaces::add);
Keyspace randomKeyspace = keyspaces.get(random.nextInt(keyspaces.size()));
List<ColumnFamilyStore> cfss = new ArrayList<>(randomKeyspace.getColumnFamilyStores());
ColumnFamilyStore cfs = cfss.get(random.nextInt(cfss.size()));
return Pair.of(cfs.getKeyspaceName(), cfs.getTableName());
}
public void takeSnapshotOnRandomTable()
{
SnapshotManager.instance.takeSnapshot(userSnapshot("snap_" + UUID.randomUUID(), getRandomKeyspaceTable()));
}
}

View File

@ -30,6 +30,7 @@ import org.apache.cassandra.db.Keyspace;
import org.apache.cassandra.io.sstable.Descriptor; import org.apache.cassandra.io.sstable.Descriptor;
import org.apache.cassandra.io.util.File; import org.apache.cassandra.io.util.File;
import org.apache.cassandra.io.util.FileUtils; import org.apache.cassandra.io.util.FileUtils;
import org.apache.cassandra.service.snapshot.SnapshotManager;
import org.openjdk.jmh.annotations.*; import org.openjdk.jmh.annotations.*;
@BenchmarkMode(Mode.AverageTime) @BenchmarkMode(Mode.AverageTime)
@ -78,8 +79,7 @@ public class CompactionBench extends CQLTester
cfs.forceBlockingFlush(ColumnFamilyStore.FlushReason.USER_FORCED); cfs.forceBlockingFlush(ColumnFamilyStore.FlushReason.USER_FORCED);
cfs.snapshot("originals"); SnapshotManager.instance.takeSnapshot("originals", cfs.getKeyspaceTableName());
snapshotFiles = cfs.getDirectories().sstableLister(Directories.OnTxnErr.IGNORE).snapshots("originals").listFiles(); snapshotFiles = cfs.getDirectories().sstableLister(Directories.OnTxnErr.IGNORE).snapshots("originals").listFiles();
} }

View File

@ -0,0 +1,63 @@
package org.apache.cassandra.test.microbench;
/*
* 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.
*/
import java.util.concurrent.TimeUnit;
import org.apache.cassandra.service.snapshot.SnapshotManager;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Level;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.TearDown;
import org.openjdk.jmh.annotations.Threads;
import org.openjdk.jmh.annotations.Warmup;
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Fork(value = 1)
@State(Scope.Benchmark)
public class SnapshotListingBench extends AbstractSnapshotManagerBase
{
@Setup(Level.Trial)
public void setup()
{
setup(true);
}
@TearDown(Level.Trial)
public void teardown()
{
super.teardown();
}
@Benchmark
@BenchmarkMode(Mode.All)
@Warmup(iterations = 0)
@Threads(4)
@Measurement(iterations = 1, time = 60)
public void listingTest()
{
SnapshotManager.instance.listSnapshots(null);
}
}

View File

@ -0,0 +1,64 @@
/*
* 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.test.microbench;
import java.util.concurrent.TimeUnit;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Level;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.TearDown;
import org.openjdk.jmh.annotations.Threads;
import org.openjdk.jmh.annotations.Warmup;
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Fork(value = 1)
@State(Scope.Benchmark)
public class SnapshotTakingBench extends AbstractSnapshotManagerBase
{
@Setup(Level.Trial)
public void setup()
{
setup(false);
}
@TearDown(Level.Trial)
public void teardown()
{
super.teardown();
}
@Benchmark
@BenchmarkMode(Mode.All)
@Warmup(iterations = 0)
@Threads(4)
@Measurement(iterations = 1, time = 60)
public void takingSnapshotTest()
{
takeSnapshotOnRandomTable();
}
}

View File

@ -0,0 +1,65 @@
/*
* 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.test.microbench;
import java.util.concurrent.TimeUnit;
import org.apache.cassandra.service.snapshot.SnapshotManager;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Level;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.TearDown;
import org.openjdk.jmh.annotations.Threads;
import org.openjdk.jmh.annotations.Warmup;
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Fork(value = 1)
@State(Scope.Benchmark)
public class SnapshotTrueSizeBench extends AbstractSnapshotManagerBase
{
@Setup(Level.Trial)
public void setup()
{
setup(true);
}
@TearDown(Level.Trial)
public void teardown()
{
super.teardown();
}
@Benchmark
@BenchmarkMode({Mode.Throughput, Mode.AverageTime, Mode.SampleTime})
@Warmup(iterations = 0)
@Threads(16)
@Measurement(iterations = 1, time = 60)
public void getTrueSizeOfSnapshots()
{
SnapshotManager.instance.getTrueSnapshotSize();
}
}

View File

@ -35,8 +35,10 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
@ -133,6 +135,8 @@ import org.apache.cassandra.schema.Schema;
import org.apache.cassandra.schema.TableMetadataRef; import org.apache.cassandra.schema.TableMetadataRef;
import org.apache.cassandra.service.StorageService; import org.apache.cassandra.service.StorageService;
import org.apache.cassandra.service.pager.PagingState; import org.apache.cassandra.service.pager.PagingState;
import org.apache.cassandra.service.snapshot.SnapshotLoader;
import org.apache.cassandra.service.snapshot.TableSnapshot;
import org.apache.cassandra.streaming.StreamResultFuture; import org.apache.cassandra.streaming.StreamResultFuture;
import org.apache.cassandra.streaming.StreamState; import org.apache.cassandra.streaming.StreamState;
import org.apache.cassandra.tcm.ClusterMetadata; import org.apache.cassandra.tcm.ClusterMetadata;
@ -147,7 +151,6 @@ import org.apache.cassandra.utils.CounterId;
import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.FilterFactory; import org.apache.cassandra.utils.FilterFactory;
import org.apache.cassandra.utils.OutputHandler; import org.apache.cassandra.utils.OutputHandler;
import org.apache.cassandra.utils.Throwables;
import org.awaitility.Awaitility; import org.awaitility.Awaitility;
import org.hamcrest.Matcher; import org.hamcrest.Matcher;
import org.mockito.Mockito; import org.mockito.Mockito;
@ -1170,23 +1173,6 @@ public class Util
.collect(Collectors.toSet()); .collect(Collectors.toSet());
} }
public static Set<Descriptor> getSnapshots(String ks, String tableName, String snapshotTag)
{
try
{
return Keyspace.open(ks)
.getColumnFamilyStore(tableName)
.getSnapshotSSTableReaders(snapshotTag)
.stream()
.map(sstr -> sstr.descriptor)
.collect(Collectors.toSet());
}
catch (IOException e)
{
throw Throwables.unchecked(e);
}
}
public static Set<Descriptor> getBackups(String ks, String tableName) public static Set<Descriptor> getBackups(String ks, String tableName)
{ {
return Keyspace.open(ks) return Keyspace.open(ks)
@ -1283,4 +1269,16 @@ public class Util
{ {
return new UnsupportedOperationException("Test must be implemented for sstable format " + DatabaseDescriptor.getSelectedSSTableFormat().getClass().getName()); return new UnsupportedOperationException("Test must be implemented for sstable format " + DatabaseDescriptor.getSelectedSSTableFormat().getClass().getName());
} }
public static Map<String, TableSnapshot> listSnapshots(ColumnFamilyStore cfs)
{
Set<TableSnapshot> snapshots = new SnapshotLoader(cfs.getDirectories()).loadSnapshots();
Map<String, TableSnapshot> tagSnapshotsMap = new HashMap<>();
for (TableSnapshot snapshot : snapshots)
tagSnapshotsMap.put(snapshot.getTag(), snapshot);
return tagSnapshotsMap;
}
} }

View File

@ -172,6 +172,7 @@ import org.apache.cassandra.serializers.TypeSerializer;
import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.service.QueryState; import org.apache.cassandra.service.QueryState;
import org.apache.cassandra.service.StorageService; import org.apache.cassandra.service.StorageService;
import org.apache.cassandra.service.snapshot.SnapshotManager;
import org.apache.cassandra.tcm.ClusterMetadataService; import org.apache.cassandra.tcm.ClusterMetadataService;
import org.apache.cassandra.transport.Event; import org.apache.cassandra.transport.Event;
import org.apache.cassandra.transport.Message; import org.apache.cassandra.transport.Message;
@ -445,6 +446,7 @@ public abstract class CQLTester
DatabaseDescriptor.setRowCacheSizeInMiB(ROW_CACHE_SIZE_IN_MIB); DatabaseDescriptor.setRowCacheSizeInMiB(ROW_CACHE_SIZE_IN_MIB);
StorageService.instance.registerMBeans(); StorageService.instance.registerMBeans();
StorageService.instance.setPartitionerUnsafe(Murmur3Partitioner.instance); StorageService.instance.setPartitionerUnsafe(Murmur3Partitioner.instance);
SnapshotManager.instance.registerMBean();
} }
@AfterClass @AfterClass

View File

@ -30,21 +30,28 @@ import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.junit.runners.Parameterized; import org.junit.runners.Parameterized;
import org.apache.cassandra.Util;
import org.apache.cassandra.config.CassandraRelevantProperties;
import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.config.DurationSpec; import org.apache.cassandra.config.DurationSpec;
import org.apache.cassandra.cql3.CQLTester; import org.apache.cassandra.cql3.CQLTester;
import org.apache.cassandra.db.ColumnFamilyStore; import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.service.snapshot.SnapshotType;
import org.apache.cassandra.service.snapshot.TableSnapshot; import org.apache.cassandra.service.snapshot.TableSnapshot;
import org.assertj.core.api.Condition; import org.assertj.core.api.Condition;
import static java.util.concurrent.TimeUnit.SECONDS; import static java.util.concurrent.TimeUnit.SECONDS;
import static java.lang.String.format; import static java.lang.String.format;
import static org.apache.cassandra.db.ColumnFamilyStore.SNAPSHOT_DROP_PREFIX;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@RunWith(Parameterized.class) @RunWith(Parameterized.class)
public class AutoSnapshotTest extends CQLTester public class AutoSnapshotTest extends CQLTester
{ {
static
{
CassandraRelevantProperties.SNAPSHOT_MIN_ALLOWED_TTL_SECONDS.setInt(1);
}
static int TTL_SECS = 1; static int TTL_SECS = 1;
public static Boolean enabledBefore; public static Boolean enabledBefore;
@ -97,7 +104,7 @@ public class AutoSnapshotTest extends CQLTester
createTable("CREATE TABLE %s (a int, b int, c int, PRIMARY KEY(a, b))"); createTable("CREATE TABLE %s (a int, b int, c int, PRIMARY KEY(a, b))");
// Check there are no snapshots // Check there are no snapshots
ColumnFamilyStore tableDir = getCurrentColumnFamilyStore(); ColumnFamilyStore tableDir = getCurrentColumnFamilyStore();
assertThat(tableDir.listSnapshots()).isEmpty(); assertThat(Util.listSnapshots(tableDir)).isEmpty();
execute("INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", 0, 0, 0); execute("INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", 0, 0, 0);
execute("INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", 0, 1, 1); execute("INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", 0, 1, 1);
@ -106,7 +113,7 @@ public class AutoSnapshotTest extends CQLTester
execute("DROP TABLE %s"); execute("DROP TABLE %s");
verifyAutoSnapshot(SNAPSHOT_DROP_PREFIX, tableDir, currentTable()); verifyAutoSnapshot(SnapshotType.DROP.label, tableDir, currentTable());
} }
@Test @Test
@ -115,7 +122,7 @@ public class AutoSnapshotTest extends CQLTester
createTable("CREATE TABLE %s (a int, b int, c int, PRIMARY KEY(a, b))"); createTable("CREATE TABLE %s (a int, b int, c int, PRIMARY KEY(a, b))");
// Check there are no snapshots // Check there are no snapshots
ColumnFamilyStore tableDir = getCurrentColumnFamilyStore(); ColumnFamilyStore tableDir = getCurrentColumnFamilyStore();
assertThat(tableDir.listSnapshots()).isEmpty(); assertThat(Util.listSnapshots(tableDir)).isEmpty();
execute("INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", 0, 0, 0); execute("INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", 0, 0, 0);
execute("INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", 0, 1, 1); execute("INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", 0, 1, 1);
@ -124,7 +131,7 @@ public class AutoSnapshotTest extends CQLTester
execute("DROP TABLE %s"); execute("DROP TABLE %s");
verifyAutoSnapshot(SNAPSHOT_DROP_PREFIX, tableDir, currentTable()); verifyAutoSnapshot(SnapshotType.DROP.label, tableDir, currentTable());
} }
@Test @Test
@ -136,13 +143,13 @@ public class AutoSnapshotTest extends CQLTester
flush(); flush();
// Check no snapshots // Check no snapshots
assertThat(tableA.listSnapshots()).isEmpty(); assertThat(Util.listSnapshots(tableA)).isEmpty();
assertThat(tableB.listSnapshots()).isEmpty(); assertThat(Util.listSnapshots(tableB)).isEmpty();
// Drop keyspace, should have snapshot for table A and B // Drop keyspace, should have snapshot for table A and B
execute(format("DROP KEYSPACE %s", keyspace())); execute(format("DROP KEYSPACE %s", keyspace()));
verifyAutoSnapshot(SNAPSHOT_DROP_PREFIX, tableA, tableA.name); verifyAutoSnapshot(SnapshotType.DROP.label, tableA, tableA.name);
verifyAutoSnapshot(SNAPSHOT_DROP_PREFIX, tableB, tableB.name); verifyAutoSnapshot(SnapshotType.DROP.label, tableB, tableB.name);
} }
private ColumnFamilyStore createAndPopulateTable() throws Throwable private ColumnFamilyStore createAndPopulateTable() throws Throwable
@ -163,11 +170,11 @@ public class AutoSnapshotTest extends CQLTester
*/ */
private void verifyAutoSnapshot(String snapshotPrefix, ColumnFamilyStore tableDir, String expectedTableName) private void verifyAutoSnapshot(String snapshotPrefix, ColumnFamilyStore tableDir, String expectedTableName)
{ {
Map<String, TableSnapshot> snapshots = tableDir.listSnapshots(); Map<String, TableSnapshot> snapshots = Util.listSnapshots(tableDir);
if (autoSnapshotEnabled) if (autoSnapshotEnabled)
{ {
assertThat(snapshots).hasSize(1); assertThat(snapshots).hasSize(1);
assertThat(snapshots).hasKeySatisfying(new Condition<>(k -> k.startsWith(snapshotPrefix), "is dropped snapshot")); assertThat(snapshots).hasKeySatisfying(new Condition<>(k -> k.contains(snapshotPrefix), "is dropped snapshot"));
TableSnapshot snapshot = snapshots.values().iterator().next(); TableSnapshot snapshot = snapshots.values().iterator().next();
assertThat(snapshot.getTableName()).isEqualTo(expectedTableName); assertThat(snapshot.getTableName()).isEqualTo(expectedTableName);
if (autoSnapshotTTl == null) if (autoSnapshotTTl == null)

View File

@ -1,21 +1,21 @@
/* /*
* Licensed to the Apache Software Foundation (ASF) under one * Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file * or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information * distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file * regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the * to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance * "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at * with the License. You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, * Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an * software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the * KIND, either express or implied. See the License for the
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
package org.apache.cassandra.db; package org.apache.cassandra.db;
import java.io.IOException; import java.io.IOException;
@ -23,18 +23,19 @@ import java.nio.ByteBuffer;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Iterators; import com.google.common.collect.Iterators;
import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.BeforeClass; import org.junit.BeforeClass;
@ -44,7 +45,9 @@ import com.googlecode.concurrenttrees.common.Iterables;
import org.apache.cassandra.SchemaLoader; import org.apache.cassandra.SchemaLoader;
import org.apache.cassandra.UpdateBuilder; import org.apache.cassandra.UpdateBuilder;
import org.apache.cassandra.Util; import org.apache.cassandra.Util;
import org.apache.cassandra.cql3.ColumnIdentifier;
import org.apache.cassandra.cql3.Operator; import org.apache.cassandra.cql3.Operator;
import org.apache.cassandra.cql3.statements.schema.IndexTarget;
import org.apache.cassandra.db.ColumnFamilyStore.FlushReason; import org.apache.cassandra.db.ColumnFamilyStore.FlushReason;
import org.apache.cassandra.db.commitlog.CommitLogPosition; import org.apache.cassandra.db.commitlog.CommitLogPosition;
import org.apache.cassandra.db.filter.ColumnFilter; import org.apache.cassandra.db.filter.ColumnFilter;
@ -59,7 +62,9 @@ import org.apache.cassandra.db.rows.Cell;
import org.apache.cassandra.db.rows.EncodingStats; import org.apache.cassandra.db.rows.EncodingStats;
import org.apache.cassandra.db.rows.Row; import org.apache.cassandra.db.rows.Row;
import org.apache.cassandra.db.rows.UnfilteredRowIterator; import org.apache.cassandra.db.rows.UnfilteredRowIterator;
import org.apache.cassandra.dht.Murmur3Partitioner;
import org.apache.cassandra.exceptions.ConfigurationException; import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.index.Index;
import org.apache.cassandra.index.transactions.UpdateTransaction; import org.apache.cassandra.index.transactions.UpdateTransaction;
import org.apache.cassandra.io.sstable.Component; import org.apache.cassandra.io.sstable.Component;
import org.apache.cassandra.io.sstable.Descriptor; import org.apache.cassandra.io.sstable.Descriptor;
@ -71,11 +76,17 @@ import org.apache.cassandra.io.util.File;
import org.apache.cassandra.io.util.FileUtils; import org.apache.cassandra.io.util.FileUtils;
import org.apache.cassandra.metrics.ClearableHistogram; import org.apache.cassandra.metrics.ClearableHistogram;
import org.apache.cassandra.schema.ColumnMetadata; import org.apache.cassandra.schema.ColumnMetadata;
import org.apache.cassandra.schema.IndexMetadata;
import org.apache.cassandra.schema.Indexes;
import org.apache.cassandra.schema.KeyspaceParams; import org.apache.cassandra.schema.KeyspaceParams;
import org.apache.cassandra.schema.SchemaConstants; import org.apache.cassandra.schema.SchemaConstants;
import org.apache.cassandra.schema.SchemaTestUtil;
import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.reads.SpeculativeRetryPolicy; import org.apache.cassandra.service.reads.SpeculativeRetryPolicy;
import org.apache.cassandra.service.snapshot.SnapshotOptions;
import org.apache.cassandra.service.snapshot.SnapshotManager;
import org.apache.cassandra.service.snapshot.SnapshotManifest; import org.apache.cassandra.service.snapshot.SnapshotManifest;
import org.apache.cassandra.service.snapshot.SnapshotType;
import org.apache.cassandra.service.snapshot.TableSnapshot; import org.apache.cassandra.service.snapshot.TableSnapshot;
import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.utils.FBUtilities;
@ -83,6 +94,10 @@ import org.apache.cassandra.utils.WrappedRunnable;
import org.apache.cassandra.utils.concurrent.OpOrder.Barrier; import org.apache.cassandra.utils.concurrent.OpOrder.Barrier;
import org.apache.cassandra.utils.concurrent.OpOrder.Group; import org.apache.cassandra.utils.concurrent.OpOrder.Group;
import static org.apache.cassandra.cql3.statements.schema.IndexTarget.Type.VALUES;
import static org.apache.cassandra.schema.IndexMetadata.Kind.COMPOSITES;
import static org.apache.cassandra.schema.IndexMetadata.Kind.CUSTOM;
import static org.apache.cassandra.schema.IndexMetadata.fromIndexTargets;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
@ -121,14 +136,27 @@ public class ColumnFamilyStoreTest
@Before @Before
public void truncateCFS() public void truncateCFS()
{ {
Keyspace.open(KEYSPACE1).getColumnFamilyStore(CF_STANDARD1).truncateBlocking(); Keyspace.open(KEYSPACE1).getColumnFamilyStore(CF_STANDARD1).truncateBlockingWithoutSnapshot();
Keyspace.open(KEYSPACE1).getColumnFamilyStore(CF_STANDARD2).truncateBlocking(); Keyspace.open(KEYSPACE1).getColumnFamilyStore(CF_STANDARD2).truncateBlockingWithoutSnapshot();
Keyspace.open(KEYSPACE1).getColumnFamilyStore(CF_INDEX1).truncateBlocking(); Keyspace.open(KEYSPACE1).getColumnFamilyStore(CF_INDEX1).truncateBlockingWithoutSnapshot();
Keyspace.open(KEYSPACE2).getColumnFamilyStore(CF_STANDARD1).truncateBlocking(); Keyspace.open(KEYSPACE2).getColumnFamilyStore(CF_STANDARD1).truncateBlockingWithoutSnapshot();
Keyspace.open(KEYSPACE3).getColumnFamilyStore(CF_SPEC_RETRY1).truncateBlockingWithoutSnapshot();
SnapshotManager.instance.clearAllSnapshots(KEYSPACE1, CF_STANDARD1);
SnapshotManager.instance.clearAllSnapshots(KEYSPACE1, CF_STANDARD2);
SnapshotManager.instance.clearAllSnapshots(KEYSPACE1, CF_INDEX1);
SnapshotManager.instance.clearAllSnapshots(KEYSPACE2, CF_STANDARD1);
SnapshotManager.instance.clearAllSnapshots(KEYSPACE3, CF_SPEC_RETRY1);
}
@After
public void afterTest()
{
SnapshotManager.instance.clearAllSnapshots();
} }
@Test @Test
public void testMemtableTimestamp() throws Throwable public void testMemtableTimestamp()
{ {
ColumnFamilyStore cfs = Keyspace.open(KEYSPACE1).getColumnFamilyStore(CF_STANDARD1); ColumnFamilyStore cfs = Keyspace.open(KEYSPACE1).getColumnFamilyStore(CF_STANDARD1);
assertEquals(Memtable.NO_MIN_TIMESTAMP, fakeMemTableWithMinTS(cfs, EncodingStats.NO_STATS.minTimestamp).getMinTimestamp()); assertEquals(Memtable.NO_MIN_TIMESTAMP, fakeMemTableWithMinTS(cfs, EncodingStats.NO_STATS.minTimestamp).getMinTimestamp());
@ -142,20 +170,20 @@ public class ColumnFamilyStoreTest
ColumnFamilyStore cfs = keyspace.getColumnFamilyStore(CF_STANDARD1); ColumnFamilyStore cfs = keyspace.getColumnFamilyStore(CF_STANDARD1);
new RowUpdateBuilder(cfs.metadata(), 0, "key1") new RowUpdateBuilder(cfs.metadata(), 0, "key1")
.clustering("Column1") .clustering("Column1")
.add("val", "asdf") .add("val", "asdf")
.build() .build()
.applyUnsafe(); .applyUnsafe();
Util.flush(cfs); Util.flush(cfs);
new RowUpdateBuilder(cfs.metadata(), 1, "key1") new RowUpdateBuilder(cfs.metadata(), 1, "key1")
.clustering("Column1") .clustering("Column1")
.add("val", "asdf") .add("val", "asdf")
.build() .build()
.applyUnsafe(); .applyUnsafe();
Util.flush(cfs); Util.flush(cfs);
((ClearableHistogram)cfs.metric.sstablesPerReadHistogram.cf).clear(); // resets counts ((ClearableHistogram) cfs.metric.sstablesPerReadHistogram.cf).clear(); // resets counts
Util.getAll(Util.cmd(cfs, "key1").includeRow("c1").build()); Util.getAll(Util.cmd(cfs, "key1").includeRow("c1").build());
assertEquals(1, cfs.metric.sstablesPerReadHistogram.cf.getCount()); assertEquals(1, cfs.metric.sstablesPerReadHistogram.cf.getCount());
} }
@ -165,7 +193,7 @@ public class ColumnFamilyStoreTest
{ {
Keyspace keyspace = Keyspace.open(KEYSPACE1); Keyspace keyspace = Keyspace.open(KEYSPACE1);
ColumnFamilyStore cfs = keyspace.getColumnFamilyStore(CF_STANDARD1); ColumnFamilyStore cfs = keyspace.getColumnFamilyStore(CF_STANDARD1);
keyspace.getColumnFamilyStores().forEach(ColumnFamilyStore::truncateBlocking); keyspace.getColumnFamilyStores().forEach(ColumnFamilyStore::truncateBlockingWithoutSnapshot);
List<Mutation> rms = new LinkedList<>(); List<Mutation> rms = new LinkedList<>();
rms.add(new RowUpdateBuilder(cfs.metadata(), 0, "key1") rms.add(new RowUpdateBuilder(cfs.metadata(), 0, "key1")
@ -195,7 +223,7 @@ public class ColumnFamilyStoreTest
{ {
Row toCheck = Util.getOnlyRowUnfiltered(Util.cmd(cfs, "key1").build()); Row toCheck = Util.getOnlyRowUnfiltered(Util.cmd(cfs, "key1").build());
Iterator<Cell<?>> iter = toCheck.cells().iterator(); Iterator<Cell<?>> iter = toCheck.cells().iterator();
assert(Iterators.size(iter) == 0); assert (Iterators.size(iter) == 0);
} }
}; };
@ -265,42 +293,34 @@ public class ColumnFamilyStoreTest
{ {
ColumnFamilyStore cfs = Keyspace.open(KEYSPACE1).getColumnFamilyStore(CF_INDEX1); ColumnFamilyStore cfs = Keyspace.open(KEYSPACE1).getColumnFamilyStore(CF_INDEX1);
//cleanup any previous test gargbage
cfs.clearSnapshot("");
int numRows = 1000; int numRows = 1000;
long[] colValues = new long [numRows * 2]; // each row has two columns long[] colValues = new long[numRows * 2]; // each row has two columns
for (int i = 0; i < colValues.length; i+=2) for (int i = 0; i < colValues.length; i += 2)
{ {
colValues[i] = (i % 4 == 0 ? 1L : 2L); // index column colValues[i] = (i % 4 == 0 ? 1L : 2L); // index column
colValues[i+1] = 3L; //other column colValues[i + 1] = 3L; //other column
} }
ScrubTest.fillIndexCF(cfs, false, colValues); ScrubTest.fillIndexCF(cfs, false, colValues);
cfs.snapshot("nonEphemeralSnapshot", null, false, false); SnapshotManager.instance.takeSnapshot(SnapshotOptions.systemSnapshot("nonEphemeralSnapshot", SnapshotType.MISC, cfs.getKeyspaceTableName()).build());
cfs.snapshot("ephemeralSnapshot", null, true, false); SnapshotManager.instance.takeSnapshot(SnapshotOptions.systemSnapshot("ephemeralSnapshot", SnapshotType.REPAIR, cfs.getKeyspaceTableName()).ephemeral().build());
Map<String, TableSnapshot> snapshotDetails = cfs.listSnapshots(); assertTrue(SnapshotManager.instance.exists(p -> p.getTag().endsWith("ephemeralSnapshot")));
assertEquals(2, snapshotDetails.size()); assertTrue(SnapshotManager.instance.exists(p -> p.getTag().endsWith("nonEphemeralSnapshot")));
assertTrue(snapshotDetails.containsKey("ephemeralSnapshot"));
assertTrue(snapshotDetails.containsKey("nonEphemeralSnapshot"));
ColumnFamilyStore.clearEphemeralSnapshots(cfs.getDirectories()); SnapshotManager.instance.clearEphemeralSnapshots();
snapshotDetails = cfs.listSnapshots(); List<TableSnapshot> snapshots = SnapshotManager.instance.getSnapshots(p -> true);
assertEquals(1, snapshotDetails.size()); assertEquals(1, snapshots.size());
assertTrue(snapshotDetails.containsKey("nonEphemeralSnapshot"));
//test cleanup
cfs.clearSnapshot(""); assertTrue(snapshots.get(0).getTag().endsWith("nonEphemeralSnapshot"));
} }
@Test @Test
public void testSnapshotSize() throws IOException public void testSnapshotSize() throws IOException
{ {
// cleanup any previous test gargbage
ColumnFamilyStore cfs = Keyspace.open(KEYSPACE1).getColumnFamilyStore(CF_STANDARD1); ColumnFamilyStore cfs = Keyspace.open(KEYSPACE1).getColumnFamilyStore(CF_STANDARD1);
cfs.clearSnapshot("");
// Add row // Add row
new RowUpdateBuilder(cfs.metadata(), 0, "key1") new RowUpdateBuilder(cfs.metadata(), 0, "key1")
@ -311,10 +331,10 @@ public class ColumnFamilyStoreTest
Util.flush(cfs); Util.flush(cfs);
// snapshot // snapshot
cfs.snapshot("basic", null, false, false); SnapshotManager.instance.takeSnapshot("basic", cfs.getKeyspaceTableName());
// check snapshot was created // check snapshot was created
Map<String, TableSnapshot> snapshotDetails = cfs.listSnapshots(); Map<String, TableSnapshot> snapshotDetails = Util.listSnapshots(cfs);
assertThat(snapshotDetails).hasSize(1); assertThat(snapshotDetails).hasSize(1);
assertThat(snapshotDetails).containsKey("basic"); assertThat(snapshotDetails).containsKey("basic");
@ -329,13 +349,121 @@ public class ColumnFamilyStoreTest
// sizeOnDisk > trueSize because trueSize does not include manifest.json // sizeOnDisk > trueSize because trueSize does not include manifest.json
// Check that truesize now is > 0 // Check that truesize now is > 0
snapshotDetails = cfs.listSnapshots(); snapshotDetails = Util.listSnapshots(cfs);
details = snapshotDetails.get("basic"); details = snapshotDetails.get("basic");
assertThat(details.computeSizeOnDiskBytes()).isEqualTo(details.computeTrueSizeBytes()); assertThat(details.computeSizeOnDiskBytes()).isEqualTo(details.computeTrueSizeBytes());
} }
@Test @Test
public void testBackupAfterFlush() throws Throwable public void testTrueSnapshotSizeWithLegacyIndex() throws Throwable
{
testTrueSnapshotSizeInternal("snapshot_sizes_legacy", "true_snapshot_size_with_legacy_index", false);
}
@Test
public void testTrueSnapshotSizeWithSaiIndex() throws Throwable
{
testTrueSnapshotSizeInternal("snapshot_sizes_sai","true_snapshot_size_with_sai_index", true);
}
private void testTrueSnapshotSizeInternal(String keyspace, String table, boolean forSai) throws Throwable
{
TableMetadata tableMetadata = SchemaLoader.standardCFMD(keyspace, table).partitioner(Murmur3Partitioner.instance).build();
SchemaLoader.createKeyspace(keyspace, KeyspaceParams.simple(1), tableMetadata);
ColumnFamilyStore cfs = Keyspace.open(keyspace).getColumnFamilyStore(table);
new RowUpdateBuilder(cfs.metadata(), 0, "key1")
.clustering("Column1")
.add("val", "value1")
.build()
.applyUnsafe();
Util.flush(cfs);
assertThat(cfs.trueSnapshotsSize()).isZero();
SnapshotManager.instance.takeSnapshot("snapshot_without_index", cfs.getKeyspaceTableName());
long firstSnapshotsSize = cfs.trueSnapshotsSize();
Map<String, TableSnapshot> listedSnapshots = Util.listSnapshots(cfs);
assertThat(firstSnapshotsSize).isPositive();
assertThat(listedSnapshots.size()).isEqualTo(1);
assertThat(listedSnapshots.get("snapshot_without_index")).isNotNull();
long withoutIndexSize = listedSnapshots.get("snapshot_without_index").computeSizeOnDiskBytes();
long withoutIndexTrueSize = listedSnapshots.get("snapshot_without_index").computeTrueSizeBytes();
assertThat(withoutIndexSize).isGreaterThan(withoutIndexTrueSize);
assertEquals(firstSnapshotsSize, withoutIndexTrueSize);
// add index, trueSnapshotSize should reflect that
ColumnIdentifier col = ColumnIdentifier.getInterned("val", true);
IndexMetadata indexMetadata;
if (forSai)
indexMetadata = fromIndexTargets(List.of(new IndexTarget(col, VALUES)), "idx", CUSTOM, Map.of("class_name", "sai"));
else
indexMetadata = fromIndexTargets(List.of(new IndexTarget(col, VALUES)), "idx", COMPOSITES, Map.of());
TableMetadata tableMetadataWithIndex = SchemaLoader.standardCFMD(keyspace, table)
.partitioner(Murmur3Partitioner.instance)
.id(tableMetadata.id)
.indexes(Indexes.of(indexMetadata))
.build();
SchemaTestUtil.announceTableUpdate(tableMetadataWithIndex);
rebuildIndices(cfs);
SnapshotManager.instance.takeSnapshot("snapshot_with_index", new HashMap<>(), cfs.getKeyspaceTableName());
long secondSnapshotSize = cfs.trueSnapshotsSize();
Map<String, TableSnapshot> secondListedSnapshots = Util.listSnapshots(cfs);
assertThat(secondSnapshotSize).isPositive();
assertThat(secondSnapshotSize).isGreaterThan(firstSnapshotsSize);
assertThat(secondListedSnapshots.size()).isEqualTo(2);
assertThat(secondListedSnapshots.get("snapshot_with_index")).isNotNull();
assertThat(secondListedSnapshots.get("snapshot_without_index")).isNotNull();
long withIndexSize = secondListedSnapshots.get("snapshot_with_index").computeSizeOnDiskBytes();
long withIndexTrueSize = secondListedSnapshots.get("snapshot_with_index").computeTrueSizeBytes();
assertThat(withIndexSize).isGreaterThan(withIndexTrueSize);
assertEquals(secondSnapshotSize, withIndexTrueSize + withoutIndexTrueSize);
// taking another one is basically a copy of the previous
SnapshotManager.instance.takeSnapshot("another_snapshot_with_index", new HashMap<>(), cfs.getKeyspaceTableName());
long thirdSnapshotSize = cfs.trueSnapshotsSize();
Map<String, TableSnapshot> thirdListedSnapshots = Util.listSnapshots(cfs);
assertThat(thirdSnapshotSize).isPositive();
assertThat(thirdSnapshotSize).isGreaterThan(secondSnapshotSize);
long anotherWithIndexSize = thirdListedSnapshots.get("another_snapshot_with_index").computeSizeOnDiskBytes();
long anotherWithIndexTrueSize = thirdListedSnapshots.get("another_snapshot_with_index").computeTrueSizeBytes();
assertEquals(withIndexSize, anotherWithIndexSize);
assertEquals(withIndexTrueSize, anotherWithIndexTrueSize);
}
private void rebuildIndices(ColumnFamilyStore cfs)
{
// be sure we build that index
for (Index index : cfs.indexManager.listIndexes())
{
try
{
cfs.indexManager.buildIndex(index).get();
}
catch (Throwable t)
{
throw new RuntimeException("Unable to build index", t);
}
}
}
@Test
public void testBackupAfterFlush()
{ {
ColumnFamilyStore cfs = Keyspace.open(KEYSPACE2).getColumnFamilyStore(CF_STANDARD1); ColumnFamilyStore cfs = Keyspace.open(KEYSPACE2).getColumnFamilyStore(CF_STANDARD1);
new RowUpdateBuilder(cfs.metadata(), 0, ByteBufferUtil.bytes("key1")).clustering("Column1").add("val", "asdf").build().applyUnsafe(); new RowUpdateBuilder(cfs.metadata(), 0, ByteBufferUtil.bytes("key1")).clustering("Column1").add("val", "asdf").build().applyUnsafe();
@ -381,8 +509,8 @@ public class ColumnFamilyStoreTest
// remember, latencies are only an estimate - off by up to 20% by the 1.2 factor between buckets. // remember, latencies are only an estimate - off by up to 20% by the 1.2 factor between buckets.
assertThat(cfs.metric.coordinatorReadLatency.getCount()).isEqualTo(count); assertThat(cfs.metric.coordinatorReadLatency.getCount()).isEqualTo(count);
assertThat(cfs.metric.coordinatorReadLatency.getSnapshot().getValue(0.5)) assertThat(cfs.metric.coordinatorReadLatency.getSnapshot().getValue(0.5))
.isBetween((double) TimeUnit.MILLISECONDS.toMicros(5839), .isBetween((double) TimeUnit.MILLISECONDS.toMicros(5839),
(double) TimeUnit.MILLISECONDS.toMicros(5840)); (double) TimeUnit.MILLISECONDS.toMicros(5840));
// Sanity check the metrics - 75th percentileof linear 0-10000ms // Sanity check the metrics - 75th percentileof linear 0-10000ms
assertThat(cfs.metric.coordinatorWriteLatency.getCount()).isEqualTo(count); assertThat(cfs.metric.coordinatorWriteLatency.getCount()).isEqualTo(count);
assertThat(cfs.metric.coordinatorWriteLatency.getSnapshot().getValue(0.75)) assertThat(cfs.metric.coordinatorWriteLatency.getSnapshot().getValue(0.75))
@ -555,7 +683,7 @@ public class ColumnFamilyStoreTest
{ {
Keyspace keyspace = Keyspace.open(KEYSPACE1); Keyspace keyspace = Keyspace.open(KEYSPACE1);
ColumnFamilyStore cfs = keyspace.getColumnFamilyStore(CF_INDEX1); ColumnFamilyStore cfs = keyspace.getColumnFamilyStore(CF_INDEX1);
cfs.truncateBlocking(); cfs.truncateBlockingWithoutSnapshot();
UpdateBuilder builder = UpdateBuilder.create(cfs.metadata.get(), "key") UpdateBuilder builder = UpdateBuilder.create(cfs.metadata.get(), "key")
.newRow() .newRow()
@ -565,7 +693,7 @@ public class ColumnFamilyStoreTest
Util.flush(cfs); Util.flush(cfs);
String snapshotName = "newSnapshot"; String snapshotName = "newSnapshot";
cfs.snapshotWithoutMemtable(snapshotName); SnapshotManager.instance.takeSnapshot(snapshotName, Map.of(SnapshotOptions.SKIP_FLUSH, "true"), cfs.getKeyspaceTableName());
File snapshotManifestFile = cfs.getDirectories().getSnapshotManifestFile(snapshotName); File snapshotManifestFile = cfs.getDirectories().getSnapshotManifestFile(snapshotName);
SnapshotManifest manifest = SnapshotManifest.deserializeFromJsonFile(snapshotManifestFile); SnapshotManifest manifest = SnapshotManifest.deserializeFromJsonFile(snapshotManifestFile);
@ -595,17 +723,18 @@ public class ColumnFamilyStoreTest
writeData(cfs); writeData(cfs);
} }
TableSnapshot snapshot = cfs.snapshot("basic"); SnapshotManager.instance.takeSnapshot("basic", cfs.getKeyspaceTableName());
TableSnapshot snapshot = SnapshotManager.instance.getSnapshot(cfs.metadata.keyspace, cfs.metadata.name, "basic").get();
assertNotNull(snapshot);
assertThat(snapshot.exists()).isTrue(); assertThat(snapshot.exists()).isTrue();
assertThat(cfs.listSnapshots().containsKey("basic")).isTrue(); assertThat(Util.listSnapshots(cfs).containsKey("basic")).isTrue();
assertThat(cfs.listSnapshots().get("basic")).isEqualTo(snapshot); assertThat(Util.listSnapshots(cfs).get("basic")).isEqualTo(snapshot);
snapshot.getDirectories().forEach(FileUtils::deleteRecursive); snapshot.getDirectories().forEach(FileUtils::deleteRecursive);
assertThat(snapshot.exists()).isFalse(); assertThat(snapshot.exists()).isFalse();
assertFalse(cfs.listSnapshots().containsKey("basic")); assertFalse(Util.listSnapshots(cfs).containsKey("basic"));
} }
private void writeData(ColumnFamilyStore cfs) private void writeData(ColumnFamilyStore cfs)
@ -623,7 +752,8 @@ public class ColumnFamilyStoreTest
} }
@Test @Test
public void testSnapshotCreationAndDeleteEmptyTable() { public void testSnapshotCreationAndDeleteEmptyTable()
{
createSnapshotAndDelete(KEYSPACE1, CF_INDEX1, false); createSnapshotAndDelete(KEYSPACE1, CF_INDEX1, false);
createSnapshotAndDelete(KEYSPACE1, CF_STANDARD1, false); createSnapshotAndDelete(KEYSPACE1, CF_STANDARD1, false);
createSnapshotAndDelete(KEYSPACE1, CF_STANDARD2, false); createSnapshotAndDelete(KEYSPACE1, CF_STANDARD2, false);
@ -632,7 +762,8 @@ public class ColumnFamilyStoreTest
} }
@Test @Test
public void testSnapshotCreationAndDeletePopulatedTable() { public void testSnapshotCreationAndDeletePopulatedTable()
{
createSnapshotAndDelete(KEYSPACE1, CF_INDEX1, true); createSnapshotAndDelete(KEYSPACE1, CF_INDEX1, true);
createSnapshotAndDelete(KEYSPACE1, CF_STANDARD1, true); createSnapshotAndDelete(KEYSPACE1, CF_STANDARD1, true);
createSnapshotAndDelete(KEYSPACE1, CF_STANDARD2, true); createSnapshotAndDelete(KEYSPACE1, CF_STANDARD2, true);
@ -651,8 +782,8 @@ public class ColumnFamilyStoreTest
String keyspace = path.getParent().getFileName().toString(); String keyspace = path.getParent().getFileName().toString();
String table = path.getFileName().toString().split("-")[0]; String table = path.getFileName().toString().split("-")[0];
Assert.assertEquals(cfs.getTableName(), table); assertEquals(cfs.getTableName(), table);
Assert.assertEquals(KEYSPACE1, keyspace); assertEquals(KEYSPACE1, keyspace);
} }
@Test @Test
@ -685,15 +816,15 @@ public class ColumnFamilyStoreTest
} }
@VisibleForTesting @VisibleForTesting
public static long getSnapshotManifestAndSchemaFileSizes(TableSnapshot snapshot) throws IOException public static long getSnapshotManifestAndSchemaFileSizes(TableSnapshot snapshot)
{ {
Optional<File> schemaFile = snapshot.getSchemaFile();
Optional<File> manifestFile = snapshot.getManifestFile();
long schemaAndManifestFileSizes = 0; long schemaAndManifestFileSizes = 0;
schemaAndManifestFileSizes += schemaFile.isPresent() ? schemaFile.get().length() : 0; for (File schemaFile : snapshot.getSchemaFiles())
schemaAndManifestFileSizes += manifestFile.isPresent() ? manifestFile.get().length() : 0; schemaAndManifestFileSizes += schemaFile.length();
for (File manifestFile : snapshot.getManifestFiles())
schemaAndManifestFileSizes += manifestFile.length();
return schemaAndManifestFileSizes; return schemaAndManifestFileSizes;
} }
@ -827,7 +958,7 @@ public class ColumnFamilyStoreTest
@Override @Override
public UnfilteredPartitionIterator public UnfilteredPartitionIterator
partitionIterator(ColumnFilter columnFilter, DataRange dataRange, SSTableReadsListener listener) partitionIterator(ColumnFilter columnFilter, DataRange dataRange, SSTableReadsListener listener)
{ {
return null; return null;
} }

View File

@ -19,8 +19,11 @@ package org.apache.cassandra.db;
import java.io.IOException; import java.io.IOException;
import java.nio.file.FileStore; import java.nio.file.FileStore;
import java.nio.file.FileVisitResult;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileAttributeView; import java.nio.file.attribute.FileAttributeView;
import java.nio.file.attribute.FileStoreAttributeView; import java.nio.file.attribute.FileStoreAttributeView;
import java.time.Instant; import java.time.Instant;
@ -93,6 +96,7 @@ import org.apache.cassandra.schema.SchemaConstants;
import org.apache.cassandra.schema.SchemaKeyspaceTables; import org.apache.cassandra.schema.SchemaKeyspaceTables;
import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.DefaultFSErrorHandler; import org.apache.cassandra.service.DefaultFSErrorHandler;
import org.apache.cassandra.service.snapshot.SnapshotLoader;
import org.apache.cassandra.service.snapshot.SnapshotManifest; import org.apache.cassandra.service.snapshot.SnapshotManifest;
import org.apache.cassandra.service.snapshot.TableSnapshot; import org.apache.cassandra.service.snapshot.TableSnapshot;
import org.apache.cassandra.utils.JVMStabilityInspector; import org.apache.cassandra.utils.JVMStabilityInspector;
@ -326,12 +330,24 @@ public class DirectoriesTest
} }
} }
private Map<String, TableSnapshot> listSnapshots(Directories directories)
{
Set<TableSnapshot> snapshots = new SnapshotLoader(directories).loadSnapshots();
Map<String, TableSnapshot> tagSnapshotsMap = new HashMap<>();
for (TableSnapshot snapshot : snapshots)
tagSnapshotsMap.put(snapshot.getTag(), snapshot);
return tagSnapshotsMap;
}
@Test @Test
public void testListSnapshots() throws Exception { public void testListSnapshots() throws Exception {
// Initial state // Initial state
TableMetadata fakeTable = createFakeTable(TABLE_NAME); TableMetadata fakeTable = createFakeTable(TABLE_NAME);
Directories directories = new Directories(fakeTable, toDataDirectories(tempDataDir)); Directories directories = new Directories(fakeTable, toDataDirectories(tempDataDir));
assertThat(directories.listSnapshots()).isEmpty(); assertThat(listSnapshots(directories)).isEmpty();
// Create snapshot with and without manifest // Create snapshot with and without manifest
FakeSnapshot snapshot1 = createFakeSnapshot(fakeTable, SNAPSHOT1, true, false); FakeSnapshot snapshot1 = createFakeSnapshot(fakeTable, SNAPSHOT1, true, false);
@ -340,7 +356,7 @@ public class DirectoriesTest
FakeSnapshot snapshot3 = createFakeSnapshot(fakeTable, SNAPSHOT3, false, true); FakeSnapshot snapshot3 = createFakeSnapshot(fakeTable, SNAPSHOT3, false, true);
// Both snapshots should be present // Both snapshots should be present
Map<String, TableSnapshot> snapshots = directories.listSnapshots(); Map<String, TableSnapshot> snapshots = listSnapshots(directories);
assertThat(snapshots.keySet()).isEqualTo(Sets.newHashSet(SNAPSHOT1, SNAPSHOT2, SNAPSHOT3)); assertThat(snapshots.keySet()).isEqualTo(Sets.newHashSet(SNAPSHOT1, SNAPSHOT2, SNAPSHOT3));
assertThat(snapshots.get(SNAPSHOT1)).isEqualTo(snapshot1.asTableSnapshot()); assertThat(snapshots.get(SNAPSHOT1)).isEqualTo(snapshot1.asTableSnapshot());
assertThat(snapshots.get(SNAPSHOT2)).isEqualTo(snapshot2.asTableSnapshot()); assertThat(snapshots.get(SNAPSHOT2)).isEqualTo(snapshot2.asTableSnapshot());
@ -350,40 +366,13 @@ public class DirectoriesTest
snapshot1.snapshotDir.deleteRecursive(); snapshot1.snapshotDir.deleteRecursive();
// Only snapshot 2 and 3 should be present // Only snapshot 2 and 3 should be present
snapshots = directories.listSnapshots(); snapshots = listSnapshots(directories);
assertThat(snapshots.keySet()).isEqualTo(Sets.newHashSet(SNAPSHOT2, SNAPSHOT3)); assertThat(snapshots.keySet()).isEqualTo(Sets.newHashSet(SNAPSHOT2, SNAPSHOT3));
assertThat(snapshots.get(SNAPSHOT2)).isEqualTo(snapshot2.asTableSnapshot()); assertThat(snapshots.get(SNAPSHOT2)).isEqualTo(snapshot2.asTableSnapshot());
assertThat(snapshots.get(SNAPSHOT3)).isEqualTo(snapshot3.asTableSnapshot()); assertThat(snapshots.get(SNAPSHOT3)).isEqualTo(snapshot3.asTableSnapshot());
assertThat(snapshots.get(SNAPSHOT3).isEphemeral()).isTrue(); assertThat(snapshots.get(SNAPSHOT3).isEphemeral()).isTrue();
} }
@Test
public void testListSnapshotDirsByTag() throws Exception {
// Initial state
TableMetadata fakeTable = createFakeTable("FakeTable");
Directories directories = new Directories(fakeTable, toDataDirectories(tempDataDir));
assertThat(directories.listSnapshotDirsByTag()).isEmpty();
// Create snapshot with and without manifest
FakeSnapshot snapshot1 = createFakeSnapshot(fakeTable, SNAPSHOT1, true, false);
FakeSnapshot snapshot2 = createFakeSnapshot(fakeTable, SNAPSHOT2, false, false);
FakeSnapshot snapshot3 = createFakeSnapshot(fakeTable, SNAPSHOT3, false, true);
// Both snapshots should be present
Map<String, Set<File>> snapshotDirs = directories.listSnapshotDirsByTag();
assertThat(snapshotDirs.keySet()).isEqualTo(Sets.newHashSet(SNAPSHOT1, SNAPSHOT2, SNAPSHOT3));
assertThat(snapshotDirs.get(SNAPSHOT1)).allMatch(snapshotDir -> snapshotDir.equals(snapshot1.snapshotDir));
assertThat(snapshotDirs.get(SNAPSHOT2)).allMatch(snapshotDir -> snapshotDir.equals(snapshot2.snapshotDir));
assertThat(snapshotDirs.get(SNAPSHOT3)).allMatch(snapshotDir -> snapshotDir.equals(snapshot3.snapshotDir));
// Now remove snapshot1
snapshot1.snapshotDir.deleteRecursive();
// Only snapshot 2 and 3 should be present
snapshotDirs = directories.listSnapshotDirsByTag();
assertThat(snapshotDirs.keySet()).isEqualTo(Sets.newHashSet(SNAPSHOT2, SNAPSHOT3));
}
@Test @Test
public void testMaybeManifestLoading() throws Exception { public void testMaybeManifestLoading() throws Exception {
for (TableMetadata cfm : CFM) for (TableMetadata cfm : CFM)
@ -412,7 +401,7 @@ public class DirectoriesTest
} }
@Test @Test
public void testSecondaryIndexDirectories() public void testSecondaryIndexDirectories() throws IOException
{ {
TableMetadata.Builder builder = TableMetadata.Builder builder =
TableMetadata.builder(KS, "cf") TableMetadata.builder(KS, "cf")
@ -447,8 +436,8 @@ public class DirectoriesTest
// check if snapshot directory exists // check if snapshot directory exists
parentSnapshotDirectory.tryCreateDirectories(); parentSnapshotDirectory.tryCreateDirectories();
assertTrue(parentDirectories.snapshotExists("test")); assertTrue(snapshotExists(parentDirectories, "test"));
assertTrue(indexDirectories.snapshotExists("test")); assertTrue(snapshotExists(indexDirectories, "test"));
// check true snapshot size // check true snapshot size
Descriptor parentSnapshot = new Descriptor(parentSnapshotDirectory, KS, PARENT_CFM.name, sstableId(0), DatabaseDescriptor.getSelectedSSTableFormat()); Descriptor parentSnapshot = new Descriptor(parentSnapshotDirectory, KS, PARENT_CFM.name, sstableId(0), DatabaseDescriptor.getSelectedSSTableFormat());
@ -456,14 +445,21 @@ public class DirectoriesTest
Descriptor indexSnapshot = new Descriptor(indexSnapshotDirectory, KS, INDEX_CFM.name, sstableId(0), DatabaseDescriptor.getSelectedSSTableFormat()); Descriptor indexSnapshot = new Descriptor(indexSnapshotDirectory, KS, INDEX_CFM.name, sstableId(0), DatabaseDescriptor.getSelectedSSTableFormat());
createFile(indexSnapshot.fileFor(Components.DATA), 40); createFile(indexSnapshot.fileFor(Components.DATA), 40);
assertEquals(30, parentDirectories.trueSnapshotsSize());
assertEquals(40, indexDirectories.trueSnapshotsSize());
// check snapshot details // check snapshot details
Map<String, TableSnapshot> parentSnapshotDetail = parentDirectories.listSnapshots(); Map<String, TableSnapshot> parentSnapshotDetail = listSnapshots(parentDirectories);
assertTrue(parentSnapshotDetail.containsKey("test")); assertTrue(parentSnapshotDetail.containsKey("test"));
Set<String> files = new HashSet<>();
Files.walkFileTree(parentSnapshotDirectory.toPath(), new SimpleFileVisitor<>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
{
files.add(file.toAbsolutePath().toString());
return FileVisitResult.CONTINUE;
}
});
// CASSANDRA-17357: include indexes when computing true size of parent table // CASSANDRA-17357: include indexes when computing true size of parent table
assertEquals(70L, parentSnapshotDetail.get("test").computeTrueSizeBytes()); assertEquals(70L, parentSnapshotDetail.get("test").computeTrueSizeBytes(files));
// check backup directory // check backup directory
File parentBackupDirectory = Directories.getBackupsDirectory(parentDesc); File parentBackupDirectory = Directories.getBackupsDirectory(parentDesc);
@ -471,6 +467,30 @@ public class DirectoriesTest
assertEquals(parentBackupDirectory, indexBackupDirectory.parent()); assertEquals(parentBackupDirectory, indexBackupDirectory.parent());
} }
private boolean snapshotExists(Directories directories, String snapshotName)
{
for (File dir : directories.getDataPaths())
{
File snapshotDir;
if (Directories.isSecondaryIndexFolder(dir))
{
snapshotDir = new File(dir.parent(), join(Directories.SNAPSHOT_SUBDIR, snapshotName, dir.name()));
}
else
{
snapshotDir = new File(dir, join(Directories.SNAPSHOT_SUBDIR, snapshotName));
}
if (snapshotDir.exists())
return true;
}
return false;
}
private static String join(String... s)
{
return StringUtils.join(s, File.pathSeparator());
}
private File createFile(File file, int size) private File createFile(File file, int size)
{ {
try (FileOutputStreamPlus writer = new FileOutputStreamPlus(file);) try (FileOutputStreamPlus writer = new FileOutputStreamPlus(file);)

View File

@ -19,34 +19,49 @@
package org.apache.cassandra.db; package org.apache.cassandra.db;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.*; import java.util.Collection;
import java.util.List;
import org.assertj.core.api.Assertions; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.apache.cassandra.Util; import org.apache.cassandra.Util;
import org.apache.cassandra.cql3.CQLTester; import org.apache.cassandra.cql3.CQLTester;
import org.apache.cassandra.cql3.ColumnIdentifier; import org.apache.cassandra.cql3.ColumnIdentifier;
import org.apache.cassandra.cql3.UntypedResultSet; import org.apache.cassandra.cql3.UntypedResultSet;
import org.apache.cassandra.db.compaction.CompactionManager;
import org.apache.cassandra.db.filter.ClusteringIndexSliceFilter;
import org.apache.cassandra.db.filter.ColumnFilter;
import org.apache.cassandra.db.filter.DataLimits;
import org.apache.cassandra.db.filter.RowFilter;
import org.apache.cassandra.db.partitions.PartitionIterator;
import org.apache.cassandra.db.rows.Cell; import org.apache.cassandra.db.rows.Cell;
import org.apache.cassandra.db.rows.Row; import org.apache.cassandra.db.rows.Row;
import org.apache.cassandra.db.rows.RowIterator; import org.apache.cassandra.db.rows.RowIterator;
import org.apache.cassandra.io.sstable.AbstractRowIndexEntry; import org.apache.cassandra.io.sstable.AbstractRowIndexEntry;
import org.apache.cassandra.db.compaction.CompactionManager;
import org.apache.cassandra.db.filter.*;
import org.apache.cassandra.db.partitions.PartitionIterator;
import org.apache.cassandra.io.sstable.format.SSTableReader; import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.io.sstable.format.big.BigTableReader; import org.apache.cassandra.io.sstable.format.big.BigTableReader;
import org.apache.cassandra.metrics.ClearableHistogram; import org.apache.cassandra.metrics.ClearableHistogram;
import org.apache.cassandra.service.snapshot.SnapshotManager;
import org.apache.cassandra.service.snapshot.TableSnapshot;
import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.utils.FBUtilities;
import org.assertj.core.api.Assertions;
import static org.junit.Assert.*; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.fail;
public class KeyspaceTest extends CQLTester public class KeyspaceTest extends CQLTester
{ {
// Test needs synchronous table drop to avoid flushes causing flaky failures of testLimitSSTables // Test needs synchronous table drop to avoid flushes causing flaky failures of testLimitSSTables
@Before
public void cleanupSnapshots()
{
SnapshotManager.instance.clearAllSnapshots();
}
@Override @Override
protected String createTable(String query) protected String createTable(String query)
{ {
@ -425,10 +440,11 @@ public class KeyspaceTest extends CQLTester
Keyspace ks = Keyspace.open(KEYSPACE_PER_TEST); Keyspace ks = Keyspace.open(KEYSPACE_PER_TEST);
String table = getCurrentColumnFamilyStore().name; String table = getCurrentColumnFamilyStore().name;
ks.snapshot("test", table); SnapshotManager.instance.takeSnapshot("test", ks.getName() + '.' + table);
assertTrue(ks.snapshotExists("test")); List<TableSnapshot> snapshots = SnapshotManager.instance.getSnapshots(ks.getName());
assertEquals(1, ks.getAllSnapshots().count()); assertEquals(1, snapshots.size());
assertEquals(snapshots.get(0).getTag(), "test");
} }
@Test @Test

View File

@ -35,6 +35,8 @@ import org.apache.cassandra.index.internal.CassandraIndex;
import org.apache.cassandra.index.sasi.SASIIndex; import org.apache.cassandra.index.sasi.SASIIndex;
import org.apache.cassandra.schema.*; import org.apache.cassandra.schema.*;
import org.apache.cassandra.service.reads.SpeculativeRetryPolicy; import org.apache.cassandra.service.reads.SpeculativeRetryPolicy;
import org.apache.cassandra.service.snapshot.SnapshotManager;
import org.apache.cassandra.service.snapshot.SnapshotOptions;
import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.JsonUtils; import org.apache.cassandra.utils.JsonUtils;
@ -431,7 +433,7 @@ public class SchemaCQLHelperTest extends CQLTester
execute("INSERT INTO %s (pk1, pk2, ck1, ck2, reg1, reg2) VALUES (?, ?, ?, ?, ?, ?)", i, i + 1, i + 2, i + 3, null, i + 5); execute("INSERT INTO %s (pk1, pk2, ck1, ck2, reg1, reg2) VALUES (?, ?, ?, ?, ?, ?)", i, i + 1, i + 2, i + 3, null, i + 5);
ColumnFamilyStore cfs = Keyspace.open(keyspace()).getColumnFamilyStore(tableName); ColumnFamilyStore cfs = Keyspace.open(keyspace()).getColumnFamilyStore(tableName);
cfs.snapshot(SNAPSHOT); SnapshotManager.instance.takeSnapshot(SnapshotOptions.userSnapshot(SNAPSHOT, cfs.getKeyspaceTableName()));
String schema = Files.toString(cfs.getDirectories().getSnapshotSchemaFile(SNAPSHOT).toJavaIOFile(), Charset.defaultCharset()); String schema = Files.toString(cfs.getDirectories().getSnapshotSchemaFile(SNAPSHOT).toJavaIOFile(), Charset.defaultCharset());
assertThat(schema, assertThat(schema,
@ -480,6 +482,7 @@ public class SchemaCQLHelperTest extends CQLTester
"INDEX IF NOT EXISTS " + tableName + "_reg2_idx ON " + keyspace() + '.' + tableName + " (reg2)" + "INDEX IF NOT EXISTS " + tableName + "_reg2_idx ON " + keyspace() + '.' + tableName + " (reg2)" +
(" USING '" + (isIndexLegacy ? CassandraIndex.NAME : DatabaseDescriptor.getDefaultSecondaryIndex()) + "'") + ";")); (" USING '" + (isIndexLegacy ? CassandraIndex.NAME : DatabaseDescriptor.getDefaultSecondaryIndex()) + "'") + ";"));
// TODO: construct manifest from SnapshotManager
JsonNode manifest = JsonUtils.JSON_OBJECT_MAPPER.readTree(cfs.getDirectories().getSnapshotManifestFile(SNAPSHOT).toJavaIOFile()); JsonNode manifest = JsonUtils.JSON_OBJECT_MAPPER.readTree(cfs.getDirectories().getSnapshotManifestFile(SNAPSHOT).toJavaIOFile());
JsonNode files = manifest.get("files"); JsonNode files = manifest.get("files");
// two files, the second is index // two files, the second is index
@ -508,7 +511,7 @@ public class SchemaCQLHelperTest extends CQLTester
execute("INSERT INTO %s (pk1, pk2, ck1, ck2, reg1) VALUES (?, ?, ?, ?, ?)", i, i + 1, i + 2, i + 3, null); execute("INSERT INTO %s (pk1, pk2, ck1, ck2, reg1) VALUES (?, ?, ?, ?, ?)", i, i + 1, i + 2, i + 3, null);
ColumnFamilyStore cfs = Keyspace.open(keyspace()).getColumnFamilyStore(tableName); ColumnFamilyStore cfs = Keyspace.open(keyspace()).getColumnFamilyStore(tableName);
cfs.snapshot(SNAPSHOT); SnapshotManager.instance.takeSnapshot(SNAPSHOT, cfs.getKeyspaceTableName());
String schema = Files.toString(cfs.getDirectories().getSnapshotSchemaFile(SNAPSHOT).toJavaIOFile(), Charset.defaultCharset()); String schema = Files.toString(cfs.getDirectories().getSnapshotSchemaFile(SNAPSHOT).toJavaIOFile(), Charset.defaultCharset());
schema = schema.substring(schema.indexOf("CREATE TABLE")); // trim to ensure order schema = schema.substring(schema.indexOf("CREATE TABLE")); // trim to ensure order
@ -551,7 +554,7 @@ public class SchemaCQLHelperTest extends CQLTester
execute("INSERT INTO %s (pk1, reg1) VALUES (?, ?)", i, i + 1); execute("INSERT INTO %s (pk1, reg1) VALUES (?, ?)", i, i + 1);
ColumnFamilyStore cfs = Keyspace.open(keyspace()).getColumnFamilyStore(tableName); ColumnFamilyStore cfs = Keyspace.open(keyspace()).getColumnFamilyStore(tableName);
cfs.snapshot(SNAPSHOT); SnapshotManager.instance.takeSnapshot(SNAPSHOT, cfs.getKeyspaceTableName());
String schema = Files.toString(cfs.getDirectories().getSnapshotSchemaFile(SNAPSHOT).toJavaIOFile(), Charset.defaultCharset()); String schema = Files.toString(cfs.getDirectories().getSnapshotSchemaFile(SNAPSHOT).toJavaIOFile(), Charset.defaultCharset());
schema = schema.substring(schema.indexOf("CREATE TABLE")); // trim to ensure order schema = schema.substring(schema.indexOf("CREATE TABLE")); // trim to ensure order
@ -577,7 +580,7 @@ public class SchemaCQLHelperTest extends CQLTester
public void testSystemKsSnapshot() public void testSystemKsSnapshot()
{ {
ColumnFamilyStore cfs = Keyspace.open("system").getColumnFamilyStore("peers"); ColumnFamilyStore cfs = Keyspace.open("system").getColumnFamilyStore("peers");
cfs.snapshot(SNAPSHOT); SnapshotManager.instance.takeSnapshot(SNAPSHOT, cfs.getKeyspaceTableName());
Assert.assertTrue(cfs.getDirectories().getSnapshotManifestFile(SNAPSHOT).exists()); Assert.assertTrue(cfs.getDirectories().getSnapshotManifestFile(SNAPSHOT).exists());
Assert.assertFalse(cfs.getDirectories().getSnapshotSchemaFile(SNAPSHOT).exists()); Assert.assertFalse(cfs.getDirectories().getSnapshotSchemaFile(SNAPSHOT).exists());

View File

@ -27,6 +27,7 @@ import org.apache.cassandra.cql3.CQLTester;
import org.apache.cassandra.io.sstable.format.SSTableFormat.Components; import org.apache.cassandra.io.sstable.format.SSTableFormat.Components;
import org.apache.cassandra.io.sstable.format.SSTableReader; import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.io.util.File; import org.apache.cassandra.io.util.File;
import org.apache.cassandra.service.snapshot.SnapshotManager;
public class SnapshotTest extends CQLTester public class SnapshotTest extends CQLTester
{ {
@ -41,6 +42,7 @@ public class SnapshotTest extends CQLTester
File toc = sstable.descriptor.fileFor(Components.TOC); File toc = sstable.descriptor.fileFor(Components.TOC);
Files.write(toc.toPath(), new byte[0], StandardOpenOption.TRUNCATE_EXISTING); Files.write(toc.toPath(), new byte[0], StandardOpenOption.TRUNCATE_EXISTING);
} }
getCurrentColumnFamilyStore().snapshot("hello");
SnapshotManager.instance.takeSnapshot("hello", getCurrentColumnFamilyStore().getKeyspaceTableName());
} }
} }

View File

@ -25,6 +25,7 @@ import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
import org.apache.cassandra.SchemaLoader; import org.apache.cassandra.SchemaLoader;
import org.apache.cassandra.Util;
import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.cql3.QueryProcessor; import org.apache.cassandra.cql3.QueryProcessor;
import org.apache.cassandra.cql3.UntypedResultSet; import org.apache.cassandra.cql3.UntypedResultSet;
@ -34,7 +35,7 @@ import org.apache.cassandra.dht.Token;
import org.apache.cassandra.locator.InetAddressAndPort; import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.schema.SchemaConstants; import org.apache.cassandra.schema.SchemaConstants;
import org.apache.cassandra.schema.SchemaKeyspace; import org.apache.cassandra.schema.SchemaKeyspace;
import org.apache.cassandra.service.StorageService; import org.apache.cassandra.service.snapshot.SnapshotManager;
import org.apache.cassandra.transport.ProtocolVersion; import org.apache.cassandra.transport.ProtocolVersion;
import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.CassandraVersion; import org.apache.cassandra.utils.CassandraVersion;
@ -100,14 +101,14 @@ public class SystemKeyspaceTest
// First, check that in the absence of any previous installed version, we don't create snapshots // First, check that in the absence of any previous installed version, we don't create snapshots
for (ColumnFamilyStore cfs : Keyspace.open(SchemaConstants.SYSTEM_KEYSPACE_NAME).getColumnFamilyStores()) for (ColumnFamilyStore cfs : Keyspace.open(SchemaConstants.SYSTEM_KEYSPACE_NAME).getColumnFamilyStores())
cfs.clearUnsafe(); cfs.clearUnsafe();
StorageService.instance.clearSnapshot(Collections.emptyMap(), null, SchemaConstants.SYSTEM_KEYSPACE_NAME); SnapshotManager.instance.clearSnapshot(null, Collections.emptyMap(), SchemaConstants.SYSTEM_KEYSPACE_NAME);
SystemKeyspace.snapshotOnVersionChange(); SystemKeyspace.snapshotOnVersionChange();
assertDeleted(); assertDeleted();
// now setup system.local as if we're upgrading from a previous version // now setup system.local as if we're upgrading from a previous version
setupReleaseVersion(getOlderVersionString()); setupReleaseVersion(getOlderVersionString());
StorageService.instance.clearSnapshot(Collections.emptyMap(), null, SchemaConstants.SYSTEM_KEYSPACE_NAME); SnapshotManager.instance.clearSnapshot(null, Collections.emptyMap(), SchemaConstants.SYSTEM_KEYSPACE_NAME);
assertDeleted(); assertDeleted();
// Compare versions again & verify that snapshots were created for all tables in the system ks // Compare versions again & verify that snapshots were created for all tables in the system ks
@ -120,7 +121,7 @@ public class SystemKeyspaceTest
// clear out the snapshots & set the previous recorded version equal to the latest, we shouldn't // clear out the snapshots & set the previous recorded version equal to the latest, we shouldn't
// see any new snapshots created this time. // see any new snapshots created this time.
StorageService.instance.clearSnapshot(Collections.emptyMap(), null, SchemaConstants.SYSTEM_KEYSPACE_NAME); SnapshotManager.instance.clearSnapshot(null, Collections.emptyMap(), SchemaConstants.SYSTEM_KEYSPACE_NAME);
setupReleaseVersion(FBUtilities.getReleaseVersionString()); setupReleaseVersion(FBUtilities.getReleaseVersionString());
SystemKeyspace.snapshotOnVersionChange(); SystemKeyspace.snapshotOnVersionChange();
@ -129,7 +130,7 @@ public class SystemKeyspaceTest
// 10 files expected. // 10 files expected.
assertDeleted(); assertDeleted();
StorageService.instance.clearSnapshot(Collections.emptyMap(), null, SchemaConstants.SYSTEM_KEYSPACE_NAME); SnapshotManager.instance.clearSnapshot(null, Collections.emptyMap(), SchemaConstants.SYSTEM_KEYSPACE_NAME);
} }
@Test @Test
@ -170,7 +171,7 @@ public class SystemKeyspaceTest
Set<String> snapshottedTableNames = new HashSet<>(); Set<String> snapshottedTableNames = new HashSet<>();
for (ColumnFamilyStore cfs : Keyspace.open(keyspace).getColumnFamilyStores()) for (ColumnFamilyStore cfs : Keyspace.open(keyspace).getColumnFamilyStores())
{ {
if (!cfs.listSnapshots().isEmpty()) if (!Util.listSnapshots(cfs).isEmpty())
snapshottedTableNames.add(cfs.getTableName()); snapshottedTableNames.add(cfs.getTableName());
} }
return snapshottedTableNames; return snapshottedTableNames;

View File

@ -19,27 +19,30 @@
package org.apache.cassandra.db.virtual; package org.apache.cassandra.db.virtual;
import java.time.Instant; import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Collections; import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.List;
import java.util.Map;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import org.junit.After; import org.junit.After;
import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.apache.cassandra.config.DurationSpec;
import org.apache.cassandra.cql3.CQLTester; import org.apache.cassandra.cql3.CQLTester;
import org.apache.cassandra.cql3.UntypedResultSet; import org.apache.cassandra.cql3.UntypedResultSet;
import org.apache.cassandra.service.StorageService; import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.utils.Clock; import org.apache.cassandra.service.snapshot.SnapshotOptions;
import org.apache.cassandra.service.snapshot.SnapshotManager;
import org.apache.cassandra.service.snapshot.TableSnapshot;
public class SnapshotsTableTest extends CQLTester public class SnapshotsTableTest extends CQLTester
{ {
private static final String KS_NAME = "vts"; private static final String KS_NAME = "vts";
private static final String SNAPSHOT_TTL = "snapshotTtl"; private static final String SNAPSHOT_TTL = "snapshotTtl";
private static final String SNAPSHOT_NO_TTL = "snapshotNoTtl"; private static final String SNAPSHOT_NO_TTL = "snapshotNoTtl";
private static final DurationSpec.IntSecondsBound ttl = new DurationSpec.IntSecondsBound("4h"); private static final String TTL = "4h";
@Before @Before
public void before() throws Throwable public void before() throws Throwable
@ -58,48 +61,56 @@ public class SnapshotsTableTest extends CQLTester
@After @After
public void after() public void after()
{ {
StorageService.instance.clearSnapshot(Collections.emptyMap(), SNAPSHOT_NO_TTL, KEYSPACE); SnapshotManager.instance.clearSnapshot(SNAPSHOT_NO_TTL, Collections.emptyMap(), KEYSPACE);
StorageService.instance.clearSnapshot(Collections.emptyMap(), SNAPSHOT_TTL, KEYSPACE); SnapshotManager.instance.clearSnapshot(SNAPSHOT_TTL, Collections.emptyMap(), KEYSPACE);
schemaChange(String.format("DROP TABLE %s", KEYSPACE + '.' + currentTable()));
}
schemaChange(String.format("DROP TABLE %s", KEYSPACE + "." + currentTable())); private static Date toDate(Instant instant)
{
return new Date(instant.toEpochMilli());
}
private static TableSnapshot createSnapshot(String snapshotTtl, Map<String, String> options, ColumnFamilyStore cfs)
{
List<TableSnapshot> snapshots = SnapshotManager.instance.takeSnapshot(SnapshotOptions.userSnapshot(snapshotTtl, options, cfs.getKeyspaceTableName()));
Assert.assertEquals(1, snapshots.size());
return snapshots.iterator().next();
} }
@Test @Test
public void testSnapshots() throws Throwable public void testSnapshots()
{ {
Instant now = Instant.ofEpochMilli(Clock.Global.currentTimeMillis()).truncatedTo(ChronoUnit.MILLIS); ColumnFamilyStore cfs = getCurrentColumnFamilyStore(KEYSPACE);
Date createdAt = new Date(now.toEpochMilli()); TableSnapshot snapshotWithTtl = createSnapshot(SNAPSHOT_TTL, Map.of(SnapshotOptions.TTL, TTL), cfs);
Date expiresAt = new Date(now.plusSeconds(ttl.toSeconds()).toEpochMilli()); TableSnapshot snapshotWithoutTtl = createSnapshot(SNAPSHOT_NO_TTL, Collections.emptyMap(), cfs);
getCurrentColumnFamilyStore(KEYSPACE).snapshot(SNAPSHOT_NO_TTL, null, false, false, null, null, now);
getCurrentColumnFamilyStore(KEYSPACE).snapshot(SNAPSHOT_TTL, null, false, false, ttl, null, now);
// query all from snapshots virtual table // query all from snapshots virtual table
UntypedResultSet result = execute("SELECT name, keyspace_name, table_name, created_at, expires_at, ephemeral FROM vts.snapshots"); UntypedResultSet result = execute("SELECT name, keyspace_name, table_name, created_at, expires_at, ephemeral FROM vts.snapshots");
assertRowsIgnoringOrder(result, assertRowsIgnoringOrder(result,
row(SNAPSHOT_NO_TTL, KEYSPACE, currentTable(), createdAt, null, false), row(SNAPSHOT_NO_TTL, KEYSPACE, currentTable(), toDate(snapshotWithoutTtl.getCreatedAt()), null, false),
row(SNAPSHOT_TTL, KEYSPACE, currentTable(), createdAt, expiresAt, false)); row(SNAPSHOT_TTL, KEYSPACE, currentTable(), toDate(snapshotWithTtl.getCreatedAt()), toDate(snapshotWithTtl.getExpiresAt()), false));
// query with conditions // query with conditions
result = execute("SELECT name, keyspace_name, table_name, created_at, expires_at, ephemeral FROM vts.snapshots where ephemeral = false"); result = execute("SELECT name, keyspace_name, table_name, created_at, expires_at, ephemeral FROM vts.snapshots where ephemeral = false");
assertRows(result, assertRows(result,
row(SNAPSHOT_NO_TTL, KEYSPACE, currentTable(), createdAt, null, false), row(SNAPSHOT_NO_TTL, KEYSPACE, currentTable(), toDate(snapshotWithoutTtl.getCreatedAt()), null, false),
row(SNAPSHOT_TTL, KEYSPACE, currentTable(), createdAt, expiresAt, false)); row(SNAPSHOT_TTL, KEYSPACE, currentTable(), toDate(snapshotWithTtl.getCreatedAt()), toDate(snapshotWithTtl.getExpiresAt()), false));
result = execute("SELECT name, keyspace_name, table_name, created_at, expires_at, ephemeral FROM vts.snapshots where size_on_disk > 1000"); result = execute("SELECT name, keyspace_name, table_name, created_at, expires_at, ephemeral FROM vts.snapshots where size_on_disk > 1000");
assertRows(result, assertRows(result,
row(SNAPSHOT_NO_TTL, KEYSPACE, currentTable(), createdAt, null, false), row(SNAPSHOT_NO_TTL, KEYSPACE, currentTable(), toDate(snapshotWithoutTtl.getCreatedAt()), null, false),
row(SNAPSHOT_TTL, KEYSPACE, currentTable(), createdAt, expiresAt, false)); row(SNAPSHOT_TTL, KEYSPACE, currentTable(), toDate(snapshotWithTtl.getCreatedAt()), toDate(snapshotWithTtl.getExpiresAt()), false));
result = execute("SELECT name, keyspace_name, table_name, created_at, expires_at, ephemeral FROM vts.snapshots where name = ?", SNAPSHOT_TTL); result = execute("SELECT name, keyspace_name, table_name, created_at, expires_at, ephemeral FROM vts.snapshots where name = ?", SNAPSHOT_TTL);
assertRows(result, assertRows(result,
row(SNAPSHOT_TTL, KEYSPACE, currentTable(), createdAt, expiresAt, false)); row(SNAPSHOT_TTL, KEYSPACE, currentTable(), toDate(snapshotWithTtl.getCreatedAt()), toDate(snapshotWithTtl.getExpiresAt()), false));
// clear some snapshots // clear some snapshots
StorageService.instance.clearSnapshot(Collections.emptyMap(), SNAPSHOT_NO_TTL, KEYSPACE); SnapshotManager.instance.clearSnapshot(SNAPSHOT_NO_TTL, Collections.emptyMap(), KEYSPACE);
result = execute("SELECT name, keyspace_name, table_name, created_at, expires_at, ephemeral FROM vts.snapshots"); result = execute("SELECT name, keyspace_name, table_name, created_at, expires_at, ephemeral FROM vts.snapshots");
assertRowsIgnoringOrder(result, assertRowsIgnoringOrder(result,
row(SNAPSHOT_TTL, KEYSPACE, currentTable(), createdAt, expiresAt, false)); row(SNAPSHOT_TTL, KEYSPACE, currentTable(), toDate(snapshotWithTtl.getCreatedAt()), toDate(snapshotWithTtl.getExpiresAt()), false));
} }
} }

View File

@ -102,7 +102,8 @@ import org.apache.cassandra.schema.MockSchema;
import org.apache.cassandra.schema.Schema; import org.apache.cassandra.schema.Schema;
import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.StorageService; import org.apache.cassandra.service.StorageService;
import org.apache.cassandra.service.snapshot.TableSnapshot; import org.apache.cassandra.service.snapshot.SnapshotManager;
import org.apache.cassandra.service.snapshot.SnapshotOptions;
import org.apache.cassandra.utils.ConfigGenBuilder; import org.apache.cassandra.utils.ConfigGenBuilder;
import org.apache.cassandra.utils.JVMStabilityInspector; import org.apache.cassandra.utils.JVMStabilityInspector;
import org.apache.cassandra.utils.Throwables; import org.apache.cassandra.utils.Throwables;
@ -683,8 +684,7 @@ public abstract class SAITester extends CQLTester.Fuzzed
protected int snapshot(String snapshotName) protected int snapshot(String snapshotName)
{ {
ColumnFamilyStore cfs = getCurrentColumnFamilyStore(); ColumnFamilyStore cfs = getCurrentColumnFamilyStore();
TableSnapshot snapshot = cfs.snapshot(snapshotName); return SnapshotManager.instance.takeSnapshot(SnapshotOptions.userSnapshot(snapshotName, cfs.getKeyspaceTableName())).iterator().next().getDirectories().size();
return snapshot.getDirectories().size();
} }
protected void restoreSnapshot(String snapshot) protected void restoreSnapshot(String snapshot)

View File

@ -126,6 +126,7 @@ import org.apache.cassandra.schema.Schema;
import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.serializers.MarshalException; import org.apache.cassandra.serializers.MarshalException;
import org.apache.cassandra.serializers.TypeSerializer; import org.apache.cassandra.serializers.TypeSerializer;
import org.apache.cassandra.service.snapshot.SnapshotManager;
import org.apache.cassandra.service.snapshot.SnapshotManifest; import org.apache.cassandra.service.snapshot.SnapshotManifest;
import org.apache.cassandra.service.snapshot.TableSnapshot; import org.apache.cassandra.service.snapshot.TableSnapshot;
import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.ByteBufferUtil;
@ -199,7 +200,7 @@ public class SASIIndexTest
try try
{ {
store.snapshot(snapshotName); SnapshotManager.instance.takeSnapshot(snapshotName, store.getKeyspaceTableName());
// Compact to make true snapshot size != 0 // Compact to make true snapshot size != 0
store.forceMajorCompaction(); store.forceMajorCompaction();
@ -208,6 +209,7 @@ public class SASIIndexTest
SnapshotManifest manifest = SnapshotManifest.deserializeFromJsonFile(store.getDirectories().getSnapshotManifestFile(snapshotName)); SnapshotManifest manifest = SnapshotManifest.deserializeFromJsonFile(store.getDirectories().getSnapshotManifestFile(snapshotName));
Assert.assertFalse(ssTableReaders.isEmpty()); Assert.assertFalse(ssTableReaders.isEmpty());
Assert.assertNotNull(manifest.files);
Assert.assertFalse(manifest.files.isEmpty()); Assert.assertFalse(manifest.files.isEmpty());
Assert.assertEquals(ssTableReaders.size(), manifest.files.size()); Assert.assertEquals(ssTableReaders.size(), manifest.files.size());
@ -243,7 +245,7 @@ public class SASIIndexTest
} }
} }
TableSnapshot details = store.listSnapshots().get(snapshotName); TableSnapshot details = Util.listSnapshots(store).get(snapshotName);
// check that SASI components are included in the computation of snapshot size // check that SASI components are included in the computation of snapshot size
long snapshotSize = tableSize + indexSize + getSnapshotManifestAndSchemaFileSizes(details); long snapshotSize = tableSize + indexSize + getSnapshotManifestAndSchemaFileSizes(details);
@ -251,7 +253,7 @@ public class SASIIndexTest
} }
finally finally
{ {
store.clearSnapshot(snapshotName); SnapshotManager.instance.clearSnapshot(store.getKeyspaceName(), store.getTableName(), snapshotName);
} }
} }

View File

@ -31,6 +31,7 @@ import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.apache.cassandra.SchemaLoader; import org.apache.cassandra.SchemaLoader;
import org.apache.cassandra.Util;
import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.cql3.QueryProcessor; import org.apache.cassandra.cql3.QueryProcessor;
import org.apache.cassandra.cql3.UntypedResultSet; import org.apache.cassandra.cql3.UntypedResultSet;
@ -129,7 +130,7 @@ public class SchemaKeyspaceTest
SchemaTestUtil.announceTableDrop(keyspaceName, tableName); SchemaTestUtil.announceTableDrop(keyspaceName, tableName);
assertFalse(cfs.listSnapshots().isEmpty()); assertFalse(Util.listSnapshots(cfs).isEmpty());
} }
@Test @Test
@ -147,7 +148,7 @@ public class SchemaKeyspaceTest
SchemaTestUtil.announceTableDrop(keyspaceName, tableName); SchemaTestUtil.announceTableDrop(keyspaceName, tableName);
assertTrue(cfs.listSnapshots().isEmpty()); assertTrue(Util.listSnapshots(cfs).isEmpty());
} }
private static void updateTable(String keyspace, TableMetadata oldTable, TableMetadata newTable) private static void updateTable(String keyspace, TableMetadata oldTable, TableMetadata newTable)

View File

@ -61,6 +61,7 @@ import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.locator.Replica; import org.apache.cassandra.locator.Replica;
import org.apache.cassandra.repair.messages.RepairOption; import org.apache.cassandra.repair.messages.RepairOption;
import org.apache.cassandra.schema.KeyspaceParams; import org.apache.cassandra.schema.KeyspaceParams;
import org.apache.cassandra.service.snapshot.TableSnapshot;
import org.apache.cassandra.streaming.PreviewKind; import org.apache.cassandra.streaming.PreviewKind;
import org.apache.cassandra.tcm.ClusterMetadata; import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.tcm.membership.NodeAddresses; import org.apache.cassandra.tcm.membership.NodeAddresses;
@ -293,7 +294,7 @@ public class ActiveRepairServiceTest
true, PreviewKind.NONE); true, PreviewKind.NONE);
createSSTables(store, 2); createSSTables(store, 2);
store.getRepairManager().snapshot(prsId.toString(), ranges, false); store.getRepairManager().snapshot(prsId.toString(), ranges, false);
try (Refs<SSTableReader> refs = store.getSnapshotSSTableReaders(prsId.toString())) try (Refs<SSTableReader> refs = TableSnapshot.getSnapshotSSTableReaders(store, prsId.toString()))
{ {
assertEquals(original, Sets.newHashSet(refs.iterator())); assertEquals(original, Sets.newHashSet(refs.iterator()));
} }

View File

@ -55,6 +55,7 @@ import org.apache.cassandra.schema.ReplicationParams;
import org.apache.cassandra.schema.SchemaConstants; import org.apache.cassandra.schema.SchemaConstants;
import org.apache.cassandra.schema.SchemaKeyspaceTables; import org.apache.cassandra.schema.SchemaKeyspaceTables;
import org.apache.cassandra.schema.SchemaTestUtil; import org.apache.cassandra.schema.SchemaTestUtil;
import org.apache.cassandra.service.snapshot.SnapshotManager;
import org.apache.cassandra.tcm.ClusterMetadata; import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.tcm.ClusterMetadataService; import org.apache.cassandra.tcm.ClusterMetadataService;
import org.apache.cassandra.tcm.membership.Location; import org.apache.cassandra.tcm.membership.Location;
@ -144,6 +145,7 @@ public class StorageServiceServerTest
@Before @Before
public void resetCMS() public void resetCMS()
{ {
SnapshotManager.instance.clearAllSnapshots();
ServerTestUtils.resetCMS(); ServerTestUtils.resetCMS();
} }
@ -158,21 +160,27 @@ public class StorageServiceServerTest
public void testSnapshotWithFlush() throws IOException public void testSnapshotWithFlush() throws IOException
{ {
// no need to insert extra data, even an "empty" database will have a little information in the system keyspace // no need to insert extra data, even an "empty" database will have a little information in the system keyspace
StorageService.instance.takeSnapshot(UUID.randomUUID().toString()); String snapshotName = UUID.randomUUID().toString();
StorageService.instance.takeSnapshot(snapshotName);
Assert.assertTrue(SnapshotManager.instance.exists(p -> p.getTag().equals(snapshotName)));
} }
@Test @Test
public void testTableSnapshot() throws IOException public void testTableSnapshot() throws IOException
{ {
// no need to insert extra data, even an "empty" database will have a little information in the system keyspace // no need to insert extra data, even an "empty" database will have a little information in the system keyspace
StorageService.instance.takeTableSnapshot(SchemaConstants.SCHEMA_KEYSPACE_NAME, SchemaKeyspaceTables.KEYSPACES, UUID.randomUUID().toString()); String snapshotName = UUID.randomUUID().toString();
StorageService.instance.takeTableSnapshot(SchemaConstants.SCHEMA_KEYSPACE_NAME, SchemaKeyspaceTables.KEYSPACES, snapshotName);
Assert.assertTrue(SnapshotManager.instance.exists(SchemaConstants.SCHEMA_KEYSPACE_NAME, SchemaKeyspaceTables.KEYSPACES, snapshotName));
} }
@Test @Test
public void testSnapshot() throws IOException public void testSnapshot() throws IOException
{ {
// no need to insert extra data, even an "empty" database will have a little information in the system keyspace // no need to insert extra data, even an "empty" database will have a little information in the system keyspace
StorageService.instance.takeSnapshot(UUID.randomUUID().toString(), SchemaConstants.SCHEMA_KEYSPACE_NAME); String snapshotName = UUID.randomUUID().toString();
StorageService.instance.takeSnapshot(snapshotName, SchemaConstants.SCHEMA_KEYSPACE_NAME);
Assert.assertTrue(SnapshotManager.instance.exists(p -> p.getTag().equals(snapshotName) && p.getKeyspaceName().equals(SchemaConstants.SCHEMA_KEYSPACE_NAME)));
} }
@Test @Test

View File

@ -22,26 +22,22 @@ import java.time.Instant;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.atomic.AtomicReference;
import com.google.common.util.concurrent.Uninterruptibles; import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import org.junit.ClassRule; import org.junit.ClassRule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.TemporaryFolder; import org.junit.rules.TemporaryFolder;
import org.apache.cassandra.config.CassandraRelevantProperties;
import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.io.util.FileUtils; import org.apache.cassandra.io.util.FileUtils;
import org.apache.cassandra.service.DefaultFSErrorHandler; import org.apache.cassandra.service.DefaultFSErrorHandler;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.apache.cassandra.service.snapshot.TableSnapshotTest.createFolders; import static org.apache.cassandra.service.snapshot.TableSnapshotTest.createFolders;
import static org.apache.cassandra.utils.Clock.Global.currentTimeMillis;
import static org.apache.cassandra.utils.FBUtilities.now; import static org.apache.cassandra.utils.FBUtilities.now;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await;
import static org.junit.Assert.assertTrue;
public class MetadataSnapshotsTest public class MetadataSnapshotsTest
{ {
@ -50,6 +46,9 @@ public class MetadataSnapshotsTest
@BeforeClass @BeforeClass
public static void beforeClass() public static void beforeClass()
{ {
CassandraRelevantProperties.SNAPSHOT_CLEANUP_INITIAL_DELAY_SECONDS.setInt(3);
CassandraRelevantProperties.SNAPSHOT_CLEANUP_PERIOD_SECONDS.setInt(3);
DatabaseDescriptor.daemonInitialization(); DatabaseDescriptor.daemonInitialization();
FileUtils.setFSErrorHandler(new DefaultFSErrorHandler()); FileUtils.setFSErrorHandler(new DefaultFSErrorHandler());
} }
@ -57,6 +56,21 @@ public class MetadataSnapshotsTest
@ClassRule @ClassRule
public static TemporaryFolder temporaryFolder = new TemporaryFolder(); public static TemporaryFolder temporaryFolder = new TemporaryFolder();
private SnapshotManager manager;
@Before
public void beforeTest()
{
manager = SnapshotManager.instance;
}
@After
public void afterTest() throws Exception
{
SnapshotManager.instance.clearAllSnapshots();
SnapshotManager.instance.close();
}
private TableSnapshot generateSnapshotDetails(String tag, Instant expiration, boolean ephemeral) private TableSnapshot generateSnapshotDetails(String tag, Instant expiration, boolean ephemeral)
{ {
try try
@ -77,25 +91,28 @@ public class MetadataSnapshotsTest
} }
@Test @Test
public void testLoadSnapshots() throws Exception { public void testExpiringSnapshots()
{
TableSnapshot expired = generateSnapshotDetails("expired", Instant.EPOCH, false); TableSnapshot expired = generateSnapshotDetails("expired", Instant.EPOCH, false);
TableSnapshot nonExpired = generateSnapshotDetails("non-expired", now().plusSeconds(ONE_DAY_SECS), false); TableSnapshot nonExpired = generateSnapshotDetails("non-expired", now().plusSeconds(ONE_DAY_SECS), false);
TableSnapshot nonExpiring = generateSnapshotDetails("non-expiring", null, false); TableSnapshot nonExpiring = generateSnapshotDetails("non-expiring", null, false);
List<TableSnapshot> snapshots = Arrays.asList(expired, nonExpired, nonExpiring); List<TableSnapshot> snapshots = Arrays.asList(expired, nonExpired, nonExpiring);
// Create SnapshotManager with 3 snapshots: expired, non-expired and non-expiring // Create SnapshotManager with 3 snapshots: expired, non-expired and non-expiring
SnapshotManager manager = new SnapshotManager(3, 3); manager.start(false);
manager.addSnapshots(snapshots); for (TableSnapshot snapshot : snapshots)
manager.addSnapshot(snapshot);
// Only expiring snapshots should be loaded // Only expiring snapshots should be loaded
assertThat(manager.getExpiringSnapshots()).hasSize(2); assertThat(manager.getSnapshots(TableSnapshot::isExpiring)).hasSize(2);
assertThat(manager.getExpiringSnapshots()).contains(expired); assertThat(manager.getSnapshots(TableSnapshot::isExpiring)).contains(expired);
assertThat(manager.getExpiringSnapshots()).contains(nonExpired); assertThat(manager.getSnapshots(TableSnapshot::isExpiring)).contains(nonExpired);
} }
@Test @Test
public void testClearExpiredSnapshots() throws Exception { public void testClearExpiredSnapshots()
SnapshotManager manager = new SnapshotManager(3, 3); {
manager.start(false);
// Add 3 snapshots: expired, non-expired and non-expiring // Add 3 snapshots: expired, non-expired and non-expiring
TableSnapshot expired = generateSnapshotDetails("expired", Instant.EPOCH, false); TableSnapshot expired = generateSnapshotDetails("expired", Instant.EPOCH, false);
@ -106,119 +123,66 @@ public class MetadataSnapshotsTest
manager.addSnapshot(nonExpiring); manager.addSnapshot(nonExpiring);
// Only expiring snapshot should be indexed and all should exist // Only expiring snapshot should be indexed and all should exist
assertThat(manager.getExpiringSnapshots()).hasSize(2); assertThat(manager.getSnapshots(TableSnapshot::isExpiring)).hasSize(2);
assertThat(manager.getExpiringSnapshots()).contains(expired); assertThat(manager.getSnapshots(TableSnapshot::isExpiring)).contains(expired);
assertThat(manager.getExpiringSnapshots()).contains(nonExpired); assertThat(manager.getSnapshots(TableSnapshot::isExpiring)).contains(nonExpired);
assertThat(expired.exists()).isTrue(); assertThat(expired.exists()).isTrue();
assertThat(nonExpired.exists()).isTrue(); assertThat(nonExpired.exists()).isTrue();
assertThat(nonExpiring.exists()).isTrue(); assertThat(nonExpiring.exists()).isTrue();
// After clearing expired snapshots, expired snapshot should be removed while the others should remain // After clearing expired snapshots, expired snapshot should be removed while the others should remain
manager.clearExpiredSnapshots(); manager.clearExpiredSnapshots();
assertThat(manager.getExpiringSnapshots()).hasSize(1); assertThat(manager.getSnapshots(TableSnapshot::isExpiring)).hasSize(1);
assertThat(manager.getExpiringSnapshots()).contains(nonExpired); assertThat(manager.getSnapshots(TableSnapshot::isExpiring)).contains(nonExpired);
assertThat(expired.exists()).isFalse(); assertThat(expired.exists()).isFalse();
assertThat(nonExpired.exists()).isTrue(); assertThat(nonExpired.exists()).isTrue();
assertThat(nonExpiring.exists()).isTrue(); assertThat(nonExpiring.exists()).isTrue();
} }
@Test @Test
public void testScheduledCleanup() throws Exception { public void testScheduledCleanup() throws Exception
SnapshotManager manager = new SnapshotManager(0, 1); {
try manager.start(true);
{
// Start snapshot manager which should start expired snapshot cleanup thread
manager.start();
// Add 2 expiring snapshots: one to expire in 2 seconds, another in 1 day // Add 2 expiring snapshots: one to expire in 6 seconds, another in 1 day
int TTL_SECS = 2; int TTL_SECS = 6;
TableSnapshot toExpire = generateSnapshotDetails("to-expire", now().plusSeconds(TTL_SECS), false); TableSnapshot toExpire = generateSnapshotDetails("to-expire", now().plusSeconds(TTL_SECS), false);
TableSnapshot nonExpired = generateSnapshotDetails("non-expired", now().plusMillis(ONE_DAY_SECS), false); TableSnapshot nonExpired = generateSnapshotDetails("non-expired", now().plusMillis(ONE_DAY_SECS), false);
manager.addSnapshot(toExpire); manager.addSnapshot(toExpire);
manager.addSnapshot(nonExpired); manager.addSnapshot(nonExpired);
// Check both snapshots still exist // Check both snapshots still exist
assertThat(toExpire.exists()).isTrue(); assertThat(toExpire.exists()).isTrue();
assertThat(nonExpired.exists()).isTrue(); assertThat(nonExpired.exists()).isTrue();
assertThat(manager.getExpiringSnapshots()).hasSize(2); assertThat(manager.getSnapshots(TableSnapshot::isExpiring)).hasSize(2);
assertThat(manager.getExpiringSnapshots()).contains(toExpire); assertThat(manager.getSnapshots(TableSnapshot::isExpiring)).contains(toExpire);
assertThat(manager.getExpiringSnapshots()).contains(nonExpired); assertThat(manager.getSnapshots(TableSnapshot::isExpiring)).contains(nonExpired);
// Sleep 4 seconds // Sleep 10 seconds
Thread.sleep((TTL_SECS + 2) * 1000L); Thread.sleep((TTL_SECS + 4) * 1000L);
// Snapshot with ttl=2s should be gone, while other should remain // Snapshot with ttl=6s should be gone, while other should remain
assertThat(manager.getExpiringSnapshots()).hasSize(1); assertThat(manager.getSnapshots(TableSnapshot::isExpiring)).hasSize(1);
assertThat(manager.getExpiringSnapshots()).contains(nonExpired); assertThat(manager.getSnapshots(TableSnapshot::isExpiring)).contains(nonExpired);
assertThat(toExpire.exists()).isFalse(); assertThat(toExpire.exists()).isFalse();
assertThat(nonExpired.exists()).isTrue(); assertThat(nonExpired.exists()).isTrue();
}
finally
{
manager.stop();
}
} }
@Test @Test
public void testClearSnapshot() throws Exception public void testClearSnapshot()
{ {
// Given // Given
SnapshotManager manager = new SnapshotManager(1, 3); manager.start(false);
TableSnapshot expiringSnapshot = generateSnapshotDetails("snapshot", now().plusMillis(50000), false); TableSnapshot expiringSnapshot = generateSnapshotDetails("snapshot", now().plusMillis(50000), false);
manager.addSnapshot(expiringSnapshot); manager.addSnapshot(expiringSnapshot);
assertThat(manager.getExpiringSnapshots()).contains(expiringSnapshot); assertThat(manager.getSnapshots(TableSnapshot::isExpiring)).contains(expiringSnapshot);
assertThat(expiringSnapshot.exists()).isTrue(); assertThat(expiringSnapshot.exists()).isTrue();
// When // When
manager.clearSnapshot(expiringSnapshot); manager.clearSnapshot(expiringSnapshot);
// Then // Then
assertThat(manager.getExpiringSnapshots()).doesNotContain(expiringSnapshot); assertThat(manager.getSnapshots(TableSnapshot::isExpiring)).doesNotContain(expiringSnapshot);
assertThat(expiringSnapshot.exists()).isFalse(); assertThat(expiringSnapshot.exists()).isFalse();
} }
@Test // see CASSANDRA-18211
public void testConcurrentClearingOfSnapshots() throws Exception
{
AtomicReference<Long> firstInvocationTime = new AtomicReference<>(0L);
AtomicReference<Long> secondInvocationTime = new AtomicReference<>(0L);
SnapshotManager manager = new SnapshotManager(0, 5)
{
@Override
public synchronized void clearSnapshot(TableSnapshot snapshot)
{
if (snapshot.getTag().equals("mysnapshot"))
{
firstInvocationTime.set(currentTimeMillis());
Uninterruptibles.sleepUninterruptibly(10, SECONDS);
}
else if (snapshot.getTag().equals("mysnapshot2"))
{
secondInvocationTime.set(currentTimeMillis());
}
super.clearSnapshot(snapshot);
}
};
TableSnapshot expiringSnapshot = generateSnapshotDetails("mysnapshot", Instant.now().plusSeconds(15), false);
manager.addSnapshot(expiringSnapshot);
manager.resumeSnapshotCleanup();
Thread nonExpiringSnapshotCleanupThred = new Thread(() -> manager.clearSnapshot(generateSnapshotDetails("mysnapshot2", null, false)));
// wait until the first snapshot expires
await().pollInterval(1, SECONDS)
.pollDelay(0, SECONDS)
.timeout(1, MINUTES)
.until(() -> firstInvocationTime.get() > 0);
// this will block until the first snapshot is cleaned up
nonExpiringSnapshotCleanupThred.start();
nonExpiringSnapshotCleanupThred.join();
assertTrue(secondInvocationTime.get() - firstInvocationTime.get() > 10_000);
}
} }

View File

@ -28,10 +28,12 @@ import java.util.UUID;
import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.ThreadLocalRandom;
import org.junit.Assert; import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.ClassRule; import org.junit.ClassRule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.TemporaryFolder; import org.junit.rules.TemporaryFolder;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.config.DurationSpec; import org.apache.cassandra.config.DurationSpec;
import org.apache.cassandra.db.Directories; import org.apache.cassandra.db.Directories;
import org.apache.cassandra.io.util.File; import org.apache.cassandra.io.util.File;
@ -67,6 +69,12 @@ public class SnapshotLoaderTest
@ClassRule @ClassRule
public static TemporaryFolder tmpDir = new TemporaryFolder(); public static TemporaryFolder tmpDir = new TemporaryFolder();
@BeforeClass
public static void setup()
{
DatabaseDescriptor.daemonInitialization();
}
@Test @Test
public void testMatcher() public void testMatcher()
{ {

View File

@ -0,0 +1,274 @@
/*
* 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.service.snapshot;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Instant;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.function.Consumer;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.apache.cassandra.config.CassandraRelevantProperties;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.io.FSWriteError;
import org.apache.cassandra.io.util.File;
import org.apache.cassandra.io.util.PathUtils;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import static java.lang.String.format;
import static org.apache.cassandra.config.CassandraRelevantProperties.SNAPSHOT_CLEANUP_INITIAL_DELAY_SECONDS;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class SnapshotManagerTest
{
@ClassRule
public static TemporaryFolder temporaryFolder = new TemporaryFolder();
@ClassRule
public static TemporaryFolder temporaryFolder2 = new TemporaryFolder();
private static File rootDir1;
private static File rootDir2;
private static String[] dataDirs;
@BeforeClass
public static void beforeClass() throws Exception
{
CassandraRelevantProperties.SNAPSHOT_CLEANUP_PERIOD_SECONDS.setInt(10);
SNAPSHOT_CLEANUP_INITIAL_DELAY_SECONDS.setInt(5);
rootDir1 = new File(temporaryFolder.getRoot());
rootDir2 = new File(temporaryFolder2.getRoot());
dataDirs = new String[]{
rootDir1.toPath().toAbsolutePath().toString(),
rootDir2.toPath().toAbsolutePath().toString()
};
}
private static class ThrowingTask extends AbstractSnapshotTask<Void>
{
public ThrowingTask()
{
super(null);
}
@Override
public Void call()
{
throw new RuntimeException("an exception");
}
@Override
public SnapshotTaskType getTaskType()
{
return SnapshotTaskType.SNAPSHOT;
}
}
@Test
public void testTaskThrowingException()
{
doWithManager(manager -> assertThatThrownBy(() -> manager.executeTask(new ThrowingTask())).isInstanceOf(RuntimeException.class)
.hasRootCauseMessage("an exception")
.hasMessageContaining("Exception occured while executing"));
doWithManager(manager -> {
manager.close();
assertThatThrownBy(() -> manager.executeTask(new ThrowingTask())).isInstanceOf(RuntimeException.class)
.hasRootCauseMessage("an exception")
.hasMessageContaining("Exception occured while executing");
manager.start(false);
assertThatThrownBy(() -> manager.executeTask(new ThrowingTask())).isInstanceOf(RuntimeException.class)
.hasRootCauseMessage("an exception")
.hasMessageContaining("Exception occured while executing");
});
}
/**
* Tests that if we remove all manifests files, that equals to stopping manager to track
* such snapshot, however no data will be removed.
*/
@Test
public void testRemovingManifestsLogicallyRemovesSnapshot()
{
doWithManager(manager -> {
List<TableSnapshot> tableSnapshots = generateTableSnapshots(10, 100);
for (TableSnapshot snapshot : tableSnapshots)
manager.addSnapshot(snapshot);
// we still have 1000 snapshots because we removed just one manifest
removeManifestOfSnapshot(tableSnapshots.get(0));
assertEquals(1000, manager.getSnapshots((t) -> true).size());
// remove the second manifest, that will render snapshot to be logically removed
removeManifestOfSnapshot(tableSnapshots.get(0));
assertEquals(999, manager.getSnapshots((t) -> true).size());
// check that data are still there
assertFalse(tableSnapshots.get(0).hasManifest());
assertTrue(tableSnapshots.get(0).exists());
for (File snapshotDir : tableSnapshots.get(0).getDirectories())
assertTrue(snapshotDir.exists());
});
}
private void doWithManager(Consumer<SnapshotManager> action)
{
try (MockedStatic<DatabaseDescriptor> mockedDD = Mockito.mockStatic(DatabaseDescriptor.class))
{
mockedDD.when(() -> DatabaseDescriptor.getNonLocalSystemKeyspacesDataFileLocations()).thenReturn(dataDirs);
mockedDD.when(() -> DatabaseDescriptor.getLocalSystemKeyspacesDataFileLocations()).thenReturn(new String[]{});
mockedDD.when(() -> DatabaseDescriptor.getAllDataFileLocations()).thenReturn(dataDirs);
mockedDD.when(() -> DatabaseDescriptor.getDumpHeapOnUncaughtException()).thenReturn(false);
try (SnapshotManager snapshotManager = new SnapshotManager(SNAPSHOT_CLEANUP_INITIAL_DELAY_SECONDS.getInt(10),
SNAPSHOT_CLEANUP_INITIAL_DELAY_SECONDS.getInt(5),
dataDirs).start(true))
{
action.accept(snapshotManager);
}
finally
{
clearDirectory(rootDir1.toPath());
clearDirectory(rootDir2.toPath());
}
}
}
private List<TableSnapshot> generateTableSnapshots(int keyspaces, int tables) throws RuntimeException
{
List<TableSnapshot> tableSnapshots = new ArrayList<>();
for (int i = 0; i < keyspaces; i++)
{
for (int j = 0; j < tables; j++)
{
String snapshotName = format("mysnapshot_%s_%s", i, j);
File dir1 = new File(Paths.get(rootDir1.absolutePath(), "ks", "tb-1b255f4def2540a60000000000000005", "snapshots", snapshotName));
File dir2 = new File(Paths.get(rootDir2.absolutePath(), "ks", "tb-1b255f4def2540a60000000000000005", "snapshots", snapshotName));
dir1.tryCreateDirectories();
dir2.tryCreateDirectories();
TableSnapshot snapshot = generateSnapshotDetails(Set.of(dir1, dir2), snapshotName, "ks", "tb-1b255f4def2540a60000000000000005", null, false);
SnapshotManifest manifest = new SnapshotManifest(List.of(), null, snapshot.getCreatedAt(), snapshot.isEphemeral());
try
{
manifest.serializeToJsonFile(new File(dir1.toPath().resolve("manifest.json")));
manifest.serializeToJsonFile(new File(dir2.toPath().resolve("manifest.json")));
generateFileInSnapshot(snapshot);
}
catch (Exception ex)
{
throw new RuntimeException(ex);
}
tableSnapshots.add(snapshot);
}
}
return tableSnapshots;
}
private void generateFileInSnapshot(TableSnapshot tableSnapshot) throws IOException
{
for (File snapshotDir : tableSnapshot.getDirectories())
Files.createFile(snapshotDir.toPath().resolve("schema.cql"));
}
private void removeManifestOfSnapshot(TableSnapshot tableSnapshot)
{
for (File snapshotDir : tableSnapshot.getDirectories())
{
if (snapshotDir.exists())
{
File manifest = new File(snapshotDir, "manifest.json");
if (!manifest.exists())
continue;
manifest.delete();
return;
}
}
}
private TableSnapshot generateSnapshotDetails(Set<File> roots,
String tag,
String keyspace,
String table,
Instant expiration,
boolean ephemeral)
{
try
{
Set<File> snapshotDirs = new HashSet<>();
for (File root : roots)
{
root.tryCreateDirectories();
snapshotDirs.add(root);
}
return new TableSnapshot(keyspace,
table,
UUID.randomUUID(),
tag,
Instant.EPOCH,
expiration,
snapshotDirs,
ephemeral);
}
catch (Exception ex)
{
throw new RuntimeException(ex);
}
}
/**
* Empties everything in directory of "path" but keeps the directory itself.
*
* @param path directory to be emptied
* @throws FSWriteError if any part of the tree cannot be deleted
*/
public static void clearDirectory(Path path)
{
if (PathUtils.isDirectory(path))
PathUtils.forEach(path, PathUtils::deleteRecursive);
}
}

View File

@ -0,0 +1,64 @@
/*
* 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.service.snapshot;
import java.time.Instant;
import java.util.EnumSet;
import java.util.List;
import com.google.common.util.concurrent.RateLimiter;
import org.junit.Test;
import static java.lang.String.format;
import static org.junit.Assert.assertEquals;
public class SnapshotOptionsTest
{
@Test
public void testSnapshotName()
{
List<SnapshotType> sameNameTypes = List.of(SnapshotType.DIAGNOSTICS, SnapshotType.REPAIR, SnapshotType.USER);
for (SnapshotType type : sameNameTypes)
{
SnapshotOptions options = SnapshotOptions.systemSnapshot("a_name", type, "ks.tb")
.rateLimiter(RateLimiter.create(5))
.build();
String snapshotName = options.getSnapshotName(Instant.now());
assertEquals("a_name", snapshotName);
}
EnumSet<SnapshotType> snapshotTypes = EnumSet.allOf(SnapshotType.class);
snapshotTypes.removeAll(sameNameTypes);
for (SnapshotType type : snapshotTypes)
{
SnapshotOptions options = SnapshotOptions.systemSnapshot("a_name", type, "ks.tb")
.rateLimiter(RateLimiter.create(5))
.build();
Instant now = Instant.now();
String snapshotName = options.getSnapshotName(now);
assertEquals(format("%d-%s-%s", now.toEpochMilli(), type.label, "a_name"), snapshotName);
}
}
}

View File

@ -19,7 +19,7 @@
package org.apache.cassandra.service.snapshot; package org.apache.cassandra.service.snapshot;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Paths; import java.nio.file.Files;
import java.time.Instant; import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@ -38,10 +38,16 @@ import org.apache.cassandra.io.util.File;
import org.apache.cassandra.io.util.FileOutputStreamPlus; import org.apache.cassandra.io.util.FileOutputStreamPlus;
import org.apache.cassandra.io.util.FileUtils; import org.apache.cassandra.io.util.FileUtils;
import org.apache.cassandra.utils.Pair; import org.apache.cassandra.utils.Pair;
import org.mockito.MockedStatic;
import static org.apache.cassandra.utils.FBUtilities.now; import static org.apache.cassandra.utils.FBUtilities.now;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.times;
public class TableSnapshotTest public class TableSnapshotTest
{ {
@ -62,13 +68,68 @@ public class TableSnapshotTest
{ {
File subfolder = new File(folder, folderName); File subfolder = new File(folder, folderName);
subfolder.tryCreateDirectories(); subfolder.tryCreateDirectories();
assertThat(subfolder.exists()); File manifest = new File(subfolder.toPath().resolve("manifest.json"));
File schema = new File(subfolder.toPath().resolve("schema.cql"));
manifest.createFileIfNotExists();
schema.createFileIfNotExists();
Files.write(manifest.toPath(), "{}".getBytes());
Files.write(schema.toPath(), "cql schema".getBytes());
folders.add(subfolder); folders.add(subfolder);
} }
;
return folders; return folders;
} }
@Test
public void testTableSnapshotEquality() throws IOException
{
TableSnapshot snapshot1 = new TableSnapshot("ks", "tbl", UUID.randomUUID(), "some", null, null, createFolders(tempFolder), false);
TableSnapshot snapshot2 = new TableSnapshot("ks", "tbl", UUID.randomUUID(), "some", null, null, createFolders(tempFolder), false);
// they are not equal, because table id is random for each
assertNotEquals(snapshot1, snapshot2);
UUID tableId = UUID.randomUUID();
snapshot1 = new TableSnapshot("ks", "tbl", tableId, "some", null, null, createFolders(tempFolder), false);
snapshot2 = new TableSnapshot("ks", "tbl", tableId, "some", null, null, createFolders(tempFolder), false);
// they are equal, even their directories differ
assertEquals(snapshot1, snapshot2);
Set<File> folders = createFolders(tempFolder);
snapshot1 = new TableSnapshot("ks", "tbl", tableId, "some", Instant.now(), null, folders, false);
snapshot2 = new TableSnapshot("ks", "tbl", tableId, "some", Instant.now().plusSeconds(1), null, folders, false);
// they are equal, even their creation times differ
assertEquals(snapshot1, snapshot2);
snapshot1 = new TableSnapshot("ks", "tbl", tableId, "some", null, Instant.now(), folders, false);
snapshot2 = new TableSnapshot("ks", "tbl", tableId, "some", null, Instant.now().plusSeconds(1), folders, false);
// they are equal, even their expiration times differ
assertEquals(snapshot1, snapshot2);
snapshot1 = new TableSnapshot("ks", "tbl", tableId, "some", null, null, folders, false);
snapshot2 = new TableSnapshot("ks", "tbl", UUID.randomUUID(), "some", null, null, folders, false);
// they are not equal, because their tableId differs
assertNotEquals(snapshot1, snapshot2);
snapshot1 = new TableSnapshot("ks", "tbl", tableId, "some1", null, null, folders, false);
snapshot2 = new TableSnapshot("ks", "tbl", tableId, "some2", null, null, folders, false);
// they are not equal, because their tag differs
assertNotEquals(snapshot1, snapshot2);
snapshot1 = new TableSnapshot("ks", "tbl", tableId, "some1", null, null, folders, false);
snapshot2 = new TableSnapshot("ks", "tbl2", tableId, "some1", null, null, folders, false);
// they are not equal, because their table differs
assertNotEquals(snapshot1, snapshot2);
}
@Test @Test
public void testSnapshotExists() throws IOException public void testSnapshotExists() throws IOException
{ {
@ -186,8 +247,18 @@ public class TableSnapshotTest
res += FileUtils.folderSize(dir); res += FileUtils.folderSize(dir);
} }
assertThat(tableDetails.computeSizeOnDiskBytes()).isGreaterThan(0L); try (MockedStatic<FileUtils> fileUtilsMock = mockStatic(FileUtils.class))
assertThat(tableDetails.computeSizeOnDiskBytes()).isEqualTo(res); {
fileUtilsMock.when(() -> FileUtils.folderSize(any())).thenCallRealMethod();
assertThat(tableDetails.computeSizeOnDiskBytes()).isGreaterThan(0L);
assertThat(tableDetails.computeSizeOnDiskBytes()).isEqualTo(res);
// when we invoke computeSizeOnDiskBytes for the second time, it will use cached value
// 3 invocations for folderSize are from the first invocation of computeSizeOnDiskBytes because
// we have 3 data dirs, if we have not cached it, the number of invocations would be 6.
fileUtilsMock.verify(() -> FileUtils.folderSize(any()), times(3));
}
} }
@Test @Test
@ -208,15 +279,27 @@ public class TableSnapshotTest
Long res = 0L; Long res = 0L;
Set<String> files = new HashSet<>();
for (File dir : folders) for (File dir : folders)
{ {
File file = new File(dir, "tmp"); File file = new File(dir, "tmp");
files.add(file.toAbsolute().toString());
writeBatchToFile(file); writeBatchToFile(file);
res += file.length(); res += file.length();
res += new File(dir, "manifest.json").length();
res += new File(dir, "schema.cql").length();
} }
assertThat(tableDetails.computeTrueSizeBytes()).isGreaterThan(0L); try (MockedStatic<TableSnapshot> tableSnapshotMock = mockStatic(TableSnapshot.class))
assertThat(tableDetails.computeTrueSizeBytes()).isEqualTo(res); {
tableSnapshotMock.when(() -> TableSnapshot.getLiveFileFromSnapshotFile(any())).thenCallRealMethod();
assertThat(tableDetails.computeTrueSizeBytes(files)).isGreaterThan(0L);
assertThat(tableDetails.computeTrueSizeBytes(files)).isEqualTo(res);
// 6 because we avoided to call it for manifest and schema because they are cached
tableSnapshotMock.verify(() -> TableSnapshot.getLiveFileFromSnapshotFile(any()), times(6));
}
} }
@Test @Test
@ -298,7 +381,7 @@ public class TableSnapshotTest
// 1. snapshot to clear is not ephemeral // 1. snapshot to clear is not ephemeral
// 2. tag to clear is null, empty, or it is equal to snapshot tag // 2. tag to clear is null, empty, or it is equal to snapshot tag
// 3. byTimestamp is true // 3. byTimestamp is true
if (TableSnapshot.shouldClearSnapshot(testingTag, olderThanTimestamp).test(snapshot)) if (ClearSnapshotTask.getClearSnapshotPredicate(testingTag, Set.of(keyspace), olderThanTimestamp, false).test(snapshot))
{ {
// shouldClearTag = true // shouldClearTag = true
boolean shouldClearTag = (testingTag == null || testingTag.isEmpty()) || snapshot.getTag().equals(testingTag); boolean shouldClearTag = (testingTag == null || testingTag.isEmpty()) || snapshot.getTag().equals(testingTag);
@ -321,23 +404,4 @@ public class TableSnapshotTest
} }
} }
} }
@Test
public void testGetLiveFileFromSnapshotFile()
{
testGetLiveFileFromSnapshotFile("~/.ccm/test/node1/data0/test_ks/tbl-e03faca0813211eca100c705ea09b5ef/snapshots/1643481737850/me-1-big-Data.db",
"~/.ccm/test/node1/data0/test_ks/tbl-e03faca0813211eca100c705ea09b5ef/me-1-big-Data.db");
}
@Test
public void testGetLiveFileFromSnapshotIndexFile()
{
testGetLiveFileFromSnapshotFile("~/.ccm/test/node1/data0/test_ks/tbl-e03faca0813211eca100c705ea09b5ef/snapshots/1643481737850/.tbl_val_idx/me-1-big-Summary.db",
"~/.ccm/test/node1/data0/test_ks/tbl-e03faca0813211eca100c705ea09b5ef/.tbl_val_idx/me-1-big-Summary.db");
}
public void testGetLiveFileFromSnapshotFile(String snapshotFile, String expectedLiveFile)
{
assertThat(TableSnapshot.getLiveFileFromSnapshotFile(Paths.get(snapshotFile)).toString()).isEqualTo(expectedLiveFile);
}
} }

View File

@ -18,7 +18,6 @@
package org.apache.cassandra.tools; package org.apache.cassandra.tools;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -34,7 +33,7 @@ import org.apache.cassandra.distributed.shared.WithProperties;
import org.apache.cassandra.exceptions.ConfigurationException; import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.exceptions.StartupException; import org.apache.cassandra.exceptions.StartupException;
import org.apache.cassandra.io.sstable.LegacySSTableTest; import org.apache.cassandra.io.sstable.LegacySSTableTest;
import org.apache.cassandra.service.StorageService; import org.apache.cassandra.service.snapshot.SnapshotManager;
import org.apache.cassandra.tools.ToolRunner.ToolResult; import org.apache.cassandra.tools.ToolRunner.ToolResult;
import org.assertj.core.api.Assertions; import org.assertj.core.api.Assertions;
@ -93,9 +92,7 @@ public class StandaloneUpgraderOnSStablesTest
{ {
LegacySSTableTest.truncateLegacyTables(legacyId); LegacySSTableTest.truncateLegacyTables(legacyId);
LegacySSTableTest.loadLegacyTables(legacyId); LegacySSTableTest.loadLegacyTables(legacyId);
StorageService.instance.takeSnapshot("testsnapshot", SnapshotManager.instance.takeSnapshot("testsnapshot", "legacy_tables.legacy_" + legacyId + "_simple");
Collections.emptyMap(),
"legacy_tables.legacy_" + legacyId + "_simple");
ToolResult tool = ToolRunner.invokeClass(StandaloneUpgrader.class, ToolResult tool = ToolRunner.invokeClass(StandaloneUpgrader.class,
"-k", "-k",

View File

@ -30,6 +30,7 @@ import java.util.regex.Pattern;
import javax.management.openmbean.TabularData; import javax.management.openmbean.TabularData;
import org.junit.AfterClass; import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
@ -37,6 +38,7 @@ import org.apache.cassandra.cql3.CQLTester;
import org.apache.cassandra.db.Keyspace; import org.apache.cassandra.db.Keyspace;
import org.apache.cassandra.io.util.File; import org.apache.cassandra.io.util.File;
import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.snapshot.SnapshotManager;
import org.apache.cassandra.service.snapshot.SnapshotManifest; import org.apache.cassandra.service.snapshot.SnapshotManifest;
import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeProbe;
import org.apache.cassandra.tools.ToolRunner.ToolResult; import org.apache.cassandra.tools.ToolRunner.ToolResult;
@ -65,6 +67,12 @@ public class ClearSnapshotTest extends CQLTester
probe = new NodeProbe(jmxHost, jmxPort); probe = new NodeProbe(jmxHost, jmxPort);
} }
@Before
public void clearAllSnapshots()
{
SnapshotManager.instance.clearAllSnapshots();
}
@AfterClass @AfterClass
public static void teardown() throws IOException public static void teardown() throws IOException
{ {
@ -315,11 +323,15 @@ public class ClearSnapshotTest extends CQLTester
String tableId2 = DASH_PATTERN.matcher(tableMetadata2.orElseThrow(() -> new IllegalStateException(format("no metadata found for %s.%s", keyspace2, tableName2))) String tableId2 = DASH_PATTERN.matcher(tableMetadata2.orElseThrow(() -> new IllegalStateException(format("no metadata found for %s.%s", keyspace2, tableName2)))
.id.asUUID().toString()).replaceAll(""); .id.asUUID().toString()).replaceAll("");
SnapshotManager.instance.close();
rewriteManifest(tableId, getAllDataFileLocations(), KEYSPACE, tableName, "snapshot-to-clear-ks1-tb1", start.minus(5, HOURS)); rewriteManifest(tableId, getAllDataFileLocations(), KEYSPACE, tableName, "snapshot-to-clear-ks1-tb1", start.minus(5, HOURS));
rewriteManifest(tableId, getAllDataFileLocations(), KEYSPACE, tableName, "some-other-snapshot-ks1-tb1", start.minus(2, HOURS)); rewriteManifest(tableId, getAllDataFileLocations(), KEYSPACE, tableName, "some-other-snapshot-ks1-tb1", start.minus(2, HOURS));
rewriteManifest(tableId, getAllDataFileLocations(), KEYSPACE, tableName, "last-snapshot-ks1-tb1", start.minus(1, SECONDS)); rewriteManifest(tableId, getAllDataFileLocations(), KEYSPACE, tableName, "last-snapshot-ks1-tb1", start.minus(1, SECONDS));
rewriteManifest(tableId2, getAllDataFileLocations(), keyspace2, tableName2, "snapshot-to-clear-ks2-tb2", start.minus(5, HOURS)); rewriteManifest(tableId2, getAllDataFileLocations(), keyspace2, tableName2, "snapshot-to-clear-ks2-tb2", start.minus(5, HOURS));
rewriteManifest(tableId2, getAllDataFileLocations(), keyspace2, tableName2, "some-other-snapshot-ks2-tb2", start.minus(2, HOURS)); rewriteManifest(tableId2, getAllDataFileLocations(), keyspace2, tableName2, "some-other-snapshot-ks2-tb2", start.minus(2, HOURS));
rewriteManifest(tableId2, getAllDataFileLocations(), keyspace2, tableName2, "last-snapshot-ks2-tb2", start.minus(1, SECONDS)); rewriteManifest(tableId2, getAllDataFileLocations(), keyspace2, tableName2, "last-snapshot-ks2-tb2", start.minus(1, SECONDS));
SnapshotManager.instance.start(true);
} }
} }

View File

@ -340,7 +340,7 @@ public abstract class CompactionStress implements Runnable
} }
double currentSizeGiB; double currentSizeGiB;
while ((currentSizeGiB = directories.getRawDiretoriesSize() / BYTES_IN_GIB) < totalSizeGiB) while ((currentSizeGiB = directories.getRawDirectoriesSize() / BYTES_IN_GIB) < totalSizeGiB)
{ {
if (finished.getCount() == 0) if (finished.getCount() == 0)
break; break;
@ -353,7 +353,7 @@ public abstract class CompactionStress implements Runnable
workManager.stop(); workManager.stop();
Uninterruptibles.awaitUninterruptibly(finished); Uninterruptibles.awaitUninterruptibly(finished);
currentSizeGiB = directories.getRawDiretoriesSize() / BYTES_IN_GIB; currentSizeGiB = directories.getRawDirectoriesSize() / BYTES_IN_GIB;
System.out.println(String.format("Finished writing %.2fGB", currentSizeGiB)); System.out.println(String.format("Finished writing %.2fGB", currentSizeGiB));
} }
} }