From 4454ab86994b486a399937158ffbaa2837846af9 Mon Sep 17 00:00:00 2001 From: Sam Tunnicliffe Date: Tue, 17 Mar 2026 11:11:22 +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 Patch by Sam Tunnicliffe; reviewed by Marcus Eriksson for CASSANDRA-21144 --- CHANGES.txt | 1 + .../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 | 19 +- .../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 +- 25 files changed, 805 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} (63%) 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/CHANGES.txt b/CHANGES.txt index 5c0ff70a6e..f9c6004d36 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ 5.1 + * Improve performance when calculating settled placements during range movements (CASSANDRA-21144) * Make shadow gossip round parameters configurable for testing (CASSANDRA-21149) * Avoid potential gossip thread deadlock during decommission (CASSANDRA-21143) * Improve construction of consensus groups for range movements (CASSANDRA-21142) diff --git a/src/java/org/apache/cassandra/db/DiskBoundaryManager.java b/src/java/org/apache/cassandra/db/DiskBoundaryManager.java index 1b35423fe9..e1059f485e 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.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.ownership.DataPlacement; -import org.apache.cassandra.utils.FBUtilities; public class DiskBoundaryManager { @@ -143,22 +141,17 @@ public class DiskBoundaryManager 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 bac2342c12..424df75b97 100644 --- a/src/java/org/apache/cassandra/service/StorageService.java +++ b/src/java/org/apache/cassandra/service/StorageService.java @@ -418,7 +418,7 @@ public class StorageService extends NotificationBroadcasterSupport implements IE 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 fb92038cbf..660dca619b 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 @@ public class PaxosCleanup extends AsyncFuture implements Runnable 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 fa2d8f5ee5..4a5f67c1fc 100644 --- a/src/java/org/apache/cassandra/tcm/ClusterMetadata.java +++ b/src/java/org/apache/cassandra/tcm/ClusterMetadata.java @@ -32,6 +32,7 @@ import java.util.Set; 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.EndpointsForToken; 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.NodeAddresses; 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 class ClusterMetadata 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); + + NodeId localId = myNodeId(); + synchronized (this) { - ClusterMetadata metadata = this; - Iterator> iter = metadata.inProgressSequences.iterator(); - while (iter.hasNext()) + if (localRangesAllSettled != null) + return localRangesAllSettled.getOrDefault(ksm.params.replication, EMPTY_LOCAL_RANGES); + + Map builder = Maps.newHashMapWithExpectedSize(this.placements.size()); + DataPlacements settled = placementsAllSettledForNode(localId); + 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 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 DataPlacements placementsAllSettledForNode(NodeId peer) + { + Iterator> iter = inProgressSequences.iterator(); + ClusterMetadata metadata = this; + 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 class ClusterMetadata 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 7083f27b64..03164243b7 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 @@ import org.apache.cassandra.tcm.serialization.MetadataSerializer; */ 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 abstract class MultiStepOperation */ 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 9660bd255a..38fcdfde96 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 class Delta 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 6ba80a854b..760cd0243f 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 @@ package org.apache.cassandra.tcm.ownership; 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 class PlacementDeltas extends ReplicationMap 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 0d5ee2f067..ec516854a0 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.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.NodeId; import org.apache.cassandra.tcm.serialization.AsymmetricMetadataSerializer; import org.apache.cassandra.tcm.serialization.MetadataSerializer; @@ -125,6 +126,12 @@ public class AddToCMS extends MultiStepOperation 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 b410097f84..d2909bd873 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 @@ package org.apache.cassandra.tcm.sequences; 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.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.NodeId; import org.apache.cassandra.tcm.membership.NodeState; import org.apache.cassandra.tcm.ownership.DataPlacement; @@ -185,6 +187,16 @@ public class BootstrapAndJoin extends MultiStepOperation 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 cb34179d44..32afe23491 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.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.NodeId; import org.apache.cassandra.tcm.membership.NodeState; import org.apache.cassandra.tcm.ownership.DataPlacement; @@ -180,6 +181,16 @@ public class BootstrapAndReplace extends MultiStepOperation 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 0a3512296a..c95fe94435 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 @@ package org.apache.cassandra.tcm.sequences; 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.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.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 class DropAccordTable extends MultiStepOperation 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 a26d6ded45..a7f316e38f 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.gms.IFailureDetector; 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.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.NodeId; import org.apache.cassandra.tcm.membership.NodeState; import org.apache.cassandra.tcm.ownership.DataPlacements; @@ -194,6 +196,16 @@ public class Move extends MultiStepOperation 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 ce2daf6b46..39613ce7b8 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.MetadataKey; 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 class ReconfigureCMS extends MultiStepOperation 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 83d543a8dd..da8ae74e6a 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.config.DatabaseDescriptor; 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 class UnbootstrapAndLeave extends MultiStepOperation 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 2a1eaba7e0..10f48609d4 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 abstract class ApplyPlacementDeltas implements Transformation 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 063f94857c..1598c5ca89 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 class ClusterMetadataTestHelper { 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 class ClusterMetadataTestHelper 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 class ClusterMetadataTestHelper 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 82e864b3ef..b13cab2660 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.harry.model.TokenPlacementModel; 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.NodeId; 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,27 @@ public class MetadataChangeSimulationTest extends CMSTestBase 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 = sut.service.metadata() + .placementsAllSettledForNode(n.nodeId()) + .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 0000000000..8b5917b420 --- /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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