Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
b030fc1
[CASSANDRA-20476] Add dtest for CMS rediscovery
beobal May 29, 2025
e67e83f
[CASSANDRA-20476] Introduce CMSLookup
beobal Jun 23, 2025
ccd89e4
[CASSANDRA-20476] Perform rediscovery of CMS at startup if addresses …
beobal Jun 2, 2025
b51da1b
[CASSANDRA-20476] Attempt to wait for all address changes to be enact…
beobal Jun 25, 2025
8ae56a4
[CASSANDRA-20476] Some minor logging additions
beobal Jun 24, 2025
c18ba33
[CASSANDRA-20476] Make sure to start messaging service
Aug 5, 2025
3ffee0d
[CASSANDRA-20476] Don't attempt to catch up from peers or CMS while l…
beobal Jan 22, 2026
d918d3e
[CASSANDRA-20476] Remove unused CMSLookup.addressMap
beobal Feb 16, 2026
46e6890
[CASSANDRA-20476] Remove unnecessary map retrievals
beobal Feb 16, 2026
7747808
[CASSANDRA-20476] Make CMSLookup.overrides an ImmutableMap
beobal Feb 16, 2026
12bda58
[CASSANDRA-20476] When CMSLookup is RETIRED, remove log listener
beobal Feb 16, 2026
571df66
[CASSANDRA-20476] Include seeds in initial rediscovery candidates
beobal Feb 17, 2026
1ecc2cd
[CASSANDRA-20476] Make number of discovery rounds configurable
beobal Feb 18, 2026
f43f1cb
[CASSANDRA-20476] Improve comment about target quorum size during red…
beobal Feb 18, 2026
1a39507
[CASSANDRA-20476] Simplify Discovery.state to a boolean
beobal Feb 18, 2026
65d7fc6
[CASSANDRA-20476] Include node ids in CMS description
beobal Mar 3, 2026
672e54a
[CASSANDRA-20476] Reduce message timeout when sending Startup transform
beobal Jul 3, 2026
0945944
[CASSANDRA-20476] Don't attempt rediscovery if upgrading from a post …
beobal Mar 9, 2026
0048124
[CASSANDRA-20476] Fix ip changes while still in gossip mode
Apr 28, 2026
3188c09
[CASSANDRA-20476] Don't return CMS_ONLY Discovery responses when cons…
May 6, 2026
45af389
[CASSANDRA-20476] Minor logging changes
May 6, 2026
561ae8d
[CASSANDRA-20476] Relocate Discovery to new package
Jun 1, 2026
42fc3ee
[CASSANDRA-20476] Include ClusterMetadata.metadataId in survey req/rs…
Jun 8, 2026
fc802b7
[CASSANDRA-20476] Lower ProgressBarrier backoff in DiscoverNewCMSTest
Jun 8, 2026
718e4a4
[CASSANDRA-20476] Improvements to RemoteProcessor.CandidateIterator
beobal Jul 16, 2026
1cafe66
[CASSANDRA-20476] Break up new long running test
beobal Jul 21, 2026
665839a
[CASSANDRA-20476] Ensure proper exit from NewGossiper shadow round af…
beobal Jul 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/java/org/apache/cassandra/config/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -1581,6 +1581,7 @@ public static void log(Config config)
public volatile DurationSpec.LongMillisecondsBound progress_barrier_timeout = new DurationSpec.LongMillisecondsBound("3600000ms");
public volatile DurationSpec.LongMillisecondsBound progress_barrier_backoff = new DurationSpec.LongMillisecondsBound("1000ms");
public volatile DurationSpec.LongSecondsBound discovery_timeout = new DurationSpec.LongSecondsBound("30s");
public volatile int discovery_rounds = 5;
public boolean unsafe_tcm_mode = false;
public boolean legacy_state_listener_sync_local_updates = true;

Expand Down
5 changes: 5 additions & 0 deletions src/java/org/apache/cassandra/config/DatabaseDescriptor.java
Original file line number Diff line number Diff line change
Expand Up @@ -6339,6 +6339,11 @@ public static long getDiscoveryTimeout(TimeUnit unit)
return conf.discovery_timeout.to(unit);
}

public static int getDiscoveryRounds()
{
return conf.discovery_rounds;
}

public static boolean getUnsafeTCMMode()
{
return conf.unsafe_tcm_mode;
Expand Down
5 changes: 5 additions & 0 deletions src/java/org/apache/cassandra/gms/FailureDetector.java
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,11 @@ public boolean isAlive(InetAddressAndPort ep)
// registration via the metadata log, or a full gossip round). This is perfectly harmless, so no need to log
// an error in that case.
ClusterMetadata metadata = ClusterMetadata.current();
if (metadata.cmsLookup.isActive() && metadata.fullCMSMembers().contains(ep))
{
logger.trace("Found endpoint {} in active CMS lookup, assuming it is alive", ep);
return true;
}
if (!metadata.directory.allJoinedEndpoints().contains(ep) && !metadata.fullCMSMembers().contains(ep))
logger.error("Unknown endpoint: " + ep, new UnknownEndpointException(ep));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public void doVerb(Message<GossipDigestAck> message)
if (logger.isDebugEnabled())
logger.debug("Received an ack from {}, which may trigger exit from shadow round", from);

NewGossiper.instance.onAck(epStateMap);
NewGossiper.instance.onAck(message.from(), epStateMap);
return;
}
if (epStateMap.size() > 0)
Expand Down
13 changes: 12 additions & 1 deletion src/java/org/apache/cassandra/gms/Gossiper.java
Original file line number Diff line number Diff line change
Expand Up @@ -720,6 +720,17 @@ public void unsafeAnnulEndpoint(InetAddressAndPort endpoint)
unreachableEndpoints.remove(endpoint);
}

/**
* determine which endpoint started up earlier
*/
public int compareEndpointStartup(InetAddressAndPort addr1, InetAddressAndPort addr2)
{
EndpointState ep1 = getEndpointStateForEndpoint(addr1);
EndpointState ep2 = getEndpointStateForEndpoint(addr2);
assert ep1 != null && ep2 != null;
return ep1.getHeartBeatState().getGeneration() - ep2.getHeartBeatState().getGeneration();
}

