SSTable format API

Summary of the changes:

Format, reader and writer
---------------------------
There are a lot of refactorings around sstable related classes aiming to extract the most generic functionality to the top-level entities and push down implementation-specific stuff to the actual implementation. In Particular, the top-level, implementation agnostic classes/interfaces are SSTableFormat interface, SSTable, SSTableReader, SSTableWriter, IVerifier, and IScrubber. The rest of the codebase has been reviewed for explicit usages of big table format-specific usages of sstable classes and refactored. SSTable, SSTableReader, and SSTableWriter have their builders. Builders make a hierarchy that follows the same inheritance structure as readers and writers.

There are also partial implementations that add support for some features and may or may not be used by the custom implementations. They include:
- AbstractSSTableFormat - adds an implementation of some initialization methods - in practice, all of the format implementations should extend this class
- SSTableReaderWithFilter - add support for Bloom filter to the reader
- SortedTableWriter - generic implementation for a writer which writes partitions in the default order to the data file, supports Bloom filter and some index of partitions
- IndexSummarySupport - interface implemented by the readers using index summaries
- KeyCacheSupport - interface implemented by the readers using row key cache

Descriptor
---------------------------
Refactored the Descriptor class so that:
- All paths are created from the base directory File rather than from a String
- All the methods named *filename* producing full paths were made private; their current implementations are returning file names rather than paths (the naming was inconsistent)
- The usages of the `filenameFor` method were refactored to use the `fileFor` method
- The usages of the `fromFilename` method were refactored to use a  `fromFileWithComponent(..., false).left` expression
In essence, the Descriptor class is no longer working on String-based paths.

Index summaries
---------------------------
Removed the index summary from the generic SSTableReader class and created an interface IndexSummarySupport to be implemented by the readers that need it. Methods in related classes that refer back to the reader were refactored to support just readers of the SSTableReader & IndexSummarySupport type. Therefore, we will no longer need to assume that the generic SSTableReader has anything to do with an index summary.

A new IndexSummaryComponent class encloses data fields from the index summary file (note that aside from the index summary itself, the file includes the first and last partition of the sstable). The class has been extracted to deal with those fields and have that logic in a single place.

Filter
---------------------------
Refactored IFilter and its serialization - in particular, added the `serialize` method to the IFilter interface and moved loading/saving logic to a separate utility class FilterComponent.
Extracted the SSTableReaderWithFilter abstract reader extending the generic SSTableReader with filter support.
Extracted bloom filter metrics into separate entities allowing to plug them in if the implementation uses a filter.

Cache
---------------------------
Refactored CacheService to support different key-cache values. CacheService now supports arbitrary IRowIndexEntry implementation as a key-cache value. A new version of the auto-saving cache was created ("g") because some information about the type of serialized row index entry needs to be known before it is deserialized (or skipped). Therefore, the SSTableFormat type ordinal number is stored, which is sufficient because the IRowIndexEntry serializer is specific to the sstable format type.
Similarly to the IndexSummarySupport, a new KeyCacheSupport interface has to be implemented to mark the reader as supporting key-cache. It contains the default implementation of several methods the rest of the system relies on when the key-cache is supported.

Other changes
---------------------------
- Fixed disabling chunk cache - enable(boolean) method in ChunkCache does not make any sense - it makes a false impression it can disable chunk cache once enabled, while in fact, it only clears it. Added setFileCacheEnabled to DatabaseDescriptor

- Made WrappingUnfilteredRowIterator an interface

- DataInputStreamPlus extends InputStream - this makes it possible for input stream-based inheritors of DataInputPlus to extend DataInputStreamPlus. It simplifies coding because sometimes we want to get DataInputPlus implementation extending InputStream as an argument.

- Table and keyspace metrics were made pluggable - in particular, added the ability for a certain format to register gauges that are specific only to that format and make no sense for others

- Implemented mmapped region extension for compressed data

- Refactored FileHandle so that it is no longer closable

- Implemented WrappingRebufferer

- Introduced the SSTable.Owner interface to make SSTable implementation not reference higher-level entities directly. SSTable accepts passing null as the owner when there is no owner (like sometimes in offline tools) or passing a mock when needed in tests.

Individual commits
---------------------------

[4a87cd36fe] Fix disabling chunk cache
[c84c75ccf3] Made WrappingUnfilteredRowIterator an interface
[253d2b828e] Add getType to SSTableFormat
[3f169dcc20] Remove getIndexSerializer from SSTableFormat
[05bae1833b] Pull down rowIndexEntrySerializer field
[da675f2809] Moved RowIndexEntry
[673f0c5c39] Reduce usages of RowIndexEntry
[c72538be91] Refactor CacheService to support for different key cache values
[54d33ee656] Minor refactoring of ColumnIndex
[93862df967] Just moved AbstractSSTableIterator to o.a.c.io.sstable.format
[9e4566a1de] Refactored AbstractSSTableIterator
[a4e61e80bb] Extracted IScrubber and IVerifier interfaces
[20f78c7419] Push down implementation of SSTableReader.firstKeyBeyond
[f2c24e5774] Moved SSTableReader.getSampleIndexesForRanges to IndexSummary
[b6c3a6c1ea] Moved SSTableReader.getKeySamples implementation to IndexSummary
[c4b90ebb33] Refactor InstanceTidier so that it is more generic
[918d5a9e74] Refactor dropping page cache
[a52fb4d558] Refactor sstable metrics
[f6d10f930f] NEW (fix up) - DataInputStreamPlus extends InputStream
[8f6a56d972] Getting rid of index summary in SSTableReader
[4a918bf725] Removed direct usages of primary index from SSTableReader
[358fa32602] Refactor KeyIterator so that it is sstable format agnostic
[14c09d89c2] Remove explicit usage of Components outside of format specific classes
[feff14e137] Move clone methods implementation from SSTableReader to BigTableReader
[64e9787b10] Move saveIndexSummary and saveBloomFilter to SSTableReaderBuilder
[ae71fe6ed8] Moved indexSummary field to BigTableReader and made it private
[df9fd8c4b9] Moved ifile field to BigTableReader and made it private
[2be6ea9ecf] Moved static open methods for BigTableReader to the reader factory
[bc0e55ac48] Minor refactoring around IFilter and its serialization
[5b95704beb] Minor refactorings around IndexSummary
[87812335e8] Extracted TOCComponent class to deal with TOC file
[fdad092a6a] Extracted CompressionInfoComponent class
[39b47e388d] Extracted StatsComponent as a helper for elements of SSTable metadata
[cdb55bff47] Fix SSTable.getMinimalKey
[b99c6d5805] Refactor FileHandle so that it is no longer closable
[77b7f7ace5] Implement WrappingRebufferer
[b6868914dd] Add progressPercentage to ProgressInfo
[7fd4956e5b] Moved copy/rename/hardLink methods from SSTableWriter to SSTable
[1ccc6bf148] Create generic SSTableBuilder and IOOptions
[da58a81102] Refactor SSTableReaderBuilder
[4501ddba1c] Refactor ColumnIndex
[d4f9e1a64b] Extracted non-big-table-specific functionality from BigTableWriter to SortedTableWriter
[379525d01e] Refactor BigTableZeroCopyWriter to SSTableZeroCopyWriter as it is not specific to big format
[8ac37f83bc] Extract EmptySSTableScanner out from BigTableScanner
[ee6673f1cf] Implement SSTableWriterBuilder
[bb26629235] Refactor opening early / final
[a327595015] Refactored SSTableWriter factory
[16ffd7334b] Extract non-big-format-specific logic from scrubber and verifier
[75e02db6af] Allow to specify the default SSTableFormat via system property
[a7b9d0d628] Small fixes around streaming
[407f977c36] Move guard collection size
[0529e57d2f] Remove explicit references to big format
[61509963ec] Unclassified minor changes
[da28d1af3a] Replaced getCreationTimeFor(Component) with getDataCreationTime()
[e99c834de6] !!! Reformatting
[882b7baa5a] Rename SSTableReader.maybePresent and fix its redundant usages
[b70c983bea] Implement mmapped region extension for compressed data
[d7ff3970de] Introduce SSTable.Owner interface
[e9feb9c462] Replaced getCreationTimeFor(Component) with getDataCreationTime()
[ee8082fb07] Created SSTableFormat.deleteOrphanedComponents
[e62950fd3d] Refactor metrics further
[cefa5b3814] Extract key cache support into separate entity
[dd55101ca1] Extracted SSTableReaderWithFilter
[510b651824] Implement customizable component types
[2be512d9fa] Pluggable SSTableFormat by making SSTableFormat.Type not an enum
[670836b55d] Refactor CRC and digest validators
[00c91103bc] Extract delete method to delete SSTables and purge row cache entries
[0819dc9fc2] Extracted trySkipFileCacheBefore(key) to SSTableReader
[732f841750] Added missing overrides in ForwardingSSTableReader
[db623218fd] Update DatabaseDescriptorRefTest
[c018c468e5] Cleanup
[eafc836242] Add @SuppressWarnings("resource") where needed
[3b7c911dd6] Documentation

patch by Jacek Lewandowski, reviewed by Branimir Lambov for CASSANDRA-17056

Co-authored-by: @jacek-lewandowski
Co-authored-by: @blambov
This commit is contained in:
Jacek Lewandowski 2023-03-02 12:46:25 +01:00 committed by Jacek Lewandowski
parent 0a0e06847b
commit b7e1e44a90
299 changed files with 15695 additions and 9289 deletions

View File

@ -1,4 +1,5 @@
4.2
* CEP-17: SSTable API (CASSANDRA-17056)
* Gossip stateMapOrdering does not have correct ordering when both EndpointState are in the bootstrapping set (CASSANDRA-18292)
* Snapshot only sstables containing mismatching ranges on preview repair mismatch (CASSANDRA-17561)
* More accurate skipping of sstables in read path (CASSANDRA-18134)

View File

@ -125,7 +125,8 @@ New features
- On virtual tables, it is not strictly necessary to specify `ALLOW FILTERING` for select statements which would
normally require it, except `system_views.system_logs`.
- More accurate skipping of sstables in read path due to better handling of min/max clustering and lower bound;
SSTable format has been bumped to 'nc' because there are new fields in stats metadata
SSTable format has been bumped to 'nc' because there are new fields in stats metadata\
- SSTal
Upgrading
---------
@ -140,6 +141,7 @@ Upgrading
upgrades involving 3.x and 4.x nodes. The fix for that issue makes it can now appear during rolling upgrades from
4.1.0 or 4.0.0-4.0.7. If that is your case, please use protocol v4 or higher in your driver. See CASSANDRA-17507
for further details.
- Added API for alternative sstable implementations. For details, see src/java/org/apache/cassandra/io/sstable/SSTable_API.md
Deprecation
-----------

View File

@ -1910,3 +1910,14 @@ drop_compact_storage_enabled: false
# excluded_keyspaces: # comma separated list of keyspaces to exclude from the check
# excluded_tables: # comma separated list of keyspace.table pairs to exclude from the check
# Supported sstable formats
# This is a list of elements consisting of class_name and parameters, where class_name should point to the class
# implementing org.apache.cassandra.io.sstable.format.SSTableFormat. Parameters must include unique 'id' integer
# which is used in some serialization to denote the format type in a compact way (such as local key cache); and 'name'
# which will be used to recognize the format type - in particular that name will be used in sstable file names and in
# stream headers so the name has to be the same for the same format across all the nodes in the cluster.
sstable_formats:
- class_name: org.apache.cassandra.io.sstable.format.big.BigFormat
parameters:
id: 0
name: big

View File

@ -17,13 +17,13 @@
*/
package org.apache.cassandra.cache;
import java.io.BufferedInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.NoSuchFileException;
import java.util.*;
import java.util.ArrayDeque;
import java.util.Iterator;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
@ -33,19 +33,28 @@ import org.slf4j.LoggerFactory;
import org.apache.cassandra.concurrent.ExecutorPlus;
import org.apache.cassandra.concurrent.ScheduledExecutors;
import org.apache.cassandra.schema.TableId;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.schema.Schema;
import org.apache.cassandra.schema.SchemaConstants;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.compaction.CompactionInfo;
import org.apache.cassandra.db.compaction.CompactionInfo.Unit;
import org.apache.cassandra.db.compaction.CompactionManager;
import org.apache.cassandra.db.compaction.OperationType;
import org.apache.cassandra.db.compaction.CompactionInfo.Unit;
import org.apache.cassandra.io.FSWriteError;
import org.apache.cassandra.io.util.*;
import org.apache.cassandra.io.util.ChecksummedRandomAccessReader;
import org.apache.cassandra.io.util.ChecksummedSequentialWriter;
import org.apache.cassandra.io.util.CorruptFileException;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataInputPlus.DataInputStreamPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.io.util.DataOutputStreamPlus;
import org.apache.cassandra.io.util.File;
import org.apache.cassandra.io.util.FileUtils;
import org.apache.cassandra.io.util.SequentialWriterOption;
import org.apache.cassandra.io.util.WrappedDataOutputStreamPlus;
import org.apache.cassandra.schema.Schema;
import org.apache.cassandra.schema.SchemaConstants;
import org.apache.cassandra.schema.TableId;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.CacheService;
import org.apache.cassandra.utils.JVMStabilityInspector;
import org.apache.cassandra.utils.Pair;
@ -59,8 +68,9 @@ public class AutoSavingCache<K extends CacheKey, V> extends InstrumentingCache<K
{
public interface IStreamFactory
{
InputStream getInputStream(File dataPath, File crcPath) throws IOException;
OutputStream getOutputStream(File dataPath, File crcPath);
DataInputStreamPlus getInputStream(File dataPath, File crcPath) throws IOException;
DataOutputStreamPlus getOutputStream(File dataPath, File crcPath);
}
private static final Logger logger = LoggerFactory.getLogger(AutoSavingCache.class);
@ -85,8 +95,10 @@ public class AutoSavingCache<K extends CacheKey, V> extends InstrumentingCache<K
* "e" introduced with CASSANDRA-11206, omits IndexInfo from key-cache, stores offset into index-file
*
* "f" introduced with CASSANDRA-9425, changes "keyspace.table.index" in cache keys to TableMetadata.id+TableMetadata.indexName
*
* "g" introduced an explicit sstable format type ordinal number so that the entry can be skipped regardless of the actual implementation and used serializer
*/
private static final String CURRENT_VERSION = "f";
private static final String CURRENT_VERSION = "g";
private static volatile IStreamFactory streamFactory = new IStreamFactory()
{
@ -95,12 +107,12 @@ public class AutoSavingCache<K extends CacheKey, V> extends InstrumentingCache<K
.trickleFsyncByteInterval(DatabaseDescriptor.getTrickleFsyncIntervalInKiB() * 1024)
.finishOnClose(true).build();
public InputStream getInputStream(File dataPath, File crcPath) throws IOException
public DataInputStreamPlus getInputStream(File dataPath, File crcPath) throws IOException
{
return ChecksummedRandomAccessReader.open(dataPath, crcPath);
}
public OutputStream getOutputStream(File dataPath, File crcPath)
public DataOutputStreamPlus getOutputStream(File dataPath, File crcPath)
{
return new ChecksummedSequentialWriter(dataPath, crcPath, null, writerOption);
}
@ -175,6 +187,7 @@ public class AutoSavingCache<K extends CacheKey, V> extends InstrumentingCache<K
return cacheLoad;
}
@SuppressWarnings("resource")
public int loadSaved()
{
int count = 0;
@ -189,15 +202,15 @@ public class AutoSavingCache<K extends CacheKey, V> extends InstrumentingCache<K
try
{
logger.info("reading saved cache {}", dataPath);
in = new DataInputStreamPlus(new LengthAvailableInputStream(new BufferedInputStream(streamFactory.getInputStream(dataPath, crcPath)), dataPath.length()));
in = streamFactory.getInputStream(dataPath, crcPath);
//Check the schema has not changed since CFs are looked up by name which is ambiguous
UUID schemaVersion = new UUID(in.readLong(), in.readLong());
if (!schemaVersion.equals(Schema.instance.getVersion()))
throw new RuntimeException("Cache schema version "
+ schemaVersion
+ " does not match current schema version "
+ Schema.instance.getVersion());
+ schemaVersion
+ " does not match current schema version "
+ Schema.instance.getVersion());
ArrayDeque<Future<Pair<K, V>>> futures = new ArrayDeque<>();
long loadByNanos = start + TimeUnit.SECONDS.toNanos(DatabaseDescriptor.getCacheLoadTimeout());
@ -268,7 +281,7 @@ public class AutoSavingCache<K extends CacheKey, V> extends InstrumentingCache<K
}
if (logger.isTraceEnabled())
logger.trace("completed reading ({} ms; {} keys) saved cache {}",
TimeUnit.NANOSECONDS.toMillis(nanoTime() - start), count, dataPath);
TimeUnit.NANOSECONDS.toMillis(nanoTime() - start), count, dataPath);
return count;
}

View File

@ -23,15 +23,22 @@ package org.apache.cassandra.cache;
import java.nio.ByteBuffer;
import java.util.concurrent.atomic.AtomicInteger;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Throwables;
import com.google.common.collect.Iterables;
import com.github.benmanes.caffeine.cache.*;
import com.github.benmanes.caffeine.cache.CacheLoader;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.github.benmanes.caffeine.cache.RemovalCause;
import com.github.benmanes.caffeine.cache.RemovalListener;
import org.apache.cassandra.concurrent.ImmediateExecutor;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.io.sstable.CorruptSSTableException;
import org.apache.cassandra.io.util.*;
import org.apache.cassandra.io.util.ChannelProxy;
import org.apache.cassandra.io.util.ChunkReader;
import org.apache.cassandra.io.util.FileHandle;
import org.apache.cassandra.io.util.Rebufferer;
import org.apache.cassandra.io.util.RebuffererFactory;
import org.apache.cassandra.metrics.ChunkCacheMetrics;
import org.apache.cassandra.utils.memory.BufferPool;
import org.apache.cassandra.utils.memory.BufferPools;
@ -170,7 +177,7 @@ public class ChunkCache
cache.invalidateAll();
}
private RebuffererFactory wrap(ChunkReader file)
public RebuffererFactory wrap(ChunkReader file)
{
return new CachingRebufferer(file);
}
@ -196,14 +203,6 @@ public class ChunkCache
cache.invalidateAll(Iterables.filter(cache.asMap().keySet(), x -> x.path.equals(fileName)));
}
@VisibleForTesting
public void enable(boolean enabled)
{
ChunkCache.enabled = enabled;
cache.invalidateAll();
metrics.reset();
}
// TODO: Invalidate caches for obsoleted/MOVED_START tables?
/**
@ -319,4 +318,4 @@ public class ChunkCache
.map(policy -> policy.weightedSize().orElseGet(cache::estimatedSize))
.orElseGet(cache::estimatedSize);
}
}
}

View File

@ -341,6 +341,9 @@ public enum CassandraRelevantProperties
SEED_COUNT_WARN_THRESHOLD("cassandra.seed_count_warn_threshold"),
SSTABLE_FORMAT_DEFAULT("cassandra.sstable.format.default"),
/** When enabled, recursive directory deletion will be executed using a unix command `rm -rf` instead of traversing
* and removing individual files. This is now used only tests, but eventually we will make it true by default.*/
USE_NIX_RECURSIVE_DELETE("cassandra.use_nix_recursive_delete"),
@ -356,6 +359,7 @@ public enum CassandraRelevantProperties
* can be also done manually for that particular case: {@code flush(SchemaConstants.SCHEMA_KEYSPACE_NAME);}. */
FLUSH_LOCAL_SCHEMA_CHANGES("cassandra.test.flush_local_schema_changes", "true"),
TOMBSTONE_HISTOGRAM_TTL_ROUND_SECONDS("cassandra.streaminghistogram.roundseconds", "60"),
;
CassandraRelevantProperties(String key, String defaultVal)
@ -561,6 +565,21 @@ public enum CassandraRelevantProperties
return Enum.valueOf(defaultValue.getDeclaringClass(), toUppercase ? value.toUpperCase() : value);
}
/**
* Gets the value of a system property as an enum, optionally calling {@link String#toUpperCase()} first.
* If the value is missing, the default value for this property is used
*
* @param toUppercase before converting to enum
* @param enumClass enumeration class
* @param <T> type
* @return enum value
*/
public <T extends Enum<T>> T getEnum(boolean toUppercase, Class<T> enumClass)
{
String value = System.getProperty(key, defaultVal);
return Enum.valueOf(enumClass, toUppercase ? value.toUpperCase() : value);
}
/**
* Sets the value into system properties.
* @param value to set

View File

@ -21,25 +21,27 @@ import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.function.Supplier;
import javax.annotation.Nullable;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.audit.AuditLogOptions;
import org.apache.cassandra.db.ConsistencyLevel;
import org.apache.cassandra.fql.FullQueryLoggerOptions;
import org.apache.cassandra.io.sstable.format.big.BigFormat;
import org.apache.cassandra.service.StartupChecks.StartupCheckType;
/**
@ -70,6 +72,9 @@ public class Config
*/
public static final String PROPERTY_PREFIX = "cassandra.";
public static final String SSTABLE_FORMAT_ID = "id";
public static final String SSTABLE_FORMAT_NAME = "name";
public String cluster_name = "Test Cluster";
public String authenticator;
public String authorizer;
@ -352,6 +357,10 @@ public class Config
public String[] data_file_directories = new String[0];
public List<ParameterizedClass> sstable_formats = ImmutableList.of(new ParameterizedClass(BigFormat.class.getName(),// "org.apache.cassandra.io.sstable.format.big.BigFormat",
ImmutableMap.of(SSTABLE_FORMAT_ID, "0",
SSTABLE_FORMAT_NAME, "big")));
/**
* The directory to use for storing the system keyspaces data.
* If unspecified the data will be stored in the first of the data_file_directories.

View File

@ -33,6 +33,8 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@ -43,12 +45,14 @@ import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.primitives.Ints;
import com.google.common.primitives.Longs;
import com.google.common.util.concurrent.RateLimiter;
@ -78,6 +82,7 @@ import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.fql.FullQueryLoggerOptions;
import org.apache.cassandra.gms.IFailureDetector;
import org.apache.cassandra.io.FSWriteError;
import org.apache.cassandra.io.sstable.format.SSTableFormat;
import org.apache.cassandra.io.util.DiskOptimizationStrategy;
import org.apache.cassandra.io.util.File;
import org.apache.cassandra.io.util.FileUtils;
@ -185,6 +190,8 @@ public class DatabaseDescriptor
private static GuardrailsOptions guardrails;
private static StartupChecksOptions startupChecksOptions;
private static Map<String, Supplier<SSTableFormat<?, ?>>> sstableFormatFactories;
private static Function<CommitLog, AbstractCommitLogSegmentManager> commitLogSegmentMgrProvider = c -> DatabaseDescriptor.isCDCEnabled()
? new CommitLogSegmentManagerCDC(c, DatabaseDescriptor.getCommitLogLocation())
: new CommitLogSegmentManagerStandard(c, DatabaseDescriptor.getCommitLogLocation());
@ -247,6 +254,8 @@ public class DatabaseDescriptor
setConfig(loadConfig());
applySSTableFormats();
applySimpleConfig();
applyPartitioner();
@ -264,6 +273,14 @@ public class DatabaseDescriptor
clientInitialization(true);
}
/**
* Equivalent to {@link #clientInitialization(boolean) clientInitialization(true, Config::new)}.
*/
public static void clientInitialization(boolean failIfDaemonOrTool)
{
clientInitialization(failIfDaemonOrTool, Config::new);
}
/**
* Initializes this class as a client, which means that just an empty configuration will
* be used.
@ -272,7 +289,7 @@ public class DatabaseDescriptor
* {@link #toolInitialization()} has been performed before, an
* {@link AssertionError} will be thrown.
*/
public static void clientInitialization(boolean failIfDaemonOrTool)
public static void clientInitialization(boolean failIfDaemonOrTool, Supplier<Config> configSupplier)
{
if (!failIfDaemonOrTool && (daemonInitialized || toolInitialized))
{
@ -291,8 +308,9 @@ public class DatabaseDescriptor
clientInitialized = true;
setDefaultFailureDetector();
Config.setClientMode(true);
conf = new Config();
conf = configSupplier.get();
diskOptimizationStrategy = new SpinningDiskOptimizationStrategy();
applySSTableFormats();
}
public static boolean isClientInitialized()
@ -379,6 +397,8 @@ public class DatabaseDescriptor
private static void applyAll() throws ConfigurationException
{
//InetAddressAndPort cares that applySimpleConfig runs first
applySSTableFormats();
applySimpleConfig();
applyPartitioner();
@ -1330,6 +1350,74 @@ public class DatabaseDescriptor
paritionerName = partitioner.getClass().getCanonicalName();
}
@VisibleForTesting
public static Map<String, Supplier<SSTableFormat<?, ?>>> loadSSTableFormatFactories(List<ParameterizedClass> configuredFormats)
{
ImmutableMap.Builder<String, Supplier<SSTableFormat<?, ?>>> sstableFormatFactories = ImmutableMap.builderWithExpectedSize(configuredFormats.size());
Set<String> names = new HashSet<>(configuredFormats.size());
Set<Integer> ids = new HashSet<>(configuredFormats.size());
for (ParameterizedClass formatConfig : configuredFormats)
{
assert formatConfig.parameters != null;
Map<String, String> params = new HashMap<>(formatConfig.parameters);
String name = params.get(Config.SSTABLE_FORMAT_NAME);
if (name == null)
throw new ConfigurationException("Missing 'name' parameter in sstable format configuration for " + formatConfig.class_name);
if (!name.matches("^[a-z]+$"))
throw new ConfigurationException("'name' parameter in sstable format configuration for " + formatConfig.class_name + " must be non-empty, lower-case letters only string");
if (names.contains(name))
throw new ConfigurationException("Name '" + name + "' of sstable format " + formatConfig.class_name + " is already defined for another sstable format");
params.remove(Config.SSTABLE_FORMAT_NAME);
String idString = params.get(Config.SSTABLE_FORMAT_ID);
if (idString == null)
throw new ConfigurationException("Missing 'id' parameter in sstable format configuration for " + formatConfig.class_name);
int id;
try
{
id = Integer.parseInt(idString);
}
catch (RuntimeException ex)
{
throw new ConfigurationException("'id' parameter in sstable format configuration for " + formatConfig.class_name + " must be an integer");
}
if (id < 0 || id > 127)
throw new ConfigurationException("'id' parameter in sstable format configuration for " + formatConfig.class_name + " must be within bounds [0..127] range");
if (ids.contains(id))
throw new ConfigurationException("ID '" + id + "' of sstable format " + formatConfig.class_name + " is already defined for another sstable format");
params.remove(Config.SSTABLE_FORMAT_ID);
Supplier<SSTableFormat<?, ?>> factory = () -> {
Class<SSTableFormat<?, ?>> cls = FBUtilities.classForName(formatConfig.class_name, "sstable format");
if (!SSTableFormat.class.isAssignableFrom(cls))
throw new ConfigurationException(String.format("Class %s for sstable format %s does not implement %s", formatConfig.class_name, name, SSTableFormat.class.getName()));
SSTableFormat<?, ?> sstableFormat = FBUtilities.instanceOrConstruct(cls.getName(), "sstable format");
sstableFormat.setup(id, name, params);
return sstableFormat;
};
sstableFormatFactories.put(name, factory);
names.add(name);
ids.add(id);
}
return sstableFormatFactories.build();
}
private static void applySSTableFormats()
{
if (sstableFormatFactories != null)
logger.warn("Reinitializing SSTableFactories - this should happen only in tests");
sstableFormatFactories = loadSSTableFormatFactories(conf.sstable_formats);
Iterable<SSTableFormat.Type> types = SSTableFormat.Type.values(); // make sure we know where those types get initialized
types.forEach(t -> t.info.allComponents()); // make sure to reach all supported components for a type so that we know all of them are registered
logger.info("Supported sstable formats are: {}", Lists.newArrayList(types).stream().map(f -> f.name + " -> " + f.info.getClass().getName() + " with singleton components: " + f.info.allComponents()).collect(Collectors.joining(", ")));
}
/**
* Computes the sum of the 2 specified positive values returning {@code Long.MAX_VALUE} if the sum overflow.
*
@ -3208,6 +3296,12 @@ public class DatabaseDescriptor
return conf.file_cache_enabled;
}
@VisibleForTesting
public static void setFileCacheEnabled(boolean enabled)
{
conf.file_cache_enabled = enabled;
}
public static int getFileCacheSizeInMiB()
{
if (conf.file_cache_size == null)
@ -4578,4 +4672,9 @@ public class DatabaseDescriptor
{
conf.client_request_size_metrics_enabled = enabled;
}
public static Map<String, Supplier<SSTableFormat<?, ?>>> getSSTableFormatFactories()
{
return Objects.requireNonNull(sstableFormatFactories, "Forgot to initialize DatabaseDescriptor?");
}
}

View File

@ -88,17 +88,16 @@ import org.apache.cassandra.db.compaction.CompactionInfo;
import org.apache.cassandra.db.compaction.CompactionManager;
import org.apache.cassandra.db.compaction.CompactionStrategyManager;
import org.apache.cassandra.db.compaction.OperationType;
import org.apache.cassandra.db.compaction.Verifier;
import org.apache.cassandra.db.filter.ClusteringIndexFilter;
import org.apache.cassandra.db.filter.DataLimits;
import org.apache.cassandra.db.memtable.Flushing;
import org.apache.cassandra.db.memtable.Memtable;
import org.apache.cassandra.db.memtable.ShardBoundaries;
import org.apache.cassandra.db.lifecycle.LifecycleNewTracker;
import org.apache.cassandra.db.lifecycle.LifecycleTransaction;
import org.apache.cassandra.db.lifecycle.SSTableSet;
import org.apache.cassandra.db.lifecycle.Tracker;
import org.apache.cassandra.db.lifecycle.View;
import org.apache.cassandra.db.memtable.Flushing;
import org.apache.cassandra.db.memtable.Memtable;
import org.apache.cassandra.db.memtable.ShardBoundaries;
import org.apache.cassandra.db.partitions.CachedPartition;
import org.apache.cassandra.db.partitions.PartitionUpdate;
import org.apache.cassandra.db.repair.CassandraTableRepairManager;
@ -120,17 +119,19 @@ 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.Descriptor;
import org.apache.cassandra.io.sstable.IScrubber;
import org.apache.cassandra.io.sstable.IVerifier;
import org.apache.cassandra.io.sstable.SSTable;
import org.apache.cassandra.io.sstable.SSTableId;
import org.apache.cassandra.io.sstable.SSTableIdFactory;
import org.apache.cassandra.io.sstable.SSTableMultiWriter;
import org.apache.cassandra.io.sstable.format.SSTableFormat;
import org.apache.cassandra.io.sstable.format.SSTableFormat.Components;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.io.sstable.format.Version;
import org.apache.cassandra.io.sstable.metadata.MetadataCollector;
import org.apache.cassandra.io.util.File;
import org.apache.cassandra.io.util.FileOutputStreamPlus;
import org.apache.cassandra.io.util.FileUtils;
import org.apache.cassandra.metrics.Sampler;
import org.apache.cassandra.metrics.Sampler.Sample;
import org.apache.cassandra.metrics.Sampler.SamplerType;
@ -186,7 +187,7 @@ import static org.apache.cassandra.utils.Throwables.maybeFail;
import static org.apache.cassandra.utils.Throwables.merge;
import static org.apache.cassandra.utils.concurrent.CountDownLatch.newCountDownLatch;
public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner
public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner, SSTable.Owner
{
private static final Logger logger = LoggerFactory.getLogger(ColumnFamilyStore.class);
@ -507,7 +508,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner
if (data.loadsstables)
{
Directories.SSTableLister sstableFiles = directories.sstableLister(Directories.OnTxnErr.IGNORE).skipTemporary(true);
sstables = SSTableReader.openAll(sstableFiles.list().entrySet(), metadata);
sstables = SSTableReader.openAll(this, sstableFiles.list().entrySet(), metadata);
data.addInitialSSTablesWithoutUpdatingSize(sstables);
}
@ -795,19 +796,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner
}
}
File dataFile = new File(desc.filenameFor(Component.DATA));
if (components.contains(Component.DATA) && dataFile.length() > 0)
// everything appears to be in order... moving on.
continue;
// missing the DATA file! all components are orphaned
logger.warn("Removing orphans for {}: {}", desc, components);
for (Component component : components)
{
File file = new File(desc.filenameFor(component));
if (file.exists())
FileUtils.deleteWithConfirm(desc.filenameFor(component));
}
desc.getFormat().deleteOrphanedComponents(desc, components);
}
// cleanup incomplete saved caches
@ -889,7 +878,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner
sstableIdGenerator.get(),
descriptor.formatType);
}
while (newDescriptor.fileFor(Component.DATA).exists());
while (newDescriptor.fileFor(Components.DATA).exists());
return newDescriptor;
}
@ -949,7 +938,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner
name,
sstableIdGenerator.get(),
format);
assert !newDescriptor.fileFor(Component.DATA).exists();
assert !newDescriptor.fileFor(Components.DATA).exists();
return newDescriptor;
}
@ -1530,7 +1519,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner
View view = data.getView();
List<SSTableReader> sortedByFirst = Lists.newArrayList(sstables);
Collections.sort(sortedByFirst, (o1, o2) -> o1.first.compareTo(o2.first));
sortedByFirst.sort(SSTableReader.sstableComparator);
List<AbstractBounds<PartitionPosition>> bounds = new ArrayList<>();
DecoratedKey first = null, last = null;
@ -1547,21 +1536,21 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner
{
if (first == null)
{
first = sstable.first;
last = sstable.last;
first = sstable.getFirst();
last = sstable.getLast();
}
else
{
if (sstable.first.compareTo(last) <= 0) // we do overlap
if (sstable.getFirst().compareTo(last) <= 0) // we do overlap
{
if (sstable.last.compareTo(last) > 0)
last = sstable.last;
if (sstable.getLast().compareTo(last) > 0)
last = sstable.getLast();
}
else
{
bounds.add(AbstractBounds.bounds(first, true, last, true));
first = sstable.first;
last = sstable.last;
first = sstable.getFirst();
last = sstable.getLast();
}
}
}
@ -1669,13 +1658,13 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner
return CompactionManager.instance.performCleanup(ColumnFamilyStore.this, jobs);
}
public CompactionManager.AllSSTableOpStatus scrub(boolean disableSnapshot, boolean skipCorrupted, boolean checkData, boolean reinsertOverflowedTTL, int jobs) throws ExecutionException, InterruptedException
public CompactionManager.AllSSTableOpStatus scrub(boolean disableSnapshot, IScrubber.Options options, int jobs) throws ExecutionException, InterruptedException
{
return scrub(disableSnapshot, skipCorrupted, reinsertOverflowedTTL, false, checkData, jobs);
return scrub(disableSnapshot, false, options, jobs);
}
@VisibleForTesting
public CompactionManager.AllSSTableOpStatus scrub(boolean disableSnapshot, boolean skipCorrupted, boolean reinsertOverflowedTTL, boolean alwaysFail, boolean checkData, 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
if(!disableSnapshot)
@ -1687,7 +1676,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner
try
{
return CompactionManager.instance.performScrub(ColumnFamilyStore.this, skipCorrupted, checkData, reinsertOverflowedTTL, jobs);
return CompactionManager.instance.performScrub(ColumnFamilyStore.this, options, jobs);
}
catch(Throwable t)
{
@ -1722,7 +1711,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner
return true;
}
public CompactionManager.AllSSTableOpStatus verify(Verifier.Options options) throws ExecutionException, InterruptedException
public CompactionManager.AllSSTableOpStatus verify(IVerifier.Options options) throws ExecutionException, InterruptedException
{
return CompactionManager.instance.performVerify(ColumnFamilyStore.this, options);
}
@ -1730,7 +1719,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner
/**
* Rewrites all SSTables according to specified parameters
*
* @param skipIfCurrentVersion - if {@link true}, will rewrite only SSTables that have version older than the current one ({@link org.apache.cassandra.io.sstable.format.big.BigFormat#latestVersion})
* @param skipIfCurrentVersion - if {@link true}, will rewrite only SSTables that have version older than the current one ({@link SSTableFormat#getLatestVersion()})
* @param skipIfNewerThanTimestamp - max timestamp (local creation time) for SSTable; SSTables created _after_ this timestamp will be excluded from compaction
* @param skipIfCompressionMatches - if {@link true}, will rewrite only SSTables whose compression parameters are different from {@link TableMetadata#params#getCompressionParameters()} ()}
* @param jobs number of jobs for parallel execution
@ -1973,7 +1962,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner
for (SSTableReader sstr : select(View.select(SSTableSet.LIVE, dk)).sstables)
{
// check if the key actually exists in this sstable, without updating cache and stats
if (sstr.getPosition(dk, SSTableReader.Operator.EQ, false) != null)
if (sstr.getPosition(dk, SSTableReader.Operator.EQ, false) >= 0)
mapped.add(mapper.apply(sstr));
}
return mapped;
@ -2136,7 +2125,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner
private List<String> mapToDataFilenames(Collection<SSTableReader> sstables)
{
return sstables.stream().map(s -> s.descriptor.relativeFilenameFor(Component.DATA)).collect(Collectors.toList());
return sstables.stream().map(s -> s.descriptor.relativeFilenameFor(Components.DATA)).collect(Collectors.toList());
}
private void writeSnapshotSchema(File schemaFile)
@ -2194,7 +2183,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner
if (logger.isTraceEnabled())
logger.trace("using snapshot sstable {}", entries.getKey());
// open offline so we don't modify components or track hotness.
sstable = SSTableReader.open(entries.getKey(), entries.getValue(), metadata, true, true);
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();
@ -2944,6 +2933,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner
}
@Override
public Double getCrcCheckChance()
{
return crcCheckChance.value();
@ -3289,10 +3279,10 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner
DecoratedKey last = null;
for (SSTableReader sstable : sstables)
{
if (first == null || first.compareTo(sstable.first) > 0)
first = sstable.first;
if (last == null || last.compareTo(sstable.last) < 0)
last = sstable.last;
if (first == null || first.compareTo(sstable.getFirst()) > 0)
first = sstable.getFirst();
if (last == null || last.compareTo(sstable.getLast()) < 0)
last = sstable.getLast();
}
DiskBoundaries diskBoundaries = getDiskBoundaries();
@ -3476,4 +3466,16 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner
return null;
return topPartitions.topTombstones().lastUpdate;
}
@Override
public OpOrder.Barrier newReadOrderingBarrier()
{
return readOrdering.newBarrier();
}
@Override
public TableMetrics getMetrics()
{
return metric;
}
}

View File

@ -30,11 +30,12 @@ import org.apache.cassandra.dht.Token.KeyBound;
import org.apache.cassandra.schema.ColumnMetadata;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.IFilter.FilterKey;
import org.apache.cassandra.utils.MurmurHash;
import org.apache.cassandra.utils.bytecomparable.ByteComparable;
import org.apache.cassandra.utils.bytecomparable.ByteSource;
import org.apache.cassandra.utils.bytecomparable.ByteSourceInverse;
import org.apache.cassandra.utils.IFilter.FilterKey;
import org.apache.cassandra.utils.MurmurHash;
import org.apache.cassandra.utils.memory.HeapCloner;
/**
* Represents a decorated key, handy for certain operations
@ -47,13 +48,7 @@ import org.apache.cassandra.utils.MurmurHash;
*/
public abstract class DecoratedKey implements PartitionPosition, FilterKey
{
public static final Comparator<DecoratedKey> comparator = new Comparator<DecoratedKey>()
{
public int compare(DecoratedKey o1, DecoratedKey o2)
{
return o1.compareTo(o2);
}
};
public static final Comparator<DecoratedKey> comparator = DecoratedKey::compareTo;
private final Token token;
@ -200,6 +195,17 @@ public abstract class DecoratedKey implements PartitionPosition, FilterKey
public abstract ByteBuffer getKey();
public abstract int getKeyLength();
/**
* If this key occupies only part of a larger buffer, allocate a new buffer that is only as large as necessary.
* Otherwise, it returns this key.
*/
public DecoratedKey retainable()
{
return ByteBufferUtil.canMinimize(getKey())
? new BufferDecoratedKey(getToken(), HeapCloner.instance.clone(getKey()))
: this;
}
public void filterHash(long[] dest)
{
ByteBuffer key = getKey();
@ -240,4 +246,4 @@ public abstract class DecoratedKey implements PartitionPosition, FilterKey
assert terminator == ByteSource.TERMINATOR : "Decorated key encoding must end in terminator.";
return keyBytes;
}
}
}

View File

@ -17,46 +17,59 @@
*/
package org.apache.cassandra.db;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.BiPredicate;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import com.google.common.annotations.VisibleForTesting;
import java.io.IOError;
import java.io.IOException;
import java.nio.file.FileStore;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.Spliterator;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.BiPredicate;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.config.*;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.db.lifecycle.LifecycleTransaction;
import org.apache.cassandra.io.FSDiskFullWriteError;
import org.apache.cassandra.io.FSError;
import org.apache.cassandra.io.FSNoDiskAvailableForWriteError;
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.Descriptor;
import org.apache.cassandra.io.sstable.SSTable;
import org.apache.cassandra.io.sstable.SSTableId;
import org.apache.cassandra.io.util.File;
import org.apache.cassandra.io.util.FileStoreUtils;
import org.apache.cassandra.io.util.FileUtils;
import org.apache.cassandra.io.sstable.*;
import org.apache.cassandra.schema.SchemaConstants;
import org.apache.cassandra.io.util.PathUtils;
import org.apache.cassandra.schema.SchemaConstants;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.snapshot.SnapshotManifest;
import org.apache.cassandra.service.snapshot.TableSnapshot;
@ -275,7 +288,7 @@ public class Directories
if (file.isDirectory())
return false;
Descriptor desc = SSTable.tryDescriptorFromFilename(file);
Descriptor desc = SSTable.tryDescriptorFromFile(file);
return desc != null && desc.ksname.equals(metadata.keyspace) && desc.cfname.equals(metadata.name);
});
for (File indexFile : indexFiles)
@ -322,7 +335,7 @@ public class Directories
{
File file = new File(dir, filename);
if (file.exists())
return Descriptor.fromFilename(file);
return Descriptor.fromFileWithComponent(file, false).left;
}
return null;
}
@ -969,7 +982,7 @@ public class Directories
{
for (Component c : entry.getValue())
{
l.add(new File(entry.getKey().filenameFor(c)));
l.add(entry.getKey().fileFor(c));
}
}
return l;
@ -1310,7 +1323,7 @@ public class Directories
public boolean isAcceptable(Path path)
{
File file = new File(path);
Descriptor desc = SSTable.tryDescriptorFromFilename(file);
Descriptor desc = SSTable.tryDescriptorFromFile(file);
return desc != null
&& desc.ksname.equals(metadata.keyspace)
&& desc.cfname.equals(metadata.name)

View File

@ -23,9 +23,6 @@ import java.util.concurrent.TimeUnit;
import com.google.common.annotations.VisibleForTesting;
import org.apache.cassandra.db.rows.UnfilteredRowIterator;
import org.apache.cassandra.db.virtual.VirtualKeyspaceRegistry;
import org.apache.cassandra.db.virtual.VirtualTable;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.db.filter.ClusteringIndexFilter;
import org.apache.cassandra.db.filter.ColumnFilter;
@ -38,14 +35,17 @@ import org.apache.cassandra.db.partitions.PartitionIterator;
import org.apache.cassandra.db.partitions.UnfilteredPartitionIterator;
import org.apache.cassandra.db.partitions.UnfilteredPartitionIterators;
import org.apache.cassandra.db.rows.BaseRowIterator;
import org.apache.cassandra.db.rows.UnfilteredRowIterator;
import org.apache.cassandra.db.transform.RTBoundValidator;
import org.apache.cassandra.db.transform.Transformation;
import org.apache.cassandra.db.virtual.VirtualKeyspaceRegistry;
import org.apache.cassandra.db.virtual.VirtualTable;
import org.apache.cassandra.dht.AbstractBounds;
import org.apache.cassandra.dht.Bounds;
import org.apache.cassandra.exceptions.RequestExecutionException;
import org.apache.cassandra.index.Index;
import org.apache.cassandra.io.sstable.SSTableReadsListener;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.io.sstable.format.SSTableReadsListener;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.metrics.TableMetrics;

View File

@ -28,19 +28,20 @@ import java.util.Set;
import java.util.UUID;
import com.google.common.annotations.VisibleForTesting;
import org.apache.cassandra.io.util.File;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.db.compaction.Verifier;
import org.apache.cassandra.db.lifecycle.SSTableSet;
import org.apache.cassandra.io.sstable.Component;
import org.apache.cassandra.io.sstable.Descriptor;
import org.apache.cassandra.io.sstable.IVerifier;
import org.apache.cassandra.io.sstable.KeyIterator;
import org.apache.cassandra.io.sstable.SSTable;
import org.apache.cassandra.io.sstable.format.SSTableFormat.Components;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.io.sstable.format.SSTableWriter;
import org.apache.cassandra.io.util.File;
import org.apache.cassandra.service.ActiveRepairService;
import org.apache.cassandra.utils.OutputHandler;
import org.apache.cassandra.utils.Pair;
import org.apache.cassandra.utils.concurrent.Refs;
@ -185,7 +186,7 @@ public class SSTableImporter
for (SSTableReader reader : newSSTables)
{
if (options.invalidateCaches && cfs.isRowCacheEnabled())
invalidateCachesForSSTable(reader.descriptor);
invalidateCachesForSSTable(reader);
}
}
@ -209,8 +210,8 @@ public class SSTableImporter
private static String formatMetadata(SSTableReader sstable)
{
return String.format("{[%s, %s], %d, %s, %d}",
sstable.first.getToken(),
sstable.last.getToken(),
sstable.getFirst().getToken(),
sstable.getLast().getToken(),
sstable.getSSTableLevel(),
sstable.isRepaired(),
sstable.onDiskLength());
@ -233,7 +234,7 @@ public class SSTableImporter
SSTableReader sstable = null;
try
{
sstable = SSTableReader.open(descriptor, components, cfs.metadata);
sstable = SSTableReader.open(cfs, descriptor, components, cfs.metadata);
targetDirectory = cfs.getDirectories().getLocationForDisk(cfs.diskBoundaryManager.getDiskBoundaries(cfs).getCorrectDiskForSSTable(sstable));
}
finally
@ -241,7 +242,7 @@ public class SSTableImporter
if (sstable != null)
sstable.selfRef().release();
}
return targetDirectory == null ? cfs.getDirectories().getWriteableLocationToLoadFile(new File(descriptor.baseFilename())) : targetDirectory;
return targetDirectory == null ? cfs.getDirectories().getWriteableLocationToLoadFile(descriptor.baseFile()) : targetDirectory;
}
/**
@ -304,11 +305,11 @@ public class SSTableImporter
{
for (MovedSSTable movedSSTable : movedSSTables)
{
if (new File(movedSSTable.newDescriptor.filenameFor(Component.DATA)).exists())
if (movedSSTable.newDescriptor.fileFor(Components.DATA).exists())
{
logger.debug("Moving sstable {} back to {}", movedSSTable.newDescriptor.filenameFor(Component.DATA)
, movedSSTable.oldDescriptor.filenameFor(Component.DATA));
SSTableWriter.rename(movedSSTable.newDescriptor, movedSSTable.oldDescriptor, movedSSTable.components);
logger.debug("Moving sstable {} back to {}", movedSSTable.newDescriptor.fileFor(Components.DATA)
, movedSSTable.oldDescriptor.fileFor(Components.DATA));
SSTable.rename(movedSSTable.newDescriptor, movedSSTable.oldDescriptor, movedSSTable.components);
}
}
}
@ -324,11 +325,8 @@ public class SSTableImporter
logger.debug("Removing copied SSTables which were left in data directories after failed SSTable import.");
for (MovedSSTable movedSSTable : movedSSTables)
{
if (new File(movedSSTable.newDescriptor.filenameFor(Component.DATA)).exists())
{
// no logging here as for moveSSTablesBack case above as logging is done in delete method
SSTableWriter.delete(movedSSTable.newDescriptor, movedSSTable.components);
}
// no logging here as for moveSSTablesBack case above as logging is done in delete method
movedSSTable.newDescriptor.getFormat().delete(movedSSTable.newDescriptor);
}
}
@ -336,9 +334,9 @@ public class SSTableImporter
* Iterates over all keys in the sstable index and invalidates the row cache
*/
@VisibleForTesting
void invalidateCachesForSSTable(Descriptor desc)
void invalidateCachesForSSTable(SSTableReader reader)
{
try (KeyIterator iter = new KeyIterator(desc, cfs.metadata()))
try (KeyIterator iter = reader.keyIterator())
{
while (iter.hasNext())
{
@ -346,6 +344,10 @@ public class SSTableImporter
cfs.invalidateCachedPartition(decoratedKey);
}
}
catch (IOException ex)
{
throw new RuntimeException("Failed to import sstable " + reader.getFilename(), ex);
}
}
/**
@ -360,14 +362,15 @@ public class SSTableImporter
SSTableReader reader = null;
try
{
reader = SSTableReader.open(descriptor, components, cfs.metadata);
Verifier.Options verifierOptions = Verifier.options()
.extendedVerification(extendedVerify)
.checkOwnsTokens(verifyTokens)
.quick(!verifySSTables)
.invokeDiskFailurePolicy(false)
.mutateRepairStatus(false).build();
try (Verifier verifier = new Verifier(cfs, reader, false, verifierOptions))
reader = SSTableReader.open(cfs, descriptor, components, cfs.metadata);
IVerifier.Options verifierOptions = IVerifier.options()
.extendedVerification(extendedVerify)
.checkOwnsTokens(verifyTokens)
.quick(!verifySSTables)
.invokeDiskFailurePolicy(false)
.mutateRepairStatus(false).build();
try (IVerifier verifier = reader.getVerifier(cfs, new OutputHandler.LogOutput(), false, verifierOptions))
{
verifier.verify();
}
@ -389,7 +392,7 @@ public class SSTableImporter
*/
private void maybeMutateMetadata(Descriptor descriptor, Options options) throws IOException
{
if (new File(descriptor.filenameFor(Component.STATS)).exists())
if (descriptor.fileFor(Components.STATS).exists())
{
if (options.resetLevel)
{

View File

@ -19,7 +19,12 @@ package org.apache.cassandra.db;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.NavigableSet;
import java.util.TreeSet;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@ -30,18 +35,37 @@ import org.apache.cassandra.cache.IRowCacheEntry;
import org.apache.cassandra.cache.RowCacheKey;
import org.apache.cassandra.cache.RowCacheSentinel;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.db.filter.*;
import org.apache.cassandra.db.lifecycle.*;
import org.apache.cassandra.db.filter.ClusteringIndexFilter;
import org.apache.cassandra.db.filter.ClusteringIndexNamesFilter;
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.lifecycle.SSTableSet;
import org.apache.cassandra.db.lifecycle.View;
import org.apache.cassandra.db.memtable.Memtable;
import org.apache.cassandra.db.partitions.*;
import org.apache.cassandra.db.rows.*;
import org.apache.cassandra.db.partitions.CachedBTreePartition;
import org.apache.cassandra.db.partitions.CachedPartition;
import org.apache.cassandra.db.partitions.ImmutableBTreePartition;
import org.apache.cassandra.db.partitions.PartitionIterator;
import org.apache.cassandra.db.partitions.PartitionIterators;
import org.apache.cassandra.db.partitions.SingletonUnfilteredPartitionIterator;
import org.apache.cassandra.db.partitions.UnfilteredPartitionIterator;
import org.apache.cassandra.db.rows.Cell;
import org.apache.cassandra.db.rows.Row;
import org.apache.cassandra.db.rows.Rows;
import org.apache.cassandra.db.rows.Unfiltered;
import org.apache.cassandra.db.rows.UnfilteredRowIterator;
import org.apache.cassandra.db.rows.UnfilteredRowIteratorWithLowerBound;
import org.apache.cassandra.db.rows.UnfilteredRowIterators;
import org.apache.cassandra.db.rows.WrappingUnfilteredRowIterator;
import org.apache.cassandra.db.transform.RTBoundValidator;
import org.apache.cassandra.db.transform.Transformation;
import org.apache.cassandra.db.virtual.VirtualKeyspaceRegistry;
import org.apache.cassandra.db.virtual.VirtualTable;
import org.apache.cassandra.exceptions.RequestExecutionException;
import org.apache.cassandra.io.sstable.SSTableReadsListener;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.io.sstable.format.SSTableReadsListener;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.metrics.TableMetrics;
@ -49,7 +73,9 @@ import org.apache.cassandra.net.Verb;
import org.apache.cassandra.schema.ColumnMetadata;
import org.apache.cassandra.schema.IndexMetadata;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.*;
import org.apache.cassandra.service.CacheService;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.service.StorageProxy;
import org.apache.cassandra.tracing.Tracing;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.btree.BTreeSet;
@ -538,20 +564,26 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
try
{
// Use a custom iterator instead of DataLimits to avoid stopping the original iterator
UnfilteredRowIterator toCacheIterator = new WrappingUnfilteredRowIterator(iter)
UnfilteredRowIterator toCacheIterator = new WrappingUnfilteredRowIterator()
{
private int rowsCounted = 0;
@Override
public UnfilteredRowIterator wrapped()
{
return iter;
}
@Override
public boolean hasNext()
{
return rowsCounted < rowsToCache && super.hasNext();
return rowsCounted < rowsToCache && iter.hasNext();
}
@Override
public Unfiltered next()
{
Unfiltered unfiltered = super.next();
Unfiltered unfiltered = iter.next();
if (unfiltered.isRow())
{
Row row = (Row) unfiltered;
@ -1307,7 +1339,7 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
private int mergedSSTables;
@Override
public void onSSTableSelected(SSTableReader sstable, RowIndexEntry<?> indexEntry, SelectionReason reason)
public void onSSTableSelected(SSTableReader sstable, SelectionReason reason)
{
sstable.incrementReadCount();
mergedSSTables++;

View File

@ -23,8 +23,8 @@ import org.apache.cassandra.db.filter.ColumnFilter;
import org.apache.cassandra.db.partitions.PartitionUpdate;
import org.apache.cassandra.db.rows.UnfilteredRowIterator;
import org.apache.cassandra.db.rows.UnfilteredRowIteratorWithLowerBound;
import org.apache.cassandra.io.sstable.SSTableReadsListener;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.io.sstable.format.SSTableReadsListener;
import org.apache.cassandra.schema.TableId;
import org.apache.cassandra.utils.FBUtilities;

View File

@ -17,34 +17,39 @@
*/
package org.apache.cassandra.db.compaction;
import java.util.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap;
import org.apache.cassandra.db.Directories;
import org.apache.cassandra.db.SerializationHeader;
import org.apache.cassandra.index.Index;
import org.apache.cassandra.db.lifecycle.LifecycleNewTracker;
import org.apache.cassandra.io.sstable.Descriptor;
import org.apache.cassandra.io.sstable.SSTableMultiWriter;
import org.apache.cassandra.io.sstable.SimpleSSTableMultiWriter;
import org.apache.cassandra.io.sstable.format.SSTableReader;
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.SerializationHeader;
import org.apache.cassandra.db.lifecycle.LifecycleNewTracker;
import org.apache.cassandra.db.lifecycle.LifecycleTransaction;
import org.apache.cassandra.dht.Range;
import org.apache.cassandra.dht.Token;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.index.Index;
import org.apache.cassandra.io.sstable.Descriptor;
import org.apache.cassandra.io.sstable.ISSTableScanner;
import org.apache.cassandra.io.sstable.SSTableMultiWriter;
import org.apache.cassandra.io.sstable.SimpleSSTableMultiWriter;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.io.sstable.metadata.MetadataCollector;
import org.apache.cassandra.io.sstable.metadata.StatsMetadata;
import org.apache.cassandra.schema.CompactionParams;
import org.apache.cassandra.utils.TimeUUID;
import static org.apache.cassandra.io.sstable.Component.DATA;
import static org.apache.cassandra.utils.Clock.Global.currentTimeMillis;
/**
@ -399,7 +404,7 @@ public abstract class AbstractCompactionStrategy
// since we use estimations to calculate, there is a chance that compaction will not drop tombstones actually.
// if that happens we will end up in infinite compaction loop, so first we check enough if enough time has
// elapsed since SSTable created.
if (currentTimeMillis() < sstable.getCreationTimeFor(DATA) + tombstoneCompactionInterval * 1000)
if (currentTimeMillis() < sstable.getDataCreationTime() + tombstoneCompactionInterval * 1000)
return false;
double droppableRatio = sstable.getEstimatedDroppableTombstoneRatio(gcBefore);
@ -423,16 +428,16 @@ public abstract class AbstractCompactionStrategy
else
{
// what percentage of columns do we expect to compact outside of overlap?
if (sstable.getIndexSummarySize() < 2)
if (!sstable.isEstimationInformative())
{
// we have too few samples to estimate correct percentage
return false;
}
// first, calculate estimated keys that do not overlap
long keys = sstable.estimatedKeys();
Set<Range<Token>> ranges = new HashSet<Range<Token>>(overlaps.size());
Set<Range<Token>> ranges = new HashSet<>(overlaps.size());
for (SSTableReader overlap : overlaps)
ranges.add(new Range<>(overlap.first.getToken(), overlap.last.getToken()));
ranges.add(new Range<>(overlap.getFirst().getToken(), overlap.getLast().getToken()));
long remainingKeys = keys - sstable.estimatedKeysForRanges(ranges);
// next, calculate what percentage of columns we have within those keys
long columns = sstable.getEstimatedCellPerPartitionCount().mean() * remainingKeys;
@ -560,7 +565,7 @@ public abstract class AbstractCompactionStrategy
Collection<Index> indexes,
LifecycleNewTracker lifecycleNewTracker)
{
return SimpleSSTableMultiWriter.create(descriptor, keyCount, repairedAt, pendingRepair, isTransient, cfs.metadata, meta, header, indexes, lifecycleNewTracker);
return SimpleSSTableMultiWriter.create(descriptor, keyCount, repairedAt, pendingRepair, isTransient, cfs.metadata, meta, header, indexes, lifecycleNewTracker, cfs);
}
public boolean supportsEarlyOpen()

View File

@ -17,7 +17,14 @@
*/
package org.apache.cassandra.db.compaction;
import java.util.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.LongPredicate;
import com.google.common.base.Predicates;
@ -27,7 +34,10 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.config.Config;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.AbstractCompactionController;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.PartitionPosition;
import org.apache.cassandra.db.memtable.Memtable;
import org.apache.cassandra.db.rows.UnfilteredRowIterator;
import org.apache.cassandra.io.sstable.format.SSTableReader;
@ -251,7 +261,7 @@ public class CompactionController extends AbstractCompactionController
for (SSTableReader sstable: filteredSSTables)
{
if (sstable.maybePresent(key))
if (sstable.mayContainAssumingKeyIsInRange(key))
{
minTimestampSeen = Math.min(minTimestampSeen, sstable.getMinTimestamp());
hasTimestamp = true;
@ -316,8 +326,8 @@ public class CompactionController extends AbstractCompactionController
reader.getMaxTimestamp() <= minTimestamp ||
tombstoneOnly && !reader.mayHaveTombstones())
return null;
RowIndexEntry<?> position = reader.getPosition(key, SSTableReader.Operator.EQ);
if (position == null)
long position = reader.getPosition(key, SSTableReader.Operator.EQ);
if (position < 0)
return null;
FileDataInput dfile = openDataFiles.computeIfAbsent(reader, this::openDataFile);
return reader.simpleIterator(dfile, key, position, tombstoneOnly);

View File

@ -57,7 +57,7 @@ public final class CompactionInfo
private final ImmutableSet<SSTableReader> sstables;
private final String targetDirectory;
public CompactionInfo(TableMetadata metadata, OperationType tasktype, long completed, long total, Unit unit, TimeUUID compactionId, Collection<SSTableReader> sstables, String targetDirectory)
public CompactionInfo(TableMetadata metadata, OperationType tasktype, long completed, long total, Unit unit, TimeUUID compactionId, Collection<? extends SSTableReader> sstables, String targetDirectory)
{
this.tasktype = tasktype;
this.completed = completed;
@ -74,7 +74,7 @@ public final class CompactionInfo
this(metadata, tasktype, completed, total, Unit.BYTES, compactionId, sstables, targetDirectory);
}
public CompactionInfo(TableMetadata metadata, OperationType tasktype, long completed, long total, TimeUUID compactionId, Collection<SSTableReader> sstables)
public CompactionInfo(TableMetadata metadata, OperationType tasktype, long completed, long total, TimeUUID compactionId, Collection<? extends SSTableReader> sstables)
{
this(metadata, tasktype, completed, total, Unit.BYTES, compactionId, sstables, null);
}

View File

@ -17,33 +17,51 @@
*/
package org.apache.cassandra.db.compaction;
import java.util.*;
import java.util.function.LongPredicate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.LongPredicate;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Ordering;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.dht.Token;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.schema.Schema;
import org.apache.cassandra.schema.SchemaConstants;
import org.apache.cassandra.schema.TableId;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.db.transform.DuplicateRowChecker;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.AbstractCompactionController;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.Columns;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.DeletionTime;
import org.apache.cassandra.db.EmptyIterators;
import org.apache.cassandra.db.Keyspace;
import org.apache.cassandra.db.RegularAndStaticColumns;
import org.apache.cassandra.db.SystemKeyspace;
import org.apache.cassandra.db.filter.ColumnFilter;
import org.apache.cassandra.db.partitions.PurgeFunction;
import org.apache.cassandra.db.partitions.UnfilteredPartitionIterator;
import org.apache.cassandra.db.partitions.UnfilteredPartitionIterators;
import org.apache.cassandra.db.rows.*;
import org.apache.cassandra.db.rows.RangeTombstoneBoundMarker;
import org.apache.cassandra.db.rows.RangeTombstoneMarker;
import org.apache.cassandra.db.rows.Row;
import org.apache.cassandra.db.rows.Rows;
import org.apache.cassandra.db.rows.Unfiltered;
import org.apache.cassandra.db.rows.UnfilteredRowIterator;
import org.apache.cassandra.db.rows.UnfilteredRowIterators;
import org.apache.cassandra.db.rows.WrappingUnfilteredRowIterator;
import org.apache.cassandra.db.transform.DuplicateRowChecker;
import org.apache.cassandra.db.transform.Transformation;
import org.apache.cassandra.dht.Token;
import org.apache.cassandra.index.transactions.CompactionTransaction;
import org.apache.cassandra.io.sstable.ISSTableScanner;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.metrics.TopPartitionTracker;
import org.apache.cassandra.schema.CompactionParams.TombstoneOption;
import org.apache.cassandra.schema.Schema;
import org.apache.cassandra.schema.SchemaConstants;
import org.apache.cassandra.schema.TableId;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.paxos.PaxosRepairHistory;
import org.apache.cassandra.service.paxos.uncommitted.PaxosRows;
import org.apache.cassandra.utils.TimeUUID;
@ -385,8 +403,10 @@ public class CompactionIterator extends CompactionInfo.Holder implements Unfilte
* The result produced by this iterator is such that when merged with tombSource it produces the same output
* as the merge of dataSource and tombSource.
*/
private static class GarbageSkippingUnfilteredRowIterator extends WrappingUnfilteredRowIterator
private static class GarbageSkippingUnfilteredRowIterator implements WrappingUnfilteredRowIterator
{
private final UnfilteredRowIterator wrapped;
final UnfilteredRowIterator tombSource;
final DeletionTime partitionLevelDeletion;
final Row staticRow;
@ -413,7 +433,7 @@ public class CompactionIterator extends CompactionInfo.Holder implements Unfilte
*/
protected GarbageSkippingUnfilteredRowIterator(UnfilteredRowIterator dataSource, UnfilteredRowIterator tombSource, boolean cellLevelGC)
{
super(dataSource);
this.wrapped = dataSource;
this.tombSource = tombSource;
this.cellLevelGC = cellLevelGC;
metadata = dataSource.metadata();
@ -433,6 +453,12 @@ public class CompactionIterator extends CompactionInfo.Holder implements Unfilte
dataNext = advance(dataSource);
}
@Override
public UnfilteredRowIterator wrapped()
{
return wrapped;
}
private static Unfiltered advance(UnfilteredRowIterator source)
{
return source.hasNext() ? source.next() : null;
@ -446,7 +472,7 @@ public class CompactionIterator extends CompactionInfo.Holder implements Unfilte
public void close()
{
super.close();
wrapped.close();
tombSource.close();
}
@ -719,4 +745,4 @@ public class CompactionIterator extends CompactionInfo.Holder implements Unfilte
{
return cfs.name.equals(SystemKeyspace.PAXOS) && cfs.keyspace.getName().equals(SchemaConstants.SYSTEM_KEYSPACE_NAME);
}
}
}

View File

@ -18,7 +18,16 @@
package org.apache.cassandra.db.compaction;
import java.io.IOException;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
@ -34,25 +43,33 @@ import javax.management.openmbean.TabularData;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.*;
import com.google.common.base.Predicates;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Collections2;
import com.google.common.collect.ConcurrentHashMultiset;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multiset;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.RateLimiter;
import com.google.common.util.concurrent.Uninterruptibles;
import org.apache.cassandra.concurrent.ExecutorFactory;
import org.apache.cassandra.concurrent.WrappedExecutorPlus;
import org.apache.cassandra.dht.AbstractBounds;
import org.apache.cassandra.io.util.File;
import org.apache.cassandra.locator.RangesAtEndpoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.openhft.chronicle.core.util.ThrowingSupplier;
import org.apache.cassandra.cache.AutoSavingCache;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.repair.NoSuchRepairSessionException;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.concurrent.ExecutorFactory;
import org.apache.cassandra.concurrent.WrappedExecutorPlus;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.schema.Schema;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.Directories;
import org.apache.cassandra.db.DiskBoundaries;
import org.apache.cassandra.db.Keyspace;
import org.apache.cassandra.db.SerializationHeader;
import org.apache.cassandra.db.SystemKeyspace;
import org.apache.cassandra.db.compaction.CompactionInfo.Holder;
import org.apache.cassandra.db.lifecycle.ILifecycleTransaction;
import org.apache.cassandra.db.lifecycle.LifecycleTransaction;
@ -62,27 +79,40 @@ import org.apache.cassandra.db.lifecycle.View;
import org.apache.cassandra.db.lifecycle.WrappedLifecycleTransaction;
import org.apache.cassandra.db.rows.UnfilteredRowIterator;
import org.apache.cassandra.db.view.ViewBuilderTask;
import org.apache.cassandra.dht.Bounds;
import org.apache.cassandra.dht.AbstractBounds;
import org.apache.cassandra.dht.Range;
import org.apache.cassandra.dht.Token;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.index.SecondaryIndexBuilder;
import org.apache.cassandra.io.sstable.Component;
import org.apache.cassandra.io.sstable.Descriptor;
import org.apache.cassandra.io.sstable.ISSTableScanner;
import org.apache.cassandra.io.sstable.IndexSummaryRedistribution;
import org.apache.cassandra.io.sstable.IScrubber;
import org.apache.cassandra.io.sstable.IVerifier;
import org.apache.cassandra.io.sstable.SSTableRewriter;
import org.apache.cassandra.io.sstable.format.SSTableFormat;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.io.sstable.format.SSTableWriter;
import org.apache.cassandra.io.sstable.metadata.MetadataCollector;
import org.apache.cassandra.io.sstable.metadata.StatsMetadata;
import org.apache.cassandra.io.util.File;
import org.apache.cassandra.io.util.FileUtils;
import org.apache.cassandra.locator.RangesAtEndpoint;
import org.apache.cassandra.metrics.CompactionMetrics;
import org.apache.cassandra.metrics.TableMetrics;
import org.apache.cassandra.repair.NoSuchRepairSessionException;
import org.apache.cassandra.schema.CompactionParams.TombstoneOption;
import org.apache.cassandra.schema.Schema;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.ActiveRepairService;
import org.apache.cassandra.service.StorageService;
import org.apache.cassandra.streaming.PreviewKind;
import org.apache.cassandra.utils.*;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.JVMStabilityInspector;
import org.apache.cassandra.utils.MBeanWrapper;
import org.apache.cassandra.utils.OutputHandler;
import org.apache.cassandra.utils.Throwables;
import org.apache.cassandra.utils.TimeUUID;
import org.apache.cassandra.utils.WrappedRunnable;
import org.apache.cassandra.utils.concurrent.Future;
import org.apache.cassandra.utils.concurrent.ImmediateFuture;
import org.apache.cassandra.utils.concurrent.Refs;
@ -455,16 +485,7 @@ public class CompactionManager implements CompactionManagerMBean
}
}
public AllSSTableOpStatus performScrub(final ColumnFamilyStore cfs, final boolean skipCorrupted, final boolean checkData,
int jobs)
throws InterruptedException, ExecutionException
{
return performScrub(cfs, skipCorrupted, checkData, false, jobs);
}
public AllSSTableOpStatus performScrub(final ColumnFamilyStore cfs, final boolean skipCorrupted, final boolean checkData,
final boolean reinsertOverflowedTTL, int jobs)
throws InterruptedException, ExecutionException
public AllSSTableOpStatus performScrub(ColumnFamilyStore cfs, IScrubber.Options options, int jobs)
{
return parallelAllSSTableOperation(cfs, new OneSSTableOperation()
{
@ -477,12 +498,12 @@ public class CompactionManager implements CompactionManagerMBean
@Override
public void execute(LifecycleTransaction input)
{
scrubOne(cfs, input, skipCorrupted, checkData, reinsertOverflowedTTL, active);
scrubOne(cfs, input, options, active);
}
}, jobs, OperationType.SCRUB);
}
public AllSSTableOpStatus performVerify(ColumnFamilyStore cfs, Verifier.Options options) throws InterruptedException, ExecutionException
public AllSSTableOpStatus performVerify(ColumnFamilyStore cfs, IVerifier.Options options) throws InterruptedException, ExecutionException
{
assert !cfs.isIndex();
return parallelAllSSTableOperation(cfs, new OneSSTableOperation()
@ -513,7 +534,7 @@ public class CompactionManager implements CompactionManagerMBean
return false;
// Skip if SSTable creation time is past given timestamp
if (sstable.getCreationTimeFor(Component.DATA) > skipIfOlderThanTimestamp)
if (sstable.getDataCreationTime() > skipIfOlderThanTimestamp)
return false;
TableMetadata metadata = cfs.metadata.get();
@ -598,8 +619,8 @@ public class CompactionManager implements CompactionManagerMBean
{
logger.debug("Skipping {} ([{}, {}]) for cleanup; all rows should be kept. Needs cleanup full ranges: {} Needs cleanup transient ranges: {} Repaired: {}",
sstable,
sstable.first.getToken(),
sstable.last.getToken(),
sstable.getFirst().getToken(),
sstable.getLast().getToken(),
needsCleanupFull,
needsCleanupTransient,
sstable.isRepaired());
@ -862,7 +883,7 @@ public class CompactionManager implements CompactionManagerMBean
List<Range<Token>> normalizedRanges = Range.normalize(ranges.ranges());
for (SSTableReader sstable : sstables)
{
Bounds<Token> bounds = new Bounds<>(sstable.first.getToken(), sstable.last.getToken());
AbstractBounds<Token> bounds = sstable.getBounds();
if (!Iterables.any(normalizedRanges, r -> (r.contains(bounds.left) && r.contains(bounds.right)) || r.intersects(bounds)))
{
@ -884,12 +905,12 @@ public class CompactionManager implements CompactionManagerMBean
{
SSTableReader sstable = sstableIterator.next();
Bounds<Token> sstableBounds = new Bounds<>(sstable.first.getToken(), sstable.last.getToken());
AbstractBounds<Token> sstableBounds = sstable.getBounds();
for (Range<Token> r : normalizedRanges)
{
// ranges are normalized - no wrap around - if first and last are contained we know that all tokens are contained in the range
if (r.contains(sstable.first.getToken()) && r.contains(sstable.last.getToken()))
if (r.contains(sstable.getFirst().getToken()) && r.contains(sstable.getLast().getToken()))
{
logger.info("{} SSTable {} fully contained in range {}, mutating repairedAt instead of anticompacting", PreviewKind.NONE.logPrefix(parentRepairSession), sstable, r);
fullyContainedSSTables.add(sstable);
@ -1005,7 +1026,7 @@ public class CompactionManager implements CompactionManagerMBean
{
forceCompaction(cfStore,
() -> sstablesInBounds(cfStore, ranges),
sstable -> new Bounds<>(sstable.first.getToken(), sstable.last.getToken()).intersects(ranges));
sstable -> sstable.getBounds().intersects(ranges));
}
/**
@ -1043,24 +1064,12 @@ public class CompactionManager implements CompactionManagerMBean
public void forceCompactionForKey(ColumnFamilyStore cfStore, DecoratedKey key)
{
forceCompaction(cfStore, () -> sstablesWithKey(cfStore, key), sstable -> sstable.maybePresent(key));
forceCompaction(cfStore, () -> sstablesWithKey(cfStore, key), Predicates.alwaysTrue());
}
public void forceCompactionForKeys(ColumnFamilyStore cfStore, Collection<DecoratedKey> keys)
{
com.google.common.base.Predicate<SSTableReader> predicate = sstable -> {
for (DecoratedKey key : keys)
{
if(sstable.maybePresent(key))
{
return true;
}
}
return false;
};
forceCompaction(cfStore, () -> sstablesWithKeys(cfStore, keys), predicate);
forceCompaction(cfStore, () -> sstablesWithKeys(cfStore, keys), Predicates.alwaysTrue());
}
private static Collection<SSTableReader> sstablesWithKey(ColumnFamilyStore cfs, DecoratedKey key)
@ -1070,7 +1079,7 @@ public class CompactionManager implements CompactionManagerMBean
key.getToken().maxKeyBound());
for (SSTableReader sstable : liveTables)
{
if (sstable.maybePresent(key))
if (sstable.mayContainAssumingKeyIsInRange(key))
sstables.add(sstable);
}
return sstables.isEmpty() ? Collections.emptyList() : sstables;
@ -1096,7 +1105,7 @@ public class CompactionManager implements CompactionManagerMBean
for (String filename : filenames)
{
// extract keyspace and columnfamily name from filename
Descriptor desc = Descriptor.fromFilename(filename.trim());
Descriptor desc = Descriptor.fromFileWithComponent(new File(filename.trim()), false).left;
if (Schema.instance.getTableMetadataRef(desc) == null)
{
logger.warn("Schema does not exist for file {}. Skipping.", filename);
@ -1122,7 +1131,7 @@ public class CompactionManager implements CompactionManagerMBean
for (String filename : filenames)
{
// extract keyspace and columnfamily name from filename
Descriptor desc = Descriptor.fromFilename(filename.trim());
Descriptor desc = Descriptor.fromFileWithComponent(new File(filename.trim()), false).left;
if (Schema.instance.getTableMetadataRef(desc) == null)
{
logger.warn("Schema does not exist for file {}. Skipping.", filename);
@ -1243,11 +1252,11 @@ public class CompactionManager implements CompactionManagerMBean
}
@VisibleForTesting
void scrubOne(ColumnFamilyStore cfs, LifecycleTransaction modifier, boolean skipCorrupted, boolean checkData, boolean reinsertOverflowedTTL, ActiveCompactionsTracker activeCompactions)
void scrubOne(ColumnFamilyStore cfs, LifecycleTransaction modifier, IScrubber.Options options, ActiveCompactionsTracker activeCompactions)
{
CompactionInfo.Holder scrubInfo = null;
try (Scrubber scrubber = new Scrubber(cfs, modifier, skipCorrupted, checkData, reinsertOverflowedTTL))
SSTableFormat format = modifier.onlyOne().descriptor.getFormat();
try (IScrubber scrubber = format.getScrubber(cfs, modifier, new OutputHandler.LogOutput(), options))
{
scrubInfo = scrubber.getScrubInfo();
activeCompactions.beginCompaction(scrubInfo);
@ -1261,11 +1270,10 @@ public class CompactionManager implements CompactionManagerMBean
}
@VisibleForTesting
void verifyOne(ColumnFamilyStore cfs, SSTableReader sstable, Verifier.Options options, ActiveCompactionsTracker activeCompactions)
void verifyOne(ColumnFamilyStore cfs, SSTableReader sstable, IVerifier.Options options, ActiveCompactionsTracker activeCompactions)
{
CompactionInfo.Holder verifyInfo = null;
try (Verifier verifier = new Verifier(cfs, sstable, false, options))
try (IVerifier verifier = sstable.getVerifier(cfs, new OutputHandler.LogOutput(), false, options))
{
verifyInfo = verifier.getVerifyInfo();
activeCompactions.beginCompaction(verifyInfo);
@ -1296,7 +1304,7 @@ public class CompactionManager implements CompactionManagerMBean
// see if there are any keys LTE the token for the start of the first range
// (token range ownership is exclusive on the LHS.)
Range<Token> firstRange = sortedRanges.get(0);
if (sstable.first.getToken().compareTo(firstRange.left) <= 0)
if (sstable.getFirst().getToken().compareTo(firstRange.left) <= 0)
return true;
// then, iterate over all owned ranges and see if the next key beyond the end of the owned
@ -1352,11 +1360,11 @@ public class CompactionManager implements CompactionManagerMBean
SSTableReader sstable = txn.onlyOne();
// if ranges is empty and no index, entire sstable is discarded
if (!hasIndexes && !new Bounds<>(sstable.first.getToken(), sstable.last.getToken()).intersects(allRanges))
if (!hasIndexes && !sstable.getBounds().intersects(allRanges))
{
txn.obsoleteOriginals();
txn.finish();
logger.info("SSTable {} ([{}, {}]) does not intersect the owned ranges ({}), dropping it", sstable, sstable.first.getToken(), sstable.last.getToken(), allRanges);
logger.info("SSTable {} ([{}, {}]) does not intersect the owned ranges ({}), dropping it", sstable, sstable.getFirst().getToken(), sstable.getLast().getToken(), allRanges);
return;
}
@ -1556,16 +1564,18 @@ public class CompactionManager implements CompactionManagerMBean
{
FileUtils.createDirectory(compactionFileLocation);
return SSTableWriter.create(cfs.metadata,
cfs.newSSTableDescriptor(compactionFileLocation),
expectedBloomFilterSize,
repairedAt,
pendingRepair,
isTransient,
sstable.getSSTableLevel(),
sstable.header,
cfs.indexManager.listIndexes(),
txn);
Descriptor descriptor = cfs.newSSTableDescriptor(compactionFileLocation);
return descriptor.getFormat().getWriterFactory().builder(descriptor)
.setKeyCount(expectedBloomFilterSize)
.setRepairedAt(repairedAt)
.setPendingRepair(pendingRepair)
.setTransientSSTable(isTransient)
.setTableMetadataRef(cfs.metadata)
.setMetadataCollector(new MetadataCollector(cfs.metadata().comparator).sstableLevel(sstable.getSSTableLevel()))
.setSerializationHeader(sstable.header)
.addDefaultComponents()
.addFlushObserversForSecondaryIndexes(cfs.indexManager.listIndexes(), txn.opType())
.build(txn, cfs);
}
public static SSTableWriter createWriterForAntiCompaction(ColumnFamilyStore cfs,
@ -1593,16 +1603,19 @@ public class CompactionManager implements CompactionManagerMBean
break;
}
}
return SSTableWriter.create(cfs.newSSTableDescriptor(compactionFileLocation),
(long) expectedBloomFilterSize,
repairedAt,
pendingRepair,
isTransient,
cfs.metadata,
new MetadataCollector(sstables, cfs.metadata().comparator, minLevel),
SerializationHeader.make(cfs.metadata(), sstables),
cfs.indexManager.listIndexes(),
txn);
Descriptor descriptor = cfs.newSSTableDescriptor(compactionFileLocation);
return descriptor.getFormat().getWriterFactory().builder(descriptor)
.setKeyCount(expectedBloomFilterSize)
.setRepairedAt(repairedAt)
.setPendingRepair(pendingRepair)
.setTransientSSTable(isTransient)
.setTableMetadataRef(cfs.metadata)
.setMetadataCollector(new MetadataCollector(sstables, cfs.metadata().comparator, minLevel))
.setSerializationHeader(SerializationHeader.make(cfs.metadata(), sstables))
.addDefaultComponents()
.addFlushObserversForSecondaryIndexes(cfs.indexManager.listIndexes(), txn.opType())
.build(txn, cfs);
}
/**
@ -1894,22 +1907,16 @@ public class CompactionManager implements CompactionManagerMBean
return executor.submitIfRunning(runnable, "cache write");
}
public List<SSTableReader> runIndexSummaryRedistribution(IndexSummaryRedistribution redistribution) throws IOException
public <T, E extends Throwable> T runAsActiveCompaction(Holder activeCompactionInfo, ThrowingSupplier<T, E> callable) throws E
{
return runIndexSummaryRedistribution(redistribution, active);
}
@VisibleForTesting
List<SSTableReader> runIndexSummaryRedistribution(IndexSummaryRedistribution redistribution, ActiveCompactionsTracker activeCompactions) throws IOException
{
activeCompactions.beginCompaction(redistribution);
active.beginCompaction(activeCompactionInfo);
try
{
return redistribution.redistributeSummaries();
return callable.get();
}
finally
{
activeCompactions.finishCompaction(redistribution);
active.finishCompaction(activeCompactionInfo);
}
}

View File

@ -58,11 +58,11 @@ import org.apache.cassandra.db.lifecycle.SSTableSet;
import org.apache.cassandra.dht.Range;
import org.apache.cassandra.dht.Token;
import org.apache.cassandra.index.Index;
import org.apache.cassandra.io.sstable.Component;
import org.apache.cassandra.io.sstable.Descriptor;
import org.apache.cassandra.io.sstable.ISSTableScanner;
import org.apache.cassandra.io.sstable.SSTable;
import org.apache.cassandra.io.sstable.SSTableMultiWriter;
import org.apache.cassandra.io.sstable.format.SSTableFormat.Components;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.io.sstable.metadata.MetadataCollector;
import org.apache.cassandra.io.sstable.metadata.StatsMetadata;
@ -252,8 +252,8 @@ public class CompactionStrategyManager implements INotificationConsumer
.stream()
.filter(s -> !compacting.contains(s) && !s.descriptor.version.isLatestVersion())
.sorted((o1, o2) -> {
File f1 = new File(o1.descriptor.filenameFor(Component.DATA));
File f2 = new File(o2.descriptor.filenameFor(Component.DATA));
File f1 = o1.descriptor.fileFor(Components.DATA);
File f2 = o2.descriptor.fileFor(Components.DATA);
return Longs.compare(f1.lastModified(), f2.lastModified());
}).collect(Collectors.toList());
for (SSTableReader sstable : potentialUpgrade)

View File

@ -241,7 +241,7 @@ public class CompactionTask extends AbstractCompactionTask
StringBuilder newSSTableNames = new StringBuilder();
for (SSTableReader reader : newSStables)
newSSTableNames.append(reader.descriptor.baseFilename()).append(",");
newSSTableNames.append(reader.descriptor.baseFile()).append(",");
long totalSourceRows = 0;
for (int i = 0; i < mergedRowCounts.length; i++)
totalSourceRows += mergedRowCounts[i] * (i + 1);

View File

@ -17,7 +17,16 @@
*/
package org.apache.cassandra.db.compaction;
import java.util.*;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import com.google.common.annotations.VisibleForTesting;
@ -27,18 +36,16 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.google.common.primitives.Ints;
import org.apache.cassandra.db.PartitionPosition;
import org.apache.cassandra.io.sstable.Component;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.PartitionPosition;
import org.apache.cassandra.dht.Bounds;
import org.apache.cassandra.dht.Range;
import org.apache.cassandra.dht.Token;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.service.StorageService;
import org.apache.cassandra.utils.Pair;
@ -115,7 +122,7 @@ public class LeveledManifest
long maxModificationTime = Long.MIN_VALUE;
for (SSTableReader ssTableReader : level)
{
long modificationTime = ssTableReader.getCreationTimeFor(Component.DATA);
long modificationTime = ssTableReader.getDataCreationTime();
if (modificationTime >= maxModificationTime)
{
sstableWithMaxModificationTime = ssTableReader;

View File

@ -1,864 +0,0 @@
/*
* 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.db.compaction;
import java.io.IOError;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableSet;
import org.apache.cassandra.io.util.File;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.lifecycle.LifecycleTransaction;
import org.apache.cassandra.db.rows.*;
import org.apache.cassandra.db.partitions.*;
import org.apache.cassandra.io.sstable.*;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.io.sstable.format.SSTableWriter;
import org.apache.cassandra.io.sstable.metadata.StatsMetadata;
import org.apache.cassandra.io.util.FileUtils;
import org.apache.cassandra.io.util.RandomAccessReader;
import org.apache.cassandra.service.ActiveRepairService;
import org.apache.cassandra.utils.*;
import org.apache.cassandra.utils.concurrent.Refs;
import org.apache.cassandra.utils.memory.HeapCloner;
import static org.apache.cassandra.utils.TimeUUID.Generator.nextTimeUUID;
public class Scrubber implements Closeable
{
private final ColumnFamilyStore cfs;
private final SSTableReader sstable;
private final LifecycleTransaction transaction;
private final File destination;
private final boolean skipCorrupted;
private final boolean reinsertOverflowedTTLRows;
private final boolean isCommutative;
private final boolean isIndex;
private final boolean checkData;
private final long expectedBloomFilterSize;
private final ReadWriteLock fileAccessLock;
private final RandomAccessReader dataFile;
private final RandomAccessReader indexFile;
private final ScrubInfo scrubInfo;
private final RowIndexEntry.IndexSerializer rowIndexEntrySerializer;
private int goodPartitions;
private int badPartitions;
private int emptyPartitions;
private ByteBuffer currentIndexKey;
private ByteBuffer nextIndexKey;
private long currentPartitionPositionFromIndex;
private long nextPartitionPositionFromIndex;
private NegativeLocalDeletionInfoMetrics negativeLocalDeletionInfoMetrics = new NegativeLocalDeletionInfoMetrics();
private final OutputHandler outputHandler;
private static final Comparator<Partition> partitionComparator = Comparator.comparing(Partition::partitionKey);
private final SortedSet<Partition> outOfOrder = new TreeSet<>(partitionComparator);
public Scrubber(ColumnFamilyStore cfs, LifecycleTransaction transaction, boolean skipCorrupted, boolean checkData)
{
this(cfs, transaction, skipCorrupted, checkData, false);
}
public Scrubber(ColumnFamilyStore cfs, LifecycleTransaction transaction, boolean skipCorrupted, boolean checkData,
boolean reinsertOverflowedTTLRows)
{
this(cfs, transaction, skipCorrupted, new OutputHandler.LogOutput(), checkData, reinsertOverflowedTTLRows);
}
@SuppressWarnings("resource")
public Scrubber(ColumnFamilyStore cfs,
LifecycleTransaction transaction,
boolean skipCorrupted,
OutputHandler outputHandler,
boolean checkData,
boolean reinsertOverflowedTTLRows)
{
this.cfs = cfs;
this.transaction = transaction;
this.sstable = transaction.onlyOne();
this.outputHandler = outputHandler;
this.skipCorrupted = skipCorrupted;
this.reinsertOverflowedTTLRows = reinsertOverflowedTTLRows;
this.rowIndexEntrySerializer = sstable.descriptor.version.getSSTableFormat().getIndexSerializer(cfs.metadata(),
sstable.descriptor.version,
sstable.header);
List<SSTableReader> toScrub = Collections.singletonList(sstable);
this.destination = cfs.getDirectories().getLocationForDisk(cfs.getDiskBoundaries().getCorrectDiskForSSTable(sstable));
this.isCommutative = cfs.metadata().isCounter();
boolean hasIndexFile = (new File(sstable.descriptor.filenameFor(Component.PRIMARY_INDEX))).exists();
this.isIndex = cfs.isIndex();
if (!hasIndexFile)
{
// if there's any corruption in the -Data.db then partitions can't be skipped over. but it's worth a shot.
outputHandler.warn("Missing component: " + sstable.descriptor.filenameFor(Component.PRIMARY_INDEX));
}
this.checkData = checkData && !this.isIndex; //LocalByPartitionerType does not support validation
this.expectedBloomFilterSize = Math.max(
cfs.metadata().params.minIndexInterval,
hasIndexFile ? SSTableReader.getApproximateKeyCount(toScrub) : 0);
this.fileAccessLock = new ReentrantReadWriteLock();
// loop through each partition, deserializing to check for damage.
// We'll also loop through the index at the same time, using the position from the index to recover if the
// partition header (key or data size) is corrupt. (This means our position in the index file will be one
// partition "ahead" of the data file.)
this.dataFile = transaction.isOffline()
? sstable.openDataReader()
: sstable.openDataReader(CompactionManager.instance.getRateLimiter());
this.indexFile = hasIndexFile
? RandomAccessReader.open(new File(sstable.descriptor.filenameFor(Component.PRIMARY_INDEX)))
: null;
this.scrubInfo = new ScrubInfo(dataFile, sstable, fileAccessLock.readLock());
this.currentPartitionPositionFromIndex = 0;
this.nextPartitionPositionFromIndex = 0;
if (reinsertOverflowedTTLRows)
outputHandler.output("Starting scrub with reinsert overflowed TTL option");
}
private UnfilteredRowIterator withValidation(UnfilteredRowIterator iter, String filename)
{
return checkData ? UnfilteredRowIterators.withValidation(iter, filename) : iter;
}
private String keyString(DecoratedKey key)
{
if (key == null)
return "(unknown)";
try
{
return cfs.metadata().partitionKeyType.getString(key.getKey());
}
catch (Exception e)
{
return String.format("(corrupted; hex value: %s)", ByteBufferUtil.bytesToHex(key.getKey()));
}
}
public void scrub()
{
List<SSTableReader> finished = new ArrayList<>();
outputHandler.output(String.format("Scrubbing %s (%s)", sstable, FBUtilities.prettyPrintMemory(dataFile.length())));
try (SSTableRewriter writer = SSTableRewriter.construct(cfs, transaction, false, sstable.maxDataAge);
Refs<SSTableReader> refs = Refs.ref(Collections.singleton(sstable)))
{
nextIndexKey = indexAvailable() ? ByteBufferUtil.readWithShortLength(indexFile) : null;
if (indexAvailable())
{
// throw away variable so we don't have a side effect in the assert
long firstRowPositionFromIndex = rowIndexEntrySerializer.deserializePositionAndSkip(indexFile);
assert firstRowPositionFromIndex == 0 : firstRowPositionFromIndex;
}
StatsMetadata metadata = sstable.getSSTableMetadata();
writer.switchWriter(CompactionManager.createWriter(cfs, destination, expectedBloomFilterSize, metadata.repairedAt, metadata.pendingRepair, metadata.isTransient, sstable, transaction));
DecoratedKey prevKey = null;
while (!dataFile.isEOF())
{
if (scrubInfo.isStopRequested())
throw new CompactionInterruptedException(scrubInfo.getCompactionInfo());
long partitionStart = dataFile.getFilePointer();
outputHandler.debug("Reading row at " + partitionStart);
DecoratedKey key = null;
try
{
ByteBuffer raw = ByteBufferUtil.readWithShortLength(dataFile);
if (!cfs.metadata.getLocal().isIndex())
cfs.metadata.getLocal().partitionKeyType.validate(raw);
key = sstable.decorateKey(raw);
}
catch (Throwable th)
{
throwIfFatal(th);
// check for null key below
}
updateIndexKey();
long dataStart = dataFile.getFilePointer();
long dataStartFromIndex = -1;
long dataSizeFromIndex = -1;
if (currentIndexKey != null)
{
dataStartFromIndex = currentPartitionPositionFromIndex + 2 + currentIndexKey.remaining();
dataSizeFromIndex = nextPartitionPositionFromIndex - dataStartFromIndex;
}
String keyName = key == null ? "(unreadable key)" : keyString(key);
outputHandler.debug(String.format("partition %s is %s", keyName, FBUtilities.prettyPrintMemory(dataSizeFromIndex)));
assert currentIndexKey != null || !indexAvailable();
try
{
if (key == null)
throw new IOError(new IOException("Unable to read partition key from data file"));
if (currentIndexKey != null && !key.getKey().equals(currentIndexKey))
{
throw new IOError(new IOException(String.format("Key from data file (%s) does not match key from index file (%s)",
//ByteBufferUtil.bytesToHex(key.getKey()), ByteBufferUtil.bytesToHex(currentIndexKey))));
"_too big_", ByteBufferUtil.bytesToHex(currentIndexKey))));
}
if (indexFile != null && dataSizeFromIndex > dataFile.length())
throw new IOError(new IOException("Impossible partition size (greater than file length): " + dataSizeFromIndex));
if (indexFile != null && dataStart != dataStartFromIndex)
outputHandler.warn(String.format("Data file partition position %d differs from index file row position %d", dataStart, dataStartFromIndex));
if (tryAppend(prevKey, key, writer))
prevKey = key;
}
catch (Throwable th)
{
throwIfFatal(th);
outputHandler.warn(String.format("Error reading partition %s (stacktrace follows):", keyName), th);
if (currentIndexKey != null
&& (key == null || !key.getKey().equals(currentIndexKey) || dataStart != dataStartFromIndex))
{
outputHandler.output(String.format("Retrying from partition index; data is %s bytes starting at %s",
dataSizeFromIndex, dataStartFromIndex));
key = sstable.decorateKey(currentIndexKey);
try
{
if (!cfs.metadata.getLocal().isIndex())
cfs.metadata.getLocal().partitionKeyType.validate(key.getKey());
dataFile.seek(dataStartFromIndex);
if (tryAppend(prevKey, key, writer))
prevKey = key;
}
catch (Throwable th2)
{
throwIfFatal(th2);
throwIfCannotContinue(key, th2);
outputHandler.warn("Retry failed too. Skipping to next partition (retry's stacktrace follows)", th2);
badPartitions++;
if (!seekToNextPartition())
break;
}
}
else
{
throwIfCannotContinue(key, th);
outputHandler.warn("Partition starting at position " + dataStart + " is unreadable; skipping to next");
badPartitions++;
if (currentIndexKey != null)
if (!seekToNextPartition())
break;
}
}
}
if (!outOfOrder.isEmpty())
{
// out of order partitions/rows, but no bad partition found - we can keep our repairedAt time
long repairedAt = badPartitions > 0 ? ActiveRepairService.UNREPAIRED_SSTABLE : sstable.getSSTableMetadata().repairedAt;
SSTableReader newInOrderSstable;
try (SSTableWriter inOrderWriter = CompactionManager.createWriter(cfs, destination, expectedBloomFilterSize, repairedAt, metadata.pendingRepair, metadata.isTransient, sstable, transaction))
{
for (Partition partition : outOfOrder)
inOrderWriter.append(partition.unfilteredIterator());
newInOrderSstable = inOrderWriter.finish(-1, sstable.maxDataAge, true);
}
transaction.update(newInOrderSstable, false);
finished.add(newInOrderSstable);
outputHandler.warn(String.format("%d out of order partition (or partitions with out of order rows) found while scrubbing %s; " +
"Those have been written (in order) to a new sstable (%s)", outOfOrder.size(), sstable, newInOrderSstable));
}
// finish obsoletes the old sstable
finished.addAll(writer.setRepairedAt(badPartitions > 0 ? ActiveRepairService.UNREPAIRED_SSTABLE : sstable.getSSTableMetadata().repairedAt).finish());
}
catch (IOException e)
{
throw new RuntimeException(e);
}
finally
{
if (transaction.isOffline())
finished.forEach(sstable -> sstable.selfRef().release());
}
if (!finished.isEmpty())
{
outputHandler.output("Scrub of " + sstable + " complete: " + goodPartitions + " partitions in new sstable and " + emptyPartitions + " empty (tombstoned) partitions dropped");
if (negativeLocalDeletionInfoMetrics.fixedRows > 0)
outputHandler.output("Fixed " + negativeLocalDeletionInfoMetrics.fixedRows + " rows with overflowed local deletion time.");
if (badPartitions > 0)
outputHandler.warn("Unable to recover " + badPartitions + " partitions that were skipped. You can attempt manual recovery from the pre-scrub snapshot. You can also run nodetool repair to transfer the data from a healthy replica, if any");
}
else
{
if (badPartitions > 0)
outputHandler.warn("No valid partitions found while scrubbing " + sstable + "; it is marked for deletion now. If you want to attempt manual recovery, you can find a copy in the pre-scrub snapshot");
else
outputHandler.output("Scrub of " + sstable + " complete; looks like all " + emptyPartitions + " partitions were tombstoned");
}
}
@SuppressWarnings("resource")
private boolean tryAppend(DecoratedKey prevKey, DecoratedKey key, SSTableRewriter writer)
{
// OrderCheckerIterator will check, at iteration time, that the rows are in the proper order. If it detects
// that one row is out of order, it will stop returning them. The remaining rows will be sorted and added
// to the outOfOrder set that will be later written to a new SSTable.
OrderCheckerIterator sstableIterator = new OrderCheckerIterator(getIterator(key),
cfs.metadata().comparator);
try (UnfilteredRowIterator iterator = withValidation(sstableIterator, dataFile.getPath()))
{
if (prevKey != null && prevKey.compareTo(key) > 0)
{
saveOutOfOrderPartition(prevKey, key, iterator);
return false;
}
if (writer.tryAppend(iterator) == null)
emptyPartitions++;
else
goodPartitions++;
}
if (sstableIterator.hasRowsOutOfOrder())
{
outputHandler.warn(String.format("Out of order rows found in partition: %s", keyString(key)));
outOfOrder.add(sstableIterator.getRowsOutOfOrder());
}
return true;
}
/**
* Only wrap with {@link FixNegativeLocalDeletionTimeIterator} if {@link #reinsertOverflowedTTLRows} option
* is specified
*/
@SuppressWarnings("resource")
private UnfilteredRowIterator getIterator(DecoratedKey key)
{
RowMergingSSTableIterator rowMergingIterator = new RowMergingSSTableIterator(SSTableIdentityIterator.create(sstable, dataFile, key), outputHandler);
return reinsertOverflowedTTLRows ? new FixNegativeLocalDeletionTimeIterator(rowMergingIterator,
outputHandler,
negativeLocalDeletionInfoMetrics) : rowMergingIterator;
}
private void updateIndexKey()
{
currentIndexKey = nextIndexKey;
currentPartitionPositionFromIndex = nextPartitionPositionFromIndex;
try
{
nextIndexKey = !indexAvailable() ? null : ByteBufferUtil.readWithShortLength(indexFile);
nextPartitionPositionFromIndex = !indexAvailable()
? dataFile.length()
: rowIndexEntrySerializer.deserializePositionAndSkip(indexFile);
}
catch (Throwable th)
{
JVMStabilityInspector.inspectThrowable(th);
outputHandler.warn("Error reading index file", th);
nextIndexKey = null;
nextPartitionPositionFromIndex = dataFile.length();
}
}
private boolean indexAvailable()
{
return indexFile != null && !indexFile.isEOF();
}
private boolean seekToNextPartition()
{
while(nextPartitionPositionFromIndex < dataFile.length())
{
try
{
dataFile.seek(nextPartitionPositionFromIndex);
return true;
}
catch (Throwable th)
{
throwIfFatal(th);
outputHandler.warn(String.format("Failed to seek to next partition position %d", nextPartitionPositionFromIndex), th);
badPartitions++;
}
updateIndexKey();
}
return false;
}
private void saveOutOfOrderPartition(DecoratedKey prevKey, DecoratedKey key, UnfilteredRowIterator iterator)
{
// TODO bitch if the row is too large? if it is there's not much we can do ...
outputHandler.warn(String.format("Out of order partition detected (%s found after %s)",
keyString(key), keyString(prevKey)));
outOfOrder.add(ImmutableBTreePartition.create(iterator));
}
private void throwIfFatal(Throwable th)
{
if (th instanceof Error && !(th instanceof AssertionError || th instanceof IOError))
throw (Error) th;
}
private void throwIfCannotContinue(DecoratedKey key, Throwable th)
{
if (isIndex)
{
outputHandler.warn(String.format("An error occurred while scrubbing the partition with key '%s' for an index table. " +
"Scrubbing will abort for this table and the index will be rebuilt.", keyString(key)));
throw new IOError(th);
}
if (isCommutative && !skipCorrupted)
{
outputHandler.warn(String.format("An error occurred while scrubbing the partition with key '%s'. Skipping corrupt " +
"data in counter tables will result in undercounts for the affected " +
"counters (see CASSANDRA-2759 for more details), so by default the scrub will " +
"stop at this point. If you would like to skip the row anyway and continue " +
"scrubbing, re-run the scrub with the --skip-corrupted option.",
keyString(key)));
throw new IOError(th);
}
}
public void close()
{
fileAccessLock.writeLock().lock();
try
{
FileUtils.closeQuietly(dataFile);
FileUtils.closeQuietly(indexFile);
}
finally
{
fileAccessLock.writeLock().unlock();
}
}
public CompactionInfo.Holder getScrubInfo()
{
return scrubInfo;
}
private static class ScrubInfo extends CompactionInfo.Holder
{
private final RandomAccessReader dataFile;
private final SSTableReader sstable;
private final TimeUUID scrubCompactionId;
private final Lock fileReadLock;
public ScrubInfo(RandomAccessReader dataFile, SSTableReader sstable, Lock fileReadLock)
{
this.dataFile = dataFile;
this.sstable = sstable;
this.fileReadLock = fileReadLock;
scrubCompactionId = nextTimeUUID();
}
public CompactionInfo getCompactionInfo()
{
fileReadLock.lock();
try
{
return new CompactionInfo(sstable.metadata(),
OperationType.SCRUB,
dataFile.getFilePointer(),
dataFile.length(),
scrubCompactionId,
ImmutableSet.of(sstable),
Paths.get(sstable.getFilename()).getParent().toString());
}
catch (Exception e)
{
throw new RuntimeException(e);
}
finally
{
fileReadLock.unlock();
}
}
public boolean isGlobal()
{
return false;
}
}
@VisibleForTesting
public ScrubResult scrubWithResult()
{
scrub();
return new ScrubResult(this);
}
public static final class ScrubResult
{
public final int goodPartitions;
public final int badPartitions;
public final int emptyPartitions;
public ScrubResult(Scrubber scrubber)
{
this.goodPartitions = scrubber.goodPartitions;
this.badPartitions = scrubber.badPartitions;
this.emptyPartitions = scrubber.emptyPartitions;
}
}
public class NegativeLocalDeletionInfoMetrics
{
public volatile int fixedRows = 0;
}
/**
* During 2.x migration, under some circumstances rows might have gotten duplicated.
* Merging iterator merges rows with same clustering.
*
* For more details, refer to CASSANDRA-12144.
*/
private static class RowMergingSSTableIterator extends WrappingUnfilteredRowIterator
{
Unfiltered nextToOffer = null;
private final OutputHandler output;
RowMergingSSTableIterator(UnfilteredRowIterator source, OutputHandler output)
{
super(source);
this.output = output;
}
@Override
public boolean hasNext()
{
return nextToOffer != null || wrapped.hasNext();
}
@Override
public Unfiltered next()
{
Unfiltered next = nextToOffer != null ? nextToOffer : wrapped.next();
if (next.isRow())
{
boolean logged = false;
while (wrapped.hasNext())
{
Unfiltered peek = wrapped.next();
if (!peek.isRow() || !next.clustering().equals(peek.clustering()))
{
nextToOffer = peek; // Offer peek in next call
return next;
}
// Duplicate row, merge it.
next = Rows.merge((Row) next, (Row) peek);
if (!logged)
{
String partitionKey = metadata().partitionKeyType.getString(partitionKey().getKey());
output.warn("Duplicate row detected in " + metadata().keyspace + '.' + metadata().name + ": " + partitionKey + " " + next.clustering().toString(metadata()));
logged = true;
}
}
}
nextToOffer = null;
return next;
}
}
/**
* In some case like CASSANDRA-12127 the cells might have been stored in the wrong order. This decorator check the
* cells order and collect the out of order cells to correct the problem.
*/
private static final class OrderCheckerIterator extends AbstractIterator<Unfiltered> implements UnfilteredRowIterator
{
/**
* The decorated iterator.
*/
private final UnfilteredRowIterator iterator;
private final ClusteringComparator comparator;
private Unfiltered previous;
/**
* The partition containing the rows which are out of order.
*/
private Partition rowsOutOfOrder;
public OrderCheckerIterator(UnfilteredRowIterator iterator, ClusteringComparator comparator)
{
this.iterator = iterator;
this.comparator = comparator;
}
public TableMetadata metadata()
{
return iterator.metadata();
}
public boolean isReverseOrder()
{
return iterator.isReverseOrder();
}
public RegularAndStaticColumns columns()
{
return iterator.columns();
}
public DecoratedKey partitionKey()
{
return iterator.partitionKey();
}
public Row staticRow()
{
return iterator.staticRow();
}
@Override
public boolean isEmpty()
{
return iterator.isEmpty();
}
public void close()
{
iterator.close();
}
public DeletionTime partitionLevelDeletion()
{
return iterator.partitionLevelDeletion();
}
public EncodingStats stats()
{
return iterator.stats();
}
public boolean hasRowsOutOfOrder()
{
return rowsOutOfOrder != null;
}
public Partition getRowsOutOfOrder()
{
return rowsOutOfOrder;
}
protected Unfiltered computeNext()
{
if (!iterator.hasNext())
return endOfData();
Unfiltered next = iterator.next();
// If we detect that some rows are out of order we will store and sort the remaining ones to insert them
// in a separate SSTable.
if (previous != null && comparator.compare(next, previous) < 0)
{
rowsOutOfOrder = ImmutableBTreePartition.create(UnfilteredRowIterators.concat(next, iterator), false);
return endOfData();
}
previous = next;
return next;
}
}
/**
* This iterator converts negative {@link AbstractCell#localDeletionTime()} into {@link AbstractCell#MAX_DELETION_TIME}
*
* This is to recover entries with overflowed localExpirationTime due to CASSANDRA-14092
*/
private static final class FixNegativeLocalDeletionTimeIterator extends AbstractIterator<Unfiltered> implements UnfilteredRowIterator
{
/**
* The decorated iterator.
*/
private final UnfilteredRowIterator iterator;
private final OutputHandler outputHandler;
private final NegativeLocalDeletionInfoMetrics negativeLocalExpirationTimeMetrics;
public FixNegativeLocalDeletionTimeIterator(UnfilteredRowIterator iterator, OutputHandler outputHandler,
NegativeLocalDeletionInfoMetrics negativeLocalDeletionInfoMetrics)
{
this.iterator = iterator;
this.outputHandler = outputHandler;
this.negativeLocalExpirationTimeMetrics = negativeLocalDeletionInfoMetrics;
}
public TableMetadata metadata()
{
return iterator.metadata();
}
public boolean isReverseOrder()
{
return iterator.isReverseOrder();
}
public RegularAndStaticColumns columns()
{
return iterator.columns();
}
public DecoratedKey partitionKey()
{
return iterator.partitionKey();
}
public Row staticRow()
{
return iterator.staticRow();
}
@Override
public boolean isEmpty()
{
return iterator.isEmpty();
}
public void close()
{
iterator.close();
}
public DeletionTime partitionLevelDeletion()
{
return iterator.partitionLevelDeletion();
}
public EncodingStats stats()
{
return iterator.stats();
}
protected Unfiltered computeNext()
{
if (!iterator.hasNext())
return endOfData();
Unfiltered next = iterator.next();
if (!next.isRow())
return next;
if (hasNegativeLocalExpirationTime((Row) next))
{
outputHandler.debug(String.format("Found row with negative local expiration time: %s", next.toString(metadata(), false)));
negativeLocalExpirationTimeMetrics.fixedRows++;
return fixNegativeLocalExpirationTime((Row) next);
}
return next;
}
private boolean hasNegativeLocalExpirationTime(Row next)
{
Row row = next;
if (row.primaryKeyLivenessInfo().isExpiring() && row.primaryKeyLivenessInfo().localExpirationTime() < 0)
{
return true;
}
for (ColumnData cd : row)
{
if (cd.column().isSimple())
{
Cell<?> cell = (Cell<?>)cd;
if (cell.isExpiring() && cell.localDeletionTime() < 0)
return true;
}
else
{
ComplexColumnData complexData = (ComplexColumnData)cd;
for (Cell<?> cell : complexData)
{
if (cell.isExpiring() && cell.localDeletionTime() < 0)
return true;
}
}
}
return false;
}
private Unfiltered fixNegativeLocalExpirationTime(Row row)
{
LivenessInfo livenessInfo = row.primaryKeyLivenessInfo();
if (livenessInfo.isExpiring() && livenessInfo.localExpirationTime() < 0)
livenessInfo = livenessInfo.withUpdatedTimestampAndLocalDeletionTime(livenessInfo.timestamp() + 1, AbstractCell.MAX_DELETION_TIME);
return row.transformAndFilter(livenessInfo, row.deletion(), cd -> {
if (cd.column().isSimple())
{
Cell cell = (Cell)cd;
return cell.isExpiring() && cell.localDeletionTime() < 0
? cell.withUpdatedTimestampAndLocalDeletionTime(cell.timestamp() + 1, AbstractCell.MAX_DELETION_TIME)
: cell;
}
else
{
ComplexColumnData complexData = (ComplexColumnData)cd;
return complexData.transformAndFilter(cell -> cell.isExpiring() && cell.localDeletionTime() < 0
? cell.withUpdatedTimestampAndLocalDeletionTime(cell.timestamp() + 1, AbstractCell.MAX_DELETION_TIME)
: cell);
}
}).clone(HeapCloner.instance);
}
}
}

View File

@ -27,8 +27,8 @@ import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.Directories;
import org.apache.cassandra.db.compaction.writers.CompactionAwareWriter;
import org.apache.cassandra.db.lifecycle.LifecycleTransaction;
import org.apache.cassandra.io.sstable.Component;
import org.apache.cassandra.io.sstable.CorruptSSTableException;
import org.apache.cassandra.io.sstable.format.SSTableFormat.Components;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.io.sstable.metadata.StatsMetadata;
@ -82,7 +82,7 @@ public class SingleSSTableLCSTask extends AbstractCompactionTask
catch (Throwable t)
{
transaction.abort();
throw new CorruptSSTableException(t, sstable.descriptor.filenameFor(Component.DATA));
throw new CorruptSSTableException(t, sstable.descriptor.fileFor(Components.DATA));
}
cfs.getTracker().notifySSTableMetadataChanged(sstable, metadataBefore);
}

View File

@ -17,7 +17,7 @@
*/
package org.apache.cassandra.db.compaction;
import java.util.*;
import java.util.Arrays;
import java.util.function.LongPredicate;
import com.google.common.base.Throwables;
@ -25,9 +25,10 @@ import com.google.common.collect.Sets;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.lifecycle.LifecycleTransaction;
import org.apache.cassandra.db.SerializationHeader;
import org.apache.cassandra.io.sstable.*;
import org.apache.cassandra.db.lifecycle.LifecycleTransaction;
import org.apache.cassandra.io.sstable.Descriptor;
import org.apache.cassandra.io.sstable.SSTableRewriter;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.io.sstable.format.SSTableWriter;
import org.apache.cassandra.io.sstable.metadata.MetadataCollector;
@ -72,16 +73,19 @@ public class Upgrader
{
MetadataCollector sstableMetadataCollector = new MetadataCollector(cfs.getComparator());
sstableMetadataCollector.sstableLevel(sstable.getSSTableLevel());
return SSTableWriter.create(cfs.newSSTableDescriptor(directory),
estimatedRows,
metadata.repairedAt,
metadata.pendingRepair,
metadata.isTransient,
cfs.metadata,
sstableMetadataCollector,
SerializationHeader.make(cfs.metadata(), Sets.newHashSet(sstable)),
cfs.indexManager.listIndexes(),
transaction);
Descriptor descriptor = cfs.newSSTableDescriptor(directory);
return descriptor.getFormat().getWriterFactory().builder(descriptor)
.setKeyCount(estimatedRows)
.setRepairedAt(metadata.repairedAt)
.setPendingRepair(metadata.pendingRepair)
.setTransientSSTable(metadata.isTransient)
.setTableMetadataRef(cfs.metadata)
.setMetadataCollector(sstableMetadataCollector)
.setSerializationHeader(SerializationHeader.make(cfs.metadata(), Sets.newHashSet(sstable)))
.addDefaultComponents()
.addFlushObserversForSecondaryIndexes(cfs.indexManager.listIndexes(), transaction.opType())
.build(transaction, cfs);
}
public void upgrade(boolean keepOriginals)

View File

@ -1,726 +0,0 @@
/*
* 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.db.compaction;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableSet;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.rows.Cell;
import org.apache.cassandra.db.rows.Row;
import org.apache.cassandra.db.rows.Unfiltered;
import org.apache.cassandra.db.rows.UnfilteredRowIterator;
import org.apache.cassandra.dht.LocalPartitioner;
import org.apache.cassandra.dht.Range;
import org.apache.cassandra.dht.Token;
import org.apache.cassandra.io.sstable.Component;
import org.apache.cassandra.io.sstable.CorruptSSTableException;
import org.apache.cassandra.io.sstable.IndexSummary;
import org.apache.cassandra.io.sstable.KeyIterator;
import org.apache.cassandra.io.sstable.SSTableIdentityIterator;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.io.sstable.metadata.MetadataComponent;
import org.apache.cassandra.io.sstable.metadata.MetadataType;
import org.apache.cassandra.io.sstable.metadata.ValidationMetadata;
import org.apache.cassandra.io.util.DataIntegrityMetadata;
import org.apache.cassandra.io.util.DataIntegrityMetadata.FileDigestValidator;
import org.apache.cassandra.io.util.FileInputStreamPlus;
import org.apache.cassandra.io.util.FileUtils;
import org.apache.cassandra.io.util.RandomAccessReader;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.ActiveRepairService;
import org.apache.cassandra.service.StorageService;
import org.apache.cassandra.utils.BloomFilterSerializer;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.IFilter;
import org.apache.cassandra.utils.OutputHandler;
import org.apache.cassandra.utils.TimeUUID;
import java.io.Closeable;
import java.io.DataInputStream;
import java.io.IOError;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Function;
import java.util.function.LongPredicate;
import org.apache.cassandra.io.util.File;
import static org.apache.cassandra.utils.TimeUUID.Generator.nextTimeUUID;
public class Verifier implements Closeable
{
private final ColumnFamilyStore cfs;
private final SSTableReader sstable;
private final CompactionController controller;
private final ReadWriteLock fileAccessLock;
private final RandomAccessReader dataFile;
private final RandomAccessReader indexFile;
private final VerifyInfo verifyInfo;
private final RowIndexEntry.IndexSerializer rowIndexEntrySerializer;
private final Options options;
private final boolean isOffline;
/**
* Given a keyspace, return the set of local and pending token ranges. By default {@link StorageService#getLocalAndPendingRanges(String)}
* is expected, but for the standalone verifier case we can't use that, so this is here to allow the CLI to provide
* the token ranges.
*/
private final Function<String, ? extends Collection<Range<Token>>> tokenLookup;
private int goodRows;
private final OutputHandler outputHandler;
private FileDigestValidator validator;
public Verifier(ColumnFamilyStore cfs, SSTableReader sstable, boolean isOffline, Options options)
{
this(cfs, sstable, new OutputHandler.LogOutput(), isOffline, options);
}
public Verifier(ColumnFamilyStore cfs, SSTableReader sstable, OutputHandler outputHandler, boolean isOffline, Options options)
{
this.cfs = cfs;
this.sstable = sstable;
this.outputHandler = outputHandler;
this.rowIndexEntrySerializer = sstable.descriptor.version.getSSTableFormat().getIndexSerializer(cfs.metadata(), sstable.descriptor.version, sstable.header);
this.controller = new VerifyController(cfs);
this.fileAccessLock = new ReentrantReadWriteLock();
this.dataFile = isOffline
? sstable.openDataReader()
: sstable.openDataReader(CompactionManager.instance.getRateLimiter());
this.indexFile = RandomAccessReader.open(new File(sstable.descriptor.filenameFor(Component.PRIMARY_INDEX)));
this.verifyInfo = new VerifyInfo(dataFile, sstable, fileAccessLock.readLock());
this.options = options;
this.isOffline = isOffline;
this.tokenLookup = options.tokenLookup;
}
public void verify()
{
boolean extended = options.extendedVerification;
long rowStart = 0;
outputHandler.output(String.format("Verifying %s (%s)", sstable, FBUtilities.prettyPrintMemory(dataFile.length())));
if (options.checkVersion && !sstable.descriptor.version.isLatestVersion())
{
String msg = String.format("%s is not the latest version, run upgradesstables", sstable);
outputHandler.output(msg);
// don't use markAndThrow here because we don't want a CorruptSSTableException for this.
throw new RuntimeException(msg);
}
outputHandler.output(String.format("Deserializing sstable metadata for %s ", sstable));
try
{
EnumSet<MetadataType> types = EnumSet.of(MetadataType.VALIDATION, MetadataType.STATS, MetadataType.HEADER);
Map<MetadataType, MetadataComponent> sstableMetadata = sstable.descriptor.getMetadataSerializer().deserialize(sstable.descriptor, types);
if (sstableMetadata.containsKey(MetadataType.VALIDATION) &&
!((ValidationMetadata)sstableMetadata.get(MetadataType.VALIDATION)).partitioner.equals(sstable.getPartitioner().getClass().getCanonicalName()))
throw new IOException("Partitioner does not match validation metadata");
}
catch (Throwable t)
{
outputHandler.warn(t);
markAndThrow(t, false);
}
try
{
outputHandler.debug("Deserializing index for "+sstable);
deserializeIndex(sstable);
}
catch (Throwable t)
{
outputHandler.warn(t);
markAndThrow(t);
}
try
{
outputHandler.debug("Deserializing index summary for "+sstable);
deserializeIndexSummary(sstable);
}
catch (Throwable t)
{
outputHandler.output("Index summary is corrupt - if it is removed it will get rebuilt on startup "+sstable.descriptor.filenameFor(Component.SUMMARY));
outputHandler.warn(t);
markAndThrow(t, false);
}
try
{
outputHandler.debug("Deserializing bloom filter for "+sstable);
deserializeBloomFilter(sstable);
}
catch (Throwable t)
{
outputHandler.warn(t);
markAndThrow(t);
}
if (options.checkOwnsTokens && !isOffline && !(cfs.getPartitioner() instanceof LocalPartitioner))
{
outputHandler.debug("Checking that all tokens are owned by the current node");
try (KeyIterator iter = new KeyIterator(sstable.descriptor, sstable.metadata()))
{
List<Range<Token>> ownedRanges = Range.normalize(tokenLookup.apply(cfs.metadata.keyspace));
if (ownedRanges.isEmpty())
return;
RangeOwnHelper rangeOwnHelper = new RangeOwnHelper(ownedRanges);
while (iter.hasNext())
{
DecoratedKey key = iter.next();
rangeOwnHelper.validate(key);
}
}
catch (Throwable t)
{
outputHandler.warn(t);
markAndThrow(t);
}
}
if (options.quick)
return;
// Verify will use the Digest files, which works for both compressed and uncompressed sstables
outputHandler.output(String.format("Checking computed hash of %s ", sstable));
try
{
validator = null;
if (new File(sstable.descriptor.filenameFor(Component.DIGEST)).exists())
{
validator = DataIntegrityMetadata.fileDigestValidator(sstable.descriptor);
validator.validate();
}
else
{
outputHandler.output("Data digest missing, assuming extended verification of disk values");
extended = true;
}
}
catch (IOException e)
{
outputHandler.warn(e);
markAndThrow(e);
}
finally
{
FileUtils.closeQuietly(validator);
}
if (!extended)
return;
outputHandler.output("Extended Verify requested, proceeding to inspect values");
try
{
ByteBuffer nextIndexKey = ByteBufferUtil.readWithShortLength(indexFile);
{
long firstRowPositionFromIndex = rowIndexEntrySerializer.deserializePositionAndSkip(indexFile);
if (firstRowPositionFromIndex != 0)
markAndThrow(new RuntimeException("firstRowPositionFromIndex != 0: "+firstRowPositionFromIndex));
}
List<Range<Token>> ownedRanges = isOffline ? Collections.emptyList() : Range.normalize(tokenLookup.apply(cfs.metadata().keyspace));
RangeOwnHelper rangeOwnHelper = new RangeOwnHelper(ownedRanges);
DecoratedKey prevKey = null;
while (!dataFile.isEOF())
{
if (verifyInfo.isStopRequested())
throw new CompactionInterruptedException(verifyInfo.getCompactionInfo());
rowStart = dataFile.getFilePointer();
outputHandler.debug("Reading row at " + rowStart);
DecoratedKey key = null;
try
{
key = sstable.decorateKey(ByteBufferUtil.readWithShortLength(dataFile));
}
catch (Throwable th)
{
throwIfFatal(th);
// check for null key below
}
if (options.checkOwnsTokens && ownedRanges.size() > 0 && !(cfs.getPartitioner() instanceof LocalPartitioner))
{
try
{
rangeOwnHelper.validate(key);
}
catch (Throwable t)
{
outputHandler.warn(String.format("Key %s in sstable %s not owned by local ranges %s", key, sstable, ownedRanges), t);
markAndThrow(t);
}
}
ByteBuffer currentIndexKey = nextIndexKey;
long nextRowPositionFromIndex = 0;
try
{
nextIndexKey = indexFile.isEOF() ? null : ByteBufferUtil.readWithShortLength(indexFile);
nextRowPositionFromIndex = indexFile.isEOF()
? dataFile.length()
: rowIndexEntrySerializer.deserializePositionAndSkip(indexFile);
}
catch (Throwable th)
{
markAndThrow(th);
}
long dataStart = dataFile.getFilePointer();
long dataStartFromIndex = currentIndexKey == null
? -1
: rowStart + 2 + currentIndexKey.remaining();
long dataSize = nextRowPositionFromIndex - dataStartFromIndex;
// avoid an NPE if key is null
String keyName = key == null ? "(unreadable key)" : ByteBufferUtil.bytesToHex(key.getKey());
outputHandler.debug(String.format("row %s is %s", keyName, FBUtilities.prettyPrintMemory(dataSize)));
assert currentIndexKey != null || indexFile.isEOF();
try
{
if (key == null || dataSize > dataFile.length())
markAndThrow(new RuntimeException(String.format("key = %s, dataSize=%d, dataFile.length() = %d", key, dataSize, dataFile.length())));
try (UnfilteredRowIterator iterator = SSTableIdentityIterator.create(sstable, dataFile, key))
{
Row first = null;
int duplicateRows = 0;
long minTimestamp = Long.MAX_VALUE;
long maxTimestamp = Long.MIN_VALUE;
while (iterator.hasNext())
{
Unfiltered uf = iterator.next();
if (uf.isRow())
{
Row row = (Row) uf;
if (first != null && first.clustering().equals(row.clustering()))
{
duplicateRows++;
for (Cell cell : row.cells())
{
maxTimestamp = Math.max(cell.timestamp(), maxTimestamp);
minTimestamp = Math.min(cell.timestamp(), minTimestamp);
}
}
else
{
if (duplicateRows > 0)
logDuplicates(key, first, duplicateRows, minTimestamp, maxTimestamp);
duplicateRows = 0;
first = row;
maxTimestamp = Long.MIN_VALUE;
minTimestamp = Long.MAX_VALUE;
}
}
}
if (duplicateRows > 0)
logDuplicates(key, first, duplicateRows, minTimestamp, maxTimestamp);
}
if ( (prevKey != null && prevKey.compareTo(key) > 0) || !key.getKey().equals(currentIndexKey) || dataStart != dataStartFromIndex )
markAndThrow(new RuntimeException("Key out of order: previous = "+prevKey + " : current = " + key));
goodRows++;
prevKey = key;
outputHandler.debug(String.format("Row %s at %s valid, moving to next row at %s ", goodRows, rowStart, nextRowPositionFromIndex));
dataFile.seek(nextRowPositionFromIndex);
}
catch (Throwable th)
{
markAndThrow(th);
}
}
}
catch (Throwable t)
{
Throwables.throwIfUnchecked(t);
throw new RuntimeException(t);
}
finally
{
controller.close();
}
outputHandler.output("Verify of " + sstable + " succeeded. All " + goodRows + " rows read successfully");
}
private void logDuplicates(DecoratedKey key, Row first, int duplicateRows, long minTimestamp, long maxTimestamp)
{
String keyString = sstable.metadata().partitionKeyType.getString(key.getKey());
long firstMaxTs = Long.MIN_VALUE;
long firstMinTs = Long.MAX_VALUE;
for (Cell cell : first.cells())
{
firstMaxTs = Math.max(firstMaxTs, cell.timestamp());
firstMinTs = Math.min(firstMinTs, cell.timestamp());
}
outputHandler.output(String.format("%d duplicate rows found for [%s %s] in %s.%s (%s), timestamps: [first row (%s, %s)], [duplicates (%s, %s, eq:%b)]",
duplicateRows,
keyString, first.clustering().toString(sstable.metadata()),
sstable.metadata().keyspace,
sstable.metadata().name,
sstable,
dateString(firstMinTs), dateString(firstMaxTs),
dateString(minTimestamp), dateString(maxTimestamp), minTimestamp == maxTimestamp));
}
private String dateString(long time)
{
return Instant.ofEpochMilli(TimeUnit.MICROSECONDS.toMillis(time)).toString();
}
/**
* Use the fact that check(..) is called with sorted tokens - we keep a pointer in to the normalized ranges
* and only bump the pointer if the key given is out of range. This is done to avoid calling .contains(..) many
* times for each key (with vnodes for example)
*/
@VisibleForTesting
public static class RangeOwnHelper
{
private final List<Range<Token>> normalizedRanges;
private int rangeIndex = 0;
private DecoratedKey lastKey;
public RangeOwnHelper(List<Range<Token>> normalizedRanges)
{
this.normalizedRanges = normalizedRanges;
Range.assertNormalized(normalizedRanges);
}
/**
* check if the given key is contained in any of the given ranges
*
* Must be called in sorted order - key should be increasing
*
* @param key the key
* @throws RuntimeException if the key is not contained
*/
public void validate(DecoratedKey key)
{
if (!check(key))
throw new RuntimeException("Key " + key + " is not contained in the given ranges");
}
/**
* check if the given key is contained in any of the given ranges
*
* Must be called in sorted order - key should be increasing
*
* @param key the key
* @return boolean
*/
public boolean check(DecoratedKey key)
{
assert lastKey == null || key.compareTo(lastKey) > 0;
lastKey = key;
if (normalizedRanges.isEmpty()) // handle tests etc where we don't have any ranges
return true;
if (rangeIndex > normalizedRanges.size() - 1)
throw new IllegalStateException("RangeOwnHelper can only be used to find the first out-of-range-token");
while (!normalizedRanges.get(rangeIndex).contains(key.getToken()))
{
rangeIndex++;
if (rangeIndex > normalizedRanges.size() - 1)
return false;
}
return true;
}
}
private void deserializeIndex(SSTableReader sstable) throws IOException
{
try (RandomAccessReader primaryIndex = RandomAccessReader.open(new File(sstable.descriptor.filenameFor(Component.PRIMARY_INDEX))))
{
long indexSize = primaryIndex.length();
while ((primaryIndex.getFilePointer()) != indexSize)
{
ByteBuffer key = ByteBufferUtil.readWithShortLength(primaryIndex);
RowIndexEntry.Serializer.skip(primaryIndex, sstable.descriptor.version);
}
}
}
private void deserializeIndexSummary(SSTableReader sstable) throws IOException
{
File file = new File(sstable.descriptor.filenameFor(Component.SUMMARY));
TableMetadata metadata = cfs.metadata();
try (DataInputStream iStream = new DataInputStream(Files.newInputStream(file.toPath())))
{
try (IndexSummary indexSummary = IndexSummary.serializer.deserialize(iStream,
cfs.getPartitioner(),
metadata.params.minIndexInterval,
metadata.params.maxIndexInterval))
{
ByteBufferUtil.readWithLength(iStream);
ByteBufferUtil.readWithLength(iStream);
}
}
}
private void deserializeBloomFilter(SSTableReader sstable) throws IOException
{
File bfPath = new File(sstable.descriptor.filenameFor(Component.FILTER));
if (bfPath.exists())
{
try (FileInputStreamPlus stream = bfPath.newInputStream();
IFilter bf = BloomFilterSerializer.deserialize(stream, sstable.descriptor.version.hasOldBfFormat()))
{
}
}
}
public void close()
{
fileAccessLock.writeLock().lock();
try
{
FileUtils.closeQuietly(dataFile);
FileUtils.closeQuietly(indexFile);
}
finally
{
fileAccessLock.writeLock().unlock();
}
}
private void throwIfFatal(Throwable th)
{
if (th instanceof Error && !(th instanceof AssertionError || th instanceof IOError))
throw (Error) th;
}
private void markAndThrow(Throwable cause)
{
markAndThrow(cause, true);
}
private void markAndThrow(Throwable cause, boolean mutateRepaired)
{
if (mutateRepaired && options.mutateRepairStatus) // if we are able to mutate repaired flag, an incremental repair should be enough
{
try
{
sstable.mutateRepairedAndReload(ActiveRepairService.UNREPAIRED_SSTABLE, sstable.getPendingRepair(), sstable.isTransient());
cfs.getTracker().notifySSTableRepairedStatusChanged(Collections.singleton(sstable));
}
catch(IOException ioe)
{
outputHandler.output("Error mutating repairedAt for SSTable " + sstable.getFilename() + ", as part of markAndThrow");
}
}
Exception e = new Exception(String.format("Invalid SSTable %s, please force %srepair", sstable.getFilename(), (mutateRepaired && options.mutateRepairStatus) ? "" : "a full "), cause);
if (options.invokeDiskFailurePolicy)
throw new CorruptSSTableException(e, sstable.getFilename());
else
throw new RuntimeException(e);
}
public CompactionInfo.Holder getVerifyInfo()
{
return verifyInfo;
}
private static class VerifyInfo extends CompactionInfo.Holder
{
private final RandomAccessReader dataFile;
private final SSTableReader sstable;
private final TimeUUID verificationCompactionId;
private final Lock fileReadLock;
public VerifyInfo(RandomAccessReader dataFile, SSTableReader sstable, Lock fileReadLock)
{
this.dataFile = dataFile;
this.sstable = sstable;
this.fileReadLock = fileReadLock;
verificationCompactionId = nextTimeUUID();
}
public CompactionInfo getCompactionInfo()
{
fileReadLock.lock();
try
{
return new CompactionInfo(sstable.metadata(),
OperationType.VERIFY,
dataFile.getFilePointer(),
dataFile.length(),
verificationCompactionId,
ImmutableSet.of(sstable));
}
catch (Exception e)
{
throw new RuntimeException();
}
finally
{
fileReadLock.unlock();
}
}
public boolean isGlobal()
{
return false;
}
}
private static class VerifyController extends CompactionController
{
public VerifyController(ColumnFamilyStore cfs)
{
super(cfs, Integer.MAX_VALUE);
}
@Override
public LongPredicate getPurgeEvaluator(DecoratedKey key)
{
return time -> false;
}
}
public static Options.Builder options()
{
return new Options.Builder();
}
public static class Options
{
public final boolean invokeDiskFailurePolicy;
public final boolean extendedVerification;
public final boolean checkVersion;
public final boolean mutateRepairStatus;
public final boolean checkOwnsTokens;
public final boolean quick;
public final Function<String, ? extends Collection<Range<Token>>> tokenLookup;
private Options(boolean invokeDiskFailurePolicy, boolean extendedVerification, boolean checkVersion, boolean mutateRepairStatus, boolean checkOwnsTokens, boolean quick, Function<String, ? extends Collection<Range<Token>>> tokenLookup)
{
this.invokeDiskFailurePolicy = invokeDiskFailurePolicy;
this.extendedVerification = extendedVerification;
this.checkVersion = checkVersion;
this.mutateRepairStatus = mutateRepairStatus;
this.checkOwnsTokens = checkOwnsTokens;
this.quick = quick;
this.tokenLookup = tokenLookup;
}
@Override
public String toString()
{
return "Options{" +
"invokeDiskFailurePolicy=" + invokeDiskFailurePolicy +
", extendedVerification=" + extendedVerification +
", checkVersion=" + checkVersion +
", mutateRepairStatus=" + mutateRepairStatus +
", checkOwnsTokens=" + checkOwnsTokens +
", quick=" + quick +
'}';
}
public static class Builder
{
private boolean invokeDiskFailurePolicy = false; // invoking disk failure policy can stop the node if we find a corrupt stable
private boolean extendedVerification = false;
private boolean checkVersion = false;
private boolean mutateRepairStatus = false; // mutating repair status can be dangerous
private boolean checkOwnsTokens = false;
private boolean quick = false;
private Function<String, ? extends Collection<Range<Token>>> tokenLookup = StorageService.instance::getLocalAndPendingRanges;
public Builder invokeDiskFailurePolicy(boolean param)
{
this.invokeDiskFailurePolicy = param;
return this;
}
public Builder extendedVerification(boolean param)
{
this.extendedVerification = param;
return this;
}
public Builder checkVersion(boolean param)
{
this.checkVersion = param;
return this;
}
public Builder mutateRepairStatus(boolean param)
{
this.mutateRepairStatus = param;
return this;
}
public Builder checkOwnsTokens(boolean param)
{
this.checkOwnsTokens = param;
return this;
}
public Builder quick(boolean param)
{
this.quick = param;
return this;
}
public Builder tokenLookup(Function<String, ? extends Collection<Range<Token>>> tokenLookup)
{
this.tokenLookup = tokenLookup;
return this;
}
public Options build()
{
return new Options(invokeDiskFailurePolicy, extendedVerification, checkVersion, mutateRepairStatus, checkOwnsTokens, quick, tokenLookup);
}
}
}
}

View File

@ -31,16 +31,19 @@ import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.Directories;
import org.apache.cassandra.db.DiskBoundaries;
import org.apache.cassandra.db.PartitionPosition;
import org.apache.cassandra.db.rows.UnfilteredRowIterator;
import org.apache.cassandra.db.SerializationHeader;
import org.apache.cassandra.db.compaction.CompactionTask;
import org.apache.cassandra.db.lifecycle.LifecycleTransaction;
import org.apache.cassandra.db.rows.UnfilteredRowIterator;
import org.apache.cassandra.io.sstable.Descriptor;
import org.apache.cassandra.io.sstable.SSTableRewriter;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.io.sstable.format.SSTableWriter;
import org.apache.cassandra.io.sstable.metadata.MetadataCollector;
import org.apache.cassandra.io.util.File;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.TimeUUID;
import org.apache.cassandra.utils.concurrent.Transactional;
import org.apache.cassandra.io.util.File;
/**
@ -246,4 +249,24 @@ public abstract class CompactionAwareWriter extends Transactional.AbstractTransa
{
return cfs.getExpectedCompactedFileSize(nonExpiredSSTables, txn.opType());
}
/**
* It is up to the caller to set the following fields:
* - {@link SSTableWriter.Builder#setKeyCount(long)},
* - {@link SSTableWriter.Builder#setSerializationHeader(SerializationHeader)} and,
* - {@link SSTableWriter.Builder#setMetadataCollector(MetadataCollector)}
*
* @param descriptor
* @return
*/
protected SSTableWriter.Builder<?, ?> newWriterBuilder(Descriptor descriptor)
{
return descriptor.getFormat().getWriterFactory().builder(descriptor)
.setTableMetadataRef(cfs.metadata)
.setTransientSSTable(isTransient)
.setRepairedAt(minRepairedAt)
.setPendingRepair(pendingRepair)
.addFlushObserversForSecondaryIndexes(cfs.indexManager.listIndexes(), txn.opType())
.addDefaultComponents();
}
}

View File

@ -26,8 +26,9 @@ import org.slf4j.LoggerFactory;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.Directories;
import org.apache.cassandra.db.SerializationHeader;
import org.apache.cassandra.db.rows.UnfilteredRowIterator;
import org.apache.cassandra.db.lifecycle.LifecycleTransaction;
import org.apache.cassandra.db.rows.UnfilteredRowIterator;
import org.apache.cassandra.io.sstable.Descriptor;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.io.sstable.format.SSTableWriter;
import org.apache.cassandra.io.sstable.metadata.MetadataCollector;
@ -69,17 +70,15 @@ public class DefaultCompactionWriter extends CompactionAwareWriter
{
sstableDirectory = directory;
Descriptor descriptor = cfs.newSSTableDescriptor(getDirectories().getLocationForDisk(directory));
MetadataCollector collector = new MetadataCollector(txn.originals(), cfs.metadata().comparator, sstableLevel);
SerializationHeader header = SerializationHeader.make(cfs.metadata(), nonExpiredSSTables);
@SuppressWarnings("resource")
SSTableWriter writer = SSTableWriter.create(cfs.newSSTableDescriptor(getDirectories().getLocationForDisk(directory)),
estimatedTotalKeys,
minRepairedAt,
pendingRepair,
isTransient,
cfs.metadata,
new MetadataCollector(txn.originals(), cfs.metadata().comparator, sstableLevel),
SerializationHeader.make(cfs.metadata(), nonExpiredSSTables),
cfs.indexManager.listIndexes(),
txn);
SSTableWriter writer = newWriterBuilder(descriptor).setMetadataCollector(collector)
.setSerializationHeader(header)
.setKeyCount(estimatedTotalKeys)
.build(txn, cfs);
sstableWriter.switchWriter(writer);
}

View File

@ -21,11 +21,12 @@ import java.util.Set;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.Directories;
import org.apache.cassandra.db.RowIndexEntry;
import org.apache.cassandra.db.SerializationHeader;
import org.apache.cassandra.db.rows.UnfilteredRowIterator;
import org.apache.cassandra.db.compaction.LeveledManifest;
import org.apache.cassandra.db.lifecycle.LifecycleTransaction;
import org.apache.cassandra.db.rows.UnfilteredRowIterator;
import org.apache.cassandra.io.sstable.AbstractRowIndexEntry;
import org.apache.cassandra.io.sstable.Descriptor;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.io.sstable.format.SSTableWriter;
import org.apache.cassandra.io.sstable.metadata.MetadataCollector;
@ -69,7 +70,7 @@ public class MajorLeveledCompactionWriter extends CompactionAwareWriter
@SuppressWarnings("resource")
public boolean realAppend(UnfilteredRowIterator partition)
{
RowIndexEntry rie = sstableWriter.append(partition);
AbstractRowIndexEntry rie = sstableWriter.append(partition);
partitionsWritten++;
long totalWrittenInCurrentWriter = sstableWriter.currentWriter().getEstimatedOnDiskBytesWritten();
if (totalWrittenInCurrentWriter > maxSSTableSize)
@ -91,16 +92,18 @@ public class MajorLeveledCompactionWriter extends CompactionAwareWriter
{
sstableDirectory = location;
averageEstimatedKeysPerSSTable = Math.round(((double) averageEstimatedKeysPerSSTable * sstablesWritten + partitionsWritten) / (sstablesWritten + 1));
sstableWriter.switchWriter(SSTableWriter.create(cfs.newSSTableDescriptor(getDirectories().getLocationForDisk(sstableDirectory)),
keysPerSSTable,
minRepairedAt,
pendingRepair,
isTransient,
cfs.metadata,
new MetadataCollector(txn.originals(), cfs.metadata().comparator, currentLevel),
SerializationHeader.make(cfs.metadata(), txn.originals()),
cfs.indexManager.listIndexes(),
txn));
Descriptor descriptor = cfs.newSSTableDescriptor(getDirectories().getLocationForDisk(sstableDirectory));
MetadataCollector collector = new MetadataCollector(txn.originals(), cfs.metadata().comparator, currentLevel);
SerializationHeader serializationHeader = SerializationHeader.make(cfs.metadata(), txn.originals());
@SuppressWarnings("resource")
SSTableWriter writer = newWriterBuilder(descriptor).setKeyCount(keysPerSSTable)
.setSerializationHeader(serializationHeader)
.setMetadataCollector(collector)
.build(txn, cfs);
sstableWriter.switchWriter(writer);
partitionsWritten = 0;
sstablesWritten = 0;
}

View File

@ -21,11 +21,12 @@ import java.util.Set;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.Directories;
import org.apache.cassandra.db.RowIndexEntry;
import org.apache.cassandra.db.SerializationHeader;
import org.apache.cassandra.db.compaction.OperationType;
import org.apache.cassandra.db.rows.UnfilteredRowIterator;
import org.apache.cassandra.db.lifecycle.LifecycleTransaction;
import org.apache.cassandra.db.rows.UnfilteredRowIterator;
import org.apache.cassandra.io.sstable.AbstractRowIndexEntry;
import org.apache.cassandra.io.sstable.Descriptor;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.io.sstable.format.SSTableWriter;
import org.apache.cassandra.io.sstable.metadata.MetadataCollector;
@ -80,7 +81,7 @@ public class MaxSSTableSizeWriter extends CompactionAwareWriter
protected boolean realAppend(UnfilteredRowIterator partition)
{
RowIndexEntry rie = sstableWriter.append(partition);
AbstractRowIndexEntry rie = sstableWriter.append(partition);
if (sstableWriter.currentWriter().getEstimatedOnDiskBytesWritten() > maxSSTableSize)
{
switchCompactionLocation(sstableDirectory);
@ -92,18 +93,16 @@ public class MaxSSTableSizeWriter extends CompactionAwareWriter
public void switchCompactionLocation(Directories.DataDirectory location)
{
sstableDirectory = location;
@SuppressWarnings("resource")
SSTableWriter writer = SSTableWriter.create(cfs.newSSTableDescriptor(getDirectories().getLocationForDisk(sstableDirectory)),
estimatedTotalKeys / estimatedSSTables,
minRepairedAt,
pendingRepair,
isTransient,
cfs.metadata,
new MetadataCollector(allSSTables, cfs.metadata().comparator, level),
SerializationHeader.make(cfs.metadata(), nonExpiredSSTables),
cfs.indexManager.listIndexes(),
txn);
Descriptor descriptor = cfs.newSSTableDescriptor(getDirectories().getLocationForDisk(sstableDirectory));
MetadataCollector collector = new MetadataCollector(allSSTables, cfs.metadata().comparator, level);
SerializationHeader header = SerializationHeader.make(cfs.metadata(), nonExpiredSSTables);
@SuppressWarnings("resource")
SSTableWriter writer = newWriterBuilder(descriptor).setKeyCount(estimatedTotalKeys / estimatedSSTables)
.setMetadataCollector(collector)
.setSerializationHeader(header)
.build(txn, cfs);
sstableWriter.switchWriter(writer);
}

View File

@ -25,10 +25,11 @@ import org.slf4j.LoggerFactory;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.Directories;
import org.apache.cassandra.db.RowIndexEntry;
import org.apache.cassandra.db.SerializationHeader;
import org.apache.cassandra.db.rows.UnfilteredRowIterator;
import org.apache.cassandra.db.lifecycle.LifecycleTransaction;
import org.apache.cassandra.db.rows.UnfilteredRowIterator;
import org.apache.cassandra.io.sstable.AbstractRowIndexEntry;
import org.apache.cassandra.io.sstable.Descriptor;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.io.sstable.format.SSTableWriter;
import org.apache.cassandra.io.sstable.metadata.MetadataCollector;
@ -85,7 +86,7 @@ public class SplittingSizeTieredCompactionWriter extends CompactionAwareWriter
@Override
public boolean realAppend(UnfilteredRowIterator partition)
{
RowIndexEntry rie = sstableWriter.append(partition);
AbstractRowIndexEntry rie = sstableWriter.append(partition);
if (sstableWriter.currentWriter().getEstimatedOnDiskBytesWritten() > currentBytesToWrite && currentRatioIndex < ratios.length - 1) // if we underestimate how many keys we have, the last sstable might get more than we expect
{
currentRatioIndex++;
@ -102,17 +103,15 @@ public class SplittingSizeTieredCompactionWriter extends CompactionAwareWriter
{
sstableDirectory = location;
long currentPartitionsToWrite = Math.round(ratios[currentRatioIndex] * estimatedTotalKeys);
Descriptor descriptor = cfs.newSSTableDescriptor(getDirectories().getLocationForDisk(location));
MetadataCollector collector = new MetadataCollector(allSSTables, cfs.metadata().comparator, 0);
SerializationHeader header = SerializationHeader.make(cfs.metadata(), nonExpiredSSTables);
@SuppressWarnings("resource")
SSTableWriter writer = SSTableWriter.create(cfs.newSSTableDescriptor(getDirectories().getLocationForDisk(location)),
currentPartitionsToWrite,
minRepairedAt,
pendingRepair,
isTransient,
cfs.metadata,
new MetadataCollector(allSSTables, cfs.metadata().comparator, 0),
SerializationHeader.make(cfs.metadata(), nonExpiredSSTables),
cfs.indexManager.listIndexes(),
txn);
SSTableWriter writer = newWriterBuilder(descriptor).setKeyCount(currentPartitionsToWrite)
.setMetadataCollector(collector)
.setSerializationHeader(header)
.build(txn, cfs);
logger.trace("Switching writer, currentPartitionsToWrite = {}", currentPartitionsToWrite);
sstableWriter.switchWriter(writer);
}

View File

@ -17,16 +17,27 @@
*/
package org.apache.cassandra.db.lifecycle;
import java.util.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.google.common.base.Predicate;
import com.google.common.collect.*;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import org.apache.cassandra.io.sstable.SSTable;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.utils.Throwables;
import static com.google.common.base.Predicates.*;
import static com.google.common.base.Predicates.and;
import static com.google.common.base.Predicates.equalTo;
import static com.google.common.base.Predicates.in;
import static com.google.common.base.Predicates.not;
import static com.google.common.base.Predicates.or;
import static com.google.common.collect.Iterables.any;
import static com.google.common.collect.Iterables.concat;
import static com.google.common.collect.Iterables.filter;
@ -50,7 +61,7 @@ class Helpers
* really present, and that the items to add are not (unless we're also removing them)
* @return a new identity map with the contents of the provided one modified
*/
static <T> Map<T, T> replace(Map<T, T> original, Set<T> remove, Iterable<T> add)
static <T> Map<T, T> replace(Map<T, T> original, Set<? extends T> remove, Iterable<? extends T> add)
{
// ensure the ones being removed are the exact same ones present
for (T reader : remove)

View File

@ -18,33 +18,54 @@
package org.apache.cassandra.db.lifecycle;
import java.nio.file.Path;
import java.util.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Set;
import java.util.function.BiPredicate;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Predicate;
import com.google.common.collect.*;
import org.apache.cassandra.io.util.File;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.Directories;
import org.apache.cassandra.db.compaction.OperationType;
import org.apache.cassandra.io.sstable.SSTable;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.io.sstable.format.SSTableReader.UniqueIdentifier;
import org.apache.cassandra.io.util.File;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.utils.Throwables;
import org.apache.cassandra.utils.TimeUUID;
import org.apache.cassandra.utils.concurrent.Transactional;
import static com.google.common.base.Functions.compose;
import static com.google.common.base.Predicates.*;
import static com.google.common.base.Predicates.in;
import static com.google.common.collect.ImmutableSet.copyOf;
import static com.google.common.collect.Iterables.*;
import static com.google.common.collect.Iterables.concat;
import static com.google.common.collect.Iterables.getFirst;
import static com.google.common.collect.Iterables.transform;
import static java.util.Collections.singleton;
import static org.apache.cassandra.db.lifecycle.Helpers.*;
import static org.apache.cassandra.db.lifecycle.Helpers.abortObsoletion;
import static org.apache.cassandra.db.lifecycle.Helpers.checkNotReplaced;
import static org.apache.cassandra.db.lifecycle.Helpers.concatUniq;
import static org.apache.cassandra.db.lifecycle.Helpers.emptySet;
import static org.apache.cassandra.db.lifecycle.Helpers.filterIn;
import static org.apache.cassandra.db.lifecycle.Helpers.filterOut;
import static org.apache.cassandra.db.lifecycle.Helpers.markObsolete;
import static org.apache.cassandra.db.lifecycle.Helpers.orIn;
import static org.apache.cassandra.db.lifecycle.Helpers.prepareForObsoletion;
import static org.apache.cassandra.db.lifecycle.Helpers.select;
import static org.apache.cassandra.db.lifecycle.Helpers.selectFirst;
import static org.apache.cassandra.db.lifecycle.Helpers.setReplaced;
import static org.apache.cassandra.db.lifecycle.View.updateCompacting;
import static org.apache.cassandra.db.lifecycle.View.updateLiveSet;
import static org.apache.cassandra.utils.Throwables.maybeFail;
@ -160,12 +181,12 @@ public class LifecycleTransaction extends Transactional.AbstractTransactional im
}
@SuppressWarnings("resource") // log closed during postCleanup
LifecycleTransaction(Tracker tracker, OperationType operationType, Iterable<SSTableReader> readers)
LifecycleTransaction(Tracker tracker, OperationType operationType, Iterable<? extends SSTableReader> readers)
{
this(tracker, new LogTransaction(operationType, tracker), readers);
}
LifecycleTransaction(Tracker tracker, LogTransaction log, Iterable<SSTableReader> readers)
LifecycleTransaction(Tracker tracker, LogTransaction log, Iterable<? extends SSTableReader> readers)
{
this.tracker = tracker;
this.log = log;
@ -456,7 +477,7 @@ public class LifecycleTransaction extends Transactional.AbstractTransactional im
private List<SSTableReader> restoreUpdatedOriginals()
{
Iterable<SSTableReader> torestore = filterIn(originals, logged.update, logged.obsolete);
return ImmutableList.copyOf(transform(torestore, (reader) -> current(reader).cloneWithRestoredStart(reader.first)));
return ImmutableList.copyOf(transform(torestore, (reader) -> current(reader).cloneWithRestoredStart(reader.getFirst())));
}
/**

View File

@ -21,15 +21,22 @@
package org.apache.cassandra.db.lifecycle;
import java.nio.file.Path;
import java.util.*;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.NavigableSet;
import java.util.Optional;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Iterables;
import org.apache.cassandra.io.util.File;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -37,8 +44,9 @@ import org.slf4j.LoggerFactory;
import org.apache.cassandra.db.compaction.OperationType;
import org.apache.cassandra.db.lifecycle.LogRecord.Type;
import org.apache.cassandra.io.sstable.SSTable;
import org.apache.cassandra.io.sstable.format.SSTableFormat;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.io.sstable.format.big.BigFormat;
import org.apache.cassandra.io.util.File;
import org.apache.cassandra.utils.Throwables;
import org.apache.cassandra.utils.TimeUUID;
@ -62,7 +70,7 @@ final class LogFile implements AutoCloseable
static String EXT = ".log";
static char SEP = '_';
// cc_txn_opname_id.log (where cc is one of the sstable versions defined in BigVersion)
static Pattern FILE_REGEX = Pattern.compile(String.format("^(.{2})_txn_(.*)_(.*)%s$", EXT));
static Pattern FILE_REGEX = Pattern.compile(String.format("^(.{2}_)?txn_(.*)_(.*)%s$", EXT));
// A set of physical files on disk, each file is an identical replica
private final LogReplicaSet replicas = new LogReplicaSet();
@ -496,18 +504,14 @@ final class LogFile implements AutoCloseable
private String getFileName()
{
return StringUtils.join(BigFormat.latestVersion,
LogFile.SEP,
"txn",
LogFile.SEP,
type.fileName,
LogFile.SEP,
id.toString(),
LogFile.EXT);
return StringUtils.join(SSTableFormat.Type.current().info.getLatestVersion(), LogFile.SEP, // remove version and separator when downgrading to 4.x is becomes unsupported
"txn", LogFile.SEP,
type.fileName, LogFile.SEP,
id.toString(), LogFile.EXT);
}
public boolean isEmpty()
{
return records.isEmpty();
}
}
}

View File

@ -22,7 +22,16 @@ package org.apache.cassandra.db.lifecycle;
import java.nio.file.Path;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import java.util.function.BiPredicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -152,7 +161,7 @@ final class LogRecord
public static LogRecord make(Type type, SSTable table)
{
String absoluteTablePath = absolutePath(table.descriptor.baseFilename());
String absoluteTablePath = absolutePath(table.descriptor.baseFile());
return make(type, getExistingFiles(absoluteTablePath), table.getAllFilePaths().size(), absoluteTablePath);
}
@ -161,7 +170,7 @@ final class LogRecord
// contains a mapping from sstable absolute path (everything up until the 'Data'/'Index'/etc part of the filename) to the sstable
Map<String, SSTable> absolutePaths = new HashMap<>();
for (SSTableReader table : tables)
absolutePaths.put(absolutePath(table.descriptor.baseFilename()), table);
absolutePaths.put(absolutePath(table.descriptor.baseFile()), table);
// maps sstable base file name to the actual files on disk
Map<String, List<File>> existingFiles = getExistingFiles(absolutePaths.keySet());
@ -176,9 +185,9 @@ final class LogRecord
return records;
}
private static String absolutePath(String baseFilename)
private static String absolutePath(File baseFile)
{
return FileUtils.getCanonicalPath(baseFilename + Component.separator);
return baseFile.withSuffix(String.valueOf(Component.separator)).canonicalPath();
}
public LogRecord withExistingFiles(List<File> existingFiles)

View File

@ -22,18 +22,22 @@ import java.io.IOException;
import java.io.PrintStream;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.util.concurrent.Runnables;
import com.codahale.metrics.Counter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.codahale.metrics.Counter;
import org.apache.cassandra.concurrent.ScheduledExecutors;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.db.Directories;
@ -41,15 +45,17 @@ import org.apache.cassandra.db.SystemKeyspace;
import org.apache.cassandra.db.compaction.OperationType;
import org.apache.cassandra.db.lifecycle.LogRecord.Type;
import org.apache.cassandra.io.FSWriteError;
import org.apache.cassandra.io.sstable.Component;
import org.apache.cassandra.io.sstable.Descriptor;
import org.apache.cassandra.io.sstable.SSTable;
import org.apache.cassandra.io.sstable.format.SSTableFormat.Components;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.io.util.File;
import org.apache.cassandra.io.util.FileUtils;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.StorageService;
import org.apache.cassandra.utils.*;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.Throwables;
import org.apache.cassandra.utils.TimeUUID;
import org.apache.cassandra.utils.concurrent.Ref;
import org.apache.cassandra.utils.concurrent.RefCounted;
import org.apache.cassandra.utils.concurrent.Transactional;
@ -387,18 +393,14 @@ class LogTransaction extends Transactional.AbstractTransactional implements Tran
try
{
// If we can't successfully delete the DATA component, set the task to be retried later: see TransactionTidier
File datafile = new File(desc.filenameFor(Component.DATA));
if (logger.isTraceEnabled())
logger.trace("Tidier running for old sstable {}", desc.baseFilename());
logger.trace("Tidier running for old sstable {}", desc);
if (datafile.exists())
delete(datafile);
else if (!wasNew)
if (!desc.fileFor(Components.DATA).exists() && !wasNew)
logger.error("SSTableTidier ran with no existing data file for an sstable that was not new");
// let the remainder be cleaned up by delete
SSTable.delete(desc, SSTable.discoverComponentsFor(desc));
desc.getFormat().delete(desc);
}
catch (Throwable t)
{

View File

@ -17,7 +17,11 @@
*/
package org.apache.cassandra.db.lifecycle;
import java.util.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicReference;
@ -25,23 +29,34 @@ import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.*;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.Directories;
import org.apache.cassandra.db.memtable.Memtable;
import org.apache.cassandra.db.commitlog.CommitLogPosition;
import org.apache.cassandra.io.util.File;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.Directories;
import org.apache.cassandra.db.commitlog.CommitLogPosition;
import org.apache.cassandra.db.compaction.OperationType;
import org.apache.cassandra.db.memtable.Memtable;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.io.sstable.metadata.StatsMetadata;
import org.apache.cassandra.io.util.File;
import org.apache.cassandra.io.util.FileUtils;
import org.apache.cassandra.metrics.StorageMetrics;
import org.apache.cassandra.notifications.*;
import org.apache.cassandra.notifications.INotification;
import org.apache.cassandra.notifications.INotificationConsumer;
import org.apache.cassandra.notifications.InitialSSTableAddedNotification;
import org.apache.cassandra.notifications.MemtableDiscardedNotification;
import org.apache.cassandra.notifications.MemtableRenewedNotification;
import org.apache.cassandra.notifications.MemtableSwitchedNotification;
import org.apache.cassandra.notifications.SSTableAddedNotification;
import org.apache.cassandra.notifications.SSTableDeletingNotification;
import org.apache.cassandra.notifications.SSTableListChangedNotification;
import org.apache.cassandra.notifications.SSTableMetadataChanged;
import org.apache.cassandra.notifications.SSTableRepairStatusChanged;
import org.apache.cassandra.notifications.TruncationNotification;
import org.apache.cassandra.utils.Pair;
import org.apache.cassandra.utils.Throwables;
import org.apache.cassandra.utils.concurrent.OpOrder;
@ -51,7 +66,11 @@ import static com.google.common.collect.ImmutableSet.copyOf;
import static com.google.common.collect.Iterables.filter;
import static java.util.Collections.singleton;
import static java.util.Collections.singletonList;
import static org.apache.cassandra.db.lifecycle.Helpers.*;
import static org.apache.cassandra.db.lifecycle.Helpers.abortObsoletion;
import static org.apache.cassandra.db.lifecycle.Helpers.markObsolete;
import static org.apache.cassandra.db.lifecycle.Helpers.notIn;
import static org.apache.cassandra.db.lifecycle.Helpers.prepareForObsoletion;
import static org.apache.cassandra.db.lifecycle.Helpers.setupOnline;
import static org.apache.cassandra.db.lifecycle.View.permitCompacting;
import static org.apache.cassandra.db.lifecycle.View.updateCompacting;
import static org.apache.cassandra.db.lifecycle.View.updateLiveSet;
@ -99,7 +118,7 @@ public class Tracker
/**
* @return a Transaction over the provided sstables if we are able to mark the given @param sstables as compacted, before anyone else
*/
public LifecycleTransaction tryModify(Iterable<SSTableReader> sstables, OperationType operationType)
public LifecycleTransaction tryModify(Iterable<? extends SSTableReader> sstables, OperationType operationType)
{
if (Iterables.isEmpty(sstables))
return new LifecycleTransaction(this, operationType, sstables);

View File

@ -17,17 +17,22 @@
*/
package org.apache.cassandra.db.lifecycle;
import java.util.*;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.base.Predicate;
import com.google.common.collect.*;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.memtable.Memtable;
import org.apache.cassandra.db.PartitionPosition;
import org.apache.cassandra.db.memtable.Memtable;
import org.apache.cassandra.dht.AbstractBounds;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.utils.Interval;
@ -136,13 +141,30 @@ public class View
case NONCOMPACTING:
return filter(sstables, (s) -> !compacting.contains(s));
case CANONICAL:
// When early open is not in play, the LIVE and CANONICAL sets are the same.
// However, when we do have early-open sstables, we will have some unfinished sources in the live set.
// For these sources we need to extract the originals, in their non-moved-start versions, from the
// compacting set.
// This creates a problem when the compaction completes, as then both:
// - the source is in the compacting set
// - the result is in the live set
// This currently causes the CANONICAL set to return both source and result when early-open is disabled,
// and is otherwise worked around by opening early the last sstable in the result set (which pushes it
// in the compacting set with EARLY openReason) and the !compacting.contains(sstable) check in the
// second loop below.
// Unfortunately there does not appear to be a way to avoid this workaround. Filtering the compacting
// set through having an early-open version in live does not work because sources are fully removed from
// the live set when they are completely exhausted.
// Add the compacting versions first because they will be the canonical versions of compaction sources.
Set<SSTableReader> canonicalSSTables = new HashSet<>(sstables.size() + compacting.size());
for (SSTableReader sstable : compacting)
if (sstable.openReason != SSTableReader.OpenReason.EARLY)
canonicalSSTables.add(sstable);
// reason for checking if compacting contains the sstable is that if compacting has an EARLY version
// of a NORMAL sstable, we still have the canonical version of that sstable in sstables.
// note that the EARLY version is equal, but not == since it is a different instance of the same sstable.
// Add anything that is not compacting, removing any compaction result where we still have the
// compaction sources.
// note that the EARLY version is equal to the original, i.e. the set itself can guarantee early-open
// versions of sstables in compacting won't be added, but we also want to remove the results.
for (SSTableReader sstable : sstables)
if (!compacting.contains(sstable) && sstable.openReason != SSTableReader.OpenReason.EARLY)
canonicalSSTables.add(sstable);
@ -241,7 +263,7 @@ public class View
// METHODS TO CONSTRUCT FUNCTIONS FOR MODIFYING A VIEW:
// return a function to un/mark the provided readers compacting in a view
static Function<View, View> updateCompacting(final Set<SSTableReader> unmark, final Iterable<SSTableReader> mark)
static Function<View, View> updateCompacting(final Set<? extends SSTableReader> unmark, final Iterable<? extends SSTableReader> mark)
{
if (unmark.isEmpty() && Iterables.isEmpty(mark))
return Functions.identity();
@ -259,7 +281,7 @@ public class View
// construct a predicate to reject views that do not permit us to mark these readers compacting;
// i.e. one of them is either already compacting, has been compacted, or has been replaced
static Predicate<View> permitCompacting(final Iterable<SSTableReader> readers)
static Predicate<View> permitCompacting(final Iterable<? extends SSTableReader> readers)
{
return new Predicate<View>()
{
@ -353,4 +375,4 @@ public class View
}
};
}
}
}

View File

@ -52,7 +52,7 @@ import org.apache.cassandra.dht.Bounds;
import org.apache.cassandra.dht.IncludingExcludingBounds;
import org.apache.cassandra.dht.Range;
import org.apache.cassandra.index.transactions.UpdateTransaction;
import org.apache.cassandra.io.sstable.format.SSTableReadsListener;
import org.apache.cassandra.io.sstable.SSTableReadsListener;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.schema.TableMetadataRef;
import org.apache.cassandra.utils.concurrent.OpOrder;

View File

@ -51,7 +51,7 @@ import org.apache.cassandra.dht.IncludingExcludingBounds;
import org.apache.cassandra.dht.Murmur3Partitioner.LongToken;
import org.apache.cassandra.dht.Range;
import org.apache.cassandra.index.transactions.UpdateTransaction;
import org.apache.cassandra.io.sstable.format.SSTableReadsListener;
import org.apache.cassandra.io.sstable.SSTableReadsListener;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.schema.TableMetadataRef;
import org.apache.cassandra.utils.ObjectSizes;

View File

@ -63,7 +63,7 @@ import org.apache.cassandra.dht.IncludingExcludingBounds;
import org.apache.cassandra.dht.Range;
import org.apache.cassandra.index.transactions.UpdateTransaction;
import org.apache.cassandra.io.compress.BufferType;
import org.apache.cassandra.io.sstable.format.SSTableReadsListener;
import org.apache.cassandra.io.sstable.SSTableReadsListener;
import org.apache.cassandra.metrics.TableMetrics;
import org.apache.cassandra.metrics.TrieMemtableMetricsView;
import org.apache.cassandra.schema.TableMetadata;

View File

@ -21,13 +21,13 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import com.google.common.annotations.VisibleForTesting;
import org.apache.cassandra.db.DeletionTime;
import org.apache.cassandra.db.partitions.UnfilteredPartitionIterator;
import org.apache.cassandra.utils.AbstractIterator;
import org.apache.cassandra.utils.CloseableIterator;
import com.google.common.annotations.VisibleForTesting;
/**
* A utility class to split the given {@link#UnfilteredRowIterator} into smaller chunks each
* having at most {@link #throttle} + 1 unfiltereds.
@ -83,7 +83,7 @@ public class ThrottledUnfilteredIterator extends AbstractIterator<UnfilteredRowI
return throttledItr = origin;
}
throttledItr = new WrappingUnfilteredRowIterator(origin)
throttledItr = new WrappingUnfilteredRowIterator()
{
private int count = 0;
private boolean isFirst = throttledItr == null;
@ -96,10 +96,16 @@ public class ThrottledUnfilteredIterator extends AbstractIterator<UnfilteredRowI
// it must be consumed as last element of current batch
private RangeTombstoneMarker closeMarker = null;
@Override
public UnfilteredRowIterator wrapped()
{
return origin;
}
@Override
public boolean hasNext()
{
return (withinLimit() && wrapped.hasNext()) || closeMarker != null;
return (withinLimit() && origin.hasNext()) || closeMarker != null;
}
@Override
@ -119,7 +125,7 @@ public class ThrottledUnfilteredIterator extends AbstractIterator<UnfilteredRowI
if (overflowed.hasNext())
next = overflowed.next();
else
next = wrapped.next();
next = origin.next();
recordNext(next);
return next;
}
@ -132,8 +138,8 @@ public class ThrottledUnfilteredIterator extends AbstractIterator<UnfilteredRowI
// when reach throttle with a remaining openMarker, we need to create corresponding closeMarker.
if (count == throttle && openMarker != null)
{
assert wrapped.hasNext();
closeOpenMarker(wrapped.next());
assert origin.hasNext();
closeOpenMarker(origin.next());
}
}
@ -191,13 +197,13 @@ public class ThrottledUnfilteredIterator extends AbstractIterator<UnfilteredRowI
@Override
public DeletionTime partitionLevelDeletion()
{
return isFirst ? wrapped.partitionLevelDeletion() : DeletionTime.LIVE;
return isFirst ? origin.partitionLevelDeletion() : DeletionTime.LIVE;
}
@Override
public Row staticRow()
{
return isFirst ? wrapped.staticRow() : Rows.EMPTY_STATIC_ROW;
return isFirst ? origin.staticRow() : Rows.EMPTY_STATIC_ROW;
}
@Override
@ -258,4 +264,4 @@ public class ThrottledUnfilteredIterator extends AbstractIterator<UnfilteredRowI
}
};
}
}
}

View File

@ -17,15 +17,21 @@
*/
package org.apache.cassandra.db.rows;
import java.io.IOException;
import java.io.IOError;
import java.io.IOException;
import java.nio.BufferOverflowException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.DeletionTime;
import org.apache.cassandra.db.EmptyIterators;
import org.apache.cassandra.db.RegularAndStaticColumns;
import org.apache.cassandra.db.SerializationHeader;
import org.apache.cassandra.db.TypeSizes;
import org.apache.cassandra.db.filter.ColumnFilter;
import org.apache.cassandra.io.sstable.format.big.BigFormatPartitionWriter;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.schema.TableMetadata;
@ -60,7 +66,7 @@ import org.apache.cassandra.utils.ByteBufferUtil;
*
* Please note that the format described above is the on-wire format. On-disk, the format is basically the
* same, but the header is written once per sstable, not once per-partition. Further, the actual row and
* range tombstones are not written using this class, but rather by {@link ColumnIndex}.
* range tombstones are not written using this class, but rather by {@link BigFormatPartitionWriter}.
*/
public class UnfilteredRowIteratorSerializer
{
@ -280,4 +286,4 @@ public class UnfilteredRowIteratorSerializer
sHeader, key, isReversed, isEmpty, partitionDeletion, staticRow, rowEstimate);
}
}
}
}

View File

@ -20,7 +20,6 @@
*/
package org.apache.cassandra.db.rows;
import java.io.IOException;
import java.util.Comparator;
import java.util.Optional;
@ -32,14 +31,14 @@ import org.apache.cassandra.db.ClusteringPrefix;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.DeletionTime;
import org.apache.cassandra.db.RegularAndStaticColumns;
import org.apache.cassandra.db.RowIndexEntry;
import org.apache.cassandra.db.Slices;
import org.apache.cassandra.db.filter.ClusteringIndexFilter;
import org.apache.cassandra.db.filter.ColumnFilter;
import org.apache.cassandra.db.transform.RTBoundValidator;
import org.apache.cassandra.io.sstable.IndexInfo;
import org.apache.cassandra.io.sstable.SSTable;
import org.apache.cassandra.io.sstable.SSTableReadsListener;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.io.sstable.format.SSTableReadsListener;
import org.apache.cassandra.io.sstable.keycache.KeyCacheSupport;
import org.apache.cassandra.io.sstable.metadata.StatsMetadata;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.utils.IteratorWithLowerBound;
@ -197,21 +196,10 @@ public class UnfilteredRowIteratorWithLowerBound extends LazilyInitializedUnfilt
*/
private ClusteringBound<?> maybeGetLowerBoundFromKeyCache()
{
RowIndexEntry<?> rowIndexEntry = sstable.getCachedPosition(partitionKey(), false);
if (rowIndexEntry == null || !rowIndexEntry.indexOnHeap())
return null;
if (sstable instanceof KeyCacheSupport<?>)
return ((KeyCacheSupport<?>) sstable).getLowerBoundPrefixFromCache(partitionKey(), isReverseOrder);
try (RowIndexEntry.IndexInfoRetriever onHeapRetriever = rowIndexEntry.openWithIndex(null))
{
IndexInfo columns = onHeapRetriever.columnsIndex(isReverseOrder() ? rowIndexEntry.columnsIndexCount() - 1 : 0);
ClusteringBound<?> bound = isReverseOrder() ? columns.lastName.asEndBound() : columns.firstName.asStartBound();
assertBoundSize(bound);
return bound.artificialLowerBound(isReverseOrder());
}
catch (IOException e)
{
throw new RuntimeException("should never occur", e);
}
return null;
}
/**
@ -253,16 +241,16 @@ public class UnfilteredRowIteratorWithLowerBound extends LazilyInitializedUnfilt
final StatsMetadata m = sstable.getSSTableMetadata();
ClusteringBound<?> bound = m.coveredClustering.open(isReverseOrder);
assertBoundSize(bound);
assertBoundSize(bound, sstable);
return bound.artificialLowerBound(isReverseOrder);
}
private void assertBoundSize(ClusteringPrefix<?> lowerBound)
public static void assertBoundSize(ClusteringPrefix<?> lowerBound, SSTable sstable)
{
assert lowerBound.size() <= metadata().comparator.size() :
assert lowerBound.size() <= sstable.metadata().comparator.size() :
String.format("Unexpected number of clustering values %d, expected %d or fewer for %s",
lowerBound.size(),
metadata().comparator.size(),
sstable.metadata().comparator.size(),
sstable.getFilename());
}
}

View File

@ -17,12 +17,17 @@
*/
package org.apache.cassandra.db.rows;
import java.util.*;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.Columns;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.DeletionTime;
import org.apache.cassandra.db.Digest;
import org.apache.cassandra.db.EmptyIterators;
import org.apache.cassandra.db.RegularAndStaticColumns;
import org.apache.cassandra.db.filter.ColumnFilter;
import org.apache.cassandra.db.transform.FilteredRows;
import org.apache.cassandra.db.transform.MoreRows;
@ -263,16 +268,22 @@ public abstract class UnfilteredRowIterators
/**
* Returns an iterator that concatenate the specified atom with the iterator.
*/
public static UnfilteredRowIterator concat(final Unfiltered first, final UnfilteredRowIterator rest)
public static UnfilteredRowIterator concat(final Unfiltered first, final UnfilteredRowIterator wrapped)
{
return new WrappingUnfilteredRowIterator(rest)
return new WrappingUnfilteredRowIterator()
{
private boolean hasReturnedFirst;
@Override
public UnfilteredRowIterator wrapped()
{
return wrapped;
}
@Override
public boolean hasNext()
{
return hasReturnedFirst ? super.hasNext() : true;
return hasReturnedFirst ? wrapped.hasNext() : true;
}
@Override
@ -283,7 +294,7 @@ public abstract class UnfilteredRowIterators
hasReturnedFirst = true;
return first;
}
return super.next();
return wrapped.next();
}
};
}
@ -604,4 +615,4 @@ public abstract class UnfilteredRowIterators
}
}
}
}

View File

@ -23,7 +23,7 @@ import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.Slices;
import org.apache.cassandra.db.filter.ColumnFilter;
import org.apache.cassandra.db.partitions.UnfilteredPartitionIterator;
import org.apache.cassandra.io.sstable.format.SSTableReadsListener;
import org.apache.cassandra.io.sstable.SSTableReadsListener;
/**
* Common data access interface for sstables and memtables.

View File

@ -17,76 +17,72 @@
*/
package org.apache.cassandra.db.rows;
import com.google.common.collect.UnmodifiableIterator;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.DeletionTime;
import org.apache.cassandra.db.RegularAndStaticColumns;
import org.apache.cassandra.db.transform.Transformation;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.db.*;
/**
* Abstract class to make writing unfiltered iterators that wrap another iterator
* easier. By default, the wrapping iterator simply delegate every call to
* the wrapped iterator so concrete implementations will have to override
* some of the methods.
* the wrapped iterator so concrete implementations will have to override some methods.
* <p>
* Note that if most of what you want to do is modifying/filtering the returned
* {@code Unfiltered}, {@link org.apache.cassandra.db.transform.Transformation#merge(UnfilteredRowIterator,Transformation)} can be a simpler option.
* {@code Unfiltered}, {@link org.apache.cassandra.db.transform.Transformation#apply(UnfilteredRowIterator, Transformation)}
* can be a simpler option.
*/
public abstract class WrappingUnfilteredRowIterator extends UnmodifiableIterator<Unfiltered> implements UnfilteredRowIterator
public interface WrappingUnfilteredRowIterator extends UnfilteredRowIterator
{
protected final UnfilteredRowIterator wrapped;
UnfilteredRowIterator wrapped();
protected WrappingUnfilteredRowIterator(UnfilteredRowIterator wrapped)
default TableMetadata metadata()
{
this.wrapped = wrapped;
return wrapped().metadata();
}
public TableMetadata metadata()
default RegularAndStaticColumns columns()
{
return wrapped.metadata();
return wrapped().columns();
}
public RegularAndStaticColumns columns()
default boolean isReverseOrder()
{
return wrapped.columns();
return wrapped().isReverseOrder();
}
public boolean isReverseOrder()
default DecoratedKey partitionKey()
{
return wrapped.isReverseOrder();
return wrapped().partitionKey();
}
public DecoratedKey partitionKey()
default DeletionTime partitionLevelDeletion()
{
return wrapped.partitionKey();
return wrapped().partitionLevelDeletion();
}
public DeletionTime partitionLevelDeletion()
default Row staticRow()
{
return wrapped.partitionLevelDeletion();
return wrapped().staticRow();
}
public Row staticRow()
default EncodingStats stats()
{
return wrapped.staticRow();
return wrapped().stats();
}
public EncodingStats stats()
default boolean hasNext()
{
return wrapped.stats();
return wrapped().hasNext();
}
public boolean hasNext()
default Unfiltered next()
{
return wrapped.hasNext();
return wrapped().next();
}
public Unfiltered next()
default void close()
{
return wrapped.next();
wrapped().close();
}
public void close()
{
wrapped.close();
}
}
}

View File

@ -27,12 +27,12 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.io.compress.CompressionMetadata;
import org.apache.cassandra.io.sstable.Component;
import org.apache.cassandra.io.sstable.format.SSTableFormat.Components;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.io.util.ChannelProxy;
import org.apache.cassandra.streaming.ProgressInfo;
import org.apache.cassandra.streaming.StreamingDataOutputPlus;
import org.apache.cassandra.streaming.StreamSession;
import org.apache.cassandra.streaming.StreamingDataOutputPlus;
import org.apache.cassandra.utils.FBUtilities;
/**
@ -71,7 +71,7 @@ public class CassandraCompressedStreamWriter extends CassandraStreamWriter
int sectionIdx = 0;
// stream each of the required sections of the file
String filename = sstable.descriptor.filenameFor(Component.DATA);
String filename = sstable.descriptor.fileFor(Components.DATA).toString();
for (Section section : sections)
{
// length of the section to stream

View File

@ -25,17 +25,20 @@ import java.util.function.UnaryOperator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.Directories;
import org.apache.cassandra.db.lifecycle.LifecycleNewTracker;
import org.apache.cassandra.io.compress.BufferType;
import org.apache.cassandra.io.sstable.Component;
import org.apache.cassandra.io.sstable.Descriptor;
import org.apache.cassandra.io.sstable.IOOptions;
import org.apache.cassandra.io.sstable.SSTableMultiWriter;
import org.apache.cassandra.io.sstable.format.SSTableFormat;
import org.apache.cassandra.io.sstable.format.big.BigTableZeroCopyWriter;
import org.apache.cassandra.io.sstable.SSTableZeroCopyWriter;
import org.apache.cassandra.io.sstable.metadata.StatsMetadata;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.File;
import org.apache.cassandra.io.util.SequentialWriterOption;
import org.apache.cassandra.schema.TableId;
import org.apache.cassandra.streaming.ProgressInfo;
import org.apache.cassandra.streaming.StreamReceiver;
@ -60,9 +63,6 @@ public class CassandraEntireSSTableStreamReader implements IStreamReader
public CassandraEntireSSTableStreamReader(StreamMessageHeader messageHeader, CassandraStreamHeader streamHeader, StreamSession session)
{
if (streamHeader.format != SSTableFormat.Type.BIG)
throw new AssertionError("Unsupported SSTable format " + streamHeader.format);
if (session.getPendingRepair() != null)
{
// we should only ever be streaming pending repair sstables if the session has a pending repair id
@ -84,7 +84,7 @@ public class CassandraEntireSSTableStreamReader implements IStreamReader
*/
@SuppressWarnings("resource") // input needs to remain open, streams on top of it can't be closed
@Override
public SSTableMultiWriter read(DataInputPlus in) throws Throwable
public SSTableMultiWriter read(DataInputPlus in) throws IOException
{
ColumnFamilyStore cfs = ColumnFamilyStore.getIfExists(tableId);
if (cfs == null)
@ -103,7 +103,7 @@ public class CassandraEntireSSTableStreamReader implements IStreamReader
prettyPrintMemory(totalSize),
cfs.metadata());
BigTableZeroCopyWriter writer = null;
SSTableZeroCopyWriter writer = null;
try
{
@ -122,7 +122,7 @@ public class CassandraEntireSSTableStreamReader implements IStreamReader
prettyPrintMemory(totalSize));
writer.writeComponent(component.type, in, length);
session.progress(writer.descriptor.filenameFor(component), ProgressInfo.Direction.IN, length, length, length);
session.progress(writer.descriptor.fileFor(component).toString(), ProgressInfo.Direction.IN, length, length, length);
bytesRead += length;
logger.debug("[Stream #{}] Finished receiving {} component from {}, componentSize = {}, readBytes = {}, totalSize = {}",
@ -145,7 +145,11 @@ public class CassandraEntireSSTableStreamReader implements IStreamReader
{
logger.error("[Stream {}] Error while reading sstable from stream for table = {}", session.planId(), cfs.metadata(), e);
if (writer != null)
e = writer.abort(e);
{
Throwable e2 = writer.abort(null);
if (e2 != null)
e.addSuppressed(e2);
}
throw e;
}
}
@ -165,7 +169,7 @@ public class CassandraEntireSSTableStreamReader implements IStreamReader
}
@SuppressWarnings("resource")
protected BigTableZeroCopyWriter createWriter(ColumnFamilyStore cfs, long totalSize, Collection<Component> components) throws IOException
protected SSTableZeroCopyWriter createWriter(ColumnFamilyStore cfs, long totalSize, Collection<Component> components) throws IOException
{
File dataDir = getDataDir(cfs, totalSize);
@ -176,8 +180,24 @@ public class CassandraEntireSSTableStreamReader implements IStreamReader
Descriptor desc = cfs.newSSTableDescriptor(dataDir, header.version, header.format);
logger.debug("[Table #{}] {} Components to write: {}", cfs.metadata(), desc.filenameFor(Component.DATA), components);
IOOptions ioOptions = new IOOptions(DatabaseDescriptor.getDiskOptimizationStrategy(),
DatabaseDescriptor.getDiskAccessMode(),
DatabaseDescriptor.getIndexAccessMode(),
DatabaseDescriptor.getDiskOptimizationEstimatePercentile(),
SequentialWriterOption.newBuilder()
.trickleFsync(false)
.bufferSize(2 << 20)
.bufferType(BufferType.OFF_HEAP)
.build(),
DatabaseDescriptor.getFlushCompression());
return new BigTableZeroCopyWriter(desc, cfs.metadata, lifecycleNewTracker, components);
logger.debug("[Table #{}] {} Components to write: {}", cfs.metadata(), desc, components);
return desc.getFormat()
.getWriterFactory()
.builder(desc)
.setComponents(components)
.setTableMetadataRef(cfs.metadata)
.setIOOptions(ioOptions)
.createZeroCopyWriter(lifecycleNewTracker, cfs);
}
}

View File

@ -27,9 +27,9 @@ import org.slf4j.LoggerFactory;
import org.apache.cassandra.io.sstable.Component;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.streaming.ProgressInfo;
import org.apache.cassandra.streaming.StreamingDataOutputPlus;
import org.apache.cassandra.streaming.StreamManager;
import org.apache.cassandra.streaming.StreamSession;
import org.apache.cassandra.streaming.StreamingDataOutputPlus;
import static org.apache.cassandra.streaming.StreamManager.StreamRateLimiter;
import static org.apache.cassandra.utils.FBUtilities.prettyPrintMemory;
@ -93,7 +93,7 @@ public class CassandraEntireSSTableStreamWriter
long bytesWritten = out.writeFileToChannel(channel, limiter);
progress += bytesWritten;
session.progress(sstable.descriptor.filenameFor(component), ProgressInfo.Direction.OUT, bytesWritten, bytesWritten, length);
session.progress(sstable.descriptor.fileFor(component).toString(), ProgressInfo.Direction.OUT, bytesWritten, bytesWritten, length);
logger.debug("[Stream #{}] Finished streaming {}.{} gen {} component {} to {}, xfered = {}, length = {}, totalSize = {}",
session.planId(),

View File

@ -182,7 +182,7 @@ public class CassandraStreamHeader
if (header.isEntireSSTable)
{
ComponentManifest.serializer.serialize(header.componentManifest, out, version);
ComponentManifest.serializers.get(header.format).serialize(header.componentManifest, out, version);
ByteBufferUtil.writeWithVIntLength(header.firstKey.getKey(), out);
}
}
@ -201,8 +201,9 @@ public class CassandraStreamHeader
@VisibleForTesting
public CassandraStreamHeader deserialize(DataInputPlus in, int version, Function<TableId, IPartitioner> partitionerMapper) throws IOException
{
Version sstableVersion = SSTableFormat.Type.current().info.getVersion(in.readUTF());
SSTableFormat.Type format = SSTableFormat.Type.validate(in.readUTF());
String sstableVersionString = in.readUTF();
SSTableFormat.Type format = SSTableFormat.Type.getByName(in.readUTF());
Version sstableVersion = format.info.getVersion(sstableVersionString);
long estimatedKeys = in.readLong();
int count = in.readInt();
@ -221,7 +222,7 @@ public class CassandraStreamHeader
if (isEntireSSTable)
{
manifest = ComponentManifest.serializer.deserialize(in, version);
manifest = ComponentManifest.serializers.get(format).deserialize(in, version);
ByteBuffer keyBuf = ByteBufferUtil.readWithVIntLength(in);
IPartitioner partitioner = partitionerMapper.apply(tableId);
if (partitioner == null)
@ -267,7 +268,7 @@ public class CassandraStreamHeader
if (header.isEntireSSTable)
{
size += ComponentManifest.serializer.serializedSize(header.componentManifest, version);
size += ComponentManifest.serializers.get(header.format).serializedSize(header.componentManifest, version);
size += ByteBufferUtil.serializedSizeWithVIntLength(header.firstKey.getKey());
}
return size;

View File

@ -39,9 +39,9 @@ import org.apache.cassandra.db.rows.Row;
import org.apache.cassandra.db.rows.Unfiltered;
import org.apache.cassandra.db.rows.UnfilteredRowIterator;
import org.apache.cassandra.exceptions.UnknownColumnException;
import org.apache.cassandra.io.sstable.RangeAwareSSTableWriter;
import org.apache.cassandra.io.sstable.SSTableMultiWriter;
import org.apache.cassandra.io.sstable.SSTableSimpleIterator;
import org.apache.cassandra.io.sstable.format.RangeAwareSSTableWriter;
import org.apache.cassandra.io.sstable.format.SSTableFormat;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.io.sstable.format.Version;

View File

@ -21,23 +21,21 @@ import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Collection;
import org.apache.cassandra.io.util.File;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.jpountz.lz4.LZ4Compressor;
import net.jpountz.lz4.LZ4Factory;
import org.apache.cassandra.io.compress.BufferType;
import org.apache.cassandra.io.sstable.Component;
import org.apache.cassandra.io.sstable.format.SSTableFormat.Components;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.io.util.ChannelProxy;
import org.apache.cassandra.io.util.DataIntegrityMetadata;
import org.apache.cassandra.io.util.DataIntegrityMetadata.ChecksumValidator;
import org.apache.cassandra.streaming.ProgressInfo;
import org.apache.cassandra.streaming.StreamingDataOutputPlus;
import org.apache.cassandra.streaming.StreamManager;
import org.apache.cassandra.streaming.StreamManager.StreamRateLimiter;
import org.apache.cassandra.streaming.StreamSession;
import org.apache.cassandra.streaming.StreamingDataOutputPlus;
import org.apache.cassandra.streaming.async.StreamCompressionSerializer;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.memory.BufferPools;
@ -84,9 +82,7 @@ public class CassandraStreamWriter
sstable.getFilename(), session.peer, sstable.getSSTableMetadata().repairedAt, totalSize);
try(ChannelProxy proxy = sstable.getDataChannel().newChannel();
ChecksumValidator validator = new File(sstable.descriptor.filenameFor(Component.CRC)).exists()
? DataIntegrityMetadata.checksumValidator(sstable.descriptor)
: null)
ChecksumValidator validator = sstable.maybeGetChecksumValidator())
{
int bufferSize = validator == null ? DEFAULT_CHUNK_SIZE: validator.chunkSize;
@ -94,7 +90,7 @@ public class CassandraStreamWriter
long progress = 0L;
// stream each of the required sections of the file
String filename = sstable.descriptor.filenameFor(Component.DATA);
String filename = sstable.descriptor.fileFor(Components.DATA).toString();
for (SSTableReader.PartitionPositionBounds section : sections)
{
long start = validator == null ? section.lowerPosition : validator.chunkStart(section.lowerPosition);

View File

@ -18,32 +18,23 @@
package org.apache.cassandra.db.streaming;
import com.google.common.collect.ImmutableSet;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.io.sstable.Component;
import org.apache.cassandra.io.sstable.Descriptor;
import org.apache.cassandra.io.util.FileUtils;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* Mutable SSTable components and their hardlinks to avoid concurrent sstable component modification
* during entire-sstable-streaming.
*/
import org.apache.cassandra.io.util.File;
import org.apache.cassandra.io.util.FileUtils;
public class ComponentContext implements AutoCloseable
{
private static final Logger logger = LoggerFactory.getLogger(ComponentContext.class);
private static final Set<Component> MUTABLE_COMPONENTS = ImmutableSet.of(Component.STATS, Component.SUMMARY);
private final Map<Component, File> hardLinks;
private final ComponentManifest manifest;
@ -57,13 +48,13 @@ public class ComponentContext implements AutoCloseable
{
Map<Component, File> hardLinks = new HashMap<>(1);
for (Component component : MUTABLE_COMPONENTS)
for (Component component : descriptor.getFormat().mutableComponents())
{
File file = new File(descriptor.filenameFor(component));
File file = descriptor.fileFor(component);
if (!file.exists())
continue;
File hardlink = new File(descriptor.tmpFilenameForStreaming(component));
File hardlink = descriptor.tmpFileForStreaming(component);
FileUtils.createHardLink(file, hardlink);
hardLinks.put(component, hardlink);
}
@ -81,9 +72,9 @@ public class ComponentContext implements AutoCloseable
*/
public FileChannel channel(Descriptor descriptor, Component component, long size) throws IOException
{
String toTransfer = hardLinks.containsKey(component) ? hardLinks.get(component).path() : descriptor.filenameFor(component);
File toTransfer = hardLinks.containsKey(component) ? hardLinks.get(component) : descriptor.fileFor(component);
@SuppressWarnings("resource") // file channel will be closed by Caller
FileChannel channel = new File(toTransfer).newReadChannel();
FileChannel channel = toTransfer.newReadChannel();
assert size == channel.size() : String.format("Entire sstable streaming expects %s file size to be %s but got %s.",
component, size, channel.size());

View File

@ -19,26 +19,30 @@
package org.apache.cassandra.db.streaming;
import java.io.IOException;
import java.util.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterators;
import org.apache.cassandra.db.TypeSizes;
import org.apache.cassandra.io.IVersionedSerializer;
import org.apache.cassandra.io.sstable.Component;
import org.apache.cassandra.io.sstable.Descriptor;
import org.apache.cassandra.io.sstable.format.SSTableFormat;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.io.util.File;
/**
* SSTable components and their sizes to be tranfered via entire-sstable-streaming
*/
public final class ComponentManifest implements Iterable<Component>
{
private static final List<Component> STREAM_COMPONENTS = ImmutableList.of(Component.DATA, Component.PRIMARY_INDEX, Component.STATS,
Component.COMPRESSION_INFO, Component.FILTER, Component.SUMMARY,
Component.DIGEST, Component.CRC);
private final LinkedHashMap<Component, Long> components;
public ComponentManifest(Map<Component, Long> components)
@ -49,11 +53,11 @@ public final class ComponentManifest implements Iterable<Component>
@VisibleForTesting
public static ComponentManifest create(Descriptor descriptor)
{
LinkedHashMap<Component, Long> components = new LinkedHashMap<>(STREAM_COMPONENTS.size());
LinkedHashMap<Component, Long> components = new LinkedHashMap<>(descriptor.getFormat().streamingComponents().size());
for (Component component : STREAM_COMPONENTS)
for (Component component : descriptor.getFormat().streamingComponents())
{
File file = new File(descriptor.filenameFor(component));
File file = descriptor.fileFor(component);
if (!file.exists())
continue;
@ -111,45 +115,58 @@ public final class ComponentManifest implements Iterable<Component>
'}';
}
public static final IVersionedSerializer<ComponentManifest> serializer = new IVersionedSerializer<ComponentManifest>()
public static final Map<SSTableFormat.Type, IVersionedSerializer<ComponentManifest>> serializers;
static
{
public void serialize(ComponentManifest manifest, DataOutputPlus out, int version) throws IOException
ImmutableMap.Builder<SSTableFormat.Type, IVersionedSerializer<ComponentManifest>> b = ImmutableMap.builder();
for (SSTableFormat.Type formatType : SSTableFormat.Type.values())
{
out.writeUnsignedVInt32(manifest.components.size());
for (Map.Entry<Component, Long> entry : manifest.components.entrySet())
IVersionedSerializer<ComponentManifest> serializer = new IVersionedSerializer<ComponentManifest>()
{
out.writeUTF(entry.getKey().name);
out.writeUnsignedVInt(entry.getValue());
}
public void serialize(ComponentManifest manifest, DataOutputPlus out, int version) throws IOException
{
out.writeUnsignedVInt32(manifest.components.size());
for (Map.Entry<Component, Long> entry : manifest.components.entrySet())
{
out.writeUTF(entry.getKey().name);
out.writeUnsignedVInt(entry.getValue());
}
}
public ComponentManifest deserialize(DataInputPlus in, int version) throws IOException
{
int size = in.readUnsignedVInt32();
LinkedHashMap<Component, Long> components = new LinkedHashMap<>(size);
for (int i = 0; i < size; i++)
{
Component component = Component.parse(in.readUTF(), formatType);
long length = in.readUnsignedVInt();
components.put(component, length);
}
return new ComponentManifest(components);
}
public long serializedSize(ComponentManifest manifest, int version)
{
long size = TypeSizes.sizeofUnsignedVInt(manifest.components.size());
for (Map.Entry<Component, Long> entry : manifest.components.entrySet())
{
size += TypeSizes.sizeof(entry.getKey().name);
size += TypeSizes.sizeofUnsignedVInt(entry.getValue());
}
return size;
}
};
b.put(formatType, serializer);
}
public ComponentManifest deserialize(DataInputPlus in, int version) throws IOException
{
int size = in.readUnsignedVInt32();
LinkedHashMap<Component, Long> components = new LinkedHashMap<>(size);
for (int i = 0; i < size; i++)
{
Component component = Component.parse(in.readUTF());
long length = in.readUnsignedVInt();
components.put(component, length);
}
return new ComponentManifest(components);
}
public long serializedSize(ComponentManifest manifest, int version)
{
long size = TypeSizes.sizeofUnsignedVInt(manifest.components.size());
for (Map.Entry<Component, Long> entry : manifest.components.entrySet())
{
size += TypeSizes.sizeof(entry.getKey().name);
size += TypeSizes.sizeofUnsignedVInt(entry.getValue());
}
return size;
}
};
serializers = b.build();
}
@Override
public Iterator<Component> iterator()

View File

@ -20,8 +20,6 @@
*/
package org.apache.cassandra.db.transform;
import java.util.Collections;
import org.apache.cassandra.db.partitions.BasePartitionIterator;
import org.apache.cassandra.db.rows.BaseRowIterator;
import org.apache.cassandra.utils.Throwables;
@ -112,7 +110,7 @@ implements BasePartitionIterator<R>
catch (Throwable t)
{
if (next != null)
Throwables.close(t, Collections.singleton(next));
Throwables.close(t, next);
throw t;
}
}

View File

@ -23,11 +23,17 @@ import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.concurrent.*;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import com.google.common.net.HostAndPort;
import org.apache.cassandra.io.util.File;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -40,6 +46,7 @@ import org.apache.cassandra.hadoop.ConfigHelper;
import org.apache.cassandra.hadoop.HadoopCompat;
import org.apache.cassandra.io.sstable.CQLSSTableWriter;
import org.apache.cassandra.io.sstable.SSTableLoader;
import org.apache.cassandra.io.util.File;
import org.apache.cassandra.io.util.FileUtils;
import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.schema.TableMetadataRef;
@ -321,6 +328,6 @@ public class CqlBulkRecordWriter extends RecordWriter<Object, List<ByteBuffer>>
public void output(String msg) {}
public void debug(String msg) {}
public void warn(String msg) {}
public void warn(String msg, Throwable th) {}
public void warn(Throwable th, String msg) {}
}
}
}

View File

@ -26,13 +26,19 @@ import java.util.Set;
import java.util.concurrent.Callable;
import java.util.function.BiFunction;
import org.apache.cassandra.db.memtable.Memtable;
import org.apache.cassandra.schema.ColumnMetadata;
import org.apache.cassandra.cql3.Operator;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.DeletionTime;
import org.apache.cassandra.db.RangeTombstone;
import org.apache.cassandra.db.ReadCommand;
import org.apache.cassandra.db.ReadExecutionController;
import org.apache.cassandra.db.RegularAndStaticColumns;
import org.apache.cassandra.db.WriteContext;
import org.apache.cassandra.db.compaction.OperationType;
import org.apache.cassandra.db.filter.RowFilter;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.memtable.Memtable;
import org.apache.cassandra.db.partitions.PartitionIterator;
import org.apache.cassandra.db.partitions.PartitionUpdate;
import org.apache.cassandra.db.partitions.UnfilteredPartitionIterator;
@ -42,8 +48,9 @@ import org.apache.cassandra.index.internal.CollatedViewIndexBuilder;
import org.apache.cassandra.index.transactions.IndexTransaction;
import org.apache.cassandra.io.sstable.Descriptor;
import org.apache.cassandra.io.sstable.ReducingKeyIterator;
import org.apache.cassandra.io.sstable.format.SSTableFlushObserver;
import org.apache.cassandra.io.sstable.SSTableFlushObserver;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.schema.ColumnMetadata;
import org.apache.cassandra.schema.IndexMetadata;
/**

View File

@ -17,16 +17,30 @@
*/
package org.apache.cassandra.index.sasi;
import java.util.*;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.NavigableMap;
import java.util.Optional;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.Callable;
import java.util.function.BiFunction;
import com.googlecode.concurrenttrees.common.Iterables;
import org.apache.cassandra.config.*;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.cql3.Operator;
import org.apache.cassandra.cql3.statements.schema.IndexTarget;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.CassandraWriteContext;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.DeletionTime;
import org.apache.cassandra.db.RangeTombstone;
import org.apache.cassandra.db.ReadCommand;
import org.apache.cassandra.db.RegularAndStaticColumns;
import org.apache.cassandra.db.WriteContext;
import org.apache.cassandra.db.compaction.CompactionManager;
import org.apache.cassandra.db.compaction.OperationType;
import org.apache.cassandra.db.filter.RowFilter;
@ -49,9 +63,15 @@ import org.apache.cassandra.index.sasi.disk.PerSSTableIndexWriter;
import org.apache.cassandra.index.sasi.plan.QueryPlan;
import org.apache.cassandra.index.transactions.IndexTransaction;
import org.apache.cassandra.io.sstable.Descriptor;
import org.apache.cassandra.io.sstable.format.SSTableFlushObserver;
import org.apache.cassandra.io.sstable.SSTableFlushObserver;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.notifications.*;
import org.apache.cassandra.notifications.INotification;
import org.apache.cassandra.notifications.INotificationConsumer;
import org.apache.cassandra.notifications.MemtableDiscardedNotification;
import org.apache.cassandra.notifications.MemtableRenewedNotification;
import org.apache.cassandra.notifications.MemtableSwitchedNotification;
import org.apache.cassandra.notifications.SSTableAddedNotification;
import org.apache.cassandra.notifications.SSTableListChangedNotification;
import org.apache.cassandra.schema.ColumnMetadata;
import org.apache.cassandra.schema.IndexMetadata;
import org.apache.cassandra.schema.Schema;

View File

@ -21,13 +21,13 @@
package org.apache.cassandra.index.sasi;
import java.io.IOException;
import java.util.*;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.SortedMap;
import org.apache.cassandra.io.util.File;
import org.apache.cassandra.schema.ColumnMetadata;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.RowIndexEntry;
import org.apache.cassandra.db.compaction.CompactionInfo;
import org.apache.cassandra.db.compaction.CompactionInterruptedException;
import org.apache.cassandra.db.compaction.OperationType;
@ -36,11 +36,12 @@ import org.apache.cassandra.index.SecondaryIndexBuilder;
import org.apache.cassandra.index.sasi.conf.ColumnIndex;
import org.apache.cassandra.index.sasi.disk.PerSSTableIndexWriter;
import org.apache.cassandra.io.FSReadError;
import org.apache.cassandra.io.sstable.KeyIterator;
import org.apache.cassandra.io.sstable.SSTable;
import org.apache.cassandra.io.sstable.KeyReader;
import org.apache.cassandra.io.sstable.SSTableIdentityIterator;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.io.util.File;
import org.apache.cassandra.io.util.RandomAccessReader;
import org.apache.cassandra.schema.ColumnMetadata;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.TimeUUID;
@ -57,22 +58,23 @@ class SASIIndexBuilder extends SecondaryIndexBuilder
private final SortedMap<SSTableReader, Map<ColumnMetadata, ColumnIndex>> sstables;
private long bytesProcessed = 0;
private final long totalSizeInBytes;
private final long totalBytesToProcess;
public SASIIndexBuilder(ColumnFamilyStore cfs, SortedMap<SSTableReader, Map<ColumnMetadata, ColumnIndex>> sstables)
{
long totalIndexBytes = 0;
long totalBytesToProcess = 0;
for (SSTableReader sstable : sstables.keySet())
totalIndexBytes += getPrimaryIndexLength(sstable);
totalBytesToProcess += sstable.uncompressedLength();
this.cfs = cfs;
this.sstables = sstables;
this.totalSizeInBytes = totalIndexBytes;
this.totalBytesToProcess = totalBytesToProcess;
}
public void build()
{
AbstractType<?> keyValidator = cfs.metadata().partitionKeyType;
long processedBytesInFinishedSSTables = 0;
for (Map.Entry<SSTableReader, Map<ColumnMetadata, ColumnIndex>> e : sstables.entrySet())
{
SSTableReader sstable = e.getKey();
@ -83,46 +85,45 @@ class SASIIndexBuilder extends SecondaryIndexBuilder
PerSSTableIndexWriter indexWriter = SASIIndex.newWriter(keyValidator, sstable.descriptor, indexes, OperationType.COMPACTION);
targetDirectory = indexWriter.getDescriptor().directory.path();
long previousKeyPosition = 0;
try (KeyIterator keys = new KeyIterator(sstable.descriptor, cfs.metadata()))
try (KeyReader keys = sstable.keyReader())
{
while (keys.hasNext())
while (!keys.isExhausted())
{
if (isStopRequested())
throw new CompactionInterruptedException(getCompactionInfo());
final DecoratedKey key = keys.next();
final long keyPosition = keys.getKeyPosition();
final DecoratedKey key = sstable.decorateKey(keys.key());
final long keyPosition = keys.keyPositionForSecondaryIndex();
indexWriter.startPartition(key, keyPosition);
indexWriter.startPartition(key, keys.dataPosition(), keyPosition);
try
dataFile.seek(keys.dataPosition());
ByteBufferUtil.readWithShortLength(dataFile); // key
try (SSTableIdentityIterator partition = SSTableIdentityIterator.create(sstable, dataFile, key))
{
RowIndexEntry indexEntry = sstable.getPosition(key, SSTableReader.Operator.EQ);
dataFile.seek(indexEntry.position);
ByteBufferUtil.readWithShortLength(dataFile); // key
try (SSTableIdentityIterator partition = SSTableIdentityIterator.create(sstable, dataFile, key))
// if the row has statics attached, it has to be indexed separately
if (cfs.metadata().hasStaticColumns())
{
// if the row has statics attached, it has to be indexed separately
if (cfs.metadata().hasStaticColumns())
indexWriter.nextUnfilteredCluster(partition.staticRow());
while (partition.hasNext())
indexWriter.nextUnfilteredCluster(partition.next());
indexWriter.nextUnfilteredCluster(partition.staticRow());
}
}
catch (IOException ex)
{
throw new FSReadError(ex, sstable.getFilename());
while (partition.hasNext())
indexWriter.nextUnfilteredCluster(partition.next());
}
bytesProcessed += keyPosition - previousKeyPosition;
previousKeyPosition = keyPosition;
keys.advance();
long dataPosition = keys.isExhausted() ? sstable.uncompressedLength() : keys.dataPosition();
bytesProcessed = processedBytesInFinishedSSTables + dataPosition;
}
completeSSTable(indexWriter, sstable, indexes.values());
}
catch (IOException ex)
{
throw new FSReadError(ex, sstable.getFilename());
}
processedBytesInFinishedSSTables += sstable.uncompressedLength();
}
}
}
@ -132,25 +133,19 @@ class SASIIndexBuilder extends SecondaryIndexBuilder
return new CompactionInfo(cfs.metadata(),
OperationType.INDEX_BUILD,
bytesProcessed,
totalSizeInBytes,
totalBytesToProcess,
compactionId,
sstables.keySet(),
targetDirectory);
}
private long getPrimaryIndexLength(SSTable sstable)
{
File primaryIndex = new File(sstable.getIndexFilename());
return primaryIndex.exists() ? primaryIndex.length() : 0;
}
private void completeSSTable(PerSSTableIndexWriter indexWriter, SSTableReader sstable, Collection<ColumnIndex> indexes)
{
indexWriter.complete();
for (ColumnIndex index : indexes)
{
File tmpIndex = new File(sstable.descriptor.filenameFor(index.getComponent()));
File tmpIndex = sstable.descriptor.fileFor(index.getComponent());
if (!tmpIndex.exists()) // no data was inserted into the index for given sstable
continue;

View File

@ -22,6 +22,9 @@ import java.nio.ByteBuffer;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import com.google.common.base.Function;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.index.sasi.conf.ColumnIndex;
@ -36,10 +39,6 @@ import org.apache.cassandra.io.util.File;
import org.apache.cassandra.io.util.FileUtils;
import org.apache.cassandra.utils.concurrent.Ref;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import com.google.common.base.Function;
public class SSTableIndex
{
private final ColumnIndex columnIndex;
@ -176,7 +175,7 @@ public class SSTableIndex
{
try
{
return sstable.keyAt(offset);
return sstable.keyAtPositionFromSecondaryIndex(offset);
}
catch (IOException e)
{

View File

@ -28,13 +28,12 @@ import java.util.concurrent.atomic.AtomicReference;
import com.google.common.annotations.VisibleForTesting;
import org.apache.cassandra.schema.ColumnMetadata;
import org.apache.cassandra.cql3.Operator;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.memtable.Memtable;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.marshal.AsciiType;
import org.apache.cassandra.db.marshal.UTF8Type;
import org.apache.cassandra.db.memtable.Memtable;
import org.apache.cassandra.db.rows.Cell;
import org.apache.cassandra.db.rows.Row;
import org.apache.cassandra.index.sasi.analyzer.AbstractAnalyzer;
@ -47,7 +46,9 @@ import org.apache.cassandra.index.sasi.plan.Expression.Op;
import org.apache.cassandra.index.sasi.utils.RangeIterator;
import org.apache.cassandra.index.sasi.utils.RangeUnionIterator;
import org.apache.cassandra.io.sstable.Component;
import org.apache.cassandra.io.sstable.format.SSTableFormat.Components;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.schema.ColumnMetadata;
import org.apache.cassandra.schema.IndexMetadata;
import org.apache.cassandra.utils.FBUtilities;
@ -78,7 +79,7 @@ public class ColumnIndex
this.mode = IndexMode.getMode(column, config);
this.memtable = new AtomicReference<>(new IndexMemtable(this));
this.tracker = new DataTracker(keyValidator, this);
this.component = new Component(Component.Type.SECONDARY_INDEX, String.format(FILE_NAME_FORMAT, getIndexName()));
this.component = Components.Types.SECONDARY_INDEX.createComponent(String.format(FILE_NAME_FORMAT, getIndexName()));
this.isTokenized = getAnalyzer().isTokenizing();
}

View File

@ -24,6 +24,9 @@ import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.index.sasi.SSTableIndex;
import org.apache.cassandra.index.sasi.conf.view.View;
@ -31,9 +34,6 @@ import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.io.util.File;
import org.apache.cassandra.utils.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** a pared-down version of DataTracker and DT.View. need one for each index of each column family */
public class DataTracker
{
@ -145,7 +145,7 @@ public class DataTracker
if (sstable.isMarkedCompacted())
continue;
File indexFile = new File(sstable.descriptor.filenameFor(columnIndex.getComponent()));
File indexFile = sstable.descriptor.fileFor(columnIndex.getComponent());
if (!indexFile.exists())
continue;

View File

@ -17,21 +17,33 @@
*/
package org.apache.cassandra.index.sasi.disk;
import java.io.*;
import java.io.Closeable;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.TreeMap;
import java.util.stream.Collectors;
import com.google.common.base.Function;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import com.google.common.collect.PeekingIterator;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.index.sasi.Term;
import org.apache.cassandra.index.sasi.plan.Expression;
import org.apache.cassandra.index.sasi.plan.Expression.Op;
import org.apache.cassandra.index.sasi.utils.MappedBuffer;
import org.apache.cassandra.index.sasi.utils.RangeUnionIterator;
import org.apache.cassandra.index.sasi.utils.AbstractIterator;
import org.apache.cassandra.index.sasi.utils.MappedBuffer;
import org.apache.cassandra.index.sasi.utils.RangeIterator;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.index.sasi.utils.RangeUnionIterator;
import org.apache.cassandra.io.FSReadError;
import org.apache.cassandra.io.util.ChannelProxy;
import org.apache.cassandra.io.util.File;
@ -40,11 +52,6 @@ import org.apache.cassandra.io.util.FileUtils;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.FBUtilities;
import com.google.common.base.Function;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import com.google.common.collect.PeekingIterator;
import static org.apache.cassandra.index.sasi.disk.OnDiskBlock.SearchResult;
public class OnDiskIndex implements Iterable<OnDiskIndex.DataTerm>, Closeable
@ -144,7 +151,7 @@ public class OnDiskIndex implements Iterable<OnDiskIndex.DataTerm>, Closeable
FileChannel channel = index.newReadChannel();
indexSize = channel.size();
indexFile = new MappedBuffer(new ChannelProxy(indexPath, channel));
indexFile = new MappedBuffer(new ChannelProxy(index, channel));
}
catch (IOException e)
{

View File

@ -21,35 +21,37 @@ import java.nio.ByteBuffer;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.*;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Maps;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.io.util.File;
import org.apache.cassandra.concurrent.ExecutorPlus;
import org.apache.cassandra.schema.ColumnMetadata;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.compaction.OperationType;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.rows.Row;
import org.apache.cassandra.db.rows.Unfiltered;
import org.apache.cassandra.index.sasi.analyzer.AbstractAnalyzer;
import org.apache.cassandra.index.sasi.conf.ColumnIndex;
import org.apache.cassandra.index.sasi.utils.CombinedTermIterator;
import org.apache.cassandra.index.sasi.utils.TypeUtil;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.io.FSError;
import org.apache.cassandra.io.sstable.Descriptor;
import org.apache.cassandra.io.sstable.format.SSTableFlushObserver;
import org.apache.cassandra.io.sstable.SSTableFlushObserver;
import org.apache.cassandra.io.util.File;
import org.apache.cassandra.io.util.FileUtils;
import org.apache.cassandra.schema.ColumnMetadata;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.Pair;
import org.apache.cassandra.utils.concurrent.CountDownLatch;
import org.apache.cassandra.utils.concurrent.ImmediateFuture;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Maps;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory;
import static org.apache.cassandra.utils.Clock.Global.nanoTime;
import static org.apache.cassandra.utils.concurrent.CountDownLatch.newCountDownLatch;
@ -98,15 +100,24 @@ public class PerSSTableIndexWriter implements SSTableFlushObserver
indexes.put(entry.getKey(), newIndex(entry.getValue()));
}
@Override
public void begin()
{}
public void startPartition(DecoratedKey key, long curPosition)
@Override
public void startPartition(DecoratedKey key, long keyPosition, long KeyPositionForSASI)
{
currentKey = key;
currentKeyPosition = curPosition;
currentKeyPosition = KeyPositionForSASI;
}
@Override
public void staticRow(Row staticRow)
{
nextUnfilteredCluster(staticRow);
}
@Override
public void nextUnfilteredCluster(Unfiltered unfiltered)
{
if (!unfiltered.isRow())
@ -126,6 +137,7 @@ public class PerSSTableIndexWriter implements SSTableFlushObserver
});
}
@Override
public void complete()
{
if (isComplete)
@ -168,7 +180,7 @@ public class PerSSTableIndexWriter implements SSTableFlushObserver
protected class Index
{
@VisibleForTesting
protected final String outputFile;
protected final File outputFile;
private final ColumnIndex columnIndex;
private final AbstractAnalyzer analyzer;
@ -183,7 +195,7 @@ public class PerSSTableIndexWriter implements SSTableFlushObserver
public Index(ColumnIndex columnIndex)
{
this.columnIndex = columnIndex;
this.outputFile = descriptor.filenameFor(columnIndex.getComponent());
this.outputFile = descriptor.fileFor(columnIndex.getComponent());
this.analyzer = columnIndex.getAnalyzer();
this.segments = new HashSet<>();
this.maxMemorySize = maxMemorySize(columnIndex);
@ -244,15 +256,14 @@ public class PerSSTableIndexWriter implements SSTableFlushObserver
final OnDiskIndexBuilder builder = currentBuilder;
currentBuilder = newIndexBuilder();
final String segmentFile = filename(isFinal);
final File segmentFile = file(isFinal);
return () -> {
long start = nanoTime();
try
{
File index = new File(segmentFile);
return builder.finish(index) ? new OnDiskIndex(index, columnIndex.getValidator(), null) : null;
return builder.finish(segmentFile) ? new OnDiskIndex(segmentFile, columnIndex.getValidator(), null) : null;
}
catch (Exception | FSError e)
{
@ -310,13 +321,13 @@ public class PerSSTableIndexWriter implements SSTableFlushObserver
OnDiskIndexBuilder builder = newIndexBuilder();
builder.finish(Pair.create(combinedMin, combinedMax),
new File(outputFile),
outputFile,
new CombinedTermIterator(parts));
}
catch (Exception | FSError e)
{
logger.error("Failed to flush index {}.", outputFile, e);
FileUtils.delete(outputFile);
outputFile.tryDelete();
}
finally
{
@ -330,7 +341,7 @@ public class PerSSTableIndexWriter implements SSTableFlushObserver
if (part != null)
FileUtils.closeQuietly(part);
FileUtils.delete(outputFile + "_" + segment);
outputFile.withSuffix("_" + segment).tryDelete();
}
latch.decrement();
@ -348,9 +359,9 @@ public class PerSSTableIndexWriter implements SSTableFlushObserver
return new OnDiskIndexBuilder(keyValidator, columnIndex.getValidator(), columnIndex.getMode().mode);
}
public String filename(boolean isFinal)
public File file(boolean isFinal)
{
return outputFile + (isFinal ? "" : "_" + segmentNumber++);
return isFinal ? outputFile : outputFile.withSuffix("_" + segmentNumber++);
}
}

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.io;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
public interface IGenericSerializer<T, I extends DataInput, O extends DataOutput>
{
void serialize(T t, O out) throws IOException;
T deserialize(I in) throws IOException;
long serializedSize(T t);
}

View File

@ -22,7 +22,7 @@ import java.io.IOException;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
public interface ISerializer<T>
public interface ISerializer<T> extends IGenericSerializer<T, DataInputPlus, DataOutputPlus>
{
/**
* Serialize the specified type into the specified DataOutput instance.
@ -32,7 +32,8 @@ public interface ISerializer<T>
* @param out DataOutput into which serialization needs to happen.
* @throws java.io.IOException
*/
public void serialize(T t, DataOutputPlus out) throws IOException;
@Override
void serialize(T t, DataOutputPlus out) throws IOException;
/**
* Deserialize from the specified DataInput instance.
@ -40,11 +41,13 @@ public interface ISerializer<T>
* @throws IOException
* @return the type that was deserialized
*/
public T deserialize(DataInputPlus in) throws IOException;
@Override
T deserialize(DataInputPlus in) throws IOException;
public long serializedSize(T t);
@Override
long serializedSize(T t);
public default void skip(DataInputPlus in) throws IOException
default void skip(DataInputPlus in) throws IOException
{
deserialize(in);
}

View File

@ -29,7 +29,12 @@ import org.apache.cassandra.io.FSReadError;
import org.apache.cassandra.io.FSWriteError;
import org.apache.cassandra.io.sstable.CorruptSSTableException;
import org.apache.cassandra.io.sstable.metadata.MetadataCollector;
import org.apache.cassandra.io.util.*;
import org.apache.cassandra.io.util.ChecksumWriter;
import org.apache.cassandra.io.util.DataPosition;
import org.apache.cassandra.io.util.File;
import org.apache.cassandra.io.util.FileUtils;
import org.apache.cassandra.io.util.SequentialWriter;
import org.apache.cassandra.io.util.SequentialWriterOption;
import org.apache.cassandra.schema.CompressionParams;
import org.apache.cassandra.utils.ByteBufferUtil;
@ -66,14 +71,14 @@ public class CompressedSequentialWriter extends SequentialWriter
* Create CompressedSequentialWriter without digest file.
*
* @param file File to write
* @param offsetsPath File name to write compression metadata
* @param offsetsFile File to write compression metadata
* @param digestFile File to write digest
* @param option Write option (buffer size and type will be set the same as compression params)
* @param parameters Compression mparameters
* @param sstableMetadataCollector Metadata collector
*/
public CompressedSequentialWriter(File file,
String offsetsPath,
File offsetsFile,
File digestFile,
SequentialWriterOption option,
CompressionParams parameters,
@ -95,7 +100,7 @@ public class CompressedSequentialWriter extends SequentialWriter
maxCompressedLength = parameters.maxCompressedLength();
/* Index File (-CompressionInfo.db component) and it's header */
metadataWriter = CompressionMetadata.Writer.open(parameters, offsetsPath);
metadataWriter = CompressionMetadata.Writer.open(parameters, offsetsFile);
this.sstableMetadataCollector = sstableMetadataCollector;
crcMetadata = new ChecksumWriter(new DataOutputStream(Channels.newOutputStream(channel)));
@ -397,4 +402,4 @@ public class CompressedSequentialWriter extends SequentialWriter
this.nextChunkIndex = nextChunkIndex;
}
}
}
}

View File

@ -17,14 +17,11 @@
*/
package org.apache.cassandra.io.compress;
import java.nio.file.NoSuchFileException;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.EOFException;
import org.apache.cassandra.io.util.*;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.NoSuchFileException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
@ -39,18 +36,25 @@ import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.io.FSReadError;
import org.apache.cassandra.io.FSWriteError;
import org.apache.cassandra.io.IVersionedSerializer;
import org.apache.cassandra.io.sstable.Component;
import org.apache.cassandra.io.sstable.CorruptSSTableException;
import org.apache.cassandra.io.sstable.Descriptor;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.io.util.File;
import org.apache.cassandra.io.util.FileInputStreamPlus;
import org.apache.cassandra.io.util.FileOutputStreamPlus;
import org.apache.cassandra.io.util.Memory;
import org.apache.cassandra.io.util.SafeMemory;
import org.apache.cassandra.schema.CompressionParams;
import org.apache.cassandra.utils.concurrent.Transactional;
import org.apache.cassandra.utils.concurrent.Ref;
import org.apache.cassandra.utils.concurrent.Transactional;
import org.apache.cassandra.utils.concurrent.WrappedSharedCloseable;
/**
* Holds metadata about compressed file
* TODO extract interface ICompressionMetadata which will just provide non-resource properties
*/
public class CompressionMetadata
public class CompressionMetadata extends WrappedSharedCloseable
{
// dataLength can represent either the true length of the file
// or some shorter value, in the case we want to impose a shorter limit on readers
@ -59,42 +63,18 @@ public class CompressionMetadata
public final long compressedFileLength;
private final Memory chunkOffsets;
private final long chunkOffsetsSize;
public final String indexFilePath;
public final File chunksIndexFile;
public final CompressionParams parameters;
/**
* Create metadata about given compressed file including uncompressed data length, chunk size
* and list of the chunk offsets of the compressed data.
*
* This is an expensive operation! Don't create more than one for each
* sstable.
*
* @param dataFilePath Path to the compressed file
*
* @return metadata about given compressed file.
*/
public static CompressionMetadata create(String dataFilePath)
{
return createWithLength(dataFilePath, new File(dataFilePath).length());
}
public static CompressionMetadata createWithLength(String dataFilePath, long compressedLength)
{
return new CompressionMetadata(Descriptor.fromFilename(dataFilePath), compressedLength);
}
@VisibleForTesting
public CompressionMetadata(Descriptor desc, long compressedLength)
@SuppressWarnings("resource")
public static CompressionMetadata open(File chunksIndexFile, long compressedLength, boolean hasMaxCompressedSize)
{
this(desc.filenameFor(Component.COMPRESSION_INFO), compressedLength, desc.version.hasMaxCompressedLength());
}
CompressionParams parameters;
long dataLength;
Memory chunkOffsets;
@VisibleForTesting
public CompressionMetadata(String indexFilePath, long compressedLength, boolean hasMaxCompressedSize)
{
this.indexFilePath = indexFilePath;
try (FileInputStreamPlus stream = new File(indexFilePath).newInputStream())
try (FileInputStreamPlus stream = chunksIndexFile.newInputStream())
{
String compressorName = stream.readUTF();
int optionCount = stream.readInt();
@ -119,7 +99,6 @@ public class CompressionMetadata
}
dataLength = stream.readLong();
compressedFileLength = compressedLength;
chunkOffsets = readChunkOffsets(stream);
}
catch (FileNotFoundException | NoSuchFileException e)
@ -128,22 +107,39 @@ public class CompressionMetadata
}
catch (IOException e)
{
throw new CorruptSSTableException(e, indexFilePath);
throw new CorruptSSTableException(e, chunksIndexFile);
}
this.chunkOffsetsSize = chunkOffsets.size();
return new CompressionMetadata(chunksIndexFile, parameters, chunkOffsets, chunkOffsets.size(), dataLength, compressedLength);
}
// do not call this constructor directly, unless used in testing
@VisibleForTesting
public CompressionMetadata(String filePath, CompressionParams parameters, Memory offsets, long offsetsSize, long dataLength, long compressedLength)
public CompressionMetadata(File chunksIndexFile,
CompressionParams parameters,
Memory chunkOffsets,
long chunkOffsetsSize,
long dataLength,
long compressedFileLength)
{
this.indexFilePath = filePath;
super(chunkOffsets);
this.chunksIndexFile = chunksIndexFile;
this.parameters = parameters;
this.dataLength = dataLength;
this.compressedFileLength = compressedLength;
this.chunkOffsets = offsets;
this.chunkOffsetsSize = offsetsSize;
this.compressedFileLength = compressedFileLength;
this.chunkOffsets = chunkOffsets;
this.chunkOffsetsSize = chunkOffsetsSize;
}
private CompressionMetadata(CompressionMetadata copy)
{
super(copy);
this.chunksIndexFile = copy.chunksIndexFile;
this.parameters = copy.parameters;
this.dataLength = copy.dataLength;
this.compressedFileLength = copy.compressedFileLength;
this.chunkOffsets = copy.chunkOffsets;
this.chunkOffsetsSize = copy.chunkOffsetsSize;
}
public ICompressor compressor()
@ -170,11 +166,19 @@ public class CompressionMetadata
return chunkOffsets.size();
}
@Override
public void addTo(Ref.IdentityCollection identities)
{
super.addTo(identities);
identities.add(chunkOffsets);
}
@Override
public CompressionMetadata sharedCopy()
{
return new CompressionMetadata(this);
}
/**
* Read offsets of the individual chunks from the given input.
*
@ -182,7 +186,7 @@ public class CompressionMetadata
*
* @return collection of the chunk offsets.
*/
private Memory readChunkOffsets(DataInput input)
private static Memory readChunkOffsets(FileInputStreamPlus input)
{
final int chunkCount;
try
@ -193,7 +197,7 @@ public class CompressionMetadata
}
catch (IOException e)
{
throw new FSReadError(e, indexFilePath);
throw new FSReadError(e, input.file);
}
@SuppressWarnings("resource")
@ -217,10 +221,10 @@ public class CompressionMetadata
if (e instanceof EOFException)
{
String msg = String.format("Corrupted Index File %s: read %d but expected %d chunks.",
indexFilePath, i, chunkCount);
throw new CorruptSSTableException(new IOException(msg, e), indexFilePath);
input.file.path(), i, chunkCount);
throw new CorruptSSTableException(new IOException(msg, e), input.file);
}
throw new FSReadError(e, indexFilePath);
throw new FSReadError(e, input.file);
}
}
@ -236,11 +240,11 @@ public class CompressionMetadata
long idx = 8 * (position / parameters.chunkLength());
if (idx >= chunkOffsetsSize)
throw new CorruptSSTableException(new EOFException(), indexFilePath);
throw new CorruptSSTableException(new EOFException(), chunksIndexFile);
if (idx < 0)
throw new CorruptSSTableException(new IllegalArgumentException(String.format("Invalid negative chunk index %d with position %d", idx, position)),
indexFilePath);
chunksIndexFile);
long chunkOffset = chunkOffsets.getLong(idx);
long nextChunkOffset = (idx + 8 == chunkOffsetsSize)
@ -250,6 +254,28 @@ public class CompressionMetadata
return new Chunk(chunkOffset, (int) (nextChunkOffset - chunkOffset - 4)); // "4" bytes reserved for checksum
}
public long getDataOffsetForChunkOffset(long chunkOffset)
{
long l = 0;
long h = (chunkOffsetsSize >> 3) - 1;
long idx, offset;
while (l <= h)
{
idx = (l + h) >>> 1;
offset = chunkOffsets.getLong(idx << 3);
if (offset < chunkOffset)
l = idx + 1;
else if (offset > chunkOffset)
h = idx - 1;
else
return idx * parameters.chunkLength();
}
throw new IllegalArgumentException("No chunk with offset " + chunkOffset);
}
/**
* @param sections Collection of sections in uncompressed file. Should not contain sections that overlap each other.
* @return Total chunk size in bytes for given sections including checksum.
@ -314,16 +340,11 @@ public class CompressionMetadata
return offsets.toArray(new Chunk[offsets.size()]);
}
public void close()
{
chunkOffsets.close();
}
public static class Writer extends Transactional.AbstractTransactional implements Transactional
{
// path to the file
private final CompressionParams parameters;
private final String filePath;
private final File file;
private int maxCount = 100;
private SafeMemory offsets = new SafeMemory(maxCount * 8L);
private int count = 0;
@ -331,15 +352,15 @@ public class CompressionMetadata
// provided by user when setDescriptor
private long dataLength, chunkCount;
private Writer(CompressionParams parameters, String path)
private Writer(CompressionParams parameters, File file)
{
this.parameters = parameters;
filePath = path;
this.file = file;
}
public static Writer open(CompressionParams parameters, String path)
public static Writer open(CompressionParams parameters, File file)
{
return new Writer(parameters, path);
return new Writer(parameters, file);
}
public void addOffset(long offset)
@ -374,7 +395,7 @@ public class CompressionMetadata
}
catch (IOException e)
{
throw new FSWriteError(e, filePath);
throw new FSWriteError(e, file);
}
}
@ -386,6 +407,7 @@ public class CompressionMetadata
return this;
}
@Override
public void doPrepare()
{
assert chunkCount == count;
@ -400,7 +422,7 @@ public class CompressionMetadata
}
// flush the data to disk
try (FileOutputStreamPlus out = new FileOutputStreamPlus(filePath))
try (FileOutputStreamPlus out = file.newOutputStream(File.WriteMode.OVERWRITE))
{
writeHeader(out, dataLength, count);
for (int i = 0; i < count; i++)
@ -415,7 +437,7 @@ public class CompressionMetadata
}
catch (IOException e)
{
throw new FSWriteError(e, filePath);
throw new FSWriteError(e, file);
}
}
@ -434,7 +456,7 @@ public class CompressionMetadata
if (tCount < this.count)
compressedLength = tOffsets.getLong(tCount * 8L);
return new CompressionMetadata(filePath, parameters, tOffsets, tCount * 8L, dataLength, compressedLength);
return new CompressionMetadata(file, parameters, tOffsets, tCount * 8L, dataLength, compressedLength);
}
/**
@ -460,16 +482,19 @@ public class CompressionMetadata
count = chunkIndex;
}
@Override
protected Throwable doPostCleanup(Throwable failed)
{
return offsets.close(failed);
}
@Override
protected Throwable doCommit(Throwable accumulate)
{
return accumulate;
}
@Override
protected Throwable doAbort(Throwable accumulate)
{
return accumulate;
@ -494,6 +519,7 @@ public class CompressionMetadata
this.length = length;
}
@Override
public boolean equals(Object o)
{
if (this == o) return true;
@ -503,6 +529,7 @@ public class CompressionMetadata
return length == chunk.length && offset == chunk.offset;
}
@Override
public int hashCode()
{
int result = (int) (offset ^ (offset >>> 32));
@ -510,6 +537,7 @@ public class CompressionMetadata
return result;
}
@Override
public String toString()
{
return String.format("Chunk<offset: %d, length: %d>", offset, length);
@ -518,17 +546,20 @@ public class CompressionMetadata
static class ChunkSerializer implements IVersionedSerializer<Chunk>
{
@Override
public void serialize(Chunk chunk, DataOutputPlus out, int version) throws IOException
{
out.writeLong(chunk.offset);
out.writeInt(chunk.length);
}
@Override
public Chunk deserialize(DataInputPlus in, int version) throws IOException
{
return new Chunk(in.readLong(), in.readInt());
}
@Override
public long serializedSize(Chunk chunk, int version)
{
long size = TypeSizes.sizeof(chunk.offset);

View File

@ -0,0 +1,44 @@
/*
* 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.io.sstable;
import java.util.function.BiFunction;
import java.util.function.Function;
import org.apache.cassandra.io.sstable.format.SSTableReader;
public abstract class AbstractMetricsProviders<R extends SSTableReader> implements MetricsProviders
{
protected final <T extends Number> GaugeProvider<T> newGaugeProvider(String name, Function<Iterable<R>, T> combiner)
{
return new SimpleGaugeProvider<>(this::map, name, combiner);
}
protected final <T extends Number> GaugeProvider<T> newGaugeProvider(String name, T neutralValue, Function<R, T> extractor, BiFunction<T, T, T> combiner)
{
return new SimpleGaugeProvider<>(this::map, name, readers -> {
T total = neutralValue;
for (R reader : readers)
total = combiner.apply(total, extractor.apply(reader));
return total;
});
}
protected abstract R map(SSTableReader r);
}

View File

@ -0,0 +1,75 @@
/*
* 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.io.sstable;
import java.io.IOException;
import org.apache.cassandra.cache.IMeasurableMemory;
import org.apache.cassandra.db.DeletionTime;
import org.apache.cassandra.io.sstable.format.SSTableFormat;
import org.apache.cassandra.io.util.DataOutputPlus;
/**
* The base RowIndexEntry is not stored on disk, only specifies a position in the data file
*/
public abstract class AbstractRowIndexEntry implements IMeasurableMemory
{
public final long position;
public AbstractRowIndexEntry(long position)
{
this.position = position;
}
/**
* Row position in a data file
*/
public long getPosition()
{
return position;
}
/**
* @return true if this index entry contains the row-level tombstone and column summary. Otherwise,
* caller should fetch these from the row header.
*/
public boolean isIndexed()
{
return columnsIndexCount() > 1;
}
public DeletionTime deletionTime()
{
throw new UnsupportedOperationException();
}
public int columnsIndexCount()
{
return 0;
}
public abstract SSTableFormat<?, ?> getSSTableFormat();
/**
* Serialize this entry for key cache
*
* @param out the output stream for serialized entry
*/
public abstract void serializeForCache(DataOutputPlus out) throws IOException;
}

View File

@ -15,20 +15,34 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.db.columniterator;
package org.apache.cassandra.io.sstable;
import java.io.Closeable;
import java.io.IOException;
import java.util.Comparator;
import java.util.Iterator;
import java.util.NoSuchElementException;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.BufferClusteringBound;
import org.apache.cassandra.db.ClusteringBound;
import org.apache.cassandra.db.Columns;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.DeletionTime;
import org.apache.cassandra.db.RegularAndStaticColumns;
import org.apache.cassandra.db.Slice;
import org.apache.cassandra.db.Slices;
import org.apache.cassandra.db.UnfilteredDeserializer;
import org.apache.cassandra.db.UnfilteredValidation;
import org.apache.cassandra.db.filter.ColumnFilter;
import org.apache.cassandra.db.rows.*;
import org.apache.cassandra.io.sstable.CorruptSSTableException;
import org.apache.cassandra.io.sstable.IndexInfo;
import org.apache.cassandra.db.rows.DeserializationHelper;
import org.apache.cassandra.db.rows.EncodingStats;
import org.apache.cassandra.db.rows.RangeTombstoneBoundMarker;
import org.apache.cassandra.db.rows.RangeTombstoneMarker;
import org.apache.cassandra.db.rows.Row;
import org.apache.cassandra.db.rows.Rows;
import org.apache.cassandra.db.rows.Unfiltered;
import org.apache.cassandra.db.rows.UnfilteredRowIterator;
import org.apache.cassandra.db.rows.UnfilteredSerializer;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.io.util.DataPosition;
import org.apache.cassandra.io.util.FileDataInput;
import org.apache.cassandra.io.util.FileHandle;
import org.apache.cassandra.schema.TableMetadata;
@ -37,7 +51,7 @@ import org.apache.cassandra.utils.ByteBufferUtil;
import static org.apache.cassandra.utils.vint.VIntCoding.VIntOutOfRangeException;
public abstract class AbstractSSTableIterator implements UnfilteredRowIterator
public abstract class AbstractSSTableIterator<RIE extends AbstractRowIndexEntry> implements UnfilteredRowIterator
{
protected final SSTableReader sstable;
// We could use sstable.metadata(), but that can change during execution so it's good hygiene to grab an immutable instance
@ -62,7 +76,7 @@ public abstract class AbstractSSTableIterator implements UnfilteredRowIterator
protected AbstractSSTableIterator(SSTableReader sstable,
FileDataInput file,
DecoratedKey key,
RowIndexEntry indexEntry,
RIE indexEntry,
Slices slices,
ColumnFilter columnFilter,
FileHandle ifile)
@ -179,9 +193,9 @@ public abstract class AbstractSSTableIterator implements UnfilteredRowIterator
}
}
protected abstract Reader createReaderInternal(RowIndexEntry indexEntry, FileDataInput file, boolean shouldCloseFile);
protected abstract Reader createReaderInternal(RIE indexEntry, FileDataInput file, boolean shouldCloseFile);
private Reader createReader(RowIndexEntry indexEntry, FileDataInput file, boolean shouldCloseFile)
private Reader createReader(RIE indexEntry, FileDataInput file, boolean shouldCloseFile)
{
return slices.isEmpty() ? new NoRowsReader(file, shouldCloseFile)
: createReaderInternal(indexEntry, file, shouldCloseFile);
@ -258,7 +272,7 @@ public abstract class AbstractSSTableIterator implements UnfilteredRowIterator
e.addSuppressed(suppressed);
}
sstable.markSuspect();
throw new CorruptSSTableException(e, reader.file.getPath());
throw new CorruptSSTableException(e, reader.toString());
}
}
@ -289,21 +303,28 @@ public abstract class AbstractSSTableIterator implements UnfilteredRowIterator
catch (IOException e)
{
sstable.markSuspect();
throw new CorruptSSTableException(e, reader.file.getPath());
throw new CorruptSSTableException(e, reader.toString());
}
}
protected abstract class Reader implements Iterator<Unfiltered>
public interface Reader extends Iterator<Unfiltered>, Closeable {
void setForSlice(Slice slice) throws IOException;
void seekToPosition(long columnOffset) throws IOException;
}
public abstract class AbstractReader implements Reader
{
private final boolean shouldCloseFile;
public FileDataInput file;
protected UnfilteredDeserializer deserializer;
public UnfilteredDeserializer deserializer;
// Records the currently open range tombstone (if any)
protected DeletionTime openMarker = null;
public DeletionTime openMarker;
protected Reader(FileDataInput file, boolean shouldCloseFile)
protected AbstractReader(FileDataInput file, boolean shouldCloseFile)
{
this.file = file;
this.shouldCloseFile = shouldCloseFile;
@ -318,7 +339,7 @@ public abstract class AbstractSSTableIterator implements UnfilteredRowIterator
deserializer = UnfilteredDeserializer.create(metadata, file, sstable.header, helper);
}
protected void seekToPosition(long position) throws IOException
public void seekToPosition(long position) throws IOException
{
// This may be the first time we're actually looking into the file
if (file == null)
@ -329,6 +350,7 @@ public abstract class AbstractSSTableIterator implements UnfilteredRowIterator
else
{
file.seek(position);
deserializer.clearState();
}
}
@ -355,7 +377,7 @@ public abstract class AbstractSSTableIterator implements UnfilteredRowIterator
e.addSuppressed(suppressed);
}
sstable.markSuspect();
throw new CorruptSSTableException(e, reader.file.getPath());
throw new CorruptSSTableException(e, toString());
}
}
@ -376,7 +398,7 @@ public abstract class AbstractSSTableIterator implements UnfilteredRowIterator
e.addSuppressed(suppressed);
}
sstable.markSuspect();
throw new CorruptSSTableException(e, reader.file.getPath());
throw new CorruptSSTableException(e, toString());
}
}
@ -386,229 +408,178 @@ public abstract class AbstractSSTableIterator implements UnfilteredRowIterator
protected abstract boolean hasNextInternal() throws IOException;
protected abstract Unfiltered nextInternal() throws IOException;
@Override
public void close() throws IOException
{
if (shouldCloseFile && file != null)
file.close();
}
@Override
public String toString()
{
return file != null ? file.toString() : "null";
}
}
// Reader for when we have Slices.NONE but need to read static row or partition level deletion
private class NoRowsReader extends AbstractSSTableIterator.Reader
protected class ForwardReader extends AbstractReader
{
private NoRowsReader(FileDataInput file, boolean shouldCloseFile)
// The start of the current slice. This will be null as soon as we know we've passed that bound.
protected ClusteringBound<?> start;
// The end of the current slice. Will never be null.
protected ClusteringBound<?> end = BufferClusteringBound.TOP;
protected Unfiltered next; // the next element to return: this is computed by hasNextInternal().
protected boolean sliceDone; // set to true once we know we have no more result for the slice. This is in particular
// used by the indexed reader when we know we can't have results based on the index.
public ForwardReader(FileDataInput file, boolean shouldCloseFile)
{
super(file, shouldCloseFile);
}
public void setForSlice(Slice slice) throws IOException
{
return;
start = slice.start().isBottom() ? null : slice.start();
end = slice.end();
sliceDone = false;
next = null;
}
// Skip all data that comes before the currently set slice.
// Return what should be returned at the end of this, or null if nothing should.
private Unfiltered handlePreSliceData() throws IOException
{
assert deserializer != null;
// Note that the following comparison is not strict. The reason is that the only cases
// where it can be == is if the "next" is a RT start marker (either a '[' of a ')[' boundary),
// and if we had a strict inequality and an open RT marker before this, we would issue
// the open marker first, and then return then next later, which would send in the
// stream both '[' (or '(') and then ')[' for the same clustering value, which is wrong.
// By using a non-strict inequality, we avoid that problem (if we do get ')[' for the same
// clustering value than the slice, we'll simply record it in 'openMarker').
while (deserializer.hasNext() && deserializer.compareNextTo(start) <= 0)
{
if (deserializer.nextIsRow())
deserializer.skipNext();
else
updateOpenMarker((RangeTombstoneMarker)deserializer.readNext());
}
ClusteringBound<?> sliceStart = start;
start = null;
// We've reached the beginning of our queried slice. If we have an open marker
// we should return that first.
if (openMarker != null)
return new RangeTombstoneBoundMarker(sliceStart, openMarker);
return null;
}
// Compute the next element to return, assuming we're in the middle to the slice
// and the next element is either in the slice, or just after it. Returns null
// if we're done with the slice.
protected Unfiltered computeNext() throws IOException
{
assert deserializer != null;
while (true)
{
// We use a same reasoning as in handlePreSliceData regarding the strictness of the inequality below.
// We want to exclude deserialized unfiltered equal to end, because 1) we won't miss any rows since those
// woudn't be equal to a slice bound and 2) a end bound can be equal to a start bound
// (EXCL_END(x) == INCL_START(x) for instance) and in that case we don't want to return start bound because
// it's fundamentally excluded. And if the bound is a end (for a range tombstone), it means it's exactly
// our slice end, but in that case we will properly close the range tombstone anyway as part of our "close
// an open marker" code in hasNextInterna
if (!deserializer.hasNext() || deserializer.compareNextTo(end) >= 0)
return null;
Unfiltered next = deserializer.readNext();
UnfilteredValidation.maybeValidateUnfiltered(next, metadata(), key, sstable);
// We may get empty row for the same reason expressed on UnfilteredSerializer.deserializeOne.
if (next.isEmpty())
continue;
if (next.kind() == Unfiltered.Kind.RANGE_TOMBSTONE_MARKER)
updateOpenMarker((RangeTombstoneMarker) next);
return next;
}
}
protected boolean hasNextInternal() throws IOException
{
if (next != null)
return true;
if (sliceDone)
return false;
if (start != null)
{
Unfiltered unfiltered = handlePreSliceData();
if (unfiltered != null)
{
next = unfiltered;
return true;
}
}
next = computeNext();
if (next != null)
return true;
// for current slice, no data read from deserialization
sliceDone = true;
// If we have an open marker, we should not close it, there could be more slices
if (openMarker != null)
{
next = new RangeTombstoneBoundMarker(end, openMarker);
return true;
}
return false;
}
protected Unfiltered nextInternal() throws IOException
{
if (!hasNextInternal())
throw new NoSuchElementException();
Unfiltered toReturn = next;
next = null;
return toReturn;
}
}
// Reader for when we have Slices.NONE but need to read static row or partition level deletion
private class NoRowsReader extends AbstractReader
{
private NoRowsReader(FileDataInput file, boolean shouldCloseFile)
{
super(file, shouldCloseFile);
}
@Override
public void setForSlice(Slice slice)
{
// no-op
}
@Override
public boolean hasNextInternal()
{
return false;
}
@Override
protected Unfiltered nextInternal() throws IOException
{
throw new NoSuchElementException();
}
}
// Used by indexed readers to store where they are of the index.
public static class IndexState implements AutoCloseable
{
private final Reader reader;
private final ClusteringComparator comparator;
private final RowIndexEntry indexEntry;
private final RowIndexEntry.IndexInfoRetriever indexInfoRetriever;
private final boolean reversed;
private int currentIndexIdx;
// Marks the beginning of the block corresponding to currentIndexIdx.
private DataPosition mark;
public IndexState(Reader reader, ClusteringComparator comparator, RowIndexEntry indexEntry, boolean reversed, FileHandle indexFile)
{
this.reader = reader;
this.comparator = comparator;
this.indexEntry = indexEntry;
this.indexInfoRetriever = indexEntry.openWithIndex(indexFile);
this.reversed = reversed;
this.currentIndexIdx = reversed ? indexEntry.columnsIndexCount() : -1;
}
public boolean isDone()
{
return reversed ? currentIndexIdx < 0 : currentIndexIdx >= indexEntry.columnsIndexCount();
}
// Sets the reader to the beginning of blockIdx.
public void setToBlock(int blockIdx) throws IOException
{
if (blockIdx >= 0 && blockIdx < indexEntry.columnsIndexCount())
{
reader.seekToPosition(columnOffset(blockIdx));
mark = reader.file.mark();
reader.deserializer.clearState();
}
currentIndexIdx = blockIdx;
reader.openMarker = blockIdx > 0 ? index(blockIdx - 1).endOpenMarker : null;
}
private long columnOffset(int i) throws IOException
{
return indexEntry.position + index(i).offset;
}
public int blocksCount()
{
return indexEntry.columnsIndexCount();
}
// Update the block idx based on the current reader position if we're past the current block.
// This only makes sense for forward iteration (for reverse ones, when we reach the end of a block we
// should seek to the previous one, not update the index state and continue).
public void updateBlock() throws IOException
{
assert !reversed;
// If we get here with currentBlockIdx < 0, it means setToBlock() has never been called, so it means
// we're about to read from the beginning of the partition, but haven't "prepared" the IndexState yet.
// Do so by setting us on the first block.
if (currentIndexIdx < 0)
{
setToBlock(0);
return;
}
while (currentIndexIdx + 1 < indexEntry.columnsIndexCount() && isPastCurrentBlock())
{
reader.openMarker = currentIndex().endOpenMarker;
++currentIndexIdx;
// We have to set the mark, and we have to set it at the beginning of the block. So if we're not at the beginning of the block, this forces us to a weird seek dance.
// This can only happen when reading old file however.
long startOfBlock = columnOffset(currentIndexIdx);
long currentFilePointer = reader.file.getFilePointer();
if (startOfBlock == currentFilePointer)
{
mark = reader.file.mark();
}
else
{
reader.seekToPosition(startOfBlock);
mark = reader.file.mark();
reader.seekToPosition(currentFilePointer);
}
}
}
// Check if we've crossed an index boundary (based on the mark on the beginning of the index block).
public boolean isPastCurrentBlock() throws IOException
{
assert reader.deserializer != null;
return reader.file.bytesPastMark(mark) >= currentIndex().width;
}
public int currentBlockIdx()
{
return currentIndexIdx;
}
public IndexInfo currentIndex() throws IOException
{
return index(currentIndexIdx);
}
public IndexInfo index(int i) throws IOException
{
return indexInfoRetriever.columnsIndex(i);
}
// Finds the index of the first block containing the provided bound, starting at the provided index.
// Will be -1 if the bound is before any block, and blocksCount() if it is after every block.
public int findBlockIndex(ClusteringBound<?> bound, int fromIdx) throws IOException
{
if (bound.isBottom())
return -1;
if (bound.isTop())
return blocksCount();
return indexFor(bound, fromIdx);
}
public int indexFor(ClusteringPrefix<?> name, int lastIndex) throws IOException
{
IndexInfo target = new IndexInfo(name, name, 0, 0, null);
/*
Take the example from the unit test, and say your index looks like this:
[0..5][10..15][20..25]
and you look for the slice [13..17].
When doing forward slice, we are doing a binary search comparing 13 (the start of the query)
to the lastName part of the index slot. You'll end up with the "first" slot, going from left to right,
that may contain the start.
When doing a reverse slice, we do the same thing, only using as a start column the end of the query,
i.e. 17 in this example, compared to the firstName part of the index slots. bsearch will give us the
first slot where firstName > start ([20..25] here), so we subtract an extra one to get the slot just before.
*/
int startIdx = 0;
int endIdx = indexEntry.columnsIndexCount() - 1;
if (reversed)
{
if (lastIndex < endIdx)
{
endIdx = lastIndex;
}
}
else
{
if (lastIndex > 0)
{
startIdx = lastIndex;
}
}
int index = binarySearch(target, comparator.indexComparator(reversed), startIdx, endIdx);
return (index < 0 ? -index - (reversed ? 2 : 1) : index);
}
private int binarySearch(IndexInfo key, Comparator<IndexInfo> c, int low, int high) throws IOException
{
while (low <= high)
{
int mid = (low + high) >>> 1;
IndexInfo midVal = index(mid);
int cmp = c.compare(midVal, key);
if (cmp < 0)
low = mid + 1;
else if (cmp > 0)
high = mid - 1;
else
return mid;
}
return -(low + 1);
}
@Override
public String toString()
{
return String.format("IndexState(indexSize=%d, currentBlock=%d, reversed=%b)", indexEntry.columnsIndexCount(), currentIndexIdx, reversed);
}
@Override
public void close() throws IOException
{
indexInfoRetriever.close();
}
}
}

View File

@ -18,8 +18,8 @@
package org.apache.cassandra.io.sstable;
import java.io.IOException;
import java.io.Closeable;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
@ -27,7 +27,9 @@ import java.util.Collections;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Stream;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.RegularAndStaticColumns;
import org.apache.cassandra.db.SerializationHeader;
import org.apache.cassandra.db.partitions.PartitionUpdate;
import org.apache.cassandra.db.rows.EncodingStats;
import org.apache.cassandra.io.sstable.format.SSTableFormat;
@ -64,7 +66,7 @@ abstract class AbstractSSTableSimpleWriter implements Closeable
this.makeRangeAware = makeRangeAware;
}
protected SSTableTxnWriter createWriter() throws IOException
protected SSTableTxnWriter createWriter(SSTable.Owner owner) throws IOException
{
SerializationHeader header = new SerializationHeader(true, metadata.get(), columns, EncodingStats.NO_STATS);
@ -79,7 +81,8 @@ abstract class AbstractSSTableSimpleWriter implements Closeable
false,
0,
header,
Collections.emptySet());
Collections.emptySet(),
owner);
}
private static Descriptor createDescriptor(File directory, final String keyspace, final String columnFamily, final SSTableFormat.Type fmt) throws IOException
@ -95,7 +98,7 @@ abstract class AbstractSSTableSimpleWriter implements Closeable
try (Stream<Path> existingPaths = Files.list(directory.toPath()))
{
Stream<SSTableId> existingIds = existingPaths.map(File::new)
.map(SSTable::tryDescriptorFromFilename)
.map(SSTable::tryDescriptorFromFile)
.filter(d -> d != null && d.cfname.equals(columnFamily))
.map(d -> d.id);

View File

@ -17,11 +17,18 @@
*/
package org.apache.cassandra.io.sstable;
import java.util.EnumSet;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.regex.Pattern;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.collect.Iterables;
import org.apache.cassandra.io.sstable.format.SSTableFormat;
import org.apache.cassandra.io.sstable.format.SSTableFormat.Components.Types;
/**
* SSTables are made up of multiple components in separate files. Components are
@ -32,84 +39,149 @@ public class Component
{
public static final char separator = '-';
final static EnumSet<Type> TYPES = EnumSet.allOf(Type.class);
/**
* WARNING: Be careful while changing the names or string representation of the enum
* members. Streaming code depends on the names during streaming (Ref: CASSANDRA-14556).
*/
public enum Type
public final static class Type
{
// the base data for an sstable: the remaining components can be regenerated
// based on the data component
DATA("Data.db"),
// index of the row keys with pointers to their positions in the data file
PRIMARY_INDEX("Index.db"),
// serialized bloom filter for the row keys in the sstable
FILTER("Filter.db"),
// file to hold information about uncompressed data length, chunk offsets etc.
COMPRESSION_INFO("CompressionInfo.db"),
// statistical metadata about the content of the sstable
STATS("Statistics.db"),
// holds CRC32 checksum of the data file
DIGEST("Digest.crc32"),
// holds the CRC32 for chunks in an a uncompressed file.
CRC("CRC.db"),
// holds SSTable Index Summary (sampling of Index component)
SUMMARY("Summary.db"),
// table of contents, stores the list of all components for the sstable
TOC("TOC.txt"),
// built-in secondary index (may be multiple per sstable)
SECONDARY_INDEX("SI_.*.db"),
// custom component, used by e.g. custom compaction strategy
CUSTOM(null);
private final static CopyOnWriteArrayList<Type> typesCollector = new CopyOnWriteArrayList<>();
final String repr;
public static final List<Type> all = Collections.unmodifiableList(typesCollector);
Type(String repr)
public final int id;
public final String name;
public final String repr;
private final Component singleton;
@SuppressWarnings("rawtypes")
public final Class<? extends SSTableFormat> formatClass;
/**
* Creates a new non-singleton type and registers it a global type registry - see {@link #registerType(Type)}.
*
* @param name type name, must be unique for this and all parent formats
* @param repr the regular expression to be used to recognize a name represents this type
* @param formatClass format class for which this type is defined for
*/
public static Type create(String name, String repr, Class<? extends SSTableFormat<?, ?>> formatClass)
{
return new Type(name, repr, false, formatClass);
}
/**
* Creates a new singleton type and registers it in a global type registry - see {@link #registerType(Type)}.
*
* @param name type name, must be unique for this and all parent formats
* @param repr the regular expression to be used to recognize a name represents this type
* @param formatClass format class for which this type is defined for
*/
public static Type createSingleton(String name, String repr, Class<? extends SSTableFormat<?, ?>> formatClass)
{
return new Type(name, repr, true, formatClass);
}
private Type(String name, String repr, boolean isSingleton, Class<? extends SSTableFormat<?, ?>> formatClass)
{
this.name = Objects.requireNonNull(name);
this.repr = repr;
this.id = typesCollector.size();
this.formatClass = formatClass == null ? SSTableFormat.class : formatClass;
this.singleton = isSingleton ? new Component(this) : null;
registerType(this);
}
/**
* If you have two formats registered, they may both define a type say `INDEX`. It is allowed even though
* they have the same name because they are not in the same branch. Though, we cannot let a custom type
* define a type `TOC` which is declared on the top level.
* So, e.g. given we have `TOC@SSTableFormat`, and `BigFormat` tries to define `TOC@BigFormat`, we should
* forbid that; but, given we have `INDEX@BigFormat`, we should allow to define `INDEX@TrieFormat` as those
* types are be distinguishable via format type.
*
* @param type a type to be registered
*/
private static void registerType(Type type)
{
synchronized (typesCollector)
{
if (typesCollector.stream().anyMatch(t -> (Objects.equals(t.name, type.name) || Objects.equals(t.repr, type.repr)) && (t.formatClass.isAssignableFrom(type.formatClass))))
throw new AssertionError("Type named " + type.name + " is already registered");
typesCollector.add(type);
}
}
@VisibleForTesting
public static Type fromRepresentation(String repr)
public static Type fromRepresentation(String repr, SSTableFormat.Type formatType)
{
for (Type type : TYPES)
for (Type type : Type.all)
{
if (type.repr != null && Pattern.matches(type.repr, repr))
if (type.repr != null && Pattern.matches(type.repr, repr) && type.formatClass.isAssignableFrom(formatType.info.getClass()))
return type;
}
return CUSTOM;
return Types.CUSTOM;
}
public static Component createComponent(String repr, SSTableFormat.Type formatType)
{
Type type = fromRepresentation(repr, formatType);
if (type.singleton != null)
return type.singleton;
else
return new Component(type, repr);
}
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Type type = (Type) o;
return id == type.id;
}
@Override
public int hashCode()
{
return id;
}
@Override
public String toString()
{
return name;
}
public Component getSingleton()
{
return Objects.requireNonNull(singleton);
}
public Component createComponent(String repr)
{
Preconditions.checkArgument(singleton == null);
return new Component(this, repr);
}
}
// singleton components for types that don't need ids
public final static Component DATA = new Component(Type.DATA);
public final static Component PRIMARY_INDEX = new Component(Type.PRIMARY_INDEX);
public final static Component FILTER = new Component(Type.FILTER);
public final static Component COMPRESSION_INFO = new Component(Type.COMPRESSION_INFO);
public final static Component STATS = new Component(Type.STATS);
public final static Component DIGEST = new Component(Type.DIGEST);
public final static Component CRC = new Component(Type.CRC);
public final static Component SUMMARY = new Component(Type.SUMMARY);
public final static Component TOC = new Component(Type.TOC);
public final Type type;
public final String name;
public final int hashCode;
public Component(Type type)
private Component(Type type)
{
this(type, type.repr);
assert type != Type.CUSTOM;
}
public Component(Type type, String name)
private Component(Type type, String name)
{
assert name != null : "Component name cannot be null";
this.type = type;
this.name = name;
this.hashCode = Objects.hashCode(type, name);
this.hashCode = Objects.hash(type, name);
}
/**
@ -127,26 +199,24 @@ public class Component
* @return the component corresponding to {@code name}. Note that this always return a component as an unrecognized
* name is parsed into a CUSTOM component.
*/
public static Component parse(String name)
public static Component parse(String name, SSTableFormat.Type formatType)
{
Type type = Type.fromRepresentation(name);
return Type.createComponent(name, formatType);
}
// Build (or retrieve singleton for) the component object
switch (type)
{
case DATA: return Component.DATA;
case PRIMARY_INDEX: return Component.PRIMARY_INDEX;
case FILTER: return Component.FILTER;
case COMPRESSION_INFO: return Component.COMPRESSION_INFO;
case STATS: return Component.STATS;
case DIGEST: return Component.DIGEST;
case CRC: return Component.CRC;
case SUMMARY: return Component.SUMMARY;
case TOC: return Component.TOC;
case SECONDARY_INDEX: return new Component(Type.SECONDARY_INDEX, name);
case CUSTOM: return new Component(Type.CUSTOM, name);
default: throw new AssertionError();
}
public static Iterable<Component> getSingletonsFor(SSTableFormat<?, ?> format)
{
return Iterables.transform(Iterables.filter(Type.all, t -> t.singleton != null && t.formatClass.isAssignableFrom(format.getClass())), t -> t.singleton);
}
public static Iterable<Component> getSingletonsFor(Class<? extends SSTableFormat<?, ?>> formatClass)
{
return Iterables.transform(Iterables.filter(Type.all, t -> t.singleton != null && t.formatClass.isAssignableFrom(formatClass)), t -> t.singleton);
}
public boolean isValidFor(Descriptor descriptor)
{
return type.formatClass.isAssignableFrom(descriptor.formatType.info.getClass());
}
@Override
@ -162,8 +232,8 @@ public class Component
return true;
if (!(o instanceof Component))
return false;
Component that = (Component)o;
return this.type == that.type && this.name.equals(that.name);
Component that = (Component) o;
return this.hashCode == that.hashCode && this.type == that.type && this.name.equals(that.name);
}
@Override

View File

@ -17,14 +17,17 @@
*/
package org.apache.cassandra.io.sstable;
import java.util.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Objects;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -36,6 +39,8 @@ import org.apache.cassandra.io.sstable.metadata.MetadataSerializer;
import org.apache.cassandra.io.util.File;
import org.apache.cassandra.utils.Pair;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.apache.cassandra.io.sstable.Component.separator;
import static org.apache.cassandra.utils.TimeUUID.Generator.nextTimeUUID;
@ -84,6 +89,8 @@ public class Descriptor
public final SSTableId id;
public final SSTableFormat.Type formatType;
private final int hashCode;
private final String prefix;
private final File baseFile;
/**
* A descriptor that assumes CURRENT_VERSION.
@ -110,7 +117,13 @@ public class Descriptor
public Descriptor(Version version, File directory, String ksname, String cfname, SSTableId id, SSTableFormat.Type formatType)
{
assert version != null && directory != null && ksname != null && cfname != null && formatType.info.getLatestVersion().getClass().equals(version.getClass());
checkNotNull(version);
checkNotNull(directory);
checkNotNull(ksname);
checkNotNull(cfname);
checkNotNull(formatType);
checkArgument(version.getSSTableFormat() == formatType.info);
this.version = version;
this.directory = directory.toCanonical();
this.ksname = ksname;
@ -118,41 +131,53 @@ public class Descriptor
this.id = id;
this.formatType = formatType;
StringBuilder buf = new StringBuilder();
appendFileName(buf);
this.prefix = buf.toString();
this.baseFile = new File(directory.toPath().resolve(prefix));
// directory is unnecessary for hashCode, and for simulator consistency we do not include it
hashCode = Objects.hashCode(version, id, ksname, cfname, formatType);
}
public String tmpFilenameFor(Component component)
private String tmpFilenameFor(Component component)
{
return filenameFor(component) + TMP_EXT;
return fileFor(component) + TMP_EXT;
}
/**
* @return a unique temporary file name for given component during entire-sstable-streaming.
*/
public String tmpFilenameForStreaming(Component component)
public File tmpFileFor(Component component)
{
return new File(directory.toPath().resolve(tmpFilenameFor(component)));
}
private String tmpFilenameForStreaming(Component component)
{
// Use UUID to handle concurrent streamings on the same sstable.
// TMP_EXT allows temp file to be removed by {@link ColumnFamilyStore#scrubDataDirectories}
return String.format("%s.%s%s", filenameFor(component), nextTimeUUID(), TMP_EXT);
}
public String filenameFor(Component component)
/**
* @return a unique temporary file name for given component during entire-sstable-streaming.
*/
public File tmpFileForStreaming(Component component)
{
return baseFilename() + separator + component.name();
return new File(directory.toPath().resolve(tmpFilenameForStreaming(component)));
}
private String filenameFor(Component component)
{
return prefix + separator + component.name();
}
public File fileFor(Component component)
{
return new File(filenameFor(component));
return new File(directory.toPath().resolve(filenameFor(component)));
}
public String baseFilename()
public File baseFile()
{
StringBuilder buff = new StringBuilder();
buff.append(directory).append(File.pathSeparator());
appendFileName(buff);
return buff.toString();
return baseFile;
}
private void appendFileName(StringBuilder buff)
@ -175,7 +200,7 @@ public class Descriptor
return buff.toString();
}
public SSTableFormat getFormat()
public SSTableFormat<?, ?> getFormat()
{
return formatType.info;
}
@ -193,29 +218,28 @@ public class Descriptor
return ret;
}
/**
* Returns the set of components consisting of the provided mandatory components and those optional components
* for which the corresponding file exists.
*/
public Set<Component> getComponents(Set<Component> mandatory, Set<Component> optional)
{
ImmutableSet.Builder<Component> builder = ImmutableSet.builder();
builder.addAll(mandatory);
for (Component component : optional)
{
if (fileFor(component).exists())
builder.add(component);
}
return builder.build();
}
public static boolean isValidFile(File file)
{
String filename = file.name();
return filename.endsWith(".db") && !LEGACY_TMP_REGEX.matcher(filename).matches();
}
/**
* Parse a sstable filename into a Descriptor.
* <p>
* This is a shortcut for {@code fromFilename(new File(filename))}.
*
* @param filename the filename to a sstable component.
* @return the descriptor for the parsed file.
*
* @throws IllegalArgumentException if the provided {@code file} does point to a valid sstable filename. This could
* mean either that the filename doesn't look like a sstable file, or that it is for an old and unsupported
* versions.
*/
public static Descriptor fromFilename(String filename)
{
return fromFilenameWithComponent(new File(filename), false).left;
}
/**
* Parse a sstable filename into a Descriptor.
* <p>
@ -233,9 +257,9 @@ public class Descriptor
* mean either that the filename doesn't look like a sstable file, or that it is for an old and unsupported
* versions.
*/
public static Descriptor fromFilename(File file)
public static Descriptor fromFile(File file)
{
return fromFilenameWithComponent(file).left;
return fromFileWithComponent(file).left;
}
/**
@ -249,12 +273,12 @@ public class Descriptor
* mean either that the filename doesn't look like a sstable file, or that it is for an old and unsupported
* versions.
*/
public static Pair<Descriptor, Component> fromFilenameWithComponent(File file)
public static Pair<Descriptor, Component> fromFileWithComponent(File file)
{
return fromFilenameWithComponent(file, true);
return fromFileWithComponent(file, true);
}
public static Pair<Descriptor, Component> fromFilenameWithComponent(File file, boolean validateDirs)
public static Pair<Descriptor, Component> fromFileWithComponent(File file, boolean validateDirs)
{
// We need to extract the keyspace and table names from the parent directories, so make sure we deal with the
// absolute path.
@ -307,11 +331,11 @@ public class Descriptor
* mean either that the filename doesn't look like a sstable file, or that it is for an old and unsupported
* versions.
*/
public static Pair<Descriptor, Component> fromFilenameWithComponent(File file, String keyspace, String table)
public static Pair<Descriptor, Component> fromFileWithComponent(File file, String keyspace, String table)
{
if (null == keyspace || null == table)
{
return fromFilenameWithComponent(file);
return fromFileWithComponent(file);
}
SSTableInfo info = validateAndExtractInfo(file);
@ -354,14 +378,14 @@ public class Descriptor
SSTableFormat.Type format;
try
{
format = SSTableFormat.Type.validate(formatString);
format = SSTableFormat.Type.getByName(formatString);
}
catch (RuntimeException e)
{
throw invalidSSTable(name, "unknown 'format' part (%s)", formatString);
}
Component component = Component.parse(tokens.get(3));
Component component = Component.parse(tokens.get(3), format);
Version version = format.info.getVersion(versionString);
if (!version.isCompatible())
@ -412,10 +436,21 @@ public class Descriptor
return version.isCompatible();
}
public Set<Component> discoverComponents()
{
Set<Component> components = Sets.newHashSetWithExpectedSize(Component.Type.all.size());
for (Component component : Component.getSingletonsFor(formatType.info))
{
if (fileFor(component).exists())
components.add(component);
}
return components;
}
@Override
public String toString()
{
return baseFilename();
return baseFile().absolutePath();
}
@Override
@ -426,6 +461,8 @@ public class Descriptor
if (!(o instanceof Descriptor))
return false;
Descriptor that = (Descriptor)o;
if (this.hashCode != that.hashCode)
return false;
return that.directory.equals(this.directory)
&& that.id.equals(this.id)
&& that.ksname.equals(this.ksname)

View File

@ -0,0 +1,78 @@
/*
* 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.io.sstable;
import java.util.Set;
import com.google.common.collect.ImmutableSet;
import org.apache.cassandra.db.partitions.AbstractUnfilteredPartitionIterator;
import org.apache.cassandra.db.rows.UnfilteredRowIterator;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.schema.TableMetadata;
public class EmptySSTableScanner extends AbstractUnfilteredPartitionIterator implements ISSTableScanner
{
private final SSTableReader sstable;
public EmptySSTableScanner(SSTableReader sstable)
{
this.sstable = sstable;
}
public long getBytesScanned()
{
return 0;
}
public long getLengthInBytes()
{
return 0;
}
public long getCompressedLengthInBytes()
{
return 0;
}
public Set<SSTableReader> getBackingSSTables()
{
return ImmutableSet.of(sstable);
}
public long getCurrentPosition()
{
return 0;
}
public TableMetadata metadata()
{
return sstable.metadata();
}
public boolean hasNext()
{
return false;
}
public UnfilteredRowIterator next()
{
return null;
}
}

View File

@ -0,0 +1,39 @@
/*
* 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.io.sstable;
import com.codahale.metrics.Gauge;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.Keyspace;
public abstract class GaugeProvider<T extends Number>
{
public final String name;
public GaugeProvider(String name)
{
this.name = name;
}
public abstract Gauge<T> getTableGauge(ColumnFamilyStore cfs);
public abstract Gauge<T> getKeyspaceGauge(Keyspace keyspace);
public abstract Gauge<T> getGlobalGauge();
}

View File

@ -0,0 +1,62 @@
/*
* 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.io.sstable;
import org.apache.cassandra.config.Config;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.io.util.DiskOptimizationStrategy;
import org.apache.cassandra.io.util.SequentialWriterOption;
public class IOOptions
{
public static IOOptions fromDatabaseDescriptor()
{
return new IOOptions(DatabaseDescriptor.getDiskOptimizationStrategy(),
DatabaseDescriptor.getDiskAccessMode(),
DatabaseDescriptor.getIndexAccessMode(),
DatabaseDescriptor.getDiskOptimizationEstimatePercentile(),
SequentialWriterOption.newBuilder()
.trickleFsync(DatabaseDescriptor.getTrickleFsync())
.trickleFsyncByteInterval(DatabaseDescriptor.getTrickleFsyncIntervalInKiB() * 1024)
.build(),
DatabaseDescriptor.getFlushCompression());
}
public final DiskOptimizationStrategy diskOptimizationStrategy;
public final Config.DiskAccessMode defaultDiskAccessMode;
public final Config.DiskAccessMode indexDiskAccessMode;
public final double diskOptimizationEstimatePercentile;
public final SequentialWriterOption writerOptions;
public final Config.FlushCompression flushCompression;
public IOOptions(DiskOptimizationStrategy diskOptimizationStrategy,
Config.DiskAccessMode defaultDiskAccessMode,
Config.DiskAccessMode indexDiskAccessMode,
double diskOptimizationEstimatePercentile,
SequentialWriterOption writerOptions,
Config.FlushCompression flushCompression)
{
this.diskOptimizationStrategy = diskOptimizationStrategy;
this.defaultDiskAccessMode = defaultDiskAccessMode;
this.indexDiskAccessMode = indexDiskAccessMode;
this.diskOptimizationEstimatePercentile = diskOptimizationEstimatePercentile;
this.writerOptions = writerOptions;
this.flushCompression = flushCompression;
}
}

View File

@ -0,0 +1,129 @@
/*
* 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.io.sstable;
import java.util.StringJoiner;
import com.google.common.annotations.VisibleForTesting;
import org.apache.cassandra.db.compaction.CompactionInfo;
import org.apache.cassandra.utils.Closeable;
public interface IScrubber extends Closeable
{
void scrub();
void close();
CompactionInfo.Holder getScrubInfo();
@VisibleForTesting
ScrubResult scrubWithResult();
static Options.Builder options()
{
return new Options.Builder();
}
final class ScrubResult
{
public final int goodPartitions;
public final int badPartitions;
public final int emptyPartitions;
public ScrubResult(int goodPartitions, int badPartitions, int emptyPartitions)
{
this.goodPartitions = goodPartitions;
this.badPartitions = badPartitions;
this.emptyPartitions = emptyPartitions;
}
}
class Options
{
public final boolean checkData;
public final boolean reinsertOverflowedTTLRows;
public final boolean skipCorrupted;
private Options(boolean checkData, boolean reinsertOverflowedTTLRows, boolean skipCorrupted)
{
this.checkData = checkData;
this.reinsertOverflowedTTLRows = reinsertOverflowedTTLRows;
this.skipCorrupted = skipCorrupted;
}
@Override
public String toString()
{
return new StringJoiner(", ", Options.class.getSimpleName() + "[", "]")
.add("checkData=" + checkData)
.add("reinsertOverflowedTTLRows=" + reinsertOverflowedTTLRows)
.add("skipCorrupted=" + skipCorrupted)
.toString();
}
public static class Builder
{
private boolean checkData = false;
private boolean reinsertOverflowedTTLRows = false;
private boolean skipCorrupted = false;
public Builder checkData()
{
this.checkData = true;
return this;
}
public Builder checkData(boolean checkData)
{
this.checkData = checkData;
return this;
}
public Builder reinsertOverflowedTTLRows()
{
this.reinsertOverflowedTTLRows = true;
return this;
}
public Builder reinsertOverflowedTTLRows(boolean reinsertOverflowedTTLRows)
{
this.reinsertOverflowedTTLRows = reinsertOverflowedTTLRows;
return this;
}
public Builder skipCorrupted()
{
this.skipCorrupted = true;
return this;
}
public Builder skipCorrupted(boolean skipCorrupted)
{
this.skipCorrupted = skipCorrupted;
return this;
}
public Options build()
{
return new Options(checkData, reinsertOverflowedTTLRows, skipCorrupted);
}
}
}
}

View File

@ -0,0 +1,153 @@
/*
* 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.io.sstable;
import java.io.Closeable;
import java.util.Collection;
import java.util.function.Function;
import org.apache.cassandra.db.compaction.CompactionInfo;
import org.apache.cassandra.dht.Range;
import org.apache.cassandra.dht.Token;
import org.apache.cassandra.service.StorageService;
public interface IVerifier extends Closeable
{
static Options.Builder options()
{
return new Options.Builder();
}
void verify();
@Override
void close();
CompactionInfo.Holder getVerifyInfo();
class Options
{
public final boolean invokeDiskFailurePolicy;
/**
* Force extended verification - unless it is enabled, extended verificiation will be done only
* if there is no digest present. Setting it along with quick makes no sense.
*/
public final boolean extendedVerification;
public final boolean checkVersion;
public final boolean mutateRepairStatus;
public final boolean checkOwnsTokens;
/**
* Quick check which does not include sstable data verification.
*/
public final boolean quick;
public final Function<String, ? extends Collection<Range<Token>>> tokenLookup;
private Options(boolean invokeDiskFailurePolicy,
boolean extendedVerification,
boolean checkVersion,
boolean mutateRepairStatus,
boolean checkOwnsTokens,
boolean quick,
Function<String, ? extends Collection<Range<Token>>> tokenLookup)
{
this.invokeDiskFailurePolicy = invokeDiskFailurePolicy;
this.extendedVerification = extendedVerification;
this.checkVersion = checkVersion;
this.mutateRepairStatus = mutateRepairStatus;
this.checkOwnsTokens = checkOwnsTokens;
this.quick = quick;
this.tokenLookup = tokenLookup;
}
@Override
public String toString()
{
return "Options{" +
"invokeDiskFailurePolicy=" + invokeDiskFailurePolicy +
", extendedVerification=" + extendedVerification +
", checkVersion=" + checkVersion +
", mutateRepairStatus=" + mutateRepairStatus +
", checkOwnsTokens=" + checkOwnsTokens +
", quick=" + quick +
'}';
}
public static class Builder
{
private boolean invokeDiskFailurePolicy = false; // invoking disk failure policy can stop the node if we find a corrupt stable
private boolean extendedVerification = false;
private boolean checkVersion = false;
private boolean mutateRepairStatus = false; // mutating repair status can be dangerous
private boolean checkOwnsTokens = false;
private boolean quick = false;
private Function<String, ? extends Collection<Range<Token>>> tokenLookup = StorageService.instance::getLocalAndPendingRanges;
public Builder invokeDiskFailurePolicy(boolean param)
{
this.invokeDiskFailurePolicy = param;
return this;
}
public Builder extendedVerification(boolean param)
{
this.extendedVerification = param;
return this;
}
public Builder checkVersion(boolean param)
{
this.checkVersion = param;
return this;
}
public Builder mutateRepairStatus(boolean param)
{
this.mutateRepairStatus = param;
return this;
}
public Builder checkOwnsTokens(boolean param)
{
this.checkOwnsTokens = param;
return this;
}
public Builder quick(boolean param)
{
this.quick = param;
return this;
}
public Builder tokenLookup(Function<String, ? extends Collection<Range<Token>>> tokenLookup)
{
this.tokenLookup = tokenLookup;
return this;
}
public Options build()
{
return new Options(invokeDiskFailurePolicy, extendedVerification, checkVersion, mutateRepairStatus, checkOwnsTokens, quick, tokenLookup);
}
}
}
}

View File

@ -18,17 +18,21 @@
package org.apache.cassandra.io.sstable;
import org.apache.cassandra.db.*;
import java.io.IOException;
import java.util.List;
import org.apache.cassandra.db.ClusteringPrefix;
import org.apache.cassandra.db.DeletionTime;
import org.apache.cassandra.db.SerializationHeader;
import org.apache.cassandra.db.TypeSizes;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.io.ISerializer;
import org.apache.cassandra.io.sstable.format.Version;
import org.apache.cassandra.io.sstable.format.big.RowIndexEntry;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.utils.ObjectSizes;
import java.io.IOException;
import java.util.List;
/**
* {@code IndexInfo} is embedded in the indexed version of {@link RowIndexEntry}.
* Each instance roughly covers a range of {@link org.apache.cassandra.config.Config#column_index_size column_index_size} KiB

View File

@ -19,103 +19,48 @@ package org.apache.cassandra.io.sstable;
import java.io.IOException;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.RowIndexEntry;
import org.apache.cassandra.dht.IPartitioner;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.File;
import org.apache.cassandra.io.util.RandomAccessReader;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.utils.AbstractIterator;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.CloseableIterator;
public class KeyIterator extends AbstractIterator<DecoratedKey> implements CloseableIterator<DecoratedKey>
{
private final static class In
{
private final File path;
private volatile RandomAccessReader in;
public In(File path)
{
this.path = path;
}
private void maybeInit()
{
if (in != null)
return;
synchronized (this)
{
if (in == null)
{
in = RandomAccessReader.open(path);
}
}
}
public DataInputPlus get()
{
maybeInit();
return in;
}
public boolean isEOF()
{
maybeInit();
return in.isEOF();
}
public void close()
{
if (in != null)
in.close();
}
public long getFilePointer()
{
maybeInit();
return in.getFilePointer();
}
public long length()
{
maybeInit();
return in.length();
}
}
private final Descriptor desc;
private final In in;
private final IPartitioner partitioner;
private final KeyReader it;
private final ReadWriteLock fileAccessLock;
private final long totalBytes;
private long keyPosition;
private boolean initialized = false;
public KeyIterator(Descriptor desc, TableMetadata metadata)
public KeyIterator(KeyReader it, IPartitioner partitioner, long totalBytes, ReadWriteLock fileAccessLock)
{
this.desc = desc;
in = new In(new File(desc.filenameFor(Component.PRIMARY_INDEX)));
partitioner = metadata.partitioner;
fileAccessLock = new ReentrantReadWriteLock();
this.it = it;
this.partitioner = partitioner;
this.totalBytes = totalBytes;
this.fileAccessLock = fileAccessLock;
}
protected DecoratedKey computeNext()
{
fileAccessLock.readLock().lock();
if (fileAccessLock != null)
fileAccessLock.readLock().lock();
try
{
if (in.isEOF())
return endOfData();
keyPosition = in.getFilePointer();
DecoratedKey key = partitioner.decorateKey(ByteBufferUtil.readWithShortLength(in.get()));
RowIndexEntry.Serializer.skip(in.get(), desc.version); // skip remainder of the entry
return key;
if (!initialized)
{
initialized = true;
return it.isExhausted()
? endOfData()
: partitioner.decorateKey(it.key());
}
else
{
return it.advance()
? partitioner.decorateKey(it.key())
: endOfData();
}
}
catch (IOException e)
{
@ -123,45 +68,44 @@ public class KeyIterator extends AbstractIterator<DecoratedKey> implements Close
}
finally
{
fileAccessLock.readLock().unlock();
if (fileAccessLock != null)
fileAccessLock.readLock().unlock();
}
}
public void close()
{
fileAccessLock.writeLock().lock();
if (fileAccessLock != null)
fileAccessLock.writeLock().lock();
try
{
in.close();
it.close();
}
finally
{
fileAccessLock.writeLock().unlock();
if (fileAccessLock != null)
fileAccessLock.writeLock().unlock();
}
}
public long getBytesRead()
{
fileAccessLock.readLock().lock();
if (fileAccessLock != null)
fileAccessLock.readLock().lock();
try
{
return in.getFilePointer();
return it.isExhausted() ? totalBytes : it.dataPosition();
}
finally
{
fileAccessLock.readLock().unlock();
if (fileAccessLock != null)
fileAccessLock.readLock().unlock();
}
}
public long getTotalBytes()
{
// length is final in the referenced object.
// no need to acquire the lock
return in.length();
return totalBytes;
}
public long getKeyPosition()
{
return keyPosition;
}
}

View File

@ -0,0 +1,74 @@
/*
* 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.io.sstable;
import java.io.Closeable;
import java.io.IOException;
import java.nio.ByteBuffer;
/**
* Reads keys from an SSTable.
* <p/>
* It is specific to SSTable format how the keys are read but in general the assumption is that it will read all the
* keys in the order as they are placed in data file.
* <p/>
* After creating it, it should be at the first key. Unless the SSTable is empty, {@link #key()},
* {@link #keyPositionForSecondaryIndex()} and {@link #dataPosition()} should return approriate values. If there is
* no data, {@link #isExhausted()} returns {@code true}. In order to move to the next key, {@link #advance()} should be
* called. It returns {@code true} if the reader moved to the next key; otherwise, there is no more data to read and
* the reader is exhausted. When the reader is exhausted, return values of {@link #key()},
* {@link #keyPositionForSecondaryIndex()} and {@link #dataPosition()} are undefined.
*/
public interface KeyReader extends Closeable
{
/**
* Current key
*/
ByteBuffer key();
/**
* Position in the component preferred for reading keys. This is specific to SSTable implementation
*/
long keyPositionForSecondaryIndex();
/**
* Position in the data file where the associated content resides
*/
long dataPosition();
/**
* Moves the iterator forward. Returns false if we reach EOF and there nothing more to read
*/
boolean advance() throws IOException;
/**
* Returns true if we reach EOF
*/
boolean isExhausted();
/**
* Resets the iterator to the initial position
*/
void reset() throws IOException;
/**
* Closes the iterator quietly
*/
@Override
void close();
}

View File

@ -16,14 +16,9 @@
* limitations under the License.
*/
package org.apache.cassandra.db.filter;
package org.apache.cassandra.io.sstable;
import org.apache.cassandra.db.RejectException;
public class RowIndexEntryReadSizeTooLargeException extends RejectException
public interface MetricsProviders
{
public RowIndexEntryReadSizeTooLargeException(String message)
{
super(message);
}
Iterable<GaugeProvider<?>> getGaugeProviders();
}

View File

@ -15,7 +15,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.io.sstable.format;
package org.apache.cassandra.io.sstable;
import java.io.IOException;
import java.util.ArrayList;
@ -30,8 +30,8 @@ import org.apache.cassandra.db.PartitionPosition;
import org.apache.cassandra.db.SerializationHeader;
import org.apache.cassandra.db.lifecycle.LifecycleNewTracker;
import org.apache.cassandra.db.rows.UnfilteredRowIterator;
import org.apache.cassandra.io.sstable.Descriptor;
import org.apache.cassandra.io.sstable.SSTableMultiWriter;
import org.apache.cassandra.io.sstable.format.SSTableFormat;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.schema.TableId;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.TimeUUID;

View File

@ -17,15 +17,18 @@
*/
package org.apache.cassandra.io.sstable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.io.util.FileUtils;
import org.apache.cassandra.utils.CloseableIterator;
import org.apache.cassandra.utils.IMergeIterator;
import org.apache.cassandra.utils.MergeIterator;
import org.apache.cassandra.utils.Throwables;
/**
* Caller must acquire and release references to the sstables used here.
@ -39,7 +42,17 @@ public class ReducingKeyIterator implements CloseableIterator<DecoratedKey>
{
iters = new ArrayList<>(sstables.size());
for (SSTableReader sstable : sstables)
iters.add(new KeyIterator(sstable.descriptor, sstable.metadata()));
{
try
{
iters.add(sstable.keyIterator());
}
catch (IOException ex)
{
iters.forEach(FileUtils::closeQuietly);
throw new RuntimeException("Failed to create a key iterator for sstable " + sstable.getFilename());
}
}
}
private void maybeInit()
@ -78,7 +91,17 @@ public class ReducingKeyIterator implements CloseableIterator<DecoratedKey>
public void close()
{
if (mi != null)
{
mi.close();
}
else
{
// if merging iterator was not initialized before this reducing iterator is closed, we need to close the
// underlying iterators manually
Throwable err = Throwables.close(null, iters);
if (err != null)
throw Throwables.unchecked(err);
}
}
public long getTotalBytes()
@ -121,4 +144,4 @@ public class ReducingKeyIterator implements CloseableIterator<DecoratedKey>
{
throw new UnsupportedOperationException();
}
}
}

View File

@ -17,16 +17,15 @@
*/
package org.apache.cassandra.io.sstable;
import java.io.FileNotFoundException;
import java.io.IOError;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.util.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import javax.annotation.Nullable;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
@ -34,43 +33,35 @@ import com.google.common.base.Predicates;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import org.apache.cassandra.io.util.File;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.db.BufferDecoratedKey;
import org.apache.cassandra.cache.ChunkCache;
import org.apache.cassandra.config.CassandraRelevantProperties;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.RowIndexEntry;
import org.apache.cassandra.dht.AbstractBounds;
import org.apache.cassandra.dht.IPartitioner;
import org.apache.cassandra.dht.Token;
import org.apache.cassandra.io.FSWriteError;
import org.apache.cassandra.io.util.DiskOptimizationStrategy;
import org.apache.cassandra.io.util.FileOutputStreamPlus;
import org.apache.cassandra.io.sstable.format.SSTableFormat;
import org.apache.cassandra.io.sstable.format.SSTableFormat.Components;
import org.apache.cassandra.io.sstable.format.TOCComponent;
import org.apache.cassandra.io.util.File;
import org.apache.cassandra.io.util.FileUtils;
import org.apache.cassandra.io.util.RandomAccessReader;
import org.apache.cassandra.metrics.TableMetrics;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.schema.TableMetadataRef;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.Pair;
import org.apache.cassandra.utils.TimeUUID;
import org.apache.cassandra.utils.memory.HeapCloner;
import org.apache.cassandra.utils.concurrent.OpOrder;
import org.apache.cassandra.utils.concurrent.SharedCloseable;
import static org.apache.cassandra.io.util.File.WriteMode.APPEND;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.apache.cassandra.service.ActiveRepairService.NO_PENDING_REPAIR;
import static org.apache.cassandra.service.ActiveRepairService.UNREPAIRED_SSTABLE;
/**
* This class is built on top of the SequenceFile. It stores
* data on disk in sorted fashion. However the sorting is upto
* the application. This class expects keys to be handed to it
* in sorted order.
*
* A separate index file is maintained as well, containing the
* SSTable keys and the offset into the SSTable at which they are found.
* Every 1/indexInterval key is read into memory when the SSTable is opened.
*
* Finally, a bloom filter file is also kept for the keys in each SSTable.
* This class represents an abstract sstable on disk whose keys and corresponding partitions are stored in
* a {@link SSTableFormat.Components#DATA} file in order as imposed by {@link DecoratedKey#comparator}.
*/
public abstract class SSTable
{
@ -78,70 +69,101 @@ public abstract class SSTable
public static final int TOMBSTONE_HISTOGRAM_BIN_SIZE = 100;
public static final int TOMBSTONE_HISTOGRAM_SPOOL_SIZE = 100000;
public static final int TOMBSTONE_HISTOGRAM_TTL_ROUND_SECONDS = Integer.valueOf(System.getProperty("cassandra.streaminghistogram.roundseconds", "60"));
public static final int TOMBSTONE_HISTOGRAM_TTL_ROUND_SECONDS = CassandraRelevantProperties.TOMBSTONE_HISTOGRAM_TTL_ROUND_SECONDS.getInt();
public final Descriptor descriptor;
protected final Set<Component> components;
public final boolean compression;
public DecoratedKey first;
public DecoratedKey last;
protected final DiskOptimizationStrategy optimizationStrategy;
protected final TableMetadataRef metadata;
protected SSTable(Descriptor descriptor, Set<Component> components, TableMetadataRef metadata, DiskOptimizationStrategy optimizationStrategy)
{
// In almost all cases, metadata shouldn't be null, but allowing null allows to create a mostly functional SSTable without
// full schema definition. SSTableLoader use that ability
assert descriptor != null;
assert components != null;
public final ChunkCache chunkCache;
public final IOOptions ioOptions;
this.descriptor = descriptor;
Set<Component> dataComponents = new HashSet<>(components);
this.compression = dataComponents.contains(Component.COMPRESSION_INFO);
this.components = new CopyOnWriteArraySet<>(dataComponents);
this.metadata = metadata;
this.optimizationStrategy = Objects.requireNonNull(optimizationStrategy);
@Nullable
private final WeakReference<Owner> owner;
public SSTable(Builder<?, ?> builder, Owner owner)
{
this.owner = new WeakReference<>(owner);
checkNotNull(builder.descriptor);
checkNotNull(builder.getComponents());
this.descriptor = builder.descriptor;
this.ioOptions = builder.getIOOptions();
this.components = new CopyOnWriteArraySet<>(builder.getComponents());
this.compression = components.contains(Components.COMPRESSION_INFO);
this.metadata = builder.getTableMetadataRef();
this.chunkCache = builder.getChunkCache();
}
public final Optional<Owner> owner()
{
if (owner == null)
return Optional.empty();
return Optional.ofNullable(owner.get());
}
public static void rename(Descriptor tmpdesc, Descriptor newdesc, Set<Component> components)
{
components.stream()
.filter(c -> !newdesc.getFormat().generatedOnLoadComponents().contains(c))
.filter(c -> !c.equals(Components.DATA))
.forEach(c -> tmpdesc.fileFor(c).move(newdesc.fileFor(c)));
// do -Data last because -Data present should mean the sstable was completely renamed before crash
tmpdesc.fileFor(Components.DATA).move(newdesc.fileFor(Components.DATA));
// rename it without confirmation because summary can be available for loadNewSSTables but not for closeAndOpenReader
components.stream()
.filter(c -> newdesc.getFormat().generatedOnLoadComponents().contains(c))
.forEach(c -> tmpdesc.fileFor(c).tryMove(newdesc.fileFor(c)));
}
public static void copy(Descriptor tmpdesc, Descriptor newdesc, Set<Component> components)
{
components.stream()
.filter(c -> !newdesc.getFormat().generatedOnLoadComponents().contains(c))
.filter(c -> !c.equals(Components.DATA))
.forEach(c -> FileUtils.copyWithConfirm(tmpdesc.fileFor(c), newdesc.fileFor(c)));
// do -Data last because -Data present should mean the sstable was completely copied before crash
FileUtils.copyWithConfirm(tmpdesc.fileFor(Components.DATA), newdesc.fileFor(Components.DATA));
// copy it without confirmation because summary can be available for loadNewSSTables but not for closeAndOpenReader
components.stream()
.filter(c -> newdesc.getFormat().generatedOnLoadComponents().contains(c))
.forEach(c -> FileUtils.copyWithOutConfirm(tmpdesc.fileFor(c), newdesc.fileFor(c)));
}
public static void hardlink(Descriptor tmpdesc, Descriptor newdesc, Set<Component> components)
{
components.stream()
.filter(c -> !newdesc.getFormat().generatedOnLoadComponents().contains(c))
.filter(c -> !c.equals(Components.DATA))
.forEach(c -> FileUtils.createHardLinkWithConfirm(tmpdesc.fileFor(c), newdesc.fileFor(c)));
// do -Data last because -Data present should mean the sstable was completely copied before crash
FileUtils.createHardLinkWithConfirm(tmpdesc.fileFor(Components.DATA), newdesc.fileFor(Components.DATA));
// copy it without confirmation because summary can be available for loadNewSSTables but not for closeAndOpenReader
components.stream()
.filter(c -> newdesc.getFormat().generatedOnLoadComponents().contains(c))
.forEach(c -> FileUtils.createHardLinkWithoutConfirm(tmpdesc.fileFor(c), newdesc.fileFor(c)));
}
public abstract DecoratedKey getFirst();
public abstract DecoratedKey getLast();
public abstract AbstractBounds<Token> getBounds();
@VisibleForTesting
public Set<Component> getComponents()
{
return ImmutableSet.copyOf(components);
}
/**
* We use a ReferenceQueue to manage deleting files that have been compacted
* and for which no more SSTable references exist. But this is not guaranteed
* to run for each such file because of the semantics of the JVM gc. So,
* we write a marker to `compactedFilename` when a file is compacted;
* if such a marker exists on startup, the file should be removed.
*
* This method will also remove SSTables that are marked as temporary.
*
* @return true if the file was deleted
*/
public static boolean delete(Descriptor desc, Set<Component> components)
{
logger.info("Deleting sstable: {}", desc);
// remove the DATA component first if it exists
if (components.contains(Component.DATA))
FileUtils.deleteWithConfirm(desc.filenameFor(Component.DATA));
for (Component component : components)
{
if (component.equals(Component.DATA) || component.equals(Component.SUMMARY))
continue;
FileUtils.deleteWithConfirm(desc.filenameFor(component));
}
if (components.contains(Component.SUMMARY))
FileUtils.delete(desc.filenameFor(Component.SUMMARY));
return true;
}
public TableMetadata metadata()
{
return metadata.get();
@ -157,25 +179,9 @@ public abstract class SSTable
return getPartitioner().decorateKey(key);
}
/**
* If the given @param key occupies only part of a larger buffer, allocate a new buffer that is only
* as large as necessary.
*/
public static DecoratedKey getMinimalKey(DecoratedKey key)
{
return key.getKey().position() > 0 || key.getKey().hasRemaining() || !key.getKey().hasArray()
? new BufferDecoratedKey(key.getToken(), HeapCloner.instance.clone(key.getKey()))
: key;
}
public String getFilename()
{
return descriptor.filenameFor(Component.DATA);
}
public String getIndexFilename()
{
return descriptor.filenameFor(Component.PRIMARY_INDEX);
return descriptor.fileFor(Components.DATA).absolutePath();
}
public String getColumnFamilyName()
@ -192,10 +198,31 @@ public abstract class SSTable
{
List<String> ret = new ArrayList<>(components.size());
for (Component component : components)
ret.add(descriptor.filenameFor(component));
ret.add(descriptor.fileFor(component).absolutePath());
return ret;
}
/**
* The method sets fields for this sstable representation on the provided {@link Builder}. The method is intended
* to be called from the overloaded {@code unbuildTo} method in subclasses.
*
* @param builder the builder on which the fields should be set
* @param sharedCopy whether the {@link SharedCloseable} resources should be passed as shared copies or directly;
* note that the method will overwrite the fields representing {@link SharedCloseable} only if
* they are not set in the builder yet (the relevant fields in the builder are {@code null}).
* Although {@link SSTable} does not keep any references to resources, the parameters is added
* for the possible future fields and for consistency with the overloaded implementations in
* subclasses
* @return the same instance of builder as provided
*/
protected final <B extends Builder<?, B>> B unbuildTo(B builder, boolean sharedCopy)
{
return builder.setTableMetadataRef(metadata)
.setComponents(components)
.setChunkCache(chunkCache)
.setIOOptions(ioOptions);
}
/**
* Parse a sstable filename into both a {@link Descriptor} and {@code Component} object.
*
@ -208,7 +235,7 @@ public abstract class SSTable
{
try
{
return Descriptor.fromFilenameWithComponent(file);
return Descriptor.fromFileWithComponent(file);
}
catch (Throwable e)
{
@ -230,7 +257,7 @@ public abstract class SSTable
{
try
{
return Descriptor.fromFilenameWithComponent(file, keyspace, table);
return Descriptor.fromFileWithComponent(file, keyspace, table);
}
catch (Throwable e)
{
@ -248,11 +275,11 @@ public abstract class SSTable
* @return the {@code Descriptor} corresponding to {@code file} if it corresponds to a valid and supported sstable
* filename, {@code null} otherwise.
*/
public static Descriptor tryDescriptorFromFilename(File file)
public static Descriptor tryDescriptorFromFile(File file)
{
try
{
return Descriptor.fromFilename(file);
return Descriptor.fromFile(file);
}
catch (Throwable e)
{
@ -260,140 +287,10 @@ public abstract class SSTable
}
}
/**
* Discovers existing components for the descriptor. Slow: only intended for use outside the critical path.
*/
public static Set<Component> componentsFor(final Descriptor desc)
{
try
{
try
{
return readTOC(desc);
}
catch (FileNotFoundException | NoSuchFileException e)
{
Set<Component> components = discoverComponentsFor(desc);
if (components.isEmpty())
return components; // sstable doesn't exist yet
if (!components.contains(Component.TOC))
components.add(Component.TOC);
appendTOC(desc, components);
return components;
}
}
catch (IOException e)
{
throw new IOError(e);
}
}
public static Set<Component> discoverComponentsFor(Descriptor desc)
{
Set<Component.Type> knownTypes = Sets.difference(Component.TYPES, Collections.singleton(Component.Type.CUSTOM));
Set<Component> components = Sets.newHashSetWithExpectedSize(knownTypes.size());
for (Component.Type componentType : knownTypes)
{
Component component = new Component(componentType);
if (new File(desc.filenameFor(component)).exists())
components.add(component);
}
return components;
}
/** @return An estimate of the number of keys contained in the given index file. */
public static long estimateRowsFromIndex(RandomAccessReader ifile, Descriptor descriptor) throws IOException
{
// collect sizes for the first 10000 keys, or first 10 mebibytes of data
final int SAMPLES_CAP = 10000, BYTES_CAP = (int)Math.min(10000000, ifile.length());
int keys = 0;
while (ifile.getFilePointer() < BYTES_CAP && keys < SAMPLES_CAP)
{
ByteBufferUtil.skipShortLength(ifile);
RowIndexEntry.Serializer.skip(ifile, descriptor.version);
keys++;
}
assert keys > 0 && ifile.getFilePointer() > 0 && ifile.length() > 0 : "Unexpected empty index file: " + ifile;
long estimatedRows = ifile.length() / (ifile.getFilePointer() / keys);
ifile.seek(0);
return estimatedRows;
}
@Override
public String toString()
{
return getClass().getSimpleName() + "(" +
"path='" + getFilename() + '\'' +
')';
}
/**
* Reads the list of components from the TOC component.
* @return set of components found in the TOC
*/
protected static Set<Component> readTOC(Descriptor descriptor) throws IOException
{
return readTOC(descriptor, true);
}
/**
* Reads the list of components from the TOC component.
* @param skipMissing, skip adding the component to the returned set if the corresponding file is missing.
* @return set of components found in the TOC
*/
protected static Set<Component> readTOC(Descriptor descriptor, boolean skipMissing) throws IOException
{
File tocFile = new File(descriptor.filenameFor(Component.TOC));
List<String> componentNames = Files.readAllLines(tocFile.toPath());
Set<Component> components = Sets.newHashSetWithExpectedSize(componentNames.size());
for (String componentName : componentNames)
{
Component component = new Component(Component.Type.fromRepresentation(componentName), componentName);
if (skipMissing && !new File(descriptor.filenameFor(component)).exists())
logger.error("Missing component: {}", descriptor.filenameFor(component));
else
components.add(component);
}
return components;
}
/**
* Appends new component names to the TOC component.
*/
protected static void appendTOC(Descriptor descriptor, Collection<Component> components)
{
File tocFile = new File(descriptor.filenameFor(Component.TOC));
try (FileOutputStreamPlus out = tocFile.newOutputStream(APPEND);
PrintWriter w = new PrintWriter(out))
{
for (Component component : components)
w.println(component.name);
w.flush();
out.sync();
}
catch (IOException e)
{
throw new FSWriteError(e, tocFile);
}
}
/**
* Registers new custom components. Used by custom compaction strategies.
* Adding a component for the second time is a no-op.
* Don't remove this - this method is a part of the public API, intended for use by custom compaction strategies.
* @param newComponents collection of components to be added
*/
public synchronized void addComponents(Collection<Component> newComponents)
{
Collection<Component> componentsToAdd = Collections2.filter(newComponents, Predicates.not(Predicates.in(components)));
appendTOC(descriptor, componentsToAdd);
components.addAll(componentsToAdd);
}
public AbstractBounds<Token> getBounds()
{
return AbstractBounds.bounds(first.getToken(), true, last.getToken(), true);
return String.format("%s:%s(path='%s')", getClass().getSimpleName(), descriptor.formatType.name, getFilename());
}
public static void validateRepairedMetadata(long repairedAt, TimeUUID pendingRepair, boolean isTransient)
@ -402,6 +299,118 @@ public abstract class SSTable
"pendingRepair cannot be set on a repaired sstable");
Preconditions.checkArgument(!isTransient || (pendingRepair != NO_PENDING_REPAIR),
"isTransient can only be true for sstables pending repair");
}
/**
* Registers new custom components. Used by custom compaction strategies.
* Adding a component for the second time is a no-op.
* Don't remove this - this method is a part of the public API, intended for use by custom compaction strategies.
*
* @param newComponents collection of components to be added
*/
public synchronized void addComponents(Collection<Component> newComponents)
{
Collection<Component> componentsToAdd = Collections2.filter(newComponents, Predicates.not(Predicates.in(components)));
TOCComponent.appendTOC(descriptor, componentsToAdd);
components.addAll(componentsToAdd);
}
public interface Owner
{
Double getCrcCheckChance();
OpOrder.Barrier newReadOrderingBarrier();
TableMetrics getMetrics();
}
/**
* A builder of this sstable representation. It should be extended for each implementation with the specific fields.
*
* @param <S> type of the sstable representation to be build with this builder
* @param <B> type of this builder
*/
public static class Builder<S extends SSTable, B extends Builder<S, B>>
{
public final Descriptor descriptor;
private Set<Component> components;
private TableMetadataRef tableMetadataRef;
private ChunkCache chunkCache = ChunkCache.instance;
private IOOptions ioOptions = IOOptions.fromDatabaseDescriptor();
public Builder(Descriptor descriptor)
{
checkNotNull(descriptor);
this.descriptor = descriptor;
}
public B setComponents(Collection<Component> components)
{
if (components != null)
{
components.forEach(c -> Preconditions.checkState(c.isValidFor(descriptor), "Invalid component type for sstable format " + descriptor.formatType.name));
this.components = ImmutableSet.copyOf(components);
}
else
{
this.components = null;
}
return (B) this;
}
public B addComponents(Collection<Component> components)
{
if (components == null || components.isEmpty())
return (B) this;
if (this.components == null)
return setComponents(components);
return setComponents(Sets.union(this.components, ImmutableSet.copyOf(components)));
}
public B setTableMetadataRef(TableMetadataRef ref)
{
this.tableMetadataRef = ref;
return (B) this;
}
public B setChunkCache(ChunkCache chunkCache)
{
this.chunkCache = chunkCache;
return (B) this;
}
public B setIOOptions(IOOptions ioOptions)
{
this.ioOptions = ioOptions;
return (B) this;
}
public Descriptor getDescriptor()
{
return descriptor;
}
public Set<Component> getComponents()
{
return components;
}
public TableMetadataRef getTableMetadataRef()
{
return tableMetadataRef;
}
public ChunkCache getChunkCache()
{
return chunkCache;
}
public IOOptions getIOOptions()
{
return ioOptions;
}
}
}

View File

@ -15,10 +15,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.io.sstable.format;
package org.apache.cassandra.io.sstable;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.rows.Row;
import org.apache.cassandra.db.rows.Unfiltered;
import org.apache.cassandra.io.sstable.format.SSTableReader;
/**
* Observer for events in the lifecycle of writing out an sstable.
@ -34,10 +36,22 @@ public interface SSTableFlushObserver
* Called when a new partition in being written to the sstable,
* but before any cells are processed (see {@link #nextUnfilteredCluster(Unfiltered)}).
*
* @param key The key being appended to SSTable.
* @param indexPosition The position of the key in the SSTable PRIMARY_INDEX file.
* @param key the key being appended to SSTable.
* @param keyPosition the position of the key in the SSTable data file
* @param KeyPositionForSASI SSTable format specific key position for storage attached indexes, it can be
* in data file or in some index file. It is the same position as returned by
* {@link KeyReader#keyPositionForSecondaryIndex()} for the same format, and the same
* position as expected by {@link SSTableReader#keyAtPositionFromSecondaryIndex(long)}.
*/
void startPartition(DecoratedKey key, long indexPosition);
void startPartition(DecoratedKey key, long keyPosition, long KeyPositionForSASI);
/**
* Called when a static row is being written to the sstable. If static columns are present in the table, it is called
* after {@link #startPartition(DecoratedKey, long, long)} and before any calls to {@link #nextUnfilteredCluster(Unfiltered)}.
*
* @param staticRow static row appended to the sstable, can be empty, may not be {@code null}
*/
void staticRow(Row staticRow);
/**
* Called after the unfiltered cluster is written to the sstable.
@ -52,4 +66,4 @@ public interface SSTableFlushObserver
* Called when all data is written to the file and it's ready to be finished up.
*/
void complete();
}
}

View File

@ -35,7 +35,6 @@ import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.cassandra.io.util.File;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -54,8 +53,11 @@ import org.apache.cassandra.db.marshal.MapType;
import org.apache.cassandra.db.marshal.SetType;
import org.apache.cassandra.db.marshal.TupleType;
import org.apache.cassandra.db.marshal.UserType;
import org.apache.cassandra.io.sstable.format.SSTableFormat.Components;
import org.apache.cassandra.io.sstable.format.SSTableFormat.Components.Types;
import org.apache.cassandra.io.sstable.metadata.MetadataComponent;
import org.apache.cassandra.io.sstable.metadata.MetadataType;
import org.apache.cassandra.io.util.File;
import org.apache.cassandra.schema.ColumnMetadata;
import org.apache.cassandra.schema.IndexMetadata;
import org.apache.cassandra.schema.Schema;
@ -301,7 +303,7 @@ public abstract class SSTableHeaderFix
.filter(p -> {
try
{
return Descriptor.fromFilenameWithComponent(new File(p)).right.type == Component.Type.DATA;
return Descriptor.fromFileWithComponent(new File(p)).right.type == Types.DATA;
}
catch (IllegalArgumentException t)
{
@ -309,8 +311,8 @@ public abstract class SSTableHeaderFix
return false;
}
})
.map(Path::toString)
.map(Descriptor::fromFilename)
.map(File::new)
.map(file -> Descriptor.fromFileWithComponent(file, false).left)
.forEach(descriptors::add);
}
@ -342,8 +344,8 @@ public abstract class SSTableHeaderFix
return;
}
Set<Component> components = SSTable.discoverComponentsFor(desc);
if (components.stream().noneMatch(c -> c.type == Component.Type.STATS))
Set<Component> components = desc.discoverComponents();
if (components.stream().noneMatch(c -> c.type == Types.STATS))
{
error("sstable %s has no -Statistics.db component.", desc);
return;
@ -845,7 +847,7 @@ public abstract class SSTableHeaderFix
private void writeNewMetadata(Descriptor desc, Map<MetadataType, MetadataComponent> newMetadata)
{
String file = desc.filenameFor(Component.STATS);
File file = desc.fileFor(Components.STATS);
info.accept(String.format(" Writing new metadata file %s", file));
try
{

View File

@ -33,7 +33,7 @@ import org.apache.cassandra.io.util.File;
* A new implementation must adhere to the following invariants:
* - Must be locally sortable - that is, the comparison must reflect the comparison of generation times of identifiers
* generated on the same node
* - String representation must *not* include the {@link Descriptor#FILENAME_SEPARATOR} character, see {@link Descriptor#fromFilenameWithComponent(File)}
* - String representation must *not* include the {@link Descriptor#FILENAME_SEPARATOR} character, see {@link Descriptor#fromFileWithComponent(File)}
* - must be case-insensitive because the sstables can be stored on case-insensitive file system
* <p>
*/
@ -50,7 +50,7 @@ public interface SSTableId
* {@link Builder#fromString(String)}
* <p>
* Must not contain any {@link Descriptor#FILENAME_SEPARATOR} character as it is used in the Descriptor
* see {@link Descriptor#fromFilenameWithComponent(File)}
* see {@link Descriptor#fromFileWithComponent(File)}
*/
@Override
String toString();

View File

@ -17,17 +17,24 @@
*/
package org.apache.cassandra.io.sstable;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.rows.*;
import java.io.IOError;
import java.io.IOException;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.DeletionTime;
import org.apache.cassandra.db.RegularAndStaticColumns;
import org.apache.cassandra.db.UnfilteredValidation;
import org.apache.cassandra.db.rows.DeserializationHelper;
import org.apache.cassandra.db.rows.EncodingStats;
import org.apache.cassandra.db.rows.Row;
import org.apache.cassandra.db.rows.Unfiltered;
import org.apache.cassandra.db.rows.UnfilteredRowIterator;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.io.util.FileDataInput;
import org.apache.cassandra.io.util.RandomAccessReader;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.utils.ByteBufferUtil;
import java.io.IOError;
import java.io.IOException;
import static org.apache.cassandra.utils.vint.VIntCoding.VIntOutOfRangeException;
public class SSTableIdentityIterator implements Comparable<SSTableIdentityIterator>, UnfilteredRowIterator
@ -72,13 +79,16 @@ public class SSTableIdentityIterator implements Comparable<SSTableIdentityIterat
}
@SuppressWarnings("resource")
public static SSTableIdentityIterator create(SSTableReader sstable, FileDataInput dfile, RowIndexEntry<?> indexEntry, DecoratedKey key, boolean tombstoneOnly)
public static SSTableIdentityIterator create(SSTableReader sstable, FileDataInput dfile, long dataPosition, DecoratedKey key, boolean tombstoneOnly)
{
try
{
dfile.seek(indexEntry.position);
dfile.seek(dataPosition);
ByteBufferUtil.skipShortLength(dfile); // Skip partition key
DeletionTime partitionLevelDeletion = DeletionTime.serializer.deserialize(dfile);
if (!partitionLevelDeletion.validate())
UnfilteredValidation.handleInvalid(sstable.metadata(), key, sstable, "partitionLevelDeletion="+partitionLevelDeletion.toString());
DeserializationHelper helper = new DeserializationHelper(sstable.metadata(), sstable.descriptor.version.correspondingMessagingVersion(), DeserializationHelper.Flag.LOCAL);
SSTableSimpleIterator iterator = tombstoneOnly
? SSTableSimpleIterator.createTombstoneOnly(sstable.metadata(), dfile, sstable.header, helper, partitionLevelDeletion)

View File

@ -17,25 +17,43 @@
*/
package org.apache.cassandra.io.sstable;
import java.util.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import org.apache.cassandra.db.streaming.CassandraOutgoingFile;
import org.apache.cassandra.io.util.File;
import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.io.FSError;
import org.apache.cassandra.schema.TableMetadataRef;
import org.apache.cassandra.db.Directories;
import org.apache.cassandra.db.lifecycle.LifecycleTransaction;
import org.apache.cassandra.db.streaming.CassandraOutgoingFile;
import org.apache.cassandra.dht.Range;
import org.apache.cassandra.dht.Token;
import org.apache.cassandra.io.FSError;
import org.apache.cassandra.io.sstable.format.SSTableFormat.Components;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.streaming.*;
import org.apache.cassandra.io.util.File;
import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.schema.TableMetadataRef;
import org.apache.cassandra.streaming.OutgoingStream;
import org.apache.cassandra.streaming.PreviewKind;
import org.apache.cassandra.streaming.StreamEvent;
import org.apache.cassandra.streaming.StreamEventHandler;
import org.apache.cassandra.streaming.StreamOperation;
import org.apache.cassandra.streaming.StreamPlan;
import org.apache.cassandra.streaming.StreamResultFuture;
import org.apache.cassandra.streaming.StreamState;
import org.apache.cassandra.streaming.StreamingChannel;
import org.apache.cassandra.utils.OutputHandler;
import org.apache.cassandra.utils.Pair;
import org.apache.cassandra.utils.concurrent.Ref;
import static org.apache.cassandra.streaming.StreamingChannel.Factory.Global.streamingFactory;
@ -55,7 +73,6 @@ public class SSTableLoader implements StreamEventHandler
private final Set<InetAddressAndPort> failedHosts = new HashSet<>();
private final List<SSTableReader> sstables = new ArrayList<>();
private final Multimap<InetAddressAndPort, OutgoingStream> streamingDetails = HashMultimap.create();
public SSTableLoader(File directory, Client client, OutputHandler outputHandler)
{
@ -78,10 +95,11 @@ public class SSTableLoader implements StreamEventHandler
}
@SuppressWarnings("resource")
protected Collection<SSTableReader> openSSTables(final Map<InetAddressAndPort, Collection<Range<Token>>> ranges)
private Multimap<InetAddressAndPort, CassandraOutgoingFile> openSSTables(final Map<InetAddressAndPort, Collection<Range<Token>>> ranges)
{
outputHandler.output("Opening sstables and calculating sections to stream");
Multimap<InetAddressAndPort, CassandraOutgoingFile> streamingDetails = HashMultimap.create();
LifecycleTransaction.getFiles(directory.toPath(),
(file, type) ->
{
@ -105,13 +123,16 @@ public class SSTableLoader implements StreamEventHandler
}
Descriptor desc = p == null ? null : p.left;
if (p == null || !p.right.equals(Component.DATA))
if (p == null || !p.right.equals(Components.DATA))
return false;
if (!new File(desc.filenameFor(Component.PRIMARY_INDEX)).exists())
for (Component c : desc.getFormat().primaryComponents())
{
outputHandler.output(String.format("Skipping file %s because index is missing", name));
return false;
if (!desc.fileFor(c).exists())
{
outputHandler.output(String.format("Skipping file %s because %s is missing", name, c.name));
return false;
}
}
TableMetadataRef metadata = client.getTableMetadata(desc.cfname);
@ -121,22 +142,14 @@ public class SSTableLoader implements StreamEventHandler
return false;
}
Set<Component> components = new HashSet<>();
components.add(Component.DATA);
components.add(Component.PRIMARY_INDEX);
if (new File(desc.filenameFor(Component.SUMMARY)).exists())
components.add(Component.SUMMARY);
if (new File(desc.filenameFor(Component.COMPRESSION_INFO)).exists())
components.add(Component.COMPRESSION_INFO);
if (new File(desc.filenameFor(Component.STATS)).exists())
components.add(Component.STATS);
Set<Component> components = desc.getComponents(desc.getFormat().primaryComponents(), desc.getFormat().uploadComponents());
try
{
// To conserve memory, open SSTableReaders without bloom filters and discard
// the index summary after calculating the file sections to stream and the estimated
// number of keys for each endpoint. See CASSANDRA-5555 for details.
SSTableReader sstable = SSTableReader.openForBatch(desc, components, metadata);
SSTableReader sstable = SSTableReader.openForBatch(null, desc, components, metadata);
sstables.add(sstable);
// calculate the sstable sections to stream as well as the estimated number of
@ -154,12 +167,12 @@ public class SSTableLoader implements StreamEventHandler
long estimatedKeys = sstable.estimatedKeysForRanges(tokenRanges);
Ref<SSTableReader> ref = sstable.ref();
OutgoingStream stream = new CassandraOutgoingFile(StreamOperation.BULK_LOAD, ref, sstableSections, tokenRanges, estimatedKeys);
CassandraOutgoingFile stream = new CassandraOutgoingFile(StreamOperation.BULK_LOAD, ref, sstableSections, tokenRanges, estimatedKeys);
streamingDetails.put(endpoint, stream);
}
// to conserve heap space when bulk loading
sstable.releaseSummary();
sstable.releaseInMemoryComponents();
}
catch (FSError e)
{
@ -170,7 +183,7 @@ public class SSTableLoader implements StreamEventHandler
},
Directories.OnTxnErr.IGNORE);
return sstables;
return streamingDetails;
}
public StreamResultFuture stream()
@ -186,14 +199,14 @@ public class SSTableLoader implements StreamEventHandler
StreamPlan plan = new StreamPlan(StreamOperation.BULK_LOAD, connectionsPerHost, false, null, PreviewKind.NONE).connectionFactory(client.getConnectionFactory());
Map<InetAddressAndPort, Collection<Range<Token>>> endpointToRanges = client.getEndpointToRangesMap();
openSSTables(endpointToRanges);
if (sstables.isEmpty())
Multimap<InetAddressAndPort, CassandraOutgoingFile> streamingDetails = openSSTables(endpointToRanges);
if (streamingDetails.isEmpty())
{
// return empty result
return plan.execute();
}
outputHandler.output(String.format("Streaming relevant part of %s to %s", names(sstables), endpointToRanges.keySet()));
outputHandler.output(String.format("Streaming relevant part of %s to %s", names(streamingDetails.values()), endpointToRanges.keySet()));
for (Map.Entry<InetAddressAndPort, Collection<Range<Token>>> entry : endpointToRanges.entrySet())
{
@ -201,13 +214,8 @@ public class SSTableLoader implements StreamEventHandler
if (toIgnore.contains(remote))
continue;
List<OutgoingStream> streams = new LinkedList<>();
// references are acquired when constructing the SSTableStreamingSections above
for (OutgoingStream stream : streamingDetails.get(remote))
{
streams.add(stream);
}
List<OutgoingStream> streams = new LinkedList<>(streamingDetails.get(remote));
plan.transferStreams(remote, streams);
}
@ -229,10 +237,13 @@ public class SSTableLoader implements StreamEventHandler
*/
private void releaseReferences()
{
for (SSTableReader sstable : sstables)
Iterator<SSTableReader> it = sstables.iterator();
while (it.hasNext())
{
SSTableReader sstable = it.next();
sstable.selfRef().release();
assert sstable.selfRef().globalCount() == 0 : String.format("for sstable = %s, ref count = %d", sstable, sstable.selfRef().globalCount());
it.remove();
}
}
@ -246,12 +257,9 @@ public class SSTableLoader implements StreamEventHandler
}
}
private String names(Collection<SSTableReader> sstables)
private String names(Collection<CassandraOutgoingFile> sstables)
{
StringBuilder builder = new StringBuilder();
for (SSTableReader sstable : sstables)
builder.append(sstable.descriptor.filenameFor(Component.DATA)).append(" ");
return builder.toString();
return sstables.stream().map(CassandraOutgoingFile::getName).distinct().collect(Collectors.joining(" "));
}
public Set<InetAddressAndPort> getFailedHosts()

View File

@ -15,9 +15,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.io.sstable.format;
package org.apache.cassandra.io.sstable;
import org.apache.cassandra.db.RowIndexEntry;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.tracing.Tracing;
/**
* Listener for receiving notifications associated with reading SSTables.
@ -29,10 +30,22 @@ public interface SSTableReadsListener
*/
enum SkippingReason
{
BLOOM_FILTER,
MIN_MAX_KEYS,
PARTITION_INDEX_LOOKUP,
INDEX_ENTRY_NOT_FOUND;
BLOOM_FILTER("Bloom filter allows skipping sstable {}"),
MIN_MAX_KEYS("Check against min and max keys allows skipping sstable {}"),
PARTITION_INDEX_LOOKUP("Partition index lookup allows skipping sstable {}"),
INDEX_ENTRY_NOT_FOUND("Partition index lookup complete (bloom filter false positive) for sstable {}");
private final String message;
SkippingReason(String message)
{
this.message = message;
}
public void trace(Descriptor descriptor)
{
Tracing.trace(message, descriptor.id);
}
}
/**
@ -40,8 +53,20 @@ public interface SSTableReadsListener
*/
enum SelectionReason
{
KEY_CACHE_HIT,
INDEX_ENTRY_FOUND;
KEY_CACHE_HIT("Key cache hit for sstable {}, size = {}"),
INDEX_ENTRY_FOUND("Partition index found for sstable {}, size = {}");
private final String message;
SelectionReason(String message)
{
this.message = message;
}
public void trace(Descriptor descriptor, AbstractRowIndexEntry entry)
{
Tracing.trace(message, descriptor.id, entry.columnsIndexCount());
}
}
/**
@ -66,7 +91,7 @@ public interface SSTableReadsListener
* @param indexEntry the index entry
* @param reason the reason for which the SSTable has been selected
*/
default void onSSTableSelected(SSTableReader sstable, RowIndexEntry<?> indexEntry, SelectionReason reason)
default void onSSTableSelected(SSTableReader sstable, SelectionReason reason)
{
}

View File

@ -17,22 +17,18 @@
*/
package org.apache.cassandra.io.sstable;
import java.lang.ref.WeakReference;
import java.util.*;
import java.util.ArrayList;
import java.util.List;
import com.google.common.annotations.VisibleForTesting;
import org.apache.cassandra.cache.InstrumentingCache;
import org.apache.cassandra.cache.KeyCacheKey;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.RowIndexEntry;
import org.apache.cassandra.db.lifecycle.ILifecycleTransaction;
import org.apache.cassandra.db.rows.UnfilteredRowIterator;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.io.sstable.format.SSTableWriter;
import org.apache.cassandra.utils.NativeLibrary;
import org.apache.cassandra.utils.concurrent.Transactional;
/**
@ -43,12 +39,12 @@ import org.apache.cassandra.utils.concurrent.Transactional;
* for on-close (i.e. when all references expire) that drops the page cache prior to that key position
*
* hard-links are created for each partially written sstable so that readers opened against them continue to work past
* the rename of the temporary file, which is deleted once all readers against the hard-link have been closed.
* renaming of the temporary file, which is deleted once all readers against the hard-link have been closed.
* If for any reason the writer is rolled over, we immediately rename and fully expose the completed file in the Tracker.
*
* On abort we restore the original lower bounds to the existing readers and delete any temporary files we had in progress,
* but leave any hard-links in place for the readers we opened to cleanup when they're finished as we would had we finished
* successfully.
* On abort, we restore the original lower bounds to the existing readers and delete any temporary files we had in progress,
* but leave any hard-links in place for the readers we opened, and clean-up when the readers finish as we would do
* if we had finished successfully.
*/
public class SSTableRewriter extends Transactional.AbstractTransactional implements Transactional
{
@ -69,7 +65,6 @@ public class SSTableRewriter extends Transactional.AbstractTransactional impleme
private final boolean eagerWriterMetaRelease; // true if the writer metadata should be released when switch is called
private SSTableWriter writer;
private Map<DecoratedKey, RowIndexEntry> cachedKeys = new HashMap<>();
// for testing (TODO: remove when have byteman setup)
private boolean throwEarly, throwLate;
@ -117,31 +112,16 @@ public class SSTableRewriter extends Transactional.AbstractTransactional impleme
return writer;
}
public RowIndexEntry append(UnfilteredRowIterator partition)
public AbstractRowIndexEntry append(UnfilteredRowIterator partition)
{
// we do this before appending to ensure we can resetAndTruncate() safely if the append fails
// we do this before appending to ensure we can resetAndTruncate() safely if appending fails
DecoratedKey key = partition.partitionKey();
maybeReopenEarly(key);
RowIndexEntry index = writer.append(partition);
if (DatabaseDescriptor.shouldMigrateKeycacheOnCompaction())
{
if (!transaction.isOffline() && index != null)
{
for (SSTableReader reader : transaction.originals())
{
if (reader.getCachedPosition(key, false) != null)
{
cachedKeys.put(key, index);
break;
}
}
}
}
return index;
return writer.append(partition);
}
// attempts to append the row, if fails resets the writer position
public RowIndexEntry tryAppend(UnfilteredRowIterator partition)
public AbstractRowIndexEntry tryAppend(UnfilteredRowIterator partition)
{
writer.mark();
try
@ -163,20 +143,18 @@ public class SSTableRewriter extends Transactional.AbstractTransactional impleme
{
for (SSTableReader reader : transaction.originals())
{
RowIndexEntry index = reader.getPosition(key, SSTableReader.Operator.GE);
NativeLibrary.trySkipCache(reader.getFilename(), 0, index == null ? 0 : index.position);
reader.trySkipFileCacheBefore(key);
}
}
else
{
SSTableReader reader = writer.setMaxDataAge(maxAge).openEarly();
if (reader != null)
{
writer.setMaxDataAge(maxAge);
writer.openEarly(reader -> {
transaction.update(reader, false);
currentlyOpenedEarlyAt = writer.getFilePointer();
moveStarts(reader, reader.last);
moveStarts(reader.last);
transaction.checkpoint();
}
});
}
}
}
@ -210,27 +188,13 @@ public class SSTableRewriter extends Transactional.AbstractTransactional impleme
* one, the old *instance* will have reference count == 0 and if we were to start a new compaction with that old
* instance, we would get exceptions.
*
* @param newReader the rewritten reader that replaces them for this region
* @param lowerbound if !reset, must be non-null, and marks the exclusive lowerbound of the start for each sstable
*/
private void moveStarts(SSTableReader newReader, DecoratedKey lowerbound)
private void moveStarts(DecoratedKey lowerbound)
{
if (transaction.isOffline() || preemptiveOpenInterval == Long.MAX_VALUE)
return;
newReader.setupOnline();
List<DecoratedKey> invalidateKeys = null;
if (!cachedKeys.isEmpty())
{
invalidateKeys = new ArrayList<>(cachedKeys.size());
for (Map.Entry<DecoratedKey, RowIndexEntry> cacheKey : cachedKeys.entrySet())
{
invalidateKeys.add(cacheKey.getKey());
newReader.cacheKey(cacheKey.getKey(), cacheKey.getValue());
}
}
cachedKeys.clear();
for (SSTableReader sstable : transaction.originals())
{
// we call getCurrentReplacement() to support multiple rewriters operating over the same source readers at once.
@ -241,49 +205,19 @@ public class SSTableRewriter extends Transactional.AbstractTransactional impleme
if (latest.first.compareTo(lowerbound) > 0)
continue;
Runnable runOnClose = invalidateKeys != null ? new InvalidateKeys(latest, invalidateKeys) : null;
if (lowerbound.compareTo(latest.last) >= 0)
{
if (!transaction.isObsolete(latest))
{
if (runOnClose != null)
{
latest.runOnClose(runOnClose);
}
transaction.obsolete(latest);
}
continue;
}
DecoratedKey newStart = latest.firstKeyBeyond(lowerbound);
assert newStart != null;
SSTableReader replacement = latest.cloneWithNewStart(newStart, runOnClose);
transaction.update(replacement, true);
}
}
private static final class InvalidateKeys implements Runnable
{
final List<KeyCacheKey> cacheKeys = new ArrayList<>();
final WeakReference<InstrumentingCache<KeyCacheKey, ?>> cacheRef;
private InvalidateKeys(SSTableReader reader, Collection<DecoratedKey> invalidate)
{
this.cacheRef = new WeakReference<>(reader.getKeyCache());
if (cacheRef.get() != null)
if (!transaction.isObsolete(latest))
{
for (DecoratedKey key : invalidate)
cacheKeys.add(reader.getCacheKey(key));
}
}
public void run()
{
for (KeyCacheKey key : cacheKeys)
{
InstrumentingCache<KeyCacheKey, ?> cache = cacheRef.get();
if (cache != null)
cache.remove(key);
DecoratedKey newStart = latest.firstKeyBeyond(lowerbound);
assert newStart != null;
SSTableReader replacement = latest.cloneWithNewStart(newStart);
transaction.update(replacement, true);
}
}
}
@ -291,7 +225,10 @@ public class SSTableRewriter extends Transactional.AbstractTransactional impleme
public void switchWriter(SSTableWriter newWriter)
{
if (newWriter != null)
writers.add(newWriter.setMaxDataAge(maxAge));
{
newWriter.setMaxDataAge(maxAge);
writers.add(newWriter);
}
if (eagerWriterMetaRelease && writer != null)
writer.releaseMetadataOverhead();
@ -310,12 +247,15 @@ public class SSTableRewriter extends Transactional.AbstractTransactional impleme
return;
}
// Open fully completed sstables early. This is also required for the final sstable in a set (where newWriter
// is null) to permit the compilation of a canonical set of sstables (see View.select).
if (preemptiveOpenInterval != Long.MAX_VALUE)
{
// we leave it as a tmp file, but we open it and add it to the Tracker
SSTableReader reader = writer.setMaxDataAge(maxAge).openFinalEarly();
writer.setMaxDataAge(maxAge);
SSTableReader reader = writer.openFinalEarly();
transaction.update(reader, false);
moveStarts(reader, reader.last);
moveStarts(reader.last);
transaction.checkpoint();
}
@ -370,7 +310,9 @@ public class SSTableRewriter extends Transactional.AbstractTransactional impleme
for (SSTableWriter writer : writers)
{
assert writer.getFilePointer() > 0;
writer.setRepairedAt(repairedAt).setOpenResult(true).prepareToCommit();
writer.setRepairedAt(repairedAt);
writer.setOpenResult(true);
writer.prepareToCommit();
SSTableReader reader = writer.finished();
transaction.update(reader, false);
preparedForCommit.add(reader);

View File

@ -214,7 +214,7 @@ class SSTableSimpleUnsortedWriter extends AbstractSSTableSimpleWriter
if (b == SENTINEL)
return;
try (SSTableTxnWriter writer = createWriter())
try (SSTableTxnWriter writer = createWriter(null))
{
for (Map.Entry<DecoratedKey, PartitionUpdate.Builder> entry : b.entrySet())
writer.append(entry.getValue().build().unfilteredIterator());

View File

@ -21,7 +21,8 @@ import java.io.IOException;
import com.google.common.base.Throwables;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.RegularAndStaticColumns;
import org.apache.cassandra.db.partitions.PartitionUpdate;
import org.apache.cassandra.io.util.File;
import org.apache.cassandra.schema.TableMetadataRef;
@ -51,7 +52,7 @@ class SSTableSimpleWriter extends AbstractSSTableSimpleWriter
private SSTableTxnWriter getOrCreateWriter() throws IOException
{
if (writer == null)
writer = createWriter();
writer = createWriter(null);
return writer;
}

View File

@ -28,7 +28,6 @@ import org.apache.cassandra.db.compaction.OperationType;
import org.apache.cassandra.db.lifecycle.LifecycleTransaction;
import org.apache.cassandra.db.rows.UnfilteredRowIterator;
import org.apache.cassandra.index.Index;
import org.apache.cassandra.io.sstable.format.RangeAwareSSTableWriter;
import org.apache.cassandra.io.sstable.format.SSTableFormat;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.io.sstable.metadata.MetadataCollector;
@ -144,12 +143,13 @@ public class SSTableTxnWriter extends Transactional.AbstractTransactional implem
boolean isTransient,
int sstableLevel,
SerializationHeader header,
Collection<Index> indexes)
Collection<Index> indexes,
SSTable.Owner owner)
{
// if the column family store does not exist, we create a new default SSTableMultiWriter to use:
LifecycleTransaction txn = LifecycleTransaction.offline(OperationType.WRITE);
MetadataCollector collector = new MetadataCollector(metadata.get().comparator).sstableLevel(sstableLevel);
SSTableMultiWriter writer = SimpleSSTableMultiWriter.create(descriptor, keyCount, repairedAt, pendingRepair, isTransient, metadata, collector, header, indexes, txn);
SSTableMultiWriter writer = SimpleSSTableMultiWriter.create(descriptor, keyCount, repairedAt, pendingRepair, isTransient, metadata, collector, header, indexes, txn, owner);
return new SSTableTxnWriter(txn, writer);
}

View File

@ -15,89 +15,81 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.io.sstable.format.big;
package org.apache.cassandra.io.sstable;
import java.io.EOFException;
import java.io.IOException;
import java.nio.channels.ClosedChannelException;
import java.util.Collection;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import org.apache.cassandra.db.lifecycle.LifecycleNewTracker;
import org.apache.cassandra.io.util.File;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.lifecycle.LifecycleNewTracker;
import org.apache.cassandra.db.rows.UnfilteredRowIterator;
import org.apache.cassandra.dht.AbstractBounds;
import org.apache.cassandra.dht.Token;
import org.apache.cassandra.io.FSWriteError;
import org.apache.cassandra.io.compress.BufferType;
import org.apache.cassandra.io.sstable.Component;
import org.apache.cassandra.io.sstable.Descriptor;
import org.apache.cassandra.io.sstable.SSTable;
import org.apache.cassandra.io.sstable.SSTableMultiWriter;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.SequentialWriter;
import org.apache.cassandra.io.util.SequentialWriterOption;
import org.apache.cassandra.net.AsyncStreamingInputPlus;
import org.apache.cassandra.schema.TableId;
import org.apache.cassandra.schema.TableMetadataRef;
import static java.lang.String.format;
import static org.apache.cassandra.utils.FBUtilities.prettyPrintMemory;
public class BigTableZeroCopyWriter extends SSTable implements SSTableMultiWriter
public class SSTableZeroCopyWriter extends SSTable implements SSTableMultiWriter
{
private static final Logger logger = LoggerFactory.getLogger(BigTableZeroCopyWriter.class);
private static final Logger logger = LoggerFactory.getLogger(SSTableZeroCopyWriter.class);
private final TableMetadataRef metadata;
private volatile SSTableReader finalReader;
private final Map<Component.Type, SequentialWriter> componentWriters;
private static final SequentialWriterOption WRITER_OPTION =
SequentialWriterOption.newBuilder()
.trickleFsync(false)
.bufferSize(2 << 20)
.bufferType(BufferType.OFF_HEAP)
.build();
private static final ImmutableSet<Component> SUPPORTED_COMPONENTS =
ImmutableSet.of(Component.DATA,
Component.PRIMARY_INDEX,
Component.SUMMARY,
Component.STATS,
Component.COMPRESSION_INFO,
Component.FILTER,
Component.DIGEST,
Component.CRC);
public BigTableZeroCopyWriter(Descriptor descriptor,
TableMetadataRef metadata,
LifecycleNewTracker lifecycleNewTracker,
final Collection<Component> components)
public SSTableZeroCopyWriter(Builder<?, ?> builder,
LifecycleNewTracker lifecycleNewTracker,
SSTable.Owner owner)
{
super(descriptor, ImmutableSet.copyOf(components), metadata, DatabaseDescriptor.getDiskOptimizationStrategy());
super(builder, owner);
lifecycleNewTracker.trackNew(this);
this.metadata = metadata;
this.componentWriters = new EnumMap<>(Component.Type.class);
this.componentWriters = new HashMap<>();
if (!SUPPORTED_COMPONENTS.containsAll(components))
if (!descriptor.getFormat().streamingComponents().containsAll(components))
throw new AssertionError(format("Unsupported streaming component detected %s",
Sets.difference(ImmutableSet.copyOf(components), SUPPORTED_COMPONENTS)));
Sets.difference(ImmutableSet.copyOf(components), descriptor.getFormat().streamingComponents())));
for (Component c : components)
componentWriters.put(c.type, makeWriter(descriptor, c));
}
private static SequentialWriter makeWriter(Descriptor descriptor, Component component)
@Override
public DecoratedKey getFirst()
{
return new SequentialWriter(new File(descriptor.filenameFor(component)), WRITER_OPTION, false);
throw new UnsupportedOperationException();
}
@Override
public DecoratedKey getLast()
{
throw new UnsupportedOperationException();
}
@Override
public AbstractBounds<Token> getBounds()
{
throw new UnsupportedOperationException();
}
private SequentialWriter makeWriter(Descriptor descriptor, Component component)
{
return new SequentialWriter(descriptor.fileFor(component), ioOptions.writerOptions, false);
}
private void write(DataInputPlus in, long size, SequentialWriter out) throws FSWriteError
@ -119,14 +111,14 @@ public class BigTableZeroCopyWriter extends SSTable implements SSTableMultiWrite
}
catch (IOException e)
{
throw new FSWriteError(e, out.getPath());
throw new FSWriteError(e, out.getFile());
}
}
@Override
public boolean append(UnfilteredRowIterator partition)
{
throw new UnsupportedOperationException("Operation not supported by BigTableBlockWriter");
throw new UnsupportedOperationException();
}
@Override
@ -150,7 +142,7 @@ public class BigTableZeroCopyWriter extends SSTable implements SSTableMultiWrite
public Collection<SSTableReader> finished()
{
if (finalReader == null)
finalReader = SSTableReader.open(descriptor, components, metadata);
finalReader = SSTableReader.open(owner().orElse(null), descriptor, components, metadata);
return ImmutableList.of(finalReader);
}

View File

@ -0,0 +1,267 @@
# SSTable API
[CEP-17](https://cwiki.apache.org/confluence/display/CASSANDRA/CEP-17%3A+SSTable+format+API)
/ [CASSANDRA-17056](https://issues.apache.org/jira/browse/CASSANDRA-17056)
## Configuration specification
SSTable formats and options are specified under the `sstable_formats` key in the _cassandra.yaml_ configuration file.
By default, the first sstable format implementation specified is the default one, which means that newly produced
sstables will use that implementation. Other implementations are used to read the sstables.
```yaml
sstable_formats:
- class_name: 〈class name of the default SSTableFormat implementation〉
parameters:
id: 〈unique integer identifier of the implementation〉
name: 〈unique string identifier of the implementation〉
param1: 〈format specific parameter 1〉
param2: 〈format specific parameter 2〉
# ...
- class_name: 〈class name of additional SSTableFormat implementation〉
parameters:
id: 〈unique integer identifier of the implementation〉
name: 〈unique string identifier of the implementation〉
param1: 〈format specific parameter 1〉
param2: 〈format specific parameter 2〉
# ...
# ...
```
Each sstable format definition includes the class name of the class implementing [`SSTableFormat`](format/SSTableFormat.java)
interface and a map of parameters. Two parameters are mandatory - _id_ and _name_, and they have to be unique across
all the defined formats. Additionally, they need to obey the following rules:
- _id_ must be an integer between 0 and 127
- _name_ must contain only lowercase ASCII letters
The _name_ parameter is the string put in the sstable file name. Therefore, it is the way Cassandra chooses the sstable
format implementation when loading an sstable. The _id_ parameter is used in saved cache files. Both parameters should
remain the same for the same format when upgrading. In particular, previous Cassandra versions have only
[_big_ format](format/big/BigFormat.java) implementation, thus when upgrading from the old version, the configuration
should keep _id_ set to _0_ and _name_ set to _big_ for that implementation (this is the default configuration), and any
new implementations should use different values for those parameters.
The remaining parameters are optional and passed as a map to the `setup` method of a class implementing `SSTableFormat`.
Default configuration:
```yaml
sstable_formats:
- class_name: org.apache.cassandra.io.sstable.format.big.BigFormat
parameters:
id: 0
name: big
```
Example configuration which uses `BtiFormat` as the default one keeping the right parameters for the `BigFormat`
implementation.
```yaml
sstable_formats:
- class_name: org.apache.cassandra.io.sstable.format.bti.BtiFormat
parameters:
id: 1
name: bti
- class_name: org.apache.cassandra.io.sstable.format.big.BigFormat
parameters:
id: 0
name: big
```
## Components
Each sstable consists of a set of components - required and optional. A component constitutes an identifier required
to obtain the exact file with an sstable descriptor. Components are grouped by type. A type may define either
a singleton component (for example, _stats_ component) or a non-singleton component (for example, _secondary index_
component).
A set of generic types of components that are thought of as common to all the sstable implementations is defined in the
[`SSTableFormat.Components`](format/SSTableFormat.java) class. They include singleton types like `DATA`,
`COMPRESSION_INFO`, `STATS`, `FILTER`, `DIGEST`, `CRC`, and `TOC`, which comes with predefined singleton component
instances, as well as non-singleton types like `SECONDARY_INDEX` and `CUSTOM`.
Apart from the generic components, each sstable format implementation may describe its specific component types.
For example, the _big table_ format describes additionally `PRIMARY_INDEX` and `SUMMARY` singleton types and
the corresponding singleton components (see [`BigFormat.Components`](format/big/BigFormat.java)).
Custom types can be created with one of the `Component.Type.create(name, repr, formatClass)`,
`Component.Type.createSingleton(name, repr, formatClass)` methods. Each created type is registered in a global types'
registry. Types registry is hierarchical which means that an sstable implementation may use types defined for its
format class and for all parent format classes (for example, the types defined for the `BigFormat` class extend the set
of types defined for the `SSTableFormat` interface).
For example, types defined for `BigFormat`:
```java
public static class Types extends SSTableFormat.Components.Types
{
public static final Component.Type PRIMARY_INDEX = Component.Type.createSingleton("PRIMARY_INDEX", "Index.db", BigFormat.class);
public static final Component.Type SUMMARY = Component.Type.createSingleton("SUMMARY", "Summary.db", BigFormat.class);
}
```
Singleton components are immediately associated with the singleton types and retrieved with the `<type>.getSingleton()`
method:
```java
public static class Components extends AbstractSSTableFormat.Components
{
public final static Component PRIMARY_INDEX = Types.PRIMARY_INDEX.getSingleton();
public final static Component SUMMARY = Types.SUMMARY.getSingleton();
}
```
Non-singleton components are created explicitly as follows:
```java
Component idx1 = Types.SECONDARY_INDEX.createComponent("SI_idx1.db");
```
## Implementation
We strongly suggest the main format class to extend [`AbstractSSTableFormat`](format/AbstractSSTableFormat.java) because
it includes the expected implementation of a couple of methods that should not be reimplemented differently.
### Initialization
Cassandra either initializes the sstable format class as a singleton by calling its constructor or obtains the instance
by accessing a static field called `instance` in the class. As a part of the initialization, Cassandra calls the `setup`
method and provides the configuration parameters. Right after initialization, Cassandra calls the `allComponents` method
to confirm all the components defined for the format are initialized and usable.
### Predefined sets of components
SSTable format defines a couple of collections of components. You should declare those collections as constant and
immutable sets.
### Reader
#### Construction
An sstable reader ([`SSTableReader`](format/SSTableReader.java)) is responsible for reading the data from an sstable.
It is created by a _simple builder_ ([`SSTableReader.Builder`](format/SSTableReader.java)) or a _loading
builder_ ([`SSTableReaderLoadingBuilder`](format/SSTableReaderLoadingBuilder.java)). The builders are supplied by
a _reader factory_ ([`SSTableFormat.SSTableReaderFactory`](format/SSTableFormat.java)).
The constructor of a particular `SSTableReader` implementation should accept two parameters - one is the format-specific
_simple builder_ and the other one is an sstable owner (usually a `ColumnFamilyStore` instance, but it can be null
either). The constructor should be simple and not do anything but assign internal fields with values from the builder.
A simple builder does not perform any logic except basic validation - it only stores the provided values the reader
constructor can access. A new reader implementation should include a public static simple builder inner class that
extends the `SSTableReader.Builder` generic reader builder (or `SSTableReaderWithFilter.Builder`, see [below](#filter)).
In contrast to the simple builder, a loading builder can perform additional operations like more complex validation,
opening resources, loading caches, indexes, filters, etc. It internally creates a simple builder and eventually
instantiates a reader.
#### General notes
Note that if the builder carries some closeable resources to the reader, they should be returned by the `setupInstance`
method.
You will find some `cloneXXX` methods to implement - remember to create a reader clone in a lambda passed to
the `runWithLock()` method.
#### Unbuilding
It is convenient to implement the `unbuildTo` method, which takes a _simple builder_ and initializes it so that
the builder can produce the same reader. The method should also take the `sharedCopy` boolean argument denoting whether
it should copy the fields referencing closeable resources to the builder directly or as (shared) copies. The convention
also requires copying the resources only if they are unset in the builder (the field is null). The method should call
the `super.unbuildTo` method as a first step so that all the fields managed by the parent class are copied and in
the actual implementation only the fields specific to this format have to be assigned.
For example, the implementation of that method in a reader for the _big table_ format is as follows:
```java
protected final Builder unbuildTo(Builder builder, boolean sharedCopy)
{
Builder b = super.unbuildTo(builder, sharedCopy);
if (builder.getIndexFile() == null)
b.setIndexFile(sharedCopy ? sharedCopyOrNull(ifile) : ifile);
if (builder.getIndexSummary() == null)
b.setIndexSummary(sharedCopy ? sharedCopyOrNull(indexSummary) : indexSummary);
b.setKeyCache(keyCache);
return b;
}
```
#### Filter
If the sstable includes a _filter_, the reader class should extend
the [`SSTableReaderWithFilter`](format/SSTableReaderWithFilter.java) abstract reader (and its _simple builder_ should
extend the `SSTableReaderWithFilter.SSTableReaderWithFilterBuilder` builder).
The reader with filter provides the `isPresentInFilter` method for extending implementation. It also implements other
filter-specific methods the system relies on if the implemented reader extends that class.
Note that if the implemented reader extends the `SSTableReaderWithFilter` class, it should include the `FILTER`
component in the appropriate component sets.
The reader with filter implementation comes with additional [metrics](filter/BloomFilterMetrics.java) - read more about custom
metrics support [here](#metrics).
#### Index summary
Some sstable format implementations, such as _big table_ format, may use _index summaries_. If a reader uses _index
summaries_ it should implement the [`IndexSummarySupport`](indexsummary/IndexSummarySupport.java) interface.
The support for _index summaries_ comes with additional [metrics](indexsummary/IndexSummaryMetrics.java) - read more
about custom metrics support [here](#metrics).
#### Key cache
If an sstable format implementation uses row key cache, it should implement
the[`KeyCacheSupport`](keycache/KeyCacheSupport.java) interface. In particular, it should store a `KeyCache` instance
and return it with the `getKeyCache()` method. The interface has the default implementations of several methods
the system relies on if the reader implements the `KeyCacheSupport` interface.
The interface comes with additional [metrics](keycache/KeyCacheMetrics.java) - read more about custom metrics support
[here](#metrics).
#### Metrics
A custom sstable format implementation may provide additional metrics on a table, keyspace, and global level. Those
metrics are accessible via JMX. The `SSTableFormat` implementation exposes the additional metrics by implementing the
`SSTableFormat.getFormatSpecificMetricsProviders` method. The method should return a singleton object implementing the
[`MetricsProviders`](MetricsProviders.java) interface. Currently, there is only support for custom gauges, but it can be
extended when needed.
Each custom metric (gauge) is an implementation of the [GaugeProvider](GaugeProvider.java) abstract class. Although the
class expects the implementation to provide a gauge for each level of aggregation, there is a helper class -
[SimpleGaugeProvider](SimpleGaugeProvider.java) - which does that automatically with a supplied reduction lambda. There
is [`AbstractMetricsProviders`](AbstractMetricsProviders.java) class which is a partial implementation of the
`MetricsProviders` interface and leverages `SimpleGaugeProvider` in the offered methods.
Example - additional metrics for sstables supporting index summaries (see
[`IndexSummaryMetrics`](indexsummary/IndexSummaryMetrics.java) for a full example):
```java
private final GaugeProvider<Long> indexSummaryOffHeapMemoryUsed = newGaugeProvider("IndexSummaryOffHeapMemoryUsed",
0L,
r -> r.getIndexSummary().getOffHeapSize(),
Long::sum);
```
### Writer
#### Construction
An sstable writer ([`SSTableWriter`](format/SSTableWriter.java)) is responsible for writing the data to sstable files.
It is created by a _builder_ ([`SSTableWriter.Builder`](format/SSTableWriter.java)). The builder is supplied by
a _writer factory_ ([`SSTableFormat.SSTableWriterFactory`](format/SSTableFormat.java)).
#### SortedTableWriter
There are not many methods to be implemented in the writer. The most notable one is `append` which should write
the provided partition to disk. However, there is a generic default implementation
[`SortedTableWriter`](format/SortedTableWriter.java) which handles things like writing to the data file using
the default serializers, generic support for a partition index, notifications, metadata collection, and building
a filter. The writer triggers fine-grained events when data are added and those methods can be overridden in
the subclasses to apply specific behaviours (for example, `onPartitionStart`, `onRow`, `onStaticRow`, etc.).
Eventually it calls an abstract `createRowIndexEntry` method which should be implemented in the subclass.
### Scrubber and verifier
A custom sstable format should also come with its own verifier and scrubber implementing [`IVerifier`](IVerifier.java)
and [`IScrubber`](IScrubber.java) interfaces correspondingly. A generic partial implementation is provided in
[`SortedTableVerifier`](format/SortedTableVerifier.java) and [`SortedTableScrubber`](format/SortedTableScrubber.java).

Some files were not shown because too many files have changed in this diff Show More