mirror of https://github.com/apache/cassandra
CEP-14: Paxos Improvements
This work encompasses a range of improvements to Paxos, summarised as: - The introduction of Paxos Repair for ensuring consistency during range movements - The reduction of network round-trips by a factor of 2x for reads and writes patch by Benedict Elliott Smith and Blake Eggleston; reviewed by Alex Petrov, Aleksey Yeschenko and Sam Tunnicliffe for CASSANDRA-17164
This commit is contained in:
parent
8960b4d851
commit
d2923275e3
|
|
@ -74,6 +74,7 @@
|
|||
<exclude name="**/tools/cqlstress-lwt-example.yaml"/>
|
||||
<!-- Documentation files -->
|
||||
<exclude NAME="**/doc/modules/**/*"/>
|
||||
<exclude NAME="**/src/java/**/Paxos.md"/>
|
||||
<!-- NOTICE files -->
|
||||
<exclude NAME="**/NOTICE.md"/>
|
||||
<!-- LICENSE files -->
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ jobs:
|
|||
echo "***java tests***"
|
||||
|
||||
# get all of our unit test filenames
|
||||
set -eo pipefail && circleci tests glob "$HOME/cassandra/test/distributed/**/*.java" > /tmp/all_java_unit_tests.txt
|
||||
set -eo pipefail && circleci tests glob "$HOME/cassandra/test/distributed/**/*.java" "$HOME/cassandra/test/simulator/test/**/*.java" > /tmp/all_java_unit_tests.txt
|
||||
|
||||
# split up the unit tests into groups based on the number of containers we have
|
||||
set -eo pipefail && circleci tests split --split-by=timings --timings-type=filename --index=${CIRCLE_NODE_INDEX} --total=${CIRCLE_NODE_TOTAL} /tmp/all_java_unit_tests.txt > /tmp/java_tests_${CIRCLE_NODE_INDEX}.txt
|
||||
|
|
@ -1723,7 +1723,7 @@ jobs:
|
|||
echo "***java tests***"
|
||||
|
||||
# get all of our unit test filenames
|
||||
set -eo pipefail && circleci tests glob "$HOME/cassandra/test/distributed/**/*.java" > /tmp/all_java_unit_tests.txt
|
||||
set -eo pipefail && circleci tests glob "$HOME/cassandra/test/distributed/**/*.java" "$HOME/cassandra/test/simulator/test/**/*.java" > /tmp/all_java_unit_tests.txt
|
||||
|
||||
# split up the unit tests into groups based on the number of containers we have
|
||||
set -eo pipefail && circleci tests split --split-by=timings --timings-type=filename --index=${CIRCLE_NODE_INDEX} --total=${CIRCLE_NODE_TOTAL} /tmp/all_java_unit_tests.txt > /tmp/java_tests_${CIRCLE_NODE_INDEX}.txt
|
||||
|
|
@ -2335,7 +2335,7 @@ jobs:
|
|||
echo "***java tests***"
|
||||
|
||||
# get all of our unit test filenames
|
||||
set -eo pipefail && circleci tests glob "$HOME/cassandra/test/distributed/**/*.java" > /tmp/all_java_unit_tests.txt
|
||||
set -eo pipefail && circleci tests glob "$HOME/cassandra/test/distributed/**/*.java" "$HOME/cassandra/test/simulator/test/**/*.java" > /tmp/all_java_unit_tests.txt
|
||||
|
||||
# split up the unit tests into groups based on the number of containers we have
|
||||
set -eo pipefail && circleci tests split --split-by=timings --timings-type=filename --index=${CIRCLE_NODE_INDEX} --total=${CIRCLE_NODE_TOTAL} /tmp/all_java_unit_tests.txt > /tmp/java_tests_${CIRCLE_NODE_INDEX}.txt
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ jobs:
|
|||
echo "***java tests***"
|
||||
|
||||
# get all of our unit test filenames
|
||||
set -eo pipefail && circleci tests glob "$HOME/cassandra/test/distributed/**/*.java" > /tmp/all_java_unit_tests.txt
|
||||
set -eo pipefail && circleci tests glob "$HOME/cassandra/test/distributed/**/*.java" "$HOME/cassandra/test/simulator/test/**/*.java" > /tmp/all_java_unit_tests.txt
|
||||
|
||||
# split up the unit tests into groups based on the number of containers we have
|
||||
set -eo pipefail && circleci tests split --split-by=timings --timings-type=filename --index=${CIRCLE_NODE_INDEX} --total=${CIRCLE_NODE_TOTAL} /tmp/all_java_unit_tests.txt > /tmp/java_tests_${CIRCLE_NODE_INDEX}.txt
|
||||
|
|
@ -1723,7 +1723,7 @@ jobs:
|
|||
echo "***java tests***"
|
||||
|
||||
# get all of our unit test filenames
|
||||
set -eo pipefail && circleci tests glob "$HOME/cassandra/test/distributed/**/*.java" > /tmp/all_java_unit_tests.txt
|
||||
set -eo pipefail && circleci tests glob "$HOME/cassandra/test/distributed/**/*.java" "$HOME/cassandra/test/simulator/test/**/*.java" > /tmp/all_java_unit_tests.txt
|
||||
|
||||
# split up the unit tests into groups based on the number of containers we have
|
||||
set -eo pipefail && circleci tests split --split-by=timings --timings-type=filename --index=${CIRCLE_NODE_INDEX} --total=${CIRCLE_NODE_TOTAL} /tmp/all_java_unit_tests.txt > /tmp/java_tests_${CIRCLE_NODE_INDEX}.txt
|
||||
|
|
@ -2335,7 +2335,7 @@ jobs:
|
|||
echo "***java tests***"
|
||||
|
||||
# get all of our unit test filenames
|
||||
set -eo pipefail && circleci tests glob "$HOME/cassandra/test/distributed/**/*.java" > /tmp/all_java_unit_tests.txt
|
||||
set -eo pipefail && circleci tests glob "$HOME/cassandra/test/distributed/**/*.java" "$HOME/cassandra/test/simulator/test/**/*.java" > /tmp/all_java_unit_tests.txt
|
||||
|
||||
# split up the unit tests into groups based on the number of containers we have
|
||||
set -eo pipefail && circleci tests split --split-by=timings --timings-type=filename --index=${CIRCLE_NODE_INDEX} --total=${CIRCLE_NODE_TOTAL} /tmp/all_java_unit_tests.txt > /tmp/java_tests_${CIRCLE_NODE_INDEX}.txt
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ jobs:
|
|||
echo "***java tests***"
|
||||
|
||||
# get all of our unit test filenames
|
||||
set -eo pipefail && circleci tests glob "$HOME/cassandra/test/distributed/**/*.java" > /tmp/all_java_unit_tests.txt
|
||||
set -eo pipefail && circleci tests glob "$HOME/cassandra/test/distributed/**/*.java" "$HOME/cassandra/test/simulator/test/**/*.java" > /tmp/all_java_unit_tests.txt
|
||||
|
||||
# split up the unit tests into groups based on the number of containers we have
|
||||
set -eo pipefail && circleci tests split --split-by=timings --timings-type=filename --index=${CIRCLE_NODE_INDEX} --total=${CIRCLE_NODE_TOTAL} /tmp/all_java_unit_tests.txt > /tmp/java_tests_${CIRCLE_NODE_INDEX}.txt
|
||||
|
|
@ -1723,7 +1723,7 @@ jobs:
|
|||
echo "***java tests***"
|
||||
|
||||
# get all of our unit test filenames
|
||||
set -eo pipefail && circleci tests glob "$HOME/cassandra/test/distributed/**/*.java" > /tmp/all_java_unit_tests.txt
|
||||
set -eo pipefail && circleci tests glob "$HOME/cassandra/test/distributed/**/*.java" "$HOME/cassandra/test/simulator/test/**/*.java" > /tmp/all_java_unit_tests.txt
|
||||
|
||||
# split up the unit tests into groups based on the number of containers we have
|
||||
set -eo pipefail && circleci tests split --split-by=timings --timings-type=filename --index=${CIRCLE_NODE_INDEX} --total=${CIRCLE_NODE_TOTAL} /tmp/all_java_unit_tests.txt > /tmp/java_tests_${CIRCLE_NODE_INDEX}.txt
|
||||
|
|
@ -2335,7 +2335,7 @@ jobs:
|
|||
echo "***java tests***"
|
||||
|
||||
# get all of our unit test filenames
|
||||
set -eo pipefail && circleci tests glob "$HOME/cassandra/test/distributed/**/*.java" > /tmp/all_java_unit_tests.txt
|
||||
set -eo pipefail && circleci tests glob "$HOME/cassandra/test/distributed/**/*.java" "$HOME/cassandra/test/simulator/test/**/*.java" > /tmp/all_java_unit_tests.txt
|
||||
|
||||
# split up the unit tests into groups based on the number of containers we have
|
||||
set -eo pipefail && circleci tests split --split-by=timings --timings-type=filename --index=${CIRCLE_NODE_INDEX} --total=${CIRCLE_NODE_TOTAL} /tmp/all_java_unit_tests.txt > /tmp/java_tests_${CIRCLE_NODE_INDEX}.txt
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ jobs:
|
|||
echo "***java tests***"
|
||||
|
||||
# get all of our unit test filenames
|
||||
set -eo pipefail && circleci tests glob "$HOME/cassandra/test/distributed/**/*.java" > /tmp/all_java_unit_tests.txt
|
||||
set -eo pipefail && circleci tests glob "$HOME/cassandra/test/distributed/**/*.java" "$HOME/cassandra/test/simulator/test/**/*.java" > /tmp/all_java_unit_tests.txt
|
||||
|
||||
# split up the unit tests into groups based on the number of containers we have
|
||||
set -eo pipefail && circleci tests split --split-by=timings --timings-type=filename --index=${CIRCLE_NODE_INDEX} --total=${CIRCLE_NODE_TOTAL} /tmp/all_java_unit_tests.txt > /tmp/java_tests_${CIRCLE_NODE_INDEX}.txt
|
||||
|
|
@ -1723,7 +1723,7 @@ jobs:
|
|||
echo "***java tests***"
|
||||
|
||||
# get all of our unit test filenames
|
||||
set -eo pipefail && circleci tests glob "$HOME/cassandra/test/distributed/**/*.java" > /tmp/all_java_unit_tests.txt
|
||||
set -eo pipefail && circleci tests glob "$HOME/cassandra/test/distributed/**/*.java" "$HOME/cassandra/test/simulator/test/**/*.java" > /tmp/all_java_unit_tests.txt
|
||||
|
||||
# split up the unit tests into groups based on the number of containers we have
|
||||
set -eo pipefail && circleci tests split --split-by=timings --timings-type=filename --index=${CIRCLE_NODE_INDEX} --total=${CIRCLE_NODE_TOTAL} /tmp/all_java_unit_tests.txt > /tmp/java_tests_${CIRCLE_NODE_INDEX}.txt
|
||||
|
|
@ -2335,7 +2335,7 @@ jobs:
|
|||
echo "***java tests***"
|
||||
|
||||
# get all of our unit test filenames
|
||||
set -eo pipefail && circleci tests glob "$HOME/cassandra/test/distributed/**/*.java" > /tmp/all_java_unit_tests.txt
|
||||
set -eo pipefail && circleci tests glob "$HOME/cassandra/test/distributed/**/*.java" "$HOME/cassandra/test/simulator/test/**/*.java" > /tmp/all_java_unit_tests.txt
|
||||
|
||||
# split up the unit tests into groups based on the number of containers we have
|
||||
set -eo pipefail && circleci tests split --split-by=timings --timings-type=filename --index=${CIRCLE_NODE_INDEX} --total=${CIRCLE_NODE_TOTAL} /tmp/all_java_unit_tests.txt > /tmp/java_tests_${CIRCLE_NODE_INDEX}.txt
|
||||
|
|
|
|||
24
NEWS.txt
24
NEWS.txt
|
|
@ -74,6 +74,18 @@ New features
|
|||
- A new ALL TABLES IN KEYSPACE resource has been added. It allows to grant permissions for all tables and user types
|
||||
in a keyspace while preventing the user to use those permissions on the keyspace itself.
|
||||
- Added support for type casting in the WHERE clause components and in the values of INSERT and UPDATE statements.
|
||||
- A new implementation of Paxos (named v2) has been included that improves the safety and performance of LWT operations.
|
||||
Importantly, v2 guarantees linearizability across safe range movements, so users are encouraged to enable v2.
|
||||
v2 also halves the number of WAN messages required to be exchanged if used on conjunction with the new Paxos Repair
|
||||
mechanism (see below) and with some minor modifications to applications using LWTs.
|
||||
The new implementation may be enabled at any time by setting paxos_variant: v2, and disabled by setting to v1,
|
||||
and this alone will reduce the number of WAN round-trips by between one and two for reads, and one for writes.
|
||||
- A new Paxos Repair mechanism has been introduced as part of Repair, that permits further reducing the number of WAN
|
||||
round-trips for write LWTs. This process may be manually executed for v1 and is run automatically alongside normal
|
||||
repairs for v2. Once users are running regular repairs that include paxos repairs they are encouraged to set
|
||||
paxos_state_purging: repaired. Once this has been set across the cluster, users are encouraged to set their
|
||||
applications to supply a Commit consistency level of ANY with their LWT write operations, saving one additional WAN
|
||||
round-trip. See upgrade notes below.
|
||||
- Warn/abort thresholds added to read queries notifying clients when these thresholds trigger (by
|
||||
emitting a client warning or aborting the query). This feature is disabled by default, scheduled
|
||||
to be enabled in 4.2; it is controlled with the configuration track_warnings.enabled,
|
||||
|
|
@ -122,6 +134,18 @@ Upgrading
|
|||
what was actually applied in previous versions. This also affects the setters and getters for these properties in
|
||||
the JMX MBean `org.apache.cassandra.db:type=StorageService` and the nodetool commands `set/getstreamthroughput`
|
||||
and `set/getinterdcstreamthroughput`.
|
||||
- Steps for upgrading Paxos
|
||||
- Set paxos_variant: v2 across the cluster. This may be set via JMX, but should also be written
|
||||
persistently to any yaml.
|
||||
- Ensure paxos repairs are running regularly, either as part of normal incremental repair workflows or on their
|
||||
own separate schedule. These operations are cheap and better to run frequently (e.g. once per hour)
|
||||
- Set paxos_state_purging: repaired across the cluster. This may be set via JMX, but should also be written
|
||||
persistently to any yaml. NOTE: once this has been set, you must not restore paxos_state_purging: legacy. If
|
||||
this setting must be disabled you must instead set paxos_state_purging: gc_grace. This may be necessary if
|
||||
paxos repairs must be disabled for some reason on an extended basis, but in this case your applications must
|
||||
restore default commit consistency to ensure correctness.
|
||||
- Applications may now safely be updated to use ANY commit consistency level (or LOCAL_QUORUM, as preferred).
|
||||
Uncontended writes should now take 2 round-trips, and uncontended reads should typically take one round-trip.
|
||||
|
||||
Deprecation
|
||||
-----------
|
||||
|
|
|
|||
42
build.xml
42
build.xml
|
|
@ -66,6 +66,7 @@
|
|||
<property name="test.simulator.src" value="${test.dir}/simulator/main"/>
|
||||
<property name="test.simulator-asm.src" value="${test.dir}/simulator/asm"/>
|
||||
<property name="test.simulator-bootstrap.src" value="${test.dir}/simulator/bootstrap"/>
|
||||
<property name="test.simulator-test.src" value="${test.dir}/simulator/test"/>
|
||||
<property name="test.driver.connection_timeout_ms" value="5000"/>
|
||||
<property name="test.driver.read_timeout_ms" value="12000"/>
|
||||
<property name="dist.dir" value="${build.dir}/dist"/>
|
||||
|
|
@ -1070,6 +1071,7 @@
|
|||
<include name="**/asm-*${asm.version}.jar" />
|
||||
</fileset>
|
||||
</classpath>
|
||||
<compilerarg value="-XDignore.symbol.file"/>
|
||||
</javac>
|
||||
</target>
|
||||
|
||||
|
|
@ -1154,10 +1156,10 @@
|
|||
<attribute name="Can-Retransform-Classes" value="true"/>
|
||||
</manifest>
|
||||
<mkdir dir="${simulator-asm.build.classes}/META-INF" />
|
||||
<mkdir dir="${build.dir}/test/lib/" />
|
||||
<jar destfile="${build.dir}/test/lib/simulator-asm.jar" manifest="${simulator-asm.manifest}">
|
||||
<mkdir dir="${test.lib}/jars/" />
|
||||
<jar destfile="${test.lib}/jars/simulator-asm.jar" manifest="${simulator-asm.manifest}">
|
||||
<fileset dir="${simulator-asm.build.classes}"/>
|
||||
<fileset dir="${test.lib}">
|
||||
<fileset dir="${test.lib}/jars">
|
||||
<include name="**/asm-*${asm.version}.jar" />
|
||||
</fileset>
|
||||
</jar>
|
||||
|
|
@ -1167,10 +1169,10 @@
|
|||
<attribute name="Built-By" value="Benedict Elliott Smith"/>
|
||||
</manifest>
|
||||
<mkdir dir="${simulator-bootstrap.build.classes}/META-INF" />
|
||||
<mkdir dir="${build.dir}/test/lib/" />
|
||||
<jar destfile="${build.dir}/test/lib/simulator-bootstrap.jar" manifest="${simulator-bootstrap.manifest}">
|
||||
<mkdir dir="${test.lib}/jars/" />
|
||||
<jar destfile="${test.lib}/jars/simulator-bootstrap.jar" manifest="${simulator-bootstrap.manifest}">
|
||||
<fileset dir="${simulator-bootstrap.build.classes}"/>
|
||||
<fileset dir="${test.lib}">
|
||||
<fileset dir="${test.lib}/jars">
|
||||
<include name="**/asm-*${asm.version}.jar" />
|
||||
</fileset>
|
||||
</jar>
|
||||
|
|
@ -1367,7 +1369,7 @@
|
|||
</jar>
|
||||
</target>
|
||||
|
||||
<target name="build-test" depends="_main-jar,stress-build,fqltool-build,resolver-dist-lib" unless="no-build-test"
|
||||
<target name="build-test" depends="_main-jar,stress-build,fqltool-build,resolver-dist-lib,simulator-jars" unless="no-build-test"
|
||||
description="Compile test classes">
|
||||
<antcall target="_build-test"/>
|
||||
</target>
|
||||
|
|
@ -1397,6 +1399,7 @@
|
|||
<src path="${test.simulator.src}"/>
|
||||
<src path="${test.simulator-asm.src}"/>
|
||||
<src path="${test.simulator-bootstrap.src}"/>
|
||||
<src path="${test.simulator-test.src}"/>
|
||||
</javac>
|
||||
|
||||
<!-- Non-java resources needed by the test suite -->
|
||||
|
|
@ -1504,6 +1507,8 @@
|
|||
<pathelement location="${stress.test.classes}"/>
|
||||
<pathelement location="${fqltool.test.classes}"/>
|
||||
<pathelement location="${test.conf}"/>
|
||||
<pathelement path="${java.class.path}"/>
|
||||
<path refid="cassandra.classpath" />
|
||||
<fileset dir="${test.lib}">
|
||||
<include name="**/*.jar" />
|
||||
<exclude name="**/ant-*.jar"/>
|
||||
|
|
@ -1960,23 +1965,36 @@
|
|||
</target>
|
||||
|
||||
<target name="dtest-jar" depends="build-test, build" description="Create dtest-compatible jar, including all dependencies">
|
||||
<jar jarfile="${build.dir}/dtest-${base.version}.jar">
|
||||
<zipgroupfileset dir="${build.lib}" includes="*.jar" excludes="META-INF/*.SF"/>
|
||||
<zipgroupfileset dir="${build.dir.lib}/jars" includes="javassist-*.jar,reflections-*.jar" excludes="META-INF/*.SF"/>
|
||||
<zipgroupfileset dir="${build.dir.lib}/jars" includes="jimfs-1.1.jar" excludes="META-INF/*.SF"/>
|
||||
<jar jarfile="${build.dir}/dtest-${base.version}.jar" duplicate="preserve">
|
||||
<fileset dir="${build.classes.main}"/>
|
||||
<fileset dir="${test.classes}"/>
|
||||
<fileset dir="${test.conf}" />
|
||||
<zipgroupfileset dir="${build.lib}" includes="*.jar" excludes="META-INF/*.SF"/>
|
||||
<zipgroupfileset dir="${test.lib}/jars" includes="jimfs-1.1.jar,dtest-api-*.jar,asm-*.jar,javassist-*.jar,reflections-*.jar,semver4j-*.jar" excludes="META-INF/*.SF"/>
|
||||
<zipgroupfileset dir="${build.dir.lib}/jars" includes="asm-*.jar" excludes="META-INF/*.SF"/>
|
||||
</jar>
|
||||
</target>
|
||||
|
||||
<target name="test-jvm-dtest" depends="build-test" description="Execute in-jvm dtests">
|
||||
<testmacro inputdir="${test.distributed.src}" timeout="${test.distributed.timeout}" forkmode="once" showoutput="true" filter="**/test/*Test.java">
|
||||
<testmacro inputdir="${test.distributed.src}" timeout="${test.distributed.timeout}" forkmode="once" showoutput="true" filter="**/test/${test.name}.java">
|
||||
<jvmarg value="-Dlogback.configurationFile=test/conf/logback-dtest.xml"/>
|
||||
<jvmarg value="-Dcassandra.ring_delay_ms=10000"/>
|
||||
<jvmarg value="-Dcassandra.tolerate_sstable_size=true"/>
|
||||
<jvmarg value="-Dcassandra.skip_sync=true" />
|
||||
</testmacro>
|
||||
<testmacro inputdir="${test.simulator-test.src}" timeout="${test.distributed.timeout}" forkmode="perTest" showoutput="true" filter="**/test/${test.name}.java">
|
||||
<jvmarg value="-Dlogback.configurationFile=test/conf/logback-simulator.xml"/>
|
||||
<jvmarg value="-Dcassandra.ring_delay_ms=10000"/>
|
||||
<jvmarg value="-Dcassandra.tolerate_sstable_size=true"/>
|
||||
<jvmarg value="-Dcassandra.skip_sync=true" />
|
||||
<!-- Support Simulator Tests -->
|
||||
<jvmarg line="-javaagent:${test.lib}/jars/simulator-asm.jar"/>
|
||||
<jvmarg line="-Xbootclasspath/a:${test.lib}/jars/simulator-bootstrap.jar"/>
|
||||
<jvmarg line="-XX:ActiveProcessorCount=4"/>
|
||||
<jvmarg line="-XX:-TieredCompilation"/>
|
||||
<jvmarg line="-XX:Tier4CompileThreshold=1000"/>
|
||||
<jvmarg line="-XX:ReservedCodeCacheSize=256M"/>
|
||||
</testmacro>
|
||||
</target>
|
||||
|
||||
<target name="test-jvm-upgrade-dtest" depends="build-test" description="Execute in-jvm dtests">
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.Iterables;
|
||||
|
|
@ -50,7 +51,6 @@ import org.apache.cassandra.db.Mutation;
|
|||
import org.apache.cassandra.db.SystemKeyspace;
|
||||
import org.apache.cassandra.db.WriteType;
|
||||
import org.apache.cassandra.db.marshal.BytesType;
|
||||
import org.apache.cassandra.db.marshal.UUIDType;
|
||||
import org.apache.cassandra.db.partitions.PartitionUpdate;
|
||||
import org.apache.cassandra.dht.Token;
|
||||
import org.apache.cassandra.exceptions.WriteFailureException;
|
||||
|
|
@ -501,9 +501,9 @@ public class BatchlogManager implements BatchlogManagerMBean
|
|||
}
|
||||
}
|
||||
|
||||
ReplicaPlan.ForTokenWrite replicaPlan = new ReplicaPlan.ForTokenWrite(keyspace, liveAndDown.replicationStrategy(),
|
||||
ConsistencyLevel.ONE, liveRemoteOnly.pending(), liveRemoteOnly.all(), liveRemoteOnly.all(), liveRemoteOnly.all());
|
||||
ReplayWriteResponseHandler<Mutation> handler = new ReplayWriteResponseHandler<>(replicaPlan, nanoTime());
|
||||
ReplicaPlan.ForWrite replicaPlan = new ReplicaPlan.ForWrite(keyspace, liveAndDown.replicationStrategy(),
|
||||
ConsistencyLevel.ONE, liveRemoteOnly.pending(), liveRemoteOnly.all(), liveRemoteOnly.all(), liveRemoteOnly.all());
|
||||
ReplayWriteResponseHandler<Mutation> handler = new ReplayWriteResponseHandler<>(replicaPlan, mutation, nanoTime());
|
||||
Message<Mutation> message = Message.outWithFlag(MUTATION_REQ, mutation, MessageFlag.CALL_BACK_ON_FAILURE);
|
||||
for (Replica replica : liveRemoteOnly.all())
|
||||
MessagingService.instance().sendWriteWithCallback(message, replica, handler, false);
|
||||
|
|
@ -526,9 +526,10 @@ public class BatchlogManager implements BatchlogManagerMBean
|
|||
{
|
||||
private final Set<InetAddressAndPort> undelivered = Collections.newSetFromMap(new ConcurrentHashMap<>());
|
||||
|
||||
ReplayWriteResponseHandler(ReplicaPlan.ForTokenWrite replicaPlan, long queryStartNanoTime)
|
||||
// TODO: should we be hinting here, since presumably batch log will retry? Maintaining historical behaviour for the moment.
|
||||
ReplayWriteResponseHandler(ReplicaPlan.ForWrite replicaPlan, Supplier<Mutation> hintOnFailure, long queryStartNanoTime)
|
||||
{
|
||||
super(replicaPlan, null, WriteType.UNLOGGED_BATCH, queryStartNanoTime);
|
||||
super(replicaPlan, null, WriteType.UNLOGGED_BATCH, hintOnFailure, queryStartNanoTime);
|
||||
Iterables.addAll(undelivered, replicaPlan.contacts().endpoints());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import org.apache.cassandra.utils.JVMStabilityInspector;
|
|||
import org.apache.cassandra.utils.Shared;
|
||||
|
||||
import static java.lang.Thread.*;
|
||||
import static org.apache.cassandra.concurrent.ExecutorFactory.SimulatorSemantics.NORMAL;
|
||||
import static org.apache.cassandra.concurrent.InfiniteLoopExecutor.Daemon.DAEMON;
|
||||
import static org.apache.cassandra.concurrent.InfiniteLoopExecutor.Interrupts.UNSYNCHRONIZED;
|
||||
import static org.apache.cassandra.concurrent.NamedThreadFactory.createThread;
|
||||
|
|
@ -72,6 +73,11 @@ public interface ExecutorFactory extends ExecutorBuilderFactory.Jmxable<Executor
|
|||
default LocalAwareSubFactoryWithJMX withJmxInternal() { return withJmx("internal"); }
|
||||
}
|
||||
|
||||
public enum SimulatorSemantics
|
||||
{
|
||||
NORMAL, DISCARD
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a factory that configures executors that propagate {@link ExecutorLocals} to the executing thread
|
||||
*/
|
||||
|
|
@ -83,6 +89,13 @@ public interface ExecutorFactory extends ExecutorBuilderFactory.Jmxable<Executor
|
|||
*/
|
||||
default ScheduledExecutorPlus scheduled(String name) { return scheduled(true, name, NORM_PRIORITY); }
|
||||
|
||||
/**
|
||||
* @param name the name of the executor, the executor's thread group, and of any worker threads
|
||||
* @param simulatorSemantics indicate special semantics for the executor under simulation
|
||||
* @return a default-configured {@link ScheduledExecutorPlus}
|
||||
*/
|
||||
default ScheduledExecutorPlus scheduled(String name, SimulatorSemantics simulatorSemantics) { return scheduled(true, name, NORM_PRIORITY, simulatorSemantics); }
|
||||
|
||||
/**
|
||||
* @param executeOnShutdown if false, waiting tasks will be cancelled on shutdown
|
||||
* @param name the name of the executor, the executor's thread group, and of any worker threads
|
||||
|
|
@ -96,7 +109,16 @@ public interface ExecutorFactory extends ExecutorBuilderFactory.Jmxable<Executor
|
|||
* @param priority the thread priority of workers
|
||||
* @return a {@link ScheduledExecutorPlus}
|
||||
*/
|
||||
ScheduledExecutorPlus scheduled(boolean executeOnShutdown, String name, int priority);
|
||||
default ScheduledExecutorPlus scheduled(boolean executeOnShutdown, String name, int priority) { return scheduled(executeOnShutdown, name, priority, NORMAL); }
|
||||
|
||||
/**
|
||||
* @param executeOnShutdown if false, waiting tasks will be cancelled on shutdown
|
||||
* @param name the name of the executor, the executor's thread group, and of any worker threads
|
||||
* @param priority the thread priority of workers
|
||||
* @param simulatorSemantics indicate special semantics for the executor under simulation
|
||||
* @return a {@link ScheduledExecutorPlus}
|
||||
*/
|
||||
ScheduledExecutorPlus scheduled(boolean executeOnShutdown, String name, int priority, SimulatorSemantics simulatorSemantics);
|
||||
|
||||
/**
|
||||
* Create and start a new thread to execute {@code runnable}
|
||||
|
|
@ -249,7 +271,7 @@ public interface ExecutorFactory extends ExecutorBuilderFactory.Jmxable<Executor
|
|||
}
|
||||
|
||||
@Override
|
||||
public ScheduledExecutorPlus scheduled(boolean executeOnShutdown, String name, int priority)
|
||||
public ScheduledExecutorPlus scheduled(boolean executeOnShutdown, String name, int priority, SimulatorSemantics simulatorSemantics)
|
||||
{
|
||||
ScheduledThreadPoolExecutorPlus executor = new ScheduledThreadPoolExecutorPlus(newThreadFactory(name, priority));
|
||||
if (!executeOnShutdown)
|
||||
|
|
|
|||
|
|
@ -30,9 +30,9 @@ import org.apache.cassandra.utils.concurrent.AsyncFuture;
|
|||
*/
|
||||
public class FutureTask<V> extends AsyncFuture<V> implements RunnableFuture<V>
|
||||
{
|
||||
private Callable<V> call;
|
||||
private Callable<? extends V> call;
|
||||
|
||||
public FutureTask(Callable<V> call)
|
||||
public FutureTask(Callable<? extends V> call)
|
||||
{
|
||||
this.call = call;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,21 +29,25 @@ import java.util.function.BiFunction;
|
|||
import java.util.function.Consumer;
|
||||
|
||||
import org.apache.cassandra.utils.Shared;
|
||||
import org.apache.cassandra.utils.concurrent.Condition;
|
||||
import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException;
|
||||
|
||||
import static org.apache.cassandra.concurrent.InfiniteLoopExecutor.InternalState.SHUTTING_DOWN_NOW;
|
||||
import static org.apache.cassandra.concurrent.InfiniteLoopExecutor.InternalState.TERMINATED;
|
||||
import static org.apache.cassandra.concurrent.InfiniteLoopExecutor.Interrupts.SYNCHRONIZED;
|
||||
import static org.apache.cassandra.concurrent.InfiniteLoopExecutor.Interrupts.UNSYNCHRONIZED;
|
||||
import static org.apache.cassandra.concurrent.Interruptible.State.INTERRUPTED;
|
||||
import static org.apache.cassandra.concurrent.Interruptible.State.NORMAL;
|
||||
import static org.apache.cassandra.concurrent.Interruptible.State.SHUTTING_DOWN;
|
||||
import static org.apache.cassandra.utils.Clock.Global.nanoTime;
|
||||
import static org.apache.cassandra.utils.concurrent.Condition.newOneTimeCondition;
|
||||
|
||||
public class InfiniteLoopExecutor implements Interruptible
|
||||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(InfiniteLoopExecutor.class);
|
||||
|
||||
@Shared(scope = Shared.Scope.SIMULATION)
|
||||
public enum InternalState { TERMINATED }
|
||||
public enum InternalState { SHUTTING_DOWN_NOW, TERMINATED }
|
||||
|
||||
@Shared(scope = Shared.Scope.SIMULATION)
|
||||
public enum SimulatorSafe { SAFE, UNSAFE }
|
||||
|
|
@ -59,6 +63,7 @@ public class InfiniteLoopExecutor implements Interruptible
|
|||
private final Task task;
|
||||
private volatile Object state = NORMAL;
|
||||
private final Consumer<Thread> interruptHandler;
|
||||
private final Condition isTerminated = newOneTimeCondition();
|
||||
|
||||
public InfiniteLoopExecutor(String name, Task task, Daemon daemon)
|
||||
{
|
||||
|
|
@ -102,33 +107,41 @@ public class InfiniteLoopExecutor implements Interruptible
|
|||
private void loop()
|
||||
{
|
||||
boolean interrupted = false;
|
||||
while (true)
|
||||
try
|
||||
{
|
||||
try
|
||||
while (true)
|
||||
{
|
||||
Object cur = state;
|
||||
if (cur == TERMINATED) break;
|
||||
try
|
||||
{
|
||||
Object cur = state;
|
||||
if (cur == SHUTTING_DOWN_NOW) break;
|
||||
|
||||
interrupted |= Thread.interrupted();
|
||||
if (cur == NORMAL && interrupted) cur = INTERRUPTED;
|
||||
task.run((State) cur);
|
||||
interrupted |= Thread.interrupted();
|
||||
if (cur == NORMAL && interrupted) cur = INTERRUPTED;
|
||||
task.run((State) cur);
|
||||
|
||||
interrupted = false;
|
||||
if (cur == SHUTTING_DOWN) state = TERMINATED;
|
||||
}
|
||||
catch (TerminateException ignore)
|
||||
{
|
||||
state = TERMINATED;
|
||||
}
|
||||
catch (UncheckedInterruptedException | InterruptedException ignore)
|
||||
{
|
||||
interrupted = true;
|
||||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
logger.error("Exception thrown by runnable, continuing with loop", t);
|
||||
interrupted = false;
|
||||
if (cur == SHUTTING_DOWN) break;
|
||||
}
|
||||
catch (TerminateException ignore)
|
||||
{
|
||||
break;
|
||||
}
|
||||
catch (UncheckedInterruptedException | InterruptedException ignore)
|
||||
{
|
||||
interrupted = true;
|
||||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
logger.error("Exception thrown by runnable, continuing with loop", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
state = TERMINATED;
|
||||
isTerminated.signal();
|
||||
}
|
||||
}
|
||||
|
||||
public void interrupt()
|
||||
|
|
@ -138,13 +151,13 @@ public class InfiniteLoopExecutor implements Interruptible
|
|||
|
||||
public void shutdown()
|
||||
{
|
||||
stateUpdater.updateAndGet(this, cur -> cur != TERMINATED ? SHUTTING_DOWN : TERMINATED);
|
||||
stateUpdater.updateAndGet(this, cur -> cur != TERMINATED && cur != SHUTTING_DOWN_NOW ? SHUTTING_DOWN : cur);
|
||||
interruptHandler.accept(thread);
|
||||
}
|
||||
|
||||
public Object shutdownNow()
|
||||
{
|
||||
state = TERMINATED;
|
||||
stateUpdater.updateAndGet(this, cur -> cur != TERMINATED ? SHUTTING_DOWN_NOW : TERMINATED);
|
||||
interruptHandler.accept(thread);
|
||||
return null;
|
||||
}
|
||||
|
|
@ -152,12 +165,16 @@ public class InfiniteLoopExecutor implements Interruptible
|
|||
@Override
|
||||
public boolean isTerminated()
|
||||
{
|
||||
return state == TERMINATED && !thread.isAlive();
|
||||
return state == TERMINATED;
|
||||
}
|
||||
|
||||
public boolean awaitTermination(long time, TimeUnit unit) throws InterruptedException
|
||||
{
|
||||
thread.join(unit.toMillis(time));
|
||||
if (isTerminated())
|
||||
return true;
|
||||
|
||||
long deadlineNanos = nanoTime() + unit.toNanos(time);
|
||||
isTerminated.awaitUntil(deadlineNanos);
|
||||
return isTerminated();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -53,16 +53,18 @@ public enum Stage
|
|||
MISC ("MiscStage", "internal", () -> 1, null, Stage::singleThreadedStage),
|
||||
TRACING ("TracingStage", "internal", () -> 1, null, Stage::tracingStage),
|
||||
INTERNAL_RESPONSE ("InternalResponseStage", "internal", FBUtilities::getAvailableProcessors, null, Stage::multiThreadedStage),
|
||||
IMMEDIATE ("ImmediateStage", "internal", () -> 0, null, Stage::immediateExecutor);
|
||||
IMMEDIATE ("ImmediateStage", "internal", () -> 0, null, Stage::immediateExecutor),
|
||||
PAXOS_REPAIR ("PaxosRepairStage", "internal", FBUtilities::getAvailableProcessors, null, Stage::multiThreadedStage),
|
||||
;
|
||||
|
||||
public final String jmxName;
|
||||
private final Supplier<ExecutorPlus> initialiser;
|
||||
private volatile ExecutorPlus executor = null;
|
||||
private final Supplier<ExecutorPlus> executorSupplier;
|
||||
private volatile ExecutorPlus executor;
|
||||
|
||||
Stage(String jmxName, String jmxType, IntSupplier numThreads, LocalAwareExecutorPlus.MaximumPoolSizeListener onSetMaximumPoolSize, ExecutorServiceInitialiser initialiser)
|
||||
Stage(String jmxName, String jmxType, IntSupplier numThreads, LocalAwareExecutorPlus.MaximumPoolSizeListener onSetMaximumPoolSize, ExecutorServiceInitialiser executorSupplier)
|
||||
{
|
||||
this.jmxName = jmxName;
|
||||
this.initialiser = () -> initialiser.init(jmxName, jmxType, numThreads.getAsInt(), onSetMaximumPoolSize);
|
||||
this.executorSupplier = () -> executorSupplier.init(jmxName, jmxType, numThreads.getAsInt(), onSetMaximumPoolSize);
|
||||
}
|
||||
|
||||
private static String normalizeName(String stageName)
|
||||
|
|
@ -128,7 +130,7 @@ public enum Stage
|
|||
{
|
||||
if (executor == null)
|
||||
{
|
||||
executor = initialiser.get();
|
||||
executor = executorSupplier.get();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,8 +71,7 @@ public class SyncFutureTask<T> extends SyncFuture<T> implements RunnableFuture<T
|
|||
catch (Throwable t)
|
||||
{
|
||||
tryFailure(t);
|
||||
Thread thread = Thread.currentThread();
|
||||
thread.getUncaughtExceptionHandler().uncaughtException(thread, t);
|
||||
ExecutionFailure.handle(t);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -244,7 +244,9 @@ public enum CassandraRelevantProperties
|
|||
TEST_SIMULATOR_PRINT_ASM("cassandra.test.simulator.print_asm", "none"),
|
||||
TEST_SIMULATOR_PRINT_ASM_TYPES("cassandra.test.simulator.print_asm_types", ""),
|
||||
TEST_SIMULATOR_LIVENESS_CHECK("cassandra.test.simulator.livenesscheck", "true"),
|
||||
TEST_SIMULATOR_DEBUG("cassandra.test.simulator.debug", "true"),
|
||||
TEST_SIMULATOR_DEBUG("cassandra.test.simulator.debug", "false"),
|
||||
TEST_SIMULATOR_DETERMINISM_CHECK("cassandra.test.simulator.determinismcheck", "none"),
|
||||
TEST_JVM_DTEST_DISABLE_SSL("cassandra.test.disable_ssl", "false"),
|
||||
|
||||
// determinism properties for testing
|
||||
DETERMINISM_SSTABLE_COMPRESSION_DEFAULT("cassandra.sstable_compression_default", "true"),
|
||||
|
|
@ -320,6 +322,15 @@ public enum CassandraRelevantProperties
|
|||
return STRING_CONVERTER.convert(value);
|
||||
}
|
||||
|
||||
public <T> T convert(PropertyConverter<T> converter)
|
||||
{
|
||||
String value = System.getProperty(key);
|
||||
if (value == null)
|
||||
value = defaultVal;
|
||||
|
||||
return converter.convert(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value into system properties.
|
||||
* @param value to set
|
||||
|
|
@ -415,7 +426,7 @@ public enum CassandraRelevantProperties
|
|||
System.setProperty(key, Long.toString(value));
|
||||
}
|
||||
|
||||
private interface PropertyConverter<T>
|
||||
public interface PropertyConverter<T>
|
||||
{
|
||||
T convert(String value);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import java.util.TreeMap;
|
|||
import java.util.function.Supplier;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
|
@ -48,6 +49,20 @@ public class Config
|
|||
{
|
||||
private static final Logger logger = LoggerFactory.getLogger(Config.class);
|
||||
|
||||
public static Set<String> splitCommaDelimited(String src)
|
||||
{
|
||||
if (src == null)
|
||||
return ImmutableSet.of();
|
||||
String[] split = src.split(",\\s*");
|
||||
ImmutableSet.Builder<String> builder = ImmutableSet.builder();
|
||||
for (String s : split)
|
||||
{
|
||||
s = s.trim();
|
||||
if (!s.isEmpty())
|
||||
builder.add(s);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
/*
|
||||
* Prefix for Java properties for internal Cassandra configuration options
|
||||
*/
|
||||
|
|
@ -125,11 +140,13 @@ public class Config
|
|||
public volatile SmallestDurationMilliseconds counter_write_request_timeout = new SmallestDurationMilliseconds("5000ms");
|
||||
|
||||
@Replaces(oldName = "cas_contention_timeout_in_ms", converter = Converters.MILLIS_DURATION, deprecated = true)
|
||||
public volatile SmallestDurationMilliseconds cas_contention_timeout = new SmallestDurationMilliseconds("1000ms");
|
||||
public volatile SmallestDurationMilliseconds cas_contention_timeout = new SmallestDurationMilliseconds("1800ms");
|
||||
|
||||
@Replaces(oldName = "truncate_request_timeout_in_ms", converter = Converters.MILLIS_DURATION, deprecated = true)
|
||||
public volatile SmallestDurationMilliseconds truncate_request_timeout = new SmallestDurationMilliseconds("60000ms");
|
||||
|
||||
public volatile Long repair_request_timeout_in_ms = 120000L;
|
||||
|
||||
public Integer streaming_connections_per_host = 1;
|
||||
@Replaces(oldName = "streaming_keep_alive_period_in_secs", converter = Converters.SECONDS_DURATION, deprecated = true)
|
||||
public SmallestDurationSeconds streaming_keep_alive_period = new SmallestDurationSeconds("300s");
|
||||
|
|
@ -406,6 +423,8 @@ public class Config
|
|||
public volatile SmallestDurationSeconds counter_cache_save_period = new SmallestDurationSeconds("7200s");
|
||||
public volatile int counter_cache_keys_to_save = Integer.MAX_VALUE;
|
||||
|
||||
public SmallestDataStorageMebibytes paxos_cache_size = null;
|
||||
|
||||
@Replaces(oldName = "cache_load_timeout_seconds ", converter = Converters.SECONDS_DURATION, deprecated = true)
|
||||
public SmallestDurationSeconds cache_load_timeout = new SmallestDurationSeconds("30s");
|
||||
|
||||
|
|
@ -780,13 +799,190 @@ public class Config
|
|||
/** The configuration of startup checks. */
|
||||
public volatile Map<StartupCheckType, Map<String, Object>> startup_checks = new HashMap<>();
|
||||
|
||||
/**
|
||||
* The variants of paxos implementation and semantics supported by Cassandra.
|
||||
*/
|
||||
public enum PaxosVariant
|
||||
{
|
||||
v1_without_linearizable_reads, // with legacy semantics for read/read linearizability (i.e. not guaranteed)
|
||||
v1
|
||||
/**
|
||||
* v1 Paxos lacks most optimisations. Expect 4RTs for a write and 2RTs for a read.
|
||||
*
|
||||
* With legacy semantics for read/read and rejected write linearizability, i.e. not guaranteed.
|
||||
*/
|
||||
v1_without_linearizable_reads_or_rejected_writes,
|
||||
|
||||
/**
|
||||
* v1 Paxos lacks most optimisations. Expect 4RTs for a write and 3RTs for a read.
|
||||
*/
|
||||
v1,
|
||||
|
||||
/**
|
||||
* v2 Paxos. With PaxosStatePurging.repaired safe to use ANY Commit consistency.
|
||||
* Expect 2RTs for a write and 1RT for a read.
|
||||
*
|
||||
* With legacy semantics for read/read linearizability, i.e. not guaranteed.
|
||||
*/
|
||||
v2_without_linearizable_reads,
|
||||
|
||||
/**
|
||||
* v2 Paxos. With PaxosStatePurging.repaired safe to use ANY Commit consistency.
|
||||
* Expect 2RTs for a write and 1RT for a read.
|
||||
*
|
||||
* With legacy semantics for read/read and rejected write linearizability, i.e. not guaranteed.
|
||||
*/
|
||||
v2_without_linearizable_reads_or_rejected_writes,
|
||||
|
||||
/**
|
||||
* v2 Paxos. With PaxosStatePurging.repaired safe to use ANY Commit consistency.
|
||||
* Expect 2RTs for a write, and either 1RT or 2RT for a read.
|
||||
*/
|
||||
v2
|
||||
}
|
||||
|
||||
/**
|
||||
* Select the kind of paxos state purging to use. Migration to repaired is recommended, but requires that
|
||||
* regular paxos repairs are performed (which by default run as part of incremental repair).
|
||||
*
|
||||
* Once migrated from legacy it is unsafe to return to legacy, but gc_grace mode may be used in its place
|
||||
* and performs a very similar cleanup process.
|
||||
*
|
||||
* Should only be modified once paxos_variant = v2.
|
||||
*/
|
||||
public enum PaxosStatePurging
|
||||
{
|
||||
/**
|
||||
* system.paxos records are written and garbage collected with TTLs. Unsafe to use with Commit consistency ANY.
|
||||
* Once migrated from, cannot be migrated back to safely. Must use gc_grace or repaired instead, as TTLs
|
||||
* will not have been set.
|
||||
*/
|
||||
legacy,
|
||||
|
||||
/**
|
||||
* Functionally similar to legacy, but the gc_grace expiry is applied at compaction and read time rather than
|
||||
* using TTLs, so may be safely enabled at any point.
|
||||
*/
|
||||
gc_grace,
|
||||
|
||||
/**
|
||||
* Clears system.paxos records only once they are known to be persisted to a quorum of replica's base tables
|
||||
* through the use of paxos repair. Requires that regular paxos repairs are performed on the cluster
|
||||
* (which by default are included in incremental repairs if paxos_variant = v2).
|
||||
*
|
||||
* This setting permits the use of Commit consistency ANY or LOCAL_QUORUM without any loss of durability or consistency,
|
||||
* saving 1 RT.
|
||||
*/
|
||||
repaired;
|
||||
|
||||
public static PaxosStatePurging fromBoolean(boolean enabled)
|
||||
{
|
||||
return enabled ? repaired : gc_grace;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link PaxosVariant}. Defaults to v1, recommend upgrading to v2 at earliest opportunity.
|
||||
*/
|
||||
public volatile PaxosVariant paxos_variant = PaxosVariant.v1;
|
||||
|
||||
/**
|
||||
* If true, paxos topology change repair will not run on a topology change - this option should only be used in
|
||||
* rare operation circumstances e.g. where for some reason the repair is impossible to perform (e.g. too few replicas)
|
||||
* and an unsafe topology change must be made
|
||||
*/
|
||||
public volatile boolean skip_paxos_repair_on_topology_change = Boolean.getBoolean("cassandra.skip_paxos_repair_on_topology_change");
|
||||
|
||||
/**
|
||||
* A safety margin when purging paxos state information that has been safely replicated to a quorum.
|
||||
* Data for transactions initiated within this grace period will not be expunged.
|
||||
*/
|
||||
public volatile DurationSpec paxos_purge_grace_period = new SmallestDurationSeconds("60s");
|
||||
|
||||
/**
|
||||
* A safety mechanism for detecting incorrect paxos state, that may be down either to a bug or incorrect usage of LWTs
|
||||
* (most likely due to unsafe mixing of SERIAL and LOCAL_SERIAL operations), and rejecting
|
||||
*/
|
||||
public enum PaxosOnLinearizabilityViolation
|
||||
{
|
||||
// reject an operation when a linearizability violation is detected.
|
||||
// note this does not guarantee a violation has been averted,
|
||||
// as it may be a prior operation that invalidated the state.
|
||||
fail,
|
||||
// log any detected linearizability violation
|
||||
log,
|
||||
// ignore any detected linearizability violation
|
||||
ignore
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link PaxosOnLinearizabilityViolation}.
|
||||
*
|
||||
* Default is to ignore, as applications may readily mix SERIAL and LOCAL_SERIAL and this is the most likely source
|
||||
* of linearizability violations. this facility should be activated only for debugging Cassandra or by power users
|
||||
* who are investigating their own application behaviour.
|
||||
*/
|
||||
public volatile PaxosOnLinearizabilityViolation paxos_on_linearizability_violations = PaxosOnLinearizabilityViolation.ignore;
|
||||
|
||||
/**
|
||||
* See {@link PaxosStatePurging} default is legacy.
|
||||
*/
|
||||
public volatile PaxosStatePurging paxos_state_purging;
|
||||
|
||||
/**
|
||||
* Enable/disable paxos repair. This is a global flag that not only determines default behaviour but overrides
|
||||
* explicit paxos repair requests, paxos repair on topology changes and paxos auto repairs.
|
||||
*/
|
||||
public volatile boolean paxos_repair_enabled = true;
|
||||
|
||||
/**
|
||||
* If true, paxos topology change repair only requires a global quorum of live nodes. If false,
|
||||
* it requires a global quorum as well as a local quorum for each dc (EACH_QUORUM), with the
|
||||
* exception explained in paxos_topology_repair_strict_each_quorum
|
||||
*/
|
||||
public boolean paxos_topology_repair_no_dc_checks = false;
|
||||
|
||||
/**
|
||||
* If true, a quorum will be required for the global and local quorum checks. If false, we will
|
||||
* accept a quorum OR n - 1 live nodes. This is to allow for topologies like 2:2:2, where paxos queries
|
||||
* always use SERIAL, and a single node down in a dc should not preclude a paxos repair
|
||||
*/
|
||||
public boolean paxos_topology_repair_strict_each_quorum = false;
|
||||
|
||||
/**
|
||||
* If necessary for operational purposes, permit certain keyspaces to be ignored for paxos topology repairs
|
||||
*/
|
||||
public volatile Set<String> skip_paxos_repair_on_topology_change_keyspaces = splitCommaDelimited(System.getProperty("cassandra.skip_paxos_repair_on_topology_change_keyspaces"));
|
||||
|
||||
/**
|
||||
* See {@link org.apache.cassandra.service.paxos.ContentionStrategy}
|
||||
*/
|
||||
public String paxos_contention_wait_randomizer;
|
||||
|
||||
/**
|
||||
* See {@link org.apache.cassandra.service.paxos.ContentionStrategy}
|
||||
*/
|
||||
public String paxos_contention_min_wait;
|
||||
|
||||
/**
|
||||
* See {@link org.apache.cassandra.service.paxos.ContentionStrategy}
|
||||
*/
|
||||
public String paxos_contention_max_wait;
|
||||
|
||||
/**
|
||||
* See {@link org.apache.cassandra.service.paxos.ContentionStrategy}
|
||||
*/
|
||||
public String paxos_contention_min_delta;
|
||||
|
||||
/**
|
||||
* The amount of disk space paxos uncommitted key files can consume before we begin automatically scheduling paxos repairs.
|
||||
* Note that these repairs are uncoordinated and so do not contribute to expunging system.paxos records.
|
||||
*/
|
||||
public volatile int paxos_auto_repair_threshold_mb = 32;
|
||||
|
||||
/**
|
||||
* The number of keys we may simultaneously attempt to finish incomplete paxos operations for.
|
||||
*/
|
||||
public volatile int paxos_repair_parallelism = -1;
|
||||
|
||||
public static Supplier<Config> getOverrideLoadConfig()
|
||||
{
|
||||
return overrideLoadConfig;
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ public class DataStorageSpec
|
|||
|
||||
public DataStorageSpec (String value, DataStorageUnit minUnit)
|
||||
{
|
||||
if (value == null || value.equals("null"))
|
||||
if (value == null || value.equals("null") || value.equals("0"))
|
||||
{
|
||||
quantity = 0;
|
||||
unit = minUnit;
|
||||
|
|
|
|||
|
|
@ -32,6 +32,15 @@ import com.google.common.primitives.Ints;
|
|||
import com.google.common.primitives.Longs;
|
||||
import com.google.common.util.concurrent.RateLimiter;
|
||||
|
||||
import org.apache.cassandra.config.Config.PaxosOnLinearizabilityViolation;
|
||||
import org.apache.cassandra.db.ConsistencyLevel;
|
||||
import org.apache.cassandra.gms.IFailureDetector;
|
||||
import org.apache.cassandra.io.util.File;
|
||||
import org.apache.cassandra.config.Config.PaxosStatePurging;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.apache.cassandra.audit.AuditLogOptions;
|
||||
import org.apache.cassandra.fql.FullQueryLoggerOptions;
|
||||
import org.apache.cassandra.auth.AllowAllInternodeAuthenticator;
|
||||
|
|
@ -42,15 +51,12 @@ import org.apache.cassandra.auth.IInternodeAuthenticator;
|
|||
import org.apache.cassandra.auth.INetworkAuthorizer;
|
||||
import org.apache.cassandra.auth.IRoleManager;
|
||||
import org.apache.cassandra.config.Config.CommitLogSync;
|
||||
import org.apache.cassandra.db.ConsistencyLevel;
|
||||
import org.apache.cassandra.db.commitlog.AbstractCommitLogSegmentManager;
|
||||
import org.apache.cassandra.db.commitlog.CommitLog;
|
||||
import org.apache.cassandra.db.commitlog.CommitLogSegmentManagerCDC;
|
||||
import org.apache.cassandra.db.commitlog.CommitLogSegmentManagerStandard;
|
||||
import org.apache.cassandra.dht.IPartitioner;
|
||||
import org.apache.cassandra.exceptions.ConfigurationException;
|
||||
import org.apache.cassandra.gms.IFailureDetector;
|
||||
import org.apache.cassandra.io.util.File;
|
||||
import org.apache.cassandra.io.FSWriteError;
|
||||
import org.apache.cassandra.io.util.DiskOptimizationStrategy;
|
||||
import org.apache.cassandra.io.util.FileUtils;
|
||||
|
|
@ -81,6 +87,7 @@ import static org.apache.cassandra.config.CassandraRelevantProperties.OS_ARCH;
|
|||
import static org.apache.cassandra.config.CassandraRelevantProperties.SUN_ARCH_DATA_MODEL;
|
||||
import static org.apache.cassandra.io.util.FileUtils.ONE_GIB;
|
||||
import static org.apache.cassandra.io.util.FileUtils.ONE_MIB;
|
||||
import static org.apache.cassandra.config.CassandraRelevantProperties.TEST_JVM_DTEST_DISABLE_SSL;
|
||||
import static org.apache.cassandra.utils.Clock.Global.logInitializationOutcome;
|
||||
|
||||
public class DatabaseDescriptor
|
||||
|
|
@ -132,6 +139,7 @@ public class DatabaseDescriptor
|
|||
private static long preparedStatementsCacheSizeInMiB;
|
||||
|
||||
private static long keyCacheSizeInMiB;
|
||||
private static long paxosCacheSizeInMiB;
|
||||
private static long counterCacheSizeInMiB;
|
||||
private static long indexSummaryCapacityInMiB;
|
||||
|
||||
|
|
@ -730,6 +738,28 @@ public class DatabaseDescriptor
|
|||
+ (conf.counter_cache_size !=null ?conf.counter_cache_size.toString() : null) + "', supported values are <integer> >= 0.", false);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// if paxosCacheSizeInMiB option was set to "auto" then size of the cache should be "min(1% of Heap (in MB), 50MB)
|
||||
paxosCacheSizeInMiB = (conf.paxos_cache_size == null)
|
||||
? Math.min(Math.max(1, (int) (Runtime.getRuntime().totalMemory() * 0.01 / 1024 / 1024)), 50)
|
||||
: conf.paxos_cache_size.toMebibytes();
|
||||
|
||||
if (paxosCacheSizeInMiB < 0)
|
||||
throw new NumberFormatException(); // to escape duplicating error message
|
||||
}
|
||||
catch (NumberFormatException e)
|
||||
{
|
||||
throw new ConfigurationException("paxos_cache_size option was set incorrectly to '"
|
||||
+ conf.paxos_cache_size + "', supported values are <integer> >= 0.", false);
|
||||
}
|
||||
|
||||
if (conf.paxos_auto_repair_threshold_mb < 0)
|
||||
{
|
||||
throw new ConfigurationException("paxos_auto_repair_threshold_mb option was set incorrectly to '"
|
||||
+ conf.paxos_auto_repair_threshold_mb + "', supported values are <integer> >= 0.", false);
|
||||
}
|
||||
|
||||
// if set to empty/"auto" then use 5% of Heap size
|
||||
indexSummaryCapacityInMiB = (conf.index_summary_capacity == null)
|
||||
? Math.max(1, (int) (Runtime.getRuntime().totalMemory() * 0.05 / 1024 / 1024))
|
||||
|
|
@ -844,18 +874,12 @@ public class DatabaseDescriptor
|
|||
conf.default_keyspace_rf, conf.minimum_keyspace_rf));
|
||||
}
|
||||
|
||||
if (conf.paxos_variant == Config.PaxosVariant.v1_without_linearizable_reads)
|
||||
{
|
||||
logger.warn("This node was started with paxos_variant config option set to v1_norrl. " +
|
||||
"SERIAL (and LOCAL_SERIAL) reads coordinated by this node " +
|
||||
"will not offer linearizability (see CASSANDRA-12126 for details on what this mean) with " +
|
||||
"respect to other SERIAL operations. Please note that, with this option, SERIAL reads will be " +
|
||||
"slower than QUORUM reads, yet offer no more guarantee. This flag should only be used in " +
|
||||
"the restricted case of upgrading from a pre-CASSANDRA-12126 version, and only if you " +
|
||||
"understand the tradeoff.");
|
||||
}
|
||||
if (conf.paxos_repair_parallelism <= 0)
|
||||
conf.paxos_repair_parallelism = Math.max(1, conf.concurrent_writes / 8);
|
||||
|
||||
Paxos.setPaxosVariant(conf.paxos_variant);
|
||||
if (conf.paxos_state_purging == null)
|
||||
conf.paxos_state_purging = PaxosStatePurging.legacy;
|
||||
|
||||
logInitializationOutcome(logger);
|
||||
}
|
||||
|
|
@ -1053,6 +1077,9 @@ public class DatabaseDescriptor
|
|||
|
||||
public static void applySslContext()
|
||||
{
|
||||
if (TEST_JVM_DTEST_DISABLE_SSL.getBoolean())
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
SSLFactory.validateSslContext("Internode messaging", conf.server_encryption_options, true, true);
|
||||
|
|
@ -1756,6 +1783,16 @@ public class DatabaseDescriptor
|
|||
conf.truncate_request_timeout = SmallestDurationMilliseconds.inMilliseconds(timeOutInMillis);
|
||||
}
|
||||
|
||||
public static long getRepairRpcTimeout()
|
||||
{
|
||||
return conf.repair_request_timeout_in_ms;
|
||||
}
|
||||
|
||||
public static void setRepairRpcTimeout(Long timeOutInMillis)
|
||||
{
|
||||
conf.repair_request_timeout_in_ms = timeOutInMillis;
|
||||
}
|
||||
|
||||
public static boolean hasCrossNodeTimeout()
|
||||
{
|
||||
return conf.internode_timeout;
|
||||
|
|
@ -2462,6 +2499,126 @@ public class DatabaseDescriptor
|
|||
conf.paxos_variant = variant;
|
||||
}
|
||||
|
||||
public static String getPaxosContentionWaitRandomizer()
|
||||
{
|
||||
return conf.paxos_contention_wait_randomizer;
|
||||
}
|
||||
|
||||
public static String getPaxosContentionMinWait()
|
||||
{
|
||||
return conf.paxos_contention_min_wait;
|
||||
}
|
||||
|
||||
public static String getPaxosContentionMaxWait()
|
||||
{
|
||||
return conf.paxos_contention_max_wait;
|
||||
}
|
||||
|
||||
public static String getPaxosContentionMinDelta()
|
||||
{
|
||||
return conf.paxos_contention_min_delta;
|
||||
}
|
||||
|
||||
public static void setPaxosContentionWaitRandomizer(String waitRandomizer)
|
||||
{
|
||||
conf.paxos_contention_wait_randomizer = waitRandomizer;
|
||||
}
|
||||
|
||||
public static void setPaxosContentionMinWait(String minWait)
|
||||
{
|
||||
conf.paxos_contention_min_wait = minWait;
|
||||
}
|
||||
|
||||
public static void setPaxosContentionMaxWait(String maxWait)
|
||||
{
|
||||
conf.paxos_contention_max_wait = maxWait;
|
||||
}
|
||||
|
||||
public static void setPaxosContentionMinDelta(String minDelta)
|
||||
{
|
||||
conf.paxos_contention_min_delta = minDelta;
|
||||
}
|
||||
|
||||
public static boolean skipPaxosRepairOnTopologyChange()
|
||||
{
|
||||
return conf.skip_paxos_repair_on_topology_change;
|
||||
}
|
||||
|
||||
public static void setSkipPaxosRepairOnTopologyChange(boolean value)
|
||||
{
|
||||
conf.skip_paxos_repair_on_topology_change = value;
|
||||
}
|
||||
|
||||
public static long getPaxosPurgeGrace(TimeUnit units)
|
||||
{
|
||||
return conf.paxos_purge_grace_period.to(units);
|
||||
}
|
||||
|
||||
public static void setPaxosPurgeGrace(long value, TimeUnit units)
|
||||
{
|
||||
conf.paxos_purge_grace_period = new DurationSpec(value, units);
|
||||
}
|
||||
|
||||
public static PaxosOnLinearizabilityViolation paxosOnLinearizabilityViolations()
|
||||
{
|
||||
return conf.paxos_on_linearizability_violations;
|
||||
}
|
||||
|
||||
public static void setPaxosOnLinearizabilityViolations(PaxosOnLinearizabilityViolation v)
|
||||
{
|
||||
conf.paxos_on_linearizability_violations = v;
|
||||
}
|
||||
|
||||
public static PaxosStatePurging paxosStatePurging()
|
||||
{
|
||||
return conf.paxos_state_purging;
|
||||
}
|
||||
|
||||
public static void setPaxosStatePurging(PaxosStatePurging v)
|
||||
{
|
||||
conf.paxos_state_purging = v;
|
||||
}
|
||||
|
||||
public static boolean paxosRepairEnabled()
|
||||
{
|
||||
return conf.paxos_repair_enabled;
|
||||
}
|
||||
|
||||
public static void setPaxosRepairEnabled(boolean v)
|
||||
{
|
||||
conf.paxos_repair_enabled = v;
|
||||
}
|
||||
|
||||
public static Set<String> skipPaxosRepairOnTopologyChangeKeyspaces()
|
||||
{
|
||||
return conf.skip_paxos_repair_on_topology_change_keyspaces;
|
||||
}
|
||||
|
||||
public static void setSkipPaxosRepairOnTopologyChangeKeyspaces(String keyspaces)
|
||||
{
|
||||
conf.skip_paxos_repair_on_topology_change_keyspaces = Config.splitCommaDelimited(keyspaces);
|
||||
}
|
||||
|
||||
public static boolean paxoTopologyRepairNoDcChecks()
|
||||
{
|
||||
return conf.paxos_topology_repair_no_dc_checks;
|
||||
}
|
||||
|
||||
public static boolean paxoTopologyRepairStrictEachQuorum()
|
||||
{
|
||||
return conf.paxos_topology_repair_strict_each_quorum;
|
||||
}
|
||||
|
||||
public static int getPaxosAutoRepairThresholdMB()
|
||||
{
|
||||
return conf.paxos_auto_repair_threshold_mb;
|
||||
}
|
||||
|
||||
public static void setPaxosAutoRepairThresholdMB(int threshold)
|
||||
{
|
||||
conf.paxos_auto_repair_threshold_mb = threshold;
|
||||
}
|
||||
|
||||
public static void setNativeTransportMaxConcurrentRequestsInBytesPerIp(long maxConcurrentRequestsInBytes)
|
||||
{
|
||||
conf.native_transport_max_concurrent_requests_in_bytes_per_ip = maxConcurrentRequestsInBytes;
|
||||
|
|
@ -2952,6 +3109,11 @@ public class DatabaseDescriptor
|
|||
return conf.row_cache_keys_to_save;
|
||||
}
|
||||
|
||||
public static long getPaxosCacheSizeInMiB()
|
||||
{
|
||||
return paxosCacheSizeInMiB;
|
||||
}
|
||||
|
||||
public static long getCounterCacheSizeInMiB()
|
||||
{
|
||||
return counterCacheSizeInMiB;
|
||||
|
|
@ -3081,6 +3243,17 @@ public class DatabaseDescriptor
|
|||
conf.repair_session_space = SmallestDataStorageMebibytes.inMebibytes(sizeInMiB);
|
||||
}
|
||||
|
||||
public static int getPaxosRepairParallelism()
|
||||
{
|
||||
return conf.paxos_repair_parallelism;
|
||||
}
|
||||
|
||||
public static void setPaxosRepairParallelism(int v)
|
||||
{
|
||||
Preconditions.checkArgument(v > 0);
|
||||
conf.paxos_repair_parallelism = v;
|
||||
}
|
||||
|
||||
public static Float getMemtableCleanupThreshold()
|
||||
{
|
||||
return conf.memtable_cleanup_threshold;
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ public class DurationSpec
|
|||
|
||||
public DurationSpec(String value)
|
||||
{
|
||||
if (value == null || value.equals("null") || value.toLowerCase(Locale.ROOT).equals("nan"))
|
||||
if (value == null || value.equals("null") || value.toLowerCase(Locale.ROOT).equals("nan") || value.equals("0"))
|
||||
{
|
||||
quantity = 0;
|
||||
unit = MILLISECONDS;
|
||||
|
|
@ -74,7 +74,7 @@ public class DurationSpec
|
|||
//parse the string field value
|
||||
Matcher matcher = TIME_UNITS_PATTERN.matcher(value);
|
||||
|
||||
if(matcher.find())
|
||||
if (matcher.find())
|
||||
{
|
||||
quantity = Long.parseLong(matcher.group(1));
|
||||
unit = fromSymbol(matcher.group(2));
|
||||
|
|
|
|||
|
|
@ -25,6 +25,10 @@ import java.util.Map;
|
|||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
import javax.net.ssl.SSLException;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
|
|
@ -33,6 +37,8 @@ import org.slf4j.LoggerFactory;
|
|||
|
||||
import org.apache.cassandra.locator.IEndpointSnitch;
|
||||
import org.apache.cassandra.locator.InetAddressAndPort;
|
||||
import org.apache.cassandra.security.AbstractSslContextFactory;
|
||||
import org.apache.cassandra.security.DisableSslContextFactory;
|
||||
import org.apache.cassandra.security.ISslContextFactory;
|
||||
import org.apache.cassandra.utils.FBUtilities;
|
||||
|
||||
|
|
@ -277,8 +283,15 @@ public class EncryptionOptions
|
|||
putSslContextFactoryParameter(sslContextFactoryParameters, ConfigKey.ENABLED, this.enabled);
|
||||
putSslContextFactoryParameter(sslContextFactoryParameters, ConfigKey.OPTIONAL, this.optional);
|
||||
|
||||
sslContextFactoryInstance = FBUtilities.newSslContextFactory(ssl_context_factory.class_name,
|
||||
sslContextFactoryParameters);
|
||||
if (CassandraRelevantProperties.TEST_JVM_DTEST_DISABLE_SSL.getBoolean())
|
||||
{
|
||||
sslContextFactoryInstance = new DisableSslContextFactory();
|
||||
}
|
||||
else
|
||||
{
|
||||
sslContextFactoryInstance = FBUtilities.newSslContextFactory(ssl_context_factory.class_name,
|
||||
sslContextFactoryParameters);
|
||||
}
|
||||
}
|
||||
|
||||
private void putSslContextFactoryParameter(Map<String,Object> existingParameters, ConfigKey configKey,
|
||||
|
|
|
|||
|
|
@ -60,6 +60,11 @@ public abstract class QueryOptions
|
|||
return new DefaultQueryOptions(consistency, values, false, SpecificOptions.DEFAULT, ProtocolVersion.V3);
|
||||
}
|
||||
|
||||
public static QueryOptions forInternalCallsWithNowInSec(int nowInSec, ConsistencyLevel consistency, List<ByteBuffer> values)
|
||||
{
|
||||
return new DefaultQueryOptions(consistency, values, false, SpecificOptions.DEFAULT.withNowInSec(nowInSec), ProtocolVersion.CURRENT);
|
||||
}
|
||||
|
||||
public static QueryOptions forInternalCalls(List<ByteBuffer> values)
|
||||
{
|
||||
return new DefaultQueryOptions(ConsistencyLevel.ONE, values, false, SpecificOptions.DEFAULT, ProtocolVersion.V3);
|
||||
|
|
@ -209,6 +214,12 @@ public abstract class QueryOptions
|
|||
/** The keyspace that this query is bound to, or null if not relevant. */
|
||||
public String getKeyspace() { return getSpecificOptions().keyspace; }
|
||||
|
||||
public int getNowInSec(int ifNotSet)
|
||||
{
|
||||
int nowInSec = getSpecificOptions().nowInSeconds;
|
||||
return nowInSec != Integer.MIN_VALUE ? nowInSec : ifNotSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* The protocol version for the query.
|
||||
*/
|
||||
|
|
@ -506,6 +517,11 @@ public abstract class QueryOptions
|
|||
this.keyspace = keyspace;
|
||||
this.nowInSeconds = nowInSeconds;
|
||||
}
|
||||
|
||||
public SpecificOptions withNowInSec(int nowInSec)
|
||||
{
|
||||
return new SpecificOptions(pageSize, state, serialConsistency, timestamp, keyspace, nowInSec);
|
||||
}
|
||||
}
|
||||
|
||||
private static class Codec implements CBCodec<QueryOptions>
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ import org.apache.cassandra.cql3.functions.Function;
|
|||
import org.apache.cassandra.cql3.functions.FunctionName;
|
||||
import org.apache.cassandra.cql3.statements.*;
|
||||
import org.apache.cassandra.db.*;
|
||||
import org.apache.cassandra.db.rows.Row;
|
||||
import org.apache.cassandra.db.rows.RowIterator;
|
||||
import org.apache.cassandra.db.partitions.PartitionIterator;
|
||||
import org.apache.cassandra.db.partitions.PartitionIterators;
|
||||
|
|
@ -375,6 +376,16 @@ public class QueryProcessor implements QueryHandler
|
|||
}
|
||||
|
||||
private static QueryOptions makeInternalOptions(CQLStatement prepared, Object[] values, ConsistencyLevel cl)
|
||||
{
|
||||
return makeInternalOptionsWithNowInSec(prepared, FBUtilities.nowInSeconds(), values, cl);
|
||||
}
|
||||
|
||||
public static QueryOptions makeInternalOptionsWithNowInSec(CQLStatement prepared, int nowInSec, Object[] values)
|
||||
{
|
||||
return makeInternalOptionsWithNowInSec(prepared, nowInSec, values, ConsistencyLevel.ONE);
|
||||
}
|
||||
|
||||
private static QueryOptions makeInternalOptionsWithNowInSec(CQLStatement prepared, int nowInSec, Object[] values, ConsistencyLevel cl)
|
||||
{
|
||||
if (prepared.getBindVariables().size() != values.length)
|
||||
throw new IllegalArgumentException(String.format("Invalid number of values. Expecting %d but got %d", prepared.getBindVariables().size(), values.length));
|
||||
|
|
@ -383,10 +394,10 @@ public class QueryProcessor implements QueryHandler
|
|||
for (int i = 0; i < values.length; i++)
|
||||
{
|
||||
Object value = values[i];
|
||||
AbstractType type = prepared.getBindVariables().get(i).type;
|
||||
boundValues.add(value instanceof ByteBuffer || value == null ? (ByteBuffer)value : type.decompose(value));
|
||||
AbstractType<?> type = prepared.getBindVariables().get(i).type;
|
||||
boundValues.add(value instanceof ByteBuffer || value == null ? (ByteBuffer)value : type.decomposeUntyped(value));
|
||||
}
|
||||
return QueryOptions.forInternalCalls(cl, boundValues);
|
||||
return QueryOptions.forInternalCallsWithNowInSec(nowInSec, cl, boundValues);
|
||||
}
|
||||
|
||||
public static Prepared prepareInternal(String query) throws RequestValidationException
|
||||
|
|
@ -497,6 +508,16 @@ public class QueryProcessor implements QueryHandler
|
|||
return execute(query, cl, internalQueryState(), values);
|
||||
}
|
||||
|
||||
public static UntypedResultSet executeInternalWithNowInSec(String query, int nowInSec, Object... values)
|
||||
{
|
||||
Prepared prepared = prepareInternal(query);
|
||||
ResultMessage result = prepared.statement.executeLocally(internalQueryState(), makeInternalOptionsWithNowInSec(prepared.statement, nowInSec, values));
|
||||
if (result instanceof ResultMessage.Rows)
|
||||
return UntypedResultSet.create(((ResultMessage.Rows)result).result);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
public static UntypedResultSet execute(String query, ConsistencyLevel cl, QueryState state, Object... values)
|
||||
throws RequestExecutionException
|
||||
{
|
||||
|
|
@ -572,6 +593,19 @@ public class QueryProcessor implements QueryHandler
|
|||
return UntypedResultSet.create(((ResultMessage.Rows)result).result);
|
||||
}
|
||||
|
||||
/**
|
||||
* A special version of executeInternal that takes the time used as "now" for the query in argument.
|
||||
* Note that this only make sense for Selects so this only accept SELECT statements and is only useful in rare
|
||||
* cases.
|
||||
*/
|
||||
public static Map<DecoratedKey, List<Row>> executeInternalRawWithNow(int nowInSec, String query, Object... values)
|
||||
{
|
||||
Prepared prepared = prepareInternal(query);
|
||||
assert prepared.statement instanceof SelectStatement;
|
||||
SelectStatement select = (SelectStatement)prepared.statement;
|
||||
return select.executeRawInternal(makeInternalOptions(prepared.statement, values), internalQueryState().getClientState(), nowInSec);
|
||||
}
|
||||
|
||||
public static UntypedResultSet resultify(String query, RowIterator partition)
|
||||
{
|
||||
return resultify(query, PartitionIterators.singletonIterator(partition));
|
||||
|
|
|
|||
|
|
@ -374,6 +374,12 @@ public abstract class UntypedResultSet implements Iterable<UntypedResultSet.Row>
|
|||
return Int32Type.instance.compose(data.get(column));
|
||||
}
|
||||
|
||||
public int getInt(String column, int ifNull)
|
||||
{
|
||||
ByteBuffer bytes = data.get(column);
|
||||
return bytes == null ? ifNull : Int32Type.instance.compose(bytes);
|
||||
}
|
||||
|
||||
public double getDouble(String column)
|
||||
{
|
||||
return DoubleType.instance.compose(data.get(column));
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ public abstract class TimeFcts
|
|||
|
||||
/**
|
||||
* Function that convert a value of <code>TIMEUUID</code> into a value of type <code>TIMESTAMP</code>.
|
||||
* @deprecated Replaced by the {@link #timeUuidToTimestamp} function
|
||||
* @deprecated Replaced by the {@link #toTimestamp} function
|
||||
*/
|
||||
public static final NativeScalarFunction dateOfFct = new NativeScalarFunction("dateof", TimestampType.instance, TimeUUIDType.instance)
|
||||
{
|
||||
|
|
@ -120,7 +120,7 @@ public abstract class TimeFcts
|
|||
|
||||
/**
|
||||
* Function that convert a value of type <code>TIMEUUID</code> into an UNIX timestamp.
|
||||
* @deprecated Replaced by the {@link #timeUuidToUnixTimestamp} function
|
||||
* @deprecated Replaced by the {@link #toUnixTimestamp} function
|
||||
*/
|
||||
public static final NativeScalarFunction unixTimestampOfFct = new NativeScalarFunction("unixtimestampof", LongType.instance, TimeUUIDType.instance)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ import org.apache.cassandra.db.partitions.PartitionUpdate;
|
|||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
import org.apache.cassandra.service.CASRequest;
|
||||
import org.apache.cassandra.service.ClientState;
|
||||
import org.apache.cassandra.service.paxos.Ballot;
|
||||
import org.apache.cassandra.utils.Pair;
|
||||
import org.apache.cassandra.utils.TimeUUID;
|
||||
|
||||
|
|
@ -180,7 +181,7 @@ public class CQL3CasRequest implements CASRequest
|
|||
return new RegularAndStaticColumns(statics, regulars);
|
||||
}
|
||||
|
||||
public SinglePartitionReadQuery readCommand(int nowInSec)
|
||||
public SinglePartitionReadCommand readCommand(int nowInSec)
|
||||
{
|
||||
assert staticConditions != null || !conditions.isEmpty();
|
||||
|
||||
|
|
@ -190,7 +191,7 @@ public class CQL3CasRequest implements CASRequest
|
|||
// With only a static condition, we still want to make the distinction between a non-existing partition and one
|
||||
// that exists (has some live data) but has not static content. So we query the first live row of the partition.
|
||||
if (conditions.isEmpty())
|
||||
return SinglePartitionReadQuery.create(metadata,
|
||||
return SinglePartitionReadCommand.create(metadata,
|
||||
nowInSec,
|
||||
columnFilter,
|
||||
RowFilter.NONE,
|
||||
|
|
@ -199,7 +200,7 @@ public class CQL3CasRequest implements CASRequest
|
|||
new ClusteringIndexSliceFilter(Slices.ALL, false));
|
||||
|
||||
ClusteringIndexNamesFilter filter = new ClusteringIndexNamesFilter(conditions.navigableKeySet(), false);
|
||||
return SinglePartitionReadQuery.create(metadata, nowInSec, key, columnFilter, filter);
|
||||
return SinglePartitionReadCommand.create(metadata, nowInSec, key, columnFilter, filter);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -232,14 +233,14 @@ public class CQL3CasRequest implements CASRequest
|
|||
return builder.build();
|
||||
}
|
||||
|
||||
public PartitionUpdate makeUpdates(FilteredPartition current, ClientState state, TimeUUID ballot) throws InvalidRequestException
|
||||
public PartitionUpdate makeUpdates(FilteredPartition current, ClientState clientState, Ballot ballot) throws InvalidRequestException
|
||||
{
|
||||
PartitionUpdate.Builder updateBuilder = new PartitionUpdate.Builder(metadata, key, updatedColumns(), conditions.size());
|
||||
long timeUuidNanos = 0;
|
||||
for (RowUpdate upd : updates)
|
||||
timeUuidNanos = upd.applyUpdates(current, updateBuilder, state, ballot.msb(), timeUuidNanos);
|
||||
timeUuidNanos = upd.applyUpdates(current, updateBuilder, clientState, ballot.msb(), timeUuidNanos);
|
||||
for (RangeDeletion upd : rangeDeletions)
|
||||
upd.applyUpdates(current, updateBuilder, state);
|
||||
upd.applyUpdates(current, updateBuilder, clientState);
|
||||
|
||||
PartitionUpdate partitionUpdate = updateBuilder.build();
|
||||
IndexRegistry.obtain(metadata).validate(partitionUpdate);
|
||||
|
|
|
|||
|
|
@ -51,18 +51,18 @@ import org.apache.cassandra.exceptions.*;
|
|||
import org.apache.cassandra.service.ClientState;
|
||||
import org.apache.cassandra.service.QueryState;
|
||||
import org.apache.cassandra.service.StorageProxy;
|
||||
import org.apache.cassandra.service.paxos.Ballot;
|
||||
import org.apache.cassandra.service.paxos.BallotGenerator;
|
||||
import org.apache.cassandra.service.paxos.Commit;
|
||||
import org.apache.cassandra.service.paxos.Commit.Proposal;
|
||||
import org.apache.cassandra.transport.messages.ResultMessage;
|
||||
import org.apache.cassandra.triggers.TriggerExecutor;
|
||||
import org.apache.cassandra.utils.FBUtilities;
|
||||
import org.apache.cassandra.utils.MD5Digest;
|
||||
import org.apache.cassandra.utils.Pair;
|
||||
import org.apache.cassandra.utils.TimeUUID;
|
||||
import org.apache.cassandra.utils.UUIDGen;
|
||||
|
||||
import static org.apache.cassandra.cql3.statements.RequestValidations.checkFalse;
|
||||
import static org.apache.cassandra.cql3.statements.RequestValidations.checkNull;
|
||||
import static org.apache.cassandra.service.paxos.Ballot.Flag.NONE;
|
||||
import static org.apache.cassandra.utils.Clock.Global.nanoTime;
|
||||
|
||||
/*
|
||||
|
|
@ -660,7 +660,7 @@ public abstract class ModificationStatement implements CQLStatement.SingleKeyspa
|
|||
|
||||
static RowIterator casInternal(ClientState state, CQL3CasRequest request, long timestamp, int nowInSeconds)
|
||||
{
|
||||
TimeUUID ballot = BallotGenerator.Global.randomBallot(timestamp, false);
|
||||
Ballot ballot = BallotGenerator.Global.atUnixMicros(timestamp, NONE);
|
||||
|
||||
SinglePartitionReadQuery readCommand = request.readCommand(nowInSeconds);
|
||||
FilteredPartition current;
|
||||
|
|
@ -676,7 +676,7 @@ public abstract class ModificationStatement implements CQLStatement.SingleKeyspa
|
|||
PartitionUpdate updates = request.makeUpdates(current, state, ballot);
|
||||
updates = TriggerExecutor.instance.execute(updates);
|
||||
|
||||
Commit proposal = Commit.newProposal(ballot, updates);
|
||||
Proposal proposal = Proposal.of(ballot, updates);
|
||||
proposal.makeMutation().apply();
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -254,7 +254,7 @@ public class SelectStatement implements CQLStatement.SingleKeyspaceCqlStatement
|
|||
query.trackWarnings();
|
||||
|
||||
if (aggregationSpec == null && (pageSize <= 0 || (query.limits().count() <= pageSize)))
|
||||
return execute(query, options, state, selectors, nowInSec, userLimit, queryStartNanoTime);
|
||||
return execute(query, options, state.getClientState(), selectors, nowInSec, userLimit, queryStartNanoTime);
|
||||
|
||||
QueryPager pager = getPager(query, options);
|
||||
|
||||
|
|
@ -300,12 +300,12 @@ public class SelectStatement implements CQLStatement.SingleKeyspaceCqlStatement
|
|||
|
||||
private ResultMessage.Rows execute(ReadQuery query,
|
||||
QueryOptions options,
|
||||
QueryState state,
|
||||
ClientState state,
|
||||
Selectors selectors,
|
||||
int nowInSec,
|
||||
int userLimit, long queryStartNanoTime) throws RequestValidationException, RequestExecutionException
|
||||
{
|
||||
try (PartitionIterator data = query.execute(options.getConsistency(), state.getClientState(), queryStartNanoTime))
|
||||
try (PartitionIterator data = query.execute(options.getConsistency(), state, queryStartNanoTime))
|
||||
{
|
||||
return processResults(data, options, selectors, nowInSec, userLimit);
|
||||
}
|
||||
|
|
@ -491,6 +491,50 @@ public class SelectStatement implements CQLStatement.SingleKeyspaceCqlStatement
|
|||
return new AggregationQueryPager(pager, query.limits());
|
||||
}
|
||||
|
||||
public Map<DecoratedKey, List<Row>> executeRawInternal(QueryOptions options, ClientState state, int nowInSec) throws RequestExecutionException, RequestValidationException
|
||||
{
|
||||
int userLimit = getLimit(options);
|
||||
int userPerPartitionLimit = getPerPartitionLimit(options);
|
||||
if (options.getPageSize() > 0)
|
||||
throw new IllegalStateException();
|
||||
if (aggregationSpec != null)
|
||||
throw new IllegalStateException();
|
||||
|
||||
Selectors selectors = selection.newSelectors(options);
|
||||
ReadQuery query = getQuery(options, state, selectors.getColumnFilter(), nowInSec, userLimit, userPerPartitionLimit, Integer.MAX_VALUE);
|
||||
|
||||
Map<DecoratedKey, List<Row>> result = Collections.emptyMap();
|
||||
try (ReadExecutionController executionController = query.executionController())
|
||||
{
|
||||
try (PartitionIterator data = query.executeInternal(executionController))
|
||||
{
|
||||
while (data.hasNext())
|
||||
{
|
||||
try (RowIterator in = data.next())
|
||||
{
|
||||
List<Row> out = Collections.emptyList();
|
||||
while (in.hasNext())
|
||||
{
|
||||
switch (out.size())
|
||||
{
|
||||
case 0: out = Collections.singletonList(in.next()); break;
|
||||
case 1: out = new ArrayList<>(out);
|
||||
default: out.add(in.next());
|
||||
}
|
||||
}
|
||||
switch (result.size())
|
||||
{
|
||||
case 0: result = Collections.singletonMap(in.partitionKey(), out); break;
|
||||
case 1: result = new TreeMap<>(result);
|
||||
default: result.put(in.partitionKey(), out);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ResultSet process(PartitionIterator partitions, int nowInSec) throws InvalidRequestException
|
||||
{
|
||||
QueryOptions options = QueryOptions.DEFAULT;
|
||||
|
|
|
|||
|
|
@ -40,10 +40,10 @@ import java.util.stream.Collectors;
|
|||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.*;
|
||||
import com.google.common.base.Throwables;
|
||||
import com.google.common.collect.*;
|
||||
import com.google.common.util.concurrent.*;
|
||||
|
||||
import org.apache.cassandra.service.paxos.Ballot;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
import org.apache.cassandra.utils.TimeUUID;
|
||||
import org.apache.cassandra.utils.concurrent.AsyncPromise;
|
||||
|
|
@ -99,7 +99,15 @@ import org.apache.cassandra.service.StorageService;
|
|||
import org.apache.cassandra.service.snapshot.SnapshotManifest;
|
||||
import org.apache.cassandra.service.snapshot.TableSnapshot;
|
||||
import org.apache.cassandra.streaming.TableStreamManager;
|
||||
import org.apache.cassandra.utils.*;
|
||||
import org.apache.cassandra.service.paxos.PaxosRepairHistory;
|
||||
import org.apache.cassandra.service.paxos.TablePaxosRepairHistory;
|
||||
import org.apache.cassandra.utils.DefaultValue;
|
||||
import org.apache.cassandra.utils.ExecutorUtils;
|
||||
import org.apache.cassandra.utils.FBUtilities;
|
||||
import org.apache.cassandra.utils.JVMStabilityInspector;
|
||||
import org.apache.cassandra.utils.MBeanWrapper;
|
||||
import org.apache.cassandra.utils.NoSpamLogger;
|
||||
import org.apache.cassandra.utils.WrappedRunnable;
|
||||
import org.apache.cassandra.utils.concurrent.OpOrder;
|
||||
import org.apache.cassandra.utils.concurrent.Promise;
|
||||
import org.apache.cassandra.utils.concurrent.Refs;
|
||||
|
|
@ -228,6 +236,29 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean
|
|||
|
||||
private volatile boolean neverPurgeTombstones = false;
|
||||
|
||||
private class PaxosRepairHistoryLoader
|
||||
{
|
||||
private TablePaxosRepairHistory history;
|
||||
|
||||
TablePaxosRepairHistory get()
|
||||
{
|
||||
if (history != null)
|
||||
return history;
|
||||
|
||||
synchronized (this)
|
||||
{
|
||||
if (history != null)
|
||||
return history;
|
||||
|
||||
history = TablePaxosRepairHistory.load(keyspace.getName(), name);
|
||||
return history;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private final PaxosRepairHistoryLoader paxosRepairHistory = new PaxosRepairHistoryLoader();
|
||||
|
||||
public static void shutdownPostFlushExecutor() throws InterruptedException
|
||||
{
|
||||
postFlushExecutor.shutdown();
|
||||
|
|
@ -604,7 +635,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean
|
|||
return createColumnFamilyStore(keyspace, metadata.name, metadata, loadSSTables);
|
||||
}
|
||||
|
||||
public static synchronized ColumnFamilyStore createColumnFamilyStore(Keyspace keyspace,
|
||||
public static ColumnFamilyStore createColumnFamilyStore(Keyspace keyspace,
|
||||
String columnFamily,
|
||||
TableMetadataRef metadata,
|
||||
boolean loadSSTables)
|
||||
|
|
@ -1143,7 +1174,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean
|
|||
* with CL as we do with memtables/CFS-backed SecondaryIndexes.
|
||||
*/
|
||||
if (flushNonCf2i)
|
||||
indexManager.flushAllNonCFSBackedIndexesBlocking();
|
||||
indexManager.flushAllNonCFSBackedIndexesBlocking(memtable);
|
||||
|
||||
flushResults = Lists.newArrayList(FBUtilities.waitOnFutures(futures));
|
||||
}
|
||||
|
|
@ -1731,6 +1762,31 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean
|
|||
|| filter.isFullyCoveredBy(cached);
|
||||
}
|
||||
|
||||
public PaxosRepairHistory getPaxosRepairHistory()
|
||||
{
|
||||
return paxosRepairHistory.get().getHistory();
|
||||
}
|
||||
|
||||
public PaxosRepairHistory getPaxosRepairHistoryForRanges(Collection<Range<Token>> ranges)
|
||||
{
|
||||
return paxosRepairHistory.get().getHistoryForRanges(ranges);
|
||||
}
|
||||
|
||||
public void syncPaxosRepairHistory(PaxosRepairHistory sync, boolean flush)
|
||||
{
|
||||
paxosRepairHistory.get().merge(sync, flush);
|
||||
}
|
||||
|
||||
public void onPaxosRepairComplete(Collection<Range<Token>> ranges, Ballot highBallot)
|
||||
{
|
||||
paxosRepairHistory.get().add(ranges, highBallot, true);
|
||||
}
|
||||
|
||||
public Ballot getPaxosRepairLowBound(DecoratedKey key)
|
||||
{
|
||||
return paxosRepairHistory.get().getBallotForToken(key.getToken());
|
||||
}
|
||||
|
||||
public int gcBefore(int nowInSec)
|
||||
{
|
||||
return nowInSec - metadata().params.gcGraceSeconds;
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ package org.apache.cassandra.db;
|
|||
|
||||
import com.carrotsearch.hppc.ObjectIntHashMap;
|
||||
import org.apache.cassandra.locator.Endpoints;
|
||||
import org.apache.cassandra.locator.InOurDc;
|
||||
import org.apache.cassandra.schema.TableMetadata;
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.exceptions.InvalidRequestException;
|
||||
|
|
@ -28,7 +29,6 @@ import org.apache.cassandra.locator.NetworkTopologyStrategy;
|
|||
import org.apache.cassandra.transport.ProtocolException;
|
||||
|
||||
import static org.apache.cassandra.locator.Replicas.addToCountPerDc;
|
||||
import static org.apache.cassandra.locator.Replicas.countInOurDc;
|
||||
|
||||
public enum ConsistencyLevel
|
||||
{
|
||||
|
|
@ -41,7 +41,7 @@ public enum ConsistencyLevel
|
|||
LOCAL_QUORUM(6, true),
|
||||
EACH_QUORUM (7),
|
||||
SERIAL (8),
|
||||
LOCAL_SERIAL(9),
|
||||
LOCAL_SERIAL(9, true),
|
||||
LOCAL_ONE (10, true),
|
||||
NODE_LOCAL (11, true);
|
||||
|
||||
|
|
@ -173,7 +173,7 @@ public enum ConsistencyLevel
|
|||
break;
|
||||
case LOCAL_ONE: case LOCAL_QUORUM: case LOCAL_SERIAL:
|
||||
// we will only count local replicas towards our response count, as these queries only care about local guarantees
|
||||
blockFor += countInOurDc(pending).allReplicas();
|
||||
blockFor += pending.count(InOurDc.replicas());
|
||||
break;
|
||||
case ONE: case TWO: case THREE:
|
||||
case QUORUM: case EACH_QUORUM:
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import java.io.IOException;
|
|||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Objects;
|
||||
|
|
@ -82,6 +83,12 @@ public class CounterMutation implements IMutation
|
|||
return mutation.getPartitionUpdates();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Supplier<Mutation> hintOnFailure()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public void validateSize(int version, int overhead)
|
||||
{
|
||||
long totalSize = serializedSize(version) + overhead;
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ package org.apache.cassandra.db;
|
|||
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.db.partitions.PartitionUpdate;
|
||||
|
|
@ -35,6 +36,7 @@ public interface IMutation
|
|||
public long getTimeout(TimeUnit unit);
|
||||
public String toString(boolean shallow);
|
||||
public Collection<PartitionUpdate> getPartitionUpdates();
|
||||
public Supplier<Mutation> hintOnFailure();
|
||||
|
||||
public default void validateIndexedColumns()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -119,6 +119,11 @@ public class Keyspace
|
|||
|
||||
private static volatile boolean initialized = false;
|
||||
|
||||
public static boolean isInitialized()
|
||||
{
|
||||
return initialized;
|
||||
}
|
||||
|
||||
public static void setInitialized()
|
||||
{
|
||||
initialized = true;
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import java.io.IOException;
|
|||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import com.google.common.collect.ImmutableCollection;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
|
|
@ -44,7 +45,7 @@ import static org.apache.cassandra.net.MessagingService.VERSION_3014;
|
|||
import static org.apache.cassandra.net.MessagingService.VERSION_40;
|
||||
import static org.apache.cassandra.utils.MonotonicClock.Global.approxTime;
|
||||
|
||||
public class Mutation implements IMutation
|
||||
public class Mutation implements IMutation, Supplier<Mutation>
|
||||
{
|
||||
public static final MutationSerializer serializer = new MutationSerializer();
|
||||
|
||||
|
|
@ -132,6 +133,18 @@ public class Mutation implements IMutation
|
|||
return modifications.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Supplier<Mutation> hintOnFailure()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mutation get()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
public void validateSize(int version, int overhead)
|
||||
{
|
||||
long totalSize = serializedSize(version) + overhead;
|
||||
|
|
@ -210,9 +223,14 @@ public class Mutation implements IMutation
|
|||
return ks.applyFuture(this, Keyspace.open(keyspaceName).getMetadata().params.durableWrites, true);
|
||||
}
|
||||
|
||||
private void apply(Keyspace keyspace, boolean durableWrites, boolean isDroppable)
|
||||
{
|
||||
keyspace.apply(this, durableWrites, true, isDroppable);
|
||||
}
|
||||
|
||||
public void apply(boolean durableWrites, boolean isDroppable)
|
||||
{
|
||||
Keyspace.open(keyspaceName).apply(this, durableWrites, true, isDroppable);
|
||||
apply(Keyspace.open(keyspaceName), durableWrites, isDroppable);
|
||||
}
|
||||
|
||||
public void apply(boolean durableWrites)
|
||||
|
|
@ -226,7 +244,8 @@ public class Mutation implements IMutation
|
|||
*/
|
||||
public void apply()
|
||||
{
|
||||
apply(Keyspace.open(keyspaceName).getMetadata().params.durableWrites);
|
||||
Keyspace keyspace = Keyspace.open(keyspaceName);
|
||||
apply(keyspace, keyspace.getMetadata().params.durableWrites, true);
|
||||
}
|
||||
|
||||
public void applyUnsafe()
|
||||
|
|
|
|||
|
|
@ -40,19 +40,11 @@ public class MutationVerbHandler implements IVerbHandler<Mutation>
|
|||
public void doVerb(Message<Mutation> message)
|
||||
{
|
||||
// Check if there were any forwarding headers in this message
|
||||
InetAddressAndPort from = message.respondTo();
|
||||
InetAddressAndPort respondToAddress;
|
||||
if (from == null)
|
||||
{
|
||||
respondToAddress = message.from();
|
||||
ForwardingInfo forwardTo = message.forwardTo();
|
||||
if (forwardTo != null) forwardToLocalNodes(message, forwardTo);
|
||||
}
|
||||
else
|
||||
{
|
||||
respondToAddress = from;
|
||||
}
|
||||
ForwardingInfo forwardTo = message.forwardTo();
|
||||
if (forwardTo != null)
|
||||
forwardToLocalNodes(message, forwardTo);
|
||||
|
||||
InetAddressAndPort respondToAddress = message.respondTo();
|
||||
try
|
||||
{
|
||||
message.payload.applyFuture().addCallback(o -> respond(message, respondToAddress), wto -> failed());
|
||||
|
|
|
|||
|
|
@ -293,7 +293,7 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
|
|||
return dataRange.isReversed();
|
||||
}
|
||||
|
||||
public PartitionIterator execute(ConsistencyLevel consistency, ClientState clientState, long queryStartNanoTime) throws RequestExecutionException
|
||||
public PartitionIterator execute(ConsistencyLevel consistency, ClientState state, long queryStartNanoTime) throws RequestExecutionException
|
||||
{
|
||||
return StorageProxy.getRangeSlice(this, consistency, queryStartNanoTime);
|
||||
}
|
||||
|
|
@ -512,7 +512,7 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
|
|||
}
|
||||
|
||||
@Override
|
||||
public PartitionIterator execute(ConsistencyLevel consistency, ClientState clientState, long queryStartNanoTime) throws RequestExecutionException
|
||||
public PartitionIterator execute(ConsistencyLevel consistency, ClientState state, long queryStartNanoTime) throws RequestExecutionException
|
||||
{
|
||||
return executeInternal(executionController());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ public interface ReadQuery
|
|||
return ReadExecutionController.empty();
|
||||
}
|
||||
|
||||
public PartitionIterator execute(ConsistencyLevel consistency, ClientState clientState, long queryStartNanoTime) throws RequestExecutionException
|
||||
public PartitionIterator execute(ConsistencyLevel consistency, ClientState state, long queryStartNanoTime) throws RequestExecutionException
|
||||
{
|
||||
return EmptyIterators.partition();
|
||||
}
|
||||
|
|
@ -140,12 +140,10 @@ public interface ReadQuery
|
|||
* Executes the query at the provided consistency level.
|
||||
*
|
||||
* @param consistency the consistency level to achieve for the query.
|
||||
* @param clientState the {@code ClientState} for the query. In practice, this can be null unless
|
||||
* {@code consistency} is a serial consistency.
|
||||
*
|
||||
* @param state client state
|
||||
* @return the result of the query.
|
||||
*/
|
||||
public PartitionIterator execute(ConsistencyLevel consistency, ClientState clientState, long queryStartNanoTime) throws RequestExecutionException;
|
||||
public PartitionIterator execute(ConsistencyLevel consistency, ClientState state, long queryStartNanoTime) throws RequestExecutionException;
|
||||
|
||||
/**
|
||||
* Execute the query for internal queries (that is, it basically executes the query locally).
|
||||
|
|
|
|||
|
|
@ -34,6 +34,8 @@ import org.apache.cassandra.net.MessagingService;
|
|||
import org.apache.cassandra.schema.TableMetadata;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
|
||||
import static org.apache.cassandra.db.RepairedDataInfo.NO_OP_REPAIRED_DATA_INFO;
|
||||
|
||||
public abstract class ReadResponse
|
||||
{
|
||||
// Serializer for single partition read response
|
||||
|
|
@ -48,6 +50,11 @@ public abstract class ReadResponse
|
|||
return new LocalDataResponse(data, command, rdi);
|
||||
}
|
||||
|
||||
public static ReadResponse createSimpleDataResponse(UnfilteredPartitionIterator data, ColumnFilter selection)
|
||||
{
|
||||
return new LocalDataResponse(data, selection);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public static ReadResponse createRemoteDataResponse(UnfilteredPartitionIterator data,
|
||||
ByteBuffer repairedDataDigest,
|
||||
|
|
@ -184,6 +191,11 @@ public abstract class ReadResponse
|
|||
DeserializationHelper.Flag.LOCAL);
|
||||
}
|
||||
|
||||
private LocalDataResponse(UnfilteredPartitionIterator iter, ColumnFilter selection)
|
||||
{
|
||||
super(build(iter, selection), null, false, MessagingService.current_version, DeserializationHelper.Flag.LOCAL);
|
||||
}
|
||||
|
||||
private static ByteBuffer build(UnfilteredPartitionIterator iter, ColumnFilter selection)
|
||||
{
|
||||
try (DataOutputBuffer buffer = new DataOutputBuffer())
|
||||
|
|
|
|||
|
|
@ -36,7 +36,6 @@ import org.apache.cassandra.db.marshal.*;
|
|||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
import org.apache.cassandra.utils.CounterId;
|
||||
import org.apache.cassandra.utils.FBUtilities;
|
||||
import org.apache.cassandra.utils.UUIDGen;
|
||||
|
||||
import static org.apache.cassandra.utils.TimeUUID.Generator.nextTimeUUIDAsBytes;
|
||||
|
||||
|
|
|
|||
|
|
@ -434,12 +434,13 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
|
|||
return cmd;
|
||||
}
|
||||
|
||||
public PartitionIterator execute(ConsistencyLevel consistency, ClientState clientState, long queryStartNanoTime) throws RequestExecutionException
|
||||
@Override
|
||||
public PartitionIterator execute(ConsistencyLevel consistency, ClientState state, long queryStartNanoTime) throws RequestExecutionException
|
||||
{
|
||||
if (clusteringIndexFilter.isEmpty(metadata().comparator))
|
||||
return EmptyIterators.partition();
|
||||
|
||||
return StorageProxy.read(Group.one(this), consistency, clientState, queryStartNanoTime);
|
||||
return StorageProxy.read(Group.one(this), consistency, queryStartNanoTime);
|
||||
}
|
||||
|
||||
protected void recordLatency(TableMetrics metric, long latencyNanos)
|
||||
|
|
@ -1221,9 +1222,9 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
|
|||
new Group(commands, limits);
|
||||
}
|
||||
|
||||
public PartitionIterator execute(ConsistencyLevel consistency, ClientState clientState, long queryStartNanoTime) throws RequestExecutionException
|
||||
public PartitionIterator execute(ConsistencyLevel consistency, ClientState state, long queryStartNanoTime) throws RequestExecutionException
|
||||
{
|
||||
return StorageProxy.read(this, consistency, clientState, queryStartNanoTime);
|
||||
return StorageProxy.read(this, consistency, queryStartNanoTime);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1235,13 +1236,13 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
|
|||
}
|
||||
|
||||
@Override
|
||||
public PartitionIterator execute(ConsistencyLevel consistency, ClientState clientState, long queryStartNanoTime) throws RequestExecutionException
|
||||
public PartitionIterator execute(ConsistencyLevel consistency, ClientState state, long queryStartNanoTime) throws RequestExecutionException
|
||||
{
|
||||
if (queries.size() == 1)
|
||||
return queries.get(0).execute(consistency, clientState, queryStartNanoTime);
|
||||
return queries.get(0).execute(consistency, state, queryStartNanoTime);
|
||||
|
||||
return PartitionIterators.concat(queries.stream()
|
||||
.map(q -> q.execute(consistency, clientState, queryStartNanoTime))
|
||||
.map(q -> q.execute(consistency, state, queryStartNanoTime))
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
}
|
||||
|
|
@ -1314,7 +1315,7 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
|
|||
}
|
||||
|
||||
@Override
|
||||
public PartitionIterator execute(ConsistencyLevel consistency, ClientState clientState, long queryStartNanoTime) throws RequestExecutionException
|
||||
public PartitionIterator execute(ConsistencyLevel consistency, ClientState state, long queryStartNanoTime) throws RequestExecutionException
|
||||
{
|
||||
return executeInternal(executionController());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ import com.google.common.annotations.VisibleForTesting;
|
|||
import com.google.common.collect.HashMultimap;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.SetMultimap;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.common.io.ByteStreams;
|
||||
|
|
@ -51,6 +52,7 @@ import org.apache.cassandra.db.compaction.CompactionHistoryTabularData;
|
|||
import org.apache.cassandra.db.marshal.*;
|
||||
import org.apache.cassandra.db.partitions.PartitionUpdate;
|
||||
import org.apache.cassandra.db.rows.Rows;
|
||||
import org.apache.cassandra.db.rows.Row;
|
||||
import org.apache.cassandra.dht.*;
|
||||
import org.apache.cassandra.exceptions.ConfigurationException;
|
||||
import org.apache.cassandra.io.util.*;
|
||||
|
|
@ -60,10 +62,14 @@ import org.apache.cassandra.metrics.RestorableMeter;
|
|||
import org.apache.cassandra.net.MessagingService;
|
||||
import org.apache.cassandra.schema.*;
|
||||
import org.apache.cassandra.service.StorageService;
|
||||
import org.apache.cassandra.service.paxos.Commit;
|
||||
import org.apache.cassandra.service.paxos.PaxosState;
|
||||
import org.apache.cassandra.streaming.StreamOperation;
|
||||
import org.apache.cassandra.transport.ProtocolVersion;
|
||||
import org.apache.cassandra.service.paxos.*;
|
||||
import org.apache.cassandra.service.paxos.Commit.Accepted;
|
||||
import org.apache.cassandra.service.paxos.Commit.AcceptedWithTTL;
|
||||
import org.apache.cassandra.service.paxos.Commit.Committed;
|
||||
import org.apache.cassandra.service.paxos.uncommitted.PaxosRows;
|
||||
import org.apache.cassandra.service.paxos.uncommitted.PaxosUncommittedIndex;
|
||||
import org.apache.cassandra.utils.*;
|
||||
import org.apache.cassandra.utils.concurrent.Future;
|
||||
|
||||
|
|
@ -71,8 +77,12 @@ import static java.lang.String.format;
|
|||
import static java.util.Collections.emptyMap;
|
||||
import static java.util.Collections.singletonMap;
|
||||
|
||||
import static org.apache.cassandra.config.Config.PaxosStatePurging.*;
|
||||
import static org.apache.cassandra.config.DatabaseDescriptor.paxosStatePurging;
|
||||
import static org.apache.cassandra.cql3.QueryProcessor.executeInternal;
|
||||
import static org.apache.cassandra.cql3.QueryProcessor.executeInternalWithNowInSec;
|
||||
import static org.apache.cassandra.cql3.QueryProcessor.executeOnceInternal;
|
||||
import static org.apache.cassandra.service.paxos.Commit.latest;
|
||||
import static org.apache.cassandra.utils.Clock.Global.currentTimeMillis;
|
||||
import static org.apache.cassandra.utils.Clock.Global.nanoTime;
|
||||
import static org.apache.cassandra.utils.FBUtilities.now;
|
||||
|
|
@ -100,6 +110,7 @@ public final class SystemKeyspace
|
|||
|
||||
public static final String BATCHES = "batches";
|
||||
public static final String PAXOS = "paxos";
|
||||
public static final String PAXOS_REPAIR_HISTORY = "paxos_repair_history";
|
||||
public static final String BUILT_INDEXES = "IndexInfo";
|
||||
public static final String LOCAL = "local";
|
||||
public static final String PEERS_V2 = "peers_v2";
|
||||
|
|
@ -153,6 +164,7 @@ public final class SystemKeyspace
|
|||
+ "row_key blob,"
|
||||
+ "cf_id UUID,"
|
||||
+ "in_progress_ballot timeuuid,"
|
||||
+ "in_progress_read_ballot timeuuid,"
|
||||
+ "most_recent_commit blob,"
|
||||
+ "most_recent_commit_at timeuuid,"
|
||||
+ "most_recent_commit_version int,"
|
||||
|
|
@ -161,6 +173,7 @@ public final class SystemKeyspace
|
|||
+ "proposal_version int,"
|
||||
+ "PRIMARY KEY ((row_key), cf_id))")
|
||||
.compaction(CompactionParams.lcs(emptyMap()))
|
||||
.indexes(PaxosUncommittedIndex.indexes())
|
||||
.build();
|
||||
|
||||
private static final TableMetadata BuiltIndexes =
|
||||
|
|
@ -173,6 +186,17 @@ public final class SystemKeyspace
|
|||
+ "PRIMARY KEY ((table_name), index_name)) ")
|
||||
.build();
|
||||
|
||||
private static final TableMetadata PaxosRepairHistoryTable =
|
||||
parse(PAXOS_REPAIR_HISTORY,
|
||||
"paxos repair history",
|
||||
"CREATE TABLE %s ("
|
||||
+ "keyspace_name text,"
|
||||
+ "table_name text,"
|
||||
+ "points frozen<list<tuple<blob, timeuuid>>>, "
|
||||
+ "PRIMARY KEY (keyspace_name, table_name))"
|
||||
+ "WITH COMMENT='Last successful paxos repairs by range'")
|
||||
.build();
|
||||
|
||||
private static final TableMetadata Local =
|
||||
parse(LOCAL,
|
||||
"information about the local node",
|
||||
|
|
@ -425,6 +449,7 @@ public final class SystemKeyspace
|
|||
return Tables.of(BuiltIndexes,
|
||||
Batches,
|
||||
Paxos,
|
||||
PaxosRepairHistoryTable,
|
||||
Local,
|
||||
PeersV2,
|
||||
LegacyPeers,
|
||||
|
|
@ -1179,72 +1204,177 @@ public final class SystemKeyspace
|
|||
return null;
|
||||
}
|
||||
|
||||
public static PaxosState loadPaxosState(DecoratedKey key, TableMetadata metadata, int nowInSec)
|
||||
/**
|
||||
* Load the current paxos state for the table and key
|
||||
*/
|
||||
public static PaxosState.Snapshot loadPaxosState(DecoratedKey partitionKey, TableMetadata metadata, int nowInSec)
|
||||
{
|
||||
String req = "SELECT * FROM system.%s WHERE row_key = ? AND cf_id = ?";
|
||||
UntypedResultSet results = QueryProcessor.executeInternalWithNow(nowInSec, nanoTime(), format(req, PAXOS), key.getKey(), metadata.id.asUUID());
|
||||
if (results.isEmpty())
|
||||
return new PaxosState(key, metadata);
|
||||
UntypedResultSet.Row row = results.one();
|
||||
String cql = "SELECT * FROM system." + PAXOS + " WHERE row_key = ? AND cf_id = ?";
|
||||
List<Row> results = QueryProcessor.executeInternalRawWithNow(nowInSec, cql, partitionKey.getKey(), metadata.id.asUUID()).get(partitionKey);
|
||||
if (results == null || results.isEmpty())
|
||||
{
|
||||
Committed noneCommitted = Committed.none(partitionKey, metadata);
|
||||
return new PaxosState.Snapshot(Ballot.none(), Ballot.none(), null, noneCommitted);
|
||||
}
|
||||
|
||||
Row row = results.get(0);
|
||||
|
||||
Ballot promisedWrite = PaxosRows.getWritePromise(row);
|
||||
Ballot promised = latest(promisedWrite, PaxosRows.getPromise(row));
|
||||
|
||||
Commit promised = row.has("in_progress_ballot")
|
||||
? new Commit(row.getTimeUUID("in_progress_ballot"), new PartitionUpdate.Builder(metadata, key, metadata.regularAndStaticColumns(), 1).build())
|
||||
: Commit.emptyCommit(key, metadata);
|
||||
// either we have both a recently accepted ballot and update or we have neither
|
||||
Commit accepted = row.has("proposal_version") && row.has("proposal")
|
||||
? new Commit(row.getTimeUUID("proposal_ballot"),
|
||||
PartitionUpdate.fromBytes(row.getBytes("proposal"), row.getInt("proposal_version")))
|
||||
: Commit.emptyCommit(key, metadata);
|
||||
// either most_recent_commit and most_recent_commit_at will both be set, or neither
|
||||
Commit mostRecent = row.has("most_recent_commit_version") && row.has("most_recent_commit")
|
||||
? new Commit(row.getTimeUUID("most_recent_commit_at"),
|
||||
PartitionUpdate.fromBytes(row.getBytes("most_recent_commit"), row.getInt("most_recent_commit_version")))
|
||||
: Commit.emptyCommit(key, metadata);
|
||||
return new PaxosState(promised, accepted, mostRecent);
|
||||
Accepted accepted = PaxosRows.getAccepted(row);
|
||||
Committed committed = PaxosRows.getCommitted(metadata, partitionKey, row);
|
||||
// fix a race with TTL/deletion resolution, where TTL expires after equal deletion is inserted; TTL wins the resolution, and is read using an old ballot's nowInSec
|
||||
if (accepted != null && !accepted.isAfter(committed))
|
||||
accepted = null;
|
||||
|
||||
return new PaxosState.Snapshot(promised, promisedWrite, accepted, committed);
|
||||
}
|
||||
|
||||
public static void savePaxosPromise(Commit promise)
|
||||
public static int legacyPaxosTtlSec(TableMetadata metadata)
|
||||
{
|
||||
String req = "UPDATE system.%s USING TIMESTAMP ? AND TTL ? SET in_progress_ballot = ? WHERE row_key = ? AND cf_id = ?";
|
||||
executeInternal(format(req, PAXOS),
|
||||
promise.ballot.unixMicros(),
|
||||
paxosTtlSec(promise.update.metadata()),
|
||||
promise.ballot,
|
||||
promise.update.partitionKey().getKey(),
|
||||
promise.update.metadata().id.asUUID());
|
||||
// keep paxos state around for at least 3h
|
||||
return Math.max(3 * 3600, metadata.params.gcGraceSeconds);
|
||||
}
|
||||
|
||||
public static void savePaxosWritePromise(DecoratedKey key, TableMetadata metadata, Ballot ballot)
|
||||
{
|
||||
if (paxosStatePurging() == legacy)
|
||||
{
|
||||
String cql = "UPDATE system." + PAXOS + " USING TIMESTAMP ? AND TTL ? SET in_progress_ballot = ? WHERE row_key = ? AND cf_id = ?";
|
||||
executeInternal(cql,
|
||||
ballot.unixMicros(),
|
||||
legacyPaxosTtlSec(metadata),
|
||||
ballot,
|
||||
key.getKey(),
|
||||
metadata.id.asUUID());
|
||||
}
|
||||
else
|
||||
{
|
||||
String cql = "UPDATE system." + PAXOS + " USING TIMESTAMP ? SET in_progress_ballot = ? WHERE row_key = ? AND cf_id = ?";
|
||||
executeInternal(cql,
|
||||
ballot.unixMicros(),
|
||||
ballot,
|
||||
key.getKey(),
|
||||
metadata.id.asUUID());
|
||||
}
|
||||
}
|
||||
|
||||
public static void savePaxosReadPromise(DecoratedKey key, TableMetadata metadata, Ballot ballot)
|
||||
{
|
||||
if (paxosStatePurging() == legacy)
|
||||
{
|
||||
String cql = "UPDATE system." + PAXOS + " USING TIMESTAMP ? AND TTL ? SET in_progress_read_ballot = ? WHERE row_key = ? AND cf_id = ?";
|
||||
executeInternal(cql,
|
||||
ballot.unixMicros(),
|
||||
legacyPaxosTtlSec(metadata),
|
||||
ballot,
|
||||
key.getKey(),
|
||||
metadata.id.asUUID());
|
||||
}
|
||||
else
|
||||
{
|
||||
String cql = "UPDATE system." + PAXOS + " USING TIMESTAMP ? SET in_progress_read_ballot = ? WHERE row_key = ? AND cf_id = ?";
|
||||
executeInternal(cql,
|
||||
ballot.unixMicros(),
|
||||
ballot,
|
||||
key.getKey(),
|
||||
metadata.id.asUUID());
|
||||
}
|
||||
}
|
||||
|
||||
public static void savePaxosProposal(Commit proposal)
|
||||
{
|
||||
executeInternal(format("UPDATE system.%s USING TIMESTAMP ? AND TTL ? SET proposal_ballot = ?, proposal = ?, proposal_version = ? WHERE row_key = ? AND cf_id = ?", PAXOS),
|
||||
proposal.ballot.unixMicros(),
|
||||
paxosTtlSec(proposal.update.metadata()),
|
||||
proposal.ballot,
|
||||
PartitionUpdate.toBytes(proposal.update, MessagingService.current_version),
|
||||
MessagingService.current_version,
|
||||
proposal.update.partitionKey().getKey(),
|
||||
proposal.update.metadata().id.asUUID());
|
||||
}
|
||||
|
||||
public static int paxosTtlSec(TableMetadata metadata)
|
||||
{
|
||||
// keep paxos state around for at least 3h
|
||||
return Math.max(3 * 3600, metadata.params.gcGraceSeconds);
|
||||
if (proposal instanceof AcceptedWithTTL)
|
||||
{
|
||||
int localDeletionTime = ((Commit.AcceptedWithTTL) proposal).localDeletionTime;
|
||||
int ttlInSec = legacyPaxosTtlSec(proposal.update.metadata());
|
||||
int nowInSec = localDeletionTime - ttlInSec;
|
||||
String cql = "UPDATE system." + PAXOS + " USING TIMESTAMP ? AND TTL ? SET proposal_ballot = ?, proposal = ?, proposal_version = ? WHERE row_key = ? AND cf_id = ?";
|
||||
executeInternalWithNowInSec(cql,
|
||||
nowInSec,
|
||||
proposal.ballot.unixMicros(),
|
||||
ttlInSec,
|
||||
proposal.ballot,
|
||||
PartitionUpdate.toBytes(proposal.update, MessagingService.current_version),
|
||||
MessagingService.current_version,
|
||||
proposal.update.partitionKey().getKey(),
|
||||
proposal.update.metadata().id.asUUID());
|
||||
}
|
||||
else
|
||||
{
|
||||
String cql = "UPDATE system." + PAXOS + " USING TIMESTAMP ? SET proposal_ballot = ?, proposal = ?, proposal_version = ? WHERE row_key = ? AND cf_id = ?";
|
||||
executeInternal(cql,
|
||||
proposal.ballot.unixMicros(),
|
||||
proposal.ballot,
|
||||
PartitionUpdate.toBytes(proposal.update, MessagingService.current_version),
|
||||
MessagingService.current_version,
|
||||
proposal.update.partitionKey().getKey(),
|
||||
proposal.update.metadata().id.asUUID());
|
||||
}
|
||||
}
|
||||
|
||||
public static void savePaxosCommit(Commit commit)
|
||||
{
|
||||
// We always erase the last proposal (with the commit timestamp to no erase more recent proposal in case the commit is old)
|
||||
// even though that's really just an optimization since SP.beginAndRepairPaxos will exclude accepted proposal older than the mrc.
|
||||
String cql = "UPDATE system.%s USING TIMESTAMP ? AND TTL ? SET proposal_ballot = null, proposal = null, most_recent_commit_at = ?, most_recent_commit = ?, most_recent_commit_version = ? WHERE row_key = ? AND cf_id = ?";
|
||||
executeInternal(format(cql, PAXOS),
|
||||
commit.ballot.unixMicros(),
|
||||
paxosTtlSec(commit.update.metadata()),
|
||||
commit.ballot,
|
||||
PartitionUpdate.toBytes(commit.update, MessagingService.current_version),
|
||||
MessagingService.current_version,
|
||||
commit.update.partitionKey().getKey(),
|
||||
commit.update.metadata().id.asUUID());
|
||||
if (commit instanceof Commit.CommittedWithTTL)
|
||||
{
|
||||
int localDeletionTime = ((Commit.CommittedWithTTL) commit).localDeletionTime;
|
||||
int ttlInSec = legacyPaxosTtlSec(commit.update.metadata());
|
||||
int nowInSec = localDeletionTime - ttlInSec;
|
||||
String cql = "UPDATE system." + PAXOS + " USING TIMESTAMP ? AND TTL ? SET proposal_ballot = null, proposal = null, proposal_version = null, most_recent_commit_at = ?, most_recent_commit = ?, most_recent_commit_version = ? WHERE row_key = ? AND cf_id = ?";
|
||||
executeInternalWithNowInSec(cql,
|
||||
nowInSec,
|
||||
commit.ballot.unixMicros(),
|
||||
ttlInSec,
|
||||
commit.ballot,
|
||||
PartitionUpdate.toBytes(commit.update, MessagingService.current_version),
|
||||
MessagingService.current_version,
|
||||
commit.update.partitionKey().getKey(),
|
||||
commit.update.metadata().id.asUUID());
|
||||
}
|
||||
else
|
||||
{
|
||||
String cql = "UPDATE system." + PAXOS + " USING TIMESTAMP ? SET proposal_ballot = null, proposal = null, proposal_version = null, most_recent_commit_at = ?, most_recent_commit = ?, most_recent_commit_version = ? WHERE row_key = ? AND cf_id = ?";
|
||||
executeInternal(cql,
|
||||
commit.ballot.unixMicros(),
|
||||
commit.ballot,
|
||||
PartitionUpdate.toBytes(commit.update, MessagingService.current_version),
|
||||
MessagingService.current_version,
|
||||
commit.update.partitionKey().getKey(),
|
||||
commit.update.metadata().id.asUUID());
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public static void savePaxosRepairHistory(String keyspace, String table, PaxosRepairHistory history, boolean flush)
|
||||
{
|
||||
String cql = "INSERT INTO system.%s (keyspace_name, table_name, points) VALUES (?, ?, ?)";
|
||||
executeInternal(String.format(cql, PAXOS_REPAIR_HISTORY), keyspace, table, history.toTupleBufferList());
|
||||
if (flush)
|
||||
flushPaxosRepairHistory();
|
||||
}
|
||||
|
||||
public static void flushPaxosRepairHistory()
|
||||
{
|
||||
Schema.instance.getColumnFamilyStoreInstance(PaxosRepairHistoryTable.id).forceBlockingFlush();
|
||||
}
|
||||
|
||||
public static PaxosRepairHistory loadPaxosRepairHistory(String keyspace, String table)
|
||||
{
|
||||
if (SchemaConstants.LOCAL_SYSTEM_KEYSPACE_NAMES.contains(keyspace))
|
||||
return PaxosRepairHistory.EMPTY;
|
||||
|
||||
UntypedResultSet results = executeInternal(String.format("SELECT * FROM system.%s WHERE keyspace_name=? AND table_name=?", PAXOS_REPAIR_HISTORY), keyspace, table);
|
||||
if (results.isEmpty())
|
||||
return PaxosRepairHistory.EMPTY;
|
||||
|
||||
UntypedResultSet.Row row = Iterables.getOnlyElement(results);
|
||||
List<ByteBuffer> points = row.getList("points", BytesType.instance);
|
||||
|
||||
return PaxosRepairHistory.fromTupleBufferList(points);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -52,7 +52,6 @@ import org.apache.cassandra.schema.Schema;
|
|||
import org.apache.cassandra.schema.SchemaConstants;
|
||||
import org.apache.cassandra.schema.TableId;
|
||||
import org.apache.cassandra.schema.TableMetadataRef;
|
||||
import org.apache.cassandra.service.StorageService;
|
||||
import org.apache.cassandra.utils.FBUtilities;
|
||||
import org.apache.cassandra.utils.WrappedRunnable;
|
||||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,6 @@ import org.apache.cassandra.db.lifecycle.LifecycleTransaction;
|
|||
import org.apache.cassandra.dht.Range;
|
||||
import org.apache.cassandra.dht.Token;
|
||||
import org.apache.cassandra.exceptions.ConfigurationException;
|
||||
import org.apache.cassandra.io.sstable.Component;
|
||||
import org.apache.cassandra.io.sstable.ISSTableScanner;
|
||||
import org.apache.cassandra.io.sstable.metadata.MetadataCollector;
|
||||
import org.apache.cassandra.io.sstable.metadata.StatsMetadata;
|
||||
|
|
|
|||
|
|
@ -17,16 +17,14 @@
|
|||
*/
|
||||
package org.apache.cassandra.db.compaction;
|
||||
|
||||
import org.apache.cassandra.utils.Shared;
|
||||
|
||||
@Shared
|
||||
public class CompactionInterruptedException extends RuntimeException
|
||||
{
|
||||
private static final long serialVersionUID = -8651427062512310398L;
|
||||
|
||||
public CompactionInterruptedException(CompactionInfo info)
|
||||
{
|
||||
super("Compaction interrupted: " + info);
|
||||
}
|
||||
|
||||
public CompactionInterruptedException(String info)
|
||||
public CompactionInterruptedException(Object info)
|
||||
{
|
||||
super("Compaction interrupted: " + info);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,11 +19,17 @@ package org.apache.cassandra.db.compaction;
|
|||
|
||||
import java.util.*;
|
||||
import java.util.function.LongPredicate;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Ordering;
|
||||
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.dht.Token;
|
||||
import org.apache.cassandra.io.sstable.format.SSTableReader;
|
||||
import org.apache.cassandra.schema.Schema;
|
||||
import org.apache.cassandra.schema.SchemaConstants;
|
||||
import org.apache.cassandra.schema.TableId;
|
||||
import org.apache.cassandra.schema.TableMetadata;
|
||||
|
||||
import org.apache.cassandra.db.transform.DuplicateRowChecker;
|
||||
|
|
@ -37,8 +43,14 @@ import org.apache.cassandra.db.transform.Transformation;
|
|||
import org.apache.cassandra.index.transactions.CompactionTransaction;
|
||||
import org.apache.cassandra.io.sstable.ISSTableScanner;
|
||||
import org.apache.cassandra.schema.CompactionParams.TombstoneOption;
|
||||
import org.apache.cassandra.service.paxos.PaxosRepairHistory;
|
||||
import org.apache.cassandra.service.paxos.uncommitted.PaxosRows;
|
||||
import org.apache.cassandra.utils.TimeUUID;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.MICROSECONDS;
|
||||
import static org.apache.cassandra.config.Config.PaxosStatePurging.legacy;
|
||||
import static org.apache.cassandra.config.DatabaseDescriptor.paxosStatePurging;
|
||||
|
||||
/**
|
||||
* Merge multiple iterators over the content of sstable into a "compacted" iterator.
|
||||
* <p>
|
||||
|
|
@ -110,7 +122,10 @@ public class CompactionIterator extends CompactionInfo.Holder implements Unfilte
|
|||
? EmptyIterators.unfilteredPartition(controller.cfs.metadata())
|
||||
: UnfilteredPartitionIterators.merge(scanners, listener());
|
||||
merged = Transformation.apply(merged, new GarbageSkipper(controller));
|
||||
merged = Transformation.apply(merged, new Purger(controller, nowInSec));
|
||||
Transformation<UnfilteredRowIterator> purger = isPaxos(controller.cfs) && paxosStatePurging() != legacy
|
||||
? new PaxosPurger(nowInSec)
|
||||
: new Purger(controller, nowInSec);
|
||||
merged = Transformation.apply(merged, purger);
|
||||
merged = DuplicateRowChecker.duringCompaction(merged, type);
|
||||
compacted = Transformation.apply(merged, new AbortableUnfilteredPartitionTransformation(this));
|
||||
}
|
||||
|
|
@ -556,6 +571,78 @@ public class CompactionIterator extends CompactionInfo.Holder implements Unfilte
|
|||
}
|
||||
}
|
||||
|
||||
private class PaxosPurger extends Transformation<UnfilteredRowIterator>
|
||||
{
|
||||
private final long nowInSec;
|
||||
private final long paxosPurgeGraceMicros = DatabaseDescriptor.getPaxosPurgeGrace(MICROSECONDS);
|
||||
private final Map<TableId, PaxosRepairHistory.Searcher> tableIdToHistory = new HashMap<>();
|
||||
private Token currentToken;
|
||||
private int compactedUnfiltered;
|
||||
|
||||
private PaxosPurger(long nowInSec)
|
||||
{
|
||||
this.nowInSec = nowInSec;
|
||||
}
|
||||
|
||||
protected void onEmptyPartitionPostPurge(DecoratedKey key)
|
||||
{
|
||||
if (type == OperationType.COMPACTION)
|
||||
controller.cfs.invalidateCachedPartition(key);
|
||||
}
|
||||
|
||||
protected void updateProgress()
|
||||
{
|
||||
if ((++compactedUnfiltered) % UNFILTERED_TO_UPDATE_PROGRESS == 0)
|
||||
updateBytesRead();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("resource")
|
||||
protected UnfilteredRowIterator applyToPartition(UnfilteredRowIterator partition)
|
||||
{
|
||||
currentToken = partition.partitionKey().getToken();
|
||||
UnfilteredRowIterator purged = Transformation.apply(partition, this);
|
||||
if (purged.isEmpty())
|
||||
{
|
||||
onEmptyPartitionPostPurge(purged.partitionKey());
|
||||
purged.close();
|
||||
return null;
|
||||
}
|
||||
|
||||
return purged;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Row applyToRow(Row row)
|
||||
{
|
||||
updateProgress();
|
||||
TableId tableId = PaxosRows.getTableId(row);
|
||||
|
||||
switch (paxosStatePurging())
|
||||
{
|
||||
default: throw new AssertionError();
|
||||
case legacy:
|
||||
case gc_grace:
|
||||
{
|
||||
TableMetadata metadata = Schema.instance.getTableMetadata(tableId);
|
||||
return row.purgeDataOlderThan(TimeUnit.SECONDS.toMicros(nowInSec - (metadata == null ? (3 * 3600) : metadata.params.gcGraceSeconds)), false);
|
||||
}
|
||||
case repaired:
|
||||
{
|
||||
PaxosRepairHistory.Searcher history = tableIdToHistory.computeIfAbsent(tableId, find -> {
|
||||
TableMetadata metadata = Schema.instance.getTableMetadata(find);
|
||||
if (metadata == null)
|
||||
return null;
|
||||
return Keyspace.openAndGetStore(metadata).getPaxosRepairHistory().searcher();
|
||||
});
|
||||
|
||||
return history == null ? row :
|
||||
row.purgeDataOlderThan(history.ballotForToken(currentToken).unixMicros() - paxosPurgeGraceMicros, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class AbortableUnfilteredPartitionTransformation extends Transformation<UnfilteredRowIterator>
|
||||
{
|
||||
private final AbortableUnfilteredRowTransformation abortableIter;
|
||||
|
|
@ -574,7 +661,7 @@ public class CompactionIterator extends CompactionInfo.Holder implements Unfilte
|
|||
}
|
||||
}
|
||||
|
||||
private static class AbortableUnfilteredRowTransformation extends Transformation
|
||||
private static class AbortableUnfilteredRowTransformation extends Transformation<UnfilteredRowIterator>
|
||||
{
|
||||
private final CompactionIterator iter;
|
||||
|
||||
|
|
@ -590,4 +677,9 @@ public class CompactionIterator extends CompactionInfo.Holder implements Unfilte
|
|||
return row;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isPaxos(ColumnFamilyStore cfs)
|
||||
{
|
||||
return cfs.name.equals(SystemKeyspace.PAXOS) && cfs.keyspace.getName().equals(SchemaConstants.SYSTEM_KEYSPACE_NAME);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import org.apache.cassandra.db.Digest;
|
|||
import org.apache.cassandra.db.TypeSizes;
|
||||
import org.apache.cassandra.io.util.DataInputPlus;
|
||||
import org.apache.cassandra.io.util.DataOutputPlus;
|
||||
import org.apache.cassandra.service.paxos.Ballot;
|
||||
import org.apache.cassandra.utils.TimeUUID;
|
||||
import org.apache.cassandra.utils.ByteArrayUtil;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
|
|
@ -241,6 +242,12 @@ public class ByteArrayAccessor implements ValueAccessor<byte[]>
|
|||
return TimeUUID.fromBytes(getLong(value, 0), getLong(value, 8));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Ballot toBallot(byte[] value)
|
||||
{
|
||||
return Ballot.deserialize(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int putShort(byte[] dst, int offset, short value)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import org.apache.cassandra.db.Digest;
|
|||
import org.apache.cassandra.db.TypeSizes;
|
||||
import org.apache.cassandra.io.util.DataInputPlus;
|
||||
import org.apache.cassandra.io.util.DataOutputPlus;
|
||||
import org.apache.cassandra.service.paxos.Ballot;
|
||||
import org.apache.cassandra.utils.TimeUUID;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
import org.apache.cassandra.utils.FastByteOperations;
|
||||
|
|
@ -245,6 +246,12 @@ public class ByteBufferAccessor implements ValueAccessor<ByteBuffer>
|
|||
return TimeUUID.fromBytes(value.getLong(value.position()), value.getLong(value.position() + 8));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Ballot toBallot(ByteBuffer value)
|
||||
{
|
||||
return Ballot.deserialize(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int putShort(ByteBuffer dst, int offset, short value)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -205,9 +205,17 @@ public class TupleType extends AbstractType<ByteBuffer>
|
|||
*/
|
||||
public ByteBuffer[] split(ByteBuffer value)
|
||||
{
|
||||
ByteBuffer[] components = new ByteBuffer[size()];
|
||||
return split(value, size(), this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Split a tuple value into its component values.
|
||||
*/
|
||||
public static ByteBuffer[] split(ByteBuffer value, int numberOfElements, TupleType type)
|
||||
{
|
||||
ByteBuffer[] components = new ByteBuffer[numberOfElements];
|
||||
ByteBuffer input = value.duplicate();
|
||||
for (int i = 0; i < size(); i++)
|
||||
for (int i = 0; i < numberOfElements; i++)
|
||||
{
|
||||
if (!input.hasRemaining())
|
||||
return Arrays.copyOfRange(components, 0, i);
|
||||
|
|
@ -226,7 +234,7 @@ public class TupleType extends AbstractType<ByteBuffer>
|
|||
{
|
||||
throw new InvalidRequestException(String.format(
|
||||
"Expected %s %s for %s column, but got more",
|
||||
size(), size() == 1 ? "value" : "values", this.asCQL3Type()));
|
||||
numberOfElements, numberOfElements == 1 ? "value" : "values", type.asCQL3Type()));
|
||||
}
|
||||
|
||||
return components;
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ import org.apache.cassandra.db.rows.CellPath;
|
|||
import org.apache.cassandra.io.util.DataInputPlus;
|
||||
import org.apache.cassandra.io.util.DataOutputPlus;
|
||||
import org.apache.cassandra.schema.ColumnMetadata;
|
||||
import org.apache.cassandra.service.paxos.Ballot;
|
||||
import org.apache.cassandra.utils.TimeUUID;
|
||||
|
||||
import static org.apache.cassandra.db.ClusteringPrefix.Kind.*;
|
||||
|
|
@ -326,6 +327,9 @@ public interface ValueAccessor<V>
|
|||
/** returns a TimeUUID from offset 0 */
|
||||
TimeUUID toTimeUUID(V value);
|
||||
|
||||
/** returns a TimeUUID from offset 0 */
|
||||
Ballot toBallot(V value);
|
||||
|
||||
/**
|
||||
* writes the short value {@param value} to {@param dst} at offset {@param offset}
|
||||
* @return the number of bytes written to {@param value}
|
||||
|
|
|
|||
|
|
@ -375,26 +375,52 @@ public abstract class AbstractBTreePartition implements Partition, Iterable<Row>
|
|||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
return toString(true);
|
||||
}
|
||||
|
||||
sb.append(String.format("[%s] key=%s partition_deletion=%s columns=%s",
|
||||
metadata(),
|
||||
metadata().partitionKeyType.getString(partitionKey().getKey()),
|
||||
partitionLevelDeletion(),
|
||||
columns()));
|
||||
public String toString(boolean includeFullDetails)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (includeFullDetails)
|
||||
{
|
||||
sb.append(String.format("[%s.%s] key=%s partition_deletion=%s columns=%s",
|
||||
metadata().keyspace,
|
||||
metadata().name,
|
||||
metadata().partitionKeyType.getString(partitionKey().getKey()),
|
||||
partitionLevelDeletion(),
|
||||
columns()));
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append("key=").append(metadata().partitionKeyType.getString(partitionKey().getKey()));
|
||||
}
|
||||
|
||||
if (staticRow() != Rows.EMPTY_STATIC_ROW)
|
||||
sb.append("\n ").append(staticRow().toString(metadata(), true));
|
||||
sb.append("\n ").append(staticRow().toString(metadata(), includeFullDetails));
|
||||
|
||||
try (UnfilteredRowIterator iter = unfilteredIterator())
|
||||
{
|
||||
while (iter.hasNext())
|
||||
sb.append("\n ").append(iter.next().toString(metadata(), true));
|
||||
sb.append("\n ").append(iter.next().toString(metadata(), includeFullDetails));
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (!(obj instanceof PartitionUpdate))
|
||||
return false;
|
||||
|
||||
PartitionUpdate that = (PartitionUpdate) obj;
|
||||
Holder a = this.holder(), b = that.holder();
|
||||
return partitionKey.equals(that.partitionKey)
|
||||
&& metadata().id.equals(that.metadata().id)
|
||||
&& a.deletionInfo.equals(b.deletionInfo)
|
||||
&& a.staticRow.equals(b.staticRow)
|
||||
&& Iterators.elementsEqual(iterator(), that.iterator());
|
||||
}
|
||||
|
||||
public int rowCount()
|
||||
{
|
||||
return BTree.size(holder().tree);
|
||||
|
|
|
|||
|
|
@ -17,13 +17,16 @@
|
|||
*/
|
||||
package org.apache.cassandra.db.partitions;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Iterators;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.primitives.Ints;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
|
@ -38,6 +41,9 @@ import org.apache.cassandra.schema.TableId;
|
|||
import org.apache.cassandra.schema.TableMetadata;
|
||||
import org.apache.cassandra.utils.btree.BTree;
|
||||
import org.apache.cassandra.utils.btree.UpdateFunction;
|
||||
import org.apache.cassandra.utils.vint.VIntCoding;
|
||||
|
||||
import static org.apache.cassandra.db.rows.UnfilteredRowIteratorSerializer.IS_EMPTY;
|
||||
|
||||
/**
|
||||
* Stores updates made on a partition.
|
||||
|
|
@ -322,23 +328,19 @@ public class PartitionUpdate extends AbstractBTreePartition
|
|||
*/
|
||||
public int dataSize()
|
||||
{
|
||||
int size = 0;
|
||||
return Ints.saturatedCast(BTree.<Row>accumulate(holder.tree, (row, value) -> row.dataSize() + value, 0L)
|
||||
+ holder.staticRow.dataSize() + holder.deletionInfo.dataSize());
|
||||
}
|
||||
|
||||
if (holder.staticRow != null)
|
||||
{
|
||||
for (ColumnData cd : holder.staticRow.columnData())
|
||||
{
|
||||
size += cd.dataSize();
|
||||
}
|
||||
}
|
||||
|
||||
for (Row row : this)
|
||||
{
|
||||
size += row.clustering().dataSize();
|
||||
for (ColumnData cd : row)
|
||||
size += cd.dataSize();
|
||||
}
|
||||
return size;
|
||||
/**
|
||||
* The size of the data contained in this update.
|
||||
*
|
||||
* @return the size of the data contained in this update.
|
||||
*/
|
||||
public long unsharedHeapSize()
|
||||
{
|
||||
return BTree.<Row>accumulate(holder.tree, (row, value) -> row.unsharedHeapSize() + value, 0L)
|
||||
+ holder.staticRow.unsharedHeapSize() + holder.deletionInfo.unsharedHeapSize();
|
||||
}
|
||||
|
||||
public TableMetadata metadata()
|
||||
|
|
@ -667,6 +669,21 @@ public class PartitionUpdate extends AbstractBTreePartition
|
|||
false);
|
||||
}
|
||||
|
||||
public static boolean isEmpty(ByteBuffer in, DeserializationHelper.Flag flag, DecoratedKey key) throws IOException
|
||||
{
|
||||
int position = in.position();
|
||||
position += 16; // CFMetaData.serializer.deserialize(in, version);
|
||||
if (position >= in.limit())
|
||||
throw new EOFException();
|
||||
// DecoratedKey key = metadata.decorateKey(ByteBufferUtil.readWithVIntLength(in));
|
||||
int keyLength = (int) VIntCoding.getUnsignedVInt(in, position);
|
||||
position += keyLength + VIntCoding.computeUnsignedVIntSize(keyLength);
|
||||
if (position >= in.limit())
|
||||
throw new EOFException();
|
||||
int flags = in.get(position) & 0xff;
|
||||
return (flags & IS_EMPTY) != 0;
|
||||
}
|
||||
|
||||
public long serializedSize(PartitionUpdate update, int version)
|
||||
{
|
||||
try (UnfilteredRowIterator iter = update.unfilteredIterator())
|
||||
|
|
|
|||
|
|
@ -98,6 +98,11 @@ public abstract class AbstractCell<V> extends Cell<V>
|
|||
return this;
|
||||
}
|
||||
|
||||
public Cell<?> purgeDataOlderThan(long timestamp)
|
||||
{
|
||||
return this.timestamp() < timestamp ? null : this;
|
||||
}
|
||||
|
||||
public Cell<?> copy(AbstractAllocator allocator)
|
||||
{
|
||||
CellPath path = path();
|
||||
|
|
|
|||
|
|
@ -474,6 +474,18 @@ public class BTreeRow extends AbstractRow
|
|||
return transformAndFilter(newInfo, newDeletion, (cd) -> cd.purge(purger, nowInSec));
|
||||
}
|
||||
|
||||
public Row purgeDataOlderThan(long timestamp, boolean enforceStrictLiveness)
|
||||
{
|
||||
LivenessInfo newInfo = primaryKeyLivenessInfo.timestamp() < timestamp ? LivenessInfo.EMPTY : primaryKeyLivenessInfo;
|
||||
Deletion newDeletion = deletion.time().markedForDeleteAt() < timestamp ? Deletion.LIVE : deletion;
|
||||
|
||||
// when enforceStrictLiveness is set, a row is considered dead when it's PK liveness info is not present
|
||||
if (enforceStrictLiveness && newDeletion.isLive() && newInfo.isEmpty())
|
||||
return null;
|
||||
|
||||
return transformAndFilter(newInfo, newDeletion, cd -> cd.purgeDataOlderThan(timestamp));
|
||||
}
|
||||
|
||||
private Row transformAndFilter(LivenessInfo info, Deletion deletion, Function<ColumnData, ColumnData> function)
|
||||
{
|
||||
Object[] transformed = BTree.transformAndFilter(btree, function);
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ import org.apache.cassandra.schema.ColumnMetadata;
|
|||
import org.apache.cassandra.db.marshal.ByteType;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
import org.apache.cassandra.utils.ObjectSizes;
|
||||
import org.apache.cassandra.utils.memory.AbstractAllocator;
|
||||
|
||||
import static java.lang.String.format;
|
||||
|
||||
|
|
@ -134,14 +133,6 @@ public class BufferCell extends AbstractCell<ByteBuffer>
|
|||
return withUpdatedValue(ByteBufferUtil.EMPTY_BYTE_BUFFER);
|
||||
}
|
||||
|
||||
public Cell<?> copy(AbstractAllocator allocator)
|
||||
{
|
||||
if (!value.hasRemaining())
|
||||
return this;
|
||||
|
||||
return new BufferCell(column, timestamp, ttl, localDeletionTime, allocator.clone(value), path == null ? null : path.copy(allocator));
|
||||
}
|
||||
|
||||
@Override
|
||||
public long unsharedHeapSize()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -167,6 +167,10 @@ public abstract class Cell<V> extends ColumnData
|
|||
// Overrides super type to provide a more precise return type.
|
||||
public abstract Cell<?> purge(DeletionPurger purger, int nowInSec);
|
||||
|
||||
@Override
|
||||
// Overrides super type to provide a more precise return type.
|
||||
public abstract Cell<?> purgeDataOlderThan(long timestamp);
|
||||
|
||||
/**
|
||||
* The serialization format for cell is:
|
||||
* [ flags ][ timestamp ][ deletion time ][ ttl ][ path size ][ path ][ value size ][ value ]
|
||||
|
|
|
|||
|
|
@ -65,6 +65,8 @@ public abstract class CellPath implements IMeasurableMemory
|
|||
|
||||
public abstract long unsharedHeapSizeExcludingData();
|
||||
|
||||
public abstract long unsharedHeapSize();
|
||||
|
||||
@Override
|
||||
public final int hashCode()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -58,6 +58,8 @@ public abstract class ColumnData implements IMeasurableMemory
|
|||
|
||||
public abstract long unsharedHeapSizeExcludingData();
|
||||
|
||||
public abstract long unsharedHeapSize();
|
||||
|
||||
/**
|
||||
* Validate the column data.
|
||||
*
|
||||
|
|
@ -95,6 +97,7 @@ public abstract class ColumnData implements IMeasurableMemory
|
|||
public abstract ColumnData markCounterLocalToBeCleared();
|
||||
|
||||
public abstract ColumnData purge(DeletionPurger purger, int nowInSec);
|
||||
public abstract ColumnData purgeDataOlderThan(long timestamp);
|
||||
|
||||
public abstract long maxTimestamp();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -121,13 +121,10 @@ public class ComplexColumnData extends ColumnData implements Iterable<Cell<?>>
|
|||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long unsharedHeapSize()
|
||||
{
|
||||
long heapSize = EMPTY_SIZE + ObjectSizes.sizeOfArray(cells);
|
||||
for (Cell<?> cell : this)
|
||||
heapSize += cell.unsharedHeapSize();
|
||||
return heapSize;
|
||||
long heapSize = EMPTY_SIZE + ObjectSizes.sizeOfArray(cells) + complexDeletion.unsharedHeapSize();
|
||||
return BTree.<Cell>accumulate(cells, (cell, value) -> value + cell.unsharedHeapSize(), heapSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -205,6 +202,12 @@ public class ComplexColumnData extends ColumnData implements Iterable<Cell<?>>
|
|||
return transformAndFilter(complexDeletion, (cell) -> filter.fetchedCellIsQueried(column, cell.path()) ? null : cell);
|
||||
}
|
||||
|
||||
public ComplexColumnData purgeDataOlderThan(long timestamp)
|
||||
{
|
||||
DeletionTime newDeletion = complexDeletion.markedForDeleteAt() < timestamp ? DeletionTime.LIVE : complexDeletion;
|
||||
return transformAndFilter(newDeletion, (cell) -> cell.purgeDataOlderThan(timestamp));
|
||||
}
|
||||
|
||||
private ComplexColumnData transformAndFilter(DeletionTime newDeletion, Function<? super Cell<?>, ? extends Cell<?>> function)
|
||||
{
|
||||
Object[] transformed = BTree.transformAndFilter(cells, function);
|
||||
|
|
|
|||
|
|
@ -177,5 +177,4 @@ public class NativeCell extends AbstractCell<ByteBuffer>
|
|||
{
|
||||
return EMPTY_SIZE;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@ public interface Row extends Unfiltered, Iterable<ColumnData>, IMeasurableMemory
|
|||
*
|
||||
* @param nowInSec the current time to decide what is deleted and what isn't
|
||||
* @param enforceStrictLiveness whether the row should be purged if there is no PK liveness info,
|
||||
* normally retrieved from {@link CFMetaData#enforceStrictLiveness()}
|
||||
* normally retrieved from {@link TableMetadata#enforceStrictLiveness()}
|
||||
* @return true if there is some live information
|
||||
*/
|
||||
public boolean hasLiveData(int nowInSec, boolean enforceStrictLiveness);
|
||||
|
|
@ -249,6 +249,11 @@ public interface Row extends Unfiltered, Iterable<ColumnData>, IMeasurableMemory
|
|||
*/
|
||||
public Row withOnlyQueriedData(ColumnFilter filter);
|
||||
|
||||
/*
|
||||
* Returns a copy of this row without any data with a timestamp older than the one provided
|
||||
*/
|
||||
public Row purgeDataOlderThan(long timestamp, boolean enforceStrictLiveness);
|
||||
|
||||
/**
|
||||
* Returns a copy of this row where all counter cells have they "local" shard marked for clearing.
|
||||
*/
|
||||
|
|
@ -281,6 +286,7 @@ public interface Row extends Unfiltered, Iterable<ColumnData>, IMeasurableMemory
|
|||
public long unsharedHeapSizeExcludingData();
|
||||
|
||||
public String toString(TableMetadata metadata, boolean fullDetails);
|
||||
public long unsharedHeapSize();
|
||||
|
||||
/**
|
||||
* Apply a function to every column in a row
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ public class UnfilteredRowIteratorSerializer
|
|||
{
|
||||
protected static final Logger logger = LoggerFactory.getLogger(UnfilteredRowIteratorSerializer.class);
|
||||
|
||||
private static final int IS_EMPTY = 0x01;
|
||||
public static final int IS_EMPTY = 0x01;
|
||||
private static final int IS_REVERSED = 0x02;
|
||||
private static final int HAS_PARTITION_DELETION = 0x04;
|
||||
private static final int HAS_STATIC_ROW = 0x08;
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ import java.util.function.Predicate;
|
|||
|
||||
import com.google.common.collect.Iterables;
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.db.Keyspace;
|
||||
import org.apache.cassandra.dht.Token;
|
||||
import org.apache.cassandra.locator.AbstractReplicationStrategy;
|
||||
import org.apache.cassandra.locator.EndpointsForToken;
|
||||
|
|
|
|||
|
|
@ -23,10 +23,12 @@ import java.util.UUID;
|
|||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.cassandra.db.DecoratedKey;
|
||||
import org.apache.cassandra.db.marshal.TimeUUIDType;
|
||||
import org.apache.cassandra.db.marshal.UUIDType;
|
||||
import org.apache.cassandra.schema.TableMetadata;
|
||||
import org.apache.cassandra.streaming.StreamManager;
|
||||
import org.apache.cassandra.streaming.StreamingState;
|
||||
import org.apache.cassandra.utils.TimeUUID;
|
||||
|
||||
import static org.apache.cassandra.cql3.statements.schema.CreateTableStatement.parse;
|
||||
|
||||
|
|
@ -35,7 +37,7 @@ public class StreamingVirtualTable extends AbstractVirtualTable
|
|||
public StreamingVirtualTable(String keyspace)
|
||||
{
|
||||
super(parse("CREATE TABLE streaming (" +
|
||||
" id uuid,\n" +
|
||||
" id timeuuid,\n" +
|
||||
" follower boolean,\n" +
|
||||
" operation text, \n" +
|
||||
" peers frozen<list<text>>,\n" +
|
||||
|
|
@ -76,7 +78,7 @@ public class StreamingVirtualTable extends AbstractVirtualTable
|
|||
@Override
|
||||
public DataSet data(DecoratedKey partitionKey)
|
||||
{
|
||||
UUID id = UUIDType.instance.compose(partitionKey.getKey());
|
||||
TimeUUID id = TimeUUIDType.instance.compose(partitionKey.getKey());
|
||||
SimpleDataSet result = new SimpleDataSet(metadata());
|
||||
StreamingState state = StreamManager.instance.getStreamingState(id);
|
||||
if (state != null)
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ package org.apache.cassandra.db.virtual;
|
|||
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import com.google.common.base.MoreObjects;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
|
|
@ -26,6 +27,7 @@ import com.google.common.collect.ImmutableMap;
|
|||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.db.DecoratedKey;
|
||||
import org.apache.cassandra.db.IMutation;
|
||||
import org.apache.cassandra.db.Mutation;
|
||||
import org.apache.cassandra.db.partitions.PartitionUpdate;
|
||||
import org.apache.cassandra.schema.TableId;
|
||||
|
||||
|
|
@ -104,6 +106,12 @@ public final class VirtualMutation implements IMutation
|
|||
return modifications.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Supplier<Mutation> hintOnFailure()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateIndexedColumns()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -144,7 +144,7 @@ public class Murmur3Partitioner implements IPartitioner
|
|||
{
|
||||
static final long serialVersionUID = -5833580143318243006L;
|
||||
|
||||
final long token;
|
||||
public final long token;
|
||||
|
||||
public LongToken(long token)
|
||||
{
|
||||
|
|
@ -204,11 +204,16 @@ public class Murmur3Partitioner implements IPartitioner
|
|||
}
|
||||
|
||||
@Override
|
||||
public Token increaseSlightly()
|
||||
public LongToken increaseSlightly()
|
||||
{
|
||||
return new LongToken(token + 1);
|
||||
}
|
||||
|
||||
public LongToken decreaseSlightly()
|
||||
{
|
||||
return new LongToken(token - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverses murmur3 to find a possible 16 byte key that generates a given token
|
||||
*/
|
||||
|
|
@ -216,7 +221,7 @@ public class Murmur3Partitioner implements IPartitioner
|
|||
public static ByteBuffer keyForToken(LongToken token)
|
||||
{
|
||||
ByteBuffer result = ByteBuffer.allocate(16);
|
||||
long[] inv = MurmurHash.inv_hash3_x64_128(new long[] {token.token, 0L});
|
||||
long[] inv = MurmurHash.inv_hash3_x64_128(new long[]{ token.token, 0L });
|
||||
result.putLong(inv[0]).putLong(inv[1]).position(0);
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -483,7 +483,7 @@ public class Range<T extends RingPosition<T>> extends AbstractBounds<T> implemen
|
|||
* Given a list of unwrapped ranges sorted by left position, return an
|
||||
* equivalent list of ranges but with no overlapping ranges.
|
||||
*/
|
||||
private static <T extends RingPosition<T>> List<Range<T>> deoverlap(List<Range<T>> ranges)
|
||||
public static <T extends RingPosition<T>> List<Range<T>> deoverlap(List<Range<T>> ranges)
|
||||
{
|
||||
if (ranges.isEmpty())
|
||||
return ranges;
|
||||
|
|
|
|||
|
|
@ -20,13 +20,14 @@ package org.apache.cassandra.exceptions;
|
|||
import org.apache.cassandra.db.ConsistencyLevel;
|
||||
import org.apache.cassandra.db.WriteType;
|
||||
|
||||
|
||||
public class CasWriteTimeoutException extends WriteTimeoutException
|
||||
{
|
||||
public final int contentions;
|
||||
|
||||
public CasWriteTimeoutException(WriteType writeType, ConsistencyLevel consistency, int received, int blockFor, int contentions)
|
||||
{
|
||||
super(writeType, consistency, received, blockFor, String.format("CAS operation timed out - encountered contentions: %d", contentions));
|
||||
super(writeType, consistency, received, blockFor, String.format("CAS operation timed out: received %d of %d required responses after %d contention retries", received, blockFor, contentions));
|
||||
this.contentions = contentions;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ public class RequestFailureException extends RequestExecutionException
|
|||
this(code, buildErrorMessage(received, failureReasonByEndpoint), consistency, received, blockFor, failureReasonByEndpoint);
|
||||
}
|
||||
|
||||
protected RequestFailureException(ExceptionCode code, String msg, ConsistencyLevel consistency, int received, int blockFor, Map<InetAddressAndPort, RequestFailureReason> failureReasonByEndpoint)
|
||||
public RequestFailureException(ExceptionCode code, String msg, ConsistencyLevel consistency, int received, int blockFor, Map<InetAddressAndPort, RequestFailureReason> failureReasonByEndpoint)
|
||||
{
|
||||
super(code, buildErrorMessage(msg, failureReasonByEndpoint));
|
||||
this.consistency = consistency;
|
||||
|
|
|
|||
|
|
@ -36,8 +36,9 @@ public enum RequestFailureReason
|
|||
READ_TOO_MANY_TOMBSTONES (1),
|
||||
TIMEOUT (2),
|
||||
INCOMPATIBLE_SCHEMA (3),
|
||||
READ_SIZE (4);
|
||||
|
||||
READ_SIZE (4),
|
||||
NODE_DOWN (5);
|
||||
|
||||
public static final Serializer serializer = new Serializer();
|
||||
|
||||
public final int code;
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ import org.apache.cassandra.io.IVersionedSerializer;
|
|||
import org.apache.cassandra.io.util.DataInputPlus;
|
||||
import org.apache.cassandra.io.util.DataOutputPlus;
|
||||
import org.apache.cassandra.utils.CassandraVersion;
|
||||
import org.apache.cassandra.utils.NullableSerializer;
|
||||
|
||||
import static org.apache.cassandra.utils.Clock.Global.nanoTime;
|
||||
|
||||
|
|
@ -41,13 +42,12 @@ import static org.apache.cassandra.utils.Clock.Global.nanoTime;
|
|||
* This abstraction represents both the HeartBeatState and the ApplicationState in an EndpointState
|
||||
* instance. Any state for a given endpoint can be retrieved from this instance.
|
||||
*/
|
||||
|
||||
|
||||
public class EndpointState
|
||||
{
|
||||
protected static final Logger logger = LoggerFactory.getLogger(EndpointState.class);
|
||||
|
||||
public final static IVersionedSerializer<EndpointState> serializer = new EndpointStateSerializer();
|
||||
public final static IVersionedSerializer<EndpointState> nullableSerializer = NullableSerializer.wrap(serializer);
|
||||
|
||||
private volatile HeartBeatState hbState;
|
||||
private final AtomicReference<Map<ApplicationState, VersionedValue>> applicationState;
|
||||
|
|
@ -255,6 +255,20 @@ public class EndpointState
|
|||
{
|
||||
return "EndpointState: HeartBeatState = " + hbState + ", AppStateMap = " + applicationState.get();
|
||||
}
|
||||
|
||||
public boolean isSupersededBy(EndpointState that)
|
||||
{
|
||||
int thisGeneration = this.getHeartBeatState().getGeneration();
|
||||
int thatGeneration = that.getHeartBeatState().getGeneration();
|
||||
|
||||
if (thatGeneration > thisGeneration)
|
||||
return true;
|
||||
|
||||
if (thisGeneration > thatGeneration)
|
||||
return false;
|
||||
|
||||
return Gossiper.getMaxEndpointStateVersion(that) > Gossiper.getMaxEndpointStateVersion(this);
|
||||
}
|
||||
}
|
||||
|
||||
class EndpointStateSerializer implements IVersionedSerializer<EndpointState>
|
||||
|
|
|
|||
|
|
@ -43,11 +43,6 @@ public class GossipDigestAck2
|
|||
{
|
||||
this.epStateMap = epStateMap;
|
||||
}
|
||||
|
||||
Map<InetAddressAndPort, EndpointState> getEndpointStateMap()
|
||||
{
|
||||
return epStateMap;
|
||||
}
|
||||
}
|
||||
|
||||
class GossipDigestAck2Serializer implements IVersionedSerializer<GossipDigestAck2>
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ public class GossipDigestAck2VerbHandler extends GossipVerbHandler<GossipDigestA
|
|||
logger.trace("Ignoring GossipDigestAck2Message because gossip is disabled");
|
||||
return;
|
||||
}
|
||||
Map<InetAddressAndPort, EndpointState> remoteEpStateMap = message.payload.getEndpointStateMap();
|
||||
Map<InetAddressAndPort, EndpointState> remoteEpStateMap = message.payload.epStateMap;
|
||||
/* Notify the Failure Detector */
|
||||
Gossiper.instance.notifyFailureDetector(remoteEpStateMap);
|
||||
Gossiper.instance.applyStateLocally(remoteEpStateMap);
|
||||
|
|
|
|||
|
|
@ -610,7 +610,7 @@ public class Gossiper implements IFailureDetectionEventListener, GossiperMBean
|
|||
* @param epState
|
||||
* @return
|
||||
*/
|
||||
int getMaxEndpointStateVersion(EndpointState epState)
|
||||
static int getMaxEndpointStateVersion(EndpointState epState)
|
||||
{
|
||||
int maxVersion = epState.getHeartBeatState().getHeartBeatVersion();
|
||||
for (Map.Entry<ApplicationState, VersionedValue> state : epState.states())
|
||||
|
|
@ -1144,6 +1144,15 @@ public class Gossiper implements IFailureDetectionEventListener, GossiperMBean
|
|||
return ImmutableSet.copyOf(endpointStateMap.keySet());
|
||||
}
|
||||
|
||||
public String getForEndpoint(InetAddressAndPort ep, ApplicationState state)
|
||||
{
|
||||
EndpointState epState = endpointStateMap.get(ep);
|
||||
if (epState == null)
|
||||
return null;
|
||||
VersionedValue value = epState.getApplicationState(state);
|
||||
return value == null ? null : value.value;
|
||||
}
|
||||
|
||||
public int getEndpointCount()
|
||||
{
|
||||
return endpointStateMap.size();
|
||||
|
|
@ -1261,7 +1270,7 @@ public class Gossiper implements IFailureDetectionEventListener, GossiperMBean
|
|||
return ep1.getHeartBeatState().getGeneration() - ep2.getHeartBeatState().getGeneration();
|
||||
}
|
||||
|
||||
void notifyFailureDetector(Map<InetAddressAndPort, EndpointState> remoteEpStateMap)
|
||||
public void notifyFailureDetector(Map<InetAddressAndPort, EndpointState> remoteEpStateMap)
|
||||
{
|
||||
for (Entry<InetAddressAndPort, EndpointState> entry : remoteEpStateMap.entrySet())
|
||||
{
|
||||
|
|
@ -2143,7 +2152,7 @@ public class Gossiper implements IFailureDetectionEventListener, GossiperMBean
|
|||
}
|
||||
}
|
||||
|
||||
protected boolean isInShadowRound()
|
||||
public boolean isInShadowRound()
|
||||
{
|
||||
return inShadowRound;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -190,6 +190,14 @@ public class VersionedValue implements Comparable<VersionedValue>
|
|||
Long.toString(expireTime)));
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public VersionedValue left(Collection<Token> tokens, long expireTime, int generation)
|
||||
{
|
||||
return new VersionedValue(versionString(VersionedValue.STATUS_LEFT,
|
||||
makeTokenString(tokens),
|
||||
Long.toString(expireTime)), generation);
|
||||
}
|
||||
|
||||
public VersionedValue moving(Token token)
|
||||
{
|
||||
return new VersionedValue(VersionedValue.STATUS_MOVING + VersionedValue.DELIMITER + partitioner.getTokenFactory().toString(token));
|
||||
|
|
|
|||
|
|
@ -54,8 +54,8 @@ import org.apache.cassandra.dht.*;
|
|||
import org.apache.cassandra.hadoop.*;
|
||||
import org.apache.cassandra.utils.*;
|
||||
|
||||
import static org.apache.cassandra.utils.Clock.Global.nanoTime;
|
||||
import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory;
|
||||
import static org.apache.cassandra.utils.Clock.Global.nanoTime;
|
||||
|
||||
/**
|
||||
* Hadoop InputFormat allowing map/reduce against Cassandra rows within one ColumnFamily.
|
||||
|
|
|
|||
|
|
@ -267,7 +267,27 @@ public interface Index
|
|||
public Optional<ColumnFamilyStore> getBackingTable();
|
||||
|
||||
/**
|
||||
* Return a task which performs a blocking flush of the index's data to persistent storage.
|
||||
* Return a task which performs a blocking flush of the index's data corresponding to the provided
|
||||
* base table's Memtable. This may extract any necessary data from the base table's Memtable as part of the flush.
|
||||
*
|
||||
* This version of the method is invoked whenever we flush the base table. If the index stores no in-memory data
|
||||
* of its own, it is safe to only implement this method.
|
||||
*
|
||||
* @return task to be executed by the index manager to perform the flush.
|
||||
*/
|
||||
public default Callable<?> getBlockingFlushTask(Memtable baseCfs)
|
||||
{
|
||||
return getBlockingFlushTask();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a task which performs a blocking flush of any in-memory index data to persistent storage,
|
||||
* independent of any flush of the base table.
|
||||
*
|
||||
* Note that this method is only invoked outside of normal flushes: if there is no in-memory storage
|
||||
* for this index, and it only extracts data on flush from the base table's Memtable, then it is safe to
|
||||
* perform no work.
|
||||
*
|
||||
* @return task to be executed by the index manager to perform the flush.
|
||||
*/
|
||||
public Callable<?> getBlockingFlushTask();
|
||||
|
|
|
|||
|
|
@ -38,7 +38,9 @@ import org.slf4j.Logger;
|
|||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.apache.cassandra.concurrent.ExecutorPlus;
|
||||
import org.apache.cassandra.concurrent.FutureTask;
|
||||
import org.apache.cassandra.concurrent.ImmediateExecutor;
|
||||
import org.apache.cassandra.concurrent.ScheduledExecutors;
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.cql3.statements.schema.IndexTarget;
|
||||
import org.apache.cassandra.db.*;
|
||||
|
|
@ -63,6 +65,8 @@ import org.apache.cassandra.notifications.SSTableAddedNotification;
|
|||
import org.apache.cassandra.schema.ColumnMetadata;
|
||||
import org.apache.cassandra.schema.IndexMetadata;
|
||||
import org.apache.cassandra.schema.Indexes;
|
||||
import org.apache.cassandra.schema.Schema;
|
||||
import org.apache.cassandra.schema.SchemaConstants;
|
||||
import org.apache.cassandra.service.pager.SinglePartitionPager;
|
||||
import org.apache.cassandra.tracing.Tracing;
|
||||
import org.apache.cassandra.transport.ProtocolVersion;
|
||||
|
|
@ -209,13 +213,15 @@ public class SecondaryIndexManager implements IndexRegistry, INotificationConsum
|
|||
|
||||
markIndexesBuilding(ImmutableSet.of(index), true, isNewCF);
|
||||
|
||||
Callable<?> initialBuildTask = null;
|
||||
FutureTask<?> initialBuildTask = null;
|
||||
// if the index didn't register itself, we can probably assume that no initialization needs to happen
|
||||
if (indexes.containsKey(indexDef.name))
|
||||
{
|
||||
try
|
||||
{
|
||||
initialBuildTask = index.getInitializationTask();
|
||||
Callable<?> call = index.getInitializationTask();
|
||||
if (call != null)
|
||||
initialBuildTask = new FutureTask<>(call);
|
||||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
|
|
@ -233,16 +239,20 @@ public class SecondaryIndexManager implements IndexRegistry, INotificationConsum
|
|||
|
||||
// otherwise run the initialization task asynchronously with a callback to mark it built or failed
|
||||
final Promise<Void> initialization = new AsyncPromise<>();
|
||||
asyncExecutor.submit(initialBuildTask)
|
||||
.addCallback(
|
||||
success -> {
|
||||
markIndexBuilt(index, true);
|
||||
initialization.trySuccess(null);
|
||||
},
|
||||
failure -> {
|
||||
logAndMarkIndexesFailed(Collections.singleton(index), failure, true);
|
||||
initialization.tryFailure(failure);
|
||||
});
|
||||
// we want to ensure we invoke this task asynchronously, so we want to add our callback before submission
|
||||
// to ensure the work is not completed before we register the callback and so it gets performed by us.
|
||||
// This is because Keyspace.open("system") can transitively attempt to open Keyspace.open("system")
|
||||
initialBuildTask.addCallback(
|
||||
success -> {
|
||||
markIndexBuilt(index, true);
|
||||
initialization.trySuccess(null);
|
||||
},
|
||||
failure -> {
|
||||
logAndMarkIndexesFailed(Collections.singleton(index), failure, true);
|
||||
initialization.tryFailure(failure);
|
||||
}
|
||||
);
|
||||
asyncExecutor.execute(initialBuildTask);
|
||||
|
||||
return initialization;
|
||||
}
|
||||
|
|
@ -667,7 +677,7 @@ public class SecondaryIndexManager implements IndexRegistry, INotificationConsum
|
|||
if (counter.decrementAndGet() == 0)
|
||||
{
|
||||
inProgressBuilds.remove(indexName);
|
||||
if (!needsFullRebuild.contains(indexName) && DatabaseDescriptor.isDaemonInitialized())
|
||||
if (!needsFullRebuild.contains(indexName) && DatabaseDescriptor.isDaemonInitialized() && Keyspace.isInitialized())
|
||||
SystemKeyspace.setIndexBuilt(baseCfs.keyspace.getName(), indexName);
|
||||
}
|
||||
}
|
||||
|
|
@ -798,17 +808,6 @@ public class SecondaryIndexManager implements IndexRegistry, INotificationConsum
|
|||
flushIndexesBlocking(indexes, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a blocking flush of all custom indexes
|
||||
*/
|
||||
public void flushAllNonCFSBackedIndexesBlocking()
|
||||
{
|
||||
executeAllBlocking(indexes.values()
|
||||
.stream()
|
||||
.filter(index -> !index.getBackingTable().isPresent()),
|
||||
Index::getBlockingFlushTask, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a blocking execution of pre-join tasks of all indexes
|
||||
*/
|
||||
|
|
@ -843,6 +842,18 @@ public class SecondaryIndexManager implements IndexRegistry, INotificationConsum
|
|||
FBUtilities.waitOnFutures(wait);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a blocking flush of all custom indexes
|
||||
*/
|
||||
public void flushAllNonCFSBackedIndexesBlocking(Memtable baseCfsMemtable)
|
||||
{
|
||||
executeAllBlocking(indexes.values()
|
||||
.stream()
|
||||
.filter(index -> !index.getBackingTable().isPresent()),
|
||||
index -> index.getBlockingFlushTask(baseCfsMemtable),
|
||||
null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return all indexes which are marked as built and ready to use
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -28,10 +28,4 @@ public class FSDiskFullWriteError extends FSWriteError
|
|||
mutationSize,
|
||||
keyspace)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "FSDiskFullWriteError";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,17 +24,30 @@ import org.apache.cassandra.io.util.File;
|
|||
|
||||
public abstract class FSError extends IOError
|
||||
{
|
||||
final String message;
|
||||
public final String path;
|
||||
|
||||
public FSError(Throwable cause, File path)
|
||||
{
|
||||
super(cause);
|
||||
this.path = path.toString();
|
||||
this(null, cause, path);
|
||||
}
|
||||
|
||||
public FSError(Throwable cause, Path path)
|
||||
{
|
||||
this(null, cause, path);
|
||||
}
|
||||
|
||||
public FSError(String message, Throwable cause, File path)
|
||||
{
|
||||
super(cause);
|
||||
this.message = message;
|
||||
this.path = path.toString();
|
||||
}
|
||||
|
||||
public FSError(String message, Throwable cause, Path path)
|
||||
{
|
||||
super(cause);
|
||||
this.message = message;
|
||||
this.path = path.toString();
|
||||
}
|
||||
|
||||
|
|
@ -53,4 +66,10 @@ public abstract class FSError extends IOError
|
|||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return getClass().getSimpleName() + (message != null ? ' ' + message : "") + (path != null ? " in " + path : "");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,10 +30,4 @@ public class FSNoDiskAvailableForWriteError extends FSWriteError
|
|||
super(new IOException(String.format("The data directories for the %s keyspace have been marked as unwritable",
|
||||
keyspace)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "FSNoDiskAvailableForWriteError";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,9 +39,23 @@ public class FSReadError extends FSError
|
|||
this(cause, new File(path));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
public FSReadError(String message, Throwable cause, Path path)
|
||||
{
|
||||
return "FSReadError in " + path;
|
||||
super(message, cause, path);
|
||||
}
|
||||
|
||||
public FSReadError(String message, Throwable cause, File path)
|
||||
{
|
||||
super(message, cause, path);
|
||||
}
|
||||
|
||||
public FSReadError(String message, Throwable cause, String path)
|
||||
{
|
||||
this(message, cause, new File(path));
|
||||
}
|
||||
|
||||
public FSReadError(String message, Throwable cause)
|
||||
{
|
||||
this(message, cause, new File(""));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,9 +44,23 @@ public class FSWriteError extends FSError
|
|||
this(cause, new File(""));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
public FSWriteError(String message, Throwable cause, Path path)
|
||||
{
|
||||
return "FSWriteError in " + path;
|
||||
super(message, cause, path);
|
||||
}
|
||||
|
||||
public FSWriteError(String message, Throwable cause, File path)
|
||||
{
|
||||
super(message, cause, path);
|
||||
}
|
||||
|
||||
public FSWriteError(String message, Throwable cause, String path)
|
||||
{
|
||||
this(message, cause, new File(path));
|
||||
}
|
||||
|
||||
public FSWriteError(String message, Throwable cause)
|
||||
{
|
||||
this(message, cause, new File(""));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -251,7 +251,7 @@ public class CQLSSTableWriter implements Closeable
|
|||
ClientState.forInternalCalls(),
|
||||
options,
|
||||
insert.getTimestamp(TimeUnit.MILLISECONDS.toMicros(now), options),
|
||||
(int) TimeUnit.MILLISECONDS.toSeconds(now),
|
||||
options.getNowInSec((int) TimeUnit.MILLISECONDS.toSeconds(now)),
|
||||
insert.getTimeToLive(options),
|
||||
Collections.emptyMap());
|
||||
|
||||
|
|
|
|||
|
|
@ -281,6 +281,11 @@ public class IndexSummaryManager implements IndexSummaryManagerMBean
|
|||
@VisibleForTesting
|
||||
public void shutdownAndWait(long timeout, TimeUnit unit) throws InterruptedException, TimeoutException
|
||||
{
|
||||
ExecutorUtils.shutdownNowAndWait(timeout, unit, executor);
|
||||
if (future != null)
|
||||
{
|
||||
future.cancel(false);
|
||||
future = null;
|
||||
}
|
||||
ExecutorUtils.shutdownAndWait(timeout, unit, executor);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@ import com.google.common.primitives.Longs;
|
|||
import com.google.common.util.concurrent.RateLimiter;
|
||||
|
||||
import org.apache.cassandra.config.CassandraRelevantProperties;
|
||||
import org.apache.cassandra.io.util.File;
|
||||
import org.apache.cassandra.concurrent.ExecutorPlus;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@
|
|||
package org.apache.cassandra.io.sstable.format.big;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.apache.cassandra.io.sstable.SSTable;
|
||||
import org.apache.cassandra.schema.TableMetadata;
|
||||
|
|
|
|||
|
|
@ -638,7 +638,7 @@ public class BigTableWriter extends SSTableWriter
|
|||
|
||||
protected Throwable doAbort(Throwable accumulate)
|
||||
{
|
||||
return indexFile.abort(accumulate);
|
||||
return summary.close(indexFile.abort(accumulate));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -36,7 +36,6 @@ import org.apache.cassandra.io.ISerializer;
|
|||
import org.apache.cassandra.io.sstable.format.Version;
|
||||
import org.apache.cassandra.io.util.DataInputPlus;
|
||||
import org.apache.cassandra.io.util.DataOutputPlus;
|
||||
import org.apache.cassandra.net.MessagingService;
|
||||
import org.apache.cassandra.utils.ByteBufferUtil;
|
||||
import org.apache.cassandra.utils.EstimatedHistogram;
|
||||
import org.apache.cassandra.utils.TimeUUID;
|
||||
|
|
|
|||
|
|
@ -20,8 +20,6 @@ package org.apache.cassandra.io.util;
|
|||
import java.nio.ByteBuffer;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.apache.cassandra.io.util.File;
|
||||
|
||||
public class ChecksummedSequentialWriter extends SequentialWriter
|
||||
{
|
||||
private static final SequentialWriterOption CRC_WRITER_OPTION = SequentialWriterOption.newBuilder()
|
||||
|
|
|
|||
|
|
@ -18,7 +18,9 @@
|
|||
|
||||
package org.apache.cassandra.io.util;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.net.URI;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.file.*; // checkstyle: permit this import
|
||||
|
|
@ -34,6 +36,7 @@ import javax.annotation.Nullable;
|
|||
|
||||
import com.google.common.util.concurrent.RateLimiter;
|
||||
|
||||
import net.openhft.chronicle.core.util.ThrowingFunction;
|
||||
import org.apache.cassandra.io.FSWriteError;
|
||||
|
||||
import static org.apache.cassandra.io.util.PathUtils.filename;
|
||||
|
|
@ -164,6 +167,16 @@ public class File implements Comparable<File>
|
|||
maybeFail(delete(null, null));
|
||||
}
|
||||
|
||||
/**
|
||||
* This file will be deleted, with any failures being reported with an FSError
|
||||
* @throws FSWriteError if cannot be deleted
|
||||
*/
|
||||
public void deleteIfExists()
|
||||
{
|
||||
if (path != null)
|
||||
PathUtils.deleteIfExists(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* This file will be deleted, obeying the provided rate limiter.
|
||||
* @throws FSWriteError if cannot be deleted
|
||||
|
|
@ -336,6 +349,11 @@ public class File implements Comparable<File>
|
|||
return PathUtils.createFileIfNotExists(toPathForWrite());
|
||||
}
|
||||
|
||||
public boolean createDirectoriesIfNotExists()
|
||||
{
|
||||
return PathUtils.createDirectoriesIfNotExists(toPathForWrite());
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to create a directory at this path.
|
||||
* Return true if a new directory was created at this path, and false otherwise.
|
||||
|
|
@ -436,12 +454,29 @@ public class File implements Comparable<File>
|
|||
PathUtils.forEachRecursive(path, path -> forEach.accept(new File(path)));
|
||||
}
|
||||
|
||||
private static <V> ThrowingFunction<IOException, V, RuntimeException> nulls() { return ignore -> null; }
|
||||
private static <V> ThrowingFunction<IOException, V, IOException> rethrow()
|
||||
{
|
||||
return fail -> {
|
||||
if (fail == null) throw new FileNotFoundException();
|
||||
throw fail;
|
||||
};
|
||||
}
|
||||
private static <V> ThrowingFunction<IOException, V, UncheckedIOException> unchecked()
|
||||
{
|
||||
return fail -> {
|
||||
if (fail == null) fail = new FileNotFoundException();
|
||||
throw new UncheckedIOException(fail);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return if a directory, the names of the files within; null otherwise
|
||||
*/
|
||||
public String[] tryListNames()
|
||||
{
|
||||
return tryListNames(path, Function.identity());
|
||||
return tryListNames(nulls());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -449,7 +484,7 @@ public class File implements Comparable<File>
|
|||
*/
|
||||
public String[] tryListNames(BiPredicate<File, String> filter)
|
||||
{
|
||||
return tryList(path, stream -> stream.map(PathUtils::filename).filter(filename -> filter.test(this, filename)), String[]::new);
|
||||
return tryListNames(filter, nulls());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -457,7 +492,7 @@ public class File implements Comparable<File>
|
|||
*/
|
||||
public File[] tryList()
|
||||
{
|
||||
return tryList(path, Function.identity());
|
||||
return tryList(nulls());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -465,7 +500,7 @@ public class File implements Comparable<File>
|
|||
*/
|
||||
public File[] tryList(Predicate<File> filter)
|
||||
{
|
||||
return tryList(path, stream -> stream.filter(filter));
|
||||
return tryList(filter, nulls());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -473,28 +508,148 @@ public class File implements Comparable<File>
|
|||
*/
|
||||
public File[] tryList(BiPredicate<File, String> filter)
|
||||
{
|
||||
return tryList(path, stream -> stream.filter(file -> filter.test(this, file.name())));
|
||||
return tryList(filter, nulls());
|
||||
}
|
||||
|
||||
private static String[] tryListNames(Path path, Function<Stream<File>, Stream<File>> toFiles)
|
||||
/**
|
||||
* @return if a directory, the names of the files within; null otherwise
|
||||
*/
|
||||
public String[] listNames() throws IOException
|
||||
{
|
||||
if (path == null)
|
||||
return null;
|
||||
return PathUtils.tryList(path, stream -> toFiles.apply(stream.map(File::new)).map(File::name), String[]::new);
|
||||
return tryListNames(rethrow());
|
||||
}
|
||||
|
||||
private static <T> T[] tryList(Path path, Function<Stream<Path>, Stream<T>> transformation, IntFunction<T[]> constructor)
|
||||
/**
|
||||
* @return if a directory, the names of the files within, filtered by the provided predicate; null otherwise
|
||||
*/
|
||||
public String[] listNames(BiPredicate<File, String> filter) throws IOException
|
||||
{
|
||||
if (path == null)
|
||||
return null;
|
||||
return PathUtils.tryList(path, transformation, constructor);
|
||||
return tryListNames(filter, rethrow());
|
||||
}
|
||||
|
||||
private static File[] tryList(Path path, Function<Stream<File>, Stream<File>> toFiles)
|
||||
/**
|
||||
* @return if a directory, the files within; null otherwise
|
||||
*/
|
||||
public File[] list() throws IOException
|
||||
{
|
||||
return tryList(rethrow());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return if a directory, the files within, filtered by the provided predicate; null otherwise
|
||||
*/
|
||||
public File[] list(Predicate<File> filter) throws IOException
|
||||
{
|
||||
return tryList(filter, rethrow());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return if a directory, the files within, filtered by the provided predicate; null otherwise
|
||||
*/
|
||||
public File[] list(BiPredicate<File, String> filter) throws IOException
|
||||
{
|
||||
return tryList(filter, rethrow());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return if a directory, the names of the files within; null otherwise
|
||||
*/
|
||||
public String[] listNamesUnchecked() throws UncheckedIOException
|
||||
{
|
||||
return tryListNames(unchecked());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return if a directory, the names of the files within, filtered by the provided predicate; null otherwise
|
||||
*/
|
||||
public String[] listNamesUnchecked(BiPredicate<File, String> filter) throws UncheckedIOException
|
||||
{
|
||||
return tryListNames(filter, unchecked());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return if a directory, the files within; null otherwise
|
||||
*/
|
||||
public File[] listUnchecked() throws UncheckedIOException
|
||||
{
|
||||
return tryList(unchecked());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return if a directory, the files within, filtered by the provided predicate; null otherwise
|
||||
*/
|
||||
public File[] listUnchecked(Predicate<File> filter) throws UncheckedIOException
|
||||
{
|
||||
return tryList(filter, unchecked());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return if a directory, the files within, filtered by the provided predicate; throw an UncheckedIO exception otherwise
|
||||
*/
|
||||
public File[] listUnchecked(BiPredicate<File, String> filter) throws UncheckedIOException
|
||||
{
|
||||
return tryList(filter, unchecked());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return if a directory, the names of the files within; null otherwise
|
||||
*/
|
||||
public <T extends Throwable> String[] tryListNames(ThrowingFunction<IOException, String[], T> orElse) throws T
|
||||
{
|
||||
return tryListNames(path, Function.identity(), orElse);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return if a directory, the names of the files within, filtered by the provided predicate; null otherwise
|
||||
*/
|
||||
public <T extends Throwable> String[] tryListNames(BiPredicate<File, String> filter, ThrowingFunction<IOException, String[], T> orElse) throws T
|
||||
{
|
||||
return tryList(path, stream -> stream.map(PathUtils::filename).filter(filename -> filter.test(this, filename)), String[]::new, orElse);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return if a directory, the files within; null otherwise
|
||||
*/
|
||||
private <T extends Throwable> File[] tryList(ThrowingFunction<IOException, File[], T> orElse) throws T
|
||||
{
|
||||
return tryList(path, Function.identity(), orElse);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return if a directory, the files within, filtered by the provided predicate; null otherwise
|
||||
*/
|
||||
private <T extends Throwable> File[] tryList(Predicate<File> filter, ThrowingFunction<IOException, File[], T> orElse) throws T
|
||||
{
|
||||
return tryList(path, stream -> stream.filter(filter), orElse);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return if a directory, the files within, filtered by the provided predicate; null otherwise
|
||||
*/
|
||||
private <T extends Throwable> File[] tryList(BiPredicate<File, String> filter, ThrowingFunction<IOException, File[], T> orElse) throws T
|
||||
{
|
||||
return tryList(path, stream -> stream.filter(file -> filter.test(this, file.name())), orElse);
|
||||
}
|
||||
|
||||
private static <T extends Throwable> String[] tryListNames(Path path, Function<Stream<File>, Stream<File>> toFiles, ThrowingFunction<IOException, String[], T> orElse) throws T
|
||||
{
|
||||
if (path == null)
|
||||
return null;
|
||||
return PathUtils.tryList(path, stream -> toFiles.apply(stream.map(File::new)), File[]::new);
|
||||
return orElse.apply(null);
|
||||
return PathUtils.tryList(path, stream -> toFiles.apply(stream.map(File::new)).map(File::name), String[]::new, orElse);
|
||||
}
|
||||
|
||||
private static <T extends Throwable, V> V[] tryList(Path path, Function<Stream<Path>, Stream<V>> transformation, IntFunction<V[]> constructor, ThrowingFunction<IOException, V[], T> orElse) throws T
|
||||
{
|
||||
if (path == null)
|
||||
return orElse.apply(null);
|
||||
return PathUtils.tryList(path, transformation, constructor, orElse);
|
||||
}
|
||||
|
||||
private static <T extends Throwable> File[] tryList(Path path, Function<Stream<File>, Stream<File>> toFiles, ThrowingFunction<IOException, File[], T> orElse) throws T
|
||||
{
|
||||
if (path == null)
|
||||
return orElse.apply(null);
|
||||
return PathUtils.tryList(path, stream -> toFiles.apply(stream.map(File::new)), File[]::new, orElse);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ import com.google.common.util.concurrent.RateLimiter;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import net.openhft.chronicle.core.util.ThrowingFunction;
|
||||
import org.apache.cassandra.config.CassandraRelevantProperties;
|
||||
import org.apache.cassandra.io.FSError;
|
||||
import org.apache.cassandra.io.FSReadError;
|
||||
|
|
@ -132,7 +133,7 @@ public final class PathUtils
|
|||
}
|
||||
}
|
||||
|
||||
public static <T> T[] tryList(Path path, Function<Stream<Path>, Stream<T>> transform, IntFunction<T[]> arrayFactory)
|
||||
public static <T extends Throwable, V> V[] tryList(Path path, Function<Stream<Path>, Stream<V>> transform, IntFunction<V[]> arrayFactory, ThrowingFunction<IOException, V[], T> orElse) throws T
|
||||
{
|
||||
try (Stream<Path> stream = Files.list(path))
|
||||
{
|
||||
|
|
@ -141,7 +142,7 @@ public final class PathUtils
|
|||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
return null;
|
||||
return orElse.apply(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -255,6 +256,22 @@ public final class PathUtils
|
|||
}
|
||||
}
|
||||
|
||||
public static void deleteIfExists(Path file)
|
||||
{
|
||||
try
|
||||
{
|
||||
Files.delete(file);
|
||||
onDeletion.accept(file);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
if (e instanceof FileNotFoundException | e instanceof NoSuchFileException)
|
||||
return;
|
||||
|
||||
throw propagateUnchecked(e, file, true);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean tryDelete(Path file)
|
||||
{
|
||||
try
|
||||
|
|
@ -375,9 +392,8 @@ public final class PathUtils
|
|||
{
|
||||
logger.trace("Could not move file {} to {}", from, to, e);
|
||||
|
||||
// TODO: this should be an FSError (either read or write)?
|
||||
// (but for now this is maintaining legacy semantics)
|
||||
throw new RuntimeException(String.format("Failed to rename %s to %s", from, to), e);
|
||||
// TODO: try to decide if is read or write? for now, have assumed write
|
||||
throw propagateUnchecked(String.format("Failed to rename %s to %s", from, to), e, to, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -733,6 +749,14 @@ public final class PathUtils
|
|||
* propagate an IOException as an FSWriteError, FSReadError or UncheckedIOException
|
||||
*/
|
||||
public static RuntimeException propagateUnchecked(IOException ioe, Path path, boolean write)
|
||||
{
|
||||
return propagateUnchecked(null, ioe, path, write);
|
||||
}
|
||||
|
||||
/**
|
||||
* propagate an IOException as an FSWriteError, FSReadError or UncheckedIOException
|
||||
*/
|
||||
public static RuntimeException propagateUnchecked(String message, IOException ioe, Path path, boolean write)
|
||||
{
|
||||
if (ioe instanceof FileAlreadyExistsException
|
||||
|| ioe instanceof NoSuchFileException
|
||||
|
|
@ -741,10 +765,10 @@ public final class PathUtils
|
|||
|| ioe instanceof java.nio.file.FileSystemLoopException
|
||||
|| ioe instanceof java.nio.file.NotDirectoryException
|
||||
|| ioe instanceof java.nio.file.NotLinkException)
|
||||
throw new UncheckedIOException(ioe);
|
||||
throw new UncheckedIOException(message, ioe);
|
||||
|
||||
if (write) throw new FSWriteError(ioe, path);
|
||||
else throw new FSReadError(ioe, path);
|
||||
if (write) throw new FSWriteError(message, ioe, path);
|
||||
else throw new FSReadError(message, ioe, path);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ public class SequentialWriterOption
|
|||
* </ul>
|
||||
*/
|
||||
public static final SequentialWriterOption DEFAULT = SequentialWriterOption.newBuilder().build();
|
||||
public static final SequentialWriterOption FINISH_ON_CLOSE = SequentialWriterOption.newBuilder().finishOnClose(true).build();
|
||||
|
||||
private final int bufferSize;
|
||||
private final BufferType bufferType;
|
||||
|
|
|
|||
|
|
@ -32,9 +32,12 @@ import java.util.Comparator;
|
|||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.RandomAccess;
|
||||
import java.util.Set;
|
||||
import java.util.Spliterator;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BinaryOperator;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
|
|
@ -45,6 +48,9 @@ import java.util.stream.Stream;
|
|||
* A collection like class for Replica objects. Since the Replica class contains inetaddress, range, and
|
||||
* transient replication status, basic contains and remove methods can be ambiguous. Replicas forces you
|
||||
* to be explicit about what you're checking the container for, or removing from it.
|
||||
*
|
||||
* TODO: there's nothing about this collection that's unique to Replicas, and the implementation
|
||||
* could make a useful general purpose immutable list<->set
|
||||
*/
|
||||
public abstract class AbstractReplicaCollection<C extends AbstractReplicaCollection<C>> implements ReplicaCollection<C>
|
||||
{
|
||||
|
|
@ -69,8 +75,10 @@ public abstract class AbstractReplicaCollection<C extends AbstractReplicaCollect
|
|||
* A simple list with no comodification checks and immutability by default (only append permitted, and only one initial copy)
|
||||
* this permits us to reduce the amount of garbage generated, by not wrapping iterators or unnecessarily copying
|
||||
* and reduces the amount of indirection necessary, as well as ensuring monomorphic callsites
|
||||
*
|
||||
* TODO flatten into AbstractReplicaCollection?
|
||||
*/
|
||||
protected static class ReplicaList implements Iterable<Replica>
|
||||
protected final static class ReplicaList implements Iterable<Replica>
|
||||
{
|
||||
private static final Replica[] EMPTY = new Replica[0];
|
||||
Replica[] contents;
|
||||
|
|
@ -125,7 +133,7 @@ public abstract class AbstractReplicaCollection<C extends AbstractReplicaCollect
|
|||
return new ReplicaList(contents, this.begin + begin, end - begin);
|
||||
}
|
||||
|
||||
public ReplicaList sorted(Comparator<Replica> comparator)
|
||||
public ReplicaList sorted(Comparator<? super Replica> comparator)
|
||||
{
|
||||
Replica[] copy = Arrays.copyOfRange(contents, begin, begin + size);
|
||||
Arrays.sort(copy, comparator);
|
||||
|
|
@ -137,6 +145,37 @@ public abstract class AbstractReplicaCollection<C extends AbstractReplicaCollect
|
|||
return Arrays.stream(contents, begin, begin + size);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forEach(Consumer<? super Replica> forEach)
|
||||
{
|
||||
for (int i = begin, end = begin + size ; i < end ; ++i)
|
||||
forEach.accept(contents[i]);
|
||||
}
|
||||
|
||||
/** see {@link ReplicaCollection#count(Predicate)}*/
|
||||
public int count(Predicate<? super Replica> test)
|
||||
{
|
||||
int count = 0;
|
||||
for (int i = begin, end = i + size ; i < end ; ++i)
|
||||
if (test.test(contents[i]))
|
||||
++count;
|
||||
return count;
|
||||
}
|
||||
|
||||
public final boolean anyMatch(Predicate<? super Replica> predicate)
|
||||
{
|
||||
for (int i = begin, end = i + size ; i < end ; ++i)
|
||||
if (predicate.test(contents[i]))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Spliterator<Replica> spliterator()
|
||||
{
|
||||
return Arrays.spliterator(contents, begin, begin + size);
|
||||
}
|
||||
|
||||
// we implement our own iterator, because it is trivial to do so, and in monomorphic call sites
|
||||
// will compile down to almost optimal indexed for loop
|
||||
@Override
|
||||
|
|
@ -163,7 +202,7 @@ public abstract class AbstractReplicaCollection<C extends AbstractReplicaCollect
|
|||
|
||||
// we implement our own iterator, because it is trivial to do so, and in monomorphic call sites
|
||||
// will compile down to almost optimal indexed for loop
|
||||
public <K> Iterator<K> transformIterator(Function<Replica, K> function)
|
||||
public <K> Iterator<K> transformIterator(Function<? super Replica, ? extends K> function)
|
||||
{
|
||||
return new Iterator<K>()
|
||||
{
|
||||
|
|
@ -186,7 +225,7 @@ public abstract class AbstractReplicaCollection<C extends AbstractReplicaCollect
|
|||
// we implement our own iterator, because it is trivial to do so, and in monomorphic call sites
|
||||
// will compile down to almost optimal indexed for loop
|
||||
// in this case, especially, it is impactful versus Iterables.limit(Iterables.filter())
|
||||
private Iterator<Replica> filterIterator(Predicate<Replica> predicate, int limit)
|
||||
private Iterator<Replica> filterIterator(Predicate<? super Replica> predicate, int limit)
|
||||
{
|
||||
return new Iterator<Replica>()
|
||||
{
|
||||
|
|
@ -218,6 +257,12 @@ public abstract class AbstractReplicaCollection<C extends AbstractReplicaCollect
|
|||
};
|
||||
}
|
||||
|
||||
protected <T> void forEach(Function<? super Replica, T> function, Consumer<? super T> action)
|
||||
{
|
||||
for (int i = begin, end = begin + size ; i < end ; ++i)
|
||||
action.accept(function.apply(contents[i]));
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public boolean equals(Object to)
|
||||
{
|
||||
|
|
@ -263,6 +308,12 @@ public abstract class AbstractReplicaCollection<C extends AbstractReplicaCollect
|
|||
public boolean contains(Object o) { return containsKey(o); }
|
||||
@Override
|
||||
public Iterator<K> iterator() { return list.transformIterator(toKey); }
|
||||
|
||||
@Override
|
||||
public void forEach(Consumer<? super K> action)
|
||||
{
|
||||
list.forEach(toKey, action);
|
||||
}
|
||||
}
|
||||
|
||||
class EntrySet extends AbstractImmutableSet<Entry<K, Replica>>
|
||||
|
|
@ -318,7 +369,7 @@ public abstract class AbstractReplicaCollection<C extends AbstractReplicaCollect
|
|||
public boolean containsKey(Object key)
|
||||
{
|
||||
Preconditions.checkNotNull(key);
|
||||
return get((K)key) != null;
|
||||
return get(key) != null;
|
||||
}
|
||||
|
||||
public Replica get(Object key)
|
||||
|
|
@ -368,6 +419,35 @@ public abstract class AbstractReplicaCollection<C extends AbstractReplicaCollect
|
|||
}
|
||||
}
|
||||
|
||||
static class AsList<T> extends AbstractList<T> implements RandomAccess
|
||||
{
|
||||
final Function<Replica, T> view;
|
||||
final ReplicaList list;
|
||||
|
||||
AsList(Function<Replica, T> view, ReplicaList list)
|
||||
{
|
||||
this.view = view;
|
||||
this.list = list;
|
||||
}
|
||||
|
||||
public final T get(int index)
|
||||
{
|
||||
return view.apply(list.get(index));
|
||||
}
|
||||
|
||||
public final int size()
|
||||
{
|
||||
return list.size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void forEach(Consumer<? super T> forEach)
|
||||
{
|
||||
list.forEach(view, forEach);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected final ReplicaList list;
|
||||
AbstractReplicaCollection(ReplicaList list)
|
||||
{
|
||||
|
|
@ -398,40 +478,30 @@ public abstract class AbstractReplicaCollection<C extends AbstractReplicaCollect
|
|||
return snapshot(subList);
|
||||
}
|
||||
|
||||
public final <T> List<T> asList(Function<Replica, T> viewTransform)
|
||||
public final <T> List<T> asList(Function<Replica, T> view)
|
||||
{
|
||||
return new AbstractList<T>()
|
||||
{
|
||||
public T get(int index)
|
||||
{
|
||||
return viewTransform.apply(list.get(index));
|
||||
}
|
||||
|
||||
public int size()
|
||||
{
|
||||
return list.size;
|
||||
}
|
||||
};
|
||||
return new AsList<>(view, list);
|
||||
}
|
||||
|
||||
/** see {@link ReplicaCollection#count(Predicate)}*/
|
||||
public int count(Predicate<Replica> predicate)
|
||||
public final int count(Predicate<? super Replica> test)
|
||||
{
|
||||
int count = 0;
|
||||
for (int i = 0 ; i < list.size() ; ++i)
|
||||
if (predicate.test(list.get(i)))
|
||||
++count;
|
||||
return count;
|
||||
return list.count(test);
|
||||
}
|
||||
|
||||
public final boolean anyMatch(Predicate<? super Replica> test)
|
||||
{
|
||||
return list.anyMatch(test);
|
||||
}
|
||||
|
||||
/** see {@link ReplicaCollection#filter(Predicate)}*/
|
||||
public final C filter(Predicate<Replica> predicate)
|
||||
public final C filter(Predicate<? super Replica> predicate)
|
||||
{
|
||||
return filter(predicate, Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
/** see {@link ReplicaCollection#filter(Predicate, int)}*/
|
||||
public final C filter(Predicate<Replica> predicate, int limit)
|
||||
public final C filter(Predicate<? super Replica> predicate, int limit)
|
||||
{
|
||||
if (isEmpty())
|
||||
return snapshot();
|
||||
|
|
@ -475,19 +545,19 @@ public abstract class AbstractReplicaCollection<C extends AbstractReplicaCollect
|
|||
}
|
||||
|
||||
/** see {@link ReplicaCollection#filterLazily(Predicate)}*/
|
||||
public final Iterable<Replica> filterLazily(Predicate<Replica> predicate)
|
||||
public final Iterable<Replica> filterLazily(Predicate<? super Replica> predicate)
|
||||
{
|
||||
return filterLazily(predicate, Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
/** see {@link ReplicaCollection#filterLazily(Predicate,int)}*/
|
||||
public final Iterable<Replica> filterLazily(Predicate<Replica> predicate, int limit)
|
||||
public final Iterable<Replica> filterLazily(Predicate<? super Replica> predicate, int limit)
|
||||
{
|
||||
return () -> list.filterIterator(predicate, limit);
|
||||
}
|
||||
|
||||
/** see {@link ReplicaCollection#sorted(Comparator)}*/
|
||||
public final C sorted(Comparator<Replica> comparator)
|
||||
public final C sorted(Comparator<? super Replica> comparator)
|
||||
{
|
||||
return snapshot(list.sorted(comparator));
|
||||
}
|
||||
|
|
@ -512,6 +582,11 @@ public abstract class AbstractReplicaCollection<C extends AbstractReplicaCollect
|
|||
return list.iterator();
|
||||
}
|
||||
|
||||
public final void forEach(Consumer<? super Replica> forEach)
|
||||
{
|
||||
list.forEach(forEach);
|
||||
}
|
||||
|
||||
public final Stream<Replica> stream() { return list.stream(); }
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import java.util.Collection;
|
|||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
|
|
@ -32,6 +33,7 @@ import org.slf4j.LoggerFactory;
|
|||
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.db.ConsistencyLevel;
|
||||
import org.apache.cassandra.db.Mutation;
|
||||
import org.apache.cassandra.db.WriteType;
|
||||
import org.apache.cassandra.dht.Range;
|
||||
import org.apache.cassandra.dht.RingPosition;
|
||||
|
|
@ -128,17 +130,20 @@ public abstract class AbstractReplicationStrategy
|
|||
*/
|
||||
public abstract EndpointsForRange calculateNaturalReplicas(Token searchToken, TokenMetadata tokenMetadata);
|
||||
|
||||
public <T> AbstractWriteResponseHandler<T> getWriteResponseHandler(ReplicaPlan.ForTokenWrite replicaPlan,
|
||||
public <T> AbstractWriteResponseHandler<T> getWriteResponseHandler(ReplicaPlan.ForWrite replicaPlan,
|
||||
Runnable callback,
|
||||
WriteType writeType,
|
||||
Supplier<Mutation> hintOnFailure,
|
||||
long queryStartNanoTime)
|
||||
{
|
||||
return getWriteResponseHandler(replicaPlan, callback, writeType, queryStartNanoTime, DatabaseDescriptor.getIdealConsistencyLevel());
|
||||
return getWriteResponseHandler(replicaPlan, callback, writeType, hintOnFailure,
|
||||
queryStartNanoTime, DatabaseDescriptor.getIdealConsistencyLevel());
|
||||
}
|
||||
|
||||
public <T> AbstractWriteResponseHandler<T> getWriteResponseHandler(ReplicaPlan.ForTokenWrite replicaPlan,
|
||||
public <T> AbstractWriteResponseHandler<T> getWriteResponseHandler(ReplicaPlan.ForWrite replicaPlan,
|
||||
Runnable callback,
|
||||
WriteType writeType,
|
||||
Supplier<Mutation> hintOnFailure,
|
||||
long queryStartNanoTime,
|
||||
ConsistencyLevel idealConsistencyLevel)
|
||||
{
|
||||
|
|
@ -146,15 +151,15 @@ public abstract class AbstractReplicationStrategy
|
|||
if (replicaPlan.consistencyLevel().isDatacenterLocal())
|
||||
{
|
||||
// block for in this context will be localnodes block.
|
||||
resultResponseHandler = new DatacenterWriteResponseHandler<T>(replicaPlan, callback, writeType, queryStartNanoTime);
|
||||
resultResponseHandler = new DatacenterWriteResponseHandler<T>(replicaPlan, callback, writeType, hintOnFailure, queryStartNanoTime);
|
||||
}
|
||||
else if (replicaPlan.consistencyLevel() == ConsistencyLevel.EACH_QUORUM && (this instanceof NetworkTopologyStrategy))
|
||||
{
|
||||
resultResponseHandler = new DatacenterSyncWriteResponseHandler<T>(replicaPlan, callback, writeType, queryStartNanoTime);
|
||||
resultResponseHandler = new DatacenterSyncWriteResponseHandler<T>(replicaPlan, callback, writeType, hintOnFailure, queryStartNanoTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
resultResponseHandler = new WriteResponseHandler<T>(replicaPlan, callback, writeType, queryStartNanoTime);
|
||||
resultResponseHandler = new WriteResponseHandler<T>(replicaPlan, callback, writeType, hintOnFailure, queryStartNanoTime);
|
||||
}
|
||||
|
||||
//Check if tracking the ideal consistency level is configured
|
||||
|
|
@ -173,6 +178,7 @@ public abstract class AbstractReplicationStrategy
|
|||
AbstractWriteResponseHandler<T> idealHandler = getWriteResponseHandler(replicaPlan.withConsistencyLevel(idealConsistencyLevel),
|
||||
callback,
|
||||
writeType,
|
||||
hintOnFailure,
|
||||
queryStartNanoTime,
|
||||
idealConsistencyLevel);
|
||||
resultResponseHandler.setIdealCLResponseHandler(idealHandler);
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ package org.apache.cassandra.locator;
|
|||
import org.apache.cassandra.locator.ReplicaCollection.Builder.Conflict;
|
||||
import org.apache.cassandra.utils.FBUtilities;
|
||||
|
||||
import java.util.AbstractList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
|
@ -54,20 +53,14 @@ public abstract class Endpoints<E extends Endpoints<E>> extends AbstractReplicaC
|
|||
return byEndpoint().keySet();
|
||||
}
|
||||
|
||||
public InetAddressAndPort endpoint(int i)
|
||||
{
|
||||
return get(i).endpoint();
|
||||
}
|
||||
|
||||
public List<InetAddressAndPort> endpointList()
|
||||
{
|
||||
return new AbstractList<InetAddressAndPort>()
|
||||
{
|
||||
public InetAddressAndPort get(int index)
|
||||
{
|
||||
return list.get(index).endpoint();
|
||||
}
|
||||
|
||||
public int size()
|
||||
{
|
||||
return list.size;
|
||||
}
|
||||
};
|
||||
return asList(Replica::endpoint);
|
||||
}
|
||||
|
||||
public Map<InetAddressAndPort, Replica> byEndpoint()
|
||||
|
|
@ -87,6 +80,11 @@ public abstract class Endpoints<E extends Endpoints<E>> extends AbstractReplicaC
|
|||
replica);
|
||||
}
|
||||
|
||||
public boolean contains(InetAddressAndPort endpoint)
|
||||
{
|
||||
return endpoint != null && byEndpoint().containsKey(endpoint);
|
||||
}
|
||||
|
||||
public E withoutSelf()
|
||||
{
|
||||
InetAddressAndPort self = FBUtilities.getBroadcastAddressAndPort();
|
||||
|
|
|
|||
|
|
@ -19,7 +19,11 @@
|
|||
package org.apache.cassandra.locator;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
import org.apache.cassandra.db.Keyspace;
|
||||
import org.apache.cassandra.dht.Token;
|
||||
import org.apache.cassandra.schema.TableMetadata;
|
||||
import org.apache.cassandra.service.StorageService;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
|
@ -67,6 +71,11 @@ public class EndpointsForToken extends Endpoints<EndpointsForToken>
|
|||
return new EndpointsForToken(token, newList, byEndpoint);
|
||||
}
|
||||
|
||||
public Replica lookup(InetAddressAndPort endpoint)
|
||||
{
|
||||
return byEndpoint().get(endpoint);
|
||||
}
|
||||
|
||||
public static class Builder extends EndpointsForToken implements ReplicaCollection.Builder<EndpointsForToken>
|
||||
{
|
||||
boolean built;
|
||||
|
|
@ -146,4 +155,34 @@ public class EndpointsForToken extends Endpoints<EndpointsForToken>
|
|||
if (replicas.isEmpty()) return empty(token);
|
||||
return builder(token, replicas.size()).addAll(replicas).build();
|
||||
}
|
||||
|
||||
public static EndpointsForToken natural(Keyspace keyspace, Token token)
|
||||
{
|
||||
return keyspace.getReplicationStrategy().getNaturalReplicasForToken(token);
|
||||
}
|
||||
|
||||
public static EndpointsForToken natural(AbstractReplicationStrategy replicationStrategy, Token token)
|
||||
{
|
||||
return replicationStrategy.getNaturalReplicasForToken(token);
|
||||
}
|
||||
|
||||
public static EndpointsForToken natural(TableMetadata table, Token token)
|
||||
{
|
||||
return natural(Keyspace.open(table.keyspace), token);
|
||||
}
|
||||
|
||||
public static EndpointsForToken pending(TableMetadata table, Token token)
|
||||
{
|
||||
return pending(table.keyspace, token);
|
||||
}
|
||||
|
||||
public static EndpointsForToken pending(Keyspace keyspace, Token token)
|
||||
{
|
||||
return pending(keyspace.getName(), token);
|
||||
}
|
||||
|
||||
public static EndpointsForToken pending(String keyspace, Token token)
|
||||
{
|
||||
return StorageService.instance.getTokenMetadata().pendingEndpointsForToken(token, keyspace);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,11 +18,12 @@
|
|||
|
||||
package org.apache.cassandra.locator;
|
||||
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public class InOurDcTester
|
||||
import static org.apache.cassandra.config.DatabaseDescriptor.getEndpointSnitch;
|
||||
import static org.apache.cassandra.config.DatabaseDescriptor.getLocalDataCenter;
|
||||
|
||||
public class InOurDc
|
||||
{
|
||||
private static ReplicaTester replicas;
|
||||
private static EndpointTester endpoints;
|
||||
|
|
@ -30,7 +31,7 @@ public class InOurDcTester
|
|||
final String dc;
|
||||
final IEndpointSnitch snitch;
|
||||
|
||||
private InOurDcTester(String dc, IEndpointSnitch snitch)
|
||||
private InOurDc(String dc, IEndpointSnitch snitch)
|
||||
{
|
||||
this.dc = dc;
|
||||
this.snitch = snitch;
|
||||
|
|
@ -38,15 +39,15 @@ public class InOurDcTester
|
|||
|
||||
boolean stale()
|
||||
{
|
||||
return dc != DatabaseDescriptor.getLocalDataCenter()
|
||||
|| snitch != DatabaseDescriptor.getEndpointSnitch()
|
||||
return dc != getLocalDataCenter()
|
||||
|| snitch != getEndpointSnitch()
|
||||
// this final clause checks if somehow the snitch/localDc have got out of whack;
|
||||
// presently, this is possible but very unlikely, but this check will also help
|
||||
// resolve races on these global fields as well
|
||||
|| !dc.equals(snitch.getLocalDatacenter());
|
||||
}
|
||||
|
||||
private static final class ReplicaTester extends InOurDcTester implements Predicate<Replica>
|
||||
private static final class ReplicaTester extends InOurDc implements Predicate<Replica>
|
||||
{
|
||||
private ReplicaTester(String dc, IEndpointSnitch snitch)
|
||||
{
|
||||
|
|
@ -60,7 +61,7 @@ public class InOurDcTester
|
|||
}
|
||||
}
|
||||
|
||||
private static final class EndpointTester extends InOurDcTester implements Predicate<InetAddressAndPort>
|
||||
private static final class EndpointTester extends InOurDc implements Predicate<InetAddressAndPort>
|
||||
{
|
||||
private EndpointTester(String dc, IEndpointSnitch snitch)
|
||||
{
|
||||
|
|
@ -78,7 +79,7 @@ public class InOurDcTester
|
|||
{
|
||||
ReplicaTester cur = replicas;
|
||||
if (cur == null || cur.stale())
|
||||
replicas = cur = new ReplicaTester(DatabaseDescriptor.getLocalDataCenter(), DatabaseDescriptor.getEndpointSnitch());
|
||||
replicas = cur = new ReplicaTester(getLocalDataCenter(), getEndpointSnitch());
|
||||
return cur;
|
||||
}
|
||||
|
||||
|
|
@ -86,8 +87,18 @@ public class InOurDcTester
|
|||
{
|
||||
EndpointTester cur = endpoints;
|
||||
if (cur == null || cur.stale())
|
||||
endpoints = cur = new EndpointTester(DatabaseDescriptor.getLocalDataCenter(), DatabaseDescriptor.getEndpointSnitch());
|
||||
endpoints = cur = new EndpointTester(getLocalDataCenter(), getEndpointSnitch());
|
||||
return cur;
|
||||
}
|
||||
|
||||
public static boolean isInOurDc(Replica replica)
|
||||
{
|
||||
return replicas().test(replica);
|
||||
}
|
||||
|
||||
public static boolean isInOurDc(InetAddressAndPort endpoint)
|
||||
{
|
||||
return endpoints().test(endpoint);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -62,14 +62,14 @@ public interface ReplicaCollection<C extends ReplicaCollection<C>> extends Itera
|
|||
/**
|
||||
* @return the number of replicas that match the predicate
|
||||
*/
|
||||
public abstract int count(Predicate<Replica> predicate);
|
||||
public abstract int count(Predicate<? super Replica> predicate);
|
||||
|
||||
/**
|
||||
* @return a *eagerly constructed* copy of this collection containing the Replica that match the provided predicate.
|
||||
* An effort will be made to either return ourself, or a subList, where possible.
|
||||
* It is guaranteed that no changes to any upstream Builder will affect the state of the result.
|
||||
*/
|
||||
public abstract C filter(Predicate<Replica> predicate);
|
||||
public abstract C filter(Predicate<? super Replica> predicate);
|
||||
|
||||
/**
|
||||
* @return a *eagerly constructed* copy of this collection containing the Replica that match the provided predicate.
|
||||
|
|
@ -77,18 +77,18 @@ public interface ReplicaCollection<C extends ReplicaCollection<C>> extends Itera
|
|||
* It is guaranteed that no changes to any upstream Builder will affect the state of the result.
|
||||
* Only the first maxSize items will be returned.
|
||||
*/
|
||||
public abstract C filter(Predicate<Replica> predicate, int maxSize);
|
||||
public abstract C filter(Predicate<? super Replica> predicate, int maxSize);
|
||||
|
||||
/**
|
||||
* @return a *lazily constructed* Iterable over this collection, containing the Replica that match the provided predicate.
|
||||
*/
|
||||
public abstract Iterable<Replica> filterLazily(Predicate<Replica> predicate);
|
||||
public abstract Iterable<Replica> filterLazily(Predicate<? super Replica> predicate);
|
||||
|
||||
/**
|
||||
* @return a *lazily constructed* Iterable over this collection, containing the Replica that match the provided predicate.
|
||||
* Only the first maxSize matching items will be returned.
|
||||
*/
|
||||
public abstract Iterable<Replica> filterLazily(Predicate<Replica> predicate, int maxSize);
|
||||
public abstract Iterable<Replica> filterLazily(Predicate<? super Replica> predicate, int maxSize);
|
||||
|
||||
/**
|
||||
* @return an *eagerly constructed* copy of this collection containing the Replica at positions [start..end);
|
||||
|
|
@ -101,7 +101,7 @@ public interface ReplicaCollection<C extends ReplicaCollection<C>> extends Itera
|
|||
* @return an *eagerly constructed* copy of this collection containing the Replica re-ordered according to this comparator
|
||||
* It is guaranteed that no changes to any upstream Builder will affect the state of the result.
|
||||
*/
|
||||
public abstract C sorted(Comparator<Replica> comparator);
|
||||
public abstract C sorted(Comparator<? super Replica> comparator);
|
||||
|
||||
public abstract Iterator<Replica> iterator();
|
||||
public abstract Stream<Replica> stream();
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ import org.apache.cassandra.db.PartitionPosition;
|
|||
import org.apache.cassandra.dht.AbstractBounds;
|
||||
import org.apache.cassandra.dht.Token;
|
||||
import org.apache.cassandra.gms.FailureDetector;
|
||||
import org.apache.cassandra.service.StorageService;
|
||||
import org.apache.cassandra.utils.FBUtilities;
|
||||
|
||||
import java.util.Set;
|
||||
|
|
@ -169,13 +168,14 @@ public abstract class ReplicaLayout<E extends Endpoints<E>>
|
|||
@Override
|
||||
public Token token() { return natural().token(); }
|
||||
|
||||
public ReplicaLayout.ForTokenWrite filter(Predicate<Replica> filter)
|
||||
public ForTokenWrite filter(Predicate<Replica> filter)
|
||||
{
|
||||
EndpointsForToken filtered = all().filter(filter);
|
||||
// AbstractReplicaCollection.filter returns itself if all elements match the filter
|
||||
if (filtered == all()) return this;
|
||||
if (pending().isEmpty()) return new ForTokenWrite(replicationStrategy(), filtered, pending(), filtered);
|
||||
// unique by endpoint, so can for efficiency filter only on endpoint
|
||||
return new ReplicaLayout.ForTokenWrite(
|
||||
return new ForTokenWrite(
|
||||
replicationStrategy(),
|
||||
natural().keep(filtered.endpoints()),
|
||||
pending().keep(filtered.endpoints()),
|
||||
|
|
@ -206,8 +206,8 @@ public abstract class ReplicaLayout<E extends Endpoints<E>>
|
|||
// TODO: these should be cached, not the natural replicas
|
||||
// TODO: race condition to fetch these. implications??
|
||||
AbstractReplicationStrategy replicationStrategy = keyspace.getReplicationStrategy();
|
||||
EndpointsForToken natural = replicationStrategy.getNaturalReplicasForToken(token);
|
||||
EndpointsForToken pending = StorageService.instance.getTokenMetadata().pendingEndpointsForToken(token, keyspace.getName());
|
||||
EndpointsForToken natural = EndpointsForToken.natural(replicationStrategy, token);
|
||||
EndpointsForToken pending = EndpointsForToken.pending(keyspace, token);
|
||||
return forTokenWrite(replicationStrategy, natural, pending);
|
||||
}
|
||||
|
||||
|
|
@ -325,7 +325,7 @@ public abstract class ReplicaLayout<E extends Endpoints<E>>
|
|||
* @return the read layout for a token - this includes only live natural replicas, i.e. those that are not pending
|
||||
* and not marked down by the failure detector. these are reverse sorted by the badness score of the configured snitch
|
||||
*/
|
||||
static ReplicaLayout.ForTokenRead forTokenReadLiveSorted(AbstractReplicationStrategy replicationStrategy, Token token)
|
||||
public static ReplicaLayout.ForTokenRead forTokenReadLiveSorted(AbstractReplicationStrategy replicationStrategy, Token token)
|
||||
{
|
||||
EndpointsForToken replicas = replicationStrategy.getNaturalReplicasForToken(token);
|
||||
replicas = DatabaseDescriptor.getEndpointSnitch().sortedByProximity(FBUtilities.getBroadcastAddressAndPort(), replicas);
|
||||
|
|
|
|||
|
|
@ -25,69 +25,89 @@ import org.apache.cassandra.db.PartitionPosition;
|
|||
import org.apache.cassandra.dht.AbstractBounds;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public abstract class ReplicaPlan<E extends Endpoints<E>>
|
||||
public interface ReplicaPlan<E extends Endpoints<E>, P extends ReplicaPlan<E, P>>
|
||||
{
|
||||
protected final Keyspace keyspace;
|
||||
protected final ConsistencyLevel consistencyLevel;
|
||||
// The snapshot of the replication strategy when instantiating.
|
||||
// It could be different than the one fetched from Keyspace later, e.g. RS altered during the query.
|
||||
// Use the snapshot to calculate {@code blockFor} in order to have a consistent view of RS for the query.
|
||||
protected final AbstractReplicationStrategy replicationStrategy;
|
||||
Keyspace keyspace();
|
||||
AbstractReplicationStrategy replicationStrategy();
|
||||
ConsistencyLevel consistencyLevel();
|
||||
|
||||
// all nodes we will contact via any mechanism, including hints
|
||||
// i.e., for:
|
||||
// - reads, only live natural replicas
|
||||
// ==> live.natural().subList(0, blockFor + initial speculate)
|
||||
// - writes, includes all full, and any pending replicas, (and only any necessary transient ones to make up the difference)
|
||||
// ==> liveAndDown.natural().filter(isFull) ++ liveAndDown.pending() ++ live.natural.filter(isTransient, req)
|
||||
// - paxos, includes all live replicas (natural+pending), for this DC if SERIAL_LOCAL
|
||||
// ==> live.all() (if consistencyLevel.isDCLocal(), then .filter(consistencyLevel.isLocal))
|
||||
private final E contacts;
|
||||
E contacts();
|
||||
|
||||
ReplicaPlan(Keyspace keyspace, AbstractReplicationStrategy replicationStrategy, ConsistencyLevel consistencyLevel, E contacts)
|
||||
Replica lookup(InetAddressAndPort endpoint);
|
||||
P withContacts(E contacts);
|
||||
|
||||
interface ForRead<E extends Endpoints<E>, P extends ReplicaPlan.ForRead<E, P>> extends ReplicaPlan<E, P>
|
||||
{
|
||||
assert contacts != null;
|
||||
this.keyspace = keyspace;
|
||||
this.replicationStrategy = replicationStrategy;
|
||||
this.consistencyLevel = consistencyLevel;
|
||||
this.contacts = contacts;
|
||||
int readQuorum();
|
||||
E readCandidates();
|
||||
|
||||
default Replica firstUncontactedCandidate(Predicate<Replica> extraPredicate)
|
||||
{
|
||||
return Iterables.tryFind(readCandidates(), r -> extraPredicate.test(r) && !contacts().contains(r)).orNull();
|
||||
}
|
||||
}
|
||||
|
||||
public abstract int blockFor();
|
||||
abstract class AbstractReplicaPlan<E extends Endpoints<E>, P extends ReplicaPlan<E, P>> implements ReplicaPlan<E, P>
|
||||
{
|
||||
protected final Keyspace keyspace;
|
||||
protected final ConsistencyLevel consistencyLevel;
|
||||
// The snapshot of the replication strategy when instantiating.
|
||||
// It could be different than the one fetched from Keyspace later, e.g. RS altered during the query.
|
||||
// Use the snapshot to calculate {@code blockFor} in order to have a consistent view of RS for the query.
|
||||
protected final AbstractReplicationStrategy replicationStrategy;
|
||||
|
||||
public E contacts() { return contacts; }
|
||||
// all nodes we will contact via any mechanism, including hints
|
||||
// i.e., for:
|
||||
// - reads, only live natural replicas
|
||||
// ==> live.natural().subList(0, blockFor + initial speculate)
|
||||
// - writes, includes all full, and any pending replicas, (and only any necessary transient ones to make up the difference)
|
||||
// ==> liveAndDown.natural().filter(isFull) ++ liveAndDown.pending() ++ live.natural.filter(isTransient, req)
|
||||
// - paxos, includes all live replicas (natural+pending), for this DC if SERIAL_LOCAL
|
||||
// ==> live.all() (if consistencyLevel.isDCLocal(), then .filter(consistencyLevel.isLocal))
|
||||
private final E contacts;
|
||||
|
||||
// TODO: should this semantically return true if we contain the endpoint, not the exact replica?
|
||||
public boolean contacts(Replica replica) { return contacts.contains(replica); }
|
||||
public Keyspace keyspace() { return keyspace; }
|
||||
public AbstractReplicationStrategy replicationStrategy() { return replicationStrategy; }
|
||||
public ConsistencyLevel consistencyLevel() { return consistencyLevel; }
|
||||
AbstractReplicaPlan(Keyspace keyspace, AbstractReplicationStrategy replicationStrategy, ConsistencyLevel consistencyLevel, E contacts)
|
||||
{
|
||||
assert contacts != null;
|
||||
this.keyspace = keyspace;
|
||||
this.replicationStrategy = replicationStrategy;
|
||||
this.consistencyLevel = consistencyLevel;
|
||||
this.contacts = contacts;
|
||||
}
|
||||
|
||||
public static abstract class ForRead<E extends Endpoints<E>> extends ReplicaPlan<E>
|
||||
public E contacts() { return contacts; }
|
||||
|
||||
public Keyspace keyspace() { return keyspace; }
|
||||
public AbstractReplicationStrategy replicationStrategy() { return replicationStrategy; }
|
||||
public ConsistencyLevel consistencyLevel() { return consistencyLevel; }
|
||||
}
|
||||
|
||||
public static abstract class AbstractForRead<E extends Endpoints<E>, P extends ForRead<E, P>> extends AbstractReplicaPlan<E, P> implements ForRead<E, P>
|
||||
{
|
||||
// all nodes we *could* contacts; typically all natural replicas that are believed to be alive
|
||||
// we will consult this collection to find uncontacted nodes we might contact if we doubt we will meet consistency level
|
||||
private final E candidates;
|
||||
final E candidates;
|
||||
|
||||
ForRead(Keyspace keyspace, AbstractReplicationStrategy replicationStrategy, ConsistencyLevel consistencyLevel, E candidates, E contacts)
|
||||
AbstractForRead(Keyspace keyspace, AbstractReplicationStrategy replicationStrategy, ConsistencyLevel consistencyLevel, E candidates, E contacts)
|
||||
{
|
||||
super(keyspace, replicationStrategy, consistencyLevel, contacts);
|
||||
this.candidates = candidates;
|
||||
}
|
||||
|
||||
public int blockFor() { return consistencyLevel.blockFor(replicationStrategy); }
|
||||
public int readQuorum() { return consistencyLevel.blockFor(replicationStrategy); }
|
||||
|
||||
public E candidates() { return candidates; }
|
||||
public E readCandidates() { return candidates; }
|
||||
|
||||
public Replica firstUncontactedCandidate(Predicate<Replica> extraPredicate)
|
||||
{
|
||||
return Iterables.tryFind(candidates(), r -> extraPredicate.test(r) && !contacts(r)).orNull();
|
||||
return Iterables.tryFind(readCandidates(), r -> extraPredicate.test(r) && !contacts().contains(r)).orNull();
|
||||
}
|
||||
|
||||
public Replica lookup(InetAddressAndPort endpoint)
|
||||
{
|
||||
return candidates().byEndpoint().get(endpoint);
|
||||
return readCandidates().byEndpoint().get(endpoint);
|
||||
}
|
||||
|
||||
public String toString()
|
||||
|
|
@ -96,7 +116,7 @@ public abstract class ReplicaPlan<E extends Endpoints<E>>
|
|||
}
|
||||
}
|
||||
|
||||
public static class ForTokenRead extends ForRead<EndpointsForToken>
|
||||
public static class ForTokenRead extends AbstractForRead<EndpointsForToken, ForTokenRead>
|
||||
{
|
||||
public ForTokenRead(Keyspace keyspace,
|
||||
AbstractReplicationStrategy replicationStrategy,
|
||||
|
|
@ -107,13 +127,13 @@ public abstract class ReplicaPlan<E extends Endpoints<E>>
|
|||
super(keyspace, replicationStrategy, consistencyLevel, candidates, contacts);
|
||||
}
|
||||
|
||||
ForTokenRead withContact(EndpointsForToken newContact)
|
||||
public ForTokenRead withContacts(EndpointsForToken newContact)
|
||||
{
|
||||
return new ForTokenRead(keyspace, replicationStrategy, consistencyLevel, candidates(), newContact);
|
||||
return new ForTokenRead(keyspace, replicationStrategy, consistencyLevel, candidates, newContact);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ForRangeRead extends ForRead<EndpointsForRange>
|
||||
public static class ForRangeRead extends AbstractForRead<EndpointsForRange, ForRangeRead>
|
||||
{
|
||||
final AbstractBounds<PartitionPosition> range;
|
||||
final int vnodeCount;
|
||||
|
|
@ -138,20 +158,20 @@ public abstract class ReplicaPlan<E extends Endpoints<E>>
|
|||
*/
|
||||
public int vnodeCount() { return vnodeCount; }
|
||||
|
||||
ForRangeRead withContact(EndpointsForRange newContact)
|
||||
public ForRangeRead withContacts(EndpointsForRange newContact)
|
||||
{
|
||||
return new ForRangeRead(keyspace, replicationStrategy, consistencyLevel, range, candidates(), newContact, vnodeCount);
|
||||
return new ForRangeRead(keyspace, replicationStrategy, consistencyLevel, range, readCandidates(), newContact, vnodeCount);
|
||||
}
|
||||
}
|
||||
|
||||
public static abstract class ForWrite<E extends Endpoints<E>> extends ReplicaPlan<E>
|
||||
public static class ForWrite extends AbstractReplicaPlan<EndpointsForToken, ForWrite>
|
||||
{
|
||||
// TODO: this is only needed because of poor isolation of concerns elsewhere - we can remove it soon, and will do so in a follow-up patch
|
||||
final E pending;
|
||||
final E liveAndDown;
|
||||
final E live;
|
||||
final EndpointsForToken pending;
|
||||
final EndpointsForToken liveAndDown;
|
||||
final EndpointsForToken live;
|
||||
|
||||
ForWrite(Keyspace keyspace, AbstractReplicationStrategy replicationStrategy, ConsistencyLevel consistencyLevel, E pending, E liveAndDown, E live, E contact)
|
||||
public ForWrite(Keyspace keyspace, AbstractReplicationStrategy replicationStrategy, ConsistencyLevel consistencyLevel, EndpointsForToken pending, EndpointsForToken liveAndDown, EndpointsForToken live, EndpointsForToken contact)
|
||||
{
|
||||
super(keyspace, replicationStrategy, consistencyLevel, contact);
|
||||
this.pending = pending;
|
||||
|
|
@ -159,46 +179,43 @@ public abstract class ReplicaPlan<E extends Endpoints<E>>
|
|||
this.live = live;
|
||||
}
|
||||
|
||||
public int blockFor() { return consistencyLevel.blockForWrite(replicationStrategy, pending()); }
|
||||
public int writeQuorum() { return consistencyLevel.blockForWrite(replicationStrategy, pending()); }
|
||||
|
||||
/** Replicas that a region of the ring is moving to; not yet ready to serve reads, but should receive writes */
|
||||
public E pending() { return pending; }
|
||||
public EndpointsForToken pending() { return pending; }
|
||||
|
||||
/** Replicas that can participate in the write - this always includes all nodes (pending and natural) in all DCs, except for paxos LOCAL_QUORUM (which is local DC only) */
|
||||
public E liveAndDown() { return liveAndDown; }
|
||||
public EndpointsForToken liveAndDown() { return liveAndDown; }
|
||||
|
||||
/** The live replicas present in liveAndDown, usually derived from FailureDetector.isReplicaAlive */
|
||||
public E live() { return live; }
|
||||
public EndpointsForToken live() { return live; }
|
||||
|
||||
/** Calculate which live endpoints we could have contacted, but chose not to */
|
||||
public E liveUncontacted() { return live().filter(r -> !contacts(r)); }
|
||||
public EndpointsForToken liveUncontacted() { return live().filter(r -> !contacts().contains(r)); }
|
||||
|
||||
/** Test liveness, consistent with the upfront analysis done for this operation (i.e. test membership of live()) */
|
||||
public boolean isAlive(Replica replica) { return live.endpoints().contains(replica.endpoint()); }
|
||||
|
||||
public Replica lookup(InetAddressAndPort endpoint)
|
||||
{
|
||||
return liveAndDown().byEndpoint().get(endpoint);
|
||||
}
|
||||
|
||||
private ForWrite copy(ConsistencyLevel newConsistencyLevel, EndpointsForToken newContact)
|
||||
{
|
||||
return new ForWrite(keyspace, replicationStrategy, newConsistencyLevel, pending(), liveAndDown(), live(), newContact);
|
||||
}
|
||||
|
||||
ForWrite withConsistencyLevel(ConsistencyLevel newConsistencylevel) { return copy(newConsistencylevel, contacts()); }
|
||||
public ForWrite withContacts(EndpointsForToken newContact) { return copy(consistencyLevel, newContact); }
|
||||
|
||||
public String toString()
|
||||
{
|
||||
return "ReplicaPlan.ForWrite [ CL: " + consistencyLevel + " keyspace: " + keyspace + " liveAndDown: " + liveAndDown + " live: " + live + " contacts: " + contacts() + " ]";
|
||||
}
|
||||
}
|
||||
|
||||
public static class ForTokenWrite extends ForWrite<EndpointsForToken>
|
||||
{
|
||||
public ForTokenWrite(Keyspace keyspace, AbstractReplicationStrategy replicationStrategy, ConsistencyLevel consistencyLevel, EndpointsForToken pending, EndpointsForToken liveAndDown, EndpointsForToken live, EndpointsForToken contact)
|
||||
{
|
||||
super(keyspace, replicationStrategy, consistencyLevel, pending, liveAndDown, live, contact);
|
||||
}
|
||||
|
||||
private ReplicaPlan.ForTokenWrite copy(ConsistencyLevel newConsistencyLevel, EndpointsForToken newContact)
|
||||
{
|
||||
return new ReplicaPlan.ForTokenWrite(keyspace, replicationStrategy, newConsistencyLevel, pending(), liveAndDown(), live(), newContact);
|
||||
}
|
||||
|
||||
ForTokenWrite withConsistencyLevel(ConsistencyLevel newConsistencylevel) { return copy(newConsistencylevel, contacts()); }
|
||||
public ForTokenWrite withContact(EndpointsForToken newContact) { return copy(consistencyLevel, newContact); }
|
||||
}
|
||||
|
||||
public static class ForPaxosWrite extends ForWrite<EndpointsForToken>
|
||||
public static class ForPaxosWrite extends ForWrite
|
||||
{
|
||||
final int requiredParticipants;
|
||||
|
||||
|
|
@ -219,8 +236,11 @@ public abstract class ReplicaPlan<E extends Endpoints<E>>
|
|||
* the constructor should be visible by the normal process of sharing data between threads (i.e. executors, etc)
|
||||
* and any updates will either be seen or not seen, perhaps not promptly, but certainly not incompletely.
|
||||
* The contained ReplicaPlan has only final member properties, so it cannot be seen partially initialised.
|
||||
*
|
||||
* TODO: there's no reason this couldn't be achieved instead by a ReplicaPlan with mutable contacts,
|
||||
* simplifying the hierarchy
|
||||
*/
|
||||
public interface Shared<E extends Endpoints<E>, P extends ReplicaPlan<E>>
|
||||
public interface Shared<E extends Endpoints<E>, P extends ReplicaPlan<E, P>> extends Supplier<P>
|
||||
{
|
||||
/**
|
||||
* add the provided replica to this shared plan, by updating the internal reference
|
||||
|
|
@ -230,29 +250,22 @@ public abstract class ReplicaPlan<E extends Endpoints<E>>
|
|||
* get the shared replica plan, non-volatile (so maybe stale) but no risk of partially initialised
|
||||
*/
|
||||
public P get();
|
||||
/**
|
||||
* get the shared replica plan, non-volatile (so maybe stale) but no risk of partially initialised,
|
||||
* but replace its 'contacts' with those provided
|
||||
*/
|
||||
public abstract P getWithContacts(E endpoints);
|
||||
}
|
||||
|
||||
public static class SharedForTokenRead implements Shared<EndpointsForToken, ForTokenRead>
|
||||
{
|
||||
private ForTokenRead replicaPlan;
|
||||
SharedForTokenRead(ForTokenRead replicaPlan) { this.replicaPlan = replicaPlan; }
|
||||
public void addToContacts(Replica replica) { replicaPlan = replicaPlan.withContact(Endpoints.append(replicaPlan.contacts(), replica)); }
|
||||
public void addToContacts(Replica replica) { replicaPlan = replicaPlan.withContacts(Endpoints.append(replicaPlan.contacts(), replica)); }
|
||||
public ForTokenRead get() { return replicaPlan; }
|
||||
public ForTokenRead getWithContacts(EndpointsForToken newContact) { return replicaPlan.withContact(newContact); }
|
||||
}
|
||||
|
||||
public static class SharedForRangeRead implements Shared<EndpointsForRange, ForRangeRead>
|
||||
{
|
||||
private ForRangeRead replicaPlan;
|
||||
SharedForRangeRead(ForRangeRead replicaPlan) { this.replicaPlan = replicaPlan; }
|
||||
public void addToContacts(Replica replica) { replicaPlan = replicaPlan.withContact(Endpoints.append(replicaPlan.contacts(), replica)); }
|
||||
public void addToContacts(Replica replica) { replicaPlan = replicaPlan.withContacts(Endpoints.append(replicaPlan.contacts(), replica)); }
|
||||
public ForRangeRead get() { return replicaPlan; }
|
||||
public ForRangeRead getWithContacts(EndpointsForRange newContact) { return replicaPlan.withContact(newContact); }
|
||||
}
|
||||
|
||||
public static SharedForTokenRead shared(ForTokenRead replicaPlan) { return new SharedForTokenRead(replicaPlan); }
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ public class ReplicaPlans
|
|||
if (logger.isTraceEnabled())
|
||||
{
|
||||
logger.trace(String.format("Local replicas %s are insufficient to satisfy LOCAL_QUORUM requirement of %d live replicas and %d full replicas in '%s'",
|
||||
allLive.filter(InOurDcTester.replicas()), blockFor, blockForFullReplicas, DatabaseDescriptor.getLocalDataCenter()));
|
||||
allLive.filter(InOurDc.replicas()), blockFor, blockForFullReplicas, DatabaseDescriptor.getLocalDataCenter()));
|
||||
}
|
||||
throw UnavailableException.create(consistencyLevel, blockFor, blockForFullReplicas, localLive.allReplicas(), localLive.fullReplicas());
|
||||
}
|
||||
|
|
@ -175,23 +175,23 @@ public class ReplicaPlans
|
|||
/**
|
||||
* Construct a ReplicaPlan for writing to exactly one node, with CL.ONE. This node is *assumed* to be alive.
|
||||
*/
|
||||
public static ReplicaPlan.ForTokenWrite forSingleReplicaWrite(Keyspace keyspace, Token token, Replica replica)
|
||||
public static ReplicaPlan.ForWrite forSingleReplicaWrite(Keyspace keyspace, Token token, Replica replica)
|
||||
{
|
||||
EndpointsForToken one = EndpointsForToken.of(token, replica);
|
||||
EndpointsForToken empty = EndpointsForToken.empty(token);
|
||||
return new ReplicaPlan.ForTokenWrite(keyspace, keyspace.getReplicationStrategy(), ConsistencyLevel.ONE, empty, one, one, one);
|
||||
return new ReplicaPlan.ForWrite(keyspace, keyspace.getReplicationStrategy(), ConsistencyLevel.ONE, empty, one, one, one);
|
||||
}
|
||||
|
||||
/**
|
||||
* A forwarding counter write is always sent to a single owning coordinator for the range, by the original coordinator
|
||||
* (if it is not itself an owner)
|
||||
*/
|
||||
public static ReplicaPlan.ForTokenWrite forForwardingCounterWrite(Keyspace keyspace, Token token, Replica replica)
|
||||
public static ReplicaPlan.ForWrite forForwardingCounterWrite(Keyspace keyspace, Token token, Replica replica)
|
||||
{
|
||||
return forSingleReplicaWrite(keyspace, token, replica);
|
||||
}
|
||||
|
||||
public static ReplicaPlan.ForTokenWrite forLocalBatchlogWrite()
|
||||
public static ReplicaPlan.ForWrite forLocalBatchlogWrite()
|
||||
{
|
||||
Token token = DatabaseDescriptor.getPartitioner().getMinimumToken();
|
||||
Keyspace systemKeypsace = Keyspace.open(SchemaConstants.SYSTEM_KEYSPACE_NAME);
|
||||
|
|
@ -211,7 +211,7 @@ public class ReplicaPlans
|
|||
*
|
||||
* @param isAny if batch consistency level is ANY, in which case a local node will be picked
|
||||
*/
|
||||
public static ReplicaPlan.ForTokenWrite forBatchlogWrite(boolean isAny) throws UnavailableException
|
||||
public static ReplicaPlan.ForWrite forBatchlogWrite(boolean isAny) throws UnavailableException
|
||||
{
|
||||
// A single case we write not for range or token, but multiple mutations to many tokens
|
||||
Token token = DatabaseDescriptor.getPartitioner().getMinimumToken();
|
||||
|
|
@ -317,41 +317,41 @@ public class ReplicaPlans
|
|||
return result;
|
||||
}
|
||||
|
||||
public static ReplicaPlan.ForTokenWrite forReadRepair(Token token, ReplicaPlan.ForRead<?> readPlan) throws UnavailableException
|
||||
public static ReplicaPlan.ForWrite forReadRepair(Token token, ReplicaPlan<?, ?> readPlan) throws UnavailableException
|
||||
{
|
||||
return forWrite(readPlan.keyspace, readPlan.consistencyLevel, token, writeReadRepair(readPlan));
|
||||
return forWrite(readPlan.keyspace(), readPlan.consistencyLevel(), token, writeReadRepair(readPlan));
|
||||
}
|
||||
|
||||
public static ReplicaPlan.ForTokenWrite forWrite(Keyspace keyspace, ConsistencyLevel consistencyLevel, Token token, Selector selector) throws UnavailableException
|
||||
public static ReplicaPlan.ForWrite forWrite(Keyspace keyspace, ConsistencyLevel consistencyLevel, Token token, Selector selector) throws UnavailableException
|
||||
{
|
||||
return forWrite(keyspace, consistencyLevel, ReplicaLayout.forTokenWriteLiveAndDown(keyspace, token), selector);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public static ReplicaPlan.ForTokenWrite forWrite(Keyspace keyspace, ConsistencyLevel consistencyLevel, EndpointsForToken natural, EndpointsForToken pending, Predicate<Replica> isAlive, Selector selector) throws UnavailableException
|
||||
public static ReplicaPlan.ForWrite forWrite(Keyspace keyspace, ConsistencyLevel consistencyLevel, EndpointsForToken natural, EndpointsForToken pending, Predicate<Replica> isAlive, Selector selector) throws UnavailableException
|
||||
{
|
||||
return forWrite(keyspace, consistencyLevel, ReplicaLayout.forTokenWrite(keyspace.getReplicationStrategy(), natural, pending), isAlive, selector);
|
||||
}
|
||||
|
||||
public static ReplicaPlan.ForTokenWrite forWrite(Keyspace keyspace, ConsistencyLevel consistencyLevel, ReplicaLayout.ForTokenWrite liveAndDown, Selector selector) throws UnavailableException
|
||||
public static ReplicaPlan.ForWrite forWrite(Keyspace keyspace, ConsistencyLevel consistencyLevel, ReplicaLayout.ForTokenWrite liveAndDown, Selector selector) throws UnavailableException
|
||||
{
|
||||
return forWrite(keyspace, consistencyLevel, liveAndDown, FailureDetector.isReplicaAlive, selector);
|
||||
}
|
||||
|
||||
private static ReplicaPlan.ForTokenWrite forWrite(Keyspace keyspace, ConsistencyLevel consistencyLevel, ReplicaLayout.ForTokenWrite liveAndDown, Predicate<Replica> isAlive, Selector selector) throws UnavailableException
|
||||
private static ReplicaPlan.ForWrite forWrite(Keyspace keyspace, ConsistencyLevel consistencyLevel, ReplicaLayout.ForTokenWrite liveAndDown, Predicate<Replica> isAlive, Selector selector) throws UnavailableException
|
||||
{
|
||||
ReplicaLayout.ForTokenWrite live = liveAndDown.filter(isAlive);
|
||||
return forWrite(keyspace, consistencyLevel, liveAndDown, live, selector);
|
||||
}
|
||||
|
||||
public static ReplicaPlan.ForTokenWrite forWrite(Keyspace keyspace, ConsistencyLevel consistencyLevel, ReplicaLayout.ForTokenWrite liveAndDown, ReplicaLayout.ForTokenWrite live, Selector selector) throws UnavailableException
|
||||
public static ReplicaPlan.ForWrite forWrite(Keyspace keyspace, ConsistencyLevel consistencyLevel, ReplicaLayout.ForTokenWrite liveAndDown, ReplicaLayout.ForTokenWrite live, Selector selector) throws UnavailableException
|
||||
{
|
||||
assert liveAndDown.replicationStrategy() == live.replicationStrategy()
|
||||
: "ReplicaLayout liveAndDown and live should be derived from the same replication strategy.";
|
||||
AbstractReplicationStrategy replicationStrategy = liveAndDown.replicationStrategy();
|
||||
EndpointsForToken contacts = selector.select(consistencyLevel, liveAndDown, live);
|
||||
assureSufficientLiveReplicasForWrite(replicationStrategy, consistencyLevel, live.all(), liveAndDown.pending());
|
||||
return new ReplicaPlan.ForTokenWrite(keyspace, replicationStrategy, consistencyLevel, liveAndDown.pending(), liveAndDown.all(), live.all(), contacts);
|
||||
return new ReplicaPlan.ForWrite(keyspace, replicationStrategy, consistencyLevel, liveAndDown.pending(), liveAndDown.all(), live.all(), contacts);
|
||||
}
|
||||
|
||||
public interface Selector
|
||||
|
|
@ -434,7 +434,7 @@ public class ReplicaPlans
|
|||
* the minimal number of nodes to meet the consistency level, and prefer nodes we contacted on read to minimise
|
||||
* data transfer.
|
||||
*/
|
||||
public static Selector writeReadRepair(ReplicaPlan.ForRead<?> readPlan)
|
||||
public static Selector writeReadRepair(ReplicaPlan<?, ?> readPlan)
|
||||
{
|
||||
return new Selector()
|
||||
{
|
||||
|
|
@ -497,7 +497,7 @@ public class ReplicaPlans
|
|||
{
|
||||
// TODO: we should cleanup our semantics here, as we're filtering ALL nodes to localDC which is unexpected for ReplicaPlan
|
||||
// Restrict natural and pending to node in the local DC only
|
||||
liveAndDown = liveAndDown.filter(InOurDcTester.replicas());
|
||||
liveAndDown = liveAndDown.filter(InOurDc.replicas());
|
||||
}
|
||||
|
||||
ReplicaLayout.ForTokenWrite live = liveAndDown.filter(FailureDetector.isReplicaAlive);
|
||||
|
|
@ -526,7 +526,7 @@ public class ReplicaPlans
|
|||
private static <E extends Endpoints<E>> E candidatesForRead(ConsistencyLevel consistencyLevel, E liveNaturalReplicas)
|
||||
{
|
||||
return consistencyLevel.isDatacenterLocal()
|
||||
? liveNaturalReplicas.filter(InOurDcTester.replicas())
|
||||
? liveNaturalReplicas.filter(InOurDc.replicas())
|
||||
: liveNaturalReplicas;
|
||||
}
|
||||
|
||||
|
|
@ -621,7 +621,7 @@ public class ReplicaPlans
|
|||
{
|
||||
// TODO: should we be asserting that the ranges are adjacent?
|
||||
AbstractBounds<PartitionPosition> newRange = left.range().withNewRight(right.range().right);
|
||||
EndpointsForRange mergedCandidates = left.candidates().keep(right.candidates().endpoints());
|
||||
EndpointsForRange mergedCandidates = left.readCandidates().keep(right.readCandidates().endpoints());
|
||||
AbstractReplicationStrategy replicationStrategy = keyspace.getReplicationStrategy();
|
||||
|
||||
// Check if there are enough shared endpoints for the merge to be possible.
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ public class Replicas
|
|||
public static ReplicaCount countInOurDc(ReplicaCollection<?> replicas)
|
||||
{
|
||||
ReplicaCount count = new ReplicaCount();
|
||||
Predicate<Replica> inOurDc = InOurDcTester.replicas();
|
||||
Predicate<Replica> inOurDc = InOurDc.replicas();
|
||||
for (Replica replica : replicas)
|
||||
if (inOurDc.test(replica))
|
||||
count.increment(replica);
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue