More accurate skipping of sstables in read path

This patch improves the following things:
1. SSTable metadata will store a covered slice instead of min/max clusterings. The difference is that for slices there is available the type of a bound rather than just a clustering. In particular it will provide the information whether the lower and upper bound of an sstable is opened or closed.
2. SSTable metadata will store a flag whether the SSTable contains any partition level deletions or not
3. The above two changes required to introduce a new major format for SSTables - oa
4. Single partition read command makes use of the above changes. In particular an sstable can be skipped when it does not intersect with the column filter, does not have partition level deletions and does not have statics; In case there are partition level deletions, but the other conditions are satisfied, only the partition header needs to be accessed (tests attached)
5. Skipping SSTables assuming those three conditions are satisfied has been implemented also for partition range queries (tests attached). Also added minor separate statistics to record the number of accessed sstables in partition reads because now not all of them need to be accessed.
6. Artificial lower bound marker is now an object on its own and is not implemented as a special case of range tombstone bound.
7. Extended the lower bound optimization usage due the 1 and 2
8. Do not initialize iterator just to get a cached partition and associated columns index. The purpose of using lower bound optimization was to avoid opening an iterator of an sstable if possible.
9. Add key range to stats metadata

[f369595b1c] Add fields to sstable version and placeholders in stats serializer
[f5c3f772e2] Add hasKeyRange and hasLegacyMinMax
[3cde51f4e1] Add partition level deletion presence marker to sstable stats
[67b2ee2152] Extract AbstractTypeSerializer
[c77b475d6c] Refactor slices intersection checking
[ceb5af3a38] Store min and max clustering as a slice in stats metadata as and improved min/max
[d1f8973929] Implement MetadataCollectorBench
[335369da84] Apply partition level deletion presence marker optimizations to single partition read command
[2497a009b9] Lower bound optimization - add slices and isReverseOrder fields to UnfilteredRowIteratorWithLowerBound
[e32ee31177] Lower bound optimization - Replace usage of RangeTombstoneMarker as a lower bound with ArtificialBoundMarker
[e213e712c4] Lower bound optimization - improve usage of lower bound optimization
[c4f93006b1] Apply read path improvements to partition range queries
[5fa462266c] Add key range to StatsMetadata
[79a7339ed4] Use key range from stats if possible
[266ed2749b] Added new sstables for LegacySSTableTest

patch by Jacek Lewandowski; reviewed by Branimir Lambov and C. Scott Andreas for CASSANDRA-18134

Co-authored-by: Branimir Lambov <blambov>
Co-authored-by: Sylvain Lebresne <pcmanus>
Co-authored-by: Jacek Lewandowski <jacek-lewandowski>
Co-authored-by: Jakub Zytka <jakubzytka>
This commit is contained in:
Jacek Lewandowski 2023-01-04 16:22:52 +01:00
parent 4a555f47ee
commit 24ebd24c79
84 changed files with 2002 additions and 754 deletions

View File

@ -1,4 +1,5 @@
4.2
* More accurate skipping of sstables in read path (CASSANDRA-18134)
* Prepare for JDK17 experimental support (CASSANDRA-18179)
* Remove Scripted UDFs internals; hooks to be added later in CASSANDRA-17281 (CASSANDRA-18252)
* Update JNA to 5.13.0 (CASSANDRA-18050)

View File

@ -124,6 +124,8 @@ New features
- `mask_hash` replaces the data by its hash, according to the specified algorithm.
- 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
Upgrading
---------

View File

@ -118,4 +118,4 @@ abstract class AbstractReadQuery extends MonitorableImpl implements ReadQuery
}
protected abstract void appendCQLWhereClause(StringBuilder sb);
}
}

View File

@ -56,6 +56,18 @@ public interface Clustering<V> extends ClusteringPrefix<V>, IMeasurableMemory
return new BufferClustering(newValues);
}
@Override
default ClusteringBound<V> asStartBound()
{
return ClusteringBound.inclusiveStartOf(this);
}
@Override
default ClusteringBound<V> asEndBound()
{
return ClusteringBound.inclusiveEndOf(this);
}
public default String toString(TableMetadata metadata)
{
StringBuilder sb = new StringBuilder();
@ -178,4 +190,4 @@ public interface Clustering<V> extends ClusteringPrefix<V>, IMeasurableMemory
}
}
}
}
}

View File

@ -21,22 +21,27 @@
package org.apache.cassandra.db;
import java.nio.ByteBuffer;
import java.util.List;
import org.apache.cassandra.db.marshal.ByteBufferAccessor;
import org.apache.cassandra.utils.memory.ByteBufferCloner;
import static org.apache.cassandra.db.AbstractBufferClusteringPrefix.EMPTY_VALUES_ARRAY;
/**
* The start or end of a range of clusterings, either inclusive or exclusive.
*/
public interface ClusteringBound<V> extends ClusteringBoundOrBoundary<V>
{
/** The smallest start bound, i.e. the one that starts before any row. */
public static final ClusteringBound<?> BOTTOM = new BufferClusteringBound(ClusteringPrefix.Kind.INCL_START_BOUND, BufferClusteringBound.EMPTY_VALUES_ARRAY);
ClusteringBound<?> BOTTOM = new BufferClusteringBound(ClusteringPrefix.Kind.INCL_START_BOUND, EMPTY_VALUES_ARRAY);
/** The biggest end bound, i.e. the one that ends after any row. */
public static final ClusteringBound<?> TOP = new BufferClusteringBound(ClusteringPrefix.Kind.INCL_END_BOUND, BufferClusteringBound.EMPTY_VALUES_ARRAY);
ClusteringBound<?> TOP = new BufferClusteringBound(ClusteringPrefix.Kind.INCL_END_BOUND, EMPTY_VALUES_ARRAY);
public static ClusteringPrefix.Kind boundKind(boolean isStart, boolean isInclusive)
/** The biggest start bound, i.e. the one that starts after any row. */
ClusteringBound<?> MAX_START = new BufferClusteringBound(Kind.EXCL_START_BOUND, EMPTY_VALUES_ARRAY);
/** The smallest end bound, i.e. the one that end before any row. */
ClusteringBound<?> MIN_END = new BufferClusteringBound(Kind.EXCL_END_BOUND, EMPTY_VALUES_ARRAY);
static ClusteringPrefix.Kind boundKind(boolean isStart, boolean isInclusive)
{
return isStart
? (isInclusive ? ClusteringPrefix.Kind.INCL_START_BOUND : ClusteringPrefix.Kind.EXCL_START_BOUND)
@ -69,32 +74,14 @@ public interface ClusteringBound<V> extends ClusteringBoundOrBoundary<V>
return kind() == Kind.EXCL_START_BOUND || kind() == Kind.EXCL_END_BOUND;
}
// For use by intersects, it's called with the sstable bound opposite to the slice bound
// (so if the slice bound is a start, it's call with the max sstable bound)
default int compareTo(ClusteringComparator comparator, List<ByteBuffer> sstableBound)
default boolean isArtificial()
{
for (int i = 0; i < sstableBound.size(); i++)
{
// Say the slice bound is a start. It means we're in the case where the max
// sstable bound is say (1:5) while the slice start is (1). So the start
// does start before the sstable end bound (and intersect it). It's the exact
// inverse with a end slice bound.
if (i >= size())
return isStart() ? -1 : 1;
return kind() == Kind.SSTABLE_LOWER_BOUND || kind() == Kind.SSTABLE_UPPER_BOUND;
}
int cmp = comparator.compareComponent(i, get(i), accessor(), sstableBound.get(i), ByteBufferAccessor.instance);
if (cmp != 0)
return cmp;
}
// Say the slice bound is a start. I means we're in the case where the max
// sstable bound is say (1), while the slice start is (1:5). This again means
// that the slice start before the end bound.
if (size() > sstableBound.size())
return isStart() ? -1 : 1;
// The slice bound is equal to the sstable bound. Results depends on whether the slice is inclusive or not
return isInclusive() ? 0 : (isStart() ? 1 : -1);
default ClusteringBound<V> artificialLowerBound(boolean isReversed)
{
return create(!isReversed ? Kind.SSTABLE_LOWER_BOUND : Kind.SSTABLE_UPPER_BOUND, this);
}
static <V> ClusteringBound<V> create(ClusteringPrefix.Kind kind, ClusteringPrefix<V> from)
@ -102,27 +89,27 @@ public interface ClusteringBound<V> extends ClusteringBoundOrBoundary<V>
return from.accessor().factory().bound(kind, from.getRawValues());
}
public static ClusteringBound<?> inclusiveStartOf(ClusteringPrefix<?> from)
static <V> ClusteringBound<V> inclusiveStartOf(ClusteringPrefix<V> from)
{
return create(ClusteringPrefix.Kind.INCL_START_BOUND, from);
}
public static ClusteringBound<?> inclusiveEndOf(ClusteringPrefix<?> from)
static <V> ClusteringBound<V> inclusiveEndOf(ClusteringPrefix<V> from)
{
return create(ClusteringPrefix.Kind.INCL_END_BOUND, from);
}
public static ClusteringBound<?> exclusiveStartOf(ClusteringPrefix<?> from)
static <V> ClusteringBound<V> exclusiveStartOf(ClusteringPrefix<V> from)
{
return create(ClusteringPrefix.Kind.EXCL_START_BOUND, from);
}
public static ClusteringBound<?> exclusiveEndOf(ClusteringPrefix<?> from)
static <V> ClusteringBound<V> exclusiveEndOf(ClusteringPrefix<V> from)
{
return create(ClusteringPrefix.Kind.EXCL_END_BOUND, from);
}
public static ClusteringBound<?> create(ClusteringComparator comparator, boolean isStart, boolean isInclusive, Object... values)
static ClusteringBound<?> create(ClusteringComparator comparator, boolean isStart, boolean isInclusive, Object... values)
{
CBuilder builder = CBuilder.create(comparator);
for (Object val : values)
@ -134,4 +121,18 @@ public interface ClusteringBound<V> extends ClusteringBoundOrBoundary<V>
}
return builder.buildBound(isStart, isInclusive);
}
}
@Override
default ClusteringBound<V> asStartBound()
{
assert isStart();
return this;
}
@Override
default ClusteringBound<V> asEndBound()
{
assert isEnd();
return this;
}
}

View File

@ -37,4 +37,16 @@ public interface ClusteringBoundary<V> extends ClusteringBoundOrBoundary<V>
{
return from.accessor().factory().boundary(kind, from.getRawValues());
}
}
@Override
default ClusteringBound<V> asStartBound()
{
return openBound(false);
}
@Override
default ClusteringBound<V> asEndBound()
{
return closeBound(false);
}
}

View File

@ -445,6 +445,11 @@ public class ClusteringComparator implements Comparator<Clusterable>
return accessor.factory().bound(isEnd ? ClusteringPrefix.Kind.INCL_END_BOUND
: ClusteringPrefix.Kind.EXCL_START_BOUND,
Arrays.copyOf(components, cc));
case ByteSource.LTLT_NEXT_COMPONENT:
case ByteSource.GTGT_NEXT_COMPONENT:
throw new AssertionError("Unexpected sstable lower/upper bound - byte comparable representation of artificial sstable bounds is not supported");
default:
throw new AssertionError("Unexpected separator " + Integer.toHexString(sep) + " in ClusteringBound encoding");
}
@ -550,4 +555,4 @@ public class ClusteringComparator implements Comparator<Clusterable>
{
return Objects.hashCode(clusteringTypes);
}
}
}

View File

@ -64,19 +64,20 @@ public interface ClusteringPrefix<V> extends IMeasurableMemory, Clusterable<V>
{
// WARNING: the ordering of that enum matters because we use ordinal() in the serialization
EXCL_END_BOUND (0, -1, v -> ByteSource.LT_NEXT_COMPONENT),
INCL_START_BOUND (0, -1, v -> ByteSource.LT_NEXT_COMPONENT),
EXCL_END_INCL_START_BOUNDARY(0, -1, v -> ByteSource.LT_NEXT_COMPONENT),
STATIC_CLUSTERING (1, -1, v -> v == Version.LEGACY
? ByteSource.LT_NEXT_COMPONENT + 1
: ByteSource.EXCLUDED),
CLUSTERING (2, 0, v -> v == Version.LEGACY
? ByteSource.NEXT_COMPONENT
: ByteSource.TERMINATOR),
INCL_END_EXCL_START_BOUNDARY(3, 1, v -> ByteSource.GT_NEXT_COMPONENT),
INCL_END_BOUND (3, 1, v -> ByteSource.GT_NEXT_COMPONENT),
EXCL_START_BOUND (3, 1, v -> ByteSource.GT_NEXT_COMPONENT);
// @formatter:off
EXCL_END_BOUND ( 0, -1, v -> ByteSource.LT_NEXT_COMPONENT),
INCL_START_BOUND ( 0, -1, v -> ByteSource.LT_NEXT_COMPONENT),
EXCL_END_INCL_START_BOUNDARY ( 0, -1, v -> ByteSource.LT_NEXT_COMPONENT),
STATIC_CLUSTERING ( 1, -1, v -> v == Version.LEGACY ? ByteSource.LT_NEXT_COMPONENT + 1
: ByteSource.EXCLUDED),
CLUSTERING ( 2, 0, v -> v == Version.LEGACY ? ByteSource.NEXT_COMPONENT
: ByteSource.TERMINATOR),
INCL_END_EXCL_START_BOUNDARY ( 3, 1, v -> ByteSource.GT_NEXT_COMPONENT),
INCL_END_BOUND ( 3, 1, v -> ByteSource.GT_NEXT_COMPONENT),
EXCL_START_BOUND ( 3, 1, v -> ByteSource.GT_NEXT_COMPONENT),
SSTABLE_LOWER_BOUND (-1, -1, v -> ByteSource.LTLT_NEXT_COMPONENT),
SSTABLE_UPPER_BOUND ( 4, 1, v -> ByteSource.GTGT_NEXT_COMPONENT);
// @formatter:on
private final int comparison;
@ -312,6 +313,22 @@ public interface ClusteringPrefix<V> extends IMeasurableMemory, Clusterable<V>
*/
public String toString(TableMetadata metadata);
/**
* Returns this prefix as a start bound.
* If this prefix is a bound, just returns it asserting that it is a start bound.
* If this prefix is a clustering, returns an included start bound.
* If this prefix is a boundary, returns an open bound of it
*/
ClusteringBound<V> asStartBound();
/**
* Returns this prefix as an end bound.
* If this prefix is a bound, just returns it asserting that it is an end bound.
* If this prefix is a clustering, returns an included end bound.
* In this prefix is a boundary, returns a close bound of it.
*/
ClusteringBound<V> asEndBound();
/*
* TODO: we should stop using Clustering for partition keys. Maybe we can add
* a few methods to DecoratedKey so we don't have to (note that while using a Clustering
@ -715,4 +732,4 @@ public interface ClusteringPrefix<V> extends IMeasurableMemory, Clusterable<V>
return equals(prefix, (ClusteringPrefix<?>) o);
}
}
}

View File

@ -18,10 +18,12 @@
package org.apache.cassandra.db;
import java.io.IOException;
import java.util.List;
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;
@ -62,6 +64,7 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
protected static final SelectionDeserializer selectionDeserializer = new Deserializer();
protected final DataRange dataRange;
protected final Slices requestedSlices;
private PartitionRangeReadCommand(boolean isDigest,
int digestVersion,
@ -77,6 +80,8 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
{
super(Kind.PARTITION_RANGE, isDigest, digestVersion, acceptsTransient, metadata, nowInSec, columnFilter, rowFilter, limits, index, trackWarnings);
this.dataRange = dataRange;
this.requestedSlices = dataRange.clusteringIndexFilter.getSlices(metadata());
}
private static PartitionRangeReadCommand create(boolean isDigest,
@ -329,20 +334,43 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
inputCollector.addMemtableIterator(RTBoundValidator.validate(iter, RTBoundValidator.Stage.MEMTABLE, false));
}
int selectedSSTablesCnt = 0;
for (SSTableReader sstable : view.sstables)
{
boolean intersects = intersects(sstable);
boolean hasPartitionLevelDeletions = hasPartitionLevelDeletions(sstable);
boolean hasRequiredStatics = hasRequiredStatics(sstable);
if (!intersects && !hasPartitionLevelDeletions && !hasRequiredStatics)
continue;
@SuppressWarnings("resource") // We close on exception and on closing the result returned by this method
UnfilteredPartitionIterator iter = sstable.partitionIterator(columnFilter(), dataRange(), readCountUpdater);
inputCollector.addSSTableIterator(sstable, RTBoundValidator.validate(iter, RTBoundValidator.Stage.SSTABLE, false));
if (!sstable.isRepaired())
controller.updateMinOldestUnrepairedTombstone(sstable.getMinLocalDeletionTime());
selectedSSTablesCnt++;
}
final int finalSelectedSSTables = selectedSSTablesCnt;
// iterators can be empty for offline tools
if (inputCollector.isEmpty())
return EmptyIterators.unfilteredPartition(metadata());
return checkCacheFilter(UnfilteredPartitionIterators.mergeLazily(inputCollector.finalizeIterators(cfs, nowInSec(), controller.oldestUnrepairedTombstone())), cfs);
List<UnfilteredPartitionIterator> finalizedIterators = inputCollector.finalizeIterators(cfs, nowInSec(), controller.oldestUnrepairedTombstone());
UnfilteredPartitionIterator merged = UnfilteredPartitionIterators.mergeLazily(finalizedIterators);
return checkCacheFilter(Transformation.apply(merged, new Transformation<UnfilteredRowIterator>()
{
@Override
protected void onClose()
{
super.onClose();
cfs.metric.updateSSTableIteratedInRangeRead(finalSelectedSSTables);
}
}), cfs);
}
catch (RuntimeException | Error e)
{
@ -358,6 +386,12 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
}
}
@Override
protected boolean intersects(SSTableReader sstable)
{
return requestedSlices.intersects(sstable.getSSTableMetadata().coveredClustering);
}
/**
* Creates a new {@code SSTableReadsListener} to update the SSTables read counts.
* @return a new {@code SSTableReadsListener} to update the SSTables read counts.
@ -545,4 +579,4 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
return executionController();
}
}
}
}

View File

@ -788,6 +788,19 @@ public abstract class ReadCommand extends AbstractReadQuery
return msg;
}
protected abstract boolean intersects(SSTableReader sstable);
protected boolean hasRequiredStatics(SSTableReader sstable) {
// If some static columns are queried, we should always include the sstable: the clustering values stats of the sstable
// don't tell us if the sstable contains static values in particular.
return !columnFilter().fetchedColumns().statics.isEmpty() && sstable.header.hasStatic();
}
protected boolean hasPartitionLevelDeletions(SSTableReader sstable)
{
return sstable.getSSTableMetadata().hasPartitionLevelDeletions;
}
public abstract Verb verb();
protected abstract void appendCQLWhereClause(StringBuilder sb);
@ -1123,4 +1136,4 @@ public abstract class ReadCommand extends AbstractReadQuery
+ command.indexSerializedSize(version);
}
}
}
}

View File

@ -25,7 +25,6 @@ import com.google.common.collect.ImmutableList;
import org.apache.cassandra.db.filter.ColumnFilter;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.marshal.TypeParser;
import org.apache.cassandra.db.marshal.UTF8Type;
import org.apache.cassandra.db.rows.EncodingStats;
import org.apache.cassandra.exceptions.UnknownColumnException;
@ -38,6 +37,7 @@ import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.schema.ColumnMetadata;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.serializers.AbstractTypeSerializer;
import org.apache.cassandra.utils.ByteBufferUtil;
public class SerializationHeader
@ -398,6 +398,8 @@ public class SerializationHeader
public static class Serializer implements IMetadataComponentSerializer<Component>
{
private final AbstractTypeSerializer typeSerializer = new AbstractTypeSerializer();
public void serializeForMessaging(SerializationHeader header, ColumnFilter selection, DataOutputPlus out, boolean hasStatic) throws IOException
{
EncodingStats.serializer.serialize(header.stats, out);
@ -462,10 +464,8 @@ public class SerializationHeader
{
EncodingStats.serializer.serialize(header.stats, out);
writeType(header.keyType, out);
out.writeUnsignedVInt32(header.clusteringTypes.size());
for (AbstractType<?> type : header.clusteringTypes)
writeType(type, out);
typeSerializer.serialize(header.keyType, out);
typeSerializer.serializeList(header.clusteringTypes, out);
writeColumnsWithTypes(header.staticColumns, out);
writeColumnsWithTypes(header.regularColumns, out);
@ -476,17 +476,11 @@ public class SerializationHeader
{
EncodingStats stats = EncodingStats.serializer.deserialize(in);
AbstractType<?> keyType = readType(in);
int size = in.readUnsignedVInt32();
List<AbstractType<?>> clusteringTypes = new ArrayList<>(size);
for (int i = 0; i < size; i++)
clusteringTypes.add(readType(in));
AbstractType<?> keyType = typeSerializer.deserialize(in);
List<AbstractType<?>> clusteringTypes = typeSerializer.deserializeList(in);
Map<ByteBuffer, AbstractType<?>> staticColumns = new LinkedHashMap<>();
Map<ByteBuffer, AbstractType<?>> regularColumns = new LinkedHashMap<>();
readColumnsWithType(in, staticColumns);
readColumnsWithType(in, regularColumns);
Map<ByteBuffer, AbstractType<?>> staticColumns = readColumnsWithType(in);
Map<ByteBuffer, AbstractType<?>> regularColumns = readColumnsWithType(in);
return new Component(keyType, clusteringTypes, staticColumns, regularColumns, stats);
}
@ -496,10 +490,8 @@ public class SerializationHeader
{
int size = EncodingStats.serializer.serializedSize(header.stats);
size += sizeofType(header.keyType);
size += TypeSizes.sizeofUnsignedVInt(header.clusteringTypes.size());
for (AbstractType<?> type : header.clusteringTypes)
size += sizeofType(type);
size += typeSerializer.serializedSize(header.keyType);
size += typeSerializer.serializedListSize(header.clusteringTypes);
size += sizeofColumnsWithTypes(header.staticColumns);
size += sizeofColumnsWithTypes(header.regularColumns);
@ -512,7 +504,7 @@ public class SerializationHeader
for (Map.Entry<ByteBuffer, AbstractType<?>> entry : columns.entrySet())
{
ByteBufferUtil.writeWithVIntLength(entry.getKey(), out);
writeType(entry.getValue(), out);
typeSerializer.serialize(entry.getValue(), out);
}
}
@ -522,36 +514,21 @@ public class SerializationHeader
for (Map.Entry<ByteBuffer, AbstractType<?>> entry : columns.entrySet())
{
size += ByteBufferUtil.serializedSizeWithVIntLength(entry.getKey());
size += sizeofType(entry.getValue());
size += typeSerializer.serializedSize(entry.getValue());
}
return size;
}
private void readColumnsWithType(DataInputPlus in, Map<ByteBuffer, AbstractType<?>> typeMap) throws IOException
private Map<ByteBuffer, AbstractType<?>> readColumnsWithType(DataInputPlus in) throws IOException
{
int length = in.readUnsignedVInt32();
int length = in.readUnsignedVInt32();
Map<ByteBuffer, AbstractType<?>> typeMap = new LinkedHashMap<>(length);
for (int i = 0; i < length; i++)
{
ByteBuffer name = ByteBufferUtil.readWithVIntLength(in);
typeMap.put(name, readType(in));
typeMap.put(name, typeSerializer.deserialize(in));
}
}
private void writeType(AbstractType<?> type, DataOutputPlus out) throws IOException
{
// TODO: we should have a terser serializaion format. Not a big deal though
ByteBufferUtil.writeWithVIntLength(UTF8Type.instance.decompose(type.toString()), out);
}
private AbstractType<?> readType(DataInputPlus in) throws IOException
{
ByteBuffer raw = ByteBufferUtil.readWithVIntLength(in);
return TypeParser.parse(UTF8Type.instance.compose(raw));
}
private int sizeofType(AbstractType<?> type)
{
return ByteBufferUtil.serializedSizeWithVIntLength(UTF8Type.instance.decompose(type.toString()));
return typeMap;
}
}
}
}

View File

@ -713,14 +713,26 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
break;
}
if (shouldInclude(sstable))
boolean intersects = intersects(sstable);
boolean hasRequiredStatics = hasRequiredStatics(sstable);
boolean hasPartitionLevelDeletions = hasPartitionLevelDeletions(sstable);
if (!intersects && !hasRequiredStatics && !hasPartitionLevelDeletions)
{
nonIntersectingSSTables++;
continue;
}
if (intersects || hasRequiredStatics)
{
if (!sstable.isRepaired())
controller.updateMinOldestUnrepairedTombstone(sstable.getMinLocalDeletionTime());
// 'iter' is added to iterators which is closed on exception, or through the closing of the final merged iterator
@SuppressWarnings("resource")
UnfilteredRowIteratorWithLowerBound iter = makeIterator(cfs, sstable, metricsCollector);
UnfilteredRowIterator iter = intersects ? makeRowIteratorWithLowerBound(cfs, sstable, metricsCollector)
: makeRowIteratorWithSkippedNonStaticContent(cfs, sstable, metricsCollector);
inputCollector.addSSTableIterator(sstable, iter);
mostRecentPartitionTombstone = Math.max(mostRecentPartitionTombstone,
iter.partitionLevelDeletion().markedForDeleteAt());
@ -728,27 +740,30 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
else
{
nonIntersectingSSTables++;
// sstable contains no tombstone if maxLocalDeletionTime == Integer.MAX_VALUE, so we can safely skip those entirely
if (sstable.mayHaveTombstones())
// if the sstable contained range or cell tombstones, it would intersect; since we are here, it means
// that there are no cell or range tombstones we are interested in (due to the filter)
// however, we know that there are partition level deletions in this sstable and we need to make
// an iterator figure out that (see `StatsMetadata.hasPartitionLevelDeletions`)
// 'iter' is added to iterators which is closed on exception, or through the closing of the final merged iterator
@SuppressWarnings("resource")
UnfilteredRowIterator iter = makeRowIteratorWithSkippedNonStaticContent(cfs, sstable, metricsCollector);
// if the sstable contains a partition delete, then we must include it regardless of whether it
// shadows any other data seen locally as we can't guarantee that other replicas have seen it
if (!iter.partitionLevelDeletion().isLive())
{
// 'iter' is added to iterators which is closed on exception, or through the closing of the final merged iterator
@SuppressWarnings("resource")
UnfilteredRowIteratorWithLowerBound iter = makeIterator(cfs, sstable, metricsCollector);
// if the sstable contains a partition delete, then we must include it regardless of whether it
// shadows any other data seen locally as we can't guarantee that other replicas have seen it
if (!iter.partitionLevelDeletion().isLive())
{
if (!sstable.isRepaired())
controller.updateMinOldestUnrepairedTombstone(sstable.getMinLocalDeletionTime());
inputCollector.addSSTableIterator(sstable, iter);
includedDueToTombstones++;
mostRecentPartitionTombstone = Math.max(mostRecentPartitionTombstone,
iter.partitionLevelDeletion().markedForDeleteAt());
}
else
{
iter.close();
}
if (!sstable.isRepaired())
controller.updateMinOldestUnrepairedTombstone(sstable.getMinLocalDeletionTime());
inputCollector.addSSTableIterator(sstable, iter);
includedDueToTombstones++;
mostRecentPartitionTombstone = Math.max(mostRecentPartitionTombstone,
iter.partitionLevelDeletion().markedForDeleteAt());
}
else
{
iter.close();
}
}
}
@ -779,30 +794,52 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
}
}
private boolean shouldInclude(SSTableReader sstable)
@Override
protected boolean intersects(SSTableReader sstable)
{
// If some static columns are queried, we should always include the sstable: the clustering values stats of the sstable
// don't tell us if the sstable contains static values in particular.
// TODO: we could record if a sstable contains any static value at all.
if (!columnFilter().fetchedColumns().statics.isEmpty())
return true;
return clusteringIndexFilter().shouldInclude(sstable);
return clusteringIndexFilter().intersects(sstable.metadata().comparator, sstable.getSSTableMetadata().coveredClustering);
}
private UnfilteredRowIteratorWithLowerBound makeIterator(ColumnFamilyStore cfs,
SSTableReader sstable,
SSTableReadsListener listener)
private UnfilteredRowIteratorWithLowerBound makeRowIteratorWithLowerBound(ColumnFamilyStore cfs,
SSTableReader sstable,
SSTableReadsListener listener)
{
return StorageHook.instance.makeRowIteratorWithLowerBound(cfs,
partitionKey(),
sstable,
partitionKey(),
clusteringIndexFilter(),
columnFilter(),
listener);
}
private UnfilteredRowIterator makeRowIterator(ColumnFamilyStore cfs,
SSTableReader sstable,
ClusteringIndexNamesFilter clusteringIndexFilter,
SSTableReadsListener listener)
{
return StorageHook.instance.makeRowIterator(cfs,
sstable,
partitionKey(),
clusteringIndexFilter.getSlices(cfs.metadata()),
columnFilter(),
clusteringIndexFilter.isReversed(),
listener);
}
private UnfilteredRowIterator makeRowIteratorWithSkippedNonStaticContent(ColumnFamilyStore cfs,
SSTableReader sstable,
SSTableReadsListener listener)
{
return StorageHook.instance.makeRowIterator(cfs,
sstable,
partitionKey(),
Slices.NONE,
columnFilter(),
clusteringIndexFilter().isReversed(),
listener);
}
/**
* Return a wrapped iterator that when closed will update the sstables iterated and READ sample metrics.
* Note that we cannot use the Transformations framework because they greedily get the static row, which
@ -895,23 +932,21 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
if (filter == null)
break;
if (!shouldInclude(sstable))
boolean intersects = intersects(sstable);
boolean hasRequiredStatics = hasRequiredStatics(sstable);
boolean hasPartitionLevelDeletions = hasPartitionLevelDeletions(sstable);
if (!intersects && !hasRequiredStatics)
{
// This mean that nothing queried by the filter can be in the sstable. One exception is the top-level partition deletion
// however: if it is set, it impacts everything and must be included. Getting that top-level partition deletion costs us
// some seek in general however (unless the partition is indexed and is in the key cache), so we first check if the sstable
// has any tombstone at all as a shortcut.
if (!sstable.mayHaveTombstones())
if (!hasPartitionLevelDeletions)
continue; // no tombstone at all, we can skip that sstable
// We need to get the partition deletion and include it if it's live. In any case though, we're done with that sstable.
try (UnfilteredRowIterator iter = StorageHook.instance.makeRowIterator(cfs,
sstable,
partitionKey(),
filter.getSlices(metadata()),
columnFilter(),
filter.isReversed(),
metricsCollector))
try (UnfilteredRowIterator iter = makeRowIteratorWithSkippedNonStaticContent(cfs, sstable, metricsCollector))
{
if (!iter.partitionLevelDeletion().isLive())
{
@ -938,13 +973,7 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
continue;
}
try (UnfilteredRowIterator iter = StorageHook.instance.makeRowIterator(cfs,
sstable,
partitionKey(),
filter.getSlices(metadata()),
columnFilter(),
filter.isReversed(),
metricsCollector))
try (UnfilteredRowIterator iter = makeRowIterator(cfs, sstable, filter, metricsCollector))
{
if (iter.isEmpty())
continue;
@ -1025,7 +1054,7 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
toRemove = new TreeSet<>(result.metadata().comparator);
toRemove.add(clustering);
}
}
}
}
try (UnfilteredRowIterator iterator = result.unfilteredIterator(columnFilter(), clusterings, false))
@ -1339,4 +1368,4 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
return executionController();
}
}
}
}

View File

@ -19,24 +19,28 @@ package org.apache.cassandra.db;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.List;
import java.util.Objects;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.utils.Comparables;
/**
* A slice represents the selection of a range of rows.
* <p>
* A slice has a start and an end bound that are both (potentially full) clustering prefixes.
* A slice selects every rows whose clustering is bigger than the slice start prefix but smaller
* than the end prefix. Both start and end can be either inclusive or exclusive.
* A slice selects every row whose clustering is included within its start and end bounds.
* Both start and end can be either inclusive or exclusive.
*/
public class Slice
{
public static final Serializer serializer = new Serializer();
/** The slice selecting all rows (of a given partition) */
/**
* The slice selecting all rows (of a given partition)
*/
public static final Slice ALL = new Slice(BufferClusteringBound.BOTTOM, BufferClusteringBound.TOP)
{
@Override
@ -46,9 +50,9 @@ public class Slice
}
@Override
public boolean intersects(ClusteringComparator comparator, List<ByteBuffer> minClusteringValues, List<ByteBuffer> maxClusteringValues)
public boolean intersects(ClusteringComparator comparator, Slice other)
{
return true;
return !other.isEmpty(comparator);
}
@Override
@ -91,6 +95,9 @@ public class Slice
return new Slice(builder.buildBound(true, true), builder.buildBound(false, true));
}
/**
* Makes a slice covering a single clustering
*/
public static Slice make(Clustering<?> clustering)
{
// This doesn't give us what we want with the clustering prefix
@ -98,14 +105,25 @@ public class Slice
return new Slice(ClusteringBound.inclusiveStartOf(clustering), ClusteringBound.inclusiveEndOf(clustering));
}
/**
* Makes a slice covering a range from start to end clusterings, with both start and end included
*/
public static Slice make(Clustering<?> start, Clustering<?> end)
{
// This doesn't give us what we want with the clustering prefix
assert start != Clustering.STATIC_CLUSTERING && end != Clustering.STATIC_CLUSTERING;
return new Slice(ClusteringBound.inclusiveStartOf(start), ClusteringBound.inclusiveEndOf(end));
}
/**
* Makes a slice for the given bounds
*/
public static Slice make(ClusteringBoundOrBoundary<?> start, ClusteringBoundOrBoundary<?> end)
{
// This doesn't give us what we want with the clustering prefix
return make(start.asStartBound(), end.asEndBound());
}
public ClusteringBound<?> start()
{
return start;
@ -141,31 +159,23 @@ public class Slice
* Return whether the slice formed by the two provided bound is empty or not.
*
* @param comparator the comparator to compare the bounds.
* @param start the start for the slice to consider. This must be a start bound.
* @param end the end for the slice to consider. This must be an end bound.
* @param start the start for the slice to consider. This must be a start bound.
* @param end the end for the slice to consider. This must be an end bound.
* @return whether the slice formed by {@code start} and {@code end} is
* empty or not.
*/
public static boolean isEmpty(ClusteringComparator comparator, ClusteringBound<?> start, ClusteringBound<?> end)
{
assert start.isStart() && end.isEnd();
int cmp = comparator.compare(start, end);
if (cmp < 0)
return false;
else if (cmp > 0)
return true;
else
return start.isExclusive() || end.isExclusive();
// Note: the comparator orders inclusive starts and exclusive ends as equal, and inclusive ends as being greater than starts.
return comparator.compare(start, end) >= 0;
}
/**
* Returns whether a given clustering or bound is included in this slice.
*
* @param comparator the comparator for the table this is a slice of.
* @param bound the bound to test inclusion of.
*
* @param bound the bound to test inclusion of.
* @return whether {@code bound} is within the bounds of this slice.
*/
public boolean includes(ClusteringComparator comparator, ClusteringPrefix<?> bound)
@ -176,13 +186,12 @@ public class Slice
/**
* Returns a slice for continuing paging from the last returned clustering prefix.
*
* @param comparator the comparator for the table this is a filter for.
* @param comparator the comparator for the table this is a filter for.
* @param lastReturned the last clustering that was returned for the query we are paging for. The
* resulting slices will be such that only results coming stricly after {@code lastReturned} are returned
* (where coming after means "greater than" if {@code !reversed} and "lesser than" otherwise).
* @param inclusive whether or not we want to include the {@code lastReturned} in the newly returned page of results.
* @param reversed whether the query we're paging for is reversed or not.
*
* resulting slices will be such that only results coming stricly after {@code lastReturned} are returned
* (where coming after means "greater than" if {@code !reversed} and "lesser than" otherwise).
* @param inclusive whether we want to include the {@code lastReturned} in the newly returned page of results.
* @param reversed whether the query we're paging for is reversed or not.
* @return a new slice that selects results coming after {@code lastReturned}, or {@code null} if paging
* the resulting slice selects nothing (i.e. if it originally selects nothing coming after {@code lastReturned}).
*/
@ -229,20 +238,18 @@ public class Slice
}
/**
* Given the per-clustering column minimum and maximum value a sstable contains, whether or not this slice potentially
* intersects that sstable or not.
* Whether this slice and the provided slice intersects.
*
* @param comparator the comparator for the table this is a slice of.
* @param minClusteringValues the smallest values for each clustering column that a sstable contains.
* @param maxClusteringValues the biggest values for each clustering column that a sstable contains.
*
* @return whether the slice might intersects with the sstable having {@code minClusteringValues} and
* {@code maxClusteringValues}.
* @param other the other slice to check intersection with.
* @return whether this slice intersects {@code other}.
*/
public boolean intersects(ClusteringComparator comparator, List<ByteBuffer> minClusteringValues, List<ByteBuffer> maxClusteringValues)
public boolean intersects(ClusteringComparator comparator, Slice other)
{
// If this slice starts after max clustering or ends before min clustering, it can't intersect
return start.compareTo(comparator, maxClusteringValues) <= 0 && end.compareTo(comparator, minClusteringValues) >= 0;
// Construct the intersection of the two slices and check if it is non-empty.
// This also works correctly when one or more of the inputs are be empty (i.e. with end <= start).
return comparator.compare(Comparables.max(start, other.start, comparator),
Comparables.min(end, other.end, comparator)) < 0;
}
public String toString(ClusteringComparator comparator)
@ -269,12 +276,12 @@ public class Slice
@Override
public boolean equals(Object other)
{
if(!(other instanceof Slice))
if (!(other instanceof Slice))
return false;
Slice that = (Slice)other;
Slice that = (Slice) other;
return this.start().equals(that.start())
&& this.end().equals(that.end());
&& this.end().equals(that.end());
}
@Override
@ -294,7 +301,7 @@ public class Slice
public long serializedSize(Slice slice, int version, List<AbstractType<?>> types)
{
return ClusteringBound.serializer.serializedSize(slice.start, version, types)
+ ClusteringBound.serializer.serializedSize(slice.end, version, types);
+ ClusteringBound.serializer.serializedSize(slice.end, version, types);
}
public Slice deserialize(DataInputPlus in, int version, List<AbstractType<?>> types) throws IOException
@ -304,4 +311,4 @@ public class Slice
return new Slice(start, end);
}
}
}
}

