mirror of https://github.com/apache/cassandra
remove Table.switchlock and introduce o.a.c.utils.memory package
patch by Benedict Elliott Smith; reviewed by jbellis for CASSANDRA-5549
This commit is contained in:
parent
00fc31868a
commit
4b54b8acd2
|
|
@ -357,7 +357,7 @@
|
|||
</dependency>
|
||||
<dependency groupId="com.googlecode.json-simple" artifactId="json-simple" version="1.1"/>
|
||||
<dependency groupId="com.github.stephenc.high-scale-lib" artifactId="high-scale-lib" version="1.1.2"/>
|
||||
<dependency groupId="com.github.stephenc" artifactId="jamm" version="0.2.5"/>
|
||||
<dependency groupId="com.github.stephenc" artifactId="jamm" version="0.2.6"/>
|
||||
<dependency groupId="com.thinkaurelius.thrift" artifactId="thrift-server" version="0.3.3">
|
||||
<exclusion groupId="org.slf4j" artifactId="slf4j-log4j12"/>
|
||||
</dependency>
|
||||
|
|
@ -656,7 +656,7 @@
|
|||
<pathelement location="${test.conf}"/>
|
||||
</classpath>
|
||||
<jvmarg value="-Dstorage-config=${test.conf}"/>
|
||||
<jvmarg value="-javaagent:${basedir}/lib/jamm-0.2.5.jar" />
|
||||
<jvmarg value="-javaagent:${basedir}/lib/jamm-0.2.6.jar" />
|
||||
<jvmarg value="-ea"/>
|
||||
</java>
|
||||
</target>
|
||||
|
|
@ -1051,7 +1051,7 @@
|
|||
<formatter type="brief" usefile="false"/>
|
||||
<jvmarg value="-Dstorage-config=${test.conf}"/>
|
||||
<jvmarg value="-Djava.awt.headless=true"/>
|
||||
<jvmarg value="-javaagent:${basedir}/lib/jamm-0.2.5.jar" />
|
||||
<jvmarg value="-javaagent:${basedir}/lib/jamm-0.2.6.jar" />
|
||||
<jvmarg value="-ea"/>
|
||||
<jvmarg value="-Xss256k"/>
|
||||
<optjvmargs/>
|
||||
|
|
|
|||
|
|
@ -165,7 +165,7 @@ JVM_OPTS="$JVM_OPTS -ea"
|
|||
if [ "$JVM_VENDOR" != "OpenJDK" -o "$JVM_VERSION" \> "1.6.0" ] \
|
||||
|| [ "$JVM_VERSION" = "1.6.0" -a "$JVM_PATCH_VERSION" -ge 23 ]
|
||||
then
|
||||
JVM_OPTS="$JVM_OPTS -javaagent:$CASSANDRA_HOME/lib/jamm-0.2.5.jar"
|
||||
JVM_OPTS="$JVM_OPTS -javaagent:$CASSANDRA_HOME/lib/jamm-0.2.6.jar"
|
||||
fi
|
||||
|
||||
# enable thread priorities, primarily so we can give periodic tasks
|
||||
|
|
|
|||
|
|
@ -280,6 +280,10 @@ concurrent_counter_writes: 32
|
|||
# If omitted, Cassandra will set it to 1/4 of the heap.
|
||||
# memtable_total_space_in_mb: 2048
|
||||
|
||||
# Ratio of occupied non-flushing memtable size to total permitted size
|
||||
# that will trigger a flush of the largest memtable.
|
||||
memtable_cleanup_threshold: 0.4
|
||||
|
||||
# Total space to use for commitlogs. Since commitlog segments are
|
||||
# mmapped, and hence use up address space, the default size is 32
|
||||
# on 32-bit JVMs, and 1024 on 64-bit JVMs.
|
||||
|
|
@ -297,11 +301,6 @@ concurrent_counter_writes: 32
|
|||
# By default this will be set to the amount of data directories defined.
|
||||
#memtable_flush_writers: 1
|
||||
|
||||
# the number of full memtables to allow pending flush, that is,
|
||||
# waiting for a writer thread. At a minimum, this should be set to
|
||||
# the maximum number of secondary indexes created on a single CF.
|
||||
memtable_flush_queue_size: 4
|
||||
|
||||
# A fixed memory pool size in MB for for SSTable index summaries. If left
|
||||
# empty, this will default to 5% of the heap size. If the memory usage of
|
||||
# all index summaries exceeds this limit, SSTables with low read rates will
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
|
|
@ -54,7 +54,7 @@ public class ConcurrentLinkedHashCache<K extends IMeasurableMemory, V extends IM
|
|||
{
|
||||
public int weightOf(K key, V value)
|
||||
{
|
||||
long size = key.memorySize() + value.memorySize();
|
||||
long size = key.unsharedHeapSize() + value.unsharedHeapSize();
|
||||
assert size <= Integer.MAX_VALUE : "Serialized size cannot be more than 2GB/Integer.MAX_VALUE";
|
||||
return (int) size;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,11 +22,17 @@ import java.util.Arrays;
|
|||
import java.util.UUID;
|
||||
|
||||
import org.apache.cassandra.config.Schema;
|
||||
import org.apache.cassandra.cql3.ColumnIdentifier;
|
||||
import org.apache.cassandra.db.composites.CellName;
|
||||
import org.apache.cassandra.db.composites.CellNames;
|
||||
import org.apache.cassandra.db.composites.SimpleSparseCellName;
|
||||
import org.apache.cassandra.utils.*;
|
||||
|
||||
public class CounterCacheKey implements CacheKey
|
||||
{
|
||||
private static final long EMPTY_SIZE = ObjectSizes.measure(new CounterCacheKey(null, ByteBufferUtil.EMPTY_BYTE_BUFFER, CellNames.simpleDense(ByteBuffer.allocate(1))))
|
||||
+ ObjectSizes.measure(new UUID(0, 0));
|
||||
|
||||
public final UUID cfId;
|
||||
public final byte[] partitionKey;
|
||||
public final byte[] cellName;
|
||||
|
|
@ -49,11 +55,11 @@ public class CounterCacheKey implements CacheKey
|
|||
return new PathInfo(cf.left, cf.right, cfId);
|
||||
}
|
||||
|
||||
public long memorySize()
|
||||
public long unsharedHeapSize()
|
||||
{
|
||||
return ObjectSizes.getFieldSize(3 * ObjectSizes.getReferenceSize())
|
||||
+ ObjectSizes.getArraySize(partitionKey)
|
||||
+ ObjectSizes.getArraySize(cellName);
|
||||
return EMPTY_SIZE
|
||||
+ ObjectSizes.sizeOfArray(partitionKey)
|
||||
+ ObjectSizes.sizeOfArray(cellName);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -23,5 +23,10 @@ package org.apache.cassandra.cache;
|
|||
|
||||
public interface IMeasurableMemory
|
||||
{
|
||||
public long memorySize();
|
||||
/**
|
||||
* @return the amount of on-heap memory retained by the object that might be reclaimed if the object were reclaimed,
|
||||
* i.e. it should try to exclude globally cached data where possible, or counting portions of arrays that are
|
||||
* referenced by the object but used by other objects only (e.g. slabbed byte-buffers), etc.
|
||||
*/
|
||||
public long unsharedHeapSize();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@ public class KeyCacheKey implements CacheKey
|
|||
public final UUID cfId;
|
||||
public final Descriptor desc;
|
||||
|
||||
private static final long EMPTY_SIZE = ObjectSizes.measure(new KeyCacheKey(null, null, ByteBufferUtil.EMPTY_BYTE_BUFFER));
|
||||
|
||||
// keeping an array instead of a ByteBuffer lowers the overhead of the key cache working set,
|
||||
// without extra copies on lookup since client-provided key ByteBuffers will be array-backed already
|
||||
public final byte[] key;
|
||||
|
|
@ -52,15 +54,9 @@ public class KeyCacheKey implements CacheKey
|
|||
return String.format("KeyCacheKey(%s, %s)", desc, ByteBufferUtil.bytesToHex(ByteBuffer.wrap(key)));
|
||||
}
|
||||
|
||||
public long memorySize()
|
||||
public long unsharedHeapSize()
|
||||
{
|
||||
return ObjectSizes.getFieldSize(// cfId
|
||||
ObjectSizes.getReferenceSize() +
|
||||
// desc
|
||||
ObjectSizes.getReferenceSize() +
|
||||
// key
|
||||
ObjectSizes.getReferenceSize())
|
||||
+ ObjectSizes.getArraySize(key);
|
||||
return EMPTY_SIZE + ObjectSizes.sizeOfArray(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -33,6 +33,8 @@ public class RowCacheKey implements CacheKey, Comparable<RowCacheKey>
|
|||
public final UUID cfId;
|
||||
public final byte[] key;
|
||||
|
||||
private static final long EMPTY_SIZE = ObjectSizes.measure(new RowCacheKey(null, ByteBufferUtil.EMPTY_BYTE_BUFFER));
|
||||
|
||||
public RowCacheKey(UUID cfId, DecoratedKey key)
|
||||
{
|
||||
this(cfId, key.key);
|
||||
|
|
@ -51,13 +53,9 @@ public class RowCacheKey implements CacheKey, Comparable<RowCacheKey>
|
|||
return new PathInfo(cf.left, cf.right, cfId);
|
||||
}
|
||||
|
||||
public long memorySize()
|
||||
public long unsharedHeapSize()
|
||||
{
|
||||
return ObjectSizes.getFieldSize(// cfId
|
||||
ObjectSizes.getReferenceSize() +
|
||||
// key
|
||||
ObjectSizes.getReferenceSize())
|
||||
+ ObjectSizes.getArraySize(key);
|
||||
return EMPTY_SIZE + ObjectSizes.sizeOfArray(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -57,10 +57,4 @@ public class RowCacheSentinel implements IRowCacheEntry
|
|||
{
|
||||
return Objects.hashCode(sentinelId);
|
||||
}
|
||||
|
||||
public long memorySize()
|
||||
{
|
||||
// Only long reference.
|
||||
return ObjectSizes.getFieldSize(TypeSizes.NATIVE.sizeof(sentinelId));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -79,6 +79,7 @@ public class Config
|
|||
|
||||
public Integer memtable_flush_writers = null; // will get set to the length of data dirs in DatabaseDescriptor
|
||||
public Integer memtable_total_space_in_mb;
|
||||
public float memtable_cleanup_threshold = 0.4f;
|
||||
|
||||
public Integer storage_port = 7000;
|
||||
public Integer ssl_storage_port = 7001;
|
||||
|
|
@ -160,7 +161,6 @@ public class Config
|
|||
public boolean compaction_preheat_key_cache = true;
|
||||
|
||||
public volatile boolean incremental_backups = false;
|
||||
public int memtable_flush_queue_size = 4;
|
||||
public boolean trickle_fsync = false;
|
||||
public int trickle_fsync_interval_in_kb = 10240;
|
||||
|
||||
|
|
@ -187,7 +187,7 @@ public class Config
|
|||
|
||||
public boolean inter_dc_tcp_nodelay = true;
|
||||
|
||||
public String memtable_allocator = "SlabAllocator";
|
||||
public String memtable_allocator = "HeapSlabPool";
|
||||
|
||||
private static boolean outboundBindAny = false;
|
||||
|
||||
|
|
|
|||
|
|
@ -49,9 +49,9 @@ import org.apache.cassandra.net.MessagingService;
|
|||
import org.apache.cassandra.scheduler.IRequestScheduler;
|
||||
import org.apache.cassandra.scheduler.NoScheduler;
|
||||
import org.apache.cassandra.service.CacheService;
|
||||
import org.apache.cassandra.utils.Allocator;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
import org.apache.cassandra.utils.FBUtilities;
|
||||
import org.apache.cassandra.utils.memory.Pool;
|
||||
|
||||
public class DatabaseDescriptor
|
||||
{
|
||||
|
|
@ -93,7 +93,7 @@ public class DatabaseDescriptor
|
|||
private static String localDC;
|
||||
private static Comparator<InetAddress> localComparator;
|
||||
|
||||
private static Class<? extends Allocator> memtableAllocator;
|
||||
private static Class<? extends Pool> memtablePool;
|
||||
|
||||
static
|
||||
{
|
||||
|
|
@ -261,8 +261,8 @@ public class DatabaseDescriptor
|
|||
if (conf.memtable_total_space_in_mb == null)
|
||||
conf.memtable_total_space_in_mb = (int) (Runtime.getRuntime().maxMemory() / (4 * 1048576));
|
||||
if (conf.memtable_total_space_in_mb <= 0)
|
||||
throw new ConfigurationException("memtable_total_space_in_mb must be positive");
|
||||
logger.info("Global memtable threshold is enabled at {}MB", conf.memtable_total_space_in_mb);
|
||||
throw new ConfigurationException("memtable_heap_space_in_mb must be positive");
|
||||
logger.info("Global memtable heap threshold is enabled at {}MB", conf.memtable_total_space_in_mb);
|
||||
|
||||
/* Memtable flush writer threads */
|
||||
if (conf.memtable_flush_writers != null && conf.memtable_flush_writers < 1)
|
||||
|
|
@ -482,10 +482,10 @@ public class DatabaseDescriptor
|
|||
conf.server_encryption_options = conf.encryption_options;
|
||||
}
|
||||
|
||||
String allocatorClass = conf.memtable_allocator;
|
||||
if (!allocatorClass.contains("."))
|
||||
allocatorClass = "org.apache.cassandra.utils." + allocatorClass;
|
||||
memtableAllocator = FBUtilities.classForName(allocatorClass, "allocator");
|
||||
String allocatorPoolClass = conf.memtable_allocator;
|
||||
if (!allocatorPoolClass.contains("."))
|
||||
allocatorPoolClass = "org.apache.cassandra.utils.memory." + allocatorPoolClass;
|
||||
memtablePool = FBUtilities.classForName(allocatorPoolClass, "allocator pool");
|
||||
|
||||
// Hardcoded system keyspaces
|
||||
List<KSMetaData> systemKeyspaces = Arrays.asList(KSMetaData.systemKeyspace());
|
||||
|
|
@ -1235,23 +1235,11 @@ public class DatabaseDescriptor
|
|||
conf.incremental_backups = value;
|
||||
}
|
||||
|
||||
public static int getFlushQueueSize()
|
||||
{
|
||||
return conf.memtable_flush_queue_size;
|
||||
}
|
||||
|
||||
public static int getFileCacheSizeInMB()
|
||||
{
|
||||
return conf.file_cache_size_in_mb;
|
||||
}
|
||||
|
||||
public static int getTotalMemtableSpaceInMB()
|
||||
{
|
||||
// should only be called if estimatesRealMemtableSize() is true
|
||||
assert conf.memtable_total_space_in_mb > 0;
|
||||
return conf.memtable_total_space_in_mb;
|
||||
}
|
||||
|
||||
public static long getTotalCommitlogSpaceInMB()
|
||||
{
|
||||
return conf.commitlog_total_space_in_mb;
|
||||
|
|
@ -1382,15 +1370,17 @@ public class DatabaseDescriptor
|
|||
return conf.preheat_kernel_page_cache;
|
||||
}
|
||||
|
||||
public static Allocator getMemtableAllocator()
|
||||
public static Pool getMemtableAllocatorPool()
|
||||
{
|
||||
try
|
||||
{
|
||||
return memtableAllocator.newInstance();
|
||||
return memtablePool
|
||||
.getConstructor(long.class, float.class, Runnable.class)
|
||||
.newInstance(conf.memtable_total_space_in_mb << 20, conf.memtable_cleanup_threshold, new ColumnFamilyStore.FlushLargestColumnFamily());
|
||||
}
|
||||
catch (InstantiationException | IllegalAccessException e)
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,19 +22,22 @@ import java.nio.ByteBuffer;
|
|||
|
||||
import org.apache.cassandra.cache.IMeasurableMemory;
|
||||
import org.apache.cassandra.cql3.statements.Selectable;
|
||||
import org.apache.cassandra.db.TypeSizes;
|
||||
import org.apache.cassandra.db.marshal.AbstractType;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
import org.apache.cassandra.utils.ObjectSizes;
|
||||
import org.apache.cassandra.utils.memory.AbstractAllocator;
|
||||
|
||||
/**
|
||||
* Represents an identifer for a CQL column definition.
|
||||
* TODO : should support light-weight mode without text representation for when not interned
|
||||
*/
|
||||
public class ColumnIdentifier implements Selectable, Comparable<ColumnIdentifier>, IMeasurableMemory
|
||||
{
|
||||
public final ByteBuffer bytes;
|
||||
private final String text;
|
||||
|
||||
private static final long EMPTY_SIZE = ObjectSizes.measure(new ColumnIdentifier("", true));
|
||||
|
||||
public ColumnIdentifier(String rawText, boolean keepCase)
|
||||
{
|
||||
this.text = keepCase ? rawText : rawText.toLowerCase(Locale.US);
|
||||
|
|
@ -47,6 +50,12 @@ public class ColumnIdentifier implements Selectable, Comparable<ColumnIdentifier
|
|||
this.text = type.getString(bytes);
|
||||
}
|
||||
|
||||
private ColumnIdentifier(ByteBuffer bytes, String text)
|
||||
{
|
||||
this.bytes = bytes;
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int hashCode()
|
||||
{
|
||||
|
|
@ -73,11 +82,18 @@ public class ColumnIdentifier implements Selectable, Comparable<ColumnIdentifier
|
|||
return text;
|
||||
}
|
||||
|
||||
public long memorySize()
|
||||
public long unsharedHeapSize()
|
||||
{
|
||||
return ObjectSizes.getFieldSize(2 * ObjectSizes.getReferenceSize())
|
||||
+ ObjectSizes.getSize(bytes)
|
||||
+ TypeSizes.NATIVE.sizeof(text);
|
||||
return EMPTY_SIZE
|
||||
+ ObjectSizes.sizeOnHeapOf(bytes)
|
||||
+ ObjectSizes.sizeOf(text);
|
||||
}
|
||||
|
||||
public long excessHeapSizeExcludingData()
|
||||
{
|
||||
return EMPTY_SIZE
|
||||
+ ObjectSizes.sizeOnHeapExcludingData(bytes)
|
||||
+ ObjectSizes.sizeOf(text);
|
||||
}
|
||||
|
||||
public int compareTo(ColumnIdentifier other)
|
||||
|
|
@ -87,4 +103,10 @@ public class ColumnIdentifier implements Selectable, Comparable<ColumnIdentifier
|
|||
|
||||
return bytes.compareTo(other.bytes);
|
||||
}
|
||||
|
||||
public ColumnIdentifier clone(AbstractAllocator allocator)
|
||||
{
|
||||
return new ColumnIdentifier(allocator.clone(bytes), text);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ public class QueryProcessor
|
|||
public static final SemanticVersion CQL_VERSION = new SemanticVersion("3.1.4");
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(QueryProcessor.class);
|
||||
private static final MemoryMeter meter = new MemoryMeter();
|
||||
private static final MemoryMeter meter = new MemoryMeter().withGuessing(MemoryMeter.Guess.FALLBACK_BEST);
|
||||
private static final long MAX_CACHE_PREPARED_MEMORY = Runtime.getRuntime().maxMemory() / 256;
|
||||
private static final int MAX_CACHE_PREPARED_COUNT = 10000;
|
||||
|
||||
|
|
@ -76,28 +76,14 @@ public class QueryProcessor
|
|||
|
||||
static
|
||||
{
|
||||
if (MemoryMeter.isInitialized())
|
||||
{
|
||||
preparedStatements = new ConcurrentLinkedHashMap.Builder<MD5Digest, CQLStatement>()
|
||||
.maximumWeightedCapacity(MAX_CACHE_PREPARED_MEMORY)
|
||||
.weigher(cqlMemoryUsageWeigher)
|
||||
.build();
|
||||
thriftPreparedStatements = new ConcurrentLinkedHashMap.Builder<Integer, CQLStatement>()
|
||||
.maximumWeightedCapacity(MAX_CACHE_PREPARED_MEMORY)
|
||||
.weigher(thriftMemoryUsageWeigher)
|
||||
.build();
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.error("Unable to initialize MemoryMeter (jamm not specified as javaagent). This means "
|
||||
+ "Cassandra will be unable to measure object sizes accurately and may consequently OOM.");
|
||||
preparedStatements = new ConcurrentLinkedHashMap.Builder<MD5Digest, CQLStatement>()
|
||||
.maximumWeightedCapacity(MAX_CACHE_PREPARED_COUNT)
|
||||
.build();
|
||||
thriftPreparedStatements = new ConcurrentLinkedHashMap.Builder<Integer, CQLStatement>()
|
||||
.maximumWeightedCapacity(MAX_CACHE_PREPARED_COUNT)
|
||||
.build();
|
||||
}
|
||||
preparedStatements = new ConcurrentLinkedHashMap.Builder<MD5Digest, CQLStatement>()
|
||||
.maximumWeightedCapacity(MAX_CACHE_PREPARED_MEMORY)
|
||||
.weigher(cqlMemoryUsageWeigher)
|
||||
.build();
|
||||
thriftPreparedStatements = new ConcurrentLinkedHashMap.Builder<Integer, CQLStatement>()
|
||||
.maximumWeightedCapacity(MAX_CACHE_PREPARED_MEMORY)
|
||||
.weigher(thriftMemoryUsageWeigher)
|
||||
.build();
|
||||
}
|
||||
|
||||
private static final List<PreExecutionHook> preExecutionHooks = new CopyOnWriteArrayList<>();
|
||||
|
|
@ -449,9 +435,6 @@ public class QueryProcessor
|
|||
|
||||
private static long measure(Object key)
|
||||
{
|
||||
if (!MemoryMeter.isInitialized())
|
||||
return 1;
|
||||
|
||||
return key instanceof MeasurableForPreparedCache
|
||||
? ((MeasurableForPreparedCache)key).measureForPreparedCache(meter)
|
||||
: meter.measureDeep(key);
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ import org.apache.cassandra.db.composites.CellName;
|
|||
import org.apache.cassandra.db.composites.CellNameType;
|
||||
import org.apache.cassandra.db.composites.Composite;
|
||||
import org.apache.cassandra.db.filter.ColumnSlice;
|
||||
import org.apache.cassandra.utils.Allocator;
|
||||
import org.apache.cassandra.utils.memory.AbstractAllocator;
|
||||
|
||||
/**
|
||||
* A ColumnFamily backed by an ArrayList.
|
||||
|
|
@ -101,7 +101,7 @@ public class ArrayBackedSortedColumns extends AbstractThreadUnsafeSortedColumns
|
|||
* without knowing about (we can revisit that decision later if we have
|
||||
* use cases where most insert are in sorted order but a few are not).
|
||||
*/
|
||||
public void addColumn(Cell cell, Allocator allocator)
|
||||
public void addColumn(Cell cell, AbstractAllocator allocator)
|
||||
{
|
||||
if (cells.isEmpty())
|
||||
{
|
||||
|
|
@ -131,7 +131,7 @@ public class ArrayBackedSortedColumns extends AbstractThreadUnsafeSortedColumns
|
|||
if (pos >= 0)
|
||||
resolveAgainst(pos, cell, allocator);
|
||||
else
|
||||
cells.add(-pos-1, cell);
|
||||
cells.add(-pos - 1, cell);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -139,7 +139,7 @@ public class ArrayBackedSortedColumns extends AbstractThreadUnsafeSortedColumns
|
|||
* Resolve against element at position i.
|
||||
* Assume that i is a valid position.
|
||||
*/
|
||||
private void resolveAgainst(int i, Cell cell, Allocator allocator)
|
||||
private void resolveAgainst(int i, Cell cell, AbstractAllocator allocator)
|
||||
{
|
||||
Cell oldCell = cells.get(i);
|
||||
|
||||
|
|
@ -184,7 +184,7 @@ public class ArrayBackedSortedColumns extends AbstractThreadUnsafeSortedColumns
|
|||
return -mid - (result < 0 ? 1 : 2);
|
||||
}
|
||||
|
||||
public void addAll(ColumnFamily cm, Allocator allocator, Function<Cell, Cell> transformation)
|
||||
public void addAll(ColumnFamily cm, AbstractAllocator allocator, Function<Cell, Cell> transformation)
|
||||
{
|
||||
delete(cm.deletionInfo());
|
||||
if (cm.getColumnCount() == 0)
|
||||
|
|
|
|||
|
|
@ -18,25 +18,28 @@
|
|||
package org.apache.cassandra.db;
|
||||
|
||||
import java.util.AbstractCollection;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Functions;
|
||||
import com.google.common.collect.Iterators;
|
||||
import com.google.common.collect.*;
|
||||
|
||||
import org.apache.cassandra.config.CFMetaData;
|
||||
import org.apache.cassandra.db.composites.CellName;
|
||||
import org.apache.cassandra.db.composites.CellNameType;
|
||||
import org.apache.cassandra.db.filter.ColumnSlice;
|
||||
import org.apache.cassandra.db.index.SecondaryIndexManager;
|
||||
import org.apache.cassandra.utils.Allocator;
|
||||
import org.apache.cassandra.utils.ObjectSizes;
|
||||
import org.apache.cassandra.utils.btree.BTree;
|
||||
import org.apache.cassandra.utils.btree.BTreeSet;
|
||||
import org.apache.cassandra.utils.btree.ReplaceFunction;
|
||||
import org.apache.cassandra.utils.btree.UpdateFunction;
|
||||
import org.apache.cassandra.utils.memory.AbstractAllocator;
|
||||
|
||||
import static org.apache.cassandra.db.index.SecondaryIndexManager.Updater;
|
||||
|
||||
|
|
@ -51,6 +54,9 @@ import static org.apache.cassandra.db.index.SecondaryIndexManager.Updater;
|
|||
*/
|
||||
public class AtomicBTreeColumns extends ColumnFamily
|
||||
{
|
||||
static final long HEAP_SIZE = ObjectSizes.measure(new AtomicBTreeColumns(CFMetaData.IndexCf, null))
|
||||
+ ObjectSizes.measure(new Holder(null, null));
|
||||
|
||||
private static final Function<Cell, CellName> NAME = new Function<Cell, CellName>()
|
||||
{
|
||||
public CellName apply(Cell column)
|
||||
|
|
@ -152,52 +158,58 @@ public class AtomicBTreeColumns extends ColumnFamily
|
|||
}
|
||||
}
|
||||
|
||||
public void addColumn(Cell column, Allocator allocator)
|
||||
public void addAll(ColumnFamily cm, AbstractAllocator allocator, Function<Cell, Cell> transformation)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
Holder current = ref;
|
||||
Holder update = ref.update(this, current.deletionInfo, metadata.comparator.columnComparator(), Arrays.asList(column), null);
|
||||
if (refUpdater.compareAndSet(this, current, update))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public void addAll(ColumnFamily cm, Allocator allocator, Function<Cell, Cell> transformation)
|
||||
{
|
||||
addAllWithSizeDelta(cm, allocator, transformation, SecondaryIndexManager.nullUpdater);
|
||||
addAllWithSizeDelta(cm, allocator, transformation, SecondaryIndexManager.nullUpdater, new Delta());
|
||||
}
|
||||
|
||||
// the function we provide to the btree utilities to perform any column replacements
|
||||
private static final class ColumnUpdater implements ReplaceFunction<Cell>
|
||||
private static final class ColumnUpdater implements UpdateFunction<Cell>
|
||||
{
|
||||
final Allocator allocator;
|
||||
final AtomicBTreeColumns updating;
|
||||
final Holder ref;
|
||||
final AbstractAllocator allocator;
|
||||
final Function<Cell, Cell> transform;
|
||||
final Updater indexer;
|
||||
long delta;
|
||||
final Delta delta;
|
||||
|
||||
private ColumnUpdater(Allocator allocator, Function<Cell, Cell> transform, Updater indexer)
|
||||
private ColumnUpdater(AtomicBTreeColumns updating, Holder ref, AbstractAllocator allocator, Function<Cell, Cell> transform, Updater indexer, Delta delta)
|
||||
{
|
||||
this.updating = updating;
|
||||
this.ref = ref;
|
||||
this.allocator = allocator;
|
||||
this.transform = transform;
|
||||
this.indexer = indexer;
|
||||
this.delta = delta;
|
||||
}
|
||||
|
||||
public Cell apply(Cell inserted)
|
||||
{
|
||||
indexer.insert(inserted);
|
||||
delta += inserted.dataSize();
|
||||
delta.insert(inserted);
|
||||
return transform.apply(inserted);
|
||||
}
|
||||
|
||||
public Cell apply(Cell replaced, Cell update)
|
||||
public Cell apply(Cell existing, Cell update)
|
||||
{
|
||||
Cell reconciled = update.reconcile(replaced, allocator);
|
||||
indexer.update(replaced, reconciled);
|
||||
delta += reconciled.dataSize() - replaced.dataSize();
|
||||
|
||||
Cell reconciled = update.reconcile(existing, allocator);
|
||||
indexer.update(existing, reconciled);
|
||||
if (existing != reconciled)
|
||||
delta.swap(existing, reconciled);
|
||||
else
|
||||
delta.abort(update);
|
||||
return transform.apply(reconciled);
|
||||
}
|
||||
|
||||
public boolean abortEarly()
|
||||
{
|
||||
return updating.ref != ref;
|
||||
}
|
||||
|
||||
public void allocated(long heapSize)
|
||||
{
|
||||
delta.addHeapSize(heapSize);
|
||||
}
|
||||
}
|
||||
|
||||
private static Collection<Cell> transform(Comparator<Cell> cmp, ColumnFamily cf, Function<Cell, Cell> transformation, boolean sort)
|
||||
|
|
@ -215,11 +227,11 @@ public class AtomicBTreeColumns extends ColumnFamily
|
|||
}
|
||||
|
||||
/**
|
||||
* This is only called by Memtable.resolve, so only AtomicSortedColumns needs to implement it.
|
||||
* This is only called by Memtable.resolve, so only AtomicBTreeColumns needs to implement it.
|
||||
*
|
||||
* @return the difference in size seen after merging the given columns
|
||||
*/
|
||||
public long addAllWithSizeDelta(final ColumnFamily cm, Allocator allocator, Function<Cell, Cell> transformation, Updater indexer)
|
||||
public Delta addAllWithSizeDelta(final ColumnFamily cm, AbstractAllocator allocator, Function<Cell, Cell> transformation, Updater indexer, Delta delta)
|
||||
{
|
||||
boolean transformed = false;
|
||||
Collection<Cell> insert;
|
||||
|
|
@ -248,11 +260,14 @@ public class AtomicBTreeColumns extends ColumnFamily
|
|||
}
|
||||
}
|
||||
}
|
||||
deletionInfo = current.deletionInfo.copy().add(deletionInfo);
|
||||
|
||||
ColumnUpdater updater = new ColumnUpdater(allocator, transformation, indexer);
|
||||
Holder h = current.update(this, deletionInfo, metadata.comparator.columnComparator(), insert, updater);
|
||||
if (h != null && refUpdater.compareAndSet(this, current, h))
|
||||
delta.reset();
|
||||
deletionInfo = current.deletionInfo.copy().add(deletionInfo);
|
||||
delta.addHeapSize(deletionInfo.unsharedHeapSize() - current.deletionInfo.unsharedHeapSize());
|
||||
ColumnUpdater updater = new ColumnUpdater(this, current, allocator, transformation, indexer, delta);
|
||||
Object[] tree = BTree.update(current.tree, metadata.comparator.columnComparator(), insert, true, updater);
|
||||
|
||||
if (tree != null && refUpdater.compareAndSet(this, current, new Holder(tree, deletionInfo)))
|
||||
{
|
||||
indexer.updateRowLevelIndexes();
|
||||
return updater.delta;
|
||||
|
|
@ -268,25 +283,20 @@ public class AtomicBTreeColumns extends ColumnFamily
|
|||
|
||||
}
|
||||
|
||||
// no particular reason not to implement these next methods, we just haven't needed them yet
|
||||
|
||||
public void addColumn(Cell column, AbstractAllocator allocator)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public boolean replace(Cell oldColumn, Cell newColumn)
|
||||
{
|
||||
if (!oldColumn.name().equals(newColumn.name()))
|
||||
throw new IllegalArgumentException();
|
||||
|
||||
while (true)
|
||||
{
|
||||
Holder current = ref;
|
||||
Holder modified = current.update(this, current.deletionInfo, metadata.comparator.columnComparator(), Arrays.asList(newColumn), null);
|
||||
if (modified == current)
|
||||
return false;
|
||||
if (refUpdater.compareAndSet(this, current, modified))
|
||||
return true;
|
||||
}
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public void clear()
|
||||
{
|
||||
// no particular reason not to implement this, we just haven't needed it yet
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
|
|
@ -377,35 +387,71 @@ public class AtomicBTreeColumns extends ColumnFamily
|
|||
{
|
||||
return new Holder(this.tree, info);
|
||||
}
|
||||
|
||||
Holder update(AtomicBTreeColumns container, DeletionInfo deletionInfo, Comparator<Cell> cmp, Collection<Cell> update, ReplaceFunction<Cell> replaceF)
|
||||
{
|
||||
Object[] r = BTree.update(tree, cmp, update, true, replaceF, new TerminateEarly(container, this));
|
||||
// result can be null if terminate early kicks in, in which case we need to propagate the early failure so we can retry
|
||||
if (r == null)
|
||||
return null;
|
||||
return new Holder(r, deletionInfo);
|
||||
}
|
||||
}
|
||||
|
||||
// a function provided to the btree functions that aborts the modification
|
||||
// if we already know the final cas will fail
|
||||
private static final class TerminateEarly implements Function<Object, Boolean>
|
||||
{
|
||||
final AtomicBTreeColumns columns;
|
||||
final Holder ref;
|
||||
// TODO: create a stack-allocation-friendly list to help optimise garbage for updates to rows with few columns
|
||||
|
||||
private TerminateEarly(AtomicBTreeColumns columns, Holder ref)
|
||||
/**
|
||||
* tracks the size changes made while merging a new group of cells in
|
||||
*/
|
||||
public static final class Delta
|
||||
{
|
||||
private long dataSize;
|
||||
private long heapSize;
|
||||
|
||||
// we track the discarded cells (cells that were in the btree, but replaced by new ones)
|
||||
// separately from aborted ones (were part of an update but older than existing cells)
|
||||
// since we need to reset the former when we race on the btree update, but not the latter
|
||||
private List<Cell> discarded = new ArrayList<>();
|
||||
private List<Cell> aborted;
|
||||
|
||||
protected void reset()
|
||||
{
|
||||
this.columns = columns;
|
||||
this.ref = ref;
|
||||
this.dataSize = 0;
|
||||
this.heapSize = 0;
|
||||
discarded.clear();
|
||||
}
|
||||
|
||||
public Boolean apply(Object o)
|
||||
protected void addHeapSize(long heapSize)
|
||||
{
|
||||
if (ref != columns.ref)
|
||||
return Boolean.TRUE;
|
||||
return Boolean.FALSE;
|
||||
this.heapSize += heapSize;
|
||||
}
|
||||
|
||||
protected void swap(Cell old, Cell updated)
|
||||
{
|
||||
dataSize += updated.dataSize() - old.dataSize();
|
||||
heapSize += updated.excessHeapSizeExcludingData() - old.excessHeapSizeExcludingData();
|
||||
discarded.add(old);
|
||||
}
|
||||
|
||||
protected void insert(Cell insert)
|
||||
{
|
||||
this.dataSize += insert.dataSize();
|
||||
this.heapSize += insert.excessHeapSizeExcludingData();
|
||||
}
|
||||
|
||||
private void abort(Cell neverUsed)
|
||||
{
|
||||
if (aborted == null)
|
||||
aborted = new ArrayList<>();
|
||||
aborted.add(neverUsed);
|
||||
}
|
||||
|
||||
public long dataSize()
|
||||
{
|
||||
return dataSize;
|
||||
}
|
||||
|
||||
public long excessHeapSize()
|
||||
{
|
||||
return heapSize;
|
||||
}
|
||||
|
||||
public Iterable<Cell> reclaimed()
|
||||
{
|
||||
if (aborted == null)
|
||||
return discarded;
|
||||
return Iterables.concat(discarded, aborted);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,14 +29,16 @@ import com.google.common.collect.AbstractIterator;
|
|||
import org.apache.cassandra.config.CFMetaData;
|
||||
import org.apache.cassandra.db.composites.CellName;
|
||||
import org.apache.cassandra.db.composites.CellNameType;
|
||||
import org.apache.cassandra.db.composites.CellNames;
|
||||
import org.apache.cassandra.db.marshal.*;
|
||||
import org.apache.cassandra.io.sstable.Descriptor;
|
||||
import org.apache.cassandra.io.util.DataOutputBuffer;
|
||||
import org.apache.cassandra.serializers.MarshalException;
|
||||
import org.apache.cassandra.utils.Allocator;
|
||||
import org.apache.cassandra.utils.ObjectSizes;
|
||||
import org.apache.cassandra.utils.memory.AbstractAllocator;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
import org.apache.cassandra.utils.FBUtilities;
|
||||
import org.apache.cassandra.utils.HeapAllocator;
|
||||
import org.apache.cassandra.utils.memory.HeapAllocator;
|
||||
|
||||
/**
|
||||
* Cell is immutable, which prevents all kinds of confusion in a multithreaded environment.
|
||||
|
|
@ -45,6 +47,8 @@ public class Cell implements OnDiskAtom
|
|||
{
|
||||
public static final int MAX_NAME_LENGTH = FBUtilities.MAX_UNSIGNED_SHORT;
|
||||
|
||||
private static final long EMPTY_SIZE = ObjectSizes.measure(new Cell(CellNames.simpleDense(ByteBuffer.allocate(1))));
|
||||
|
||||
/**
|
||||
* For 2.0-formatted sstables (where column count is not stored), @param count should be Integer.MAX_VALUE,
|
||||
* and we will look for the end-of-row column name marker instead of relying on that.
|
||||
|
|
@ -158,7 +162,14 @@ public class Cell implements OnDiskAtom
|
|||
|
||||
public int dataSize()
|
||||
{
|
||||
return name().dataSize() + value.remaining() + TypeSizes.NATIVE.sizeof(timestamp);
|
||||
return name.dataSize() + value.remaining() + TypeSizes.NATIVE.sizeof(timestamp);
|
||||
}
|
||||
|
||||
// returns the size of the Cell and all references on the heap, excluding any costs associated with byte arrays
|
||||
// that would be allocated by a localCopy, as these will be accounted for by the allocator
|
||||
public long excessHeapSizeExcludingData()
|
||||
{
|
||||
return EMPTY_SIZE + name.excessHeapSizeExcludingData() + ObjectSizes.sizeOnHeapExcludingData(value);
|
||||
}
|
||||
|
||||
public int serializedSize(CellNameType type, TypeSizes typeSizes)
|
||||
|
|
@ -215,7 +226,7 @@ public class Cell implements OnDiskAtom
|
|||
return reconcile(cell, HeapAllocator.instance);
|
||||
}
|
||||
|
||||
public Cell reconcile(Cell cell, Allocator allocator)
|
||||
public Cell reconcile(Cell cell, AbstractAllocator allocator)
|
||||
{
|
||||
// tombstones take precedence. (if both are tombstones, then it doesn't matter which one we use.)
|
||||
if (isMarkedForDelete(System.currentTimeMillis()))
|
||||
|
|
@ -252,7 +263,7 @@ public class Cell implements OnDiskAtom
|
|||
return result;
|
||||
}
|
||||
|
||||
public Cell localCopy(ColumnFamilyStore cfs, Allocator allocator)
|
||||
public Cell localCopy(ColumnFamilyStore cfs, AbstractAllocator allocator)
|
||||
{
|
||||
return new Cell(name.copy(allocator), allocator.clone(value), timestamp);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,9 @@ import org.apache.cassandra.utils.ObjectSizes;
|
|||
|
||||
public class ClockAndCount implements IMeasurableMemory
|
||||
{
|
||||
|
||||
private static final long EMPTY_SIZE = ObjectSizes.measure(new ClockAndCount(0, 0));
|
||||
|
||||
public static ClockAndCount BLANK = ClockAndCount.create(0L, 0L);
|
||||
|
||||
public final long clock;
|
||||
|
|
@ -40,10 +43,9 @@ public class ClockAndCount implements IMeasurableMemory
|
|||
return new ClockAndCount(clock, count);
|
||||
}
|
||||
|
||||
public long memorySize()
|
||||
public long unsharedHeapSize()
|
||||
{
|
||||
return ObjectSizes.getFieldSize(TypeSizes.NATIVE.sizeof(clock))
|
||||
+ ObjectSizes.getFieldSize(TypeSizes.NATIVE.sizeof(count));
|
||||
return EMPTY_SIZE;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ import org.apache.cassandra.db.marshal.CounterColumnType;
|
|||
import org.apache.cassandra.io.sstable.SSTableReader;
|
||||
import org.apache.cassandra.io.util.FileUtils;
|
||||
import org.apache.cassandra.tracing.Tracing;
|
||||
import org.apache.cassandra.utils.HeapAllocator;
|
||||
import org.apache.cassandra.utils.memory.HeapAllocator;
|
||||
|
||||
public class CollationController
|
||||
{
|
||||
|
|
|
|||
|
|
@ -31,6 +31,9 @@ import java.util.UUID;
|
|||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Functions;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
|
||||
import org.apache.cassandra.utils.memory.AbstractAllocator;
|
||||
import org.apache.cassandra.utils.memory.HeapAllocator;
|
||||
import org.apache.commons.lang3.builder.HashCodeBuilder;
|
||||
|
||||
import org.apache.cassandra.cache.IRowCacheEntry;
|
||||
|
|
@ -198,7 +201,7 @@ public abstract class ColumnFamily implements Iterable<Cell>, IRowCacheEntry
|
|||
* If a cell with the same name is already present in the map, it will
|
||||
* be replaced by the newly added cell.
|
||||
*/
|
||||
public abstract void addColumn(Cell cell, Allocator allocator);
|
||||
public abstract void addColumn(Cell cell, AbstractAllocator allocator);
|
||||
|
||||
/**
|
||||
* Adds all the columns of a given column map to this column map.
|
||||
|
|
@ -209,7 +212,7 @@ public abstract class ColumnFamily implements Iterable<Cell>, IRowCacheEntry
|
|||
* </code>
|
||||
* but is potentially faster.
|
||||
*/
|
||||
public abstract void addAll(ColumnFamily cm, Allocator allocator, Function<Cell, Cell> transformation);
|
||||
public abstract void addAll(ColumnFamily cm, AbstractAllocator allocator, Function<Cell, Cell> transformation);
|
||||
|
||||
/**
|
||||
* Replace oldCell if present by newCell.
|
||||
|
|
@ -282,7 +285,7 @@ public abstract class ColumnFamily implements Iterable<Cell>, IRowCacheEntry
|
|||
delete(columns.deletionInfo());
|
||||
}
|
||||
|
||||
public void addAll(ColumnFamily cf, Allocator allocator)
|
||||
public void addAll(ColumnFamily cf, AbstractAllocator allocator)
|
||||
{
|
||||
addAll(cf, allocator, Functions.<Cell>identity());
|
||||
}
|
||||
|
|
@ -404,7 +407,7 @@ public abstract class ColumnFamily implements Iterable<Cell>, IRowCacheEntry
|
|||
resolve(cf, HeapAllocator.instance);
|
||||
}
|
||||
|
||||
public void resolve(ColumnFamily cf, Allocator allocator)
|
||||
public void resolve(ColumnFamily cf, AbstractAllocator allocator)
|
||||
{
|
||||
// Row _does_ allow null CF objects :( seems a necessary evil for efficiency
|
||||
if (cf == null)
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ import java.nio.ByteBuffer;
|
|||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.regex.Pattern;
|
||||
import javax.management.*;
|
||||
|
|
@ -32,6 +31,14 @@ import javax.management.*;
|
|||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.collect.*;
|
||||
import com.google.common.util.concurrent.*;
|
||||
import org.apache.cassandra.concurrent.NamedThreadFactory;
|
||||
import org.apache.cassandra.utils.concurrent.OpOrder;
|
||||
import org.apache.cassandra.concurrent.StageManager;
|
||||
import org.apache.cassandra.db.filter.ColumnSlice;
|
||||
import org.apache.cassandra.db.filter.SliceQueryFilter;
|
||||
import org.apache.cassandra.utils.memory.HeapAllocator;
|
||||
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.Striped;
|
||||
import com.google.common.util.concurrent.Uninterruptibles;
|
||||
|
|
@ -46,8 +53,10 @@ import org.apache.cassandra.db.columniterator.OnDiskAtomIterator;
|
|||
import org.apache.cassandra.db.commitlog.CommitLog;
|
||||
import org.apache.cassandra.db.commitlog.ReplayPosition;
|
||||
import org.apache.cassandra.db.compaction.*;
|
||||
import org.apache.cassandra.db.filter.ExtendedFilter;
|
||||
import org.apache.cassandra.db.filter.IDiskAtomFilter;
|
||||
import org.apache.cassandra.db.filter.QueryFilter;
|
||||
import org.apache.cassandra.db.composites.CellName;
|
||||
import org.apache.cassandra.db.filter.*;
|
||||
import org.apache.cassandra.db.composites.CellNameType;
|
||||
import org.apache.cassandra.db.composites.Composite;
|
||||
import org.apache.cassandra.db.index.SecondaryIndex;
|
||||
|
|
@ -74,7 +83,19 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean
|
|||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(ColumnFamilyStore.class);
|
||||
|
||||
public static final ExecutorService postFlushExecutor = new JMXEnabledThreadPoolExecutor("MemtablePostFlusher");
|
||||
private static final ExecutorService flushExecutor = new JMXEnabledThreadPoolExecutor(DatabaseDescriptor.getFlushWriters(),
|
||||
StageManager.KEEPALIVE,
|
||||
TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<Runnable>(),
|
||||
new NamedThreadFactory("MemtableFlushWriter"),
|
||||
"internal");
|
||||
// post-flush executor is single threaded to provide guarantee that any flush Future on a CF will never return until prior flushes have completed
|
||||
public static final ExecutorService postFlushExecutor = new JMXEnabledThreadPoolExecutor(1,
|
||||
StageManager.KEEPALIVE,
|
||||
TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<Runnable>(),
|
||||
new NamedThreadFactory("MemtablePostFlush"),
|
||||
"internal");
|
||||
|
||||
public final Keyspace keyspace;
|
||||
public final String name;
|
||||
|
|
@ -83,7 +104,14 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean
|
|||
private final String mbeanName;
|
||||
private volatile boolean valid = true;
|
||||
|
||||
/* Memtables and SSTables on disk for this column family */
|
||||
/**
|
||||
* Memtables and SSTables on disk for this column family.
|
||||
*
|
||||
* We synchronize on the DataTracker to ensure isolation when we want to make sure
|
||||
* that the memtable we're acting on doesn't change out from under us. I.e., flush
|
||||
* syncronizes on it to make sure it can submit on both executors atomically,
|
||||
* so anyone else who wants to make sure flush doesn't interfere should as well.
|
||||
*/
|
||||
private final DataTracker data;
|
||||
|
||||
/* This is used to generate the next index for a SSTable */
|
||||
|
|
@ -98,11 +126,6 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean
|
|||
|
||||
public final Directories directories;
|
||||
|
||||
/** ratio of in-memory memtable size, to serialized size */
|
||||
volatile double liveRatio = 10.0; // reasonable default until we compute what it is based on actual data
|
||||
/** ops count last time we computed liveRatio */
|
||||
private final AtomicLong liveRatioComputedAt = new AtomicLong(32);
|
||||
|
||||
public final ColumnFamilyMetrics metric;
|
||||
public volatile long sampleLatencyNanos;
|
||||
|
||||
|
|
@ -128,8 +151,8 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean
|
|||
|
||||
// If the CF comparator has changed, we need to change the memtable,
|
||||
// because the old one still aliases the previous comparator.
|
||||
if (getMemtableThreadSafe().initialComparator != metadata.comparator)
|
||||
switchMemtable(true, true);
|
||||
if (data.getView().getCurrentMemtable().initialComparator != metadata.comparator)
|
||||
switchMemtable();
|
||||
}
|
||||
|
||||
private void maybeReloadCompactionStrategy()
|
||||
|
|
@ -158,14 +181,23 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean
|
|||
{
|
||||
protected void runMayThrow() throws Exception
|
||||
{
|
||||
if (getMemtableThreadSafe().isExpired())
|
||||
synchronized (data)
|
||||
{
|
||||
// if memtable is already expired but didn't flush because it's empty,
|
||||
// then schedule another flush.
|
||||
if (isClean())
|
||||
scheduleFlush();
|
||||
else
|
||||
forceFlush(); // scheduleFlush() will be called by the constructor of the new memtable.
|
||||
Memtable current = data.getView().getCurrentMemtable();
|
||||
// if we're not expired, we've been hit by a scheduled flush for an already flushed memtable, so ignore
|
||||
if (current.isExpired())
|
||||
{
|
||||
if (current.isClean())
|
||||
{
|
||||
// if we're still clean, instead of swapping just reschedule a flush for later
|
||||
scheduleFlush();
|
||||
}
|
||||
else
|
||||
{
|
||||
// we'll be rescheduled by the constructor of the Memtable.
|
||||
forceFlush();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -737,128 +769,83 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean
|
|||
}
|
||||
|
||||
/**
|
||||
* Switch and flush the current memtable, if it was dirty. The forceSwitch
|
||||
* flag allow to force switching the memtable even if it is clean (though
|
||||
* in that case we don't flush, as there is no point).
|
||||
* Switches the memtable iff the live memtable is the one provided
|
||||
*
|
||||
* @param memtable
|
||||
*/
|
||||
public Future<?> switchMemtable(final boolean writeCommitLog, boolean forceSwitch)
|
||||
public Future<?> switchMemtableIfCurrent(Memtable memtable)
|
||||
{
|
||||
/*
|
||||
* If we can get the writelock, that means no new updates can come in and
|
||||
* all ongoing updates to memtables have completed. We can get the tail
|
||||
* of the log and use it as the starting position for log replay on recovery.
|
||||
*
|
||||
* This is why we Keyspace.switchLock needs to be global instead of per-Keyspace:
|
||||
* we need to schedule discardCompletedSegments calls in the same order as their
|
||||
* contexts (commitlog position) were read, even though the flush executor
|
||||
* is multithreaded.
|
||||
*/
|
||||
Keyspace.switchLock.writeLock().lock();
|
||||
try
|
||||
synchronized (data)
|
||||
{
|
||||
final Future<ReplayPosition> ctx = writeCommitLog ? CommitLog.instance.getContext() : Futures.immediateFuture(ReplayPosition.NONE);
|
||||
|
||||
// submit the memtable for any indexed sub-cfses, and our own.
|
||||
final List<ColumnFamilyStore> icc = new ArrayList<ColumnFamilyStore>();
|
||||
// don't assume that this.memtable is dirty; forceFlush can bring us here during index build even if it is not
|
||||
for (ColumnFamilyStore cfs : concatWithIndexes())
|
||||
{
|
||||
if (forceSwitch || !cfs.getMemtableThreadSafe().isClean())
|
||||
icc.add(cfs);
|
||||
}
|
||||
|
||||
final CountDownLatch latch = new CountDownLatch(icc.size());
|
||||
for (ColumnFamilyStore cfs : icc)
|
||||
{
|
||||
Memtable memtable = cfs.data.switchMemtable();
|
||||
// With forceSwitch it's possible to get a clean memtable here.
|
||||
// In that case, since we've switched it already, just remove
|
||||
// it from the memtable pending flush right away.
|
||||
if (memtable.isClean())
|
||||
{
|
||||
cfs.replaceFlushed(memtable, null);
|
||||
latch.countDown();
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.info("Enqueuing flush of {}", memtable);
|
||||
memtable.flushAndSignal(latch, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
if (metric.memtableSwitchCount.count() == Long.MAX_VALUE)
|
||||
metric.memtableSwitchCount.clear();
|
||||
metric.memtableSwitchCount.inc();
|
||||
|
||||
// when all the memtables have been written, including for indexes, mark the flush in the commitlog header.
|
||||
// a second executor makes sure the onMemtableFlushes get called in the right order,
|
||||
// while keeping the wait-for-flush (future.get) out of anything latency-sensitive.
|
||||
return postFlushExecutor.submit(new WrappedRunnable()
|
||||
{
|
||||
public void runMayThrow() throws InterruptedException, ExecutionException
|
||||
{
|
||||
latch.await();
|
||||
|
||||
if (!icc.isEmpty())
|
||||
{
|
||||
//only valid when memtables exist
|
||||
|
||||
for (SecondaryIndex index : indexManager.getIndexesNotBackedByCfs())
|
||||
{
|
||||
// flush any non-cfs backed indexes
|
||||
logger.info("Flushing SecondaryIndex {}", index);
|
||||
index.forceBlockingFlush();
|
||||
}
|
||||
}
|
||||
|
||||
if (writeCommitLog)
|
||||
{
|
||||
// if we're not writing to the commit log, we are replaying the log, so marking
|
||||
// the log header with "you can discard anything written before the context" is not valid
|
||||
CommitLog.instance.discardCompletedSegments(metadata.cfId, ctx.get());
|
||||
}
|
||||
}
|
||||
});
|
||||
if (data.getView().getCurrentMemtable() == memtable)
|
||||
return switchMemtable();
|
||||
}
|
||||
finally
|
||||
return Futures.immediateFuture(null);
|
||||
}
|
||||
|
||||
/*
|
||||
* switchMemtable puts Memtable.getSortedContents on the writer executor. When the write is complete,
|
||||
* we turn the writer into an SSTableReader and add it to ssTables where it is available for reads.
|
||||
* This method does not block except for synchronizing on DataTracker, but the Future it returns will
|
||||
* not complete until the Memtable (and all prior Memtables) have been successfully flushed, and the CL
|
||||
* marked clean up to the position owned by the Memtable.
|
||||
*/
|
||||
public ListenableFuture<?> switchMemtable()
|
||||
{
|
||||
logger.info("Enqueuing flush of {}", name);
|
||||
synchronized (data)
|
||||
{
|
||||
Keyspace.switchLock.writeLock().unlock();
|
||||
Flush flush = new Flush(false);
|
||||
flushExecutor.execute(flush);
|
||||
ListenableFutureTask<?> task = ListenableFutureTask.create(flush.postFlush, null);
|
||||
postFlushExecutor.submit(task);
|
||||
return task;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isClean()
|
||||
public ListenableFuture<?> forceFlush()
|
||||
{
|
||||
// during index build, 2ary index memtables can be dirty even if parent is not. if so,
|
||||
// we want flushLargestMemtables to flush the 2ary index ones too.
|
||||
for (ColumnFamilyStore cfs : concatWithIndexes())
|
||||
if (!cfs.getMemtableThreadSafe().isClean())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
return forceFlush(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a future, with a guarantee that any data inserted prior to the forceFlush() call is fully flushed
|
||||
* by the time future.get() returns. Never returns null.
|
||||
* Flush if there is unflushed data that was written to the CommitLog before @param flushIfDirtyBefore
|
||||
* (inclusive). If @param flushIfDirtyBefore is null, flush if there is any unflushed data.
|
||||
*
|
||||
* @return a Future such that when the future completes, all data inserted before forceFlush was called,
|
||||
* will be flushed.
|
||||
*/
|
||||
public Future<?> forceFlush()
|
||||
public ListenableFuture<?> forceFlush(ReplayPosition flushIfDirtyBefore)
|
||||
{
|
||||
if (isClean())
|
||||
// we synchronize on the data tracker to ensure we don't race against other calls to switchMemtable(),
|
||||
// unnecessarily queueing memtables that are about to be made clean
|
||||
synchronized (data)
|
||||
{
|
||||
// We could have a memtable for this column family that is being
|
||||
// flushed. Make sure the future returned wait for that so callers can
|
||||
// assume that any data inserted prior to the call are fully flushed
|
||||
// when the future returns (see #5241).
|
||||
return postFlushExecutor.submit(new Runnable()
|
||||
{
|
||||
public void run()
|
||||
{
|
||||
logger.debug("forceFlush requested but everything is clean in {}", name);
|
||||
}
|
||||
});
|
||||
}
|
||||
// during index build, 2ary index memtables can be dirty even if parent is not. if so,
|
||||
// we want to flush the 2ary index ones too.
|
||||
boolean clean = true;
|
||||
for (ColumnFamilyStore cfs : concatWithIndexes())
|
||||
clean &= cfs.data.getView().getCurrentMemtable().isCleanAfter(flushIfDirtyBefore);
|
||||
|
||||
return switchMemtable(true, false);
|
||||
if (clean)
|
||||
{
|
||||
// We could have a memtable for this column family that is being
|
||||
// flushed. Make sure the future returned wait for that so callers can
|
||||
// assume that any data inserted prior to the call are fully flushed
|
||||
// when the future returns (see #5241).
|
||||
ListenableFutureTask<?> task = ListenableFutureTask.create(new Runnable()
|
||||
{
|
||||
public void run()
|
||||
{
|
||||
logger.debug("forceFlush requested but everything is clean in {}", name);
|
||||
}
|
||||
}, null);
|
||||
postFlushExecutor.execute(task);
|
||||
return task;
|
||||
}
|
||||
|
||||
return switchMemtable();
|
||||
}
|
||||
}
|
||||
|
||||
public void forceBlockingFlush()
|
||||
|
|
@ -866,6 +853,201 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean
|
|||
FBUtilities.waitOnFuture(forceFlush());
|
||||
}
|
||||
|
||||
/**
|
||||
* Both synchronises custom secondary indexes and provides ordering guarantees for futures on switchMemtable/flush
|
||||
* etc, which expect to be able to wait until the flush (and all prior flushes) requested have completed.
|
||||
*/
|
||||
private final class PostFlush implements Runnable
|
||||
{
|
||||
final boolean flushSecondaryIndexes;
|
||||
final OpOrder.Barrier writeBarrier;
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
volatile ReplayPosition lastReplayPosition;
|
||||
|
||||
private PostFlush(boolean flushSecondaryIndexes, OpOrder.Barrier writeBarrier)
|
||||
{
|
||||
this.writeBarrier = writeBarrier;
|
||||
this.flushSecondaryIndexes = flushSecondaryIndexes;
|
||||
}
|
||||
|
||||
public void run()
|
||||
{
|
||||
writeBarrier.await();
|
||||
|
||||
/**
|
||||
* we can flush 2is as soon as the barrier completes, as they will be consistent with (or ahead of) the
|
||||
* flushed memtables and CL position, which is as good as we can guarantee.
|
||||
* TODO: SecondaryIndex should support setBarrier(), so custom implementations can co-ordinate exactly
|
||||
* with CL as we do with memtables/CFS-backed SecondaryIndexes.
|
||||
*/
|
||||
|
||||
if (flushSecondaryIndexes)
|
||||
{
|
||||
for (SecondaryIndex index : indexManager.getIndexesNotBackedByCfs())
|
||||
{
|
||||
// flush any non-cfs backed indexes
|
||||
logger.info("Flushing SecondaryIndex {}", index);
|
||||
index.forceBlockingFlush();
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// we wait on the latch for the lastReplayPosition to be set, and so that waiters
|
||||
// on this task can rely on all prior flushes being complete
|
||||
latch.await();
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
// must check lastReplayPosition != null because Flush may find that all memtables are clean
|
||||
// and so not set a lastReplayPosition
|
||||
if (lastReplayPosition != null)
|
||||
{
|
||||
CommitLog.instance.discardCompletedSegments(metadata.cfId, lastReplayPosition);
|
||||
}
|
||||
|
||||
metric.pendingFlushes.dec();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Should only be constructed/used from switchMemtable() or truncate(), with ownership of the DataTracker monitor.
|
||||
* In the constructor the current memtable(s) are swapped, and a barrer on outstanding writes is issued;
|
||||
* when run by the flushWriter the barrier is waited on to ensure all outstanding writes have completed
|
||||
* before all memtables are immediately written, and the CL is either immediately marked clean or, if
|
||||
* there are custom secondary indexes, the post flush clean up is left to update those indexes and mark
|
||||
* the CL clean
|
||||
*/
|
||||
private final class Flush implements Runnable
|
||||
{
|
||||
final OpOrder.Barrier writeBarrier;
|
||||
final List<Memtable> memtables;
|
||||
final PostFlush postFlush;
|
||||
final boolean truncate;
|
||||
|
||||
private Flush(boolean truncate)
|
||||
{
|
||||
// if true, we won't flush, we'll just wait for any outstanding writes, switch the memtable, and discard
|
||||
this.truncate = truncate;
|
||||
|
||||
metric.pendingFlushes.inc();
|
||||
/**
|
||||
* To ensure correctness of switch without blocking writes, run() needs to wait for all write operations
|
||||
* started prior to the switch to complete. We do this by creating a Barrier on the writeOrdering
|
||||
* that all write operations register themselves with, and assigning this barrier to the memtables,
|
||||
* after which we *.issue()* the barrier. This barrier is used to direct write operations started prior
|
||||
* to the barrier.issue() into the memtable we have switched out, and any started after to its replacement.
|
||||
* In doing so it also tells the write operations to update the lastReplayPosition of the memtable, so
|
||||
* that we know the CL position we are dirty to, which can be marked clean when we complete.
|
||||
*/
|
||||
writeBarrier = keyspace.writeOrder.newBarrier();
|
||||
memtables = new ArrayList<>();
|
||||
|
||||
// submit flushes for the memtable for any indexed sub-cfses, and our own
|
||||
final ReplayPosition minReplayPosition = CommitLog.instance.getContext();
|
||||
for (ColumnFamilyStore cfs : concatWithIndexes())
|
||||
{
|
||||
// switch all memtables, regardless of their dirty status, setting the barrier
|
||||
// so that we can reach a coordinated decision about cleanliness once they
|
||||
// are no longer possible to be modified
|
||||
Memtable mt = cfs.data.switchMemtable(truncate);
|
||||
mt.setDiscarding(writeBarrier, minReplayPosition);
|
||||
memtables.add(mt);
|
||||
}
|
||||
|
||||
writeBarrier.issue();
|
||||
postFlush = new PostFlush(!truncate, writeBarrier);
|
||||
}
|
||||
|
||||
public void run()
|
||||
{
|
||||
// mark writes older than the barrier as blocking progress, permitting them to exceed our memory limit
|
||||
// if they are stuck waiting on it, then wait for them all to complete
|
||||
writeBarrier.markBlocking();
|
||||
writeBarrier.await();
|
||||
|
||||
// mark all memtables as flushing, removing them from the live memtable list, and
|
||||
// remove any memtables that are already clean from the set we need to flush
|
||||
Iterator<Memtable> iter = memtables.iterator();
|
||||
while (iter.hasNext())
|
||||
{
|
||||
Memtable memtable = iter.next();
|
||||
memtable.cfs.data.markFlushing(memtable);
|
||||
if (memtable.isClean() || truncate)
|
||||
{
|
||||
memtable.cfs.replaceFlushed(memtable, null);
|
||||
memtable.setDiscarded();
|
||||
iter.remove();
|
||||
}
|
||||
}
|
||||
|
||||
if (memtables.isEmpty())
|
||||
{
|
||||
postFlush.latch.countDown();
|
||||
return;
|
||||
}
|
||||
|
||||
metric.memtableSwitchCount.inc();
|
||||
|
||||
for (final Memtable memtable : memtables)
|
||||
{
|
||||
// flush the memtable
|
||||
MoreExecutors.sameThreadExecutor().execute(memtable.flushRunnable());
|
||||
memtable.setDiscarded();
|
||||
}
|
||||
|
||||
// signal the post-flush we've done our work
|
||||
postFlush.lastReplayPosition = memtables.get(0).getLastReplayPosition();
|
||||
postFlush.latch.countDown();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the largest memtable, as a percentage of *either* on- or off-heap memory limits, and immediately
|
||||
* queues it for flushing. If the memtable selected is flushed before this completes, no work is done.
|
||||
*/
|
||||
public static class FlushLargestColumnFamily implements Runnable
|
||||
{
|
||||
public void run()
|
||||
{
|
||||
float largestRatio = 0f;
|
||||
Memtable largest = null;
|
||||
for (ColumnFamilyStore cfs : ColumnFamilyStore.all())
|
||||
{
|
||||
// we take a reference to the current main memtable for the CF prior to snapping its ownership ratios
|
||||
// to ensure we have some ordering guarantee for performing the switchMemtableIf(), i.e. we will only
|
||||
// swap if the memtables we are measuring here haven't already been swapped by the time we try to swap them
|
||||
Memtable current = cfs.getDataTracker().getView().getCurrentMemtable();
|
||||
|
||||
// find the total ownership ratio for the memtable and all SecondaryIndexes owned by this CF,
|
||||
// both on- and off-heap, and select the largest of the two ratios to weight this CF
|
||||
float onHeap = 0f;
|
||||
onHeap += current.getAllocator().ownershipRatio();
|
||||
|
||||
for (SecondaryIndex index : cfs.indexManager.getIndexes())
|
||||
{
|
||||
if (index.getOnHeapAllocator() != null)
|
||||
onHeap += index.getOnHeapAllocator().ownershipRatio();
|
||||
}
|
||||
|
||||
if (onHeap > largestRatio)
|
||||
{
|
||||
largest = current;
|
||||
largestRatio = onHeap;
|
||||
}
|
||||
}
|
||||
|
||||
if (largest != null)
|
||||
{
|
||||
largest.cfs.switchMemtableIfCurrent(largest);
|
||||
logger.info("Reclaiming {} of {} retained memtable bytes", largest.getAllocator().reclaiming(), Memtable.memoryPool.used());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void maybeUpdateRowCache(DecoratedKey key)
|
||||
{
|
||||
if (!isRowCacheEnabled())
|
||||
|
|
@ -882,28 +1064,14 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean
|
|||
* param @ key - key for update/insert
|
||||
* param @ columnFamily - columnFamily changes
|
||||
*/
|
||||
public void apply(DecoratedKey key, ColumnFamily columnFamily, SecondaryIndexManager.Updater indexer)
|
||||
public void apply(DecoratedKey key, ColumnFamily columnFamily, SecondaryIndexManager.Updater indexer, OpOrder.Group opGroup, ReplayPosition replayPosition)
|
||||
{
|
||||
long start = System.nanoTime();
|
||||
|
||||
Memtable mt = getMemtableThreadSafe();
|
||||
mt.put(key, columnFamily, indexer);
|
||||
Memtable mt = data.getMemtableFor(opGroup);
|
||||
mt.put(key, columnFamily, indexer, opGroup, replayPosition);
|
||||
maybeUpdateRowCache(key);
|
||||
metric.writeLatency.addNano(System.nanoTime() - start);
|
||||
|
||||
// recompute liveRatio, if we have doubled the number of ops since last calculated
|
||||
while (true)
|
||||
{
|
||||
long last = liveRatioComputedAt.get();
|
||||
long operations = metric.writeLatency.latency.count();
|
||||
if (operations < 2 * last)
|
||||
break;
|
||||
if (liveRatioComputedAt.compareAndSet(last, operations))
|
||||
{
|
||||
logger.debug("computing liveRatio of {} at {} ops", this, operations);
|
||||
mt.updateLiveRatio();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1149,35 +1317,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean
|
|||
|
||||
public long getMemtableDataSize()
|
||||
{
|
||||
return metric.memtableDataSize.value();
|
||||
}
|
||||
|
||||
public long getTotalMemtableLiveSize()
|
||||
{
|
||||
return getMemtableDataSize() + indexManager.getTotalLiveSize();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the live size of all the memtables (the current active one and pending flush).
|
||||
*/
|
||||
public long getAllMemtablesLiveSize()
|
||||
{
|
||||
long size = 0;
|
||||
for (Memtable mt : getDataTracker().getAllMemtables())
|
||||
size += mt.getLiveSize();
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the size of all the memtables, including the pending flush ones and 2i memtables, if any.
|
||||
*/
|
||||
public long getTotalAllMemtablesLiveSize()
|
||||
{
|
||||
long size = getAllMemtablesLiveSize();
|
||||
if (indexManager.hasIndexes())
|
||||
for (ColumnFamilyStore index : indexManager.getIndexesBackedByCfs())
|
||||
size += index.getAllMemtablesLiveSize();
|
||||
return size;
|
||||
return metric.memtableHeapSize.value();
|
||||
}
|
||||
|
||||
public int getMemtableSwitchCount()
|
||||
|
|
@ -1185,11 +1325,6 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean
|
|||
return (int) metric.memtableSwitchCount.count();
|
||||
}
|
||||
|
||||
Memtable getMemtableThreadSafe()
|
||||
{
|
||||
return data.getMemtable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Package protected for access from the CompactionManager.
|
||||
*/
|
||||
|
|
@ -1245,7 +1380,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean
|
|||
|
||||
public int getPendingTasks()
|
||||
{
|
||||
return metric.pendingTasks.value();
|
||||
return (int) metric.pendingFlushes.count();
|
||||
}
|
||||
|
||||
public long getWriteCount()
|
||||
|
|
@ -1472,7 +1607,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean
|
|||
// retry w/ new view
|
||||
}
|
||||
|
||||
return new ViewFragment(sstables, Iterables.concat(Collections.singleton(view.memtable), view.memtablesPendingFlush));
|
||||
return new ViewFragment(sstables, view.getAllMemtables());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -2049,22 +2184,16 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean
|
|||
// that was part of the flushed we forced; otherwise on a tie, it won't get deleted.
|
||||
Uninterruptibles.sleepUninterruptibly(1, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
// nuke the memtable data w/o writing to disk first
|
||||
Keyspace.switchLock.writeLock().lock();
|
||||
try
|
||||
else
|
||||
{
|
||||
for (ColumnFamilyStore cfs : concatWithIndexes())
|
||||
// just nuke the memtable data w/o writing to disk first
|
||||
synchronized (data)
|
||||
{
|
||||
Memtable mt = cfs.getMemtableThreadSafe();
|
||||
if (!mt.isClean())
|
||||
mt.cfs.data.renewMemtable();
|
||||
final Flush flush = new Flush(true);
|
||||
flushExecutor.execute(flush);
|
||||
postFlushExecutor.submit(flush.postFlush);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Keyspace.switchLock.writeLock().unlock();
|
||||
}
|
||||
|
||||
Runnable truncateRunnable = new Runnable()
|
||||
{
|
||||
|
|
@ -2337,12 +2466,9 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean
|
|||
|
||||
public Iterable<ColumnFamilyStore> concatWithIndexes()
|
||||
{
|
||||
return Iterables.concat(indexManager.getIndexesBackedByCfs(), Collections.singleton(this));
|
||||
}
|
||||
|
||||
public Set<Memtable> getMemtablesPendingFlush()
|
||||
{
|
||||
return data.getMemtablesPendingFlush();
|
||||
// we return the main CFS first, which we rely on for simplicity in switchMemtable(), for getting the
|
||||
// latest replay position
|
||||
return Iterables.concat(Collections.singleton(this), indexManager.getIndexesBackedByCfs());
|
||||
}
|
||||
|
||||
public List<String> getBuiltIndexes()
|
||||
|
|
@ -2381,17 +2507,13 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean
|
|||
*/
|
||||
public long oldestUnflushedMemtable()
|
||||
{
|
||||
DataTracker.View view = data.getView();
|
||||
long oldest = view.memtable.creationTime();
|
||||
for (Memtable memtable : view.memtablesPendingFlush)
|
||||
oldest = Math.min(oldest, memtable.creationTime());
|
||||
return oldest;
|
||||
return data.getView().getOldestMemtable().creationTime();
|
||||
}
|
||||
|
||||
public boolean isEmpty()
|
||||
{
|
||||
DataTracker.View view = data.getView();
|
||||
return view.sstables.isEmpty() && view.memtable.getOperations() == 0 && view.memtablesPendingFlush.isEmpty();
|
||||
return view.sstables.isEmpty() && view.getCurrentMemtable().getOperations() == 0 && view.getCurrentMemtable() == view.getOldestMemtable();
|
||||
}
|
||||
|
||||
private boolean isRowCacheEnabled()
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ public interface ColumnFamilyStoreMBean
|
|||
* Returns the total amount of data stored in the memtable, including
|
||||
* column related overhead.
|
||||
*
|
||||
* @see org.apache.cassandra.metrics.ColumnFamilyMetrics#memtableDataSize
|
||||
* @see org.apache.cassandra.metrics.ColumnFamilyMetrics#memtableHeapSize
|
||||
* @return The size in bytes.
|
||||
* @deprecated
|
||||
*/
|
||||
|
|
@ -146,7 +146,7 @@ public interface ColumnFamilyStoreMBean
|
|||
public double getRecentWriteLatencyMicros();
|
||||
|
||||
/**
|
||||
* @see org.apache.cassandra.metrics.ColumnFamilyMetrics#pendingTasks
|
||||
* @see org.apache.cassandra.metrics.ColumnFamilyMetrics#pendingFlushes
|
||||
* @return the estimated number of tasks pending for this column family
|
||||
*/
|
||||
@Deprecated
|
||||
|
|
|
|||
|
|
@ -20,15 +20,15 @@ package org.apache.cassandra.db;
|
|||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.MessageDigest;
|
||||
import org.apache.cassandra.serializers.MarshalException;
|
||||
import org.apache.cassandra.utils.memory.AbstractAllocator;
|
||||
import org.apache.cassandra.utils.memory.HeapAllocator;
|
||||
|
||||
import org.apache.cassandra.config.CFMetaData;
|
||||
import org.apache.cassandra.db.composites.CellName;
|
||||
import org.apache.cassandra.db.composites.CellNameType;
|
||||
import org.apache.cassandra.db.context.CounterContext;
|
||||
import org.apache.cassandra.io.util.DataOutputBuffer;
|
||||
import org.apache.cassandra.serializers.MarshalException;
|
||||
import org.apache.cassandra.utils.Allocator;
|
||||
import org.apache.cassandra.utils.*;
|
||||
|
||||
/**
|
||||
* A column that represents a partitioned counter.
|
||||
|
|
@ -142,7 +142,7 @@ public class CounterCell extends Cell
|
|||
}
|
||||
|
||||
@Override
|
||||
public Cell reconcile(Cell cell, Allocator allocator)
|
||||
public Cell reconcile(Cell cell, AbstractAllocator allocator)
|
||||
{
|
||||
// live + tombstone: track last tombstone
|
||||
if (cell.isMarkedForDelete(Long.MIN_VALUE)) // cannot be an expired cell, so the current time is irrelevant
|
||||
|
|
@ -190,7 +190,7 @@ public class CounterCell extends Cell
|
|||
}
|
||||
|
||||
@Override
|
||||
public Cell localCopy(ColumnFamilyStore cfs, Allocator allocator)
|
||||
public Cell localCopy(ColumnFamilyStore cfs, AbstractAllocator allocator)
|
||||
{
|
||||
return new CounterCell(name.copy(allocator), allocator.clone(value), timestamp, timestampOfLastDelete);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,6 +33,9 @@ import org.apache.cassandra.exceptions.WriteTimeoutException;
|
|||
import org.apache.cassandra.io.IVersionedSerializer;
|
||||
import org.apache.cassandra.net.MessageOut;
|
||||
import org.apache.cassandra.net.MessagingService;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
import org.apache.cassandra.utils.memory.AbstractAllocator;
|
||||
import org.apache.cassandra.utils.memory.HeapAllocator;
|
||||
import org.apache.cassandra.service.CacheService;
|
||||
import org.apache.cassandra.tracing.Tracing;
|
||||
import org.apache.cassandra.utils.*;
|
||||
|
|
@ -134,7 +137,7 @@ public class CounterMutation implements IMutation
|
|||
// Replaces all the CounterUpdateCell-s with updated regular CounterCell-s
|
||||
private ColumnFamily processModifications(ColumnFamily changesCF)
|
||||
{
|
||||
Allocator allocator = HeapAllocator.instance;
|
||||
AbstractAllocator allocator = HeapAllocator.instance;
|
||||
ColumnFamilyStore cfs = Keyspace.open(getKeyspaceName()).getColumnFamilyStore(changesCF.id());
|
||||
|
||||
ColumnFamily resultCF = changesCF.cloneMeShallow();
|
||||
|
|
|
|||
|
|
@ -20,9 +20,9 @@ package org.apache.cassandra.db;
|
|||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.apache.cassandra.db.composites.CellName;
|
||||
import org.apache.cassandra.db.composites.CellNameType;
|
||||
import org.apache.cassandra.utils.Allocator;
|
||||
import org.apache.cassandra.utils.memory.AbstractAllocator;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
import org.apache.cassandra.db.composites.CellNameType;
|
||||
|
||||
/**
|
||||
* A counter update while it hasn't been applied yet by the leader replica.
|
||||
|
|
@ -56,7 +56,7 @@ public class CounterUpdateCell extends Cell
|
|||
}
|
||||
|
||||
@Override
|
||||
public Cell reconcile(Cell cell, Allocator allocator)
|
||||
public Cell reconcile(Cell cell, AbstractAllocator allocator)
|
||||
{
|
||||
// The only time this could happen is if a batchAdd ships two
|
||||
// increment for the same cell. Hence we simply sums the delta.
|
||||
|
|
@ -78,7 +78,7 @@ public class CounterUpdateCell extends Cell
|
|||
}
|
||||
|
||||
@Override
|
||||
public Cell localCopy(ColumnFamilyStore cfs, Allocator allocator)
|
||||
public Cell localCopy(ColumnFamilyStore cfs, AbstractAllocator allocator)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,9 @@ import java.util.concurrent.atomic.AtomicReference;
|
|||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.*;
|
||||
|
||||
import org.apache.cassandra.utils.concurrent.OpOrder;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
|
@ -51,23 +54,22 @@ public class DataTracker
|
|||
this.init();
|
||||
}
|
||||
|
||||
public Memtable getMemtable()
|
||||
// get the Memtable that the ordered writeOp should be directed to
|
||||
public Memtable getMemtableFor(OpOrder.Group opGroup)
|
||||
{
|
||||
return view.get().memtable;
|
||||
}
|
||||
// since any new memtables appended to the list after we fetch it will be for operations started
|
||||
// after us, we can safely assume that we will always find the memtable that 'accepts' us;
|
||||
// if the barrier for any memtable is set whilst we are reading the list, it must accept us.
|
||||
|
||||
public Set<Memtable> getMemtablesPendingFlush()
|
||||
{
|
||||
return view.get().memtablesPendingFlush;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the active memtable and all the memtables that are pending flush.
|
||||
*/
|
||||
public Iterable<Memtable> getAllMemtables()
|
||||
{
|
||||
View snapshot = view.get();
|
||||
return Iterables.concat(snapshot.memtablesPendingFlush, Collections.singleton(snapshot.memtable));
|
||||
// there may be multiple memtables in the list that would 'accept' us, however we only ever choose
|
||||
// the oldest such memtable, as accepts() only prevents us falling behind (i.e. ensures we don't
|
||||
// assign operations to a memtable that was retired/queued before we started)
|
||||
for (Memtable memtable : view.get().liveMemtables)
|
||||
{
|
||||
if (memtable.accepts(opGroup))
|
||||
return memtable;
|
||||
}
|
||||
throw new AssertionError(view.get().liveMemtables.toString());
|
||||
}
|
||||
|
||||
public Set<SSTableReader> getSSTables()
|
||||
|
|
@ -98,46 +100,41 @@ public class DataTracker
|
|||
}
|
||||
|
||||
/**
|
||||
* Switch the current memtable.
|
||||
* This atomically adds the current memtable to the memtables pending
|
||||
* flush and replace it with a fresh memtable.
|
||||
* Switch the current memtable. This atomically appends a new memtable to the end of the list of active memtables,
|
||||
* returning the previously last memtable. It leaves the previous Memtable in the list of live memtables until
|
||||
* discarding(memtable) is called. These two methods must be synchronized/paired, i.e. m = switchMemtable
|
||||
* must be followed by discarding(m), they cannot be interleaved.
|
||||
*
|
||||
* @return the previous current memtable (the one added to the pending
|
||||
* flush)
|
||||
* @return the previously active memtable
|
||||
*/
|
||||
public Memtable switchMemtable()
|
||||
public Memtable switchMemtable(boolean truncating)
|
||||
{
|
||||
// atomically change the current memtable
|
||||
Memtable newMemtable = new Memtable(cfstore);
|
||||
Memtable toFlushMemtable;
|
||||
View currentView, newView;
|
||||
do
|
||||
{
|
||||
currentView = view.get();
|
||||
toFlushMemtable = currentView.memtable;
|
||||
toFlushMemtable = currentView.getCurrentMemtable();
|
||||
newView = currentView.switchMemtable(newMemtable);
|
||||
}
|
||||
while (!view.compareAndSet(currentView, newView));
|
||||
|
||||
if (truncating)
|
||||
notifyRenewed(newMemtable);
|
||||
|
||||
return toFlushMemtable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renew the current memtable without putting the old one for a flush.
|
||||
* Used when we flush but a memtable is clean (in which case we must
|
||||
* change it because it was frozen).
|
||||
*/
|
||||
public void renewMemtable()
|
||||
public void markFlushing(Memtable memtable)
|
||||
{
|
||||
Memtable newMemtable = new Memtable(cfstore);
|
||||
View currentView, newView;
|
||||
do
|
||||
{
|
||||
currentView = view.get();
|
||||
newView = currentView.renewMemtable(newMemtable);
|
||||
newView = currentView.markFlushing(memtable);
|
||||
}
|
||||
while (!view.compareAndSet(currentView, newView));
|
||||
notifyRenewed(currentView.memtable);
|
||||
}
|
||||
|
||||
public void replaceFlushed(Memtable memtable, SSTableReader sstable)
|
||||
|
|
@ -320,11 +317,12 @@ public class DataTracker
|
|||
/** (Re)initializes the tracker, purging all references. */
|
||||
void init()
|
||||
{
|
||||
view.set(new View(new Memtable(cfstore),
|
||||
Collections.<Memtable>emptySet(),
|
||||
Collections.<SSTableReader>emptySet(),
|
||||
Collections.<SSTableReader>emptySet(),
|
||||
SSTableIntervalTree.empty()));
|
||||
view.set(new View(
|
||||
ImmutableList.of(new Memtable(cfstore)),
|
||||
ImmutableList.<Memtable>of(),
|
||||
Collections.<SSTableReader>emptySet(),
|
||||
Collections.<SSTableReader>emptySet(),
|
||||
SSTableIntervalTree.empty()));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -533,66 +531,128 @@ public class DataTracker
|
|||
* flush, the sstables for a column family, and the sstables that are active
|
||||
* in compaction (a subset of the sstables).
|
||||
*/
|
||||
static class View
|
||||
public static class View
|
||||
{
|
||||
public final Memtable memtable;
|
||||
public final Set<Memtable> memtablesPendingFlush;
|
||||
/**
|
||||
* ordinarily a list of size 1, but when preparing to flush will contain both the memtable we will flush
|
||||
* and the new replacement memtable, until all outstanding write operations on the old table complete.
|
||||
* The last item in the list is always the "current" memtable.
|
||||
*/
|
||||
private final List<Memtable> liveMemtables;
|
||||
/**
|
||||
* contains all memtables that are no longer referenced for writing and are queued for / in the process of being
|
||||
* flushed. In chronologically ascending order.
|
||||
*/
|
||||
private final List<Memtable> flushingMemtables;
|
||||
public final Set<SSTableReader> compacting;
|
||||
public final Set<SSTableReader> sstables;
|
||||
public final SSTableIntervalTree intervalTree;
|
||||
|
||||
View(Memtable memtable, Set<Memtable> pendingFlush, Set<SSTableReader> sstables, Set<SSTableReader> compacting, SSTableIntervalTree intervalTree)
|
||||
View(List<Memtable> liveMemtables, List<Memtable> flushingMemtables, Set<SSTableReader> sstables, Set<SSTableReader> compacting, SSTableIntervalTree intervalTree)
|
||||
{
|
||||
this.memtable = memtable;
|
||||
this.memtablesPendingFlush = pendingFlush;
|
||||
this.liveMemtables = liveMemtables;
|
||||
this.flushingMemtables = flushingMemtables;
|
||||
this.sstables = sstables;
|
||||
this.compacting = compacting;
|
||||
this.intervalTree = intervalTree;
|
||||
}
|
||||
|
||||
public Memtable getOldestMemtable()
|
||||
{
|
||||
if (!flushingMemtables.isEmpty())
|
||||
return flushingMemtables.get(0);
|
||||
return liveMemtables.get(0);
|
||||
}
|
||||
|
||||
public Memtable getCurrentMemtable()
|
||||
{
|
||||
return liveMemtables.get(liveMemtables.size() - 1);
|
||||
}
|
||||
|
||||
public Iterable<Memtable> getMemtablesPendingFlush()
|
||||
{
|
||||
if (liveMemtables.size() == 1)
|
||||
return flushingMemtables;
|
||||
return Iterables.concat(liveMemtables.subList(0, 1), flushingMemtables);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the active memtable and all the memtables that are pending flush.
|
||||
*/
|
||||
public Iterable<Memtable> getAllMemtables()
|
||||
{
|
||||
return Iterables.concat(flushingMemtables, liveMemtables);
|
||||
}
|
||||
|
||||
public Sets.SetView<SSTableReader> nonCompactingSStables()
|
||||
{
|
||||
return Sets.difference(ImmutableSet.copyOf(sstables), compacting);
|
||||
}
|
||||
|
||||
public View switchMemtable(Memtable newMemtable)
|
||||
View switchMemtable(Memtable newMemtable)
|
||||
{
|
||||
Set<Memtable> newPending = ImmutableSet.<Memtable>builder().addAll(memtablesPendingFlush).add(memtable).build();
|
||||
return new View(newMemtable, newPending, sstables, compacting, intervalTree);
|
||||
List<Memtable> newLiveMemtables = ImmutableList.<Memtable>builder().addAll(liveMemtables).add(newMemtable).build();
|
||||
return new View(newLiveMemtables, flushingMemtables, sstables, compacting, intervalTree);
|
||||
}
|
||||
|
||||
public View renewMemtable(Memtable newMemtable)
|
||||
View markFlushing(Memtable toFlushMemtable)
|
||||
{
|
||||
return new View(newMemtable, memtablesPendingFlush, sstables, compacting, intervalTree);
|
||||
List<Memtable> live = liveMemtables, flushing = flushingMemtables;
|
||||
|
||||
// since we can have multiple flushes queued, we may occasionally race and start a flush out of order,
|
||||
// so must locate it in the list to remove, rather than just removing from the beginning
|
||||
int i = live.indexOf(toFlushMemtable);
|
||||
assert i < live.size() - 1;
|
||||
List<Memtable> newLive = ImmutableList.<Memtable>builder()
|
||||
.addAll(live.subList(0, i))
|
||||
.addAll(live.subList(i + 1, live.size()))
|
||||
.build();
|
||||
|
||||
// similarly, if we out-of-order markFlushing once, we may afterwards need to insert a memtable into the
|
||||
// flushing list in a position other than the end, though this will be rare
|
||||
i = flushing.size();
|
||||
while (i > 0 && flushing.get(i - 1).creationTime() > toFlushMemtable.creationTime())
|
||||
i--;
|
||||
List<Memtable> newFlushing = ImmutableList.<Memtable>builder()
|
||||
.addAll(flushing.subList(0, i))
|
||||
.add(toFlushMemtable)
|
||||
.addAll(flushing.subList(i, flushing.size()))
|
||||
.build();
|
||||
|
||||
return new View(newLive, newFlushing, sstables, compacting, intervalTree);
|
||||
}
|
||||
|
||||
public View replaceFlushed(Memtable flushedMemtable, SSTableReader newSSTable)
|
||||
View replaceFlushed(Memtable flushedMemtable, SSTableReader newSSTable)
|
||||
{
|
||||
Set<Memtable> newPending = ImmutableSet.copyOf(Sets.difference(memtablesPendingFlush, Collections.singleton(flushedMemtable)));
|
||||
int index = flushingMemtables.indexOf(flushedMemtable);
|
||||
List<Memtable> newQueuedMemtables = ImmutableList.<Memtable>builder()
|
||||
.addAll(flushingMemtables.subList(0, index))
|
||||
.addAll(flushingMemtables.subList(index + 1, flushingMemtables.size()))
|
||||
.build();
|
||||
Set<SSTableReader> newSSTables = newSSTable == null
|
||||
? sstables
|
||||
: newSSTables(newSSTable);
|
||||
? sstables
|
||||
: newSSTables(newSSTable);
|
||||
SSTableIntervalTree intervalTree = buildIntervalTree(newSSTables);
|
||||
return new View(memtable, newPending, newSSTables, compacting, intervalTree);
|
||||
return new View(liveMemtables, newQueuedMemtables, newSSTables, compacting, intervalTree);
|
||||
}
|
||||
|
||||
public View replace(Collection<SSTableReader> oldSSTables, Iterable<SSTableReader> replacements)
|
||||
View replace(Collection<SSTableReader> oldSSTables, Iterable<SSTableReader> replacements)
|
||||
{
|
||||
Set<SSTableReader> newSSTables = newSSTables(oldSSTables, replacements);
|
||||
SSTableIntervalTree intervalTree = buildIntervalTree(newSSTables);
|
||||
return new View(memtable, memtablesPendingFlush, newSSTables, compacting, intervalTree);
|
||||
return new View(liveMemtables, flushingMemtables, newSSTables, compacting, intervalTree);
|
||||
}
|
||||
|
||||
public View markCompacting(Collection<SSTableReader> tomark)
|
||||
View markCompacting(Collection<SSTableReader> tomark)
|
||||
{
|
||||
Set<SSTableReader> compactingNew = ImmutableSet.<SSTableReader>builder().addAll(compacting).addAll(tomark).build();
|
||||
return new View(memtable, memtablesPendingFlush, sstables, compactingNew, intervalTree);
|
||||
return new View(liveMemtables, flushingMemtables, sstables, compactingNew, intervalTree);
|
||||
}
|
||||
|
||||
public View unmarkCompacting(Iterable<SSTableReader> tounmark)
|
||||
View unmarkCompacting(Iterable<SSTableReader> tounmark)
|
||||
{
|
||||
Set<SSTableReader> compactingNew = ImmutableSet.copyOf(Sets.difference(compacting, ImmutableSet.copyOf(tounmark)));
|
||||
return new View(memtable, memtablesPendingFlush, sstables, compactingNew, intervalTree);
|
||||
return new View(liveMemtables, flushingMemtables, sstables, compactingNew, intervalTree);
|
||||
}
|
||||
|
||||
private Set<SSTableReader> newSSTables(SSTableReader newSSTable)
|
||||
|
|
@ -621,7 +681,7 @@ public class DataTracker
|
|||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("View(pending_count=%d, sstables=%s, compacting=%s)", memtablesPendingFlush.size(), sstables, compacting);
|
||||
return String.format("View(pending_count=%d, sstables=%s, compacting=%s)", liveMemtables.size() + flushingMemtables.size() - 1, sstables, compacting);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,8 +25,9 @@ import org.apache.cassandra.config.CFMetaData;
|
|||
import org.apache.cassandra.db.composites.CellName;
|
||||
import org.apache.cassandra.io.util.DataOutputBuffer;
|
||||
import org.apache.cassandra.serializers.MarshalException;
|
||||
import org.apache.cassandra.utils.Allocator;
|
||||
import org.apache.cassandra.utils.memory.AbstractAllocator;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
import org.apache.cassandra.utils.memory.HeapAllocator;
|
||||
|
||||
public class DeletedCell extends Cell
|
||||
{
|
||||
|
|
@ -89,7 +90,7 @@ public class DeletedCell extends Cell
|
|||
}
|
||||
|
||||
@Override
|
||||
public Cell reconcile(Cell cell, Allocator allocator)
|
||||
public Cell reconcile(Cell cell, AbstractAllocator allocator)
|
||||
{
|
||||
if (cell instanceof DeletedCell)
|
||||
return super.reconcile(cell, allocator);
|
||||
|
|
@ -97,7 +98,7 @@ public class DeletedCell extends Cell
|
|||
}
|
||||
|
||||
@Override
|
||||
public Cell localCopy(ColumnFamilyStore cfs, Allocator allocator)
|
||||
public Cell localCopy(ColumnFamilyStore cfs, AbstractAllocator allocator)
|
||||
{
|
||||
return new DeletedCell(name.copy(allocator), allocator.clone(value), timestamp);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,16 +25,20 @@ import java.util.*;
|
|||
import com.google.common.base.Objects;
|
||||
import com.google.common.collect.Iterators;
|
||||
|
||||
import org.apache.cassandra.cache.IMeasurableMemory;
|
||||
import org.apache.cassandra.db.composites.CType;
|
||||
import org.apache.cassandra.db.composites.Composite;
|
||||
import org.apache.cassandra.io.IVersionedSerializer;
|
||||
import org.apache.cassandra.utils.ObjectSizes;
|
||||
|
||||
/**
|
||||
* A combination of a top-level (or row) tombstone and range tombstones describing the deletions
|
||||
* within a {@link ColumnFamily} (or row).
|
||||
*/
|
||||
public class DeletionInfo
|
||||
public class DeletionInfo implements IMeasurableMemory
|
||||
{
|
||||
private static final long EMPTY_SIZE = ObjectSizes.measure(new DeletionInfo(0, 0));
|
||||
|
||||
/**
|
||||
* This represents a deletion of the entire row. We can't represent this within the RangeTombstoneList, so it's
|
||||
* kept separately. This also slightly optimizes the common case of a full row deletion.
|
||||
|
|
@ -317,6 +321,12 @@ public class DeletionInfo
|
|||
return Objects.hashCode(topLevel, ranges);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long unsharedHeapSize()
|
||||
{
|
||||
return EMPTY_SIZE + topLevel.unsharedHeapSize() + (ranges == null ? 0 : ranges.unsharedHeapSize());
|
||||
}
|
||||
|
||||
public static class Serializer implements IVersionedSerializer<DeletionInfo>
|
||||
{
|
||||
private final RangeTombstoneList.Serializer rtlSerializer;
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import java.io.IOException;
|
|||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Objects;
|
||||
|
||||
import org.apache.cassandra.cache.IMeasurableMemory;
|
||||
import org.apache.cassandra.io.ISerializer;
|
||||
import org.apache.cassandra.io.util.FileUtils;
|
||||
import org.apache.cassandra.utils.ObjectSizes;
|
||||
|
|
@ -31,8 +32,10 @@ import org.apache.cassandra.utils.ObjectSizes;
|
|||
/**
|
||||
* A top-level (row) tombstone.
|
||||
*/
|
||||
public class DeletionTime implements Comparable<DeletionTime>
|
||||
public class DeletionTime implements Comparable<DeletionTime>, IMeasurableMemory
|
||||
{
|
||||
private static final long EMPTY_SIZE = ObjectSizes.measure(new DeletionTime(0, 0));
|
||||
|
||||
/**
|
||||
* A special DeletionTime that signifies that there is no top-level (row) tombstone.
|
||||
*/
|
||||
|
|
@ -105,10 +108,9 @@ public class DeletionTime implements Comparable<DeletionTime>
|
|||
return atom.maxTimestamp() <= markedForDeleteAt;
|
||||
}
|
||||
|
||||
public long memorySize()
|
||||
public long unsharedHeapSize()
|
||||
{
|
||||
long fields = TypeSizes.NATIVE.sizeof(markedForDeleteAt) + TypeSizes.NATIVE.sizeof(localDeletionTime);
|
||||
return ObjectSizes.getFieldSize(fields);
|
||||
return EMPTY_SIZE;
|
||||
}
|
||||
|
||||
public static class Serializer implements ISerializer<DeletionTime>
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ import java.util.Iterator;
|
|||
import org.apache.cassandra.config.CFMetaData;
|
||||
import org.apache.cassandra.db.composites.CellName;
|
||||
import org.apache.cassandra.db.filter.ColumnSlice;
|
||||
import org.apache.cassandra.utils.Allocator;
|
||||
import org.apache.cassandra.utils.memory.AbstractAllocator;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.collect.Iterators;
|
||||
|
|
@ -63,12 +63,12 @@ public class EmptyColumns extends AbstractThreadUnsafeSortedColumns
|
|||
return factory;
|
||||
}
|
||||
|
||||
public void addColumn(Cell cell, Allocator allocator)
|
||||
public void addColumn(Cell cell, AbstractAllocator allocator)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public void addAll(ColumnFamily cm, Allocator allocator, Function<Cell, Cell> transformation)
|
||||
public void addAll(ColumnFamily cm, AbstractAllocator allocator, Function<Cell, Cell> transformation)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,8 @@ import org.apache.cassandra.db.composites.CellName;
|
|||
import org.apache.cassandra.db.composites.CellNameType;
|
||||
import org.apache.cassandra.io.util.DataOutputBuffer;
|
||||
import org.apache.cassandra.serializers.MarshalException;
|
||||
import org.apache.cassandra.utils.Allocator;
|
||||
import org.apache.cassandra.utils.memory.AbstractAllocator;
|
||||
import org.apache.cassandra.utils.memory.HeapAllocator;
|
||||
|
||||
/**
|
||||
* Alternative to Cell that have an expiring time.
|
||||
|
|
@ -132,7 +133,7 @@ public class ExpiringCell extends Cell
|
|||
}
|
||||
|
||||
@Override
|
||||
public Cell localCopy(ColumnFamilyStore cfs, Allocator allocator)
|
||||
public Cell localCopy(ColumnFamilyStore cfs, AbstractAllocator allocator)
|
||||
{
|
||||
return new ExpiringCell(name.copy(allocator), allocator.clone(value), timestamp, timeToLive, localExpirationTime);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,10 +23,12 @@ import java.util.*;
|
|||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
import org.apache.cassandra.utils.concurrent.OpOrder;
|
||||
import org.apache.cassandra.db.commitlog.ReplayPosition;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
|
@ -54,14 +56,6 @@ public class Keyspace
|
|||
|
||||
private static final Logger logger = LoggerFactory.getLogger(Keyspace.class);
|
||||
|
||||
/**
|
||||
* accesses to CFS.memtable should acquire this for thread safety.
|
||||
* CFS.maybeSwitchMemtable should aquire the writeLock; see that method for the full explanation.
|
||||
* <p/>
|
||||
* (Enabling fairness in the RRWL is observed to decrease throughput, so we leave it off.)
|
||||
*/
|
||||
public static final ReentrantReadWriteLock switchLock = new ReentrantReadWriteLock();
|
||||
|
||||
// It is possible to call Keyspace.open without a running daemon, so it makes sense to ensure
|
||||
// proper directories here as well as in CassandraDaemon.
|
||||
static
|
||||
|
|
@ -71,6 +65,7 @@ public class Keyspace
|
|||
}
|
||||
|
||||
public final KSMetaData metadata;
|
||||
public final OpOrder writeOrder = new OpOrder();
|
||||
|
||||
/* ColumnFamilyStore per column family */
|
||||
private final ConcurrentMap<UUID, ColumnFamilyStore> columnFamilyStores = new ConcurrentHashMap<UUID, ColumnFamilyStore>();
|
||||
|
|
@ -343,15 +338,21 @@ public class Keyspace
|
|||
*/
|
||||
public void apply(Mutation mutation, boolean writeCommitLog, boolean updateIndexes)
|
||||
{
|
||||
// write the mutation to the commitlog and memtables
|
||||
Tracing.trace("Acquiring switchLock read lock");
|
||||
switchLock.readLock().lock();
|
||||
final OpOrder.Group opGroup = writeOrder.start();
|
||||
try
|
||||
{
|
||||
// write the mutation to the commitlog and memtables
|
||||
final ReplayPosition replayPosition;
|
||||
if (writeCommitLog)
|
||||
{
|
||||
Tracing.trace("Appending to commitlog");
|
||||
CommitLog.instance.add(mutation);
|
||||
replayPosition = CommitLog.instance.add(mutation);
|
||||
}
|
||||
else
|
||||
{
|
||||
// we don't need the replayposition, but grab one anyway so that it stays stack allocated.
|
||||
// (the JVM will not stack allocate if the object may be null.)
|
||||
replayPosition = CommitLog.instance.getContext();
|
||||
}
|
||||
|
||||
DecoratedKey key = StorageService.getPartitioner().decorateKey(mutation.key());
|
||||
|
|
@ -365,12 +366,13 @@ public class Keyspace
|
|||
}
|
||||
|
||||
Tracing.trace("Adding to {} memtable", cf.metadata().cfName);
|
||||
cfs.apply(key, cf, updateIndexes ? cfs.indexManager.updaterFor(key, cf) : SecondaryIndexManager.nullUpdater);
|
||||
SecondaryIndexManager.Updater updater = updateIndexes ? cfs.indexManager.updaterFor(key, opGroup) : SecondaryIndexManager.nullUpdater;
|
||||
cfs.apply(key, cf, updater, opGroup, replayPosition);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
switchLock.readLock().unlock();
|
||||
opGroup.finishOne();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -389,11 +391,11 @@ public class Keyspace
|
|||
if (logger.isDebugEnabled())
|
||||
logger.debug("Indexing row {} ", cfs.metadata.getKeyValidator().getString(key.key));
|
||||
|
||||
Collection<SecondaryIndex> indexes = cfs.indexManager.getIndexesByNames(idxNames);
|
||||
|
||||
switchLock.readLock().lock();
|
||||
final OpOrder.Group opGroup = cfs.keyspace.writeOrder.start();
|
||||
try
|
||||
{
|
||||
Collection<SecondaryIndex> indexes = cfs.indexManager.getIndexesByNames(idxNames);
|
||||
|
||||
Iterator<ColumnFamily> pager = QueryPagers.pageRowLocally(cfs, key.key, DEFAULT_PAGE_SIZE);
|
||||
while (pager.hasNext())
|
||||
{
|
||||
|
|
@ -404,12 +406,12 @@ public class Keyspace
|
|||
if (cfs.indexManager.indexes(cell.name(), indexes))
|
||||
cf2.addColumn(cell);
|
||||
}
|
||||
cfs.indexManager.indexRow(key.key, cf2);
|
||||
cfs.indexManager.indexRow(key.key, cf2, opGroup);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
switchLock.readLock().unlock();
|
||||
opGroup.finishOne();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,75 +21,50 @@ import java.io.File;
|
|||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Throwables;
|
||||
import org.cliffc.high_scale_lib.NonBlockingHashSet;
|
||||
|
||||
import org.apache.cassandra.db.commitlog.CommitLog;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
import org.apache.cassandra.utils.concurrent.OpOrder;
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.db.index.SecondaryIndexManager;
|
||||
import org.apache.cassandra.dht.LongToken;
|
||||
import org.apache.cassandra.io.util.DiskAwareRunnable;
|
||||
|
||||
import org.apache.cassandra.utils.memory.AbstractAllocator;
|
||||
import org.apache.cassandra.utils.memory.ContextAllocator;
|
||||
import org.apache.cassandra.utils.ObjectSizes;
|
||||
import org.apache.cassandra.utils.memory.Pool;
|
||||
import org.apache.cassandra.utils.memory.PoolAllocator;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.apache.cassandra.concurrent.JMXEnabledThreadPoolExecutor;
|
||||
import org.apache.cassandra.concurrent.NamedThreadFactory;
|
||||
import org.apache.cassandra.concurrent.StageManager;
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.db.composites.CellNameType;
|
||||
import org.apache.cassandra.db.commitlog.ReplayPosition;
|
||||
import org.apache.cassandra.db.index.SecondaryIndexManager;
|
||||
import org.apache.cassandra.db.composites.CellNameType;
|
||||
import org.apache.cassandra.io.sstable.SSTableReader;
|
||||
import org.apache.cassandra.io.sstable.SSTableWriter;
|
||||
import org.apache.cassandra.io.sstable.metadata.MetadataCollector;
|
||||
import org.apache.cassandra.io.util.DiskAwareRunnable;
|
||||
import org.apache.cassandra.utils.Allocator;
|
||||
import org.github.jamm.MemoryMeter;
|
||||
|
||||
public class Memtable
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(Memtable.class);
|
||||
|
||||
/*
|
||||
* switchMemtable puts Memtable.getSortedContents on the writer executor. When the write is complete,
|
||||
* we turn the writer into an SSTableReader and add it to ssTables where it is available for reads.
|
||||
*
|
||||
* There are two other things that switchMemtable does.
|
||||
* First, it puts the Memtable into memtablesPendingFlush, where it stays until the flush is complete
|
||||
* and it's been added as an SSTableReader to ssTables_. Second, it adds an entry to commitLogUpdater
|
||||
* that waits for the flush to complete, then calls onMemtableFlush. This allows multiple flushes
|
||||
* to happen simultaneously on multicore systems, while still calling onMF in the correct order,
|
||||
* which is necessary for replay in case of a restart since CommitLog assumes that when onMF is
|
||||
* called, all data up to the given context has been persisted to SSTables.
|
||||
*/
|
||||
private static final ExecutorService flushWriter
|
||||
= new JMXEnabledThreadPoolExecutor(DatabaseDescriptor.getFlushWriters(),
|
||||
StageManager.KEEPALIVE,
|
||||
TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<Runnable>(DatabaseDescriptor.getFlushQueueSize()),
|
||||
new NamedThreadFactory("FlushWriter"),
|
||||
"internal");
|
||||
static final Pool memoryPool = DatabaseDescriptor.getMemtableAllocatorPool();
|
||||
private static final int ROW_OVERHEAD_HEAP_SIZE;
|
||||
|
||||
// size in memory can never be less than serialized size
|
||||
private static final double MIN_SANE_LIVE_RATIO = 1.0;
|
||||
// max liveratio seen w/ 1-byte columns on a 64-bit jvm was 19. If it gets higher than 64 something is probably broken.
|
||||
private static final double MAX_SANE_LIVE_RATIO = 64.0;
|
||||
|
||||
// We need to take steps to avoid retaining inactive membtables in memory, because counting is slow (can be
|
||||
// minutes, for a large memtable and a busy server). A strictly FIFO Memtable queue could keep memtables
|
||||
// alive waiting for metering after they're flushed and would otherwise be GC'd. Instead, the approach we take
|
||||
// is to enqueue the CFS instead of the memtable, and to meter whatever the active memtable is when the executor
|
||||
// starts to work on it. We use a Set to make sure we don't enqueue redundant tasks for the same CFS.
|
||||
private static final Set<ColumnFamilyStore> meteringInProgress = new NonBlockingHashSet<ColumnFamilyStore>();
|
||||
private static final ExecutorService meterExecutor = new JMXEnabledThreadPoolExecutor(1,
|
||||
Integer.MAX_VALUE,
|
||||
TimeUnit.MILLISECONDS,
|
||||
new LinkedBlockingQueue<Runnable>(),
|
||||
new NamedThreadFactory("MemoryMeter"),
|
||||
"internal");
|
||||
private final MemoryMeter meter;
|
||||
|
||||
volatile static ColumnFamilyStore activelyMeasuring;
|
||||
|
||||
private final AtomicLong currentSize = new AtomicLong(0);
|
||||
private final PoolAllocator allocator;
|
||||
private final AtomicLong liveDataSize = new AtomicLong(0);
|
||||
private final AtomicLong currentOperations = new AtomicLong(0);
|
||||
|
||||
// the write barrier for directing writes to this memtable during a switch
|
||||
private volatile OpOrder.Barrier writeBarrier;
|
||||
// the last ReplayPosition owned by this Memtable; all ReplayPositions lower are owned by this or an earlier Memtable
|
||||
private final AtomicReference<ReplayPosition> lastReplayPosition = new AtomicReference<>();
|
||||
// the "first" ReplayPosition owned by this Memtable; this is inaccurate, and only used as a convenience to prevent CLSM flushing wantonly
|
||||
private final ReplayPosition minReplayPosition = CommitLog.instance.getContext();
|
||||
|
||||
// We index the memtable by RowPosition only for the purpose of being able
|
||||
// to select key range using Token.KeyBound. However put() ensures that we
|
||||
// actually only store DecoratedKey.
|
||||
|
|
@ -98,16 +73,6 @@ public class Memtable
|
|||
private final long creationTime = System.currentTimeMillis();
|
||||
private final long creationNano = System.nanoTime();
|
||||
|
||||
private final Allocator allocator = DatabaseDescriptor.getMemtableAllocator();
|
||||
// We really only need one column by allocator but one by memtable is not a big waste and avoids needing allocators to know about CFS
|
||||
private final Function<Cell, Cell> localCopyFunction = new Function<Cell, Cell>()
|
||||
{
|
||||
public Cell apply(Cell c)
|
||||
{
|
||||
return c.localCopy(cfs, allocator);
|
||||
}
|
||||
};
|
||||
|
||||
// Record the comparator of the CFS at the creation of the memtable. This
|
||||
// is only used when a user update the CF comparator, to know if the
|
||||
// memtable was created with the new or old comparator.
|
||||
|
|
@ -116,31 +81,19 @@ public class Memtable
|
|||
public Memtable(ColumnFamilyStore cfs)
|
||||
{
|
||||
this.cfs = cfs;
|
||||
this.allocator = memoryPool.newAllocator(cfs.keyspace.writeOrder);
|
||||
this.initialComparator = cfs.metadata.comparator;
|
||||
this.cfs.scheduleFlush();
|
||||
|
||||
Callable<Set<Object>> provider = new Callable<Set<Object>>()
|
||||
{
|
||||
public Set<Object> call() throws Exception
|
||||
{
|
||||
// avoid counting this once for each row
|
||||
Set<Object> set = Collections.newSetFromMap(new IdentityHashMap<Object, Boolean>());
|
||||
set.add(Memtable.this.cfs.metadata);
|
||||
return set;
|
||||
}
|
||||
};
|
||||
meter = new MemoryMeter().omitSharedBufferOverhead().withTrackerProvider(provider);
|
||||
}
|
||||
|
||||
public long getLiveSize()
|
||||
public AbstractAllocator getAllocator()
|
||||
{
|
||||
long estimatedSize = (long) (currentSize.get() * cfs.liveRatio);
|
||||
return allocator;
|
||||
}
|
||||
|
||||
// liveRatio is just an estimate; we can get a lower bound directly from the allocator
|
||||
if (estimatedSize < allocator.getMinimumSize())
|
||||
return allocator.getMinimumSize();
|
||||
|
||||
return estimatedSize;
|
||||
public long getLiveDataSize()
|
||||
{
|
||||
return liveDataSize.get();
|
||||
}
|
||||
|
||||
public long getOperations()
|
||||
|
|
@ -148,54 +101,109 @@ public class Memtable
|
|||
return currentOperations.get();
|
||||
}
|
||||
|
||||
void setDiscarding(OpOrder.Barrier writeBarrier, ReplayPosition minLastReplayPosition)
|
||||
{
|
||||
assert this.writeBarrier == null;
|
||||
this.lastReplayPosition.set(minLastReplayPosition);
|
||||
this.writeBarrier = writeBarrier;
|
||||
allocator.setDiscarding();
|
||||
}
|
||||
|
||||
void setDiscarded()
|
||||
{
|
||||
allocator.setDiscarded();
|
||||
}
|
||||
|
||||
public boolean accepts(OpOrder.Group opGroup)
|
||||
{
|
||||
OpOrder.Barrier barrier = this.writeBarrier;
|
||||
return barrier == null || barrier.isAfter(opGroup);
|
||||
}
|
||||
|
||||
public boolean isLive()
|
||||
{
|
||||
return allocator.isLive();
|
||||
}
|
||||
|
||||
public boolean isClean()
|
||||
{
|
||||
return rows.isEmpty();
|
||||
}
|
||||
|
||||
public boolean isCleanAfter(ReplayPosition position)
|
||||
{
|
||||
return isClean() || (position != null && minReplayPosition.compareTo(position) >= 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Should only be called by ColumnFamilyStore.apply. NOT a public API.
|
||||
* (CFS handles locking to avoid submitting an op
|
||||
* to a flushing memtable. Any other way is unsafe.)
|
||||
*/
|
||||
void put(DecoratedKey key, ColumnFamily columnFamily, SecondaryIndexManager.Updater indexer)
|
||||
* @return true if this memtable is expired. Expiration time is determined by CF's memtable_flush_period_in_ms.
|
||||
*/
|
||||
public boolean isExpired()
|
||||
{
|
||||
resolve(key, columnFamily, indexer);
|
||||
int period = cfs.metadata.getMemtableFlushPeriod();
|
||||
return period > 0 && (System.nanoTime() - creationNano >= TimeUnit.MILLISECONDS.toNanos(period));
|
||||
}
|
||||
|
||||
public void updateLiveRatio() throws RuntimeException
|
||||
/**
|
||||
* Should only be called by ColumnFamilyStore.apply via Keyspace.apply, which supplies the appropriate
|
||||
* OpOrdering.
|
||||
*
|
||||
* replayPosition should only be null if this is a secondary index, in which case it is *expected* to be null
|
||||
*/
|
||||
void put(DecoratedKey key, ColumnFamily cf, SecondaryIndexManager.Updater indexer, OpOrder.Group opGroup, ReplayPosition replayPosition)
|
||||
{
|
||||
if (!MemoryMeter.isInitialized())
|
||||
if (replayPosition != null && writeBarrier != null)
|
||||
{
|
||||
// hack for openjdk. we log a warning about this in the startup script too.
|
||||
logger.error("MemoryMeter uninitialized (jamm not specified as java agent); assuming liveRatio of {}. "
|
||||
+ " Usually this means cassandra-env.sh disabled jamm because you are using a buggy JRE; "
|
||||
+ " upgrade to the Sun JRE instead", cfs.liveRatio);
|
||||
return;
|
||||
// if the writeBarrier is set, we want to maintain lastReplayPosition; this is an optimisation to avoid
|
||||
// casing it for every write, but still ensure it is correct when writeBarrier.await() completes.
|
||||
// we clone the replay position so that the object passed in does not "escape", permitting stack allocation
|
||||
replayPosition = replayPosition.clone();
|
||||
while (true)
|
||||
{
|
||||
ReplayPosition last = lastReplayPosition.get();
|
||||
if (last.compareTo(replayPosition) >= 0)
|
||||
break;
|
||||
if (lastReplayPosition.compareAndSet(last, replayPosition))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!meteringInProgress.add(cfs))
|
||||
{
|
||||
logger.debug("Metering already pending or active for {}; skipping liveRatio update", cfs);
|
||||
return;
|
||||
}
|
||||
|
||||
meterExecutor.submit(new MeteringRunnable(cfs));
|
||||
}
|
||||
|
||||
private void resolve(DecoratedKey key, ColumnFamily cf, SecondaryIndexManager.Updater indexer)
|
||||
{
|
||||
AtomicBTreeColumns previous = rows.get(key);
|
||||
|
||||
if (previous == null)
|
||||
{
|
||||
AtomicBTreeColumns empty = cf.cloneMeShallow(AtomicBTreeColumns.factory, false);
|
||||
final DecoratedKey cloneKey = new DecoratedKey(key.token, allocator.clone(key.key, opGroup));
|
||||
// We'll add the columns later. This avoids wasting works if we get beaten in the putIfAbsent
|
||||
previous = rows.putIfAbsent(new DecoratedKey(key.token, allocator.clone(key.key)), empty);
|
||||
previous = rows.putIfAbsent(cloneKey, empty);
|
||||
if (previous == null)
|
||||
{
|
||||
previous = empty;
|
||||
// allocate the row overhead after the fact; this saves over allocating and having to free after, but
|
||||
// means we can overshoot our declared limit.
|
||||
int overhead = (int) (cfs.partitioner.getHeapSizeOf(key.token) + ROW_OVERHEAD_HEAP_SIZE);
|
||||
allocator.allocate(overhead, opGroup);
|
||||
}
|
||||
else
|
||||
{
|
||||
allocator.free(cloneKey.key);
|
||||
}
|
||||
}
|
||||
|
||||
long sizeDelta = previous.addAllWithSizeDelta(cf, allocator, localCopyFunction, indexer);
|
||||
currentSize.addAndGet(sizeDelta);
|
||||
ContextAllocator contextAllocator = allocator.wrap(opGroup, cfs);
|
||||
AtomicBTreeColumns.Delta delta = previous.addAllWithSizeDelta(cf, contextAllocator, contextAllocator, indexer, new AtomicBTreeColumns.Delta());
|
||||
liveDataSize.addAndGet(delta.dataSize());
|
||||
currentOperations.addAndGet((cf.getColumnCount() == 0)
|
||||
? cf.isMarkedForDelete() ? 1 : 0
|
||||
: cf.getColumnCount());
|
||||
|
||||
// allocate or free the delta in column overhead after the fact
|
||||
for (Cell cell : delta.reclaimed())
|
||||
{
|
||||
cell.name.free(allocator);
|
||||
allocator.free(cell.value);
|
||||
}
|
||||
allocator.allocate((int) delta.excessHeapSize(), opGroup);
|
||||
}
|
||||
|
||||
// for debugging
|
||||
|
|
@ -211,15 +219,15 @@ public class Memtable
|
|||
return builder.toString();
|
||||
}
|
||||
|
||||
public void flushAndSignal(final CountDownLatch latch, final Future<ReplayPosition> context)
|
||||
public FlushRunnable flushRunnable()
|
||||
{
|
||||
flushWriter.execute(new FlushRunnable(latch, context));
|
||||
return new FlushRunnable(lastReplayPosition.get());
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
return String.format("Memtable-%s@%s(%s/%s serialized/live bytes, %s ops)",
|
||||
cfs.name, hashCode(), currentSize, getLiveSize(), currentOperations);
|
||||
return String.format("Memtable-%s@%s(%s serialized bytes, %s ops, %.0f%% of heap limit)",
|
||||
cfs.name, hashCode(), liveDataSize, currentOperations, 100 * allocator.ownershipRatio());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -254,26 +262,12 @@ public class Memtable
|
|||
public void remove()
|
||||
{
|
||||
iter.remove();
|
||||
currentSize.addAndGet(-currentEntry.getValue().dataSize());
|
||||
liveDataSize.addAndGet(-currentEntry.getValue().dataSize());
|
||||
currentEntry = null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public boolean isClean()
|
||||
{
|
||||
return rows.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if this memtable is expired. Expiration time is determined by CF's memtable_flush_period_in_ms.
|
||||
*/
|
||||
public boolean isExpired()
|
||||
{
|
||||
int period = cfs.metadata.getMemtableFlushPeriod();
|
||||
return period > 0 && (System.nanoTime() - creationNano >= TimeUnit.MILLISECONDS.toNanos(period));
|
||||
}
|
||||
|
||||
public ColumnFamily getColumnFamily(DecoratedKey key)
|
||||
{
|
||||
return rows.get(key);
|
||||
|
|
@ -284,15 +278,18 @@ public class Memtable
|
|||
return creationTime;
|
||||
}
|
||||
|
||||
public ReplayPosition getLastReplayPosition()
|
||||
{
|
||||
return lastReplayPosition.get();
|
||||
}
|
||||
|
||||
class FlushRunnable extends DiskAwareRunnable
|
||||
{
|
||||
private final CountDownLatch latch;
|
||||
private final Future<ReplayPosition> context;
|
||||
private final ReplayPosition context;
|
||||
private final long estimatedSize;
|
||||
|
||||
FlushRunnable(CountDownLatch latch, Future<ReplayPosition> context)
|
||||
FlushRunnable(ReplayPosition context)
|
||||
{
|
||||
this.latch = latch;
|
||||
this.context = context;
|
||||
|
||||
long keySize = 0;
|
||||
|
|
@ -304,7 +301,7 @@ public class Memtable
|
|||
}
|
||||
estimatedSize = (long) ((keySize // index entries
|
||||
+ keySize // keys in data file
|
||||
+ currentSize.get()) // data
|
||||
+ liveDataSize.get()) // data
|
||||
* 1.2); // bloom filter and row index overhead
|
||||
}
|
||||
|
||||
|
|
@ -319,7 +316,6 @@ public class Memtable
|
|||
|
||||
SSTableReader sstable = writeSortedContents(context, sstableDirectory);
|
||||
cfs.replaceFlushed(Memtable.this, sstable);
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
protected Directories getDirectories()
|
||||
|
|
@ -327,7 +323,7 @@ public class Memtable
|
|||
return cfs.directories;
|
||||
}
|
||||
|
||||
private SSTableReader writeSortedContents(Future<ReplayPosition> context, File sstableDirectory)
|
||||
private SSTableReader writeSortedContents(ReplayPosition context, File sstableDirectory)
|
||||
throws ExecutionException, InterruptedException
|
||||
{
|
||||
logger.info("Writing {}", Memtable.this.toString());
|
||||
|
|
@ -361,15 +357,16 @@ public class Memtable
|
|||
{
|
||||
ssTable = writer.closeAndOpenReader();
|
||||
logger.info(String.format("Completed flushing %s (%d bytes) for commitlog position %s",
|
||||
ssTable.getFilename(), new File(ssTable.getFilename()).length(), context.get()));
|
||||
ssTable.getFilename(), new File(ssTable.getFilename()).length(), context));
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.abort();
|
||||
ssTable = null;
|
||||
logger.info("Completed flushing; nothing needed to be retained. Commitlog position was {}",
|
||||
context.get());
|
||||
context);
|
||||
}
|
||||
|
||||
return ssTable;
|
||||
}
|
||||
catch (Throwable e)
|
||||
|
|
@ -381,7 +378,7 @@ public class Memtable
|
|||
|
||||
public SSTableWriter createFlushWriter(String filename) throws ExecutionException, InterruptedException
|
||||
{
|
||||
MetadataCollector sstableMetadataCollector = new MetadataCollector(cfs.metadata.comparator).replayPosition(context.get());
|
||||
MetadataCollector sstableMetadataCollector = new MetadataCollector(cfs.metadata.comparator).replayPosition(context);
|
||||
return new SSTableWriter(filename,
|
||||
rows.size(),
|
||||
cfs.metadata,
|
||||
|
|
@ -390,62 +387,19 @@ public class Memtable
|
|||
}
|
||||
}
|
||||
|
||||
private static class MeteringRunnable implements Runnable
|
||||
static
|
||||
{
|
||||
// we might need to wait in the meter queue for a while. measure whichever memtable is active at that point,
|
||||
// rather than keeping the original memtable referenced (and thus un-freeable) until this runs.
|
||||
private final ColumnFamilyStore cfs;
|
||||
|
||||
public MeteringRunnable(ColumnFamilyStore cfs)
|
||||
{
|
||||
this.cfs = cfs;
|
||||
}
|
||||
|
||||
public void run()
|
||||
{
|
||||
try
|
||||
{
|
||||
activelyMeasuring = cfs;
|
||||
Memtable memtable = cfs.getMemtableThreadSafe();
|
||||
|
||||
long start = System.nanoTime();
|
||||
// ConcurrentSkipListMap has cycles, so measureDeep will have to track a reference to EACH object it visits.
|
||||
// So to reduce the memory overhead of doing a measurement, we break it up to row-at-a-time.
|
||||
long deepSize = memtable.meter.measure(memtable.rows);
|
||||
int objects = 0;
|
||||
for (Map.Entry<RowPosition, AtomicBTreeColumns> entry : memtable.rows.entrySet())
|
||||
{
|
||||
deepSize += memtable.meter.measureDeep(entry.getKey()) + memtable.meter.measureDeep(entry.getValue());
|
||||
objects += entry.getValue().getColumnCount();
|
||||
}
|
||||
double newRatio = (double) deepSize / memtable.currentSize.get();
|
||||
|
||||
if (newRatio < MIN_SANE_LIVE_RATIO)
|
||||
{
|
||||
logger.debug("setting live ratio to minimum of {} instead of {}", MIN_SANE_LIVE_RATIO, newRatio);
|
||||
newRatio = MIN_SANE_LIVE_RATIO;
|
||||
}
|
||||
if (newRatio > MAX_SANE_LIVE_RATIO)
|
||||
{
|
||||
logger.warn("setting live ratio to maximum of {} instead of {}", MAX_SANE_LIVE_RATIO, newRatio);
|
||||
newRatio = MAX_SANE_LIVE_RATIO;
|
||||
}
|
||||
|
||||
// we want to be very conservative about our estimate, since the penalty for guessing low is OOM
|
||||
// death. thus, higher estimates are believed immediately; lower ones are averaged w/ the old
|
||||
if (newRatio > cfs.liveRatio)
|
||||
cfs.liveRatio = newRatio;
|
||||
else
|
||||
cfs.liveRatio = (cfs.liveRatio + newRatio) / 2.0;
|
||||
|
||||
logger.info("{} liveRatio is {} (just-counted was {}). calculation took {}ms for {} cells",
|
||||
cfs, cfs.liveRatio, newRatio, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start), objects);
|
||||
}
|
||||
finally
|
||||
{
|
||||
activelyMeasuring = null;
|
||||
meteringInProgress.remove(cfs);
|
||||
}
|
||||
}
|
||||
// calculate row overhead
|
||||
int rowOverhead;
|
||||
ConcurrentNavigableMap<RowPosition, Object> rows = new ConcurrentSkipListMap<>();
|
||||
final int count = 100000;
|
||||
final Object val = new Object();
|
||||
for (int i = 0 ; i < count ; i++)
|
||||
rows.put(new DecoratedKey(new LongToken((long) i), ByteBufferUtil.EMPTY_BYTE_BUFFER), val);
|
||||
double avgSize = ObjectSizes.measureDeep(rows) / (double) count;
|
||||
rowOverhead = (int) ((avgSize - Math.floor(avgSize)) < 0.05 ? Math.floor(avgSize) : Math.ceil(avgSize));
|
||||
rowOverhead -= ObjectSizes.measureDeep(new LongToken((long) 0));
|
||||
rowOverhead += AtomicBTreeColumns.HEAP_SIZE;
|
||||
ROW_OVERHEAD_HEAP_SIZE = rowOverhead;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,131 +0,0 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.cassandra.db;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
|
||||
public class MeteredFlusher implements Runnable
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(MeteredFlusher.class);
|
||||
|
||||
public void run()
|
||||
{
|
||||
long totalMemtableBytesAllowed = DatabaseDescriptor.getTotalMemtableSpaceInMB() * 1048576L;
|
||||
|
||||
// first, find how much memory non-active memtables are using
|
||||
long flushingBytes = Memtable.activelyMeasuring == null
|
||||
? 0
|
||||
: Memtable.activelyMeasuring.getMemtableThreadSafe().getLiveSize();
|
||||
flushingBytes += countFlushingBytes();
|
||||
if (flushingBytes > 0)
|
||||
logger.debug("Currently flushing {} bytes of {} max", flushingBytes, totalMemtableBytesAllowed);
|
||||
|
||||
// next, flush CFs using more than 1 / (maximum number of memtables it could have in the pipeline)
|
||||
// of the total size allotted. Then, flush other CFs in order of size if necessary.
|
||||
long liveBytes = 0;
|
||||
try
|
||||
{
|
||||
long totalMemtableBytesUnused = totalMemtableBytesAllowed - flushingBytes;
|
||||
for (ColumnFamilyStore cfs : ColumnFamilyStore.all())
|
||||
{
|
||||
long size = cfs.getTotalMemtableLiveSize();
|
||||
int maxInFlight = (int) Math.ceil((double) (1 // live memtable
|
||||
+ 1 // potentially a flushed memtable being counted by jamm
|
||||
+ DatabaseDescriptor.getFlushWriters()
|
||||
+ DatabaseDescriptor.getFlushQueueSize())
|
||||
/ (1 + cfs.indexManager.getIndexesBackedByCfs().size()));
|
||||
if (cfs.getCompactionStrategy().isAffectedByMeteredFlusher() && totalMemtableBytesUnused > 0 && size > totalMemtableBytesUnused / maxInFlight)
|
||||
{
|
||||
logger.info("flushing high-traffic column family {} (estimated {} bytes)", cfs, size);
|
||||
cfs.forceFlush();
|
||||
}
|
||||
else
|
||||
{
|
||||
liveBytes += size;
|
||||
}
|
||||
}
|
||||
|
||||
if (flushingBytes + liveBytes <= totalMemtableBytesAllowed)
|
||||
return;
|
||||
|
||||
logger.info("estimated {} live and {} flushing bytes used by all memtables", liveBytes, flushingBytes);
|
||||
|
||||
// sort memtables by size
|
||||
List<ColumnFamilyStore> sorted = new ArrayList<ColumnFamilyStore>();
|
||||
Iterables.addAll(sorted, ColumnFamilyStore.all());
|
||||
Collections.sort(sorted, new Comparator<ColumnFamilyStore>()
|
||||
{
|
||||
public int compare(ColumnFamilyStore o1, ColumnFamilyStore o2)
|
||||
{
|
||||
long size1 = o1.getTotalMemtableLiveSize();
|
||||
long size2 = o2.getTotalMemtableLiveSize();
|
||||
if (size1 < size2)
|
||||
return -1;
|
||||
if (size1 > size2)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
|
||||
// flush largest first until we get below our threshold.
|
||||
// although it looks like liveBytes + flushingBytes will stay a constant, it will not if flushes finish
|
||||
// while we loop, which is especially likely to happen if the flush queue fills up (so further forceFlush calls block)
|
||||
while (!sorted.isEmpty())
|
||||
{
|
||||
flushingBytes = countFlushingBytes();
|
||||
if (liveBytes + flushingBytes <= totalMemtableBytesAllowed)
|
||||
break;
|
||||
|
||||
ColumnFamilyStore cfs = sorted.remove(sorted.size() - 1);
|
||||
if (cfs.getCompactionStrategy().isAffectedByMeteredFlusher())
|
||||
{
|
||||
long size = cfs.getTotalMemtableLiveSize();
|
||||
if (size == 0)
|
||||
break;
|
||||
logger.info("flushing {} to free up {} bytes", cfs, size);
|
||||
liveBytes -= size;
|
||||
cfs.forceFlush();
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
logger.trace("memtable memory usage is {} bytes with {} live", liveBytes + flushingBytes, liveBytes);
|
||||
}
|
||||
}
|
||||
|
||||
private long countFlushingBytes()
|
||||
{
|
||||
long flushingBytes = 0;
|
||||
for (ColumnFamilyStore cfs : ColumnFamilyStore.all())
|
||||
{
|
||||
for (Memtable memtable : cfs.getMemtablesPendingFlush())
|
||||
flushingBytes += memtable.getLiveSize();
|
||||
}
|
||||
return flushingBytes;
|
||||
}
|
||||
}
|
||||
|
|
@ -26,11 +26,13 @@ import java.util.Iterator;
|
|||
|
||||
import com.google.common.collect.AbstractIterator;
|
||||
|
||||
import org.apache.cassandra.cache.IMeasurableMemory;
|
||||
import org.apache.cassandra.db.composites.CType;
|
||||
import org.apache.cassandra.db.composites.Composite;
|
||||
import org.apache.cassandra.io.IVersionedSerializer;
|
||||
import org.apache.cassandra.net.MessagingService;
|
||||
|
||||
import org.apache.cassandra.utils.ObjectSizes;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
|
@ -50,10 +52,12 @@ import org.slf4j.LoggerFactory;
|
|||
* The only use of the local deletion time is to know when a given tombstone can
|
||||
* be purged, which will be done by the purge() method.
|
||||
*/
|
||||
public class RangeTombstoneList implements Iterable<RangeTombstone>
|
||||
public class RangeTombstoneList implements Iterable<RangeTombstone>, IMeasurableMemory
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(RangeTombstoneList.class);
|
||||
|
||||
private static long EMPTY_SIZE = ObjectSizes.measure(new RangeTombstoneList(null, 0));
|
||||
|
||||
private final Comparator<Composite> comparator;
|
||||
|
||||
// Note: we don't want to use a List for the markedAts and delTimes to avoid boxing. We could
|
||||
|
|
@ -63,9 +67,10 @@ public class RangeTombstoneList implements Iterable<RangeTombstone>
|
|||
private long[] markedAts;
|
||||
private int[] delTimes;
|
||||
|
||||
private long boundaryHeapSize;
|
||||
private int size;
|
||||
|
||||
private RangeTombstoneList(Comparator<Composite> comparator, Composite[] starts, Composite[] ends, long[] markedAts, int[] delTimes, int size)
|
||||
private RangeTombstoneList(Comparator<Composite> comparator, Composite[] starts, Composite[] ends, long[] markedAts, int[] delTimes, long boundaryHeapSize, int size)
|
||||
{
|
||||
assert starts.length == ends.length && starts.length == markedAts.length && starts.length == delTimes.length;
|
||||
this.comparator = comparator;
|
||||
|
|
@ -74,11 +79,12 @@ public class RangeTombstoneList implements Iterable<RangeTombstone>
|
|||
this.markedAts = markedAts;
|
||||
this.delTimes = delTimes;
|
||||
this.size = size;
|
||||
this.boundaryHeapSize = boundaryHeapSize;
|
||||
}
|
||||
|
||||
public RangeTombstoneList(Comparator<Composite> comparator, int capacity)
|
||||
{
|
||||
this(comparator, new Composite[capacity], new Composite[capacity], new long[capacity], new int[capacity], 0);
|
||||
this(comparator, new Composite[capacity], new Composite[capacity], new long[capacity], new int[capacity], 0, 0);
|
||||
}
|
||||
|
||||
public boolean isEmpty()
|
||||
|
|
@ -103,7 +109,7 @@ public class RangeTombstoneList implements Iterable<RangeTombstone>
|
|||
Arrays.copyOf(ends, size),
|
||||
Arrays.copyOf(markedAts, size),
|
||||
Arrays.copyOf(delTimes, size),
|
||||
size);
|
||||
boundaryHeapSize, size);
|
||||
}
|
||||
|
||||
public void add(RangeTombstone tombstone)
|
||||
|
|
@ -138,6 +144,7 @@ public class RangeTombstoneList implements Iterable<RangeTombstone>
|
|||
int pos = Arrays.binarySearch(ends, 0, size, start, comparator);
|
||||
insertFrom((pos >= 0 ? pos+1 : -pos-1), start, end, markedAt, delTime);
|
||||
}
|
||||
boundaryHeapSize += start.unsharedHeapSize() + end.unsharedHeapSize();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -375,6 +382,7 @@ public class RangeTombstoneList implements Iterable<RangeTombstone>
|
|||
System.arraycopy(src.markedAts, 0, dst.markedAts, 0, src.size);
|
||||
System.arraycopy(src.delTimes, 0, dst.delTimes, 0, src.size);
|
||||
dst.size = src.size;
|
||||
dst.boundaryHeapSize = src.boundaryHeapSize;
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -572,14 +580,31 @@ public class RangeTombstoneList implements Iterable<RangeTombstone>
|
|||
System.arraycopy(ends, i, ends, i+1, size - i);
|
||||
System.arraycopy(markedAts, i, markedAts, i+1, size - i);
|
||||
System.arraycopy(delTimes, i, delTimes, i+1, size - i);
|
||||
// we set starts[i] to null to indicate the position is now empty, so that we update boundaryHeapSize
|
||||
// when we set it
|
||||
starts[i] = null;
|
||||
}
|
||||
|
||||
private void setInternal(int i, Composite start, Composite end, long markedAt, int delTime)
|
||||
{
|
||||
if (starts[i] != null)
|
||||
boundaryHeapSize -= starts[i].unsharedHeapSize() + ends[i].unsharedHeapSize();
|
||||
starts[i] = start;
|
||||
ends[i] = end;
|
||||
markedAts[i] = markedAt;
|
||||
delTimes[i] = delTime;
|
||||
boundaryHeapSize += start.unsharedHeapSize() + end.unsharedHeapSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long unsharedHeapSize()
|
||||
{
|
||||
return EMPTY_SIZE
|
||||
+ boundaryHeapSize
|
||||
+ ObjectSizes.sizeOfArray(starts)
|
||||
+ ObjectSizes.sizeOfArray(ends)
|
||||
+ ObjectSizes.sizeOfArray(markedAts)
|
||||
+ ObjectSizes.sizeOfArray(delTimes);
|
||||
}
|
||||
|
||||
public static class Serializer implements IVersionedSerializer<RangeTombstoneList>
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import java.io.DataInput;
|
|||
import java.io.DataOutput;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
|
|
@ -36,6 +37,8 @@ import org.apache.cassandra.utils.ObjectSizes;
|
|||
|
||||
public class RowIndexEntry implements IMeasurableMemory
|
||||
{
|
||||
private static final long EMPTY_SIZE = ObjectSizes.measure(new RowIndexEntry(0));
|
||||
|
||||
public final long position;
|
||||
|
||||
public RowIndexEntry(long position)
|
||||
|
|
@ -81,9 +84,9 @@ public class RowIndexEntry implements IMeasurableMemory
|
|||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
public long memorySize()
|
||||
public long unsharedHeapSize()
|
||||
{
|
||||
return ObjectSizes.getFieldSize(TypeSizes.NATIVE.sizeof(position));
|
||||
return EMPTY_SIZE;
|
||||
}
|
||||
|
||||
public static class Serializer
|
||||
|
|
@ -161,6 +164,9 @@ public class RowIndexEntry implements IMeasurableMemory
|
|||
{
|
||||
private final DeletionTime deletionTime;
|
||||
private final List<IndexHelper.IndexInfo> columnsIndex;
|
||||
private static final long BASE_SIZE =
|
||||
ObjectSizes.measure(new IndexedEntry(0, DeletionTime.LIVE, Arrays.<IndexHelper.IndexInfo>asList(null, null)))
|
||||
+ ObjectSizes.measure(new ArrayList<>(1));
|
||||
|
||||
private IndexedEntry(long position, DeletionTime deletionTime, List<IndexHelper.IndexInfo> columnsIndex)
|
||||
{
|
||||
|
|
@ -197,19 +203,16 @@ public class RowIndexEntry implements IMeasurableMemory
|
|||
}
|
||||
|
||||
@Override
|
||||
public long memorySize()
|
||||
public long unsharedHeapSize()
|
||||
{
|
||||
long entrySize = 0;
|
||||
for (IndexHelper.IndexInfo idx : columnsIndex)
|
||||
entrySize += idx.memorySize();
|
||||
entrySize += idx.excessHeapSize();
|
||||
|
||||
return ObjectSizes.getSuperClassFieldSize(TypeSizes.NATIVE.sizeof(position))
|
||||
+ ObjectSizes.getFieldSize(// deletionTime
|
||||
ObjectSizes.getReferenceSize() +
|
||||
// columnsIndex
|
||||
ObjectSizes.getReferenceSize())
|
||||
+ deletionTime.memorySize()
|
||||
+ ObjectSizes.getArraySize(columnsIndex.size(), ObjectSizes.getReferenceSize()) + entrySize + 4;
|
||||
return BASE_SIZE
|
||||
+ entrySize
|
||||
+ deletionTime.unsharedHeapSize()
|
||||
+ ObjectSizes.sizeOfReferenceArray(columnsIndex.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ import org.apache.cassandra.config.CFMetaData;
|
|||
import org.apache.cassandra.db.composites.CellName;
|
||||
import org.apache.cassandra.db.composites.CellNameType;
|
||||
import org.apache.cassandra.db.filter.ColumnSlice;
|
||||
import org.apache.cassandra.utils.Allocator;
|
||||
import org.apache.cassandra.utils.memory.AbstractAllocator;
|
||||
|
||||
public class TreeMapBackedSortedColumns extends AbstractThreadUnsafeSortedColumns
|
||||
{
|
||||
|
|
@ -80,7 +80,7 @@ public class TreeMapBackedSortedColumns extends AbstractThreadUnsafeSortedColumn
|
|||
* If we find an old cell that has the same name
|
||||
* the ask it to resolve itself else add the new cell
|
||||
*/
|
||||
public void addColumn(Cell cell, Allocator allocator)
|
||||
public void addColumn(Cell cell, AbstractAllocator allocator)
|
||||
{
|
||||
CellName name = cell.name();
|
||||
// this is a slightly unusual way to structure this; a more natural way is shown in ThreadSafeSortedColumns,
|
||||
|
|
@ -98,7 +98,7 @@ public class TreeMapBackedSortedColumns extends AbstractThreadUnsafeSortedColumn
|
|||
/**
|
||||
* We need to go through each column in the column container and resolve it before adding
|
||||
*/
|
||||
public void addAll(ColumnFamily cm, Allocator allocator, Function<Cell, Cell> transformation)
|
||||
public void addAll(ColumnFamily cm, AbstractAllocator allocator, Function<Cell, Cell> transformation)
|
||||
{
|
||||
delete(cm.deletionInfo());
|
||||
for (Cell cell : cm)
|
||||
|
|
@ -173,4 +173,5 @@ public class TreeMapBackedSortedColumns extends AbstractThreadUnsafeSortedColumn
|
|||
{
|
||||
return new ColumnSlice.NavigableMapIterator(map.descendingMap(), slices);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ import com.google.common.collect.Iterables;
|
|||
import org.apache.cassandra.config.CFMetaData;
|
||||
import org.apache.cassandra.db.composites.CellName;
|
||||
import org.apache.cassandra.db.filter.ColumnSlice;
|
||||
import org.apache.cassandra.utils.Allocator;
|
||||
import org.apache.cassandra.utils.memory.AbstractAllocator;
|
||||
|
||||
/**
|
||||
* A ColumnFamily that allows inserting in any order, even unsorted.
|
||||
|
|
@ -79,12 +79,12 @@ public class UnsortedColumns extends AbstractThreadUnsafeSortedColumns
|
|||
cells.clear();
|
||||
}
|
||||
|
||||
public void addColumn(Cell cell, Allocator allocator)
|
||||
public void addColumn(Cell cell, AbstractAllocator allocator)
|
||||
{
|
||||
cells.add(cell);
|
||||
}
|
||||
|
||||
public void addAll(ColumnFamily cm, Allocator allocator, Function<Cell, Cell> transformation)
|
||||
public void addAll(ColumnFamily cm, AbstractAllocator allocator, Function<Cell, Cell> transformation)
|
||||
{
|
||||
delete(cm.deletionInfo());
|
||||
for (Cell cell : cm)
|
||||
|
|
@ -141,4 +141,5 @@ public class UnsortedColumns extends AbstractThreadUnsafeSortedColumns
|
|||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
*/
|
||||
package org.apache.cassandra.db.commitlog;
|
||||
|
||||
import org.apache.cassandra.utils.WaitQueue;
|
||||
import org.apache.cassandra.utils.concurrent.WaitQueue;
|
||||
import org.slf4j.*;
|
||||
|
||||
import java.util.concurrent.Semaphore;
|
||||
|
|
@ -155,9 +155,11 @@ public abstract class AbstractCommitLogService
|
|||
/**
|
||||
* Sync immediately, but don't block for the sync to cmplete
|
||||
*/
|
||||
public void requestExtraSync()
|
||||
public WaitQueue.Signal requestExtraSync()
|
||||
{
|
||||
haveWork.release();
|
||||
WaitQueue.Signal signal = syncComplete.register();
|
||||
haveWork.release(1);
|
||||
return signal;
|
||||
}
|
||||
|
||||
public void shutdown()
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ public class CommitLog implements CommitLogMBean
|
|||
|
||||
public final CommitLogSegmentManager allocator;
|
||||
public final CommitLogArchiver archiver = new CommitLogArchiver();
|
||||
private final CommitLogMetrics metrics;
|
||||
final CommitLogMetrics metrics;
|
||||
final AbstractCommitLogService executor;
|
||||
|
||||
private CommitLog()
|
||||
|
|
@ -151,9 +151,9 @@ public class CommitLog implements CommitLogMBean
|
|||
* @return a Future representing a ReplayPosition such that when it is ready,
|
||||
* all Allocations created prior to the getContext call will be written to the log
|
||||
*/
|
||||
public Future<ReplayPosition> getContext()
|
||||
public ReplayPosition getContext()
|
||||
{
|
||||
return Futures.immediateFuture(allocator.allocatingFrom().getContext());
|
||||
return allocator.allocatingFrom().getContext();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -191,18 +191,26 @@ public class CommitLog implements CommitLogMBean
|
|||
*
|
||||
* @param mutation the Mutation to add to the log
|
||||
*/
|
||||
public void add(Mutation mutation)
|
||||
public ReplayPosition add(Mutation mutation)
|
||||
{
|
||||
Allocation alloc = add(mutation, new Allocation());
|
||||
return alloc.getReplayPosition();
|
||||
}
|
||||
|
||||
private Allocation add(Mutation mutation, Allocation alloc)
|
||||
{
|
||||
assert mutation != null;
|
||||
|
||||
long size = Mutation.serializer.serializedSize(mutation, MessagingService.current_version);
|
||||
|
||||
long totalSize = size + ENTRY_OVERHEAD_SIZE;
|
||||
if (totalSize > MAX_MUTATION_SIZE)
|
||||
{
|
||||
logger.warn("Skipping commitlog append of extremely large mutation ({} bytes)", totalSize);
|
||||
return;
|
||||
return alloc;
|
||||
}
|
||||
|
||||
Allocation alloc = allocator.allocate(mutation, (int) totalSize, new Allocation());
|
||||
allocator.allocate(mutation, (int) totalSize, alloc);
|
||||
try
|
||||
{
|
||||
PureJavaCrc32 checksum = new PureJavaCrc32();
|
||||
|
|
@ -227,6 +235,7 @@ public class CommitLog implements CommitLogMBean
|
|||
}
|
||||
|
||||
executor.finishWriteFor(alloc);
|
||||
return alloc;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -33,10 +33,8 @@ import java.util.UUID;
|
|||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReadWriteLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
||||
import org.apache.cassandra.utils.concurrent.OpOrder;
|
||||
|
||||
import org.cliffc.high_scale_lib.NonBlockingHashMap;
|
||||
import org.slf4j.Logger;
|
||||
|
|
@ -50,7 +48,7 @@ import org.apache.cassandra.db.Mutation;
|
|||
import org.apache.cassandra.io.FSWriteError;
|
||||
import org.apache.cassandra.io.util.FileUtils;
|
||||
import org.apache.cassandra.utils.PureJavaCrc32;
|
||||
import org.apache.cassandra.utils.WaitQueue;
|
||||
import org.apache.cassandra.utils.concurrent.WaitQueue;
|
||||
|
||||
/*
|
||||
* A single commit log file on disk. Manages creation of the file and writing mutations to disk,
|
||||
|
|
@ -70,8 +68,8 @@ public class CommitLogSegment
|
|||
// The commit log (chained) sync marker/header size in bytes (int: length + long: checksum [segmentId, position])
|
||||
static final int SYNC_MARKER_SIZE = 4 + 8;
|
||||
|
||||
// The current AppendLock object - i.e. the one all threads adding new log records should use to synchronise
|
||||
private final AtomicReference<AppendLock> appendLock = new AtomicReference<>(new AppendLock());
|
||||
// The OpOrder used to order appends wrt sync
|
||||
private final OpOrder appendOrder = new OpOrder();
|
||||
|
||||
private final AtomicInteger allocatePosition = new AtomicInteger();
|
||||
|
||||
|
|
@ -172,40 +170,29 @@ public class CommitLogSegment
|
|||
*/
|
||||
boolean allocate(Mutation mutation, int size, Allocation alloc)
|
||||
{
|
||||
final AppendLock appendLock = lockForAppend();
|
||||
final OpOrder.Group commandOrder = appendOrder.start();
|
||||
try
|
||||
{
|
||||
int position = allocate(size);
|
||||
if (position < 0)
|
||||
{
|
||||
appendLock.unlock();
|
||||
commandOrder.finishOne();
|
||||
return false;
|
||||
}
|
||||
alloc.buffer = (ByteBuffer) buffer.duplicate().position(position).limit(position + size);
|
||||
alloc.position = position;
|
||||
alloc.segment = this;
|
||||
alloc.appendLock = appendLock;
|
||||
alloc.appendOp = commandOrder;
|
||||
markDirty(mutation, position);
|
||||
return true;
|
||||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
appendLock.unlock();
|
||||
commandOrder.finishOne();
|
||||
throw t;
|
||||
}
|
||||
}
|
||||
|
||||
// obtain the current AppendLock and lock it for record appending
|
||||
private AppendLock lockForAppend()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
AppendLock appendLock = this.appendLock.get();
|
||||
if (appendLock.lock())
|
||||
return appendLock;
|
||||
}
|
||||
}
|
||||
|
||||
// allocate bytes in the segment, or return -1 if not enough space
|
||||
private int allocate(int size)
|
||||
{
|
||||
|
|
@ -221,20 +208,44 @@ public class CommitLogSegment
|
|||
}
|
||||
|
||||
// ensures no more of this segment is writeable, by allocating any unused section at the end and marking it discarded
|
||||
synchronized void discardUnusedTail()
|
||||
void discardUnusedTail()
|
||||
{
|
||||
if (discardedTailFrom > 0)
|
||||
return;
|
||||
while (true)
|
||||
// we guard this with the OpOrdering instead of synchronised due to potential dead-lock with CLSM.advanceAllocatingFrom()
|
||||
// this actually isn't strictly necessary, as currently all calls to discardUnusedTail occur within a block
|
||||
// already protected by this OpOrdering, but to prevent future potential mistakes, we duplicate the protection here
|
||||
// so that the contract between discardUnusedTail() and sync() is more explicit.
|
||||
OpOrder.Group group = appendOrder.start();
|
||||
try
|
||||
{
|
||||
int prev = allocatePosition.get();
|
||||
int next = buffer.capacity();
|
||||
if (allocatePosition.compareAndSet(prev, next))
|
||||
while (true)
|
||||
{
|
||||
discardedTailFrom = prev;
|
||||
return;
|
||||
int prev = allocatePosition.get();
|
||||
// we set allocatePosition past buffer.capacity() to make sure we always set discardedTailFrom
|
||||
int next = buffer.capacity() + 1;
|
||||
if (prev == next)
|
||||
return;
|
||||
if (allocatePosition.compareAndSet(prev, next))
|
||||
{
|
||||
discardedTailFrom = prev;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
group.finishOne();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for any appends or discardUnusedTail() operations started before this method was called
|
||||
*/
|
||||
private synchronized void waitForModifications()
|
||||
{
|
||||
// issue a barrier and wait for it
|
||||
OpOrder.Barrier barrier = appendOrder.newBarrier();
|
||||
barrier.issue();
|
||||
barrier.await();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -259,6 +270,9 @@ public class CommitLogSegment
|
|||
discardUnusedTail();
|
||||
close = true;
|
||||
|
||||
// wait for modifications guards both discardedTailFrom, and any outstanding appends
|
||||
waitForModifications();
|
||||
|
||||
if (discardedTailFrom < buffer.capacity() - SYNC_MARKER_SIZE)
|
||||
{
|
||||
// if there's room in the discard section to write an empty header, use that as the nextMarker
|
||||
|
|
@ -270,11 +284,12 @@ public class CommitLogSegment
|
|||
nextMarker = buffer.capacity();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
waitForModifications();
|
||||
}
|
||||
|
||||
// swap the append lock
|
||||
AppendLock curAppendLock = appendLock.get();
|
||||
appendLock.set(new AppendLock());
|
||||
curAppendLock.expireAndWaitForCompletion();
|
||||
assert nextMarker > lastSyncedOffset;
|
||||
|
||||
// write previous sync marker to point to next sync marker
|
||||
// we don't chain the crcs here to ensure this method is idempotent if it fails
|
||||
|
|
@ -311,9 +326,9 @@ public class CommitLogSegment
|
|||
}
|
||||
}
|
||||
|
||||
public boolean isFullySynced()
|
||||
public boolean isStillAllocating()
|
||||
{
|
||||
return lastSyncedOffset == buffer.capacity();
|
||||
return allocatePosition.get() < buffer.capacity();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -445,7 +460,7 @@ public class CommitLogSegment
|
|||
private void removeCleanFromDirty()
|
||||
{
|
||||
// if we're still allocating from this segment, don't touch anything since it can't be done thread-safely
|
||||
if (!isFullySynced())
|
||||
if (isStillAllocating())
|
||||
return;
|
||||
|
||||
Iterator<Map.Entry<UUID, AtomicInteger>> iter = cfClean.entrySet().iterator();
|
||||
|
|
@ -463,7 +478,6 @@ public class CommitLogSegment
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return a collection of dirty CFIDs for this segment file.
|
||||
*/
|
||||
|
|
@ -489,8 +503,9 @@ public class CommitLogSegment
|
|||
*/
|
||||
public boolean isUnused()
|
||||
{
|
||||
// if it's not fully synced, we assume we're still in use as the active allocatingFrom
|
||||
if (!isFullySynced())
|
||||
// if room to allocate, we're still in use as the active allocatingFrom,
|
||||
// so we don't want to race with updates to cfClean with removeCleanFromDirty
|
||||
if (isStillAllocating())
|
||||
return false;
|
||||
|
||||
removeCleanFromDirty();
|
||||
|
|
@ -536,64 +551,16 @@ public class CommitLogSegment
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A relatively simple class for synchronising flushes() with log message writers:
|
||||
* Log writers take the readLock prior to allocating themselves space in the segment;
|
||||
* once they complete writing the record they release the read lock. A call to sync()
|
||||
* will first check the position we have allocated space up until, then allocate a new AppendLock object,
|
||||
* take the writeLock of the previous AppendLock, and invalidate it for further log writes. All appends are
|
||||
* redirected to the new AppendLock so they do not block; only the sync() blocks waiting to obtain the writeLock.
|
||||
* Once it obtains the lock it is guaranteed that all writes up to the allocation position it checked at
|
||||
* the start have been completely written to.
|
||||
*/
|
||||
private static final class AppendLock
|
||||
{
|
||||
final ReadWriteLock syncLock = new ReentrantReadWriteLock();
|
||||
final Lock logLock = syncLock.readLock();
|
||||
// a map of Cfs with log records that have not been synced to disk, so cannot be marked clean yet
|
||||
|
||||
boolean expired;
|
||||
|
||||
// false if the lock could not be acquired for adding a log record;
|
||||
// a new AppendLock object will already be available, so fetch appendLock().get()
|
||||
// and retry
|
||||
boolean lock()
|
||||
{
|
||||
if (!logLock.tryLock())
|
||||
return false;
|
||||
if (expired)
|
||||
{
|
||||
logLock.unlock();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// release the lock so that a appendLock() may complete
|
||||
void unlock()
|
||||
{
|
||||
logLock.unlock();
|
||||
}
|
||||
|
||||
void expireAndWaitForCompletion()
|
||||
{
|
||||
// wait for log records to complete (take writeLock)
|
||||
syncLock.writeLock().lock();
|
||||
expired = true;
|
||||
// release lock immediately, though effectively a NOOP since we use tryLock() for log record appends
|
||||
syncLock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple class for tracking information about the portion of a segment that has been allocated to a log write.
|
||||
* The constructor leaves the fields uninitialized for population by CommitlogManager, so that it can be
|
||||
* stack-allocated by escape analysis in CommitLog.add.
|
||||
*/
|
||||
static final class Allocation
|
||||
static class Allocation
|
||||
{
|
||||
|
||||
private CommitLogSegment segment;
|
||||
private AppendLock appendLock;
|
||||
private OpOrder.Group appendOp;
|
||||
private int position;
|
||||
private ByteBuffer buffer;
|
||||
|
||||
|
|
@ -608,19 +575,30 @@ public class CommitLogSegment
|
|||
}
|
||||
|
||||
// markWritten() MUST be called once we are done with the segment or the CL will never flush
|
||||
// but must not be called more than once
|
||||
void markWritten()
|
||||
{
|
||||
appendLock.unlock();
|
||||
appendOp.finishOne();
|
||||
}
|
||||
|
||||
void awaitDiskSync()
|
||||
{
|
||||
while (segment.lastSyncedOffset < position)
|
||||
{
|
||||
WaitQueue.Signal signal = segment.syncComplete.register();
|
||||
WaitQueue.Signal signal = segment.syncComplete.register(CommitLog.instance.metrics.waitingOnCommit.time());
|
||||
if (segment.lastSyncedOffset < position)
|
||||
signal.awaitUninterruptibly();
|
||||
else
|
||||
signal.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
public ReplayPosition getReplayPosition()
|
||||
{
|
||||
// always allocate a ReplayPosition to let stack allocation do its magic. If we return null, we always
|
||||
// have to allocate an object on the stack
|
||||
return new ReplayPosition(segment == null ? -1 : segment.id, segment == null ? 0 : buffer.limit());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,8 @@ import java.util.concurrent.atomic.AtomicLong;
|
|||
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.util.concurrent.*;
|
||||
import org.apache.cassandra.service.paxos.Commit;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
|
@ -49,7 +51,7 @@ import org.apache.cassandra.net.MessagingService;
|
|||
import org.apache.cassandra.service.StorageService;
|
||||
import org.apache.cassandra.utils.FBUtilities;
|
||||
import org.apache.cassandra.utils.Pair;
|
||||
import org.apache.cassandra.utils.WaitQueue;
|
||||
import org.apache.cassandra.utils.concurrent.WaitQueue;
|
||||
import org.apache.cassandra.utils.WrappedRunnable;
|
||||
|
||||
import static org.apache.cassandra.db.commitlog.CommitLogSegment.Allocation;
|
||||
|
|
@ -135,7 +137,7 @@ public class CommitLogSegmentManager
|
|||
if (spaceToReclaim + unused >= 0)
|
||||
break;
|
||||
}
|
||||
flushDataFrom(segmentsToRecycle);
|
||||
flushDataFrom(segmentsToRecycle, false);
|
||||
}
|
||||
|
||||
try
|
||||
|
|
@ -239,26 +241,29 @@ public class CommitLogSegmentManager
|
|||
}
|
||||
|
||||
// no more segments, so register to receive a signal when not empty
|
||||
WaitQueue.Signal signal = hasAvailableSegments.register();
|
||||
WaitQueue.Signal signal = hasAvailableSegments.register(CommitLog.instance.metrics.waitingOnSegmentAllocation.time());
|
||||
|
||||
// trigger the management thread; this must occur after registering
|
||||
// the signal to ensure we are woken by any new segment creation
|
||||
wakeManager();
|
||||
|
||||
// check if the queue has already been added to before waiting on the signal, to catch modifications
|
||||
// that happened prior to registering the signal
|
||||
if (availableSegments.isEmpty())
|
||||
// that happened prior to registering the signal; *then* check to see if we've been beaten to making the change
|
||||
if (!availableSegments.isEmpty() || allocatingFrom != old)
|
||||
{
|
||||
// check to see if we've been beaten to it
|
||||
signal.cancel();
|
||||
// if we've been beaten, just stop immediately
|
||||
if (allocatingFrom != old)
|
||||
return;
|
||||
|
||||
// can only reach here if the queue hasn't been inserted into
|
||||
// before we registered the signal, as we only remove items from the queue
|
||||
// after updating allocatingFrom. Can safely block until we are signalled
|
||||
// by the allocator that new segments have been published
|
||||
signal.awaitUninterruptibly();
|
||||
// otherwise try again, as there should be an available segment
|
||||
continue;
|
||||
}
|
||||
|
||||
// can only reach here if the queue hasn't been inserted into
|
||||
// before we registered the signal, as we only remove items from the queue
|
||||
// after updating allocatingFrom. Can safely block until we are signalled
|
||||
// by the allocator that new segments have been published
|
||||
signal.awaitUninterruptibly();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -282,13 +287,12 @@ public class CommitLogSegmentManager
|
|||
*/
|
||||
void forceRecycleAll()
|
||||
{
|
||||
CommitLogSegment last = allocatingFrom;
|
||||
last.discardUnusedTail();
|
||||
List<CommitLogSegment> segmentsToRecycle = new ArrayList<>(activeSegments);
|
||||
CommitLogSegment last = segmentsToRecycle.get(segmentsToRecycle.size() - 1);
|
||||
advanceAllocatingFrom(last);
|
||||
|
||||
// flush and wait for all CFs that are dirty in segments up-to and including 'last'
|
||||
Future<?> future = flushDataFrom(segmentsToRecycle);
|
||||
Future<?> future = flushDataFrom(segmentsToRecycle, true);
|
||||
try
|
||||
{
|
||||
future.get();
|
||||
|
|
@ -443,10 +447,14 @@ public class CommitLogSegmentManager
|
|||
*
|
||||
* @return a Future that will finish when all the flushes are complete.
|
||||
*/
|
||||
private Future<?> flushDataFrom(Collection<CommitLogSegment> segments)
|
||||
private Future<?> flushDataFrom(List<CommitLogSegment> segments, boolean force)
|
||||
{
|
||||
if (segments.isEmpty())
|
||||
return Futures.immediateFuture(null);
|
||||
final ReplayPosition maxReplayPosition = segments.get(segments.size() - 1).getContext();
|
||||
|
||||
// a map of CfId -> forceFlush() to ensure we only queue one flush per cf
|
||||
final Map<UUID, Future<?>> flushes = new LinkedHashMap<>();
|
||||
final Map<UUID, ListenableFuture<?>> flushes = new LinkedHashMap<>();
|
||||
|
||||
for (CommitLogSegment segment : segments)
|
||||
{
|
||||
|
|
@ -464,29 +472,14 @@ public class CommitLogSegmentManager
|
|||
{
|
||||
String keyspace = pair.left;
|
||||
final ColumnFamilyStore cfs = Keyspace.open(keyspace).getColumnFamilyStore(dirtyCFId);
|
||||
// Push the flush out to another thread to avoid potential deadlock: Table.add
|
||||
// acquires switchlock, and could be blocking for the manager thread. So if the manager
|
||||
// thread itself tries to acquire switchlock (via flush -> switchMemtable) we'd have a problem.
|
||||
Runnable runnable = new Runnable()
|
||||
{
|
||||
public void run()
|
||||
{
|
||||
cfs.forceFlush();
|
||||
}
|
||||
};
|
||||
flushes.put(dirtyCFId, StorageService.optionalTasks.submit(runnable));
|
||||
// can safely call forceFlush here as we will only ever block (briefly) for other attempts to flush,
|
||||
// no deadlock possibility since switchLock removal
|
||||
flushes.put(dirtyCFId, force ? cfs.forceFlush() : cfs.forceFlush(maxReplayPosition));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new FutureTask<>(new Callable<Object>()
|
||||
{
|
||||
public Object call()
|
||||
{
|
||||
FBUtilities.waitOnFutures(flushes.values());
|
||||
return null;
|
||||
}
|
||||
});
|
||||
return Futures.allAsList(flushes.values());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
package org.apache.cassandra.db.commitlog;
|
||||
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.utils.WaitQueue;
|
||||
import org.apache.cassandra.utils.concurrent.WaitQueue;
|
||||
|
||||
class PeriodicCommitLogService extends AbstractCommitLogService
|
||||
{
|
||||
|
|
@ -39,9 +39,11 @@ class PeriodicCommitLogService extends AbstractCommitLogService
|
|||
pending.incrementAndGet();
|
||||
while (waitForSyncToCatchUp(started))
|
||||
{
|
||||
WaitQueue.Signal signal = syncComplete.register();
|
||||
WaitQueue.Signal signal = syncComplete.register(CommitLog.instance.metrics.waitingOnCommit.time());
|
||||
if (waitForSyncToCatchUp(started))
|
||||
signal.awaitUninterruptibly();
|
||||
else
|
||||
signal.cancel();
|
||||
}
|
||||
pending.decrementAndGet();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -117,6 +117,11 @@ public class ReplayPosition implements Comparable<ReplayPosition>
|
|||
')';
|
||||
}
|
||||
|
||||
public ReplayPosition clone()
|
||||
{
|
||||
return new ReplayPosition(segment, position);
|
||||
}
|
||||
|
||||
public static class ReplayPositionSerializer implements ISerializer<ReplayPosition>
|
||||
{
|
||||
public void serialize(ReplayPosition rp, DataOutput out) throws IOException
|
||||
|
|
|
|||
|
|
@ -30,6 +30,9 @@ import javax.management.openmbean.TabularData;
|
|||
import com.google.common.base.Throwables;
|
||||
import com.google.common.collect.*;
|
||||
import com.google.common.util.concurrent.RateLimiter;
|
||||
|
||||
import org.apache.cassandra.utils.concurrent.OpOrder;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
|
@ -700,14 +703,14 @@ public class CompactionManager implements CompactionManagerMBean
|
|||
if (indexedColumnsInRow != null && !indexedColumnsInRow.isEmpty())
|
||||
{
|
||||
// acquire memtable lock here because secondary index deletion may cause a race. See CASSANDRA-3712
|
||||
Keyspace.switchLock.readLock().lock();
|
||||
final OpOrder.Group opGroup = cfs.keyspace.writeOrder.start();
|
||||
try
|
||||
{
|
||||
cfs.indexManager.deleteFromIndexes(row.getKey(), indexedColumnsInRow);
|
||||
cfs.indexManager.deleteFromIndexes(row.getKey(), indexedColumnsInRow, opGroup);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Keyspace.switchLock.readLock().unlock();
|
||||
opGroup.finishOne();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -22,8 +22,8 @@ import java.io.IOException;
|
|||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.google.common.base.Throwables;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.common.base.*;
|
||||
import com.google.common.collect.*;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
|
@ -107,8 +107,14 @@ public class CompactionTask extends AbstractCompactionTask
|
|||
cfs.snapshotWithoutFlush(System.currentTimeMillis() + "-compact-" + cfs.name);
|
||||
|
||||
// sanity check: all sstables must belong to the same cfs
|
||||
for (SSTableReader sstable : toCompact)
|
||||
assert sstable.descriptor.cfname.equals(cfs.name);
|
||||
assert !Iterables.any(toCompact, new Predicate<SSTableReader>()
|
||||
{
|
||||
@Override
|
||||
public boolean apply(SSTableReader sstable)
|
||||
{
|
||||
return !sstable.descriptor.cfname.equals(cfs.name);
|
||||
}
|
||||
});
|
||||
|
||||
UUID taskId = SystemKeyspace.startCompaction(cfs, toCompact);
|
||||
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ public class LazilyCompactedRow extends AbstractCompactedRow
|
|||
super(rows.get(0).getKey());
|
||||
this.rows = rows;
|
||||
this.controller = controller;
|
||||
indexer = controller.cfs.indexManager.updaterFor(key);
|
||||
indexer = controller.cfs.indexManager.gcUpdaterFor(key);
|
||||
|
||||
// Combine top-level tombstones, keeping the one with the highest markedForDeleteAt timestamp. This may be
|
||||
// purged (depending on gcBefore), but we need to remember it to properly delete columns during the merge
|
||||
|
|
@ -96,7 +96,7 @@ public class LazilyCompactedRow extends AbstractCompactedRow
|
|||
// are shadowed by a row or range tombstone; removeDeletedColumnsOnly(cf, Integer.MIN_VALUE) will accomplish this
|
||||
// without purging tombstones.
|
||||
int overriddenGCBefore = shouldPurge ? controller.gcBefore : Integer.MIN_VALUE;
|
||||
ColumnFamilyStore.removeDeletedColumnsOnly(cf, overriddenGCBefore, controller.cfs.indexManager.updaterFor(key));
|
||||
ColumnFamilyStore.removeDeletedColumnsOnly(cf, overriddenGCBefore, controller.cfs.indexManager.gcUpdaterFor(key));
|
||||
}
|
||||
|
||||
public RowIndexEntry write(long currentPosition, DataOutput out) throws IOException
|
||||
|
|
|
|||
|
|
@ -19,12 +19,14 @@ package org.apache.cassandra.db.composites;
|
|||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.apache.cassandra.db.TypeSizes;
|
||||
import org.apache.cassandra.utils.Allocator;
|
||||
import org.apache.cassandra.utils.memory.AbstractAllocator;
|
||||
import org.apache.cassandra.utils.ObjectSizes;
|
||||
import org.apache.cassandra.utils.memory.PoolAllocator;
|
||||
|
||||
public class BoundedComposite extends AbstractComposite
|
||||
{
|
||||
private static final long EMPTY_SIZE = ObjectSizes.measure(new BoundedComposite(null, false));
|
||||
|
||||
private final Composite wrapped;
|
||||
private final boolean isStart;
|
||||
|
||||
|
|
@ -82,14 +84,19 @@ public class BoundedComposite extends AbstractComposite
|
|||
return bb;
|
||||
}
|
||||
|
||||
public long memorySize()
|
||||
public long unsharedHeapSize()
|
||||
{
|
||||
return ObjectSizes.getFieldSize(ObjectSizes.getReferenceSize() + TypeSizes.NATIVE.sizeof(isStart))
|
||||
+ wrapped.memorySize();
|
||||
return EMPTY_SIZE + wrapped.unsharedHeapSize();
|
||||
}
|
||||
|
||||
public Composite copy(Allocator allocator)
|
||||
public Composite copy(AbstractAllocator allocator)
|
||||
{
|
||||
return new BoundedComposite(wrapped.copy(allocator), isStart);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void free(PoolAllocator<?> allocator)
|
||||
{
|
||||
wrapped.free(allocator);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ package org.apache.cassandra.db.composites;
|
|||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.apache.cassandra.cql3.ColumnIdentifier;
|
||||
import org.apache.cassandra.utils.Allocator;
|
||||
import org.apache.cassandra.utils.memory.AbstractAllocator;
|
||||
|
||||
/**
|
||||
* A CellName is a Composite, but for which, for the sake of CQL3, we
|
||||
|
|
@ -70,5 +70,7 @@ public interface CellName extends Composite
|
|||
// If cellnames were sharing some prefix components, this will break it, so
|
||||
// we might want to try to do better.
|
||||
@Override
|
||||
public CellName copy(Allocator allocator);
|
||||
public CellName copy(AbstractAllocator allocator);
|
||||
|
||||
public long excessHeapSizeExcludingData();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,8 @@ import java.nio.ByteBuffer;
|
|||
|
||||
import org.apache.cassandra.cache.IMeasurableMemory;
|
||||
import org.apache.cassandra.db.filter.ColumnSlice;
|
||||
import org.apache.cassandra.utils.Allocator;
|
||||
import org.apache.cassandra.utils.memory.AbstractAllocator;
|
||||
import org.apache.cassandra.utils.memory.PoolAllocator;
|
||||
|
||||
/**
|
||||
* A composite value.
|
||||
|
|
@ -72,5 +73,6 @@ public interface Composite extends IMeasurableMemory
|
|||
public ByteBuffer toByteBuffer();
|
||||
|
||||
public int dataSize();
|
||||
public Composite copy(Allocator allocator);
|
||||
public Composite copy(AbstractAllocator allocator);
|
||||
public void free(PoolAllocator<?> allocator);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,8 +20,9 @@ package org.apache.cassandra.db.composites;
|
|||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.apache.cassandra.db.filter.ColumnSlice;
|
||||
import org.apache.cassandra.utils.Allocator;
|
||||
import org.apache.cassandra.utils.memory.AbstractAllocator;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
import org.apache.cassandra.utils.memory.PoolAllocator;
|
||||
|
||||
public abstract class Composites
|
||||
{
|
||||
|
|
@ -92,7 +93,7 @@ public abstract class Composites
|
|||
return 0;
|
||||
}
|
||||
|
||||
public long memorySize()
|
||||
public long unsharedHeapSize()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -102,9 +103,15 @@ public abstract class Composites
|
|||
return true;
|
||||
}
|
||||
|
||||
public Composite copy(Allocator allocator)
|
||||
public Composite copy(AbstractAllocator allocator)
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void free(PoolAllocator<?> allocator)
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,15 +19,17 @@ package org.apache.cassandra.db.composites;
|
|||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.apache.cassandra.db.TypeSizes;
|
||||
import org.apache.cassandra.utils.Allocator;
|
||||
import org.apache.cassandra.utils.memory.AbstractAllocator;
|
||||
import org.apache.cassandra.utils.ObjectSizes;
|
||||
import org.apache.cassandra.utils.memory.PoolAllocator;
|
||||
|
||||
/**
|
||||
* A "truly-composite" Composite.
|
||||
*/
|
||||
public class CompoundComposite extends AbstractComposite
|
||||
{
|
||||
private static final long EMPTY_SIZE = ObjectSizes.measure(new CompoundComposite(null, 0));
|
||||
|
||||
// We could use a List, but we'll create such object *a lot* and using a array+size is not
|
||||
// all that harder, so we save the List object allocation.
|
||||
final ByteBuffer[] elements;
|
||||
|
|
@ -49,7 +51,7 @@ public class CompoundComposite extends AbstractComposite
|
|||
return elements[i];
|
||||
}
|
||||
|
||||
protected ByteBuffer[] elementsCopy(Allocator allocator)
|
||||
protected ByteBuffer[] elementsCopy(AbstractAllocator allocator)
|
||||
{
|
||||
ByteBuffer[] elementsCopy = new ByteBuffer[size];
|
||||
for (int i = 0; i < size; i++)
|
||||
|
|
@ -57,14 +59,26 @@ public class CompoundComposite extends AbstractComposite
|
|||
return elementsCopy;
|
||||
}
|
||||
|
||||
public long memorySize()
|
||||
public long unsharedHeapSize()
|
||||
{
|
||||
return ObjectSizes.getFieldSize(TypeSizes.NATIVE.sizeof(size))
|
||||
+ ObjectSizes.getArraySize(elements);
|
||||
return EMPTY_SIZE + ObjectSizes.sizeOnHeapOf(elements);
|
||||
}
|
||||
|
||||
public Composite copy(Allocator allocator)
|
||||
public long excessHeapSizeExcludingData()
|
||||
{
|
||||
return EMPTY_SIZE + ObjectSizes.sizeOnHeapExcludingData(elements);
|
||||
}
|
||||
|
||||
public Composite copy(AbstractAllocator allocator)
|
||||
{
|
||||
return new CompoundComposite(elementsCopy(allocator), size);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void free(PoolAllocator<?> allocator)
|
||||
{
|
||||
for (ByteBuffer element : elements)
|
||||
allocator.free(element);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,11 +20,14 @@ package org.apache.cassandra.db.composites;
|
|||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.apache.cassandra.cql3.ColumnIdentifier;
|
||||
import org.apache.cassandra.utils.Allocator;
|
||||
import org.apache.cassandra.utils.memory.AbstractAllocator;
|
||||
import org.apache.cassandra.utils.ObjectSizes;
|
||||
|
||||
public class CompoundDenseCellName extends CompoundComposite implements CellName
|
||||
{
|
||||
|
||||
private static final long HEAP_SIZE = ObjectSizes.measure(new CompoundDenseCellName(new ByteBuffer[0]));
|
||||
|
||||
// Not meant to be used directly, you should use the CellNameType method instead
|
||||
CompoundDenseCellName(ByteBuffer[] elements)
|
||||
{
|
||||
|
|
@ -58,13 +61,20 @@ public class CompoundDenseCellName extends CompoundComposite implements CellName
|
|||
}
|
||||
|
||||
@Override
|
||||
public long memorySize()
|
||||
public long unsharedHeapSize()
|
||||
{
|
||||
return ObjectSizes.getSuperClassFieldSize(super.memorySize());
|
||||
return HEAP_SIZE + ObjectSizes.sizeOnHeapOf(elements);
|
||||
}
|
||||
|
||||
public CellName copy(Allocator allocator)
|
||||
@Override
|
||||
public long excessHeapSizeExcludingData()
|
||||
{
|
||||
return HEAP_SIZE + ObjectSizes.sizeOnHeapExcludingData(elements);
|
||||
}
|
||||
|
||||
public CellName copy(AbstractAllocator allocator)
|
||||
{
|
||||
return new CompoundDenseCellName(elementsCopy(allocator));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,13 +20,17 @@ package org.apache.cassandra.db.composites;
|
|||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.apache.cassandra.cql3.ColumnIdentifier;
|
||||
import org.apache.cassandra.utils.Allocator;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
import org.apache.cassandra.utils.memory.AbstractAllocator;
|
||||
import org.apache.cassandra.utils.ObjectSizes;
|
||||
import org.apache.cassandra.utils.memory.PoolAllocator;
|
||||
|
||||
public class CompoundSparseCellName extends CompoundComposite implements CellName
|
||||
{
|
||||
private static final ByteBuffer[] EMPTY_PREFIX = new ByteBuffer[0];
|
||||
|
||||
private static final long HEAP_SIZE = ObjectSizes.measure(new CompoundSparseCellName(null));
|
||||
|
||||
protected final ColumnIdentifier columnName;
|
||||
|
||||
// Not meant to be used directly, you should use the CellNameType method instead
|
||||
|
|
@ -89,7 +93,7 @@ public class CompoundSparseCellName extends CompoundComposite implements CellNam
|
|||
return true;
|
||||
}
|
||||
|
||||
public CellName copy(Allocator allocator)
|
||||
public CellName copy(AbstractAllocator allocator)
|
||||
{
|
||||
if (elements.length == 0)
|
||||
return this;
|
||||
|
|
@ -98,15 +102,10 @@ public class CompoundSparseCellName extends CompoundComposite implements CellNam
|
|||
return new CompoundSparseCellName(elementsCopy(allocator), columnName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long memorySize()
|
||||
{
|
||||
return ObjectSizes.getSuperClassFieldSize(super.memorySize())
|
||||
+ ObjectSizes.getFieldSize(ObjectSizes.getReferenceSize()) + columnName.memorySize();
|
||||
}
|
||||
|
||||
public static class WithCollection extends CompoundSparseCellName
|
||||
{
|
||||
private static final long HEAP_SIZE = ObjectSizes.measure(new WithCollection(null, ByteBufferUtil.EMPTY_BYTE_BUFFER));
|
||||
|
||||
private final ByteBuffer collectionElement;
|
||||
|
||||
WithCollection(ColumnIdentifier columnName, ByteBuffer collectionElement)
|
||||
|
|
@ -148,17 +147,29 @@ public class CompoundSparseCellName extends CompoundComposite implements CellNam
|
|||
}
|
||||
|
||||
@Override
|
||||
public CellName copy(Allocator allocator)
|
||||
public CellName copy(AbstractAllocator allocator)
|
||||
{
|
||||
// We don't copy columnName because it's interned in SparseCellNameType
|
||||
return new CompoundSparseCellName.WithCollection(elements.length == 0 ? elements : elementsCopy(allocator), size, columnName, allocator.clone(collectionElement));
|
||||
}
|
||||
|
||||
@Override
|
||||
public long memorySize()
|
||||
public long unsharedHeapSize()
|
||||
{
|
||||
return ObjectSizes.getSuperClassFieldSize(super.memorySize())
|
||||
+ ObjectSizes.getFieldSize(ObjectSizes.getReferenceSize()) + ObjectSizes.getSize(collectionElement);
|
||||
return super.unsharedHeapSize() + ObjectSizes.sizeOnHeapOf(collectionElement);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long excessHeapSizeExcludingData()
|
||||
{
|
||||
return super.excessHeapSizeExcludingData() + ObjectSizes.sizeOnHeapExcludingData(collectionElement);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void free(PoolAllocator<?> allocator)
|
||||
{
|
||||
super.free(allocator);
|
||||
allocator.free(collectionElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,14 +19,17 @@ package org.apache.cassandra.db.composites;
|
|||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.apache.cassandra.utils.Allocator;
|
||||
import org.apache.cassandra.utils.memory.AbstractAllocator;
|
||||
import org.apache.cassandra.utils.ObjectSizes;
|
||||
import org.apache.cassandra.utils.memory.PoolAllocator;
|
||||
|
||||
/**
|
||||
* A "simple" (not-truly-composite) Composite.
|
||||
*/
|
||||
public class SimpleComposite extends AbstractComposite
|
||||
{
|
||||
private static final long EMPTY_SIZE = ObjectSizes.measure(new SimpleComposite(ByteBuffer.allocate(1)));
|
||||
|
||||
protected final ByteBuffer element;
|
||||
|
||||
SimpleComposite(ByteBuffer element)
|
||||
|
|
@ -64,13 +67,19 @@ public class SimpleComposite extends AbstractComposite
|
|||
return element;
|
||||
}
|
||||
|
||||
public long memorySize()
|
||||
public long unsharedHeapSize()
|
||||
{
|
||||
return ObjectSizes.getFieldSize(ObjectSizes.getSize(element));
|
||||
return EMPTY_SIZE + ObjectSizes.sizeOnHeapOf(element);
|
||||
}
|
||||
|
||||
public Composite copy(Allocator allocator)
|
||||
public Composite copy(AbstractAllocator allocator)
|
||||
{
|
||||
return new SimpleComposite(allocator.clone(element));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void free(PoolAllocator<?> allocator)
|
||||
{
|
||||
allocator.free(element);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,11 +20,13 @@ package org.apache.cassandra.db.composites;
|
|||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.apache.cassandra.cql3.ColumnIdentifier;
|
||||
import org.apache.cassandra.utils.Allocator;
|
||||
import org.apache.cassandra.utils.memory.AbstractAllocator;
|
||||
import org.apache.cassandra.utils.ObjectSizes;
|
||||
|
||||
public class SimpleDenseCellName extends SimpleComposite implements CellName
|
||||
{
|
||||
private static final long EMPTY_SIZE = ObjectSizes.measure(new SimpleDenseCellName(ByteBuffer.allocate(1)));
|
||||
|
||||
// Not meant to be used directly, you should use the CellNameType method instead
|
||||
SimpleDenseCellName(ByteBuffer element)
|
||||
{
|
||||
|
|
@ -58,16 +60,23 @@ public class SimpleDenseCellName extends SimpleComposite implements CellName
|
|||
}
|
||||
|
||||
@Override
|
||||
public long memorySize()
|
||||
public long unsharedHeapSize()
|
||||
{
|
||||
return ObjectSizes.getSuperClassFieldSize(super.memorySize());
|
||||
return EMPTY_SIZE + ObjectSizes.sizeOnHeapOf(element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long excessHeapSizeExcludingData()
|
||||
{
|
||||
return EMPTY_SIZE + ObjectSizes.sizeOnHeapExcludingData(element);
|
||||
}
|
||||
|
||||
// If cellnames were sharing some prefix components, this will break it, so
|
||||
// we might want to try to do better.
|
||||
@Override
|
||||
public CellName copy(Allocator allocator)
|
||||
public CellName copy(AbstractAllocator allocator)
|
||||
{
|
||||
return new SimpleDenseCellName(allocator.clone(element));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,11 +20,14 @@ package org.apache.cassandra.db.composites;
|
|||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.apache.cassandra.cql3.ColumnIdentifier;
|
||||
import org.apache.cassandra.utils.Allocator;
|
||||
import org.apache.cassandra.utils.memory.AbstractAllocator;
|
||||
import org.apache.cassandra.utils.ObjectSizes;
|
||||
import org.apache.cassandra.utils.memory.PoolAllocator;
|
||||
|
||||
public class SimpleSparseCellName extends AbstractComposite implements CellName
|
||||
{
|
||||
private static final long EMPTY_SIZE = ObjectSizes.measure(new SimpleSparseCellName(null));
|
||||
|
||||
private final ColumnIdentifier columnName;
|
||||
|
||||
// Not meant to be used directly, you should use the CellNameType method instead
|
||||
|
|
@ -84,16 +87,23 @@ public class SimpleSparseCellName extends AbstractComposite implements CellName
|
|||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long memorySize()
|
||||
public long excessHeapSizeExcludingData()
|
||||
{
|
||||
return ObjectSizes.getFieldSize(ObjectSizes.getReferenceSize()) + columnName.memorySize();
|
||||
return EMPTY_SIZE + columnName.excessHeapSizeExcludingData();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CellName copy(Allocator allocator)
|
||||
public long unsharedHeapSize()
|
||||
{
|
||||
// We're interning those instance in SparceCellNameType so don't need to copy.
|
||||
return this;
|
||||
return EMPTY_SIZE + columnName.unsharedHeapSize();
|
||||
}
|
||||
|
||||
public CellName copy(AbstractAllocator allocator)
|
||||
{
|
||||
return new SimpleSparseCellName(columnName.clone(allocator));
|
||||
}
|
||||
|
||||
public void free(PoolAllocator<?> allocator)
|
||||
{
|
||||
allocator.free(columnName.bytes);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ public class SimpleSparseCellNameType extends AbstractSimpleCellNameType
|
|||
|
||||
public void addCQL3Column(ColumnIdentifier id)
|
||||
{
|
||||
internedNames.put(id.bytes, new SimpleSparseCellName(id));
|
||||
internedNames.put(id.bytes, new SimpleSparseInternedCellName(id));
|
||||
}
|
||||
|
||||
public void removeCQL3Column(ColumnIdentifier id)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.cassandra.db.composites;
|
||||
|
||||
import org.apache.cassandra.cql3.ColumnIdentifier;
|
||||
import org.apache.cassandra.utils.memory.AbstractAllocator;
|
||||
import org.apache.cassandra.utils.memory.PoolAllocator;
|
||||
|
||||
public class SimpleSparseInternedCellName extends SimpleSparseCellName
|
||||
{
|
||||
|
||||
// Not meant to be used directly, you should use the CellNameType method instead
|
||||
SimpleSparseInternedCellName(ColumnIdentifier columnName)
|
||||
{
|
||||
super(columnName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long excessHeapSizeExcludingData()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long unsharedHeapSize()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CellName copy(AbstractAllocator allocator)
|
||||
{
|
||||
// We're interning those instance in SparceCellNameType so don't need to copy.
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void free(PoolAllocator<?> allocator)
|
||||
{
|
||||
// no-op, never copied
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -22,6 +22,8 @@ import java.security.MessageDigest;
|
|||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.cassandra.serializers.MarshalException;
|
||||
import org.apache.cassandra.utils.memory.AbstractAllocator;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
|
@ -102,7 +104,7 @@ public class CounterContext
|
|||
/**
|
||||
* Creates a counter context with a single global, 2.1+ shard (a result of increment).
|
||||
*/
|
||||
public ByteBuffer createGlobal(CounterId id, long clock, long count, Allocator allocator)
|
||||
public ByteBuffer createGlobal(CounterId id, long clock, long count, AbstractAllocator allocator)
|
||||
{
|
||||
ContextState state = ContextState.allocate(1, 0, 0, allocator);
|
||||
state.writeGlobal(id, clock, count);
|
||||
|
|
@ -113,7 +115,7 @@ public class CounterContext
|
|||
* Creates a counter context with a single local shard.
|
||||
* For use by tests of compatibility with pre-2.1 counters only.
|
||||
*/
|
||||
public ByteBuffer createLocal(long count, Allocator allocator)
|
||||
public ByteBuffer createLocal(long count, AbstractAllocator allocator)
|
||||
{
|
||||
ContextState state = ContextState.allocate(0, 1, 0, allocator);
|
||||
state.writeLocal(CounterId.getLocalId(), 1L, count);
|
||||
|
|
@ -124,7 +126,7 @@ public class CounterContext
|
|||
* Creates a counter context with a single remote shard.
|
||||
* For use by tests of compatibility with pre-2.1 counters only.
|
||||
*/
|
||||
public ByteBuffer createRemote(CounterId id, long clock, long count, Allocator allocator)
|
||||
public ByteBuffer createRemote(CounterId id, long clock, long count, AbstractAllocator allocator)
|
||||
{
|
||||
ContextState state = ContextState.allocate(0, 0, 1, allocator);
|
||||
state.writeRemote(id, clock, count);
|
||||
|
|
@ -255,7 +257,7 @@ public class CounterContext
|
|||
* @param right counter context.
|
||||
* @param allocator An allocator for the merged value.
|
||||
*/
|
||||
public ByteBuffer merge(ByteBuffer left, ByteBuffer right, Allocator allocator)
|
||||
public ByteBuffer merge(ByteBuffer left, ByteBuffer right, AbstractAllocator allocator)
|
||||
{
|
||||
int globalCount = 0;
|
||||
int localCount = 0;
|
||||
|
|
@ -699,7 +701,7 @@ public class CounterContext
|
|||
* Allocate a new context big enough for globalCount + localCount + remoteCount elements
|
||||
* and return the initial corresponding ContextState.
|
||||
*/
|
||||
public static ContextState allocate(int globalCount, int localCount, int remoteCount, Allocator allocator)
|
||||
public static ContextState allocate(int globalCount, int localCount, int remoteCount, AbstractAllocator allocator)
|
||||
{
|
||||
int headerLength = HEADER_SIZE_LENGTH + (globalCount + localCount) * HEADER_ELT_LENGTH;
|
||||
int bodyLength = (globalCount + localCount + remoteCount) * STEP_LENGTH;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.cassandra.db.context;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.apache.cassandra.utils.memory.AbstractAllocator;
|
||||
|
||||
/**
|
||||
* An opaque commutative context.
|
||||
*
|
||||
* Maintains a ByteBuffer context that represents a partitioned commutative value.
|
||||
*/
|
||||
public interface IContext
|
||||
{
|
||||
public static enum ContextRelationship
|
||||
{
|
||||
EQUAL,
|
||||
GREATER_THAN,
|
||||
LESS_THAN,
|
||||
DISJOINT
|
||||
};
|
||||
|
||||
/**
|
||||
* Determine the relationship between two contexts.
|
||||
*
|
||||
* EQUAL: Equal set of nodes and every count is equal.
|
||||
* GREATER_THAN: Superset of nodes and every count is equal or greater than its corollary.
|
||||
* LESS_THAN: Subset of nodes and every count is equal or less than its corollary.
|
||||
* DISJOINT: Node sets are not equal and/or counts are not all greater or less than.
|
||||
*
|
||||
* @param left
|
||||
* context.
|
||||
* @param right
|
||||
* context.
|
||||
* @return the ContextRelationship between the contexts.
|
||||
*/
|
||||
public ContextRelationship diff(ByteBuffer left, ByteBuffer right);
|
||||
|
||||
/**
|
||||
* Return a context w/ an aggregated count for each node id.
|
||||
*
|
||||
* @param left
|
||||
* context.
|
||||
* @param right
|
||||
* context.
|
||||
* @param allocator
|
||||
* an allocator to allocate the new context from.
|
||||
*/
|
||||
public ByteBuffer merge(ByteBuffer left, ByteBuffer right, AbstractAllocator allocator);
|
||||
|
||||
/**
|
||||
* Human-readable String from context.
|
||||
*
|
||||
* @param context
|
||||
* context.
|
||||
* @return a human-readable String of the context.
|
||||
*/
|
||||
public String toString(ByteBuffer context);
|
||||
}
|
||||
|
|
@ -34,7 +34,8 @@ import org.apache.cassandra.db.marshal.AbstractType;
|
|||
import org.apache.cassandra.io.ISerializer;
|
||||
import org.apache.cassandra.io.IVersionedSerializer;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
import org.apache.cassandra.utils.Allocator;
|
||||
import org.apache.cassandra.utils.memory.AbstractAllocator;
|
||||
import org.apache.cassandra.utils.memory.PoolAllocator;
|
||||
|
||||
public class ColumnSlice
|
||||
{
|
||||
|
|
@ -298,12 +299,24 @@ public class ColumnSlice
|
|||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public CellName copy(Allocator allocator)
|
||||
public CellName copy(AbstractAllocator allocator)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public long memorySize()
|
||||
@Override
|
||||
public long excessHeapSizeExcludingData()
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void free(PoolAllocator<?> allocator)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public long unsharedHeapSize()
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ import org.apache.cassandra.db.composites.CellName;
|
|||
import org.apache.cassandra.db.composites.Composite;
|
||||
import org.apache.cassandra.io.sstable.SSTableReader;
|
||||
import org.apache.cassandra.io.util.FileDataInput;
|
||||
import org.apache.cassandra.utils.HeapAllocator;
|
||||
import org.apache.cassandra.utils.memory.HeapAllocator;
|
||||
import org.apache.cassandra.utils.MergeIterator;
|
||||
|
||||
public class QueryFilter
|
||||
|
|
|
|||
|
|
@ -18,7 +18,10 @@
|
|||
package org.apache.cassandra.db.index;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import org.apache.cassandra.utils.FBUtilities;
|
||||
import org.apache.cassandra.utils.concurrent.OpOrder;
|
||||
import org.apache.cassandra.config.CFMetaData;
|
||||
import org.apache.cassandra.config.ColumnDefinition;
|
||||
import org.apache.cassandra.db.*;
|
||||
|
|
@ -27,6 +30,7 @@ import org.apache.cassandra.db.composites.CellNameType;
|
|||
import org.apache.cassandra.db.marshal.*;
|
||||
import org.apache.cassandra.dht.*;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
import org.apache.cassandra.utils.memory.AbstractAllocator;
|
||||
|
||||
/**
|
||||
* Implements a secondary index for a column family using a second column family
|
||||
|
|
@ -83,6 +87,21 @@ public abstract class AbstractSimplePerColumnSecondaryIndex extends PerColumnSec
|
|||
}
|
||||
|
||||
public void delete(ByteBuffer rowKey, Cell cell)
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
public void insert(ByteBuffer rowKey, Cell cell)
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
public void update(ByteBuffer rowKey, Cell cell)
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
public void delete(ByteBuffer rowKey, Cell cell, OpOrder.Group opGroup)
|
||||
{
|
||||
if (cell.isMarkedForDelete(System.currentTimeMillis()))
|
||||
return;
|
||||
|
|
@ -91,12 +110,12 @@ public abstract class AbstractSimplePerColumnSecondaryIndex extends PerColumnSec
|
|||
int localDeletionTime = (int) (System.currentTimeMillis() / 1000);
|
||||
ColumnFamily cfi = ArrayBackedSortedColumns.factory.create(indexCfs.metadata);
|
||||
cfi.addTombstone(makeIndexColumnName(rowKey, cell), localDeletionTime, cell.timestamp());
|
||||
indexCfs.apply(valueKey, cfi, SecondaryIndexManager.nullUpdater);
|
||||
indexCfs.apply(valueKey, cfi, SecondaryIndexManager.nullUpdater, opGroup, null);
|
||||
if (logger.isDebugEnabled())
|
||||
logger.debug("removed index entry for cleaned-up value {}:{}", valueKey, cfi);
|
||||
}
|
||||
|
||||
public void insert(ByteBuffer rowKey, Cell cell)
|
||||
public void insert(ByteBuffer rowKey, Cell cell, OpOrder.Group opGroup)
|
||||
{
|
||||
DecoratedKey valueKey = getIndexKeyFor(getIndexedValue(rowKey, cell));
|
||||
ColumnFamily cfi = ArrayBackedSortedColumns.factory.create(indexCfs.metadata);
|
||||
|
|
@ -113,12 +132,12 @@ public abstract class AbstractSimplePerColumnSecondaryIndex extends PerColumnSec
|
|||
if (logger.isDebugEnabled())
|
||||
logger.debug("applying index row {} in {}", indexCfs.metadata.getKeyValidator().getString(valueKey.key), cfi);
|
||||
|
||||
indexCfs.apply(valueKey, cfi, SecondaryIndexManager.nullUpdater);
|
||||
indexCfs.apply(valueKey, cfi, SecondaryIndexManager.nullUpdater, opGroup, null);
|
||||
}
|
||||
|
||||
public void update(ByteBuffer rowKey, Cell col)
|
||||
public void update(ByteBuffer rowKey, Cell col, OpOrder.Group opGroup)
|
||||
{
|
||||
insert(rowKey, col);
|
||||
insert(rowKey, col, opGroup);
|
||||
}
|
||||
|
||||
public void removeIndex(ByteBuffer columnName)
|
||||
|
|
@ -128,7 +147,13 @@ public abstract class AbstractSimplePerColumnSecondaryIndex extends PerColumnSec
|
|||
|
||||
public void forceBlockingFlush()
|
||||
{
|
||||
indexCfs.forceBlockingFlush();
|
||||
Future<?> wait;
|
||||
// we synchronise on the baseCfs to make sure we are ordered correctly with other flushes to the base CFS
|
||||
synchronized (baseCfs.getDataTracker())
|
||||
{
|
||||
wait = indexCfs.forceFlush();
|
||||
}
|
||||
FBUtilities.waitOnFuture(wait);
|
||||
}
|
||||
|
||||
public void invalidate()
|
||||
|
|
@ -151,9 +176,9 @@ public abstract class AbstractSimplePerColumnSecondaryIndex extends PerColumnSec
|
|||
return indexCfs.name;
|
||||
}
|
||||
|
||||
public long getLiveSize()
|
||||
public AbstractAllocator getOnHeapAllocator()
|
||||
{
|
||||
return indexCfs.getMemtableDataSize();
|
||||
return indexCfs.getDataTracker().getView().getCurrentMemtable().getAllocator();
|
||||
}
|
||||
|
||||
public void reload()
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ package org.apache.cassandra.db.index;
|
|||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.apache.cassandra.utils.concurrent.OpOrder;
|
||||
import org.apache.cassandra.db.Cell;
|
||||
import org.apache.cassandra.utils.FBUtilities;
|
||||
|
||||
|
|
@ -29,12 +30,12 @@ import org.apache.cassandra.utils.FBUtilities;
|
|||
public abstract class PerColumnSecondaryIndex extends SecondaryIndex
|
||||
{
|
||||
/**
|
||||
* Delete a column from the index
|
||||
* Delete a column from the index.
|
||||
*
|
||||
* @param rowKey the underlying row key which is indexed
|
||||
* @param col all the column info
|
||||
*/
|
||||
public abstract void delete(ByteBuffer rowKey, Cell col);
|
||||
public abstract void delete(ByteBuffer rowKey, Cell col, OpOrder.Group opGroup);
|
||||
|
||||
/**
|
||||
* insert a column to the index
|
||||
|
|
@ -42,7 +43,7 @@ public abstract class PerColumnSecondaryIndex extends SecondaryIndex
|
|||
* @param rowKey the underlying row key which is indexed
|
||||
* @param col all the column info
|
||||
*/
|
||||
public abstract void insert(ByteBuffer rowKey, Cell col);
|
||||
public abstract void insert(ByteBuffer rowKey, Cell col, OpOrder.Group opGroup);
|
||||
|
||||
/**
|
||||
* update a column from the index
|
||||
|
|
@ -50,14 +51,13 @@ public abstract class PerColumnSecondaryIndex extends SecondaryIndex
|
|||
* @param rowKey the underlying row key which is indexed
|
||||
* @param col all the column info
|
||||
*/
|
||||
public abstract void update(ByteBuffer rowKey, Cell col);
|
||||
public abstract void update(ByteBuffer rowKey, Cell col, OpOrder.Group opGroup);
|
||||
|
||||
public String getNameForSystemKeyspace(ByteBuffer column)
|
||||
{
|
||||
return getIndexName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validate(Cell cell)
|
||||
{
|
||||
return cell.value().remaining() < FBUtilities.MAX_UNSIGNED_SHORT;
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ package org.apache.cassandra.db.index;
|
|||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.CharacterCodingException;
|
||||
|
||||
import org.apache.cassandra.utils.concurrent.OpOrder;
|
||||
import org.apache.cassandra.db.Cell;
|
||||
import org.apache.cassandra.db.ColumnFamily;
|
||||
import org.apache.cassandra.db.DecoratedKey;
|
||||
|
|
@ -43,9 +44,8 @@ public abstract class PerRowSecondaryIndex extends SecondaryIndex
|
|||
*
|
||||
* @param key
|
||||
*/
|
||||
public abstract void delete(DecoratedKey key);
|
||||
public abstract void delete(DecoratedKey key, OpOrder.Group opGroup);
|
||||
|
||||
@Override
|
||||
public String getNameForSystemKeyspace(ByteBuffer columnName)
|
||||
{
|
||||
try
|
||||
|
|
@ -58,7 +58,6 @@ public abstract class PerRowSecondaryIndex extends SecondaryIndex
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validate(Cell cell)
|
||||
{
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import java.nio.ByteBuffer;
|
|||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
import org.apache.cassandra.utils.memory.AbstractAllocator;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
|
@ -137,14 +138,14 @@ public abstract class SecondaryIndex
|
|||
protected abstract SecondaryIndexSearcher createSecondaryIndexSearcher(Set<ByteBuffer> columns);
|
||||
|
||||
/**
|
||||
* Forces this indexes in memory data to disk
|
||||
* Forces this indexes' in memory data to disk
|
||||
*/
|
||||
public abstract void forceBlockingFlush();
|
||||
|
||||
/**
|
||||
* Get current amount of memory this index is consuming (in bytes)
|
||||
*/
|
||||
public abstract long getLiveSize();
|
||||
public abstract AbstractAllocator getOnHeapAllocator();
|
||||
|
||||
/**
|
||||
* Allow access to the underlying column family store if there is one
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ import java.nio.ByteBuffer;
|
|||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
import org.apache.cassandra.utils.concurrent.OpOrder;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
|
@ -312,8 +314,22 @@ public class SecondaryIndexManager
|
|||
*/
|
||||
public void flushIndexesBlocking()
|
||||
{
|
||||
// despatch flushes for all CFS backed indexes
|
||||
List<Future<?>> wait = new ArrayList<>();
|
||||
synchronized (baseCfs.getDataTracker())
|
||||
{
|
||||
for (SecondaryIndex index : indexesByColumn.values())
|
||||
if (index.getIndexCfs() != null)
|
||||
wait.add(index.getIndexCfs().forceFlush());
|
||||
}
|
||||
|
||||
// blockingFlush any non-CFS-backed indexes
|
||||
for (SecondaryIndex index : indexesByColumn.values())
|
||||
index.forceBlockingFlush();
|
||||
if (index.getIndexCfs() == null)
|
||||
index.forceBlockingFlush();
|
||||
|
||||
// wait for the CFS-backed index flushes to complete
|
||||
FBUtilities.waitOnFutures(wait);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -383,24 +399,13 @@ public class SecondaryIndexManager
|
|||
return !indexesByColumn.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return total current ram size of all indexes
|
||||
*/
|
||||
public long getTotalLiveSize()
|
||||
{
|
||||
long total = 0;
|
||||
for (SecondaryIndex index : getIndexes())
|
||||
total += index.getLiveSize();
|
||||
return total;
|
||||
}
|
||||
|
||||
/**
|
||||
* When building an index against existing data, add the given row to the index
|
||||
*
|
||||
* @param key the row key
|
||||
* @param cf the current rows data
|
||||
*/
|
||||
public void indexRow(ByteBuffer key, ColumnFamily cf)
|
||||
public void indexRow(ByteBuffer key, ColumnFamily cf, OpOrder.Group opGroup)
|
||||
{
|
||||
// Update entire row only once per row level index
|
||||
Set<Class<? extends SecondaryIndex>> appliedRowLevelIndexes = null;
|
||||
|
|
@ -419,7 +424,7 @@ public class SecondaryIndexManager
|
|||
{
|
||||
for (Cell cell : cf)
|
||||
if (index.indexes(cell.name()))
|
||||
((PerColumnSecondaryIndex) index).insert(key, cell);
|
||||
((PerColumnSecondaryIndex) index).insert(key, cell, opGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -430,7 +435,7 @@ public class SecondaryIndexManager
|
|||
* @param key the row key
|
||||
* @param indexedColumnsInRow all column names in row
|
||||
*/
|
||||
public void deleteFromIndexes(DecoratedKey key, List<Cell> indexedColumnsInRow)
|
||||
public void deleteFromIndexes(DecoratedKey key, List<Cell> indexedColumnsInRow, OpOrder.Group opGroup)
|
||||
{
|
||||
// Update entire row only once per row level index
|
||||
Set<Class<? extends SecondaryIndex>> cleanedRowLevelIndexes = null;
|
||||
|
|
@ -448,11 +453,11 @@ public class SecondaryIndexManager
|
|||
cleanedRowLevelIndexes = new HashSet<>();
|
||||
|
||||
if (cleanedRowLevelIndexes.add(index.getClass()))
|
||||
((PerRowSecondaryIndex)index).delete(key);
|
||||
((PerRowSecondaryIndex)index).delete(key, opGroup);
|
||||
}
|
||||
else
|
||||
{
|
||||
((PerColumnSecondaryIndex) index).delete(key.key, cell);
|
||||
((PerColumnSecondaryIndex) index).delete(key.key, cell, opGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -464,19 +469,27 @@ public class SecondaryIndexManager
|
|||
* can get updated. Note: only a CF backed by AtomicSortedColumns implements
|
||||
* this behaviour fully, other types simply ignore the index updater.
|
||||
*/
|
||||
public Updater updaterFor(DecoratedKey key, ColumnFamily cf)
|
||||
public Updater updaterFor(DecoratedKey key, ColumnFamily cf, OpOrder.Group opGroup)
|
||||
{
|
||||
return (indexesByColumn.isEmpty() && rowLevelIndexMap.isEmpty())
|
||||
? nullUpdater
|
||||
: new StandardUpdater(key, cf);
|
||||
: new StandardUpdater(key, cf, opGroup);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updated closure with only the modified row key.
|
||||
*/
|
||||
public Updater updaterFor(DecoratedKey key)
|
||||
public Updater updaterFor(DecoratedKey key, OpOrder.Group opGroup)
|
||||
{
|
||||
return updaterFor(key, null);
|
||||
return updaterFor(key, null, opGroup);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updated closure with only the modified row key.
|
||||
*/
|
||||
public Updater gcUpdaterFor(DecoratedKey key)
|
||||
{
|
||||
return new GCUpdater(key);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -583,15 +596,65 @@ public class SecondaryIndexManager
|
|||
public void updateRowLevelIndexes();
|
||||
}
|
||||
|
||||
private class StandardUpdater implements Updater
|
||||
private final class GCUpdater implements Updater
|
||||
{
|
||||
private final DecoratedKey key;
|
||||
|
||||
public GCUpdater(DecoratedKey key)
|
||||
{
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public void insert(Cell cell)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public void update(Cell oldCell, Cell newCell)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public void remove(Cell cell)
|
||||
{
|
||||
if (cell.isMarkedForDelete(System.currentTimeMillis()))
|
||||
return;
|
||||
|
||||
for (SecondaryIndex index : indexFor(cell.name()))
|
||||
{
|
||||
if (index instanceof PerColumnSecondaryIndex)
|
||||
{
|
||||
OpOrder.Group opGroup = baseCfs.keyspace.writeOrder.start();
|
||||
try
|
||||
{
|
||||
((PerColumnSecondaryIndex) index).delete(key.key, cell, opGroup);
|
||||
}
|
||||
finally
|
||||
{
|
||||
opGroup.finishOne();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void updateRowLevelIndexes()
|
||||
{
|
||||
for (SecondaryIndex index : rowLevelIndexMap.values())
|
||||
((PerRowSecondaryIndex) index).index(key.key, null);
|
||||
}
|
||||
}
|
||||
|
||||
private final class StandardUpdater implements Updater
|
||||
{
|
||||
private final DecoratedKey key;
|
||||
private final ColumnFamily cf;
|
||||
private final OpOrder.Group opGroup;
|
||||
|
||||
public StandardUpdater(DecoratedKey key, ColumnFamily cf)
|
||||
public StandardUpdater(DecoratedKey key, ColumnFamily cf, OpOrder.Group opGroup)
|
||||
{
|
||||
this.key = key;
|
||||
this.cf = cf;
|
||||
this.opGroup = opGroup;
|
||||
}
|
||||
|
||||
public void insert(Cell cell)
|
||||
|
|
@ -601,7 +664,7 @@ public class SecondaryIndexManager
|
|||
|
||||
for (SecondaryIndex index : indexFor(cell.name()))
|
||||
if (index instanceof PerColumnSecondaryIndex)
|
||||
((PerColumnSecondaryIndex) index).insert(key.key, cell);
|
||||
((PerColumnSecondaryIndex) index).insert(key.key, cell, opGroup);
|
||||
}
|
||||
|
||||
public void update(Cell oldCell, Cell cell)
|
||||
|
|
@ -616,8 +679,8 @@ public class SecondaryIndexManager
|
|||
// insert the new value before removing the old one, so we never have a period
|
||||
// where the row is invisible to both queries (the opposite seems preferable); see CASSANDRA-5540
|
||||
if (!cell.isMarkedForDelete(System.currentTimeMillis()))
|
||||
((PerColumnSecondaryIndex) index).insert(key.key, cell);
|
||||
((PerColumnSecondaryIndex) index).delete(key.key, oldCell);
|
||||
((PerColumnSecondaryIndex) index).insert(key.key, cell, opGroup);
|
||||
((PerColumnSecondaryIndex) index).delete(key.key, oldCell, opGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -629,7 +692,7 @@ public class SecondaryIndexManager
|
|||
|
||||
for (SecondaryIndex index : indexFor(cell.name()))
|
||||
if (index instanceof PerColumnSecondaryIndex)
|
||||
((PerColumnSecondaryIndex) index).delete(key.key, cell);
|
||||
((PerColumnSecondaryIndex) index).delete(key.key, cell, opGroup);
|
||||
}
|
||||
|
||||
public void updateRowLevelIndexes()
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import java.util.HashMap;
|
|||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.cassandra.utils.concurrent.OpOrder;
|
||||
import org.apache.cassandra.config.CFMetaData;
|
||||
import org.apache.cassandra.config.ColumnDefinition;
|
||||
import org.apache.cassandra.db.*;
|
||||
|
|
@ -130,13 +131,21 @@ public abstract class CompositesIndex extends AbstractSimplePerColumnSecondaryIn
|
|||
|
||||
public void delete(IndexedEntry entry)
|
||||
{
|
||||
int localDeletionTime = (int) (System.currentTimeMillis() / 1000);
|
||||
ColumnFamily cfi = ArrayBackedSortedColumns.factory.create(indexCfs.metadata);
|
||||
cfi.addTombstone(entry.indexEntry, localDeletionTime, entry.timestamp);
|
||||
indexCfs.apply(entry.indexValue, cfi, SecondaryIndexManager.nullUpdater);
|
||||
if (logger.isDebugEnabled())
|
||||
logger.debug("removed index entry for cleaned-up value {}:{}", entry.indexValue, cfi);
|
||||
|
||||
// start a mini-transaction for this delete, to ensure safe memtable updates
|
||||
OpOrder.Group opGroup = baseCfs.keyspace.writeOrder.start();
|
||||
try
|
||||
{
|
||||
int localDeletionTime = (int) (System.currentTimeMillis() / 1000);
|
||||
ColumnFamily cfi = ArrayBackedSortedColumns.factory.create(indexCfs.metadata);
|
||||
cfi.addTombstone(entry.indexEntry, localDeletionTime, entry.timestamp);
|
||||
indexCfs.apply(entry.indexValue, cfi, SecondaryIndexManager.nullUpdater, opGroup, null);
|
||||
if (logger.isDebugEnabled())
|
||||
logger.debug("removed index entry for cleaned-up value {}:{}", entry.indexValue, cfi);
|
||||
}
|
||||
finally
|
||||
{
|
||||
opGroup.finishOne();
|
||||
}
|
||||
}
|
||||
|
||||
protected AbstractType<?> getExpressionComparator()
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@ import java.util.Iterator;
|
|||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.cassandra.utils.concurrent.OpOrder;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
|
@ -38,7 +40,7 @@ import org.apache.cassandra.db.filter.QueryFilter;
|
|||
import org.apache.cassandra.db.index.*;
|
||||
import org.apache.cassandra.dht.AbstractBounds;
|
||||
import org.apache.cassandra.dht.Range;
|
||||
import org.apache.cassandra.utils.HeapAllocator;
|
||||
import org.apache.cassandra.utils.memory.HeapAllocator;
|
||||
|
||||
public class KeysSearcher extends SecondaryIndexSearcher
|
||||
{
|
||||
|
|
@ -186,7 +188,15 @@ public class KeysSearcher extends SecondaryIndexSearcher
|
|||
{
|
||||
// delete the index entry w/ its own timestamp
|
||||
Cell dummyCell = new Cell(primaryColumn, indexKey.key, cell.timestamp());
|
||||
((PerColumnSecondaryIndex)index).delete(dk.key, dummyCell);
|
||||
OpOrder.Group opGroup = baseCfs.keyspace.writeOrder.start();
|
||||
try
|
||||
{
|
||||
((PerColumnSecondaryIndex)index).delete(dk.key, dummyCell, opGroup);
|
||||
}
|
||||
finally
|
||||
{
|
||||
opGroup.finishOne();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
return new Row(dk, data);
|
||||
|
|
|
|||
|
|
@ -17,14 +17,24 @@
|
|||
*/
|
||||
package org.apache.cassandra.dht;
|
||||
|
||||
import org.apache.cassandra.utils.ObjectSizes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class ByteOrderedPartitioner extends AbstractByteOrderedPartitioner
|
||||
{
|
||||
private static final long EMPTY_SIZE = ObjectSizes.measure(MINIMUM);
|
||||
|
||||
public BytesToken getToken(ByteBuffer key)
|
||||
{
|
||||
if (key.remaining() == 0)
|
||||
return MINIMUM;
|
||||
return new BytesToken(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getHeapSizeOf(BytesToken token)
|
||||
{
|
||||
return EMPTY_SIZE + ObjectSizes.sizeOfArray(token.token);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,6 +55,13 @@ public interface IPartitioner<T extends Token>
|
|||
*/
|
||||
public T getToken(ByteBuffer key);
|
||||
|
||||
/**
|
||||
*
|
||||
* @param token
|
||||
* @return the on-heap memory used by the provided token
|
||||
*/
|
||||
public long getHeapSizeOf(T token);
|
||||
|
||||
/**
|
||||
* @return a randomly generated token
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -25,9 +25,12 @@ import java.util.Map;
|
|||
import org.apache.cassandra.db.DecoratedKey;
|
||||
import org.apache.cassandra.db.marshal.AbstractType;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
import org.apache.cassandra.utils.ObjectSizes;
|
||||
|
||||
public class LocalPartitioner extends AbstractPartitioner<LocalToken>
|
||||
{
|
||||
private static final long EMPTY_SIZE = ObjectSizes.measure(new LocalToken(null, null));
|
||||
|
||||
private final AbstractType<?> comparator;
|
||||
|
||||
public LocalPartitioner(AbstractType<?> comparator)
|
||||
|
|
@ -55,6 +58,11 @@ public class LocalPartitioner extends AbstractPartitioner<LocalToken>
|
|||
return new LocalToken(comparator, key);
|
||||
}
|
||||
|
||||
public long getHeapSizeOf(LocalToken token)
|
||||
{
|
||||
return EMPTY_SIZE + ObjectSizes.sizeOnHeapOf(token.token);
|
||||
}
|
||||
|
||||
public LocalToken getRandomToken()
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import org.apache.cassandra.exceptions.ConfigurationException;
|
|||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
import org.apache.cassandra.utils.FBUtilities;
|
||||
import org.apache.cassandra.utils.MurmurHash;
|
||||
import org.apache.cassandra.utils.ObjectSizes;
|
||||
|
||||
/**
|
||||
* This class generates a BigIntegerToken using a Murmur3 hash.
|
||||
|
|
@ -41,6 +42,8 @@ public class Murmur3Partitioner extends AbstractPartitioner<LongToken>
|
|||
public static final LongToken MINIMUM = new LongToken(Long.MIN_VALUE);
|
||||
public static final long MAXIMUM = Long.MAX_VALUE;
|
||||
|
||||
private static final int HEAP_SIZE = (int) ObjectSizes.measureDeep(MINIMUM);
|
||||
|
||||
public DecoratedKey decorateKey(ByteBuffer key)
|
||||
{
|
||||
return new DecoratedKey(getToken(key), key);
|
||||
|
|
@ -94,6 +97,11 @@ public class Murmur3Partitioner extends AbstractPartitioner<LongToken>
|
|||
return new LongToken(normalize(hash[0]));
|
||||
}
|
||||
|
||||
public long getHeapSizeOf(LongToken token)
|
||||
{
|
||||
return HEAP_SIZE;
|
||||
}
|
||||
|
||||
public LongToken getRandomToken()
|
||||
{
|
||||
return new LongToken(normalize(FBUtilities.threadLocalRandom().nextLong()));
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import org.apache.cassandra.gms.VersionedValue;
|
|||
import org.apache.cassandra.service.StorageService;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
import org.apache.cassandra.utils.FBUtilities;
|
||||
import org.apache.cassandra.utils.ObjectSizes;
|
||||
import org.apache.cassandra.utils.Pair;
|
||||
|
||||
public class OrderPreservingPartitioner extends AbstractPartitioner<StringToken>
|
||||
|
|
@ -39,6 +40,8 @@ public class OrderPreservingPartitioner extends AbstractPartitioner<StringToken>
|
|||
|
||||
public static final BigInteger CHAR_MASK = new BigInteger("65535");
|
||||
|
||||
private static final long EMPTY_SIZE = ObjectSizes.measure(MINIMUM);
|
||||
|
||||
public DecoratedKey decorateKey(ByteBuffer key)
|
||||
{
|
||||
return new DecoratedKey(getToken(key), key);
|
||||
|
|
@ -169,6 +172,11 @@ public class OrderPreservingPartitioner extends AbstractPartitioner<StringToken>
|
|||
return new StringToken(skey);
|
||||
}
|
||||
|
||||
public long getHeapSizeOf(StringToken token)
|
||||
{
|
||||
return EMPTY_SIZE + ObjectSizes.sizeOf(token.token);
|
||||
}
|
||||
|
||||
public Map<Token, Float> describeOwnership(List<Token> sortedTokens)
|
||||
{
|
||||
// allTokens will contain the count and be returned, sorted_ranges is shorthand for token<->token math.
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import org.apache.cassandra.db.marshal.IntegerType;
|
|||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
import org.apache.cassandra.utils.FBUtilities;
|
||||
import org.apache.cassandra.utils.GuidGenerator;
|
||||
import org.apache.cassandra.utils.ObjectSizes;
|
||||
import org.apache.cassandra.utils.Pair;
|
||||
|
||||
/**
|
||||
|
|
@ -40,6 +41,8 @@ public class RandomPartitioner extends AbstractPartitioner<BigIntegerToken>
|
|||
public static final BigIntegerToken MINIMUM = new BigIntegerToken("-1");
|
||||
public static final BigInteger MAXIMUM = new BigInteger("2").pow(127);
|
||||
|
||||
private static final int EMPTY_SIZE = (int) ObjectSizes.measureDeep(new BigIntegerToken(FBUtilities.hashToBigInteger(ByteBuffer.allocate(1))));
|
||||
|
||||
public DecoratedKey decorateKey(ByteBuffer key)
|
||||
{
|
||||
return new DecoratedKey(getToken(key), key);
|
||||
|
|
@ -123,6 +126,11 @@ public class RandomPartitioner extends AbstractPartitioner<BigIntegerToken>
|
|||
return new BigIntegerToken(FBUtilities.hashToBigInteger(key));
|
||||
}
|
||||
|
||||
public long getHeapSizeOf(BigIntegerToken token)
|
||||
{
|
||||
return EMPTY_SIZE;
|
||||
}
|
||||
|
||||
public Map<Token, Float> describeOwnership(List<Token> sortedTokens)
|
||||
{
|
||||
Map<Token, Float> ownerships = new HashMap<Token, Float>();
|
||||
|
|
|
|||
|
|
@ -31,8 +31,8 @@ import org.apache.cassandra.db.context.CounterContext;
|
|||
import org.apache.cassandra.dht.IPartitioner;
|
||||
import org.apache.cassandra.io.sstable.metadata.MetadataCollector;
|
||||
import org.apache.cassandra.utils.CounterId;
|
||||
import org.apache.cassandra.utils.HeapAllocator;
|
||||
import org.apache.cassandra.utils.Pair;
|
||||
import org.apache.cassandra.utils.memory.HeapAllocator;
|
||||
|
||||
public abstract class AbstractSSTableSimpleWriter
|
||||
{
|
||||
|
|
|
|||
|
|
@ -155,6 +155,8 @@ public class IndexHelper
|
|||
|
||||
public static class IndexInfo
|
||||
{
|
||||
private static final long EMPTY_SIZE = ObjectSizes.measure(new IndexInfo(null, null, 0, 0));
|
||||
|
||||
public final long width;
|
||||
public final Composite lastName;
|
||||
public final Composite firstName;
|
||||
|
|
@ -202,15 +204,9 @@ public class IndexHelper
|
|||
}
|
||||
}
|
||||
|
||||
public long memorySize()
|
||||
public long excessHeapSize()
|
||||
{
|
||||
return ObjectSizes.getFieldSize(// firstName
|
||||
ObjectSizes.getReferenceSize() +
|
||||
// lastName
|
||||
ObjectSizes.getReferenceSize() +
|
||||
TypeSizes.NATIVE.sizeof(offset) +
|
||||
TypeSizes.NATIVE.sizeof(width))
|
||||
+ firstName.memorySize() + lastName.memorySize();
|
||||
return EMPTY_SIZE + firstName.unsharedHeapSize() + lastName.unsharedHeapSize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ import org.apache.cassandra.io.FSWriteError;
|
|||
import org.apache.cassandra.io.util.FileUtils;
|
||||
import org.apache.cassandra.io.util.RandomAccessReader;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
import org.apache.cassandra.utils.HeapAllocator;
|
||||
import org.apache.cassandra.utils.memory.HeapAllocator;
|
||||
import org.apache.cassandra.utils.Pair;
|
||||
|
||||
/**
|
||||
|
|
@ -125,7 +125,7 @@ public abstract class SSTable
|
|||
*/
|
||||
public static DecoratedKey getMinimalKey(DecoratedKey key)
|
||||
{
|
||||
return key.key.position() > 0 || key.key.hasRemaining()
|
||||
return key.key.position() > 0 || key.key.hasRemaining() || !key.key.hasArray()
|
||||
? new DecoratedKey(key.token, HeapAllocator.instance.clone(key.key))
|
||||
: key;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import org.apache.cassandra.io.sstable.*;
|
|||
import org.apache.cassandra.utils.EstimatedHistogram;
|
||||
import org.apache.cassandra.utils.MurmurHash;
|
||||
import org.apache.cassandra.utils.StreamingHistogram;
|
||||
import org.apache.cassandra.utils.memory.HeapAllocator;
|
||||
|
||||
public class MetadataCollector
|
||||
{
|
||||
|
|
|
|||
|
|
@ -37,9 +37,6 @@ public class SequentialWriter extends OutputStream
|
|||
// absolute path to the given file
|
||||
private final String filePath;
|
||||
|
||||
// so we can use the write(int) path w/o tons of new byte[] allocations
|
||||
private final byte[] singleByteBuffer = new byte[1];
|
||||
|
||||
protected byte[] buffer;
|
||||
private final boolean skipIOCache;
|
||||
private final int fd;
|
||||
|
|
@ -112,8 +109,18 @@ public class SequentialWriter extends OutputStream
|
|||
|
||||
public void write(int value) throws ClosedChannelException
|
||||
{
|
||||
singleByteBuffer[0] = (byte) value;
|
||||
write(singleByteBuffer, 0, 1);
|
||||
if (current >= bufferOffset + buffer.length)
|
||||
reBuffer();
|
||||
|
||||
assert current < bufferOffset + buffer.length
|
||||
: String.format("File (%s) offset %d, buffer offset %d.", getPath(), current, bufferOffset);
|
||||
|
||||
buffer[bufferCursor()] = (byte) value;
|
||||
|
||||
validBufferBytes += 1;
|
||||
current += 1;
|
||||
isDirty = true;
|
||||
syncNeeded = true;
|
||||
}
|
||||
|
||||
public void write(byte[] buffer) throws ClosedChannelException
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ import com.yammer.metrics.core.*;
|
|||
import com.yammer.metrics.util.RatioGauge;
|
||||
|
||||
import org.apache.cassandra.db.ColumnFamilyStore;
|
||||
import org.apache.cassandra.db.Keyspace;
|
||||
import org.apache.cassandra.io.sstable.SSTableReader;
|
||||
import org.apache.cassandra.io.sstable.metadata.MetadataCollector;
|
||||
import org.apache.cassandra.utils.EstimatedHistogram;
|
||||
|
|
@ -34,10 +33,14 @@ import org.apache.cassandra.utils.EstimatedHistogram;
|
|||
*/
|
||||
public class ColumnFamilyMetrics
|
||||
{
|
||||
/** Total amount of data stored in the memtable, including column related overhead. */
|
||||
public final Gauge<Long> memtableDataSize;
|
||||
/** Total amount of data stored in the memtables (2i and pending flush memtables included). */
|
||||
public final Gauge<Long> allMemtablesDataSize;
|
||||
/** Total amount of data stored in the memtable that resides on-heap, including column related overhead and overwritten rows. */
|
||||
public final Gauge<Long> memtableHeapSize;
|
||||
/** Total amount of live data stored in the memtable, excluding any data structure overhead */
|
||||
public final Gauge<Long> memtableLiveDataSize;
|
||||
/** Total amount of data stored in the memtables (2i and pending flush memtables included) that resides on-heap. */
|
||||
public final Gauge<Long> allMemtablesHeapSize;
|
||||
/** Total amount of live data stored in the memtables (2i and pending flush memtables included) that resides off-heap, excluding any data structure overhead */
|
||||
public final Gauge<Long> allMemtablesLiveDataSize;
|
||||
/** Total number of columns present in the memtable. */
|
||||
public final Gauge<Long> memtableColumnsCount;
|
||||
/** Number of times flush has resulted in the memtable being switched out. */
|
||||
|
|
@ -55,7 +58,7 @@ public class ColumnFamilyMetrics
|
|||
/** (Local) write metrics */
|
||||
public final LatencyMetrics writeLatency;
|
||||
/** Estimated number of tasks pending for this column family */
|
||||
public final Gauge<Integer> pendingTasks;
|
||||
public final Counter pendingFlushes;
|
||||
/** Number of SSTables on disk for this CF */
|
||||
public final Gauge<Integer> liveSSTableCount;
|
||||
/** Disk space used by SSTables belonging to this CF */
|
||||
|
|
@ -90,6 +93,9 @@ public class ColumnFamilyMetrics
|
|||
public final Timer coordinatorReadLatency;
|
||||
public final Timer coordinatorScanLatency;
|
||||
|
||||
/** Time spent waiting for free memtable space, either on- or off-heap */
|
||||
public final Timer waitingOnFreeMemtableSpace;
|
||||
|
||||
private final MetricNameFactory factory;
|
||||
|
||||
public final Counter speculativeRetries;
|
||||
|
|
@ -111,21 +117,41 @@ public class ColumnFamilyMetrics
|
|||
{
|
||||
public Long value()
|
||||
{
|
||||
return cfs.getDataTracker().getMemtable().getOperations();
|
||||
return cfs.getDataTracker().getView().getCurrentMemtable().getOperations();
|
||||
}
|
||||
});
|
||||
memtableDataSize = Metrics.newGauge(factory.createMetricName("MemtableDataSize"), new Gauge<Long>()
|
||||
memtableHeapSize = Metrics.newGauge(factory.createMetricName("MemtableHeapSize"), new Gauge<Long>()
|
||||
{
|
||||
public Long value()
|
||||
{
|
||||
return cfs.getDataTracker().getMemtable().getLiveSize();
|
||||
return cfs.getDataTracker().getView().getCurrentMemtable().getAllocator().owns();
|
||||
}
|
||||
});
|
||||
allMemtablesDataSize = Metrics.newGauge(factory.createMetricName("AllMemtablesDataSize"), new Gauge<Long>()
|
||||
memtableLiveDataSize = Metrics.newGauge(factory.createMetricName("MemtableLiveDataSize"), new Gauge<Long>()
|
||||
{
|
||||
public Long value()
|
||||
{
|
||||
return cfs.getTotalAllMemtablesLiveSize();
|
||||
return cfs.getDataTracker().getView().getCurrentMemtable().getLiveDataSize();
|
||||
}
|
||||
});
|
||||
allMemtablesHeapSize = Metrics.newGauge(factory.createMetricName("AllMemtablesHeapSize"), new Gauge<Long>()
|
||||
{
|
||||
public Long value()
|
||||
{
|
||||
long size = 0;
|
||||
for (ColumnFamilyStore cfs2 : cfs.concatWithIndexes())
|
||||
size += cfs2.getDataTracker().getView().getCurrentMemtable().getAllocator().owns();
|
||||
return size;
|
||||
}
|
||||
});
|
||||
allMemtablesLiveDataSize = Metrics.newGauge(factory.createMetricName("AllMemtablesLiveDataSize"), new Gauge<Long>()
|
||||
{
|
||||
public Long value()
|
||||
{
|
||||
long size = 0;
|
||||
for (ColumnFamilyStore cfs2 : cfs.concatWithIndexes())
|
||||
size += cfs2.getDataTracker().getView().getCurrentMemtable().getLiveDataSize();
|
||||
return size;
|
||||
}
|
||||
});
|
||||
memtableSwitchCount = Metrics.newCounter(factory.createMetricName("MemtableSwitchCount"));
|
||||
|
|
@ -177,14 +203,7 @@ public class ColumnFamilyMetrics
|
|||
});
|
||||
readLatency = new LatencyMetrics(factory, "Read");
|
||||
writeLatency = new LatencyMetrics(factory, "Write");
|
||||
pendingTasks = Metrics.newGauge(factory.createMetricName("PendingTasks"), new Gauge<Integer>()
|
||||
{
|
||||
public Integer value()
|
||||
{
|
||||
// TODO this actually isn't a good measure of pending tasks
|
||||
return Keyspace.switchLock.getQueueLength();
|
||||
}
|
||||
});
|
||||
pendingFlushes = Metrics.newCounter(factory.createMetricName("PendingFlushes"));
|
||||
liveSSTableCount = Metrics.newGauge(factory.createMetricName("LiveSSTableCount"), new Gauge<Integer>()
|
||||
{
|
||||
public Integer value()
|
||||
|
|
@ -319,7 +338,8 @@ public class ColumnFamilyMetrics
|
|||
liveScannedHistogram = Metrics.newHistogram(factory.createMetricName("LiveScannedHistogram"), true);
|
||||
coordinatorReadLatency = Metrics.newTimer(factory.createMetricName("CoordinatorReadLatency"), TimeUnit.MICROSECONDS, TimeUnit.SECONDS);
|
||||
coordinatorScanLatency = Metrics.newTimer(factory.createMetricName("CoordinatorScanLatency"), TimeUnit.MICROSECONDS, TimeUnit.SECONDS);
|
||||
|
||||
waitingOnFreeMemtableSpace = Metrics.newTimer(factory.createMetricName("WaitingOnFreeMemtableSpace"), TimeUnit.MICROSECONDS, TimeUnit.SECONDS);
|
||||
|
||||
trueSnapshotsSize = Metrics.newGauge(factory.createMetricName("SnapshotsSize"), new Gauge<Long>()
|
||||
{
|
||||
public Long value()
|
||||
|
|
@ -400,4 +420,5 @@ public class ColumnFamilyMetrics
|
|||
return new MetricName(groupName, type, metricName, keyspaceName + "." + columnFamilyName, mbeanName.toString());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,9 +20,12 @@ package org.apache.cassandra.metrics;
|
|||
import com.yammer.metrics.Metrics;
|
||||
import com.yammer.metrics.core.Gauge;
|
||||
|
||||
import com.yammer.metrics.core.Timer;
|
||||
import org.apache.cassandra.db.commitlog.AbstractCommitLogService;
|
||||
import org.apache.cassandra.db.commitlog.CommitLogSegmentManager;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Metrics for commit log
|
||||
*/
|
||||
|
|
@ -36,6 +39,10 @@ public class CommitLogMetrics
|
|||
public final Gauge<Long> pendingTasks;
|
||||
/** Current size used by all the commit log segments */
|
||||
public final Gauge<Long> totalCommitLogSize;
|
||||
/** Time spent waiting for a CLS to be allocated - under normal conditions this should be zero */
|
||||
public final Timer waitingOnSegmentAllocation;
|
||||
/** The time spent waiting on CL sync; for Periodic this is only occurs when the sync is lagging its sync interval */
|
||||
public final Timer waitingOnCommit;
|
||||
|
||||
public CommitLogMetrics(final AbstractCommitLogService service, final CommitLogSegmentManager allocator)
|
||||
{
|
||||
|
|
@ -60,5 +67,7 @@ public class CommitLogMetrics
|
|||
return allocator.bytesUsed();
|
||||
}
|
||||
});
|
||||
waitingOnSegmentAllocation = Metrics.newTimer(factory.createMetricName("WaitingOnSegmentAllocation"), TimeUnit.MICROSECONDS, TimeUnit.SECONDS);
|
||||
waitingOnCommit = Metrics.newTimer(factory.createMetricName("WaitingOnCommit"), TimeUnit.MICROSECONDS, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,7 +45,6 @@ import org.apache.cassandra.config.Schema;
|
|||
import org.apache.cassandra.db.ColumnFamilyStore;
|
||||
import org.apache.cassandra.db.Directories;
|
||||
import org.apache.cassandra.db.Keyspace;
|
||||
import org.apache.cassandra.db.MeteredFlusher;
|
||||
import org.apache.cassandra.db.SystemKeyspace;
|
||||
import org.apache.cassandra.db.commitlog.CommitLog;
|
||||
import org.apache.cassandra.db.compaction.CompactionManager;
|
||||
|
|
@ -258,10 +257,6 @@ public class CassandraDaemon
|
|||
logger.warn("Unable to start GCInspector (currently only supported on the Sun JVM)");
|
||||
}
|
||||
|
||||
// MeteredFlusher can block if flush queue fills up, so don't put on scheduledTasks
|
||||
// Start it before commit log, so memtables can flush during commit log replay
|
||||
StorageService.optionalTasks.scheduleWithFixedDelay(new MeteredFlusher(), 1000, 1000, TimeUnit.MILLISECONDS);
|
||||
|
||||
// replay the log if necessary
|
||||
try
|
||||
{
|
||||
|
|
|
|||
|
|
@ -245,13 +245,6 @@ public class FBUtilities
|
|||
return out;
|
||||
}
|
||||
|
||||
public static BigInteger hashToBigInteger(ByteBuffer data)
|
||||
{
|
||||
byte[] result = hash(data);
|
||||
BigInteger hash = new BigInteger(result);
|
||||
return hash.abs();
|
||||
}
|
||||
|
||||
public static byte[] hash(ByteBuffer... data)
|
||||
{
|
||||
MessageDigest messageDigest = localMD5Digest.get();
|
||||
|
|
@ -266,6 +259,11 @@ public class FBUtilities
|
|||
return messageDigest.digest();
|
||||
}
|
||||
|
||||
public static BigInteger hashToBigInteger(ByteBuffer data)
|
||||
{
|
||||
return new BigInteger(hash(data)).abs();
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static void serialize(TSerializer serializer, TBase struct, DataOutput out)
|
||||
throws IOException
|
||||
|
|
|
|||
|
|
@ -21,242 +21,143 @@ package org.apache.cassandra.utils;
|
|||
*/
|
||||
|
||||
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.lang.management.MemoryPoolMXBean;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.github.jamm.MemoryLayoutSpecification;
|
||||
import org.github.jamm.MemoryMeter;
|
||||
|
||||
/**
|
||||
* Modified version of the code from.
|
||||
* https://github.com/twitter/commons/blob/master
|
||||
* /src/java/com/twitter/common/objectsize/ObjectSizeCalculator.java
|
||||
*
|
||||
* Difference is that we don't use reflection.
|
||||
* A convenience class for wrapping access to MemoryMeter
|
||||
*/
|
||||
public class ObjectSizes
|
||||
{
|
||||
public static final MemoryLayoutSpecification SPEC = getEffectiveMemoryLayoutSpecification();
|
||||
private static final MemoryMeter meter = new MemoryMeter().omitSharedBufferOverhead();
|
||||
private static final MemoryMeter meter = new MemoryMeter()
|
||||
.omitSharedBufferOverhead()
|
||||
.withGuessing(MemoryMeter.Guess.FALLBACK_UNSAFE);
|
||||
|
||||
/**
|
||||
* Describes constant memory overheads for various constructs in a JVM
|
||||
* implementation.
|
||||
*/
|
||||
public interface MemoryLayoutSpecification
|
||||
{
|
||||
int getArrayHeaderSize();
|
||||
|
||||
int getObjectHeaderSize();
|
||||
|
||||
int getObjectPadding();
|
||||
|
||||
int getReferenceSize();
|
||||
|
||||
int getSuperclassFieldPadding();
|
||||
}
|
||||
|
||||
/**
|
||||
* Memory a class consumes, including the object header and the size of the fields.
|
||||
* @param fieldsSize Total size of the primitive fields of a class
|
||||
* @return Total in-memory size of the class
|
||||
*/
|
||||
public static long getFieldSize(long fieldsSize)
|
||||
{
|
||||
return roundTo(SPEC.getObjectHeaderSize() + fieldsSize, SPEC.getObjectPadding());
|
||||
}
|
||||
|
||||
/**
|
||||
* Memory a super class consumes, given the primitive field sizes
|
||||
* @param fieldsSize Total size of the primitive fields of the super class
|
||||
* @return Total additional in-memory that the super class takes up
|
||||
*/
|
||||
public static long getSuperClassFieldSize(long fieldsSize)
|
||||
{
|
||||
return roundTo(fieldsSize, SPEC.getSuperclassFieldPadding());
|
||||
}
|
||||
|
||||
/**
|
||||
* Memory an array will consume
|
||||
* @param length Number of elements in the array
|
||||
* @param elementSize In-memory size of each element's primitive stored
|
||||
* @return In-memory size of the array
|
||||
*/
|
||||
public static long getArraySize(int length, long elementSize)
|
||||
{
|
||||
return roundTo(SPEC.getArrayHeaderSize() + length * elementSize, SPEC.getObjectPadding());
|
||||
}
|
||||
private static final long BUFFER_EMPTY_SIZE = measure(ByteBufferUtil.EMPTY_BYTE_BUFFER);
|
||||
private static final long STRING_EMPTY_SIZE = measure("");
|
||||
|
||||
/**
|
||||
* Memory a byte array consumes
|
||||
* @param bytes byte array to get memory size
|
||||
* @return In-memory size of the array
|
||||
* @return heap-size of the array
|
||||
*/
|
||||
public static long getArraySize(byte[] bytes)
|
||||
public static long sizeOfArray(byte[] bytes)
|
||||
{
|
||||
return getArraySize(bytes.length, 1);
|
||||
return sizeOfArray(bytes.length, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Memory a long array consumes
|
||||
* @param longs byte array to get memory size
|
||||
* @return heap-size of the array
|
||||
*/
|
||||
public static long sizeOfArray(long[] longs)
|
||||
{
|
||||
return sizeOfArray(longs.length, 8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Memory an int array consumes
|
||||
* @param ints byte array to get memory size
|
||||
* @return heap-size of the array
|
||||
*/
|
||||
public static long sizeOfArray(int[] ints)
|
||||
{
|
||||
return sizeOfArray(ints.length, 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Memory a reference array consumes
|
||||
* @param length the length of the reference array
|
||||
* @return heap-size of the array
|
||||
*/
|
||||
public static long sizeOfReferenceArray(int length)
|
||||
{
|
||||
return sizeOfArray(length, MemoryLayoutSpecification.SPEC.getReferenceSize());
|
||||
}
|
||||
|
||||
/**
|
||||
* Memory a reference array consumes itself only
|
||||
* @param objects the array to size
|
||||
* @return heap-size of the array (excluding memory retained by referenced objects)
|
||||
*/
|
||||
public static long sizeOfArray(Object[] objects)
|
||||
{
|
||||
return sizeOfReferenceArray(objects.length);
|
||||
}
|
||||
|
||||
private static long sizeOfArray(int length, long elementSize)
|
||||
{
|
||||
return MemoryLayoutSpecification.sizeOfArray(length, elementSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Memory a ByteBuffer array consumes.
|
||||
*/
|
||||
public static long getArraySize(ByteBuffer[] array)
|
||||
public static long sizeOnHeapOf(ByteBuffer[] array)
|
||||
{
|
||||
long allElementsSize = 0;
|
||||
for (int i = 0; i < array.length; i++)
|
||||
if (array[i] != null)
|
||||
allElementsSize += getSize(array[i]);
|
||||
allElementsSize += sizeOnHeapOf(array[i]);
|
||||
|
||||
return allElementsSize + getArraySize(array.length, getReferenceSize());
|
||||
return allElementsSize + sizeOfArray(array);
|
||||
}
|
||||
|
||||
public static long sizeOnHeapExcludingData(ByteBuffer[] array)
|
||||
{
|
||||
return BUFFER_EMPTY_SIZE * array.length + sizeOfArray(array);
|
||||
}
|
||||
/**
|
||||
* Memory a byte buffer consumes
|
||||
* @param buffer ByteBuffer to calculate in memory size
|
||||
* @return Total in-memory size of the byte buffer
|
||||
*/
|
||||
public static long getSize(ByteBuffer buffer)
|
||||
public static long sizeOnHeapOf(ByteBuffer buffer)
|
||||
{
|
||||
long size = 0;
|
||||
/* BB Class */
|
||||
// final byte[] hb;
|
||||
// final int offset;
|
||||
// boolean isReadOnly;
|
||||
size += ObjectSizes.getFieldSize(1L + 4 + ObjectSizes.getReferenceSize() + ObjectSizes.getArraySize(buffer.capacity(), 1));
|
||||
/* Super Class */
|
||||
// private int mark;
|
||||
// private int position;
|
||||
// private int limit;
|
||||
// private int capacity;
|
||||
size += ObjectSizes.getSuperClassFieldSize(4L + 4 + 4 + 4 + 8);
|
||||
return size;
|
||||
if (buffer.isDirect())
|
||||
return BUFFER_EMPTY_SIZE;
|
||||
// if we're only referencing a sub-portion of the ByteBuffer, don't count the array overhead (assume it's slab
|
||||
// allocated, so amortized over all the allocations the overhead is negligible and better to undercount than over)
|
||||
if (buffer.capacity() > buffer.remaining())
|
||||
return buffer.remaining();
|
||||
return BUFFER_EMPTY_SIZE + sizeOfArray(buffer.capacity(), 1);
|
||||
}
|
||||
|
||||
public static long roundTo(long x, int multiple)
|
||||
public static long sizeOnHeapExcludingData(ByteBuffer buffer)
|
||||
{
|
||||
return ((x + multiple - 1) / multiple) * multiple;
|
||||
return BUFFER_EMPTY_SIZE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Memory a reference consumes on the current architecture.
|
||||
* Memory a String consumes
|
||||
* @param str String to calculate memory size of
|
||||
* @return Total in-memory size of the String
|
||||
*/
|
||||
public static int getReferenceSize()
|
||||
public static long sizeOf(String str)
|
||||
{
|
||||
return SPEC.getReferenceSize();
|
||||
}
|
||||
|
||||
private static MemoryLayoutSpecification getEffectiveMemoryLayoutSpecification()
|
||||
{
|
||||
final String dataModel = System.getProperty("sun.arch.data.model");
|
||||
if ("32".equals(dataModel))
|
||||
{
|
||||
// Running with 32-bit data model
|
||||
return new MemoryLayoutSpecification()
|
||||
{
|
||||
public int getArrayHeaderSize()
|
||||
{
|
||||
return 12;
|
||||
}
|
||||
|
||||
public int getObjectHeaderSize()
|
||||
{
|
||||
return 8;
|
||||
}
|
||||
|
||||
public int getObjectPadding()
|
||||
{
|
||||
return 8;
|
||||
}
|
||||
|
||||
public int getReferenceSize()
|
||||
{
|
||||
return 4;
|
||||
}
|
||||
|
||||
public int getSuperclassFieldPadding()
|
||||
{
|
||||
return 4;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
final String strVmVersion = System.getProperty("java.vm.version");
|
||||
final int vmVersion = Integer.parseInt(strVmVersion.substring(0, strVmVersion.indexOf('.')));
|
||||
if (vmVersion >= 17)
|
||||
{
|
||||
long maxMemory = 0;
|
||||
for (MemoryPoolMXBean mp : ManagementFactory.getMemoryPoolMXBeans())
|
||||
{
|
||||
maxMemory += mp.getUsage().getMax();
|
||||
}
|
||||
if (maxMemory < 30L * 1024 * 1024 * 1024)
|
||||
{
|
||||
// HotSpot 17.0 and above use compressed OOPs below 30GB of RAM
|
||||
// total for all memory pools (yes, including code cache).
|
||||
return new MemoryLayoutSpecification()
|
||||
{
|
||||
public int getArrayHeaderSize()
|
||||
{
|
||||
return 16;
|
||||
}
|
||||
|
||||
public int getObjectHeaderSize()
|
||||
{
|
||||
return 12;
|
||||
}
|
||||
|
||||
public int getObjectPadding()
|
||||
{
|
||||
return 8;
|
||||
}
|
||||
|
||||
public int getReferenceSize()
|
||||
{
|
||||
return 4;
|
||||
}
|
||||
|
||||
public int getSuperclassFieldPadding()
|
||||
{
|
||||
return 4;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/* Worst case we over count. */
|
||||
|
||||
// In other cases, it's a 64-bit uncompressed OOPs object model
|
||||
return new MemoryLayoutSpecification()
|
||||
{
|
||||
public int getArrayHeaderSize()
|
||||
{
|
||||
return 24;
|
||||
}
|
||||
|
||||
public int getObjectHeaderSize()
|
||||
{
|
||||
return 16;
|
||||
}
|
||||
|
||||
public int getObjectPadding()
|
||||
{
|
||||
return 8;
|
||||
}
|
||||
|
||||
public int getReferenceSize()
|
||||
{
|
||||
return 8;
|
||||
}
|
||||
|
||||
public int getSuperclassFieldPadding()
|
||||
{
|
||||
return 8;
|
||||
}
|
||||
};
|
||||
return STRING_EMPTY_SIZE + sizeOfArray(str.length(), 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param pojo the object to measure
|
||||
* @return the size on the heap of the instance and all retained heap referenced by it, excluding portions of
|
||||
* ByteBuffer that are not directly referenced by it but including any other referenced that may also be retained
|
||||
* by other objects.
|
||||
*/
|
||||
public static long measureDeep(Object pojo)
|
||||
{
|
||||
return meter.measureDeep(pojo);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param pojo the object to measure
|
||||
* @return the size on the heap of the instance only, excluding any referenced objects
|
||||
*/
|
||||
public static long measure(Object pojo)
|
||||
{
|
||||
return meter.measure(pojo);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,264 +0,0 @@
|
|||
package org.apache.cassandra.utils;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
|
||||
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
|
||||
/**
|
||||
* <p>A relatively easy to use utility for general purpose thread signalling.</p>
|
||||
* <p>Usage on a thread awaiting a state change using a WaitQueue q is:</p>
|
||||
* <pre>
|
||||
* {@code
|
||||
* while (!conditionMet())
|
||||
* WaitSignal s = q.register();
|
||||
* if (!conditionMet()) // or, perhaps more correctly, !conditionChanged()
|
||||
* s.await();
|
||||
* else
|
||||
* s.cancel();
|
||||
* }
|
||||
* </pre>
|
||||
* A signalling thread, AFTER changing the state, then calls q.signal() to wake up one, or q.signalAll()
|
||||
* to wake up all, waiting threads.
|
||||
*
|
||||
* <p>A few notes on utilisation:</p>
|
||||
* <p>1. A thread will only exit await() when it has been signalled, but this does
|
||||
* not guarantee the condition has not been altered since it was signalled,
|
||||
* and depending on your design it is likely the outer condition will need to be
|
||||
* checked in a loop, though this is not always the case.</p>
|
||||
* <p>2. Each signal is single use, so must be re-registered after each await(). This is true even if it times out.</p>
|
||||
* <p>3. If you choose not to wait on the signal (because the condition has been met before you waited on it)
|
||||
* you must cancel() the signal if the signalling thread uses signal() to awake waiters; otherwise signals will be
|
||||
* lost</p>
|
||||
* <p>4. Care must be taken when selecting conditionMet() to ensure we are waiting on the condition that actually
|
||||
* indicates progress is possible. In some complex cases it may be tempting to wait on a condition that is only indicative
|
||||
* of local progress, not progress on the task we are aiming to complete, and a race may leave us waiting for a condition
|
||||
* to be met that we no longer need.
|
||||
* <p>5. This scheme is not fair</p>
|
||||
* <p>6. Only the thread that calls register() may call await()</p>
|
||||
* <p>To understand intuitively how this class works, the idea is simply that a thread, once it considers itself
|
||||
* incapable of making progress, registers itself to be awoken once that condition changes. However, that condition
|
||||
* could have changed between checking and registering (in which case a thread updating the state would have been unable to signal it),
|
||||
* so before going to sleep on the signal, it checks the condition again, sleeping only if it hasn't changed.</p>
|
||||
*/
|
||||
// TODO : switch to a Lock Free queue
|
||||
public final class WaitQueue
|
||||
{
|
||||
public final class Signal
|
||||
{
|
||||
private final Thread thread = Thread.currentThread();
|
||||
volatile int signalled;
|
||||
|
||||
private boolean isSignalled()
|
||||
{
|
||||
return signalled == 1;
|
||||
}
|
||||
|
||||
public boolean isCancelled()
|
||||
{
|
||||
return signalled == -1;
|
||||
}
|
||||
|
||||
private boolean signal()
|
||||
{
|
||||
if (signalledUpdater.compareAndSet(this, 0, 1))
|
||||
{
|
||||
LockSupport.unpark(thread);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void awaitUninterruptibly()
|
||||
{
|
||||
assert !isCancelled();
|
||||
if (thread != Thread.currentThread())
|
||||
throw new IllegalStateException();
|
||||
boolean interrupted = false;
|
||||
while (!isSignalled())
|
||||
{
|
||||
if (Thread.interrupted())
|
||||
interrupted = true;
|
||||
LockSupport.park();
|
||||
}
|
||||
if (interrupted)
|
||||
thread.interrupt();
|
||||
}
|
||||
|
||||
public void await() throws InterruptedException
|
||||
{
|
||||
assert !isCancelled();
|
||||
while (!isSignalled())
|
||||
{
|
||||
if (Thread.interrupted())
|
||||
{
|
||||
checkAndClear();
|
||||
throw new InterruptedException();
|
||||
}
|
||||
if (thread != Thread.currentThread())
|
||||
throw new IllegalStateException();
|
||||
LockSupport.park();
|
||||
}
|
||||
}
|
||||
|
||||
public long awaitNanos(long nanosTimeout) throws InterruptedException
|
||||
{
|
||||
assert signalled != -1;
|
||||
long start = System.nanoTime();
|
||||
while (!isSignalled())
|
||||
{
|
||||
if (Thread.interrupted())
|
||||
{
|
||||
checkAndClear();
|
||||
throw new InterruptedException();
|
||||
}
|
||||
LockSupport.parkNanos(nanosTimeout);
|
||||
}
|
||||
return nanosTimeout - (System.nanoTime() - start);
|
||||
}
|
||||
|
||||
public boolean await(long time, TimeUnit unit) throws InterruptedException
|
||||
{
|
||||
// ignores nanos atm
|
||||
long until = System.currentTimeMillis() + unit.toMillis(time);
|
||||
if (until < 0)
|
||||
until = Long.MAX_VALUE;
|
||||
return awaitUntil(until);
|
||||
}
|
||||
|
||||
public boolean awaitUntil(long until) throws InterruptedException
|
||||
{
|
||||
assert !isCancelled();
|
||||
while (until < System.currentTimeMillis() && !isSignalled())
|
||||
{
|
||||
if (Thread.interrupted())
|
||||
{
|
||||
checkAndClear();
|
||||
throw new InterruptedException();
|
||||
}
|
||||
LockSupport.parkUntil(until);
|
||||
}
|
||||
return checkAndClear();
|
||||
}
|
||||
|
||||
private boolean checkAndClear()
|
||||
{
|
||||
if (isSignalled())
|
||||
{
|
||||
signalled = -1;
|
||||
return true;
|
||||
}
|
||||
else if (signalledUpdater.compareAndSet(this, 0, -1))
|
||||
{
|
||||
cleanUpCancelled();
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// must now be signalled, as checkAndClear() can only be called by
|
||||
// owning thread if used correctly
|
||||
signalled = -1;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public void cancel()
|
||||
{
|
||||
if (signalled < 0)
|
||||
return;
|
||||
if (!signalledUpdater.compareAndSet(this, 0, -1))
|
||||
{
|
||||
signalled = -1;
|
||||
signal();
|
||||
cleanUpCancelled();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final AtomicIntegerFieldUpdater signalledUpdater = AtomicIntegerFieldUpdater.newUpdater(Signal.class, "signalled");
|
||||
|
||||
// the waiting signals
|
||||
private final ConcurrentLinkedQueue<Signal> queue = new ConcurrentLinkedQueue<>();
|
||||
|
||||
/**
|
||||
* The calling thread MUST be the thread that uses the signal (for now)
|
||||
* @return
|
||||
*/
|
||||
public Signal register()
|
||||
{
|
||||
Signal signal = new Signal();
|
||||
queue.add(signal);
|
||||
return signal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Signal one waiting thread
|
||||
*/
|
||||
public void signal()
|
||||
{
|
||||
if (queue.isEmpty())
|
||||
return;
|
||||
Iterator<Signal> iter = queue.iterator();
|
||||
while (iter.hasNext())
|
||||
{
|
||||
Signal next = iter.next();
|
||||
if (next.signal())
|
||||
{
|
||||
iter.remove();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Signal all waiting threads
|
||||
*/
|
||||
public void signalAll()
|
||||
{
|
||||
if (queue.isEmpty())
|
||||
return;
|
||||
Iterator<Signal> iter = queue.iterator();
|
||||
while (iter.hasNext())
|
||||
{
|
||||
Signal next = iter.next();
|
||||
if (next.signal())
|
||||
iter.remove();
|
||||
}
|
||||
}
|
||||
|
||||
private void cleanUpCancelled()
|
||||
{
|
||||
Iterator<Signal> iter = queue.iterator();
|
||||
while (iter.hasNext())
|
||||
{
|
||||
Signal next = iter.next();
|
||||
if (next.isCancelled())
|
||||
iter.remove();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return how many threads are waiting
|
||||
* @return
|
||||
*/
|
||||
public int getWaiting()
|
||||
{
|
||||
if (queue.isEmpty())
|
||||
return 0;
|
||||
Iterator<Signal> iter = queue.iterator();
|
||||
int count = 0;
|
||||
while (iter.hasNext())
|
||||
{
|
||||
Signal next = iter.next();
|
||||
if (next.isCancelled())
|
||||
iter.remove();
|
||||
else
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import java.util.Collection;
|
|||
import java.util.Comparator;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import org.apache.cassandra.utils.ObjectSizes;
|
||||
import com.google.common.collect.Collections2;
|
||||
|
||||
public class BTree
|
||||
|
|
@ -73,7 +74,7 @@ public class BTree
|
|||
* @param <V>
|
||||
* @return
|
||||
*/
|
||||
public static <V> Object[] build(Collection<V> source, Comparator<V> comparator, boolean sorted)
|
||||
public static <V> Object[] build(Collection<V> source, Comparator<V> comparator, boolean sorted, UpdateFunction<V> updateF)
|
||||
{
|
||||
int size = source.size();
|
||||
|
||||
|
|
@ -84,6 +85,12 @@ public class BTree
|
|||
// inline sorting since we're already calling toArray
|
||||
if (!sorted)
|
||||
Arrays.sort(values, 0, size, comparator);
|
||||
if (updateF != null)
|
||||
{
|
||||
for (int i = 0 ; i < size ; i++)
|
||||
values[i] = updateF.apply(values[i]);
|
||||
updateF.allocated(ObjectSizes.sizeOfArray(values));
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
|
|
@ -105,7 +112,7 @@ public class BTree
|
|||
*/
|
||||
public static <V> Object[] update(Object[] btree, Comparator<V> comparator, Collection<V> updateWith, boolean updateWithIsSorted)
|
||||
{
|
||||
return update(btree, comparator, updateWith, updateWithIsSorted, null, null);
|
||||
return update(btree, comparator, updateWith, updateWithIsSorted, null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -115,9 +122,7 @@ public class BTree
|
|||
* @param comparator the comparator that defines the ordering over the items in the tree
|
||||
* @param updateWith the items to either insert / update
|
||||
* @param updateWithIsSorted if false, updateWith will be copied and sorted to facilitate construction
|
||||
* @param replaceF a function to apply to a pair we are swapping
|
||||
* @param terminateEarly a function that returns Boolean.TRUE if we should terminate before finishing our work.
|
||||
* the argument to terminateEarly is ignored.
|
||||
* @param updateF the update function to apply to any pairs we are swapping, and maybe abort early
|
||||
* @param <V>
|
||||
* @return
|
||||
*/
|
||||
|
|
@ -125,15 +130,10 @@ public class BTree
|
|||
Comparator<V> comparator,
|
||||
Collection<V> updateWith,
|
||||
boolean updateWithIsSorted,
|
||||
ReplaceFunction<V> replaceF,
|
||||
Function<?, Boolean> terminateEarly)
|
||||
UpdateFunction<V> updateF)
|
||||
{
|
||||
if (btree.length == 0)
|
||||
{
|
||||
if (replaceF != null)
|
||||
updateWith = Collections2.transform(updateWith, replaceF);
|
||||
return build(updateWith, comparator, updateWithIsSorted);
|
||||
}
|
||||
return build(updateWith, comparator, updateWithIsSorted, updateF);
|
||||
|
||||
if (!updateWithIsSorted)
|
||||
updateWith = sorted(updateWith, comparator, updateWith.size());
|
||||
|
|
@ -167,13 +167,13 @@ public class BTree
|
|||
{
|
||||
// apply replaceF if it matches an existing element
|
||||
btreeOffset++;
|
||||
if (replaceF != null)
|
||||
v = replaceF.apply((V) btree[i], v);
|
||||
if (updateF != null)
|
||||
v = updateF.apply((V) btree[i], v);
|
||||
}
|
||||
else if (replaceF != null)
|
||||
else if (updateF != null)
|
||||
{
|
||||
// new element but still need to apply replaceF to handle indexing and size-tracking
|
||||
v = replaceF.apply(v);
|
||||
v = updateF.apply(v);
|
||||
}
|
||||
|
||||
merged[mergedCount++] = v;
|
||||
|
|
@ -187,19 +187,15 @@ public class BTree
|
|||
mergedCount += count;
|
||||
}
|
||||
|
||||
if (mergedCount > FAN_FACTOR)
|
||||
{
|
||||
// TODO this code will never execute since QUICK_MERGE_LIMIT == FAN_FACTOR
|
||||
int mid = (mergedCount >> 1) & ~1; // divide by two, rounding down to an even number
|
||||
return new Object[] { merged[mid],
|
||||
Arrays.copyOfRange(merged, 0, mid),
|
||||
Arrays.copyOfRange(merged, 1 + mid, mergedCount + ((mergedCount + 1) & 1)), };
|
||||
}
|
||||
assert mergedCount <= FAN_FACTOR;
|
||||
|
||||
return Arrays.copyOfRange(merged, 0, mergedCount + (mergedCount & 1));
|
||||
Object[] r = Arrays.copyOfRange(merged, 0, mergedCount + (mergedCount & 1));
|
||||
if (updateF != null)
|
||||
updateF.allocated(ObjectSizes.sizeOfArray(r) - (btree.length == 0 ? 0 : ObjectSizes.sizeOfArray(btree)));
|
||||
return r;
|
||||
}
|
||||
|
||||
return modifier.get().update(btree, comparator, updateWith, replaceF, terminateEarly);
|
||||
return modifier.get().update(btree, comparator, updateWith, updateF);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ public class BTreeSet<V> implements NavigableSet<V>
|
|||
|
||||
public BTreeSet<V> update(Collection<V> updateWith, boolean isSorted)
|
||||
{
|
||||
return new BTreeSet<>(BTree.update(tree, comparator, updateWith, isSorted, null, null), comparator);
|
||||
return new BTreeSet<>(BTree.update(tree, comparator, updateWith, isSorted, null), comparator);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -3,9 +3,6 @@ package org.apache.cassandra.utils.btree;
|
|||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
|
||||
import static org.apache.cassandra.utils.btree.BTree.EMPTY_BRANCH;
|
||||
import static org.apache.cassandra.utils.btree.BTree.EMPTY_LEAF;
|
||||
import static org.apache.cassandra.utils.btree.BTree.FAN_SHIFT;
|
||||
import static org.apache.cassandra.utils.btree.BTree.POSITIVE_INFINITY;
|
||||
|
|
@ -40,21 +37,21 @@ final class Builder
|
|||
* we assume @param source has been sorted, e.g. by BTree.update, so the update of each key resumes where
|
||||
* the previous left off.
|
||||
*/
|
||||
public <V> Object[] update(Object[] btree, Comparator<V> comparator, Collection<V> source, ReplaceFunction<V> replaceF, Function<?, Boolean> terminateEarly)
|
||||
public <V> Object[] update(Object[] btree, Comparator<V> comparator, Collection<V> source, UpdateFunction<V> updateF)
|
||||
{
|
||||
NodeBuilder current = rootBuilder;
|
||||
current.reset(btree, POSITIVE_INFINITY);
|
||||
current.reset(btree, POSITIVE_INFINITY, updateF, comparator);
|
||||
|
||||
for (V key : source)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (terminateEarly != null && terminateEarly.apply(null) == Boolean.TRUE)
|
||||
if (updateF != null && updateF.abortEarly())
|
||||
{
|
||||
rootBuilder.clear();
|
||||
return null;
|
||||
}
|
||||
NodeBuilder next = current.update(key, comparator, replaceF);
|
||||
NodeBuilder next = current.update(key);
|
||||
if (next == null)
|
||||
break;
|
||||
// we were in a subtree from a previous key that didn't contain this new key;
|
||||
|
|
@ -66,7 +63,7 @@ final class Builder
|
|||
// finish copying any remaining keys from the original btree
|
||||
while (true)
|
||||
{
|
||||
NodeBuilder next = current.update(POSITIVE_INFINITY, comparator, replaceF);
|
||||
NodeBuilder next = current.update(POSITIVE_INFINITY);
|
||||
if (next == null)
|
||||
break;
|
||||
current = next;
|
||||
|
|
@ -88,9 +85,9 @@ final class Builder
|
|||
while ((size >>= FAN_SHIFT) > 0)
|
||||
current = current.ensureChild();
|
||||
|
||||
current.reset(EMPTY_LEAF, POSITIVE_INFINITY);
|
||||
current.reset(EMPTY_LEAF, POSITIVE_INFINITY, null, null);
|
||||
for (V key : source)
|
||||
current.addNewKey(key, null);
|
||||
current.addNewKey(key);
|
||||
|
||||
current = current.ascendToRoot();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
package org.apache.cassandra.utils.btree;
|
||||
|
||||
import org.apache.cassandra.utils.ObjectSizes;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
|
||||
|
|
@ -38,6 +40,9 @@ final class NodeBuilder
|
|||
// the index of the first child node in copyFrom that has not yet been copied into the build arrays
|
||||
private int copyFromChildPosition;
|
||||
|
||||
private UpdateFunction updateFunction;
|
||||
private Comparator comparator;
|
||||
|
||||
// upper bound of range owned by this level; lets us know if we need to ascend back up the tree
|
||||
// for the next key we update when bsearch gives an insertion point past the end of the values
|
||||
// in the current node
|
||||
|
|
@ -51,7 +56,7 @@ final class NodeBuilder
|
|||
{
|
||||
if (current.upperBound != null)
|
||||
{
|
||||
current.reset(null, null);
|
||||
current.reset(null, null, null, null);
|
||||
Arrays.fill(current.buildKeys, 0, current.maxBuildKeyPosition, null);
|
||||
Arrays.fill(current.buildChildren, 0, current.maxBuildChildPosition, null);
|
||||
current.maxBuildChildPosition = current.maxBuildKeyPosition = 0;
|
||||
|
|
@ -61,10 +66,12 @@ final class NodeBuilder
|
|||
}
|
||||
|
||||
// reset counters/setup to copy from provided node
|
||||
void reset(Object[] copyFrom, Object upperBound)
|
||||
void reset(Object[] copyFrom, Object upperBound, UpdateFunction updateFunction, Comparator comparator)
|
||||
{
|
||||
this.copyFrom = copyFrom;
|
||||
this.upperBound = upperBound;
|
||||
this.updateFunction = updateFunction;
|
||||
this.comparator = comparator;
|
||||
maxBuildKeyPosition = Math.max(maxBuildKeyPosition, buildKeyPosition);
|
||||
maxBuildChildPosition = Math.max(maxBuildChildPosition, buildChildPosition);
|
||||
buildKeyPosition = 0;
|
||||
|
|
@ -81,7 +88,7 @@ final class NodeBuilder
|
|||
* a parent if we do not -- we got here from an earlier key -- and we need to ascend back up),
|
||||
* or null if we finished the update in this node.
|
||||
*/
|
||||
<V> NodeBuilder update(Object key, Comparator<V> comparator, ReplaceFunction<V> replaceF)
|
||||
<V> NodeBuilder update(Object key)
|
||||
{
|
||||
assert copyFrom != null;
|
||||
int copyFromKeyEnd = getKeyEnd(copyFrom);
|
||||
|
|
@ -104,9 +111,9 @@ final class NodeBuilder
|
|||
if (owns)
|
||||
{
|
||||
if (found)
|
||||
replaceNextKey(key, replaceF);
|
||||
replaceNextKey(key);
|
||||
else
|
||||
addNewKey(key, replaceF); // handles splitting parent if necessary via ensureRoom
|
||||
addNewKey(key); // handles splitting parent if necessary via ensureRoom
|
||||
|
||||
// done, so return null
|
||||
return null;
|
||||
|
|
@ -122,7 +129,7 @@ final class NodeBuilder
|
|||
if (found)
|
||||
{
|
||||
copyKeys(i);
|
||||
replaceNextKey(key, replaceF);
|
||||
replaceNextKey(key);
|
||||
copyChildren(i + 1);
|
||||
return null;
|
||||
}
|
||||
|
|
@ -135,7 +142,7 @@ final class NodeBuilder
|
|||
// so descend into the owning child
|
||||
Object newUpperBound = i < copyFromKeyEnd ? copyFrom[i] : upperBound;
|
||||
Object[] descendInto = (Object[]) copyFrom[copyFromKeyEnd + i];
|
||||
ensureChild().reset(descendInto, newUpperBound);
|
||||
ensureChild().reset(descendInto, newUpperBound, updateFunction, comparator);
|
||||
return child;
|
||||
}
|
||||
else
|
||||
|
|
@ -177,7 +184,7 @@ final class NodeBuilder
|
|||
Object[] toNode()
|
||||
{
|
||||
assert buildKeyPosition <= FAN_FACTOR && buildKeyPosition > 0 : buildKeyPosition;
|
||||
return buildFromRange(0, buildKeyPosition, isLeaf(copyFrom));
|
||||
return buildFromRange(0, buildKeyPosition, isLeaf(copyFrom), false);
|
||||
}
|
||||
|
||||
// finish up this level and pass any constructed children up to our parent, ensuring a parent exists
|
||||
|
|
@ -189,12 +196,12 @@ final class NodeBuilder
|
|||
{
|
||||
// split current node and move the midpoint into parent, with the two halves as children
|
||||
int mid = buildKeyPosition / 2;
|
||||
parent.addExtraChild(buildFromRange(0, mid, isLeaf), buildKeys[mid]);
|
||||
parent.finishChild(buildFromRange(mid + 1, buildKeyPosition - (mid + 1), isLeaf));
|
||||
parent.addExtraChild(buildFromRange(0, mid, isLeaf, true), buildKeys[mid]);
|
||||
parent.finishChild(buildFromRange(mid + 1, buildKeyPosition - (mid + 1), isLeaf, false));
|
||||
}
|
||||
else
|
||||
{
|
||||
parent.finishChild(buildFromRange(0, buildKeyPosition, isLeaf));
|
||||
parent.finishChild(buildFromRange(0, buildKeyPosition, isLeaf, false));
|
||||
}
|
||||
return parent;
|
||||
}
|
||||
|
|
@ -215,23 +222,23 @@ final class NodeBuilder
|
|||
}
|
||||
|
||||
// skips the next key in copyf, and puts the provided key in the builder instead
|
||||
private <V> void replaceNextKey(Object with, ReplaceFunction<V> replaceF)
|
||||
private <V> void replaceNextKey(Object with)
|
||||
{
|
||||
// (this first part differs from addNewKey in that we pass the replaced object to replaceF as well)
|
||||
ensureRoom(buildKeyPosition + 1);
|
||||
if (replaceF != null)
|
||||
with = replaceF.apply((V) copyFrom[copyFromKeyPosition], (V) with);
|
||||
if (updateFunction != null)
|
||||
with = updateFunction.apply((V) copyFrom[copyFromKeyPosition], (V) with);
|
||||
buildKeys[buildKeyPosition++] = with;
|
||||
|
||||
copyFromKeyPosition++;
|
||||
}
|
||||
|
||||
// puts the provided key in the builder, with no impact on treatment of data from copyf
|
||||
<V> void addNewKey(Object key, ReplaceFunction<V> replaceF)
|
||||
<V> void addNewKey(Object key)
|
||||
{
|
||||
ensureRoom(buildKeyPosition + 1);
|
||||
if (replaceF != null)
|
||||
key = replaceF.apply((V) key);
|
||||
if (updateFunction != null)
|
||||
key = updateFunction.apply((V) key);
|
||||
buildKeys[buildKeyPosition++] = key;
|
||||
}
|
||||
|
||||
|
|
@ -269,7 +276,7 @@ final class NodeBuilder
|
|||
return;
|
||||
|
||||
// flush even number of items so we don't waste leaf space repeatedly
|
||||
Object[] flushUp = buildFromRange(0, FAN_FACTOR, isLeaf(copyFrom));
|
||||
Object[] flushUp = buildFromRange(0, FAN_FACTOR, isLeaf(copyFrom), true);
|
||||
ensureParent().addExtraChild(flushUp, buildKeys[FAN_FACTOR]);
|
||||
int size = FAN_FACTOR + 1;
|
||||
assert size <= buildKeyPosition : buildKeyPosition + "," + nextBuildKeyPosition;
|
||||
|
|
@ -285,7 +292,7 @@ final class NodeBuilder
|
|||
}
|
||||
|
||||
// builds and returns a node from the buffered objects in the given range
|
||||
private Object[] buildFromRange(int offset, int keyLength, boolean isLeaf)
|
||||
private Object[] buildFromRange(int offset, int keyLength, boolean isLeaf, boolean isExtra)
|
||||
{
|
||||
Object[] a;
|
||||
if (isLeaf)
|
||||
|
|
@ -299,6 +306,14 @@ final class NodeBuilder
|
|||
System.arraycopy(buildKeys, offset, a, 0, keyLength);
|
||||
System.arraycopy(buildChildren, offset, a, keyLength, keyLength + 1);
|
||||
}
|
||||
if (updateFunction != null)
|
||||
{
|
||||
if (isExtra)
|
||||
updateFunction.allocated(ObjectSizes.sizeOfArray(a));
|
||||
else if (a.length != copyFrom.length)
|
||||
updateFunction.allocated(ObjectSizes.sizeOfArray(a) -
|
||||
(copyFrom.length == 0 ? 0 : ObjectSizes.sizeOfArray(copyFrom)));
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
|
|
@ -313,7 +328,7 @@ final class NodeBuilder
|
|||
parent.child = this;
|
||||
}
|
||||
if (parent.upperBound == null)
|
||||
parent.reset(EMPTY_BRANCH, upperBound);
|
||||
parent.reset(EMPTY_BRANCH, upperBound, updateFunction, comparator);
|
||||
return parent;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,17 +0,0 @@
|
|||
package org.apache.cassandra.utils.btree;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
|
||||
/**
|
||||
* An interface defining a function to be applied to both the object we are replacing in a BTree and
|
||||
* the object that is intended to replace it, returning the object to actually replace it.
|
||||
*
|
||||
* If this is a new insertion, that is there is no object to replace, the one argument variant of
|
||||
* the function will be called.
|
||||
*
|
||||
* @param <V>
|
||||
*/
|
||||
public interface ReplaceFunction<V> extends Function<V, V>
|
||||
{
|
||||
V apply(V replaced, V update);
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package org.apache.cassandra.utils.btree;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
|
||||
/**
|
||||
* An interface defining a function to be applied to both the object we are replacing in a BTree and
|
||||
* the object that is intended to replace it, returning the object to actually replace it.
|
||||
*
|
||||
* @param <V>
|
||||
*/
|
||||
public interface UpdateFunction<V> extends Function<V, V>
|
||||
{
|
||||
/**
|
||||
* @param replacing the value in the original tree we have matched
|
||||
* @param update the value in the updating collection that matched
|
||||
* @return the value to insert into the new tree
|
||||
*/
|
||||
V apply(V replacing, V update);
|
||||
|
||||
/**
|
||||
* @return true if we should fail the update
|
||||
*/
|
||||
boolean abortEarly();
|
||||
|
||||
/**
|
||||
* @param heapSize extra heap space allocated (over previous tree)
|
||||
*/
|
||||
void allocated(long heapSize);
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,411 @@
|
|||
package org.apache.cassandra.utils.concurrent;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
|
||||
|
||||
/**
|
||||
* <p>A class for providing synchronization between producers and consumers that do not
|
||||
* communicate directly with each other, but where the consumers need to process their
|
||||
* work in contiguous batches. In particular this is useful for both CommitLog and Memtable
|
||||
* where the producers (writing threads) are modifying a structure that the consumer
|
||||
* (flush executor) only batch syncs, but needs to know what 'position' the work is at
|
||||
* for co-ordination with other processes,
|
||||
*
|
||||
* <p>The typical usage is something like:
|
||||
* <pre>
|
||||
public final class ExampleShared
|
||||
{
|
||||
final OpOrder order = new OpOrder();
|
||||
volatile SharedState state;
|
||||
|
||||
static class SharedState
|
||||
{
|
||||
volatile Barrier barrier;
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
public void consume()
|
||||
{
|
||||
SharedState state = this.state;
|
||||
state.setReplacement(new State())
|
||||
state.doSomethingToPrepareForBarrier();
|
||||
|
||||
state.barrier = order.newBarrier();
|
||||
// seal() MUST be called after newBarrier() else barrier.isAfter()
|
||||
// will always return true, and barrier.await() will fail
|
||||
state.barrier.issue();
|
||||
|
||||
// wait for all producer work started prior to the barrier to complete
|
||||
state.barrier.await();
|
||||
|
||||
// change the shared state to its replacement, as the current state will no longer be used by producers
|
||||
this.state = state.getReplacement();
|
||||
|
||||
state.doSomethingWithExclusiveAccess();
|
||||
}
|
||||
|
||||
public void produce()
|
||||
{
|
||||
Group opGroup = order.start();
|
||||
try
|
||||
{
|
||||
SharedState s = state;
|
||||
while (s.barrier != null && !s.barrier.isAfter(opGroup))
|
||||
s = s.getReplacement();
|
||||
s.doProduceWork();
|
||||
}
|
||||
finally
|
||||
{
|
||||
opGroup.finishOne();
|
||||
}
|
||||
}
|
||||
}
|
||||
* </pre>
|
||||
*/
|
||||
public class OpOrder
|
||||
{
|
||||
/**
|
||||
* Constant that when an Ordered.running is equal to, indicates the Ordered is complete
|
||||
*/
|
||||
private static final int FINISHED = -1;
|
||||
|
||||
/**
|
||||
* A linked list starting with the most recent Ordered object, i.e. the one we should start new operations from,
|
||||
* with (prev) links to any incomplete Ordered instances, and (next) links to any potential future Ordered instances.
|
||||
* Once all operations started against an Ordered instance and its ancestors have been finished the next instance
|
||||
* will unlink this one
|
||||
*/
|
||||
private volatile Group current = new Group();
|
||||
|
||||
/**
|
||||
* Start an operation against this OpOrder.
|
||||
* Once the operation is completed Ordered.finishOne() MUST be called EXACTLY once for this operation.
|
||||
*
|
||||
* @return the Ordered instance that manages this OpOrder
|
||||
*/
|
||||
public Group start()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
Group current = this.current;
|
||||
if (current.register())
|
||||
return current;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new barrier. The barrier is only a placeholder until barrier.issue() is called on it,
|
||||
* after which all new operations will start against a new Group that will not be accepted
|
||||
* by barrier.isAfter(), and barrier.await() will return only once all operations started prior to the issue
|
||||
* have completed.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Barrier newBarrier()
|
||||
{
|
||||
return new Barrier();
|
||||
}
|
||||
|
||||
public Group getCurrent()
|
||||
{
|
||||
return current;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a group of identically ordered operations, i.e. all operations started in the interval between
|
||||
* two barrier issuances. For each register() call this is returned, finishOne() must be called exactly once.
|
||||
* It should be treated like taking a lock().
|
||||
*/
|
||||
public static final class Group implements Comparable<Group>
|
||||
{
|
||||
/**
|
||||
* In general this class goes through the following stages:
|
||||
* 1) LIVE: many calls to register() and finishOne()
|
||||
* 2) FINISHING: a call to expire() (after a barrier issue), means calls to register() will now fail,
|
||||
* and we are now 'in the past' (new operations will be started against a new Ordered)
|
||||
* 3) FINISHED: once the last finishOne() is called, this Ordered is done. We call unlink().
|
||||
* 4) ZOMBIE: all our operations are finished, but some operations against an earlier Ordered are still
|
||||
* running, or tidying up, so unlink() fails to remove us
|
||||
* 5) COMPLETE: all operations started on or before us are FINISHED (and COMPLETE), so we are unlinked
|
||||
* <p/>
|
||||
* Another parallel states is ISBLOCKING:
|
||||
* <p/>
|
||||
* isBlocking => a barrier that is waiting on us (either directly, or via a future Ordered) is blocking general
|
||||
* progress. This state is entered by calling Barrier.markBlocking(). If the running operations are blocked
|
||||
* on a Signal that is also registered with the isBlockingSignal (probably through isSafeBlockingSignal)
|
||||
* then they will be notified that they are blocking forward progress, and may take action to avoid that.
|
||||
*/
|
||||
|
||||
private volatile Group prev, next;
|
||||
private final long id; // monotonically increasing id for compareTo()
|
||||
private volatile int running = 0; // number of operations currently running. < 0 means we're expired, and the count of tasks still running is -(running + 1)
|
||||
private volatile boolean isBlocking; // indicates running operations are blocking future barriers
|
||||
private final WaitQueue isBlockingSignal = new WaitQueue(); // signal to wait on to indicate isBlocking is true
|
||||
private final WaitQueue waiting = new WaitQueue(); // signal to wait on for completion
|
||||
|
||||
static final AtomicIntegerFieldUpdater<Group> runningUpdater = AtomicIntegerFieldUpdater.newUpdater(Group.class, "running");
|
||||
|
||||
// constructs first instance only
|
||||
private Group()
|
||||
{
|
||||
this.id = 0;
|
||||
}
|
||||
|
||||
private Group(Group prev)
|
||||
{
|
||||
this.id = prev.id + 1;
|
||||
this.prev = prev;
|
||||
}
|
||||
|
||||
// prevents any further operations starting against this Ordered instance
|
||||
// if there are no running operations, calls unlink; otherwise, we let the last op to finishOne call it.
|
||||
// this means issue() won't have to block for ops to finish.
|
||||
private void expire()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
int current = running;
|
||||
if (current < 0)
|
||||
throw new IllegalStateException();
|
||||
if (runningUpdater.compareAndSet(this, current, -1 - current))
|
||||
{
|
||||
// if we're already finished (no running ops), unlink ourselves
|
||||
if (current == 0)
|
||||
unlink();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// attempts to start an operation against this Ordered instance, and returns true if successful.
|
||||
private boolean register()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
int current = running;
|
||||
if (current < 0)
|
||||
return false;
|
||||
if (runningUpdater.compareAndSet(this, current, current + 1))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* To be called exactly once for each register() call this object is returned for, indicating the operation
|
||||
* is complete
|
||||
*/
|
||||
public void finishOne()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
int current = running;
|
||||
if (current < 0)
|
||||
{
|
||||
if (runningUpdater.compareAndSet(this, current, current + 1))
|
||||
{
|
||||
if (current + 1 == FINISHED)
|
||||
{
|
||||
// if we're now finished, unlink ourselves
|
||||
unlink();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (runningUpdater.compareAndSet(this, current, current - 1))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* called once we know all operations started against this Ordered have completed,
|
||||
* however we do not know if operations against its ancestors have completed, or
|
||||
* if its descendants have completed ahead of it, so we attempt to create the longest
|
||||
* chain from the oldest still linked Ordered. If we can't reach the oldest through
|
||||
* an unbroken chain of completed Ordered, we abort, and leave the still completing
|
||||
* ancestor to tidy up.
|
||||
*/
|
||||
private void unlink()
|
||||
{
|
||||
// walk back in time to find the start of the list
|
||||
Group start = this;
|
||||
while (true)
|
||||
{
|
||||
Group prev = start.prev;
|
||||
if (prev == null)
|
||||
break;
|
||||
// if we haven't finished this Ordered yet abort and let it clean up when it's done
|
||||
if (prev.running != FINISHED)
|
||||
return;
|
||||
start = prev;
|
||||
}
|
||||
|
||||
// now walk forwards in time, in case we finished up late
|
||||
Group end = this.next;
|
||||
while (end.running == FINISHED)
|
||||
end = end.next;
|
||||
|
||||
// now walk from first to last, unlinking the prev pointer and waking up any blocking threads
|
||||
while (start != end)
|
||||
{
|
||||
Group next = start.next;
|
||||
next.prev = null;
|
||||
start.waiting.signalAll();
|
||||
start = next;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if a barrier we are behind is, or may be, blocking general progress,
|
||||
* so we should try more aggressively to progress
|
||||
*/
|
||||
public boolean isBlocking()
|
||||
{
|
||||
return isBlocking;
|
||||
}
|
||||
|
||||
/**
|
||||
* register to be signalled when a barrier waiting on us is, or maybe, blocking general progress,
|
||||
* so we should try more aggressively to progress
|
||||
*/
|
||||
public WaitQueue.Signal isBlockingSignal()
|
||||
{
|
||||
return isBlockingSignal.register();
|
||||
}
|
||||
|
||||
/**
|
||||
* wrap the provided signal to also be signalled if the operation gets marked blocking
|
||||
*/
|
||||
public WaitQueue.Signal isBlockingSignal(WaitQueue.Signal signal)
|
||||
{
|
||||
return WaitQueue.any(signal, isBlockingSignal());
|
||||
}
|
||||
|
||||
public int compareTo(Group that)
|
||||
{
|
||||
// we deliberately use subtraction, as opposed to Long.compareTo() as we care about ordering
|
||||
// not which is the smaller value, so this permits wrapping in the unlikely event we exhaust the long space
|
||||
long c = this.id - that.id;
|
||||
if (c > 0)
|
||||
return 1;
|
||||
else if (c < 0)
|
||||
return -1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class represents a synchronisation point providing ordering guarantees on operations started
|
||||
* against the enclosing OpOrder. When issue() is called upon it (may only happen once per Barrier), the
|
||||
* Barrier atomically partitions new operations from those already running (by expiring the current Group),
|
||||
* and activates its isAfter() method
|
||||
* which indicates if an operation was started before or after this partition. It offers methods to
|
||||
* determine, or block until, all prior operations have finished, and a means to indicate to those operations
|
||||
* that they are blocking forward progress. See {@link OpOrder} for idiomatic usage.
|
||||
*/
|
||||
public final class Barrier
|
||||
{
|
||||
// this Barrier was issued after all Group operations started against orderOnOrBefore
|
||||
private volatile Group orderOnOrBefore;
|
||||
|
||||
/**
|
||||
* @return true if @param group was started prior to the issuing of the barrier.
|
||||
*
|
||||
* (Until issue is called, always returns true, but if you rely on this behavior you are probably
|
||||
* Doing It Wrong.)
|
||||
*/
|
||||
public boolean isAfter(Group group)
|
||||
{
|
||||
if (orderOnOrBefore == null)
|
||||
return true;
|
||||
// we subtract to permit wrapping round the full range of Long - so we only need to ensure
|
||||
// there are never Long.MAX_VALUE * 2 total Group objects in existence at any one timem which will
|
||||
// take care of itself
|
||||
return orderOnOrBefore.id - group.id >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Issues (seals) the barrier, meaning no new operations may be issued against it, and expires the current
|
||||
* Group. Must be called before await() for isAfter() to be properly synchronised.
|
||||
*/
|
||||
public void issue()
|
||||
{
|
||||
if (orderOnOrBefore != null)
|
||||
throw new IllegalStateException("Can only call issue() once on each Barrier");
|
||||
|
||||
final Group current;
|
||||
synchronized (OpOrder.this)
|
||||
{
|
||||
current = OpOrder.this.current;
|
||||
orderOnOrBefore = current;
|
||||
OpOrder.this.current = current.next = new Group(current);
|
||||
}
|
||||
current.expire();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark all prior operations as blocking, potentially signalling them to more aggressively make progress
|
||||
*/
|
||||
public void markBlocking()
|
||||
{
|
||||
Group current = orderOnOrBefore;
|
||||
while (current != null)
|
||||
{
|
||||
current.isBlocking = true;
|
||||
current.isBlockingSignal.signalAll();
|
||||
current = current.prev;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register to be signalled once allPriorOpsAreFinished() or allPriorOpsAreFinishedOrSafe() may return true
|
||||
*/
|
||||
public WaitQueue.Signal register()
|
||||
{
|
||||
return orderOnOrBefore.waiting.register();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if all operations started prior to barrier.issue() have completed
|
||||
*/
|
||||
public boolean allPriorOpsAreFinished()
|
||||
{
|
||||
Group current = orderOnOrBefore;
|
||||
if (current == null)
|
||||
throw new IllegalStateException("This barrier needs to have issue() called on it before prior operations can complete");
|
||||
if (current.next.prev == null)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* wait for all operations started prior to issuing the barrier to complete
|
||||
*/
|
||||
public void await()
|
||||
{
|
||||
while (!allPriorOpsAreFinished())
|
||||
{
|
||||
WaitQueue.Signal signal = register();
|
||||
if (allPriorOpsAreFinished())
|
||||
{
|
||||
signal.cancel();
|
||||
return;
|
||||
}
|
||||
else
|
||||
signal.awaitUninterruptibly();
|
||||
}
|
||||
assert orderOnOrBefore.running == FINISHED;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the Group we are waiting on - any Group with .compareTo(getSyncPoint()) <= 0
|
||||
* must complete before await() returns
|
||||
*/
|
||||
public Group getSyncPoint()
|
||||
{
|
||||
return orderOnOrBefore;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,508 @@
|
|||
package org.apache.cassandra.utils.concurrent;
|
||||
|
||||
import com.yammer.metrics.core.TimerContext;
|
||||
import org.slf4j.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
|
||||
/**
|
||||
* <p>A relatively easy to use utility for general purpose thread signalling.</p>
|
||||
* <p>Usage on a thread awaiting a state change using a WaitQueue q is:</p>
|
||||
* <pre>
|
||||
* {@code
|
||||
* while (!conditionMet())
|
||||
* Signal s = q.register();
|
||||
* if (!conditionMet()) // or, perhaps more correctly, !conditionChanged()
|
||||
* s.await();
|
||||
* else
|
||||
* s.cancel();
|
||||
* }
|
||||
* </pre>
|
||||
* A signalling thread, AFTER changing the state, then calls q.signal() to wake up one, or q.signalAll()
|
||||
* to wake up all, waiting threads.
|
||||
* <p>To understand intuitively how this class works, the idea is simply that a thread, once it considers itself
|
||||
* incapable of making progress, registers to be awoken once that changes. Since this could have changed between
|
||||
* checking and registering (in which case the thread that made this change would have been unable to signal it),
|
||||
* it checks the condition again, sleeping only if it hasn't changed/still is not met.</p>
|
||||
* <p>This thread synchronisation scheme has some advantages over Condition objects and Object.wait/notify in that no monitor
|
||||
* acquisition is necessary and, in fact, besides the actual waiting on a signal, all operations are non-blocking.
|
||||
* As a result consumers can never block producers, nor each other, or vice versa, from making progress.
|
||||
* Threads that are signalled are also put into a RUNNABLE state almost simultaneously, so they can all immediately make
|
||||
* progress without having to serially acquire the monitor/lock, reducing scheduler delay incurred.</p>
|
||||
*
|
||||
* <p>A few notes on utilisation:</p>
|
||||
* <p>1. A thread will only exit await() when it has been signalled, but this does not guarantee the condition has not
|
||||
* been altered since it was signalled, and depending on your design it is likely the outer condition will need to be
|
||||
* checked in a loop, though this is not always the case.</p>
|
||||
* <p>2. Each signal is single use, so must be re-registered after each await(). This is true even if it times out.</p>
|
||||
* <p>3. If you choose not to wait on the signal (because the condition has been met before you waited on it)
|
||||
* you must cancel() the signal if the signalling thread uses signal() to awake waiters; otherwise signals will be
|
||||
* lost. If signalAll() is used but infrequent, and register() is frequent, cancel() should still be used to prevent the
|
||||
* queue growing unboundedly. Similarly, if you provide a TimerContext, cancel should be used to ensure it is not erroneously
|
||||
* counted towards wait time.</p>
|
||||
* <p>4. Care must be taken when selecting conditionMet() to ensure we are waiting on the condition that actually
|
||||
* indicates progress is possible. In some complex cases it may be tempting to wait on a condition that is only indicative
|
||||
* of local progress, not progress on the task we are aiming to complete, and a race may leave us waiting for a condition
|
||||
* to be met that we no longer need.
|
||||
* <p>5. This scheme is not fair</p>
|
||||
* <p>6. Only the thread that calls register() may call await()</p>
|
||||
*/
|
||||
public final class WaitQueue
|
||||
{
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(WaitQueue.class);
|
||||
|
||||
private static final int CANCELLED = -1;
|
||||
private static final int SIGNALLED = 1;
|
||||
private static final int NOT_SET = 0;
|
||||
|
||||
private static final AtomicIntegerFieldUpdater signalledUpdater = AtomicIntegerFieldUpdater.newUpdater(RegisteredSignal.class, "state");
|
||||
|
||||
// the waiting signals
|
||||
private final ConcurrentLinkedDeque<RegisteredSignal> queue = new ConcurrentLinkedDeque<>();
|
||||
|
||||
/**
|
||||
* The calling thread MUST be the thread that uses the signal
|
||||
* @return
|
||||
*/
|
||||
public Signal register()
|
||||
{
|
||||
RegisteredSignal signal = new RegisteredSignal();
|
||||
queue.add(signal);
|
||||
return signal;
|
||||
}
|
||||
|
||||
/**
|
||||
* The calling thread MUST be the thread that uses the signal.
|
||||
* If the Signal is waited on, context.stop() will be called when the wait times out, the Signal is signalled,
|
||||
* or the waiting thread is interrupted.
|
||||
* @return
|
||||
*/
|
||||
public Signal register(TimerContext context)
|
||||
{
|
||||
assert context != null;
|
||||
RegisteredSignal signal = new TimedSignal(context);
|
||||
queue.add(signal);
|
||||
return signal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Signal one waiting thread
|
||||
*/
|
||||
public boolean signal()
|
||||
{
|
||||
if (!hasWaiters())
|
||||
return false;
|
||||
while (true)
|
||||
{
|
||||
RegisteredSignal s = queue.poll();
|
||||
if (s == null || s.signal())
|
||||
return s != null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Signal all waiting threads
|
||||
*/
|
||||
public void signalAll()
|
||||
{
|
||||
if (!hasWaiters())
|
||||
return;
|
||||
List<Thread> woke = null;
|
||||
if (logger.isTraceEnabled())
|
||||
woke = new ArrayList<>();
|
||||
long start = System.nanoTime();
|
||||
// we wake up only a snapshot of the queue, to avoid a race where the condition is not met and the woken thread
|
||||
// immediately waits on the queue again
|
||||
RegisteredSignal last = queue.getLast();
|
||||
Iterator<RegisteredSignal> iter = queue.iterator();
|
||||
while (iter.hasNext())
|
||||
{
|
||||
RegisteredSignal signal = iter.next();
|
||||
if (logger.isTraceEnabled())
|
||||
{
|
||||
Thread thread = signal.thread;
|
||||
if (signal.signal())
|
||||
woke.add(thread);
|
||||
}
|
||||
else
|
||||
signal.signal();
|
||||
|
||||
iter.remove();
|
||||
|
||||
if (signal == last)
|
||||
break;
|
||||
}
|
||||
long end = System.nanoTime();
|
||||
if (woke != null)
|
||||
logger.trace("Woke up {} in {}ms from {}", woke, (end - start) * 0.000001d, Thread.currentThread().getStackTrace()[2]);
|
||||
}
|
||||
|
||||
private void cleanUpCancelled()
|
||||
{
|
||||
// attempt to remove the cancelled from the beginning only, but if we fail to remove any proceed to cover
|
||||
// the whole list
|
||||
Iterator<RegisteredSignal> iter = queue.iterator();
|
||||
while (iter.hasNext())
|
||||
{
|
||||
RegisteredSignal s = iter.next();
|
||||
if (s.isCancelled())
|
||||
iter.remove();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasWaiters()
|
||||
{
|
||||
return !queue.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return how many threads are waiting
|
||||
* @return
|
||||
*/
|
||||
public int getWaiting()
|
||||
{
|
||||
if (queue.isEmpty())
|
||||
return 0;
|
||||
Iterator<RegisteredSignal> iter = queue.iterator();
|
||||
int count = 0;
|
||||
while (iter.hasNext())
|
||||
{
|
||||
Signal next = iter.next();
|
||||
if (!next.isCancelled())
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* A Signal is a one-time-use mechanism for a thread to wait for notification that some condition
|
||||
* state has transitioned that it may be interested in (and hence should check if it is).
|
||||
* It is potentially transient, i.e. the state can change in the meantime, it only indicates
|
||||
* that it should be checked, not necessarily anything about what the expected state should be.
|
||||
*
|
||||
* Signal implementations should never wake up spuriously, they are always woken up by a
|
||||
* signal() or signalAll().
|
||||
*
|
||||
* This abstract definition of Signal does not need to be tied to a WaitQueue.
|
||||
* Whilst RegisteredSignal is the main building block of Signals, this abstract
|
||||
* definition allows us to compose Signals in useful ways. The Signal is 'owned' by the
|
||||
* thread that registered itself with WaitQueue(s) to obtain the underlying RegisteredSignal(s);
|
||||
* only the owning thread should use a Signal.
|
||||
*/
|
||||
public static interface Signal
|
||||
{
|
||||
|
||||
/**
|
||||
* @return true if signalled; once true, must be discarded by the owning thread.
|
||||
*/
|
||||
public boolean isSignalled();
|
||||
|
||||
/**
|
||||
* @return true if cancelled; once cancelled, must be discarded by the owning thread.
|
||||
*/
|
||||
public boolean isCancelled();
|
||||
|
||||
/**
|
||||
* @return isSignalled() || isCancelled(). Once true, the state is fixed and the Signal should be discarded
|
||||
* by the owning thread.
|
||||
*/
|
||||
public boolean isSet();
|
||||
|
||||
/**
|
||||
* atomically: cancels the Signal if !isSet(), or returns true if isSignalled()
|
||||
*
|
||||
* @return true if isSignalled()
|
||||
*/
|
||||
public boolean checkAndClear();
|
||||
|
||||
/**
|
||||
* Should only be called by the owning thread. Indicates the signal can be retired,
|
||||
* and if signalled propagates the signal to another waiting thread
|
||||
*/
|
||||
public abstract void cancel();
|
||||
|
||||
/**
|
||||
* Wait, without throwing InterruptedException, until signalled. On exit isSignalled() must be true.
|
||||
* If the thread is interrupted in the meantime, the interrupted flag will be set.
|
||||
*/
|
||||
public void awaitUninterruptibly();
|
||||
|
||||
/**
|
||||
* Wait until signalled, or throw an InterruptedException if interrupted before this happens.
|
||||
* On normal exit isSignalled() must be true; however if InterruptedException is thrown isCancelled()
|
||||
* will be true.
|
||||
* @throws InterruptedException
|
||||
*/
|
||||
public void await() throws InterruptedException;
|
||||
|
||||
/**
|
||||
* Wait until signalled, or the provided time is reached, or the thread is interrupted. If signalled,
|
||||
* isSignalled() will be true on exit, and the method will return true; if timedout, the method will return
|
||||
* false and isCancelled() will be true; if interrupted an InterruptedException will be thrown and isCancelled()
|
||||
* will be true.
|
||||
* @param until System.currentTimeMillis() to wait until
|
||||
* @return true if signalled, false if timed out
|
||||
* @throws InterruptedException
|
||||
*/
|
||||
public boolean awaitUntil(long until) throws InterruptedException;
|
||||
}
|
||||
|
||||
/**
|
||||
* An abstract signal implementation
|
||||
*/
|
||||
public static abstract class AbstractSignal implements Signal
|
||||
{
|
||||
public void awaitUninterruptibly()
|
||||
{
|
||||
boolean interrupted = false;
|
||||
while (!isSignalled())
|
||||
{
|
||||
if (Thread.currentThread().interrupted())
|
||||
interrupted = true;
|
||||
LockSupport.park();
|
||||
}
|
||||
if (interrupted)
|
||||
Thread.currentThread().interrupt();
|
||||
checkAndClear();
|
||||
}
|
||||
|
||||
public void await() throws InterruptedException
|
||||
{
|
||||
while (!isSignalled())
|
||||
{
|
||||
checkInterrupted();
|
||||
LockSupport.park();
|
||||
}
|
||||
checkAndClear();
|
||||
}
|
||||
|
||||
public boolean awaitUntil(long until) throws InterruptedException
|
||||
{
|
||||
while (until < System.currentTimeMillis() && !isSignalled())
|
||||
{
|
||||
checkInterrupted();
|
||||
LockSupport.parkUntil(until);
|
||||
}
|
||||
return checkAndClear();
|
||||
}
|
||||
|
||||
private void checkInterrupted() throws InterruptedException
|
||||
{
|
||||
if (Thread.interrupted())
|
||||
{
|
||||
cancel();
|
||||
throw new InterruptedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A signal registered with this WaitQueue
|
||||
*/
|
||||
private class RegisteredSignal extends AbstractSignal
|
||||
{
|
||||
private volatile Thread thread = Thread.currentThread();
|
||||
volatile int state;
|
||||
|
||||
public boolean isSignalled()
|
||||
{
|
||||
return state == SIGNALLED;
|
||||
}
|
||||
|
||||
public boolean isCancelled()
|
||||
{
|
||||
return state == CANCELLED;
|
||||
}
|
||||
|
||||
public boolean isSet()
|
||||
{
|
||||
return state != NOT_SET;
|
||||
}
|
||||
|
||||
private boolean signal()
|
||||
{
|
||||
if (!isSet() && signalledUpdater.compareAndSet(this, NOT_SET, SIGNALLED))
|
||||
{
|
||||
LockSupport.unpark(thread);
|
||||
thread = null;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean checkAndClear()
|
||||
{
|
||||
if (!isSet() && signalledUpdater.compareAndSet(this, NOT_SET, CANCELLED))
|
||||
{
|
||||
thread = null;
|
||||
cleanUpCancelled();
|
||||
return false;
|
||||
}
|
||||
// must now be signalled assuming correct API usage
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should only be called by the registered thread. Indicates the signal can be retired,
|
||||
* and if signalled propagates the signal to another waiting thread
|
||||
*/
|
||||
public void cancel()
|
||||
{
|
||||
if (isCancelled())
|
||||
return;
|
||||
if (!signalledUpdater.compareAndSet(this, NOT_SET, CANCELLED))
|
||||
{
|
||||
// must already be signalled - switch to cancelled and
|
||||
state = CANCELLED;
|
||||
// propagate the signal
|
||||
WaitQueue.this.signal();
|
||||
}
|
||||
thread = null;
|
||||
cleanUpCancelled();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A RegisteredSignal that stores a TimerContext, and stops the timer when either cancelled or
|
||||
* finished waiting. i.e. if the timer is started when the signal is registered it tracks the
|
||||
* time in between registering and invalidating the signal.
|
||||
*/
|
||||
private final class TimedSignal extends RegisteredSignal
|
||||
{
|
||||
private final TimerContext context;
|
||||
|
||||
private TimedSignal(TimerContext context)
|
||||
{
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkAndClear()
|
||||
{
|
||||
context.stop();
|
||||
return super.checkAndClear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel()
|
||||
{
|
||||
if (!isCancelled())
|
||||
{
|
||||
context.stop();
|
||||
super.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An abstract signal wrapping multiple delegate signals
|
||||
*/
|
||||
private abstract static class MultiSignal extends AbstractSignal
|
||||
{
|
||||
final Signal[] signals;
|
||||
protected MultiSignal(Signal[] signals)
|
||||
{
|
||||
this.signals = signals;
|
||||
}
|
||||
|
||||
public boolean isCancelled()
|
||||
{
|
||||
for (Signal signal : signals)
|
||||
if (!signal.isCancelled())
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean checkAndClear()
|
||||
{
|
||||
for (Signal signal : signals)
|
||||
signal.checkAndClear();
|
||||
return isSignalled();
|
||||
}
|
||||
|
||||
public void cancel()
|
||||
{
|
||||
for (Signal signal : signals)
|
||||
signal.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A Signal that wraps multiple Signals and returns when any single one of them would have returned
|
||||
*/
|
||||
private static class AnySignal extends MultiSignal
|
||||
{
|
||||
protected AnySignal(Signal ... signals)
|
||||
{
|
||||
super(signals);
|
||||
}
|
||||
|
||||
public boolean isSignalled()
|
||||
{
|
||||
for (Signal signal : signals)
|
||||
if (signal.isSignalled())
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isSet()
|
||||
{
|
||||
for (Signal signal : signals)
|
||||
if (signal.isSet())
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A Signal that wraps multiple Signals and returns when all of them would have finished returning
|
||||
*/
|
||||
private static class AllSignal extends MultiSignal
|
||||
{
|
||||
protected AllSignal(Signal ... signals)
|
||||
{
|
||||
super(signals);
|
||||
}
|
||||
|
||||
public boolean isSignalled()
|
||||
{
|
||||
for (Signal signal : signals)
|
||||
if (!signal.isSignalled())
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isSet()
|
||||
{
|
||||
for (Signal signal : signals)
|
||||
if (!signal.isSet())
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param signals
|
||||
* @return a signal that returns only when any of the provided signals would have returned
|
||||
*/
|
||||
public static Signal any(Signal ... signals)
|
||||
{
|
||||
return new AnySignal(signals);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param signals
|
||||
* @return a signal that returns only when all provided signals would have returned
|
||||
*/
|
||||
public static Signal all(Signal ... signals)
|
||||
{
|
||||
return new AllSignal(signals);
|
||||
}
|
||||
}
|
||||
|
|
@ -15,11 +15,13 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.cassandra.utils;
|
||||
package org.apache.cassandra.utils.memory;
|
||||
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public abstract class Allocator
|
||||
public abstract class AbstractAllocator
|
||||
{
|
||||
/**
|
||||
* Allocate a slice of the given length.
|
||||
|
|
@ -27,6 +29,8 @@ public abstract class Allocator
|
|||
public ByteBuffer clone(ByteBuffer buffer)
|
||||
{
|
||||
assert buffer != null;
|
||||
if (buffer.remaining() == 0)
|
||||
return ByteBufferUtil.EMPTY_BYTE_BUFFER;
|
||||
ByteBuffer cloned = allocate(buffer.remaining());
|
||||
|
||||
cloned.mark();
|
||||
|
|
@ -37,5 +41,22 @@ public abstract class Allocator
|
|||
|
||||
public abstract ByteBuffer allocate(int size);
|
||||
|
||||
public abstract long getMinimumSize();
|
||||
//
|
||||
// only really applicable to Pooled subclasses, but we provide default implementations here
|
||||
//
|
||||
|
||||
public long owns()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public float ownershipRatio()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public long reclaiming()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
package org.apache.cassandra.utils.memory;
|
||||
|
||||
import com.google.common.base.*;
|
||||
import org.apache.cassandra.db.Cell;
|
||||
import org.apache.cassandra.utils.concurrent.OpOrder;
|
||||
import org.apache.cassandra.db.ColumnFamilyStore;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Wraps calls to a PoolAllocator with the provided writeOp. Also doubles as a Function that clones Cells
|
||||
* using itself
|
||||
*/
|
||||
public final class ContextAllocator extends AbstractAllocator implements Function<Cell, Cell>
|
||||
{
|
||||
private final OpOrder.Group opGroup;
|
||||
private final PoolAllocator allocator;
|
||||
private final ColumnFamilyStore cfs;
|
||||
|
||||
public ContextAllocator(OpOrder.Group opGroup, PoolAllocator allocator, ColumnFamilyStore cfs)
|
||||
{
|
||||
this.opGroup = opGroup;
|
||||
this.allocator = allocator;
|
||||
this.cfs = cfs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer clone(ByteBuffer buffer)
|
||||
{
|
||||
return allocator.clone(buffer, opGroup);
|
||||
}
|
||||
|
||||
public ByteBuffer allocate(int size)
|
||||
{
|
||||
return allocator.allocate(size, opGroup);
|
||||
}
|
||||
|
||||
public Cell apply(Cell column)
|
||||
{
|
||||
return column.localCopy(cfs, this);
|
||||
}
|
||||
|
||||
public long owns()
|
||||
{
|
||||
return allocator.owns();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float ownershipRatio()
|
||||
{
|
||||
return allocator.ownershipRatio();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long reclaiming()
|
||||
{
|
||||
return allocator.reclaiming();
|
||||
}
|
||||
}
|
||||
|
|
@ -15,11 +15,13 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.cassandra.utils;
|
||||
package org.apache.cassandra.utils.memory;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public final class HeapAllocator extends Allocator
|
||||
import sun.reflect.generics.reflectiveObjects.NotImplementedException;
|
||||
|
||||
public final class HeapAllocator extends AbstractAllocator
|
||||
{
|
||||
public static final HeapAllocator instance = new HeapAllocator();
|
||||
|
||||
|
|
@ -27,15 +29,10 @@ public final class HeapAllocator extends Allocator
|
|||
* Normally you should use HeapAllocator.instance, since there is no per-Allocator state.
|
||||
* This is exposed so that the reflection done by Memtable works when SlabAllocator is disabled.
|
||||
*/
|
||||
public HeapAllocator() {}
|
||||
private HeapAllocator() {}
|
||||
|
||||
public ByteBuffer allocate(int size)
|
||||
{
|
||||
return ByteBuffer.allocate(size);
|
||||
}
|
||||
|
||||
public long getMinimumSize()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue