mirror of https://github.com/apache/cassandra
Merge commit '017ec3e99e704db5e1a36ad153af08d6e7eca523' into cassandra-2.2
* commit '017ec3e99e704db5e1a36ad153af08d6e7eca523': Avoid stalling Paxos when the paxos state expires
This commit is contained in:
commit
6555a87bde
|
|
@ -30,6 +30,7 @@
|
|||
* Always close cluster with connection in CqlRecordWriter (CASSANDRA-11553)
|
||||
* Fix slice queries on ordered COMPACT tables (CASSANDRA-10988)
|
||||
Merged from 2.1:
|
||||
* Avoid stalling paxos when the paxos state expires (CASSANDRA-12043)
|
||||
* Remove finished incoming streaming connections from MessagingService (CASSANDRA-11854)
|
||||
* Don't try to get sstables for non-repairing column families (CASSANDRA-12077)
|
||||
* Prevent select statements with clustering key > 64k (CASSANDRA-11882)
|
||||
|
|
|
|||
|
|
@ -291,7 +291,7 @@ public class QueryProcessor implements QueryHandler
|
|||
return QueryOptions.forInternalCalls(boundValues);
|
||||
}
|
||||
|
||||
private static ParsedStatement.Prepared prepareInternal(String query) throws RequestValidationException
|
||||
public static ParsedStatement.Prepared prepareInternal(String query) throws RequestValidationException
|
||||
{
|
||||
ParsedStatement.Prepared prepared = internalStatements.get(query);
|
||||
if (prepared != null)
|
||||
|
|
@ -340,6 +340,32 @@ public class QueryProcessor implements QueryHandler
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 UntypedResultSet executeInternalWithNow(long now, String query, Object... values)
|
||||
{
|
||||
try
|
||||
{
|
||||
ParsedStatement.Prepared prepared = prepareInternal(query);
|
||||
assert prepared.statement instanceof SelectStatement;
|
||||
SelectStatement select = (SelectStatement)prepared.statement;
|
||||
ResultMessage result = select.executeInternal(internalQueryState(), makeInternalOptions(prepared, values), now);
|
||||
assert result instanceof ResultMessage.Rows;
|
||||
return UntypedResultSet.create(((ResultMessage.Rows)result).result);
|
||||
}
|
||||
catch (RequestExecutionException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
catch (RequestValidationException e)
|
||||
{
|
||||
throw new RuntimeException("Error validating query " + query, e);
|
||||
}
|
||||
}
|
||||
|
||||
public static UntypedResultSet resultify(String query, Row row)
|
||||
{
|
||||
return resultify(query, Collections.singletonList(row));
|
||||
|
|
|
|||
|
|
@ -290,9 +290,13 @@ public class SelectStatement implements CQLStatement
|
|||
}
|
||||
|
||||
public ResultMessage.Rows executeInternal(QueryState state, QueryOptions options) throws RequestExecutionException, RequestValidationException
|
||||
{
|
||||
return executeInternal(state, options, System.currentTimeMillis());
|
||||
}
|
||||
|
||||
public ResultMessage.Rows executeInternal(QueryState state, QueryOptions options, long now) throws RequestExecutionException, RequestValidationException
|
||||
{
|
||||
int limit = getLimit(options);
|
||||
long now = System.currentTimeMillis();
|
||||
Pageable command = getPageableCommand(options, limit, now);
|
||||
int pageSize = getPageSize(options);
|
||||
|
||||
|
|
|
|||
|
|
@ -894,10 +894,10 @@ public final class SystemKeyspace
|
|||
return null;
|
||||
}
|
||||
|
||||
public static PaxosState loadPaxosState(ByteBuffer key, CFMetaData metadata)
|
||||
public static PaxosState loadPaxosState(ByteBuffer key, CFMetaData metadata, long now)
|
||||
{
|
||||
String req = "SELECT * FROM system.%s WHERE row_key = ? AND cf_id = ?";
|
||||
UntypedResultSet results = executeInternal(String.format(req, PAXOS), key, metadata.cfId);
|
||||
UntypedResultSet results = QueryProcessor.executeInternalWithNow(now, String.format(req, PAXOS), key, metadata.cfId);
|
||||
if (results.isEmpty())
|
||||
return new PaxosState(key, metadata);
|
||||
UntypedResultSet.Row row = results.one();
|
||||
|
|
@ -937,7 +937,7 @@ public final class SystemKeyspace
|
|||
proposal.update.id());
|
||||
}
|
||||
|
||||
private static int paxosTtl(CFMetaData metadata)
|
||||
public static int paxosTtl(CFMetaData metadata)
|
||||
{
|
||||
// keep paxos state around for at least 3h
|
||||
return Math.max(3 * 3600, metadata.getGcGraceSeconds());
|
||||
|
|
|
|||
|
|
@ -425,7 +425,7 @@ public class StorageProxy implements StorageProxyMBean
|
|||
// https://issues.apache.org/jira/browse/CASSANDRA-5062?focusedCommentId=13619810&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-13619810)
|
||||
// Since we waited for quorum nodes, if some of them haven't seen the last commit (which may just be a timing issue, but may also
|
||||
// mean we lost messages), we pro-actively "repair" those nodes, and retry.
|
||||
Iterable<InetAddress> missingMRC = summary.replicasMissingMostRecentCommit();
|
||||
Iterable<InetAddress> missingMRC = summary.replicasMissingMostRecentCommit(metadata, ballotMicros);
|
||||
if (Iterables.size(missingMRC) > 0)
|
||||
{
|
||||
Tracing.trace("Repairing replicas that missed the most recent commit");
|
||||
|
|
|
|||
|
|
@ -63,7 +63,13 @@ public class PaxosState
|
|||
lock.lock();
|
||||
try
|
||||
{
|
||||
PaxosState state = SystemKeyspace.loadPaxosState(toPrepare.key, toPrepare.update.metadata());
|
||||
// When preparing, we need to use the same time as "now" (that's the time we use to decide if something
|
||||
// is expired or not) accross nodes otherwise we may have a window where a Most Recent Commit shows up
|
||||
// on some replica and not others during a new proposal (in StorageProxy.beginAndRepairPaxos()), and no
|
||||
// amount of re-submit will fix this (because the node on which the commit has expired will have a
|
||||
// tombstone that hides any re-submit). See CASSANDRA-12043 for details.
|
||||
long now = UUIDGen.unixTimestamp(toPrepare.ballot);
|
||||
PaxosState state = SystemKeyspace.loadPaxosState(toPrepare.key, toPrepare.update.metadata(), now);
|
||||
if (toPrepare.isAfter(state.promised))
|
||||
{
|
||||
Tracing.trace("Promising ballot {}", toPrepare.ballot);
|
||||
|
|
@ -98,7 +104,8 @@ public class PaxosState
|
|||
lock.lock();
|
||||
try
|
||||
{
|
||||
PaxosState state = SystemKeyspace.loadPaxosState(proposal.key, proposal.update.metadata());
|
||||
long now = UUIDGen.unixTimestamp(proposal.ballot);
|
||||
PaxosState state = SystemKeyspace.loadPaxosState(proposal.key, proposal.update.metadata(), now);
|
||||
if (proposal.hasBallot(state.promised.ballot) || proposal.isAfter(state.promised))
|
||||
{
|
||||
Tracing.trace("Accepting proposal {}", proposal);
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ package org.apache.cassandra.service.paxos;
|
|||
|
||||
import java.net.InetAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
|
|
@ -33,7 +34,9 @@ import org.slf4j.Logger;
|
|||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.apache.cassandra.config.CFMetaData;
|
||||
import org.apache.cassandra.db.SystemKeyspace;
|
||||
import org.apache.cassandra.net.MessageIn;
|
||||
import org.apache.cassandra.utils.UUIDGen;
|
||||
|
||||
public class PrepareCallback extends AbstractPaxosCallback<PrepareResponse>
|
||||
{
|
||||
|
|
@ -86,8 +89,21 @@ public class PrepareCallback extends AbstractPaxosCallback<PrepareResponse>
|
|||
latch.countDown();
|
||||
}
|
||||
|
||||
public Iterable<InetAddress> replicasMissingMostRecentCommit()
|
||||
public Iterable<InetAddress> replicasMissingMostRecentCommit(CFMetaData metadata, long now)
|
||||
{
|
||||
// In general, we need every replicas that have answered to the prepare (a quorum) to agree on the MRC (see
|
||||
// coment in StorageProxy.beginAndRepairPaxos(), but basically we need to make sure at least a quorum of nodes
|
||||
// have learn a commit before commit a new one otherwise that previous commit is not guaranteed to have reach a
|
||||
// quorum and further commit may proceed on incomplete information).
|
||||
// However, if that commit is too hold, it may have been expired from some of the replicas paxos table (we don't
|
||||
// keep the paxos state forever or that could grow unchecked), and we could end up in some infinite loop as
|
||||
// explained on CASSANDRA-12043. To avoid that, we ignore a MRC that is too old, i.e. older than the TTL we set
|
||||
// on paxos tables. For such old commit, we rely on hints and repair to ensure the commit has indeed be
|
||||
// propagated to all nodes.
|
||||
long paxosTtlMicros = SystemKeyspace.paxosTtl(metadata) * 1000 * 1000;
|
||||
if (UUIDGen.microsTimestamp(mostRecentCommit.ballot) + paxosTtlMicros < now)
|
||||
return Collections.emptySet();
|
||||
|
||||
return Iterables.filter(commitsByReplica.keySet(), new Predicate<InetAddress>()
|
||||
{
|
||||
public boolean apply(InetAddress inetAddress)
|
||||
|
|
|
|||
Loading…
Reference in New Issue