View File

@ -20,6 +20,7 @@ package org.apache.cassandra.db;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.stream.Collectors;
import com.google.common.base.Preconditions;
import com.google.common.collect.Iterators;
@ -55,7 +56,7 @@ public abstract class Slices implements Iterable<Slice>
* Creates a {@code Slices} object that contains a single slice.
*
* @param comparator the comparator for the table {@code slice} is a slice of.
* @param slice the single slice that the return object should contains.
* @param slice the single slice that the return object should contain.
*
* @return the newly created {@code Slices} object.
*/
@ -69,16 +70,16 @@ public abstract class Slices implements Iterable<Slice>
}
/**
* Whether the slices has a lower bound, that is whether it's first slice start is {@code Slice.BOTTOM}.
* Whether the slices instance has a lower bound, that is whether it's first slice start is {@code Slice.BOTTOM}.
*
* @return whether the slices has a lower bound.
* @return whether this slices instance has a lower bound.
*/
public abstract boolean hasLowerBound();
/**
* Whether the slices has an upper bound, that is whether it's last slice end is {@code Slice.TOP}.
* Whether the slices instance has an upper bound, that is whether it's last slice end is {@code Slice.TOP}.
*
* @return whether the slices has an upper bound.
* @return whether this slices instance has an upper bound.
*/
public abstract boolean hasUpperBound();
@ -96,6 +97,16 @@ public abstract class Slices implements Iterable<Slice>
*/
public abstract Slice get(int i);
public ClusteringBound<?> start()
{
return get(0).start();
}
public ClusteringBound<?> end()
{
return get(size() - 1).end();
}
/**
* Returns slices for continuing the paging of those slices given the last returned clustering prefix.
*
@ -103,7 +114,7 @@ public abstract class Slices implements Iterable<Slice>
* @param lastReturned the last clustering that was returned for the query we are paging for. The
* resulting slices will be such that only results coming stricly after {@code lastReturned} are returned
* (where coming after means "greater than" if {@code !reversed} and "lesser than" otherwise).
* @param inclusive whether or not we want to include the {@code lastReturned} in the newly returned page of results.
* @param inclusive whether we want to include the {@code lastReturned} in the newly returned page of results.
* @param reversed whether the query we're paging for is reversed or not.
*
* @return new slices that select results coming after {@code lastReturned}.
@ -130,18 +141,13 @@ public abstract class Slices implements Iterable<Slice>
*/
public abstract boolean selects(Clustering<?> clustering);
/**
* Given the per-clustering column minimum and maximum value a sstable contains, whether or not this slices potentially
* intersects that sstable or not.
* Checks whether any of the slices intersects witht the given one.
*
* @param minClusteringValues the smallest values for each clustering column that a sstable contains.
* @param maxClusteringValues the biggest values for each clustering column that a sstable contains.
*
* @return whether the slices might intersects with the sstable having {@code minClusteringValues} and
* {@code maxClusteringValues}.
* @return {@code true} if there exists a slice which ({@link Slice#intersects(ClusteringComparator, Slice)}) with
* the provided slice
*/
public abstract boolean intersects(List<ByteBuffer> minClusteringValues, List<ByteBuffer> maxClusteringValues);
public abstract boolean intersects(Slice slice);
public abstract String toCQLString(TableMetadata metadata, RowFilter rowFilter);
@ -157,12 +163,12 @@ public abstract class Slices implements Iterable<Slice>
/**
* In simple object that allows to test the inclusion of rows in those slices assuming those rows
* are passed (to {@link #includes}) in clustering order (or reverse clustering ordered, depending
* of the argument passed to {@link #inOrderTester}).
* on the argument passed to {@link #inOrderTester}).
*/
public interface InOrderTester
{
public boolean includes(Clustering<?> value);
public boolean isDone();
boolean includes(Clustering<?> value);
boolean isDone();
}
/**
@ -243,17 +249,12 @@ public abstract class Slices implements Iterable<Slice>
if (slices.size() <= 1)
return slices;
Collections.sort(slices, new Comparator<Slice>()
{
@Override
public int compare(Slice s1, Slice s2)
{
int c = comparator.compare(s1.start(), s2.start());
if (c != 0)
return c;
slices.sort((s1, s2) -> {
int c = comparator.compare(s1.start(), s2.start());
if (c != 0)
return c;
return comparator.compare(s1.end(), s2.end());
}
return comparator.compare(s2.end(), s1.end());
});
List<Slice> slicesCopy = new ArrayList<>(slices.size());
@ -278,12 +279,7 @@ public abstract class Slices implements Iterable<Slice>
}
if (includesStart)
{
last = Slice.make(last.start(), s2.end());
continue;
}
assert !includesFinish;
}
slicesCopy.add(last);
@ -302,7 +298,7 @@ public abstract class Slices implements Iterable<Slice>
return;
List<AbstractType<?>> types = slices == ALL
? Collections.<AbstractType<?>>emptyList()
? Collections.emptyList()
: ((ArrayBackedSlices)slices).comparator.subtypes();
for (Slice slice : slices)
@ -317,7 +313,7 @@ public abstract class Slices implements Iterable<Slice>
return size;
List<AbstractType<?>> types = slices instanceof SelectAllSlices
? Collections.<AbstractType<?>>emptyList()
? Collections.emptyList()
: ((ArrayBackedSlices)slices).comparator.subtypes();
for (Slice slice : slices)
@ -441,11 +437,12 @@ public abstract class Slices implements Iterable<Slice>
return Slices.NONE;
}
public boolean intersects(List<ByteBuffer> minClusteringValues, List<ByteBuffer> maxClusteringValues)
@Override
public boolean intersects(Slice slice)
{
for (Slice slice : this)
for (Slice s : this)
{
if (slice.intersects(comparator, minClusteringValues, maxClusteringValues))
if (s.intersects(comparator, slice))
return true;
}
return false;
@ -540,15 +537,7 @@ public abstract class Slices implements Iterable<Slice>
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("{");
for (int i = 0; i < slices.length; i++)
{
if (i > 0)
sb.append(", ");
sb.append(slices[i].toString(comparator));
}
return sb.append("}").toString();
return Arrays.stream(slices).map(s -> s.toString(comparator)).collect(Collectors.joining(", ", "{", "}"));
}
@Override
@ -636,7 +625,7 @@ public abstract class Slices implements Iterable<Slice>
operator = first.startInclusive ? Operator.LTE : Operator.LT;
else
operator = first.startInclusive ? Operator.GTE : Operator.GT;
sb.append(' ').append(operator.toString()).append(' ')
sb.append(' ').append(operator).append(' ')
.append(column.type.toCQLString(first.startValue));
rowFilter = rowFilter.without(column, operator, first.startValue);
}
@ -650,7 +639,7 @@ public abstract class Slices implements Iterable<Slice>
operator = first.endInclusive ? Operator.GTE : Operator.GT;
else
operator = first.endInclusive ? Operator.LTE : Operator.LT;
sb.append(' ').append(operator.toString()).append(' ')
sb.append(' ').append(operator).append(' ')
.append(column.type.toCQLString(first.endValue));
rowFilter = rowFilter.without(column, operator, first.endValue);
}
@ -667,7 +656,7 @@ public abstract class Slices implements Iterable<Slice>
return sb.toString();
}
// An somewhat adhoc utility class only used by nameAsCQLString
// Somewhat adhoc utility class only used by nameAsCQLString
private static class ComponentOfSlice
{
public final boolean startInclusive;
@ -768,7 +757,8 @@ public abstract class Slices implements Iterable<Slice>
return trivialTester;
}
public boolean intersects(List<ByteBuffer> minClusteringValues, List<ByteBuffer> maxClusteringValues)
@Override
public boolean intersects(Slice slice)
{
return true;
}
@ -844,7 +834,8 @@ public abstract class Slices implements Iterable<Slice>
return trivialTester;
}
public boolean intersects(List<ByteBuffer> minClusteringValues, List<ByteBuffer> maxClusteringValues)
@Override
public boolean intersects(Slice slice)
{
return false;
}
@ -866,4 +857,4 @@ public abstract class Slices implements Iterable<Slice>
return "";
}
}
}
}

View File

@ -35,11 +35,11 @@ public interface StorageHook
public void reportWrite(TableId tableId, PartitionUpdate partitionUpdate);
public void reportRead(TableId tableId, DecoratedKey key);
public UnfilteredRowIteratorWithLowerBound makeRowIteratorWithLowerBound(ColumnFamilyStore cfs,
DecoratedKey partitionKey,
SSTableReader sstable,
ClusteringIndexFilter filter,
ColumnFilter selectedColumns,
SSTableReadsListener listener);
SSTableReader sstable,
DecoratedKey partitionKey,
ClusteringIndexFilter filter,
ColumnFilter selectedColumns,
SSTableReadsListener listener);
public UnfilteredRowIterator makeRowIterator(ColumnFamilyStore cfs,
SSTableReader sstable,
DecoratedKey key,
@ -63,8 +63,7 @@ public interface StorageHook
public void reportRead(TableId tableId, DecoratedKey key) {}
public UnfilteredRowIteratorWithLowerBound makeRowIteratorWithLowerBound(ColumnFamilyStore cfs,
DecoratedKey partitionKey,
SSTableReader sstable,
SSTableReader sstable, DecoratedKey partitionKey,
ClusteringIndexFilter filter,
ColumnFilter selectedColumns,
SSTableReadsListener listener)
@ -88,4 +87,4 @@ public interface StorageHook
}
};
}
}
}

View File

@ -23,7 +23,6 @@ import org.apache.cassandra.db.*;
import org.apache.cassandra.db.partitions.CachedPartition;
import org.apache.cassandra.db.partitions.Partition;
import org.apache.cassandra.db.rows.*;
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.schema.TableMetadata;
@ -142,13 +141,14 @@ public interface ClusteringIndexFilter
public UnfilteredRowIterator getUnfilteredRowIterator(ColumnFilter columnFilter, Partition partition);
/**
* Whether the provided sstable may contain data that is selected by this filter (based on the sstable metadata).
* Whether the data selected by this filter intersects with the provided slice.
*
* @param sstable the sstable for which we want to test the need for inclusion.
* @param comparator the comparator of the table this if a filter on.
* @param slice the slice to check intersection with,
*
* @return whether {@code sstable} should be included to answer this filter.
* @return whether the data selected by this filter intersects with {@code slice}.
*/
public boolean shouldInclude(SSTableReader sstable);
public boolean intersects(ClusteringComparator comparator, Slice slice);
public Kind kind();
@ -161,4 +161,4 @@ public interface ClusteringIndexFilter
public ClusteringIndexFilter deserialize(DataInputPlus in, int version, TableMetadata metadata) throws IOException;
public long serializedSize(ClusteringIndexFilter filter, int version);
}
}
}

View File

@ -18,7 +18,6 @@
package org.apache.cassandra.db.filter;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.*;
import org.apache.cassandra.cql3.Operator;
@ -26,7 +25,6 @@ import org.apache.cassandra.db.*;
import org.apache.cassandra.db.partitions.*;
import org.apache.cassandra.db.rows.*;
import org.apache.cassandra.db.transform.Transformation;
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.schema.ColumnMetadata;
@ -142,16 +140,11 @@ public class ClusteringIndexNamesFilter extends AbstractClusteringIndexFilter
return partition.unfilteredIterator(columnFilter, clusteringsInQueryOrder, isReversed());
}
public boolean shouldInclude(SSTableReader sstable)
public boolean intersects(ClusteringComparator comparator, Slice slice)
{
ClusteringComparator comparator = sstable.metadata().comparator;
List<ByteBuffer> minClusteringValues = sstable.getSSTableMetadata().minClusteringValues;
List<ByteBuffer> maxClusteringValues = sstable.getSSTableMetadata().maxClusteringValues;
// If any of the requested clustering is within the bounds covered by the sstable, we need to include the sstable
for (Clustering<?> clustering : clusterings)
{
if (Slice.make(clustering).intersects(comparator, minClusteringValues, maxClusteringValues))
if (slice.includes(comparator, clustering))
return true;
}
return false;
@ -255,4 +248,4 @@ public class ClusteringIndexNamesFilter extends AbstractClusteringIndexFilter
}
}
}
}
}

