mirror of https://github.com/apache/cassandra
Merge branch 'cassandra-6.0' into trunk
* cassandra-6.0: Apply performance optimizations for rows merging logic
This commit is contained in:
commit
e0e4feb120
|
|
@ -6,6 +6,7 @@
|
||||||
* Allow nodetool garbagecollect to take a user defined list of SSTables (CASSANDRA-16767)
|
* Allow nodetool garbagecollect to take a user defined list of SSTables (CASSANDRA-16767)
|
||||||
* Add a guardrail for misprepared statements (CASSANDRA-21139)
|
* Add a guardrail for misprepared statements (CASSANDRA-21139)
|
||||||
Merged from 6.0:
|
Merged from 6.0:
|
||||||
|
* Apply performance optimizations for rows merging logic (CASSANDRA-21524)
|
||||||
* Fix operationMode reporting DECOMMISSION_FAILED instead of LEAVING when resuming a failed decommission (CASSANDRA-21493)
|
* Fix operationMode reporting DECOMMISSION_FAILED instead of LEAVING when resuming a failed decommission (CASSANDRA-21493)
|
||||||
* Avoid megamorphic calls when serializing and deserializing fixed-length values (CASSANDRA-21536)
|
* Avoid megamorphic calls when serializing and deserializing fixed-length values (CASSANDRA-21536)
|
||||||
* Avoid megamorphic calls for Cell.timestamp/ttl/path/localDeletionTimeAsUnsignedInt methods (CASSANDRA-21526)
|
* Avoid megamorphic calls for Cell.timestamp/ttl/path/localDeletionTimeAsUnsignedInt methods (CASSANDRA-21526)
|
||||||
|
|
@ -32,7 +33,7 @@ Merged from 6.0:
|
||||||
* Always send TCM commit failures as Messaging failures (CASSANDRA-21457)
|
* Always send TCM commit failures as Messaging failures (CASSANDRA-21457)
|
||||||
* Fix ReadCommand serializedSize() using incorrect epoch (CASSANDRA-21438)
|
* Fix ReadCommand serializedSize() using incorrect epoch (CASSANDRA-21438)
|
||||||
* Allocation improvements in ProtocolVersion, StorageProxy and MerkleTree (CASSANDRA-21199)
|
* Allocation improvements in ProtocolVersion, StorageProxy and MerkleTree (CASSANDRA-21199)
|
||||||
* Don’t leave autocompaction disabled during bootstrap and replace (CASSANDRA-21236)
|
* Don't leave autocompaction disabled during bootstrap and replace (CASSANDRA-21236)
|
||||||
* Make nodetool abortbootstrap more robust (CASSANDRA-21235)
|
* Make nodetool abortbootstrap more robust (CASSANDRA-21235)
|
||||||
* Don't clear prepared statement cache on nodetool cms initialize (CASSANDRA-21234)
|
* Don't clear prepared statement cache on nodetool cms initialize (CASSANDRA-21234)
|
||||||
* Improve performance when deserializing cluster metadata (CASSANDRA-21224)
|
* Improve performance when deserializing cluster metadata (CASSANDRA-21224)
|
||||||
|
|
|
||||||
62
build.xml
62
build.xml
|
|
@ -651,6 +651,64 @@
|
||||||
<jflex file="${build.src.java}/org/apache/cassandra/index/sasi/analyzer/StandardTokenizerImpl.jflex" destdir="${build.src.gen-java}/" />
|
<jflex file="${build.src.java}/org/apache/cassandra/index/sasi/analyzer/StandardTokenizerImpl.jflex" destdir="${build.src.gen-java}/" />
|
||||||
</target>
|
</target>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Copies a source class into ${build.src.gen-java} under a NEW class name while keeping it
|
||||||
|
in its ORIGINAL package, so it compiles into an independent class that still enjoys
|
||||||
|
package-private access to its neighbours.
|
||||||
|
|
||||||
|
This lets a hot call site use its own dedicated copy of a class (e.g. a RowMergeIterator
|
||||||
|
copy of MergeIterator) so that the virtual calls inside it stay mono-/bi-morphic and
|
||||||
|
remain inlinable, instead of turning megamorphic once every caller in the code base
|
||||||
|
funnels the shared original through the same bytecode.
|
||||||
|
|
||||||
|
The transformation is purely textual: every whole-word occurrence of the original simple
|
||||||
|
class name is rewritten to the new name. Matching on word boundaries keeps substrings
|
||||||
|
such as IMergeIterator intact. The package declaration is left untouched, so the copy
|
||||||
|
stays in the same package as the original and no imports need to change.
|
||||||
|
-->
|
||||||
|
<macrodef name="gen-java-copy">
|
||||||
|
<!-- Fully-qualified name of the class to copy, e.g. org.apache.cassandra.utils.MergeIterator -->
|
||||||
|
<attribute name="class"/>
|
||||||
|
<!-- New simple class name for the copy, e.g. RowMergeIterator -->
|
||||||
|
<attribute name="newName"/>
|
||||||
|
<sequential>
|
||||||
|
<local name="gen.src.rel"/>
|
||||||
|
<local name="gen.simple.name"/>
|
||||||
|
<!-- source file path relative to ${build.src.java}: dots -> slashes -->
|
||||||
|
<loadresource property="gen.src.rel">
|
||||||
|
<string value="@{class}"/>
|
||||||
|
<filterchain><tokenfilter><replaceregex pattern="\." replace="/" flags="g"/></tokenfilter></filterchain>
|
||||||
|
</loadresource>
|
||||||
|
<!-- original simple class name = last segment of the fully-qualified name -->
|
||||||
|
<loadresource property="gen.simple.name">
|
||||||
|
<string value="@{class}"/>
|
||||||
|
<filterchain><tokenfilter><replaceregex pattern="^.*\.([^.]+)$" replace="\1"/></tokenfilter></filterchain>
|
||||||
|
</loadresource>
|
||||||
|
<copy todir="${build.src.gen-java}" preservelastmodified="true">
|
||||||
|
<fileset dir="${build.src.java}" includes="${gen.src.rel}.java"/>
|
||||||
|
<!-- keep the original package directory, only rename the file -->
|
||||||
|
<mapper type="regexp" from="^(.*[\\/])[^\\/]+$" to="\1@{newName}.java"/>
|
||||||
|
<filterchain>
|
||||||
|
<tokenfilter>
|
||||||
|
<replaceregex pattern="\b${gen.simple.name}\b" replace="@{newName}" flags="g"/>
|
||||||
|
</tokenfilter>
|
||||||
|
</filterchain>
|
||||||
|
</copy>
|
||||||
|
</sequential>
|
||||||
|
</macrodef>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Generate the copies of hand-picked classes before compilation. Add a
|
||||||
|
<gen-java-copy class="..." newName="..."/> line here for every class that needs a
|
||||||
|
dedicated copy.
|
||||||
|
-->
|
||||||
|
<target name="gen-java-copies" depends="init"
|
||||||
|
description="Copy selected classes under new names (e.g. MergeIterator -> RowMergeIterator) into src/gen-java to avoid megamorphic calls">
|
||||||
|
<gen-java-copy class="org.apache.cassandra.utils.MergeIterator" newName="RowMergeIterator"/>
|
||||||
|
<gen-java-copy class="org.apache.cassandra.utils.MergeIterator" newName="UnfilteredMergeIterator"/>
|
||||||
|
<gen-java-copy class="org.apache.cassandra.utils.MergeIterator" newName="ComplexCellMergeIterator"/>
|
||||||
|
</target>
|
||||||
|
|
||||||
<!-- create properties file with C version -->
|
<!-- create properties file with C version -->
|
||||||
<target name="_createVersionPropFile" depends="_get-git-sha,set-cqlsh-version,_set-build-date">
|
<target name="_createVersionPropFile" depends="_get-git-sha,set-cqlsh-version,_set-build-date">
|
||||||
<taskdef name="propertyfile" classname="org.apache.tools.ant.taskdefs.optional.PropertyFile"/>
|
<taskdef name="propertyfile" classname="org.apache.tools.ant.taskdefs.optional.PropertyFile"/>
|
||||||
|
|
@ -710,7 +768,7 @@
|
||||||
</javac>
|
</javac>
|
||||||
</target>
|
</target>
|
||||||
|
|
||||||
<target depends="init,gen-cql3-grammar,generate-cql-html,generate-jflex-java"
|
<target depends="init,gen-cql3-grammar,generate-cql-html,generate-jflex-java,gen-java-copies"
|
||||||
name="build-project">
|
name="build-project">
|
||||||
<echo message="${ant.project.name}: ${ant.file}"/>
|
<echo message="${ant.project.name}: ${ant.file}"/>
|
||||||
<!-- Order matters! -->
|
<!-- Order matters! -->
|
||||||
|
|
@ -2173,7 +2231,7 @@
|
||||||
</target>
|
</target>
|
||||||
|
|
||||||
<!-- Generate IDEA project description files -->
|
<!-- Generate IDEA project description files -->
|
||||||
<target name="generate-idea-files" depends="init,resolver-dist-lib,gen-cql3-grammar,generate-jflex-java,_createVersionPropFile" description="Generate IDEA files">
|
<target name="generate-idea-files" depends="init,resolver-dist-lib,gen-cql3-grammar,generate-jflex-java,gen-java-copies,_createVersionPropFile" description="Generate IDEA files">
|
||||||
<delete dir=".idea"/>
|
<delete dir=".idea"/>
|
||||||
<delete file="${eclipse.project.name}.iml"/>
|
<delete file="${eclipse.project.name}.iml"/>
|
||||||
<mkdir dir=".idea"/>
|
<mkdir dir=".idea"/>
|
||||||
|
|
|
||||||
|
|
@ -178,7 +178,8 @@ public abstract class DeletionTime implements Comparable<DeletionTime>, IMeasura
|
||||||
|
|
||||||
public boolean deletes(Cell<?> cell)
|
public boolean deletes(Cell<?> cell)
|
||||||
{
|
{
|
||||||
return deletes(cell.timestamp());
|
// check for LIVE first to avoid a potential cell megamorphic call
|
||||||
|
return markedForDeleteAt() != MARKED_FOR_DELETE_AT_LIVE && deletes(cell.timestamp());
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean deletes(long timestamp)
|
public boolean deletes(long timestamp)
|
||||||
|
|
|
||||||
|
|
@ -439,6 +439,11 @@ public class BTreeRow extends AbstractRow
|
||||||
return nowInSec >= minLocalDeletionTime;
|
return nowInSec >= minLocalDeletionTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long minLocalDeletionTime()
|
||||||
|
{
|
||||||
|
return minLocalDeletionTime;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean hasInvalidDeletions()
|
public boolean hasInvalidDeletions()
|
||||||
{
|
{
|
||||||
if (primaryKeyLivenessInfo().isExpiring() && (primaryKeyLivenessInfo().ttl() < 0 || primaryKeyLivenessInfo().localExpirationTime() < 0))
|
if (primaryKeyLivenessInfo().isExpiring() && (primaryKeyLivenessInfo().ttl() < 0 || primaryKeyLivenessInfo().localExpirationTime() < 0))
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@ package org.apache.cassandra.db.rows;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
@ -44,9 +43,10 @@ import org.apache.cassandra.schema.TableMetadata;
|
||||||
import org.apache.cassandra.service.paxos.Commit;
|
import org.apache.cassandra.service.paxos.Commit;
|
||||||
import org.apache.cassandra.utils.BiLongAccumulator;
|
import org.apache.cassandra.utils.BiLongAccumulator;
|
||||||
import org.apache.cassandra.utils.BulkIterator;
|
import org.apache.cassandra.utils.BulkIterator;
|
||||||
|
import org.apache.cassandra.utils.ComplexCellMergeIterator;
|
||||||
import org.apache.cassandra.utils.LongAccumulator;
|
import org.apache.cassandra.utils.LongAccumulator;
|
||||||
import org.apache.cassandra.utils.MergeIterator;
|
|
||||||
import org.apache.cassandra.utils.ObjectSizes;
|
import org.apache.cassandra.utils.ObjectSizes;
|
||||||
|
import org.apache.cassandra.utils.RowMergeIterator;
|
||||||
import org.apache.cassandra.utils.SearchIterator;
|
import org.apache.cassandra.utils.SearchIterator;
|
||||||
import org.apache.cassandra.utils.btree.BTree;
|
import org.apache.cassandra.utils.btree.BTree;
|
||||||
import org.apache.cassandra.utils.btree.UpdateFunction;
|
import org.apache.cassandra.utils.btree.UpdateFunction;
|
||||||
|
|
@ -221,6 +221,15 @@ public interface Row extends Unfiltered, Iterable<ColumnData>, IMeasurableMemory
|
||||||
*/
|
*/
|
||||||
public boolean hasDeletion(long nowInSec);
|
public boolean hasDeletion(long nowInSec);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The smallest local deletion time of all the data in this row (row deletion, primary key liveness, cells and
|
||||||
|
* complex deletions), or {@link Cell#MAX_DELETION_TIME} if the row has no deletion nor expiring data.
|
||||||
|
* <p>
|
||||||
|
* Unlike {@link #hasDeletion(long)}, this value is independent of the current time. In particular, a value of
|
||||||
|
* {@link Cell#MAX_DELETION_TIME} guarantees the row carries neither tombstones nor expiring data.
|
||||||
|
*/
|
||||||
|
public long minLocalDeletionTime();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An iterator to efficiently search data for a given column.
|
* An iterator to efficiently search data for a given column.
|
||||||
*
|
*
|
||||||
|
|
@ -762,6 +771,11 @@ public interface Row extends Unfiltered, Iterable<ColumnData>, IMeasurableMemory
|
||||||
|
|
||||||
LivenessInfo rowInfo = LivenessInfo.EMPTY;
|
LivenessInfo rowInfo = LivenessInfo.EMPTY;
|
||||||
Deletion rowDeletion = Deletion.LIVE;
|
Deletion rowDeletion = Deletion.LIVE;
|
||||||
|
int columnsCountEstimation = 0;
|
||||||
|
// Track the smallest local deletion time across all inputs: if none of them carries any deletion or
|
||||||
|
// expiring data (i.e. this stays at MAX_DELETION_TIME), the merged row can't either, so we can hand the
|
||||||
|
// value to BTreeRow.create() below and skip the full btree scan it would otherwise do to recompute it.
|
||||||
|
long minDeletionTime = Cell.MAX_DELETION_TIME;
|
||||||
for (Row row : rows)
|
for (Row row : rows)
|
||||||
{
|
{
|
||||||
if (row == null)
|
if (row == null)
|
||||||
|
|
@ -771,6 +785,11 @@ public interface Row extends Unfiltered, Iterable<ColumnData>, IMeasurableMemory
|
||||||
rowInfo = row.primaryKeyLivenessInfo();
|
rowInfo = row.primaryKeyLivenessInfo();
|
||||||
if (row.deletion().supersedes(rowDeletion))
|
if (row.deletion().supersedes(rowDeletion))
|
||||||
rowDeletion = row.deletion();
|
rowDeletion = row.deletion();
|
||||||
|
|
||||||
|
minDeletionTime = Math.min(minDeletionTime, row.minLocalDeletionTime());
|
||||||
|
|
||||||
|
columnDataIterators.add(row.iterator());
|
||||||
|
columnsCountEstimation = Math.max(columnsCountEstimation, row.columnCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rowDeletion.isShadowedBy(rowInfo))
|
if (rowDeletion.isShadowedBy(rowInfo))
|
||||||
|
|
@ -784,25 +803,12 @@ public interface Row extends Unfiltered, Iterable<ColumnData>, IMeasurableMemory
|
||||||
if (activeDeletion.deletes(rowInfo))
|
if (activeDeletion.deletes(rowInfo))
|
||||||
rowInfo = LivenessInfo.EMPTY;
|
rowInfo = LivenessInfo.EMPTY;
|
||||||
|
|
||||||
int columnsCountEstimation = 0;
|
|
||||||
for (Row row : rows)
|
|
||||||
{
|
|
||||||
if (row != null)
|
|
||||||
{
|
|
||||||
columnDataIterators.add(row.iterator());
|
|
||||||
columnsCountEstimation = Math.max(columnsCountEstimation, row.columnCount());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
columnDataIterators.add(Collections.emptyIterator());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// try to estimate and set a potential target capacity
|
// try to estimate and set a potential target capacity
|
||||||
if (dataBuffer.length < columnsCountEstimation)
|
if (dataBuffer.length < columnsCountEstimation)
|
||||||
dataBuffer = new ColumnData[columnsCountEstimation];
|
dataBuffer = new ColumnData[columnsCountEstimation];
|
||||||
|
|
||||||
columnDataReducer.setActiveDeletion(activeDeletion);
|
columnDataReducer.setActiveDeletion(activeDeletion);
|
||||||
Iterator<ColumnData> merged = MergeIterator.get(columnDataIterators, ColumnData.comparator, columnDataReducer);
|
Iterator<ColumnData> merged = RowMergeIterator.get(columnDataIterators, ColumnData.comparator, columnDataReducer);
|
||||||
while (merged.hasNext())
|
while (merged.hasNext())
|
||||||
{
|
{
|
||||||
ColumnData data = merged.next();
|
ColumnData data = merged.next();
|
||||||
|
|
@ -819,8 +825,12 @@ public interface Row extends Unfiltered, Iterable<ColumnData>, IMeasurableMemory
|
||||||
|
|
||||||
try (BulkIterator<ColumnData> it = BulkIterator.of(dataBuffer))
|
try (BulkIterator<ColumnData> it = BulkIterator.of(dataBuffer))
|
||||||
{
|
{
|
||||||
return BTreeRow.create(clustering, rowInfo, rowDeletion,
|
Object[] tree = BTree.build(it, dataBufferSize, UpdateFunction.noOp());
|
||||||
BTree.build(it, dataBufferSize, UpdateFunction.noOp()));
|
// If none of the merged rows had any deletion or expiring data, neither does the result, so we can
|
||||||
|
// pass the already-known min local deletion time and avoid rescanning the whole btree to recompute it.
|
||||||
|
return minDeletionTime == Cell.MAX_DELETION_TIME
|
||||||
|
? BTreeRow.create(clustering, rowInfo, rowDeletion, tree, Cell.MAX_DELETION_TIME)
|
||||||
|
: BTreeRow.create(clustering, rowInfo, rowDeletion, tree);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -841,10 +851,11 @@ public interface Row extends Unfiltered, Iterable<ColumnData>, IMeasurableMemory
|
||||||
return rows;
|
return rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class ColumnDataReducer extends MergeIterator.Reducer<ColumnData, ColumnData>
|
private static class ColumnDataReducer extends RowMergeIterator.Reducer<ColumnData, ColumnData>
|
||||||
{
|
{
|
||||||
private ColumnMetadata column;
|
private ColumnMetadata column;
|
||||||
private final List<ColumnData> versions;
|
private final ColumnData[] versions;
|
||||||
|
private int versionsSize;
|
||||||
|
|
||||||
private DeletionTime activeDeletion;
|
private DeletionTime activeDeletion;
|
||||||
|
|
||||||
|
|
@ -854,7 +865,7 @@ public interface Row extends Unfiltered, Iterable<ColumnData>, IMeasurableMemory
|
||||||
|
|
||||||
public ColumnDataReducer(int size, boolean hasComplex)
|
public ColumnDataReducer(int size, boolean hasComplex)
|
||||||
{
|
{
|
||||||
this.versions = new ArrayList<>(size);
|
this.versions = new ColumnData[size];
|
||||||
this.complexBuilder = hasComplex ? ComplexColumnData.builder() : null;
|
this.complexBuilder = hasComplex ? ComplexColumnData.builder() : null;
|
||||||
this.complexCells = hasComplex ? new ArrayList<>(size) : null;
|
this.complexCells = hasComplex ? new ArrayList<>(size) : null;
|
||||||
this.cellReducer = new CellReducer();
|
this.cellReducer = new CellReducer();
|
||||||
|
|
@ -870,20 +881,24 @@ public interface Row extends Unfiltered, Iterable<ColumnData>, IMeasurableMemory
|
||||||
if (useColumnMetadata(data.column()))
|
if (useColumnMetadata(data.column()))
|
||||||
column = data.column();
|
column = data.column();
|
||||||
|
|
||||||
versions.add(data);
|
versions[versionsSize++] = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines it the {@code ColumnMetadata} is the one that should be used.
|
* Determines whether {@code dataColumn} should replace the currently selected column metadata,
|
||||||
* @param dataColumn the {@code ColumnMetadata} to use.
|
* i.e. whether no column has been selected yet or {@code dataColumn} is a newer version.
|
||||||
* @return {@code true} if the {@code ColumnMetadata} is the one that should be used, {@code false} otherwise.
|
* @param dataColumn the candidate {@code ColumnMetadata} to evaluate.
|
||||||
|
* @return {@code true} if {@code dataColumn} should be used, {@code false} otherwise.
|
||||||
*/
|
*/
|
||||||
private boolean useColumnMetadata(ColumnMetadata dataColumn)
|
private boolean useColumnMetadata(ColumnMetadata dataColumn)
|
||||||
{
|
{
|
||||||
if (column == null)
|
ColumnMetadata currentColumn = column;
|
||||||
|
if (currentColumn == null)
|
||||||
return true;
|
return true;
|
||||||
|
if (currentColumn == dataColumn)
|
||||||
|
return false;
|
||||||
|
|
||||||
return ColumnMetadataVersionComparator.INSTANCE.compare(column, dataColumn) < 0;
|
return ColumnMetadataVersionComparator.INSTANCE.compare(currentColumn, dataColumn) < 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ColumnData getReduced()
|
protected ColumnData getReduced()
|
||||||
|
|
@ -891,9 +906,9 @@ public interface Row extends Unfiltered, Iterable<ColumnData>, IMeasurableMemory
|
||||||
if (column.isSimple())
|
if (column.isSimple())
|
||||||
{
|
{
|
||||||
Cell<?> merged = null;
|
Cell<?> merged = null;
|
||||||
for (int i=0, isize=versions.size(); i<isize; i++)
|
for (int i = 0; i < versionsSize; i++)
|
||||||
{
|
{
|
||||||
Cell<?> cell = (Cell<?>) versions.get(i);
|
Cell<?> cell = (Cell<?>) versions[i];
|
||||||
if (!activeDeletion.deletes(cell))
|
if (!activeDeletion.deletes(cell))
|
||||||
merged = merged == null ? cell : Cells.reconcile(merged, cell);
|
merged = merged == null ? cell : Cells.reconcile(merged, cell);
|
||||||
}
|
}
|
||||||
|
|
@ -904,9 +919,9 @@ public interface Row extends Unfiltered, Iterable<ColumnData>, IMeasurableMemory
|
||||||
complexBuilder.newColumn(column);
|
complexBuilder.newColumn(column);
|
||||||
complexCells.clear();
|
complexCells.clear();
|
||||||
DeletionTime complexDeletion = DeletionTime.LIVE;
|
DeletionTime complexDeletion = DeletionTime.LIVE;
|
||||||
for (int i=0, isize=versions.size(); i<isize; i++)
|
for (int i = 0; i < versionsSize; i++)
|
||||||
{
|
{
|
||||||
ColumnData data = versions.get(i);
|
ColumnData data = versions[i];
|
||||||
ComplexColumnData cd = (ComplexColumnData)data;
|
ComplexColumnData cd = (ComplexColumnData)data;
|
||||||
if (cd.complexDeletion().supersedes(complexDeletion))
|
if (cd.complexDeletion().supersedes(complexDeletion))
|
||||||
complexDeletion = cd.complexDeletion();
|
complexDeletion = cd.complexDeletion();
|
||||||
|
|
@ -923,7 +938,7 @@ public interface Row extends Unfiltered, Iterable<ColumnData>, IMeasurableMemory
|
||||||
cellReducer.setActiveDeletion(activeDeletion);
|
cellReducer.setActiveDeletion(activeDeletion);
|
||||||
}
|
}
|
||||||
|
|
||||||
Iterator<Cell<?>> cells = MergeIterator.get(complexCells, Cell.comparator, cellReducer);
|
Iterator<Cell<?>> cells = ComplexCellMergeIterator.get(complexCells, Cell.comparator, cellReducer);
|
||||||
while (cells.hasNext())
|
while (cells.hasNext())
|
||||||
{
|
{
|
||||||
Cell<?> merged = cells.next();
|
Cell<?> merged = cells.next();
|
||||||
|
|
@ -937,11 +952,12 @@ public interface Row extends Unfiltered, Iterable<ColumnData>, IMeasurableMemory
|
||||||
protected void onKeyChange()
|
protected void onKeyChange()
|
||||||
{
|
{
|
||||||
column = null;
|
column = null;
|
||||||
versions.clear();
|
Arrays.fill(versions, 0, versionsSize, null);
|
||||||
|
versionsSize = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class CellReducer extends MergeIterator.Reducer<Cell<?>, Cell<?>>
|
private static class CellReducer extends ComplexCellMergeIterator.Reducer<Cell<?>, Cell<?>>
|
||||||
{
|
{
|
||||||
private DeletionTime activeDeletion;
|
private DeletionTime activeDeletion;
|
||||||
private Cell<?> merged;
|
private Cell<?> merged;
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ import org.apache.cassandra.schema.TableMetadata;
|
||||||
import org.apache.cassandra.serializers.MarshalException;
|
import org.apache.cassandra.serializers.MarshalException;
|
||||||
import org.apache.cassandra.utils.FBUtilities;
|
import org.apache.cassandra.utils.FBUtilities;
|
||||||
import org.apache.cassandra.utils.IMergeIterator;
|
import org.apache.cassandra.utils.IMergeIterator;
|
||||||
import org.apache.cassandra.utils.MergeIterator;
|
import org.apache.cassandra.utils.UnfilteredMergeIterator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Static methods to work with atom iterators.
|
* Static methods to work with atom iterators.
|
||||||
|
|
@ -415,7 +415,7 @@ public abstract class UnfilteredRowIterators
|
||||||
reversed,
|
reversed,
|
||||||
EncodingStats.merge(iterators, UnfilteredRowIterator::stats));
|
EncodingStats.merge(iterators, UnfilteredRowIterator::stats));
|
||||||
|
|
||||||
this.mergeIterator = MergeIterator.get(iterators,
|
this.mergeIterator = UnfilteredMergeIterator.get(iterators,
|
||||||
reversed ? metadata.comparator.reversed() : metadata.comparator,
|
reversed ? metadata.comparator.reversed() : metadata.comparator,
|
||||||
new MergeReducer(iterators.size(), reversed, listener));
|
new MergeReducer(iterators.size(), reversed, listener));
|
||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
|
|
@ -540,7 +540,7 @@ public abstract class UnfilteredRowIterators
|
||||||
listener.close();
|
listener.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class MergeReducer extends MergeIterator.Reducer<Unfiltered, Unfiltered>
|
private class MergeReducer extends UnfilteredMergeIterator.Reducer<Unfiltered, Unfiltered>
|
||||||
{
|
{
|
||||||
private final MergeListener listener;
|
private final MergeListener listener;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -213,6 +213,12 @@ public class RowWithSource implements Row
|
||||||
return row.hasDeletion(nowInSec);
|
return row.hasDeletion(nowInSec);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long minLocalDeletionTime()
|
||||||
|
{
|
||||||
|
return row.minLocalDeletionTime();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SearchIterator<ColumnMetadata, ColumnData> searchIterator()
|
public SearchIterator<ColumnMetadata, ColumnData> searchIterator()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@ import java.util.Comparator;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import net.nicoulaj.compilecommand.annotations.DontInline;
|
||||||
|
|
||||||
import accord.utils.Invariants;
|
import accord.utils.Invariants;
|
||||||
|
|
||||||
/** Merges sorted input iterators which individually contain unique items. */
|
/** Merges sorted input iterators which individually contain unique items. */
|
||||||
|
|
@ -307,9 +309,36 @@ public abstract class MergeIterator<In,Out> extends AbstractIterator<Out> implem
|
||||||
heap[currIdx] = heap[nextIdx];
|
heap[currIdx] = heap[nextIdx];
|
||||||
currIdx = nextIdx;
|
currIdx = nextIdx;
|
||||||
}
|
}
|
||||||
|
// If the candidate has no children in the binary-heap section it is already in its final position, so we can
|
||||||
|
// skip the (rarely needed) sink below. nextIdx is the candidate's left child; anything >= size means there
|
||||||
|
// are no children. The single-child (nextIdx == size - 1) and two-children cases still have to go through
|
||||||
|
// sinkInBinaryHeap, which compares against the child(ren) and may swap or update the equalParent flag.
|
||||||
|
nextIdx = (currIdx * 2) - (sortedSectionSize - 1);
|
||||||
|
if (nextIdx >= size)
|
||||||
|
{
|
||||||
|
heap[currIdx] = candidate;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// The candidate did not settle within the sorted section; sink it through the binary-heap section. This is
|
||||||
|
// rarely reached for typical narrow, lightly-overlapping merges, so it is split into its own method to keep
|
||||||
|
// the hot sorted-section path above small enough to be inlined into advance().
|
||||||
|
sinkInBinaryHeap(candidate, currIdx, size, sortedSectionSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sink {@code candidate} down the binary-heap section of the queue (the part below the sorted section), pulling
|
||||||
|
* up the lighter child at each level, and place it in its final position.
|
||||||
|
*
|
||||||
|
* Split out of {@link #replaceAndSink} so that the common case, where the candidate settles within the sorted
|
||||||
|
* section, stays compact and inlinable.
|
||||||
|
*/
|
||||||
|
@DontInline
|
||||||
|
private void sinkInBinaryHeap(Candidate<In> candidate, int currIdx, final int size, final int sortedSectionSize)
|
||||||
|
{
|
||||||
// If size <= SORTED_SECTION_SIZE, nextIdx below will be no less than size,
|
// If size <= SORTED_SECTION_SIZE, nextIdx below will be no less than size,
|
||||||
// because currIdx == sortedSectionSize == size - 1 and nextIdx becomes
|
// because currIdx == sortedSectionSize == size - 1 and nextIdx becomes
|
||||||
// (size - 1) * 2) - (size - 1 - 1) == size.
|
// (size - 1) * 2) - (size - 1 - 1) == size.
|
||||||
|
int nextIdx;
|
||||||
|
|
||||||
// Advance in the binary heap, pulling up the lighter element from the two at each level.
|
// Advance in the binary heap, pulling up the lighter element from the two at each level.
|
||||||
while ((nextIdx = (currIdx * 2) - (sortedSectionSize - 1)) + 1 < size)
|
while ((nextIdx = (currIdx * 2) - (sortedSectionSize - 1)) + 1 < size)
|
||||||
|
|
|
||||||
|
|
@ -282,6 +282,57 @@ public class RowsTest
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Row liveRow(Clustering<?> c, long ts, ByteBuffer vVal)
|
||||||
|
{
|
||||||
|
return rowWithCell(c, ts, BufferCell.live(v, ts, vVal));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Row rowWithCell(Clustering<?> c, long ts, Cell<?> cell)
|
||||||
|
{
|
||||||
|
Row.Builder builder = createBuilder(c);
|
||||||
|
builder.addPrimaryKeyLivenessInfo(LivenessInfo.create(ts));
|
||||||
|
builder.addCell(cell);
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Row mergeRows(Row... rows)
|
||||||
|
{
|
||||||
|
boolean hasComplex = false;
|
||||||
|
for (Row row : rows)
|
||||||
|
hasComplex |= row.hasComplex();
|
||||||
|
Row.Merger merger = new Row.Merger(rows.length, hasComplex);
|
||||||
|
for (int i = 0; i < rows.length; i++)
|
||||||
|
merger.add(i, rows[i]);
|
||||||
|
return merger.merge(DeletionTime.LIVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMergerMinLocalDeletionTime()
|
||||||
|
{
|
||||||
|
long now = FBUtilities.nowInSeconds();
|
||||||
|
long ts = secondToTs(now);
|
||||||
|
|
||||||
|
// All inputs are free of deletions and expiring data, so the merged row must be too. This is the fast path in
|
||||||
|
// Row.Merger#merge that reuses Cell.MAX_DELETION_TIME instead of recomputing it by scanning the merged btree.
|
||||||
|
Row mergedLive = mergeRows(liveRow(c1, ts, BB1), liveRow(c1, ts + 1, BB2));
|
||||||
|
Assert.assertEquals(Cell.MAX_DELETION_TIME, mergedLive.minLocalDeletionTime());
|
||||||
|
Assert.assertFalse(mergedLive.hasDeletion(now));
|
||||||
|
|
||||||
|
// One input carries an expiring cell that wins reconciliation (higher timestamp): the merged row must keep
|
||||||
|
// its expiration time rather than being reported as deletion-free.
|
||||||
|
Cell<?> expiringCell = BufferCell.expiring(v, ts + 2, 100, now, BB3);
|
||||||
|
Row mergedExpiring = mergeRows(liveRow(c1, ts, BB1), rowWithCell(c1, ts + 2, expiringCell));
|
||||||
|
Assert.assertEquals(expiringCell.localDeletionTime(), mergedExpiring.minLocalDeletionTime());
|
||||||
|
Assert.assertFalse(mergedExpiring.hasDeletion(now));
|
||||||
|
Assert.assertTrue(mergedExpiring.hasDeletion(expiringCell.localDeletionTime()));
|
||||||
|
|
||||||
|
// Same for a tombstone cell winning reconciliation: the merged row must report a deletion.
|
||||||
|
Cell<?> tombstoneCell = BufferCell.tombstone(v, ts + 2, now);
|
||||||
|
Row mergedTombstone = mergeRows(liveRow(c1, ts, BB1), rowWithCell(c1, ts + 2, tombstoneCell));
|
||||||
|
Assert.assertEquals(Long.MIN_VALUE, mergedTombstone.minLocalDeletionTime());
|
||||||
|
Assert.assertTrue(mergedTombstone.hasDeletion(now));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void diff()
|
public void diff()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue