Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
97801e4
[CASSANDRA-20476] Add dtest for CMS rediscovery
beobal May 29, 2025
20cd9b6
[CASSANDRA-20476] Introduce CMSLookup
beobal Jun 23, 2025
0b97d84
[CASSANDRA-20476] Perform rediscovery of CMS at startup if addresses …
beobal Jun 2, 2025
c4229f7
[CASSANDRA-20476] Attempt to wait for all address changes to be enact…
beobal Jun 25, 2025
b99a3f8
[CASSANDRA-20476] Some minor logging additions
beobal Jun 24, 2025
ab5623e
[CASSANDRA-20476] Make sure to start messaging service
Aug 5, 2025
34371ce
[CASSANDRA-20476] Don't attempt to catch up from peers or CMS while l…
beobal Jan 22, 2026
01ee234
[CASSANDRA-20476] Remove unused CMSLookup.addressMap
beobal Feb 16, 2026
a85024e
[CASSANDRA-20476] Remove unnecessary map retrievals
beobal Feb 16, 2026
908c046
[CASSANDRA-20476] Make CMSLookup.overrides an ImmutableMap
beobal Feb 16, 2026
d888294
[CASSANDRA-20476] When CMSLookup is RETIRED, remove log listener
beobal Feb 16, 2026
2818b30
[CASSANDRA-20476] Include seeds in initial rediscovery candidates
beobal Feb 17, 2026
0324148
[CASSANDRA-20476] Make number of discovery rounds configurable
beobal Feb 18, 2026
b7a10be
[CASSANDRA-20476] Improve comment about target quorum size during red…
beobal Feb 18, 2026
5c06468
[CASSANDRA-20476] Simplify Discovery.state to a boolean
beobal Feb 18, 2026
a9220e1
[CASSANDRA-20476] Include node ids in CMS description
beobal Mar 3, 2026
14d3aed
[CASSANDRA-20476] Reduce message timeout when sending Startup transform
beobal Jul 3, 2026
390129a
[CASSANDRA-20476] Don't attempt rediscovery if upgrading from a post …
beobal Mar 9, 2026
223d269
[CASSANDRA-20476] Fix ip changes while still in gossip mode
Apr 28, 2026
08057d9
[CASSANDRA-20476] Don't return CMS_ONLY Discovery responses when cons…
May 6, 2026
c18c986
[CASSANDRA-20476] Minor logging changes
May 6, 2026
e0885c5
[CASSANDRA-20476] Relocate Discovery to new package
Jun 1, 2026
7e3c65a
[CASSANDRA-20476] Include ClusterMetadata.metadataId in survey req/rs…
Jun 8, 2026
ebc164d
[CASSANDRA-20476] Lower ProgressBarrier backoff in DiscoverNewCMSTest
Jun 8, 2026
e78792c
[CASSANDRA-20476] Improvements to RemoteProcessor.CandidateIterator
beobal Jul 16, 2026
d69d61a
[CASSANDRA-20476] Break up new long running test
beobal Jul 21, 2026
9a9f0a3
[CASSANDRA-20476] Ensure proper exit from NewGossiper shadow round af…
beobal Jul 29, 2026
4275e07
[CASSANDRA-20476] Fix (unrelated) upgrade test issues
beobal Jul 29, 2026
4a50bdd
[CASSANDRA-20476] Include current CMS member endpoints in rediscovery…
beobal Jul 30, 2026
36db396
[CASSANDRA-20476] Survey request/response improvements
beobal Aug 3, 2026
cf14fe8
[CASSANDRA-20476] Exit startup once local address changes have been c…
beobal Aug 3, 2026
0d0043e
[CASSANDRA-20476] Make CandidateIterator element set thread safe
beobal Aug 3, 2026
8d2ce55
[CASSANDRA-20476] Assert that lazily initialized CMS membership field…
beobal Aug 3, 2026
d1d4036
[CASSANDRA-20476] ShadowRoundHandler uses markDone() after receiving …
beobal Aug 4, 2026
d115267
[CASSANDRA-20476] Reword comment
beobal Aug 4, 2026
79fd3b4
[CASSANDRA-20476] Avoid double lookup in CMSLookup internal EndpointL…
beobal Aug 4, 2026
1d74f45
[CASSANDRA-20476] In DefaultReplicator, ensure node ids and endpoint …
beobal Aug 4, 2026
ba52fcb
[CASSANDRA-20476] Update response handler definition for new Discover…
beobal Aug 4, 2026
5ae8708
[CASSANDRA-20476] add tests for CMSLookup::rebuild
beobal Aug 5, 2026
8e9a9e9
[CASSANDRA-20476] tweak comment
beobal Aug 5, 2026
624f84a
[CASSANDRA-20476] Fix upgrade versions on upgrade dtest
beobal Aug 6, 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 @@ -1584,6 +1584,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 @@ -6355,6 +6355,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
45 changes: 31 additions & 14 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());
markDone();
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, RESPONSE_HANDLER ),
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, RESPONSE_HANDLER ),
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