View File

@ -18,18 +18,15 @@
package org.apache.cassandra.db.filter;
import java.io.IOException;
import java.util.List;
import java.nio.ByteBuffer;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.rows.*;
import org.apache.cassandra.db.partitions.CachedPartition;
import org.apache.cassandra.db.partitions.Partition;
import org.apache.cassandra.db.rows.*;
import org.apache.cassandra.db.transform.Transformation;
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.schema.TableMetadata;
/**
* A filter over a single partition.
@ -126,15 +123,9 @@ public class ClusteringIndexSliceFilter extends AbstractClusteringIndexFilter
return partition.unfilteredIterator(columnFilter, slices, reversed);
}
public boolean shouldInclude(SSTableReader sstable)
public boolean intersects(ClusteringComparator comparator, Slice slice)
{
List<ByteBuffer> minClusteringValues = sstable.getSSTableMetadata().minClusteringValues;
List<ByteBuffer> maxClusteringValues = sstable.getSSTableMetadata().maxClusteringValues;
if (minClusteringValues.isEmpty() || maxClusteringValues.isEmpty())
return true;
return slices.intersects(minClusteringValues, maxClusteringValues);
return slices.intersects(slice);
}
public String toString(TableMetadata metadata)
@ -176,4 +167,4 @@ public class ClusteringIndexSliceFilter extends AbstractClusteringIndexFilter
return new ClusteringIndexSliceFilter(slices, reversed);
}
}
}
}

View File

@ -18,14 +18,7 @@
package org.apache.cassandra.db.marshal;
import org.apache.cassandra.db.AbstractArrayClusteringPrefix;
import org.apache.cassandra.db.ArrayClustering;
import org.apache.cassandra.db.ArrayClusteringBound;
import org.apache.cassandra.db.ArrayClusteringBoundary;
import org.apache.cassandra.db.Clustering;
import org.apache.cassandra.db.ClusteringBound;
import org.apache.cassandra.db.ClusteringBoundary;
import org.apache.cassandra.db.ClusteringPrefix;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.rows.ArrayCell;
import org.apache.cassandra.db.rows.Cell;
import org.apache.cassandra.db.rows.CellPath;
@ -74,6 +67,13 @@ class ByteArrayObjectFactory implements ValueAccessor.ObjectFactory<byte[]>
private static final ArrayClusteringBound TOP_BOUND = new ArrayClusteringBound(ClusteringPrefix.Kind.INCL_END_BOUND,
AbstractArrayClusteringPrefix.EMPTY_VALUES_ARRAY);
/** The biggest start bound, i.e. the one that starts after any row. */
private static final ArrayClusteringBound MAX_START_BOUND = new ArrayClusteringBound(ClusteringPrefix.Kind.EXCL_START_BOUND,
AbstractArrayClusteringPrefix.EMPTY_VALUES_ARRAY);
/** The smallest end bound, i.e. the one that end before any row. */
private static final ArrayClusteringBound MIN_END_BOUND = new ArrayClusteringBound(ClusteringPrefix.Kind.EXCL_END_BOUND,
AbstractArrayClusteringPrefix.EMPTY_VALUES_ARRAY);
public Cell<byte[]> cell(ColumnMetadata column, long timestamp, int ttl, int localDeletionTime, byte[] value, CellPath path)
{
return new ArrayCell(column, timestamp, ttl, localDeletionTime, value, path);
@ -101,11 +101,19 @@ class ByteArrayObjectFactory implements ValueAccessor.ObjectFactory<byte[]>
public ClusteringBound<byte[]> bound(ClusteringPrefix.Kind kind)
{
return kind.isStart() ? BOTTOM_BOUND : TOP_BOUND;
switch (kind)
{
case EXCL_END_BOUND: return MIN_END_BOUND;
case INCL_START_BOUND: return BOTTOM_BOUND;
case INCL_END_BOUND: return TOP_BOUND;
case EXCL_START_BOUND: return MAX_START_BOUND;
default:
throw new AssertionError(String.format("Unexpected kind %s for empty bound or boundary", kind));
}
}
public ClusteringBoundary<byte[]> boundary(ClusteringPrefix.Kind kind, byte[]... values)
{
return new ArrayClusteringBoundary(kind, values);
}
}
}

View File

@ -20,14 +20,7 @@ package org.apache.cassandra.db.marshal;
import java.nio.ByteBuffer;
import org.apache.cassandra.db.AbstractBufferClusteringPrefix;
import org.apache.cassandra.db.BufferClustering;
import org.apache.cassandra.db.BufferClusteringBound;
import org.apache.cassandra.db.BufferClusteringBoundary;
import org.apache.cassandra.db.Clustering;
import org.apache.cassandra.db.ClusteringBound;
import org.apache.cassandra.db.ClusteringBoundary;
import org.apache.cassandra.db.ClusteringPrefix;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.rows.BufferCell;
import org.apache.cassandra.db.rows.Cell;
import org.apache.cassandra.db.rows.CellPath;
@ -42,6 +35,13 @@ class ByteBufferObjectFactory implements ValueAccessor.ObjectFactory<ByteBuffer>
private static final BufferClusteringBound TOP_BOUND = new BufferClusteringBound(ClusteringPrefix.Kind.INCL_END_BOUND,
AbstractBufferClusteringPrefix.EMPTY_VALUES_ARRAY);
/** The biggest start bound, i.e. the one that starts after any row. */
private static final BufferClusteringBound MAX_START_BOUND = new BufferClusteringBound(ClusteringPrefix.Kind.EXCL_START_BOUND,
AbstractBufferClusteringPrefix.EMPTY_VALUES_ARRAY);
/** The smallest end bound, i.e. the one that end before any row. */
private static final BufferClusteringBound MIN_END_BOUND = new BufferClusteringBound(ClusteringPrefix.Kind.EXCL_END_BOUND,
AbstractBufferClusteringPrefix.EMPTY_VALUES_ARRAY);
static final ValueAccessor.ObjectFactory<ByteBuffer> instance = new ByteBufferObjectFactory();
private ByteBufferObjectFactory() {}
@ -73,11 +73,19 @@ class ByteBufferObjectFactory implements ValueAccessor.ObjectFactory<ByteBuffer>
public ClusteringBound<ByteBuffer> bound(ClusteringPrefix.Kind kind)
{
return kind.isStart() ? BOTTOM_BOUND : TOP_BOUND;
switch (kind)
{
case EXCL_END_BOUND: return MIN_END_BOUND;
case INCL_START_BOUND: return BOTTOM_BOUND;
case INCL_END_BOUND: return TOP_BOUND;
case EXCL_START_BOUND: return MAX_START_BOUND;
default:
throw new AssertionError(String.format("Unexpected kind %s for empty bound or boundary", kind));
}
}
public ClusteringBoundary<ByteBuffer> boundary(ClusteringPrefix.Kind kind, ByteBuffer... values)
{
return new BufferClusteringBoundary(kind, values);
}
}
}

View File

@ -22,9 +22,10 @@ import org.apache.cassandra.db.rows.Cell;
public interface PartitionStatisticsCollector
{
public void update(LivenessInfo info);
public void update(DeletionTime deletionTime);
public void update(Cell<?> cell);
public void updateColumnSetPerRow(long columnSetInRow);
public void updateHasLegacyCounterShards(boolean hasLegacyCounterShards);
}
void update(LivenessInfo info);
void updatePartitionDeletion(DeletionTime dt);
void update(DeletionTime deletionTime);
void update(Cell<?> cell);
void updateColumnSetPerRow(long columnSetInRow);
void updateHasLegacyCounterShards(boolean hasLegacyCounterShards);
}

View File

@ -386,4 +386,4 @@ public abstract class UnfilteredPartitionIterators
};
}
}
}
}

View File

@ -0,0 +1,59 @@
/*
* 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.rows;
import java.util.Objects;
import org.apache.cassandra.db.ClusteringBound;
import org.apache.cassandra.db.DeletionTime;
import org.apache.cassandra.schema.TableMetadata;
public class ArtificialBoundMarker extends RangeTombstoneBoundMarker
{
public ArtificialBoundMarker(ClusteringBound<?> bound)
{
super(bound, DeletionTime.LIVE);
assert bound.isArtificial();
}
@Override
public boolean equals(Object other)
{
if (this == other)
return true;
if (!(other instanceof ArtificialBoundMarker))
return false;
ArtificialBoundMarker that = (ArtificialBoundMarker) other;
return Objects.equals(bound, that.bound);
}
@Override
public int hashCode()
{
return Objects.hash(bound);
}
@Override
public String toString(TableMetadata metadata)
{
return String.format("LowerBoundMarker %s", bound.toString(metadata));
}
}

View File

@ -215,6 +215,12 @@ public class EncodingStats implements IMeasurableMemory
updateLocalDeletionTime(deletionTime.localDeletionTime());
}
@Override
public void updatePartitionDeletion(DeletionTime dt)
{
update(dt);
}
public void updateTimestamp(long timestamp)
{
isTimestampSet = true;
@ -285,4 +291,4 @@ public class EncodingStats implements IMeasurableMemory
return new EncodingStats(minTimestamp, minLocalDeletionTime, minTTL);
}
}
}
}

View File

@ -21,13 +21,19 @@
package org.apache.cassandra.db.rows;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import org.apache.cassandra.db.marshal.ByteBufferAccessor;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.db.*;
import com.google.common.annotations.VisibleForTesting;
import org.apache.cassandra.db.Clusterable;
import org.apache.cassandra.db.ClusteringBound;
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;
@ -35,6 +41,7 @@ import org.apache.cassandra.io.sstable.IndexInfo;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.io.sstable.format.SSTableReadsListener;
import org.apache.cassandra.io.sstable.metadata.StatsMetadata;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.utils.IteratorWithLowerBound;
/**
@ -48,10 +55,11 @@ import org.apache.cassandra.utils.IteratorWithLowerBound;
public class UnfilteredRowIteratorWithLowerBound extends LazilyInitializedUnfilteredRowIterator implements IteratorWithLowerBound<Unfiltered>
{
private final SSTableReader sstable;
private final ClusteringIndexFilter filter;
private final Slices slices;
private final boolean isReverseOrder;
private final ColumnFilter selectedColumns;
private final SSTableReadsListener listener;
private ClusteringBound<?> lowerBound;
private Optional<Unfiltered> lowerBoundMarker;
private boolean firstItemRetrieved;
public UnfilteredRowIteratorWithLowerBound(DecoratedKey partitionKey,
@ -59,26 +67,45 @@ public class UnfilteredRowIteratorWithLowerBound extends LazilyInitializedUnfilt
ClusteringIndexFilter filter,
ColumnFilter selectedColumns,
SSTableReadsListener listener)
{
this(partitionKey, sstable, filter.getSlices(sstable.metadata()), filter.isReversed(), selectedColumns, listener);
}
@VisibleForTesting
public UnfilteredRowIteratorWithLowerBound(DecoratedKey partitionKey,
SSTableReader sstable,
Slices slices,
boolean isReverseOrder,
ColumnFilter selectedColumns,
SSTableReadsListener listener)
{
super(partitionKey);
this.sstable = sstable;
this.filter = filter;
this.slices = slices;
this.isReverseOrder = isReverseOrder;
this.selectedColumns = selectedColumns;
this.listener = listener;
this.lowerBound = null;
this.firstItemRetrieved = false;
}
public Unfiltered lowerBound()
{
if (lowerBound != null)
return makeBound(lowerBound);
if (lowerBoundMarker != null)
return lowerBoundMarker.orElse(null);
// The partition index lower bound is more accurate than the sstable metadata lower bound but it is only
// present if the iterator has already been initialized, which we only do when there are tombstones since in
// this case we cannot use the sstable metadata clustering values
ClusteringBound<?> ret = getPartitionIndexLowerBound();
return ret != null ? makeBound(ret) : makeBound(getMetadataLowerBound());
// lower bound from cache may be more accurate as it stores information about clusterings range for that exact
// row, so we try it first (without initializing iterator)
ClusteringBound<?> lowerBound = maybeGetLowerBoundFromKeyCache();
if (lowerBound == null)
// If we couldn't get the lower bound from cache, we try with metadata
lowerBound = maybeGetLowerBoundFromMetadata();
if (lowerBound != null)
lowerBoundMarker = Optional.of(makeBound(lowerBound));
else
lowerBoundMarker = Optional.empty();
return lowerBoundMarker.orElse(null);
}
private Unfiltered makeBound(ClusteringBound<?> bound)
@ -86,21 +113,15 @@ public class UnfilteredRowIteratorWithLowerBound extends LazilyInitializedUnfilt
if (bound == null)
return null;
if (lowerBound != bound)
lowerBound = bound;
return new RangeTombstoneBoundMarker(lowerBound, DeletionTime.LIVE);
return new ArtificialBoundMarker(bound);
}
@Override
protected UnfilteredRowIterator initializeIterator()
{
@SuppressWarnings("resource") // 'iter' is added to iterators which is closed on exception, or through the closing of the final merged iterator
UnfilteredRowIterator iter = RTBoundValidator.validate(
sstable.rowIterator(partitionKey(), filter.getSlices(metadata()), selectedColumns, filter.isReversed(), listener),
RTBoundValidator.Stage.SSTABLE,
false
);
UnfilteredRowIterator iter = RTBoundValidator.validate(sstable.rowIterator(partitionKey(), slices, selectedColumns, isReverseOrder, listener),
RTBoundValidator.Stage.SSTABLE, false);
return iter;
}
@ -113,19 +134,20 @@ public class UnfilteredRowIteratorWithLowerBound extends LazilyInitializedUnfilt
// Check that the lower bound is not bigger than the first item retrieved
firstItemRetrieved = true;
Unfiltered lowerBound = lowerBound();
if (lowerBound != null && ret != null)
assert comparator().compare(lowerBound, ret.clustering()) <= 0
: String.format("Lower bound [%s ]is bigger than first returned value [%s] for sstable %s",
lowerBound.toString(metadata()),
ret.toString(metadata()),
sstable.getFilename());
assert comparator().compare(lowerBound.clustering(), ret.clustering()) <= 0
: String.format("Lower bound [%s ]is bigger than first returned value [%s] for sstable %s",
lowerBound.clustering().toString(metadata()),
ret.toString(metadata()),
sstable.getFilename());
return ret;
}
private Comparator<Clusterable> comparator()
{
return filter.isReversed() ? metadata().comparator.reversed() : metadata().comparator;
return isReverseOrder ? metadata().comparator.reversed() : metadata().comparator;
}
@Override
@ -137,7 +159,7 @@ public class UnfilteredRowIteratorWithLowerBound extends LazilyInitializedUnfilt
@Override
public boolean isReverseOrder()
{
return filter.isReversed();
return isReverseOrder;
}
@Override
@ -155,7 +177,7 @@ public class UnfilteredRowIteratorWithLowerBound extends LazilyInitializedUnfilt
@Override
public DeletionTime partitionLevelDeletion()
{
if (!sstable.mayHaveTombstones())
if (!sstable.getSSTableMetadata().hasPartitionLevelDeletions)
return DeletionTime.LIVE;
return super.partitionLevelDeletion();
@ -170,39 +192,21 @@ public class UnfilteredRowIteratorWithLowerBound extends LazilyInitializedUnfilt
return super.staticRow();
}
private static <V> ClusteringBound<V> createInclusiveOpen(boolean isReversed, ClusteringPrefix<V> from)
{
return from.accessor().factory().inclusiveOpen(isReversed, from.getRawValues());
}
/**
* @return the lower bound stored on the index entry for this partition, if available.
*/
private ClusteringBound<?> getPartitionIndexLowerBound()
private ClusteringBound<?> maybeGetLowerBoundFromKeyCache()
{
// NOTE: CASSANDRA-11206 removed the lookup against the key-cache as the IndexInfo objects are no longer
// in memory for not heap backed IndexInfo objects (so, these are on disk).
// CASSANDRA-11369 is there to fix this afterwards.
// Creating the iterator ensures that rowIndexEntry is loaded if available (partitions bigger than
// DatabaseDescriptor.column_index_size)
if (!canUseMetadataLowerBound())
maybeInit();
RowIndexEntry rowIndexEntry = sstable.getCachedPosition(partitionKey(), false);
RowIndexEntry<?> rowIndexEntry = sstable.getCachedPosition(partitionKey(), false);
if (rowIndexEntry == null || !rowIndexEntry.indexOnHeap())
return null;
try (RowIndexEntry.IndexInfoRetriever onHeapRetriever = rowIndexEntry.openWithIndex(null))
{
IndexInfo column = onHeapRetriever.columnsIndex(filter.isReversed() ? rowIndexEntry.columnsIndexCount() - 1 : 0);
ClusteringPrefix<?> lowerBoundPrefix = filter.isReversed() ? column.lastName : column.firstName;
assert lowerBoundPrefix.getRawValues().length <= metadata().comparator.size() :
String.format("Unexpected number of clustering values %d, expected %d or fewer for %s",
lowerBoundPrefix.getRawValues().length,
metadata().comparator.size(),
sstable.getFilename());
return createInclusiveOpen(filter.isReversed(), lowerBoundPrefix);
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)
{
@ -212,51 +216,53 @@ public class UnfilteredRowIteratorWithLowerBound extends LazilyInitializedUnfilt
/**
* Whether we can use the clustering values in the stats of the sstable to build the lower bound.
* <p>
* Currently, the clustering values of the stats file records for each clustering component the min and max
* value seen, null excluded. In other words, having a non-null value for a component in those min/max clustering
* values does _not_ guarantee that there isn't an unfiltered in the sstable whose clustering has either no value for
* that component (it's a prefix) or a null value.
* <p>
* This is problematic as this means we can't in general build a lower bound from those values since the "min"
* values doesn't actually guarantee minimality.
* <p>
* However, we can use those values if we can guarantee that no clustering in the sstable 1) is a true prefix and
* 2) uses null values. Nat having true prefixes means having no range tombstone markers since rows use
* {@link Clustering} which is always "full" (all components are always present). As for null values, we happen to
* only allow those in compact tables (for backward compatibility), so we can simply exclude those tables.
* <p>
* Note that the information we currently have at our disposal make this condition less precise that it could be.
* In particular, {@link SSTableReader#mayHaveTombstones} could return {@code true} (making us not use the stats)
* because of cell tombstone or even expiring cells even if the sstable has no range tombstone markers, even though
* it's really only markers we want to exclude here (more precisely, as said above, we want to exclude anything
* whose clustering is not "full", but that's only markers). It wouldn't be very hard to collect whether a sstable
* has any range tombstone marker however so it's a possible improvement.
*/
private boolean canUseMetadataLowerBound()
{
// Side-note: pre-2.1 sstable stat file had clustering value arrays whose size may not match the comparator size
// and that would break getMetadataLowerBound. We don't support upgrade from 2.0 to 3.0 directly however so it's
// not a true concern. Besides, !sstable.mayHaveTombstones already ensure this is a 3.0 sstable anyway.
return !sstable.mayHaveTombstones() && !sstable.metadata().isCompactTable();
if (sstable.metadata().isCompactTable())
return false;
Slices requestedSlices = slices;
if (requestedSlices.isEmpty())
return true;
// Simply exclude the cases where lower bound would not be used anyway, that is, the start of covered range of
// clusterings in sstable is lower than the requested slice. In such case, we need to access that sstable's
// iterator anyway so there is no need to use a lower bound optimization extra complexity.
if (!isReverseOrder())
{
return !requestedSlices.hasLowerBound() ||
metadata().comparator.compare(requestedSlices.start(), sstable.getSSTableMetadata().coveredClustering.start()) < 0;
}
else
{
return !requestedSlices.hasUpperBound() ||
metadata().comparator.compare(requestedSlices.end(), sstable.getSSTableMetadata().coveredClustering.end()) > 0;
}
}
/**
* @return a global lower bound made from the clustering values stored in the sstable metadata, note that
* this currently does not correctly compare tombstone bounds, especially ranges.
*/
private ClusteringBound<?> getMetadataLowerBound()
private ClusteringBound<?> maybeGetLowerBoundFromMetadata()
{
if (!canUseMetadataLowerBound())
return null;
final StatsMetadata m = sstable.getSSTableMetadata();
List<ByteBuffer> vals = filter.isReversed() ? m.maxClusteringValues : m.minClusteringValues;
assert vals.size() <= metadata().comparator.size() :
ClusteringBound<?> bound = m.coveredClustering.open(isReverseOrder);
assertBoundSize(bound);
return bound.artificialLowerBound(isReverseOrder);
}
private void assertBoundSize(ClusteringPrefix<?> lowerBound)
{
assert lowerBound.size() <= metadata().comparator.size() :
String.format("Unexpected number of clustering values %d, expected %d or fewer for %s",
vals.size(),
lowerBound.size(),
metadata().comparator.size(),
sstable.getFilename());
return ByteBufferAccessor.instance.factory().inclusiveOpen(filter.isReversed(), vals.toArray(new ByteBuffer[vals.size()]));
}
}
}

View File

@ -88,6 +88,12 @@ public abstract class SSTableReaderBuilder
this.openReason = openReason;
this.header = header;
this.readerFactory = descriptor.getFormat().getReaderFactory();
if (statsMetadata.firstKey != null && statsMetadata.lastKey != null)
{
this.first = metadata.partitioner.decorateKey(statsMetadata.firstKey);
this.last = metadata.partitioner.decorateKey(statsMetadata.lastKey);
}
}
public abstract SSTableReader build();
@ -140,8 +146,13 @@ public abstract class SSTableReaderBuilder
metadata.partitioner,
metadata.params.minIndexInterval,
metadata.params.maxIndexInterval);
first = metadata.partitioner.decorateKey(ByteBufferUtil.readWithLength(iStream));
last = metadata.partitioner.decorateKey(ByteBufferUtil.readWithLength(iStream));
if (first == null || last == null)
{
// this is a legacy code in order to be able to load older sstables
// since version 'nc', first and last keys are stored in stats file
first = metadata.partitioner.decorateKey(ByteBufferUtil.readWithLength(iStream));
last = metadata.partitioner.decorateKey(ByteBufferUtil.readWithLength(iStream));
}
}
catch (IOException e)
{

View File

@ -327,7 +327,9 @@ public abstract class SSTableWriter extends SSTable implements Transactional
repairedAt,
pendingRepair,
isTransient,
header);
header,
SSTable.getMinimalKey(first).getKey(),
SSTable.getMinimalKey(last).getKey());
}
protected StatsMetadata statsMetadata()

View File

@ -17,19 +17,19 @@
*/
package org.apache.cassandra.io.sstable.format;
import java.util.Objects;
import java.util.regex.Pattern;
/**
* A set of feature flags associated with a SSTable format
*
* <p>
* versions are denoted as [major][minor]. Minor versions must be forward-compatible:
* new fields are allowed in e.g. the metadata component, but fields can't be removed
* or have their size changed.
*
* <p>
* Minor versions were introduced with version "hb" for Cassandra 1.0.3; prior to that,
* we always incremented the major version.
*
*/
public abstract class Version
{
@ -37,6 +37,7 @@ public abstract class Version
protected final String version;
protected final SSTableFormat format;
protected Version(SSTableFormat format, String version)
{
this.format = format;
@ -62,12 +63,31 @@ public abstract class Version
/**
* The old bloomfilter format serializes the data as BIG_ENDIAN long's, the new one uses the
* same format as in memory (serializes as bytes).
*
* @return True if the bloomfilter file is old serialization format
*/
public abstract boolean hasOldBfFormat();
/**
* @deprecated it is replaced by {@link #hasImprovedMinMax()} since 'nc' and to be completetly removed since 'oa'
*/
@Deprecated
public abstract boolean hasAccurateMinMax();
/**
* @deprecated it is replaced by {@link #hasImprovedMinMax()} since 'nc' and to be completetly removed since 'oa'
*/
@Deprecated
public abstract boolean hasLegacyMinMax();
public abstract boolean hasOriginatingHostId();
public abstract boolean hasImprovedMinMax();
public abstract boolean hasPartitionLevelDeletionsPresenceMarker();
public abstract boolean hasKeyRange();
public String getVersion()
{
return version;
@ -89,6 +109,7 @@ public abstract class Version
}
abstract public boolean isCompatible();
abstract public boolean isCompatibleForStreaming();
@Override
@ -98,16 +119,13 @@ public abstract class Version
}
@Override
public boolean equals(Object o)
public boolean equals(Object other)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (this == other) return true;
if (other == null || getClass() != other.getClass()) return false;
Version version1 = (Version) o;
if (version != null ? !version.equals(version1.version) : version1.version != null) return false;
return true;
Version otherVersion = (Version) other;
return Objects.equals(version, otherVersion.version);
}
@Override
@ -115,6 +133,4 @@ public abstract class Version
{
return version != null ? version.hashCode() : 0;
}
public abstract boolean hasOriginatingHostId();
}

View File

