mirror of https://github.com/apache/cassandra
Add cursor based optimized compaction path
Adds a compaction implementation utilizing new fixed allocation SSTable reader/writer implementations, and other purpose built code, leading to improved efficiencies. patch by Nitsan Wakart; reviewed by Branimir Lambov, Dmitry Konstantinov for CASSANDRA-20918
This commit is contained in:
parent
66a7a36647
commit
ff78780d61
|
|
@ -1,4 +1,5 @@
|
||||||
5.1
|
5.1
|
||||||
|
* Add cursor based optimized compaction path (CASSANDRA-20918)
|
||||||
* Ensure peers with LEFT status are expired from gossip state (CASSANDRA-21035)
|
* Ensure peers with LEFT status are expired from gossip state (CASSANDRA-21035)
|
||||||
* Optimize UTF8Validator.validate for ASCII prefixed Strings (CASSANDRA-21075)
|
* Optimize UTF8Validator.validate for ASCII prefixed Strings (CASSANDRA-21075)
|
||||||
* Switch LatencyMetrics to use ThreadLocalTimer/ThreadLocalCounter (CASSANDRA-21080)
|
* Switch LatencyMetrics to use ThreadLocalTimer/ThreadLocalCounter (CASSANDRA-21080)
|
||||||
|
|
|
||||||
|
|
@ -1414,6 +1414,7 @@
|
||||||
<jvmarg value="-Dcassandra.config=file:///${scm_none_yaml}"/>
|
<jvmarg value="-Dcassandra.config=file:///${scm_none_yaml}"/>
|
||||||
<jvmarg value="-Dcassandra.test.storage_compatibility_mode=NONE"/>
|
<jvmarg value="-Dcassandra.test.storage_compatibility_mode=NONE"/>
|
||||||
<jvmarg value="-Dcassandra.skip_sync=true" />
|
<jvmarg value="-Dcassandra.skip_sync=true" />
|
||||||
|
<jvmarg value="-Dcassandra.cursor_compaction_enabled=false" />
|
||||||
</testmacrohelper>
|
</testmacrohelper>
|
||||||
</sequential>
|
</sequential>
|
||||||
</macrodef>
|
</macrodef>
|
||||||
|
|
|
||||||
|
|
@ -193,6 +193,7 @@ public enum CassandraRelevantProperties
|
||||||
CONSISTENT_RANGE_MOVEMENT("cassandra.consistent.rangemovement", "true"),
|
CONSISTENT_RANGE_MOVEMENT("cassandra.consistent.rangemovement", "true"),
|
||||||
CONSISTENT_SIMULTANEOUS_MOVES_ALLOW("cassandra.consistent.simultaneousmoves.allow"),
|
CONSISTENT_SIMULTANEOUS_MOVES_ALLOW("cassandra.consistent.simultaneousmoves.allow"),
|
||||||
CRYPTO_PROVIDER_CLASS_NAME("cassandra.crypto_provider_class_name"),
|
CRYPTO_PROVIDER_CLASS_NAME("cassandra.crypto_provider_class_name"),
|
||||||
|
CURSOR_COMPACTION_ENABLED("cassandra.cursor_compaction_enabled", "true"),
|
||||||
CUSTOM_DISK_ERROR_HANDLER("cassandra.custom_disk_error_handler"),
|
CUSTOM_DISK_ERROR_HANDLER("cassandra.custom_disk_error_handler"),
|
||||||
CUSTOM_GUARDRAILS_CONFIG_PROVIDER_CLASS("cassandra.custom_guardrails_config_provider_class"),
|
CUSTOM_GUARDRAILS_CONFIG_PROVIDER_CLASS("cassandra.custom_guardrails_config_provider_class"),
|
||||||
CUSTOM_QUERY_HANDLER_CLASS("cassandra.custom_query_handler_class"),
|
CUSTOM_QUERY_HANDLER_CLASS("cassandra.custom_query_handler_class"),
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,7 @@ import org.apache.cassandra.utils.StorageCompatibilityMode;
|
||||||
|
|
||||||
import static org.apache.cassandra.config.CassandraRelevantProperties.AUTOCOMPACTION_ON_STARTUP_ENABLED;
|
import static org.apache.cassandra.config.CassandraRelevantProperties.AUTOCOMPACTION_ON_STARTUP_ENABLED;
|
||||||
import static org.apache.cassandra.config.CassandraRelevantProperties.CASSANDRA_AVAILABLE_PROCESSORS;
|
import static org.apache.cassandra.config.CassandraRelevantProperties.CASSANDRA_AVAILABLE_PROCESSORS;
|
||||||
|
import static org.apache.cassandra.config.CassandraRelevantProperties.CURSOR_COMPACTION_ENABLED;
|
||||||
import static org.apache.cassandra.config.CassandraRelevantProperties.FILE_CACHE_ENABLED;
|
import static org.apache.cassandra.config.CassandraRelevantProperties.FILE_CACHE_ENABLED;
|
||||||
import static org.apache.cassandra.config.CassandraRelevantProperties.SKIP_PAXOS_REPAIR_ON_TOPOLOGY_CHANGE;
|
import static org.apache.cassandra.config.CassandraRelevantProperties.SKIP_PAXOS_REPAIR_ON_TOPOLOGY_CHANGE;
|
||||||
import static org.apache.cassandra.config.CassandraRelevantProperties.SKIP_PAXOS_REPAIR_ON_TOPOLOGY_CHANGE_KEYSPACES;
|
import static org.apache.cassandra.config.CassandraRelevantProperties.SKIP_PAXOS_REPAIR_ON_TOPOLOGY_CHANGE_KEYSPACES;
|
||||||
|
|
@ -661,6 +662,8 @@ public class Config
|
||||||
@Replaces(oldName = "enable_drop_compact_storage", converter = Converters.IDENTITY, deprecated = true)
|
@Replaces(oldName = "enable_drop_compact_storage", converter = Converters.IDENTITY, deprecated = true)
|
||||||
public volatile boolean drop_compact_storage_enabled = false;
|
public volatile boolean drop_compact_storage_enabled = false;
|
||||||
|
|
||||||
|
public boolean cursor_compaction_enabled = CURSOR_COMPACTION_ENABLED.getBoolean();
|
||||||
|
|
||||||
public volatile boolean use_statements_enabled = true;
|
public volatile boolean use_statements_enabled = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -4725,6 +4725,17 @@ public class DatabaseDescriptor
|
||||||
conf.transient_replication_enabled = enabled;
|
conf.transient_replication_enabled = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean cursorCompactionEnabled()
|
||||||
|
{
|
||||||
|
return conf.cursor_compaction_enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public static void setCursorCompactionEnabled(boolean cursor_compaction_enabled)
|
||||||
|
{
|
||||||
|
conf.cursor_compaction_enabled = cursor_compaction_enabled;
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean enableDropCompactStorage()
|
public static boolean enableDropCompactStorage()
|
||||||
{
|
{
|
||||||
return conf.drop_compact_storage_enabled;
|
return conf.drop_compact_storage_enabled;
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ import org.apache.cassandra.utils.bytecomparable.ByteComparable;
|
||||||
|
|
||||||
public class BufferDecoratedKey extends DecoratedKey
|
public class BufferDecoratedKey extends DecoratedKey
|
||||||
{
|
{
|
||||||
private final ByteBuffer key;
|
protected ByteBuffer key;
|
||||||
|
|
||||||
public BufferDecoratedKey(Token token, ByteBuffer key)
|
public BufferDecoratedKey(Token token, ByteBuffer key)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -133,7 +133,7 @@ public interface Clustering<V> extends ClusteringPrefix<V>, IMeasurableMemory
|
||||||
/**
|
/**
|
||||||
* Serializer for Clustering object.
|
* Serializer for Clustering object.
|
||||||
* <p>
|
* <p>
|
||||||
* Because every clustering in a given table must have the same size (ant that size cannot actually change once the table
|
* Because every clustering in a given table must have the same size (and that size cannot actually change once the table
|
||||||
* has been defined), we don't record that size.
|
* has been defined), we don't record that size.
|
||||||
*/
|
*/
|
||||||
public static class Serializer
|
public static class Serializer
|
||||||
|
|
|
||||||
|
|
@ -26,14 +26,17 @@ import java.util.Objects;
|
||||||
import com.google.common.base.Joiner;
|
import com.google.common.base.Joiner;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
|
|
||||||
|
import org.apache.cassandra.io.sstable.ClusteringDescriptor;
|
||||||
|
import org.apache.cassandra.db.marshal.AbstractType;
|
||||||
|
import org.apache.cassandra.db.marshal.ByteBufferAccessor;
|
||||||
import org.apache.cassandra.db.marshal.ValueAccessor;
|
import org.apache.cassandra.db.marshal.ValueAccessor;
|
||||||
import org.apache.cassandra.db.rows.Row;
|
import org.apache.cassandra.db.rows.Row;
|
||||||
import org.apache.cassandra.db.marshal.AbstractType;
|
|
||||||
import org.apache.cassandra.serializers.MarshalException;
|
|
||||||
|
|
||||||
import org.apache.cassandra.io.sstable.IndexInfo;
|
import org.apache.cassandra.io.sstable.IndexInfo;
|
||||||
|
import org.apache.cassandra.serializers.MarshalException;
|
||||||
|
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||||
import org.apache.cassandra.utils.bytecomparable.ByteComparable;
|
import org.apache.cassandra.utils.bytecomparable.ByteComparable;
|
||||||
import org.apache.cassandra.utils.bytecomparable.ByteSource;
|
import org.apache.cassandra.utils.bytecomparable.ByteSource;
|
||||||
|
import org.apache.cassandra.utils.vint.VIntCoding;
|
||||||
|
|
||||||
import static org.apache.cassandra.utils.bytecomparable.ByteSource.EXCLUDED;
|
import static org.apache.cassandra.utils.bytecomparable.ByteSource.EXCLUDED;
|
||||||
import static org.apache.cassandra.utils.bytecomparable.ByteSource.NEXT_COMPONENT;
|
import static org.apache.cassandra.utils.bytecomparable.ByteSource.NEXT_COMPONENT;
|
||||||
|
|
@ -156,6 +159,107 @@ public class ClusteringComparator implements Comparator<Clusterable>
|
||||||
return s1 < s2 ? c1.kind().comparedToClustering : -c2.kind().comparedToClustering;
|
return s1 < s2 ? c1.kind().comparedToClustering : -c2.kind().comparedToClustering;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static int compare(ClusteringDescriptor c1, ClusteringDescriptor c2)
|
||||||
|
{
|
||||||
|
final int c1Size = c1.clusteringColumnsBound();
|
||||||
|
final int c2Size = c2.clusteringColumnsBound();
|
||||||
|
final int minColumns = Math.min(c1Size, c2Size);
|
||||||
|
|
||||||
|
final int cmp = compare(c1.clusteringTypes(), c1.clusteringBuffer(), c2.clusteringBuffer(), minColumns);
|
||||||
|
if (cmp != 0)
|
||||||
|
return cmp;
|
||||||
|
|
||||||
|
final ClusteringPrefix.Kind c1Kind = c1.clusteringKind();
|
||||||
|
final ClusteringPrefix.Kind c2Kind = c2.clusteringKind();
|
||||||
|
if (c1Size == c2Size)
|
||||||
|
{
|
||||||
|
return ClusteringPrefix.Kind.compare(c1Kind, c2Kind);
|
||||||
|
}
|
||||||
|
|
||||||
|
return c1Size < c2Size ? c1Kind.comparedToClustering : -c2Kind.comparedToClustering;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int compare(AbstractType<?>[] types, ByteBuffer c1, ByteBuffer c2) {
|
||||||
|
return compare(types, c1, c2, types.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int compare(AbstractType<?>[] types, ByteBuffer c1, ByteBuffer c2, int size)
|
||||||
|
{
|
||||||
|
long clusteringBlock1 = 0;
|
||||||
|
long clusteringBlock2 = 0;
|
||||||
|
final int position1 = c1.position();
|
||||||
|
final int position2 = c2.position();
|
||||||
|
final int limit1 = c1.limit();
|
||||||
|
final int limit2 = c2.limit();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
for (int clusteringIndex = 0; clusteringIndex < size; clusteringIndex++)
|
||||||
|
{
|
||||||
|
if (clusteringIndex % 32 == 0)
|
||||||
|
{
|
||||||
|
clusteringBlock1 = VIntCoding.readUnsignedVInt(c1);
|
||||||
|
clusteringBlock2 = VIntCoding.readUnsignedVInt(c2);
|
||||||
|
}
|
||||||
|
|
||||||
|
AbstractType<?> type = types[clusteringIndex];
|
||||||
|
|
||||||
|
byte v1Flags = (byte) (clusteringBlock1 & 0b11);
|
||||||
|
byte v2Flags = (byte) (clusteringBlock2 & 0b11);
|
||||||
|
|
||||||
|
// both values are present
|
||||||
|
if ((v1Flags|v2Flags) == 0)
|
||||||
|
{
|
||||||
|
boolean isByteOrderComparable = type.isByteOrderComparable;
|
||||||
|
int vlen1,vlen2;
|
||||||
|
if (type.isValueLengthFixed())
|
||||||
|
{
|
||||||
|
vlen1 = vlen2 = type.valueLengthIfFixed();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
vlen1 = VIntCoding.readUnsignedVInt32(c1);
|
||||||
|
vlen2 = VIntCoding.readUnsignedVInt32(c2);
|
||||||
|
}
|
||||||
|
int v1Limit = c1.position() + vlen1;
|
||||||
|
if (v1Limit > limit1)
|
||||||
|
throw new IllegalArgumentException("Value limit exceeds buffer limit.");
|
||||||
|
c1.limit(v1Limit);
|
||||||
|
int v2Limit = c2.position() + vlen2;
|
||||||
|
if (v2Limit > limit2)
|
||||||
|
throw new IllegalArgumentException("Value limit exceeds buffer limit.");
|
||||||
|
c2.limit(v2Limit);
|
||||||
|
int cmp = isByteOrderComparable ?
|
||||||
|
ByteBufferUtil.compareUnsigned(c1, c2) :
|
||||||
|
type.compareCustom(c1, ByteBufferAccessor.instance, c2, ByteBufferAccessor.instance);
|
||||||
|
if (cmp != 0)
|
||||||
|
return cmp;
|
||||||
|
c1.position(v1Limit);
|
||||||
|
c2.position(v2Limit);
|
||||||
|
c1.limit(limit1);
|
||||||
|
c2.limit(limit2);
|
||||||
|
}
|
||||||
|
// present > not present
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// null (0b10) is smaller than empty (0b01) which is smaller than valued (0b00);
|
||||||
|
// compare swapped arguments to reverse the order
|
||||||
|
int cmp = Long.compare(v2Flags, v1Flags);
|
||||||
|
if (cmp != 0)
|
||||||
|
return cmp;
|
||||||
|
// null/empty == null/empty, continue...
|
||||||
|
}
|
||||||
|
clusteringBlock1 = clusteringBlock1 >>> 2;
|
||||||
|
clusteringBlock2 = clusteringBlock2 >>> 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
c1.position(position1).limit(limit1);
|
||||||
|
c2.position(position2).limit(limit2);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
public <V1, V2> int compare(Clustering<V1> c1, Clustering<V2> c2)
|
public <V1, V2> int compare(Clustering<V1> c1, Clustering<V2> c2)
|
||||||
{
|
{
|
||||||
return compare(c1, c2, size());
|
return compare(c1, c2, size());
|
||||||
|
|
|
||||||
|
|
@ -519,7 +519,7 @@ public interface ClusteringPrefix<V> extends IMeasurableMemory, Clusterable<V>
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[][] deserializeValuesWithoutSize(DataInputPlus in, int size, int version, List<AbstractType<?>> types) throws IOException
|
public byte[][] deserializeValuesWithoutSize(DataInputPlus in, int size, int version, List<AbstractType<?>> types) throws IOException
|
||||||
{
|
{
|
||||||
// Callers of this method should handle the case where size = 0 (in all case we want to return a special value anyway).
|
// Callers of this method should handle the case where size = 0 (in all case we want to return a special value anyway).
|
||||||
assert size > 0;
|
assert size > 0;
|
||||||
|
|
|
||||||
|
|
@ -2330,6 +2330,11 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner
|
||||||
return partitionKeySetIgnoreGcGrace.contains(dk);
|
return partitionKeySetIgnoreGcGrace.contains(dk);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean shouldIgnoreGcGraceForAnyKey()
|
||||||
|
{
|
||||||
|
return !partitionKeySetIgnoreGcGrace.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
public static Iterable<ColumnFamilyStore> all()
|
public static Iterable<ColumnFamilyStore> all()
|
||||||
{
|
{
|
||||||
List<Iterable<ColumnFamilyStore>> stores = new ArrayList<>(Schema.instance.getKeyspaces().size());
|
List<Iterable<ColumnFamilyStore>> stores = new ArrayList<>(Schema.instance.getKeyspaces().size());
|
||||||
|
|
|
||||||
|
|
@ -529,6 +529,7 @@ public class Columns extends AbstractCollection<ColumnMetadata> implements Colle
|
||||||
int supersetCount = superset.size();
|
int supersetCount = superset.size();
|
||||||
if (columnCount == supersetCount)
|
if (columnCount == supersetCount)
|
||||||
{
|
{
|
||||||
|
/** This is prevented by caller for row serialization: {@link org.apache.cassandra.db.rows.UnfilteredSerializer#serializeRowBody}*/
|
||||||
out.writeUnsignedVInt32(0);
|
out.writeUnsignedVInt32(0);
|
||||||
}
|
}
|
||||||
else if (supersetCount < 64)
|
else if (supersetCount < 64)
|
||||||
|
|
|
||||||
|
|
@ -114,7 +114,7 @@ public abstract class DecoratedKey implements PartitionPosition, FilterKey
|
||||||
// The OSS50 version avoids this by adding a terminator.
|
// The OSS50 version avoids this by adding a terminator.
|
||||||
return ByteSource.withTerminatorMaybeLegacy(version,
|
return ByteSource.withTerminatorMaybeLegacy(version,
|
||||||
ByteSource.END_OF_STREAM,
|
ByteSource.END_OF_STREAM,
|
||||||
token.asComparableBytes(version),
|
getToken().asComparableBytes(version),
|
||||||
keyComparableBytes(version));
|
keyComparableBytes(version));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -127,7 +127,7 @@ public abstract class DecoratedKey implements PartitionPosition, FilterKey
|
||||||
|
|
||||||
return ByteSource.withTerminator(
|
return ByteSource.withTerminator(
|
||||||
before ? ByteSource.LT_NEXT_COMPONENT : ByteSource.GT_NEXT_COMPONENT,
|
before ? ByteSource.LT_NEXT_COMPONENT : ByteSource.GT_NEXT_COMPONENT,
|
||||||
token.asComparableBytes(version),
|
getToken().asComparableBytes(version),
|
||||||
keyComparableBytes(version));
|
keyComparableBytes(version));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,16 +19,16 @@ package org.apache.cassandra.db;
|
||||||
|
|
||||||
public interface DeletionPurger
|
public interface DeletionPurger
|
||||||
{
|
{
|
||||||
public static final DeletionPurger PURGE_ALL = (ts, ldt) -> true;
|
DeletionPurger PURGE_ALL = (ts, ldt) -> true;
|
||||||
|
|
||||||
public boolean shouldPurge(long timestamp, long localDeletionTime);
|
boolean shouldPurge(long timestamp, long localDeletionTime);
|
||||||
|
|
||||||
public default boolean shouldPurge(DeletionTime dt)
|
default boolean shouldPurge(DeletionTime dt)
|
||||||
{
|
{
|
||||||
return !dt.isLive() && shouldPurge(dt.markedForDeleteAt(), dt.localDeletionTime());
|
return !dt.isLive() && shouldPurge(dt.markedForDeleteAt(), dt.localDeletionTime());
|
||||||
}
|
}
|
||||||
|
|
||||||
public default boolean shouldPurge(LivenessInfo liveness, long nowInSec)
|
default boolean shouldPurge(LivenessInfo liveness, long nowInSec)
|
||||||
{
|
{
|
||||||
return !liveness.isLive(nowInSec) && shouldPurge(liveness.timestamp(), liveness.localExpirationTime());
|
return !liveness.isLive(nowInSec) && shouldPurge(liveness.timestamp(), liveness.localExpirationTime());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,27 +36,29 @@ import static java.lang.Math.min;
|
||||||
/**
|
/**
|
||||||
* Information on deletion of a storage engine object.
|
* Information on deletion of a storage engine object.
|
||||||
*/
|
*/
|
||||||
public class DeletionTime implements Comparable<DeletionTime>, IMeasurableMemory
|
public abstract class DeletionTime implements Comparable<DeletionTime>, IMeasurableMemory
|
||||||
{
|
{
|
||||||
public static final long EMPTY_SIZE = ObjectSizes.measure(new DeletionTime(0, 0));
|
private static final int LOCAL_DELETION_TIME_LIVE = Cell.deletionTimeLongToUnsignedInteger(Long.MAX_VALUE);
|
||||||
|
private static final long MARKED_FOR_DELETE_AT_LIVE = Long.MIN_VALUE;
|
||||||
|
public static final long EMPTY_SIZE = ObjectSizes.measure(new ImmutableDeletionTime(0, 0));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A special DeletionTime that signifies that there is no top-level (row) tombstone.
|
* A special DeletionTime that signifies that there is no top-level (row) tombstone.
|
||||||
*/
|
*/
|
||||||
public static final DeletionTime LIVE = new DeletionTime(Long.MIN_VALUE, Long.MAX_VALUE);
|
public static final DeletionTime LIVE = new ImmutableDeletionTime(MARKED_FOR_DELETE_AT_LIVE, LOCAL_DELETION_TIME_LIVE);
|
||||||
|
|
||||||
private static final Serializer serializer = new Serializer();
|
private static final Serializer serializer = new Serializer();
|
||||||
private static final Serializer legacySerializer = new LegacySerializer();
|
private static final Serializer legacySerializer = new LegacySerializer();
|
||||||
|
|
||||||
private final long markedForDeleteAt;
|
protected long markedForDeleteAt;
|
||||||
final int localDeletionTimeUnsignedInteger;
|
protected int localDeletionTimeUnsignedInteger;
|
||||||
|
|
||||||
public static DeletionTime build(long markedForDeleteAt, long localDeletionTime)
|
public static DeletionTime build(long markedForDeleteAt, long localDeletionTime)
|
||||||
{
|
{
|
||||||
// Negative ldts can only be a result of a corruption or when scrubbing legacy sstables with overflown int ldts
|
// Negative ldts can only be a result of a corruption or when scrubbing legacy sstables with overflown int ldts
|
||||||
return localDeletionTime < 0 || localDeletionTime > Cell.MAX_DELETION_TIME
|
return localDeletionTime < 0 || localDeletionTime > Cell.MAX_DELETION_TIME
|
||||||
? new InvalidDeletionTime(markedForDeleteAt)
|
? new InvalidDeletionTime(markedForDeleteAt)
|
||||||
: new DeletionTime(markedForDeleteAt, localDeletionTime);
|
: new ImmutableDeletionTime(markedForDeleteAt, localDeletionTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do not use. This is a perf optimization where some data structures known to hold valid uints are allowed to use it.
|
// Do not use. This is a perf optimization where some data structures known to hold valid uints are allowed to use it.
|
||||||
|
|
@ -65,7 +67,7 @@ public class DeletionTime implements Comparable<DeletionTime>, IMeasurableMemory
|
||||||
{
|
{
|
||||||
return CassandraUInt.compare(Cell.MAX_DELETION_TIME_UNSIGNED_INTEGER, localDeletionTimeUnsignedInteger) < 0
|
return CassandraUInt.compare(Cell.MAX_DELETION_TIME_UNSIGNED_INTEGER, localDeletionTimeUnsignedInteger) < 0
|
||||||
? new InvalidDeletionTime(markedForDeleteAt)
|
? new InvalidDeletionTime(markedForDeleteAt)
|
||||||
: new DeletionTime(markedForDeleteAt, localDeletionTimeUnsignedInteger);
|
: new ImmutableDeletionTime(markedForDeleteAt, localDeletionTimeUnsignedInteger);
|
||||||
}
|
}
|
||||||
|
|
||||||
private DeletionTime(long markedForDeleteAt, long localDeletionTime)
|
private DeletionTime(long markedForDeleteAt, long localDeletionTime)
|
||||||
|
|
@ -98,6 +100,11 @@ public class DeletionTime implements Comparable<DeletionTime>, IMeasurableMemory
|
||||||
return Cell.deletionTimeUnsignedIntegerToLong(localDeletionTimeUnsignedInteger);
|
return Cell.deletionTimeUnsignedIntegerToLong(localDeletionTimeUnsignedInteger);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int localDeletionTimeUnsignedInteger()
|
||||||
|
{
|
||||||
|
return localDeletionTimeUnsignedInteger;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether this DeletionTime is live, that is deletes no columns.
|
* Returns whether this DeletionTime is live, that is deletes no columns.
|
||||||
*/
|
*/
|
||||||
|
|
@ -143,7 +150,7 @@ public class DeletionTime implements Comparable<DeletionTime>, IMeasurableMemory
|
||||||
@Override
|
@Override
|
||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
return String.format("deletedAt=%d, localDeletion=%d", markedForDeleteAt(), localDeletionTime());
|
return isLive() ? "LIVE" : String.format("deletedAt=%d, localDeletion=%d", markedForDeleteAt(), localDeletionTime());
|
||||||
}
|
}
|
||||||
|
|
||||||
public int compareTo(DeletionTime dt)
|
public int compareTo(DeletionTime dt)
|
||||||
|
|
@ -155,6 +162,10 @@ public class DeletionTime implements Comparable<DeletionTime>, IMeasurableMemory
|
||||||
else return CassandraUInt.compare(localDeletionTimeUnsignedInteger, dt.localDeletionTimeUnsignedInteger);
|
else return CassandraUInt.compare(localDeletionTimeUnsignedInteger, dt.localDeletionTimeUnsignedInteger);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* supersedes: supplants, replaces, in this case: "is more recent"
|
||||||
|
* @return true if dt is deleted BEFORE this (markedForDeleteAt > dt.markedForDeleteAt || (markedForDeleteAt == dt.markedForDeleteAt && localDeletionTime > dt.localDeletionTime))
|
||||||
|
*/
|
||||||
public boolean supersedes(DeletionTime dt)
|
public boolean supersedes(DeletionTime dt)
|
||||||
{
|
{
|
||||||
return markedForDeleteAt() > dt.markedForDeleteAt() || (markedForDeleteAt() == dt.markedForDeleteAt() && localDeletionTime() > dt.localDeletionTime());
|
return markedForDeleteAt() > dt.markedForDeleteAt() || (markedForDeleteAt() == dt.markedForDeleteAt() && localDeletionTime() > dt.localDeletionTime());
|
||||||
|
|
@ -209,7 +220,7 @@ public class DeletionTime implements Comparable<DeletionTime>, IMeasurableMemory
|
||||||
|
|
||||||
public void serialize(DeletionTime delTime, DataOutputPlus out) throws IOException
|
public void serialize(DeletionTime delTime, DataOutputPlus out) throws IOException
|
||||||
{
|
{
|
||||||
if (delTime == LIVE)
|
if (delTime == LIVE || delTime.isLive())
|
||||||
out.writeByte(IS_LIVE_DELETION);
|
out.writeByte(IS_LIVE_DELETION);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -238,7 +249,30 @@ public class DeletionTime implements Comparable<DeletionTime>, IMeasurableMemory
|
||||||
long mfda = readBytesToMFDA(flags, bytes1, bytes2, bytes4);
|
long mfda = readBytesToMFDA(flags, bytes1, bytes2, bytes4);
|
||||||
int localDeletionTimeUnsignedInteger = in.readInt();
|
int localDeletionTimeUnsignedInteger = in.readInt();
|
||||||
|
|
||||||
return new DeletionTime(mfda, localDeletionTimeUnsignedInteger);
|
return new ImmutableDeletionTime(mfda, localDeletionTimeUnsignedInteger);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deserialize(DataInputPlus in, ReusableDeletionTime reuse) throws IOException
|
||||||
|
{
|
||||||
|
int flags = in.readByte();
|
||||||
|
if ((flags & IS_LIVE_DELETION) != 0)
|
||||||
|
{
|
||||||
|
if ((flags & 0xFF) != IS_LIVE_DELETION)
|
||||||
|
throw new IOException("Corrupted sstable. Invalid flags found deserializing DeletionTime: " + Integer.toBinaryString(flags & 0xFF));
|
||||||
|
reuse.resetLive();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Read the remaining 7 bytes
|
||||||
|
int bytes1 = in.readByte();
|
||||||
|
int bytes2 = in.readShort();
|
||||||
|
int bytes4 = in.readInt();
|
||||||
|
|
||||||
|
long mfda = readBytesToMFDA(flags, bytes1, bytes2, bytes4);
|
||||||
|
int localDeletionTimeUnsignedInteger = in.readInt();
|
||||||
|
|
||||||
|
reuse.reset(mfda, localDeletionTimeUnsignedInteger);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -256,7 +290,7 @@ public class DeletionTime implements Comparable<DeletionTime>, IMeasurableMemory
|
||||||
long mfda = buf.getLong(offset);
|
long mfda = buf.getLong(offset);
|
||||||
int localDeletionTimeUnsignedInteger = buf.getInt(offset + TypeSizes.LONG_SIZE);
|
int localDeletionTimeUnsignedInteger = buf.getInt(offset + TypeSizes.LONG_SIZE);
|
||||||
|
|
||||||
return new DeletionTime(mfda, localDeletionTimeUnsignedInteger);
|
return new ImmutableDeletionTime(mfda, localDeletionTimeUnsignedInteger);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -315,13 +349,26 @@ public class DeletionTime implements Comparable<DeletionTime>, IMeasurableMemory
|
||||||
: DeletionTime.build(mfda, ldt);
|
: DeletionTime.build(mfda, ldt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void deserialize(DataInputPlus in, ReusableDeletionTime reuse) throws IOException
|
||||||
|
{
|
||||||
|
int ldt = in.readInt();
|
||||||
|
long mfda = in.readLong();
|
||||||
|
if (mfda == Long.MIN_VALUE && ldt == Integer.MAX_VALUE) {
|
||||||
|
reuse.resetLive();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
reuse.reset(mfda, ldt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public DeletionTime deserialize(ByteBuffer buf, int offset)
|
public DeletionTime deserialize(ByteBuffer buf, int offset)
|
||||||
{
|
{
|
||||||
int ldt = buf.getInt(offset);
|
int ldt = buf.getInt(offset);
|
||||||
long mfda = buf.getLong(offset + 4);
|
long mfda = buf.getLong(offset + 4);
|
||||||
return mfda == Long.MIN_VALUE && ldt == Integer.MAX_VALUE
|
return mfda == Long.MIN_VALUE && ldt == Integer.MAX_VALUE
|
||||||
? LIVE
|
? LIVE
|
||||||
: new DeletionTime(mfda, ldt);
|
: new ImmutableDeletionTime(mfda, ldt);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void skip(DataInputPlus in) throws IOException
|
public void skip(DataInputPlus in) throws IOException
|
||||||
|
|
@ -336,8 +383,22 @@ public class DeletionTime implements Comparable<DeletionTime>, IMeasurableMemory
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static class ImmutableDeletionTime extends DeletionTime
|
||||||
|
{
|
||||||
|
private ImmutableDeletionTime(long markedForDeleteAt, long localDeletionTime)
|
||||||
|
{
|
||||||
|
super(markedForDeleteAt, localDeletionTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ImmutableDeletionTime(long markedForDeleteAt, int localDeletionTimeUnsignedInteger)
|
||||||
|
{
|
||||||
|
super(markedForDeleteAt, localDeletionTimeUnsignedInteger);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// When scrubbing legacy sstables (overflown) or upon sstable corruption we could have negative ldts
|
// When scrubbing legacy sstables (overflown) or upon sstable corruption we could have negative ldts
|
||||||
public static class InvalidDeletionTime extends DeletionTime
|
public static class InvalidDeletionTime extends ImmutableDeletionTime
|
||||||
{
|
{
|
||||||
private InvalidDeletionTime(long markedForDeleteAt)
|
private InvalidDeletionTime(long markedForDeleteAt)
|
||||||
{
|
{
|
||||||
|
|
@ -358,4 +419,61 @@ public class DeletionTime implements Comparable<DeletionTime>, IMeasurableMemory
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class ReusableDeletionTime extends DeletionTime
|
||||||
|
{
|
||||||
|
private ReusableDeletionTime(long markedForDeleteAt, int localDeletionTimeUnsignedInteger)
|
||||||
|
{
|
||||||
|
super(markedForDeleteAt, localDeletionTimeUnsignedInteger);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resetLive()
|
||||||
|
{
|
||||||
|
markedForDeleteAt = MARKED_FOR_DELETE_AT_LIVE;
|
||||||
|
localDeletionTimeUnsignedInteger = LOCAL_DELETION_TIME_LIVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset(long markedForDeleteAt, int localDeletionTimeUnsignedInteger)
|
||||||
|
{
|
||||||
|
this.markedForDeleteAt = markedForDeleteAt;
|
||||||
|
this.localDeletionTimeUnsignedInteger = localDeletionTimeUnsignedInteger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset(long markedForDeleteAt, long localDeletionTime)
|
||||||
|
{
|
||||||
|
this.markedForDeleteAt = markedForDeleteAt;
|
||||||
|
if (localDeletionTime < 0 || localDeletionTime > Cell.MAX_DELETION_TIME) // invalid
|
||||||
|
this.localDeletionTimeUnsignedInteger = Cell.MAX_DELETION_TIME_UNSIGNED_INTEGER + 1;
|
||||||
|
else
|
||||||
|
this.localDeletionTimeUnsignedInteger = Cell.deletionTimeLongToUnsignedInteger(localDeletionTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset(DeletionTime deletionTime)
|
||||||
|
{
|
||||||
|
if (deletionTime == null)
|
||||||
|
resetLive();
|
||||||
|
else
|
||||||
|
reset(deletionTime.markedForDeleteAt, deletionTime.localDeletionTimeUnsignedInteger);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean validate()
|
||||||
|
{
|
||||||
|
return localDeletionTimeUnsignedInteger == LOCAL_DELETION_TIME_LIVE || CassandraUInt.compare(Cell.MAX_DELETION_TIME_UNSIGNED_INTEGER, localDeletionTimeUnsignedInteger) >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ReusableDeletionTime copy(DeletionTime original)
|
||||||
|
{
|
||||||
|
if (original == null)
|
||||||
|
return live();
|
||||||
|
// Negative ldts can only be a result of a corruption or when scrubbing legacy sstables with overflown int ldts
|
||||||
|
return new ReusableDeletionTime(original.markedForDeleteAt, original.localDeletionTimeUnsignedInteger);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ReusableDeletionTime live()
|
||||||
|
{
|
||||||
|
// Negative ldts can only be a result of a corruption or when scrubbing legacy sstables with overflown int ldts
|
||||||
|
return new ReusableDeletionTime(LIVE.markedForDeleteAt, LIVE.localDeletionTimeUnsignedInteger);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,35 +37,28 @@ import org.apache.cassandra.utils.ObjectSizes;
|
||||||
* unaffected (of course, the rest of said row data might be ttl'ed on its own but this is
|
* unaffected (of course, the rest of said row data might be ttl'ed on its own but this is
|
||||||
* separate).
|
* separate).
|
||||||
*/
|
*/
|
||||||
public class LivenessInfo implements IMeasurableMemory
|
public interface LivenessInfo extends IMeasurableMemory
|
||||||
{
|
{
|
||||||
public static final long NO_TIMESTAMP = Long.MIN_VALUE;
|
long NO_TIMESTAMP = Long.MIN_VALUE;
|
||||||
public static final int NO_TTL = Cell.NO_TTL;
|
int NO_TTL = Cell.NO_TTL;
|
||||||
/**
|
/**
|
||||||
* Used as flag for representing an expired liveness.
|
* Used as flag for representing an expired liveness.
|
||||||
*
|
*
|
||||||
* TTL per request is at most 20 yrs, so this shouldn't conflict
|
* TTL per request is at most 20 yrs, so this shouldn't conflict
|
||||||
* (See {@link org.apache.cassandra.cql3.Attributes#MAX_TTL})
|
* (See {@link org.apache.cassandra.cql3.Attributes#MAX_TTL})
|
||||||
*/
|
*/
|
||||||
public static final int EXPIRED_LIVENESS_TTL = Integer.MAX_VALUE;
|
int EXPIRED_LIVENESS_TTL = Integer.MAX_VALUE;
|
||||||
public static final long NO_EXPIRATION_TIME = Cell.NO_DELETION_TIME;
|
long NO_EXPIRATION_TIME = Cell.NO_DELETION_TIME;
|
||||||
|
|
||||||
public static final LivenessInfo EMPTY = new LivenessInfo(NO_TIMESTAMP);
|
LivenessInfo EMPTY = new ImmutableLivenessInfo(NO_TIMESTAMP);
|
||||||
private static final long UNSHARED_HEAP_SIZE = ObjectSizes.measure(EMPTY);
|
long UNSHARED_HEAP_SIZE = ObjectSizes.measure(EMPTY);
|
||||||
|
|
||||||
protected final long timestamp;
|
static LivenessInfo create(long timestamp, long nowInSec)
|
||||||
|
|
||||||
protected LivenessInfo(long timestamp)
|
|
||||||
{
|
{
|
||||||
this.timestamp = timestamp;
|
return new ImmutableLivenessInfo(timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LivenessInfo create(long timestamp, long nowInSec)
|
static LivenessInfo expiring(long timestamp, int ttl, long nowInSec)
|
||||||
{
|
|
||||||
return new LivenessInfo(timestamp);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static LivenessInfo expiring(long timestamp, int ttl, long nowInSec)
|
|
||||||
{
|
{
|
||||||
assert ttl != EXPIRED_LIVENESS_TTL;
|
assert ttl != EXPIRED_LIVENESS_TTL;
|
||||||
return new ExpiringLivenessInfo(timestamp, ttl, ExpirationDateOverflowHandling.computeLocalExpirationTime(nowInSec, ttl));
|
return new ExpiringLivenessInfo(timestamp, ttl, ExpirationDateOverflowHandling.computeLocalExpirationTime(nowInSec, ttl));
|
||||||
|
|
@ -86,7 +79,7 @@ public class LivenessInfo implements IMeasurableMemory
|
||||||
: expiring(timestamp, ttl, nowInSec, applyOverflowPolicy);
|
: expiring(timestamp, ttl, nowInSec, applyOverflowPolicy);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LivenessInfo create(long timestamp, int ttl, long nowInSec)
|
static LivenessInfo create(long timestamp, int ttl, long nowInSec)
|
||||||
{
|
{
|
||||||
return ttl == NO_TTL
|
return ttl == NO_TTL
|
||||||
? create(timestamp, nowInSec)
|
? create(timestamp, nowInSec)
|
||||||
|
|
@ -95,11 +88,11 @@ public class LivenessInfo implements IMeasurableMemory
|
||||||
|
|
||||||
// Note that this ctor takes the expiration time, not the current time.
|
// Note that this ctor takes the expiration time, not the current time.
|
||||||
// Use when you know that's what you want.
|
// Use when you know that's what you want.
|
||||||
public static LivenessInfo withExpirationTime(long timestamp, int ttl, long localExpirationTime)
|
static LivenessInfo withExpirationTime(long timestamp, int ttl, long localExpirationTime)
|
||||||
{
|
{
|
||||||
if (ttl == EXPIRED_LIVENESS_TTL)
|
if (ttl == EXPIRED_LIVENESS_TTL)
|
||||||
return new ExpiredLivenessInfo(timestamp, ttl, localExpirationTime);
|
return new ExpiredLivenessInfo(timestamp, ttl, localExpirationTime);
|
||||||
return ttl == NO_TTL ? new LivenessInfo(timestamp) : new ExpiringLivenessInfo(timestamp, ttl, localExpirationTime);
|
return ttl == NO_TTL ? new ImmutableLivenessInfo(timestamp) : new ExpiringLivenessInfo(timestamp, ttl, localExpirationTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -107,9 +100,9 @@ public class LivenessInfo implements IMeasurableMemory
|
||||||
*
|
*
|
||||||
* @return whether this liveness info is empty or not.
|
* @return whether this liveness info is empty or not.
|
||||||
*/
|
*/
|
||||||
public boolean isEmpty()
|
default boolean isEmpty()
|
||||||
{
|
{
|
||||||
return timestamp == NO_TIMESTAMP;
|
return timestamp() == NO_TIMESTAMP;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -117,18 +110,11 @@ public class LivenessInfo implements IMeasurableMemory
|
||||||
*
|
*
|
||||||
* @return the liveness info timestamp (or {@link #NO_TIMESTAMP} if the info is empty).
|
* @return the liveness info timestamp (or {@link #NO_TIMESTAMP} if the info is empty).
|
||||||
*/
|
*/
|
||||||
public long timestamp()
|
long timestamp();
|
||||||
{
|
|
||||||
return timestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the info has a ttl.
|
* Whether the info has a ttl.
|
||||||
*/
|
*/
|
||||||
public boolean isExpiring()
|
boolean isExpiring();
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ttl (if any) on the row primary key columns or {@link #NO_TTL} if it is not
|
* The ttl (if any) on the row primary key columns or {@link #NO_TTL} if it is not
|
||||||
|
|
@ -137,19 +123,13 @@ public class LivenessInfo implements IMeasurableMemory
|
||||||
* Please note that this value is the TTL that was set originally and is thus not
|
* Please note that this value is the TTL that was set originally and is thus not
|
||||||
* changing.
|
* changing.
|
||||||
*/
|
*/
|
||||||
public int ttl()
|
int ttl();
|
||||||
{
|
|
||||||
return NO_TTL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The expiration time (in seconds) if the info is expiring ({@link #NO_EXPIRATION_TIME} otherwise).
|
* The expiration time (in seconds) if the info is expiring ({@link #NO_EXPIRATION_TIME} otherwise).
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public long localExpirationTime()
|
long localExpirationTime();
|
||||||
{
|
|
||||||
return NO_EXPIRATION_TIME;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether that info is still live.
|
* Whether that info is still live.
|
||||||
|
|
@ -160,17 +140,15 @@ public class LivenessInfo implements IMeasurableMemory
|
||||||
* @param nowInSec the current time in seconds.
|
* @param nowInSec the current time in seconds.
|
||||||
* @return whether this liveness info is live or not.
|
* @return whether this liveness info is live or not.
|
||||||
*/
|
*/
|
||||||
public boolean isLive(long nowInSec)
|
boolean isLive(long nowInSec);
|
||||||
{
|
|
||||||
return !isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds this liveness information to the provided digest.
|
* Adds this liveness information to the provided digest.
|
||||||
*
|
*
|
||||||
* @param digest the digest to add this liveness information to.
|
* @param digest the digest to add this liveness information to.
|
||||||
*/
|
*/
|
||||||
public void digest(Digest digest)
|
default void digest(Digest digest)
|
||||||
{
|
{
|
||||||
digest.updateWithLong(timestamp());
|
digest.updateWithLong(timestamp());
|
||||||
}
|
}
|
||||||
|
|
@ -180,7 +158,7 @@ public class LivenessInfo implements IMeasurableMemory
|
||||||
*
|
*
|
||||||
* @throws MarshalException if some of the data is corrupted.
|
* @throws MarshalException if some of the data is corrupted.
|
||||||
*/
|
*/
|
||||||
public void validate()
|
default void validate()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -189,18 +167,18 @@ public class LivenessInfo implements IMeasurableMemory
|
||||||
*
|
*
|
||||||
* @return the size of the data this liveness information contains.
|
* @return the size of the data this liveness information contains.
|
||||||
*/
|
*/
|
||||||
public int dataSize()
|
default int dataSize()
|
||||||
{
|
{
|
||||||
return TypeSizes.sizeof(timestamp());
|
return TypeSizes.sizeof(timestamp());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether this liveness information supersedes another one (that is
|
* Whether this liveness information supersedes another one (that is
|
||||||
* whether is has a greater timestamp than the other or not).
|
* whether it has a greater timestamp than the other or not).
|
||||||
*
|
*
|
||||||
* If timestamps are the same and none of them are expired livenessInfo,
|
* If timestamps are the same and none of them are expired livenessInfo,
|
||||||
* livenessInfo with greater TTL supersedes another. It also means, if timestamps are the same,
|
* livenessInfo with greater TTL supersedes another. It also means, if timestamps are the same,
|
||||||
* ttl superseders no-ttl. This is the same rule as {@link Conflicts#resolveRegular}
|
* ttl superseders no-ttl. This is the same rule as {@link org.apache.cassandra.db.rows.Cells#resolveRegular}
|
||||||
*
|
*
|
||||||
* If timestamps are the same and one of them is expired livenessInfo. Expired livenessInfo
|
* If timestamps are the same and one of them is expired livenessInfo. Expired livenessInfo
|
||||||
* supersedes, ie. tombstone supersedes.
|
* supersedes, ie. tombstone supersedes.
|
||||||
|
|
@ -216,10 +194,12 @@ public class LivenessInfo implements IMeasurableMemory
|
||||||
*
|
*
|
||||||
* @return whether this {@code LivenessInfo} supersedes {@code other}.
|
* @return whether this {@code LivenessInfo} supersedes {@code other}.
|
||||||
*/
|
*/
|
||||||
public boolean supersedes(LivenessInfo other)
|
default boolean supersedes(LivenessInfo other)
|
||||||
{
|
{
|
||||||
if (timestamp != other.timestamp)
|
long tTimestamp = timestamp();
|
||||||
return timestamp > other.timestamp;
|
long oTimestamp = other.timestamp();
|
||||||
|
if (tTimestamp != oTimestamp)
|
||||||
|
return tTimestamp > oTimestamp;
|
||||||
if (isExpired() ^ other.isExpired())
|
if (isExpired() ^ other.isExpired())
|
||||||
return isExpired();
|
return isExpired();
|
||||||
if (isExpiring() == other.isExpiring())
|
if (isExpiring() == other.isExpiring())
|
||||||
|
|
@ -231,7 +211,7 @@ public class LivenessInfo implements IMeasurableMemory
|
||||||
return isExpiring();
|
return isExpiring();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean isExpired()
|
default boolean isExpired()
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -244,47 +224,24 @@ public class LivenessInfo implements IMeasurableMemory
|
||||||
* as timestamp. If it has no timestamp however, this liveness info is returned
|
* as timestamp. If it has no timestamp however, this liveness info is returned
|
||||||
* unchanged.
|
* unchanged.
|
||||||
*/
|
*/
|
||||||
public LivenessInfo withUpdatedTimestamp(long newTimestamp)
|
default LivenessInfo withUpdatedTimestamp(long newTimestamp)
|
||||||
{
|
{
|
||||||
return new LivenessInfo(newTimestamp);
|
return new ImmutableLivenessInfo(newTimestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
public LivenessInfo withUpdatedTimestampAndLocalDeletionTime(long newTimestamp, long newLocalDeletionTime)
|
default LivenessInfo withUpdatedTimestampAndLocalDeletionTime(long newTimestamp, long newLocalDeletionTime)
|
||||||
{
|
{
|
||||||
return LivenessInfo.create(newTimestamp, ttl(), newLocalDeletionTime, true);
|
return LivenessInfo.create(newTimestamp, ttl(), newLocalDeletionTime, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// C14227 To prevent row resurrection and be backwards compatible sometimes we need to force an overflowed ldt
|
// C14227 To prevent row resurrection and be backwards compatible sometimes we need to force an overflowed ldt
|
||||||
public LivenessInfo withUpdatedTimestampAndLocalDeletionTime(long newTimestamp, long newLocalDeletionTime, boolean applyOverflowPolicy)
|
default LivenessInfo withUpdatedTimestampAndLocalDeletionTime(long newTimestamp, long newLocalDeletionTime, boolean applyOverflowPolicy)
|
||||||
{
|
{
|
||||||
return LivenessInfo.create(newTimestamp, ttl(), newLocalDeletionTime, applyOverflowPolicy);
|
return LivenessInfo.create(newTimestamp, ttl(), newLocalDeletionTime, applyOverflowPolicy);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString()
|
default long unsharedHeapSize()
|
||||||
{
|
|
||||||
return String.format("[ts=%d]", timestamp);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object other)
|
|
||||||
{
|
|
||||||
if(!(other instanceof LivenessInfo))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
LivenessInfo that = (LivenessInfo)other;
|
|
||||||
return this.timestamp() == that.timestamp()
|
|
||||||
&& this.ttl() == that.ttl()
|
|
||||||
&& this.localExpirationTime() == that.localExpirationTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode()
|
|
||||||
{
|
|
||||||
return Objects.hash(timestamp(), ttl(), localExpirationTime());
|
|
||||||
}
|
|
||||||
|
|
||||||
public long unsharedHeapSize()
|
|
||||||
{
|
{
|
||||||
return this == EMPTY ? 0 : UNSHARED_HEAP_SIZE;
|
return this == EMPTY ? 0 : UNSHARED_HEAP_SIZE;
|
||||||
}
|
}
|
||||||
|
|
@ -295,7 +252,7 @@ public class LivenessInfo implements IMeasurableMemory
|
||||||
*
|
*
|
||||||
* See {@link org.apache.cassandra.db.view.ViewUpdateGenerator#deleteOldEntryInternal}.
|
* See {@link org.apache.cassandra.db.view.ViewUpdateGenerator#deleteOldEntryInternal}.
|
||||||
*/
|
*/
|
||||||
private static class ExpiredLivenessInfo extends ExpiringLivenessInfo
|
class ExpiredLivenessInfo extends ExpiringLivenessInfo
|
||||||
{
|
{
|
||||||
private ExpiredLivenessInfo(long timestamp, int ttl, long localExpirationTime)
|
private ExpiredLivenessInfo(long timestamp, int ttl, long localExpirationTime)
|
||||||
{
|
{
|
||||||
|
|
@ -324,7 +281,7 @@ public class LivenessInfo implements IMeasurableMemory
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class ExpiringLivenessInfo extends LivenessInfo
|
class ExpiringLivenessInfo extends ImmutableLivenessInfo
|
||||||
{
|
{
|
||||||
private final int ttl;
|
private final int ttl;
|
||||||
private final long localExpirationTime;
|
private final long localExpirationTime;
|
||||||
|
|
@ -401,7 +358,7 @@ public class LivenessInfo implements IMeasurableMemory
|
||||||
@Override
|
@Override
|
||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
return String.format("[ts=%d ttl=%d, let=%d]", timestamp, ttl, localExpirationTime);
|
return String.format("[ts=%d ttl=%d, let=%d]", timestamp(), ttl, localExpirationTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
public long unsharedHeapSize()
|
public long unsharedHeapSize()
|
||||||
|
|
@ -409,4 +366,66 @@ public class LivenessInfo implements IMeasurableMemory
|
||||||
return UNSHARED_HEAP_SIZE;
|
return UNSHARED_HEAP_SIZE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ImmutableLivenessInfo implements LivenessInfo {
|
||||||
|
private final long timestamp;
|
||||||
|
|
||||||
|
private ImmutableLivenessInfo(long timestamp)
|
||||||
|
{
|
||||||
|
this.timestamp = timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final long timestamp()
|
||||||
|
{
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int ttl()
|
||||||
|
{
|
||||||
|
return NO_TTL;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isExpiring()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long localExpirationTime()
|
||||||
|
{
|
||||||
|
return NO_EXPIRATION_TIME;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isLive(long nowInSec)
|
||||||
|
{
|
||||||
|
return !isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return String.format("[ts=%d]", timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object other)
|
||||||
|
{
|
||||||
|
if(!(other instanceof LivenessInfo))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
LivenessInfo that = (LivenessInfo)other;
|
||||||
|
return this.timestamp() == that.timestamp()
|
||||||
|
&& this.ttl() == that.ttl()
|
||||||
|
&& this.localExpirationTime() == that.localExpirationTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode()
|
||||||
|
{
|
||||||
|
return Objects.hash(timestamp(), ttl(), localExpirationTime());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -145,7 +145,7 @@ public class RangeTombstoneList implements Iterable<RangeTombstone>, IMeasurable
|
||||||
add(tombstone.deletedSlice().start(),
|
add(tombstone.deletedSlice().start(),
|
||||||
tombstone.deletedSlice().end(),
|
tombstone.deletedSlice().end(),
|
||||||
tombstone.deletionTime().markedForDeleteAt(),
|
tombstone.deletionTime().markedForDeleteAt(),
|
||||||
tombstone.deletionTime().localDeletionTimeUnsignedInteger);
|
tombstone.deletionTime().localDeletionTimeUnsignedInteger());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
// TODO: maybe flatten into descriptor classes?
|
||||||
|
public class ReusableLivenessInfo implements LivenessInfo
|
||||||
|
{
|
||||||
|
private int ttl = NO_TTL;
|
||||||
|
private long localExpirationTime = NO_EXPIRATION_TIME;
|
||||||
|
private long timestamp = NO_TIMESTAMP;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long timestamp()
|
||||||
|
{
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int ttl()
|
||||||
|
{
|
||||||
|
return ttl;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long localExpirationTime()
|
||||||
|
{
|
||||||
|
return localExpirationTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isExpiring()
|
||||||
|
{
|
||||||
|
return localExpirationTime != NO_EXPIRATION_TIME;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link org.apache.cassandra.db.rows.AbstractCell#isTombstone()}
|
||||||
|
*/
|
||||||
|
public boolean isTombstone()
|
||||||
|
{
|
||||||
|
return localExpirationTime != NO_EXPIRATION_TIME && ttl == NO_TTL;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isLive(long nowInSec)
|
||||||
|
{
|
||||||
|
return localExpirationTime == NO_EXPIRATION_TIME || ttl != NO_TTL && !isExpired(nowInSec);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isExpired(long nowInSec)
|
||||||
|
{
|
||||||
|
return nowInSec >= localExpirationTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ttlToTombstone()
|
||||||
|
{
|
||||||
|
// LET/LDT is now the time the TTL would have expired
|
||||||
|
localExpirationTime = localExpirationTime - ttl;
|
||||||
|
ttl = NO_TTL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset(long timestamp, int ttl, long localExpirationTime)
|
||||||
|
{
|
||||||
|
this.timestamp = timestamp;
|
||||||
|
this.ttl = ttl;
|
||||||
|
this.localExpirationTime = localExpirationTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return "ReusableLivenessInfo{" + ((timestamp == NO_TIMESTAMP && ttl == NO_TTL && localExpirationTime == NO_EXPIRATION_TIME) ? "NONE }" :
|
||||||
|
"timestamp=" + (timestamp == NO_TIMESTAMP ? "NO_TIMESTAMP" : timestamp) +
|
||||||
|
", ttl=" + (ttl == NO_TTL ? "NO_TTL" : ttl) +
|
||||||
|
", localExpirationTime=" + (localExpirationTime == NO_EXPIRATION_TIME ? "NO_EXPIRATION_TIME" : localExpirationTime) +
|
||||||
|
'}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -207,6 +207,14 @@ public class SerializationHeader
|
||||||
return DeletionTime.build(markedAt, localDeletionTime);
|
return DeletionTime.build(markedAt, localDeletionTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void readDeletionTime(DataInputPlus in, DeletionTime.ReusableDeletionTime reuse) throws IOException
|
||||||
|
{
|
||||||
|
long markedAt = readTimestamp(in);
|
||||||
|
long localDeletionTime = readLocalDeletionTime(in);
|
||||||
|
reuse.reset(markedAt, localDeletionTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public long timestampSerializedSize(long timestamp)
|
public long timestampSerializedSize(long timestamp)
|
||||||
{
|
{
|
||||||
return TypeSizes.sizeofUnsignedVInt(timestamp - stats.minTimestamp);
|
return TypeSizes.sizeofUnsignedVInt(timestamp - stats.minTimestamp);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.cassandra.db.compaction;
|
||||||
|
|
||||||
|
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||||
|
import org.apache.cassandra.db.AbstractCompactionController;
|
||||||
|
import org.apache.cassandra.db.ColumnFamilyStore;
|
||||||
|
import org.apache.cassandra.db.Directories;
|
||||||
|
import org.apache.cassandra.db.lifecycle.ILifecycleTransaction;
|
||||||
|
import org.apache.cassandra.io.sstable.format.SSTableReader;
|
||||||
|
import org.apache.cassandra.utils.TimeUUID;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
abstract class AbstractCompactionPipeline extends CompactionInfo.Holder implements AutoCloseable {
|
||||||
|
static AbstractCompactionPipeline create(
|
||||||
|
CompactionTask task,
|
||||||
|
OperationType type,
|
||||||
|
AbstractCompactionStrategy.ScannerList scanners,
|
||||||
|
AbstractCompactionController controller,
|
||||||
|
long nowInSec,
|
||||||
|
TimeUUID compactionId)
|
||||||
|
{
|
||||||
|
if (DatabaseDescriptor.cursorCompactionEnabled()) {
|
||||||
|
if (CursorCompactor.isSupported(scanners, controller))
|
||||||
|
{
|
||||||
|
return new CursorCompactionPipeline(task, type, scanners, controller, nowInSec, compactionId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new IteratorCompactionPipeline(task, type, scanners, controller, nowInSec, compactionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract boolean processNextPartitionKey() throws IOException;
|
||||||
|
|
||||||
|
public abstract long[] getMergedRowCounts();
|
||||||
|
|
||||||
|
public abstract long getTotalSourceCQLRows();
|
||||||
|
|
||||||
|
public abstract long getTotalKeysWritten();
|
||||||
|
|
||||||
|
public abstract long getTotalBytesScanned();
|
||||||
|
|
||||||
|
public abstract AutoCloseable openWriterResource(ColumnFamilyStore cfs,
|
||||||
|
Directories directories,
|
||||||
|
ILifecycleTransaction transaction,
|
||||||
|
Set<SSTableReader> nonExpiredSSTables);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public abstract void close() throws IOException;
|
||||||
|
|
||||||
|
public abstract Collection<SSTableReader> finishWriting();
|
||||||
|
|
||||||
|
public abstract long estimatedKeys();
|
||||||
|
|
||||||
|
public abstract void stop();
|
||||||
|
}
|
||||||
|
|
@ -820,7 +820,7 @@ public class CompactionStrategyManager implements INotificationConsumer
|
||||||
*
|
*
|
||||||
* lives in matches the list index of the holder that's responsible for it
|
* lives in matches the list index of the holder that's responsible for it
|
||||||
*/
|
*/
|
||||||
public List<GroupedSSTableContainer> groupSSTables(Iterable<SSTableReader> sstables)
|
public final List<GroupedSSTableContainer> groupSSTables(Iterable<SSTableReader> sstables)
|
||||||
{
|
{
|
||||||
List<GroupedSSTableContainer> classified = new ArrayList<>(holders.size());
|
List<GroupedSSTableContainer> classified = new ArrayList<>(holders.size());
|
||||||
for (AbstractStrategyHolder holder : holders)
|
for (AbstractStrategyHolder holder : holders)
|
||||||
|
|
@ -970,7 +970,7 @@ public class CompactionStrategyManager implements INotificationConsumer
|
||||||
* @param ranges
|
* @param ranges
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public AbstractCompactionStrategy.ScannerList maybeGetScanners(Collection<SSTableReader> sstables, Collection<Range<Token>> ranges)
|
public final AbstractCompactionStrategy.ScannerList maybeGetScanners(Collection<SSTableReader> sstables, Collection<Range<Token>> ranges)
|
||||||
{
|
{
|
||||||
maybeReloadDiskBoundaries();
|
maybeReloadDiskBoundaries();
|
||||||
List<ISSTableScanner> scanners = new ArrayList<>(sstables.size());
|
List<ISSTableScanner> scanners = new ArrayList<>(sstables.size());
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,9 @@ import static org.apache.cassandra.utils.Clock.Global.nanoTime;
|
||||||
|
|
||||||
public class CompactionTask extends AbstractCompactionTask
|
public class CompactionTask extends AbstractCompactionTask
|
||||||
{
|
{
|
||||||
|
private static final int MEGABYTE = 1024 * 1024;
|
||||||
protected static final Logger logger = LoggerFactory.getLogger(CompactionTask.class);
|
protected static final Logger logger = LoggerFactory.getLogger(CompactionTask.class);
|
||||||
|
|
||||||
protected final long gcBefore;
|
protected final long gcBefore;
|
||||||
protected final boolean keepOriginals;
|
protected final boolean keepOriginals;
|
||||||
protected static long totalBytesCompacted = 0;
|
protected static long totalBytesCompacted = 0;
|
||||||
|
|
@ -151,9 +153,11 @@ public class CompactionTask extends AbstractCompactionTask
|
||||||
* For internal use and testing only. The rest of the system should go through the submit* methods,
|
* For internal use and testing only. The rest of the system should go through the submit* methods,
|
||||||
* which are properly serialized.
|
* which are properly serialized.
|
||||||
* Caller is in charge of marking/unmarking the sstables as compacting.
|
* Caller is in charge of marking/unmarking the sstables as compacting.
|
||||||
|
*
|
||||||
|
* NOTE: this method is a Byteman hook location
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected void runMayThrow() throws Exception
|
protected final void runMayThrow() throws Exception
|
||||||
{
|
{
|
||||||
// The collection of sstables passed may be empty (but not null); even if
|
// The collection of sstables passed may be empty (but not null); even if
|
||||||
// it is not empty, it may compact down to nothing if all rows are deleted.
|
// it is not empty, it may compact down to nothing if all rows are deleted.
|
||||||
|
|
@ -245,7 +249,7 @@ public class CompactionTask extends AbstractCompactionTask
|
||||||
long nowInSec = FBUtilities.nowInSeconds();
|
long nowInSec = FBUtilities.nowInSeconds();
|
||||||
try (Refs<SSTableReader> refs = Refs.ref(actuallyCompact);
|
try (Refs<SSTableReader> refs = Refs.ref(actuallyCompact);
|
||||||
AbstractCompactionStrategy.ScannerList scanners = strategy.getScanners(actuallyCompact, rangeList);
|
AbstractCompactionStrategy.ScannerList scanners = strategy.getScanners(actuallyCompact, rangeList);
|
||||||
CompactionIterator ci = new CompactionIterator(compactionType, scanners.scanners, controller, nowInSec, taskId))
|
AbstractCompactionPipeline ci = AbstractCompactionPipeline.create(this, compactionType, scanners, controller, nowInSec, taskId))
|
||||||
{
|
{
|
||||||
long lastCheckObsoletion = start;
|
long lastCheckObsoletion = start;
|
||||||
inputSizeBytes = scanners.getTotalCompressedSize();
|
inputSizeBytes = scanners.getTotalCompressedSize();
|
||||||
|
|
@ -253,30 +257,29 @@ public class CompactionTask extends AbstractCompactionTask
|
||||||
if (compressionRatio == MetadataCollector.NO_COMPRESSION_RATIO)
|
if (compressionRatio == MetadataCollector.NO_COMPRESSION_RATIO)
|
||||||
compressionRatio = 1.0;
|
compressionRatio = 1.0;
|
||||||
|
|
||||||
long lastBytesScanned = 0;
|
|
||||||
|
|
||||||
activeCompactions.beginCompaction(ci);
|
activeCompactions.beginCompaction(ci);
|
||||||
try (CompactionAwareWriter writer = getCompactionAwareWriter(cfs, getDirectories(), transaction, actuallyCompact))
|
try (AutoCloseable resource = getCompactionAwareWriter(actuallyCompact, ci))
|
||||||
{
|
{
|
||||||
|
long lastBytesScanned = 0;
|
||||||
// Note that we need to re-check this flag after calling beginCompaction above to avoid a window
|
// Note that we need to re-check this flag after calling beginCompaction above to avoid a window
|
||||||
// where the compaction does not exist in activeCompactions but the CSM gets paused.
|
// where the compaction does not exist in activeCompactions but the CSM gets paused.
|
||||||
// We already have the sstables marked compacting here so CompactionManager#waitForCessation will
|
// We already have the sstables marked compacting here so CompactionManager#waitForCessation will
|
||||||
// block until the below exception is thrown and the transaction is cancelled.
|
// block until the below exception is thrown and the transaction is cancelled.
|
||||||
if (!controller.cfs.getCompactionStrategyManager().isActive())
|
if (!controller.cfs.getCompactionStrategyManager().isActive())
|
||||||
throw new CompactionInterruptedException(ci.getCompactionInfo());
|
throw new CompactionInterruptedException(ci.getCompactionInfo());
|
||||||
estimatedKeys = writer.estimatedKeys();
|
estimatedKeys = ci.estimatedKeys();
|
||||||
while (ci.hasNext())
|
while (ci.processNextPartitionKey())
|
||||||
{
|
{
|
||||||
if (writer.append(ci.next()))
|
long bytesScanned = ci.getTotalBytesScanned();
|
||||||
totalKeysWritten++;
|
|
||||||
|
|
||||||
ci.setTargetDirectory(writer.getSStableDirectory().path());
|
|
||||||
long bytesScanned = scanners.getTotalBytesScanned();
|
|
||||||
|
|
||||||
|
// If we ingested less than a MB, keep going
|
||||||
|
if (bytesScanned - lastBytesScanned > MEGABYTE)
|
||||||
|
{
|
||||||
// Rate limit the scanners, and account for compression
|
// Rate limit the scanners, and account for compression
|
||||||
CompactionManager.instance.compactionRateLimiterAcquire(limiter, bytesScanned, lastBytesScanned, compressionRatio);
|
CompactionManager.instance.compactionRateLimiterAcquire(limiter, bytesScanned, lastBytesScanned, compressionRatio);
|
||||||
|
|
||||||
lastBytesScanned = bytesScanned;
|
lastBytesScanned = bytesScanned;
|
||||||
|
}
|
||||||
|
|
||||||
if (nanoTime() - lastCheckObsoletion > TimeUnit.MINUTES.toNanos(1L))
|
if (nanoTime() - lastCheckObsoletion > TimeUnit.MINUTES.toNanos(1L))
|
||||||
{
|
{
|
||||||
|
|
@ -284,19 +287,27 @@ public class CompactionTask extends AbstractCompactionTask
|
||||||
lastCheckObsoletion = nanoTime();
|
lastCheckObsoletion = nanoTime();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (ci.getTotalBytesScanned() != lastBytesScanned)
|
||||||
|
{
|
||||||
|
// Report any leftover bytes
|
||||||
|
CompactionManager.instance.compactionRateLimiterAcquire(limiter, ci.getTotalBytesScanned(), lastBytesScanned, compressionRatio);
|
||||||
|
}
|
||||||
timeSpentWritingKeys = TimeUnit.NANOSECONDS.toMillis(nanoTime() - start);
|
timeSpentWritingKeys = TimeUnit.NANOSECONDS.toMillis(nanoTime() - start);
|
||||||
|
|
||||||
// point of no return
|
// point of no return
|
||||||
newSStables = writer.finish();
|
newSStables = finish(ci);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
activeCompactions.finishCompaction(ci);
|
activeCompactions.finishCompaction(ci);
|
||||||
mergedRowCounts = ci.getMergedRowCounts();
|
mergedRowCounts = ci.getMergedRowCounts();
|
||||||
totalSourceCQLRows = ci.getTotalSourceCQLRows();
|
totalSourceCQLRows = ci.getTotalSourceCQLRows();
|
||||||
|
|
||||||
|
totalKeysWritten = ci.getTotalKeysWritten();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (transaction.isOffline())
|
if (transaction.isOffline())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
@ -343,6 +354,28 @@ public class CompactionTask extends AbstractCompactionTask
|
||||||
// update the metrics
|
// update the metrics
|
||||||
cfs.metric.compactionBytesWritten.inc(endsize);
|
cfs.metric.compactionBytesWritten.inc(endsize);
|
||||||
}
|
}
|
||||||
|
catch (Throwable e)
|
||||||
|
{
|
||||||
|
/** This should not be needed (because {@link org.apache.cassandra.utils.WrappedRunnable}) but some exceptions seem to slip by in tests */
|
||||||
|
logger.debug("Unexpected exception in compaction", e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NOTE: a Byteman hook
|
||||||
|
*/
|
||||||
|
protected Collection<SSTableReader> finish(AbstractCompactionPipeline pipeline)
|
||||||
|
{
|
||||||
|
return pipeline.finishWriting();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NOTE: a Byteman hook
|
||||||
|
*/
|
||||||
|
protected AutoCloseable getCompactionAwareWriter(Set<SSTableReader> actuallyCompact, AbstractCompactionPipeline pipeline)
|
||||||
|
{
|
||||||
|
return pipeline.openWriterResource(cfs, getDirectories(), transaction, actuallyCompact);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CompactionAwareWriter getCompactionAwareWriter(ColumnFamilyStore cfs,
|
public CompactionAwareWriter getCompactionAwareWriter(ColumnFamilyStore cfs,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,112 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.cassandra.db.compaction;
|
||||||
|
|
||||||
|
import org.apache.cassandra.db.AbstractCompactionController;
|
||||||
|
import org.apache.cassandra.db.ColumnFamilyStore;
|
||||||
|
import org.apache.cassandra.db.Directories;
|
||||||
|
import org.apache.cassandra.db.compaction.writers.CompactionAwareWriter;
|
||||||
|
import org.apache.cassandra.db.lifecycle.ILifecycleTransaction;
|
||||||
|
import org.apache.cassandra.io.sstable.format.SSTableReader;
|
||||||
|
import org.apache.cassandra.utils.TimeUUID;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
class CursorCompactionPipeline extends AbstractCompactionPipeline {
|
||||||
|
final CursorCompactor cursorCompactor;
|
||||||
|
final CompactionTask task;
|
||||||
|
long totalKeysWritten;
|
||||||
|
CompactionAwareWriter writer;
|
||||||
|
|
||||||
|
CursorCompactionPipeline(CompactionTask task, OperationType type, AbstractCompactionStrategy.ScannerList scanners, AbstractCompactionController controller, long nowInSec, TimeUUID compactionId) {
|
||||||
|
this.task = task;
|
||||||
|
cursorCompactor = new CursorCompactor(type, scanners.scanners, controller, nowInSec, compactionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AutoCloseable openWriterResource(ColumnFamilyStore cfs,
|
||||||
|
Directories directories,
|
||||||
|
ILifecycleTransaction transaction,
|
||||||
|
Set<SSTableReader> nonExpiredSSTables) {
|
||||||
|
this.writer = task.getCompactionAwareWriter(cfs, directories, transaction, nonExpiredSSTables);
|
||||||
|
return writer;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<SSTableReader> finishWriting() {
|
||||||
|
return writer.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long estimatedKeys() {
|
||||||
|
return writer.estimatedKeys();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompactionInfo getCompactionInfo() {
|
||||||
|
return cursorCompactor.getCompactionInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isGlobal() {
|
||||||
|
return cursorCompactor.isGlobal();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean processNextPartitionKey() throws IOException {
|
||||||
|
if (cursorCompactor.writeNextPartition(writer)) {
|
||||||
|
totalKeysWritten++;
|
||||||
|
cursorCompactor.setTargetDirectory(writer.getSStableDirectoryPath());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long[] getMergedRowCounts() {
|
||||||
|
return cursorCompactor.getMergedRowsCounts();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getTotalSourceCQLRows() {
|
||||||
|
return cursorCompactor.getTotalSourceCQLRows();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getTotalKeysWritten() {
|
||||||
|
return totalKeysWritten;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getTotalBytesScanned() {
|
||||||
|
return cursorCompactor.getTotalBytesScanned();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
cursorCompactor.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop() {
|
||||||
|
cursorCompactor.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,115 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.cassandra.db.compaction;
|
||||||
|
|
||||||
|
import org.apache.cassandra.db.AbstractCompactionController;
|
||||||
|
import org.apache.cassandra.db.ColumnFamilyStore;
|
||||||
|
import org.apache.cassandra.db.Directories;
|
||||||
|
import org.apache.cassandra.db.compaction.writers.CompactionAwareWriter;
|
||||||
|
import org.apache.cassandra.db.lifecycle.ILifecycleTransaction;
|
||||||
|
import org.apache.cassandra.io.sstable.format.SSTableReader;
|
||||||
|
import org.apache.cassandra.utils.TimeUUID;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
class IteratorCompactionPipeline extends AbstractCompactionPipeline {
|
||||||
|
final CompactionIterator ci;
|
||||||
|
final AbstractCompactionStrategy.ScannerList scanners;
|
||||||
|
final CompactionTask task;
|
||||||
|
long totalKeysWritten;
|
||||||
|
CompactionAwareWriter writer;
|
||||||
|
|
||||||
|
IteratorCompactionPipeline(CompactionTask task, OperationType type, AbstractCompactionStrategy.ScannerList scanners, AbstractCompactionController controller, long nowInSec, TimeUUID compactionId) {
|
||||||
|
this.task = task;
|
||||||
|
this.scanners = scanners;
|
||||||
|
ci = new CompactionIterator(type, this.scanners.scanners, controller, nowInSec, compactionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AutoCloseable openWriterResource(ColumnFamilyStore cfs,
|
||||||
|
Directories directories,
|
||||||
|
ILifecycleTransaction transaction,
|
||||||
|
Set<SSTableReader> nonExpiredSSTables) {
|
||||||
|
this.writer = task.getCompactionAwareWriter(cfs, directories, transaction, nonExpiredSSTables);
|
||||||
|
return writer;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<SSTableReader> finishWriting() {
|
||||||
|
return writer.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long estimatedKeys() {
|
||||||
|
return writer.estimatedKeys();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompactionInfo getCompactionInfo() {
|
||||||
|
return ci.getCompactionInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isGlobal() {
|
||||||
|
return ci.isGlobal();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean processNextPartitionKey() throws IOException {
|
||||||
|
if (ci.hasNext()) {
|
||||||
|
if (writer.append(ci.next()))
|
||||||
|
totalKeysWritten++;
|
||||||
|
ci.setTargetDirectory(writer.getSStableDirectoryPath());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long[] getMergedRowCounts() {
|
||||||
|
return ci.getMergedRowCounts();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getTotalSourceCQLRows() {
|
||||||
|
return ci.getTotalSourceCQLRows();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getTotalKeysWritten() {
|
||||||
|
return totalKeysWritten;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getTotalBytesScanned() {
|
||||||
|
return scanners.getTotalBytesScanned();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
ci.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop() {
|
||||||
|
ci.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -297,6 +297,7 @@ public class LeveledCompactionStrategy extends AbstractCompactionStrategy
|
||||||
return levelFanoutSize;
|
return levelFanoutSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public ScannerList getScanners(Collection<SSTableReader> sstables, Collection<Range<Token>> ranges)
|
public ScannerList getScanners(Collection<SSTableReader> sstables, Collection<Range<Token>> ranges)
|
||||||
{
|
{
|
||||||
Set<SSTableReader>[] sstablesPerLevel = manifest.getSStablesPerLevelSnapshot();
|
Set<SSTableReader>[] sstablesPerLevel = manifest.getSStablesPerLevelSnapshot();
|
||||||
|
|
@ -432,7 +433,12 @@ public class LeveledCompactionStrategy extends AbstractCompactionStrategy
|
||||||
assert sstableIterator.hasNext(); // caller should check intersecting first
|
assert sstableIterator.hasNext(); // caller should check intersecting first
|
||||||
SSTableReader currentSSTable = sstableIterator.next();
|
SSTableReader currentSSTable = sstableIterator.next();
|
||||||
currentScanner = currentSSTable.getScanner(ranges);
|
currentScanner = currentSSTable.getScanner(ranges);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFullRange()
|
||||||
|
{
|
||||||
|
return ranges == null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Collection<SSTableReader> intersecting(Collection<SSTableReader> sstables, Collection<Range<Token>> ranges)
|
public static Collection<SSTableReader> intersecting(Collection<SSTableReader> sstables, Collection<Range<Token>> ranges)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,273 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.cassandra.db.compaction;
|
||||||
|
|
||||||
|
import org.apache.cassandra.config.Config;
|
||||||
|
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||||
|
import org.apache.cassandra.db.DecoratedKey;
|
||||||
|
import org.apache.cassandra.db.UnfilteredValidation;
|
||||||
|
import org.apache.cassandra.db.marshal.AbstractType;
|
||||||
|
import org.apache.cassandra.io.sstable.PartitionDescriptor;
|
||||||
|
import org.apache.cassandra.db.ReusableLivenessInfo;
|
||||||
|
import org.apache.cassandra.io.sstable.SSTableCursorReader;
|
||||||
|
import org.apache.cassandra.io.sstable.UnfilteredDescriptor;
|
||||||
|
import org.apache.cassandra.io.sstable.format.SSTableReader;
|
||||||
|
|
||||||
|
import static org.apache.cassandra.db.rows.Cell.INVALID_DELETION_TIME;
|
||||||
|
import static org.apache.cassandra.db.rows.Cell.NO_DELETION_TIME;
|
||||||
|
import static org.apache.cassandra.io.sstable.SSTableCursorReader.State.CELL_END;
|
||||||
|
import static org.apache.cassandra.io.sstable.SSTableCursorReader.State.CELL_HEADER_START;
|
||||||
|
import static org.apache.cassandra.io.sstable.SSTableCursorReader.State.CELL_VALUE_START;
|
||||||
|
import static org.apache.cassandra.io.sstable.SSTableCursorReader.State.isState;
|
||||||
|
|
||||||
|
// Cursor state
|
||||||
|
class StatefulCursor extends SSTableCursorReader
|
||||||
|
{
|
||||||
|
private final Config.CorruptedTombstoneStrategy corruptedTombstoneStrategy = DatabaseDescriptor.getCorruptedTombstoneStrategy();
|
||||||
|
private final boolean corruptedTombstoneValidationEnabled = corruptedTombstoneStrategy != Config.CorruptedTombstoneStrategy.disabled;
|
||||||
|
|
||||||
|
private PartitionDescriptor currPartition;
|
||||||
|
/**
|
||||||
|
* used for tracking reader and writer order, as well as last pk
|
||||||
|
*/
|
||||||
|
private PartitionDescriptor prevPartition;
|
||||||
|
|
||||||
|
private final UnfilteredDescriptor unfiltered;
|
||||||
|
|
||||||
|
private boolean resetAfterDone = false;
|
||||||
|
private long bytesReadPositionSnapshot = 0;
|
||||||
|
|
||||||
|
private boolean isOpenRangeTombstonePresent = false;
|
||||||
|
|
||||||
|
public StatefulCursor(SSTableReader reader)
|
||||||
|
{
|
||||||
|
super(reader);
|
||||||
|
currPartition = new PartitionDescriptor(reader.getPartitioner().createReusableKey(0));
|
||||||
|
prevPartition = new PartitionDescriptor(reader.getPartitioner().createReusableKey(0));
|
||||||
|
unfiltered = new UnfilteredDescriptor(reader.header.clusteringTypes().toArray(AbstractType[]::new));
|
||||||
|
}
|
||||||
|
|
||||||
|
public int readPartitionHeader()
|
||||||
|
{
|
||||||
|
swapCurrAndPrevPartition();
|
||||||
|
int state = readPartitionHeader(currPartition);
|
||||||
|
|
||||||
|
if (prevPartition.keyLength() != 0 && prevPartition.key().compareTo(currPartition.key()) >= 0)
|
||||||
|
corruptSSTableKeysOOO();
|
||||||
|
if (corruptedTombstoneValidationEnabled)
|
||||||
|
validateInvalidPartitionDeletion();
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int corruptSSTableKeysOOO()
|
||||||
|
{
|
||||||
|
return corruptSSTable("Keys out of order. Current key: " + keyToString(currentKey()) + " <= " + keyToString(prevKey()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void swapCurrAndPrevPartition()
|
||||||
|
{
|
||||||
|
PartitionDescriptor temp = currPartition;
|
||||||
|
currPartition = prevPartition;
|
||||||
|
prevPartition = temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int skipUnfiltered()
|
||||||
|
{
|
||||||
|
if (isState(state(), CELL_HEADER_START | CELL_VALUE_START | CELL_END))
|
||||||
|
return super.skipRowCells(unfiltered().dataStart(), unfiltered().size(), false);
|
||||||
|
|
||||||
|
return super.skipUnfiltered(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int skipStaticRow()
|
||||||
|
{
|
||||||
|
if (isState(state(), CELL_HEADER_START | CELL_VALUE_START | CELL_END))
|
||||||
|
return super.skipRowCells(unfiltered().dataStart(), unfiltered().size(), false);
|
||||||
|
|
||||||
|
return super.skipStaticRow(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return "StatefulCursor{" +
|
||||||
|
"pHeader=" + currPartition() +
|
||||||
|
", rHeader=" + unfiltered() +
|
||||||
|
", state=" + state() +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true if reset, false if already been reset
|
||||||
|
*/
|
||||||
|
public boolean resetAfterDone()
|
||||||
|
{
|
||||||
|
if (resetAfterDone)
|
||||||
|
return false;
|
||||||
|
resetAfterDone = true;
|
||||||
|
swapCurrAndPrevPartition();
|
||||||
|
// only current is reset, prev is still needed.
|
||||||
|
currPartition().resetPartition();
|
||||||
|
unfiltered().resetUnfiltered();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
DecoratedKey currentKey()
|
||||||
|
{
|
||||||
|
return currPartition.key();
|
||||||
|
}
|
||||||
|
|
||||||
|
DecoratedKey prevKey()
|
||||||
|
{
|
||||||
|
return prevPartition.key();
|
||||||
|
}
|
||||||
|
|
||||||
|
public PartitionDescriptor currPartition()
|
||||||
|
{
|
||||||
|
return currPartition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UnfilteredDescriptor unfiltered()
|
||||||
|
{
|
||||||
|
return unfiltered;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long bytesReadSinceSnapshot()
|
||||||
|
{
|
||||||
|
long latestByteReadPosition = isEOF() ? uncompressedLength() : position();
|
||||||
|
long cursorBytesRead = latestByteReadPosition - bytesReadPositionSnapshot;
|
||||||
|
bytesReadPositionSnapshot = latestByteReadPosition;
|
||||||
|
return cursorBytesRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String keyToString(DecoratedKey key)
|
||||||
|
{
|
||||||
|
String keyString;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
keyString = ssTableReader().metadata().partitionKeyType.getString(key.getKey());
|
||||||
|
}
|
||||||
|
catch (Throwable t)
|
||||||
|
{
|
||||||
|
keyString = "[corrupt token="+key.getToken()+"]";
|
||||||
|
}
|
||||||
|
return keyString;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void readRowHeader()
|
||||||
|
{
|
||||||
|
super.readRowHeader(unfiltered);
|
||||||
|
if (corruptedTombstoneValidationEnabled)
|
||||||
|
validateInvalidRowDeletion();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void readTombstoneMarker()
|
||||||
|
{
|
||||||
|
super.readTombstoneMarker(unfiltered);
|
||||||
|
|
||||||
|
if (corruptedTombstoneValidationEnabled)
|
||||||
|
validateInvalidTombstoneDeletion();
|
||||||
|
|
||||||
|
boolean isStartBound = unfiltered.isStartBound();
|
||||||
|
if (isOpenRangeTombstonePresent && isStartBound)
|
||||||
|
corruptSSTable("Encountered an open range tombstone marker before the prev was closed: " + unfiltered);
|
||||||
|
if (!isOpenRangeTombstonePresent && !isStartBound)
|
||||||
|
corruptSSTable("Encountered an close/boundary range tombstone marker before an open one: " + unfiltered);
|
||||||
|
isOpenRangeTombstonePresent = isStartBound || unfiltered.isBoundary();
|
||||||
|
// TODO: can also add verification of open/close timestamp match
|
||||||
|
}
|
||||||
|
|
||||||
|
public void readStaticRowHeader()
|
||||||
|
{
|
||||||
|
super.readStaticRowHeader(unfiltered);
|
||||||
|
if (corruptedTombstoneValidationEnabled)
|
||||||
|
validateInvalidRowDeletion();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int readCellHeader()
|
||||||
|
{
|
||||||
|
int state = super.readCellHeader();
|
||||||
|
if (corruptedTombstoneValidationEnabled)
|
||||||
|
validateInvalidCellDeletion();
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateInvalidTombstoneDeletion()
|
||||||
|
{
|
||||||
|
if (!unfiltered.deletionTime().validate()) {
|
||||||
|
UnfilteredValidation.handleInvalid(
|
||||||
|
ssTableReader().metadata(),
|
||||||
|
currPartition.key(),
|
||||||
|
ssTableReader(),
|
||||||
|
"rowDeletion="+currPartition.deletionTime().toString());
|
||||||
|
}
|
||||||
|
if (unfiltered.isBoundary() && !unfiltered.deletionTime2().validate()) {
|
||||||
|
UnfilteredValidation.handleInvalid(
|
||||||
|
ssTableReader().metadata(),
|
||||||
|
currPartition.key(),
|
||||||
|
ssTableReader(),
|
||||||
|
"rowDeletion2="+currPartition.deletionTime().toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateInvalidCellDeletion()
|
||||||
|
{
|
||||||
|
ReusableLivenessInfo cellLiveness = cellCursor().cellLiveness;
|
||||||
|
long ldt = cellLiveness.localExpirationTime();
|
||||||
|
if (cellLiveness.ttl() < 0 || ldt == INVALID_DELETION_TIME || ldt < 0 || (cellLiveness.isExpiring() && ldt == NO_DELETION_TIME)) {
|
||||||
|
UnfilteredValidation.handleInvalid(
|
||||||
|
ssTableReader().metadata(),
|
||||||
|
currPartition.key(),
|
||||||
|
ssTableReader(),
|
||||||
|
"cellLiveness="+cellLiveness);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateInvalidRowDeletion()
|
||||||
|
{
|
||||||
|
if (!unfiltered.deletionTime().validate()) {
|
||||||
|
UnfilteredValidation.handleInvalid(
|
||||||
|
ssTableReader().metadata(),
|
||||||
|
currPartition.key(),
|
||||||
|
ssTableReader(),
|
||||||
|
"rowDeletion="+currPartition.deletionTime().toString());
|
||||||
|
}
|
||||||
|
ReusableLivenessInfo livenessInfo = unfiltered.livenessInfo();
|
||||||
|
if (livenessInfo.isExpiring() && (livenessInfo.ttl() < 0 || livenessInfo.localExpirationTime() < 0)) {
|
||||||
|
UnfilteredValidation.handleInvalid(
|
||||||
|
ssTableReader().metadata(),
|
||||||
|
currPartition.key(),
|
||||||
|
ssTableReader(),
|
||||||
|
"rowLiveness="+livenessInfo.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateInvalidPartitionDeletion()
|
||||||
|
{
|
||||||
|
if (!currPartition.deletionTime().validate()) {
|
||||||
|
UnfilteredValidation.handleInvalid(
|
||||||
|
ssTableReader().metadata(),
|
||||||
|
currPartition.key(),
|
||||||
|
ssTableReader(),
|
||||||
|
"partitionLevelDeletion="+currPartition.deletionTime().toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -40,7 +40,6 @@ import org.apache.cassandra.io.sstable.SSTableRewriter;
|
||||||
import org.apache.cassandra.io.sstable.format.SSTableReader;
|
import org.apache.cassandra.io.sstable.format.SSTableReader;
|
||||||
import org.apache.cassandra.io.sstable.format.SSTableWriter;
|
import org.apache.cassandra.io.sstable.format.SSTableWriter;
|
||||||
import org.apache.cassandra.io.sstable.metadata.MetadataCollector;
|
import org.apache.cassandra.io.sstable.metadata.MetadataCollector;
|
||||||
import org.apache.cassandra.io.util.File;
|
|
||||||
import org.apache.cassandra.utils.FBUtilities;
|
import org.apache.cassandra.utils.FBUtilities;
|
||||||
import org.apache.cassandra.utils.TimeUUID;
|
import org.apache.cassandra.utils.TimeUUID;
|
||||||
import org.apache.cassandra.utils.concurrent.Transactional;
|
import org.apache.cassandra.utils.concurrent.Transactional;
|
||||||
|
|
@ -69,7 +68,7 @@ public abstract class CompactionAwareWriter extends Transactional.AbstractTransa
|
||||||
private final List<PartitionPosition> diskBoundaries;
|
private final List<PartitionPosition> diskBoundaries;
|
||||||
private int locationIndex;
|
private int locationIndex;
|
||||||
protected Directories.DataDirectory currentDirectory;
|
protected Directories.DataDirectory currentDirectory;
|
||||||
|
protected String sstableDirectoryPath;
|
||||||
public CompactionAwareWriter(ColumnFamilyStore cfs,
|
public CompactionAwareWriter(ColumnFamilyStore cfs,
|
||||||
Directories directories,
|
Directories directories,
|
||||||
ILifecycleTransaction txn,
|
ILifecycleTransaction txn,
|
||||||
|
|
@ -151,9 +150,10 @@ public abstract class CompactionAwareWriter extends Transactional.AbstractTransa
|
||||||
return realAppend(partition);
|
return realAppend(partition);
|
||||||
}
|
}
|
||||||
|
|
||||||
public final File getSStableDirectory() throws IOException
|
// hot path, called per partition
|
||||||
|
public final String getSStableDirectoryPath() throws IOException
|
||||||
{
|
{
|
||||||
return getDirectories().getLocationForDisk(currentDirectory);
|
return sstableDirectoryPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -173,35 +173,36 @@ public abstract class CompactionAwareWriter extends Transactional.AbstractTransa
|
||||||
* specific strategy has decided a new sstable is needed.
|
* specific strategy has decided a new sstable is needed.
|
||||||
* Guaranteed to be called before the first call to realAppend.
|
* Guaranteed to be called before the first call to realAppend.
|
||||||
*/
|
*/
|
||||||
protected void maybeSwitchWriter(DecoratedKey key)
|
public final SSTableWriter maybeSwitchWriter(DecoratedKey key)
|
||||||
{
|
{
|
||||||
if (maybeSwitchLocation(key))
|
SSTableWriter newWriter = maybeSwitchLocation(key);
|
||||||
return;
|
if (newWriter == null && shouldSwitchWriterInCurrentLocation(key))
|
||||||
|
{
|
||||||
if (shouldSwitchWriterInCurrentLocation(key))
|
newWriter = switchCompactionWriter(currentDirectory, key);
|
||||||
switchCompactionWriter(currentDirectory, key);
|
}
|
||||||
|
return newWriter;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Switches the file location and writer and returns true if the new key should be placed in a different data
|
* Switches the file location and writer and returns true if the new key should be placed in a different data
|
||||||
* directory.
|
* directory.
|
||||||
*/
|
*/
|
||||||
protected boolean maybeSwitchLocation(DecoratedKey key)
|
private SSTableWriter maybeSwitchLocation(DecoratedKey key)
|
||||||
{
|
{
|
||||||
if (diskBoundaries == null)
|
if (diskBoundaries == null)
|
||||||
{
|
{
|
||||||
if (locationIndex < 0)
|
if (locationIndex < 0)
|
||||||
{
|
{
|
||||||
Directories.DataDirectory defaultLocation = getWriteDirectory(nonExpiredSSTables, getExpectedWriteSize());
|
Directories.DataDirectory defaultLocation = getWriteDirectory(nonExpiredSSTables, getExpectedWriteSize());
|
||||||
switchCompactionWriter(defaultLocation, key);
|
SSTableWriter writer = switchCompactionWriter(defaultLocation, key);
|
||||||
locationIndex = 0;
|
locationIndex = 0;
|
||||||
return true;
|
return writer;
|
||||||
}
|
}
|
||||||
return false;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (locationIndex > -1 && key.compareTo(diskBoundaries.get(locationIndex)) < 0)
|
if (locationIndex > -1 && key.compareTo(diskBoundaries.get(locationIndex)) < 0)
|
||||||
return false;
|
return null;
|
||||||
|
|
||||||
int prevIdx = locationIndex;
|
int prevIdx = locationIndex;
|
||||||
while (locationIndex == -1 || key.compareTo(diskBoundaries.get(locationIndex)) > 0)
|
while (locationIndex == -1 || key.compareTo(diskBoundaries.get(locationIndex)) > 0)
|
||||||
|
|
@ -209,8 +210,7 @@ public abstract class CompactionAwareWriter extends Transactional.AbstractTransa
|
||||||
Directories.DataDirectory newLocation = locations.get(locationIndex);
|
Directories.DataDirectory newLocation = locations.get(locationIndex);
|
||||||
if (prevIdx >= 0)
|
if (prevIdx >= 0)
|
||||||
logger.debug("Switching write location from {} to {}", locations.get(prevIdx), newLocation);
|
logger.debug("Switching write location from {} to {}", locations.get(prevIdx), newLocation);
|
||||||
switchCompactionWriter(newLocation, key);
|
return switchCompactionWriter(newLocation, key);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -223,14 +223,14 @@ public abstract class CompactionAwareWriter extends Transactional.AbstractTransa
|
||||||
* Implementations of this method should finish the current sstable writer and start writing to this directory.
|
* Implementations of this method should finish the current sstable writer and start writing to this directory.
|
||||||
* <p>
|
* <p>
|
||||||
* Called once before starting to append and then whenever we see a need to start writing to another directory.
|
* Called once before starting to append and then whenever we see a need to start writing to another directory.
|
||||||
*
|
|
||||||
* @param directory
|
|
||||||
* @param nextKey
|
|
||||||
*/
|
*/
|
||||||
protected void switchCompactionWriter(Directories.DataDirectory directory, DecoratedKey nextKey)
|
protected SSTableWriter switchCompactionWriter(Directories.DataDirectory directory, DecoratedKey nextKey)
|
||||||
{
|
{
|
||||||
currentDirectory = directory;
|
currentDirectory = directory;
|
||||||
sstableWriter.switchWriter(sstableWriter(directory, nextKey));
|
sstableDirectoryPath = getDirectories().getLocationForDisk(currentDirectory).path();
|
||||||
|
SSTableWriter newWriter = sstableWriter(directory, nextKey);
|
||||||
|
sstableWriter.switchWriter(newWriter);
|
||||||
|
return newWriter;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected SSTableWriter sstableWriter(Directories.DataDirectory directory, DecoratedKey nextKey)
|
protected SSTableWriter sstableWriter(Directories.DataDirectory directory, DecoratedKey nextKey)
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import org.apache.cassandra.db.compaction.LeveledManifest;
|
||||||
import org.apache.cassandra.db.lifecycle.ILifecycleTransaction;
|
import org.apache.cassandra.db.lifecycle.ILifecycleTransaction;
|
||||||
import org.apache.cassandra.db.rows.UnfilteredRowIterator;
|
import org.apache.cassandra.db.rows.UnfilteredRowIterator;
|
||||||
import org.apache.cassandra.io.sstable.format.SSTableReader;
|
import org.apache.cassandra.io.sstable.format.SSTableReader;
|
||||||
|
import org.apache.cassandra.io.sstable.format.SSTableWriter;
|
||||||
|
|
||||||
public class MajorLeveledCompactionWriter extends CompactionAwareWriter
|
public class MajorLeveledCompactionWriter extends CompactionAwareWriter
|
||||||
{
|
{
|
||||||
|
|
@ -87,12 +88,12 @@ public class MajorLeveledCompactionWriter extends CompactionAwareWriter
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void switchCompactionWriter(Directories.DataDirectory location, DecoratedKey nextKey)
|
public SSTableWriter switchCompactionWriter(Directories.DataDirectory location, DecoratedKey nextKey)
|
||||||
{
|
{
|
||||||
averageEstimatedKeysPerSSTable = Math.round(((double) averageEstimatedKeysPerSSTable * sstablesWritten + partitionsWritten) / (sstablesWritten + 1));
|
averageEstimatedKeysPerSSTable = Math.round(((double) averageEstimatedKeysPerSSTable * sstablesWritten + partitionsWritten) / (sstablesWritten + 1));
|
||||||
partitionsWritten = 0;
|
partitionsWritten = 0;
|
||||||
sstablesWritten = 0;
|
sstablesWritten = 0;
|
||||||
super.switchCompactionWriter(location, nextKey);
|
return super.switchCompactionWriter(location, nextKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected int sstableLevel()
|
protected int sstableLevel()
|
||||||
|
|
|
||||||
|
|
@ -53,14 +53,14 @@ public class MaxThreshold extends Threshold
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected long failValue(ClientState state)
|
public long failValue(ClientState state)
|
||||||
{
|
{
|
||||||
long failValue = failThreshold.applyAsLong(state);
|
long failValue = failThreshold.applyAsLong(state);
|
||||||
return failValue <= 0 ? Long.MAX_VALUE : failValue;
|
return failValue <= 0 ? Long.MAX_VALUE : failValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected long warnValue(ClientState state)
|
public long warnValue(ClientState state)
|
||||||
{
|
{
|
||||||
long warnValue = warnThreshold.applyAsLong(state);
|
long warnValue = warnThreshold.applyAsLong(state);
|
||||||
return warnValue <= 0 ? Long.MAX_VALUE : warnValue;
|
return warnValue <= 0 ? Long.MAX_VALUE : warnValue;
|
||||||
|
|
|
||||||
|
|
@ -288,7 +288,7 @@ public abstract class UnfilteredPartitionIterators
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Digests the the provided iterator.
|
* Digests the provided iterator.
|
||||||
*
|
*
|
||||||
* Caller must close the provided iterator.
|
* Caller must close the provided iterator.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -80,13 +80,15 @@ public class BTreeRow extends AbstractRow
|
||||||
private static final Comparator<ColumnData> COLUMN_COMPARATOR = (cd1, cd2) -> cd1.column.compareTo(cd2.column);
|
private static final Comparator<ColumnData> COLUMN_COMPARATOR = (cd1, cd2) -> cd1.column.compareTo(cd2.column);
|
||||||
|
|
||||||
|
|
||||||
// We need to filter the tombstones of a row on every read (twice in fact: first to remove purgeable tombstone, and then after reconciliation to remove
|
// We need to filter the tombstones of a row on every read (twice in fact: first to remove purgeable tombstone,
|
||||||
// all tombstone since we don't return them to the client) as well as on compaction. But it's likely that many rows won't have any tombstone at all, so
|
// and then after reconciliation to remove all tombstone since we don't return them to the client) as well as on
|
||||||
// we want to speed up that case by not having to iterate/copy the row in this case. We could keep a single boolean telling us if we have tombstones,
|
// compaction. But it's likely that many rows won't have any tombstone at all, so we want to speed up that case
|
||||||
// but that doesn't work for expiring columns. So instead we keep the deletion time for the first thing in the row to be deleted. This allow at any given
|
// by not having to iterate/copy the row in this case. We could keep a single boolean telling us if we have
|
||||||
// time to know if we have any deleted information or not. If we any "true" tombstone (i.e. not an expiring cell), this value will be forced to
|
// tombstones, but that doesn't work for expiring columns. So instead we keep the deletion time for the first
|
||||||
// Long.MIN_VALUE, but if we don't and have expiring cells, this will the time at which the first expiring cell expires. If we have no tombstones and
|
// thing in the row to be deleted. This allows at any given time to know if we have any deleted information or not.
|
||||||
// no expiring cells, this will be Cell.MAX_DELETION_TIME;
|
// If we have any "true" tombstone (i.e. not an expiring cell), this value will be forced to Long.MIN_VALUE,
|
||||||
|
// but if we don't and have expiring cells, this will the time at which the first expiring cell expires. If we
|
||||||
|
// have no tombstones and no expiring cells, this will be Cell.MAX_DELETION_TIME;
|
||||||
private final long minLocalDeletionTime;
|
private final long minLocalDeletionTime;
|
||||||
|
|
||||||
private BTreeRow(Clustering clustering,
|
private BTreeRow(Clustering clustering,
|
||||||
|
|
|
||||||
|
|
@ -258,7 +258,7 @@ public abstract class Cell<V> extends ColumnData
|
||||||
* where not all field are always present (in fact, only the [ flags ] are guaranteed to be present). The fields have the following
|
* where not all field are always present (in fact, only the [ flags ] are guaranteed to be present). The fields have the following
|
||||||
* meaning:
|
* meaning:
|
||||||
* - [ flags ] is the cell flags. It is a byte for which each bit represents a flag whose meaning is explained below (*_MASK constants)
|
* - [ flags ] is the cell flags. It is a byte for which each bit represents a flag whose meaning is explained below (*_MASK constants)
|
||||||
* - [ timestamp ] is the cell timestamp. Present unless the cell has the USE_TIMESTAMP_MASK.
|
* - [ timestamp ] is the cell timestamp. Present unless the cell has the USE_ROW_TIMESTAMP_MASK.
|
||||||
* - [ deletion time]: the local deletion time for the cell. Present if either the cell is deleted (IS_DELETED_MASK)
|
* - [ deletion time]: the local deletion time for the cell. Present if either the cell is deleted (IS_DELETED_MASK)
|
||||||
* or it is expiring (IS_EXPIRING_MASK) but doesn't have the USE_ROW_TTL_MASK.
|
* or it is expiring (IS_EXPIRING_MASK) but doesn't have the USE_ROW_TTL_MASK.
|
||||||
* - [ ttl ]: the ttl for the cell. Present if the row is expiring (IS_EXPIRING_MASK) but doesn't have the
|
* - [ ttl ]: the ttl for the cell. Present if the row is expiring (IS_EXPIRING_MASK) but doesn't have the
|
||||||
|
|
@ -269,13 +269,13 @@ public abstract class Cell<V> extends ColumnData
|
||||||
* - [ value ]: the cell value, unless it has the HAS_EMPTY_VALUE_MASK.
|
* - [ value ]: the cell value, unless it has the HAS_EMPTY_VALUE_MASK.
|
||||||
* - [ path ]: the cell path if the column this is a cell of is complex.
|
* - [ path ]: the cell path if the column this is a cell of is complex.
|
||||||
*/
|
*/
|
||||||
static class Serializer
|
public static class Serializer
|
||||||
{
|
{
|
||||||
private final static int IS_DELETED_MASK = 0x01; // Whether the cell is a tombstone or not.
|
public final static int IS_DELETED_MASK = 0x01; // Whether the cell is a tombstone or not.
|
||||||
private final static int IS_EXPIRING_MASK = 0x02; // Whether the cell is expiring.
|
public final static int IS_EXPIRING_MASK = 0x02; // Whether the cell is expiring.
|
||||||
private final static int HAS_EMPTY_VALUE_MASK = 0x04; // Wether the cell has an empty value. This will be the case for tombstone in particular.
|
public final static int HAS_EMPTY_VALUE_MASK = 0x04; // Wether the cell has an empty value. This will be the case for tombstone in particular.
|
||||||
private final static int USE_ROW_TIMESTAMP_MASK = 0x08; // Wether the cell has the same timestamp than the row this is a cell of.
|
public final static int USE_ROW_TIMESTAMP_MASK = 0x08; // Wether the cell has the same timestamp than the row this is a cell of.
|
||||||
private final static int USE_ROW_TTL_MASK = 0x10; // Wether the cell has the same ttl than the row this is a cell of.
|
public final static int USE_ROW_TTL_MASK = 0x10; // Wether the cell has the same ttl than the row this is a cell of.
|
||||||
|
|
||||||
public <T> void serialize(Cell<T> cell, ColumnMetadata column, DataOutputPlus out, LivenessInfo rowLiveness, SerializationHeader header) throws IOException
|
public <T> void serialize(Cell<T> cell, ColumnMetadata column, DataOutputPlus out, LivenessInfo rowLiveness, SerializationHeader header) throws IOException
|
||||||
{
|
{
|
||||||
|
|
@ -319,11 +319,11 @@ public abstract class Cell<V> extends ColumnData
|
||||||
public <V> Cell<V> deserialize(DataInputPlus in, LivenessInfo rowLiveness, ColumnMetadata column, SerializationHeader header, DeserializationHelper helper, ValueAccessor<V> accessor) throws IOException
|
public <V> Cell<V> deserialize(DataInputPlus in, LivenessInfo rowLiveness, ColumnMetadata column, SerializationHeader header, DeserializationHelper helper, ValueAccessor<V> accessor) throws IOException
|
||||||
{
|
{
|
||||||
int flags = in.readUnsignedByte();
|
int flags = in.readUnsignedByte();
|
||||||
boolean hasValue = (flags & HAS_EMPTY_VALUE_MASK) == 0;
|
boolean hasValue = hasValue(flags);
|
||||||
boolean isDeleted = (flags & IS_DELETED_MASK) != 0;
|
boolean isDeleted = isDeleted(flags);
|
||||||
boolean isExpiring = (flags & IS_EXPIRING_MASK) != 0;
|
boolean isExpiring = isExpiring(flags);
|
||||||
boolean useRowTimestamp = (flags & USE_ROW_TIMESTAMP_MASK) != 0;
|
boolean useRowTimestamp = useRowTimestamp(flags);
|
||||||
boolean useRowTTL = (flags & USE_ROW_TTL_MASK) != 0;
|
boolean useRowTTL = useRowTTL(flags);
|
||||||
|
|
||||||
long timestamp = useRowTimestamp ? rowLiveness.timestamp() : header.readTimestamp(in);
|
long timestamp = useRowTimestamp ? rowLiveness.timestamp() : header.readTimestamp(in);
|
||||||
|
|
||||||
|
|
@ -390,11 +390,11 @@ public abstract class Cell<V> extends ColumnData
|
||||||
public boolean skip(DataInputPlus in, ColumnMetadata column, SerializationHeader header) throws IOException
|
public boolean skip(DataInputPlus in, ColumnMetadata column, SerializationHeader header) throws IOException
|
||||||
{
|
{
|
||||||
int flags = in.readUnsignedByte();
|
int flags = in.readUnsignedByte();
|
||||||
boolean hasValue = (flags & HAS_EMPTY_VALUE_MASK) == 0;
|
boolean hasValue = hasValue(flags);
|
||||||
boolean isDeleted = (flags & IS_DELETED_MASK) != 0;
|
boolean isDeleted = isDeleted(flags);
|
||||||
boolean isExpiring = (flags & IS_EXPIRING_MASK) != 0;
|
boolean isExpiring = isExpiring(flags);
|
||||||
boolean useRowTimestamp = (flags & USE_ROW_TIMESTAMP_MASK) != 0;
|
boolean useRowTimestamp = useRowTimestamp(flags);
|
||||||
boolean useRowTTL = (flags & USE_ROW_TTL_MASK) != 0;
|
boolean useRowTTL = useRowTTL(flags);
|
||||||
|
|
||||||
if (!useRowTimestamp)
|
if (!useRowTimestamp)
|
||||||
header.skipTimestamp(in);
|
header.skipTimestamp(in);
|
||||||
|
|
@ -413,5 +413,30 @@ public abstract class Cell<V> extends ColumnData
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean useRowTTL(int cellFlags)
|
||||||
|
{
|
||||||
|
return (cellFlags & USE_ROW_TTL_MASK) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean useRowTimestamp(int cellFlags)
|
||||||
|
{
|
||||||
|
return (cellFlags & USE_ROW_TIMESTAMP_MASK) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isExpiring(int cellFlags)
|
||||||
|
{
|
||||||
|
return (cellFlags & IS_EXPIRING_MASK) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isDeleted(int cellFlags)
|
||||||
|
{
|
||||||
|
return (cellFlags & IS_DELETED_MASK) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean hasValue(int cellFlags)
|
||||||
|
{
|
||||||
|
return (cellFlags & HAS_EMPTY_VALUE_MASK) == 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ public abstract class Cells
|
||||||
private Cells() {}
|
private Cells() {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Collect statistics ont a given cell.
|
* Collect statistics on a given cell.
|
||||||
*
|
*
|
||||||
* @param cell the cell for which to collect stats.
|
* @param cell the cell for which to collect stats.
|
||||||
* @param collector the stats collector.
|
* @param collector the stats collector.
|
||||||
|
|
|
||||||
|
|
@ -159,7 +159,7 @@ public interface RangeTombstoneMarker extends Unfiltered, IMeasurableMemory
|
||||||
return DeletionTime.LIVE;
|
return DeletionTime.LIVE;
|
||||||
|
|
||||||
DeletionTime biggestDeletionTime = openMarkers[biggestOpenMarker];
|
DeletionTime biggestDeletionTime = openMarkers[biggestOpenMarker];
|
||||||
// it's only open in the merged iterator if it doesn't supersedes the partition level deletion
|
// it's only open in the merged iterator if it doesn't supersede the partition level deletion
|
||||||
return !biggestDeletionTime.supersedes(partitionDeletion) ? DeletionTime.LIVE : biggestDeletionTime;
|
return !biggestDeletionTime.supersedes(partitionDeletion) ? DeletionTime.LIVE : biggestDeletionTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -172,7 +172,7 @@ public interface RangeTombstoneMarker extends Unfiltered, IMeasurableMemory
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Note that we can have boundaries that are both open and close, but in that case all we care about
|
// Note that we can have boundaries that are both open and close, but in that case all we care about
|
||||||
// is what it the open deletion after the marker, so we favor the opening part in this case.
|
// is what is the open deletion after the marker, so we favor the opening part in this case.
|
||||||
if (marker.isOpen(reversed))
|
if (marker.isOpen(reversed))
|
||||||
openMarkers[i] = marker.openDeletionTime(reversed);
|
openMarkers[i] = marker.openDeletionTime(reversed);
|
||||||
else
|
else
|
||||||
|
|
@ -192,7 +192,7 @@ public interface RangeTombstoneMarker extends Unfiltered, IMeasurableMemory
|
||||||
{
|
{
|
||||||
DeletionTime openMarker = currentOpenDeletionTimeInMerged();
|
DeletionTime openMarker = currentOpenDeletionTimeInMerged();
|
||||||
// We only have an open marker in the merged stream if it's not shadowed by the partition deletion (which can be LIVE itself), so
|
// We only have an open marker in the merged stream if it's not shadowed by the partition deletion (which can be LIVE itself), so
|
||||||
// if have an open marker, we know it's the "active" deletion for the merged stream.
|
// if we have an open marker, we know it's the "active" deletion for the merged stream.
|
||||||
return openMarker.isLive() ? partitionDeletion : openMarker;
|
return openMarker.isLive() ? partitionDeletion : openMarker;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -99,19 +99,19 @@ public class UnfilteredSerializer
|
||||||
/*
|
/*
|
||||||
* Unfiltered flags constants.
|
* Unfiltered flags constants.
|
||||||
*/
|
*/
|
||||||
private final static int END_OF_PARTITION = 0x01; // Signal the end of the partition. Nothing follows a <flags> field with that flag.
|
public final static int END_OF_PARTITION = 0x01; // Signal the end of the partition. Nothing follows a <flags> field with that flag.
|
||||||
private final static int IS_MARKER = 0x02; // Whether the encoded unfiltered is a marker or a row. All following markers applies only to rows.
|
public final static int IS_MARKER = 0x02; // Whether the encoded unfiltered is a marker or a row. All following markers applies only to rows.
|
||||||
private final static int HAS_TIMESTAMP = 0x04; // Whether the encoded row has a timestamp (i.e. if row.partitionKeyLivenessInfo().hasTimestamp() == true).
|
public final static int HAS_TIMESTAMP = 0x04; // Whether the encoded row has a timestamp (i.e. if row.partitionKeyLivenessInfo().hasTimestamp() == true).
|
||||||
private final static int HAS_TTL = 0x08; // Whether the encoded row has some expiration info (i.e. if row.partitionKeyLivenessInfo().hasTTL() == true).
|
public final static int HAS_TTL = 0x08; // Whether the encoded row has some expiration info (i.e. if row.partitionKeyLivenessInfo().hasTTL() == true).
|
||||||
private final static int HAS_DELETION = 0x10; // Whether the encoded row has some deletion info.
|
public final static int HAS_DELETION = 0x10; // Whether the encoded row has some deletion info.
|
||||||
private final static int HAS_ALL_COLUMNS = 0x20; // Whether the encoded row has all of the columns from the header present.
|
public final static int HAS_ALL_COLUMNS = 0x20; // Whether the encoded row has all of the columns from the header present.
|
||||||
private final static int HAS_COMPLEX_DELETION = 0x40; // Whether the encoded row has some complex deletion for at least one of its columns.
|
public final static int HAS_COMPLEX_DELETION = 0x40; // Whether the encoded row has some complex deletion for at least one of its columns.
|
||||||
private final static int EXTENSION_FLAG = 0x80; // If present, another byte is read containing the "extended flags" above.
|
public final static int EXTENSION_FLAG = 0x80; // If present, another byte is read containing the "extended flags" above.
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Extended flags
|
* Extended flags
|
||||||
*/
|
*/
|
||||||
private final static int IS_STATIC = 0x01; // Whether the encoded row is a static. If there is no extended flag, the row is assumed not static.
|
public final static int IS_STATIC = 0x01; // Whether the encoded row is a static. If there is no extended flag, the row is assumed not static.
|
||||||
/**
|
/**
|
||||||
* A shadowable tombstone cannot replace a previous row deletion otherwise it could resurrect a
|
* A shadowable tombstone cannot replace a previous row deletion otherwise it could resurrect a
|
||||||
* previously deleted cell not updated by a subsequent update, SEE CASSANDRA-11500
|
* previously deleted cell not updated by a subsequent update, SEE CASSANDRA-11500
|
||||||
|
|
@ -119,7 +119,7 @@ public class UnfilteredSerializer
|
||||||
* @deprecated See CASSANDRA-11500
|
* @deprecated See CASSANDRA-11500
|
||||||
*/
|
*/
|
||||||
@Deprecated(since = "4.0")
|
@Deprecated(since = "4.0")
|
||||||
private final static int HAS_SHADOWABLE_DELETION = 0x02; // Whether the row deletion is shadowable. If there is no extended flag (or no row deletion), the deletion is assumed not shadowable.
|
public final static int HAS_SHADOWABLE_DELETION = 0x02; // Whether the row deletion is shadowable. If there is no extended flag (or no row deletion), the deletion is assumed not shadowable.
|
||||||
|
|
||||||
public void serialize(Unfiltered unfiltered, SerializationHelper helper, DataOutputPlus out, int version)
|
public void serialize(Unfiltered unfiltered, SerializationHelper helper, DataOutputPlus out, int version)
|
||||||
throws IOException
|
throws IOException
|
||||||
|
|
@ -220,14 +220,14 @@ public class UnfilteredSerializer
|
||||||
LivenessInfo pkLiveness = row.primaryKeyLivenessInfo();
|
LivenessInfo pkLiveness = row.primaryKeyLivenessInfo();
|
||||||
Row.Deletion deletion = row.deletion();
|
Row.Deletion deletion = row.deletion();
|
||||||
|
|
||||||
if ((flags & HAS_TIMESTAMP) != 0)
|
if (hasTimestamp(flags))
|
||||||
header.writeTimestamp(pkLiveness.timestamp(), out);
|
header.writeTimestamp(pkLiveness.timestamp(), out);
|
||||||
if ((flags & HAS_TTL) != 0)
|
if (hasTTL(flags))
|
||||||
{
|
{
|
||||||
header.writeTTL(pkLiveness.ttl(), out);
|
header.writeTTL(pkLiveness.ttl(), out);
|
||||||
header.writeLocalDeletionTime(pkLiveness.localExpirationTime(), out);
|
header.writeLocalDeletionTime(pkLiveness.localExpirationTime(), out);
|
||||||
}
|
}
|
||||||
if ((flags & HAS_DELETION) != 0)
|
if (hasDeletion(flags))
|
||||||
header.writeDeletionTime(deletion.time(), out);
|
header.writeDeletionTime(deletion.time(), out);
|
||||||
|
|
||||||
if ((flags & HAS_ALL_COLUMNS) == 0)
|
if ((flags & HAS_ALL_COLUMNS) == 0)
|
||||||
|
|
@ -251,7 +251,7 @@ public class UnfilteredSerializer
|
||||||
if (cd.column.isSimple())
|
if (cd.column.isSimple())
|
||||||
Cell.serializer.serialize((Cell<?>) cd, column, out, pkLiveness, header);
|
Cell.serializer.serialize((Cell<?>) cd, column, out, pkLiveness, header);
|
||||||
else
|
else
|
||||||
writeComplexColumn((ComplexColumnData) cd, column, (flags & HAS_COMPLEX_DELETION) != 0, pkLiveness, header, out);
|
writeComplexColumn((ComplexColumnData) cd, column, hasComplexDeletion(flags), pkLiveness, header, out);
|
||||||
}
|
}
|
||||||
catch (IOException e)
|
catch (IOException e)
|
||||||
{
|
{
|
||||||
|
|
@ -412,7 +412,7 @@ public class UnfilteredSerializer
|
||||||
|
|
||||||
public void writeEndOfPartition(DataOutputPlus out) throws IOException
|
public void writeEndOfPartition(DataOutputPlus out) throws IOException
|
||||||
{
|
{
|
||||||
out.writeByte((byte)1);
|
out.writeByte((byte)END_OF_PARTITION);
|
||||||
}
|
}
|
||||||
|
|
||||||
public long serializedSizeEndOfPartition()
|
public long serializedSizeEndOfPartition()
|
||||||
|
|
@ -502,12 +502,12 @@ public class UnfilteredSerializer
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
assert !isStatic(extendedFlags); // deserializeStaticRow should be used for that.
|
assert !isStatic(extendedFlags); // deserializeStaticRow should be used for that.
|
||||||
if ((flags & HAS_DELETION) != 0)
|
if (hasDeletion(flags))
|
||||||
{
|
{
|
||||||
assert header.isForSSTable();
|
assert header.isForSSTable();
|
||||||
boolean hasTimestamp = (flags & HAS_TIMESTAMP) != 0;
|
boolean hasTimestamp = hasTimestamp(flags);
|
||||||
boolean hasTTL = (flags & HAS_TTL) != 0;
|
boolean hasTTL = hasTTL(flags);
|
||||||
boolean deletionIsShadowable = (extendedFlags & HAS_SHADOWABLE_DELETION) != 0;
|
boolean deletionIsShadowable = deletionIsShadowable(extendedFlags);
|
||||||
Clustering<byte[]> clustering = Clustering.serializer.deserialize(in, helper.version, header.clusteringTypes());
|
Clustering<byte[]> clustering = Clustering.serializer.deserialize(in, helper.version, header.clusteringTypes());
|
||||||
long nextPosition = in.readUnsignedVInt() + in.getFilePointer();
|
long nextPosition = in.readUnsignedVInt() + in.getFilePointer();
|
||||||
in.readUnsignedVInt(); // skip previous unfiltered size
|
in.readUnsignedVInt(); // skip previous unfiltered size
|
||||||
|
|
@ -572,12 +572,12 @@ public class UnfilteredSerializer
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
boolean isStatic = isStatic(extendedFlags);
|
boolean isStatic = isStatic(extendedFlags);
|
||||||
boolean hasTimestamp = (flags & HAS_TIMESTAMP) != 0;
|
boolean hasTimestamp = hasTimestamp(flags);
|
||||||
boolean hasTTL = (flags & HAS_TTL) != 0;
|
boolean hasTTL = hasTTL(flags);
|
||||||
boolean hasDeletion = (flags & HAS_DELETION) != 0;
|
boolean hasDeletion = hasDeletion(flags);
|
||||||
boolean deletionIsShadowable = (extendedFlags & HAS_SHADOWABLE_DELETION) != 0;
|
boolean deletionIsShadowable = deletionIsShadowable(extendedFlags);
|
||||||
boolean hasComplexDeletion = (flags & HAS_COMPLEX_DELETION) != 0;
|
boolean hasComplexDeletion = hasComplexDeletion(flags);
|
||||||
boolean hasAllColumns = (flags & HAS_ALL_COLUMNS) != 0;
|
boolean hasAllColumns = hasAllColumns(flags);
|
||||||
Columns headerColumns = header.columns(isStatic);
|
Columns headerColumns = header.columns(isStatic);
|
||||||
|
|
||||||
if (header.isForSSTable())
|
if (header.isForSSTable())
|
||||||
|
|
@ -734,7 +734,17 @@ public class UnfilteredSerializer
|
||||||
|
|
||||||
public static Unfiltered.Kind kind(int flags)
|
public static Unfiltered.Kind kind(int flags)
|
||||||
{
|
{
|
||||||
return (flags & IS_MARKER) != 0 ? Unfiltered.Kind.RANGE_TOMBSTONE_MARKER : Unfiltered.Kind.ROW;
|
return isTombstoneMarker(flags) ? Unfiltered.Kind.RANGE_TOMBSTONE_MARKER : Unfiltered.Kind.ROW;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isTombstoneMarker(int flags)
|
||||||
|
{
|
||||||
|
return (flags & IS_MARKER) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isRow(int flags)
|
||||||
|
{
|
||||||
|
return (flags & IS_MARKER) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isStatic(int extendedFlags)
|
public static boolean isStatic(int extendedFlags)
|
||||||
|
|
@ -742,7 +752,12 @@ public class UnfilteredSerializer
|
||||||
return (extendedFlags & IS_STATIC) != 0;
|
return (extendedFlags & IS_STATIC) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isExtended(int flags)
|
public static boolean deletionIsShadowable(int extendedFlags)
|
||||||
|
{
|
||||||
|
return (extendedFlags & HAS_SHADOWABLE_DELETION) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isExtended(int flags)
|
||||||
{
|
{
|
||||||
return (flags & EXTENSION_FLAG) != 0;
|
return (flags & EXTENSION_FLAG) != 0;
|
||||||
}
|
}
|
||||||
|
|
@ -756,4 +771,29 @@ public class UnfilteredSerializer
|
||||||
{
|
{
|
||||||
return row.isStatic() || row.deletion().isShadowable();
|
return row.isStatic() || row.deletion().isShadowable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean hasTTL(int flags)
|
||||||
|
{
|
||||||
|
return (flags & HAS_TTL) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean hasTimestamp(int flags)
|
||||||
|
{
|
||||||
|
return (flags & HAS_TIMESTAMP) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean hasAllColumns(int flags)
|
||||||
|
{
|
||||||
|
return (flags & HAS_ALL_COLUMNS) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean hasComplexDeletion(int flags)
|
||||||
|
{
|
||||||
|
return (flags & HAS_COMPLEX_DELETION) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean hasDeletion(int flags)
|
||||||
|
{
|
||||||
|
return (flags & HAS_DELETION) != 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ abstract class ComparableObjectToken<C extends Comparable<C>> extends Token
|
||||||
{
|
{
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
final C token; // Package-private to allow access from subtypes, which should all reside in the dht package.
|
C token; // Package-private to allow access from subtypes, which should all reside in the dht package.
|
||||||
|
|
||||||
protected ComparableObjectToken(C token)
|
protected ComparableObjectToken(C token)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,19 @@ public interface IPartitioner
|
||||||
return DatabaseDescriptor.getPartitioner();
|
return DatabaseDescriptor.getPartitioner();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a new instance of a reusable key
|
||||||
|
*/
|
||||||
|
default ReusableDecoratedKey createReusableKey(int initialSize)
|
||||||
|
{
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
default boolean supportsReusableKeys()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transform key to object representation of the on-disk format.
|
* Transform key to object representation of the on-disk format.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -243,4 +243,38 @@ public class LocalPartitioner implements IPartitioner
|
||||||
{
|
{
|
||||||
return AccordBytesSplitter::new;
|
return AccordBytesSplitter::new;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class ReusableLocalToken extends LocalToken
|
||||||
|
{
|
||||||
|
void setToken(ByteBuffer token)
|
||||||
|
{
|
||||||
|
this.token = token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ReusableLocalKey extends ReusableDecoratedKey
|
||||||
|
{
|
||||||
|
public ReusableLocalKey(int initialSize)
|
||||||
|
{
|
||||||
|
super(new ReusableLocalToken(), initialSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void recalculateToken()
|
||||||
|
{
|
||||||
|
((ReusableLocalToken)getToken()).setToken(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReusableDecoratedKey createReusableKey(int initialSize)
|
||||||
|
{
|
||||||
|
return new ReusableLocalKey(initialSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsReusableKeys()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import java.io.IOException;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
@ -35,6 +36,7 @@ import com.google.common.primitives.Longs;
|
||||||
|
|
||||||
import accord.primitives.Ranges;
|
import accord.primitives.Ranges;
|
||||||
import org.apache.cassandra.db.DecoratedKey;
|
import org.apache.cassandra.db.DecoratedKey;
|
||||||
|
import org.apache.cassandra.db.PartitionPosition;
|
||||||
import org.apache.cassandra.db.PreHashedDecoratedKey;
|
import org.apache.cassandra.db.PreHashedDecoratedKey;
|
||||||
import org.apache.cassandra.db.TypeSizes;
|
import org.apache.cassandra.db.TypeSizes;
|
||||||
import org.apache.cassandra.db.marshal.AbstractType;
|
import org.apache.cassandra.db.marshal.AbstractType;
|
||||||
|
|
@ -177,7 +179,7 @@ public class Murmur3Partitioner implements IPartitioner
|
||||||
{
|
{
|
||||||
static final long serialVersionUID = -5833580143318243006L;
|
static final long serialVersionUID = -5833580143318243006L;
|
||||||
|
|
||||||
public final long token;
|
long token;
|
||||||
|
|
||||||
public LongToken(long token)
|
public LongToken(long token)
|
||||||
{
|
{
|
||||||
|
|
@ -320,6 +322,18 @@ public class Murmur3Partitioner implements IPartitioner
|
||||||
return new LongToken(normalize(hash[0]));
|
return new LongToken(normalize(hash[0]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private long calculateTokenValue(ByteBuffer key, long[] hash)
|
||||||
|
{
|
||||||
|
if (key.remaining() == 0)
|
||||||
|
{
|
||||||
|
hash[0] = MINIMUM.token;
|
||||||
|
hash[1] = 0;
|
||||||
|
return MINIMUM.token;
|
||||||
|
}
|
||||||
|
populateHash(key, hash);
|
||||||
|
return normalize(hash[0]);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isFixedLength()
|
public boolean isFixedLength()
|
||||||
{
|
{
|
||||||
|
|
@ -386,10 +400,15 @@ public class Murmur3Partitioner implements IPartitioner
|
||||||
private long[] getHash(ByteBuffer key)
|
private long[] getHash(ByteBuffer key)
|
||||||
{
|
{
|
||||||
long[] hash = new long[2];
|
long[] hash = new long[2];
|
||||||
MurmurHash.hash3_x64_128(key, key.position(), key.remaining(), 0, hash);
|
populateHash(key, hash);
|
||||||
return hash;
|
return hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void populateHash(ByteBuffer key, long[] hash)
|
||||||
|
{
|
||||||
|
MurmurHash.hash3_x64_128(key, key.position(), key.remaining(), 0, hash);
|
||||||
|
}
|
||||||
|
|
||||||
public LongToken getRandomToken()
|
public LongToken getRandomToken()
|
||||||
{
|
{
|
||||||
return getRandomToken(ThreadLocalRandom.current());
|
return getRandomToken(ThreadLocalRandom.current());
|
||||||
|
|
@ -565,4 +584,79 @@ public class Murmur3Partitioner implements IPartitioner
|
||||||
{
|
{
|
||||||
return ignore -> splitter;
|
return ignore -> splitter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class ReusableLongToken extends LongToken
|
||||||
|
{
|
||||||
|
public ReusableLongToken()
|
||||||
|
{
|
||||||
|
super(MINIMUM.token);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setToken(long token)
|
||||||
|
{
|
||||||
|
this.token = token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ReusableMurmurKey extends ReusableDecoratedKey
|
||||||
|
{
|
||||||
|
protected final long[] hash = new long[2];
|
||||||
|
private long tokenValue = MINIMUM.token;
|
||||||
|
ReusableMurmurKey(int initialSize)
|
||||||
|
{
|
||||||
|
super(new ReusableLongToken(), initialSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void recalculateToken()
|
||||||
|
{
|
||||||
|
Token token = getToken();
|
||||||
|
((ReusableLongToken) token).setToken(Murmur3Partitioner.instance.calculateTokenValue(key, hash));
|
||||||
|
tokenValue = token.getLongValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj)
|
||||||
|
{
|
||||||
|
return (obj instanceof ReusableMurmurKey) ? equals((ReusableMurmurKey) obj) : super.equals(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean equals(ReusableMurmurKey obj)
|
||||||
|
{
|
||||||
|
if (this == obj)
|
||||||
|
return true;
|
||||||
|
if (obj == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (tokenValue != obj.tokenValue)
|
||||||
|
return false;
|
||||||
|
return Arrays.equals(keyBytes, 0, keyLength, obj.keyBytes, 0, obj.getKeyLength()); // we compare faster than BB.equals for array backed BB
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(PartitionPosition pos)
|
||||||
|
{
|
||||||
|
return (pos instanceof ReusableMurmurKey) ? compareTo((ReusableMurmurKey) pos) : super.compareTo(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int compareTo(ReusableMurmurKey obj)
|
||||||
|
{
|
||||||
|
if (this == obj)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
int cmp = Long.compare(tokenValue, obj.tokenValue);
|
||||||
|
return cmp == 0 ? Arrays.compareUnsigned(keyBytes, 0, keyLength, obj.keyBytes, 0, obj.keyLength) : cmp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReusableDecoratedKey createReusableKey(int initialSize)
|
||||||
|
{
|
||||||
|
return new ReusableMurmurKey(initialSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsReusableKeys()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,91 @@
|
||||||
|
/*
|
||||||
|
* 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.dht;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
import org.apache.cassandra.db.BufferDecoratedKey;
|
||||||
|
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||||
|
|
||||||
|
public abstract class ReusableDecoratedKey extends BufferDecoratedKey
|
||||||
|
{
|
||||||
|
protected int keyLength = 0;
|
||||||
|
protected byte[] keyBytes;
|
||||||
|
|
||||||
|
public ReusableDecoratedKey(Token token, int initialSize)
|
||||||
|
{
|
||||||
|
super(token, ByteBuffer.wrap(new byte[initialSize]).limit(0));
|
||||||
|
keyBytes = key.array();
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract void recalculateToken();
|
||||||
|
|
||||||
|
public void copyKey(ByteBuffer newKey)
|
||||||
|
{
|
||||||
|
int length = newKey.remaining();
|
||||||
|
maybeResizeKey(length);
|
||||||
|
ByteBufferUtil.copyBytes(newKey, newKey.position(), key, 0, length);
|
||||||
|
keyLength = length;
|
||||||
|
key.limit(length);
|
||||||
|
recalculateToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** WARNING: retains ref to external buffer */
|
||||||
|
public void shadowKey(ByteBuffer newKey, byte[] newKeyBytes, int newKeyLength)
|
||||||
|
{
|
||||||
|
key = newKey;
|
||||||
|
keyBytes = newKeyBytes;
|
||||||
|
keyLength = newKeyLength;
|
||||||
|
recalculateToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** WARNING: retains ref to external buffer */
|
||||||
|
public void shadowKey(int newKeyLength)
|
||||||
|
{
|
||||||
|
keyLength = newKeyLength;
|
||||||
|
recalculateToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getKeyLength()
|
||||||
|
{
|
||||||
|
return keyLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] keyBytes()
|
||||||
|
{
|
||||||
|
return keyBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset()
|
||||||
|
{
|
||||||
|
keyLength = 0;
|
||||||
|
key.limit(0);
|
||||||
|
recalculateToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void maybeResizeKey(int length)
|
||||||
|
{
|
||||||
|
int capacity = keyBytes.length;
|
||||||
|
if (capacity > length)
|
||||||
|
return;
|
||||||
|
keyBytes = new byte[Math.max(length, capacity * 2)];
|
||||||
|
key = ByteBuffer.wrap(keyBytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -500,7 +500,7 @@ public abstract class AbstractSSTableIterator<RIE extends AbstractRowIndexEntry>
|
||||||
{
|
{
|
||||||
// We use a same reasoning as in handlePreSliceData regarding the strictness of the inequality below.
|
// We use a same reasoning as in handlePreSliceData regarding the strictness of the inequality below.
|
||||||
// We want to exclude deserialized unfiltered equal to end, because 1) we won't miss any rows since those
|
// We want to exclude deserialized unfiltered equal to end, because 1) we won't miss any rows since those
|
||||||
// woudn't be equal to a slice bound and 2) a end bound can be equal to a start bound
|
// wouldn't be equal to a slice bound and 2) a end bound can be equal to a start bound
|
||||||
// (EXCL_END(x) == INCL_START(x) for instance) and in that case we don't want to return start bound because
|
// (EXCL_END(x) == INCL_START(x) for instance) and in that case we don't want to return start bound because
|
||||||
// it's fundamentally excluded. And if the bound is a end (for a range tombstone), it means it's exactly
|
// it's fundamentally excluded. And if the bound is a end (for a range tombstone), it means it's exactly
|
||||||
// our slice end, but in that case we will properly close the range tombstone anyway as part of our "close
|
// our slice end, but in that case we will properly close the range tombstone anyway as part of our "close
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ import org.apache.cassandra.service.ActiveRepairService;
|
||||||
/**
|
/**
|
||||||
* Base class for the sstable writers used by CQLSSTableWriter.
|
* Base class for the sstable writers used by CQLSSTableWriter.
|
||||||
*/
|
*/
|
||||||
abstract class AbstractSSTableSimpleWriter implements Closeable
|
public abstract class AbstractSSTableSimpleWriter implements Closeable
|
||||||
{
|
{
|
||||||
protected final File directory;
|
protected final File directory;
|
||||||
protected final TableMetadataRef metadata;
|
protected final TableMetadataRef metadata;
|
||||||
|
|
@ -172,7 +172,7 @@ abstract class AbstractSSTableSimpleWriter implements Closeable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PartitionUpdate.Builder getUpdateFor(ByteBuffer key) throws IOException
|
public PartitionUpdate.Builder getUpdateFor(ByteBuffer key) throws IOException
|
||||||
{
|
{
|
||||||
return getUpdateFor(metadata.get().partitioner.decorateKey(key));
|
return getUpdateFor(metadata.get().partitioner.decorateKey(key));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,187 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.cassandra.io.sstable;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.cassandra.io.util.ResizableByteBuffer;
|
||||||
|
import org.apache.cassandra.db.Clustering;
|
||||||
|
import org.apache.cassandra.db.ClusteringBound;
|
||||||
|
import org.apache.cassandra.db.ClusteringPrefix;
|
||||||
|
import org.apache.cassandra.db.marshal.AbstractType;
|
||||||
|
import org.apache.cassandra.db.marshal.ByteArrayAccessor;
|
||||||
|
import org.apache.cassandra.io.util.DataInputBuffer;
|
||||||
|
import org.apache.cassandra.io.util.RandomAccessReader;
|
||||||
|
|
||||||
|
import static org.apache.cassandra.io.sstable.SSTableCursorReader.readUnfilteredClustering;
|
||||||
|
|
||||||
|
public class ClusteringDescriptor extends ResizableByteBuffer
|
||||||
|
{
|
||||||
|
private static final byte MAX_START_KIND = (byte) ClusteringBound.MAX_START.kind().ordinal();
|
||||||
|
private static final byte MIN_END_KIND = (byte) ClusteringBound.MIN_END.kind().ordinal();
|
||||||
|
|
||||||
|
static final byte EXCL_END_BOUND_CLUSTERING_KIND = (byte) ClusteringPrefix.Kind.EXCL_END_BOUND.ordinal();
|
||||||
|
static final byte INCL_START_BOUND_CLUSTERING_KIND = (byte) ClusteringPrefix.Kind.INCL_START_BOUND.ordinal();
|
||||||
|
static final byte INCL_END_EXCL_START_BOUNDARY_CLUSTERING_KIND = (byte) ClusteringPrefix.Kind.INCL_END_EXCL_START_BOUNDARY.ordinal();
|
||||||
|
|
||||||
|
static final byte STATIC_CLUSTERING_KIND = (byte)ClusteringPrefix.Kind.STATIC_CLUSTERING.ordinal();
|
||||||
|
static final byte ROW_CLUSTERING_KIND = (byte) ClusteringPrefix.Kind.CLUSTERING.ordinal();
|
||||||
|
|
||||||
|
static final byte EXCL_END_INCL_START_BOUNDARY_CLUSTERING_KIND = (byte) ClusteringPrefix.Kind.EXCL_END_INCL_START_BOUNDARY.ordinal();
|
||||||
|
static final byte INCL_END_BOUND_CLUSTERING_KIND = (byte) ClusteringPrefix.Kind.INCL_END_BOUND.ordinal();
|
||||||
|
static final byte EXCL_START_BOUND_CLUSTERING_KIND = (byte) ClusteringPrefix.Kind.EXCL_START_BOUND.ordinal();
|
||||||
|
|
||||||
|
protected final AbstractType<?>[] clusteringTypes;
|
||||||
|
protected ClusteringPrefix.Kind clusteringKind;
|
||||||
|
protected byte clusteringKindEncoded;
|
||||||
|
protected int clusteringColumnsBound;
|
||||||
|
|
||||||
|
public ClusteringDescriptor(AbstractType<?>[] clusteringTypes)
|
||||||
|
{
|
||||||
|
this.clusteringTypes = clusteringTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void loadClustering(RandomAccessReader dataReader, byte clusteringKind, int clusteringColumnsBound) throws IOException
|
||||||
|
{
|
||||||
|
set(ClusteringPrefix.Kind.values()[clusteringKind], clusteringKind, clusteringColumnsBound);
|
||||||
|
if (clusteringKind != STATIC_CLUSTERING_KIND)
|
||||||
|
readUnfilteredClustering(dataReader, clusteringTypes, this.clusteringColumnsBound, this);
|
||||||
|
else
|
||||||
|
resetBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
public final ClusteringDescriptor resetMinEnd() {
|
||||||
|
set(MIN_END_KIND, 0);
|
||||||
|
resetBuffer();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final ClusteringDescriptor resetMaxStart() {
|
||||||
|
set(MAX_START_KIND, 0);
|
||||||
|
resetBuffer();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean isMaxStart()
|
||||||
|
{
|
||||||
|
return clusteringKindEncoded == MAX_START_KIND && clusteringColumnsBound == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void resetClustering()
|
||||||
|
{
|
||||||
|
set(ClusteringPrefix.Kind.CLUSTERING, 0);
|
||||||
|
|
||||||
|
resetBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void copy(ClusteringDescriptor newClustering)
|
||||||
|
{
|
||||||
|
set(newClustering.clusteringKind, newClustering.clusteringColumnsBound());
|
||||||
|
overwrite(newClustering.clusteringBytes(), newClustering.clusteringLength());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void set(ClusteringPrefix.Kind clusteringKind, int clusteringColumnsBound) {
|
||||||
|
set(clusteringKind, (byte) clusteringKind.ordinal(), clusteringColumnsBound);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void set(byte clusteringKindEncoded, int clusteringColumnsBound) {
|
||||||
|
set(ClusteringPrefix.Kind.values()[clusteringKindEncoded], clusteringKindEncoded, clusteringColumnsBound);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void set(ClusteringPrefix.Kind clusteringKind, byte clusteringKindEncoded, int clusteringColumnsBound)
|
||||||
|
{
|
||||||
|
this.clusteringKindEncoded = clusteringKindEncoded;
|
||||||
|
this.clusteringKind = clusteringKind;
|
||||||
|
this.clusteringColumnsBound = clusteringColumnsBound;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expose and rename parent data
|
||||||
|
public final ByteBuffer clusteringBuffer() {
|
||||||
|
return buffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
public final int clusteringLength() {
|
||||||
|
return length();
|
||||||
|
}
|
||||||
|
|
||||||
|
public final byte[] clusteringBytes() {
|
||||||
|
return bytes();
|
||||||
|
}
|
||||||
|
|
||||||
|
public final AbstractType<?>[] clusteringTypes()
|
||||||
|
{
|
||||||
|
return clusteringTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final byte clusteringKindEncoded() {
|
||||||
|
return clusteringKindEncoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final ClusteringPrefix.Kind clusteringKind() {
|
||||||
|
return clusteringKind;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void clusteringKind(ClusteringPrefix.Kind kind)
|
||||||
|
{
|
||||||
|
clusteringKind = kind;
|
||||||
|
clusteringKindEncoded = (byte)kind.ordinal();
|
||||||
|
}
|
||||||
|
|
||||||
|
public final int clusteringColumnsBound() {
|
||||||
|
return clusteringColumnsBound;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean isStartBound()
|
||||||
|
{
|
||||||
|
return (clusteringKindEncoded == INCL_START_BOUND_CLUSTERING_KIND || clusteringKindEncoded == EXCL_START_BOUND_CLUSTERING_KIND);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean isEndBound()
|
||||||
|
{
|
||||||
|
return (clusteringKindEncoded == INCL_END_BOUND_CLUSTERING_KIND || clusteringKindEncoded == EXCL_END_BOUND_CLUSTERING_KIND);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean isBoundary()
|
||||||
|
{
|
||||||
|
return (clusteringKindEncoded == EXCL_END_INCL_START_BOUNDARY_CLUSTERING_KIND || clusteringKindEncoded == INCL_END_EXCL_START_BOUNDARY_CLUSTERING_KIND);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final ClusteringPrefix<?> toClusteringPrefix(List<AbstractType<?>> clusteringTypesList) {
|
||||||
|
if (clusteringKindEncoded == ROW_CLUSTERING_KIND) {
|
||||||
|
return Clustering.serializer.deserialize(clusteringBuffer(), 0, clusteringTypesList);
|
||||||
|
}
|
||||||
|
else if (clusteringColumnsBound == 0) {
|
||||||
|
return ByteArrayAccessor.factory.bound(clusteringKind);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
byte[][] values;
|
||||||
|
try (DataInputBuffer buffer = new DataInputBuffer(clusteringBuffer(), true))
|
||||||
|
{
|
||||||
|
values = ClusteringPrefix.serializer.deserializeValuesWithoutSize(buffer, clusteringColumnsBound, 0, clusteringTypesList);
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
throw new RuntimeException("Reading from an in-memory buffer shouldn't trigger an IOException", e);
|
||||||
|
}
|
||||||
|
return ByteArrayAccessor.factory.boundOrBoundary(clusteringKind, values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -56,6 +56,12 @@ public class EmptySSTableScanner extends AbstractUnfilteredPartitionIterator imp
|
||||||
return ImmutableSet.of(sstable);
|
return ImmutableSet.of(sstable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFullRange()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public long getCurrentPosition()
|
public long getCurrentPosition()
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ public interface ISSTableScanner extends UnfilteredPartitionIterator
|
||||||
public long getCurrentPosition();
|
public long getCurrentPosition();
|
||||||
public long getBytesScanned();
|
public long getBytesScanned();
|
||||||
public Set<SSTableReader> getBackingSSTables();
|
public Set<SSTableReader> getBackingSSTables();
|
||||||
|
public boolean isFullRange();
|
||||||
|
|
||||||
public static void closeAllAndPropagate(Collection<ISSTableScanner> scanners, Throwable throwable)
|
public static void closeAllAndPropagate(Collection<ISSTableScanner> scanners, Throwable throwable)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ import org.apache.cassandra.io.sstable.format.Version;
|
||||||
import org.apache.cassandra.io.sstable.format.big.RowIndexEntry;
|
import org.apache.cassandra.io.sstable.format.big.RowIndexEntry;
|
||||||
import org.apache.cassandra.io.util.DataInputPlus;
|
import org.apache.cassandra.io.util.DataInputPlus;
|
||||||
import org.apache.cassandra.io.util.DataOutputPlus;
|
import org.apache.cassandra.io.util.DataOutputPlus;
|
||||||
|
import org.apache.cassandra.schema.TableMetadata;
|
||||||
import org.apache.cassandra.utils.ObjectSizes;
|
import org.apache.cassandra.utils.ObjectSizes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -87,6 +88,17 @@ public class IndexInfo
|
||||||
return new IndexInfo.Serializer(version, header.clusteringTypes());
|
return new IndexInfo.Serializer(version, header.clusteringTypes());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String toString(TableMetadata metadata)
|
||||||
|
{
|
||||||
|
return "IndexInfo{" +
|
||||||
|
"offset=" + offset +
|
||||||
|
", width=" + width +
|
||||||
|
", firstName=" + firstName.toString(metadata) +
|
||||||
|
", lastName=" + lastName.toString(metadata) +
|
||||||
|
", endOpenMarker=" + endOpenMarker +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
|
||||||
public static class Serializer implements ISerializer<IndexInfo>
|
public static class Serializer implements ISerializer<IndexInfo>
|
||||||
{
|
{
|
||||||
// This is the default index size that we use to delta-encode width when serializing so we get better vint-encoding.
|
// This is the default index size that we use to delta-encode width when serializing so we get better vint-encoding.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,115 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.cassandra.io.sstable;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
import org.apache.cassandra.db.DecoratedKey;
|
||||||
|
import org.apache.cassandra.db.DeletionTime.ReusableDeletionTime;
|
||||||
|
import org.apache.cassandra.dht.ReusableDecoratedKey;
|
||||||
|
import org.apache.cassandra.io.util.ResizableByteBuffer;
|
||||||
|
import org.apache.cassandra.db.DeletionTime;
|
||||||
|
import org.apache.cassandra.io.util.RandomAccessReader;
|
||||||
|
|
||||||
|
public final class PartitionDescriptor extends ResizableByteBuffer
|
||||||
|
{
|
||||||
|
private long position;
|
||||||
|
private final ReusableDeletionTime deletionTime = ReusableDeletionTime.live();
|
||||||
|
private final ReusableDecoratedKey decoratedKey;
|
||||||
|
|
||||||
|
public PartitionDescriptor(ReusableDecoratedKey decoratedKey)
|
||||||
|
{
|
||||||
|
this.decoratedKey = decoratedKey;
|
||||||
|
this.decoratedKey.shadowKey(buffer(), bytes(), length());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the following structure:
|
||||||
|
* <pre>
|
||||||
|
* struct partition_header header {
|
||||||
|
* be16 key_length; // e.g. 8 if long
|
||||||
|
* byte key[key_length];
|
||||||
|
* struct deletion_time deletion_time {
|
||||||
|
* be32 local_deletion_time;
|
||||||
|
* be64 marked_for_delete_at;
|
||||||
|
* };
|
||||||
|
* };
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
void load(RandomAccessReader dataReader, DeletionTime.Serializer serializer) throws IOException
|
||||||
|
{
|
||||||
|
position = dataReader.getPosition();
|
||||||
|
|
||||||
|
boolean resize = loadShortLength(dataReader);
|
||||||
|
if (!resize)
|
||||||
|
{
|
||||||
|
decoratedKey.shadowKey(length());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
decoratedKey.shadowKey(buffer(), bytes(), length());
|
||||||
|
}
|
||||||
|
serializer.deserialize(dataReader, deletionTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long position()
|
||||||
|
{
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DeletionTime deletionTime()
|
||||||
|
{
|
||||||
|
return deletionTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DecoratedKey key()
|
||||||
|
{
|
||||||
|
return decoratedKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ByteBuffer keyBuffer() {
|
||||||
|
return super.buffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int keyLength() {
|
||||||
|
return super.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] keyBytes() {
|
||||||
|
return super.bytes();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resetPartition()
|
||||||
|
{
|
||||||
|
resetBuffer();
|
||||||
|
decoratedKey.reset();
|
||||||
|
deletionTime.resetLive();
|
||||||
|
position = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return "PartitionDescriptor{ key=" + decoratedKey +
|
||||||
|
", position=" + position +
|
||||||
|
", deletionTime=" + deletionTime +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,157 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.cassandra.io.sstable;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import javax.annotation.concurrent.NotThreadSafe;
|
||||||
|
|
||||||
|
|
||||||
|
import org.apache.cassandra.io.util.ResizableByteBuffer;
|
||||||
|
import org.apache.cassandra.io.util.FileHandle;
|
||||||
|
import org.apache.cassandra.io.util.FileUtils;
|
||||||
|
import org.apache.cassandra.io.util.RandomAccessReader;
|
||||||
|
import org.apache.cassandra.utils.Throwables;
|
||||||
|
|
||||||
|
@NotThreadSafe
|
||||||
|
public class SSTableCursorKeyReader implements AutoCloseable
|
||||||
|
{
|
||||||
|
private final FileHandle indexFile;
|
||||||
|
private final RandomAccessReader indexFileReader;
|
||||||
|
private final long initialPosition;
|
||||||
|
|
||||||
|
public static class Entry extends ResizableByteBuffer
|
||||||
|
{
|
||||||
|
private long dataPosition = -1;
|
||||||
|
private long keyPosition = -1;
|
||||||
|
|
||||||
|
public void load(RandomAccessReader indexReader) throws IOException
|
||||||
|
{
|
||||||
|
keyPosition = indexReader.getFilePointer();
|
||||||
|
super.loadShortLength(indexReader);
|
||||||
|
if (length() != 0)
|
||||||
|
{
|
||||||
|
dataPosition = indexReader.readUnsignedVInt();
|
||||||
|
// skip row index entries
|
||||||
|
int size = indexReader.readUnsignedVInt32();
|
||||||
|
if (size > 0)
|
||||||
|
indexReader.skipBytesFully(size);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
dataPosition = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public long dataPosition()
|
||||||
|
{
|
||||||
|
return dataPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long keyPosition()
|
||||||
|
{
|
||||||
|
return keyPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ByteBuffer getKey()
|
||||||
|
{
|
||||||
|
return buffer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private SSTableCursorKeyReader(FileHandle indexFile,
|
||||||
|
RandomAccessReader indexFileReader)
|
||||||
|
{
|
||||||
|
this.indexFile = indexFile;
|
||||||
|
this.indexFileReader = indexFileReader;
|
||||||
|
this.initialPosition = indexFileReader.getFilePointer();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SSTableCursorKeyReader create(RandomAccessReader indexFileReader) throws IOException
|
||||||
|
{
|
||||||
|
return new SSTableCursorKeyReader(null, indexFileReader);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({ "resource", "RedundantSuppression" }) // iFile and reader are closed in the BigTableKeyReader#close method
|
||||||
|
public static SSTableCursorKeyReader create(FileHandle indexFile) throws IOException
|
||||||
|
{
|
||||||
|
FileHandle iFile = null;
|
||||||
|
RandomAccessReader reader = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
iFile = indexFile.sharedCopy();
|
||||||
|
reader = iFile.createReader();
|
||||||
|
return new SSTableCursorKeyReader(iFile, reader);
|
||||||
|
}
|
||||||
|
catch (RuntimeException ex)
|
||||||
|
{
|
||||||
|
Throwables.closeNonNullAndAddSuppressed(ex, reader, iFile);
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close()
|
||||||
|
{
|
||||||
|
FileUtils.closeQuietly(indexFileReader);
|
||||||
|
FileUtils.closeQuietly(indexFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean advance(Entry entry) throws IOException
|
||||||
|
{
|
||||||
|
if (indexFileReader.isEOF())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
entry.load(indexFileReader);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isExhausted()
|
||||||
|
{
|
||||||
|
return indexFileReader.isEOF();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long indexPosition()
|
||||||
|
{
|
||||||
|
return indexFileReader.getFilePointer();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void seek(long position) throws IOException
|
||||||
|
{
|
||||||
|
if (position > indexLength())
|
||||||
|
throw new IndexOutOfBoundsException("The requested position exceeds the index length");
|
||||||
|
indexFileReader.seek(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long indexLength()
|
||||||
|
{
|
||||||
|
return indexFileReader.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset() throws IOException
|
||||||
|
{
|
||||||
|
indexFileReader.seek(initialPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return String.format("BigTable-SSTableCursorKeyReader(%s), indexPosition=%d", indexFile.path(), indexFileReader.getFilePointer());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,891 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.cassandra.io.sstable;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
|
||||||
|
import org.apache.cassandra.db.ReusableLivenessInfo;
|
||||||
|
import org.apache.cassandra.io.util.ResizableByteBuffer;
|
||||||
|
import org.apache.cassandra.db.ClusteringPrefix;
|
||||||
|
import org.apache.cassandra.db.Columns;
|
||||||
|
import org.apache.cassandra.db.DeletionTime;
|
||||||
|
import org.apache.cassandra.db.LivenessInfo;
|
||||||
|
import org.apache.cassandra.db.SerializationHeader;
|
||||||
|
import org.apache.cassandra.db.marshal.AbstractType;
|
||||||
|
import org.apache.cassandra.db.rows.Cell;
|
||||||
|
import org.apache.cassandra.db.rows.CellPath;
|
||||||
|
import org.apache.cassandra.db.rows.DeserializationHelper;
|
||||||
|
import org.apache.cassandra.db.rows.RangeTombstoneMarker;
|
||||||
|
import org.apache.cassandra.db.rows.Row;
|
||||||
|
import org.apache.cassandra.db.rows.SerializationHelper;
|
||||||
|
import org.apache.cassandra.db.rows.UnfilteredSerializer;
|
||||||
|
import org.apache.cassandra.io.sstable.format.SSTableReader;
|
||||||
|
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.io.util.RandomAccessReader;
|
||||||
|
import org.apache.cassandra.schema.ColumnMetadata;
|
||||||
|
import org.apache.cassandra.schema.TableMetadata;
|
||||||
|
import org.apache.cassandra.schema.TableMetadataRef;
|
||||||
|
import org.apache.cassandra.tools.Util;
|
||||||
|
import org.apache.cassandra.utils.concurrent.Ref;
|
||||||
|
|
||||||
|
import static org.apache.cassandra.io.sstable.SSTableCursorReader.State.*;
|
||||||
|
|
||||||
|
public class SSTableCursorReader implements AutoCloseable
|
||||||
|
{
|
||||||
|
private static final ColumnMetadata[] COLUMN_METADATA_TYPE = new ColumnMetadata[0];
|
||||||
|
private final boolean hasStaticColumns;
|
||||||
|
|
||||||
|
public interface State
|
||||||
|
{
|
||||||
|
/** start of file, after partition end but before EOF */
|
||||||
|
int PARTITION_START = 1;
|
||||||
|
int STATIC_ROW_START = 1 << 1;
|
||||||
|
int ROW_START = 1 << 2;
|
||||||
|
/** common to row/static row cells */
|
||||||
|
int CELL_HEADER_START = 1 << 3;
|
||||||
|
int CELL_VALUE_START = 1 << 4;
|
||||||
|
int CELL_END = 1 << 5;
|
||||||
|
int TOMBSTONE_START = 1 << 6;
|
||||||
|
|
||||||
|
/** common to rows/tombstones. Call continue(); for next unfiltered, or maybe partition end */
|
||||||
|
int UNFILTERED_END = 1 << 7;
|
||||||
|
/** at {@link UnfilteredSerializer#isEndOfPartition(int)} */
|
||||||
|
int PARTITION_END = 1 << 8;
|
||||||
|
/** EOF */
|
||||||
|
int DONE = 1 << 9;
|
||||||
|
|
||||||
|
/* Special case for seeking in file */
|
||||||
|
int SEEK = 1 << 10;
|
||||||
|
static boolean isState(int state, int mask) {
|
||||||
|
return (state & mask) != 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CellCursor {
|
||||||
|
public ReusableLivenessInfo rowLiveness;
|
||||||
|
public Columns columns;
|
||||||
|
|
||||||
|
public int columnsSize;
|
||||||
|
public int columnsIndex;
|
||||||
|
public int cellFlags;
|
||||||
|
public final ReusableLivenessInfo cellLiveness = new ReusableLivenessInfo();
|
||||||
|
public CellPath cellPath;
|
||||||
|
public AbstractType<?> cellType;
|
||||||
|
public ColumnMetadata cellColumn;
|
||||||
|
private ColumnMetadata[] columnsArray;
|
||||||
|
private AbstractType<?>[] cellTypeArray;
|
||||||
|
|
||||||
|
void init (Columns columns, ReusableLivenessInfo rowLiveness)
|
||||||
|
{
|
||||||
|
if (this.columns != columns)
|
||||||
|
{
|
||||||
|
// This will be a problem with changing columns
|
||||||
|
this.columns = columns;
|
||||||
|
columnsArray = columns.toArray(COLUMN_METADATA_TYPE);
|
||||||
|
cellTypeArray = new AbstractType<?>[columnsArray.length];
|
||||||
|
for (int i = 0; i < columnsArray.length; i++)
|
||||||
|
{
|
||||||
|
ColumnMetadata cellColumn = columnsArray[i];
|
||||||
|
cellTypeArray[i] = serializationHeader.getType(cellColumn);
|
||||||
|
}
|
||||||
|
columnsSize = columns.size();
|
||||||
|
}
|
||||||
|
this.rowLiveness = rowLiveness;
|
||||||
|
columnsIndex = 0;
|
||||||
|
cellFlags = 0;
|
||||||
|
cellPath = null;
|
||||||
|
cellType = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasNext()
|
||||||
|
{
|
||||||
|
return columnsIndex < columnsSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For Cell deserialization see {@link Cell.Serializer#deserialize}
|
||||||
|
*
|
||||||
|
* @return true if the cell has a value, false otherwise
|
||||||
|
*/
|
||||||
|
boolean readCellHeader() throws IOException
|
||||||
|
{
|
||||||
|
if (!(columnsIndex < columnsSize)) throw new IllegalStateException();
|
||||||
|
|
||||||
|
// HOTSPOT: suprisingly expensive
|
||||||
|
int currIndex = columnsIndex++;
|
||||||
|
cellColumn = columnsArray[currIndex];
|
||||||
|
cellType = cellTypeArray[currIndex];
|
||||||
|
cellFlags = dataReader.readUnsignedByte();
|
||||||
|
// TODO: specialize common case where flags == HAS_VALUE | USE_ROW_TS?
|
||||||
|
boolean hasValue = Cell.Serializer.hasValue(cellFlags);
|
||||||
|
boolean isDeleted = Cell.Serializer.isDeleted(cellFlags);
|
||||||
|
boolean isExpiring = Cell.Serializer.isExpiring(cellFlags);
|
||||||
|
boolean useRowTimestamp = Cell.Serializer.useRowTimestamp(cellFlags);
|
||||||
|
boolean useRowTTL = Cell.Serializer.useRowTTL(cellFlags);
|
||||||
|
|
||||||
|
long timestamp = useRowTimestamp ? rowLiveness.timestamp() : serializationHeader.readTimestamp(dataReader);
|
||||||
|
|
||||||
|
long localDeletionTime = useRowTTL
|
||||||
|
? rowLiveness.localExpirationTime()
|
||||||
|
: (isDeleted || isExpiring ? serializationHeader.readLocalDeletionTime(dataReader) : Cell.NO_DELETION_TIME);
|
||||||
|
|
||||||
|
int ttl = useRowTTL ? rowLiveness.ttl() : (isExpiring ? serializationHeader.readTTL(dataReader) : Cell.NO_TTL);
|
||||||
|
localDeletionTime = Cell.decodeLocalDeletionTime(localDeletionTime, ttl, deserializationHelper);
|
||||||
|
|
||||||
|
cellLiveness.reset(timestamp, ttl, localDeletionTime);
|
||||||
|
cellPath = cellColumn.isComplex()
|
||||||
|
? cellColumn.cellPathSerializer().deserialize(dataReader)
|
||||||
|
: null;
|
||||||
|
return hasValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Ref<SSTableReader> ssTableReaderRef;
|
||||||
|
private final AbstractType<?>[] clusteringColumnTypes;
|
||||||
|
private final DeserializationHelper deserializationHelper;
|
||||||
|
private final SerializationHeader serializationHeader;
|
||||||
|
|
||||||
|
// need to be closed
|
||||||
|
private final SSTableReader ssTableReader;
|
||||||
|
private final RandomAccessReader dataReader;
|
||||||
|
private final DeletionTime.Serializer deletionTimeSerializer;
|
||||||
|
|
||||||
|
private final CellCursor staticRowCellCursor = new CellCursor();
|
||||||
|
private final CellCursor rowCellCursor = new CellCursor();
|
||||||
|
|
||||||
|
|
||||||
|
private CellCursor cellCursor;
|
||||||
|
|
||||||
|
// SHARED STATIC_ROW/ROW/TOMB
|
||||||
|
private int basicUnfilteredFlags = 0;
|
||||||
|
private int extendedFlags = 0;
|
||||||
|
|
||||||
|
private int state = PARTITION_START;
|
||||||
|
|
||||||
|
public static SSTableCursorReader fromDescriptor(Descriptor desc) throws IOException
|
||||||
|
{
|
||||||
|
TableMetadata metadata = Util.metadataFromSSTable(desc);
|
||||||
|
SSTableReader reader = SSTableReader.openNoValidation(null, desc, TableMetadataRef.forOfflineTools(metadata));
|
||||||
|
return new SSTableCursorReader(reader, metadata, reader.ref());
|
||||||
|
}
|
||||||
|
|
||||||
|
public SSTableCursorReader(SSTableReader reader)
|
||||||
|
{
|
||||||
|
this(reader, reader.metadata(), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private SSTableCursorReader(SSTableReader reader, TableMetadata metadata, Ref<SSTableReader> readerRef)
|
||||||
|
{
|
||||||
|
ssTableReader = reader;
|
||||||
|
ssTableReaderRef = readerRef;
|
||||||
|
Version version = reader.descriptor.version;
|
||||||
|
deletionTimeSerializer = DeletionTime.getSerializer(version);
|
||||||
|
ImmutableList<ColumnMetadata> clusteringColumns = metadata.clusteringColumns();
|
||||||
|
int clusteringColumnCount = clusteringColumns.size();
|
||||||
|
clusteringColumnTypes = new AbstractType<?>[clusteringColumnCount];
|
||||||
|
for (int i = 0; i < clusteringColumnTypes.length; i++)
|
||||||
|
{
|
||||||
|
clusteringColumnTypes[i] = clusteringColumns.get(i).type;
|
||||||
|
}
|
||||||
|
deserializationHelper = new DeserializationHelper(metadata, version.correspondingMessagingVersion(), DeserializationHelper.Flag.LOCAL, null);
|
||||||
|
serializationHeader = reader.header;
|
||||||
|
|
||||||
|
dataReader = reader.openDataReader();
|
||||||
|
hasStaticColumns = metadata.hasStaticColumns();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close()
|
||||||
|
{
|
||||||
|
dataReader.close();
|
||||||
|
if (ssTableReaderRef != null)
|
||||||
|
ssTableReaderRef.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resetOnPartitionStart()
|
||||||
|
{
|
||||||
|
basicUnfilteredFlags = 0;
|
||||||
|
extendedFlags = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int seekPartition(long position)
|
||||||
|
{
|
||||||
|
state = SEEK;
|
||||||
|
if (position == 0)
|
||||||
|
{
|
||||||
|
dataReader.seek(position);
|
||||||
|
state = PARTITION_START;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// verify partition start is after a partition end marker
|
||||||
|
dataReader.seek(position - 1);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
basicUnfilteredFlags = dataReader.readUnsignedByte();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return corruptSSTable(e);
|
||||||
|
}
|
||||||
|
// end of partition
|
||||||
|
if (!UnfilteredSerializer.isEndOfPartition(basicUnfilteredFlags)) {
|
||||||
|
throw new IllegalArgumentException("Seeking to a partition at: " + position + " did not result in a valid state");
|
||||||
|
}
|
||||||
|
state = dataReader.isEOF() ? DONE : PARTITION_START;
|
||||||
|
}
|
||||||
|
resetOnPartitionStart();
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int seekUnfiltered(long position)
|
||||||
|
{
|
||||||
|
state = SEEK;
|
||||||
|
// partition elements (Unfiltered) have flags
|
||||||
|
dataReader.seek(position);
|
||||||
|
int state = 0;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
state = checkNextFlagsAfterStaticRowOrUnfilteredStart(false);
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
return corruptSSTable(e);
|
||||||
|
}
|
||||||
|
if (!isState(state , ROW_START | TOMBSTONE_START | DONE)) throw new IllegalStateException();
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
// struct partition {
|
||||||
|
// struct partition_header header
|
||||||
|
// optional<struct row> row
|
||||||
|
// struct unfiltered unfiltereds[];
|
||||||
|
//};
|
||||||
|
public int readPartitionHeader(PartitionDescriptor header)
|
||||||
|
{
|
||||||
|
if (state != PARTITION_START) throw new IllegalStateException();
|
||||||
|
resetOnPartitionStart();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
header.load(dataReader, deletionTimeSerializer);
|
||||||
|
return checkNextFlagsAfterPartitionStart(false);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return corruptSSTable(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// struct static_row {
|
||||||
|
// byte flags; // preloaded
|
||||||
|
// byte extended_flags; // preloaded
|
||||||
|
// varint row_body_size;
|
||||||
|
// varint prev_unfiltered_size; // for backward traversing, ignored
|
||||||
|
// optional<struct liveness_info> liveness_info;
|
||||||
|
// optional<struct delta_deletion_time> deletion_time;
|
||||||
|
// *** We read the columns in a separate method ***
|
||||||
|
// optional<varint[]> missing_columns;
|
||||||
|
// cell[] cells; // potentially only some
|
||||||
|
//};
|
||||||
|
public int readStaticRowHeader(UnfilteredDescriptor unfilteredDescriptor)
|
||||||
|
{
|
||||||
|
if (state != STATIC_ROW_START) throw new IllegalStateException();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
unfilteredDescriptor.loadStaticRow(dataReader, serializationHeader, deserializationHelper, basicUnfilteredFlags, extendedFlags);
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
return corruptSSTable(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
staticRowCellCursor.init(unfilteredDescriptor.rowColumns(), unfilteredDescriptor.livenessInfo());
|
||||||
|
cellCursor = staticRowCellCursor;
|
||||||
|
if (!staticRowCellCursor.hasNext())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return checkNextFlagsAfterStaticRowOrUnfilteredStart(false);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return corruptSSTable(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return state = State.CELL_HEADER_START;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int copyCellValue(DataOutputPlus writer, byte[] buffer) throws IOException
|
||||||
|
{
|
||||||
|
if (state != CELL_VALUE_START) throw new IllegalStateException();
|
||||||
|
if (cellCursor.cellType == null) throw new IllegalStateException();
|
||||||
|
int length = cellCursor.cellType.valueLengthIfFixed();
|
||||||
|
copyCellContents(writer, buffer, length);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!cellCursor.hasNext())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return checkNextFlagsAfterCellValuesEnd();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return corruptSSTable(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return state = State.CELL_END;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return corruptSSTable(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: move to cell cursor? maybe avoid copy through buffer?
|
||||||
|
private void copyCellContents(DataOutputPlus writer, byte[] transferBuffer, int length) throws IOException
|
||||||
|
{
|
||||||
|
if (length >= 0)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
dataReader.readFully(transferBuffer, 0, length);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
corruptSSTable(e);
|
||||||
|
}
|
||||||
|
writer.write(transferBuffer, 0, length);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
length = dataReader.readUnsignedVInt32();
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
corruptSSTable(e);
|
||||||
|
}
|
||||||
|
if (length < 0)
|
||||||
|
corruptSSTable("Corrupt (negative) value length encountered");
|
||||||
|
writer.writeUnsignedVInt32(length);
|
||||||
|
int remaining = length;
|
||||||
|
while (remaining > 0)
|
||||||
|
{
|
||||||
|
int readLength = Math.min(remaining, transferBuffer.length);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
dataReader.readFully(transferBuffer, 0, readLength);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
corruptSSTable(e);
|
||||||
|
}
|
||||||
|
writer.write(transferBuffer, 0, readLength);
|
||||||
|
remaining -= readLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// struct row {
|
||||||
|
// byte flags;
|
||||||
|
// optional<struct clustering_block[]> clustering_blocks;
|
||||||
|
// varint row_body_size;
|
||||||
|
// varint prev_unfiltered_size; // for backward traversing, ignored
|
||||||
|
// optional<struct liveness_info> liveness_info;
|
||||||
|
// optional<struct delta_deletion_time> deletion_time;
|
||||||
|
// *** We read the columns in a separate step ***
|
||||||
|
// optional<varint[]> missing_columns;
|
||||||
|
// cell[] cells; // potentially only some
|
||||||
|
//};
|
||||||
|
public int readRowHeader(UnfilteredDescriptor unfilteredDescriptor)
|
||||||
|
{
|
||||||
|
if (state != State.ROW_START) throw new IllegalStateException();
|
||||||
|
if (!UnfilteredSerializer.isRow(basicUnfilteredFlags)) throw new IllegalStateException();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
unfilteredDescriptor.loadRow(dataReader, serializationHeader, deserializationHelper, basicUnfilteredFlags);
|
||||||
|
|
||||||
|
rowCellCursor.init(unfilteredDescriptor.rowColumns(), unfilteredDescriptor.livenessInfo());
|
||||||
|
cellCursor = rowCellCursor;
|
||||||
|
if (!rowCellCursor.hasNext())
|
||||||
|
{
|
||||||
|
return checkNextFlagsAfterStaticRowOrUnfilteredStart(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return state = State.CELL_HEADER_START;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return corruptSSTable(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: introduce cell header class
|
||||||
|
public int readCellHeader()
|
||||||
|
{
|
||||||
|
if (state != State.CELL_HEADER_START) throw new IllegalStateException();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (cellCursor.readCellHeader())
|
||||||
|
{
|
||||||
|
return state = State.CELL_VALUE_START;
|
||||||
|
}
|
||||||
|
if (!cellCursor.hasNext())
|
||||||
|
return checkNextFlagsAfterCellValuesEnd();
|
||||||
|
return state = State.CELL_END;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return corruptSSTable(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int skipCellValue()
|
||||||
|
{
|
||||||
|
if (state != State.CELL_VALUE_START) throw new IllegalStateException();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
cellCursor.cellType.skipValue(dataReader);
|
||||||
|
return !cellCursor.hasNext() ? checkNextFlagsAfterCellValuesEnd() : (state = State.CELL_HEADER_START);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return corruptSSTable(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See: {@link org.apache.cassandra.db.rows.UnfilteredSerializer#serialize(RangeTombstoneMarker, SerializationHelper, DataOutputPlus, long, int)}
|
||||||
|
* <pre>
|
||||||
|
* struct range_tombstone_marker {
|
||||||
|
* byte flags = IS_MARKER;
|
||||||
|
* byte kind_ordinal;
|
||||||
|
* be16 bound_values_count;
|
||||||
|
* struct clustering_block[] clustering_blocks;
|
||||||
|
* varint marker_body_size;
|
||||||
|
* varint prev_unfiltered_size;
|
||||||
|
* };
|
||||||
|
* struct range_tombstone_bound_marker : range_tombstone_marker {
|
||||||
|
* struct delta_deletion_time deletion_time;
|
||||||
|
* };
|
||||||
|
* struct range_tombstone_boundary_marker : range_tombstone_marker {
|
||||||
|
* struct delta_deletion_time end_deletion_time;
|
||||||
|
* struct delta_deletion_time start_deletion_time;
|
||||||
|
* };
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public int readTombstoneMarker(UnfilteredDescriptor unfilteredDescriptor)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (state != TOMBSTONE_START) throw new IllegalStateException();
|
||||||
|
if (!UnfilteredSerializer.isTombstoneMarker(basicUnfilteredFlags)) throw new IllegalStateException();
|
||||||
|
unfilteredDescriptor.loadTombstone(dataReader, serializationHeader, basicUnfilteredFlags);
|
||||||
|
return checkNextFlagsAfterStaticRowOrUnfilteredStart(false);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return corruptSSTable(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link ClusteringPrefix.Serializer#deserializeValuesWithoutSize}
|
||||||
|
*/
|
||||||
|
static void readUnfilteredClustering(RandomAccessReader dataReader, AbstractType<?>[] types, int clusteringColumnsBound, ResizableByteBuffer clustering) throws IOException
|
||||||
|
{
|
||||||
|
clustering.resetBuffer();
|
||||||
|
if (clusteringColumnsBound == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
long clusteringBlockHeader = 0;
|
||||||
|
int fixedLengthClusteringLength = 0;
|
||||||
|
for (int clusteringIndex = 0; clusteringIndex < clusteringColumnsBound; clusteringIndex++)
|
||||||
|
{
|
||||||
|
// struct clustering_block {
|
||||||
|
// varint clustering_block_header;
|
||||||
|
// simple_cell[] clustering_cells;
|
||||||
|
// };
|
||||||
|
if (clusteringIndex % 32 == 0)
|
||||||
|
{
|
||||||
|
if (fixedLengthClusteringLength != 0) {
|
||||||
|
clustering.loadPart(dataReader, fixedLengthClusteringLength);
|
||||||
|
fixedLengthClusteringLength = 0;
|
||||||
|
}
|
||||||
|
clusteringBlockHeader = dataReader.readUnsignedVInt();
|
||||||
|
clustering.writeUnsignedVInt(clusteringBlockHeader);
|
||||||
|
}
|
||||||
|
|
||||||
|
// load value if present
|
||||||
|
if ((clusteringBlockHeader & 0b11) == 0)
|
||||||
|
{
|
||||||
|
AbstractType<?> type = types[clusteringIndex];
|
||||||
|
if (type.isValueLengthFixed())
|
||||||
|
{
|
||||||
|
fixedLengthClusteringLength += type.valueLengthIfFixed();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (fixedLengthClusteringLength != 0) {
|
||||||
|
clustering.loadPart(dataReader, fixedLengthClusteringLength);
|
||||||
|
fixedLengthClusteringLength = 0;
|
||||||
|
}
|
||||||
|
int varLength = dataReader.readUnsignedVInt32();
|
||||||
|
clustering.writeUnsignedVInt(varLength);
|
||||||
|
clustering.loadPart(dataReader, varLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
clusteringBlockHeader = clusteringBlockHeader >>> 2;
|
||||||
|
}
|
||||||
|
if (fixedLengthClusteringLength != 0) clustering.loadPart(dataReader, fixedLengthClusteringLength);
|
||||||
|
if (clusteringBlockHeader != 0) {
|
||||||
|
throw new IOException("Clustering block upper bits (those not associated with keys) expected to be 0:" + clusteringBlockHeader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void skipClustering(RandomAccessReader dataReader, AbstractType<?>[] types, int clusteringColumnsBound) throws IOException
|
||||||
|
{
|
||||||
|
long clusteringBlockHeader = 0;
|
||||||
|
for (int clusteringIndex = 0; clusteringIndex < clusteringColumnsBound; clusteringIndex++)
|
||||||
|
{
|
||||||
|
// struct clustering_block {
|
||||||
|
// varint clustering_block_header;
|
||||||
|
// simple_cell[] clustering_cells;
|
||||||
|
// };
|
||||||
|
if (clusteringIndex % 32 == 0)
|
||||||
|
{
|
||||||
|
clusteringBlockHeader = dataReader.readUnsignedVInt();
|
||||||
|
}
|
||||||
|
// skip value if present
|
||||||
|
if ((clusteringBlockHeader & 0b11) == 0)
|
||||||
|
{
|
||||||
|
AbstractType<?> type = types[clusteringIndex];
|
||||||
|
int len = type.isValueLengthFixed() ? type.valueLengthIfFixed() : dataReader.readUnsignedVInt32();
|
||||||
|
dataReader.skipBytes(len);
|
||||||
|
}
|
||||||
|
clusteringBlockHeader = clusteringBlockHeader >>> 2;
|
||||||
|
}
|
||||||
|
if (clusteringBlockHeader != 0) {
|
||||||
|
throw new IOException("Clustering block upper bits (those not associated with keys) expected to be 0:" + clusteringBlockHeader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link UnfilteredSerializer#deserializeRowBody(DataInputPlus, SerializationHeader, DeserializationHelper, int, int, Row.Builder)}
|
||||||
|
*/
|
||||||
|
static void readLivenessInfo(RandomAccessReader dataReader, SerializationHeader serializationHeader, DeserializationHelper deserializationHelper, int flags, ReusableLivenessInfo livenessInfo) throws IOException
|
||||||
|
{
|
||||||
|
long timestamp = LivenessInfo.NO_TIMESTAMP;
|
||||||
|
int ttl = LivenessInfo.NO_TTL;
|
||||||
|
long localExpirationTime = LivenessInfo.NO_EXPIRATION_TIME;
|
||||||
|
if (UnfilteredSerializer.hasTimestamp(flags))
|
||||||
|
{
|
||||||
|
// struct liveness_info {
|
||||||
|
// varint64 delta_timestamp;
|
||||||
|
// optional<varint32> delta_ttl;
|
||||||
|
// optional<varint64> delta_local_deletion_time;
|
||||||
|
//};
|
||||||
|
timestamp = serializationHeader.readTimestamp(dataReader);
|
||||||
|
if (UnfilteredSerializer.hasTTL(flags))
|
||||||
|
{
|
||||||
|
ttl = serializationHeader.readTTL(dataReader);
|
||||||
|
localExpirationTime = Cell.decodeLocalDeletionTime(serializationHeader.readLocalDeletionTime(dataReader), ttl, deserializationHelper);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
livenessInfo.reset(timestamp, ttl, localExpirationTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
// SKIPPING
|
||||||
|
public int skipPartition()
|
||||||
|
{
|
||||||
|
if (state == PARTITION_END)
|
||||||
|
return continueReading();
|
||||||
|
|
||||||
|
if (state == PARTITION_START)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
int partitionKeyLength = dataReader.readUnsignedShort();
|
||||||
|
dataReader.skipBytes(partitionKeyLength);
|
||||||
|
|
||||||
|
// PARTITION DELETION TIME
|
||||||
|
deletionTimeSerializer.skip(dataReader);
|
||||||
|
checkNextFlagsAfterPartitionStart(true);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return corruptSSTable(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!isState(state, STATIC_ROW_START | ROW_START | TOMBSTONE_START | PARTITION_END))
|
||||||
|
{
|
||||||
|
throw new IllegalStateException("Unexpected state: " + state);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!isState(state,PARTITION_START | DONE))
|
||||||
|
{
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case STATIC_ROW_START:
|
||||||
|
state = skipStaticRow(true);
|
||||||
|
break;
|
||||||
|
case ROW_START:
|
||||||
|
case TOMBSTONE_START:
|
||||||
|
state = skipUnfiltered(true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int skipStaticRow(boolean autoContinue)
|
||||||
|
{
|
||||||
|
if (state != State.STATIC_ROW_START) throw new IllegalStateException();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
long rowSize = dataReader.readUnsignedVInt();
|
||||||
|
dataReader.seek(dataReader.getPosition() + rowSize);
|
||||||
|
return checkNextFlagsAfterStaticRowOrUnfilteredStart(autoContinue);
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
return corruptSSTable(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int skipUnfiltered(boolean autoContinue)
|
||||||
|
{
|
||||||
|
if (!isState(state, ROW_START | TOMBSTONE_START))
|
||||||
|
throw new IllegalStateException();
|
||||||
|
|
||||||
|
AbstractType<?>[] types = clusteringColumnTypes;
|
||||||
|
int clusteringColumnsBound = types.length;
|
||||||
|
// tombstone markers have `kind` & `clusteringColumnsBound`
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!UnfilteredSerializer.isRow(basicUnfilteredFlags))
|
||||||
|
{
|
||||||
|
dataReader.readByte();// byte kind =
|
||||||
|
clusteringColumnsBound = dataReader.readUnsignedShort();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* {@link org.apache.cassandra.db.ClusteringPrefix.Deserializer}
|
||||||
|
*/
|
||||||
|
skipClustering(dataReader, types, clusteringColumnsBound);
|
||||||
|
// same for row/tombstone
|
||||||
|
long rowSize = dataReader.readUnsignedVInt();
|
||||||
|
dataReader.seek(dataReader.getPosition() + rowSize);
|
||||||
|
|
||||||
|
return checkNextFlagsAfterStaticRowOrUnfilteredStart(autoContinue);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return corruptSSTable(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int skipRowCells(long unfilteredDataStart, long unfilteredSize, boolean autoContinue)
|
||||||
|
{
|
||||||
|
if (!(isState(state,CELL_HEADER_START | CELL_VALUE_START | CELL_END))) throw new IllegalStateException();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
dataReader.seek(unfilteredDataStart + unfilteredSize);
|
||||||
|
return checkNextFlagsAfterStaticRowOrUnfilteredStart(autoContinue);
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
return corruptSSTable(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int continueReading() {
|
||||||
|
// TODO: can be optimized by pre-calculating next state when the flags are read
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case PARTITION_END:
|
||||||
|
state = dataReader.isEOF() ? DONE : PARTITION_START;
|
||||||
|
break;
|
||||||
|
case UNFILTERED_END:
|
||||||
|
if (UnfilteredSerializer.isEndOfPartition(basicUnfilteredFlags))
|
||||||
|
{
|
||||||
|
state = PARTITION_END;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
state = UnfilteredSerializer.isRow(basicUnfilteredFlags) ? ROW_START : TOMBSTONE_START;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CELL_END:
|
||||||
|
if (cellCursor.hasNext())
|
||||||
|
{
|
||||||
|
state = CELL_HEADER_START;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
state = UNFILTERED_END;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException("Cannot continue reading in current state: " + state);
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int checkNextFlagsAfterPartitionStart(boolean autoContinue) throws IOException
|
||||||
|
{
|
||||||
|
long preFlagsPosition = dataReader.getPosition();
|
||||||
|
basicUnfilteredFlags = dataReader.readUnsignedByte();
|
||||||
|
if (UnfilteredSerializer.isEndOfPartition(basicUnfilteredFlags))
|
||||||
|
{
|
||||||
|
state = !autoContinue ? PARTITION_END :
|
||||||
|
dataReader.isEOF() ? DONE : PARTITION_START;
|
||||||
|
}
|
||||||
|
else if (UnfilteredSerializer.isExtended(basicUnfilteredFlags))
|
||||||
|
{
|
||||||
|
state = STATIC_ROW_START;
|
||||||
|
extendedFlags = dataReader.readUnsignedByte();
|
||||||
|
validateStaticRowFlags(preFlagsPosition);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
state = UnfilteredSerializer.isRow(basicUnfilteredFlags) ? ROW_START : TOMBSTONE_START;
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateStaticRowFlags(long preFlagsPosition)
|
||||||
|
{
|
||||||
|
if (!UnfilteredSerializer.isStatic(extendedFlags))
|
||||||
|
{
|
||||||
|
corruptSSTable("Row at: " + preFlagsPosition + " has extended flags but is not static, extendedFlags: " + extendedFlags);
|
||||||
|
}
|
||||||
|
if (!UnfilteredSerializer.isRow(basicUnfilteredFlags))
|
||||||
|
{
|
||||||
|
corruptSSTable("Static row at: " + preFlagsPosition + " is not a row, flags: " + basicUnfilteredFlags);
|
||||||
|
}
|
||||||
|
if (!hasStaticColumns)
|
||||||
|
{
|
||||||
|
corruptSSTable("Row at: " + preFlagsPosition + " is static, but table has no static columns " + ssTableReader.metadata());
|
||||||
|
}
|
||||||
|
if (UnfilteredSerializer.deletionIsShadowable(extendedFlags))
|
||||||
|
{
|
||||||
|
throw new UnsupportedOperationException("Static row at: " + preFlagsPosition + " has deletionIsShadowable, which is deprecated since 4.0");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int checkNextFlagsAfterStaticRowOrUnfilteredStart(boolean autoContinue) throws IOException
|
||||||
|
{
|
||||||
|
int flags = this.basicUnfilteredFlags = dataReader.readUnsignedByte();
|
||||||
|
if (UnfilteredSerializer.isExtended(flags))
|
||||||
|
{
|
||||||
|
corruptSSTable("Unexpected static row (flags=" + flags + ") mid-partition, at position: " + (dataReader.getPosition() - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!autoContinue) {
|
||||||
|
return this.state = UNFILTERED_END;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return this.state = nextStateMidPartition(flags);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int checkNextFlagsAfterCellValuesEnd() throws IOException
|
||||||
|
{
|
||||||
|
int flags = this.basicUnfilteredFlags = dataReader.readUnsignedByte();
|
||||||
|
if (UnfilteredSerializer.isExtended(flags))
|
||||||
|
{
|
||||||
|
corruptSSTable("Unexpected static row (flags=" + flags + ") mid-partition, at position: " + (dataReader.getPosition() - 1));
|
||||||
|
}
|
||||||
|
return this.state = CELL_END;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int corruptSSTable(Exception e)
|
||||||
|
{
|
||||||
|
ssTableReader.markSuspect();
|
||||||
|
if (e instanceof CorruptSSTableException)
|
||||||
|
throw (CorruptSSTableException) e;
|
||||||
|
|
||||||
|
throw new CorruptSSTableException(e, ssTableReader.getFilename());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int corruptSSTable(String message)
|
||||||
|
{
|
||||||
|
return corruptSSTable(new IllegalStateException(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
private int nextStateMidPartition(int basicUnfilteredFlags)
|
||||||
|
{
|
||||||
|
if (UnfilteredSerializer.isEndOfPartition(basicUnfilteredFlags))
|
||||||
|
{
|
||||||
|
return dataReader.isEOF() ? DONE : PARTITION_START;
|
||||||
|
}
|
||||||
|
else if (UnfilteredSerializer.isRow(basicUnfilteredFlags))
|
||||||
|
{
|
||||||
|
return ROW_START;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return TOMBSTONE_START;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEOF() {
|
||||||
|
return state == DONE || dataReader.isEOF();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int state()
|
||||||
|
{
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long position() {
|
||||||
|
return dataReader.getFilePointer();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long uncompressedLength()
|
||||||
|
{
|
||||||
|
return ssTableReader.uncompressedLength();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SSTableReader ssTableReader()
|
||||||
|
{
|
||||||
|
return ssTableReader;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CellCursor cellCursor()
|
||||||
|
{
|
||||||
|
return cellCursor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,689 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.cassandra.io.sstable;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
import com.google.common.primitives.Ints;
|
||||||
|
|
||||||
|
import org.agrona.collections.IntArrayList;
|
||||||
|
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||||
|
import org.apache.cassandra.db.ClusteringPrefix;
|
||||||
|
import org.apache.cassandra.db.DecoratedKey;
|
||||||
|
import org.apache.cassandra.db.DeletionTime;
|
||||||
|
import org.apache.cassandra.db.DeletionTime.ReusableDeletionTime;
|
||||||
|
import org.apache.cassandra.db.LivenessInfo;
|
||||||
|
import org.apache.cassandra.db.ReusableLivenessInfo;
|
||||||
|
import org.apache.cassandra.db.SerializationHeader;
|
||||||
|
import org.apache.cassandra.db.guardrails.Guardrails;
|
||||||
|
import org.apache.cassandra.db.guardrails.Threshold;
|
||||||
|
import org.apache.cassandra.db.marshal.AbstractType;
|
||||||
|
import org.apache.cassandra.db.rows.Cell;
|
||||||
|
import org.apache.cassandra.db.rows.Row;
|
||||||
|
import org.apache.cassandra.db.rows.SerializationHelper;
|
||||||
|
import org.apache.cassandra.db.rows.Unfiltered;
|
||||||
|
import org.apache.cassandra.db.rows.UnfilteredSerializer;
|
||||||
|
import org.apache.cassandra.dht.IPartitioner;
|
||||||
|
import org.apache.cassandra.io.FSWriteError;
|
||||||
|
import org.apache.cassandra.io.sstable.format.SSTableReader;
|
||||||
|
import org.apache.cassandra.io.sstable.format.SortedTableWriter;
|
||||||
|
import org.apache.cassandra.io.sstable.format.big.BigFormatPartitionWriter;
|
||||||
|
import org.apache.cassandra.io.sstable.format.big.BigTableWriter;
|
||||||
|
import org.apache.cassandra.io.sstable.format.big.RowIndexEntry;
|
||||||
|
import org.apache.cassandra.io.sstable.metadata.MetadataCollector;
|
||||||
|
import org.apache.cassandra.io.util.DataOutputBuffer;
|
||||||
|
import org.apache.cassandra.io.util.DataOutputPlus;
|
||||||
|
import org.apache.cassandra.io.util.SequentialWriter;
|
||||||
|
import org.apache.cassandra.schema.ColumnMetadata;
|
||||||
|
import org.apache.cassandra.utils.BloomFilter;
|
||||||
|
import org.apache.cassandra.utils.ByteArrayUtil;
|
||||||
|
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||||
|
import org.apache.cassandra.utils.concurrent.Ref;
|
||||||
|
|
||||||
|
import static org.apache.cassandra.db.rows.UnfilteredSerializer.*;
|
||||||
|
|
||||||
|
public class SSTableCursorWriter implements AutoCloseable
|
||||||
|
{
|
||||||
|
private static final UnfilteredSerializer SERIALIZER = UnfilteredSerializer.serializer;
|
||||||
|
private static final ColumnMetadata[] EMPTY_COL_META = new ColumnMetadata[0];
|
||||||
|
private final SortedTableWriter<?,?> ssTableWriter;
|
||||||
|
private final SequentialWriter dataWriter;
|
||||||
|
private final SortedTableWriter.AbstractIndexWriter indexWriter;
|
||||||
|
private final DeletionTime.Serializer deletionTimeSerializer;
|
||||||
|
private final MetadataCollector metadataCollector;
|
||||||
|
private final SerializationHeader serializationHeader;
|
||||||
|
/**
|
||||||
|
* See: {@link BloomFilter#reusableIndexes}
|
||||||
|
*/
|
||||||
|
private final long[] reusableIndexes = new long[21];
|
||||||
|
private final boolean hasStaticColumns;
|
||||||
|
|
||||||
|
private long partitionStart;
|
||||||
|
// ROW contents, needed because of the order of writing and the var int fields
|
||||||
|
private int rowFlags; // discovered as we go along
|
||||||
|
private int rowExtendedFlags;
|
||||||
|
private final DataOutputBuffer rowHeaderBuffer = new DataOutputBuffer(); // holds the contents between FLAGS and SIZE
|
||||||
|
private final DataOutputBuffer rowBuffer = new DataOutputBuffer();
|
||||||
|
private final ReusableDeletionTime openMarker = ReusableDeletionTime.live();
|
||||||
|
|
||||||
|
private final ColumnMetadata[] staticColumns;
|
||||||
|
private final ColumnMetadata[] regularColumns;
|
||||||
|
private final IntArrayList missingColumns = new IntArrayList();
|
||||||
|
private ColumnMetadata[] columns; // points to static/regular
|
||||||
|
private int columnsWrittenCount = 0;
|
||||||
|
private int nextCellIndex = 0;
|
||||||
|
// Index info
|
||||||
|
private final DataOutputBuffer rowIndexEntries = new DataOutputBuffer();
|
||||||
|
private final IntArrayList rowIndexEntriesOffsets = new IntArrayList();
|
||||||
|
private final ClusteringDescriptor rowIndexEntryLastClustering;
|
||||||
|
private int indexBlockStartOffset;
|
||||||
|
private int rowIndexEntryOffset;
|
||||||
|
private final int indexBlockThreshold;
|
||||||
|
|
||||||
|
private SSTableCursorWriter(
|
||||||
|
Descriptor desc,
|
||||||
|
SortedTableWriter<?,?> ssTableWriter,
|
||||||
|
SequentialWriter dataWriter,
|
||||||
|
SortedTableWriter.AbstractIndexWriter indexWriter,
|
||||||
|
MetadataCollector metadataCollector,
|
||||||
|
SerializationHeader serializationHeader)
|
||||||
|
{
|
||||||
|
this.ssTableWriter = ssTableWriter;
|
||||||
|
this.dataWriter = dataWriter;
|
||||||
|
this.indexWriter = indexWriter;
|
||||||
|
this.deletionTimeSerializer = DeletionTime.getSerializer(desc.version);
|
||||||
|
this.metadataCollector = metadataCollector;
|
||||||
|
this.serializationHeader = serializationHeader;
|
||||||
|
hasStaticColumns = serializationHeader.hasStatic();
|
||||||
|
staticColumns = hasStaticColumns ? serializationHeader.columns(true).toArray(EMPTY_COL_META) : EMPTY_COL_META;
|
||||||
|
regularColumns = serializationHeader.columns(false).toArray(EMPTY_COL_META);
|
||||||
|
this.indexBlockThreshold = DatabaseDescriptor.getColumnIndexSize(BigFormatPartitionWriter.DEFAULT_GRANULARITY);
|
||||||
|
rowIndexEntryLastClustering = new ClusteringDescriptor(serializationHeader.clusteringTypes().toArray(AbstractType[]::new));
|
||||||
|
}
|
||||||
|
|
||||||
|
public SSTableCursorWriter(SortedTableWriter<?,?> ssTableWriter)
|
||||||
|
{
|
||||||
|
this(ssTableWriter.descriptor,
|
||||||
|
ssTableWriter,
|
||||||
|
ssTableWriter.dataWriter,
|
||||||
|
ssTableWriter.indexWriter,
|
||||||
|
ssTableWriter.metadataCollector,
|
||||||
|
ssTableWriter.partitionWriter.getHeader());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close()
|
||||||
|
{
|
||||||
|
SSTableReader finish = ssTableWriter.finish(false);
|
||||||
|
if (finish != null) {
|
||||||
|
Ref<SSTableReader> ref = finish.ref();
|
||||||
|
if (ref != null) ref.close();
|
||||||
|
}
|
||||||
|
ssTableWriter.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getPartitionStart()
|
||||||
|
{
|
||||||
|
return partitionStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getPosition()
|
||||||
|
{
|
||||||
|
return dataWriter.position();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int writePartitionStart(byte[] partitionKey, int partitionKeyLength, DeletionTime partitionDeletionTime) throws IOException
|
||||||
|
{
|
||||||
|
rowIndexEntries.clear();
|
||||||
|
rowIndexEntriesOffsets.clear();
|
||||||
|
rowIndexEntryOffset = 0;
|
||||||
|
openMarker.resetLive();
|
||||||
|
|
||||||
|
partitionStart = dataWriter.position();
|
||||||
|
writePartitionHeader(partitionKey, partitionKeyLength, partitionDeletionTime);
|
||||||
|
updateIndexBlockStartOffset(dataWriter.position());
|
||||||
|
return indexBlockStartOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writePartitionEnd(byte[] partitionKey, int partitionKeyLength, DeletionTime partitionDeletionTime, int headerLength) throws IOException
|
||||||
|
{
|
||||||
|
SERIALIZER.writeEndOfPartition(dataWriter);
|
||||||
|
long partitionEnd = dataWriter.position();
|
||||||
|
long partitionSize = partitionEnd - partitionStart;
|
||||||
|
addPartitionMetadata(partitionKey, partitionKeyLength, partitionSize, partitionDeletionTime);
|
||||||
|
|
||||||
|
/** {@link SortedTableWriter#endPartition(DecoratedKey, DeletionTime)}
|
||||||
|
lastWrittenKey = key; // tracked for verification, see {@link SortedTableWriter#verifyPartition(DecoratedKey)}, checking the key size and sorting
|
||||||
|
// first/last are retained for metadata {@link org.apache.cassandra.io.sstable.format.SSTableWriter#finalizeMetadata()}. They are also exposed via
|
||||||
|
// getters from the writer, but usage is unclear.
|
||||||
|
last = lastWrittenKey;
|
||||||
|
if (first == null)
|
||||||
|
first = lastWrittenKey;
|
||||||
|
// this is implemented differently for BIG/BTI
|
||||||
|
createRowIndexEntry(key, partitionLevelDeletion, partitionEnd - 1);
|
||||||
|
*/
|
||||||
|
appendBIGIndex(partitionKey, partitionKeyLength, partitionStart, headerLength, partitionDeletionTime, partitionEnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void appendBIGIndex(byte[] key, int keyLength, long partitionStart, int headerLength, DeletionTime partitionDeletionTime, long partitionEnd) throws IOException
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* {@link BigTableWriter#createRowIndexEntry(DecoratedKey, DeletionTime, long)}
|
||||||
|
* {@link BigTableWriter.IndexWriter#append(DecoratedKey, RowIndexEntry, long, ByteBuffer)}
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
BigTableWriter.IndexWriter indexWriter = (BigTableWriter.IndexWriter) this.indexWriter;
|
||||||
|
SequentialWriter indexFileWriter = indexWriter.writer;
|
||||||
|
((BloomFilter)indexWriter.bf).add(key, 0, keyLength, reusableIndexes);
|
||||||
|
long indexStart = indexFileWriter.position();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ByteArrayUtil.writeWithShortLength(key, 0, keyLength, indexFileWriter);
|
||||||
|
|
||||||
|
indexFileWriter.writeUnsignedVInt(partitionStart);
|
||||||
|
|
||||||
|
// if the list of entries has one or fewer entries, no point in index entries.
|
||||||
|
/** See: {@link org.apache.cassandra.io.sstable.format.big.RowIndexEntry#create} */
|
||||||
|
if (rowIndexEntriesOffsets.size() <= 1)
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* {@link RowIndexEntry#serialize(DataOutputPlus, ByteBuffer)}
|
||||||
|
*/
|
||||||
|
indexFileWriter.writeUnsignedVInt32(0); // size
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// add last block
|
||||||
|
long indexBlockSize = (partitionEnd - partitionStart - 1) - indexBlockStartOffset;
|
||||||
|
if (indexBlockSize != 0) {
|
||||||
|
addIndexBlock(partitionEnd - 1, indexBlockSize);
|
||||||
|
}
|
||||||
|
// if we have intermeddiate index info elements we also need to serialize the partitionDeletionTime
|
||||||
|
/** {@link RowIndexEntry.IndexedEntry#serialize(DataOutputPlus, ByteBuffer) */
|
||||||
|
// size up to the offsets?
|
||||||
|
int endOfEntries = rowIndexEntries.getLength();
|
||||||
|
// Write the headerLength, partitionDeletionTime and rowIndexEntriesOffsets.size() after the entries,
|
||||||
|
// just to calculate size.
|
||||||
|
rowIndexEntries.writeUnsignedVInt((long)headerLength);
|
||||||
|
deletionTimeSerializer.serialize(partitionDeletionTime, rowIndexEntries);
|
||||||
|
|
||||||
|
rowIndexEntries.writeUnsignedVInt32(rowIndexEntriesOffsets.size()); // number of entries
|
||||||
|
|
||||||
|
// bytes until offsets
|
||||||
|
int entriesAndOffsetsSize = rowIndexEntries.getLength() + rowIndexEntriesOffsets.size() * 4;
|
||||||
|
assert entriesAndOffsetsSize > 0;
|
||||||
|
indexFileWriter.writeUnsignedVInt32(entriesAndOffsetsSize); // size != 0
|
||||||
|
// copy the header elements
|
||||||
|
indexFileWriter.write(rowIndexEntries.getData(), endOfEntries, rowIndexEntries.getLength() - endOfEntries);
|
||||||
|
indexFileWriter.write(rowIndexEntries.getData(), 0, endOfEntries);
|
||||||
|
for (int i = 0; i < rowIndexEntriesOffsets.size(); i++)
|
||||||
|
{
|
||||||
|
int offset = rowIndexEntriesOffsets.get(i);
|
||||||
|
indexFileWriter.writeInt(offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
throw new FSWriteError(e, indexFileWriter.getPath());
|
||||||
|
}
|
||||||
|
indexWriter.summary.maybeAddEntry(key, 0, keyLength, indexStart);
|
||||||
|
}
|
||||||
|
|
||||||
|
final long guardrailsPartitionSizeWarning = Guardrails.partitionSize.warnValue(null);
|
||||||
|
final long guardrailsPartitionTombstonesWarning = Guardrails.partitionTombstones.warnValue(null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* update metadata like {@link SortedTableWriter#endPartition} and {@link SortedTableWriter#startPartition}
|
||||||
|
*/
|
||||||
|
private void addPartitionMetadata(byte[] partitionKey, int partitionKeyLength, long partitionSize, DeletionTime partitionDeletionTime)
|
||||||
|
{
|
||||||
|
if (partitionSize > guardrailsPartitionSizeWarning)
|
||||||
|
guardPartitionThreshold(Guardrails.partitionSize, partitionKey, partitionKeyLength, partitionSize);
|
||||||
|
|
||||||
|
if (metadataCollector.totalTombstones > guardrailsPartitionTombstonesWarning)
|
||||||
|
guardPartitionThreshold(Guardrails.partitionTombstones, partitionKey, partitionKeyLength, metadataCollector.totalTombstones);
|
||||||
|
|
||||||
|
metadataCollector.updatePartitionDeletion(partitionDeletionTime);
|
||||||
|
metadataCollector.addPartitionSizeInBytes(partitionSize);
|
||||||
|
metadataCollector.addKey(partitionKey, 0, partitionKeyLength);
|
||||||
|
metadataCollector.addCellPerPartitionCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void guardPartitionThreshold(Threshold guardrail, byte[] partitionKey, int partitionKeyLength, long size)
|
||||||
|
{
|
||||||
|
if (guardrail.triggersOn(size, null))
|
||||||
|
{
|
||||||
|
String message = String.format("%s.%s:%s on sstable %s",
|
||||||
|
ssTableWriter.metadata().keyspace,
|
||||||
|
ssTableWriter.metadata().name,
|
||||||
|
ssTableWriter.metadata().partitionKeyType.getString(ByteBuffer.wrap(partitionKey, 0, partitionKeyLength)),
|
||||||
|
ssTableWriter.getFilename());
|
||||||
|
guardrail.guard(size, message, true, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writePartitionHeader(byte[] partitionKey, int partitionKeyLength, DeletionTime partitionDeletionTime) throws IOException
|
||||||
|
{
|
||||||
|
dataWriter.writeShort(partitionKeyLength);
|
||||||
|
dataWriter.write(partitionKey, 0, partitionKeyLength);
|
||||||
|
deletionTimeSerializer.serialize(partitionDeletionTime, dataWriter);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean writeEmptyStaticRow() throws IOException
|
||||||
|
{
|
||||||
|
if (!hasStaticColumns)
|
||||||
|
return false;
|
||||||
|
rowFlags = UnfilteredSerializer.EXTENSION_FLAG;
|
||||||
|
rowExtendedFlags = UnfilteredSerializer.IS_STATIC;
|
||||||
|
columns = staticColumns;
|
||||||
|
// TOD: we should be able to skip the use of the row buffers in this special case, maybe it doesn't matter
|
||||||
|
rowHeaderBuffer.clear();
|
||||||
|
// NOTE: if we are to write this value (which is not used), this is where we should compute it.
|
||||||
|
rowHeaderBuffer.writeUnsignedVInt32(0);
|
||||||
|
rowBuffer.clear();
|
||||||
|
columnsWrittenCount = 0;
|
||||||
|
missingColumns.clear();
|
||||||
|
writeRowEnd(null, false);
|
||||||
|
|
||||||
|
updateIndexBlockStartOffset(dataWriter.position());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeRowStart(LivenessInfo livenessInfo, DeletionTime deletionTime, boolean isStatic) throws IOException
|
||||||
|
{
|
||||||
|
if (isStatic) {
|
||||||
|
rowFlags = UnfilteredSerializer.EXTENSION_FLAG;
|
||||||
|
rowExtendedFlags = UnfilteredSerializer.IS_STATIC;
|
||||||
|
columns = staticColumns;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
rowFlags = 0;
|
||||||
|
rowExtendedFlags = 0;
|
||||||
|
columns = regularColumns;
|
||||||
|
}
|
||||||
|
// NOTE: Data after this point needs a computed ahead of write size. This, combined with the cost of rewriting
|
||||||
|
// the size after the writing completes, means we have to buffer the row timestamps (most likely to differ in length)
|
||||||
|
// and the row columns data (will differ if they use their own timestamps, probably). Unfortunate.
|
||||||
|
// rest of header
|
||||||
|
rowHeaderBuffer.clear();
|
||||||
|
missingColumns.clear();
|
||||||
|
rowBuffer.clear();
|
||||||
|
columnsWrittenCount = 0;
|
||||||
|
nextCellIndex = 0;
|
||||||
|
|
||||||
|
// NOTE: if we are to write this value (which is not used), this is where we should compute it.
|
||||||
|
rowHeaderBuffer.writeUnsignedVInt32(0);
|
||||||
|
|
||||||
|
// copy TS/TTL/deletion data
|
||||||
|
rowFlags |= writeRowTimeData(livenessInfo, deletionTime, rowHeaderBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See {@link UnfilteredSerializer#serialize(Row, SerializationHelper, DataOutputPlus, long, int)}
|
||||||
|
*/
|
||||||
|
private int writeRowTimeData(LivenessInfo livenessInfo, DeletionTime deletionTime, DataOutputPlus writer) throws IOException
|
||||||
|
{
|
||||||
|
int flags = 0;
|
||||||
|
boolean writtenLivenessMetadata = false;
|
||||||
|
|
||||||
|
if (!livenessInfo.isEmpty())
|
||||||
|
{
|
||||||
|
flags |= HAS_TIMESTAMP;
|
||||||
|
serializationHeader.writeTimestamp(livenessInfo.timestamp(), writer);
|
||||||
|
metadataCollector.update(livenessInfo);
|
||||||
|
writtenLivenessMetadata = true;
|
||||||
|
}
|
||||||
|
if (livenessInfo.isExpiring())
|
||||||
|
{
|
||||||
|
flags |= HAS_TTL;
|
||||||
|
serializationHeader.writeTTL(livenessInfo.ttl(), writer);
|
||||||
|
serializationHeader.writeLocalDeletionTime(livenessInfo.localExpirationTime(), writer);
|
||||||
|
if (!writtenLivenessMetadata) metadataCollector.update(livenessInfo);
|
||||||
|
}
|
||||||
|
if (!deletionTime.isLive())
|
||||||
|
{
|
||||||
|
flags |= HAS_DELETION;
|
||||||
|
writeDeletionTime(deletionTime, writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Metadata calls matching: {@link org.apache.cassandra.db.rows.Rows#collectStats}
|
||||||
|
* But the collection of data is conditional and the cell metadata is collected elsewhere.
|
||||||
|
*/
|
||||||
|
return flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeDeletionTime(DeletionTime deletionTime, DataOutputPlus writer) throws IOException
|
||||||
|
{
|
||||||
|
serializationHeader.writeDeletionTime(deletionTime, writer);
|
||||||
|
metadataCollector.update(deletionTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeCellHeader(int cellFlags, ReusableLivenessInfo cellLiveness, ColumnMetadata cellColumn) throws IOException
|
||||||
|
{
|
||||||
|
for (; nextCellIndex < columns.length; nextCellIndex++) {
|
||||||
|
if (columns[nextCellIndex].compareTo(cellColumn) == 0)
|
||||||
|
break;
|
||||||
|
missingColumns.addInt(nextCellIndex);
|
||||||
|
}
|
||||||
|
if (nextCellIndex == columns.length)
|
||||||
|
throw new IllegalStateException("Column not found: " + cellColumn +" or cell writes out of order, or bug.");
|
||||||
|
nextCellIndex++;
|
||||||
|
writeCellHeader(cellFlags, cellLiveness, rowBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeCellHeader(int cellFlags, ReusableLivenessInfo cellLiveness, DataOutputPlus writer) throws IOException
|
||||||
|
{
|
||||||
|
columnsWrittenCount++;
|
||||||
|
writer.writeByte(cellFlags);
|
||||||
|
if (!Cell.Serializer.useRowTimestamp(cellFlags)) {
|
||||||
|
long timestamp = cellLiveness.timestamp();
|
||||||
|
serializationHeader.writeTimestamp(timestamp, writer);
|
||||||
|
}
|
||||||
|
if (!Cell.Serializer.useRowTTL(cellFlags)) {
|
||||||
|
boolean isDeleted = Cell.Serializer.isDeleted(cellFlags);
|
||||||
|
boolean isExpiring = Cell.Serializer.isExpiring(cellFlags);
|
||||||
|
if (isDeleted || isExpiring) {
|
||||||
|
// TODO: is this conversion from LET to LDT correct?
|
||||||
|
serializationHeader.writeLocalDeletionTime(cellLiveness.localExpirationTime(), writer);
|
||||||
|
}
|
||||||
|
if (isExpiring) {
|
||||||
|
serializationHeader.writeTTL(cellLiveness.ttl(), writer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* matching {@link org.apache.cassandra.db.rows.Cells#collectStats};
|
||||||
|
*/
|
||||||
|
metadataCollector.updateCellLiveness(cellLiveness);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int writeCellValue(SSTableCursorReader cursor, byte[] copyColumnValueBuffer) throws IOException
|
||||||
|
{
|
||||||
|
return cursor.copyCellValue(rowBuffer, copyColumnValueBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeCellValue(DataOutputBuffer tempCellBuffer) throws IOException
|
||||||
|
{
|
||||||
|
rowBuffer.write(tempCellBuffer.getData(), 0, tempCellBuffer.getLength());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeRowEnd(UnfilteredDescriptor rHeader, boolean updateClusteringMetadata) throws IOException
|
||||||
|
{
|
||||||
|
boolean isExtended = isExtended(rowFlags);
|
||||||
|
boolean isStatic = isExtended && UnfilteredSerializer.isStatic(rowExtendedFlags);
|
||||||
|
int columnsLength = columns.length;
|
||||||
|
if (columnsWrittenCount == columnsLength)
|
||||||
|
{
|
||||||
|
rowFlags |= HAS_ALL_COLUMNS;
|
||||||
|
}
|
||||||
|
else if (columnsWrittenCount == 0) {
|
||||||
|
// Same as Columns.serializer.serializeSubset(Columns.NONE, serializationHeader.columns(isStatic), rowHeaderBuffer)
|
||||||
|
if (columnsLength < 64) {
|
||||||
|
// all the bits are set, because all the columns are missing, value is always positive
|
||||||
|
rowHeaderBuffer.writeUnsignedVInt(-1L >>> (64 - columnsLength));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// no columns are present, nothing to write
|
||||||
|
rowHeaderBuffer.writeUnsignedVInt32(columnsLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (columnsWrittenCount < columnsLength)
|
||||||
|
{
|
||||||
|
for (; nextCellIndex < columnsLength; nextCellIndex++)
|
||||||
|
missingColumns.addInt(nextCellIndex);
|
||||||
|
|
||||||
|
if (columnsLength < 64) {
|
||||||
|
// set a bit for every missing column
|
||||||
|
long mask = 0;
|
||||||
|
for (int missingIndex : missingColumns) {
|
||||||
|
mask |= (1L << missingIndex);
|
||||||
|
}
|
||||||
|
rowHeaderBuffer.writeUnsignedVInt(mask);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
encodeLargeColumnsSubset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
long unfilteredStartPosition = dataWriter.position();
|
||||||
|
/** See: {@link UnfilteredSerializer#serialize} */
|
||||||
|
dataWriter.writeByte(rowFlags);
|
||||||
|
if (isExtended)
|
||||||
|
{
|
||||||
|
dataWriter.writeByte(rowExtendedFlags);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isStatic)
|
||||||
|
{
|
||||||
|
byte[] clustering = rHeader.clusteringBytes();
|
||||||
|
int clusteringLength = rHeader.clusteringLength();
|
||||||
|
dataWriter.write(clustering, 0, clusteringLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now that we know the size, write it + the rest of the data
|
||||||
|
dataWriter.writeUnsignedVInt32(rowHeaderBuffer.getLength() + rowBuffer.getLength());
|
||||||
|
|
||||||
|
dataWriter.write(rowHeaderBuffer.getData(), 0, rowHeaderBuffer.getLength());
|
||||||
|
dataWriter.write(rowBuffer.getData(), 0, rowBuffer.getLength());
|
||||||
|
|
||||||
|
long unfilteredEndPosition = getPosition();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Matching the: {@link org.apache.cassandra.db.rows.Rows#collectStats} along with above cell level metadata updates
|
||||||
|
*/
|
||||||
|
metadataCollector.updateColumnSetPerRow(columnsWrittenCount);
|
||||||
|
|
||||||
|
if (isStatic)
|
||||||
|
{
|
||||||
|
updateIndexBlockStartOffset(dataWriter.position());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
updateMetadataAndIndexBlock(rHeader, unfilteredStartPosition, unfilteredEndPosition, updateClusteringMetadata);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See: {@link org.apache.cassandra.io.sstable.format.SortedTableWriter#addRangeTomstoneMarker}
|
||||||
|
*/
|
||||||
|
public void writeRangeTombstone(UnfilteredDescriptor rangeTombstone, boolean updateClusteringMetadata) throws IOException
|
||||||
|
{
|
||||||
|
int tombstoneKind = rangeTombstone.clusteringKindEncoded();
|
||||||
|
ClusteringPrefix.Kind kind = ClusteringPrefix.Kind.values()[tombstoneKind];
|
||||||
|
long unfilteredStartPosition = getPosition();
|
||||||
|
/** See: {@link org.apache.cassandra.db.rows.UnfilteredSerializer#serialize */
|
||||||
|
dataWriter.writeByte((byte)IS_MARKER);
|
||||||
|
/** See: {@link org.apache.cassandra.db.ClusteringBoundOrBoundary.Serializer#serialize} */
|
||||||
|
dataWriter.writeByte(tombstoneKind);
|
||||||
|
dataWriter.writeShort(rangeTombstone.clusteringColumnsBound());
|
||||||
|
|
||||||
|
int clusteringLength = rangeTombstone.clusteringLength();
|
||||||
|
if (clusteringLength != 0)
|
||||||
|
{
|
||||||
|
byte[] clustering = rangeTombstone.clusteringBytes();
|
||||||
|
dataWriter.write(clustering, 0, clusteringLength);
|
||||||
|
}
|
||||||
|
rowHeaderBuffer.clear();
|
||||||
|
// TODO: previousUnfilteredSize
|
||||||
|
rowHeaderBuffer.writeUnsignedVInt32(0);
|
||||||
|
|
||||||
|
if (kind.isBoundary())
|
||||||
|
{
|
||||||
|
writeDeletionTime(rangeTombstone.deletionTime(), rowHeaderBuffer);
|
||||||
|
writeDeletionTime(rangeTombstone.deletionTime2(), rowHeaderBuffer);
|
||||||
|
openMarker.reset(rangeTombstone.deletionTime2());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
writeDeletionTime(rangeTombstone.deletionTime(), rowHeaderBuffer);
|
||||||
|
if (kind.isOpen(false))
|
||||||
|
openMarker.reset(rangeTombstone.deletionTime());
|
||||||
|
else
|
||||||
|
openMarker.resetLive();
|
||||||
|
}
|
||||||
|
|
||||||
|
dataWriter.writeUnsignedVInt32(rowHeaderBuffer.getLength());
|
||||||
|
dataWriter.write(rowHeaderBuffer.getData(), 0, rowHeaderBuffer.getLength());
|
||||||
|
|
||||||
|
long unfilteredEndPosition = getPosition();
|
||||||
|
|
||||||
|
/** {@link org.apache.cassandra.io.sstable.format.big.BigFormatPartitionWriter#addUnfiltered(Unfiltered)} */
|
||||||
|
// if we hit the index block size that we have to index after, go ahead and index it.
|
||||||
|
updateMetadataAndIndexBlock(rangeTombstone, unfilteredStartPosition, unfilteredEndPosition, updateClusteringMetadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateMetadataAndIndexBlock(
|
||||||
|
UnfilteredDescriptor unfilteredDescriptor,
|
||||||
|
long unfilteredStartPosition,
|
||||||
|
long unfilteredEndPosition,
|
||||||
|
boolean updateClusteringMetadata) throws IOException
|
||||||
|
{
|
||||||
|
if (updateClusteringMetadata) updateClusteringMetadata(unfilteredDescriptor);
|
||||||
|
// write the first clustering into rowIndexEntries buffer (we will need it unless we never write the first entry)
|
||||||
|
if (unfilteredStartPosition == indexBlockStartOffset || (rowIndexEntryOffset == rowIndexEntries.position())) {
|
||||||
|
writeClusteringToRowIndexEntries(unfilteredDescriptor);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rowIndexEntryLastClustering.copy(unfilteredDescriptor);
|
||||||
|
}
|
||||||
|
/** {@link BigFormatPartitionWriter#addUnfiltered(Unfiltered)} */
|
||||||
|
// if we hit the index block size that we have to index after, go ahead and index it.
|
||||||
|
long indexBlockSize = currentOffsetInPartition(unfilteredEndPosition) - indexBlockStartOffset;
|
||||||
|
if (indexBlockSize >= this.indexBlockThreshold)
|
||||||
|
addIndexBlock(unfilteredEndPosition, indexBlockSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateClusteringMetadata(UnfilteredDescriptor unfilteredDescriptor)
|
||||||
|
{
|
||||||
|
metadataCollector.updateClusteringValues(unfilteredDescriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See:
|
||||||
|
* {@link BigFormatPartitionWriter#addIndexBlock()}
|
||||||
|
* - {@link org.apache.cassandra.io.sstable.IndexInfo.Serializer#serialize(org.apache.cassandra.io.sstable.IndexInfo, org.apache.cassandra.io.util.DataOutputPlus)}
|
||||||
|
*/
|
||||||
|
private void addIndexBlock(long endOfRowPosition, long indexBlockSize) throws IOException
|
||||||
|
{
|
||||||
|
if (rowIndexEntriesOffsets.isEmpty() && rowIndexEntryOffset != 0) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
|
||||||
|
// serialize the index info
|
||||||
|
/** {@link org.apache.cassandra.io.sstable.IndexInfo.Serializer#serialize(org.apache.cassandra.io.sstable.IndexInfo, org.apache.cassandra.io.util.DataOutputPlus)}*/
|
||||||
|
rowIndexEntriesOffsets.addInt(rowIndexEntryOffset);
|
||||||
|
|
||||||
|
// first clustering is already in, write last entry
|
||||||
|
if (rowIndexEntryLastClustering.clusteringLength() == 0) {
|
||||||
|
// first entry is the last entry, copy it
|
||||||
|
byte[] entriesData = rowIndexEntries.getData();
|
||||||
|
long endOfFirstEntry = rowIndexEntries.position();
|
||||||
|
rowIndexEntries.write(entriesData, rowIndexEntryOffset, (int) (endOfFirstEntry - rowIndexEntryOffset));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
writeClusteringToRowIndexEntries(rowIndexEntryLastClustering);
|
||||||
|
rowIndexEntryLastClustering.resetClustering();
|
||||||
|
}
|
||||||
|
rowIndexEntries.writeUnsignedVInt((long)indexBlockStartOffset);
|
||||||
|
rowIndexEntries.writeVInt(indexBlockSize - IndexInfo.Serializer.WIDTH_BASE);
|
||||||
|
|
||||||
|
boolean isDeleteTimePresent = !openMarker.isLive();
|
||||||
|
rowIndexEntries.writeBoolean(isDeleteTimePresent);
|
||||||
|
if (isDeleteTimePresent)
|
||||||
|
deletionTimeSerializer.serialize(openMarker, rowIndexEntries);
|
||||||
|
// next block starts
|
||||||
|
rowIndexEntryOffset = Ints.checkedCast(rowIndexEntries.position());
|
||||||
|
updateIndexBlockStartOffset(endOfRowPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateIndexBlockStartOffset(long endOfRowPosition)
|
||||||
|
{
|
||||||
|
indexBlockStartOffset = (int) (endOfRowPosition - partitionStart);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeClusteringToRowIndexEntries(ClusteringDescriptor clustering) throws IOException
|
||||||
|
{
|
||||||
|
ClusteringPrefix.Kind kind = clustering.clusteringKind();
|
||||||
|
rowIndexEntries.writeByte(kind.ordinal());
|
||||||
|
if (kind != ClusteringPrefix.Kind.CLUSTERING)
|
||||||
|
rowIndexEntries.writeShort(clustering.clusteringColumnsBound());
|
||||||
|
rowIndexEntries.write(clustering.clusteringBytes(), 0, clustering.clusteringLength());
|
||||||
|
}
|
||||||
|
|
||||||
|
private long currentOffsetInPartition(long position)
|
||||||
|
{
|
||||||
|
return position - partitionStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void encodeLargeColumnsSubset() throws IOException
|
||||||
|
{
|
||||||
|
// no columns are present, nothing to write
|
||||||
|
rowHeaderBuffer.writeUnsignedVInt32(missingColumns.size());
|
||||||
|
if (missingColumns.size() > columns.length / 2)
|
||||||
|
{
|
||||||
|
// write present columns
|
||||||
|
int presentIndex = 0;
|
||||||
|
int missingIndex = 0;
|
||||||
|
for (int i = 0; i < missingColumns.size(); i++)
|
||||||
|
{
|
||||||
|
missingIndex = missingColumns.get(i);
|
||||||
|
for (; presentIndex < missingIndex; presentIndex++)
|
||||||
|
rowHeaderBuffer.writeUnsignedVInt32(presentIndex);
|
||||||
|
presentIndex = missingIndex + 1;
|
||||||
|
}
|
||||||
|
if (missingIndex < columns.length-1) {
|
||||||
|
for (; presentIndex < missingIndex; presentIndex++)
|
||||||
|
rowHeaderBuffer.writeUnsignedVInt32(presentIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// write missing columns
|
||||||
|
for (int missingIndex : missingColumns) {
|
||||||
|
rowHeaderBuffer.writeUnsignedVInt32(missingIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLast(ByteBuffer key)
|
||||||
|
{
|
||||||
|
IPartitioner partitioner = ssTableWriter.getPartitioner();
|
||||||
|
DecoratedKey last = partitioner.decorateKey(ByteBufferUtil.clone(key));
|
||||||
|
ssTableWriter.setLast(last);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFirst(ByteBuffer key)
|
||||||
|
{
|
||||||
|
IPartitioner partitioner = ssTableWriter.getPartitioner();
|
||||||
|
DecoratedKey first = partitioner.decorateKey(ByteBufferUtil.clone(key));
|
||||||
|
ssTableWriter.setFirst(first);
|
||||||
|
ssTableWriter.setLast(first);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IPartitioner partitioner()
|
||||||
|
{
|
||||||
|
return ssTableWriter.getPartitioner();
|
||||||
|
}
|
||||||
|
|
||||||
|
public DeletionTime openMarker() {
|
||||||
|
return openMarker;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -68,7 +68,7 @@ class SSTableSimpleUnsortedWriter extends AbstractSSTableSimpleWriter
|
||||||
private final BlockingQueue<Buffer> writeQueue = newBlockingQueue(0);
|
private final BlockingQueue<Buffer> writeQueue = newBlockingQueue(0);
|
||||||
private final DiskWriter diskWriter = new DiskWriter();
|
private final DiskWriter diskWriter = new DiskWriter();
|
||||||
|
|
||||||
SSTableSimpleUnsortedWriter(File directory, TableMetadataRef metadata, RegularAndStaticColumns columns, long maxSSTableSizeInMiB)
|
public SSTableSimpleUnsortedWriter(File directory, TableMetadataRef metadata, RegularAndStaticColumns columns, long maxSSTableSizeInMiB)
|
||||||
{
|
{
|
||||||
this(null, directory, metadata, columns, maxSSTableSizeInMiB);
|
this(null, directory, metadata, columns, maxSSTableSizeInMiB);
|
||||||
}
|
}
|
||||||
|
|
@ -198,7 +198,7 @@ class SSTableSimpleUnsortedWriter extends AbstractSSTableSimpleWriter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static class SyncException extends RuntimeException
|
public static class SyncException extends RuntimeException
|
||||||
{
|
{
|
||||||
SyncException(IOException ioe)
|
SyncException(IOException ioe)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ class SSTableSimpleWriter extends AbstractSSTableSimpleWriter
|
||||||
* @param maxSSTableSizeInMiB defines the max SSTable size if the value is positive.
|
* @param maxSSTableSizeInMiB defines the max SSTable size if the value is positive.
|
||||||
* Any non-positive value indicates the sstable size is unlimited.
|
* Any non-positive value indicates the sstable size is unlimited.
|
||||||
*/
|
*/
|
||||||
protected SSTableSimpleWriter(File directory, TableMetadataRef metadata, RegularAndStaticColumns columns, long maxSSTableSizeInMiB)
|
public SSTableSimpleWriter(File directory, TableMetadataRef metadata, RegularAndStaticColumns columns, long maxSSTableSizeInMiB)
|
||||||
{
|
{
|
||||||
this(null, directory, metadata, columns, maxSSTableSizeInMiB);
|
this(null, directory, metadata, columns, maxSSTableSizeInMiB);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,226 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.cassandra.io.sstable;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import org.apache.cassandra.db.Columns;
|
||||||
|
import org.apache.cassandra.db.DeletionTime.ReusableDeletionTime;
|
||||||
|
import org.apache.cassandra.db.ReusableLivenessInfo;
|
||||||
|
import org.apache.cassandra.db.SerializationHeader;
|
||||||
|
import org.apache.cassandra.db.marshal.AbstractType;
|
||||||
|
import org.apache.cassandra.db.rows.DeserializationHelper;
|
||||||
|
import org.apache.cassandra.db.rows.UnfilteredSerializer;
|
||||||
|
import org.apache.cassandra.io.util.RandomAccessReader;
|
||||||
|
|
||||||
|
public class UnfilteredDescriptor extends ClusteringDescriptor
|
||||||
|
{
|
||||||
|
private final ReusableLivenessInfo rowLivenessInfo = new ReusableLivenessInfo();
|
||||||
|
private final ReusableDeletionTime deletionTime = ReusableDeletionTime.live();
|
||||||
|
private final ReusableDeletionTime deletionTime2 = ReusableDeletionTime.live();
|
||||||
|
|
||||||
|
private long position;
|
||||||
|
private int flags;
|
||||||
|
private int extendedFlags;
|
||||||
|
|
||||||
|
private long unfilteredSize;
|
||||||
|
private long unfilteredDataStart;
|
||||||
|
private long prevUnfilteredSize;
|
||||||
|
Columns rowColumns;
|
||||||
|
|
||||||
|
public UnfilteredDescriptor(AbstractType<?>[] clusteringTypes)
|
||||||
|
{
|
||||||
|
super(clusteringTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadTombstone(RandomAccessReader dataReader,
|
||||||
|
SerializationHeader serializationHeader,
|
||||||
|
int flags) throws IOException
|
||||||
|
{
|
||||||
|
this.flags = flags;
|
||||||
|
this.extendedFlags = 0;
|
||||||
|
rowColumns = null;
|
||||||
|
byte clusteringKind = dataReader.readByte();
|
||||||
|
if (clusteringKind == STATIC_CLUSTERING_KIND || clusteringKind == ROW_CLUSTERING_KIND) {
|
||||||
|
// STATIC_CLUSTERING or CLUSTERING -> no deletion info, should not happen
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
|
||||||
|
int columnsBound = dataReader.readUnsignedShort();
|
||||||
|
loadClustering(dataReader, clusteringKind, columnsBound);
|
||||||
|
unfilteredSize = dataReader.readUnsignedVInt();
|
||||||
|
prevUnfilteredSize = dataReader.readUnsignedVInt(); // debug only, unused otherwise
|
||||||
|
if (clusteringKind == EXCL_END_INCL_START_BOUNDARY_CLUSTERING_KIND ||
|
||||||
|
clusteringKind == INCL_END_EXCL_START_BOUNDARY_CLUSTERING_KIND)
|
||||||
|
{
|
||||||
|
// boundary
|
||||||
|
// CLOSE
|
||||||
|
serializationHeader.readDeletionTime(dataReader, deletionTime);
|
||||||
|
// OPEN
|
||||||
|
serializationHeader.readDeletionTime(dataReader, deletionTime2);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// bound
|
||||||
|
// CLOSE|OPEN
|
||||||
|
serializationHeader.readDeletionTime(dataReader, deletionTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadRow(RandomAccessReader dataReader,
|
||||||
|
SerializationHeader serializationHeader,
|
||||||
|
DeserializationHelper deserializationHelper,
|
||||||
|
int flags) throws IOException {
|
||||||
|
// body = whatever is covered by size, so inclusive of the prev_row_size inclusive of flags
|
||||||
|
position = dataReader.getPosition() - 1;
|
||||||
|
this.flags = flags;
|
||||||
|
this.extendedFlags = 0;
|
||||||
|
|
||||||
|
loadClustering(dataReader, ROW_CLUSTERING_KIND, this.clusteringTypes.length);
|
||||||
|
|
||||||
|
rowColumns = serializationHeader.columns(false);
|
||||||
|
|
||||||
|
loadCommonRowFields(dataReader, serializationHeader, deserializationHelper, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadStaticRow(RandomAccessReader dataReader,
|
||||||
|
SerializationHeader serializationHeader,
|
||||||
|
DeserializationHelper deserializationHelper,
|
||||||
|
int flags,
|
||||||
|
int extendedFlags) throws IOException {
|
||||||
|
// body = whatever is covered by size, so inclusive of the prev_row_size inclusive of flags
|
||||||
|
position = dataReader.getPosition() - 2;
|
||||||
|
this.flags = flags;
|
||||||
|
this.extendedFlags = extendedFlags;
|
||||||
|
// no clustering
|
||||||
|
loadClustering(dataReader, STATIC_CLUSTERING_KIND, 0);
|
||||||
|
rowColumns = serializationHeader.columns(true);
|
||||||
|
|
||||||
|
loadCommonRowFields(dataReader, serializationHeader, deserializationHelper, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadCommonRowFields(RandomAccessReader dataReader,
|
||||||
|
SerializationHeader serializationHeader,
|
||||||
|
DeserializationHelper deserializationHelper,
|
||||||
|
int flags) throws IOException
|
||||||
|
{
|
||||||
|
unfilteredSize = dataReader.readUnsignedVInt();
|
||||||
|
unfilteredDataStart = dataReader.getPosition();
|
||||||
|
prevUnfilteredSize = dataReader.readUnsignedVInt(); // debug only, unused otherwise
|
||||||
|
|
||||||
|
SSTableCursorReader.readLivenessInfo(dataReader,
|
||||||
|
serializationHeader,
|
||||||
|
deserializationHelper,
|
||||||
|
flags,
|
||||||
|
rowLivenessInfo);
|
||||||
|
|
||||||
|
if (UnfilteredSerializer.hasDeletion(flags))
|
||||||
|
{
|
||||||
|
// struct delta_deletion_time {
|
||||||
|
// varint delta_marked_for_delete_at;
|
||||||
|
// varint delta_local_deletion_time;
|
||||||
|
//};
|
||||||
|
serializationHeader.readDeletionTime(dataReader, deletionTime);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
deletionTime.resetLive();
|
||||||
|
}
|
||||||
|
if (!UnfilteredSerializer.hasAllColumns(flags))
|
||||||
|
{
|
||||||
|
// TODO: re-implement GC free
|
||||||
|
rowColumns = Columns.serializer.deserializeSubset(rowColumns, dataReader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resetUnfiltered()
|
||||||
|
{
|
||||||
|
resetClustering();
|
||||||
|
position = 0;
|
||||||
|
flags = 0;
|
||||||
|
extendedFlags = 0;
|
||||||
|
unfilteredSize = 0;
|
||||||
|
unfilteredDataStart = 0;
|
||||||
|
prevUnfilteredSize = 0;
|
||||||
|
rowColumns = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long position()
|
||||||
|
{
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReusableLivenessInfo livenessInfo()
|
||||||
|
{
|
||||||
|
return rowLivenessInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReusableDeletionTime deletionTime()
|
||||||
|
{
|
||||||
|
return deletionTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReusableDeletionTime openDeletionTime()
|
||||||
|
{
|
||||||
|
return isBoundary() ? deletionTime2 : isEndBound() ? null : deletionTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReusableDeletionTime deletionTime2()
|
||||||
|
{
|
||||||
|
return deletionTime2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int flags()
|
||||||
|
{
|
||||||
|
return flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long size()
|
||||||
|
{
|
||||||
|
return unfilteredSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long dataStart()
|
||||||
|
{
|
||||||
|
return unfilteredDataStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Columns rowColumns()
|
||||||
|
{
|
||||||
|
return rowColumns;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return "UnfilteredDescriptor{" +
|
||||||
|
"rowLivenessInfo=" + rowLivenessInfo +
|
||||||
|
", deletionTime=" + deletionTime +
|
||||||
|
", position=" + position +
|
||||||
|
", flags=" + flags +
|
||||||
|
", extFlags=" + extendedFlags +
|
||||||
|
", unfilteredSize=" + unfilteredSize +
|
||||||
|
", prevUnfilteredSize=" + prevUnfilteredSize +
|
||||||
|
", unfilteredDataStart=" + unfilteredDataStart +
|
||||||
|
", rowColumns=" + rowColumns +
|
||||||
|
", clusteringTypes=" + Arrays.toString(clusteringTypes()) +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -926,7 +926,16 @@ public abstract class SSTableReader extends SSTable implements UnfilteredSource,
|
||||||
/**
|
/**
|
||||||
* Returns a {@link KeyReader} over all keys in the sstable.
|
* Returns a {@link KeyReader} over all keys in the sstable.
|
||||||
*/
|
*/
|
||||||
public abstract KeyReader keyReader() throws IOException;
|
public final KeyReader keyReader() throws IOException {
|
||||||
|
return keyReader(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a {@link KeyReader} over all keys in the sstable.
|
||||||
|
*
|
||||||
|
* @param detailed should the iterator also provide details per partition entry(e.g. row entry details)
|
||||||
|
*/
|
||||||
|
public abstract KeyReader keyReader(boolean detailed) throws IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a {@link KeyReader} over all keys in the sstable after a given key.
|
* Returns a {@link KeyReader} over all keys in the sstable after a given key.
|
||||||
|
|
|
||||||
|
|
@ -280,4 +280,12 @@ implements ISSTableScanner
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFullRange()
|
||||||
|
{
|
||||||
|
return dataRange == null || (dataRange.startKey().equals(sstable.getFirst()) &&
|
||||||
|
dataRange.stopKey().equals(sstable.getLast()) &&
|
||||||
|
dataRange.isUnrestricted(sstable.metadata()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -129,6 +129,13 @@ implements ISSTableScanner
|
||||||
return ImmutableSet.of(sstable);
|
return ImmutableSet.of(sstable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFullRange()
|
||||||
|
{
|
||||||
|
// hasNext will init start and end
|
||||||
|
return hasNext() && currentStartPosition == 0 && currentEndPosition == sstable.uncompressedLength();
|
||||||
|
}
|
||||||
|
|
||||||
public TableMetadata metadata()
|
public TableMetadata metadata()
|
||||||
{
|
{
|
||||||
return sstable.metadata();
|
return sstable.metadata();
|
||||||
|
|
|
||||||
|
|
@ -77,8 +77,8 @@ public abstract class SSTableWriter extends SSTable implements Transactional
|
||||||
protected boolean isTransient;
|
protected boolean isTransient;
|
||||||
protected long maxDataAge = -1;
|
protected long maxDataAge = -1;
|
||||||
protected final long keyCount;
|
protected final long keyCount;
|
||||||
protected final MetadataCollector metadataCollector;
|
public final MetadataCollector metadataCollector;
|
||||||
protected final SerializationHeader header;
|
public final SerializationHeader header;
|
||||||
protected final List<SSTableFlushObserver> observers;
|
protected final List<SSTableFlushObserver> observers;
|
||||||
protected final MmappedRegionsCache mmappedRegionsCache;
|
protected final MmappedRegionsCache mmappedRegionsCache;
|
||||||
protected final TransactionalProxy txnProxy = txnProxy();
|
protected final TransactionalProxy txnProxy = txnProxy();
|
||||||
|
|
@ -334,7 +334,7 @@ public abstract class SSTableWriter extends SSTable implements Transactional
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Map<MetadataType, MetadataComponent> finalizeMetadata()
|
protected final Map<MetadataType, MetadataComponent> finalizeMetadata()
|
||||||
{
|
{
|
||||||
return metadataCollector.finalizeMetadata(getPartitioner().getClass().getCanonicalName(),
|
return metadataCollector.finalizeMetadata(getPartitioner().getClass().getCanonicalName(),
|
||||||
metadata().params.bloomFilterFpChance,
|
metadata().params.bloomFilterFpChance,
|
||||||
|
|
@ -595,4 +595,12 @@ public abstract class SSTableWriter extends SSTable implements Transactional
|
||||||
return new SSTableZeroCopyWriter(this, txn, owner);
|
return new SSTableZeroCopyWriter(this, txn, owner);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setFirst(DecoratedKey key) {
|
||||||
|
first = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLast(DecoratedKey key) {
|
||||||
|
last = key;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,11 +43,11 @@ public abstract class SortedTablePartitionWriter implements AutoCloseable
|
||||||
private final SerializationHelper helper;
|
private final SerializationHelper helper;
|
||||||
private final Version version;
|
private final Version version;
|
||||||
|
|
||||||
private long previousRowStart;
|
private long previousRowStartOffset;
|
||||||
private long initialPosition;
|
private long partitionStartPosition;
|
||||||
private long headerLength;
|
private long headerLength;
|
||||||
|
|
||||||
protected long startPosition;
|
protected long indexBlockStartOffset;
|
||||||
protected int written;
|
protected int written;
|
||||||
|
|
||||||
protected ClusteringPrefix<?> firstClustering;
|
protected ClusteringPrefix<?> firstClustering;
|
||||||
|
|
@ -78,9 +78,9 @@ public abstract class SortedTablePartitionWriter implements AutoCloseable
|
||||||
|
|
||||||
protected void reset()
|
protected void reset()
|
||||||
{
|
{
|
||||||
this.initialPosition = writer.position();
|
this.partitionStartPosition = writer.position();
|
||||||
this.startPosition = -1;
|
this.indexBlockStartOffset = -1;
|
||||||
this.previousRowStart = 0;
|
this.previousRowStartOffset = 0;
|
||||||
this.written = 0;
|
this.written = 0;
|
||||||
this.firstClustering = null;
|
this.firstClustering = null;
|
||||||
this.lastClustering = null;
|
this.lastClustering = null;
|
||||||
|
|
@ -106,7 +106,7 @@ public abstract class SortedTablePartitionWriter implements AutoCloseable
|
||||||
|
|
||||||
if (!header.hasStatic())
|
if (!header.hasStatic())
|
||||||
{
|
{
|
||||||
this.headerLength = writer.position() - initialPosition;
|
this.headerLength = writer.position() - partitionStartPosition;
|
||||||
state = State.AWAITING_ROWS;
|
state = State.AWAITING_ROWS;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -121,7 +121,7 @@ public abstract class SortedTablePartitionWriter implements AutoCloseable
|
||||||
|
|
||||||
UnfilteredSerializer.serializer.serializeStaticRow(staticRow, helper, writer, version.correspondingMessagingVersion());
|
UnfilteredSerializer.serializer.serializeStaticRow(staticRow, helper, writer, version.correspondingMessagingVersion());
|
||||||
|
|
||||||
this.headerLength = writer.position() - initialPosition;
|
this.headerLength = writer.position() - partitionStartPosition;
|
||||||
state = State.AWAITING_ROWS;
|
state = State.AWAITING_ROWS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -129,21 +129,21 @@ public abstract class SortedTablePartitionWriter implements AutoCloseable
|
||||||
{
|
{
|
||||||
checkState(state == State.AWAITING_ROWS);
|
checkState(state == State.AWAITING_ROWS);
|
||||||
|
|
||||||
long pos = currentPosition();
|
long offset = currentOffsetInPartition();
|
||||||
|
|
||||||
if (firstClustering == null)
|
if (firstClustering == null)
|
||||||
{
|
{
|
||||||
// Beginning of an index block. Remember the start and position
|
// Beginning of an index block. Remember the start clustering and position
|
||||||
firstClustering = unfiltered.clustering();
|
firstClustering = unfiltered.clustering();
|
||||||
startOpenMarker = openMarker;
|
startOpenMarker = openMarker; // first entry is always LIVE (for BTI format)
|
||||||
startPosition = pos;
|
indexBlockStartOffset = offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
long unfilteredPosition = writer.position();
|
long unfilteredPosition = writer.position();
|
||||||
unfilteredSerializer.serialize(unfiltered, helper, writer, pos - previousRowStart, version.correspondingMessagingVersion());
|
unfilteredSerializer.serialize(unfiltered, helper, writer, offset - previousRowStartOffset, version.correspondingMessagingVersion());
|
||||||
|
|
||||||
lastClustering = unfiltered.clustering();
|
lastClustering = unfiltered.clustering();
|
||||||
previousRowStart = pos;
|
previousRowStartOffset = offset;
|
||||||
++written;
|
++written;
|
||||||
|
|
||||||
if (unfiltered.kind() == Unfiltered.Kind.RANGE_TOMBSTONE_MARKER)
|
if (unfiltered.kind() == Unfiltered.Kind.RANGE_TOMBSTONE_MARKER)
|
||||||
|
|
@ -159,19 +159,30 @@ public abstract class SortedTablePartitionWriter implements AutoCloseable
|
||||||
|
|
||||||
state = State.COMPLETED;
|
state = State.COMPLETED;
|
||||||
|
|
||||||
long endPosition = currentPosition();
|
long partitionLength = currentOffsetInPartition();
|
||||||
unfilteredSerializer.writeEndOfPartition(writer);
|
unfilteredSerializer.writeEndOfPartition(writer);
|
||||||
|
|
||||||
return endPosition;
|
return partitionLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected long currentPosition()
|
protected long currentOffsetInPartition()
|
||||||
{
|
{
|
||||||
return writer.position() - initialPosition;
|
return writer.position() - partitionStartPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getInitialPosition()
|
public long getPartitionStartPosition()
|
||||||
{
|
{
|
||||||
return initialPosition;
|
return partitionStartPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Some bullshit access for now */
|
||||||
|
public SerializationHeader getHeader()
|
||||||
|
{
|
||||||
|
return header;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SerializationHelper getHelper()
|
||||||
|
{
|
||||||
|
return helper;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -175,7 +175,7 @@ public abstract class SortedTableVerifier<R extends SSTableReaderWithFilter> imp
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
outputHandler.debug("Deserializing bloom filter for %s", sstable);
|
if (outputHandler.isDebugEnabled()) outputHandler.debug("Deserializing bloom filter for %s", sstable);
|
||||||
deserializeBloomFilter(sstable);
|
deserializeBloomFilter(sstable);
|
||||||
}
|
}
|
||||||
catch (Throwable t)
|
catch (Throwable t)
|
||||||
|
|
@ -217,7 +217,7 @@ public abstract class SortedTableVerifier<R extends SSTableReaderWithFilter> imp
|
||||||
protected int verifyOwnedRanges()
|
protected int verifyOwnedRanges()
|
||||||
{
|
{
|
||||||
List<Range<Token>> ownedRanges = Collections.emptyList();
|
List<Range<Token>> ownedRanges = Collections.emptyList();
|
||||||
outputHandler.debug("Checking that all tokens are owned by the current node");
|
if (outputHandler.isDebugEnabled()) outputHandler.debug("Checking that all tokens are owned by the current node");
|
||||||
try (KeyIterator iter = sstable.keyIterator())
|
try (KeyIterator iter = sstable.keyIterator())
|
||||||
{
|
{
|
||||||
ownedRanges = Range.normalize(tokenLookup.apply(cfs.metadata.keyspace));
|
ownedRanges = Range.normalize(tokenLookup.apply(cfs.metadata.keyspace));
|
||||||
|
|
@ -287,7 +287,7 @@ public abstract class SortedTableVerifier<R extends SSTableReaderWithFilter> imp
|
||||||
throw new CompactionInterruptedException(verifyInfo.getCompactionInfo());
|
throw new CompactionInterruptedException(verifyInfo.getCompactionInfo());
|
||||||
|
|
||||||
long rowStart = dataFile.getFilePointer();
|
long rowStart = dataFile.getFilePointer();
|
||||||
outputHandler.debug("Reading row at %d", rowStart);
|
if (outputHandler.isDebugEnabled()) outputHandler.debug("Reading row at %d", rowStart);
|
||||||
|
|
||||||
DecoratedKey key = null;
|
DecoratedKey key = null;
|
||||||
try
|
try
|
||||||
|
|
@ -332,8 +332,11 @@ public abstract class SortedTableVerifier<R extends SSTableReaderWithFilter> imp
|
||||||
|
|
||||||
long dataSize = nextRowPositionFromIndex - dataStartFromIndex;
|
long dataSize = nextRowPositionFromIndex - dataStartFromIndex;
|
||||||
// avoid an NPE if key is null
|
// avoid an NPE if key is null
|
||||||
|
if (outputHandler.isDebugEnabled())
|
||||||
|
{
|
||||||
String keyName = key == null ? "(unreadable key)" : ByteBufferUtil.bytesToHex(key.getKey());
|
String keyName = key == null ? "(unreadable key)" : ByteBufferUtil.bytesToHex(key.getKey());
|
||||||
outputHandler.debug("row %s is %s", keyName, FBUtilities.prettyPrintMemory(dataSize));
|
outputHandler.debug("row %s is %s", keyName, FBUtilities.prettyPrintMemory(dataSize));
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
@ -352,7 +355,7 @@ public abstract class SortedTableVerifier<R extends SSTableReaderWithFilter> imp
|
||||||
prevKey = key;
|
prevKey = key;
|
||||||
|
|
||||||
|
|
||||||
outputHandler.debug("Row %s at %s valid, moving to next row at %s ", goodRows, rowStart, nextRowPositionFromIndex);
|
if (outputHandler.isDebugEnabled()) outputHandler.debug("Row %s at %s valid, moving to next row at %s ", goodRows, rowStart, nextRowPositionFromIndex);
|
||||||
dataFile.seek(nextRowPositionFromIndex);
|
dataFile.seek(nextRowPositionFromIndex);
|
||||||
}
|
}
|
||||||
catch (Throwable th)
|
catch (Throwable th)
|
||||||
|
|
@ -374,7 +377,7 @@ public abstract class SortedTableVerifier<R extends SSTableReaderWithFilter> imp
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
outputHandler.debug("Deserializing index for %s", sstable);
|
if (outputHandler.isDebugEnabled()) outputHandler.debug("Deserializing index for %s", sstable);
|
||||||
deserializeIndex(sstable);
|
deserializeIndex(sstable);
|
||||||
}
|
}
|
||||||
catch (Throwable t)
|
catch (Throwable t)
|
||||||
|
|
@ -384,7 +387,7 @@ public abstract class SortedTableVerifier<R extends SSTableReaderWithFilter> imp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void deserializeIndex(SSTableReader sstable) throws IOException
|
protected void deserializeIndex(SSTableReader sstable) throws IOException
|
||||||
{
|
{
|
||||||
try (KeyReader it = sstable.keyReader())
|
try (KeyReader it = sstable.keyReader())
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -79,9 +79,9 @@ public abstract class SortedTableWriter<P extends SortedTablePartitionWriter, I
|
||||||
|
|
||||||
// TODO dataWriter is not needed to be directly accessible - we can access everything we need for the dataWriter
|
// TODO dataWriter is not needed to be directly accessible - we can access everything we need for the dataWriter
|
||||||
// from a partition writer
|
// from a partition writer
|
||||||
protected final SequentialWriter dataWriter;
|
public final SequentialWriter dataWriter;
|
||||||
protected final I indexWriter;
|
public final I indexWriter;
|
||||||
protected final P partitionWriter;
|
public final P partitionWriter;
|
||||||
private final FileHandle.Builder dataFileBuilder = new FileHandle.Builder(descriptor.fileFor(Components.DATA));
|
private final FileHandle.Builder dataFileBuilder = new FileHandle.Builder(descriptor.fileFor(Components.DATA));
|
||||||
private DecoratedKey lastWrittenKey;
|
private DecoratedKey lastWrittenKey;
|
||||||
private DataPosition dataMark;
|
private DataPosition dataMark;
|
||||||
|
|
@ -237,13 +237,15 @@ public abstract class SortedTableWriter<P extends SortedTablePartitionWriter, I
|
||||||
|
|
||||||
private AbstractRowIndexEntry endPartition(DecoratedKey key, DeletionTime partitionLevelDeletion) throws IOException
|
private AbstractRowIndexEntry endPartition(DecoratedKey key, DeletionTime partitionLevelDeletion) throws IOException
|
||||||
{
|
{
|
||||||
|
// partitionLength not inclusive of last byte for BIG/default impl, but something different for BTI (and maybe other impls)
|
||||||
long finishResult = partitionWriter.finish();
|
long finishResult = partitionWriter.finish();
|
||||||
|
|
||||||
long endPosition = dataWriter.position();
|
long endPosition = dataWriter.position();
|
||||||
long rowSize = endPosition - partitionWriter.getInitialPosition();
|
// inclusive of last byte
|
||||||
guardPartitionThreshold(Guardrails.partitionSize, key, rowSize);
|
long partitionSize = endPosition - partitionWriter.getPartitionStartPosition();
|
||||||
|
guardPartitionThreshold(Guardrails.partitionSize, key, partitionSize);
|
||||||
guardPartitionThreshold(Guardrails.partitionTombstones, key, metadataCollector.totalTombstones);
|
guardPartitionThreshold(Guardrails.partitionTombstones, key, metadataCollector.totalTombstones);
|
||||||
metadataCollector.addPartitionSizeInBytes(rowSize);
|
metadataCollector.addPartitionSizeInBytes(partitionSize);
|
||||||
metadataCollector.addKey(key.getKey());
|
metadataCollector.addKey(key.getKey());
|
||||||
metadataCollector.addCellPerPartitionCount();
|
metadataCollector.addCellPerPartitionCount();
|
||||||
|
|
||||||
|
|
@ -260,7 +262,7 @@ public abstract class SortedTableWriter<P extends SortedTablePartitionWriter, I
|
||||||
protected void onStartPartition(DecoratedKey key)
|
protected void onStartPartition(DecoratedKey key)
|
||||||
{
|
{
|
||||||
if (hasObservers())
|
if (hasObservers())
|
||||||
notifyObservers(o -> o.startPartition(key, partitionWriter.getInitialPosition(), partitionWriter.getInitialPosition()));
|
notifyObservers(o -> o.startPartition(key, partitionWriter.getPartitionStartPosition(), partitionWriter.getPartitionStartPosition()));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onStaticRow(Row row)
|
protected void onStaticRow(Row row)
|
||||||
|
|
@ -331,7 +333,7 @@ public abstract class SortedTableWriter<P extends SortedTablePartitionWriter, I
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getFilePointer()
|
public final long getFilePointer()
|
||||||
{
|
{
|
||||||
return dataWriter.position();
|
return dataWriter.position();
|
||||||
}
|
}
|
||||||
|
|
@ -435,13 +437,13 @@ public abstract class SortedTableWriter<P extends SortedTablePartitionWriter, I
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static abstract class AbstractIndexWriter extends AbstractTransactional implements Transactional
|
public static abstract class AbstractIndexWriter extends AbstractTransactional implements Transactional
|
||||||
{
|
{
|
||||||
protected final Descriptor descriptor;
|
protected final Descriptor descriptor;
|
||||||
protected final TableMetadataRef metadata;
|
protected final TableMetadataRef metadata;
|
||||||
protected final Set<Component> components;
|
protected final Set<Component> components;
|
||||||
|
|
||||||
protected final IFilter bf;
|
public final IFilter bf;
|
||||||
|
|
||||||
protected AbstractIndexWriter(Builder<?, ?, ?, ?> b)
|
protected AbstractIndexWriter(Builder<?, ?, ?, ?> b)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -48,11 +48,11 @@ public class BigFormatPartitionWriter extends SortedTablePartitionWriter
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
public static final int DEFAULT_GRANULARITY = 64 * 1024;
|
public static final int DEFAULT_GRANULARITY = 64 * 1024;
|
||||||
|
|
||||||
// used, if the row-index-entry reaches config column_index_cache_size
|
// used, if the row-index-entry reaches config switchIndexInfoToBufferThreshold
|
||||||
private DataOutputBuffer buffer;
|
private DataOutputBuffer rowIndexEntryBuffer;
|
||||||
// used to track the size of the serialized size of row-index-entry (unused for buffer)
|
// used to track the total serialized size of indexSamples (unused for buffer)
|
||||||
private int indexSamplesSerializedSize;
|
private int indexSamplesSerializedSize;
|
||||||
// used, until the row-index-entry reaches config column_index_cache_size
|
// used, until the row-index-entry reaches switchIndexInfoToBufferThreshold (from config column_index_cache_size, or default 64k)
|
||||||
private final List<IndexInfo> indexSamples = new ArrayList<>();
|
private final List<IndexInfo> indexSamples = new ArrayList<>();
|
||||||
|
|
||||||
private DataOutputBuffer reusableBuffer;
|
private DataOutputBuffer reusableBuffer;
|
||||||
|
|
@ -62,8 +62,10 @@ public class BigFormatPartitionWriter extends SortedTablePartitionWriter
|
||||||
|
|
||||||
private final ISerializer<IndexInfo> idxSerializer;
|
private final ISerializer<IndexInfo> idxSerializer;
|
||||||
|
|
||||||
private final int cacheSizeThreshold;
|
/** Beyond this limit we switch from storing IndexInfo in the list to directly serializing them into a buffer */
|
||||||
private final int indexSize;
|
private final int switchIndexInfoToBufferThreshold;
|
||||||
|
/** If a partition grows beyond this size we store inter-partition index data in IndexInfo */
|
||||||
|
private final int indexBlockThreshold;
|
||||||
|
|
||||||
BigFormatPartitionWriter(SerializationHeader header,
|
BigFormatPartitionWriter(SerializationHeader header,
|
||||||
SequentialWriter writer,
|
SequentialWriter writer,
|
||||||
|
|
@ -82,8 +84,8 @@ public class BigFormatPartitionWriter extends SortedTablePartitionWriter
|
||||||
{
|
{
|
||||||
super(header, writer, version);
|
super(header, writer, version);
|
||||||
this.idxSerializer = indexInfoSerializer;
|
this.idxSerializer = indexInfoSerializer;
|
||||||
this.cacheSizeThreshold = cacheSizeThreshold;
|
this.switchIndexInfoToBufferThreshold = cacheSizeThreshold;
|
||||||
this.indexSize = indexSize;
|
this.indexBlockThreshold = indexSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void reset()
|
public void reset()
|
||||||
|
|
@ -93,9 +95,9 @@ public class BigFormatPartitionWriter extends SortedTablePartitionWriter
|
||||||
this.indexSamplesSerializedSize = 0;
|
this.indexSamplesSerializedSize = 0;
|
||||||
this.indexSamples.clear();
|
this.indexSamples.clear();
|
||||||
|
|
||||||
if (this.buffer != null)
|
if (this.rowIndexEntryBuffer != null)
|
||||||
this.reusableBuffer = this.buffer;
|
this.reusableBuffer = this.rowIndexEntryBuffer;
|
||||||
this.buffer = null;
|
this.rowIndexEntryBuffer = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getColumnIndexCount()
|
public int getColumnIndexCount()
|
||||||
|
|
@ -105,12 +107,12 @@ public class BigFormatPartitionWriter extends SortedTablePartitionWriter
|
||||||
|
|
||||||
public ByteBuffer buffer()
|
public ByteBuffer buffer()
|
||||||
{
|
{
|
||||||
return buffer != null ? buffer.buffer() : null;
|
return rowIndexEntryBuffer != null ? rowIndexEntryBuffer.buffer() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<IndexInfo> indexSamples()
|
public List<IndexInfo> indexSamples()
|
||||||
{
|
{
|
||||||
if (indexSamplesSerializedSize + columnIndexCount * TypeSizes.sizeof(0) <= cacheSizeThreshold)
|
if (indexSamplesSerializedSize + columnIndexCount * TypeSizes.sizeof(0) <= switchIndexInfoToBufferThreshold)
|
||||||
{
|
{
|
||||||
return indexSamples;
|
return indexSamples;
|
||||||
}
|
}
|
||||||
|
|
@ -129,8 +131,8 @@ public class BigFormatPartitionWriter extends SortedTablePartitionWriter
|
||||||
{
|
{
|
||||||
IndexInfo cIndexInfo = new IndexInfo(firstClustering,
|
IndexInfo cIndexInfo = new IndexInfo(firstClustering,
|
||||||
lastClustering,
|
lastClustering,
|
||||||
startPosition,
|
indexBlockStartOffset,
|
||||||
currentPosition() - startPosition,
|
currentOffsetInPartition() - indexBlockStartOffset,
|
||||||
!openMarker.isLive() ? openMarker : null);
|
!openMarker.isLive() ? openMarker : null);
|
||||||
|
|
||||||
// indexOffsets is used for both shallow (ShallowIndexedEntry) and non-shallow IndexedEntry.
|
// indexOffsets is used for both shallow (ShallowIndexedEntry) and non-shallow IndexedEntry.
|
||||||
|
|
@ -156,8 +158,8 @@ public class BigFormatPartitionWriter extends SortedTablePartitionWriter
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
indexOffsets[columnIndexCount] =
|
indexOffsets[columnIndexCount] =
|
||||||
buffer != null
|
rowIndexEntryBuffer != null
|
||||||
? Ints.checkedCast(buffer.position())
|
? Ints.checkedCast(rowIndexEntryBuffer.position())
|
||||||
: indexSamplesSerializedSize;
|
: indexSamplesSerializedSize;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -165,16 +167,20 @@ public class BigFormatPartitionWriter extends SortedTablePartitionWriter
|
||||||
|
|
||||||
// First, we collect the IndexInfo objects until we reach Config.column_index_cache_size in an ArrayList.
|
// First, we collect the IndexInfo objects until we reach Config.column_index_cache_size in an ArrayList.
|
||||||
// When column_index_cache_size is reached, we switch to byte-buffer mode.
|
// When column_index_cache_size is reached, we switch to byte-buffer mode.
|
||||||
if (buffer == null)
|
if (rowIndexEntryBuffer == null)
|
||||||
{
|
{
|
||||||
indexSamplesSerializedSize += idxSerializer.serializedSize(cIndexInfo);
|
indexSamplesSerializedSize += idxSerializer.serializedSize(cIndexInfo);
|
||||||
if (indexSamplesSerializedSize + columnIndexCount * TypeSizes.sizeof(0) > cacheSizeThreshold)
|
if (indexSamplesSerializedSize + columnIndexCount * TypeSizes.INT_SIZE > switchIndexInfoToBufferThreshold)
|
||||||
{
|
{
|
||||||
buffer = reuseOrAllocateBuffer();
|
rowIndexEntryBuffer = reuseOrAllocateBuffer();
|
||||||
|
// serialize pre-existing samples
|
||||||
for (IndexInfo indexSample : indexSamples)
|
for (IndexInfo indexSample : indexSamples)
|
||||||
{
|
{
|
||||||
idxSerializer.serialize(indexSample, buffer);
|
/** {@link IndexInfo.Serializer#serialize} */
|
||||||
|
idxSerializer.serialize(indexSample, rowIndexEntryBuffer);
|
||||||
}
|
}
|
||||||
|
// release pre-existing samples
|
||||||
|
indexSamples.clear();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -182,9 +188,10 @@ public class BigFormatPartitionWriter extends SortedTablePartitionWriter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// don't put an else here since buffer may be allocated in preceding if block
|
// don't put an else here since buffer may be allocated in preceding if block
|
||||||
if (buffer != null)
|
if (rowIndexEntryBuffer != null)
|
||||||
{
|
{
|
||||||
idxSerializer.serialize(cIndexInfo, buffer);
|
/** {@link IndexInfo.Serializer#serialize} */
|
||||||
|
idxSerializer.serialize(cIndexInfo, rowIndexEntryBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
firstClustering = null;
|
firstClustering = null;
|
||||||
|
|
@ -201,7 +208,7 @@ public class BigFormatPartitionWriter extends SortedTablePartitionWriter
|
||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
// don't use the standard RECYCLER as that only recycles up to 1MB and requires proper cleanup
|
// don't use the standard RECYCLER as that only recycles up to 1MB and requires proper cleanup
|
||||||
return new DataOutputBuffer(cacheSizeThreshold * 2);
|
return new DataOutputBuffer(switchIndexInfoToBufferThreshold * 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -210,7 +217,8 @@ public class BigFormatPartitionWriter extends SortedTablePartitionWriter
|
||||||
super.addUnfiltered(unfiltered);
|
super.addUnfiltered(unfiltered);
|
||||||
|
|
||||||
// if we hit the column index size that we have to index after, go ahead and index it.
|
// if we hit the column index size that we have to index after, go ahead and index it.
|
||||||
if (currentPosition() - startPosition >= indexSize)
|
long sizeSinceLastIndexBlock = currentOffsetInPartition() - indexBlockStartOffset;
|
||||||
|
if (sizeSinceLastIndexBlock >= this.indexBlockThreshold)
|
||||||
addIndexBlock();
|
addIndexBlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -231,10 +239,10 @@ public class BigFormatPartitionWriter extends SortedTablePartitionWriter
|
||||||
// we have to write the offsts to these here. The offsets have already been collected
|
// we have to write the offsts to these here. The offsets have already been collected
|
||||||
// in indexOffsets[]. buffer is != null, if it exceeds Config.column_index_cache_size.
|
// in indexOffsets[]. buffer is != null, if it exceeds Config.column_index_cache_size.
|
||||||
// In the other case, when buffer==null, the offsets are serialized in RowIndexEntry.IndexedEntry.serialize().
|
// In the other case, when buffer==null, the offsets are serialized in RowIndexEntry.IndexedEntry.serialize().
|
||||||
if (buffer != null)
|
if (rowIndexEntryBuffer != null)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < columnIndexCount; i++)
|
for (int i = 0; i < columnIndexCount; i++)
|
||||||
buffer.writeInt(indexOffsets[i]);
|
rowIndexEntryBuffer.writeInt(indexOffsets[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// we should always have at least one computed index block, but we only write it out if there is more than that.
|
// we should always have at least one computed index block, but we only write it out if there is more than that.
|
||||||
|
|
@ -245,8 +253,8 @@ public class BigFormatPartitionWriter extends SortedTablePartitionWriter
|
||||||
|
|
||||||
public int indexInfoSerializedSize()
|
public int indexInfoSerializedSize()
|
||||||
{
|
{
|
||||||
return buffer != null
|
return rowIndexEntryBuffer != null
|
||||||
? buffer.buffer().limit()
|
? rowIndexEntryBuffer.buffer().limit()
|
||||||
: indexSamplesSerializedSize + columnIndexCount * TypeSizes.sizeof(0);
|
: indexSamplesSerializedSize + columnIndexCount * TypeSizes.sizeof(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -178,7 +178,7 @@ public class BigSSTableReaderLoadingBuilder extends SortedTableReaderLoadingBuil
|
||||||
checkNotNull(serializationHeader);
|
checkNotNull(serializationHeader);
|
||||||
|
|
||||||
RowIndexEntry.IndexSerializer serializer = new RowIndexEntry.Serializer(descriptor.version, serializationHeader, tableMetrics);
|
RowIndexEntry.IndexSerializer serializer = new RowIndexEntry.Serializer(descriptor.version, serializationHeader, tableMetrics);
|
||||||
return BigTableKeyReader.create(indexFile, serializer);
|
return BigTableKeyReader.create(indexFile, serializer, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -36,19 +36,23 @@ public class BigTableKeyReader implements KeyReader
|
||||||
private final RandomAccessReader indexFileReader;
|
private final RandomAccessReader indexFileReader;
|
||||||
private final IndexSerializer rowIndexEntrySerializer;
|
private final IndexSerializer rowIndexEntrySerializer;
|
||||||
private final long initialPosition;
|
private final long initialPosition;
|
||||||
|
private final boolean detailed;
|
||||||
private ByteBuffer key;
|
private ByteBuffer key;
|
||||||
private long dataPosition;
|
private long dataPosition;
|
||||||
private long keyPosition;
|
private long keyPosition;
|
||||||
|
/** only if detailed */
|
||||||
|
private RowIndexEntry rowIndexEntry;
|
||||||
|
|
||||||
private BigTableKeyReader(FileHandle indexFile,
|
private BigTableKeyReader(FileHandle indexFile,
|
||||||
RandomAccessReader indexFileReader,
|
RandomAccessReader indexFileReader,
|
||||||
IndexSerializer rowIndexEntrySerializer)
|
IndexSerializer rowIndexEntrySerializer,
|
||||||
|
boolean detailed)
|
||||||
{
|
{
|
||||||
this.indexFile = indexFile;
|
this.indexFile = indexFile;
|
||||||
this.indexFileReader = indexFileReader;
|
this.indexFileReader = indexFileReader;
|
||||||
this.rowIndexEntrySerializer = rowIndexEntrySerializer;
|
this.rowIndexEntrySerializer = rowIndexEntrySerializer;
|
||||||
this.initialPosition = indexFileReader.getFilePointer();
|
this.initialPosition = indexFileReader.getFilePointer();
|
||||||
|
this.detailed = detailed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static BigTableKeyReader create(RandomAccessReader indexFileReader, IndexSerializer serializer) throws IOException
|
public static BigTableKeyReader create(RandomAccessReader indexFileReader, IndexSerializer serializer) throws IOException
|
||||||
|
|
@ -58,7 +62,7 @@ public class BigTableKeyReader implements KeyReader
|
||||||
|
|
||||||
public static BigTableKeyReader create(FileHandle indexFile, RandomAccessReader indexFileReader, IndexSerializer serializer) throws IOException
|
public static BigTableKeyReader create(FileHandle indexFile, RandomAccessReader indexFileReader, IndexSerializer serializer) throws IOException
|
||||||
{
|
{
|
||||||
BigTableKeyReader iterator = new BigTableKeyReader(indexFile, indexFileReader, serializer);
|
BigTableKeyReader iterator = new BigTableKeyReader(indexFile, indexFileReader, serializer, false);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
iterator.advance();
|
iterator.advance();
|
||||||
|
|
@ -72,7 +76,7 @@ public class BigTableKeyReader implements KeyReader
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings({ "resource", "RedundantSuppression" }) // iFile and reader are closed in the BigTableKeyReader#close method
|
@SuppressWarnings({ "resource", "RedundantSuppression" }) // iFile and reader are closed in the BigTableKeyReader#close method
|
||||||
public static BigTableKeyReader create(FileHandle indexFile, IndexSerializer serializer) throws IOException
|
public static BigTableKeyReader create(FileHandle indexFile, IndexSerializer serializer, boolean detailed) throws IOException
|
||||||
{
|
{
|
||||||
FileHandle iFile = null;
|
FileHandle iFile = null;
|
||||||
RandomAccessReader reader = null;
|
RandomAccessReader reader = null;
|
||||||
|
|
@ -81,7 +85,7 @@ public class BigTableKeyReader implements KeyReader
|
||||||
{
|
{
|
||||||
iFile = indexFile.sharedCopy();
|
iFile = indexFile.sharedCopy();
|
||||||
reader = iFile.createReader();
|
reader = iFile.createReader();
|
||||||
iterator = new BigTableKeyReader(iFile, reader, serializer);
|
iterator = new BigTableKeyReader(iFile, reader, serializer, detailed);
|
||||||
iterator.advance();
|
iterator.advance();
|
||||||
return iterator;
|
return iterator;
|
||||||
}
|
}
|
||||||
|
|
@ -116,7 +120,14 @@ public class BigTableKeyReader implements KeyReader
|
||||||
{
|
{
|
||||||
keyPosition = indexFileReader.getFilePointer();
|
keyPosition = indexFileReader.getFilePointer();
|
||||||
key = ByteBufferUtil.readWithShortLength(indexFileReader);
|
key = ByteBufferUtil.readWithShortLength(indexFileReader);
|
||||||
|
if (detailed) {
|
||||||
|
rowIndexEntry = rowIndexEntrySerializer.deserialize(indexFileReader);
|
||||||
|
dataPosition = rowIndexEntry.getPosition();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
dataPosition = rowIndexEntrySerializer.deserializePositionAndSkip(indexFileReader);
|
dataPosition = rowIndexEntrySerializer.deserializePositionAndSkip(indexFileReader);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -152,6 +163,15 @@ public class BigTableKeyReader implements KeyReader
|
||||||
return dataPosition;
|
return dataPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public RowIndexEntry rowIndexEntry() {
|
||||||
|
assert detailed;
|
||||||
|
return rowIndexEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FileHandle indexFile() {
|
||||||
|
return indexFile;
|
||||||
|
}
|
||||||
|
|
||||||
public long indexPosition()
|
public long indexPosition()
|
||||||
{
|
{
|
||||||
return indexFileReader.getFilePointer();
|
return indexFileReader.getFilePointer();
|
||||||
|
|
|
||||||
|
|
@ -141,9 +141,9 @@ public class BigTableReader extends SSTableReaderWithFilter implements IndexSumm
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public KeyReader keyReader() throws IOException
|
public KeyReader keyReader(boolean detailed) throws IOException
|
||||||
{
|
{
|
||||||
return BigTableKeyReader.create(ifile, rowIndexEntrySerializer);
|
return BigTableKeyReader.create(ifile, rowIndexEntrySerializer, detailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -188,6 +188,7 @@ public class BigTableScanner extends SSTableScanner<BigTableReader, RowIndexEntr
|
||||||
"dfile=" + dfile +
|
"dfile=" + dfile +
|
||||||
" ifile=" + ifile +
|
" ifile=" + ifile +
|
||||||
" sstable=" + sstable +
|
" sstable=" + sstable +
|
||||||
|
" rangeIterator=" + rangeIterator +
|
||||||
")";
|
")";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,17 +18,25 @@
|
||||||
package org.apache.cassandra.io.sstable.format.big;
|
package org.apache.cassandra.io.sstable.format.big;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.file.NoSuchFileException;
|
import java.nio.file.NoSuchFileException;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.apache.cassandra.db.ClusteringComparator;
|
||||||
import org.apache.cassandra.db.ColumnFamilyStore;
|
import org.apache.cassandra.db.ColumnFamilyStore;
|
||||||
import org.apache.cassandra.db.DecoratedKey;
|
import org.apache.cassandra.db.DecoratedKey;
|
||||||
|
import org.apache.cassandra.db.rows.BTreeRow;
|
||||||
import org.apache.cassandra.db.rows.Cell;
|
import org.apache.cassandra.db.rows.Cell;
|
||||||
|
import org.apache.cassandra.db.rows.DeserializationHelper;
|
||||||
import org.apache.cassandra.db.rows.Row;
|
import org.apache.cassandra.db.rows.Row;
|
||||||
import org.apache.cassandra.db.rows.Unfiltered;
|
import org.apache.cassandra.db.rows.Unfiltered;
|
||||||
import org.apache.cassandra.db.rows.UnfilteredRowIterator;
|
import org.apache.cassandra.db.rows.UnfilteredRowIterator;
|
||||||
|
import org.apache.cassandra.db.rows.UnfilteredSerializer;
|
||||||
|
import org.apache.cassandra.io.sstable.CorruptSSTableException;
|
||||||
import org.apache.cassandra.io.sstable.IVerifier;
|
import org.apache.cassandra.io.sstable.IVerifier;
|
||||||
|
import org.apache.cassandra.io.sstable.IndexInfo;
|
||||||
import org.apache.cassandra.io.sstable.format.SSTableReader;
|
import org.apache.cassandra.io.sstable.format.SSTableReader;
|
||||||
import org.apache.cassandra.io.sstable.format.SortedTableVerifier;
|
import org.apache.cassandra.io.sstable.format.SortedTableVerifier;
|
||||||
import org.apache.cassandra.io.sstable.format.big.BigFormat.Components;
|
import org.apache.cassandra.io.sstable.format.big.BigFormat.Components;
|
||||||
|
|
@ -82,7 +90,7 @@ public class BigTableVerifier extends SortedTableVerifier<BigTableReader> implem
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
outputHandler.debug("Deserializing index summary for %s", sstable);
|
if (outputHandler.isDebugEnabled()) outputHandler.debug("Deserializing index summary for %s", sstable);
|
||||||
deserializeIndexSummary(sstable);
|
deserializeIndexSummary(sstable);
|
||||||
}
|
}
|
||||||
catch (Throwable t)
|
catch (Throwable t)
|
||||||
|
|
@ -99,6 +107,74 @@ public class BigTableVerifier extends SortedTableVerifier<BigTableReader> implem
|
||||||
super.verifyIndex();
|
super.verifyIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void deserializeIndex(SSTableReader sstable) throws IOException
|
||||||
|
{
|
||||||
|
DeserializationHelper deserializationHelper = new DeserializationHelper(sstable.metadata(),
|
||||||
|
sstable.descriptor.version.correspondingMessagingVersion(),
|
||||||
|
DeserializationHelper.Flag.LOCAL);
|
||||||
|
ClusteringComparator comparator = sstable.metadata().comparator;
|
||||||
|
try (BigTableKeyReader it = (BigTableKeyReader)sstable.keyReader(true))
|
||||||
|
{
|
||||||
|
if (it.isExhausted())
|
||||||
|
return;
|
||||||
|
ByteBuffer key;
|
||||||
|
boolean isFirst = true;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
key = it.key();
|
||||||
|
if (outputHandler.isDebugEnabled()) outputHandler.debug("Key %s", sstable.metadata().partitionKeyType.getString(key));
|
||||||
|
|
||||||
|
if (isFirst && !Objects.equals(key, sstable.getFirst().getKey()))
|
||||||
|
throw new CorruptSSTableException(new IOException("First partition does not match index"), it.toString());
|
||||||
|
else
|
||||||
|
isFirst = false;
|
||||||
|
RowIndexEntry rowIndexEntry = it.rowIndexEntry();
|
||||||
|
if (outputHandler.isDebugEnabled()) outputHandler.debug("rowIndexEntry %s", rowIndexEntry.toString());
|
||||||
|
|
||||||
|
long partitionBase = it.dataPosition();
|
||||||
|
int blockCount = rowIndexEntry.blockCount();
|
||||||
|
if (blockCount > 0)
|
||||||
|
{
|
||||||
|
long expectedNextOffset = 0;
|
||||||
|
RowIndexEntry.IndexInfoRetriever indexInfoRetriever = rowIndexEntry.openWithIndex(it.indexFile());
|
||||||
|
for (int blockIndex = 0; blockIndex < blockCount; blockIndex++)
|
||||||
|
{
|
||||||
|
IndexInfo indexInfo = indexInfoRetriever.columnsIndex(blockIndex);
|
||||||
|
if (outputHandler.isDebugEnabled()) outputHandler.debug("indexInfo %s", indexInfo.toString(sstable.metadata()));
|
||||||
|
|
||||||
|
long dataFileOffset = partitionBase + indexInfo.offset;
|
||||||
|
if (expectedNextOffset != 0)
|
||||||
|
{
|
||||||
|
if (expectedNextOffset != indexInfo.offset)
|
||||||
|
throw new CorruptSSTableException(new IOException("Row entry indexInfo offset + width should match next block offset:" + indexInfo.offset + " expected: " + expectedNextOffset), it.toString());
|
||||||
|
}
|
||||||
|
expectedNextOffset = indexInfo.offset + indexInfo.width;
|
||||||
|
dataFile.seek(dataFileOffset);
|
||||||
|
|
||||||
|
Unfiltered unfiltered = UnfilteredSerializer.serializer.deserialize(dataFile,
|
||||||
|
sstable.header,
|
||||||
|
deserializationHelper,
|
||||||
|
BTreeRow.sortedBuilder());
|
||||||
|
if (!Objects.equals(indexInfo.firstName, unfiltered.clustering()))
|
||||||
|
throw new CorruptSSTableException(new IOException("Unfiltered clustering in index does not match data:{info=" + indexInfo.toString(sstable.metadata()) + ", row:" + unfiltered.clustering().toString(sstable.metadata()) + "}"), it.toString());
|
||||||
|
|
||||||
|
if (comparator.compare(indexInfo.firstName, indexInfo.lastName) > 0)
|
||||||
|
throw new CorruptSSTableException(new IOException("First name is > Last name:{info=" + indexInfo.toString(sstable.metadata()) + "}"), it.toString());
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// The verifier checks that the partition key/position in the index and data match, here we waant to verify
|
||||||
|
// the entries clustering keys match.
|
||||||
|
while (it.advance()); // no-op, just check if index is readable
|
||||||
|
|
||||||
|
if (!Objects.equals(key, sstable.getLast().getKey()))
|
||||||
|
throw new CorruptSSTableException(new IOException("Last partition does not match index"), it.toString());
|
||||||
|
}
|
||||||
|
dataFile.reset();
|
||||||
|
}
|
||||||
|
|
||||||
private void logDuplicates(DecoratedKey key, Row first, int duplicateRows, long minTimestamp, long maxTimestamp)
|
private void logDuplicates(DecoratedKey key, Row first, int duplicateRows, long minTimestamp, long maxTimestamp)
|
||||||
{
|
{
|
||||||
String keyString = sstable.metadata().partitionKeyType.getString(key.getKey());
|
String keyString = sstable.metadata().partitionKeyType.getString(key.getKey());
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,7 @@ public class BigTableWriter extends SortedTableWriter<BigFormatPartitionWriter,
|
||||||
@Override
|
@Override
|
||||||
protected void onStartPartition(DecoratedKey key)
|
protected void onStartPartition(DecoratedKey key)
|
||||||
{
|
{
|
||||||
notifyObservers(o -> o.startPartition(key, partitionWriter.getInitialPosition(), indexWriter.writer.position()));
|
notifyObservers(o -> o.startPartition(key, partitionWriter.getPartitionStartPosition(), indexWriter.writer.position()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -97,7 +97,7 @@ public class BigTableWriter extends SortedTableWriter<BigFormatPartitionWriter,
|
||||||
// serialized size to the index-writer position
|
// serialized size to the index-writer position
|
||||||
long indexFilePosition = ByteBufferUtil.serializedSizeWithShortLength(key.getKey()) + indexWriter.writer.position();
|
long indexFilePosition = ByteBufferUtil.serializedSizeWithShortLength(key.getKey()) + indexWriter.writer.position();
|
||||||
|
|
||||||
RowIndexEntry entry = RowIndexEntry.create(partitionWriter.getInitialPosition(),
|
RowIndexEntry entry = RowIndexEntry.create(partitionWriter.getPartitionStartPosition(),
|
||||||
indexFilePosition,
|
indexFilePosition,
|
||||||
partitionLevelDeletion,
|
partitionLevelDeletion,
|
||||||
partitionWriter.getHeaderLength(),
|
partitionWriter.getHeaderLength(),
|
||||||
|
|
@ -229,16 +229,30 @@ public class BigTableWriter extends SortedTableWriter<BigFormatPartitionWriter,
|
||||||
return openInternal(null, openReason);
|
return openInternal(null, openReason);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setFirst(DecoratedKey key)
|
||||||
|
{
|
||||||
|
super.setFirst(key);
|
||||||
|
indexWriter.first = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setLast(DecoratedKey key)
|
||||||
|
{
|
||||||
|
super.setLast(key);
|
||||||
|
indexWriter.last = key;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encapsulates writing the index and filter for an SSTable. The state of this object is not valid until it has been closed.
|
* Encapsulates writing the index and filter for an SSTable. The state of this object is not valid until it has been closed.
|
||||||
*/
|
*/
|
||||||
protected static class IndexWriter extends SortedTableWriter.AbstractIndexWriter
|
public static class IndexWriter extends SortedTableWriter.AbstractIndexWriter
|
||||||
{
|
{
|
||||||
private final RowIndexEntry.IndexSerializer rowIndexEntrySerializer;
|
private final RowIndexEntry.IndexSerializer rowIndexEntrySerializer;
|
||||||
|
|
||||||
final SequentialWriter writer;
|
public final SequentialWriter writer;
|
||||||
final FileHandle.Builder builder;
|
final FileHandle.Builder builder;
|
||||||
final IndexSummaryBuilder summary;
|
public final IndexSummaryBuilder summary;
|
||||||
private DataPosition mark;
|
private DataPosition mark;
|
||||||
private DecoratedKey first;
|
private DecoratedKey first;
|
||||||
private DecoratedKey last;
|
private DecoratedKey last;
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ package org.apache.cassandra.io.sstable.format.big;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import com.codahale.metrics.Histogram;
|
import com.codahale.metrics.Histogram;
|
||||||
|
|
@ -150,6 +151,7 @@ public class RowIndexEntry extends AbstractRowIndexEntry
|
||||||
static final Histogram indexInfoCountHistogram;
|
static final Histogram indexInfoCountHistogram;
|
||||||
static final Histogram indexInfoGetsHistogram;
|
static final Histogram indexInfoGetsHistogram;
|
||||||
static final Histogram indexInfoReadsHistogram;
|
static final Histogram indexInfoReadsHistogram;
|
||||||
|
|
||||||
static
|
static
|
||||||
{
|
{
|
||||||
MetricNameFactory factory = new DefaultNameFactory(TYPE_NAME, "RowIndexEntry");
|
MetricNameFactory factory = new DefaultNameFactory(TYPE_NAME, "RowIndexEntry");
|
||||||
|
|
@ -259,7 +261,6 @@ public class RowIndexEntry extends AbstractRowIndexEntry
|
||||||
default RowIndexEntry deserialize(FileDataInput input) throws IOException
|
default RowIndexEntry deserialize(FileDataInput input) throws IOException
|
||||||
{
|
{
|
||||||
return deserialize(input, input.getFilePointer());
|
return deserialize(input, input.getFilePointer());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void serializeForCache(RowIndexEntry rie, DataOutputPlus out) throws IOException;
|
void serializeForCache(RowIndexEntry rie, DataOutputPlus out) throws IOException;
|
||||||
|
|
@ -358,6 +359,9 @@ public class RowIndexEntry extends AbstractRowIndexEntry
|
||||||
|
|
||||||
int indexedPartSize = size - serializedSize(deletionTime, headerLength, columnsIndexCount, version);
|
int indexedPartSize = size - serializedSize(deletionTime, headerLength, columnsIndexCount, version);
|
||||||
|
|
||||||
|
// We should never get here with 0/1 entries
|
||||||
|
/** See: {@link org.apache.cassandra.io.sstable.format.big.RowIndexEntry#create} */
|
||||||
|
assert columnsIndexCount > 1;
|
||||||
if (size <= DatabaseDescriptor.getColumnIndexCacheSize())
|
if (size <= DatabaseDescriptor.getColumnIndexCacheSize())
|
||||||
{
|
{
|
||||||
return new IndexedEntry(position, in, deletionTime, headerLength, columnsIndexCount,
|
return new IndexedEntry(position, in, deletionTime, headerLength, columnsIndexCount,
|
||||||
|
|
@ -663,6 +667,21 @@ public class RowIndexEntry extends AbstractRowIndexEntry
|
||||||
|
|
||||||
in.readUnsignedVInt();
|
in.readUnsignedVInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return "IndexedEntry{" +
|
||||||
|
"position=" + position +
|
||||||
|
", deletionTime=" + deletionTime +
|
||||||
|
", headerLength=" + headerLength +
|
||||||
|
", columnsIndex=" + Arrays.toString(columnsIndex) +
|
||||||
|
", offsets=" + Arrays.toString(offsets) +
|
||||||
|
", indexedPartSize=" + indexedPartSize +
|
||||||
|
", idxInfoSerializer=" + idxInfoSerializer +
|
||||||
|
", version=" + version +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -678,6 +697,7 @@ public class RowIndexEntry extends AbstractRowIndexEntry
|
||||||
BASE_SIZE = ObjectSizes.measure(new ShallowIndexedEntry(0, 0, DeletionTime.LIVE, 0, 10, 0, null, BigFormat.getInstance().getLatestVersion()));
|
BASE_SIZE = ObjectSizes.measure(new ShallowIndexedEntry(0, 0, DeletionTime.LIVE, 0, 10, 0, null, BigFormat.getInstance().getLatestVersion()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// only for cache serialization
|
||||||
private final long indexFilePosition;
|
private final long indexFilePosition;
|
||||||
|
|
||||||
private final DeletionTime deletionTime;
|
private final DeletionTime deletionTime;
|
||||||
|
|
@ -701,8 +721,6 @@ public class RowIndexEntry extends AbstractRowIndexEntry
|
||||||
{
|
{
|
||||||
super(dataFilePosition);
|
super(dataFilePosition);
|
||||||
|
|
||||||
assert columnIndexCount > 1;
|
|
||||||
|
|
||||||
this.indexFilePosition = indexFilePosition;
|
this.indexFilePosition = indexFilePosition;
|
||||||
this.headerLength = headerLength;
|
this.headerLength = headerLength;
|
||||||
this.deletionTime = deletionTime;
|
this.deletionTime = deletionTime;
|
||||||
|
|
@ -810,6 +828,23 @@ public class RowIndexEntry extends AbstractRowIndexEntry
|
||||||
|
|
||||||
in.readUnsignedVInt();
|
in.readUnsignedVInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return "ShallowIndexedEntry{" +
|
||||||
|
"position=" + position +
|
||||||
|
", indexFilePosition=" + indexFilePosition +
|
||||||
|
", deletionTime=" + deletionTime +
|
||||||
|
", headerLength=" + headerLength +
|
||||||
|
", columnsIndexCount=" + columnsIndexCount +
|
||||||
|
", indexedPartSize=" + indexedPartSize +
|
||||||
|
", offsetsOffset=" + offsetsOffset +
|
||||||
|
", idxInfoSerializer=" + idxInfoSerializer +
|
||||||
|
", fieldsSerializedSize=" + fieldsSerializedSize +
|
||||||
|
", version=" + version +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class ShallowInfoRetriever extends FileIndexInfoRetriever
|
private static final class ShallowInfoRetriever extends FileIndexInfoRetriever
|
||||||
|
|
@ -900,4 +935,12 @@ public class RowIndexEntry extends AbstractRowIndexEntry
|
||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return "RowIndexEntry{" +
|
||||||
|
"position=" + position +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,7 @@ class BtiFormatPartitionWriter extends SortedTablePartitionWriter
|
||||||
super.addUnfiltered(unfiltered);
|
super.addUnfiltered(unfiltered);
|
||||||
|
|
||||||
// if we hit the column index size that we have to index after, go ahead and index it.
|
// if we hit the column index size that we have to index after, go ahead and index it.
|
||||||
if (currentPosition() - startPosition >= rowIndexBlockSize)
|
if (currentOffsetInPartition() - indexBlockStartOffset >= rowIndexBlockSize)
|
||||||
addIndexBlock();
|
addIndexBlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -112,7 +112,7 @@ class BtiFormatPartitionWriter extends SortedTablePartitionWriter
|
||||||
|
|
||||||
protected void addIndexBlock() throws IOException
|
protected void addIndexBlock() throws IOException
|
||||||
{
|
{
|
||||||
IndexInfo cIndexInfo = new IndexInfo(startPosition, startOpenMarker);
|
IndexInfo cIndexInfo = new IndexInfo(indexBlockStartOffset, startOpenMarker);
|
||||||
rowTrie.add(firstClustering, lastClustering, cIndexInfo);
|
rowTrie.add(firstClustering, lastClustering, cIndexInfo);
|
||||||
firstClustering = null;
|
firstClustering = null;
|
||||||
++rowIndexBlockCount;
|
++rowIndexBlockCount;
|
||||||
|
|
|
||||||
|
|
@ -317,7 +317,7 @@ public class BtiTableReader extends SSTableReaderWithFilter
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PartitionIterator keyReader() throws IOException
|
public PartitionIterator keyReader(boolean detailed) throws IOException
|
||||||
{
|
{
|
||||||
return PartitionIterator.create(partitionIndex, metadata().partitioner, rowIndexFile, dfile, descriptor.version);
|
return PartitionIterator.create(partitionIndex, metadata().partitioner, rowIndexFile, dfile, descriptor.version);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,7 @@ public class BtiTableWriter extends SortedTableWriter<BtiFormatPartitionWriter,
|
||||||
@Override
|
@Override
|
||||||
protected TrieIndexEntry createRowIndexEntry(DecoratedKey key, DeletionTime partitionLevelDeletion, long finishResult) throws IOException
|
protected TrieIndexEntry createRowIndexEntry(DecoratedKey key, DeletionTime partitionLevelDeletion, long finishResult) throws IOException
|
||||||
{
|
{
|
||||||
TrieIndexEntry entry = TrieIndexEntry.create(partitionWriter.getInitialPosition(),
|
TrieIndexEntry entry = TrieIndexEntry.create(partitionWriter.getPartitionStartPosition(),
|
||||||
finishResult,
|
finishResult,
|
||||||
partitionLevelDeletion,
|
partitionLevelDeletion,
|
||||||
partitionWriter.getRowIndexBlockCount());
|
partitionWriter.getRowIndexBlockCount());
|
||||||
|
|
|
||||||
|
|
@ -145,7 +145,7 @@ public class IndexSummaryBuilder implements AutoCloseable
|
||||||
*/
|
*/
|
||||||
private static long getEntrySize(long keySize)
|
private static long getEntrySize(long keySize)
|
||||||
{
|
{
|
||||||
return keySize + TypeSizes.sizeof(0L);
|
return keySize + TypeSizes.LONG_SIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// the index file has been flushed to the provided position; stash it and use that to recalculate our max readable boundary
|
// the index file has been flushed to the provided position; stash it and use that to recalculate our max readable boundary
|
||||||
|
|
@ -188,6 +188,34 @@ public class IndexSummaryBuilder implements AutoCloseable
|
||||||
{
|
{
|
||||||
return maybeAddEntry(decoratedKey, indexStart, 0, 0);
|
return maybeAddEntry(decoratedKey, indexStart, 0, 0);
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* @param keyBytes the key data for this record
|
||||||
|
* @param offset key data offset in the keyBytes array
|
||||||
|
* @param length key data length
|
||||||
|
* @param indexStart the position in the index file this record begins
|
||||||
|
*/
|
||||||
|
public IndexSummaryBuilder maybeAddEntry(byte[] keyBytes, int offset, int length, long indexStart) throws IOException
|
||||||
|
{
|
||||||
|
if (keysWritten == nextSamplePosition)
|
||||||
|
{
|
||||||
|
if ((entries.length() + getEntrySize(length)) <= Integer.MAX_VALUE)
|
||||||
|
{
|
||||||
|
offsets.writeInt((int) entries.length());
|
||||||
|
entries.write(keyBytes, offset, length);
|
||||||
|
entries.writeLong(indexStart);
|
||||||
|
setNextSamplePosition(keysWritten);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// we cannot fully sample this sstable due to too much memory in the index summary, so let's tell the user
|
||||||
|
logger.error("Memory capacity of index summary exceeded (2GiB), index summary will not cover full sstable, " +
|
||||||
|
"you should increase min_sampling_level");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
keysWritten++;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,8 @@ import java.util.EnumMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.apache.cassandra.db.marshal.AbstractType;
|
||||||
|
import org.apache.cassandra.io.sstable.ClusteringDescriptor;
|
||||||
import com.clearspring.analytics.stream.cardinality.HyperLogLogPlus;
|
import com.clearspring.analytics.stream.cardinality.HyperLogLogPlus;
|
||||||
import com.clearspring.analytics.stream.cardinality.ICardinality;
|
import com.clearspring.analytics.stream.cardinality.ICardinality;
|
||||||
import org.apache.cassandra.db.Clustering;
|
import org.apache.cassandra.db.Clustering;
|
||||||
|
|
@ -105,7 +107,7 @@ public class MetadataCollector implements PartitionStatisticsCollector
|
||||||
}
|
}
|
||||||
|
|
||||||
protected EstimatedHistogram estimatedPartitionSize = defaultPartitionSizeHistogram();
|
protected EstimatedHistogram estimatedPartitionSize = defaultPartitionSizeHistogram();
|
||||||
// TODO: cound the number of row per partition (either with the number of cells, or instead)
|
// TODO: count the number of row per partition (either with the number of cells, or instead)
|
||||||
protected EstimatedHistogram estimatedCellPerPartitionCount = defaultCellPerPartitionCountHistogram();
|
protected EstimatedHistogram estimatedCellPerPartitionCount = defaultCellPerPartitionCountHistogram();
|
||||||
protected IntervalSet<CommitLogPosition> commitLogIntervals = IntervalSet.empty();
|
protected IntervalSet<CommitLogPosition> commitLogIntervals = IntervalSet.empty();
|
||||||
protected final MinMaxLongTracker timestampTracker = new MinMaxLongTracker();
|
protected final MinMaxLongTracker timestampTracker = new MinMaxLongTracker();
|
||||||
|
|
@ -122,6 +124,8 @@ public class MetadataCollector implements PartitionStatisticsCollector
|
||||||
* be a corresponding start bound that is smaller).
|
* be a corresponding start bound that is smaller).
|
||||||
*/
|
*/
|
||||||
private ClusteringPrefix<?> minClustering = ClusteringBound.MAX_START;
|
private ClusteringPrefix<?> minClustering = ClusteringBound.MAX_START;
|
||||||
|
private final ClusteringDescriptor minClusteringDescriptor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The largest clustering prefix for any {@link Unfiltered} in the sstable.
|
* The largest clustering prefix for any {@link Unfiltered} in the sstable.
|
||||||
*
|
*
|
||||||
|
|
@ -129,6 +133,7 @@ public class MetadataCollector implements PartitionStatisticsCollector
|
||||||
* be a corresponding end bound that is bigger).
|
* be a corresponding end bound that is bigger).
|
||||||
*/
|
*/
|
||||||
private ClusteringPrefix<?> maxClustering = ClusteringBound.MIN_END;
|
private ClusteringPrefix<?> maxClustering = ClusteringBound.MIN_END;
|
||||||
|
private final ClusteringDescriptor maxClusteringDescriptor;
|
||||||
|
|
||||||
protected boolean hasLegacyCounterShards = false;
|
protected boolean hasLegacyCounterShards = false;
|
||||||
private boolean hasPartitionLevelDeletions = false;
|
private boolean hasPartitionLevelDeletions = false;
|
||||||
|
|
@ -159,6 +164,9 @@ public class MetadataCollector implements PartitionStatisticsCollector
|
||||||
{
|
{
|
||||||
this.comparator = comparator;
|
this.comparator = comparator;
|
||||||
this.originatingHostId = originatingHostId;
|
this.originatingHostId = originatingHostId;
|
||||||
|
AbstractType<?>[] clusteringTypes = comparator.subtypes().toArray(AbstractType[]::new);
|
||||||
|
this.minClusteringDescriptor = new ClusteringDescriptor(clusteringTypes).resetMaxStart();
|
||||||
|
this.maxClusteringDescriptor = new ClusteringDescriptor(clusteringTypes).resetMinEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
public MetadataCollector(Iterable<SSTableReader> sstables, ClusteringComparator comparator)
|
public MetadataCollector(Iterable<SSTableReader> sstables, ClusteringComparator comparator)
|
||||||
|
|
@ -185,6 +193,14 @@ public class MetadataCollector implements PartitionStatisticsCollector
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MetadataCollector addKey(byte[] key, int offset, int length)
|
||||||
|
{
|
||||||
|
long hashed = MurmurHash.hash2_64(key, offset, length, 0);
|
||||||
|
cardinality.offerHashed(hashed);
|
||||||
|
totalTombstones = 0;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public MetadataCollector addPartitionSizeInBytes(long partitionSize)
|
public MetadataCollector addPartitionSizeInBytes(long partitionSize)
|
||||||
{
|
{
|
||||||
estimatedPartitionSize.add(partitionSize);
|
estimatedPartitionSize.add(partitionSize);
|
||||||
|
|
@ -236,6 +252,15 @@ public class MetadataCollector implements PartitionStatisticsCollector
|
||||||
updateTombstoneCount();
|
updateTombstoneCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cell level stats, if we accept that LDT and LET are the same...
|
||||||
|
*/
|
||||||
|
public void updateCellLiveness(LivenessInfo newInfo)
|
||||||
|
{
|
||||||
|
++currentPartitionCells;
|
||||||
|
update(newInfo);
|
||||||
|
}
|
||||||
|
|
||||||
public void updatePartitionDeletion(DeletionTime dt)
|
public void updatePartitionDeletion(DeletionTime dt)
|
||||||
{
|
{
|
||||||
if (!dt.isLive())
|
if (!dt.isLive())
|
||||||
|
|
@ -299,6 +324,32 @@ public class MetadataCollector implements PartitionStatisticsCollector
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void updateClusteringValues(ClusteringDescriptor newClustering) {
|
||||||
|
// 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 (newClustering == null || newClustering.clusteringKind().isBoundary())
|
||||||
|
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 (ClusteringComparator.compare(newClustering, maxClusteringDescriptor) > 0)
|
||||||
|
{
|
||||||
|
maxClusteringDescriptor.copy(newClustering);
|
||||||
|
if (minClusteringDescriptor.isMaxStart()) // min is unset
|
||||||
|
{
|
||||||
|
minClusteringDescriptor.copy(newClustering);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (ClusteringComparator.compare(newClustering, minClusteringDescriptor) < 0)
|
||||||
|
{
|
||||||
|
minClusteringDescriptor.copy(newClustering);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void updateClusteringValues(Clustering<?> clustering)
|
public void updateClusteringValues(Clustering<?> clustering)
|
||||||
{
|
{
|
||||||
if (clustering == Clustering.STATIC_CLUSTERING)
|
if (clustering == Clustering.STATIC_CLUSTERING)
|
||||||
|
|
@ -361,6 +412,13 @@ public class MetadataCollector implements PartitionStatisticsCollector
|
||||||
|
|
||||||
Map<MetadataType, MetadataComponent> components = new EnumMap<>(MetadataType.class);
|
Map<MetadataType, MetadataComponent> components = new EnumMap<>(MetadataType.class);
|
||||||
components.put(MetadataType.VALIDATION, new ValidationMetadata(partitioner, bloomFilterFPChance));
|
components.put(MetadataType.VALIDATION, new ValidationMetadata(partitioner, bloomFilterFPChance));
|
||||||
|
Slice coveredClustering;
|
||||||
|
if (!minClusteringDescriptor.isMaxStart()) // min is end only if the descriptors are unused
|
||||||
|
{
|
||||||
|
minClustering = minClusteringDescriptor.toClusteringPrefix(comparator.subtypes());
|
||||||
|
maxClustering = maxClusteringDescriptor.toClusteringPrefix(comparator.subtypes());
|
||||||
|
}
|
||||||
|
coveredClustering = Slice.make(minClustering.retainable().asStartBound(), maxClustering.retainable().asEndBound());
|
||||||
components.put(MetadataType.STATS, new StatsMetadata(estimatedPartitionSize,
|
components.put(MetadataType.STATS, new StatsMetadata(estimatedPartitionSize,
|
||||||
estimatedCellPerPartitionCount,
|
estimatedCellPerPartitionCount,
|
||||||
commitLogIntervals,
|
commitLogIntervals,
|
||||||
|
|
@ -374,7 +432,7 @@ public class MetadataCollector implements PartitionStatisticsCollector
|
||||||
estimatedTombstoneDropTime.build(),
|
estimatedTombstoneDropTime.build(),
|
||||||
sstableLevel,
|
sstableLevel,
|
||||||
comparator.subtypes(),
|
comparator.subtypes(),
|
||||||
Slice.make(minClustering.retainable().asStartBound(), maxClustering.retainable().asEndBound()),
|
coveredClustering,
|
||||||
hasLegacyCounterShards,
|
hasLegacyCounterShards,
|
||||||
repairedAt,
|
repairedAt,
|
||||||
totalColumnsSet,
|
totalColumnsSet,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,151 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.cassandra.io.util;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
import org.apache.cassandra.utils.vint.VIntCoding;
|
||||||
|
import org.jctools.util.Pow2;
|
||||||
|
|
||||||
|
public class ResizableByteBuffer
|
||||||
|
{
|
||||||
|
private int length = 0;
|
||||||
|
private byte[] bytes = new byte[64];
|
||||||
|
private ByteBuffer buffer = ByteBuffer.wrap(bytes);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the length (UnsignedShort) from the data reader, and then loads the new data into the buffer.
|
||||||
|
*
|
||||||
|
* @param dataReader data source
|
||||||
|
* @return true if the underlying array/buffer had to be resized to accomodate the new data, false otherwise
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public final boolean loadShortLength(RandomAccessReader dataReader) throws IOException
|
||||||
|
{
|
||||||
|
int newLength = dataReader.readUnsignedShort();
|
||||||
|
return load(dataReader, newLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the new data (of the given length) into the buffer. Buffer is rewritten.
|
||||||
|
*
|
||||||
|
* @param dataReader data source
|
||||||
|
* @param newLength new data length
|
||||||
|
* @return true if the underlying array/buffer had to be resized to accomodate the new data, false otherwise
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public final boolean load(RandomAccessReader dataReader, int newLength) throws IOException
|
||||||
|
{
|
||||||
|
boolean resize = maybeResizeForOverwrite(newLength);
|
||||||
|
dataReader.readFully(bytes, 0, newLength);
|
||||||
|
setLength(newLength);
|
||||||
|
return resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void resetBuffer() {
|
||||||
|
setLength(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final byte[] bytes()
|
||||||
|
{
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final ByteBuffer buffer()
|
||||||
|
{
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final int length()
|
||||||
|
{
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final void overwrite(byte[] newBytes, int newLength)
|
||||||
|
{
|
||||||
|
maybeResizeForOverwrite(newLength);
|
||||||
|
System.arraycopy(newBytes, 0, bytes, 0, newLength);
|
||||||
|
setLength(newLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setLength(int newLength)
|
||||||
|
{
|
||||||
|
int currentLength = length;
|
||||||
|
if (newLength != currentLength)
|
||||||
|
{
|
||||||
|
length = newLength;
|
||||||
|
buffer.limit(newLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads new partial data (of the given length) into the buffer. New data is appended.
|
||||||
|
*
|
||||||
|
* @param dataReader data source
|
||||||
|
* @param partLength new data length
|
||||||
|
* @return true if the underlying array/buffer had to be resized to accomodate the new data, false otherwise
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public final boolean loadPart(RandomAccessReader dataReader, int partLength) throws IOException
|
||||||
|
{
|
||||||
|
int newLength = length + partLength;
|
||||||
|
boolean resize = maybeResizeForAppend(newLength);
|
||||||
|
dataReader.readFully(bytes, length, partLength);
|
||||||
|
setLength(newLength);
|
||||||
|
return resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean writeUnsignedVInt(long value)
|
||||||
|
{
|
||||||
|
int currentPosition = length;
|
||||||
|
int newLength = currentPosition + 9;
|
||||||
|
boolean resize = maybeResizeForAppend(newLength);
|
||||||
|
VIntCoding.writeUnsignedVInt(value, buffer.position(currentPosition).limit(newLength));
|
||||||
|
setLength(buffer.position());
|
||||||
|
buffer.position(0);
|
||||||
|
return resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean maybeResizeForOverwrite(int newLength)
|
||||||
|
{
|
||||||
|
if (newLength > bytes.length)
|
||||||
|
{
|
||||||
|
bytes = new byte[Pow2.roundToPowerOfTwo(newLength)];
|
||||||
|
buffer = ByteBuffer.wrap(bytes);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean maybeResizeForAppend(int newLength)
|
||||||
|
{
|
||||||
|
if (newLength > bytes.length)
|
||||||
|
{
|
||||||
|
int currLength = length;
|
||||||
|
byte[] currBytes = bytes;
|
||||||
|
bytes = new byte[Pow2.roundToPowerOfTwo(newLength)];
|
||||||
|
if (currLength != 0)
|
||||||
|
System.arraycopy(currBytes,0 , bytes, 0, currLength);
|
||||||
|
buffer = ByteBuffer.wrap(bytes);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -436,8 +436,15 @@ public final class JsonTransformer
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
AbstractType<?> type = column.cellValueType();
|
AbstractType<?> type = column.cellValueType();
|
||||||
|
try
|
||||||
|
{
|
||||||
json.writeRawValue(type.toJSONString(clustering.get(i), clustering.accessor(), ProtocolVersion.CURRENT));
|
json.writeRawValue(type.toJSONString(clustering.get(i), clustering.accessor(), ProtocolVersion.CURRENT));
|
||||||
}
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
json.writeString("unsupported conversion to JSON");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
json.writeEndArray();
|
json.writeEndArray();
|
||||||
objectIndenter.setCompact(false);
|
objectIndenter.setCompact(false);
|
||||||
|
|
@ -552,8 +559,14 @@ public final class JsonTransformer
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
json.writeFieldName("value");
|
json.writeFieldName("value");
|
||||||
|
try
|
||||||
|
{
|
||||||
json.writeRawValue(cellType.toJSONString(cell.value(), cell.accessor(), ProtocolVersion.CURRENT));
|
json.writeRawValue(cellType.toJSONString(cell.value(), cell.accessor(), ProtocolVersion.CURRENT));
|
||||||
}
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
json.writeString("unsupported conversion to JSON");
|
||||||
|
}
|
||||||
|
}
|
||||||
if (liveInfo.isEmpty() || cell.timestamp() != liveInfo.timestamp())
|
if (liveInfo.isEmpty() || cell.timestamp() != liveInfo.timestamp())
|
||||||
{
|
{
|
||||||
json.writeFieldName("tstamp");
|
json.writeFieldName("tstamp");
|
||||||
|
|
|
||||||
|
|
@ -101,6 +101,22 @@ public class BloomFilter extends WrappedSharedCloseable implements IFilter
|
||||||
return indexes;
|
return indexes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private long[] indexes(byte[] key, int offset, int length)
|
||||||
|
{
|
||||||
|
// we use the same array both for storing the hash result, and for storing the indexes we return,
|
||||||
|
// so that we do not need to allocate two arrays.
|
||||||
|
long[] indexes = reusableIndexes.get();
|
||||||
|
return indexes(key, offset, length, indexes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private long[] indexes(byte[] key, int offset, int length, long[] indexes)
|
||||||
|
{
|
||||||
|
MurmurHash.hash3_x64_128(key, offset, length, 0, indexes);
|
||||||
|
|
||||||
|
setIndexes(indexes[1], indexes[0], hashCount, bitset.capacity(), indexes);
|
||||||
|
return indexes;
|
||||||
|
}
|
||||||
|
|
||||||
@Inline
|
@Inline
|
||||||
private void setIndexes(long base, long inc, int count, long max, long[] results)
|
private void setIndexes(long base, long inc, int count, long max, long[] results)
|
||||||
{
|
{
|
||||||
|
|
@ -121,6 +137,24 @@ public class BloomFilter extends WrappedSharedCloseable implements IFilter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void add(byte[] key, int offset, int length)
|
||||||
|
{
|
||||||
|
long[] indexes = indexes(key, offset, length);
|
||||||
|
for (int i = 0; i < hashCount; i++)
|
||||||
|
{
|
||||||
|
bitset.set(indexes[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(byte[] key, int offset, int length, long[] indexes)
|
||||||
|
{
|
||||||
|
indexes(key, offset, length, indexes);
|
||||||
|
for (int i = 0; i < hashCount; i++)
|
||||||
|
{
|
||||||
|
bitset.set(indexes[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final boolean isPresent(FilterKey key)
|
public final boolean isPresent(FilterKey key)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,11 @@ public class ByteArrayUtil
|
||||||
return FastByteOperations.compareUnsigned(o1, off1, len, o2, off2, len);
|
return FastByteOperations.compareUnsigned(o1, off1, len, o2, off2, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static int compareUnsigned(byte[] o1, int off1, int len1, byte[] o2, int off2, int len2)
|
||||||
|
{
|
||||||
|
return FastByteOperations.compareUnsigned(o1, off1, len1, o2, off2, len2);
|
||||||
|
}
|
||||||
|
|
||||||
public static byte[] bytes(byte b)
|
public static byte[] bytes(byte b)
|
||||||
{
|
{
|
||||||
return new byte[] {b};
|
return new byte[] {b};
|
||||||
|
|
@ -234,6 +239,16 @@ public class ByteArrayUtil
|
||||||
out.write(buffer);
|
out.write(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void writeWithShortLength(byte[] buffer, int offset, int length, DataOutput out) throws IOException
|
||||||
|
{
|
||||||
|
assert length <= buffer.length;
|
||||||
|
assert offset <= length;
|
||||||
|
assert length <= FBUtilities.MAX_UNSIGNED_SHORT
|
||||||
|
: String.format("Attempted serializing to buffer exceeded maximum of %s bytes: %s", FBUtilities.MAX_UNSIGNED_SHORT, length);
|
||||||
|
out.writeShort(length);
|
||||||
|
out.write(buffer, offset, length);
|
||||||
|
}
|
||||||
|
|
||||||
public static void writeWithVIntLength(byte[] bytes, DataOutputPlus out) throws IOException
|
public static void writeWithVIntLength(byte[] bytes, DataOutputPlus out) throws IOException
|
||||||
{
|
{
|
||||||
out.writeUnsignedVInt32(bytes.length);
|
out.writeUnsignedVInt32(bytes.length);
|
||||||
|
|
|
||||||
|
|
@ -93,6 +93,66 @@ public class MurmurHash
|
||||||
return h;
|
return h;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We can go all in and do a unified unsafe implementation to serve arrays/direct memory and layer the byte buffer
|
||||||
|
* types on top. Left for later.
|
||||||
|
*/
|
||||||
|
public static long hash2_64(byte[] key, int offset, int length, long seed)
|
||||||
|
{
|
||||||
|
long m64 = 0xc6a4a7935bd1e995L;
|
||||||
|
int r64 = 47;
|
||||||
|
|
||||||
|
long h64 = (seed & 0xffffffffL) ^ (m64 * length);
|
||||||
|
|
||||||
|
int lenLongs = length >> 3;
|
||||||
|
|
||||||
|
for (int i = 0; i < lenLongs; ++i)
|
||||||
|
{
|
||||||
|
int i_8 = i << 3;
|
||||||
|
|
||||||
|
long k64 = ((long) key[offset+i_8+0] & 0xff) + (((long) key[offset+i_8+1] & 0xff)<<8) +
|
||||||
|
(((long) key[offset+i_8+2] & 0xff)<<16) + (((long) key[offset+i_8+3] & 0xff)<<24) +
|
||||||
|
(((long) key[offset+i_8+4] & 0xff)<<32) + (((long) key[offset+i_8+5] & 0xff)<<40) +
|
||||||
|
(((long) key[offset+i_8+6] & 0xff)<<48) + (((long) key[offset+i_8+7] & 0xff)<<56);
|
||||||
|
|
||||||
|
k64 *= m64;
|
||||||
|
k64 ^= k64 >>> r64;
|
||||||
|
k64 *= m64;
|
||||||
|
|
||||||
|
h64 ^= k64;
|
||||||
|
h64 *= m64;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rem = length & 0x7;
|
||||||
|
|
||||||
|
switch (rem)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
h64 ^= (long) key[offset + length - rem + 6] << 48;
|
||||||
|
case 6:
|
||||||
|
h64 ^= (long) key[offset + length - rem + 5] << 40;
|
||||||
|
case 5:
|
||||||
|
h64 ^= (long) key[offset + length - rem + 4] << 32;
|
||||||
|
case 4:
|
||||||
|
h64 ^= (long) key[offset + length - rem + 3] << 24;
|
||||||
|
case 3:
|
||||||
|
h64 ^= (long) key[offset + length - rem + 2] << 16;
|
||||||
|
case 2:
|
||||||
|
h64 ^= (long) key[offset + length - rem + 1] << 8;
|
||||||
|
case 1:
|
||||||
|
h64 ^= (long) key[offset + length - rem];
|
||||||
|
h64 *= m64;
|
||||||
|
}
|
||||||
|
|
||||||
|
h64 ^= h64 >>> r64;
|
||||||
|
h64 *= m64;
|
||||||
|
h64 ^= h64 >>> r64;
|
||||||
|
|
||||||
|
return h64;
|
||||||
|
}
|
||||||
|
|
||||||
public static long hash2_64(ByteBuffer key, int offset, int length, long seed)
|
public static long hash2_64(ByteBuffer key, int offset, int length, long seed)
|
||||||
{
|
{
|
||||||
long m64 = 0xc6a4a7935bd1e995L;
|
long m64 = 0xc6a4a7935bd1e995L;
|
||||||
|
|
@ -159,6 +219,16 @@ public class MurmurHash
|
||||||
(((long) key.get(blockOffset + 6) & 0xff) << 48) + (((long) key.get(blockOffset + 7) & 0xff) << 56);
|
(((long) key.get(blockOffset + 6) & 0xff) << 48) + (((long) key.get(blockOffset + 7) & 0xff) << 56);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected static long getBlock(byte[] key, int offset, int index)
|
||||||
|
{
|
||||||
|
int i_8 = index << 3;
|
||||||
|
int blockOffset = offset + i_8;
|
||||||
|
return ((long) key[blockOffset + 0] & 0xff) + (((long) key[blockOffset + 1] & 0xff) << 8) +
|
||||||
|
(((long) key[blockOffset + 2] & 0xff) << 16) + (((long) key[blockOffset + 3] & 0xff) << 24) +
|
||||||
|
(((long) key[blockOffset + 4] & 0xff) << 32) + (((long) key[blockOffset + 5] & 0xff) << 40) +
|
||||||
|
(((long) key[blockOffset + 6] & 0xff) << 48) + (((long) key[blockOffset + 7] & 0xff) << 56);
|
||||||
|
}
|
||||||
|
|
||||||
protected static long rotl64(long v, int n)
|
protected static long rotl64(long v, int n)
|
||||||
{
|
{
|
||||||
return ((v << n) | (v >>> (64 - n)));
|
return ((v << n) | (v >>> (64 - n)));
|
||||||
|
|
@ -251,6 +321,82 @@ public class MurmurHash
|
||||||
result[1] = h2;
|
result[1] = h2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void hash3_x64_128(byte[] key, int offset, int length, long seed, long[] result)
|
||||||
|
{
|
||||||
|
final int nblocks = length >> 4; // Process as 128-bit blocks.
|
||||||
|
|
||||||
|
long h1 = seed;
|
||||||
|
long h2 = seed;
|
||||||
|
|
||||||
|
long c1 = 0x87c37b91114253d5L;
|
||||||
|
long c2 = 0x4cf5ad432745937fL;
|
||||||
|
|
||||||
|
//----------
|
||||||
|
// body
|
||||||
|
|
||||||
|
for(int i = 0; i < nblocks; i++)
|
||||||
|
{
|
||||||
|
long k1 = getBlock(key, offset, i * 2 + 0);
|
||||||
|
long k2 = getBlock(key, offset, i * 2 + 1);
|
||||||
|
|
||||||
|
k1 *= c1; k1 = rotl64(k1,31); k1 *= c2; h1 ^= k1;
|
||||||
|
|
||||||
|
h1 = rotl64(h1,27); h1 += h2; h1 = h1*5+0x52dce729;
|
||||||
|
|
||||||
|
k2 *= c2; k2 = rotl64(k2,33); k2 *= c1; h2 ^= k2;
|
||||||
|
|
||||||
|
h2 = rotl64(h2,31); h2 += h1; h2 = h2*5+0x38495ab5;
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------
|
||||||
|
// tail
|
||||||
|
|
||||||
|
// Advance offset to the unprocessed tail of the data.
|
||||||
|
offset += nblocks * 16;
|
||||||
|
|
||||||
|
long k1 = 0;
|
||||||
|
long k2 = 0;
|
||||||
|
|
||||||
|
switch(length & 15)
|
||||||
|
{
|
||||||
|
case 15: k2 ^= ((long) key[offset+14]) << 48;
|
||||||
|
case 14: k2 ^= ((long) key[offset+13]) << 40;
|
||||||
|
case 13: k2 ^= ((long) key[offset+12]) << 32;
|
||||||
|
case 12: k2 ^= ((long) key[offset+11]) << 24;
|
||||||
|
case 11: k2 ^= ((long) key[offset+10]) << 16;
|
||||||
|
case 10: k2 ^= ((long) key[offset+9]) << 8;
|
||||||
|
case 9: k2 ^= ((long) key[offset+8]) << 0;
|
||||||
|
k2 *= c2; k2 = rotl64(k2,33); k2 *= c1; h2 ^= k2;
|
||||||
|
|
||||||
|
case 8: k1 ^= ((long) key[offset+7]) << 56;
|
||||||
|
case 7: k1 ^= ((long) key[offset+6]) << 48;
|
||||||
|
case 6: k1 ^= ((long) key[offset+5]) << 40;
|
||||||
|
case 5: k1 ^= ((long) key[offset+4]) << 32;
|
||||||
|
case 4: k1 ^= ((long) key[offset+3]) << 24;
|
||||||
|
case 3: k1 ^= ((long) key[offset+2]) << 16;
|
||||||
|
case 2: k1 ^= ((long) key[offset+1]) << 8;
|
||||||
|
case 1: k1 ^= ((long) key[offset]);
|
||||||
|
k1 *= c1; k1 = rotl64(k1,31); k1 *= c2; h1 ^= k1;
|
||||||
|
};
|
||||||
|
|
||||||
|
//----------
|
||||||
|
// finalization
|
||||||
|
|
||||||
|
h1 ^= length; h2 ^= length;
|
||||||
|
|
||||||
|
h1 += h2;
|
||||||
|
h2 += h1;
|
||||||
|
|
||||||
|
h1 = fmix(h1);
|
||||||
|
h2 = fmix(h2);
|
||||||
|
|
||||||
|
h1 += h2;
|
||||||
|
h2 += h1;
|
||||||
|
|
||||||
|
result[0] = h1;
|
||||||
|
result[1] = h2;
|
||||||
|
}
|
||||||
|
|
||||||
protected static long invRotl64(long v, int n)
|
protected static long invRotl64(long v, int n)
|
||||||
{
|
{
|
||||||
return ((v >>> n) | (v << (64 - n)));
|
return ((v >>> n) | (v << (64 - n)));
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ public interface OutputHandler
|
||||||
void debug(String msg);
|
void debug(String msg);
|
||||||
default void debug(String msg, Object ... args)
|
default void debug(String msg, Object ... args)
|
||||||
{
|
{
|
||||||
debug(String.format(msg, args));
|
if (isDebugEnabled()) debug(String.format(msg, args));
|
||||||
}
|
}
|
||||||
|
|
||||||
// called when the user needs to be warn
|
// called when the user needs to be warn
|
||||||
|
|
@ -55,6 +55,8 @@ public interface OutputHandler
|
||||||
warn(String.format(msg, args));
|
warn(String.format(msg, args));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean isDebugEnabled();
|
||||||
|
|
||||||
class LogOutput implements OutputHandler
|
class LogOutput implements OutputHandler
|
||||||
{
|
{
|
||||||
private static Logger logger = LoggerFactory.getLogger(LogOutput.class);
|
private static Logger logger = LoggerFactory.getLogger(LogOutput.class);
|
||||||
|
|
@ -66,7 +68,7 @@ public interface OutputHandler
|
||||||
|
|
||||||
public void debug(String msg)
|
public void debug(String msg)
|
||||||
{
|
{
|
||||||
logger.trace(msg);
|
logger.debug(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void warn(String msg)
|
public void warn(String msg)
|
||||||
|
|
@ -78,6 +80,18 @@ public interface OutputHandler
|
||||||
{
|
{
|
||||||
logger.warn(msg, th);
|
logger.warn(msg, th);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isDebugEnabled() { return logger.isDebugEnabled();}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NullOutput implements OutputHandler
|
||||||
|
{
|
||||||
|
public void output(String msg) {}
|
||||||
|
public void debug(String msg, Object ... args) {}
|
||||||
|
public void debug(String msg) {}
|
||||||
|
public void warn(String msg) {}
|
||||||
|
public void warn(Throwable th, String msg) {}
|
||||||
|
public boolean isDebugEnabled() { return false;}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SystemOutput implements OutputHandler
|
class SystemOutput implements OutputHandler
|
||||||
|
|
@ -120,5 +134,10 @@ public interface OutputHandler
|
||||||
if (printStack && th != null)
|
if (printStack && th != null)
|
||||||
th.printStackTrace(warnOut);
|
th.printStackTrace(warnOut);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isDebugEnabled()
|
||||||
|
{
|
||||||
|
return debug;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -230,6 +230,7 @@ public class VIntCoding
|
||||||
{
|
{
|
||||||
return getUnsignedVInt(input, readerIndex, input.limit());
|
return getUnsignedVInt(input, readerIndex, input.limit());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static long getUnsignedVInt(ByteBuffer input, int readerIndex, int readerLimit)
|
public static long getUnsignedVInt(ByteBuffer input, int readerIndex, int readerLimit)
|
||||||
{
|
{
|
||||||
if (readerIndex < 0)
|
if (readerIndex < 0)
|
||||||
|
|
|
||||||
|
|
@ -136,6 +136,6 @@ CLASSPATH="$CLASSPATH:$CASSANDRA_HOME/test/conf/"
|
||||||
CLASSPATH="$CLASSPATH:$CASSANDRA_HOME/build/test/classes/"
|
CLASSPATH="$CLASSPATH:$CASSANDRA_HOME/build/test/classes/"
|
||||||
CLASSPATH="$CLASSPATH:$CASSANDRA_HOME/build/test/lib/jars/*"
|
CLASSPATH="$CLASSPATH:$CASSANDRA_HOME/build/test/lib/jars/*"
|
||||||
|
|
||||||
exec $NUMACTL "$JAVA" -cp "$CLASSPATH" org.openjdk.jmh.Main -jvmArgs="$cassandra_parms $JVM_OPTS" "$@"
|
exec $NUMACTL "$JAVA" -Xmx8g -XX:+UseParallelGC -cp "$CLASSPATH" org.openjdk.jmh.Main -jvmArgs="-Djmh.shutdownTimeout=120 $cassandra_parms $JVM_OPTS" "$@"
|
||||||
|
|
||||||
# vi:ai sw=4 ts=4 tw=0 et
|
# vi:ai sw=4 ts=4 tw=0 et
|
||||||
|
|
|
||||||
|
|
@ -338,6 +338,12 @@ public class FailingRepairTest extends TestBaseImpl implements Serializable
|
||||||
return Collections.emptySet();
|
return Collections.emptySet();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFullRange()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public TableMetadata metadata()
|
public TableMetadata metadata()
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
|
|
|
||||||
|
|
@ -299,7 +299,7 @@ public abstract class AccordMigrationReadRaceTestBase extends AccordTestBase
|
||||||
@Test
|
@Test
|
||||||
public void testRangeRouting() throws Throwable
|
public void testRangeRouting() throws Throwable
|
||||||
{
|
{
|
||||||
String cql = "SELECT * FROM " + qualifiedAccordTableName + " WHERE token(pk) > " + Murmur3Partitioner.MINIMUM.token;
|
String cql = "SELECT * FROM " + qualifiedAccordTableName + " WHERE token(pk) > " + Murmur3Partitioner.MINIMUM.getLongValue();
|
||||||
testSplitAndRetry(cql, this::loadData, result -> {
|
testSplitAndRetry(cql, this::loadData, result -> {
|
||||||
assertThat(result).isDeepEqualTo(dataFlat);
|
assertThat(result).isDeepEqualTo(dataFlat);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -120,7 +120,7 @@ public class InteropTokenRangeTest extends TestBaseImpl
|
||||||
{
|
{
|
||||||
NavigableSet<Long> set = new TreeSet<>();
|
NavigableSet<Long> set = new TreeSet<>();
|
||||||
while (result.hasNext())
|
while (result.hasNext())
|
||||||
set.add(Murmur3Partitioner.instance.getToken(result.next().get("pk")).token);
|
set.add(Murmur3Partitioner.instance.getToken(result.next().get("pk")).getLongValue());
|
||||||
return set;
|
return set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -405,10 +405,10 @@ public class SingleNodeTokenConflictTest extends StatefulASTBase
|
||||||
for (ByteBuffer bb : values)
|
for (ByteBuffer bb : values)
|
||||||
{
|
{
|
||||||
var token = Murmur3Partitioner.instance.getToken(bb);
|
var token = Murmur3Partitioner.instance.getToken(bb);
|
||||||
if (token.token > Long.MIN_VALUE + 1)
|
if (token.getLongValue() > Long.MIN_VALUE + 1)
|
||||||
neighbors.add(keyForToken(token.token - 1));
|
neighbors.add(keyForToken(token.getLongValue() - 1));
|
||||||
if (token.token < Long.MAX_VALUE)
|
if (token.getLongValue() < Long.MAX_VALUE)
|
||||||
neighbors.add(keyForToken(token.token + 1));
|
neighbors.add(keyForToken(token.getLongValue() + 1));
|
||||||
}
|
}
|
||||||
return neighbors.build();
|
return neighbors.build();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1005,9 +1005,9 @@ public abstract class CoordinatorPathTestBase extends FuzzTestBase
|
||||||
SinglePartitionReadCommand sprc = (SinglePartitionReadCommand) command;
|
SinglePartitionReadCommand sprc = (SinglePartitionReadCommand) command;
|
||||||
|
|
||||||
Murmur3Partitioner.LongToken requestToken = (Murmur3Partitioner.LongToken) sprc.partitionKey().getToken();
|
Murmur3Partitioner.LongToken requestToken = (Murmur3Partitioner.LongToken) sprc.partitionKey().getToken();
|
||||||
assert node.cluster.state.get().isReadReplicaFor(requestToken.token, node.matcher) :
|
assert node.cluster.state.get().isReadReplicaFor(requestToken.getLongValue(), node.matcher) :
|
||||||
String.format("Node %s is not a read replica for %s. Replicas: %s",
|
String.format("Node %s is not a read replica for %s. Replicas: %s",
|
||||||
node, requestToken.token, node.cluster.state.get().readReplicasFor(requestToken.token));
|
node, requestToken.getLongValue(), node.cluster.state.get().readReplicasFor(requestToken.getLongValue()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -1048,7 +1048,7 @@ public abstract class CoordinatorPathTestBase extends FuzzTestBase
|
||||||
|
|
||||||
Murmur3Partitioner.LongToken leftToken = (Murmur3Partitioner.LongToken) prrc.dataRange().keyRange().left.getToken();
|
Murmur3Partitioner.LongToken leftToken = (Murmur3Partitioner.LongToken) prrc.dataRange().keyRange().left.getToken();
|
||||||
Murmur3Partitioner.LongToken rightToken = (Murmur3Partitioner.LongToken) prrc.dataRange().keyRange().right.getToken();
|
Murmur3Partitioner.LongToken rightToken = (Murmur3Partitioner.LongToken) prrc.dataRange().keyRange().right.getToken();
|
||||||
assert node.cluster.state.get().isReadReplicaFor(leftToken.token, rightToken.token, node.matcher);
|
assert node.cluster.state.get().isReadReplicaFor(leftToken.getLongValue(), rightToken.getLongValue(), node.matcher);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -1085,8 +1085,8 @@ public abstract class CoordinatorPathTestBase extends FuzzTestBase
|
||||||
{
|
{
|
||||||
Mutation command = request.payload;
|
Mutation command = request.payload;
|
||||||
Murmur3Partitioner.LongToken requestToken = (Murmur3Partitioner.LongToken) command.key().getToken();
|
Murmur3Partitioner.LongToken requestToken = (Murmur3Partitioner.LongToken) command.key().getToken();
|
||||||
assert node.cluster.state.get().isWriteTargetFor(requestToken.token, node.matcher) : String.format("Node %s is not a write target for %s. Write placements: %s",
|
assert node.cluster.state.get().isWriteTargetFor(requestToken.getLongValue(), node.matcher) : String.format("Node %s is not a write target for %s. Write placements: %s",
|
||||||
node.node.idx(), requestToken, node.cluster.state.get().writePlacementsFor(requestToken.token));
|
node.node.idx(), requestToken, node.cluster.state.get().writePlacementsFor(requestToken.getLongValue()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Message<NoPayload> respondTo(Message<Mutation> request)
|
public Message<NoPayload> respondTo(Message<Mutation> request)
|
||||||
|
|
@ -1145,7 +1145,7 @@ public abstract class CoordinatorPathTestBase extends FuzzTestBase
|
||||||
|
|
||||||
public static long token(Object... pk)
|
public static long token(Object... pk)
|
||||||
{
|
{
|
||||||
return ((Murmur3Partitioner.LongToken) DatabaseDescriptor.getPartitioner().decorateKey(toBytes(pk)).getToken()).token;
|
return ((Murmur3Partitioner.LongToken) DatabaseDescriptor.getPartitioner().decorateKey(toBytes(pk)).getToken()).getLongValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ByteBuffer toBytes(Object... pk)
|
public static ByteBuffer toBytes(Object... pk)
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ public class GossipDeadlockTest extends TestBaseImpl
|
||||||
ExecutorPlus e = ExecutorFactory.Global.executorFactory().pooled("BounceMove", 2);
|
ExecutorPlus e = ExecutorFactory.Global.executorFactory().pooled("BounceMove", 2);
|
||||||
long startToken = cluster.get(2).callOnInstance(() -> {
|
long startToken = cluster.get(2).callOnInstance(() -> {
|
||||||
NodeId nodeId = ClusterMetadata.current().myNodeId();
|
NodeId nodeId = ClusterMetadata.current().myNodeId();
|
||||||
return ((Murmur3Partitioner.LongToken)ClusterMetadata.current().tokenMap.tokens(nodeId).get(0)).token;
|
return ((Murmur3Partitioner.LongToken)ClusterMetadata.current().tokenMap.tokens(nodeId).get(0)).getLongValue();
|
||||||
});
|
});
|
||||||
AtomicBoolean stop = new AtomicBoolean(false);
|
AtomicBoolean stop = new AtomicBoolean(false);
|
||||||
Future<Integer> moves = e.submit(() -> {
|
Future<Integer> moves = e.submit(() -> {
|
||||||
|
|
|
||||||
|
|
@ -555,7 +555,7 @@ public class MetadataChangeSimulationTest extends CMSTestBase
|
||||||
Assert.assertEquals(replication, sut.rf.asKeyspaceParams().replication);
|
Assert.assertEquals(replication, sut.rf.asKeyspaceParams().replication);
|
||||||
|
|
||||||
Assert.assertEquals(modelState.simulatedPlacements.nodes.stream().map(Node::token).collect(Collectors.toSet()),
|
Assert.assertEquals(modelState.simulatedPlacements.nodes.stream().map(Node::token).collect(Collectors.toSet()),
|
||||||
actualMetadata.tokenMap.tokens().stream().map(t -> ((LongToken) t).token).collect(Collectors.toSet()));
|
actualMetadata.tokenMap.tokens().stream().map(t -> ((LongToken) t).getLongValue()).collect(Collectors.toSet()));
|
||||||
|
|
||||||
for (Map.Entry<ReplicationParams, DataPlacement> e : actualMetadata.placements.asMap().entrySet())
|
for (Map.Entry<ReplicationParams, DataPlacement> e : actualMetadata.placements.asMap().entrySet())
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -66,8 +66,8 @@ public class LCSStreamingKeepLevelTest extends TestBaseImpl
|
||||||
{
|
{
|
||||||
populate(cluster);
|
populate(cluster);
|
||||||
|
|
||||||
long tokenVal = ((Murmur3Partitioner.LongToken)cluster.tokens().get(3).getToken()).token;
|
long tokenVal = ((Murmur3Partitioner.LongToken)cluster.tokens().get(3).getToken()).getLongValue();
|
||||||
long prevTokenVal = ((Murmur3Partitioner.LongToken)cluster.tokens().get(2).getToken()).token;
|
long prevTokenVal = ((Murmur3Partitioner.LongToken)cluster.tokens().get(2).getToken()).getLongValue();
|
||||||
// move node 4 to the middle point between its current position and the previous node
|
// move node 4 to the middle point between its current position and the previous node
|
||||||
long newToken = (tokenVal + prevTokenVal) / 2;
|
long newToken = (tokenVal + prevTokenVal) / 2;
|
||||||
cluster.get(4).nodetoolResult("move", String.valueOf(newToken)).asserts().success();
|
cluster.get(4).nodetoolResult("move", String.valueOf(newToken)).asserts().success();
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ public class CMSPlacementAfterMoveTest extends TestBaseImpl
|
||||||
long node1Token = cluster.get(1).callOnInstance(() -> {
|
long node1Token = cluster.get(1).callOnInstance(() -> {
|
||||||
ClusterMetadata metadata = ClusterMetadata.current();
|
ClusterMetadata metadata = ClusterMetadata.current();
|
||||||
ImmutableList<Token> tokens = ClusterMetadata.current().tokenMap.tokens(metadata.myNodeId());
|
ImmutableList<Token> tokens = ClusterMetadata.current().tokenMap.tokens(metadata.myNodeId());
|
||||||
return ((Murmur3Partitioner.LongToken) tokens.get(0)).token;
|
return ((Murmur3Partitioner.LongToken) tokens.get(0)).getLongValue();
|
||||||
});
|
});
|
||||||
long newNode4Token = node1Token + 100; // token after node1s token should be in cms
|
long newNode4Token = node1Token + 100; // token after node1s token should be in cms
|
||||||
cluster.get(4).nodetoolResult("move", String.valueOf(newNode4Token));
|
cluster.get(4).nodetoolResult("move", String.valueOf(newNode4Token));
|
||||||
|
|
|
||||||
|
|
@ -238,9 +238,9 @@ public abstract class ForwardingSSTableReader extends SSTableReader
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public KeyReader keyReader() throws IOException
|
public KeyReader keyReader(boolean detailed) throws IOException
|
||||||
{
|
{
|
||||||
return delegate.keyReader();
|
return delegate.keyReader(detailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
public KeyReader keyReader(PartitionPosition key) throws IOException
|
public KeyReader keyReader(PartitionPosition key) throws IOException
|
||||||
|
|
|
||||||
|
|
@ -304,7 +304,7 @@ public class AccordJournalBurnTest extends BurnTestBase
|
||||||
while (ci.hasNext())
|
while (ci.hasNext())
|
||||||
writer.append(ci.next());
|
writer.append(ci.next());
|
||||||
|
|
||||||
ci.setTargetDirectory(writer.getSStableDirectory().path());
|
ci.setTargetDirectory(writer.getSStableDirectoryPath());
|
||||||
// point of no return
|
// point of no return
|
||||||
newSStables = writer.finish();
|
newSStables = writer.finish();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,251 @@
|
||||||
|
/*
|
||||||
|
* 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.harry.test;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.UncheckedIOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.TemporaryFolder;
|
||||||
|
|
||||||
|
import accord.utils.Invariants;
|
||||||
|
import org.apache.cassandra.ServerTestUtils;
|
||||||
|
import org.apache.cassandra.cql3.CQLTester;
|
||||||
|
import org.apache.cassandra.db.ColumnFamilyStore;
|
||||||
|
import org.apache.cassandra.db.Keyspace;
|
||||||
|
import org.apache.cassandra.harry.ColumnSpec;
|
||||||
|
import org.apache.cassandra.harry.SchemaSpec;
|
||||||
|
import org.apache.cassandra.harry.dsl.HistoryBuilder;
|
||||||
|
import org.apache.cassandra.harry.execution.CQLTesterVisitExecutor;
|
||||||
|
import org.apache.cassandra.harry.execution.CQLVisitExecutor;
|
||||||
|
import org.apache.cassandra.harry.execution.CompiledStatement;
|
||||||
|
import org.apache.cassandra.harry.execution.DataTracker;
|
||||||
|
import org.apache.cassandra.harry.gen.Generator;
|
||||||
|
import org.apache.cassandra.harry.model.QuiescentChecker;
|
||||||
|
import org.apache.cassandra.harry.op.Operations;
|
||||||
|
import org.apache.cassandra.harry.op.Visit;
|
||||||
|
import org.apache.cassandra.harry.util.BitSet;
|
||||||
|
import org.apache.cassandra.harry.util.ThrowingRunnable;
|
||||||
|
import org.apache.cassandra.io.sstable.HarrySSTableWriter;
|
||||||
|
import org.apache.cassandra.io.util.File;
|
||||||
|
import org.apache.cassandra.service.StorageService;
|
||||||
|
|
||||||
|
import static org.apache.cassandra.harry.checker.TestHelper.withRandom;
|
||||||
|
|
||||||
|
public class HarryCompactionTest extends CQLTester
|
||||||
|
{
|
||||||
|
private static final AtomicInteger idGen = new AtomicInteger(0);
|
||||||
|
public static final int TEST_REPS = 100;
|
||||||
|
public static final int PARTITIONS_RANGE = 100;
|
||||||
|
public static final int COLUMNS_RANGE = 10;
|
||||||
|
private final Generator<BitSet> staticColumnsGenerator = BitSet.generator(3);
|
||||||
|
private final Generator<BitSet> regularColumnsGenerator = BitSet.generator(9);
|
||||||
|
|
||||||
|
private String keyspace;
|
||||||
|
private String table;
|
||||||
|
private String qualifiedTable;
|
||||||
|
private File dataDir;
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public TemporaryFolder tempFolder = new TemporaryFolder();
|
||||||
|
|
||||||
|
public void perTestSetup() throws IOException
|
||||||
|
{
|
||||||
|
keyspace = "cql_keyspace" + idGen.incrementAndGet();
|
||||||
|
table = "table" + idGen.incrementAndGet();
|
||||||
|
qualifiedTable = keyspace + '.' + table;
|
||||||
|
dataDir = new File(tempFolder.newFolder().getAbsolutePath() + File.pathSeparator() + keyspace + File.pathSeparator() + table);
|
||||||
|
assert dataDir.tryCreateDirectories();
|
||||||
|
|
||||||
|
ServerTestUtils.prepareServerNoRegister();
|
||||||
|
StorageService.instance.initServer();
|
||||||
|
requireNetwork();
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Generator<SchemaSpec> simple_schema = rng -> {
|
||||||
|
return new SchemaSpec(rng.next(),
|
||||||
|
1000,
|
||||||
|
keyspace,
|
||||||
|
table,
|
||||||
|
Arrays.asList(ColumnSpec.pk("pk1", ColumnSpec.asciiType),
|
||||||
|
ColumnSpec.pk("pk2", ColumnSpec.int64Type)),
|
||||||
|
Arrays.asList(ColumnSpec.ck("ck1", ColumnSpec.asciiType, true),
|
||||||
|
ColumnSpec.ck("ck2", ColumnSpec.int64Type, true)),
|
||||||
|
Arrays.asList(ColumnSpec.regularColumn("r1", ColumnSpec.asciiType),
|
||||||
|
ColumnSpec.regularColumn("r2", ColumnSpec.int64Type),
|
||||||
|
ColumnSpec.regularColumn("r3", ColumnSpec.int8Type),
|
||||||
|
ColumnSpec.regularColumn("r4", ColumnSpec.doubleType),
|
||||||
|
ColumnSpec.regularColumn("r5", ColumnSpec.floatType),
|
||||||
|
ColumnSpec.regularColumn("r6", ColumnSpec.int32Type),
|
||||||
|
ColumnSpec.regularColumn("r7", ColumnSpec.booleanType),
|
||||||
|
ColumnSpec.regularColumn("r8", ColumnSpec.int16Type),
|
||||||
|
ColumnSpec.regularColumn("r9", ColumnSpec.textType)),
|
||||||
|
Arrays.asList(ColumnSpec.staticColumn("s1", ColumnSpec.asciiType),
|
||||||
|
ColumnSpec.staticColumn("s2", ColumnSpec.int64Type),
|
||||||
|
ColumnSpec.staticColumn("s3", ColumnSpec.asciiType)));
|
||||||
|
};
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFlushAndCompact1() throws IOException {
|
||||||
|
testFlushAndCompact(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFlushAndCompact2() throws IOException {
|
||||||
|
testFlushAndCompact(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFlushAndCompact3() throws IOException {
|
||||||
|
testFlushAndCompact(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFlushAndCompact4() throws IOException {
|
||||||
|
testFlushAndCompact(4);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFlushAndCompact5() throws IOException
|
||||||
|
{
|
||||||
|
testFlushAndCompact(5);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testFlushAndCompact(int flushcount) throws IOException
|
||||||
|
{
|
||||||
|
for (int i = 0; i < TEST_REPS; i++)
|
||||||
|
testFlushAndCompactOnce(flushcount);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testFlushAndCompactOnce(int flushcount) throws IOException
|
||||||
|
{
|
||||||
|
perTestSetup();
|
||||||
|
withRandom( rng -> {
|
||||||
|
|
||||||
|
SchemaSpec schema = simple_schema.generate(rng);
|
||||||
|
schemaChange(String.format("CREATE KEYSPACE IF NOT EXISTS %s WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'}", schema.keyspace));
|
||||||
|
createTable(schema.compile());
|
||||||
|
|
||||||
|
HistoryBuilder history = new HistoryBuilder(schema.valueGenerators);
|
||||||
|
history.customThrowing(() -> {
|
||||||
|
ColumnFamilyStore cfs = Keyspace.open(keyspace).getColumnFamilyStore(table);
|
||||||
|
cfs.disableAutoCompaction();
|
||||||
|
}, "disable compaction");
|
||||||
|
|
||||||
|
AtomicReference<HarrySSTableWriter> sstableWriter = new AtomicReference<>();
|
||||||
|
ThrowingRunnable flushAndChangeWriter = () -> {
|
||||||
|
HarrySSTableWriter prev = sstableWriter.get();
|
||||||
|
if (prev != null)
|
||||||
|
{
|
||||||
|
prev.close();
|
||||||
|
StorageService.instance.bulkLoad(dataDir.absolutePath());
|
||||||
|
dataDir.forEach(file -> file.delete());
|
||||||
|
}
|
||||||
|
|
||||||
|
Invariants.require(sstableWriter.getAndSet(HarrySSTableWriter.builder()
|
||||||
|
.forTable(schema.compile())
|
||||||
|
.inDirectory(dataDir)
|
||||||
|
.build()) == prev);
|
||||||
|
};
|
||||||
|
flushAndChangeWriter.run();
|
||||||
|
|
||||||
|
for (int sstablesFlushed = 0; sstablesFlushed < flushcount; sstablesFlushed++)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < PARTITIONS_RANGE; i++)
|
||||||
|
{
|
||||||
|
for (int j = 0; j < COLUMNS_RANGE; j++)
|
||||||
|
{
|
||||||
|
history.insert(rng.nextInt(0, 2 * PARTITIONS_RANGE), rng.nextInt(0, 2 * COLUMNS_RANGE)); // some overlap, but not all
|
||||||
|
history.deleteRow(rng.nextInt(0, 2 * PARTITIONS_RANGE), rng.nextInt(0, 2 * COLUMNS_RANGE)); // some overlap, but not all
|
||||||
|
history.deleteColumns(rng.nextInt(0, 2 * PARTITIONS_RANGE), rng.nextInt(0, 20),
|
||||||
|
regularColumnsGenerator.generate(rng),
|
||||||
|
staticColumnsGenerator.generate(rng));
|
||||||
|
}
|
||||||
|
history.deletePartition(rng.nextInt(0, 2 * PARTITIONS_RANGE)); // some overlap, but not all
|
||||||
|
}
|
||||||
|
|
||||||
|
history.customThrowing(flushAndChangeWriter, "flush sstable" + sstablesFlushed);
|
||||||
|
}
|
||||||
|
|
||||||
|
history.customThrowing(() -> {
|
||||||
|
ColumnFamilyStore cfs = Keyspace.open(keyspace).getColumnFamilyStore(table);
|
||||||
|
cfs.forceMajorCompaction();
|
||||||
|
}, "major compaction");
|
||||||
|
|
||||||
|
for (int i = 0; i < 2*PARTITIONS_RANGE; i++)
|
||||||
|
history.selectPartition(i);
|
||||||
|
|
||||||
|
replay(schema, history, sstableWriter::get);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void replay(SchemaSpec schema, HistoryBuilder historyBuilder, Supplier<HarrySSTableWriter> writer)
|
||||||
|
{
|
||||||
|
CQLVisitExecutor executor = create(schema, historyBuilder, writer);
|
||||||
|
for (Visit visit : historyBuilder)
|
||||||
|
executor.execute(visit);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CQLVisitExecutor create(SchemaSpec schema, HistoryBuilder historyBuilder, Supplier<HarrySSTableWriter> writer)
|
||||||
|
{
|
||||||
|
DataTracker tracker = new DataTracker.SequentialDataTracker();
|
||||||
|
return new CQLTesterVisitExecutor(schema, tracker,
|
||||||
|
new QuiescentChecker(schema.valueGenerators, tracker, historyBuilder),
|
||||||
|
statement -> {
|
||||||
|
if (logger.isTraceEnabled())
|
||||||
|
logger.trace(statement.toString());
|
||||||
|
return execute(statement.cql(), statement.bindings());
|
||||||
|
})
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected void executeMutatingVisit(Visit visit, CompiledStatement statement)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
writer.get().addRow(statement.cql(), statement.bindings());
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
throw new UncheckedIOException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void executeValidatingVisit(Visit visit, List<Operations.SelectStatement> selects, CompiledStatement compiledStatement)
|
||||||
|
{
|
||||||
|
super.executeValidatingVisit(visit, selects, compiledStatement);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(Visit visit)
|
||||||
|
{
|
||||||
|
if (visit.visitedPartitions.size() > 1)
|
||||||
|
throw new IllegalStateException("SSTable Generator does not support batch statements and transactions");
|
||||||
|
|
||||||
|
super.execute(visit);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,256 @@
|
||||||
|
/*
|
||||||
|
* 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.harry.test;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.UncheckedIOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.TemporaryFolder;
|
||||||
|
|
||||||
|
import accord.utils.Invariants;
|
||||||
|
import org.apache.cassandra.ServerTestUtils;
|
||||||
|
import org.apache.cassandra.cql3.CQLTester;
|
||||||
|
import org.apache.cassandra.db.ColumnFamilyStore;
|
||||||
|
import org.apache.cassandra.db.Keyspace;
|
||||||
|
import org.apache.cassandra.harry.ColumnSpec;
|
||||||
|
import org.apache.cassandra.harry.SchemaSpec;
|
||||||
|
import org.apache.cassandra.harry.dsl.HistoryBuilder;
|
||||||
|
import org.apache.cassandra.harry.execution.CQLTesterVisitExecutor;
|
||||||
|
import org.apache.cassandra.harry.execution.CQLVisitExecutor;
|
||||||
|
import org.apache.cassandra.harry.execution.CompiledStatement;
|
||||||
|
import org.apache.cassandra.harry.execution.DataTracker;
|
||||||
|
import org.apache.cassandra.harry.gen.Generator;
|
||||||
|
import org.apache.cassandra.harry.model.QuiescentChecker;
|
||||||
|
import org.apache.cassandra.harry.op.Operations;
|
||||||
|
import org.apache.cassandra.harry.op.Visit;
|
||||||
|
import org.apache.cassandra.harry.util.BitSet;
|
||||||
|
import org.apache.cassandra.harry.util.ThrowingRunnable;
|
||||||
|
import org.apache.cassandra.io.sstable.HarrySSTableWriter;
|
||||||
|
import org.apache.cassandra.io.util.File;
|
||||||
|
import org.apache.cassandra.service.StorageService;
|
||||||
|
|
||||||
|
import static org.apache.cassandra.harry.checker.TestHelper.withRandom;
|
||||||
|
|
||||||
|
public class HarryCompactionWithRangeDeletionsTest extends CQLTester
|
||||||
|
{
|
||||||
|
private static final AtomicInteger idGen = new AtomicInteger(0);
|
||||||
|
public static final int TEST_REPS = 100;
|
||||||
|
public static final int PARTITIONS_RANGE = 100;
|
||||||
|
public static final int ROWS_RANGE = 10;
|
||||||
|
public static final int STATIC_COLS = 3;
|
||||||
|
public static final int REG_COLS = 9;
|
||||||
|
private final Generator<BitSet> staticColumnsGenerator = BitSet.generator(STATIC_COLS);
|
||||||
|
private final Generator<BitSet> regularColumnsGenerator = BitSet.generator(REG_COLS);
|
||||||
|
|
||||||
|
private String keyspace;
|
||||||
|
private String table;
|
||||||
|
private String qualifiedTable;
|
||||||
|
private File dataDir;
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public TemporaryFolder tempFolder = new TemporaryFolder();
|
||||||
|
|
||||||
|
public void perTestSetup() throws IOException
|
||||||
|
{
|
||||||
|
keyspace = "cql_keyspace" + idGen.incrementAndGet();
|
||||||
|
table = "table" + idGen.incrementAndGet();
|
||||||
|
qualifiedTable = keyspace + '.' + table;
|
||||||
|
dataDir = new File(tempFolder.newFolder().getAbsolutePath() + File.pathSeparator() + keyspace + File.pathSeparator() + table);
|
||||||
|
assert dataDir.tryCreateDirectories();
|
||||||
|
|
||||||
|
ServerTestUtils.prepareServerNoRegister();
|
||||||
|
StorageService.instance.initServer();
|
||||||
|
requireNetwork();
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Generator<SchemaSpec> schemaSpecGenerator = rng -> {
|
||||||
|
return new SchemaSpec(rng.next(),
|
||||||
|
1000,
|
||||||
|
keyspace,
|
||||||
|
table,
|
||||||
|
Arrays.asList(ColumnSpec.pk("pk1", ColumnSpec.asciiType),
|
||||||
|
ColumnSpec.pk("pk2", ColumnSpec.int64Type)),
|
||||||
|
Arrays.asList(ColumnSpec.ck("ck1", ColumnSpec.asciiType, true),
|
||||||
|
ColumnSpec.ck("ck2", ColumnSpec.int64Type, true)),
|
||||||
|
Arrays.asList(ColumnSpec.regularColumn("r1", ColumnSpec.asciiType),
|
||||||
|
ColumnSpec.regularColumn("r2", ColumnSpec.int64Type),
|
||||||
|
ColumnSpec.regularColumn("r3", ColumnSpec.int8Type),
|
||||||
|
ColumnSpec.regularColumn("r4", ColumnSpec.doubleType),
|
||||||
|
ColumnSpec.regularColumn("r5", ColumnSpec.floatType),
|
||||||
|
ColumnSpec.regularColumn("r6", ColumnSpec.int32Type),
|
||||||
|
ColumnSpec.regularColumn("r7", ColumnSpec.booleanType),
|
||||||
|
ColumnSpec.regularColumn("r8", ColumnSpec.int16Type),
|
||||||
|
ColumnSpec.regularColumn("r9", ColumnSpec.textType)),
|
||||||
|
Arrays.asList(ColumnSpec.staticColumn("s1", ColumnSpec.asciiType),
|
||||||
|
ColumnSpec.staticColumn("s2", ColumnSpec.int64Type),
|
||||||
|
ColumnSpec.staticColumn("s3", ColumnSpec.asciiType)));
|
||||||
|
};
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFlushAndCompact1() throws IOException {
|
||||||
|
testFlushAndCompact(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFlushAndCompact2() throws IOException {
|
||||||
|
testFlushAndCompact(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFlushAndCompact3() throws IOException {
|
||||||
|
testFlushAndCompact(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFlushAndCompact4() throws IOException {
|
||||||
|
testFlushAndCompact(4);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFlushAndCompact5() throws IOException
|
||||||
|
{
|
||||||
|
testFlushAndCompact(5);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testFlushAndCompact(int flushcount) throws IOException
|
||||||
|
{
|
||||||
|
for (int i = 0; i < TEST_REPS; i++)
|
||||||
|
testFlushAndCompactOnce(flushcount);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testFlushAndCompactOnce(int flushcount) throws IOException
|
||||||
|
{
|
||||||
|
perTestSetup();
|
||||||
|
withRandom(205413964293041L, rng -> {
|
||||||
|
|
||||||
|
SchemaSpec schema = schemaSpecGenerator.generate(rng);
|
||||||
|
schemaChange(String.format("CREATE KEYSPACE IF NOT EXISTS %s WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'}", schema.keyspace));
|
||||||
|
createTable(schema.compile());
|
||||||
|
|
||||||
|
HistoryBuilder history = new HistoryBuilder(schema.valueGenerators);
|
||||||
|
history.customThrowing(() -> {
|
||||||
|
ColumnFamilyStore cfs = Keyspace.open(keyspace).getColumnFamilyStore(table);
|
||||||
|
cfs.disableAutoCompaction();
|
||||||
|
}, "disable compaction");
|
||||||
|
|
||||||
|
AtomicReference<HarrySSTableWriter> sstableWriter = new AtomicReference<>();
|
||||||
|
ThrowingRunnable flushAndChangeWriter = () -> {
|
||||||
|
HarrySSTableWriter prev = sstableWriter.get();
|
||||||
|
if (prev != null)
|
||||||
|
{
|
||||||
|
prev.close();
|
||||||
|
StorageService.instance.bulkLoad(dataDir.absolutePath());
|
||||||
|
dataDir.forEach(file -> file.delete());
|
||||||
|
}
|
||||||
|
|
||||||
|
Invariants.require(sstableWriter.getAndSet(HarrySSTableWriter.builder()
|
||||||
|
.forTable(schema.compile())
|
||||||
|
.inDirectory(dataDir)
|
||||||
|
.build()) == prev);
|
||||||
|
};
|
||||||
|
flushAndChangeWriter.run();
|
||||||
|
|
||||||
|
for (int sstablesFlushed = 0; sstablesFlushed < flushcount; sstablesFlushed++)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < PARTITIONS_RANGE; i++)
|
||||||
|
{
|
||||||
|
for (int j = 0; j < ROWS_RANGE; j++)
|
||||||
|
{
|
||||||
|
history.insert(rng.nextInt(0, 2 * PARTITIONS_RANGE), rng.nextInt(0, 2 * ROWS_RANGE)); // some overlap, but not all
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int lowerBoundRowIdx = rng.nextInt(ROWS_RANGE);
|
||||||
|
int upperBoundRowIdx = rng.nextInt(lowerBoundRowIdx, 2 * ROWS_RANGE);
|
||||||
|
history.deleteRowRange(rng.nextInt(0, 2 * PARTITIONS_RANGE),
|
||||||
|
lowerBoundRowIdx,
|
||||||
|
upperBoundRowIdx,
|
||||||
|
rng.nextInt(REG_COLS),
|
||||||
|
rng.nextBoolean(),
|
||||||
|
rng.nextBoolean());
|
||||||
|
|
||||||
|
history.customThrowing(flushAndChangeWriter, "flush sstable" + sstablesFlushed);
|
||||||
|
}
|
||||||
|
|
||||||
|
history.customThrowing(() -> {
|
||||||
|
ColumnFamilyStore cfs = Keyspace.open(keyspace).getColumnFamilyStore(table);
|
||||||
|
cfs.forceMajorCompaction();
|
||||||
|
}, "major compaction");
|
||||||
|
|
||||||
|
for (int i = 0; i < 2 * PARTITIONS_RANGE; i++)
|
||||||
|
history.selectPartition(i);
|
||||||
|
|
||||||
|
replay(schema, history, sstableWriter::get);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void replay(SchemaSpec schema, HistoryBuilder historyBuilder, Supplier<HarrySSTableWriter> writer)
|
||||||
|
{
|
||||||
|
CQLVisitExecutor executor = create(schema, historyBuilder, writer);
|
||||||
|
for (Visit visit : historyBuilder)
|
||||||
|
executor.execute(visit);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CQLVisitExecutor create(SchemaSpec schema, HistoryBuilder historyBuilder, Supplier<HarrySSTableWriter> writer)
|
||||||
|
{
|
||||||
|
DataTracker tracker = new DataTracker.SequentialDataTracker();
|
||||||
|
return new CQLTesterVisitExecutor(schema, tracker,
|
||||||
|
new QuiescentChecker(schema.valueGenerators, tracker, historyBuilder),
|
||||||
|
statement -> {
|
||||||
|
if (logger.isTraceEnabled())
|
||||||
|
logger.trace(statement.toString());
|
||||||
|
return execute(statement.cql(), statement.bindings());
|
||||||
|
})
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected void executeMutatingVisit(Visit visit, CompiledStatement statement)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
writer.get().addRow(statement.cql(), statement.bindings());
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
throw new UncheckedIOException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void executeValidatingVisit(Visit visit, List<Operations.SelectStatement> selects, CompiledStatement compiledStatement)
|
||||||
|
{
|
||||||
|
super.executeValidatingVisit(visit, selects, compiledStatement);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(Visit visit)
|
||||||
|
{
|
||||||
|
if (visit.visitedPartitions.size() > 1)
|
||||||
|
throw new IllegalStateException("SSTable Generator does not support batch statements and transactions");
|
||||||
|
|
||||||
|
super.execute(visit);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,190 @@
|
||||||
|
/*
|
||||||
|
* 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.harry.test;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.UncheckedIOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.TemporaryFolder;
|
||||||
|
|
||||||
|
import accord.utils.Invariants;
|
||||||
|
import org.apache.cassandra.ServerTestUtils;
|
||||||
|
import org.apache.cassandra.cql3.CQLTester;
|
||||||
|
import org.apache.cassandra.harry.ColumnSpec;
|
||||||
|
import org.apache.cassandra.harry.SchemaSpec;
|
||||||
|
import org.apache.cassandra.harry.dsl.HistoryBuilder;
|
||||||
|
import org.apache.cassandra.harry.execution.CQLTesterVisitExecutor;
|
||||||
|
import org.apache.cassandra.harry.execution.CQLVisitExecutor;
|
||||||
|
import org.apache.cassandra.harry.execution.CompiledStatement;
|
||||||
|
import org.apache.cassandra.harry.execution.DataTracker;
|
||||||
|
import org.apache.cassandra.harry.gen.Generator;
|
||||||
|
import org.apache.cassandra.harry.model.QuiescentChecker;
|
||||||
|
import org.apache.cassandra.harry.op.Operations;
|
||||||
|
import org.apache.cassandra.harry.op.Visit;
|
||||||
|
import org.apache.cassandra.harry.util.ThrowingRunnable;
|
||||||
|
import org.apache.cassandra.io.sstable.HarrySSTableWriter;
|
||||||
|
import org.apache.cassandra.io.util.File;
|
||||||
|
import org.apache.cassandra.service.StorageService;
|
||||||
|
|
||||||
|
import static org.apache.cassandra.harry.checker.TestHelper.withRandom;
|
||||||
|
|
||||||
|
public class HarrySSTableWriterTest extends CQLTester
|
||||||
|
{
|
||||||
|
private static final AtomicInteger idGen = new AtomicInteger(0);
|
||||||
|
private static final int NUMBER_WRITES_IN_RUNNABLE = 10;
|
||||||
|
|
||||||
|
private String keyspace;
|
||||||
|
private String table;
|
||||||
|
private String qualifiedTable;
|
||||||
|
private File dataDir;
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public TemporaryFolder tempFolder = new TemporaryFolder();
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void perTestSetup() throws IOException
|
||||||
|
{
|
||||||
|
keyspace = "cql_keyspace" + idGen.incrementAndGet();
|
||||||
|
table = "table" + idGen.incrementAndGet();
|
||||||
|
qualifiedTable = keyspace + '.' + table;
|
||||||
|
dataDir = new File(tempFolder.newFolder().getAbsolutePath() + File.pathSeparator() + keyspace + File.pathSeparator() + table);
|
||||||
|
assert dataDir.tryCreateDirectories();
|
||||||
|
|
||||||
|
ServerTestUtils.prepareServerNoRegister();
|
||||||
|
StorageService.instance.initServer();
|
||||||
|
requireNetwork();
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Generator<SchemaSpec> simple_schema = rng -> {
|
||||||
|
return new SchemaSpec(rng.next(),
|
||||||
|
1000,
|
||||||
|
keyspace,
|
||||||
|
table,
|
||||||
|
Arrays.asList(ColumnSpec.pk("pk1", ColumnSpec.asciiType),
|
||||||
|
ColumnSpec.pk("pk2", ColumnSpec.int64Type)),
|
||||||
|
Arrays.asList(ColumnSpec.ck("ck1", ColumnSpec.asciiType, false),
|
||||||
|
ColumnSpec.ck("ck2", ColumnSpec.int64Type, false)),
|
||||||
|
Arrays.asList(ColumnSpec.regularColumn("r1", ColumnSpec.asciiType),
|
||||||
|
ColumnSpec.regularColumn("r2", ColumnSpec.int64Type),
|
||||||
|
ColumnSpec.regularColumn("r3", ColumnSpec.asciiType)),
|
||||||
|
Arrays.asList(ColumnSpec.staticColumn("s1", ColumnSpec.asciiType),
|
||||||
|
ColumnSpec.staticColumn("s2", ColumnSpec.int64Type),
|
||||||
|
ColumnSpec.staticColumn("s3", ColumnSpec.asciiType)));
|
||||||
|
};
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void generateSSTableTest()
|
||||||
|
{
|
||||||
|
withRandom(rng -> {
|
||||||
|
|
||||||
|
SchemaSpec schema = simple_schema.generate(rng);
|
||||||
|
schemaChange(String.format("CREATE KEYSPACE IF NOT EXISTS %s WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'}", schema.keyspace));
|
||||||
|
createTable(schema.compile());
|
||||||
|
|
||||||
|
HistoryBuilder history = new HistoryBuilder(schema.valueGenerators);
|
||||||
|
for (int i = 0; i < 100; i++)
|
||||||
|
history.insert(1);
|
||||||
|
|
||||||
|
AtomicReference<HarrySSTableWriter> sstableWriter = new AtomicReference<>();
|
||||||
|
ThrowingRunnable resetWriter = () -> {
|
||||||
|
HarrySSTableWriter prev = sstableWriter.get();
|
||||||
|
if (prev != null)
|
||||||
|
{
|
||||||
|
prev.close();
|
||||||
|
StorageService.instance.bulkLoad(dataDir.absolutePath());
|
||||||
|
}
|
||||||
|
|
||||||
|
Invariants.require(sstableWriter.getAndSet(HarrySSTableWriter.builder()
|
||||||
|
.forTable(schema.compile())
|
||||||
|
.inDirectory(dataDir)
|
||||||
|
.build()) == prev);
|
||||||
|
};
|
||||||
|
resetWriter.run();
|
||||||
|
|
||||||
|
for (int i = 0; i < 100; i++)
|
||||||
|
{
|
||||||
|
for (int j = 0; j < 10; j++)
|
||||||
|
history.insert(i, j);
|
||||||
|
}
|
||||||
|
|
||||||
|
history.customThrowing(resetWriter, "flush sstable");
|
||||||
|
|
||||||
|
for (int i = 0; i < 100; i++)
|
||||||
|
history.selectPartition(i);
|
||||||
|
|
||||||
|
replay(schema, history, sstableWriter::get);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void replay(SchemaSpec schema, HistoryBuilder historyBuilder, Supplier<HarrySSTableWriter> writer)
|
||||||
|
{
|
||||||
|
CQLVisitExecutor executor = create(schema, historyBuilder, writer);
|
||||||
|
for (Visit visit : historyBuilder)
|
||||||
|
executor.execute(visit);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CQLVisitExecutor create(SchemaSpec schema, HistoryBuilder historyBuilder, Supplier<HarrySSTableWriter> writer)
|
||||||
|
{
|
||||||
|
DataTracker tracker = new DataTracker.SequentialDataTracker();
|
||||||
|
return new CQLTesterVisitExecutor(schema, tracker,
|
||||||
|
new QuiescentChecker(schema.valueGenerators, tracker, historyBuilder),
|
||||||
|
statement -> {
|
||||||
|
if (logger.isTraceEnabled())
|
||||||
|
logger.trace(statement.toString());
|
||||||
|
return execute(statement.cql(), statement.bindings());
|
||||||
|
})
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected void executeMutatingVisit(Visit visit, CompiledStatement statement)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
writer.get().addRow(statement.cql(), statement.bindings());
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
throw new UncheckedIOException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void executeValidatingVisit(Visit visit, List<Operations.SelectStatement> selects, CompiledStatement compiledStatement)
|
||||||
|
{
|
||||||
|
super.executeValidatingVisit(visit, selects, compiledStatement);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(Visit visit)
|
||||||
|
{
|
||||||
|
if (visit.visitedPartitions.size() > 1)
|
||||||
|
throw new IllegalStateException("SSTable Generator does not support batch statements and transactions");
|
||||||
|
|
||||||
|
super.execute(visit);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,656 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.cassandra.io.sstable;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.UncheckedIOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.file.NoSuchFileException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.NavigableSet;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import org.apache.cassandra.config.CassandraRelevantProperties;
|
||||||
|
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||||
|
import org.apache.cassandra.cql3.ColumnSpecification;
|
||||||
|
import org.apache.cassandra.cql3.QueryOptions;
|
||||||
|
import org.apache.cassandra.cql3.QueryProcessor;
|
||||||
|
import org.apache.cassandra.cql3.UpdateParameters;
|
||||||
|
import org.apache.cassandra.cql3.functions.types.TypeCodec;
|
||||||
|
import org.apache.cassandra.cql3.statements.ModificationStatement;
|
||||||
|
import org.apache.cassandra.cql3.statements.schema.CreateIndexStatement;
|
||||||
|
import org.apache.cassandra.cql3.statements.schema.CreateTableStatement;
|
||||||
|
import org.apache.cassandra.cql3.statements.schema.CreateTypeStatement;
|
||||||
|
import org.apache.cassandra.db.Clustering;
|
||||||
|
import org.apache.cassandra.db.ColumnFamilyStore;
|
||||||
|
import org.apache.cassandra.db.Directories;
|
||||||
|
import org.apache.cassandra.db.Keyspace;
|
||||||
|
import org.apache.cassandra.db.Slice;
|
||||||
|
import org.apache.cassandra.db.Slices;
|
||||||
|
import org.apache.cassandra.db.marshal.AbstractType;
|
||||||
|
import org.apache.cassandra.dht.IPartitioner;
|
||||||
|
import org.apache.cassandra.dht.Murmur3Partitioner;
|
||||||
|
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||||
|
import org.apache.cassandra.exceptions.SyntaxException;
|
||||||
|
import org.apache.cassandra.index.sai.StorageAttachedIndexGroup;
|
||||||
|
import org.apache.cassandra.io.sstable.format.SSTableFormat;
|
||||||
|
import org.apache.cassandra.io.sstable.format.SSTableReader;
|
||||||
|
import org.apache.cassandra.io.util.File;
|
||||||
|
import org.apache.cassandra.schema.KeyspaceMetadata;
|
||||||
|
import org.apache.cassandra.schema.KeyspaceParams;
|
||||||
|
import org.apache.cassandra.schema.Keyspaces;
|
||||||
|
import org.apache.cassandra.schema.Schema;
|
||||||
|
import org.apache.cassandra.schema.SchemaConstants;
|
||||||
|
import org.apache.cassandra.schema.SchemaTransformation;
|
||||||
|
import org.apache.cassandra.schema.SchemaTransformations;
|
||||||
|
import org.apache.cassandra.schema.TableMetadata;
|
||||||
|
import org.apache.cassandra.schema.TableMetadataRef;
|
||||||
|
import org.apache.cassandra.schema.Tables;
|
||||||
|
import org.apache.cassandra.schema.Types;
|
||||||
|
import org.apache.cassandra.schema.UserFunctions;
|
||||||
|
import org.apache.cassandra.schema.Views;
|
||||||
|
import org.apache.cassandra.service.ClientState;
|
||||||
|
import org.apache.cassandra.tcm.ClusterMetadata;
|
||||||
|
import org.apache.cassandra.tcm.ClusterMetadataService;
|
||||||
|
import org.apache.cassandra.tcm.transformations.AlterSchema;
|
||||||
|
import org.apache.cassandra.transport.ProtocolVersion;
|
||||||
|
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||||
|
import org.apache.cassandra.utils.JavaDriverUtils;
|
||||||
|
|
||||||
|
import static org.apache.cassandra.utils.Clock.Global.currentTimeMillis;
|
||||||
|
|
||||||
|
public class HarrySSTableWriter implements Closeable
|
||||||
|
{
|
||||||
|
public static final ByteBuffer UNSET_VALUE = ByteBufferUtil.UNSET_BYTE_BUFFER;
|
||||||
|
|
||||||
|
static
|
||||||
|
{
|
||||||
|
CassandraRelevantProperties.FORCE_LOAD_LOCAL_KEYSPACES.setBoolean(true);
|
||||||
|
DatabaseDescriptor.clientInitialization(false);
|
||||||
|
// Partitioner is not set in client mode.
|
||||||
|
if (DatabaseDescriptor.getPartitioner() == null)
|
||||||
|
DatabaseDescriptor.setPartitionerUnsafe(Murmur3Partitioner.instance);
|
||||||
|
ClusterMetadataService.initializeForClients();
|
||||||
|
}
|
||||||
|
|
||||||
|
private final AbstractSSTableSimpleWriter writer;
|
||||||
|
|
||||||
|
private HarrySSTableWriter(AbstractSSTableSimpleWriter writer)
|
||||||
|
{
|
||||||
|
this.writer = writer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Builder builder()
|
||||||
|
{
|
||||||
|
return new Builder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public HarrySSTableWriter addRow(String cql, Object... values) throws IOException
|
||||||
|
{
|
||||||
|
ModificationStatement statement = prepare(cql);
|
||||||
|
List<ColumnSpecification> boundNames = statement.getBindVariables();
|
||||||
|
// TODO: avoid materializing this
|
||||||
|
List<TypeCodec<Object>> typeCodecs = boundNames.stream()
|
||||||
|
.map(bn -> JavaDriverUtils.codecFor(JavaDriverUtils.driverType(bn.type)))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
int size = Math.min(values.length, boundNames.size());
|
||||||
|
List<ByteBuffer> rawValues = new ArrayList<>(size);
|
||||||
|
for (int i = 0; i < size; i++)
|
||||||
|
{
|
||||||
|
Object value = values[i];
|
||||||
|
rawValues.add(serialize(value, typeCodecs.get(i), boundNames.get(i)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return rawAddRow(statement, rawValues, boundNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ModificationStatement prepare(String cql)
|
||||||
|
{
|
||||||
|
ModificationStatement.Parsed statement = QueryProcessor.parseStatement(cql,
|
||||||
|
ModificationStatement.Parsed.class,
|
||||||
|
"INSERT/UPDATE/DELETE");
|
||||||
|
ClientState state = ClientState.forInternalCalls();
|
||||||
|
ModificationStatement preparedModificationStatement = statement.prepare(state);
|
||||||
|
preparedModificationStatement.validate(state);
|
||||||
|
|
||||||
|
if (preparedModificationStatement.hasConditions())
|
||||||
|
throw new IllegalArgumentException("Conditional statements are not supported");
|
||||||
|
if (preparedModificationStatement.isCounter())
|
||||||
|
throw new IllegalArgumentException("Counter modification statements are not supported");
|
||||||
|
if (preparedModificationStatement.getBindVariables().isEmpty())
|
||||||
|
throw new IllegalArgumentException("Provided preparedModificationStatement statement has no bind variables");
|
||||||
|
|
||||||
|
return preparedModificationStatement;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new row to the writer given already serialized values.
|
||||||
|
* <p>
|
||||||
|
* This is a shortcut for {@code rawAddRow(Arrays.asList(values))}.
|
||||||
|
*
|
||||||
|
* @param values the row values (corresponding to the bind variables of the
|
||||||
|
* modification statement used when creating by this writer) as binary.
|
||||||
|
* @return this writer.
|
||||||
|
*/
|
||||||
|
public HarrySSTableWriter rawAddRow(ModificationStatement modificationStatement, List<ByteBuffer> values, List<ColumnSpecification> boundNames) throws InvalidRequestException, IOException
|
||||||
|
{
|
||||||
|
if (values.size() != boundNames.size())
|
||||||
|
throw new InvalidRequestException(String.format("Invalid number of arguments, expecting %d values but got %d", boundNames.size(), values.size()));
|
||||||
|
|
||||||
|
QueryOptions options = QueryOptions.forInternalCalls(null, values);
|
||||||
|
ClientState state = ClientState.forInternalCalls();
|
||||||
|
List<ByteBuffer> keys = modificationStatement.buildPartitionKeyNames(options, state);
|
||||||
|
|
||||||
|
long now = currentTimeMillis();
|
||||||
|
// Note that we asks indexes to not validate values (the last 'false' arg below) because that triggers a 'Keyspace.open'
|
||||||
|
// and that forces a lot of initialization that we don't want.
|
||||||
|
UpdateParameters params = new UpdateParameters(modificationStatement.metadata,
|
||||||
|
ClientState.forInternalCalls(),
|
||||||
|
options,
|
||||||
|
modificationStatement.getTimestamp(TimeUnit.MILLISECONDS.toMicros(now), options),
|
||||||
|
options.getNowInSec((int) TimeUnit.MILLISECONDS.toSeconds(now)),
|
||||||
|
modificationStatement.getTimeToLive(options),
|
||||||
|
Collections.emptyMap());
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (modificationStatement.hasSlices())
|
||||||
|
{
|
||||||
|
Slices slices = modificationStatement.createSlices(options);
|
||||||
|
|
||||||
|
for (ByteBuffer key : keys)
|
||||||
|
{
|
||||||
|
for (Slice slice : slices)
|
||||||
|
modificationStatement.addUpdateForKey(writer.getUpdateFor(key), slice, params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
NavigableSet<Clustering<?>> clusterings = modificationStatement.createClustering(options, state);
|
||||||
|
|
||||||
|
for (ByteBuffer key : keys)
|
||||||
|
{
|
||||||
|
for (Clustering clustering : clusterings)
|
||||||
|
modificationStatement.addUpdateForKey(writer.getUpdateFor(key), clustering, params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
catch (SSTableSimpleUnsortedWriter.SyncException e)
|
||||||
|
{
|
||||||
|
// If we use a BufferedWriter and had a problem writing to disk, the IOException has been
|
||||||
|
// wrapped in a SyncException (see BufferedWriter below). We want to extract that IOE.
|
||||||
|
throw (IOException) e.getCause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close this writer.
|
||||||
|
* <p>
|
||||||
|
* This method should be called, otherwise the produced sstables are not
|
||||||
|
* guaranteed to be complete (and won't be in practice).
|
||||||
|
*/
|
||||||
|
public void close() throws IOException
|
||||||
|
{
|
||||||
|
writer.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ByteBuffer serialize(Object value, TypeCodec codec, ColumnSpecification columnSpecification)
|
||||||
|
{
|
||||||
|
if (value == null || value == UNSET_VALUE)
|
||||||
|
return (ByteBuffer) value;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return codec.serialize(value, ProtocolVersion.CURRENT);
|
||||||
|
}
|
||||||
|
catch (ClassCastException cce)
|
||||||
|
{
|
||||||
|
// For backwards-compatibility with consumers that may be passing
|
||||||
|
// an Integer for a Date field, for example.
|
||||||
|
return ((AbstractType) columnSpecification.type).decompose(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Builder for a CQLSSTableWriter object.
|
||||||
|
*/
|
||||||
|
public static class Builder
|
||||||
|
{
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(Builder.class);
|
||||||
|
private static final long DEFAULT_BUFFER_SIZE_IN_MIB_FOR_UNSORTED = 128L;
|
||||||
|
|
||||||
|
protected SSTableFormat<?, ?> format = null;
|
||||||
|
|
||||||
|
private final List<CreateTypeStatement.Raw> typeStatements;
|
||||||
|
private final List<CreateIndexStatement.Raw> indexStatements;
|
||||||
|
|
||||||
|
private File directory;
|
||||||
|
private CreateTableStatement.Raw schemaStatement;
|
||||||
|
private IPartitioner partitioner;
|
||||||
|
private boolean sorted = false;
|
||||||
|
private long maxSSTableSizeInMiB = -1L;
|
||||||
|
private boolean buildIndexes = true;
|
||||||
|
private Consumer<Collection<SSTableReader>> sstableProducedListener;
|
||||||
|
private boolean openSSTableOnProduced = false;
|
||||||
|
|
||||||
|
protected Builder()
|
||||||
|
{
|
||||||
|
this.typeStatements = new ArrayList<>();
|
||||||
|
this.indexStatements = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The directory where to write the sstables.
|
||||||
|
* <p>
|
||||||
|
* This is a mandatory option.
|
||||||
|
*
|
||||||
|
* @param directory the directory to use, which should exists and be writable.
|
||||||
|
* @return this builder.
|
||||||
|
* @throws IllegalArgumentException if {@code directory} doesn't exist or is not writable.
|
||||||
|
*/
|
||||||
|
public Builder inDirectory(String directory)
|
||||||
|
{
|
||||||
|
return inDirectory(new File(directory));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The directory where to write the sstables (mandatory option).
|
||||||
|
* <p>
|
||||||
|
* This is a mandatory option.
|
||||||
|
*
|
||||||
|
* @param directory the directory to use, which should exists and be writable.
|
||||||
|
* @return this builder.
|
||||||
|
* @throws IllegalArgumentException if {@code directory} doesn't exist or is not writable.
|
||||||
|
*/
|
||||||
|
public Builder inDirectory(File directory)
|
||||||
|
{
|
||||||
|
if (!directory.exists())
|
||||||
|
throw new IllegalArgumentException(directory + " doesn't exists");
|
||||||
|
if (!directory.isWritable())
|
||||||
|
throw new IllegalArgumentException(directory + " exists but is not writable");
|
||||||
|
|
||||||
|
this.directory = directory;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder withType(String typeDefinition) throws SyntaxException
|
||||||
|
{
|
||||||
|
typeStatements.add(QueryProcessor.parseStatement(typeDefinition, CreateTypeStatement.Raw.class, "CREATE TYPE"));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The schema (CREATE TABLE statement) for the table for which sstable are to be created.
|
||||||
|
* <p>
|
||||||
|
* Please note that the provided CREATE TABLE statement <b>must</b> use a fully-qualified
|
||||||
|
* table name, one that include the keyspace name.
|
||||||
|
* <p>
|
||||||
|
* This is a mandatory option.
|
||||||
|
*
|
||||||
|
* @param schema the schema of the table for which sstables are to be created.
|
||||||
|
* @return this builder.
|
||||||
|
* @throws IllegalArgumentException if {@code schema} is not a valid CREATE TABLE statement
|
||||||
|
* or does not have a fully-qualified table name.
|
||||||
|
*/
|
||||||
|
public Builder forTable(String schema)
|
||||||
|
{
|
||||||
|
this.schemaStatement = QueryProcessor.parseStatement(schema, CreateTableStatement.Raw.class, "CREATE TABLE");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The schema (CREATE INDEX statement) for index to be created for the table. Only SAI indexes are supported.
|
||||||
|
*
|
||||||
|
* @param indexes CQL statements representing SAI indexes to be created.
|
||||||
|
* @return this builder
|
||||||
|
*/
|
||||||
|
public Builder withIndexes(String... indexes)
|
||||||
|
{
|
||||||
|
for (String index : indexes)
|
||||||
|
indexStatements.add(QueryProcessor.parseStatement(index, CreateIndexStatement.Raw.class, "CREATE INDEX"));
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The partitioner to use.
|
||||||
|
* <p>
|
||||||
|
* By default, {@code Murmur3Partitioner} will be used. If this is not the partitioner used
|
||||||
|
* by the cluster for which the SSTables are created, you need to use this method to
|
||||||
|
* provide the correct partitioner.
|
||||||
|
*
|
||||||
|
* @param partitioner the partitioner to use.
|
||||||
|
* @return this builder.
|
||||||
|
*/
|
||||||
|
public Builder withPartitioner(IPartitioner partitioner)
|
||||||
|
{
|
||||||
|
this.partitioner = partitioner;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines the maximum SSTable size in mebibytes when using the sorted writer.
|
||||||
|
* By default, i.e. not specified, there is no maximum size limit for the produced SSTable
|
||||||
|
*
|
||||||
|
* @param size the maximum sizein mebibytes of each individual SSTable allowed
|
||||||
|
* @return this builder
|
||||||
|
*/
|
||||||
|
public Builder withMaxSSTableSizeInMiB(int size)
|
||||||
|
{
|
||||||
|
if (size <= 0)
|
||||||
|
{
|
||||||
|
logger.warn("A non-positive value for maximum SSTable size is specified, " +
|
||||||
|
"which disables the size limiting effectively. Please supply a positive value in order " +
|
||||||
|
"to enforce size limiting for the produced SSTables.");
|
||||||
|
}
|
||||||
|
this.maxSSTableSizeInMiB = size;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The size of the buffer to use.
|
||||||
|
* <p>
|
||||||
|
* This defines how much data will be buffered before being written as
|
||||||
|
* a new SSTable. This corresponds roughly to the data size that will have the created
|
||||||
|
* sstable.
|
||||||
|
* <p>
|
||||||
|
* The default is 128MiB, which should be reasonable for a 1GiB heap. If you experience
|
||||||
|
* OOM while using the writer, you should lower this value.
|
||||||
|
*
|
||||||
|
* @param size the size to use in MiB.
|
||||||
|
* @return this builder.
|
||||||
|
* @deprecated This method is deprecated in favor of the new withMaxSSTableSizeInMiB(int size)
|
||||||
|
*/
|
||||||
|
@Deprecated(since = "5.0")
|
||||||
|
public Builder withBufferSizeInMiB(int size)
|
||||||
|
{
|
||||||
|
return withMaxSSTableSizeInMiB(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The size of the buffer to use.
|
||||||
|
* <p>
|
||||||
|
* This defines how much data will be buffered before being written as
|
||||||
|
* a new SSTable. This corresponds roughly to the data size that will have the created
|
||||||
|
* sstable.
|
||||||
|
* <p>
|
||||||
|
* The default is 128MiB, which should be reasonable for a 1GiB heap. If you experience
|
||||||
|
* OOM while using the writer, you should lower this value.
|
||||||
|
*
|
||||||
|
* @param size the size to use in MiB.
|
||||||
|
* @return this builder.
|
||||||
|
* @deprecated This method is deprecated in favor of the new withBufferSizeInMiB(int size). See CASSANDRA-17675
|
||||||
|
*/
|
||||||
|
@Deprecated(since = "4.1")
|
||||||
|
public Builder withBufferSizeInMB(int size)
|
||||||
|
{
|
||||||
|
return withBufferSizeInMiB(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a CQLSSTableWriter that expects sorted inputs.
|
||||||
|
* <p>
|
||||||
|
* If this option is used, the resulting writer will expect rows to be
|
||||||
|
* added in SSTable sorted order (and an exception will be thrown if that
|
||||||
|
* is not the case during modification). The SSTable sorted order means that
|
||||||
|
* rows are added such that their partition key respect the partitioner
|
||||||
|
* order.
|
||||||
|
* <p>
|
||||||
|
* You should thus only use this option is you know that you can provide
|
||||||
|
* the rows in order, which is rarely the case. If you can provide the
|
||||||
|
* rows in order however, using this sorted might be more efficient.
|
||||||
|
* <p>
|
||||||
|
* Note that if used, some option like withBufferSizeInMiB will be ignored.
|
||||||
|
*
|
||||||
|
* @return this builder.
|
||||||
|
*/
|
||||||
|
public Builder sorted()
|
||||||
|
{
|
||||||
|
this.sorted = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether indexes should be built and serialized to disk along data. Defaults to true.
|
||||||
|
*
|
||||||
|
* @param buildIndexes true if indexes should be built, false otherwise
|
||||||
|
* @return this builder
|
||||||
|
*/
|
||||||
|
public Builder withBuildIndexes(boolean buildIndexes)
|
||||||
|
{
|
||||||
|
this.buildIndexes = buildIndexes;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the listener to receive notifications on sstable produced
|
||||||
|
* <p>
|
||||||
|
* Note that if listener is registered, the sstables are opened into {@link SSTableReader}.
|
||||||
|
* The consumer is responsible for releasing the {@link SSTableReader}
|
||||||
|
*
|
||||||
|
* @param sstableProducedListener receives the produced sstables
|
||||||
|
* @return this builder
|
||||||
|
*/
|
||||||
|
public Builder withSSTableProducedListener(Consumer<Collection<SSTableReader>> sstableProducedListener)
|
||||||
|
{
|
||||||
|
this.sstableProducedListener = sstableProducedListener;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the produced sstable should be open or not.
|
||||||
|
* By default, the writer does not open the produced sstables
|
||||||
|
*
|
||||||
|
* @return this builder
|
||||||
|
*/
|
||||||
|
public Builder openSSTableOnProduced()
|
||||||
|
{
|
||||||
|
this.openSSTableOnProduced = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HarrySSTableWriter build()
|
||||||
|
{
|
||||||
|
if (directory == null)
|
||||||
|
throw new IllegalStateException("No ouptut directory specified, you should provide a directory with inDirectory()");
|
||||||
|
if (schemaStatement == null)
|
||||||
|
throw new IllegalStateException("Missing schema, you should provide the schema for the SSTable to create with forTable()");
|
||||||
|
|
||||||
|
Preconditions.checkState(Sets.difference(SchemaConstants.LOCAL_SYSTEM_KEYSPACE_NAMES, Schema.instance.getKeyspaces()).isEmpty(),
|
||||||
|
"Local keyspaces were not loaded. If this is running as a client, please make sure to add %s=true system property.",
|
||||||
|
CassandraRelevantProperties.FORCE_LOAD_LOCAL_KEYSPACES.getKey());
|
||||||
|
|
||||||
|
// Assign the default max SSTable size if not defined in builder
|
||||||
|
if (isMaxSSTableSizeUnset())
|
||||||
|
{
|
||||||
|
maxSSTableSizeInMiB = sorted ? -1L : DEFAULT_BUFFER_SIZE_IN_MIB_FOR_UNSORTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized (HarrySSTableWriter.class)
|
||||||
|
{
|
||||||
|
String keyspaceName = schemaStatement.keyspace();
|
||||||
|
String tableName = schemaStatement.table();
|
||||||
|
|
||||||
|
Schema.instance.submit(SchemaTransformations.addKeyspace(KeyspaceMetadata.create(keyspaceName,
|
||||||
|
KeyspaceParams.simple(1),
|
||||||
|
Tables.none(),
|
||||||
|
Views.none(),
|
||||||
|
Types.none(),
|
||||||
|
UserFunctions.none()), true));
|
||||||
|
|
||||||
|
KeyspaceMetadata ksm = KeyspaceMetadata.create(keyspaceName,
|
||||||
|
KeyspaceParams.simple(1),
|
||||||
|
Tables.none(),
|
||||||
|
Views.none(),
|
||||||
|
Types.none(),
|
||||||
|
UserFunctions.none());
|
||||||
|
|
||||||
|
TableMetadata tableMetadata = Schema.instance.getTableMetadata(keyspaceName, tableName);
|
||||||
|
if (tableMetadata == null)
|
||||||
|
{
|
||||||
|
Types types = createTypes(keyspaceName);
|
||||||
|
Schema.instance.submit(SchemaTransformations.addTypes(types, true));
|
||||||
|
tableMetadata = createTable(types, ksm.userFunctions);
|
||||||
|
Schema.instance.submit(SchemaTransformations.addTable(tableMetadata, true));
|
||||||
|
|
||||||
|
if (buildIndexes && !indexStatements.isEmpty())
|
||||||
|
{
|
||||||
|
// we need to commit keyspace metadata first so applyIndexes sees that keyspace from TCM
|
||||||
|
commitKeyspaceMetadata(ksm.withSwapped(ksm.tables.with(tableMetadata)));
|
||||||
|
applyIndexes(keyspaceName);
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyspaceMetadata keyspaceMetadata = ClusterMetadata.current().schema.getKeyspaceMetadata(keyspaceName);
|
||||||
|
tableMetadata = keyspaceMetadata.tables.getNullable(tableName);
|
||||||
|
|
||||||
|
Schema.instance.submit(SchemaTransformations.addTable(tableMetadata, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyspaceMetadata keyspaceMetadata = ClusterMetadata.current().schema.getKeyspaceMetadata(keyspaceName);
|
||||||
|
Keyspace keyspace = Keyspace.mockKS(keyspaceMetadata);
|
||||||
|
Directories directories = new Directories(tableMetadata, Collections.singleton(new Directories.DataDirectory(new File(directory.toPath()))));
|
||||||
|
ColumnFamilyStore cfs = ColumnFamilyStore.createColumnFamilyStore(keyspace,
|
||||||
|
tableName,
|
||||||
|
tableMetadata,
|
||||||
|
directories,
|
||||||
|
false,
|
||||||
|
false);
|
||||||
|
|
||||||
|
keyspace.initCfCustom(cfs);
|
||||||
|
|
||||||
|
// this is the empty directory / leftover from times we initialized ColumnFamilyStore
|
||||||
|
// it will automatically create directories for keyspace and table on disk after initialization
|
||||||
|
// we set that directory to the destination of generated SSTables so we just remove empty directories here
|
||||||
|
try
|
||||||
|
{
|
||||||
|
new File(directory, keyspaceName).deleteRecursive();
|
||||||
|
}
|
||||||
|
catch (UncheckedIOException ex)
|
||||||
|
{
|
||||||
|
if (!(ex.getCause() instanceof NoSuchFileException))
|
||||||
|
{
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TableMetadataRef ref = tableMetadata.ref;
|
||||||
|
AbstractSSTableSimpleWriter writer = sorted
|
||||||
|
? new SSTableSimpleWriter(directory, ref, cfs.metadata.get().regularAndStaticColumns(), maxSSTableSizeInMiB)
|
||||||
|
: new SSTableSimpleUnsortedWriter(directory, ref, cfs.metadata.get().regularAndStaticColumns(), maxSSTableSizeInMiB);
|
||||||
|
|
||||||
|
if (format != null)
|
||||||
|
writer.setSSTableFormatType(format);
|
||||||
|
|
||||||
|
if (buildIndexes && !indexStatements.isEmpty() && cfs != null)
|
||||||
|
{
|
||||||
|
StorageAttachedIndexGroup saiGroup = StorageAttachedIndexGroup.getIndexGroup(cfs);
|
||||||
|
if (saiGroup != null)
|
||||||
|
writer.addIndexGroup(saiGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sstableProducedListener != null)
|
||||||
|
writer.setSSTableProducedListener(sstableProducedListener);
|
||||||
|
|
||||||
|
writer.setShouldOpenProducedSSTable(openSSTableOnProduced);
|
||||||
|
|
||||||
|
return new HarrySSTableWriter(writer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isMaxSSTableSizeUnset()
|
||||||
|
{
|
||||||
|
return maxSSTableSizeInMiB <= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Types createTypes(String keyspace)
|
||||||
|
{
|
||||||
|
Types.RawBuilder builder = Types.rawBuilder(keyspace);
|
||||||
|
for (CreateTypeStatement.Raw st : typeStatements)
|
||||||
|
st.addToRawBuilder(builder);
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies any provided index definitions to the target table
|
||||||
|
*
|
||||||
|
* @param keyspaceName name of the keyspace to apply indexes for
|
||||||
|
* @return table metadata reflecting applied indexes
|
||||||
|
*/
|
||||||
|
private void applyIndexes(String keyspaceName)
|
||||||
|
{
|
||||||
|
ClientState state = ClientState.forInternalCalls();
|
||||||
|
|
||||||
|
for (CreateIndexStatement.Raw statement : indexStatements)
|
||||||
|
{
|
||||||
|
Keyspaces keyspaces = statement.prepare(state).apply(ClusterMetadata.current());
|
||||||
|
commitKeyspaceMetadata(keyspaces.getNullable(keyspaceName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void commitKeyspaceMetadata(KeyspaceMetadata keyspaceMetadata)
|
||||||
|
{
|
||||||
|
SchemaTransformation schemaTransformation = new SchemaTransformation()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public Keyspaces apply(ClusterMetadata metadata)
|
||||||
|
{
|
||||||
|
return metadata.schema.getKeyspaces().withAddedOrUpdated(keyspaceMetadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean compatibleWith(ClusterMetadata metadata)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
ClusterMetadataService.instance().commit(new AlterSchema(schemaTransformation));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the table according to schema statement
|
||||||
|
*
|
||||||
|
* @param types types this table should be created with
|
||||||
|
*/
|
||||||
|
private TableMetadata createTable(Types types, UserFunctions functions)
|
||||||
|
{
|
||||||
|
ClientState state = ClientState.forInternalCalls();
|
||||||
|
CreateTableStatement statement = schemaStatement.prepare(state);
|
||||||
|
statement.validate(ClientState.forInternalCalls());
|
||||||
|
|
||||||
|
TableMetadata.Builder builder = statement.builder(types, functions);
|
||||||
|
if (partitioner != null)
|
||||||
|
builder.partitioner(partitioner);
|
||||||
|
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -23,64 +23,87 @@ import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
|
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||||
import org.apache.cassandra.cql3.CQLTester;
|
import org.apache.cassandra.cql3.CQLTester;
|
||||||
import org.apache.cassandra.db.ColumnFamilyStore;
|
import org.apache.cassandra.db.ColumnFamilyStore;
|
||||||
import org.apache.cassandra.db.Directories;
|
import org.apache.cassandra.db.Directories;
|
||||||
import org.apache.cassandra.db.Keyspace;
|
import org.apache.cassandra.db.Keyspace;
|
||||||
import org.apache.cassandra.io.sstable.Descriptor;
|
import org.apache.cassandra.db.commitlog.CommitLog;
|
||||||
import org.apache.cassandra.io.util.File;
|
import org.apache.cassandra.io.util.File;
|
||||||
import org.apache.cassandra.io.util.FileUtils;
|
import org.apache.cassandra.io.util.FileUtils;
|
||||||
import org.apache.cassandra.service.snapshot.SnapshotManager;
|
import org.apache.cassandra.service.snapshot.SnapshotManager;
|
||||||
|
import org.apache.cassandra.tcm.ClusterMetadataService;
|
||||||
import org.openjdk.jmh.annotations.*;
|
import org.openjdk.jmh.annotations.*;
|
||||||
|
|
||||||
@BenchmarkMode(Mode.AverageTime)
|
@BenchmarkMode(Mode.AverageTime)
|
||||||
@OutputTimeUnit(TimeUnit.MILLISECONDS)
|
@OutputTimeUnit(TimeUnit.MILLISECONDS)
|
||||||
@Warmup(iterations = 25, time = 1, timeUnit = TimeUnit.SECONDS)
|
@Warmup(iterations = 25, time = 1, timeUnit = TimeUnit.SECONDS)
|
||||||
@Measurement(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS)
|
@Measurement(iterations = 10, time = 1, timeUnit = TimeUnit.SECONDS)
|
||||||
@Fork(value = 1)
|
@Fork(value = 1)
|
||||||
@Threads(1)
|
@Threads(1)
|
||||||
@State(Scope.Benchmark)
|
@State(Scope.Benchmark)
|
||||||
public class CompactionBench extends CQLTester
|
public class CompactionBench extends CQLTester
|
||||||
{
|
{
|
||||||
static String keyspace;
|
protected static String keyspace;
|
||||||
String table;
|
protected String table;
|
||||||
String writeStatement;
|
protected String writeStatement;
|
||||||
String readStatement;
|
protected ColumnFamilyStore cfs;
|
||||||
ColumnFamilyStore cfs;
|
protected List<File> snapshotFiles;
|
||||||
List<File> snapshotFiles;
|
|
||||||
List<Descriptor> liveFiles;
|
@Param("2")
|
||||||
|
protected int sstableCount = 2;
|
||||||
|
|
||||||
|
@Param("50000")
|
||||||
|
protected int rowCount = 50000;
|
||||||
|
|
||||||
|
@Param("NONE")
|
||||||
|
protected String overlap = "NONE";
|
||||||
|
|
||||||
|
@Param("true")
|
||||||
|
protected boolean isCursor = true;
|
||||||
|
|
||||||
@Setup(Level.Trial)
|
@Setup(Level.Trial)
|
||||||
public void setup() throws Throwable
|
public void setup() throws Throwable
|
||||||
{
|
{
|
||||||
CQLTester.prepareServer();
|
CQLTester.prepareServer();
|
||||||
|
DatabaseDescriptor.setCursorCompactionEnabled(isCursor);
|
||||||
|
DatabaseDescriptor.setCompactionThroughputMebibytesPerSec(10*1024); // no rate limiting
|
||||||
|
createSStables();
|
||||||
|
takeSnapshot();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void createSStables()
|
||||||
|
{
|
||||||
keyspace = createKeyspace("CREATE KEYSPACE %s with replication = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 } and durable_writes = false");
|
keyspace = createKeyspace("CREATE KEYSPACE %s with replication = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 } and durable_writes = false");
|
||||||
table = createTable(keyspace, "CREATE TABLE %s ( userid bigint, picid bigint, commentid bigint, PRIMARY KEY(userid, picid))");
|
table = createTable(keyspace, "CREATE TABLE %s ( userid bigint, picid bigint, commentid bigint, PRIMARY KEY(userid, picid))");
|
||||||
execute("use "+keyspace+";");
|
execute("use "+keyspace+";");
|
||||||
writeStatement = "INSERT INTO "+table+"(userid,picid,commentid)VALUES(?,?,?)";
|
writeStatement = "INSERT INTO "+table+"(userid,picid,commentid)VALUES(?,?,?)";
|
||||||
readStatement = "SELECT * from "+table+" limit 100";
|
|
||||||
|
|
||||||
Keyspace.system().forEach(k -> k.getColumnFamilyStores().forEach(c -> c.disableAutoCompaction()));
|
Keyspace.system().forEach(k -> k.getColumnFamilyStores().forEach(c -> c.disableAutoCompaction()));
|
||||||
|
|
||||||
cfs = Keyspace.open(keyspace).getColumnFamilyStore(table);
|
cfs = Keyspace.open(keyspace).getColumnFamilyStore(table);
|
||||||
cfs.disableAutoCompaction();
|
cfs.disableAutoCompaction();
|
||||||
|
|
||||||
//Warm up
|
for (int j = 0; j < sstableCount; j++)
|
||||||
System.err.println("Writing 50k");
|
{
|
||||||
for (long i = 0; i < 50000; i++)
|
int pPrefix = overlap.startsWith("PK") ? 0 : j * rowCount;
|
||||||
execute(writeStatement, i, i, i );
|
int rPrefix = overlap.startsWith("PK.ROW") ? 0 : j * rowCount;
|
||||||
|
for (long i = 0; i < rowCount; i++)
|
||||||
|
{
|
||||||
cfs.forceBlockingFlush(ColumnFamilyStore.FlushReason.USER_FORCED);
|
execute(writeStatement, (pPrefix + i), (rPrefix + i), j * rowCount + i);
|
||||||
|
}
|
||||||
System.err.println("Writing 50k again...");
|
|
||||||
for (long i = 0; i < 50000; i++)
|
|
||||||
execute(writeStatement, i, i, i );
|
|
||||||
|
|
||||||
cfs.forceBlockingFlush(ColumnFamilyStore.FlushReason.USER_FORCED);
|
cfs.forceBlockingFlush(ColumnFamilyStore.FlushReason.USER_FORCED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void takeSnapshot()
|
||||||
|
{
|
||||||
SnapshotManager.instance.takeSnapshot("originals", cfs.getKeyspaceTableName());
|
SnapshotManager.instance.takeSnapshot("originals", cfs.getKeyspaceTableName());
|
||||||
snapshotFiles = cfs.getDirectories().sstableLister(Directories.OnTxnErr.IGNORE).snapshots("originals").listFiles();
|
snapshotFiles = cfs.getDirectories().sstableLister(Directories.OnTxnErr.IGNORE).snapshots("originals").listFiles();
|
||||||
|
long[] sum = new long[1];
|
||||||
|
snapshotFiles.forEach(f -> sum[0] += f.length());
|
||||||
|
System.out.println("Total input size: " + sum[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@TearDown(Level.Trial)
|
@TearDown(Level.Trial)
|
||||||
|
|
@ -95,28 +118,36 @@ public class CompactionBench extends CQLTester
|
||||||
System.err.println("Thread "+t.getName());
|
System.err.println("Thread "+t.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CommitLog.instance.shutdownBlocking();
|
||||||
|
ClusterMetadataService.instance().log().close();
|
||||||
|
CQLTester.tearDownClass();
|
||||||
CQLTester.cleanup();
|
CQLTester.cleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@TearDown(Level.Invocation)
|
@TearDown(Level.Invocation)
|
||||||
public void resetSnapshot()
|
public void resetSnapshot() throws IOException, InterruptedException
|
||||||
{
|
{
|
||||||
cfs.truncateBlocking();
|
cfs.truncateBlocking();
|
||||||
|
|
||||||
List<File> directories = cfs.getDirectories().getCFDirectories();
|
List<File> directories = cfs.getDirectories().getCFDirectories();
|
||||||
|
// Sometimes deletes are unreliable...
|
||||||
|
int deleted = 0;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
deleted = 0;
|
||||||
for (File file : directories)
|
for (File file : directories)
|
||||||
{
|
{
|
||||||
for (File f : file.tryList())
|
for (File f : file.tryList())
|
||||||
{
|
{
|
||||||
if (f.isDirectory())
|
if (f.isDirectory())
|
||||||
continue;
|
continue;
|
||||||
|
f.tryDelete();
|
||||||
FileUtils.delete(f);
|
deleted++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Thread.sleep(10);
|
||||||
|
} while (deleted != 0);
|
||||||
|
|
||||||
for (File file : snapshotFiles)
|
for (File file : snapshotFiles)
|
||||||
FileUtils.createHardLink(file, new File(new File(file.toPath().getParent().getParent().getParent()), file.name()));
|
FileUtils.createHardLink(file, new File(new File(file.toPath().getParent().getParent().getParent()), file.name()));
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.cassandra.test.microbench.sstable;
|
||||||
|
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.apache.cassandra.db.ColumnFamilyStore;
|
||||||
|
import org.apache.cassandra.db.Keyspace;
|
||||||
|
import org.apache.cassandra.test.microbench.CompactionBench;
|
||||||
|
import org.openjdk.jmh.annotations.BenchmarkMode;
|
||||||
|
import org.openjdk.jmh.annotations.Fork;
|
||||||
|
import org.openjdk.jmh.annotations.Measurement;
|
||||||
|
import org.openjdk.jmh.annotations.Mode;
|
||||||
|
import org.openjdk.jmh.annotations.OutputTimeUnit;
|
||||||
|
import org.openjdk.jmh.annotations.Param;
|
||||||
|
import org.openjdk.jmh.annotations.Scope;
|
||||||
|
import org.openjdk.jmh.annotations.State;
|
||||||
|
import org.openjdk.jmh.annotations.Threads;
|
||||||
|
import org.openjdk.jmh.annotations.Warmup;
|
||||||
|
|
||||||
|
@BenchmarkMode(Mode.AverageTime)
|
||||||
|
@OutputTimeUnit(TimeUnit.MILLISECONDS)
|
||||||
|
@Warmup(iterations = 25, time = 1, timeUnit = TimeUnit.SECONDS)
|
||||||
|
@Measurement(iterations = 10, time = 1, timeUnit = TimeUnit.SECONDS)
|
||||||
|
@Fork(value = 1)
|
||||||
|
@Threads(1)
|
||||||
|
@State(Scope.Benchmark)
|
||||||
|
public class CompactionLargeCellBench extends CompactionBench
|
||||||
|
{
|
||||||
|
@Param("128")
|
||||||
|
int blobSize = 128;
|
||||||
|
|
||||||
|
protected void createSStables()
|
||||||
|
{
|
||||||
|
keyspace = createKeyspace("CREATE KEYSPACE %s with replication = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 } and durable_writes = false");
|
||||||
|
table = createTable(keyspace, "CREATE TABLE %s ( userid bigint, picid bigint, b blob, PRIMARY KEY(userid, picid))");
|
||||||
|
execute("use "+keyspace+";");
|
||||||
|
writeStatement = "INSERT INTO "+table+"(userid,picid,b)VALUES(?,?,?)";
|
||||||
|
|
||||||
|
Keyspace.system().forEach(k -> k.getColumnFamilyStores().forEach(c -> c.disableAutoCompaction()));
|
||||||
|
|
||||||
|
cfs = Keyspace.open(keyspace).getColumnFamilyStore(table);
|
||||||
|
cfs.disableAutoCompaction();
|
||||||
|
|
||||||
|
byte[] blob = new byte[blobSize];
|
||||||
|
for (int j = 0; j < sstableCount; j++)
|
||||||
|
{
|
||||||
|
int pPrefix = overlap.startsWith("PK") ? 0 : j * rowCount;
|
||||||
|
int rPrefix = overlap.startsWith("PK.ROW") ? 0 : j * rowCount;
|
||||||
|
for (long i = 0; i < rowCount; i++)
|
||||||
|
{
|
||||||
|
ThreadLocalRandom.current().nextBytes(blob);
|
||||||
|
execute(writeStatement, (pPrefix + i), (rPrefix + i), ByteBuffer.wrap(blob));
|
||||||
|
}
|
||||||
|
|
||||||
|
cfs.forceBlockingFlush(ColumnFamilyStore.FlushReason.USER_FORCED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,92 @@
|
||||||
|
/*
|
||||||
|
* 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.sstable;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.apache.cassandra.db.ColumnFamilyStore;
|
||||||
|
import org.apache.cassandra.db.Keyspace;
|
||||||
|
import org.apache.cassandra.test.microbench.CompactionBench;
|
||||||
|
import org.openjdk.jmh.annotations.BenchmarkMode;
|
||||||
|
import org.openjdk.jmh.annotations.Fork;
|
||||||
|
import org.openjdk.jmh.annotations.Measurement;
|
||||||
|
import org.openjdk.jmh.annotations.Mode;
|
||||||
|
import org.openjdk.jmh.annotations.OutputTimeUnit;
|
||||||
|
import org.openjdk.jmh.annotations.Param;
|
||||||
|
import org.openjdk.jmh.annotations.Scope;
|
||||||
|
import org.openjdk.jmh.annotations.State;
|
||||||
|
import org.openjdk.jmh.annotations.Threads;
|
||||||
|
import org.openjdk.jmh.annotations.Warmup;
|
||||||
|
|
||||||
|
@BenchmarkMode(Mode.AverageTime)
|
||||||
|
@OutputTimeUnit(TimeUnit.MILLISECONDS)
|
||||||
|
@Warmup(iterations = 25, time = 1, timeUnit = TimeUnit.SECONDS)
|
||||||
|
@Measurement(iterations = 10, time = 1, timeUnit = TimeUnit.SECONDS)
|
||||||
|
@Fork(value = 1)
|
||||||
|
@Threads(1)
|
||||||
|
@State(Scope.Benchmark)
|
||||||
|
public class CompactionStringsBench extends CompactionBench
|
||||||
|
{
|
||||||
|
@Param("128")
|
||||||
|
int stringSizeMin = 128;
|
||||||
|
@Param("256")
|
||||||
|
int stringSizeMax = 256;
|
||||||
|
|
||||||
|
protected void createSStables()
|
||||||
|
{
|
||||||
|
keyspace = createKeyspace("CREATE KEYSPACE %s with replication = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 } and durable_writes = false");
|
||||||
|
table = createTable(keyspace, "CREATE TABLE %s ( pk ascii, ck ascii, c ascii, PRIMARY KEY(pk, ck))");
|
||||||
|
execute("use "+keyspace+";");
|
||||||
|
writeStatement = "INSERT INTO "+table+"(pk,ck,c)VALUES(?,?,?)";
|
||||||
|
|
||||||
|
Keyspace.system().forEach(k -> k.getColumnFamilyStores().forEach(c -> c.disableAutoCompaction()));
|
||||||
|
|
||||||
|
cfs = Keyspace.open(keyspace).getColumnFamilyStore(table);
|
||||||
|
cfs.disableAutoCompaction();
|
||||||
|
for (int j = 0; j < sstableCount; j++)
|
||||||
|
{
|
||||||
|
Random r = new Random(42L);
|
||||||
|
int pPrefix = overlap.startsWith("PK") ? 0 : j * rowCount;
|
||||||
|
int rPrefix = overlap.startsWith("PK.ROW") ? 0 : j * rowCount;
|
||||||
|
for (long i = 0; i < rowCount; i++)
|
||||||
|
{
|
||||||
|
execute(writeStatement, (pPrefix + i) + getNextString(r), (rPrefix + i) + getNextString(r), getNextString(r));
|
||||||
|
}
|
||||||
|
|
||||||
|
cfs.forceBlockingFlush(ColumnFamilyStore.FlushReason.USER_FORCED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getNextString(Random r)
|
||||||
|
{
|
||||||
|
int rangeDelta = stringSizeMax - stringSizeMin;
|
||||||
|
int rangeRandom = rangeDelta > 0 ? r.nextInt(rangeDelta) : 0;
|
||||||
|
byte[] blob = new byte[stringSizeMin + rangeRandom];
|
||||||
|
r.nextBytes(blob);
|
||||||
|
for (int i = 0; i < blob.length; i++)
|
||||||
|
{
|
||||||
|
if (blob[i] < 0)
|
||||||
|
blob[i] = 0;
|
||||||
|
}
|
||||||
|
String nextString = new String(blob);
|
||||||
|
return nextString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,104 @@
|
||||||
|
/*
|
||||||
|
* 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.sstable;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.apache.cassandra.db.ColumnFamilyStore;
|
||||||
|
import org.apache.cassandra.db.Keyspace;
|
||||||
|
import org.apache.cassandra.test.microbench.CompactionBench;
|
||||||
|
import org.openjdk.jmh.annotations.BenchmarkMode;
|
||||||
|
import org.openjdk.jmh.annotations.Fork;
|
||||||
|
import org.openjdk.jmh.annotations.Measurement;
|
||||||
|
import org.openjdk.jmh.annotations.Mode;
|
||||||
|
import org.openjdk.jmh.annotations.OutputTimeUnit;
|
||||||
|
import org.openjdk.jmh.annotations.Param;
|
||||||
|
import org.openjdk.jmh.annotations.Scope;
|
||||||
|
import org.openjdk.jmh.annotations.State;
|
||||||
|
import org.openjdk.jmh.annotations.Threads;
|
||||||
|
import org.openjdk.jmh.annotations.Warmup;
|
||||||
|
|
||||||
|
@BenchmarkMode(Mode.AverageTime)
|
||||||
|
@OutputTimeUnit(TimeUnit.MILLISECONDS)
|
||||||
|
@Warmup(iterations = 25, time = 1, timeUnit = TimeUnit.SECONDS)
|
||||||
|
@Measurement(iterations = 10, time = 1, timeUnit = TimeUnit.SECONDS)
|
||||||
|
@Fork(value = 1)
|
||||||
|
@Threads(1)
|
||||||
|
@State(Scope.Benchmark)
|
||||||
|
public class CompactionWideRowBench extends CompactionBench
|
||||||
|
{
|
||||||
|
@Param("1")
|
||||||
|
int rowPerPkCount = 1;
|
||||||
|
|
||||||
|
@Param("1")
|
||||||
|
int ckCount = 1;
|
||||||
|
|
||||||
|
@Param("1")
|
||||||
|
int colCount = 1;
|
||||||
|
|
||||||
|
protected void createSStables()
|
||||||
|
{
|
||||||
|
keyspace = createKeyspace("CREATE KEYSPACE %s with replication = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 } and durable_writes = false");
|
||||||
|
String tableCreate = "CREATE TABLE %s ( userid bigint";
|
||||||
|
for (int i=0;i<ckCount;i++)
|
||||||
|
tableCreate += ",ck"+i+ " bigint";
|
||||||
|
for (int i=0;i<colCount;i++)
|
||||||
|
tableCreate += ",c"+i+ " bigint";
|
||||||
|
tableCreate += ", PRIMARY KEY(userid";
|
||||||
|
for (int i=0;i<ckCount;i++)
|
||||||
|
tableCreate += ",ck"+i;
|
||||||
|
tableCreate +="))";
|
||||||
|
table = createTable(keyspace, tableCreate);
|
||||||
|
execute("use "+keyspace+";");
|
||||||
|
writeStatement = "INSERT INTO "+table+"(userid";
|
||||||
|
for (int i=0;i<ckCount;i++)
|
||||||
|
writeStatement += ",ck"+i;
|
||||||
|
for (int i=0;i<colCount;i++)
|
||||||
|
writeStatement += ",c"+i;
|
||||||
|
writeStatement += ")VALUES(?";
|
||||||
|
for (int i=0;i<ckCount+colCount;i++)
|
||||||
|
writeStatement += ",?";
|
||||||
|
writeStatement += ")";
|
||||||
|
|
||||||
|
Object[] values = new Object[1+ckCount+colCount];
|
||||||
|
Keyspace.system().forEach(k -> k.getColumnFamilyStores().forEach(c -> c.disableAutoCompaction()));
|
||||||
|
|
||||||
|
cfs = Keyspace.open(keyspace).getColumnFamilyStore(table);
|
||||||
|
cfs.disableAutoCompaction();
|
||||||
|
int pkCount = rowCount/rowPerPkCount;
|
||||||
|
for (int j = 0; j < sstableCount; j++)
|
||||||
|
{
|
||||||
|
int pPrefix = overlap.startsWith("PK") ? 0 : j * rowCount;
|
||||||
|
int rPrefix = overlap.startsWith("PK.ROW") ? 0 : j * rowCount;
|
||||||
|
for (long pkIndex = 0; pkIndex < pkCount; pkIndex++)
|
||||||
|
{
|
||||||
|
for (long rowIndex = 0; rowIndex < rowPerPkCount; rowIndex++)
|
||||||
|
{
|
||||||
|
values[0] = (pPrefix + pkIndex);
|
||||||
|
Arrays.fill(values, 1, ckCount + 1, (rPrefix + rowIndex));
|
||||||
|
Arrays.fill(values, 1 + ckCount, values.length, j * rowCount + pkIndex);
|
||||||
|
execute(writeStatement, values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cfs.forceBlockingFlush(ColumnFamilyStore.FlushReason.USER_FORCED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue