From 9f60c55ba42ff56aa58c3790b9c55924c4deedf4 Mon Sep 17 00:00:00 2001 From: T Jake Luciani Date: Thu, 1 May 2014 09:47:22 -0400 Subject: [PATCH 1/3] Support consistent range movements. patch by tjake; reviewed by thobbs for CASSANDRA-2434 --- CHANGES.txt | 1 + NEWS.txt | 5 ++ .../apache/cassandra/dht/BootStrapper.java | 2 +- .../apache/cassandra/dht/RangeStreamer.java | 81 ++++++++++++++++++- .../cassandra/service/StorageService.java | 44 +++++++++- 5 files changed, 127 insertions(+), 6 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 34533cc257..be72ad1b25 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -56,6 +56,7 @@ * Optimize cellname comparison (CASSANDRA-6934) * Native protocol v3 (CASSANDRA-6855) * Optimize Cell liveness checks and clean up Cell (CASSANDRA-7119) + * Support consistent range movements (CASSANDRA-2434) Merged from 2.0: * Allow overriding cassandra-rackdc.properties file (CASSANDRA-7072) * Set JMX RMI port to 7199 (CASSANDRA-7087) diff --git a/NEWS.txt b/NEWS.txt index 86c6f64c27..5d59460654 100644 --- a/NEWS.txt +++ b/NEWS.txt @@ -30,6 +30,11 @@ New features repair session. Use nodetool repair -par -inc to use this feature. A tool to manually mark/unmark sstables as repaired is available in tools/bin/sstablerepairedset. + - Bootstrapping now ensures that range movements are consistent, + meaning the data for the new node is taken from the node that is no + longer a responsible for that range of keys. + If you want the old behavior (due to a lost node perhaps) + you can set the following property (-Dconsistent.rangemovement=false) Upgrading --------- diff --git a/src/java/org/apache/cassandra/dht/BootStrapper.java b/src/java/org/apache/cassandra/dht/BootStrapper.java index 343748bbd2..cbbd100cce 100644 --- a/src/java/org/apache/cassandra/dht/BootStrapper.java +++ b/src/java/org/apache/cassandra/dht/BootStrapper.java @@ -63,7 +63,7 @@ public class BootStrapper if (logger.isDebugEnabled()) logger.debug("Beginning bootstrap process"); - RangeStreamer streamer = new RangeStreamer(tokenMetadata, address, "Bootstrap"); + RangeStreamer streamer = new RangeStreamer(tokenMetadata, tokens, address, "Bootstrap"); streamer.addSourceFilter(new RangeStreamer.FailureDetectorSourceFilter(FailureDetector.instance)); for (String keyspaceName : Schema.instance.getNonSystemKeyspaces()) diff --git a/src/java/org/apache/cassandra/dht/RangeStreamer.java b/src/java/org/apache/cassandra/dht/RangeStreamer.java index 7ab39a49d5..2308d304b3 100644 --- a/src/java/org/apache/cassandra/dht/RangeStreamer.java +++ b/src/java/org/apache/cassandra/dht/RangeStreamer.java @@ -23,6 +23,8 @@ import java.util.*; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; +import com.google.common.collect.Sets; +import org.apache.cassandra.gms.EndpointState; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -30,6 +32,7 @@ import org.slf4j.LoggerFactory; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.db.Keyspace; import org.apache.cassandra.gms.FailureDetector; +import org.apache.cassandra.gms.Gossiper; import org.apache.cassandra.gms.IFailureDetector; import org.apache.cassandra.locator.AbstractReplicationStrategy; import org.apache.cassandra.locator.IEndpointSnitch; @@ -44,7 +47,8 @@ import org.apache.cassandra.utils.FBUtilities; public class RangeStreamer { private static final Logger logger = LoggerFactory.getLogger(RangeStreamer.class); - + public static final boolean useStrictConsistency = Boolean.valueOf(System.getProperty("consistent.rangemovement","true")); + private final Collection tokens; private final TokenMetadata metadata; private final InetAddress address; private final String description; @@ -99,9 +103,19 @@ public class RangeStreamer } } + public RangeStreamer(TokenMetadata metadata, Collection tokens, InetAddress address, String description) + { + this.metadata = metadata; + this.tokens = tokens; + this.address = address; + this.description = description; + this.streamPlan = new StreamPlan(description); + } + public RangeStreamer(TokenMetadata metadata, InetAddress address, String description) { this.metadata = metadata; + this.tokens = null; this.address = address; this.description = description; this.streamPlan = new StreamPlan(description); @@ -114,11 +128,12 @@ public class RangeStreamer public void addRanges(String keyspaceName, Collection> ranges) { - Multimap, InetAddress> rangesForKeyspace = getAllRangesWithSourcesFor(keyspaceName, ranges); + Multimap, InetAddress> rangesForKeyspace = useStrictConsistency && tokens != null + ? getAllRangesWithStrictSourcesFor(keyspaceName, ranges) : getAllRangesWithSourcesFor(keyspaceName, ranges); if (logger.isDebugEnabled()) { - for (Map.Entry, InetAddress> entry: rangesForKeyspace.entries()) + for (Map.Entry, InetAddress> entry : rangesForKeyspace.entries()) logger.debug(String.format("%s: range %s exists on %s", description, entry.getKey(), entry.getValue())); } @@ -162,6 +177,66 @@ public class RangeStreamer return rangeSources; } + /** + * Get a map of all ranges and the source that will be cleaned up once this bootstrapped node is added for the given ranges. + * For each range, the list should only contain a single source. This allows us to consistently migrate data without violating + * consistency. + */ + private Multimap, InetAddress> getAllRangesWithStrictSourcesFor(String table, Collection> desiredRanges) + { + + assert tokens != null; + AbstractReplicationStrategy strat = Keyspace.open(table).getReplicationStrategy(); + + //Active ranges + TokenMetadata metadataClone = metadata.cloneOnlyTokenMap(); + Multimap,InetAddress> addressRanges = strat.getRangeAddresses(metadataClone); + + //Pending ranges + metadataClone.updateNormalTokens(tokens, address); + Multimap,InetAddress> pendingRangeAddresses = strat.getRangeAddresses(metadataClone); + + //Collects the source that will have its range moved to the new node + Multimap, InetAddress> rangeSources = ArrayListMultimap.create(); + + for (Range desiredRange : desiredRanges) + { + for (Map.Entry, Collection> preEntry : addressRanges.asMap().entrySet()) + { + if (preEntry.getKey().contains(desiredRange)) + { + Set oldEndpoints = Sets.newHashSet(preEntry.getValue()); + Set newEndpoints = Sets.newHashSet(pendingRangeAddresses.get(desiredRange)); + + //Due to CASSANDRA-5953 we can have a higher RF then we have endpoints. + //So we need to be careful to only be strict when endpoints == RF + if (oldEndpoints.size() == strat.getReplicationFactor()) + { + oldEndpoints.removeAll(newEndpoints); + assert oldEndpoints.size() == 1 : "Expected 1 endpoint but found " + oldEndpoints.size(); + } + + rangeSources.put(desiredRange, oldEndpoints.iterator().next()); + } + } + + //Validate + Collection addressList = rangeSources.get(desiredRange); + if (addressList == null || addressList.isEmpty()) + throw new IllegalStateException("No sources found for " + desiredRange); + + if (addressList.size() > 1) + throw new IllegalStateException("Multiple endpoints found for " + desiredRange); + + InetAddress sourceIp = addressList.iterator().next(); + EndpointState sourceState = Gossiper.instance.getEndpointStateForEndpoint(sourceIp); + if (Gossiper.instance.isEnabled() && (sourceState == null || !sourceState.isAlive())) + throw new RuntimeException("A node required to move the data consistently is down ("+sourceIp+"). If you wish to move the data from a potentially inconsistent replica, restart the node with -Dconsistent.rangemovement=false"); + } + + return rangeSources; + } + /** * @param rangesWithSources The ranges we want to fetch (key) and their potential sources (value) * @param sourceFilters A (possibly empty) collection of source filters to apply. In addition to any filters given diff --git a/src/java/org/apache/cassandra/service/StorageService.java b/src/java/org/apache/cassandra/service/StorageService.java index 25a3670c0d..85c080e655 100644 --- a/src/java/org/apache/cassandra/service/StorageService.java +++ b/src/java/org/apache/cassandra/service/StorageService.java @@ -3217,7 +3217,7 @@ public class StorageService extends NotificationBroadcasterSupport implements IE // getting collection of the currently used ranges by this keyspace Collection> currentRanges = getRangesForEndpoint(keyspace, localAddress); // collection of ranges which this node will serve after move to the new token - Collection> updatedRanges = strategy.getPendingAddressRanges(tokenMetadata, newToken, localAddress); + Collection> updatedRanges = strategy.getPendingAddressRanges(tokenMetaClone, newToken, localAddress); // ring ranges and endpoints associated with them // this used to determine what nodes should we ping about range data @@ -3237,11 +3237,51 @@ public class StorageService extends NotificationBroadcasterSupport implements IE { if (range.contains(toFetch)) { - List endpoints = snitch.getSortedListByProximity(localAddress, rangeAddresses.get(range)); + List endpoints = null; + + if (RangeStreamer.useStrictConsistency) + { + Set oldEndpoints = Sets.newHashSet(rangeAddresses.get(range)); + Set newEndpoints = Sets.newHashSet(strategy.calculateNaturalEndpoints(toFetch.right, tokenMetaCloneAllSettled)); + + //Due to CASSANDRA-5953 we can have a higher RF then we have endpoints. + //So we need to be careful to only be strict when endpoints == RF + if (oldEndpoints.size() == strategy.getReplicationFactor()) + { + oldEndpoints.removeAll(newEndpoints); + + //No relocation required + if (oldEndpoints.isEmpty()) + continue; + + assert oldEndpoints.size() == 1 : "Expected 1 endpoint but found " + oldEndpoints.size(); + } + + endpoints = Lists.newArrayList(oldEndpoints.iterator().next()); + } + else + { + endpoints = snitch.getSortedListByProximity(localAddress, rangeAddresses.get(range)); + } + // storing range and preferred endpoint set rangesToFetchWithPreferredEndpoints.putAll(toFetch, endpoints); } } + + Collection addressList = rangesToFetchWithPreferredEndpoints.get(toFetch); + if (addressList == null || addressList.isEmpty()) + continue; + + if (RangeStreamer.useStrictConsistency) + { + if (addressList.size() > 1) + throw new IllegalStateException("Multiple strict sources found for " + toFetch); + + InetAddress sourceIp = addressList.iterator().next(); + if (Gossiper.instance.isEnabled() && !Gossiper.instance.getEndpointStateForEndpoint(sourceIp).isAlive()) + throw new RuntimeException("A node required to move the data consistently is down ("+sourceIp+"). If you wish to move the data from a potentially inconsistent replica, restart the node with -Dconsistent.rangemovement=false"); + } } // calculating endpoints to stream current ranges to if needed From 48727b4ccb3930d4cd56c1fde3784f7e862a2f94 Mon Sep 17 00:00:00 2001 From: Sylvain Lebresne Date: Thu, 1 May 2014 16:40:15 +0200 Subject: [PATCH 2/3] Update versions and add licenses for 2.1-beta2 release --- .rat-excludes | 1 + build.xml | 2 +- conf/logback-tools.xml | 19 +++++++++++++++++++ conf/logback.xml | 19 +++++++++++++++++++ debian/changelog | 6 ++++++ .../io/util/ChecksummedSequentialWriter.java | 17 +++++++++++++++++ test/conf/logback-test.xml | 19 +++++++++++++++++++ .../stress/settings/OptionCompaction.java | 17 +++++++++++++++++ 8 files changed, 99 insertions(+), 1 deletion(-) diff --git a/.rat-excludes b/.rat-excludes index 871f5dff62..0da5ab9f97 100644 --- a/.rat-excludes +++ b/.rat-excludes @@ -5,6 +5,7 @@ debian/** **/.project **/.pydevproject CHANGES.txt +README.asc .git/** **/*.json **/*.patch diff --git a/build.xml b/build.xml index ba29b378e8..36b999894a 100644 --- a/build.xml +++ b/build.xml @@ -25,7 +25,7 @@ - + diff --git a/conf/logback-tools.xml b/conf/logback-tools.xml index c472ae483d..ade6c12d86 100644 --- a/conf/logback-tools.xml +++ b/conf/logback-tools.xml @@ -1,3 +1,22 @@ + + diff --git a/conf/logback.xml b/conf/logback.xml index 2657174199..61e5a1341e 100644 --- a/conf/logback.xml +++ b/conf/logback.xml @@ -1,3 +1,22 @@ + + diff --git a/debian/changelog b/debian/changelog index 11f25cb854..4f880a6385 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +cassandra (2.1.0~beta2) unstable; urgency=medium + + * New beta release + + -- Sylvain Lebresne Thu, 01 May 2014 16:39:21 +0200 + cassandra (2.1.0~beta1) unstable; urgency=low * New beta release diff --git a/src/java/org/apache/cassandra/io/util/ChecksummedSequentialWriter.java b/src/java/org/apache/cassandra/io/util/ChecksummedSequentialWriter.java index 98492da262..b95bf32bcf 100644 --- a/src/java/org/apache/cassandra/io/util/ChecksummedSequentialWriter.java +++ b/src/java/org/apache/cassandra/io/util/ChecksummedSequentialWriter.java @@ -1,3 +1,20 @@ +/* + * 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.io.util; import java.io.File; diff --git a/test/conf/logback-test.xml b/test/conf/logback-test.xml index 89caa2f886..a7aa34c63f 100644 --- a/test/conf/logback-test.xml +++ b/test/conf/logback-test.xml @@ -1,3 +1,22 @@ + + ./build/test/logs/system.log diff --git a/tools/stress/src/org/apache/cassandra/stress/settings/OptionCompaction.java b/tools/stress/src/org/apache/cassandra/stress/settings/OptionCompaction.java index da74e430e8..11d5403a19 100644 --- a/tools/stress/src/org/apache/cassandra/stress/settings/OptionCompaction.java +++ b/tools/stress/src/org/apache/cassandra/stress/settings/OptionCompaction.java @@ -1,3 +1,20 @@ +/* + * 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.stress.settings; import java.util.Arrays; From d5a9c9254ff418541f22dcb335ca48b139d22ff5 Mon Sep 17 00:00:00 2001 From: Sylvain Lebresne Date: Thu, 1 May 2014 17:59:35 +0200 Subject: [PATCH 3/3] Update debian packaging following README extension change --- debian/rules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/rules b/debian/rules index 11b78e7b6a..7261aa03dd 100755 --- a/debian/rules +++ b/debian/rules @@ -60,7 +60,7 @@ binary-indep: build install dh_testroot dh_installchangelogs dh_installinit -u'start 50 2 3 4 5 . stop 50 0 1 6 .' - dh_installdocs README.txt CHANGES.txt NEWS.txt + dh_installdocs README.asc CHANGES.txt NEWS.txt dh_compress dh_fixperms dh_installdeb