@ -121,7 +121,7 @@ public class BigFormat implements SSTableFormat
// we always incremented the major version.
static class BigVersion extends Version
{
public static final String current_version = "nb";
public static final String current_version = "nc";
public static final String earliest_supported_version = "ma";
// ma (3.0.0): swap bf hash order
@ -133,19 +133,24 @@ public class BigFormat implements SSTableFormat
// na (4.0-rc1): uncompressed chunks, pending repair session, isTransient, checksummed sstable metadata file, new Bloomfilter format
// nb (4.0.0): originating host id
// nc (4.1): improved min/max, partition level deletion presence marker, key range (CASSANDRA-18134)
//
// NOTE: when adding a new version, please add that to LegacySSTableTest, too.
private final boolean isLatestVersion;
public final int correspondingMessagingVersion;
private final int correspondingMessagingVersion;
private final boolean hasCommitLogLowerBound;
private final boolean hasCommitLogIntervals;
private final boolean hasAccurateMinMax;
private final boolean hasLegacyMinMax;
private final boolean hasOriginatingHostId;
public final boolean hasMaxCompressedLength;
private final boolean hasMaxCompressedLength;
private final boolean hasPendingRepair;
private final boolean hasMetadataChecksum;
private final boolean hasIsTransient;
private final boolean hasImprovedMinMax;
private final boolean hasPartitionLevelDeletionPresenceMarker;
private final boolean hasKeyRange;
/**
* CASSANDRA-9067: 4.0 bloom filter representation changed (two longs just swapped)
@ -162,13 +167,17 @@ public class BigFormat implements SSTableFormat
hasCommitLogLowerBound = version.compareTo("mb") >= 0;
hasCommitLogIntervals = version.compareTo("mc") >= 0;
hasAccurateMinMax = version.compareTo("md") >= 0;
hasOriginatingHostId = version.matches("(m[e-z])|(n[b-z])");
hasAccurateMinMax = version.matches("(m[d-z])|(n[a-z])"); // deprecated in 'nc' and to be removed in 'oa'
hasLegacyMinMax = version.matches("(m[a-z])|(n[a-z])"); // deprecated in 'nc' and to be removed in 'oa'
hasOriginatingHostId = version.matches("(m[e-z])") || version.compareTo("nb") >= 0;
hasMaxCompressedLength = version.compareTo("na") >= 0;
hasPendingRepair = version.compareTo("na") >= 0;
hasIsTransient = version.compareTo("na") >= 0;
hasMetadataChecksum = version.compareTo("na") >= 0;
hasOldBfFormat = version.compareTo("na") < 0;
hasImprovedMinMax = version.compareTo("nc") >= 0;
hasPartitionLevelDeletionPresenceMarker = version.compareTo("nc") >= 0;
hasKeyRange = version.compareTo("nc") >= 0;
}
@Override
@ -177,6 +186,12 @@ public class BigFormat implements SSTableFormat
return isLatestVersion;
}
@Override
public int correspondingMessagingVersion()
{
return correspondingMessagingVersion;
}
@Override
public boolean hasCommitLogLowerBound()
{
@ -189,6 +204,13 @@ public class BigFormat implements SSTableFormat
return hasCommitLogIntervals;
}
@Override
public boolean hasMaxCompressedLength()
{
return hasMaxCompressedLength;
}
@Override
public boolean hasPendingRepair()
{
return hasPendingRepair;
@ -200,24 +222,55 @@ public class BigFormat implements SSTableFormat
return hasIsTransient;
}
@Override
public int correspondingMessagingVersion()
{
return correspondingMessagingVersion;
}
@Override
public boolean hasMetadataChecksum()
{
return hasMetadataChecksum;
}
@Override
public boolean hasOldBfFormat()
{
return hasOldBfFormat;
}
@Override
public boolean hasAccurateMinMax()
{
return hasAccurateMinMax;
}
@Override
public boolean hasLegacyMinMax()
{
return hasLegacyMinMax;
}
@Override
public boolean hasOriginatingHostId()
{
return hasOriginatingHostId;
}
@Override
public boolean hasImprovedMinMax()
{
return hasImprovedMinMax;
}
@Override
public boolean hasPartitionLevelDeletionsPresenceMarker()
{
return hasPartitionLevelDeletionPresenceMarker;
}
@Override
public boolean hasKeyRange()
{
return hasKeyRange;
}
@Override
public boolean isCompatible()
{
return version.compareTo(earliest_supported_version) >= 0 && version.charAt(0) <= current_version.charAt(0);
@ -228,22 +281,5 @@ public class BigFormat implements SSTableFormat
{
return isCompatible() && version.charAt(0) == current_version.charAt(0);
}
public boolean hasOriginatingHostId()
{
return hasOriginatingHostId;
}
@Override
public boolean hasMaxCompressedLength()
{
return hasMaxCompressedLength;
}
@Override
public boolean hasOldBfFormat()
{
return hasOldBfFormat;
}
}
}

View File

@ -304,7 +304,7 @@ public class BigTableWriter extends SSTableWriter
@Override
public RangeTombstoneMarker applyToMarker(RangeTombstoneMarker marker)
{
collector.updateClusteringValues(marker.clustering());
collector.updateClusteringValuesByBoundOrBoundary(marker.clustering());
if (marker.isBoundary())
{
RangeTombstoneBoundaryMarker bm = (RangeTombstoneBoundaryMarker)marker;
@ -327,7 +327,7 @@ public class BigTableWriter extends SSTableWriter
@Override
public DeletionTime applyToDeletion(DeletionTime deletionTime)
{
collector.update(deletionTime);
collector.updatePartitionDeletion(deletionTime);
return deletionTime;
}
}
@ -662,4 +662,4 @@ public class BigTableWriter extends SSTableWriter
return accumulate;
}
}
}
}

View File

@ -18,37 +18,42 @@
package org.apache.cassandra.io.sstable.metadata;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import com.google.common.base.Preconditions;
import com.clearspring.analytics.stream.cardinality.HyperLogLogPlus;
import com.clearspring.analytics.stream.cardinality.ICardinality;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.Clustering;
import org.apache.cassandra.db.ClusteringBound;
import org.apache.cassandra.db.ClusteringBoundOrBoundary;
import org.apache.cassandra.db.ClusteringComparator;
import org.apache.cassandra.db.ClusteringPrefix;
import org.apache.cassandra.db.DeletionTime;
import org.apache.cassandra.db.LivenessInfo;
import org.apache.cassandra.db.SerializationHeader;
import org.apache.cassandra.db.Slice;
import org.apache.cassandra.db.commitlog.CommitLogPosition;
import org.apache.cassandra.db.commitlog.IntervalSet;
import org.apache.cassandra.db.partitions.PartitionStatisticsCollector;
import org.apache.cassandra.db.rows.Cell;
import org.apache.cassandra.db.rows.Unfiltered;
import org.apache.cassandra.io.sstable.SSTable;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.service.ActiveRepairService;
import org.apache.cassandra.service.StorageService;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.EstimatedHistogram;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.MurmurHash;
import org.apache.cassandra.utils.TimeUUID;
import org.apache.cassandra.utils.streamhist.TombstoneHistogram;
import org.apache.cassandra.utils.streamhist.StreamingTombstoneHistogramBuilder;
import org.apache.cassandra.utils.streamhist.TombstoneHistogram;
public class MetadataCollector implements PartitionStatisticsCollector
{
public static final double NO_COMPRESSION_RATIO = -1.0;
private static final ByteBuffer[] EMPTY_CLUSTERING = new ByteBuffer[0];
static EstimatedHistogram defaultCellPerPartitionCountHistogram()
{
@ -82,15 +87,18 @@ public class MetadataCollector implements PartitionStatisticsCollector
NO_COMPRESSION_RATIO,
defaultTombstoneDropTimeHistogram(),
0,
Collections.<ByteBuffer>emptyList(),
Collections.<ByteBuffer>emptyList(),
Collections.emptyList(),
Slice.ALL,
true,
ActiveRepairService.UNREPAIRED_SSTABLE,
-1,
-1,
null,
null,
false);
false,
true,
ByteBufferUtil.EMPTY_BYTE_BUFFER,
ByteBufferUtil.EMPTY_BYTE_BUFFER);
}
protected EstimatedHistogram estimatedPartitionSize = defaultPartitionSizeHistogram();
@ -103,9 +111,25 @@ public class MetadataCollector implements PartitionStatisticsCollector
protected double compressionRatio = NO_COMPRESSION_RATIO;
protected StreamingTombstoneHistogramBuilder estimatedTombstoneDropTime = new StreamingTombstoneHistogramBuilder(SSTable.TOMBSTONE_HISTOGRAM_BIN_SIZE, SSTable.TOMBSTONE_HISTOGRAM_SPOOL_SIZE, SSTable.TOMBSTONE_HISTOGRAM_TTL_ROUND_SECONDS);
protected int sstableLevel;
private ClusteringPrefix<?> minClustering = null;
private ClusteringPrefix<?> maxClustering = null;
/**
* The smallest clustering prefix for any {@link Unfiltered} in the sstable.
*
* <p>This is always either a Clustering, or a start bound (since for any end range tombstone bound, there should
* be a corresponding start bound that is smaller).
*/
private ClusteringPrefix<?> minClustering = ClusteringBound.MAX_START;
/**
* The largest clustering prefix for any {@link Unfiltered} in the sstable.
*
* <p>This is always either a Clustering, or an end bound (since for any start range tombstone bound, there should
* be a corresponding end bound that is bigger).
*/
private ClusteringPrefix<?> maxClustering = ClusteringBound.MIN_END;
private boolean clusteringInitialized = false;
protected boolean hasLegacyCounterShards = false;
private boolean hasPartitionLevelDeletions = false;
protected long totalColumnsSet;
protected long totalRows;
public int totalTombstones;
@ -201,6 +225,12 @@ public class MetadataCollector implements PartitionStatisticsCollector
updateTombstoneCount();
}
public void updatePartitionDeletion(DeletionTime dt)
{
if (!dt.isLive())
hasPartitionLevelDeletions = true;
update(dt);
}
public void update(DeletionTime dt)
{
if (!dt.isLive())
@ -251,11 +281,54 @@ public class MetadataCollector implements PartitionStatisticsCollector
return this;
}
public MetadataCollector updateClusteringValues(ClusteringPrefix<?> clustering)
public void updateClusteringValues(Clustering<?> clustering)
{
minClustering = minClustering == null || comparator.compare(clustering, minClustering) < 0 ? clustering : minClustering;
maxClustering = maxClustering == null || comparator.compare(clustering, maxClustering) > 0 ? clustering : maxClustering;
return this;
if (clustering == Clustering.STATIC_CLUSTERING)
return;
// In case of monotonically growing stream of clusterings, we will usually require only one comparison
// because if we detected X is greater than the current MAX, then it cannot be lower than the current MIN
// at the same time. The only case when we need to update MIN when the current MAX was detected to be updated
// is the case when MIN was not yet initialized and still point the ClusteringBound.MAX_START
if (comparator.compare(clustering, maxClustering) > 0)
{
maxClustering = clustering;
if (minClustering == ClusteringBound.MAX_START)
minClustering = clustering;
}
else if (comparator.compare(clustering, minClustering) < 0)
{
minClustering = clustering;
}
}
public void updateClusteringValuesByBoundOrBoundary(ClusteringBoundOrBoundary<?> clusteringBoundOrBoundary)
{
// In a SSTable, every opening marker will be closed, so the start of a range tombstone marker will never be
// the maxClustering (the corresponding close might though) and there is no point in doing the comparison
// (and vice-versa for the close). By the same reasoning, a boundary will never be either the min or max
// clustering, and we can save on comparisons.
if (clusteringBoundOrBoundary.isBoundary())
return;
// see the comment in updateClusteringValues(Clustering)
if (comparator.compare(clusteringBoundOrBoundary, maxClustering) > 0)
{
if (clusteringBoundOrBoundary.kind().isEnd())
maxClustering = clusteringBoundOrBoundary;
// note that since we excluded boundaries above, there is no way that the provided clustering prefix is
// a start and en end at the same time
else if (minClustering == ClusteringBound.MAX_START)
minClustering = clusteringBoundOrBoundary;
}
else if (comparator.compare(clusteringBoundOrBoundary, minClustering) < 0)
{
if (clusteringBoundOrBoundary.kind().isStart())
minClustering = clusteringBoundOrBoundary;
else if (maxClustering == ClusteringBound.MIN_END)
maxClustering = clusteringBoundOrBoundary;
}
}
public void updateHasLegacyCounterShards(boolean hasLegacyCounterShards)
@ -263,12 +336,11 @@ public class MetadataCollector implements PartitionStatisticsCollector
this.hasLegacyCounterShards = this.hasLegacyCounterShards || hasLegacyCounterShards;
}
public Map<MetadataType, MetadataComponent> finalizeMetadata(String partitioner, double bloomFilterFPChance, long repairedAt, TimeUUID pendingRepair, boolean isTransient, SerializationHeader header)
public Map<MetadataType, MetadataComponent> finalizeMetadata(String partitioner, double bloomFilterFPChance, long repairedAt, TimeUUID pendingRepair, boolean isTransient, SerializationHeader header, ByteBuffer firstKey, ByteBuffer lastKey)
{
Preconditions.checkState((minClustering == null && maxClustering == null)
|| comparator.compare(maxClustering, minClustering) >= 0);
ByteBuffer[] minValues = minClustering != null ? minClustering.retainable().getBufferArray() : EMPTY_CLUSTERING;
ByteBuffer[] maxValues = maxClustering != null ? maxClustering.retainable().getBufferArray() : EMPTY_CLUSTERING;
assert minClustering.kind() == ClusteringPrefix.Kind.CLUSTERING || minClustering.kind().isStart();
assert maxClustering.kind() == ClusteringPrefix.Kind.CLUSTERING || maxClustering.kind().isEnd();
Map<MetadataType, MetadataComponent> components = new EnumMap<>(MetadataType.class);
components.put(MetadataType.VALIDATION, new ValidationMetadata(partitioner, bloomFilterFPChance));
components.put(MetadataType.STATS, new StatsMetadata(estimatedPartitionSize,
@ -283,15 +355,18 @@ public class MetadataCollector implements PartitionStatisticsCollector
compressionRatio,
estimatedTombstoneDropTime.build(),
sstableLevel,
makeList(minValues),
makeList(maxValues),
comparator.subtypes(),
Slice.make(minClustering.retainable().asStartBound(), maxClustering.retainable().asEndBound()),
hasLegacyCounterShards,
repairedAt,
totalColumnsSet,
totalRows,
originatingHostId,
pendingRepair,
isTransient));
isTransient,
hasPartitionLevelDeletions,
firstKey,
lastKey));
components.put(MetadataType.COMPACTION, new CompactionMetadata(cardinality));
components.put(MetadataType.HEADER, header.toComponent());
return components;
@ -305,18 +380,6 @@ public class MetadataCollector implements PartitionStatisticsCollector
estimatedTombstoneDropTime.releaseBuffers();
}
private static List<ByteBuffer> makeList(ByteBuffer[] values)
{
// In most case, l will be the same size than values, but it's possible for it to be smaller
List<ByteBuffer> l = new ArrayList<ByteBuffer>(values.length);
for (int i = 0; i < values.length; i++)
if (values[i] == null)
break;
else
l.add(values[i]);
return l;
}
public static class MinMaxLongTracker
{
private final long defaultMin;

View File

@ -19,28 +19,34 @@ package org.apache.cassandra.io.sstable.metadata;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.db.rows.EncodingStats;
import org.apache.cassandra.db.BufferClusteringBound;
import org.apache.cassandra.db.ClusteringBound;
import org.apache.cassandra.db.Slice;
import org.apache.cassandra.db.TypeSizes;
import org.apache.cassandra.db.commitlog.CommitLogPosition;
import org.apache.cassandra.db.commitlog.IntervalSet;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.rows.Cell;
import org.apache.cassandra.db.rows.EncodingStats;
import org.apache.cassandra.io.ISerializer;
import org.apache.cassandra.io.sstable.format.Version;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.serializers.AbstractTypeSerializer;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.EstimatedHistogram;
import org.apache.cassandra.utils.TimeUUID;
import org.apache.cassandra.utils.streamhist.TombstoneHistogram;
import org.apache.cassandra.utils.UUIDSerializer;
import org.apache.cassandra.utils.streamhist.TombstoneHistogram;
/**
* SSTable metadata that always stay on heap.
@ -62,8 +68,7 @@ public class StatsMetadata extends MetadataComponent
public final double compressionRatio;
public final TombstoneHistogram estimatedTombstoneDropTime;
public final int sstableLevel;
public final List<ByteBuffer> minClusteringValues;
public final List<ByteBuffer> maxClusteringValues;
public final Slice coveredClustering;
public final boolean hasLegacyCounterShards;
public final long repairedAt;
public final long totalColumnsSet;
@ -74,6 +79,23 @@ public class StatsMetadata extends MetadataComponent
// just holds the current encoding stats to avoid allocating - it is not serialized
public final EncodingStats encodingStats;
// Used to serialize min/max clustering. Can be null if the metadata was deserialized from a legacy version
private final List<AbstractType<?>> clusteringTypes;
/**
* This boolean is used as an approximation of whether a given key can be guaranteed not to have partition
* deletions in this sstable. Obviously, this is pretty imprecise: a single partition deletion in the sstable
* means we have to assume _any_ key may have a partition deletion. This is still likely useful as workloads that
* does not use partition level deletions, or only very rarely, are probably not that rare.
* TODO we could replace this by a small bloom-filter instead; the only downside being that we'd have to care about
* the size of this bloom filters not getting out of hands, and it's a tiny bit unclear if it's worth the added
* complexity.
*/
public final boolean hasPartitionLevelDeletions;
public final ByteBuffer firstKey;
public final ByteBuffer lastKey;
public StatsMetadata(EstimatedHistogram estimatedPartitionSize,
EstimatedHistogram estimatedCellPerPartitionCount,
IntervalSet<CommitLogPosition> commitLogIntervals,
@ -86,15 +108,18 @@ public class StatsMetadata extends MetadataComponent
double compressionRatio,
TombstoneHistogram estimatedTombstoneDropTime,
int sstableLevel,
List<ByteBuffer> minClusteringValues,
List<ByteBuffer> maxClusteringValues,
List<AbstractType<?>> clusteringTypes,
Slice coveredClustering,
boolean hasLegacyCounterShards,
long repairedAt,
long totalColumnsSet,
long totalRows,
UUID originatingHostId,
TimeUUID pendingRepair,
boolean isTransient)
boolean isTransient,
boolean hasPartitionLevelDeletions,
ByteBuffer firstKey,
ByteBuffer lastKey)
{
this.estimatedPartitionSize = estimatedPartitionSize;
this.estimatedCellPerPartitionCount = estimatedCellPerPartitionCount;
@ -108,8 +133,8 @@ public class StatsMetadata extends MetadataComponent
this.compressionRatio = compressionRatio;
this.estimatedTombstoneDropTime = estimatedTombstoneDropTime;
this.sstableLevel = sstableLevel;
this.minClusteringValues = minClusteringValues;
this.maxClusteringValues = maxClusteringValues;
this.clusteringTypes = clusteringTypes;
this.coveredClustering = coveredClustering;
this.hasLegacyCounterShards = hasLegacyCounterShards;
this.repairedAt = repairedAt;
this.totalColumnsSet = totalColumnsSet;
@ -118,6 +143,9 @@ public class StatsMetadata extends MetadataComponent
this.pendingRepair = pendingRepair;
this.isTransient = isTransient;
this.encodingStats = new EncodingStats(minTimestamp, minLocalDeletionTime, minTTL);
this.hasPartitionLevelDeletions = hasPartitionLevelDeletions;
this.firstKey = firstKey;
this.lastKey = lastKey;
}
public MetadataType getType()
@ -163,15 +191,18 @@ public class StatsMetadata extends MetadataComponent
compressionRatio,
estimatedTombstoneDropTime,
newLevel,
minClusteringValues,
maxClusteringValues,
clusteringTypes,
coveredClustering,
hasLegacyCounterShards,
repairedAt,
totalColumnsSet,
totalRows,
originatingHostId,
pendingRepair,
isTransient);
isTransient,
hasPartitionLevelDeletions,
firstKey,
lastKey);
}
public StatsMetadata mutateRepairedMetadata(long newRepairedAt, TimeUUID newPendingRepair, boolean newIsTransient)
@ -188,15 +219,18 @@ public class StatsMetadata extends MetadataComponent
compressionRatio,
estimatedTombstoneDropTime,
sstableLevel,
minClusteringValues,
maxClusteringValues,
clusteringTypes,
coveredClustering,
hasLegacyCounterShards,
newRepairedAt,
totalColumnsSet,
totalRows,
originatingHostId,
newPendingRepair,
newIsTransient);
newIsTransient,
hasPartitionLevelDeletions,
firstKey,
lastKey);
}
@Override
@ -220,13 +254,15 @@ public class StatsMetadata extends MetadataComponent
.append(estimatedTombstoneDropTime, that.estimatedTombstoneDropTime)
.append(sstableLevel, that.sstableLevel)
.append(repairedAt, that.repairedAt)
.append(maxClusteringValues, that.maxClusteringValues)
.append(minClusteringValues, that.minClusteringValues)
.append(coveredClustering, that.coveredClustering)
.append(hasLegacyCounterShards, that.hasLegacyCounterShards)
.append(totalColumnsSet, that.totalColumnsSet)
.append(totalRows, that.totalRows)
.append(originatingHostId, that.originatingHostId)
.append(pendingRepair, that.pendingRepair)
.append(hasPartitionLevelDeletions, that.hasPartitionLevelDeletions)
.append(firstKey, that.firstKey)
.append(lastKey, that.lastKey)
.build();
}
@ -247,13 +283,15 @@ public class StatsMetadata extends MetadataComponent
.append(estimatedTombstoneDropTime)
.append(sstableLevel)
.append(repairedAt)
.append(maxClusteringValues)
.append(minClusteringValues)
.append(coveredClustering)
.append(hasLegacyCounterShards)
.append(totalColumnsSet)
.append(totalRows)
.append(originatingHostId)
.append(pendingRepair)
.append(hasPartitionLevelDeletions)
.append(firstKey)
.append(lastKey)
.build();
}
@ -261,6 +299,8 @@ public class StatsMetadata extends MetadataComponent
{
private static final Logger logger = LoggerFactory.getLogger(StatsMetadataSerializer.class);
private final AbstractTypeSerializer typeSerializer = new AbstractTypeSerializer();
public int serializedSize(Version version, StatsMetadata component) throws IOException
{
int size = 0;
@ -270,14 +310,19 @@ public class StatsMetadata extends MetadataComponent
size += 8 + 8 + 4 + 4 + 4 + 4 + 8 + 8; // mix/max timestamp(long), min/maxLocalDeletionTime(int), min/max TTL, compressionRatio(double), repairedAt (long)
size += TombstoneHistogram.serializer.serializedSize(component.estimatedTombstoneDropTime);
size += TypeSizes.sizeof(component.sstableLevel);
// min column names
size += 4;
for (ByteBuffer value : component.minClusteringValues)
size += 2 + value.remaining(); // with short length
// max column names
size += 4;
for (ByteBuffer value : component.maxClusteringValues)
size += 2 + value.remaining(); // with short length
if (version.hasLegacyMinMax())
{
// min column names
size += 4;
ClusteringBound<?> minClusteringValues = component.coveredClustering.start();
size += minClusteringValues.size() * 2 /* short length */ + minClusteringValues.dataSize();
// max column names
size += 4;
ClusteringBound<?> maxClusteringValues = component.coveredClustering.end();
size += maxClusteringValues.size() * 2 /* short length */ + maxClusteringValues.dataSize();
}
size += TypeSizes.sizeof(component.hasLegacyCounterShards);
size += 8 + 8; // totalColumnsSet, totalRows
if (version.hasCommitLogLowerBound())
@ -304,6 +349,25 @@ public class StatsMetadata extends MetadataComponent
size += UUIDSerializer.serializer.serializedSize(component.originatingHostId, version.correspondingMessagingVersion());
}
if (version.hasPartitionLevelDeletionsPresenceMarker())
{
size += TypeSizes.sizeof(component.hasPartitionLevelDeletions);
}
if (version.hasImprovedMinMax())
{
size += typeSerializer.serializedListSize(component.clusteringTypes);
size += Slice.serializer.serializedSize(component.coveredClustering,
version.correspondingMessagingVersion(),
component.clusteringTypes);
}
if (version.hasKeyRange())
{
size += ByteBufferUtil.serializedSizeWithVIntLength(component.firstKey);
size += ByteBufferUtil.serializedSizeWithVIntLength(component.lastKey);
}
return size;
}
@ -322,12 +386,27 @@ public class StatsMetadata extends MetadataComponent
TombstoneHistogram.serializer.serialize(component.estimatedTombstoneDropTime, out);
out.writeInt(component.sstableLevel);
out.writeLong(component.repairedAt);
out.writeInt(component.minClusteringValues.size());
for (ByteBuffer value : component.minClusteringValues)
ByteBufferUtil.writeWithShortLength(value, out);
out.writeInt(component.maxClusteringValues.size());
for (ByteBuffer value : component.maxClusteringValues)
ByteBufferUtil.writeWithShortLength(value, out);
if (version.hasLegacyMinMax())
{
ClusteringBound<?> minClusteringValues = component.coveredClustering.start();
out.writeInt(countUntilNull(minClusteringValues.getBufferArray()));
for (ByteBuffer value : minClusteringValues.getBufferArray())
{
if (value == null)
break;
ByteBufferUtil.writeWithShortLength(value, out);
}
ClusteringBound<?> maxClusteringValues = component.coveredClustering.end();
out.writeInt(countUntilNull(maxClusteringValues.getBufferArray()));
for (ByteBuffer value : maxClusteringValues.getBufferArray())
{
if (value == null)
break;
ByteBufferUtil.writeWithShortLength(value, out);
}
}
out.writeBoolean(component.hasLegacyCounterShards);
out.writeLong(component.totalColumnsSet);
@ -368,6 +447,27 @@ public class StatsMetadata extends MetadataComponent
out.writeByte(0);
}
}
if (version.hasPartitionLevelDeletionsPresenceMarker())
{
out.writeBoolean(component.hasPartitionLevelDeletions);
}
if (version.hasImprovedMinMax())
{
assert component.clusteringTypes != null;
typeSerializer.serializeList(component.clusteringTypes, out);
Slice.serializer.serialize(component.coveredClustering,
out,
version.correspondingMessagingVersion(),
component.clusteringTypes);
}
if (version.hasKeyRange())
{
ByteBufferUtil.writeWithVIntLength(component.firstKey, out);
ByteBufferUtil.writeWithVIntLength(component.lastKey, out);
}
}
public StatsMetadata deserialize(Version version, DataInputPlus in) throws IOException
@ -407,24 +507,25 @@ public class StatsMetadata extends MetadataComponent
int sstableLevel = in.readInt();
long repairedAt = in.readLong();
// for legacy sstables, we skip deserializing the min and max clustering value
// to prevent erroneously excluding sstables from reads (see CASSANDRA-14861)
int colCount = in.readInt();
List<ByteBuffer> minClusteringValues = new ArrayList<>(colCount);
for (int i = 0; i < colCount; i++)
List<AbstractType<?>> clusteringTypes = null;
Slice coveredClustering = Slice.ALL;
if (version.hasLegacyMinMax())
{
ByteBuffer val = ByteBufferUtil.readWithShortLength(in);
if (version.hasAccurateMinMax())
minClusteringValues.add(val);
}
// We always deserialize the min/max clustering values if they are there, but we ignore them for
// legacy sstables where !hasAccurateMinMax due to CASSANDRA-14861.
int colCount = in.readInt();
ByteBuffer[] minClusteringValues = new ByteBuffer[colCount];
for (int i = 0; i < colCount; i++)
minClusteringValues[i] = ByteBufferUtil.readWithShortLength(in);
colCount = in.readInt();
ByteBuffer[] maxClusteringValues = new ByteBuffer[colCount];
for (int i = 0; i < colCount; i++)
maxClusteringValues[i] = ByteBufferUtil.readWithShortLength(in);
colCount = in.readInt();
List<ByteBuffer> maxClusteringValues = new ArrayList<>(colCount);
for (int i = 0; i < colCount; i++)
{
ByteBuffer val = ByteBufferUtil.readWithShortLength(in);
if (version.hasAccurateMinMax())
maxClusteringValues.add(val);
coveredClustering = Slice.make(BufferClusteringBound.inclusiveStartOf(minClusteringValues),
BufferClusteringBound.inclusiveEndOf(maxClusteringValues));
}
boolean hasLegacyCounterShards = in.readBoolean();
@ -438,7 +539,7 @@ public class StatsMetadata extends MetadataComponent
if (version.hasCommitLogIntervals())
commitLogIntervals = commitLogPositionSetSerializer.deserialize(in);
else
commitLogIntervals = new IntervalSet<CommitLogPosition>(commitLogLowerBound, commitLogUpperBound);
commitLogIntervals = new IntervalSet<>(commitLogLowerBound, commitLogUpperBound);
TimeUUID pendingRepair = null;
if (version.hasPendingRepair() && in.readByte() != 0)
@ -452,6 +553,28 @@ public class StatsMetadata extends MetadataComponent
if (version.hasOriginatingHostId() && in.readByte() != 0)
originatingHostId = UUIDSerializer.serializer.deserialize(in, 0);
// If not recorded, the only time we can guarantee there is no partition level deletion is if there is no
// deletion at all. Otherwise, we have to assume there may be some.
boolean hasPartitionLevelDeletions = minLocalDeletionTime != Cell.NO_DELETION_TIME;
if (version.hasPartitionLevelDeletionsPresenceMarker())
{
hasPartitionLevelDeletions = in.readBoolean();
}
if (version.hasImprovedMinMax())
{
clusteringTypes = typeSerializer.deserializeList(in);
coveredClustering = Slice.serializer.deserialize(in, version.correspondingMessagingVersion(), clusteringTypes);
}
ByteBuffer firstKey = null;
ByteBuffer lastKey = null;
if (version.hasKeyRange())
{
firstKey = ByteBufferUtil.readWithVIntLength(in);
lastKey = ByteBufferUtil.readWithVIntLength(in);
}
return new StatsMetadata(partitionSizes,
columnCounts,
commitLogIntervals,
@ -464,15 +587,25 @@ public class StatsMetadata extends MetadataComponent
compressionRatio,
tombstoneHistogram,
sstableLevel,
minClusteringValues,
maxClusteringValues,
clusteringTypes,
coveredClustering,
hasLegacyCounterShards,
repairedAt,
totalColumnsSet,
totalRows,
originatingHostId,
pendingRepair,
isTransient);
isTransient,
hasPartitionLevelDeletions,
firstKey,
lastKey);
}
private int countUntilNull(ByteBuffer[] bufferArray)
{
int i = ArrayUtils.indexOf(bufferArray, null);
return i < 0 ? bufferArray.length : i;
}
}
}

