From 22237537816335f0025aa55ce7606586acd6c90f Mon Sep 17 00:00:00 2001 From: Sam Tunnicliffe Date: Wed, 28 Jan 2026 15:44:53 +0000 Subject: [PATCH] Improve writePlacementsAllSettled performance in large clusters with many ongoing range movements Only inflight MSOs which affect the local node need to be applied to determine the settled local ranges --- .../cassandra/db/DiskBoundaryManager.java | 13 +- .../cassandra/service/StorageService.java | 2 +- .../service/paxos/cleanup/PaxosCleanup.java | 2 +- .../apache/cassandra/tcm/ClusterMetadata.java | 79 ++++- .../cassandra/tcm/MultiStepOperation.java | 43 +++ .../apache/cassandra/tcm/ownership/Delta.java | 10 + .../tcm/ownership/PlacementDeltas.java | 11 + .../cassandra/tcm/sequences/AddToCMS.java | 7 + .../tcm/sequences/BootstrapAndJoin.java | 12 + .../tcm/sequences/BootstrapAndReplace.java | 11 + .../tcm/sequences/DropAccordTable.java | 9 + .../apache/cassandra/tcm/sequences/Move.java | 12 + .../tcm/sequences/ReconfigureCMS.java | 7 + .../tcm/sequences/UnbootstrapAndLeave.java | 14 + .../transformations/ApplyPlacementDeltas.java | 10 + .../test/log/ClusterMetadataTestHelper.java | 18 +- .../log/MetadataChangeSimulationTest.java | 18 +- .../LocalRangesAllSettledBench.java | 143 +++++++++ .../CASSANDRA-21144_clustermetadata.gz | Bin 0 -> 1402477 bytes ...ansformationVersionCompatibilityTest.java} | 63 +--- .../ownership/LocalRangesAllSettledTest.java | 287 ++++++++++++++++++ .../tcm/ownership/OwnershipUtils.java | 17 ++ .../cassandra/tcm/ownership/RangeSetMap.java | 101 ++++++ .../tcm/sequences/DropAccordTableTest.java | 8 +- 24 files changed, 803 insertions(+), 94 deletions(-) create mode 100644 test/microbench/org/apache/cassandra/test/microbench/LocalRangesAllSettledBench.java create mode 100644 test/resources/cluster_metadata/CASSANDRA-21144_clustermetadata.gz rename test/unit/org/apache/cassandra/tcm/{ClusterMetadataTest.java => NewTransformationVersionCompatibilityTest.java} (62%) create mode 100644 test/unit/org/apache/cassandra/tcm/ownership/LocalRangesAllSettledTest.java create mode 100644 test/unit/org/apache/cassandra/tcm/ownership/RangeSetMap.java diff --git a/src/java/org/apache/cassandra/db/DiskBoundaryManager.java b/src/java/org/apache/cassandra/db/DiskBoundaryManager.java index 1b35423fe93b..e1059f485e8e 100644 --- a/src/java/org/apache/cassandra/db/DiskBoundaryManager.java +++ b/src/java/org/apache/cassandra/db/DiskBoundaryManager.java @@ -36,8 +36,6 @@ import org.apache.cassandra.tcm.ClusterMetadata; import org.apache.cassandra.tcm.ClusterMetadataService; import org.apache.cassandra.tcm.Epoch; -import org.apache.cassandra.tcm.ownership.DataPlacement; -import org.apache.cassandra.utils.FBUtilities; public class DiskBoundaryManager { @@ -143,22 +141,17 @@ private static DiskBoundaries getDiskBoundaryValue(ColumnFamilyStore cfs, IParti private static RangesAtEndpoint getLocalRanges(ColumnFamilyStore cfs, ClusterMetadata metadata) { - RangesAtEndpoint localRanges; - DataPlacement placement; - if (StorageService.instance.isBootstrapMode() - && !StorageService.isReplacingSameAddress()) // When replacing same address, the node marks itself as UN locally + if (StorageService.instance.isBootstrapMode() && !StorageService.isReplacingSameAddress()) // When replacing same address, the node marks itself as UN locally { - placement = metadata.placements.get(cfs.keyspace.getMetadata().params.replication); + return metadata.localWriteRanges(cfs.keyspace.getMetadata()); } else { // Reason we use the future settled metadata is that if we decommission a node, we want to stream // from that node to the correct location on disk, if we didn't, we would put new files in the wrong places. // We do this to minimize the amount of data we need to move in rebalancedisks once everything settled - placement = metadata.writePlacementAllSettled(cfs.keyspace.getMetadata()); + return metadata.localWriteRangesAllSettled(cfs.keyspace.getMetadata()); } - localRanges = placement.writes.byEndpoint().get(FBUtilities.getBroadcastAddressAndPort()); - return localRanges; } /** diff --git a/src/java/org/apache/cassandra/service/StorageService.java b/src/java/org/apache/cassandra/service/StorageService.java index dc9d9e3f680b..112367997985 100644 --- a/src/java/org/apache/cassandra/service/StorageService.java +++ b/src/java/org/apache/cassandra/service/StorageService.java @@ -415,7 +415,7 @@ public List> getLocalRanges(String ks) public Collection> getLocalAndPendingRanges(String ks) { - return ClusterMetadata.current().localWriteRanges(Keyspace.open(ks).getMetadata()); + return ClusterMetadata.current().localWriteRanges(Keyspace.open(ks).getMetadata()).ranges(); } public OwnedRanges getNormalizedLocalRanges(String keyspaceName, InetAddressAndPort broadcastAddress) diff --git a/src/java/org/apache/cassandra/service/paxos/cleanup/PaxosCleanup.java b/src/java/org/apache/cassandra/service/paxos/cleanup/PaxosCleanup.java index fb92038cbf9b..660dca619b26 100644 --- a/src/java/org/apache/cassandra/service/paxos/cleanup/PaxosCleanup.java +++ b/src/java/org/apache/cassandra/service/paxos/cleanup/PaxosCleanup.java @@ -119,7 +119,7 @@ private void finish(Ballot lowBound) private static boolean isOutOfRange(SharedContext ctx, String ksName, Collection> repairRanges) { Keyspace keyspace = Keyspace.open(ksName); - Collection> localRanges = Range.normalize(ClusterMetadata.current().writeRanges(keyspace.getMetadata(), ctx.broadcastAddressAndPort())); + Collection> localRanges = Range.normalize(ClusterMetadata.current().writeRanges(keyspace.getMetadata(), ctx.broadcastAddressAndPort()).ranges()); for (Range repairRange : Range.normalize(repairRanges)) { diff --git a/src/java/org/apache/cassandra/tcm/ClusterMetadata.java b/src/java/org/apache/cassandra/tcm/ClusterMetadata.java index fa2d8f5ee5f6..f2013acf2b70 100644 --- a/src/java/org/apache/cassandra/tcm/ClusterMetadata.java +++ b/src/java/org/apache/cassandra/tcm/ClusterMetadata.java @@ -32,6 +32,7 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Maps; import com.google.common.collect.Sets; import org.slf4j.Logger; @@ -52,6 +53,7 @@ import org.apache.cassandra.locator.InetAddressAndPort; import org.apache.cassandra.locator.Locator; import org.apache.cassandra.locator.MetaStrategy; +import org.apache.cassandra.locator.RangesAtEndpoint; import org.apache.cassandra.locator.Replica; import org.apache.cassandra.net.CMSIdentifierMismatchException; import org.apache.cassandra.schema.DistributedSchema; @@ -73,7 +75,6 @@ import org.apache.cassandra.tcm.membership.NodeId; import org.apache.cassandra.tcm.membership.NodeState; import org.apache.cassandra.tcm.membership.NodeVersion; -import org.apache.cassandra.tcm.ownership.DataPlacement; import org.apache.cassandra.tcm.ownership.DataPlacements; import org.apache.cassandra.tcm.ownership.PrimaryRangeComparator; import org.apache.cassandra.tcm.ownership.ReplicaGroups; @@ -119,7 +120,8 @@ public class ClusterMetadata private EndpointsForRange fullCMSReplicas; private Set fullCMSEndpoints; private Set fullCMSIds; - private DataPlacements writePlacementAllSettled; + private volatile Map localRangesAllSettled = null; + private static final RangesAtEndpoint EMPTY_LOCAL_RANGES = RangesAtEndpoint.empty(FBUtilities.getBroadcastAddressAndPort()); public ClusterMetadata(IPartitioner partitioner) { @@ -317,21 +319,72 @@ public Epoch nextEpoch() return epoch.nextEpoch(); } - public DataPlacement writePlacementAllSettled(KeyspaceMetadata ksm) + public RangesAtEndpoint localWriteRangesAllSettled(KeyspaceMetadata ksm) { - if (writePlacementAllSettled == null) + // Local strategy ranges are constant + if (ksm.params.replication.isLocal()) + return localWriteRanges(ksm); + + if (localRangesAllSettled != null) + return localRangesAllSettled.getOrDefault(ksm.params.replication, EMPTY_LOCAL_RANGES); + + ClusterMetadata metadata = this; + NodeId localId = metadata.myNodeId(); + synchronized (this) { - ClusterMetadata metadata = this; - Iterator> iter = metadata.inProgressSequences.iterator(); - while (iter.hasNext()) + if (localRangesAllSettled != null) + return localRangesAllSettled.get(ksm.params.replication); + + Map builder = Maps.newHashMapWithExpectedSize(this.placements.size()); + DataPlacements settled = placementsAllSettledForNode(localId, metadata); + settled.forEach((replication, placement) -> { + builder.put(replication, placement.writes.byEndpoint().get(FBUtilities.getBroadcastAddressAndPort())); + }); + localRangesAllSettled = builder; + } + return localRangesAllSettled.getOrDefault(ksm.params.replication, EMPTY_LOCAL_RANGES); + } + + /** + * Run through all inflight MultiStepOperations in the supplied metadata and if any impact the specifed node, + * apply their metadata transformations. Only used outside of tests by @{link localWriteRangesAllSettled} to + * identify how placements for the local node will be affected by in flight operations. In that case, the result + * is cached so this should be called at most once for a given ClusterMetadata instance. + */ + @VisibleForTesting + public static DataPlacements placementsAllSettledForNode(NodeId peer, ClusterMetadata metadata) + { + Iterator> iter = metadata.inProgressSequences.iterator(); + while (iter.hasNext()) + { + MultiStepOperation operation = iter.next(); + // Check whether the MSO materially affects the local ranges of the target node. + boolean isRelevantOperation = operationAffectsLocalRangesOfPeer(peer, + operation, + metadata.directory); + if (isRelevantOperation) { - Transformation.Result result = iter.next().applyTo(metadata); + logger.debug("Operation {} affects node {}, calculating local ranges after application", + operation.sequenceKey(), peer); + Transformation.Result result = operation.applyTo(metadata); assert result.isSuccess(); metadata = result.success().metadata; } - writePlacementAllSettled = metadata.placements; } - return writePlacementAllSettled.get(ksm.params.replication); + return metadata.placements; + } + + public static boolean operationAffectsLocalRangesOfPeer(NodeId peer, + MultiStepOperation operation, + Directory directory) + { + return operation.affectedPeers(directory).contains(peer); + } + + @VisibleForTesting + public void unsafeClearLocalRangesAllSettled() + { + localRangesAllSettled = null; } // TODO Remove this as it isn't really an equivalent to the previous concept of pending ranges @@ -352,14 +405,14 @@ public boolean hasPendingRangesFor(KeyspaceMetadata ksm, InetAddressAndPort endp return !writes.byEndpoint().get(endpoint).equals(reads.byEndpoint().get(endpoint)); } - public Collection> localWriteRanges(KeyspaceMetadata metadata) + public RangesAtEndpoint localWriteRanges(KeyspaceMetadata metadata) { return writeRanges(metadata, FBUtilities.getBroadcastAddressAndPort()); } - public Collection> writeRanges(KeyspaceMetadata metadata, InetAddressAndPort peer) + public RangesAtEndpoint writeRanges(KeyspaceMetadata metadata, InetAddressAndPort peer) { - return placements.get(metadata.params.replication).writes.byEndpoint().get(peer).ranges(); + return placements.get(metadata.params.replication).writes.byEndpoint().get(peer); } // TODO Remove this as it isn't really an equivalent to the previous concept of pending ranges diff --git a/src/java/org/apache/cassandra/tcm/MultiStepOperation.java b/src/java/org/apache/cassandra/tcm/MultiStepOperation.java index 7083f27b648e..03164243b7a0 100644 --- a/src/java/org/apache/cassandra/tcm/MultiStepOperation.java +++ b/src/java/org/apache/cassandra/tcm/MultiStepOperation.java @@ -19,9 +19,17 @@ package org.apache.cassandra.tcm; import java.util.List; +import java.util.Set; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.apache.cassandra.locator.InetAddressAndPort; +import org.apache.cassandra.tcm.membership.Directory; +import org.apache.cassandra.tcm.membership.NodeId; import org.apache.cassandra.tcm.sequences.AddToCMS; import org.apache.cassandra.tcm.sequences.BootstrapAndJoin; import org.apache.cassandra.tcm.sequences.BootstrapAndReplace; @@ -57,6 +65,8 @@ */ public abstract class MultiStepOperation { + private static final Logger logger = LoggerFactory.getLogger(MultiStepOperation.class); + public enum Kind { @Deprecated(since = "CEP-21") @@ -155,6 +165,39 @@ public boolean finishDuringStartup() */ public abstract Transformation.Result applyTo(ClusterMetadata metadata); + /** + * Return the id of any peer known to be involved in the execution of the operation. + * For example, in the case of a new node bootstrapping this would include all current and proposed replicas of the + * affected ranges. + * Important: this currently requires a Directory to be supplied as many MSO implementations are endpoint-centric + * The directory is used to convert endpoints to node ids, but this will become unnecessary as placements & deltas + * evolve away from endpoints to use ids directly. + * @return Node ids of the peers involved in the operation + */ + public abstract Set affectedPeers(Directory directory); + + /** + * Helper method for affectedPeers implementations to convert from endpoints to node ids + * @return set of node ids which map to the supplied endpoints using the directory. Any endpoints without a + * corresponding id are ignored + */ + protected Set endpointsToIds(Set endpoints, Directory directory) + { + Set affectedNodes = Sets.newHashSetWithExpectedSize(endpoints.size()); + for (InetAddressAndPort endpoint : endpoints) + { + NodeId id = directory.peerId(endpoint); + // TODO should we error here? + if (id == null) + logger.warn("No node id found for endpoint {} in directory with epoch {} " + + "by MultiStepOperation {} with sequence key {}", + endpoint, directory.lastModified().getEpoch(), kind(), sequenceKey()); + else + affectedNodes.add(id); + } + return affectedNodes; + } + /** * Helper method for the standard applyTo implementations where we just execute a list of transformations, starting at `next` * @return diff --git a/src/java/org/apache/cassandra/tcm/ownership/Delta.java b/src/java/org/apache/cassandra/tcm/ownership/Delta.java index 9660bd255a81..38fcdfde9651 100644 --- a/src/java/org/apache/cassandra/tcm/ownership/Delta.java +++ b/src/java/org/apache/cassandra/tcm/ownership/Delta.java @@ -19,10 +19,13 @@ package org.apache.cassandra.tcm.ownership; import java.io.IOException; +import java.util.HashSet; import java.util.Objects; +import java.util.Set; import org.apache.cassandra.io.util.DataInputPlus; import org.apache.cassandra.io.util.DataOutputPlus; +import org.apache.cassandra.locator.InetAddressAndPort; import org.apache.cassandra.locator.RangesByEndpoint; import org.apache.cassandra.tcm.serialization.MetadataSerializer; import org.apache.cassandra.tcm.serialization.Version; @@ -52,6 +55,13 @@ public Delta onlyRemovals() return new Delta(removals, RangesByEndpoint.EMPTY); } + public Set allEndpoints() + { + Set endpoints = new HashSet<>(removals.keySet()); + endpoints.addAll(additions.keySet()); + return endpoints; + } + /** * Merges this delta with `other` * diff --git a/src/java/org/apache/cassandra/tcm/ownership/PlacementDeltas.java b/src/java/org/apache/cassandra/tcm/ownership/PlacementDeltas.java index 6ba80a854bdd..760cd0243f55 100644 --- a/src/java/org/apache/cassandra/tcm/ownership/PlacementDeltas.java +++ b/src/java/org/apache/cassandra/tcm/ownership/PlacementDeltas.java @@ -21,12 +21,15 @@ import java.io.IOException; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; import java.util.Objects; +import java.util.Set; import org.apache.cassandra.db.TypeSizes; import org.apache.cassandra.io.util.DataInputPlus; import org.apache.cassandra.io.util.DataOutputPlus; +import org.apache.cassandra.locator.InetAddressAndPort; import org.apache.cassandra.schema.ReplicationParams; import org.apache.cassandra.tcm.Epoch; import org.apache.cassandra.tcm.serialization.MetadataSerializer; @@ -152,6 +155,14 @@ public PlacementDelta onlyRemovals() return new PlacementDelta(reads.onlyRemovals(), writes.onlyRemovals()); } + // TODO deltas (& placements in general) should deal in node ids, not endpoints. + public Set affectedEndpoints() + { + Set affectedEndpoints = new HashSet<>(reads.allEndpoints()); + affectedEndpoints.addAll(writes.allEndpoints()); + return affectedEndpoints; + } + public DataPlacement apply(Epoch epoch, DataPlacement placement) { DataPlacement.Builder builder = placement.unbuild(); diff --git a/src/java/org/apache/cassandra/tcm/sequences/AddToCMS.java b/src/java/org/apache/cassandra/tcm/sequences/AddToCMS.java index 0d5ee2f06710..ec516854a034 100644 --- a/src/java/org/apache/cassandra/tcm/sequences/AddToCMS.java +++ b/src/java/org/apache/cassandra/tcm/sequences/AddToCMS.java @@ -35,6 +35,7 @@ import org.apache.cassandra.tcm.Epoch; import org.apache.cassandra.tcm.MultiStepOperation; import org.apache.cassandra.tcm.Transformation; +import org.apache.cassandra.tcm.membership.Directory; import org.apache.cassandra.tcm.membership.NodeId; import org.apache.cassandra.tcm.serialization.AsymmetricMetadataSerializer; import org.apache.cassandra.tcm.serialization.MetadataSerializer; @@ -125,6 +126,12 @@ public Transformation.Result applyTo(ClusterMetadata metadata) return finishJoin.execute(metadata); } + @Override + public Set affectedPeers(Directory directory) + { + return Set.of(); + } + @Override public SequenceState executeNext() { diff --git a/src/java/org/apache/cassandra/tcm/sequences/BootstrapAndJoin.java b/src/java/org/apache/cassandra/tcm/sequences/BootstrapAndJoin.java index b410097f84a8..d2909bd8737b 100644 --- a/src/java/org/apache/cassandra/tcm/sequences/BootstrapAndJoin.java +++ b/src/java/org/apache/cassandra/tcm/sequences/BootstrapAndJoin.java @@ -21,6 +21,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collection; +import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; @@ -56,6 +57,7 @@ import org.apache.cassandra.tcm.Epoch; import org.apache.cassandra.tcm.MultiStepOperation; import org.apache.cassandra.tcm.Transformation; +import org.apache.cassandra.tcm.membership.Directory; import org.apache.cassandra.tcm.membership.NodeId; import org.apache.cassandra.tcm.membership.NodeState; import org.apache.cassandra.tcm.ownership.DataPlacement; @@ -185,6 +187,16 @@ public Transformation.Result applyTo(ClusterMetadata metadata) return applyMultipleTransformations(metadata, next, of(startJoin, midJoin, finishJoin)); } + @Override + public Set affectedPeers(Directory directory) + { + Set affectedEndpoints = new HashSet<>(); + affectedEndpoints.addAll(startJoin.affectedEndpoints()); + affectedEndpoints.addAll(midJoin.affectedEndpoints()); + affectedEndpoints.addAll(finishJoin.affectedEndpoints()); + return endpointsToIds(affectedEndpoints, directory); + } + @Override public SequenceState executeNext() { diff --git a/src/java/org/apache/cassandra/tcm/sequences/BootstrapAndReplace.java b/src/java/org/apache/cassandra/tcm/sequences/BootstrapAndReplace.java index cb34179d4456..32afe234916d 100644 --- a/src/java/org/apache/cassandra/tcm/sequences/BootstrapAndReplace.java +++ b/src/java/org/apache/cassandra/tcm/sequences/BootstrapAndReplace.java @@ -53,6 +53,7 @@ import org.apache.cassandra.tcm.Epoch; import org.apache.cassandra.tcm.MultiStepOperation; import org.apache.cassandra.tcm.Transformation; +import org.apache.cassandra.tcm.membership.Directory; import org.apache.cassandra.tcm.membership.NodeId; import org.apache.cassandra.tcm.membership.NodeState; import org.apache.cassandra.tcm.ownership.DataPlacement; @@ -180,6 +181,16 @@ public Transformation.Result applyTo(ClusterMetadata metadata) return applyMultipleTransformations(metadata, next, of(startReplace, midReplace, finishReplace)); } + @Override + public Set affectedPeers(Directory directory) + { + Set affectedEndpoints = new HashSet<>(); + affectedEndpoints.addAll(startReplace.affectedEndpoints()); + affectedEndpoints.addAll(midReplace.affectedEndpoints()); + affectedEndpoints.addAll(finishReplace.affectedEndpoints()); + return endpointsToIds(affectedEndpoints, directory); + } + @Override public SequenceState executeNext() { diff --git a/src/java/org/apache/cassandra/tcm/sequences/DropAccordTable.java b/src/java/org/apache/cassandra/tcm/sequences/DropAccordTable.java index 0a3512296aef..c95fe94435a1 100644 --- a/src/java/org/apache/cassandra/tcm/sequences/DropAccordTable.java +++ b/src/java/org/apache/cassandra/tcm/sequences/DropAccordTable.java @@ -21,6 +21,7 @@ import java.io.IOException; import java.time.Duration; import java.util.Objects; +import java.util.Set; import java.util.concurrent.ExecutionException; import org.slf4j.Logger; @@ -39,6 +40,8 @@ import org.apache.cassandra.tcm.Epoch; import org.apache.cassandra.tcm.MultiStepOperation; import org.apache.cassandra.tcm.Transformation; +import org.apache.cassandra.tcm.membership.Directory; +import org.apache.cassandra.tcm.membership.NodeId; import org.apache.cassandra.tcm.serialization.AsymmetricMetadataSerializer; import org.apache.cassandra.tcm.serialization.MetadataSerializer; import org.apache.cassandra.tcm.serialization.Version; @@ -129,6 +132,12 @@ public Transformation.Kind nextStep() return FINISH_DROP_ACCORD_TABLE; } + @Override + public Set affectedPeers(Directory directory) + { + return Set.of(); + } + @Override public SequenceState executeNext() { diff --git a/src/java/org/apache/cassandra/tcm/sequences/Move.java b/src/java/org/apache/cassandra/tcm/sequences/Move.java index a26d6ded4500..a7f316e38fac 100644 --- a/src/java/org/apache/cassandra/tcm/sequences/Move.java +++ b/src/java/org/apache/cassandra/tcm/sequences/Move.java @@ -44,6 +44,7 @@ import org.apache.cassandra.io.util.DataInputPlus; import org.apache.cassandra.io.util.DataOutputPlus; import org.apache.cassandra.locator.EndpointsByReplica; +import org.apache.cassandra.locator.InetAddressAndPort; import org.apache.cassandra.locator.RangesAtEndpoint; import org.apache.cassandra.locator.RangesByEndpoint; import org.apache.cassandra.locator.Replica; @@ -61,6 +62,7 @@ import org.apache.cassandra.tcm.Epoch; import org.apache.cassandra.tcm.MultiStepOperation; import org.apache.cassandra.tcm.Transformation; +import org.apache.cassandra.tcm.membership.Directory; import org.apache.cassandra.tcm.membership.NodeId; import org.apache.cassandra.tcm.membership.NodeState; import org.apache.cassandra.tcm.ownership.DataPlacements; @@ -194,6 +196,16 @@ public Transformation.Result applyTo(ClusterMetadata metadata) return applyMultipleTransformations(metadata, next, of(startMove, midMove, finishMove)); } + @Override + public Set affectedPeers(Directory directory) + { + Set affectedEndpoints = new HashSet<>(); + affectedEndpoints.addAll(startMove.affectedEndpoints()); + affectedEndpoints.addAll(midMove.affectedEndpoints()); + affectedEndpoints.addAll(finishMove.affectedEndpoints()); + return endpointsToIds(affectedEndpoints, directory); + } + @Override public SequenceState executeNext() { diff --git a/src/java/org/apache/cassandra/tcm/sequences/ReconfigureCMS.java b/src/java/org/apache/cassandra/tcm/sequences/ReconfigureCMS.java index ce2daf6b4670..39613ce7b86d 100644 --- a/src/java/org/apache/cassandra/tcm/sequences/ReconfigureCMS.java +++ b/src/java/org/apache/cassandra/tcm/sequences/ReconfigureCMS.java @@ -63,6 +63,7 @@ import org.apache.cassandra.tcm.MultiStepOperation; import org.apache.cassandra.tcm.Retry; import org.apache.cassandra.tcm.Transformation; +import org.apache.cassandra.tcm.membership.Directory; import org.apache.cassandra.tcm.membership.NodeId; import org.apache.cassandra.tcm.ownership.MovementMap; import org.apache.cassandra.tcm.serialization.AsymmetricMetadataSerializer; @@ -145,6 +146,12 @@ public MetadataSerializer keySerialize return next.kind(); } + @Override + public Set affectedPeers(Directory directory) + { + return Set.of(); + } + @Override public Transformation.Result applyTo(ClusterMetadata metadata) { diff --git a/src/java/org/apache/cassandra/tcm/sequences/UnbootstrapAndLeave.java b/src/java/org/apache/cassandra/tcm/sequences/UnbootstrapAndLeave.java index 83d543a8dda1..da8ae74e6a34 100644 --- a/src/java/org/apache/cassandra/tcm/sequences/UnbootstrapAndLeave.java +++ b/src/java/org/apache/cassandra/tcm/sequences/UnbootstrapAndLeave.java @@ -19,7 +19,9 @@ package org.apache.cassandra.tcm.sequences; import java.io.IOException; +import java.util.HashSet; import java.util.Objects; +import java.util.Set; import java.util.concurrent.ExecutionException; import com.google.common.annotations.VisibleForTesting; @@ -31,12 +33,14 @@ import org.apache.cassandra.io.util.DataInputPlus; import org.apache.cassandra.io.util.DataOutputPlus; import org.apache.cassandra.locator.DynamicEndpointSnitch; +import org.apache.cassandra.locator.InetAddressAndPort; import org.apache.cassandra.service.StorageService; import org.apache.cassandra.tcm.ClusterMetadata; import org.apache.cassandra.tcm.ClusterMetadataService; import org.apache.cassandra.tcm.Epoch; import org.apache.cassandra.tcm.MultiStepOperation; import org.apache.cassandra.tcm.Transformation; +import org.apache.cassandra.tcm.membership.Directory; import org.apache.cassandra.tcm.membership.Location; import org.apache.cassandra.tcm.membership.NodeId; import org.apache.cassandra.tcm.membership.NodeState; @@ -156,6 +160,16 @@ public Transformation.Result applyTo(ClusterMetadata metadata) return applyMultipleTransformations(metadata, next, of(startLeave, midLeave, finishLeave)); } + @Override + public Set affectedPeers(Directory directory) + { + Set affectedEndpoints = new HashSet<>(); + affectedEndpoints.addAll(startLeave.affectedEndpoints()); + affectedEndpoints.addAll(midLeave.affectedEndpoints()); + affectedEndpoints.addAll(finishLeave.affectedEndpoints()); + return endpointsToIds(affectedEndpoints, directory); + } + @Override public SequenceState executeNext() { diff --git a/src/java/org/apache/cassandra/tcm/transformations/ApplyPlacementDeltas.java b/src/java/org/apache/cassandra/tcm/transformations/ApplyPlacementDeltas.java index 2a1eaba7e0c4..10f48609d430 100644 --- a/src/java/org/apache/cassandra/tcm/transformations/ApplyPlacementDeltas.java +++ b/src/java/org/apache/cassandra/tcm/transformations/ApplyPlacementDeltas.java @@ -19,11 +19,14 @@ package org.apache.cassandra.tcm.transformations; import java.io.IOException; +import java.util.HashSet; import java.util.Objects; +import java.util.Set; import org.apache.cassandra.exceptions.ExceptionCode; import org.apache.cassandra.io.util.DataInputPlus; import org.apache.cassandra.io.util.DataOutputPlus; +import org.apache.cassandra.locator.InetAddressAndPort; import org.apache.cassandra.tcm.ClusterMetadata; import org.apache.cassandra.tcm.Transformation; import org.apache.cassandra.tcm.membership.NodeId; @@ -60,6 +63,13 @@ public PlacementDeltas delta() return delta; } + public Set affectedEndpoints() + { + Set affectedEndpoints = new HashSet<>(); + delta.forEach((replication, d) -> affectedEndpoints.addAll(d.affectedEndpoints())); + return affectedEndpoints; + } + @Override public final Result execute(ClusterMetadata prev) { diff --git a/test/distributed/org/apache/cassandra/distributed/test/log/ClusterMetadataTestHelper.java b/test/distributed/org/apache/cassandra/distributed/test/log/ClusterMetadataTestHelper.java index 063f94857cfc..1598c5ca8974 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/log/ClusterMetadataTestHelper.java +++ b/test/distributed/org/apache/cassandra/distributed/test/log/ClusterMetadataTestHelper.java @@ -1018,6 +1018,12 @@ public static PrepareLeave prepareLeave(int idx) { return prepareLeave(nodeId(idx)); } + + public static PrepareLeave prepareLeave(int idx, boolean force) + { + return new PrepareLeave(nodeId(idx), force, new UniformRangePlacement(), LeaveStreams.Kind.UNBOOTSTRAP); + } + public static PrepareLeave prepareLeave(NodeId nodeId) { return new PrepareLeave(nodeId, @@ -1026,10 +1032,15 @@ public static PrepareLeave prepareLeave(NodeId nodeId) LeaveStreams.Kind.UNBOOTSTRAP); } + public static PrepareMove prepareMove(int idx, Token newToken) + { + return prepareMove(nodeId(idx), newToken); + } + public static PrepareMove prepareMove(NodeId id, Token newToken) { return new PrepareMove(id, - Collections.singleton(Murmur3Partitioner.instance.getRandomToken()), + Collections.singleton(newToken), new UniformRangePlacement(), false); } @@ -1103,6 +1114,11 @@ public static BootstrapAndReplace getReplacePlan(NodeId nodeId, ClusterMetadata return (BootstrapAndReplace) metadata.inProgressSequences.get(nodeId); } + public static Move getMovePlan(int peer) + { + return getMovePlan(addr(peer)); + } + public static Move getMovePlan(InetAddressAndPort addr) { return getMovePlan(ClusterMetadata.current().directory.peerId(addr)); diff --git a/test/distributed/org/apache/cassandra/distributed/test/log/MetadataChangeSimulationTest.java b/test/distributed/org/apache/cassandra/distributed/test/log/MetadataChangeSimulationTest.java index 82e864b3ef2d..8223ca471f19 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/log/MetadataChangeSimulationTest.java +++ b/test/distributed/org/apache/cassandra/distributed/test/log/MetadataChangeSimulationTest.java @@ -53,6 +53,7 @@ import org.apache.cassandra.locator.CMSPlacementStrategy; import org.apache.cassandra.locator.EndpointsForRange; import org.apache.cassandra.locator.InetAddressAndPort; +import org.apache.cassandra.locator.RangesAtEndpoint; import org.apache.cassandra.locator.Replica; import org.apache.cassandra.schema.KeyspaceMetadata; import org.apache.cassandra.schema.ReplicationParams; @@ -66,6 +67,7 @@ import org.apache.cassandra.tcm.membership.NodeVersion; import org.apache.cassandra.tcm.ownership.DataPlacement; import org.apache.cassandra.tcm.ownership.DataPlacements; +import org.apache.cassandra.tcm.ownership.OwnershipUtils; import org.apache.cassandra.tcm.ownership.ReplicaGroups; import org.apache.cassandra.tcm.ownership.VersionedEndpoints; import org.apache.cassandra.tcm.transformations.Register; @@ -930,14 +932,26 @@ public void testPlacementsAllSettled() throws Throwable state = SimulatedOperation.leave(sut, state, toLeave); KeyspaceMetadata ksm = sut.service.metadata().schema.getKeyspaces().get("test").get(); - DataPlacement allSettled = sut.service.metadata().writePlacementAllSettled(ksm); + // Calculate the full set of data placements when all in flight operations have been completed + DataPlacement allSettled = OwnershipUtils.placementsAllSettled(sut.service.metadata()).get(ksm.params.replication); Assert.assertEquals(4, state.inFlightOperations.size()); // make sure none was rejected while (!state.inFlightOperations.isEmpty()) { state = state.inFlightOperations.get(random.nextInt(state.inFlightOperations.size())).advance(state); - Assert.assertTrue(allSettled.equivalentTo(sut.service.metadata().writePlacementAllSettled(ksm))); + // for every node, ask ClusterMetadata for local ranges after all operations are complete. These + // should not change as we progress + for (Node n : state.currentNodes) + { + RangesAtEndpoint localRanges = ClusterMetadata.placementsAllSettledForNode(n.nodeId(), sut.service.metadata()) + .get(ksm.params.replication) + .writes + .byEndpoint() + .get(n.addr()); + Assert.assertEquals(localRanges, allSettled.writes.byEndpoint().get(n.addr())); + } validatePlacements(sut, state); } + // Finally verify that the predicted placements match the actual ones Assert.assertTrue(allSettled.equivalentTo(sut.service.metadata().placements.get(ksm.params.replication))); } } diff --git a/test/microbench/org/apache/cassandra/test/microbench/LocalRangesAllSettledBench.java b/test/microbench/org/apache/cassandra/test/microbench/LocalRangesAllSettledBench.java new file mode 100644 index 000000000000..8b5917b420e8 --- /dev/null +++ b/test/microbench/org/apache/cassandra/test/microbench/LocalRangesAllSettledBench.java @@ -0,0 +1,143 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.cassandra.test.microbench; + +import java.io.FileInputStream; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.zip.GZIPInputStream; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; + +import org.apache.cassandra.Util; +import org.apache.cassandra.config.DatabaseDescriptor; +import org.apache.cassandra.dht.Murmur3Partitioner; +import org.apache.cassandra.distributed.test.log.ClusterMetadataTestHelper; +import org.apache.cassandra.io.util.DataInputPlus.DataInputStreamPlus; +import org.apache.cassandra.locator.InetAddressAndPort; +import org.apache.cassandra.locator.RangesAtEndpoint; +import org.apache.cassandra.schema.KeyspaceMetadata; +import org.apache.cassandra.tcm.ClusterMetadata; +import org.apache.cassandra.tcm.ClusterMetadataService; +import org.apache.cassandra.tcm.ownership.DataPlacement; +import org.apache.cassandra.tcm.ownership.DataPlacements; +import org.apache.cassandra.tcm.ownership.OwnershipUtils; +import org.apache.cassandra.tcm.serialization.VerboseMetadataSerializer; +import org.apache.cassandra.utils.FBUtilities; + +@State(Scope.Benchmark) +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@Fork(value = 1) +@Warmup(iterations = 5, timeUnit = TimeUnit.MILLISECONDS, time = 5000) +@Measurement(iterations = 5, timeUnit = TimeUnit.MILLISECONDS, time = 5000) +public class LocalRangesAllSettledBench +{ + static ClusterMetadata metadata; + @Setup(Level.Trial) + public void setup() throws Exception + { + DatabaseDescriptor.daemonInitialization(); + DatabaseDescriptor.setPartitionerUnsafe(Murmur3Partitioner.instance); + ClusterMetadataService.setInstance(ClusterMetadataTestHelper.syncInstanceForTest()); + metadata = metadata(); + } + + @Benchmark + public void benchLocalRangesOnlyWithRelevantMSOs() + { + Map settledByKeyspace = new HashMap<>(); + metadata.unsafeClearLocalRangesAllSettled(); + // This peer is involved in a MOVE operation + InetAddressAndPort local = InetAddressAndPort.getByNameUnchecked("10.10.9.129:7000"); + FBUtilities.setBroadcastInetAddressAndPort(local); + for (KeyspaceMetadata ksm : metadata.schema.getKeyspaces()) + settledByKeyspace.put(ksm, metadata.localWriteRangesAllSettled(ksm)); + } + + @Benchmark + public void benchLocalRangesOnlyNoRelevantMSOs() + { + Map settledByKeyspace = new HashMap<>(); + metadata.unsafeClearLocalRangesAllSettled(); + // This peer has no involvement in any in-flight MSOs + InetAddressAndPort local = InetAddressAndPort.getByNameUnchecked("10.10.14.13:7000"); + FBUtilities.setBroadcastInetAddressAndPort(local); + for (KeyspaceMetadata ksm : metadata.schema.getKeyspaces()) + settledByKeyspace.put(ksm, metadata.localWriteRangesAllSettled(ksm)); + } + + @Benchmark + public void benchPlacementsAllSettled() + { + // Emulates the previous implementation of ClusterMetadata::writePlacementsAllSettled + // which would be lazily computed during on first access. + // As this fully applies all in-flight MSOs to derive the final settled placements, + // the local broadcast address is not significant. + DataPlacements placementAllSettled = OwnershipUtils.placementsAllSettled(metadata); + Map settledByKeyspace = new HashMap<>(); + for (KeyspaceMetadata ksm : metadata.schema.getKeyspaces()) + { + DataPlacement placement = placementAllSettled.get(ksm.params.replication); + settledByKeyspace.put(ksm, placement.writes.byEndpoint().get(FBUtilities.getBroadcastAddressAndPort())); + } + } + + public ClusterMetadata metadata() throws Exception + { + Path p = Path.of(this.getClass().getClassLoader().getResource("cluster_metadata/CASSANDRA-21144_clustermetadata.gz").toURI()); + try (DataInputStreamPlus in = Util.DataInputStreamPlusImpl.wrap(new GZIPInputStream(new FileInputStream(p.toFile())))) + { + ClusterMetadata metadata = VerboseMetadataSerializer.deserialize(ClusterMetadata.serializer, in); + return metadata; + } + } + + public static void main(String[] args) throws Exception + { + Options options = new OptionsBuilder() + .include(LocalRangesAllSettledBench.class.getSimpleName()) + .build(); + new Runner(options).run(); + } +/* +$ ant microbench -Dbenchmark.name=LocalRangesAllSettledBench + + [java] Benchmark Mode Cnt Score Error Units + [java] LocalRangesAllSettledBench.benchLocalRangesOnlyNoRelevantMSOs avgt 5 18.214 ± 4.350 ms/op + [java] LocalRangesAllSettledBench.benchLocalRangesOnlyWithRelevantMSOs avgt 5 274.931 ± 14.193 ms/op + [java] LocalRangesAllSettledBench.benchPlacementsAllSettled avgt 5 11465.778 ± 370.754 ms/op + + */ +} diff --git a/test/resources/cluster_metadata/CASSANDRA-21144_clustermetadata.gz b/test/resources/cluster_metadata/CASSANDRA-21144_clustermetadata.gz new file mode 100644 index 0000000000000000000000000000000000000000..0bc749ed5b95ac85470506f31b034715dc5f6155 GIT binary patch literal 1402477 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%0D((fVO(W(Lp?U<8}e(u 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