/**
* Quarantines the endpoint for QUARANTINE_DELAY
*
Expand Down Expand Up @@ -944,7 +955,7 @@ public boolean isGossipOnlyMember(InetAddressAndPort endpoint)
ClusterMetadata metadata = ClusterMetadata.current();
NodeId nodeId = metadata.directory.peerId(endpoint);
if (nodeId == null)
return false;
return true;
return NodeState.isPreJoin(metadata.directory.states.get(nodeId));
}

Expand Down
43 changes: 30 additions & 13 deletions src/java/org/apache/cassandra/gms/NewGossiper.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
Expand All @@ -41,7 +41,6 @@
import org.apache.cassandra.net.MessagingService;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.tcm.compatibility.GossipHelper;
import org.apache.cassandra.utils.concurrent.Accumulator;
import org.apache.cassandra.utils.concurrent.AsyncPromise;
import org.apache.cassandra.utils.concurrent.Promise;

Expand All @@ -60,9 +59,14 @@ public class NewGossiper
public Map<InetAddressAndPort, EndpointState> doShadowRound()
{
Set<InetAddressAndPort> peers = new HashSet<>(SystemKeyspace.loadHostIds().keySet());
for (InetAddressAndPort seed : DatabaseDescriptor.getSeeds())
{
if (!seed.equals(getBroadcastAddressAndPort()))
peers.add(seed);
}

// implies a single node cluster with only that one node configured as a seed
if (peers.isEmpty())
peers.addAll(DatabaseDescriptor.getSeeds());
if (peers.equals(Collections.singleton(getBroadcastAddressAndPort())))
return GossipHelper.storedEpstate();

ShadowRoundHandler shadowRoundHandler = new ShadowRoundHandler(peers);
Expand All @@ -85,6 +89,9 @@ public Map<InetAddressAndPort, EndpointState> doShadowRound()
}
}
logger.warn("Not able to construct initial cluster metadata from gossip, using system tables instead");
// Mark done here so that future gossip messages don't get routed to the shadow round handler (see
// GossipDigestSynVerbHandler & GossipDigestAckVerbHandler)
handler.markDone();
return GossipHelper.storedEpstate();
}

Expand All @@ -94,18 +101,18 @@ public boolean isInShadowRound()
return srh != null && !srh.isDone();
}

void onAck( Map<InetAddressAndPort, EndpointState> epStateMap)
void onAck(InetAddressAndPort from, Map<InetAddressAndPort, EndpointState> epStateMap)
{
ShadowRoundHandler srh = handler;
if (srh != null && !srh.isDone())
srh.onAck(epStateMap);
srh.onAck(from, epStateMap);
}

public static class ShadowRoundHandler
{
private volatile boolean isDone = false;
private final Set<InetAddressAndPort> peers;
private final Accumulator<Map<InetAddressAndPort, EndpointState>> responses;
private final Map<InetAddressAndPort, Map<InetAddressAndPort, EndpointState>> responses;
private final int requiredResponses;
private final MessageDelivery messageDelivery;
private final Promise<Map<InetAddressAndPort, EndpointState>> promise = new AsyncPromise<>();
Expand All @@ -117,12 +124,19 @@ public ShadowRoundHandler(Set<InetAddressAndPort> peers)

public ShadowRoundHandler(Set<InetAddressAndPort> peers, MessageDelivery messageDelivery)
{
this.peers = peers;
requiredResponses = Math.max(peers.size() / 10, 1); // todo: is 10% reasonable?
responses = new Accumulator<>(requiredResponses);
this.peers = ConcurrentHashMap.newKeySet();
this.peers.addAll(peers);
responses = new ConcurrentHashMap<>();
requiredResponses = this.peers.size() <= 3 ? 1 : Math.max(this.peers.size() / 5, 2); // require response from 20% of the cluster
this.messageDelivery = messageDelivery;
}

public void markDone()
{
logger.info("Marking NewGossiper shadow round done");
isDone = true;
}

public boolean isDone()
{
return isDone;
Expand All @@ -146,18 +160,21 @@ public Promise<Map<InetAddressAndPort, EndpointState>> doShadowRound()
return promise;
}

public void onAck(Map<InetAddressAndPort, EndpointState> epStateMap)
public void onAck(InetAddressAndPort from, Map<InetAddressAndPort, EndpointState> epStateMap)
{
if (!isDone)
{
if (!epStateMap.isEmpty())
responses.add(epStateMap);
{
responses.put(from, epStateMap);
peers.addAll(epStateMap.keySet()); // when retrying we should query the endpoints we learned about in the previous round
}

logger.debug("Received {} responses. {} required.", responses.size(), requiredResponses);
if (responses.size() >= requiredResponses)
{
isDone = true;
Map<InetAddressAndPort, EndpointState> merged = merge(responses.snapshot());
Map<InetAddressAndPort, EndpointState> merged = merge(responses.values());
if (GossipHelper.isValidForClusterMetadata(merged))
promise.setSuccess(merged);
else
Expand Down
7 changes: 4 additions & 3 deletions src/java/org/apache/cassandra/net/MessageDelivery.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ static <REQ, RSP> Collection<Pair<InetAddressAndPort, RSP>> fanoutAndWait(Messag
{
return fanoutAndWait(messaging, sendTo, verb, payload, DatabaseDescriptor.getCmsAwaitTimeout().to(TimeUnit.MILLISECONDS), TimeUnit.MILLISECONDS);
}

static <REQ, RSP> Collection<Pair<InetAddressAndPort, RSP>> fanoutAndWait(MessageDelivery messaging, Set<InetAddressAndPort> sendTo, Verb verb, REQ payload, long timeout, TimeUnit timeUnit)
{
Accumulator<Pair<InetAddressAndPort, RSP>> responses = new Accumulator<>(sendTo.size());
Expand All @@ -62,21 +63,21 @@ static <REQ, RSP> Collection<Pair<InetAddressAndPort, RSP>> fanoutAndWait(Messag
@Override
public void onResponse(Message<RSP> msg)
{
logger.info("Received a {} response from {}: {}", msg.verb(), msg.from(), msg.payload);
logger.debug("Received a {} response from {}: {}", msg.verb(), msg.from(), msg.payload);
responses.add(Pair.create(msg.from(), msg.payload));
cdl.decrement();
}

@Override
public void onFailure(InetAddressAndPort from, RequestFailure reason)
{
logger.info("Received failure in response to {} from {}: {}", verb, from, reason);
logger.debug("Received failure in response to {} from {}: {}", verb, from, reason);
cdl.decrement();
}
};

sendTo.forEach((ep) -> {
logger.info("Election for metadata migration sending {} ({}) to {}", verb, payload.toString(), ep);
logger.debug("Sending {} ({}) to {}", verb, payload.toString(), ep);
messaging.sendWithCallback(Message.out(verb, payload), ep, callback);
});
cdl.awaitUninterruptibly(timeout, timeUnit);
Expand Down
2 changes: 2 additions & 0 deletions src/java/org/apache/cassandra/net/ResponseVerbHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ class ResponseVerbHandler implements IVerbHandler
Verb.TCM_REPLICATION,
Verb.TCM_NOTIFY_RSP,
Verb.TCM_DISCOVER_RSP,
Verb.TCM_DISCOVER_PEERS_RSP,
Verb.TCM_DISCOVER_SURVEY_RSP,
Verb.TCM_INIT_MIG_RSP);

// We skip epoch catchup for PaxosV2 verbs, since we are using PaxosV2 to serially read the log.
Expand Down
9 changes: 8 additions & 1 deletion src/java/org/apache/cassandra/net/Verb.java
Original file line number Diff line number Diff line change
Expand Up @@ -136,10 +136,13 @@
import org.apache.cassandra.streaming.DataMovement;
import org.apache.cassandra.streaming.DataMovementVerbHandler;
import org.apache.cassandra.streaming.ReplicationDoneVerbHandler;
import org.apache.cassandra.tcm.Discovery;
import org.apache.cassandra.tcm.Epoch;
import org.apache.cassandra.tcm.FetchCMSLog;
import org.apache.cassandra.tcm.FetchPeerLog;
import org.apache.cassandra.tcm.discovery.Discovery;
import org.apache.cassandra.tcm.discovery.SurveyRequest;
import org.apache.cassandra.tcm.discovery.SurveyRequestHandler;
import org.apache.cassandra.tcm.discovery.SurveyResponse;
import org.apache.cassandra.tcm.migration.CMSInitializationRequest;
import org.apache.cassandra.tcm.migration.CMSInitializationResponse;
import org.apache.cassandra.tcm.migration.Election;
Expand Down Expand Up @@ -316,6 +319,10 @@ public enum Verb
TCM_DISCOVER_REQ (813, P0, rpcTimeout, INTERNAL_METADATA, () -> NoPayload.serializer, () -> Discovery.instance.requestHandler, TCM_DISCOVER_RSP ),
TCM_FETCH_PEER_LOG_RSP (818, P0, shortTimeout, FETCH_METADATA, MessageSerializers::logStateSerializer, RESPONSE_HANDLER ),
TCM_FETCH_PEER_LOG_REQ (819, P0, rpcTimeout, FETCH_METADATA, () -> FetchPeerLog.serializer, () -> FetchPeerLog.Handler.instance, TCM_FETCH_PEER_LOG_RSP ),
TCM_DISCOVER_PEERS_RSP (820, P0, rpcTimeout, INTERNAL_METADATA, () -> Discovery.serializer, () -> ResponseVerbHandler.instance ),
TCM_DISCOVER_PEERS_REQ (821, P0, rpcTimeout, INTERNAL_METADATA, () -> NoPayload.serializer, () -> Discovery.instance.requestHandler, TCM_DISCOVER_PEERS_RSP),
TCM_DISCOVER_SURVEY_RSP(822, P0, rpcTimeout, INTERNAL_METADATA, () -> SurveyResponse.serializer, () -> ResponseVerbHandler.instance ),
TCM_DISCOVER_SURVEY_REQ(823, P0, rpcTimeout, INTERNAL_METADATA, () -> SurveyRequest.serializer, () -> SurveyRequestHandler.instance(), TCM_DISCOVER_SURVEY_RSP),

INITIATE_DATA_MOVEMENTS_RSP (814, P1, rpcTimeout, MISC, () -> NoPayload.serializer, RESPONSE_HANDLER ),
INITIATE_DATA_MOVEMENTS_REQ (815, P1, rpcTimeout, MISC, () -> DataMovement.serializer, () -> DataMovementVerbHandler.instance, INITIATE_DATA_MOVEMENTS_RSP ),
Expand Down
Loading