View File

@ -82,8 +82,10 @@ public class KeyspaceMetrics
public final LatencyMetrics rangeLatency;
/** (Local) write metrics */
public final LatencyMetrics writeLatency;
/** Histogram of the number of sstable data files accessed per read */
/** Histogram of the number of sstable data files accessed per single partition read */
public final Histogram sstablesPerReadHistogram;
/** Histogram of the number of sstable data files accessed per partition range read */
public final Histogram sstablesPerRangeReadHistogram;
/** Tombstones scanned in queries on this Keyspace */
public final Histogram tombstoneScannedHistogram;
/** Live cells scanned in queries on this Keyspace */
@ -231,6 +233,7 @@ public class KeyspaceMetrics
// create histograms for TableMetrics to replicate updates to
sstablesPerReadHistogram = createKeyspaceHistogram("SSTablesPerReadHistogram", true);
sstablesPerRangeReadHistogram = createKeyspaceHistogram("SSTablesPerRangeReadHistogram", true);
tombstoneScannedHistogram = createKeyspaceHistogram("TombstoneScannedHistogram", false);
liveScannedHistogram = createKeyspaceHistogram("LiveScannedHistogram", false);
colUpdateTimeDeltaHistogram = createKeyspaceHistogram("ColUpdateTimeDeltaHistogram", false);
@ -398,4 +401,4 @@ public class KeyspaceMetrics
return new MetricName(groupName, "keyspace", metricName, keyspaceName, mbeanName.toString());
}
}
}
}

View File

@ -100,8 +100,10 @@ public class TableMetrics
public final Gauge<Long> estimatedPartitionCount;
/** Histogram of estimated number of columns. */
public final Gauge<long[]> estimatedColumnCountHistogram;
/** Histogram of the number of sstable data files accessed per read */
/** Histogram of the number of sstable data files accessed per single partition read */
public final TableHistogram sstablesPerReadHistogram;
/** Histogram of the number of sstable data files accessed per partition range read */
public final TableHistogram sstablesPerRangeReadHistogram;
/** (Local) read metrics */
public final LatencyMetrics readLatency;
/** (Local) range slice metrics */
@ -548,6 +550,7 @@ public class TableMetrics
SSTableReader::getEstimatedCellPerPartitionCount), null);
sstablesPerReadHistogram = createTableHistogram("SSTablesPerReadHistogram", cfs.keyspace.metric.sstablesPerReadHistogram, true);
sstablesPerRangeReadHistogram = createTableHistogram("SSTablesPerRangeReadHistogram", cfs.keyspace.metric.sstablesPerRangeReadHistogram, true);
compressionRatio = createTableGauge("CompressionRatio", new Gauge<Double>()
{
public Double getValue()
@ -997,6 +1000,11 @@ public class TableMetrics
sstablesPerReadHistogram.update(count);
}
public void updateSSTableIteratedInRangeRead(int count)
{
sstablesPerRangeReadHistogram.update(count);
}
/**
* Release all associated metrics.
*/
@ -1425,4 +1433,4 @@ public class TableMetrics
return total;
}
}
}
}

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.serializers;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import org.apache.cassandra.db.TypeSizes;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.marshal.TypeParser;
import org.apache.cassandra.db.marshal.UTF8Type;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.utils.ByteBufferUtil;
public class AbstractTypeSerializer
{
public void serialize(AbstractType<?> type, DataOutputPlus out) throws IOException
{
ByteBufferUtil.writeWithVIntLength(UTF8Type.instance.decompose(type.toString()), out);
}
public void serializeList(List<AbstractType<?>> types, DataOutputPlus out) throws IOException
{
out.writeUnsignedVInt32(types.size());
for (AbstractType<?> type : types)
serialize(type, out);
}
public AbstractType<?> deserialize(DataInputPlus in) throws IOException
{
ByteBuffer raw = ByteBufferUtil.readWithVIntLength(in);
return TypeParser.parse(UTF8Type.instance.compose(raw));
}
public List<AbstractType<?>> deserializeList(DataInputPlus in) throws IOException
{
int size = (int) in.readUnsignedVInt();
List<AbstractType<?>> types = new ArrayList<>(size);
for (int i = 0; i < size; i++)
types.add(deserialize(in));
return types;
}
public long serializedSize(AbstractType<?> type)
{
return ByteBufferUtil.serializedSizeWithVIntLength(UTF8Type.instance.decompose(type.toString()));
}
public long serializedListSize(List<AbstractType<?>> types)
{
long size = TypeSizes.sizeofUnsignedVInt(types.size());
for (AbstractType<?> type : types)
size += serializedSize(type);
return size;
}
}

View File

@ -17,20 +17,12 @@
*/
package org.apache.cassandra.tools;
import static org.apache.cassandra.tools.Util.BLUE;
import static org.apache.cassandra.tools.Util.CYAN;
import static org.apache.cassandra.tools.Util.RESET;
import static org.apache.cassandra.tools.Util.WHITE;
import static org.apache.cassandra.utils.Clock.Global.currentTimeMillis;
import static org.apache.commons.lang3.time.DurationFormatUtils.formatDurationWords;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.List;
@ -38,7 +30,17 @@ import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import com.google.common.collect.MinMaxPriorityQueue;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.PosixParser;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.db.ClusteringComparator;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.SerializationHeader;
import org.apache.cassandra.db.marshal.AbstractType;
@ -65,15 +67,13 @@ import org.apache.cassandra.schema.TableMetadataRef;
import org.apache.cassandra.tools.Util.TermHistogram;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.Pair;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.PosixParser;
import com.google.common.collect.MinMaxPriorityQueue;
import static org.apache.cassandra.tools.Util.BLUE;
import static org.apache.cassandra.tools.Util.CYAN;
import static org.apache.cassandra.tools.Util.RESET;
import static org.apache.cassandra.tools.Util.WHITE;
import static org.apache.cassandra.utils.Clock.Global.currentTimeMillis;
import static org.apache.commons.lang3.time.DurationFormatUtils.formatDurationWords;
/**
* Shows the contents of sstable metadata
@ -354,22 +354,12 @@ public class SSTableMetadataViewer
field("TTL max", stats.maxTTL, toDurationString(stats.maxTTL, TimeUnit.SECONDS));
if (validation != null && header != null)
printMinMaxToken(descriptor, FBUtilities.newPartitioner(descriptor), header.getKeyType());
printMinMaxToken(descriptor, FBUtilities.newPartitioner(descriptor), header.getKeyType(), stats);
if (header != null && header.getClusteringTypes().size() == stats.minClusteringValues.size())
if (header != null)
{
List<AbstractType<?>> clusteringTypes = header.getClusteringTypes();
List<ByteBuffer> minClusteringValues = stats.minClusteringValues;
List<ByteBuffer> maxClusteringValues = stats.maxClusteringValues;
String[] minValues = new String[clusteringTypes.size()];
String[] maxValues = new String[clusteringTypes.size()];
for (int i = 0; i < clusteringTypes.size(); i++)
{
minValues[i] = clusteringTypes.get(i).getString(minClusteringValues.get(i));
maxValues[i] = clusteringTypes.get(i).getString(maxClusteringValues.get(i));
}
field("minClusteringValues", Arrays.toString(minValues));
field("maxClusteringValues", Arrays.toString(maxValues));
ClusteringComparator comparator = new ClusteringComparator(header.getClusteringTypes());
field("Covered clusterings", stats.coveredClustering.toString(comparator));
}
field("Estimated droppable tombstones",
stats.getEstimatedDroppableTombstoneRatio((int) (currentTimeMillis() / 1000) - this.gc));
@ -474,19 +464,30 @@ public class SSTableMetadataViewer
}
}
private void printMinMaxToken(Descriptor descriptor, IPartitioner partitioner, AbstractType<?> keyType)
private void printMinMaxToken(Descriptor descriptor, IPartitioner partitioner, AbstractType<?> keyType, StatsMetadata statsMetadata)
throws IOException
{
File summariesFile = new File(descriptor.filenameFor(Component.SUMMARY));
if (!summariesFile.exists())
return;
try (DataInputStream iStream = new DataInputStream(Files.newInputStream(summariesFile.toPath())))
if (descriptor.version.hasKeyRange())
{
Pair<DecoratedKey, DecoratedKey> firstLast = new IndexSummary.IndexSummarySerializer()
.deserializeFirstLastKey(iStream, partitioner);
field("First token", firstLast.left.getToken(), keyType.getString(firstLast.left.getKey()));
field("Last token", firstLast.right.getToken(), keyType.getString(firstLast.right.getKey()));
if (statsMetadata.firstKey == null || statsMetadata.lastKey == null)
return;
field("First token", partitioner.getToken(statsMetadata.firstKey), keyType.getString(statsMetadata.firstKey));
field("Last token", partitioner.getToken(statsMetadata.lastKey), keyType.getString(statsMetadata.lastKey));
}
else
{
File summariesFile = new File(descriptor.filenameFor(Component.SUMMARY));
if (!summariesFile.exists())
return;
try (DataInputStream iStream = new DataInputStream(Files.newInputStream(summariesFile.toPath())))
{
Pair<DecoratedKey, DecoratedKey> firstLast = new IndexSummary.IndexSummarySerializer()
.deserializeFirstLastKey(iStream, partitioner);
field("First token", firstLast.left.getToken(), keyType.getString(firstLast.left.getKey()));
field("Last token", firstLast.right.getToken(), keyType.getString(firstLast.right.getKey()));
}
}
}

View File

@ -0,0 +1,58 @@
/*
* 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.utils;
import java.util.Comparator;
/**
* Utility methods linked to comparing comparable values.
*/
public class Comparables
{
/**
* Returns the maximum of 2 comparable values. On ties, returns the first argument.
*/
public static <T extends Comparable<? super T>> T max(T a, T b)
{
return a.compareTo(b) < 0 ? b : a;
}
/**
* Returns the maximum of 2 values given a comparator of those values. On ties, returns the first argument.
*/
public static <C, T extends C> T max(T a, T b, Comparator<C> comparator)
{
return comparator.compare(a, b) < 0 ? b : a;
}
/**
* Returns the minimum of 2 comparable values. On ties, returns the first argument.
*/
public static <T extends Comparable<? super T>> T min(T a, T b)
{
return a.compareTo(b) > 0 ? b : a;
}
/**
* Returns the minimum of 2 values given a comparator of those values. On ties, returns the first argument.
*/
public static <C, T extends C> T min(T a, T b, Comparator<C> comparator)
{
return comparator.compare(a, b) > 0 ? b : a;
}
}

View File

@ -395,10 +395,11 @@ public abstract class MergeIterator<In,Out> extends AbstractIterator<Out> implem
private boolean isLowerBound()
{
assert item != null;
return item == lowerBound;
}
public void consume(Reducer reducer)
public <Out> void consume(Reducer<In, Out> reducer)
{
if (isLowerBound())
{
@ -488,4 +489,4 @@ public abstract class MergeIterator<In,Out> extends AbstractIterator<Out> implem
return (Out) source.next();
}
}
}
}

View File

@ -75,6 +75,10 @@ public interface ByteSource
int LT_NEXT_COMPONENT = 0x20;
int GT_NEXT_COMPONENT = 0x60;
// Unsupported, for artificial bounds
int LTLT_NEXT_COMPONENT = 0x1F; // LT_NEXT_COMPONENT - 1
int GTGT_NEXT_COMPONENT = 0x61; // GT_NEXT_COMPONENT + 1
// Special value for components that should be excluded from the normal min/max span. (static rows)
int EXCLUDED = 0x18;
@ -850,4 +854,4 @@ public interface ByteSource
? (Peekable) p
: new Peekable(p);
}
}
}

View File

@ -0,0 +1,8 @@
TOC.txt
Data.db
Statistics.db
Summary.db
Filter.db
Digest.crc32
Index.db
CompressionInfo.db

View File

@ -0,0 +1,8 @@
TOC.txt
Data.db
Statistics.db
Summary.db
Filter.db
Digest.crc32
Index.db
CompressionInfo.db

View File

@ -0,0 +1,8 @@
TOC.txt
Data.db
Statistics.db
Summary.db
Filter.db
Digest.crc32
Index.db
CompressionInfo.db

View File

@ -0,0 +1,8 @@
TOC.txt
Data.db
Statistics.db
Summary.db
Filter.db
Digest.crc32
Index.db
CompressionInfo.db

View File

@ -0,0 +1,155 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.test.microbench;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import org.apache.cassandra.db.Clustering;
import org.apache.cassandra.db.ClusteringBound;
import org.apache.cassandra.db.ClusteringBoundary;
import org.apache.cassandra.db.ClusteringPrefix.Kind;
import org.apache.cassandra.db.marshal.LongType;
import org.apache.cassandra.db.rows.BufferCell;
import org.apache.cassandra.db.rows.Cell;
import org.apache.cassandra.io.sstable.metadata.MetadataCollector;
import org.apache.cassandra.schema.ColumnMetadata;
import org.apache.cassandra.schema.TableMetadata;
import org.jctools.util.Pow2;
import org.openjdk.jmh.annotations.*;
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 10, time = 1)
@Measurement(iterations = 10, time = 1)
@Fork(1)
@State(Scope.Benchmark)
public class MetadataCollectorBench
{
@Param({ "10" })
int clusteringKeyNum;
@Param({ "10000" })
int datasetSize;
int datumIndex;
Cell<?>[] cells;
Clustering<?>[] clusterings;
ClusteringBound<?>[] clusteringBounds;
ClusteringBoundary<?>[] clusteringBoundaries;
MetadataCollector collector;
@Setup
public void setup()
{
TableMetadata.Builder tableMetadataBuilder = TableMetadata.builder("k", "t")
.addPartitionKeyColumn("pk", LongType.instance)
.addRegularColumn("rc", LongType.instance);
for (int i = 0; i < clusteringKeyNum; i++)
tableMetadataBuilder.addClusteringColumn("ck" + i, LongType.instance);
TableMetadata tableMetadata = tableMetadataBuilder.build();
collector = new MetadataCollector(tableMetadata.comparator);
ColumnMetadata columnMetadata = tableMetadata.regularColumns().iterator().next();
ThreadLocalRandom current = ThreadLocalRandom.current();
datasetSize = Pow2.roundToPowerOfTwo(datasetSize);
cells = new Cell[datasetSize];
for (int i = 0; i < datasetSize; i++)
{
cells[i] = new BufferCell(columnMetadata, current.nextLong(0, Long.MAX_VALUE), current.nextInt(1, Integer.MAX_VALUE), Cell.NO_DELETION_TIME, null, null);
}
clusterings = new Clustering[datasetSize];
clusteringBounds = new ClusteringBound[datasetSize];
clusteringBoundaries = new ClusteringBoundary[datasetSize];
ByteBuffer[] cks = new ByteBuffer[clusteringKeyNum];
Kind[] clusteringBoundKinds = new Kind[]{ Kind.INCL_START_BOUND, Kind.INCL_END_BOUND, Kind.EXCL_START_BOUND, Kind.EXCL_END_BOUND };
Kind[] clusteringBoundaryKinds = new Kind[]{ Kind.INCL_END_EXCL_START_BOUNDARY, Kind.EXCL_END_INCL_START_BOUNDARY };
for (int i = 0; i < datasetSize; i++)
{
for (int j = 0; j < clusteringKeyNum; j++)
cks[j] = LongType.instance.decompose(current.nextLong());
clusterings[i] = Clustering.make(Arrays.copyOf(cks, cks.length));
clusteringBounds[i] = ClusteringBound.create(clusteringBoundKinds[i % clusteringBoundKinds.length], clusterings[i]);
clusteringBoundaries[i] = ClusteringBoundary.create(clusteringBoundaryKinds[i % clusteringBoundaryKinds.length], clusterings[i]);
}
System.gc();
// shuffle array contents to ensure a more 'natural' layout
for (int i = 0; i < datasetSize; i++)
{
int to = current.nextInt(0, datasetSize);
Cell<?> temp = cells[i];
cells[i] = cells[to];
cells[to] = temp;
}
for (int i = 0; i < datasetSize; i++)
{
int to = current.nextInt(0, datasetSize);
Clustering<?> temp = clusterings[i];
clusterings[i] = clusterings[to];
clusterings[to] = temp;
}
}
@Benchmark
public void updateCell()
{
collector.update(nextCell());
}
@Benchmark
public void updateClustering()
{
collector.updateClusteringValues(nextClustering());
}
@Benchmark
public void updateClusteringBound()
{
collector.updateClusteringValuesByBoundOrBoundary(nextClusteringBound());
}
@Benchmark
public void updateClusteringBoundary()
{
collector.updateClusteringValuesByBoundOrBoundary(nextClusteringBoundary());
}
public Cell<?> nextCell()
{
return cells[datumIndex++ & (cells.length - 1)];
}
public Clustering<?> nextClustering()
{
return clusterings[datumIndex++ & (clusterings.length - 1)];
}
public ClusteringBound<?> nextClusteringBound()
{
return clusteringBounds[datumIndex++ & (clusteringBounds.length - 1)];
}
public ClusteringBoundary<?> nextClusteringBoundary()
{
return clusteringBoundaries[datumIndex++ & (clusteringBoundaries.length - 1)];
}
}

View File

@ -20,12 +20,16 @@
*/
package org.apache.cassandra.cql3.validation.miscellaneous;
import java.util.Arrays;
import org.junit.Test;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.cql3.CQLTester;
import org.apache.cassandra.cql3.UntypedResultSet;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.marshal.Int32Type;
import org.apache.cassandra.metrics.ClearableHistogram;
import static org.junit.Assert.assertEquals;
@ -50,6 +54,22 @@ public class SSTablesIteratedTest extends CQLTester
numSSTablesIterated);
}
private void executeAndCheckRangeQuery(String query, int numSSTables, Object[]... rows) throws Throwable
{
logger.info("Executing query: {} with parameters: {}", query, Arrays.toString(rows));
ColumnFamilyStore cfs = getCurrentColumnFamilyStore(KEYSPACE_PER_TEST);
((ClearableHistogram) cfs.metric.sstablesPerRangeReadHistogram.cf).clear(); // resets counts
assertRows(execute(query), rows);
long numSSTablesIterated = cfs.metric.sstablesPerRangeReadHistogram.cf.getSnapshot().getMax(); // max sstables read
assertEquals(String.format("Expected %d sstables iterated but got %d instead, with %d live sstables",
numSSTables, numSSTablesIterated, cfs.getLiveSSTables().size()),
numSSTables,
numSSTablesIterated);
}
@Override
protected String createTable(String query)
{
@ -431,13 +451,19 @@ public class SSTablesIteratedTest extends CQLTester
}
flush();
// The code has to read the 1st and 3rd sstables to see that everything before the 2nd sstable is deleted, and
// overall has to read the 3 sstables
executeAndCheck("SELECT * FROM %s WHERE id=1 LIMIT 1", 3, row(1, 1001, "1001"));
executeAndCheck("SELECT * FROM %s WHERE id=1 LIMIT 2", 3, row(1, 1001, "1001"), row(1, 1002, "1002"));
executeAndCheck("SELECT * FROM %s WHERE id=1", 3, allRows);
executeAndCheck("SELECT * FROM %s WHERE id=1 AND col > 1000 LIMIT 1", 2, row(1, 1001, "1001"));
// The 1st and 3rd sstables have data only up to 1000, so they will be skipped
executeAndCheck("SELECT * FROM %s WHERE id=1 AND col > 1000 LIMIT 1", 1, row(1, 1001, "1001"));
executeAndCheck("SELECT * FROM %s WHERE id=1 AND col > 1000", 1, allRows);
// The condition makes no difference to the code, and all 3 sstables have to read
executeAndCheck("SELECT * FROM %s WHERE id=1 AND col <= 2000 LIMIT 1", 3, row(1, 1001, "1001"));
executeAndCheck("SELECT * FROM %s WHERE id=1 AND col > 1000", 2, allRows);
executeAndCheck("SELECT * FROM %s WHERE id=1 AND col <= 2000", 3, allRows);
}
@ -515,14 +541,30 @@ public class SSTablesIteratedTest extends CQLTester
allRows[idx] = row(1, i, Integer.toString(i), Integer.toString(i));
}
executeAndCheck("SELECT * FROM %s WHERE id=1 LIMIT 1", 2, row(1, 1, "1", "1"));
executeAndCheck("SELECT * FROM %s WHERE id=1 LIMIT 2", 2, row(1, 1, "1", "1"), row(1, 2, "2", null));
// The 500th first rows are in the first sstable (and there is no partition deletion/static row), so the 'lower
// bound' optimization will kick in and we'll only read the 1st sstable.
executeAndCheck("SELECT * FROM %s WHERE id=1 LIMIT 1", 1, row(1, 1, "1", "1"));
executeAndCheck("SELECT * FROM %s WHERE id=1 LIMIT 2", 1, row(1, 1, "1", "1"), row(1, 2, "2", null));
// Getting everything obviously requires reading both sstables
executeAndCheck("SELECT * FROM %s WHERE id=1", 2, allRows);
// The 'lower bound' optimization don't help us because while the row to fetch is in the 1st sstable, the lower
// bound for the 2nd sstable is 501, which is lower than 1000.
executeAndCheck("SELECT * FROM %s WHERE id=1 AND col > 1000 LIMIT 1", 2, row(1, 1001, "1001", "1001"));
executeAndCheck("SELECT * FROM %s WHERE id=1 AND col <= 2000 LIMIT 1", 2, row(1, 1, "1", "1"));
// Somewhat similar to the previous one: the row is in th 2nd sstable in this case, but as the lower bound for
// the first sstable is 1, this doesn't help.
executeAndCheck("SELECT * FROM %s WHERE id=1 AND col > 500 LIMIT 1", 2, row(1, 751, "751", "751"));
executeAndCheck("SELECT * FROM %s WHERE id=1 AND col <= 500 LIMIT 1", 2, row(1, 1, "1", "1"));
// The 'col <= ?' condition in both queries doesn't impact the read path, which can still make use of the lower
// bound optimization and read only the first sstable.
executeAndCheck("SELECT * FROM %s WHERE id=1 AND col <= 2000 LIMIT 1", 1, row(1, 1, "1", "1"));
executeAndCheck("SELECT * FROM %s WHERE id=1 AND col <= 500 LIMIT 1", 1, row(1, 1, "1", "1"));
// Making sure the 'lower bound' optimization also work in reverse queries (in which it's more of a 'upper
// bound' optimization).
executeAndCheck("SELECT * FROM %s WHERE id=1 AND col <= 2000 ORDER BY col DESC LIMIT 1", 1, row(1, 2000, "2000", null));
}
@Test
@ -996,7 +1038,7 @@ public class SSTablesIteratedTest extends CQLTester
@Test
public void testCompactAndNonCompactTableWithCounter() throws Throwable
{
for (String with : new String[]{"", " WITH COMPACT STORAGE"})
for (String with : new String[]{ "", " WITH COMPACT STORAGE" })
{
createTable("CREATE TABLE %s (pk int, c int, count counter, PRIMARY KEY(pk, c))" + with);
@ -1038,7 +1080,7 @@ public class SSTablesIteratedTest extends CQLTester
executeAndCheck("SELECT s, v FROM %s WHERE pk = 3 AND c = 3", 3, row(3, set(1)));
executeAndCheck("SELECT v FROM %s WHERE pk = 1 AND c = 1", 3, row(set(3)));
executeAndCheck("SELECT v FROM %s WHERE pk = 2 AND c = 1", 2, row(set(3)));
executeAndCheck("SELECT v FROM %s WHERE pk = 3 AND c = 3", 3, row(set(1)));
executeAndCheck("SELECT v FROM %s WHERE pk = 3 AND c = 3", 1, row(set(1)));
executeAndCheck("SELECT s FROM %s WHERE pk = 1", 3, row((Integer) null));
executeAndCheck("SELECT s FROM %s WHERE pk = 2", 2, row(1), row(1));
executeAndCheck("SELECT DISTINCT s FROM %s WHERE pk = 2", 2, row(1));
@ -1087,7 +1129,7 @@ public class SSTablesIteratedTest extends CQLTester
@Test
public void testCompactAndNonCompactTableWithPartitionTombstones() throws Throwable
{
for (Boolean compact : new Boolean[] {Boolean.FALSE, Boolean.TRUE})
for (Boolean compact : new Boolean[]{ Boolean.FALSE, Boolean.TRUE })
{
String with = compact ? " WITH COMPACT STORAGE" : "";
createTable("CREATE TABLE %s (pk int PRIMARY KEY, v1 int, v2 int)" + with);
@ -1642,4 +1684,157 @@ public class SSTablesIteratedTest extends CQLTester
executeAndCheck("SELECT v1 FROM %s WHERE pk = 5", 1);
executeAndCheck("SELECT v2 FROM %s WHERE pk = 5", 1);
}
}
@Test
public void testSkippingBySliceInSinglePartitionReads() throws Throwable
{
createTable("CREATE TABLE %s (pk int, c int, v int, PRIMARY KEY(pk, c))");
execute("INSERT INTO %s (pk, c, v) VALUES (?, ?, ?) USING TIMESTAMP 1000", 1, 1, 1);
execute("INSERT INTO %s (pk, c, v) VALUES (?, ?, ?) USING TIMESTAMP 1000", 1, 2, 2);
execute("INSERT INTO %s (pk, c, v) VALUES (?, ?, ?) USING TIMESTAMP 1000", 1, 3, 3);
flush();
assertEquals(1, getCurrentColumnFamilyStore(KEYSPACE_PER_TEST).getLiveSSTables().size());
execute("INSERT INTO %s (pk, c, v) VALUES (?, ?, ?) USING TIMESTAMP 1000", 1, 2, 4);
execute("INSERT INTO %s (pk, c, v) VALUES (?, ?, ?) USING TIMESTAMP 1000", 1, 3, 5);
execute("INSERT INTO %s (pk, c, v) VALUES (?, ?, ?) USING TIMESTAMP 1000", 1, 4, 6);
flush();
assertEquals(2, getCurrentColumnFamilyStore(KEYSPACE_PER_TEST).getLiveSSTables().size());
execute("INSERT INTO %s (pk, c, v) VALUES (?, ?, ?) USING TIMESTAMP 1000", 1, 3, 7);
execute("INSERT INTO %s (pk, c, v) VALUES (?, ?, ?) USING TIMESTAMP 1000", 1, 4, 8);
execute("INSERT INTO %s (pk, c, v) VALUES (?, ?, ?) USING TIMESTAMP 1000", 1, 5, 9);
flush();
assertEquals(3, getCurrentColumnFamilyStore(KEYSPACE_PER_TEST).getLiveSSTables().size());
execute("INSERT INTO %s (pk, c, v) VALUES (?, ?, ?) USING TIMESTAMP 1000", 1, 4, 10);
execute("INSERT INTO %s (pk, c, v) VALUES (?, ?, ?) USING TIMESTAMP 1000", 1, 5, 11);
execute("INSERT INTO %s (pk, c, v) VALUES (?, ?, ?) USING TIMESTAMP 1000", 1, 6, 12);
flush();
assertEquals(4, getCurrentColumnFamilyStore(KEYSPACE_PER_TEST).getLiveSSTables().size());
// point query - test whether sstables are skipped due to not covering the requested slice
executeAndCheck("SELECT * FROM %s WHERE pk = 1 AND c = 0", 0);
executeAndCheck("SELECT * FROM %s WHERE pk = 1 AND c = 1", 1, row(1, 1, 1));
executeAndCheck("SELECT * FROM %s WHERE pk = 1 AND c = 2", 2, row(1, 2, 4));
executeAndCheck("SELECT * FROM %s WHERE pk = 1 AND c = 3", 3, row(1, 3, 7));
executeAndCheck("SELECT * FROM %s WHERE pk = 1 AND c = 4", 3, row(1, 4, 10));
executeAndCheck("SELECT * FROM %s WHERE pk = 1 AND c = 5", 2, row(1, 5, 11));
executeAndCheck("SELECT * FROM %s WHERE pk = 1 AND c = 6", 1, row(1, 6, 12));
executeAndCheck("SELECT * FROM %s WHERE pk = 1 AND c = 7", 0);
// range query - test whether sstables are skipped due to not covering the requeste slice
executeAndCheck("SELECT * FROM %s WHERE pk = 1 AND c > -10 AND c <= 0", 0);
executeAndCheck("SELECT * FROM %s WHERE pk = 1 AND c > -10 AND c < 1", 0);
executeAndCheck("SELECT * FROM %s WHERE pk = 1 AND c > -10 AND c <= 1", 1, row(1, 1, 1));
executeAndCheck("SELECT * FROM %s WHERE pk = 1 AND c > 1 AND c < 3", 2, row(1, 2, 4));
executeAndCheck("SELECT * FROM %s WHERE pk = 1 AND c >= 6", 1, row(1, 6, 12));
executeAndCheck("SELECT * FROM %s WHERE pk = 1 AND c > 6", 0);
}
@Test
public void testSkippingBySliceInPartitionRangeReads() throws Throwable
{
createTable("CREATE TABLE %s (pk int, c int, v int, PRIMARY KEY(pk, c))");
execute("INSERT INTO %s (pk, c, v) VALUES (?, ?, ?) USING TIMESTAMP 1000", 1, 1, 1);
execute("INSERT INTO %s (pk, c, v) VALUES (?, ?, ?) USING TIMESTAMP 1000", 1, 2, 2);
execute("INSERT INTO %s (pk, c, v) VALUES (?, ?, ?) USING TIMESTAMP 1000", 1, 3, 3);
flush();
assertEquals(1, getCurrentColumnFamilyStore(KEYSPACE_PER_TEST).getLiveSSTables().size());
execute("INSERT INTO %s (pk, c, v) VALUES (?, ?, ?) USING TIMESTAMP 1000", 1, 2, 4);
execute("INSERT INTO %s (pk, c, v) VALUES (?, ?, ?) USING TIMESTAMP 1000", 1, 3, 5);
execute("INSERT INTO %s (pk, c, v) VALUES (?, ?, ?) USING TIMESTAMP 1000", 1, 4, 6);
flush();
assertEquals(2, getCurrentColumnFamilyStore(KEYSPACE_PER_TEST).getLiveSSTables().size());
execute("INSERT INTO %s (pk, c, v) VALUES (?, ?, ?) USING TIMESTAMP 1000", 1, 3, 7);
execute("INSERT INTO %s (pk, c, v) VALUES (?, ?, ?) USING TIMESTAMP 1000", 1, 4, 8);
execute("INSERT INTO %s (pk, c, v) VALUES (?, ?, ?) USING TIMESTAMP 1000", 1, 5, 9);
flush();
assertEquals(3, getCurrentColumnFamilyStore(KEYSPACE_PER_TEST).getLiveSSTables().size());
execute("INSERT INTO %s (pk, c, v) VALUES (?, ?, ?) USING TIMESTAMP 1000", 1, 4, 10);
execute("INSERT INTO %s (pk, c, v) VALUES (?, ?, ?) USING TIMESTAMP 1000", 1, 5, 11);
execute("INSERT INTO %s (pk, c, v) VALUES (?, ?, ?) USING TIMESTAMP 1000", 1, 6, 12);
flush();
assertEquals(4, getCurrentColumnFamilyStore(KEYSPACE_PER_TEST).getLiveSSTables().size());
// point query - test whether sstables are skipped due to not covering the requested slice
executeAndCheckRangeQuery("SELECT * FROM %s WHERE c = 0 ALLOW FILTERING", 0);
executeAndCheckRangeQuery("SELECT * FROM %s WHERE c = 1 ALLOW FILTERING", 1, row(1, 1, 1));
executeAndCheckRangeQuery("SELECT * FROM %s WHERE c = 2 ALLOW FILTERING", 2, row(1, 2, 4));
executeAndCheckRangeQuery("SELECT * FROM %s WHERE c = 3 ALLOW FILTERING", 3, row(1, 3, 7));
executeAndCheckRangeQuery("SELECT * FROM %s WHERE c = 4 ALLOW FILTERING", 3, row(1, 4, 10));
executeAndCheckRangeQuery("SELECT * FROM %s WHERE c = 5 ALLOW FILTERING", 2, row(1, 5, 11));
executeAndCheckRangeQuery("SELECT * FROM %s WHERE c = 6 ALLOW FILTERING", 1, row(1, 6, 12));
executeAndCheckRangeQuery("SELECT * FROM %s WHERE c = 7 ALLOW FILTERING", 0);
// range query - test whether sstables are skipped due to not covering the requeste slice
executeAndCheckRangeQuery("SELECT * FROM %s WHERE c > -10 AND c <= 0 ALLOW FILTERING", 0);
executeAndCheckRangeQuery("SELECT * FROM %s WHERE c > -10 AND c < 1 ALLOW FILTERING", 0);
executeAndCheckRangeQuery("SELECT * FROM %s WHERE c > -10 AND c <= 1 ALLOW FILTERING", 1, row(1, 1, 1));
executeAndCheckRangeQuery("SELECT * FROM %s WHERE c > 1 AND c < 3 ALLOW FILTERING", 2, row(1, 2, 4));
executeAndCheckRangeQuery("SELECT * FROM %s WHERE c >= 6 ALLOW FILTERING", 1, row(1, 6, 12));
executeAndCheckRangeQuery("SELECT * FROM %s WHERE c > 6 ALLOW FILTERING", 0);
}
@Test
public void testSkippingByKeyRangeInPartitionRangeReads() throws Throwable
{
DecoratedKey[] keys = new DecoratedKey[8];
for (int i = 0; i < keys.length; i++)
keys[i] = DatabaseDescriptor.getPartitioner().decorateKey(Int32Type.instance.decompose(i));
Arrays.sort(keys);
int[] k = new int[keys.length];
String[] t = new String[keys.length];
for (int i = 0; i < keys.length; i++)
{
DecoratedKey key = keys[i];
k[i] = Int32Type.instance.compose(key.getKey());
t[i] = key.getToken().getTokenValue().toString();
}
createTable("CREATE TABLE %s (pk int, c int, v int, PRIMARY KEY(pk, c))");
execute("INSERT INTO %s (pk, c, v) VALUES (?, ?, ?) USING TIMESTAMP 1000", k[1], 1, 1);
execute("INSERT INTO %s (pk, c, v) VALUES (?, ?, ?) USING TIMESTAMP 1000", k[2], 2, 2);
execute("INSERT INTO %s (pk, c, v) VALUES (?, ?, ?) USING TIMESTAMP 1000", k[3], 3, 3);
flush();
assertEquals(1, getCurrentColumnFamilyStore(KEYSPACE_PER_TEST).getLiveSSTables().size());
execute("INSERT INTO %s (pk, c, v) VALUES (?, ?, ?) USING TIMESTAMP 1000", k[2], 2, 4);
execute("INSERT INTO %s (pk, c, v) VALUES (?, ?, ?) USING TIMESTAMP 1000", k[3], 3, 5);
execute("INSERT INTO %s (pk, c, v) VALUES (?, ?, ?) USING TIMESTAMP 1000", k[4], 4, 6);
flush();
assertEquals(2, getCurrentColumnFamilyStore(KEYSPACE_PER_TEST).getLiveSSTables().size());
execute("INSERT INTO %s (pk, c, v) VALUES (?, ?, ?) USING TIMESTAMP 1000", k[3], 3, 7);
execute("INSERT INTO %s (pk, c, v) VALUES (?, ?, ?) USING TIMESTAMP 1000", k[4], 4, 8);
execute("INSERT INTO %s (pk, c, v) VALUES (?, ?, ?) USING TIMESTAMP 1000", k[5], 5, 9);
flush();
assertEquals(3, getCurrentColumnFamilyStore(KEYSPACE_PER_TEST).getLiveSSTables().size());
execute("INSERT INTO %s (pk, c, v) VALUES (?, ?, ?) USING TIMESTAMP 1000", k[4], 4, 10);
execute("INSERT INTO %s (pk, c, v) VALUES (?, ?, ?) USING TIMESTAMP 1000", k[5], 5, 11);
execute("INSERT INTO %s (pk, c, v) VALUES (?, ?, ?) USING TIMESTAMP 1000", k[6], 6, 12);
flush();
assertEquals(4, getCurrentColumnFamilyStore(KEYSPACE_PER_TEST).getLiveSSTables().size());
// range query - test whether sstables are skipped due to not covering the requested slice
executeAndCheckRangeQuery("SELECT * FROM %s WHERE TOKEN (pk) <= " + t[0], 0);
executeAndCheckRangeQuery("SELECT * FROM %s WHERE TOKEN (pk) < " + t[1], 0);
executeAndCheckRangeQuery("SELECT * FROM %s WHERE TOKEN(pk) >= " + t[1] + " AND TOKEN (pk) < " + t[2], 1, row(k[1], 1, 1));
executeAndCheckRangeQuery("SELECT * FROM %s WHERE TOKEN(pk) >= " + t[2] + " AND TOKEN (pk) < " + t[3], 2, row(k[2], 2, 4));
executeAndCheckRangeQuery("SELECT * FROM %s WHERE TOKEN(pk) >= " + t[3] + " AND TOKEN (pk) < " + t[4], 3, row(k[3], 3, 7));
executeAndCheckRangeQuery("SELECT * FROM %s WHERE TOKEN(pk) >= " + t[3] + " AND TOKEN (pk) <= " + t[4], 4, row(k[3], 3, 7), row(k[4], 4, 10));
executeAndCheckRangeQuery("SELECT * FROM %s WHERE TOKEN(pk) >= " + t[4] + " AND TOKEN (pk) < " + t[5], 3, row(k[4], 4, 10));
executeAndCheckRangeQuery("SELECT * FROM %s WHERE TOKEN(pk) >= " + t[5] + " AND TOKEN (pk) < " + t[6], 2, row(k[5], 5, 11));
executeAndCheckRangeQuery("SELECT * FROM %s WHERE TOKEN(pk) >= " + t[6] + " AND TOKEN (pk) < " + t[7], 1, row(k[6], 6, 12));
executeAndCheckRangeQuery("SELECT * FROM %s WHERE TOKEN(pk) > " + t[6], 0);
executeAndCheckRangeQuery("SELECT * FROM %s WHERE TOKEN(pk) >= " + t[7], 0);
}
}

View File

@ -363,8 +363,8 @@ public class CompactionsTest
for (SSTableReader sstable : cfs.getLiveSSTables())
{
StatsMetadata stats = sstable.getSSTableMetadata();
assertEquals(ByteBufferUtil.bytes("0"), stats.minClusteringValues.get(0));
assertEquals(ByteBufferUtil.bytes("b"), stats.maxClusteringValues.get(0));
assertEquals(ByteBufferUtil.bytes("0"), stats.coveredClustering.start().bufferAt(0));
assertEquals(ByteBufferUtil.bytes("b"), stats.coveredClustering.end().bufferAt(0));
}
assertEquals(keys, k);
@ -565,4 +565,4 @@ public class CompactionsTest
CompactionManager.instance.setConcurrentCompactors(1);
assertEquals(1, CompactionManager.instance.getCoreCompactorThreads());
}
}
}

View File

@ -18,19 +18,30 @@
* */
package org.apache.cassandra.db.filter;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.ArrayList;
import java.util.List;
import org.apache.cassandra.db.*;
import org.junit.Test;
import org.apache.cassandra.db.BufferClusteringBound;
import org.apache.cassandra.db.Clustering;
import org.apache.cassandra.db.ClusteringBound;
import org.apache.cassandra.db.ClusteringComparator;
import org.apache.cassandra.db.ClusteringPrefix;
import org.apache.cassandra.db.Slice;
import org.apache.cassandra.db.Slices;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.marshal.Int32Type;
import org.apache.cassandra.utils.ByteBufferUtil;
import static org.apache.cassandra.db.ClusteringPrefix.Kind.*;
import static org.junit.Assert.*;
import static org.apache.cassandra.db.ClusteringPrefix.Kind.EXCL_END_BOUND;
import static org.apache.cassandra.db.ClusteringPrefix.Kind.EXCL_START_BOUND;
import static org.apache.cassandra.db.ClusteringPrefix.Kind.INCL_END_BOUND;
import static org.apache.cassandra.db.ClusteringPrefix.Kind.INCL_START_BOUND;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class SliceTest
{
@ -48,221 +59,221 @@ public class SliceTest
// filter falls entirely before sstable
Slice slice = Slice.make(makeBound(sk, 0, 0, 0), makeBound(ek, 1, 0, 0));
assertFalse(slice.intersects(cc, columnNames(2, 0, 0), columnNames(3, 0, 0)));
assertSlicesDoNotIntersect(cc, slice, Slice.make(makeBound(sk, 2, 0, 0), makeBound(ek, 3, 0, 0)));
// same case, but with empty start
slice = Slice.make(makeBound(sk), makeBound(ek, 1, 0, 0));
assertFalse(slice.intersects(cc, columnNames(2, 0, 0), columnNames(3, 0, 0)));
assertSlicesDoNotIntersect(cc, slice, Slice.make(makeBound(sk, 2, 0, 0), makeBound(ek, 3, 0, 0)));
// same case, but with missing components for start
slice = Slice.make(makeBound(sk, 0), makeBound(ek, 1, 0, 0));
assertFalse(slice.intersects(cc, columnNames(2, 0, 0), columnNames(3, 0, 0)));
assertSlicesDoNotIntersect(cc, slice, Slice.make(makeBound(sk, 2, 0, 0), makeBound(ek, 3, 0, 0)));
// same case, but with missing components for start and end
slice = Slice.make(makeBound(sk, 0), makeBound(ek, 1, 0));
assertFalse(slice.intersects(cc, columnNames(2, 0, 0), columnNames(3, 0, 0)));
assertSlicesDoNotIntersect(cc, slice, Slice.make(makeBound(sk, 2, 0, 0), makeBound(ek, 3, 0, 0)));
// end of slice matches start of sstable for the first component, but not the second component
slice = Slice.make(makeBound(sk, 0, 0, 0), makeBound(ek, 1, 0, 0));
assertFalse(slice.intersects(cc, columnNames(1, 1, 0), columnNames(3, 0, 0)));
assertSlicesDoNotIntersect(cc, slice, Slice.make(makeBound(sk, 1, 1, 0), makeBound(ek, 3, 0, 0)));
// same case, but with missing components for start
slice = Slice.make(makeBound(sk, 0), makeBound(ek, 1, 0, 0));
assertFalse(slice.intersects(cc, columnNames(1, 1, 0), columnNames(3, 0, 0)));
assertSlicesDoNotIntersect(cc, slice, Slice.make(makeBound(sk, 1, 1, 0), makeBound(ek, 3, 0, 0)));
// same case, but with missing components for start and end
slice = Slice.make(makeBound(sk, 0), makeBound(ek, 1, 0));
assertFalse(slice.intersects(cc, columnNames(1, 1, 0), columnNames(3, 0, 0)));
assertSlicesDoNotIntersect(cc, slice, Slice.make(makeBound(sk, 1, 1, 0), makeBound(ek, 3, 0, 0)));
// first two components match, but not the last
slice = Slice.make(makeBound(sk, 0, 0, 0), makeBound(ek, 1, 1, 0));
assertFalse(slice.intersects(cc, columnNames(1, 1, 1), columnNames(3, 1, 1)));
assertSlicesDoNotIntersect(cc, slice, Slice.make(makeBound(sk, 1, 1, 1), makeBound(ek, 3, 1, 1)));
// all three components in slice end match the start of the sstable
slice = Slice.make(makeBound(sk, 0, 0, 0), makeBound(ek, 1, 1, 1));
assertTrue(slice.intersects(cc, columnNames(1, 1, 1), columnNames(3, 1, 1)));
assertSlicesIntersect(cc, slice, Slice.make(makeBound(sk, 1, 1, 1), makeBound(ek, 3, 1, 1)));
// filter falls entirely after sstable
slice = Slice.make(makeBound(sk, 4, 0, 0), makeBound(ek, 4, 0, 0));
assertFalse(slice.intersects(cc, columnNames(2, 0, 0), columnNames(3, 0, 0)));
assertSlicesDoNotIntersect(cc, slice, Slice.make(makeBound(sk, 2, 0, 0), makeBound(ek, 3, 0, 0)));
// same case, but with empty end
slice = Slice.make(makeBound(sk, 4, 0, 0), makeBound(ek));
assertFalse(slice.intersects(cc, columnNames(2, 0, 0), columnNames(3, 0, 0)));
assertSlicesDoNotIntersect(cc, slice, Slice.make(makeBound(sk, 2, 0, 0), makeBound(ek, 3, 0, 0)));
// same case, but with missing components for end
slice = Slice.make(makeBound(sk, 4, 0, 0), makeBound(ek, 1));
assertFalse(slice.intersects(cc, columnNames(2, 0, 0), columnNames(3, 0, 0)));
assertSlicesDoNotIntersect(cc, slice, Slice.make(makeBound(sk, 2, 0, 0), makeBound(ek, 3, 0, 0)));
// same case, but with missing components for start and end
slice = Slice.make(makeBound(sk, 4, 0), makeBound(ek, 1));
assertFalse(slice.intersects(cc, columnNames(2, 0, 0), columnNames(3, 0, 0)));
assertSlicesDoNotIntersect(cc, slice, Slice.make(makeBound(sk, 2, 0, 0), makeBound(ek, 3, 0, 0)));
// start of slice matches end of sstable for the first component, but not the second component
slice = Slice.make(makeBound(sk, 1, 1, 1), makeBound(ek, 2, 0, 0));
assertFalse(slice.intersects(cc, columnNames(0, 0, 0), columnNames(1, 0, 0)));
assertSlicesDoNotIntersect(cc, slice, Slice.make(makeBound(sk, 0, 0, 0), makeBound(ek, 1, 0, 0)));
// start of slice matches end of sstable for the first two components, but not the last component
slice = Slice.make(makeBound(sk, 1, 1, 1), makeBound(ek, 2, 0, 0));
assertFalse(slice.intersects(cc, columnNames(0, 0, 0), columnNames(1, 1, 0)));
assertSlicesDoNotIntersect(cc, slice, Slice.make(makeBound(sk, 0, 0, 0), makeBound(ek, 1, 1, 0)));
// all three components in the slice start match the end of the sstable
slice = Slice.make(makeBound(sk, 1, 1, 1), makeBound(ek, 2, 0, 0));
assertTrue(slice.intersects(cc, columnNames(0, 0, 0), columnNames(1, 1, 1)));
assertSlicesIntersect(cc, slice, Slice.make(makeBound(sk, 0, 0, 0), makeBound(ek, 1, 1, 1)));
// slice covers entire sstable (with no matching edges)
slice = Slice.make(makeBound(sk, 0, 0, 0), makeBound(ek, 2, 0, 0));
assertTrue(slice.intersects(cc, columnNames(1, 0, 0), columnNames(1, 1, 1)));
assertSlicesIntersect(cc, slice, Slice.make(makeBound(sk, 1, 0, 0), makeBound(ek, 1, 1, 1)));
// same case, but with empty ends
slice = Slice.make(makeBound(sk), makeBound(ek));
assertTrue(slice.intersects(cc, columnNames(1, 0, 0), columnNames(1, 1, 1)));
assertSlicesIntersect(cc, slice, Slice.make(makeBound(sk, 1, 0, 0), makeBound(ek, 1, 1, 1)));
// same case, but with missing components
slice = Slice.make(makeBound(sk, 0), makeBound(ek, 2, 0));
assertTrue(slice.intersects(cc, columnNames(1, 0, 0), columnNames(1, 1, 1)));
assertSlicesIntersect(cc, slice, Slice.make(makeBound(sk, 1, 0, 0), makeBound(ek, 1, 1, 1)));
// slice covers entire sstable (with matching start)
slice = Slice.make(makeBound(sk, 1, 0, 0), makeBound(ek, 2, 0, 0));
assertTrue(slice.intersects(cc, columnNames(1, 0, 0), columnNames(1, 1, 1)));
assertSlicesIntersect(cc, slice, Slice.make(makeBound(sk, 1, 0, 0), makeBound(ek, 1, 1, 1)));
// slice covers entire sstable (with matching end)
slice = Slice.make(makeBound(sk, 0, 0, 0), makeBound(ek, 1, 1, 1));
assertTrue(slice.intersects(cc, columnNames(1, 0, 0), columnNames(1, 1, 1)));
assertSlicesIntersect(cc, slice, Slice.make(makeBound(sk, 1, 0, 0), makeBound(ek, 1, 1, 1)));
// slice covers entire sstable (with matching start and end)
slice = Slice.make(makeBound(sk, 1, 0, 0), makeBound(ek, 1, 1, 1));
assertTrue(slice.intersects(cc, columnNames(1, 0, 0), columnNames(1, 1, 1)));
assertSlicesIntersect(cc, slice, Slice.make(makeBound(sk, 1, 0, 0), makeBound(ek, 1, 1, 1)));
// slice falls entirely within sstable (with matching start)
slice = Slice.make(makeBound(sk, 1, 0, 0), makeBound(ek, 1, 1, 0));
assertTrue(slice.intersects(cc, columnNames(1, 0, 0), columnNames(1, 1, 1)));
assertSlicesIntersect(cc, slice, Slice.make(makeBound(sk, 1, 0, 0), makeBound(ek, 1, 1, 1)));
// same case, but with a missing end component
slice = Slice.make(makeBound(sk, 1, 0, 0), makeBound(ek, 1, 1));
assertTrue(slice.intersects(cc, columnNames(1, 0, 0), columnNames(1, 1, 1)));
assertSlicesIntersect(cc, slice, Slice.make(makeBound(sk, 1, 0, 0), makeBound(ek, 1, 1, 1)));
// slice falls entirely within sstable (with matching end)
slice = Slice.make(makeBound(sk, 1, 1, 0), makeBound(ek, 1, 1, 1));
assertTrue(slice.intersects(cc, columnNames(1, 0, 0), columnNames(1, 1, 1)));
assertSlicesIntersect(cc, slice, Slice.make(makeBound(sk, 1, 0, 0), makeBound(ek, 1, 1, 1)));
// same case, but with a missing start component
slice = Slice.make(makeBound(sk, 1, 1), makeBound(ek, 1, 1, 1));
assertTrue(slice.intersects(cc, columnNames(1, 0, 0), columnNames(1, 1, 1)));
assertSlicesIntersect(cc, slice, Slice.make(makeBound(sk, 1, 0, 0), makeBound(ek, 1, 1, 1)));
// slice falls entirely within sstable
slice = Slice.make(makeBound(sk, 1, 1, 0), makeBound(ek, 1, 1, 1));
assertTrue(slice.intersects(cc, columnNames(1, 0, 0), columnNames(2, 2, 2)));
assertSlicesIntersect(cc, slice, Slice.make(makeBound(sk, 1, 0, 0), makeBound(ek, 2, 2, 2)));
// same case, but with a missing start component
slice = Slice.make(makeBound(sk, 1, 1), makeBound(ek, 1, 1, 1));
assertTrue(slice.intersects(cc, columnNames(1, 0, 0), columnNames(2, 2, 2)));
assertSlicesIntersect(cc, slice, Slice.make(makeBound(sk, 1, 0, 0), makeBound(ek, 2, 2, 2)));
// same case, but with a missing start and end components
slice = Slice.make(makeBound(sk, 1), makeBound(ek, 1, 2));
assertTrue(slice.intersects(cc, columnNames(1, 0, 0), columnNames(2, 2, 2)));
assertSlicesIntersect(cc, slice, Slice.make(makeBound(sk, 1, 0, 0), makeBound(ek, 2, 2, 2)));
// same case, but with an equal first component and missing start and end components
slice = Slice.make(makeBound(sk, 1), makeBound(ek, 1));
assertTrue(slice.intersects(cc, columnNames(1, 0, 0), columnNames(2, 2, 2)));
assertSlicesIntersect(cc, slice, Slice.make(makeBound(sk, 1, 0, 0), makeBound(ek, 2, 2, 2)));
// slice falls entirely within sstable (slice start and end are the same)
slice = Slice.make(makeBound(sk, 1, 1, 1), makeBound(ek, 1, 1, 1));
assertTrue(slice.intersects(cc, columnNames(1, 0, 0), columnNames(2, 2, 2)));
assertSlicesIntersect(cc, slice, Slice.make(makeBound(sk, 1, 0, 0), makeBound(ek, 2, 2, 2)));
// slice starts within sstable, empty end
slice = Slice.make(makeBound(sk, 1, 1, 1), makeBound(ek));
assertTrue(slice.intersects(cc, columnNames(1, 0, 0), columnNames(2, 0, 0)));
assertSlicesIntersect(cc, slice, Slice.make(makeBound(sk, 1, 0, 0), makeBound(ek, 2, 0, 0)));
// same case, but with missing end components
slice = Slice.make(makeBound(sk, 1, 1, 1), makeBound(ek, 3));
assertTrue(slice.intersects(cc, columnNames(1, 0, 0), columnNames(2, 0, 0)));
assertSlicesIntersect(cc, slice, Slice.make(makeBound(sk, 1, 0, 0), makeBound(ek, 2, 0, 0)));
// slice starts within sstable (matching sstable start), empty end
slice = Slice.make(makeBound(sk, 1, 0, 0), makeBound(ek));
assertTrue(slice.intersects(cc, columnNames(1, 0, 0), columnNames(2, 0, 0)));
assertSlicesIntersect(cc, slice, Slice.make(makeBound(sk, 1, 0, 0), makeBound(ek, 2, 0, 0)));
// same case, but with missing end components
slice = Slice.make(makeBound(sk, 1, 0, 0), makeBound(ek, 3));
assertTrue(slice.intersects(cc, columnNames(1, 0, 0), columnNames(2, 0, 0)));
assertSlicesIntersect(cc, slice, Slice.make(makeBound(sk, 1, 0, 0), makeBound(ek, 2, 0, 0)));
// slice starts within sstable (matching sstable end), empty end
slice = Slice.make(makeBound(sk, 2, 0, 0), makeBound(ek));
assertTrue(slice.intersects(cc, columnNames(1, 0, 0), columnNames(2, 0, 0)));
assertSlicesIntersect(cc, slice, Slice.make(makeBound(sk, 1, 0, 0), makeBound(ek, 2, 0, 0)));
// same case, but with missing end components
slice = Slice.make(makeBound(sk, 2, 0, 0), makeBound(ek, 3));
assertTrue(slice.intersects(cc, columnNames(1, 0, 0), columnNames(2, 0, 0)));
assertSlicesIntersect(cc, slice, Slice.make(makeBound(sk, 1, 0, 0), makeBound(ek, 2, 0, 0)));
// slice ends within sstable, empty end
slice = Slice.make(makeBound(sk), makeBound(ek, 1, 1, 1));
assertTrue(slice.intersects(cc, columnNames(1, 0, 0), columnNames(2, 0, 0)));
assertSlicesIntersect(cc, slice, Slice.make(makeBound(sk, 1, 0, 0), makeBound(ek, 2, 0, 0)));
// same case, but with missing start components
slice = Slice.make(makeBound(sk, 0), makeBound(ek, 1, 1, 1));
assertTrue(slice.intersects(cc, columnNames(1, 0, 0), columnNames(2, 0, 0)));
assertSlicesIntersect(cc, slice, Slice.make(makeBound(sk, 1, 0, 0), makeBound(ek, 2, 0, 0)));
// slice ends within sstable (matching sstable start), empty start
slice = Slice.make(makeBound(sk), makeBound(ek, 1, 0, 0));
assertTrue(slice.intersects(cc, columnNames(1, 0, 0), columnNames(2, 0, 0)));
assertSlicesIntersect(cc, slice, Slice.make(makeBound(sk, 1, 0, 0), makeBound(ek, 2, 0, 0)));
// same case, but with missing start components
slice = Slice.make(makeBound(sk, 0), makeBound(ek, 1, 0, 0));
assertTrue(slice.intersects(cc, columnNames(1, 0, 0), columnNames(2, 0, 0)));
assertSlicesIntersect(cc, slice, Slice.make(makeBound(sk, 1, 0, 0), makeBound(ek, 2, 0, 0)));
// slice ends within sstable (matching sstable end), empty start
slice = Slice.make(makeBound(sk), makeBound(ek, 2, 0, 0));
assertTrue(slice.intersects(cc, columnNames(1, 0, 0), columnNames(2, 0, 0)));
assertSlicesIntersect(cc, slice, Slice.make(makeBound(sk, 1, 0, 0), makeBound(ek, 2, 0, 0)));
// same case, but with missing start components
slice = Slice.make(makeBound(sk, 0), makeBound(ek, 2, 0, 0));
assertTrue(slice.intersects(cc, columnNames(1, 0, 0), columnNames(2, 0, 0)));
assertSlicesIntersect(cc, slice, Slice.make(makeBound(sk, 1, 0, 0), makeBound(ek, 2, 0, 0)));
// empty min/max column names
slice = Slice.make(makeBound(sk), makeBound(ek));
assertTrue(slice.intersects(cc, columnNames(), columnNames()));
assertSlicesIntersect(cc, slice, Slice.make(makeBound(sk), makeBound(ek)));
slice = Slice.make(makeBound(sk, 1), makeBound(ek));
assertTrue(slice.intersects(cc, columnNames(), columnNames()));
assertSlicesIntersect(cc, slice, Slice.make(makeBound(sk), makeBound(ek)));
slice = Slice.make(makeBound(sk), makeBound(ek, 1));
assertTrue(slice.intersects(cc, columnNames(), columnNames()));
assertSlicesIntersect(cc, slice, Slice.make(makeBound(sk), makeBound(ek)));
slice = Slice.make(makeBound(sk, 1), makeBound(ek, 1));
assertTrue(slice.intersects(cc, columnNames(), columnNames()));
assertSlicesIntersect(cc, slice, Slice.make(makeBound(sk), makeBound(ek)));
slice = Slice.make(makeBound(sk), makeBound(ek));
assertTrue(slice.intersects(cc, columnNames(), columnNames(1)));
assertSlicesIntersect(cc, slice, Slice.make(makeBound(sk), makeBound(ek, 1)));
slice = Slice.make(makeBound(sk), makeBound(ek, 1));
assertTrue(slice.intersects(cc, columnNames(), columnNames(1)));
assertSlicesIntersect(cc, slice, Slice.make(makeBound(sk), makeBound(ek, 1)));
slice = Slice.make(makeBound(sk), makeBound(ek, 1));
assertTrue(slice.intersects(cc, columnNames(), columnNames(2)));
assertSlicesIntersect(cc, slice, Slice.make(makeBound(sk), makeBound(ek, 2)));
slice = Slice.make(makeBound(sk), makeBound(ek, 2));
assertTrue(slice.intersects(cc, columnNames(), columnNames(1)));
assertSlicesIntersect(cc, slice, Slice.make(makeBound(sk), makeBound(ek, 1)));
slice = Slice.make(makeBound(sk, 2), makeBound(ek, 3));
assertFalse(slice.intersects(cc, columnNames(), columnNames(1)));
assertSlicesDoNotIntersect(cc, slice, Slice.make(makeBound(sk), makeBound(ek, 1)));
// basic check on reversed slices
slice = Slice.make(makeBound(sk, 1, 0, 0), makeBound(ek, 0, 0, 0));
assertFalse(slice.intersects(cc, columnNames(2, 0, 0), columnNames(3, 0, 0)));
assertSlicesDoNotIntersect(cc, slice, Slice.make(makeBound(sk, 2, 0, 0), makeBound(ek, 3, 0, 0)));
slice = Slice.make(makeBound(sk, 1, 0, 0), makeBound(ek, 0, 0, 0));
assertFalse(slice.intersects(cc, columnNames(1, 1, 0), columnNames(3, 0, 0)));
assertSlicesDoNotIntersect(cc, slice, Slice.make(makeBound(sk, 1, 1, 0), makeBound(ek, 3, 0, 0)));
slice = Slice.make(makeBound(sk, 1, 1, 1), makeBound(ek, 1, 1, 0));
assertTrue(slice.intersects(cc, columnNames(1, 0, 0), columnNames(2, 2, 2)));
assertSlicesDoNotIntersect(cc, slice, Slice.make(makeBound(sk, 1, 0, 0), makeBound(ek, 2, 2, 2)));
}
@Test
@ -279,32 +290,32 @@ public class SliceTest
// slice does intersect
Slice slice = Slice.make(makeBound(sk), makeBound(ek));
assertTrue(slice.intersects(cc, columnNames(), columnNames(1)));
assertSlicesIntersect(cc, slice, Slice.make(makeBound(sk), makeBound(ek, 1)));
slice = Slice.make(makeBound(sk), makeBound(ek));
assertTrue(slice.intersects(cc, columnNames(1), columnNames(1, 2)));
assertSlicesIntersect(cc, slice, Slice.make(makeBound(sk, 1), makeBound(ek, 1, 2)));
slice = Slice.make(makeBound(sk), makeBound(ek, 1));
assertTrue(slice.intersects(cc, columnNames(), columnNames(1)));
assertSlicesIntersect(cc, slice, Slice.make(makeBound(sk), makeBound(ek, 1)));
slice = Slice.make(makeBound(sk, 1), makeBound(ek));
assertTrue(slice.intersects(cc, columnNames(), columnNames(1)));
assertSlicesIntersect(cc, slice, Slice.make(makeBound(sk), makeBound(ek, 1)));
slice = Slice.make(makeBound(sk, 1), makeBound(ek, 1));
assertTrue(slice.intersects(cc, columnNames(), columnNames(1)));
assertSlicesIntersect(cc, slice, Slice.make(makeBound(sk), makeBound(ek, 1)));
slice = Slice.make(makeBound(sk, 0), makeBound(ek, 1, 2, 3));
assertTrue(slice.intersects(cc, columnNames(), columnNames(1)));
assertSlicesIntersect(cc, slice, Slice.make(makeBound(sk), makeBound(ek, 1)));
slice = Slice.make(makeBound(sk, 1, 2, 3), makeBound(ek, 2));
assertTrue(slice.intersects(cc, columnNames(), columnNames(1)));
assertSlicesIntersect(cc, slice, Slice.make(makeBound(sk), makeBound(ek, 1)));
// slice does not intersect
slice = Slice.make(makeBound(sk, 2), makeBound(ek, 3, 4, 5));
assertFalse(slice.intersects(cc, columnNames(), columnNames(1)));
assertSlicesDoNotIntersect(cc, slice, Slice.make(makeBound(sk), makeBound(ek, 1)));
slice = Slice.make(makeBound(sk, 0), makeBound(ek, 0, 1, 2));
assertFalse(slice.intersects(cc, columnNames(1), columnNames(1, 2)));
assertSlicesDoNotIntersect(cc, slice, Slice.make(makeBound(sk, 1), makeBound(ek, 1, 2)));
}
@Test
@ -353,14 +364,6 @@ public class SliceTest
return BufferClusteringBound.create(kind, values);
}
private static List<ByteBuffer> columnNames(Integer ... components)
{
List<ByteBuffer> names = new ArrayList<>(components.length);
for (int component : components)
names.add(ByteBufferUtil.bytes(component));
return names;
}
private static Slice s(int start, int finish)
{
return Slice.make(makeBound(INCL_START_BOUND, start),
@ -382,4 +385,25 @@ public class SliceTest
for (int i = 0; i < expected.length; i++)
assertEquals(expected[i], slices.get(i));
}
}
private void assertSlicesIntersect(ClusteringComparator cc, Slice s1, Slice s2)
{
assertSlicesIntersectInternal(cc, s1, s2);
assertSlicesIntersectInternal(cc, Slice.ALL, s1);
assertSlicesIntersectInternal(cc, Slice.ALL, s2);
assertSlicesDoNotIntersect(cc, Slice.make(ClusteringBound.exclusiveStartOf(Clustering.EMPTY), ClusteringBound.exclusiveEndOf(Clustering.EMPTY)), s1);
assertSlicesDoNotIntersect(cc, Slice.make(ClusteringBound.exclusiveStartOf(Clustering.EMPTY), ClusteringBound.exclusiveEndOf(Clustering.EMPTY)), s2);
}
private void assertSlicesIntersectInternal(ClusteringComparator cc, Slice s1, Slice s2)
{
assertTrue(String.format("Slice %s should intersect with slice %s", s1.toString(cc), s2.toString(cc)), s1.intersects(cc, s2));
assertTrue(String.format("Slice %s should intersect with slice %s", s2.toString(cc), s1.toString(cc)), s2.intersects(cc, s1));
}
private void assertSlicesDoNotIntersect(ClusteringComparator cc, Slice s1, Slice s2)
{
assertFalse(String.format("Slice %s should not intersect with slice %s", s1.toString(cc), s2.toString(cc)), s1.intersects(cc, s2));
assertFalse(String.format("Slice %s should not intersect with slice %s", s2.toString(cc), s1.toString(cc)), s2.intersects(cc, s1));
}
}

View File

@ -42,6 +42,7 @@ import org.junit.Test;
import org.apache.cassandra.Util;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.Directories;
import org.apache.cassandra.db.SerializationHeader;
import org.apache.cassandra.db.compaction.OperationType;
@ -1275,9 +1276,10 @@ public class LogTransactionTest extends AbstractTransactionalTest
FileHandle dFile = new FileHandle.Builder(descriptor.filenameFor(Component.DATA)).complete();
FileHandle iFile = new FileHandle.Builder(descriptor.filenameFor(Component.PRIMARY_INDEX)).complete();
DecoratedKey key = MockSchema.readerBounds(generation);
SerializationHeader header = SerializationHeader.make(cfs.metadata(), Collections.emptyList());
StatsMetadata metadata = (StatsMetadata) new MetadataCollector(cfs.metadata().comparator)
.finalizeMetadata(cfs.metadata().partitioner.getClass().getCanonicalName(), 0.01f, -1, null, false, header)
.finalizeMetadata(cfs.metadata().partitioner.getClass().getCanonicalName(), 0.01f, -1, null, false, header, key.getKey().slice(), key.getKey().slice())
.get(MetadataType.STATS);
SSTableReader reader = SSTableReader.internalOpen(descriptor,
components,
@ -1290,7 +1292,7 @@ public class LogTransactionTest extends AbstractTransactionalTest
metadata,
SSTableReader.OpenReason.NORMAL,
header);
reader.first = reader.last = MockSchema.readerBounds(generation);
reader.first = reader.last = key;
return reader;
}

View File

@ -200,6 +200,12 @@ public class RowsTest
{
this.hasLegacyCounterShards |= hasLegacyCounterShards;
}
@Override
public void updatePartitionDeletion(DeletionTime dt)
{
update(dt);
}
}
private static long secondToTs(int now)
@ -648,4 +654,4 @@ public class RowsTest
liveCell(b, 1),
liveCell(a));
}
}
}

View File

@ -0,0 +1,263 @@
/*
* 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.rows;
import java.math.BigInteger;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.apache.cassandra.SchemaLoader;
import org.apache.cassandra.Util;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.cql3.QueryProcessor;
import org.apache.cassandra.db.ClusteringBound;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.Keyspace;
import org.apache.cassandra.db.SinglePartitionReadCommand;
import org.apache.cassandra.db.Slice;
import org.apache.cassandra.db.Slices;
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.marshal.Int32Type;
import org.apache.cassandra.db.marshal.IntegerType;
import org.apache.cassandra.db.marshal.UTF8Type;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.io.sstable.format.SSTableReadsListener;
import org.apache.cassandra.schema.KeyspaceParams;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.FBUtilities;
import org.mockito.Mockito;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class UnfilteredRowIteratorWithLowerBoundTest
{
private static final String KEYSPACE = "ks";
private static final String TABLE = "tbl";
private static final String SLICES_TABLE = "tbl_slices";
private static TableMetadata tableMetadata;
private static TableMetadata slicesTableMetadata;
@BeforeClass
public static void defineSchema() throws ConfigurationException
{
DatabaseDescriptor.daemonInitialization();
tableMetadata =
TableMetadata.builder(KEYSPACE, TABLE)
.addPartitionKeyColumn("k", UTF8Type.instance)
.addStaticColumn("s", UTF8Type.instance)
.addClusteringColumn("i", IntegerType.instance)
.addRegularColumn("v", UTF8Type.instance)
.build();
slicesTableMetadata = TableMetadata.builder(KEYSPACE, SLICES_TABLE)
.addPartitionKeyColumn("k", UTF8Type.instance)
.addClusteringColumn("c1", Int32Type.instance)
.addClusteringColumn("c2", Int32Type.instance)
.addRegularColumn("v", IntegerType.instance)
.build();
SchemaLoader.prepareServer();
SchemaLoader.createKeyspace(KEYSPACE, KeyspaceParams.simple(1), tableMetadata, slicesTableMetadata);
}
@Before
public void truncate()
{
Keyspace.open(KEYSPACE).getColumnFamilyStore(TABLE).truncateBlocking();
Keyspace.open(KEYSPACE).getColumnFamilyStore(SLICES_TABLE).truncateBlocking();
}
@Test
public void testLowerBoundApplicableSingleColumnAsc()
{
String query = "INSERT INTO %s.%s (k, i) VALUES ('k1', %s)";
SSTableReader sstable = createSSTable(tableMetadata, KEYSPACE, TABLE, query);
assertEquals(Slice.make(Util.clustering(tableMetadata.comparator, BigInteger.valueOf(0)),
Util.clustering(tableMetadata.comparator, BigInteger.valueOf(9))),
sstable.getSSTableMetadata().coveredClustering);
DecoratedKey key = tableMetadata.partitioner.decorateKey(ByteBufferUtil.bytes("k1"));
Slice slice1 = Slice.make(Util.clustering(tableMetadata.comparator, BigInteger.valueOf(3)).asStartBound(), ClusteringBound.TOP);
assertFalse(lowerBoundApplicable(tableMetadata, key, slice1, sstable, false));
assertTrue(lowerBoundApplicable(tableMetadata, key, slice1, sstable, true));
Slice slice2 = Slice.make(ClusteringBound.BOTTOM, Util.clustering(tableMetadata.comparator, BigInteger.valueOf(3)).asEndBound());
assertTrue(lowerBoundApplicable(tableMetadata, key, slice2, sstable, false));
assertFalse(lowerBoundApplicable(tableMetadata, key, slice2, sstable, true));
// corner cases
Slice slice3 = Slice.make(Util.clustering(tableMetadata.comparator, BigInteger.valueOf(0)).asStartBound(), ClusteringBound.TOP);
assertFalse(lowerBoundApplicable(tableMetadata, key, slice3, sstable, false));
assertTrue(lowerBoundApplicable(tableMetadata, key, slice3, sstable, true));
Slice slice4 = Slice.make(ClusteringBound.BOTTOM, Util.clustering(tableMetadata.comparator, BigInteger.valueOf(9)).asEndBound());
assertTrue(lowerBoundApplicable(tableMetadata, key, slice4, sstable, false));
assertFalse(lowerBoundApplicable(tableMetadata, key, slice4, sstable, true));
}
@Test
public void testLowerBoundApplicableSingleColumnDesc()
{
String TABLE_REVERSED = "tbl_reversed";
String createTable = String.format(
"CREATE TABLE %s.%s (k text, i varint, v int, primary key (k, i)) WITH CLUSTERING ORDER BY (i DESC)",
KEYSPACE, TABLE_REVERSED);
QueryProcessor.executeOnceInternal(createTable);
ColumnFamilyStore cfs = Keyspace.open(KEYSPACE).getColumnFamilyStore(TABLE_REVERSED);
TableMetadata metadata = cfs.metadata();
String query = "INSERT INTO %s.%s (k, i) VALUES ('k1', %s)";
SSTableReader sstable = createSSTable(metadata, KEYSPACE, TABLE_REVERSED, query);
assertEquals(Slice.make(Util.clustering(metadata.comparator, BigInteger.valueOf(9)),
Util.clustering(metadata.comparator, BigInteger.valueOf(0))),
sstable.getSSTableMetadata().coveredClustering);
DecoratedKey key = metadata.partitioner.decorateKey(ByteBufferUtil.bytes("k1"));
Slice slice1 = Slice.make(Util.clustering(metadata.comparator, BigInteger.valueOf(8)).asStartBound(), ClusteringBound.TOP);
assertFalse(lowerBoundApplicable(metadata, key, slice1, sstable, false));
assertTrue(lowerBoundApplicable(metadata, key, slice1, sstable, true));
Slice slice2 = Slice.make(ClusteringBound.BOTTOM, Util.clustering(metadata.comparator, BigInteger.valueOf(8)).asEndBound());
assertTrue(lowerBoundApplicable(metadata, key, slice2, sstable, false));
assertFalse(lowerBoundApplicable(metadata, key, slice2, sstable, true));
// corner cases
Slice slice3 = Slice.make(Util.clustering(metadata.comparator, BigInteger.valueOf(9)).asStartBound(), ClusteringBound.TOP);
assertFalse(lowerBoundApplicable(metadata, key, slice3, sstable, false));
assertTrue(lowerBoundApplicable(metadata, key, slice3, sstable, true));
Slice slice4 = Slice.make(ClusteringBound.BOTTOM, Util.clustering(metadata.comparator, BigInteger.valueOf(0)).asEndBound());
assertTrue(lowerBoundApplicable(metadata, key, slice4, sstable, false));
assertFalse(lowerBoundApplicable(metadata, key, slice4, sstable, true));
}
@Test
public void testLowerBoundApplicableMultipleColumnsAsc()
{
String query = "INSERT INTO %s.%s (k, c1, c2) VALUES ('k1', 0, %s)";
SSTableReader sstable = createSSTable(slicesTableMetadata, KEYSPACE, SLICES_TABLE, query);
assertEquals(Slice.make(Util.clustering(slicesTableMetadata.comparator, 0, 0),
Util.clustering(slicesTableMetadata.comparator, 0, 9)),
sstable.getSSTableMetadata().coveredClustering);
DecoratedKey key = slicesTableMetadata.partitioner.decorateKey(ByteBufferUtil.bytes("k1"));
Slice slice1 = Slice.make(Util.clustering(slicesTableMetadata.comparator, 0, 3).asStartBound(), ClusteringBound.TOP);
assertFalse(lowerBoundApplicable(slicesTableMetadata, key, slice1, sstable, false));
assertTrue(lowerBoundApplicable(slicesTableMetadata, key, slice1, sstable, true));
Slice slice2 = Slice.make(ClusteringBound.BOTTOM, Util.clustering(slicesTableMetadata.comparator, 0, 3).asEndBound());
assertTrue(lowerBoundApplicable(slicesTableMetadata, key, slice2, sstable, false));
assertFalse(lowerBoundApplicable(slicesTableMetadata, key, slice2, sstable, true));
// corner cases
Slice slice3 = Slice.make(Util.clustering(slicesTableMetadata.comparator, 0, 0).asStartBound(), ClusteringBound.TOP);
assertFalse(lowerBoundApplicable(slicesTableMetadata, key, slice3, sstable, false));
assertTrue(lowerBoundApplicable(slicesTableMetadata, key, slice3, sstable, true));
Slice slice4 = Slice.make(ClusteringBound.BOTTOM, Util.clustering(slicesTableMetadata.comparator, 0, 9).asEndBound());
assertTrue(lowerBoundApplicable(slicesTableMetadata, key, slice4, sstable, false));
assertFalse(lowerBoundApplicable(slicesTableMetadata, key, slice4, sstable, true));
}
@Test
public void testLowerBoundApplicableMultipleColumnsDesc()
{
String TABLE_REVERSED = "tbl_slices_reversed";
String createTable = String.format(
"CREATE TABLE %s.%s (k text, c1 int, c2 int, v int, primary key (k, c1, c2)) WITH CLUSTERING ORDER BY (c1 ASC, c2 DESC)",
KEYSPACE, TABLE_REVERSED);
QueryProcessor.executeOnceInternal(createTable);
ColumnFamilyStore cfs = Keyspace.open(KEYSPACE).getColumnFamilyStore(TABLE_REVERSED);
TableMetadata metadata = cfs.metadata();
String query = "INSERT INTO %s.%s (k, c1, c2) VALUES ('k1', 0, %s)";
SSTableReader sstable = createSSTable(metadata, KEYSPACE, TABLE_REVERSED, query);
assertEquals(Slice.make(Util.clustering(metadata.comparator, 0, 9),
Util.clustering(metadata.comparator, 0, 0)),
sstable.getSSTableMetadata().coveredClustering);
DecoratedKey key = metadata.partitioner.decorateKey(ByteBufferUtil.bytes("k1"));
Slice slice1 = Slice.make(Util.clustering(metadata.comparator, 0, 8).asStartBound(), ClusteringBound.TOP);
assertFalse(lowerBoundApplicable(metadata, key, slice1, sstable, false));
assertTrue(lowerBoundApplicable(metadata, key, slice1, sstable, true));
Slice slice2 = Slice.make(ClusteringBound.BOTTOM, Util.clustering(metadata.comparator, 0, 8).asEndBound());
assertTrue(lowerBoundApplicable(metadata, key, slice2, sstable, false));
assertFalse(lowerBoundApplicable(metadata, key, slice2, sstable, true));
// corner cases
Slice slice3 = Slice.make(Util.clustering(metadata.comparator, 0, 9).asStartBound(), ClusteringBound.TOP);
assertFalse(lowerBoundApplicable(metadata, key, slice3, sstable, false));
assertTrue(lowerBoundApplicable(metadata, key, slice3, sstable, true));
Slice slice4 = Slice.make(ClusteringBound.BOTTOM, Util.clustering(metadata.comparator, 0, 0).asEndBound());
assertTrue(lowerBoundApplicable(metadata, key, slice4, sstable, false));
assertFalse(lowerBoundApplicable(metadata, key, slice4, sstable, true));
}
private SSTableReader createSSTable(TableMetadata metadata, String keyspace, String table, String query)
{
ColumnFamilyStore cfs = Keyspace.open(keyspace).getColumnFamilyStore(table);
for (int i = 0; i < 10; i++)
QueryProcessor.executeInternal(String.format(query, keyspace, table, i));
cfs.forceBlockingFlush(ColumnFamilyStore.FlushReason.UNIT_TESTS);
DecoratedKey key = metadata.partitioner.decorateKey(ByteBufferUtil.bytes("k1"));
ColumnFamilyStore.ViewFragment view = cfs.select(View.select(SSTableSet.LIVE, key));
assertEquals(1, view.sstables.size());
return view.sstables.get(0);
}
private boolean lowerBoundApplicable(TableMetadata metadata, DecoratedKey key, Slice slice, SSTableReader sstable, boolean isReversed)
{
Slices.Builder slicesBuilder = new Slices.Builder(metadata.comparator);
slicesBuilder.add(slice);
Slices slices = slicesBuilder.build();
ClusteringIndexSliceFilter filter = new ClusteringIndexSliceFilter(slices, isReversed);
SinglePartitionReadCommand cmd = SinglePartitionReadCommand.create(metadata,
FBUtilities.nowInSeconds(),
ColumnFilter.all(metadata),
RowFilter.NONE,
DataLimits.NONE,
key,
filter);
try (UnfilteredRowIteratorWithLowerBound iter = new UnfilteredRowIteratorWithLowerBound(key,
sstable,
slices,
isReversed,
ColumnFilter.all(metadata),
Mockito.mock(SSTableReadsListener.class)))
{
return iter.lowerBound() != null;
}
}
}

View File

@ -94,7 +94,7 @@ public class LegacySSTableTest
* See {@link #testGenerateSstables()} to generate sstables.
* Take care on commit as you need to add the sstable files using {@code git add -f}
*/
public static final String[] legacyVersions = {"nb", "na", "me", "md", "mc", "mb", "ma"};
public static final String[] legacyVersions = {"nc", "nb", "na", "me", "md", "mc", "mb", "ma"};
// 1200 chars
static final String longString = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" +

View File

@ -20,6 +20,7 @@ package org.apache.cassandra.io.sstable;
import java.nio.ByteBuffer;
import java.nio.charset.CharacterCodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.junit.BeforeClass;
@ -27,7 +28,6 @@ import org.junit.Test;
import org.apache.cassandra.SchemaLoader;
import org.apache.cassandra.Util;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.Keyspace;
import org.apache.cassandra.db.RowUpdateBuilder;
@ -35,6 +35,7 @@ import org.apache.cassandra.db.marshal.AsciiType;
import org.apache.cassandra.db.marshal.IntegerType;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.schema.KeyspaceParams;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.utils.ByteBufferUtil;
import static org.junit.Assert.assertEquals;
@ -218,11 +219,11 @@ public class SSTableMetadataTest
assertEquals(1, store.getLiveSSTables().size());
for (SSTableReader sstable : store.getLiveSSTables())
{
assertEquals(ByteBufferUtil.string(sstable.getSSTableMetadata().minClusteringValues.get(0)), "0col100");
assertEquals(ByteBufferUtil.string(sstable.getSSTableMetadata().maxClusteringValues.get(0)), "7col149");
// make sure stats don't reference native or off-heap data
assertBuffersAreRetainable(sstable.getSSTableMetadata().minClusteringValues);
assertBuffersAreRetainable(sstable.getSSTableMetadata().maxClusteringValues);
assertEquals(ByteBufferUtil.string(sstable.getSSTableMetadata().coveredClustering.start().bufferAt(0)), "0col100");
assertEquals(ByteBufferUtil.string(sstable.getSSTableMetadata().coveredClustering.end().bufferAt(0)), "7col149");
// make sure the clustering values are minimised
assertTrue(sstable.getSSTableMetadata().coveredClustering.start().bufferAt(0).capacity() < 50);
assertTrue(sstable.getSSTableMetadata().coveredClustering.end().bufferAt(0).capacity() < 50);
}
String key = "row2";
@ -240,11 +241,11 @@ public class SSTableMetadataTest
assertEquals(1, store.getLiveSSTables().size());
for (SSTableReader sstable : store.getLiveSSTables())
{
assertEquals(ByteBufferUtil.string(sstable.getSSTableMetadata().minClusteringValues.get(0)), "0col100");
assertEquals(ByteBufferUtil.string(sstable.getSSTableMetadata().maxClusteringValues.get(0)), "9col298");
assertEquals(ByteBufferUtil.string(sstable.getSSTableMetadata().coveredClustering.start().bufferAt(0)), "0col100");
assertEquals(ByteBufferUtil.string(sstable.getSSTableMetadata().coveredClustering.end().bufferAt(0)), "9col298");
// make sure stats don't reference native or off-heap data
assertBuffersAreRetainable(sstable.getSSTableMetadata().minClusteringValues);
assertBuffersAreRetainable(sstable.getSSTableMetadata().maxClusteringValues);
assertBuffersAreRetainable(Arrays.asList(sstable.getSSTableMetadata().coveredClustering.start().getBufferArray()));
assertBuffersAreRetainable(Arrays.asList(sstable.getSSTableMetadata().coveredClustering.end().getBufferArray()));
}
key = "row3";
@ -258,11 +259,11 @@ public class SSTableMetadataTest
assertEquals(1, store.getLiveSSTables().size());
for (SSTableReader sstable : store.getLiveSSTables())
{
assertEquals(ByteBufferUtil.string(sstable.getSSTableMetadata().minClusteringValues.get(0)), "0");
assertEquals(ByteBufferUtil.string(sstable.getSSTableMetadata().maxClusteringValues.get(0)), "9col298");
assertEquals(ByteBufferUtil.string(sstable.getSSTableMetadata().coveredClustering.start().bufferAt(0)), "0");
assertEquals(ByteBufferUtil.string(sstable.getSSTableMetadata().coveredClustering.end().bufferAt(0)), "9col298");
// make sure stats don't reference native or off-heap data
assertBuffersAreRetainable(sstable.getSSTableMetadata().minClusteringValues);
assertBuffersAreRetainable(sstable.getSSTableMetadata().maxClusteringValues);
assertBuffersAreRetainable(Arrays.asList(sstable.getSSTableMetadata().coveredClustering.start().getBufferArray()));
assertBuffersAreRetainable(Arrays.asList(sstable.getSSTableMetadata().coveredClustering.end().getBufferArray()));
}
}
@ -278,54 +279,4 @@ public class SSTableMetadataTest
}
}
/*@Test
public void testLegacyCounterShardTracking()
{
ColumnFamilyStore cfs = Keyspace.open(KEYSPACE1).getColumnFamilyStore("Counter1");
// A cell with all shards
CounterContext.ContextState state = CounterContext.ContextState.allocate(1, 1, 1);
state.writeGlobal(CounterId.fromInt(1), 1L, 1L);
state.writeLocal(CounterId.fromInt(2), 1L, 1L);
state.writeRemote(CounterId.fromInt(3), 1L, 1L);
ColumnFamily cells = ArrayBackedSortedColumns.factory.create(cfs.metadata);
cells.addColumn(new BufferCounterCell(cellname("col"), state.context, 1L, Long.MIN_VALUE));
new Mutation(Util.dk("k").getKey(), cells).applyUnsafe();
Util.flush(cfs);
assertTrue(cfs.getLiveSSTables().iterator().next().getSSTableMetadata().hasLegacyCounterShards);
cfs.truncateBlocking();
// A cell with global and remote shards
state = CounterContext.ContextState.allocate(0, 1, 1);
state.writeLocal(CounterId.fromInt(2), 1L, 1L);
state.writeRemote(CounterId.fromInt(3), 1L, 1L);
cells = ArrayBackedSortedColumns.factory.create(cfs.metadata);
cells.addColumn(new BufferCounterCell(cellname("col"), state.context, 1L, Long.MIN_VALUE));
new Mutation(Util.dk("k").getKey(), cells).applyUnsafe();
Util.flush(cfs);
assertTrue(cfs.getLiveSSTables().iterator().next().getSSTableMetadata().hasLegacyCounterShards);
cfs.truncateBlocking();
// A cell with global and local shards
state = CounterContext.ContextState.allocate(1, 1, 0);
state.writeGlobal(CounterId.fromInt(1), 1L, 1L);
state.writeLocal(CounterId.fromInt(2), 1L, 1L);
cells = ArrayBackedSortedColumns.factory.create(cfs.metadata);
cells.addColumn(new BufferCounterCell(cellname("col"), state.context, 1L, Long.MIN_VALUE));
new Mutation(Util.dk("k").getKey(), cells).applyUnsafe();
Util.flush(cfs);
assertTrue(cfs.getLiveSSTables().iterator().next().getSSTableMetadata().hasLegacyCounterShards);
cfs.truncateBlocking();
// A cell with global only
state = CounterContext.ContextState.allocate(1, 0, 0);
state.writeGlobal(CounterId.fromInt(1), 1L, 1L);
cells = ArrayBackedSortedColumns.factory.create(cfs.metadata);
cells.addColumn(new BufferCounterCell(cellname("col"), state.context, 1L, Long.MIN_VALUE));
new Mutation(Util.dk("k").getKey(), cells).applyUnsafe();
Util.flush(cfs);
assertFalse(cfs.getLiveSSTables().iterator().next().getSSTableMetadata().hasLegacyCounterShards);
cfs.truncateBlocking();
} */
}
}

View File

@ -20,6 +20,7 @@ package org.apache.cassandra.io.sstable.metadata;
import org.apache.cassandra.io.sstable.SequenceBasedSSTableId;
import org.apache.cassandra.io.util.*;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
@ -40,6 +41,7 @@ import org.apache.cassandra.io.sstable.Descriptor;
import org.apache.cassandra.io.sstable.format.SSTableFormat;
import org.apache.cassandra.io.sstable.format.Version;
import org.apache.cassandra.io.sstable.format.big.BigFormat;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.Throwables;
import static org.junit.Assert.assertEquals;
@ -124,7 +126,9 @@ public class MetadataSerializerTest
String partitioner = RandomPartitioner.class.getCanonicalName();
double bfFpChance = 0.1;
return collector.finalizeMetadata(partitioner, bfFpChance, 0, null, false, SerializationHeader.make(cfm, Collections.emptyList()));
ByteBuffer first = ByteBufferUtil.bytes(1);
ByteBuffer last = ByteBufferUtil.bytes(2);
return collector.finalizeMetadata(partitioner, bfFpChance, 0, null, false, SerializationHeader.make(cfm, Collections.emptyList()), first, last);
}
private void testVersions(String... versions) throws Throwable
@ -159,7 +163,7 @@ public class MetadataSerializerTest
@Test
public void testNVersions() throws Throwable
{
testVersions("na", "nb");
testVersions("na", "nb", "nc");
}
public void testOldReadsNew(String oldV, String newV) throws IOException
@ -193,13 +197,34 @@ public class MetadataSerializerTest
public void pendingRepairCompatibility()
{
Arrays.asList("ma", "mb", "mc", "md", "me").forEach(v -> assertFalse(BigFormat.instance.getVersion(v).hasPendingRepair()));
Arrays.asList("na", "nb").forEach(v -> assertTrue(BigFormat.instance.getVersion(v).hasPendingRepair()));
Arrays.asList("na", "nb", "nc").forEach(v -> assertTrue(BigFormat.instance.getVersion(v).hasPendingRepair()));
}
@Test
public void originatingHostCompatibility()
{
Arrays.asList("ma", "mb", "mc", "md", "na").forEach(v -> assertFalse(BigFormat.instance.getVersion(v).hasOriginatingHostId()));
Arrays.asList("me", "nb").forEach(v -> assertTrue(BigFormat.instance.getVersion(v).hasOriginatingHostId()));
Arrays.asList("me", "nb", "nc").forEach(v -> assertTrue(BigFormat.instance.getVersion(v).hasOriginatingHostId()));
}
@Test
public void improvedMinMaxCompatibility()
{
Arrays.asList("ma", "mb", "mc", "md", "me", "na", "nb").forEach(v -> assertFalse(BigFormat.instance.getVersion(v).hasImprovedMinMax()));
Arrays.asList("nc", "oa").forEach(v -> assertTrue(BigFormat.instance.getVersion(v).hasImprovedMinMax()));
}
@Test
public void legacyMinMaxCompatiblity()
{
Arrays.asList("oa").forEach(v -> assertFalse(BigFormat.instance.getVersion(v).hasLegacyMinMax()));
Arrays.asList("ma", "mb", "mc", "md", "me", "na", "nb", "nc").forEach(v -> assertTrue(BigFormat.instance.getVersion(v).hasLegacyMinMax()));
}
@Test
public void partitionLevelDeletionPresenceMarkerCompatibility()
{
Arrays.asList("ma", "mb", "mc", "md", "me", "na", "nb").forEach(v -> assertFalse(BigFormat.instance.getVersion(v).hasPartitionLevelDeletionsPresenceMarker()));
Arrays.asList("nc", "oa").forEach(v -> assertTrue(BigFormat.instance.getVersion(v).hasPartitionLevelDeletionsPresenceMarker()));
}
}

View File

@ -41,6 +41,7 @@ import org.apache.cassandra.dht.Murmur3Partitioner;
import org.apache.cassandra.io.sstable.Component;
import org.apache.cassandra.io.sstable.Descriptor;
import org.apache.cassandra.io.sstable.IndexSummary;
import org.apache.cassandra.io.sstable.SSTable;
import org.apache.cassandra.io.sstable.SSTableId;
import org.apache.cassandra.io.sstable.format.SSTableFormat;
import org.apache.cassandra.io.sstable.format.SSTableReader;
@ -182,14 +183,18 @@ public class MockSchema
SerializationHeader header = SerializationHeader.make(cfs.metadata(), Collections.emptyList());
MetadataCollector collector = new MetadataCollector(cfs.metadata().comparator);
collector.update(new DeletionTime(timestamp, minLocalDeletionTime));
BufferDecoratedKey first = readerBounds(firstToken);
BufferDecoratedKey last = readerBounds(lastToken);
StatsMetadata metadata = (StatsMetadata) collector.sstableLevel(level)
.finalizeMetadata(cfs.metadata().partitioner.getClass().getCanonicalName(), 0.01f, UNREPAIRED_SSTABLE, null, false, header)
.finalizeMetadata(cfs.metadata().partitioner.getClass().getCanonicalName(), 0.01f, UNREPAIRED_SSTABLE, null, false, header, SSTable.getMinimalKey(first).getKey().slice(), SSTable.getMinimalKey(last).getKey().slice())
.get(MetadataType.STATS);
SSTableReader reader = SSTableReader.internalOpen(descriptor, components, cfs.metadata,
fileHandle.sharedCopy(), fileHandle.sharedCopy(), indexSummary.sharedCopy(),
new AlwaysPresentFilter(), 1L, metadata, SSTableReader.OpenReason.NORMAL, header);
reader.first = readerBounds(firstToken);
reader.last = readerBounds(lastToken);
reader.first = first ;
reader.last = last;
if (!keepRef)
reader.selfRef().release();
return reader;

View File

@ -408,9 +408,10 @@ public class ByteSourceComparisonTest extends ByteSourceTestBase
BiFunction<AbstractType, Object, ByteBuffer> decompose,
boolean testLegacy)
{
EnumSet<ClusteringPrefix.Kind> skippedKinds = EnumSet.of(ClusteringPrefix.Kind.SSTABLE_LOWER_BOUND, ClusteringPrefix.Kind.SSTABLE_UPPER_BOUND);
for (Version v : Version.values())
for (ClusteringPrefix.Kind k1 : ClusteringPrefix.Kind.values())
for (ClusteringPrefix.Kind k2 : ClusteringPrefix.Kind.values())
for (ClusteringPrefix.Kind k1 : EnumSet.complementOf(skippedKinds))
for (ClusteringPrefix.Kind k2 : EnumSet.complementOf(skippedKinds))
{
if (!testLegacy && v == Version.LEGACY)
continue;
@ -473,7 +474,7 @@ public class ByteSourceComparisonTest extends ByteSourceTestBase
return factory.staticClustering();
default:
throw new AssertionError();
throw new AssertionError(k1);
}
}
@ -1175,4 +1176,4 @@ public class ByteSourceComparisonTest extends ByteSourceTestBase
{
return ByteBuffer.allocate(paddedCapacity);
}
}
}

View File

@ -327,7 +327,8 @@ public class ByteSourceConversionTest extends ByteSourceTestBase
{
ValueAccessor<ByteBuffer> accessor = ByteBufferAccessor.instance;
ClusteringComparator comp = new ClusteringComparator();
for (ClusteringPrefix.Kind kind : ClusteringPrefix.Kind.values())
EnumSet<ClusteringPrefix.Kind> skippedKinds = EnumSet.of(ClusteringPrefix.Kind.SSTABLE_LOWER_BOUND, ClusteringPrefix.Kind.SSTABLE_UPPER_BOUND);
for (ClusteringPrefix.Kind kind : EnumSet.complementOf(skippedKinds))
{
if (kind.isBoundary())
continue;
@ -350,7 +351,8 @@ public class ByteSourceConversionTest extends ByteSourceTestBase
BiFunction<AbstractType, Object, ByteBuffer> decompose)
{
boolean checkEquals = t1 != DecimalType.instance && t2 != DecimalType.instance;
for (ClusteringPrefix.Kind k1 : ClusteringPrefix.Kind.values())
EnumSet<ClusteringPrefix.Kind> skippedKinds = EnumSet.of(ClusteringPrefix.Kind.SSTABLE_LOWER_BOUND, ClusteringPrefix.Kind.SSTABLE_UPPER_BOUND);
for (ClusteringPrefix.Kind k1 : EnumSet.complementOf(skippedKinds))
{
ClusteringComparator comp = new ClusteringComparator(t1, t2);
V[] b = accessor.createArray(2);
@ -781,4 +783,4 @@ public class ByteSourceConversionTest extends ByteSourceTestBase
{
return ByteBuffer.allocate(paddedCapacity);
}
}
}