getSpace)
{
- return PathUtils.tryGetSpace(new File(dir).toPath(), getSpace, e -> { throw new ConfigurationException("Unable check disk space in '" + dir + "'. Perhaps the Cassandra user does not have the necessary permissions"); });
+ return PathUtils.tryGetSpace(new File(dir).toPath(), getSpace, e -> {
+ throw new ConfigurationException("Unable check disk space in '" + dir + "'. Perhaps the Cassandra user does not have the necessary permissions");
+ });
}
public static IEndpointSnitch createEndpointSnitch(boolean dynamic, String snitchClassName) throws ConfigurationException
@@ -1546,6 +1567,7 @@ public class DatabaseDescriptor
{
DatabaseDescriptor.cryptoProvider = cryptoProvider;
}
+
public static IAuthenticator getAuthenticator()
{
return authenticator;
@@ -1618,7 +1640,7 @@ public class DatabaseDescriptor
{
int defaultCidrGroupsCacheRefreshInterval = 5; // mins
- if (conf.cidr_authorizer == null || conf.cidr_authorizer.parameters == null)
+ if (conf.cidr_authorizer == null || conf.cidr_authorizer.parameters == null)
return defaultCidrGroupsCacheRefreshInterval;
String cidrGroupsCacheRefreshInterval = conf.cidr_authorizer.parameters.get("cidr_groups_cache_refresh_interval");
@@ -1632,7 +1654,7 @@ public class DatabaseDescriptor
{
int defaultIpCacheMaxSize = 100;
- if (conf.cidr_authorizer == null || conf.cidr_authorizer.parameters == null)
+ if (conf.cidr_authorizer == null || conf.cidr_authorizer.parameters == null)
return defaultIpCacheMaxSize;
String ipCacheMaxSize = conf.cidr_authorizer.parameters.get("ip_cache_max_size");
@@ -1675,8 +1697,8 @@ public class DatabaseDescriptor
public static int getPermissionsUpdateInterval()
{
return conf.permissions_update_interval == null
- ? conf.permissions_validity.toMilliseconds()
- : conf.permissions_update_interval.toMilliseconds();
+ ? conf.permissions_validity.toMilliseconds()
+ : conf.permissions_update_interval.toMilliseconds();
}
public static void setPermissionsUpdateInterval(int updateInterval)
@@ -1720,8 +1742,8 @@ public class DatabaseDescriptor
public static int getRolesUpdateInterval()
{
return conf.roles_update_interval == null
- ? conf.roles_validity.toMilliseconds()
- : conf.roles_update_interval.toMilliseconds();
+ ? conf.roles_validity.toMilliseconds()
+ : conf.roles_update_interval.toMilliseconds();
}
public static void setRolesCacheActiveUpdate(boolean update)
@@ -1852,7 +1874,7 @@ public class DatabaseDescriptor
}
catch (ConfigurationException e)
{
- throw new IllegalArgumentException("Bad configuration; unable to start server: "+e.getMessage());
+ throw new IllegalArgumentException("Bad configuration; unable to start server: " + e.getMessage());
}
catch (FSWriteError e)
{
@@ -1872,6 +1894,13 @@ public class DatabaseDescriptor
/* For tests ONLY, don't use otherwise or all hell will break loose. Tests should restore value at the end. */
public static IPartitioner setPartitionerUnsafe(IPartitioner newPartitioner)
+ {
+ IPartitioner old = setOnlyPartitionerUnsafe(newPartitioner);
+ StorageService.instance.valueFactory = new VersionedValue.VersionedValueFactory(partitioner);
+ return old;
+ }
+
+ public static IPartitioner setOnlyPartitionerUnsafe(IPartitioner newPartitioner)
{
IPartitioner old = partitioner;
partitioner = newPartitioner;
@@ -1882,6 +1911,7 @@ public class DatabaseDescriptor
{
return snitch;
}
+
public static void setEndpointSnitch(IEndpointSnitch eps)
{
snitch = eps;
@@ -1909,7 +1939,7 @@ public class DatabaseDescriptor
public static void setColumnIndexSizeInKiB(int val)
{
- conf.column_index_size = val != -1 ? createIntKibibyteBoundAndEnsureItIsValidForByteConversion(val,"column_index_size") : null;
+ conf.column_index_size = val != -1 ? createIntKibibyteBoundAndEnsureItIsValidForByteConversion(val, "column_index_size") : null;
}
public static int getColumnIndexCacheSize()
@@ -1924,7 +1954,7 @@ public class DatabaseDescriptor
public static void setColumnIndexCacheSize(int val)
{
- conf.column_index_cache_size = createIntKibibyteBoundAndEnsureItIsValidForByteConversion(val,"column_index_cache_size");
+ conf.column_index_cache_size = createIntKibibyteBoundAndEnsureItIsValidForByteConversion(val, "column_index_cache_size");
}
public static int getBatchSizeWarnThreshold()
@@ -1954,7 +1984,7 @@ public class DatabaseDescriptor
public static void setBatchSizeWarnThresholdInKiB(int threshold)
{
- conf.batch_size_warn_threshold = createIntKibibyteBoundAndEnsureItIsValidForByteConversion(threshold,"batch_size_warn_threshold");
+ conf.batch_size_warn_threshold = createIntKibibyteBoundAndEnsureItIsValidForByteConversion(threshold, "batch_size_warn_threshold");
}
public static void setBatchSizeFailThresholdInKiB(int threshold)
@@ -2021,7 +2051,8 @@ public class DatabaseDescriptor
try
{
return UUID.fromString(REPLACE_NODE.getString());
- } catch (NullPointerException e)
+ }
+ catch (NullPointerException e)
{
return null;
}
@@ -2378,7 +2409,7 @@ public class DatabaseDescriptor
public static void setStreamThroughputOutboundMebibytesPerSecAsInt(int value)
{
if (MEBIBYTES_PER_SECOND.toMegabitsPerSecond(value) >= Integer.MAX_VALUE)
- throw new IllegalArgumentException("stream_throughput_outbound: " + value +
+ throw new IllegalArgumentException("stream_throughput_outbound: " + value +
" is too large; it should be less than " +
Integer.MAX_VALUE + " in megabits/s");
@@ -2502,10 +2533,10 @@ public class DatabaseDescriptor
public static String[] getLocalSystemKeyspacesDataFileLocations()
{
if (useSpecificLocationForLocalSystemData())
- return new String[] {conf.local_system_data_file_directory};
+ return new String[]{ conf.local_system_data_file_directory };
- return conf.data_file_directories.length == 0 ? conf.data_file_directories
- : new String[] {conf.data_file_directories[0]};
+ return conf.data_file_directories.length == 0 ? conf.data_file_directories
+ : new String[]{ conf.data_file_directories[0] };
}
/**
@@ -2562,11 +2593,11 @@ public class DatabaseDescriptor
conf.flush_compression = compression;
}
- /**
- * Maximum number of buffers in the compression pool. The default value is 3, it should not be set lower than that
- * (one segment in compression, one written to, one in reserve); delays in compression may cause the log to use
- * more, depending on how soon the sync policy stops all writing threads.
- */
+ /**
+ * Maximum number of buffers in the compression pool. The default value is 3, it should not be set lower than that
+ * (one segment in compression, one written to, one in reserve); delays in compression may cause the log to use
+ * more, depending on how soon the sync policy stops all writing threads.
+ */
public static int getCommitLogMaxCompressionBuffersInPool()
{
return conf.commitlog_max_compression_buffers_in_pool;
@@ -2634,6 +2665,7 @@ public class DatabaseDescriptor
* Update commitlog_segment_size in the tests.
* {@link CommitLogSegmentManagerCDC} uses the CommitLogSegmentSize to estimate the file size on allocation.
* It is important to keep the value unchanged for the estimation to be correct.
+ *
* @param sizeMebibytes
*/
@VisibleForTesting /* Only for testing */
@@ -2729,7 +2761,7 @@ public class DatabaseDescriptor
* refer to it as native address although some places still call it RPC address. It's not thrift RPC anymore
* so native is more appropriate. The address alone is not enough to uniquely identify this instance because
* multiple instances might use the same interface with different ports.
- *
+ *
* May be null, please use {@link FBUtilities#getBroadcastNativeAddressAndPort()} instead.
*/
public static InetAddress getBroadcastRpcAddress()
@@ -2829,8 +2861,8 @@ public class DatabaseDescriptor
}
/**
- * This is the port used with RPC address for the native protocol to communicate with clients. Now that thrift RPC
- * is no longer in use there is no RPC port.
+ * This is the port used with RPC address for the native protocol to communicate with clients. Now that thrift RPC
+ * is no longer in use there is no RPC port.
*/
public static int getNativeTransportPort()
{
@@ -2872,7 +2904,7 @@ public class DatabaseDescriptor
/**
* If this value is set to <= 0 it will move auth requests to the standard request pool regardless of the current
* size of the {@link org.apache.cassandra.transport.Dispatcher#authExecutor}'s active size.
- *
+ *
* see {@link org.apache.cassandra.transport.Dispatcher#dispatch} for executor selection
*/
public static void setNativeTransportMaxAuthThreads(int threads)
@@ -3122,7 +3154,7 @@ public class DatabaseDescriptor
{
DurationSpec.IntMillisecondsBound blockMillis = conf.periodic_commitlog_sync_lag_block;
return blockMillis == null
- ? (long)(getCommitLogSyncPeriod() * 1.5)
+ ? (long) (getCommitLogSyncPeriod() * 1.5)
: blockMillis.toMilliseconds();
}
@@ -3211,6 +3243,7 @@ public class DatabaseDescriptor
{
conf.auto_snapshot = autoSnapshot;
}
+
@VisibleForTesting
public static boolean getAutoSnapshot()
{
@@ -3314,7 +3347,7 @@ public class DatabaseDescriptor
public static File getSerializedCachePath(CacheType cacheType, String version, String extension)
{
String name = cacheType.toString()
- + (version == null ? "" : '-' + version + '.' + extension);
+ + (version == null ? "" : '-' + version + '.' + extension);
return new File(conf.saved_caches_directory, name);
}
@@ -3322,6 +3355,7 @@ public class DatabaseDescriptor
{
return conf.dynamic_snitch_update_interval.toMilliseconds();
}
+
public static void setDynamicUpdateInterval(int dynamicUpdateInterval)
{
conf.dynamic_snitch_update_interval = new DurationSpec.IntMillisecondsBound(dynamicUpdateInterval);
@@ -3331,6 +3365,7 @@ public class DatabaseDescriptor
{
return conf.dynamic_snitch_reset_interval.toMilliseconds();
}
+
public static void setDynamicResetInterval(int dynamicResetInterval)
{
conf.dynamic_snitch_reset_interval = new DurationSpec.IntMillisecondsBound(dynamicResetInterval);
@@ -3399,7 +3434,7 @@ public class DatabaseDescriptor
public static long getMaxHintsFileSize()
{
- return conf.max_hints_file_size.toBytesInLong();
+ return conf.max_hints_file_size.toBytesInLong();
}
public static ParameterizedClass getHintsCompression()
@@ -3513,7 +3548,9 @@ public class DatabaseDescriptor
conf.key_cache_migrate_during_compaction = migrateCacheEntry;
}
- /** This method can return negative number for disabled */
+ /**
+ * This method can return negative number for disabled
+ */
public static int getSSTablePreemptiveOpenIntervalInMiB()
{
if (conf.sstable_preemptive_open_interval == null)
@@ -3521,7 +3558,9 @@ public class DatabaseDescriptor
return conf.sstable_preemptive_open_interval.toMebibytes();
}
- /** Negative number for disabled */
+ /**
+ * Negative number for disabled
+ */
public static void setSSTablePreemptiveOpenIntervalInMiB(int mib)
{
if (mib < 0)
@@ -3809,8 +3848,10 @@ public class DatabaseDescriptor
{
switch (datamodel)
{
- case "64": return true;
- case "32": return false;
+ case "64":
+ return true;
+ case "32":
+ return false;
}
}
String arch = OS_ARCH.getString();
@@ -4073,7 +4114,7 @@ public class DatabaseDescriptor
public static FullQueryLoggerOptions getFullQueryLogOptions()
{
- return conf.full_query_logging_options;
+ return conf.full_query_logging_options;
}
public static void setFullQueryLogOptions(FullQueryLoggerOptions options)
@@ -4572,7 +4613,10 @@ public class DatabaseDescriptor
conf.row_index_read_size_fail_threshold = value;
}
- public static int getDefaultKeyspaceRF() { return conf.default_keyspace_rf; }
+ public static int getDefaultKeyspaceRF()
+ {
+ return conf.default_keyspace_rf;
+ }
public static void setDefaultKeyspaceRF(int value) throws IllegalArgumentException
{
@@ -4665,11 +4709,13 @@ public class DatabaseDescriptor
}
}
- public static DurationSpec.IntSecondsBound getStreamingSlowEventsLogTimeout() {
+ public static DurationSpec.IntSecondsBound getStreamingSlowEventsLogTimeout()
+ {
return conf.streaming_slow_events_log_timeout;
}
- public static void setStreamingSlowEventsLogTimeout(String value) {
+ public static void setStreamingSlowEventsLogTimeout(String value)
+ {
DurationSpec.IntSecondsBound next = new DurationSpec.IntSecondsBound(value);
if (!conf.streaming_slow_events_log_timeout.equals(next))
{
@@ -4785,6 +4831,7 @@ public class DatabaseDescriptor
* both the more evolved cassandra.yaml approach but also the -XX param to override it on a one-off basis so you don't
* have to change the full config of a node or a cluster in order to get a heap dump from a single node that's
* misbehaving.
+ *
* @return the absolute path of the -XX param if provided, else the heap_dump_path in cassandra.yaml
*/
public static Path getHeapDumpPath()
@@ -4911,4 +4958,94 @@ public class DatabaseDescriptor
{
return conf == null ? new RepairRetrySpec() : conf.repair.retries;
}
+
+ public static int getCmsDefaultRetryMaxTries()
+ {
+ return conf.cms_default_max_retries;
+ }
+
+ public static void setCmsDefaultRetryMaxTries(int value)
+ {
+ conf.cms_default_max_retries = value;
+ }
+
+ public static DurationSpec getDefaultRetryBackoff()
+ {
+ return conf.cms_default_retry_backoff;
+ }
+
+ public static DurationSpec getCmsAwaitTimeout()
+ {
+ return conf.cms_await_timeout;
+ }
+
+ public static int getMetadataSnapshotFrequency()
+ {
+ return conf.metadata_snapshot_frequency;
+ }
+
+ public static void setMetadataSnapshotFrequency(int frequency)
+ {
+ conf.metadata_snapshot_frequency = frequency;
+ }
+
+ public static ConsistencyLevel getProgressBarrierMinConsistencyLevel()
+ {
+ return conf.progress_barrier_min_consistency_level;
+ }
+
+ public static void setProgressBarrierMinConsistencyLevel(ConsistencyLevel newLevel)
+ {
+ conf.progress_barrier_min_consistency_level = newLevel;
+ }
+
+ public static boolean getLogOutOfTokenRangeRequests()
+ {
+ return conf.log_out_of_token_range_requests;
+ }
+
+ public static void setLogOutOfTokenRangeRequests(boolean enabled)
+ {
+ conf.log_out_of_token_range_requests = enabled;
+ }
+
+ public static boolean getRejectOutOfTokenRangeRequests()
+ {
+ return conf.reject_out_of_token_range_requests;
+ }
+
+ public static void setRejectOutOfTokenRangeRequests(boolean enabled)
+ {
+ conf.reject_out_of_token_range_requests = enabled;
+ }
+
+ public static ConsistencyLevel getProgressBarrierDefaultConsistencyLevel()
+ {
+ return conf.progress_barrier_default_consistency_level;
+ }
+
+ public static long getProgressBarrierTimeout(TimeUnit unit)
+ {
+ return conf.progress_barrier_timeout.to(unit);
+ }
+
+ public static long getProgressBarrierBackoff(TimeUnit unit)
+ {
+ return conf.progress_barrier_backoff.to(unit);
+ }
+
+ public static void setProgressBarrierTimeout(long timeOutInMillis)
+ {
+ conf.progress_barrier_timeout = new DurationSpec.LongMillisecondsBound(timeOutInMillis);
+ }
+
+ public static void setProgressBarrierBackoff(long timeOutInMillis)
+ {
+ conf.progress_barrier_backoff = new DurationSpec.LongMillisecondsBound(timeOutInMillis);
+ }
+
+ public static boolean getUnsafeTCMMode()
+ {
+ return conf.unsafe_tcm_mode;
+ }
}
diff --git a/src/java/org/apache/cassandra/cql3/QueryProcessor.java b/src/java/org/apache/cassandra/cql3/QueryProcessor.java
index fba424d7d9..c879845266 100644
--- a/src/java/org/apache/cassandra/cql3/QueryProcessor.java
+++ b/src/java/org/apache/cassandra/cql3/QueryProcessor.java
@@ -18,28 +18,63 @@
package org.apache.cassandra.cql3;
import java.nio.ByteBuffer;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
-import com.github.benmanes.caffeine.cache.Cache;
-import com.github.benmanes.caffeine.cache.Caffeine;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Predicate;
-import com.google.common.collect.*;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Iterators;
import com.google.common.primitives.Ints;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.antlr.runtime.*;
+import com.github.benmanes.caffeine.cache.Cache;
+import com.github.benmanes.caffeine.cache.Caffeine;
+import org.antlr.runtime.RecognitionException;
import org.apache.cassandra.concurrent.ImmediateExecutor;
import org.apache.cassandra.concurrent.ScheduledExecutors;
import org.apache.cassandra.config.DatabaseDescriptor;
+import org.apache.cassandra.cql3.functions.Function;
+import org.apache.cassandra.cql3.functions.FunctionName;
+import org.apache.cassandra.cql3.functions.UDAggregate;
+import org.apache.cassandra.cql3.functions.UDFunction;
+import org.apache.cassandra.cql3.selection.ResultSetBuilder;
+import org.apache.cassandra.cql3.statements.BatchStatement;
+import org.apache.cassandra.cql3.statements.ModificationStatement;
+import org.apache.cassandra.cql3.statements.QualifiedStatement;
+import org.apache.cassandra.cql3.statements.SelectStatement;
+import org.apache.cassandra.cql3.statements.schema.AlterSchemaStatement;
+import org.apache.cassandra.db.ConsistencyLevel;
+import org.apache.cassandra.db.DecoratedKey;
+import org.apache.cassandra.db.ReadCommand;
+import org.apache.cassandra.db.ReadQuery;
+import org.apache.cassandra.db.ReadResponse;
+import org.apache.cassandra.db.SinglePartitionReadQuery;
+import org.apache.cassandra.db.SystemKeyspace;
+import org.apache.cassandra.db.marshal.AbstractType;
+import org.apache.cassandra.db.partitions.PartitionIterator;
+import org.apache.cassandra.db.partitions.PartitionIterators;
import org.apache.cassandra.db.partitions.UnfilteredPartitionIterators;
+import org.apache.cassandra.db.rows.Row;
+import org.apache.cassandra.db.rows.RowIterator;
+import org.apache.cassandra.exceptions.CassandraException;
+import org.apache.cassandra.exceptions.InvalidRequestException;
+import org.apache.cassandra.exceptions.IsBootstrappingException;
+import org.apache.cassandra.exceptions.RequestExecutionException;
+import org.apache.cassandra.exceptions.RequestValidationException;
+import org.apache.cassandra.exceptions.SyntaxException;
import org.apache.cassandra.locator.InetAddressAndPort;
+import org.apache.cassandra.metrics.CQLMetrics;
import org.apache.cassandra.metrics.ClientRequestMetrics;
import org.apache.cassandra.metrics.ClientRequestsMetricsHolder;
import org.apache.cassandra.net.Message;
@@ -49,27 +84,20 @@ import org.apache.cassandra.schema.Schema;
import org.apache.cassandra.schema.SchemaChangeListener;
import org.apache.cassandra.schema.SchemaConstants;
import org.apache.cassandra.schema.TableMetadata;
-import org.apache.cassandra.cql3.functions.UDAggregate;
-import org.apache.cassandra.cql3.functions.UDFunction;
-import org.apache.cassandra.cql3.functions.Function;
-import org.apache.cassandra.cql3.functions.FunctionName;
-import org.apache.cassandra.cql3.selection.ResultSetBuilder;
-import org.apache.cassandra.cql3.statements.*;
-import org.apache.cassandra.db.*;
-import org.apache.cassandra.db.rows.Row;
-import org.apache.cassandra.db.rows.RowIterator;
-import org.apache.cassandra.db.partitions.PartitionIterator;
-import org.apache.cassandra.db.partitions.PartitionIterators;
-import org.apache.cassandra.db.marshal.AbstractType;
-import org.apache.cassandra.exceptions.*;
-import org.apache.cassandra.gms.Gossiper;
-import org.apache.cassandra.metrics.CQLMetrics;
-import org.apache.cassandra.service.*;
+import org.apache.cassandra.service.ClientState;
+import org.apache.cassandra.service.QueryState;
+import org.apache.cassandra.service.StorageService;
import org.apache.cassandra.service.pager.QueryPager;
+import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.tracing.Tracing;
import org.apache.cassandra.transport.ProtocolVersion;
import org.apache.cassandra.transport.messages.ResultMessage;
-import org.apache.cassandra.utils.*;
+import org.apache.cassandra.utils.ByteBufferUtil;
+import org.apache.cassandra.utils.CassandraVersion;
+import org.apache.cassandra.utils.FBUtilities;
+import org.apache.cassandra.utils.JVMStabilityInspector;
+import org.apache.cassandra.utils.MD5Digest;
+import org.apache.cassandra.utils.ObjectSizes;
import org.apache.cassandra.utils.concurrent.Future;
import org.apache.cassandra.utils.concurrent.FutureCombiner;
@@ -206,7 +234,6 @@ public class QueryProcessor implements QueryHandler
private QueryProcessor()
{
- Schema.instance.registerListener(new StatementInvalidatingListener());
}
@VisibleForTesting
@@ -273,7 +300,7 @@ public class QueryProcessor implements QueryHandler
private ResultMessage processNodeLocalWrite(CQLStatement statement, QueryState queryState, QueryOptions options)
{
- ClientRequestMetrics levelMetrics = ClientRequestsMetricsHolder.writeMetricsForLevel(ConsistencyLevel.NODE_LOCAL);
+ ClientRequestMetrics levelMetrics = ClientRequestsMetricsHolder.writeMetricsForLevel(ConsistencyLevel.NODE_LOCAL);
ClientRequestMetrics globalMetrics = ClientRequestsMetricsHolder.writeMetrics;
long startTime = nanoTime();
@@ -428,11 +455,16 @@ public class QueryProcessor implements QueryHandler
qualifiedStatement.setKeyspace(clientState);
keyspace = qualifiedStatement.keyspace();
}
-
// Note: if 2 threads prepare the same query, we'll live so don't bother synchronizing
CQLStatement statement = raw.prepare(clientState);
statement.validate(clientState);
+ // Set CQL string for AlterSchemaStatement as this is used to serialize the transformation
+ // in the cluster metadata log
+ if (statement instanceof AlterSchemaStatement)
+ ((AlterSchemaStatement)statement).setCql(query);
+
+
if (isInternal)
return new Prepared(statement, "", fullyQualified, keyspace);
else
@@ -641,7 +673,7 @@ public class QueryProcessor implements QueryHandler
synchronized (this)
{
- CassandraVersion minVersion = Gossiper.instance.getMinVersion(DatabaseDescriptor.getWriteRpcTimeout(TimeUnit.MILLISECONDS), TimeUnit.MILLISECONDS);
+ CassandraVersion minVersion = ClusterMetadata.current().directory.clusterMinVersion.cassandraVersion;
if (minVersion != null && minVersion.compareTo(NEW_PREPARED_STATEMENT_BEHAVIOUR_SINCE_40, true) >= 0)
{
logger.info("Fully upgraded to at least {}", minVersion);
@@ -854,7 +886,13 @@ public class QueryProcessor implements QueryHandler
((QualifiedStatement) statement).setKeyspace(clientState);
Tracing.trace("Preparing statement");
- return statement.prepare(clientState);
+ CQLStatement prepared = statement.prepare(clientState);
+ // Set CQL string for AlterSchemaStatement as this is used to serialize the transformation
+ // in the cluster metadata log
+ if (prepared instanceof AlterSchemaStatement)
+ ((AlterSchemaStatement) prepared).setCql(queryStr);
+
+ return prepared;
}
public static T parseStatement(String queryStr, Class klass, String type) throws SyntaxException
@@ -917,6 +955,11 @@ public class QueryProcessor implements QueryHandler
preparedStatements.asMap().clear();
}
+ public static void registerStatementInvalidatingListener()
+ {
+ Schema.instance.registerListener(new StatementInvalidatingListener());
+ }
+
private static class StatementInvalidatingListener implements SchemaChangeListener
{
private static void removeInvalidPreparedStatements(String ksName, String cfName)
@@ -1019,7 +1062,7 @@ public class QueryProcessor implements QueryHandler
{
// in case there are other overloads, we have to remove all overloads since argument type
// matching may change (due to type casting)
- if (Schema.instance.getKeyspaceMetadata(ksName).userFunctions.get(new FunctionName(ksName, functionName)).size() > 1)
+ if (!Schema.instance.getKeyspaceMetadata(ksName).userFunctions.get(new FunctionName(ksName, functionName)).isEmpty())
removeInvalidPreparedStatementsForFunction(ksName, functionName);
}
@@ -1077,4 +1120,4 @@ public class QueryProcessor implements QueryHandler
removeInvalidPreparedStatementsForFunction(aggregate.name().keyspace, aggregate.name().name);
}
}
-}
+}
\ No newline at end of file
diff --git a/src/java/org/apache/cassandra/cql3/functions/UDAggregate.java b/src/java/org/apache/cassandra/cql3/functions/UDAggregate.java
index 2b15c8d935..161ac8e353 100644
--- a/src/java/org/apache/cassandra/cql3/functions/UDAggregate.java
+++ b/src/java/org/apache/cassandra/cql3/functions/UDAggregate.java
@@ -17,6 +17,7 @@
*/
package org.apache.cassandra.cql3.functions;
+import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.*;
@@ -30,13 +31,20 @@ import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.marshal.UserType;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.exceptions.InvalidRequestException;
+import org.apache.cassandra.io.util.DataInputPlus;
+import org.apache.cassandra.io.util.DataOutputPlus;
+import org.apache.cassandra.tcm.serialization.Version;
+import org.apache.cassandra.schema.CQLTypeParser;
import org.apache.cassandra.schema.Difference;
import org.apache.cassandra.schema.UserFunctions;
+import org.apache.cassandra.schema.Types;
import org.apache.cassandra.tracing.Tracing;
import org.apache.cassandra.transport.ProtocolVersion;
+import org.apache.cassandra.utils.ByteBufferUtil;
import static com.google.common.collect.Iterables.any;
import static com.google.common.collect.Iterables.transform;
+import static org.apache.cassandra.db.TypeSizes.sizeof;
import static org.apache.cassandra.utils.Clock.Global.nanoTime;
/**
@@ -44,6 +52,8 @@ import static org.apache.cassandra.utils.Clock.Global.nanoTime;
*/
public class UDAggregate extends UserFunction implements AggregateFunction
{
+ public static final Serializer serializer = new Serializer();
+
protected static final Logger logger = LoggerFactory.getLogger(UDAggregate.class);
private final UDFDataType stateType;
@@ -375,4 +385,69 @@ public class UDAggregate extends UserFunction implements AggregateFunction
return builder.append(";")
.toString();
}
+
+ // Not quite a MetadataSerializer, or even a UDTAwareMetadataSerializer, as it needs the collection of UDFs during deserialization.
+ public static class Serializer
+ {
+ public void serialize(UDAggregate t, DataOutputPlus out, Version version) throws IOException
+ {
+ out.writeUTF(t.name().keyspace);
+ out.writeUTF(t.name().name);
+ out.writeInt(t.argumentsList().size());
+ for (String arg : t.argumentsList())
+ out.writeUTF(arg);
+ out.writeUTF(t.returnType().asCQL3Type().toString());
+ out.writeUTF(t.stateFunction.name().name);
+ out.writeUTF(t.stateType.toAbstractType().asCQL3Type().toString());
+ out.writeBoolean(t.finalFunction() != null);
+ if (t.finalFunction() != null)
+ out.writeUTF(t.finalFunction().name().name);
+ out.writeBoolean(t.initialCondition() != null);
+ if (t.initialCondition() != null)
+ ByteBufferUtil.writeWithShortLength(t.initialCondition(), out);
+ }
+
+ public UDAggregate deserialize(DataInputPlus in, Types types, Collection functions, Version version) throws IOException
+ {
+ String ks = in.readUTF();
+ String name = in.readUTF();
+ FunctionName fn = new FunctionName(ks, name);
+ int argCount = in.readInt();
+ List> argList = new ArrayList<>(argCount);
+ for (int i = 0; i < argCount; i++)
+ argList.add(CQLTypeParser.parse(ks, in.readUTF(), types).udfType());
+ AbstractType> returnType = CQLTypeParser.parse(ks, in.readUTF(), types).udfType();
+ FunctionName stateFunction = new FunctionName(ks, in.readUTF());
+ AbstractType> stateType = CQLTypeParser.parse(ks, in.readUTF(), types).udfType();
+ boolean hasFinalFunction = in.readBoolean();
+ FunctionName finalFunction = null;
+ if (hasFinalFunction)
+ finalFunction = new FunctionName(ks, in.readUTF());
+ boolean hasInitialCondition = in.readBoolean();
+ ByteBuffer initCond = null;
+ if (hasInitialCondition)
+ initCond = ByteBufferUtil.readWithShortLength(in);
+ return UDAggregate.create(functions, fn, argList, returnType, stateFunction, finalFunction, stateType, initCond);
+ }
+
+ public long serializedSize(UDAggregate t, Version version)
+ {
+ long size = sizeof(t.name().keyspace) + sizeof(t.name().name);
+
+ size += sizeof(t.argumentsList().size());
+ for (String arg : t.argumentsList())
+ size += sizeof(arg);
+
+ size += sizeof(t.returnType().asCQL3Type().toString());
+ size += sizeof(t.stateFunction.name().name);
+ size += sizeof(t.stateType.toAbstractType().asCQL3Type().toString());
+ size += sizeof(t.finalFunction() != null);
+ if (t.finalFunction() != null)
+ size += sizeof(t.finalFunction().name().name);
+ size += sizeof(t.initialCondition() != null);
+ if (t.initialCondition() != null)
+ size += ByteBufferUtil.serializedSizeWithShortLength(t.initialCondition());
+ return size;
+ }
+ }
}
diff --git a/src/java/org/apache/cassandra/cql3/functions/UDFunction.java b/src/java/org/apache/cassandra/cql3/functions/UDFunction.java
index 538d80e992..ce660acdef 100644
--- a/src/java/org/apache/cassandra/cql3/functions/UDFunction.java
+++ b/src/java/org/apache/cassandra/cql3/functions/UDFunction.java
@@ -17,11 +17,13 @@
*/
package org.apache.cassandra.cql3.functions;
+import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadMXBean;
import java.net.InetAddress;
import java.net.URL;
import java.nio.ByteBuffer;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
@@ -34,6 +36,7 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
+import java.util.stream.Collectors;
import com.google.common.base.Objects;
import com.google.common.collect.Lists;
@@ -50,6 +53,10 @@ import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.marshal.UserType;
import org.apache.cassandra.exceptions.FunctionExecutionException;
import org.apache.cassandra.exceptions.InvalidRequestException;
+import org.apache.cassandra.io.util.DataInputPlus;
+import org.apache.cassandra.io.util.DataOutputPlus;
+import org.apache.cassandra.tcm.serialization.UDTAwareMetadataSerializer;
+import org.apache.cassandra.tcm.serialization.Version;
import org.apache.cassandra.schema.*;
import org.apache.cassandra.service.ClientWarn;
import org.apache.cassandra.tracing.Tracing;
@@ -59,6 +66,8 @@ import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException;
import static com.google.common.collect.Iterables.any;
import static com.google.common.collect.Iterables.transform;
+import static org.apache.cassandra.db.TypeSizes.*;
+import static org.apache.cassandra.schema.SchemaKeyspace.bbToString;
import static org.apache.cassandra.utils.Clock.Global.nanoTime;
/**
@@ -66,6 +75,8 @@ import static org.apache.cassandra.utils.Clock.Global.nanoTime;
*/
public abstract class UDFunction extends UserFunction implements ScalarFunction
{
+ public static final Serializer serializer = new Serializer();
+
protected static final Logger logger = LoggerFactory.getLogger(UDFunction.class);
static final ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
@@ -732,4 +743,66 @@ public abstract class UDFunction extends UserFunction implements ScalarFunction
return super.loadClass(name);
}
}
+
+ public static class Serializer implements UDTAwareMetadataSerializer
+ {
+ public void serialize(UDFunction t, DataOutputPlus out, Version version) throws IOException
+ {
+ out.writeUTF(t.name().keyspace);
+ out.writeUTF(t.name().name);
+ out.writeUTF(t.body());
+ out.writeUTF(t.language());
+ out.writeUTF(t.returnType().asCQL3Type().toString());
+ out.writeBoolean(t.isCalledOnNullInput());
+ List arguments = t.argNames().stream().map(c -> bbToString(c.bytes)).collect(Collectors.toList());
+ out.writeInt(arguments.size());
+ for (String argument : arguments)
+ out.writeUTF(argument);
+
+ out.writeInt(t.argumentsList().size());
+ for (String type : t.argumentsList())
+ out.writeUTF(type);
+ }
+
+ public UDFunction deserialize(DataInputPlus in, Types types, Version version) throws IOException
+ {
+ String keyspace = in.readUTF();
+ String name = in.readUTF();
+ FunctionName fn = new FunctionName(keyspace, name);
+ String body = in.readUTF();
+ String language = in.readUTF();
+ AbstractType> returnType = CQLTypeParser.parse(keyspace, in.readUTF(), types).udfType();
+ boolean isCalledOnNullInput = in.readBoolean();
+ int argumentCount = in.readInt();
+ List arguments = new ArrayList<>(argumentCount);
+ for (int i = 0; i < argumentCount; i++)
+ arguments.add(new ColumnIdentifier(in.readUTF(), true));
+
+ int argumentTypeCount = in.readInt();
+ List> argTypes = new ArrayList<>(argumentTypeCount);
+ for (int i = 0; i < argumentTypeCount; i++)
+ argTypes.add(CQLTypeParser.parse(keyspace, in.readUTF(), types).udfType());
+
+ return UDFunction.create(fn, arguments, argTypes, returnType, isCalledOnNullInput, language, body);
+ }
+
+ public long serializedSize(UDFunction t, Version version)
+ {
+ long size = sizeof(t.name().keyspace);
+ size += sizeof(t.name().name);
+ size += sizeof(t.body());
+ size += sizeof(t.language());
+ size += sizeof(t.returnType().asCQL3Type().toString());
+ size += sizeof(t.isCalledOnNullInput());
+ List arguments = t.argNames().stream().map(c -> bbToString(c.bytes)).collect(Collectors.toList());
+ size += sizeof(arguments.size());
+ for (String argument : arguments)
+ size += sizeof(argument);
+
+ size += sizeof(t.argumentsList().size());
+ for (String type : t.argumentsList())
+ size += sizeof(type);
+ return size;
+ }
+ }
}
diff --git a/src/java/org/apache/cassandra/cql3/functions/masking/ColumnMask.java b/src/java/org/apache/cassandra/cql3/functions/masking/ColumnMask.java
index e8b7931718..de05aa58e2 100644
--- a/src/java/org/apache/cassandra/cql3/functions/masking/ColumnMask.java
+++ b/src/java/org/apache/cassandra/cql3/functions/masking/ColumnMask.java
@@ -18,6 +18,7 @@
package org.apache.cassandra.cql3.functions.masking;
+import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
@@ -40,10 +41,19 @@ import org.apache.cassandra.cql3.functions.Function;
import org.apache.cassandra.cql3.functions.FunctionName;
import org.apache.cassandra.cql3.functions.FunctionResolver;
import org.apache.cassandra.cql3.functions.ScalarFunction;
+import org.apache.cassandra.db.TypeSizes;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.marshal.ReversedType;
import org.apache.cassandra.exceptions.InvalidRequestException;
+import org.apache.cassandra.io.util.DataInputPlus;
+import org.apache.cassandra.io.util.DataOutputPlus;
+import org.apache.cassandra.schema.CQLTypeParser;
+import org.apache.cassandra.schema.Types;
+import org.apache.cassandra.schema.UserFunctions;
+import org.apache.cassandra.tcm.serialization.Version;
import org.apache.cassandra.transport.ProtocolVersion;
+import org.apache.cassandra.utils.ByteBufferUtil;
+import org.apache.cassandra.utils.vint.VIntCoding;
import static java.lang.String.format;
import static org.apache.cassandra.cql3.statements.RequestValidations.invalidRequest;
@@ -67,6 +77,7 @@ import static org.apache.cassandra.cql3.statements.RequestValidations.invalidReq
*/
public class ColumnMask
{
+ public static Serializer serializer = new Serializer();
public static final String DISABLED_ERROR_MESSAGE = "Cannot mask columns because dynamic data masking is not " +
"enabled. You can enable it with the " +
"dynamic_data_masking_enabled property on cassandra.yaml";
@@ -268,4 +279,72 @@ public class ColumnMask
return format("%s(%s)", name, StringUtils.join(rawPartialArguments, ", "));
}
}
+
+ public static class Serializer
+ {
+ public void serialize(ColumnMask columnMask, DataOutputPlus out, Version version) throws IOException
+ {
+ out.writeUTF(columnMask.function.name().keyspace);
+ out.writeUTF(columnMask.function.name().name);
+ List> argTypes = columnMask.partialArgumentTypes();
+ int numArgs = argTypes.size();
+ out.writeUnsignedVInt32(numArgs);
+ for (int i = 0; i < numArgs; i++)
+ {
+ out.writeUTF(argTypes.get(i).asCQL3Type().toString());
+ ByteBuffer value = columnMask.partialArgumentValues[i];
+ out.writeBoolean(value != null);
+ if (value != null)
+ ByteBufferUtil.writeWithVIntLength(value, out);
+ }
+ }
+
+ public ColumnMask deserialize(DataInputPlus in, String keyspace, AbstractType> columnType, Types types, UserFunctions functions, Version version) throws IOException
+ {
+ FunctionName functionName = new FunctionName(in.readUTF(), in.readUTF());
+
+ int numArgs = in.readUnsignedVInt32();
+ List> argTypes = new ArrayList<>(numArgs + 1);
+ argTypes.set(0, columnType);
+ ByteBuffer[] partialArgValues = new ByteBuffer[numArgs];
+ for (int i = 0; i < numArgs; i++)
+ {
+ AbstractType> argType = CQLTypeParser.parse(keyspace, in.readUTF(), types);
+ argTypes.set(i + 1, argType);
+ boolean valuePresent = in.readBoolean();
+ partialArgValues[i] = valuePresent ? null : ByteBufferUtil.readWithVIntLength(in);
+ }
+
+ Function function = FunctionResolver.get(keyspace, functionName, argTypes, null, null, null, functions);
+ if (function == null)
+ {
+ throw new AssertionError(format("Unable to find masking function %s(%s)", functionName, argTypes));
+ }
+ else if (!(function instanceof ScalarFunction))
+ {
+ throw new AssertionError(format("Function %s is not a scalar masking function", function));
+ }
+ return new ColumnMask((ScalarFunction) function, partialArgValues);
+ }
+
+ public long serializedSize(ColumnMask columnMask, Version version)
+ {
+ List> argTypes = columnMask.partialArgumentTypes();
+ int numArgs = argTypes.size();
+ long size = TypeSizes.sizeof(columnMask.function.name().keyspace) +
+ TypeSizes.sizeof(columnMask.function.name().name) +
+ VIntCoding.computeUnsignedVIntSize(numArgs);
+
+ for (int i = 0; i < numArgs; i++)
+ {
+ size += TypeSizes.sizeof(argTypes.get(i).asCQL3Type().toString());
+ size += TypeSizes.BOOL_SIZE;
+ ByteBuffer value = columnMask.partialArgumentValues[i];
+ if (value != null)
+ size += ByteBufferUtil.serializedSizeWithVIntLength(value);
+ }
+ return size;
+ }
+ }
+
}
diff --git a/src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java b/src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java
index dc9324e03a..f7261e6768 100644
--- a/src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java
+++ b/src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java
@@ -177,8 +177,9 @@ public final class StatementRestrictions
{
this(type, table, allowFiltering);
- final IndexRegistry indexRegistry = type.allowUseOfSecondaryIndices() ? IndexRegistry.obtain(table) : null;
-
+ final IndexRegistry indexRegistry = type.allowUseOfSecondaryIndices() && allowUseOfSecondaryIndices
+ ? IndexRegistry.obtain(table)
+ : null;
/*
* WHERE clause. For a given entity, rules are:
* - EQ relation conflicts with anything else (including a 2nd EQ)
diff --git a/src/java/org/apache/cassandra/cql3/statements/DescribeStatement.java b/src/java/org/apache/cassandra/cql3/statements/DescribeStatement.java
index 90382dfe57..30e039e6b4 100644
--- a/src/java/org/apache/cassandra/cql3/statements/DescribeStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/DescribeStatement.java
@@ -132,13 +132,9 @@ public abstract class DescribeStatement extends CQLStatement.Raw implements C
@Override
public ResultMessage executeLocally(QueryState state, QueryOptions options)
{
- DistributedSchema schema = Schema.instance.getDistributedSchemaBlocking();
-
- Keyspaces keyspaces = Keyspaces.builder()
- .add(schema.getKeyspaces())
- .add(Schema.instance.getLocalKeyspaces())
- .add(VirtualKeyspaceRegistry.instance.virtualKeyspacesMetadata())
- .build();
+ Keyspaces keyspaces = Schema.instance.distributedAndLocalKeyspaces();
+ UUID schemaVersion = Schema.instance.getVersion();
+ keyspaces = keyspaces.with(VirtualKeyspaceRegistry.instance.virtualKeyspacesMetadata());
PagingState pagingState = options.getPagingState();
@@ -156,7 +152,7 @@ public abstract class DescribeStatement extends CQLStatement.Raw implements C
// (vint bytes) serialized schema hash (currently the result of Keyspaces.hashCode())
//
- long offset = getOffset(pagingState, schema.getVersion());
+ long offset = getOffset(pagingState, schemaVersion);
int pageSize = options.getPageSize();
Stream extends T> stream = describe(state.getClientState(), keyspaces);
@@ -173,7 +169,7 @@ public abstract class DescribeStatement extends CQLStatement.Raw implements C
ResultSet result = new ResultSet(resultMetadata, rows);
if (pageSize > 0 && rows.size() == pageSize)
- result.metadata.setHasMorePages(getPagingState(offset + pageSize, schema.getVersion()));
+ result.metadata.setHasMorePages(getPagingState(offset + pageSize, schemaVersion));
return new ResultMessage.Rows(result);
}
diff --git a/src/java/org/apache/cassandra/cql3/statements/PropertyDefinitions.java b/src/java/org/apache/cassandra/cql3/statements/PropertyDefinitions.java
index 65ec8fca67..a80e9ae693 100644
--- a/src/java/org/apache/cassandra/cql3/statements/PropertyDefinitions.java
+++ b/src/java/org/apache/cassandra/cql3/statements/PropertyDefinitions.java
@@ -82,7 +82,7 @@ public class PropertyDefinitions
return properties.containsKey(name);
}
- protected String getString(String name) throws SyntaxException
+ public String getString(String name) throws SyntaxException
{
Object val = properties.get(name);
if (val == null)
diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/AlterKeyspaceStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/AlterKeyspaceStatement.java
index b8a27af3ba..14bdef0f68 100644
--- a/src/java/org/apache/cassandra/cql3/statements/schema/AlterKeyspaceStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/schema/AlterKeyspaceStatement.java
@@ -18,21 +18,19 @@
package org.apache.cassandra.cql3.statements.schema;
import java.util.HashSet;
-import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
-import java.util.stream.Stream;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.apache.cassandra.audit.AuditLogContext;
import org.apache.cassandra.audit.AuditLogEntryType;
import org.apache.cassandra.auth.Permission;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.cql3.CQLStatement;
-import org.apache.cassandra.db.ColumnFamilyStore;
-import org.apache.cassandra.db.Keyspace;
import org.apache.cassandra.db.guardrails.Guardrails;
import org.apache.cassandra.exceptions.ConfigurationException;
-import org.apache.cassandra.gms.Gossiper;
import org.apache.cassandra.locator.AbstractReplicationStrategy;
import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.locator.LocalStrategy;
@@ -42,7 +40,11 @@ import org.apache.cassandra.schema.KeyspaceMetadata;
import org.apache.cassandra.schema.KeyspaceMetadata.KeyspaceDiff;
import org.apache.cassandra.schema.Keyspaces;
import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff;
+import org.apache.cassandra.schema.SchemaConstants;
+import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.ClientState;
+import org.apache.cassandra.tcm.ClusterMetadata;
+import org.apache.cassandra.tcm.membership.NodeId;
import org.apache.cassandra.transport.Event.SchemaChange;
import org.apache.cassandra.transport.Event.SchemaChange.Change;
import org.apache.cassandra.utils.FBUtilities;
@@ -52,9 +54,10 @@ import static org.apache.cassandra.config.CassandraRelevantProperties.ALLOW_UNSA
public final class AlterKeyspaceStatement extends AlterSchemaStatement
{
+ private static final Logger logger = LoggerFactory.getLogger(AlterKeyspaceStatement.class);
+
private static final boolean allow_alter_rf_during_range_movement = ALLOW_ALTER_RF_DURING_RANGE_MOVEMENT.getBoolean();
private static final boolean allow_unsafe_transient_changes = ALLOW_UNSAFE_TRANSIENT_CHANGES.getBoolean();
- private final HashSet clientWarnings = new HashSet<>();
private final KeyspaceAttributes attrs;
private final boolean ifExists;
@@ -66,10 +69,11 @@ public final class AlterKeyspaceStatement extends AlterSchemaStatement
this.ifExists = ifExists;
}
- public Keyspaces apply(Keyspaces schema)
+ public Keyspaces apply(ClusterMetadata metadata)
{
attrs.validate();
+ Keyspaces schema = metadata.schema.getKeyspaces();
KeyspaceMetadata keyspace = schema.getNullable(keyspaceName);
if (null == keyspace)
{
@@ -83,17 +87,29 @@ public final class AlterKeyspaceStatement extends AlterSchemaStatement
if (attrs.getReplicationStrategyClass() != null && attrs.getReplicationStrategyClass().equals(SimpleStrategy.class.getSimpleName()))
Guardrails.simpleStrategyEnabled.ensureEnabled(state);
+ if (keyspace.params.replication.isMeta() && !keyspace.name.equals(SchemaConstants.METADATA_KEYSPACE_NAME))
+ throw ire("Can not alter a keyspace to use MetaReplicationStrategy");
+
if (newKeyspace.params.replication.klass.equals(LocalStrategy.class))
throw ire("Unable to use given strategy class: LocalStrategy is reserved for internal use.");
- newKeyspace.params.validate(keyspaceName, state);
+ newKeyspace.params.validate(keyspaceName, state, metadata);
+ newKeyspace.replicationStrategy.validate(metadata);
validateNoRangeMovements();
- validateTransientReplication(keyspace.createReplicationStrategy(), newKeyspace.createReplicationStrategy());
+ validateTransientReplication(keyspace, newKeyspace);
- Keyspaces res = schema.withAddedOrUpdated(newKeyspace);
+ // Because we used to not properly validate unrecognized options, we only log a warning if we find one.
+ try
+ {
+ newKeyspace.replicationStrategy.validateExpectedOptions(metadata);
+ }
+ catch (ConfigurationException e)
+ {
+ logger.warn("Ignoring {}", e.getMessage());
+ }
- return res;
+ return schema.withAddedOrUpdated(newKeyspace);
}
SchemaChange schemaChangeEvent(KeyspacesDiff diff)
@@ -109,13 +125,14 @@ public final class AlterKeyspaceStatement extends AlterSchemaStatement
@Override
Set clientWarnings(KeyspacesDiff diff)
{
+ HashSet clientWarnings = new HashSet<>();
if (diff.isEmpty())
return clientWarnings;
KeyspaceDiff keyspaceDiff = diff.altered.get(0);
- AbstractReplicationStrategy before = keyspaceDiff.before.createReplicationStrategy();
- AbstractReplicationStrategy after = keyspaceDiff.after.createReplicationStrategy();
+ AbstractReplicationStrategy before = keyspaceDiff.before.replicationStrategy;
+ AbstractReplicationStrategy after = keyspaceDiff.after.replicationStrategy;
if (before.getReplicationFactor().fullReplicas < after.getReplicationFactor().fullReplicas)
clientWarnings.add("When increasing replication factor you need to run a full (-full) repair to distribute the data.");
@@ -128,29 +145,36 @@ public final class AlterKeyspaceStatement extends AlterSchemaStatement
if (allow_alter_rf_during_range_movement)
return;
- Stream unreachableNotAdministrativelyInactive =
- Gossiper.instance.getUnreachableMembers().stream().filter(endpoint -> !FBUtilities.getBroadcastAddressAndPort().equals(endpoint) &&
- !Gossiper.instance.isAdministrativelyInactiveState(endpoint));
- Stream endpoints = Stream.concat(Gossiper.instance.getLiveMembers().stream(),
- unreachableNotAdministrativelyInactive);
- List notNormalEndpoints = endpoints.filter(endpoint -> !FBUtilities.getBroadcastAddressAndPort().equals(endpoint) &&
- !Gossiper.instance.getEndpointStateForEndpoint(endpoint).isNormalState())
- .collect(Collectors.toList());
+ ClusterMetadata metadata = ClusterMetadata.current();
+ NodeId nodeId = metadata.directory.peerId(FBUtilities.getBroadcastAddressAndPort());
+ Set notNormalEndpoints = metadata.directory.states.entrySet().stream().filter(e -> !e.getKey().equals(nodeId)).filter(e -> {
+ switch (e.getValue())
+ {
+ case BOOTSTRAPPING:
+ case LEAVING:
+ case MOVING:
+ return true;
+ default:
+ return false;
+ }
+
+ }).map(e -> metadata.directory.endpoint(e.getKey())).collect(Collectors.toSet());
+
if (!notNormalEndpoints.isEmpty())
{
throw new ConfigurationException("Cannot alter RF while some endpoints are not in normal state (no range movements): " + notNormalEndpoints);
}
}
- private void validateTransientReplication(AbstractReplicationStrategy oldStrategy, AbstractReplicationStrategy newStrategy)
+ private void validateTransientReplication(KeyspaceMetadata current, KeyspaceMetadata proposed)
{
//If there is no read traffic there are some extra alterations you can safely make, but this is so atypical
//that a good default is to not allow unsafe changes
if (allow_unsafe_transient_changes)
return;
- ReplicationFactor oldRF = oldStrategy.getReplicationFactor();
- ReplicationFactor newRF = newStrategy.getReplicationFactor();
+ ReplicationFactor oldRF = current.replicationStrategy.getReplicationFactor();
+ ReplicationFactor newRF = proposed.replicationStrategy.getReplicationFactor();
int oldTrans = oldRF.transientReplicas();
int oldFull = oldRF.fullReplicas;
@@ -162,19 +186,13 @@ public final class AlterKeyspaceStatement extends AlterSchemaStatement
if (DatabaseDescriptor.getNumTokens() > 1)
throw new ConfigurationException(String.format("Transient replication is not supported with vnodes yet"));
- Keyspace ks = Keyspace.open(keyspaceName);
- for (ColumnFamilyStore cfs : ks.getColumnFamilyStores())
- {
- if (cfs.viewManager.hasViews())
- {
- throw new ConfigurationException("Cannot use transient replication on keyspaces using materialized views");
- }
- if (cfs.indexManager.hasIndexes())
- {
+ if (!current.views.isEmpty())
+ throw new ConfigurationException("Cannot use transient replication on keyspaces using materialized views");
+
+ for (TableMetadata table : current.tables)
+ if (!table.indexes.isEmpty())
throw new ConfigurationException("Cannot use transient replication on keyspaces using secondary indexes");
- }
- }
}
//This is true right now because the transition from transient -> full lacks the pending state
diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/AlterSchemaStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/AlterSchemaStatement.java
index a539ea74e8..8907a93f8f 100644
--- a/src/java/org/apache/cassandra/cql3/statements/schema/AlterSchemaStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/schema/AlterSchemaStatement.java
@@ -34,6 +34,7 @@ import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.service.ClientWarn;
import org.apache.cassandra.service.QueryState;
+import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.transport.Event.SchemaChange;
import org.apache.cassandra.transport.messages.ResultMessage;
@@ -41,12 +42,46 @@ abstract public class AlterSchemaStatement implements CQLStatement.SingleKeyspac
{
protected final String keyspaceName; // name of the keyspace affected by the statement
protected ClientState state;
+ // TODO: not sure if this is going to stay the same, or will be replaced by more efficient serialization/sanitation means
+ // or just `toString` for every statement
+ private String cql;
protected AlterSchemaStatement(String keyspaceName)
{
this.keyspaceName = keyspaceName;
}
+ public void setCql(String cql)
+ {
+ this.cql = cql;
+ }
+
+ @Override
+ public String cql()
+ {
+ assert cql != null;
+ return cql;
+ }
+
+ @Override
+ public void enterExecution()
+ {
+ ClientWarn.instance.pauseCapture();
+ ClientState localState = state;
+ if (localState != null)
+ localState.pauseGuardrails();
+ }
+
+ @Override
+ public void exitExecution()
+ {
+ ClientWarn.instance.resumeCapture();
+ ClientState localState = state;
+ if (localState != null)
+ localState.resumeGuardrails();
+ }
+
+ // TODO: validation should be performed during application
public void validate(ClientState state)
{
// validation is performed while executing the statement, in apply()
@@ -57,7 +92,7 @@ abstract public class AlterSchemaStatement implements CQLStatement.SingleKeyspac
public ResultMessage execute(QueryState state, QueryOptions options, long queryStartNanoTime)
{
- return execute(state, false);
+ return execute(state);
}
@Override
@@ -68,7 +103,7 @@ abstract public class AlterSchemaStatement implements CQLStatement.SingleKeyspac
public ResultMessage executeLocally(QueryState state, QueryOptions options)
{
- return execute(state, true);
+ return execute(state);
}
/**
@@ -100,7 +135,7 @@ abstract public class AlterSchemaStatement implements CQLStatement.SingleKeyspac
return ImmutableSet.of();
}
- public ResultMessage execute(QueryState state, boolean locally)
+ public ResultMessage execute(QueryState state)
{
if (SchemaConstants.isLocalSystemKeyspace(keyspaceName))
throw ire("System keyspace '%s' is not user-modifiable", keyspaceName);
@@ -110,12 +145,24 @@ abstract public class AlterSchemaStatement implements CQLStatement.SingleKeyspac
throw ire("Virtual keyspace '%s' is not user-modifiable", keyspaceName);
validateKeyspaceName();
+ // Perform a 'dry-run' attempt to apply the transformation locally before submitting to the CMS. This can save a
+ // round trip to the CMS for things syntax errors, but also fail fast for things like configuration errors.
+ // Such failures may be dependent on the specific node's config (for things like guardrails/memtable
+ // config/etc), but executing a schema change which has already been committed by the CMS should always succeed
+ // or else the node cannot make progress on any subsequent metadata changes. For this reason, validation errors
+ // during execution are trapped and the node will fall back to safe default config wherever possible. Attempting
+ // to apply the SchemaTransformation at this point will catch any such error which occurs locally before
+ // submission to the CMS, but it can't guarantee that the statement can be applied as-is on every node in the
+ // cluster, as config can be heterogenous falling back to safe defaults may occur on some nodes.
+ ClusterMetadata metadata = ClusterMetadata.current();
+ apply(metadata);
- SchemaTransformationResult result = Schema.instance.transform(this, locally);
+ ClusterMetadata result = Schema.instance.submit(this);
- clientWarnings(result.diff).forEach(ClientWarn.instance::warn);
+ KeyspacesDiff diff = Keyspaces.diff(metadata.schema.getKeyspaces(), result.schema.getKeyspaces());
+ clientWarnings(diff).forEach(ClientWarn.instance::warn);
- if (result.diff.isEmpty())
+ if (diff.isEmpty())
return new ResultMessage.Void();
/*
@@ -127,9 +174,9 @@ abstract public class AlterSchemaStatement implements CQLStatement.SingleKeyspac
*/
AuthenticatedUser user = state.getClientState().getUser();
if (null != user && !user.isAnonymous())
- createdResources(result.diff).forEach(r -> grantPermissionsOnResource(r, user));
+ createdResources(diff).forEach(r -> grantPermissionsOnResource(r, user));
- return new ResultMessage.SchemaChange(schemaChangeEvent(result.diff));
+ return new ResultMessage.SchemaChange(schemaChangeEvent(diff));
}
private void validateKeyspaceName()
@@ -170,4 +217,12 @@ abstract public class AlterSchemaStatement implements CQLStatement.SingleKeyspac
{
return new InvalidRequestException(String.format(format, args));
}
+
+ public String toString()
+ {
+ return "AlterSchemaStatement{" +
+ "keyspaceName='" + keyspaceName + '\'' +
+ ", cql='" + cql() + '\'' +
+ '}';
+ }
}
diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/AlterTableStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/AlterTableStatement.java
index fc0b4cffe6..24f9535d9f 100644
--- a/src/java/org/apache/cassandra/cql3/statements/schema/AlterTableStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/schema/AlterTableStatement.java
@@ -24,14 +24,15 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
import com.google.common.base.Splitter;
+import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
-
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -44,38 +45,38 @@ import org.apache.cassandra.cql3.CQLStatement;
import org.apache.cassandra.cql3.ColumnIdentifier;
import org.apache.cassandra.cql3.QualifiedName;
import org.apache.cassandra.cql3.functions.masking.ColumnMask;
-import org.apache.cassandra.db.Keyspace;
import org.apache.cassandra.db.guardrails.Guardrails;
import org.apache.cassandra.db.marshal.AbstractType;
-
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.gms.ApplicationState;
import org.apache.cassandra.gms.Gossiper;
+import org.apache.cassandra.index.TargetParser;
import org.apache.cassandra.locator.InetAddressAndPort;
-import org.apache.cassandra.net.MessagingService;
import org.apache.cassandra.schema.ColumnMetadata;
import org.apache.cassandra.schema.IndexMetadata;
import org.apache.cassandra.schema.KeyspaceMetadata;
import org.apache.cassandra.schema.Keyspaces;
import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff;
+import org.apache.cassandra.schema.MemtableParams;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.schema.TableParams;
import org.apache.cassandra.schema.ViewMetadata;
import org.apache.cassandra.schema.Views;
import org.apache.cassandra.service.ClientState;
-import org.apache.cassandra.service.StorageService;
import org.apache.cassandra.service.reads.repair.ReadRepairStrategy;
+import org.apache.cassandra.tcm.ClusterMetadata;
+import org.apache.cassandra.tcm.Epoch;
+import org.apache.cassandra.tcm.membership.Directory;
import org.apache.cassandra.transport.Event.SchemaChange;
import org.apache.cassandra.transport.Event.SchemaChange.Change;
import org.apache.cassandra.transport.Event.SchemaChange.Target;
+import org.apache.cassandra.utils.CassandraVersion;
import org.apache.cassandra.utils.NoSpamLogger;
-
-import static java.lang.String.format;
-import static java.lang.String.join;
+import org.apache.cassandra.utils.Pair;
import static com.google.common.collect.Iterables.isEmpty;
-import static com.google.common.collect.Iterables.transform;
-
+import static java.lang.String.format;
+import static java.lang.String.join;
import static org.apache.cassandra.schema.TableMetadata.Flag;
public abstract class AlterTableStatement extends AlterSchemaStatement
@@ -100,8 +101,9 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
this.state = state;
}
- public Keyspaces apply(Keyspaces schema)
+ public Keyspaces apply(ClusterMetadata metadata)
{
+ Keyspaces schema = metadata.schema.getKeyspaces();
KeyspaceMetadata keyspace = schema.getNullable(keyspaceName);
TableMetadata table = null == keyspace
@@ -118,7 +120,7 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
if (table.isView())
throw ire("Cannot use ALTER TABLE on a materialized view; use ALTER MATERIALIZED VIEW instead");
- return schema.withAddedOrUpdated(apply(keyspace, table));
+ return schema.withAddedOrUpdated(apply(metadata.nextEpoch(), keyspace, table));
}
SchemaChange schemaChangeEvent(KeyspacesDiff diff)
@@ -142,7 +144,7 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
return format("%s (%s, %s)", getClass().getSimpleName(), keyspaceName, tableName);
}
- abstract KeyspaceMetadata apply(KeyspaceMetadata keyspace, TableMetadata table);
+ abstract KeyspaceMetadata apply(Epoch epoch, KeyspaceMetadata keyspace, TableMetadata table);
/**
* {@code ALTER TABLE [IF EXISTS] ALTER TYPE ;}
@@ -156,7 +158,7 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
super(keyspaceName, tableName, ifTableExists);
}
- public KeyspaceMetadata apply(KeyspaceMetadata keyspace, TableMetadata table)
+ public KeyspaceMetadata apply(Epoch epoch, KeyspaceMetadata keyspace, TableMetadata table)
{
throw ire("Altering column types is no longer supported");
}
@@ -196,7 +198,7 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
}
@Override
- public KeyspaceMetadata apply(KeyspaceMetadata keyspace, TableMetadata table)
+ public KeyspaceMetadata apply(Epoch epoch, KeyspaceMetadata keyspace, TableMetadata table)
{
ColumnMetadata column = table.getColumn(columnName);
@@ -214,7 +216,7 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
if (Objects.equals(oldMask, newMask))
return keyspace;
- TableMetadata.Builder tableBuilder = table.unbuild();
+ TableMetadata.Builder tableBuilder = table.unbuild().epoch(epoch);
tableBuilder.alterColumnMask(columnName, newMask);
TableMetadata newTable = tableBuilder.build();
newTable.validate();
@@ -274,10 +276,10 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
newColumns.forEach(c -> c.type.validate(state, "Column " + c.name));
}
- public KeyspaceMetadata apply(KeyspaceMetadata keyspace, TableMetadata table)
+ public KeyspaceMetadata apply(Epoch epoch, KeyspaceMetadata keyspace, TableMetadata table)
{
Guardrails.alterTableEnabled.ensureEnabled("ALTER TABLE changing columns", state);
- TableMetadata.Builder tableBuilder = table.unbuild();
+ TableMetadata.Builder tableBuilder = table.unbuild().epoch(epoch);
Views.Builder viewsBuilder = keyspace.views.unbuild();
newColumns.forEach(c -> addColumn(keyspace, table, c, ifColumnNotExists, tableBuilder, viewsBuilder));
@@ -360,6 +362,38 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
}
}
+ private static void validateIndexesForColumnModification(TableMetadata table,
+ ColumnIdentifier colId,
+ boolean isRename)
+ {
+ ColumnMetadata column = table.getColumn(colId);
+ Set dependentIndexes = new HashSet<>();
+ for (IndexMetadata index : table.indexes)
+ {
+ Optional> target = TargetParser.tryParse(table, index);
+ if (target.isEmpty())
+ {
+ // The target column(s) of this index is not trivially discernible from its metadata.
+ // This implies an external custom index implementation and without instantiating the
+ // index itself we cannot be sure that the column metadata is safe to modify.
+ dependentIndexes.add(index.name);
+ }
+ else if (target.get().left.equals(column))
+ {
+ // The index metadata declares an explicit dependency on the column being modified, so
+ // the mutation must be rejected.
+ dependentIndexes.add(index.name);
+ }
+ }
+ if (!dependentIndexes.isEmpty())
+ {
+ throw ire("Cannot %s column %s because it has dependent secondary indexes (%s)",
+ isRename ? "rename" : "drop",
+ colId,
+ join(", ", dependentIndexes));
+ }
+ }
+
/**
* {@code ALTER TABLE [IF EXISTS] DROP [IF EXISTS] }
* {@code ALTER TABLE [IF EXISTS] DROP [IF EXISTS] ( , , ... )}
@@ -379,7 +413,7 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
this.timestamp = timestamp;
}
- public KeyspaceMetadata apply(KeyspaceMetadata keyspace, TableMetadata table)
+ public KeyspaceMetadata apply(Epoch epoch, KeyspaceMetadata keyspace, TableMetadata table)
{
Guardrails.alterTableEnabled.ensureEnabled("ALTER TABLE changing columns", state);
TableMetadata.Builder builder = table.unbuild();
@@ -407,14 +441,8 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
if (currentColumn.type.isUDT() && currentColumn.type.isMultiCell())
throw ire("Cannot drop non-frozen column %s of user type %s", column, currentColumn.type.asCQL3Type());
- // TODO: some day try and find a way to not rely on Keyspace/IndexManager/Index to find dependent indexes
- Set dependentIndexes = Keyspace.openAndGetStore(table).indexManager.getDependentIndexes(currentColumn);
- if (!dependentIndexes.isEmpty())
- {
- throw ire("Cannot drop column %s because it has dependent secondary indexes (%s)",
- currentColumn,
- join(", ", transform(dependentIndexes, i -> i.name)));
- }
+ if (!table.indexes.isEmpty())
+ AlterTableStatement.validateIndexesForColumnModification(table, column, false);
if (!isEmpty(keyspace.views.forTable(table.id)))
throw ire("Cannot drop column %s on base table %s with materialized views", currentColumn, table.name);
@@ -447,10 +475,10 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
this.ifColumnsExists = ifColumnsExists;
}
- public KeyspaceMetadata apply(KeyspaceMetadata keyspace, TableMetadata table)
+ public KeyspaceMetadata apply(Epoch epoch, KeyspaceMetadata keyspace, TableMetadata table)
{
Guardrails.alterTableEnabled.ensureEnabled("ALTER TABLE changing columns", state);
- TableMetadata.Builder tableBuilder = table.unbuild();
+ TableMetadata.Builder tableBuilder = table.unbuild().epoch(epoch);
Views.Builder viewsBuilder = keyspace.views.unbuild();
renamedColumns.forEach((o, n) -> renameColumn(keyspace, table, o, n, ifColumnsExists, tableBuilder, viewsBuilder));
@@ -485,14 +513,8 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
table);
}
- // TODO: some day try and find a way to not rely on Keyspace/IndexManager/Index to find dependent indexes
- Set dependentIndexes = Keyspace.openAndGetStore(table).indexManager.getDependentIndexes(column);
- if (!dependentIndexes.isEmpty())
- {
- throw ire("Can't rename column %s because it has dependent secondary indexes (%s)",
- oldName,
- join(", ", transform(dependentIndexes, i -> i.name)));
- }
+ if (!table.indexes.isEmpty())
+ AlterTableStatement.validateIndexesForColumnModification(table, oldName, true);
for (ViewMetadata view : keyspace.views.forTable(table.id))
{
@@ -523,13 +545,15 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
public void validate(ClientState state)
{
super.validate(state);
-
+ // If a memtable configuration is specified, validate it against config
+ if (attrs.hasOption(TableParams.Option.MEMTABLE))
+ MemtableParams.get(attrs.getString(TableParams.Option.MEMTABLE.toString()));
Guardrails.tableProperties.guard(attrs.updatedProperties(), attrs::removeProperty, state);
validateDefaultTimeToLive(attrs.asNewTableParams());
}
- public KeyspaceMetadata apply(KeyspaceMetadata keyspace, TableMetadata table)
+ public KeyspaceMetadata apply(Epoch epoch, KeyspaceMetadata keyspace, TableMetadata table)
{
attrs.validate();
@@ -547,7 +571,7 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
"before being replayed.");
}
- if (keyspace.createReplicationStrategy().hasTransientReplicas()
+ if (keyspace.replicationStrategy.hasTransientReplicas()
&& params.readRepair != ReadRepairStrategy.NONE)
{
throw ire("read_repair must be set to 'NONE' for transiently replicated keyspaces");
@@ -573,7 +597,7 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
super(keyspaceName, tableName, ifTableExists);
}
- public KeyspaceMetadata apply(KeyspaceMetadata keyspace, TableMetadata table)
+ public KeyspaceMetadata apply(Epoch epoch, KeyspaceMetadata keyspace, TableMetadata table)
{
if (!DatabaseDescriptor.enableDropCompactStorage())
throw new InvalidRequestException("DROP COMPACT STORAGE is disabled. Enable in cassandra.yaml to use.");
@@ -587,7 +611,7 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
? ImmutableSet.of(Flag.COMPOUND, Flag.COUNTER)
: ImmutableSet.of(Flag.COMPOUND);
- return keyspace.withSwapped(keyspace.tables.withSwapped(table.withSwapped(flags)));
+ return keyspace.withSwapped(keyspace.tables.withSwapped(table.unbuild().flags(flags).build()));
}
/**
@@ -608,40 +632,45 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
Set preC15897nodes = new HashSet<>();
Set with2xSStables = new HashSet<>();
Splitter onComma = Splitter.on(',').omitEmptyStrings().trimResults();
- for (InetAddressAndPort node : StorageService.instance.getTokenMetadata().getAllEndpoints())
+ Directory directory = ClusterMetadata.current().directory;
+ for (InetAddressAndPort node : directory.allAddresses())
{
- if (MessagingService.instance().versions.knows(node) &&
- MessagingService.instance().versions.getRaw(node) < MessagingService.VERSION_40)
+
+ CassandraVersion version = directory.version(directory.peerId(node)).cassandraVersion;
+
+ if (version.compareTo(CassandraVersion.CASSANDRA_4_0) < 0)
{
+ // if the cluster contains any pre-4.0 nodes (which really shouldn't be the case), reject this
+ // operation as we can't be certain all peers can support it.
before4.add(node);
- continue;
}
-
- String sstableVersionsString = Gossiper.instance.getApplicationState(node, ApplicationState.SSTABLE_VERSIONS);
- if (sstableVersionsString == null)
+ else
{
- preC15897nodes.add(node);
- continue;
- }
-
- try
- {
- boolean has2xSStables = onComma.splitToList(sstableVersionsString)
- .stream()
- .anyMatch(v -> v.compareTo("big-ma")<=0);
- if (has2xSStables)
- with2xSStables.add(node);
- }
- catch (IllegalArgumentException e)
- {
- // Means VersionType::fromString didn't parse a version correctly. Which shouldn't happen, we shouldn't
- // have garbage in Gossip. But crashing the request is not ideal, so we log the error but ignore the
- // node otherwise.
- noSpamLogger.error("Unexpected error parsing sstable versions from gossip for {} (gossiped value " +
- "is '{}'). This is a bug and should be reported. Cannot ensure that {} has no " +
- "non-upgraded 2.x sstables anymore. If after this DROP COMPACT STORAGE some old " +
- "sstables cannot be read anymore, please use `upgradesstables` with the " +
- "`--force-compact-storage-on` option.", node, sstableVersionsString, node);
+ // any peer on a version greater than 4.0.0 must include CASSANDRA-15897, so just check that
+ // its min sstable version. Note: this app state may be empty/unset if the full StorageService
+ // initialisation hasn't been done, i.e. in tests.
+ String sstableVersionsString = Gossiper.instance.getApplicationState(node, ApplicationState.SSTABLE_VERSIONS);
+ if (Strings.isNullOrEmpty(sstableVersionsString))
+ continue;
+ try
+ {
+ boolean has2xSStables = onComma.splitToList(sstableVersionsString)
+ .stream()
+ .anyMatch(v -> v.compareTo("big-ma")<=0);
+ if (has2xSStables)
+ with2xSStables.add(node);
+ }
+ catch (IllegalArgumentException e)
+ {
+ // Means VersionType::fromString didn't parse a version correctly. Which shouldn't happen, we shouldn't
+ // have garbage in Gossip. But crashing the request is not ideal, so we log the error but ignore the
+ // node otherwise.
+ noSpamLogger.error("Unexpected error parsing sstable versions from gossip for {} (gossiped value " +
+ "is '{}'). This is a bug and should be reported. Cannot ensure that {} has no " +
+ "non-upgraded 2.x sstables anymore. If after this DROP COMPACT STORAGE some old " +
+ "sstables cannot be read anymore, please use `upgradesstables` with the " +
+ "`--force-compact-storage-on` option.", node, sstableVersionsString, node);
+ }
}
}
@@ -649,10 +678,6 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
throw new InvalidRequestException(format("Cannot DROP COMPACT STORAGE as some nodes in the cluster (%s) " +
"are not on 4.0+ yet. Please upgrade those nodes and run " +
"`upgradesstables` before retrying.", before4));
- if (!preC15897nodes.isEmpty())
- throw new InvalidRequestException(format("Cannot guarantee that DROP COMPACT STORAGE is safe as some nodes " +
- "in the cluster (%s) do not have https://issues.apache.org/jira/browse/CASSANDRA-15897. " +
- "Please upgrade those nodes and retry.", preC15897nodes));
if (!with2xSStables.isEmpty())
throw new InvalidRequestException(format("Cannot DROP COMPACT STORAGE as some nodes in the cluster (%s) " +
"has some non-upgraded 2.x sstables. Please run `upgradesstables` " +
diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/AlterTypeStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/AlterTypeStatement.java
index 40bca4aac9..fefe70e1c6 100644
--- a/src/java/org/apache/cassandra/cql3/statements/schema/AlterTypeStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/schema/AlterTypeStatement.java
@@ -34,6 +34,7 @@ import org.apache.cassandra.schema.KeyspaceMetadata;
import org.apache.cassandra.schema.Keyspaces;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.ClientState;
+import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.transport.Event.SchemaChange;
import org.apache.cassandra.transport.Event.SchemaChange.Change;
import org.apache.cassandra.transport.Event.SchemaChange.Target;
@@ -69,8 +70,10 @@ public abstract class AlterTypeStatement extends AlterSchemaStatement
return new SchemaChange(Change.UPDATED, Target.TYPE, keyspaceName, typeName);
}
- public Keyspaces apply(Keyspaces schema)
+ @Override
+ public Keyspaces apply(ClusterMetadata metadata)
{
+ Keyspaces schema = metadata.schema.getKeyspaces();
KeyspaceMetadata keyspace = schema.getNullable(keyspaceName);
UserType type = null == keyspace
diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/AlterViewStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/AlterViewStatement.java
index 7e707f476b..831e46b0ca 100644
--- a/src/java/org/apache/cassandra/cql3/statements/schema/AlterViewStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/schema/AlterViewStatement.java
@@ -26,6 +26,7 @@ import org.apache.cassandra.db.guardrails.Guardrails;
import org.apache.cassandra.schema.*;
import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff;
import org.apache.cassandra.service.ClientState;
+import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.transport.Event.SchemaChange;
import org.apache.cassandra.transport.Event.SchemaChange.Change;
import org.apache.cassandra.transport.Event.SchemaChange.Target;
@@ -54,8 +55,10 @@ public final class AlterViewStatement extends AlterSchemaStatement
this.state = state;
}
- public Keyspaces apply(Keyspaces schema)
+ @Override
+ public Keyspaces apply(ClusterMetadata metadata)
{
+ Keyspaces schema = metadata.schema.getKeyspaces();
KeyspaceMetadata keyspace = schema.getNullable(keyspaceName);
ViewMetadata view = null == keyspace
diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/CreateAggregateStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/CreateAggregateStatement.java
index eb9f33a949..8950f67d9a 100644
--- a/src/java/org/apache/cassandra/cql3/statements/schema/CreateAggregateStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/schema/CreateAggregateStatement.java
@@ -44,6 +44,7 @@ import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff;
import org.apache.cassandra.schema.Schema;
import org.apache.cassandra.serializers.MarshalException;
import org.apache.cassandra.service.ClientState;
+import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.transport.Event.SchemaChange;
import org.apache.cassandra.transport.Event.SchemaChange.Change;
import org.apache.cassandra.transport.Event.SchemaChange.Target;
@@ -89,7 +90,8 @@ public final class CreateAggregateStatement extends AlterSchemaStatement
this.ifNotExists = ifNotExists;
}
- public Keyspaces apply(Keyspaces schema)
+ @Override
+ public Keyspaces apply(ClusterMetadata metadata)
{
if (ifNotExists && orReplace)
throw ire("Cannot use both 'OR REPLACE' and 'IF NOT EXISTS' directives");
@@ -105,6 +107,7 @@ public final class CreateAggregateStatement extends AlterSchemaStatement
if (!rawStateType.isImplicitlyFrozen() && rawStateType.isFrozen())
throw ire("State type '%s' cannot be frozen; remove frozen<> modifier from '%s'", rawStateType, rawStateType);
+ Keyspaces schema = metadata.schema.getKeyspaces();
KeyspaceMetadata keyspace = schema.getNullable(keyspaceName);
if (null == keyspace)
throw ire("Keyspace '%s' doesn't exist", keyspaceName);
diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/CreateFunctionStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/CreateFunctionStatement.java
index f04ae37cd5..20923687ff 100644
--- a/src/java/org/apache/cassandra/cql3/statements/schema/CreateFunctionStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/schema/CreateFunctionStatement.java
@@ -40,6 +40,7 @@ import org.apache.cassandra.schema.Keyspaces;
import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff;
import org.apache.cassandra.schema.Schema;
import org.apache.cassandra.service.ClientState;
+import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.transport.Event.SchemaChange;
import org.apache.cassandra.transport.Event.SchemaChange.Change;
import org.apache.cassandra.transport.Event.SchemaChange.Target;
@@ -82,7 +83,7 @@ public final class CreateFunctionStatement extends AlterSchemaStatement
}
// TODO: replace affected aggregates !!
- public Keyspaces apply(Keyspaces schema)
+ public Keyspaces apply(ClusterMetadata metadata)
{
if (ifNotExists && orReplace)
throw ire("Cannot use both 'OR REPLACE' and 'IF NOT EXISTS' directives");
@@ -103,6 +104,7 @@ public final class CreateFunctionStatement extends AlterSchemaStatement
if (!rawReturnType.isImplicitlyFrozen() && rawReturnType.isFrozen())
throw ire("Return type '%s' cannot be frozen; remove frozen<> modifier from '%s'", rawReturnType, rawReturnType);
+ Keyspaces schema = metadata.schema.getKeyspaces();
KeyspaceMetadata keyspace = schema.getNullable(keyspaceName);
if (null == keyspace)
throw ire("Keyspace '%s' doesn't exist", keyspaceName);
diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/CreateIndexStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/CreateIndexStatement.java
index b53e066f90..c1626d0f09 100644
--- a/src/java/org/apache/cassandra/cql3/statements/schema/CreateIndexStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/schema/CreateIndexStatement.java
@@ -31,7 +31,6 @@ import org.apache.cassandra.cql3.CQLStatement;
import org.apache.cassandra.cql3.ColumnIdentifier;
import org.apache.cassandra.cql3.QualifiedName;
import org.apache.cassandra.cql3.statements.schema.IndexTarget.Type;
-import org.apache.cassandra.db.Keyspace;
import org.apache.cassandra.db.guardrails.Guardrails;
import org.apache.cassandra.db.marshal.MapType;
import org.apache.cassandra.exceptions.InvalidRequestException;
@@ -40,6 +39,7 @@ import org.apache.cassandra.index.sasi.SASIIndex;
import org.apache.cassandra.schema.*;
import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff;
import org.apache.cassandra.service.ClientState;
+import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.transport.Event.SchemaChange;
import org.apache.cassandra.transport.Event.SchemaChange.Change;
import org.apache.cassandra.transport.Event.SchemaChange.Target;
@@ -112,7 +112,8 @@ public final class CreateIndexStatement extends AlterSchemaStatement
this.state = state;
}
- public Keyspaces apply(Keyspaces schema)
+ @Override
+ public Keyspaces apply(ClusterMetadata metadata)
{
attrs.validate();
@@ -121,6 +122,7 @@ public final class CreateIndexStatement extends AlterSchemaStatement
if (attrs.isCustom && attrs.customClass.equals(SASIIndex.class.getName()) && !DatabaseDescriptor.getSASIIndexesEnabled())
throw new InvalidRequestException(SASI_INDEX_DISABLED);
+ Keyspaces schema = metadata.schema.getKeyspaces();
KeyspaceMetadata keyspace = schema.getNullable(keyspaceName);
if (null == keyspace)
throw ire(KEYSPACE_DOES_NOT_EXIST, keyspaceName);
@@ -143,7 +145,7 @@ public final class CreateIndexStatement extends AlterSchemaStatement
if (table.isView())
throw ire(MATERIALIZED_VIEWS_NOT_SUPPORTED);
- if (Keyspace.open(table.keyspace).getReplicationStrategy().hasTransientReplicas())
+ if (keyspace.replicationStrategy.hasTransientReplicas())
throw new InvalidRequestException(TRANSIENTLY_REPLICATED_KEYSPACE_NOT_SUPPORTED);
// guardrails to limit number of secondary indexes per table.
diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/CreateKeyspaceStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/CreateKeyspaceStatement.java
index 13d52b1e15..16a117c890 100644
--- a/src/java/org/apache/cassandra/cql3/statements/schema/CreateKeyspaceStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/schema/CreateKeyspaceStatement.java
@@ -17,7 +17,6 @@
*/
package org.apache.cassandra.cql3.statements.schema;
-import java.util.HashSet;
import java.util.Set;
import com.google.common.collect.ImmutableSet;
@@ -36,12 +35,11 @@ import org.apache.cassandra.db.guardrails.Guardrails;
import org.apache.cassandra.exceptions.AlreadyExistsException;
import org.apache.cassandra.locator.LocalStrategy;
import org.apache.cassandra.locator.SimpleStrategy;
-import org.apache.cassandra.schema.KeyspaceMetadata;
+import org.apache.cassandra.schema.*;
import org.apache.cassandra.schema.KeyspaceParams.Option;
-import org.apache.cassandra.schema.Keyspaces;
import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff;
-import org.apache.cassandra.schema.Schema;
import org.apache.cassandra.service.ClientState;
+import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.transport.Event.SchemaChange;
import org.apache.cassandra.transport.Event.SchemaChange.Change;
@@ -51,7 +49,6 @@ public final class CreateKeyspaceStatement extends AlterSchemaStatement
private final KeyspaceAttributes attrs;
private final boolean ifNotExists;
- private final HashSet clientWarnings = new HashSet<>();
public CreateKeyspaceStatement(String keyspaceName, KeyspaceAttributes attrs, boolean ifNotExists)
{
@@ -60,7 +57,7 @@ public final class CreateKeyspaceStatement extends AlterSchemaStatement
this.ifNotExists = ifNotExists;
}
- public Keyspaces apply(Keyspaces schema)
+ public Keyspaces apply(ClusterMetadata metadata)
{
attrs.validate();
@@ -70,6 +67,7 @@ public final class CreateKeyspaceStatement extends AlterSchemaStatement
if (attrs.getReplicationStrategyClass() != null && attrs.getReplicationStrategyClass().equals(SimpleStrategy.class.getSimpleName()))
Guardrails.simpleStrategyEnabled.ensureEnabled("SimpleStrategy", state);
+ Keyspaces schema = metadata.schema.getKeyspaces();
if (schema.containsKeyspace(keyspaceName))
{
if (ifNotExists)
@@ -83,7 +81,11 @@ public final class CreateKeyspaceStatement extends AlterSchemaStatement
if (keyspace.params.replication.klass.equals(LocalStrategy.class))
throw ire("Unable to use given strategy class: LocalStrategy is reserved for internal use.");
- keyspace.params.validate(keyspaceName, state);
+ if (keyspace.params.replication.isMeta())
+ throw ire("Can not create a keyspace with MetaReplicationStrategy");
+
+ keyspace.params.validate(keyspaceName, state, metadata);
+ keyspace.replicationStrategy.validateExpectedOptions(metadata);
return schema.withAddedOrUpdated(keyspace);
}
diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/CreateTableStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/CreateTableStatement.java
index 747837f305..b606a96d99 100644
--- a/src/java/org/apache/cassandra/cql3/statements/schema/CreateTableStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/schema/CreateTableStatement.java
@@ -34,15 +34,16 @@ import org.apache.cassandra.audit.AuditLogEntryType;
import org.apache.cassandra.auth.DataResource;
import org.apache.cassandra.auth.IResource;
import org.apache.cassandra.auth.Permission;
+import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.cql3.*;
import org.apache.cassandra.cql3.functions.masking.ColumnMask;
-import org.apache.cassandra.db.Keyspace;
import org.apache.cassandra.db.guardrails.Guardrails;
import org.apache.cassandra.db.marshal.*;
import org.apache.cassandra.exceptions.AlreadyExistsException;
import org.apache.cassandra.schema.*;
import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff;
import org.apache.cassandra.service.ClientState;
+import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.service.reads.repair.ReadRepairStrategy;
import org.apache.cassandra.transport.Event.SchemaChange;
import org.apache.cassandra.transport.Event.SchemaChange.Change;
@@ -94,8 +95,9 @@ public final class CreateTableStatement extends AlterSchemaStatement
this.useCompactStorage = useCompactStorage;
}
- public Keyspaces apply(Keyspaces schema)
+ public Keyspaces apply(ClusterMetadata metadata)
{
+ Keyspaces schema = metadata.schema.getKeyspaces();
KeyspaceMetadata keyspace = schema.getNullable(keyspaceName);
if (null == keyspace)
throw ire("Keyspace '%s' doesn't exist", keyspaceName);
@@ -108,10 +110,13 @@ public final class CreateTableStatement extends AlterSchemaStatement
throw new AlreadyExistsException(keyspaceName, tableName);
}
- TableMetadata table = builder(keyspace.types).build();
+ TableMetadata.Builder builder = builder(keyspace.types).epoch(metadata.nextEpoch());
+ if (!builder.hasId() && !DatabaseDescriptor.useDeterministicTableID())
+ builder.id(TableId.get(metadata));
+ TableMetadata table = builder.build();
table.validate();
- if (keyspace.createReplicationStrategy().hasTransientReplicas()
+ if (keyspace.replicationStrategy.hasTransientReplicas()
&& table.params.readRepair != ReadRepairStrategy.NONE)
{
throw ire("read_repair must be set to 'NONE' for transiently replicated keyspaces");
@@ -128,6 +133,10 @@ public final class CreateTableStatement extends AlterSchemaStatement
{
super.validate(state);
+ // If a memtable configuration is specified, validate it against config
+ if (attrs.hasOption(TableParams.Option.MEMTABLE))
+ MemtableParams.get(attrs.getString(TableParams.Option.MEMTABLE.toString()));
+
// Guardrail on table properties
Guardrails.tableProperties.guard(attrs.updatedProperties(), attrs::removeProperty, state);
@@ -139,8 +148,7 @@ public final class CreateTableStatement extends AlterSchemaStatement
{
int totalUserTables = Schema.instance.getUserKeyspaces()
.stream()
- .map(Keyspace::open)
- .mapToInt(keyspace -> keyspace.getColumnFamilyStores().size())
+ .mapToInt(ksm -> ksm.tables.size())
.sum();
Guardrails.tables.guard(totalUserTables + 1, tableName, false, state);
}
diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/CreateTriggerStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/CreateTriggerStatement.java
index e85ffd80ae..392996b2f3 100644
--- a/src/java/org/apache/cassandra/cql3/statements/schema/CreateTriggerStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/schema/CreateTriggerStatement.java
@@ -24,6 +24,7 @@ import org.apache.cassandra.cql3.QualifiedName;
import org.apache.cassandra.schema.*;
import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff;
import org.apache.cassandra.service.ClientState;
+import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.triggers.TriggerExecutor;
import org.apache.cassandra.transport.Event.SchemaChange;
import org.apache.cassandra.transport.Event.SchemaChange.Change;
@@ -45,8 +46,10 @@ public final class CreateTriggerStatement extends AlterSchemaStatement
this.ifNotExists = ifNotExists;
}
- public Keyspaces apply(Keyspaces schema)
+ @Override
+ public Keyspaces apply(ClusterMetadata metadata)
{
+ Keyspaces schema = metadata.schema.getKeyspaces();
KeyspaceMetadata keyspace = schema.getNullable(keyspaceName);
if (null == keyspace)
throw ire("Keyspace '%s' doesn't exist", keyspaceName);
diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/CreateTypeStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/CreateTypeStatement.java
index d76c8089f6..bd63310f18 100644
--- a/src/java/org/apache/cassandra/cql3/statements/schema/CreateTypeStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/schema/CreateTypeStatement.java
@@ -34,6 +34,7 @@ import org.apache.cassandra.schema.Keyspaces;
import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff;
import org.apache.cassandra.schema.Types;
import org.apache.cassandra.service.ClientState;
+import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.transport.Event.SchemaChange;
import org.apache.cassandra.transport.Event.SchemaChange.Change;
import org.apache.cassandra.transport.Event.SchemaChange.Target;
@@ -75,8 +76,9 @@ public final class CreateTypeStatement extends AlterSchemaStatement
}
}
- public Keyspaces apply(Keyspaces schema)
+ public Keyspaces apply(ClusterMetadata metadata)
{
+ Keyspaces schema = metadata.schema.getKeyspaces();
KeyspaceMetadata keyspace = schema.getNullable(keyspaceName);
if (null == keyspace)
throw ire("Keyspace '%s' doesn't exist", keyspaceName);
diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/CreateViewStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/CreateViewStatement.java
index 05629e00fd..a4a8ddcfbe 100644
--- a/src/java/org/apache/cassandra/cql3/statements/schema/CreateViewStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/schema/CreateViewStatement.java
@@ -41,6 +41,7 @@ import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.schema.*;
import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff;
import org.apache.cassandra.service.ClientState;
+import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.transport.Event.SchemaChange;
import org.apache.cassandra.transport.Event.SchemaChange.Change;
import org.apache.cassandra.transport.Event.SchemaChange.Target;
@@ -110,7 +111,8 @@ public final class CreateViewStatement extends AlterSchemaStatement
this.state = state;
}
- public Keyspaces apply(Keyspaces schema)
+ @Override
+ public Keyspaces apply(ClusterMetadata metadata)
{
if (!DatabaseDescriptor.getMaterializedViewsEnabled())
throw ire("Materialized views are disabled. Enable in cassandra.yaml to use.");
@@ -119,11 +121,12 @@ public final class CreateViewStatement extends AlterSchemaStatement
* Basic dependency validations
*/
+ Keyspaces schema = metadata.schema.getKeyspaces();
KeyspaceMetadata keyspace = schema.getNullable(keyspaceName);
if (null == keyspace)
throw ire("Keyspace '%s' doesn't exist", keyspaceName);
- if (keyspace.createReplicationStrategy().hasTransientReplicas())
+ if (keyspace.replicationStrategy.hasTransientReplicas())
throw new InvalidRequestException("Materialized views are not supported on transiently replicated keyspaces");
TableMetadata table = keyspace.tables.getNullable(tableName);
@@ -324,6 +327,8 @@ public final class CreateViewStatement extends AlterSchemaStatement
if (attrs.hasProperty(TableAttributes.ID))
builder.id(attrs.getId());
+ else if (!builder.hasId() && !DatabaseDescriptor.useDeterministicTableID())
+ builder.id(TableId.get(metadata));
builder.params(attrs.asNewTableParams())
.kind(TableMetadata.Kind.VIEW);
diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/DropAggregateStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/DropAggregateStatement.java
index d83fbbf97f..e61332ee12 100644
--- a/src/java/org/apache/cassandra/cql3/statements/schema/DropAggregateStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/schema/DropAggregateStatement.java
@@ -35,6 +35,7 @@ import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.schema.*;
import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff;
import org.apache.cassandra.service.ClientState;
+import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.transport.Event.SchemaChange;
import org.apache.cassandra.transport.Event.SchemaChange.Change;
@@ -64,13 +65,14 @@ public final class DropAggregateStatement extends AlterSchemaStatement
this.ifExists = ifExists;
}
- public Keyspaces apply(Keyspaces schema)
+ public Keyspaces apply(ClusterMetadata metadata)
{
String name =
argumentsSpeficied
? format("%s.%s(%s)", keyspaceName, aggregateName, join(", ", transform(arguments, CQL3Type.Raw::toString)))
: format("%s.%s", keyspaceName, aggregateName);
+ Keyspaces schema = metadata.schema.getKeyspaces();
KeyspaceMetadata keyspace = schema.getNullable(keyspaceName);
if (null == keyspace)
{
diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/DropFunctionStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/DropFunctionStatement.java
index af82206322..0353964683 100644
--- a/src/java/org/apache/cassandra/cql3/statements/schema/DropFunctionStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/schema/DropFunctionStatement.java
@@ -35,6 +35,7 @@ import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.schema.*;
import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff;
import org.apache.cassandra.service.ClientState;
+import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.transport.Event.SchemaChange;
import org.apache.cassandra.transport.Event.SchemaChange.Change;
@@ -65,13 +66,15 @@ public final class DropFunctionStatement extends AlterSchemaStatement
this.ifExists = ifExists;
}
- public Keyspaces apply(Keyspaces schema)
+ @Override
+ public Keyspaces apply(ClusterMetadata metadata)
{
String name =
argumentsSpeficied
? format("%s.%s(%s)", keyspaceName, functionName, join(", ", transform(arguments, CQL3Type.Raw::toString)))
: format("%s.%s", keyspaceName, functionName);
+ Keyspaces schema = metadata.schema.getKeyspaces();
KeyspaceMetadata keyspace = schema.getNullable(keyspaceName);
if (null == keyspace)
{
diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/DropIndexStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/DropIndexStatement.java
index 24b372d8c3..06cbc4be99 100644
--- a/src/java/org/apache/cassandra/cql3/statements/schema/DropIndexStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/schema/DropIndexStatement.java
@@ -26,6 +26,7 @@ import org.apache.cassandra.schema.*;
import org.apache.cassandra.schema.KeyspaceMetadata.KeyspaceDiff;
import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff;
import org.apache.cassandra.service.ClientState;
+import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.transport.Event.SchemaChange;
import org.apache.cassandra.transport.Event.SchemaChange.Change;
import org.apache.cassandra.transport.Event.SchemaChange.Target;
@@ -42,8 +43,10 @@ public final class DropIndexStatement extends AlterSchemaStatement
this.ifExists = ifExists;
}
- public Keyspaces apply(Keyspaces schema)
+ @Override
+ public Keyspaces apply(ClusterMetadata metadata)
{
+ Keyspaces schema = metadata.schema.getKeyspaces();
KeyspaceMetadata keyspace = schema.getNullable(keyspaceName);
TableMetadata table = null == keyspace
diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/DropKeyspaceStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/DropKeyspaceStatement.java
index 47e514a527..e074da54a3 100644
--- a/src/java/org/apache/cassandra/cql3/statements/schema/DropKeyspaceStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/schema/DropKeyspaceStatement.java
@@ -25,6 +25,7 @@ import org.apache.cassandra.db.guardrails.Guardrails;
import org.apache.cassandra.schema.Keyspaces;
import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff;
import org.apache.cassandra.service.ClientState;
+import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.transport.Event.SchemaChange;
import org.apache.cassandra.transport.Event.SchemaChange.Change;
@@ -38,10 +39,12 @@ public final class DropKeyspaceStatement extends AlterSchemaStatement
this.ifExists = ifExists;
}
- public Keyspaces apply(Keyspaces schema)
+ @Override
+ public Keyspaces apply(ClusterMetadata metadata)
{
Guardrails.dropKeyspaceEnabled.ensureEnabled(state);
+ Keyspaces schema = metadata.schema.getKeyspaces();
if (schema.containsKeyspace(keyspaceName))
return schema.without(keyspaceName);
diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/DropTableStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/DropTableStatement.java
index 78c98be3a7..56848a8c22 100644
--- a/src/java/org/apache/cassandra/cql3/statements/schema/DropTableStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/schema/DropTableStatement.java
@@ -26,6 +26,7 @@ import org.apache.cassandra.db.guardrails.Guardrails;
import org.apache.cassandra.schema.*;
import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff;
import org.apache.cassandra.service.ClientState;
+import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.transport.Event.SchemaChange;
import org.apache.cassandra.transport.Event.SchemaChange.Change;
import org.apache.cassandra.transport.Event.SchemaChange.Target;
@@ -47,10 +48,11 @@ public final class DropTableStatement extends AlterSchemaStatement
this.ifExists = ifExists;
}
- public Keyspaces apply(Keyspaces schema)
+ public Keyspaces apply(ClusterMetadata metadata)
{
Guardrails.dropTruncateTableEnabled.ensureEnabled(state);
+ Keyspaces schema = metadata.schema.getKeyspaces();
KeyspaceMetadata keyspace = schema.getNullable(keyspaceName);
TableMetadata table = null == keyspace
diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/DropTriggerStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/DropTriggerStatement.java
index 967e56834f..15007c3b1e 100644
--- a/src/java/org/apache/cassandra/cql3/statements/schema/DropTriggerStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/schema/DropTriggerStatement.java
@@ -24,6 +24,7 @@ import org.apache.cassandra.cql3.QualifiedName;
import org.apache.cassandra.schema.*;
import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff;
import org.apache.cassandra.service.ClientState;
+import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.transport.Event.SchemaChange;
import org.apache.cassandra.transport.Event.SchemaChange.Change;
import org.apache.cassandra.transport.Event.SchemaChange.Target;
@@ -42,8 +43,10 @@ public final class DropTriggerStatement extends AlterSchemaStatement
this.ifExists = ifExists;
}
- public Keyspaces apply(Keyspaces schema)
+ @Override
+ public Keyspaces apply(ClusterMetadata metadata)
{
+ Keyspaces schema = metadata.schema.getKeyspaces();
KeyspaceMetadata keyspace = schema.getNullable(keyspaceName);
TableMetadata table = null == keyspace
diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/DropTypeStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/DropTypeStatement.java
index 97830c882a..105c8f5db8 100644
--- a/src/java/org/apache/cassandra/cql3/statements/schema/DropTypeStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/schema/DropTypeStatement.java
@@ -31,6 +31,7 @@ import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff;
import org.apache.cassandra.schema.Keyspaces;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.ClientState;
+import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.transport.Event.SchemaChange.Change;
import org.apache.cassandra.transport.Event.SchemaChange.Target;
import org.apache.cassandra.transport.Event.SchemaChange;
@@ -55,10 +56,12 @@ public final class DropTypeStatement extends AlterSchemaStatement
}
// TODO: expand types into tuples in all dropped columns of all tables
- public Keyspaces apply(Keyspaces schema)
+ @Override
+ public Keyspaces apply(ClusterMetadata metadata)
{
ByteBuffer name = bytes(typeName);
+ Keyspaces schema = metadata.schema.getKeyspaces();
KeyspaceMetadata keyspace = schema.getNullable(keyspaceName);
UserType type = null == keyspace
diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/DropViewStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/DropViewStatement.java
index 2c73717546..121575503c 100644
--- a/src/java/org/apache/cassandra/cql3/statements/schema/DropViewStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/schema/DropViewStatement.java
@@ -25,6 +25,7 @@ import org.apache.cassandra.cql3.QualifiedName;
import org.apache.cassandra.schema.*;
import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff;
import org.apache.cassandra.service.ClientState;
+import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.transport.Event.SchemaChange;
import org.apache.cassandra.transport.Event.SchemaChange.Change;
import org.apache.cassandra.transport.Event.SchemaChange.Target;
@@ -41,8 +42,10 @@ public final class DropViewStatement extends AlterSchemaStatement
this.ifExists = ifExists;
}
- public Keyspaces apply(Keyspaces schema)
+ @Override
+ public Keyspaces apply(ClusterMetadata metadata)
{
+ Keyspaces schema = metadata.schema.getKeyspaces();
KeyspaceMetadata keyspace = schema.getNullable(keyspaceName);
ViewMetadata view = null == keyspace
diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/TableAttributes.java b/src/java/org/apache/cassandra/cql3/statements/schema/TableAttributes.java
index 93d477c847..87af6b840b 100644
--- a/src/java/org/apache/cassandra/cql3/statements/schema/TableAttributes.java
+++ b/src/java/org/apache/cassandra/cql3/statements/schema/TableAttributes.java
@@ -115,8 +115,8 @@ public final class TableAttributes extends PropertyDefinitions
if (hasOption(COMPRESSION))
builder.compression(CompressionParams.fromMap(getMap(COMPRESSION)));
- if (hasOption(MEMTABLE))
- builder.memtable(MemtableParams.get(getString(MEMTABLE)));
+ if (hasOption(Option.MEMTABLE))
+ builder.memtable(MemtableParams.getWithFallback(getString(Option.MEMTABLE)));
if (hasOption(DEFAULT_TIME_TO_LIVE))
builder.defaultTimeToLive(getInt(DEFAULT_TIME_TO_LIVE));
diff --git a/src/java/org/apache/cassandra/db/AbstractMutationVerbHandler.java b/src/java/org/apache/cassandra/db/AbstractMutationVerbHandler.java
new file mode 100644
index 0000000000..cfea7eb45c
--- /dev/null
+++ b/src/java/org/apache/cassandra/db/AbstractMutationVerbHandler.java
@@ -0,0 +1,185 @@
+/*
+ * 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.db;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.cassandra.db.partitions.PartitionUpdate;
+import org.apache.cassandra.exceptions.CoordinatorBehindException;
+import org.apache.cassandra.exceptions.InvalidRoutingException;
+import org.apache.cassandra.locator.InetAddressAndPort;
+import org.apache.cassandra.metrics.TCMMetrics;
+import org.apache.cassandra.net.IVerbHandler;
+import org.apache.cassandra.net.Message;
+import org.apache.cassandra.schema.SchemaConstants;
+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.VersionedEndpoints;
+import org.apache.cassandra.utils.NoSpamLogger;
+
+public abstract class AbstractMutationVerbHandler implements IVerbHandler
+{
+ private static final Logger logger = LoggerFactory.getLogger(AbstractMutationVerbHandler.class);
+ private static final String logMessageTemplate = "Received mutation from {} for token {} outside valid range for keyspace {}";
+
+ public void doVerb(Message message) throws IOException
+ {
+ processMessage(message, message.respondTo());
+ }
+
+ protected void processMessage(Message message, InetAddressAndPort respondTo)
+ {
+ if (message.epoch().isAfter(Epoch.EMPTY))
+ {
+ ClusterMetadata metadata = ClusterMetadata.current();
+ metadata = checkTokenOwnership(metadata, message);
+ metadata = checkSchemaVersion(metadata, message);
+ }
+ applyMutation(message, respondTo);
+ }
+
+ abstract void applyMutation(Message message, InetAddressAndPort respondToAddress);
+
+ private ClusterMetadata checkTokenOwnership(ClusterMetadata metadata, Message message)
+ {
+ String keyspace = message.payload.getKeyspaceName();
+ DecoratedKey key = message.payload.key();
+
+ VersionedEndpoints.ForToken forToken = writePlacements(metadata, keyspace, key);
+
+ if (message.epoch().isAfter(metadata.epoch))
+ {
+ // If replica detects that coordinator has made an out-of-range request, it has to catch up blockingly,
+ // since coordinator's routing may be more recent.
+ if (!forToken.get().containsSelf())
+ {
+ metadata = ClusterMetadataService.instance().fetchLogFromPeerOrCMS(metadata, message.from(), message.epoch());
+ forToken = writePlacements(metadata, keyspace, key);
+ }
+ // Otherwise, coordinator and the replica agree about the placement of the givent token, so catch-up can be async
+ else
+ {
+ ClusterMetadataService.instance().fetchLogFromPeerOrCMSAsync(metadata, message.from(), message.epoch());
+ }
+ }
+
+ if (!forToken.get().containsSelf())
+ {
+ StorageService.instance.incOutOfRangeOperationCount();
+ Keyspace.open(message.payload.getKeyspaceName()).metric.outOfRangeTokenWrites.inc();
+ NoSpamLogger.log(logger, NoSpamLogger.Level.WARN, 1, TimeUnit.SECONDS, logMessageTemplate, message.from(), key.getToken(), message.payload.getKeyspaceName());
+ throw InvalidRoutingException.forWrite(message.from(), key.getToken(), metadata.epoch, message.payload);
+ }
+
+ if (forToken.lastModified().isAfter(message.epoch()))
+ {
+ TCMMetrics.instance.coordinatorBehindPlacements.mark();
+ throw new CoordinatorBehindException(String.format("Routing is correct, but coordinator needs to catch-up at least to epoch %s to maintain consistency. Current coordinator epoch is %s",
+ forToken.lastModified(), message.epoch()));
+ }
+
+ return metadata;
+ }
+
+ private ClusterMetadata checkSchemaVersion(ClusterMetadata metadata, Message message)
+ {
+ if (SchemaConstants.isSystemKeyspace(message.payload.getKeyspaceName()) || message.epoch().is(metadata.epoch))
+ return metadata;
+ String keyspace = message.payload.getKeyspaceName();
+ Keyspace ks = metadata.schema.getKeyspace(keyspace);
+ if (ks != null)
+ {
+ if (message.epoch().isAfter(metadata.epoch))
+ {
+ // coordinator is ahead - check each partition update if the schema is ahead of the schema we have for the table
+ for (PartitionUpdate pu : message.payload.getPartitionUpdates())
+ {
+ Epoch remoteSchemaEpoch = pu.serializedAtEpoch;
+ if (remoteSchemaEpoch != null && remoteSchemaEpoch.isAfter(metadata.epoch))
+ {
+ // the partition update was serialized after the epoch we currently know, catch up and
+ // make sure we've seen the epoch it has seen, otherwise fail request.
+ metadata = ClusterMetadataService.instance().fetchLogFromPeerOrCMS(metadata, message.from(), message.epoch());
+ if (pu.serializedAtEpoch.isAfter(metadata.epoch))
+ throw new IllegalStateException(String.format("Coordinator %s is still ahead after fetching log, our epoch = %s, their epoch = %s",
+ message.from(),
+ metadata.epoch, message.epoch()));
+ }
+ }
+ }
+ else if (message.epoch().isBefore(metadata.schema.lastModified()))
+ {
+ // coordinator might not have seen the latest schema change - check each modified table individually
+ for (PartitionUpdate pu : message.payload.getPartitionUpdates())
+ {
+ // coordinator could be behind, check local tables
+ ColumnFamilyStore cfs = ks.getColumnFamilyStore(pu.metadata().id);
+ if (cfs != null)
+ {
+ Epoch remoteSchemaEpoch = pu.serializedAtEpoch;
+ if (remoteSchemaEpoch != null && remoteSchemaEpoch.isBefore(cfs.metadata().epoch))
+ {
+ TCMMetrics.instance.coordinatorBehindSchema.mark();
+ throw new CoordinatorBehindException(String.format("Coordinator %s is behind, our epoch = %s, their epoch = %s",
+ message.from(),
+ metadata.epoch, message.epoch()));
+ }
+ }
+ else
+ {
+ TCMMetrics.instance.coordinatorBehindSchema.mark();
+ throw new CoordinatorBehindException(String.format("Schema mismatch, coordinator %s is behind, we're missing table %s.%s, our epoch = %s, their epoch = %s",
+ message.from(),
+ pu.metadata().keyspace,
+ pu.metadata().name,
+ metadata.epoch, message.epoch()));
+ }
+ }
+ }
+ }
+ else
+ {
+ if (message.epoch().isBefore(metadata.schema.lastModified()))
+ {
+ TCMMetrics.instance.coordinatorBehindSchema.mark();
+ throw new CoordinatorBehindException(String.format("Schema mismatch, coordinator %s is behind, we're missing keyspace %s, our epoch = %s, their epoch = %s",
+ message.from(),
+ keyspace,
+ metadata.epoch, message.epoch()));
+ }
+ else
+ {
+ metadata = ClusterMetadataService.instance().fetchLogFromPeerOrCMS(metadata, message.from(), message.epoch());
+ }
+ }
+
+ return metadata;
+ }
+
+ private static VersionedEndpoints.ForToken writePlacements(ClusterMetadata metadata, String keyspace, DecoratedKey key)
+ {
+ return metadata.placements.get(metadata.schema.getKeyspace(keyspace).getMetadata().params.replication).writes.forToken(key.getToken());
+ }
+}
diff --git a/src/java/org/apache/cassandra/db/ColumnFamilyStore.java b/src/java/org/apache/cassandra/db/ColumnFamilyStore.java
index 1ef67878d5..83957a6260 100644
--- a/src/java/org/apache/cassandra/db/ColumnFamilyStore.java
+++ b/src/java/org/apache/cassandra/db/ColumnFamilyStore.java
@@ -90,14 +90,14 @@ import org.apache.cassandra.db.compaction.CompactionStrategyManager;
import org.apache.cassandra.db.compaction.OperationType;
import org.apache.cassandra.db.filter.ClusteringIndexFilter;
import org.apache.cassandra.db.filter.DataLimits;
+import org.apache.cassandra.db.memtable.Flushing;
+import org.apache.cassandra.db.memtable.Memtable;
+import org.apache.cassandra.db.memtable.ShardBoundaries;
import org.apache.cassandra.db.lifecycle.LifecycleNewTracker;
import org.apache.cassandra.db.lifecycle.LifecycleTransaction;
import org.apache.cassandra.db.lifecycle.SSTableSet;
import org.apache.cassandra.db.lifecycle.Tracker;
import org.apache.cassandra.db.lifecycle.View;
-import org.apache.cassandra.db.memtable.Flushing;
-import org.apache.cassandra.db.memtable.Memtable;
-import org.apache.cassandra.db.memtable.ShardBoundaries;
import org.apache.cassandra.db.partitions.CachedPartition;
import org.apache.cassandra.db.partitions.PartitionUpdate;
import org.apache.cassandra.db.repair.CassandraTableRepairManager;
@@ -161,6 +161,8 @@ import org.apache.cassandra.service.snapshot.SnapshotLoader;
import org.apache.cassandra.service.snapshot.SnapshotManifest;
import org.apache.cassandra.service.snapshot.TableSnapshot;
import org.apache.cassandra.streaming.TableStreamManager;
+import org.apache.cassandra.tcm.ClusterMetadata;
+import org.apache.cassandra.tcm.Epoch;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.DefaultValue;
import org.apache.cassandra.utils.ExecutorUtils;
@@ -261,7 +263,8 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner
static final String TOKEN_DELIMITER = ":";
/** Special values used when the local ranges are not changed with ring changes (e.g. local tables). */
- public static final int RING_VERSION_IRRELEVANT = -1;
+ // TODO - make this Epoch.EMPTY
+ public static final Epoch RING_VERSION_IRRELEVANT = Epoch.create(-1);
static
{
@@ -375,26 +378,31 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner
}
public void reload()
+ {
+ reload(metadata());
+ }
+
+ public void reload(TableMetadata tableMetadata)
{
// metadata object has been mutated directly. make all the members jibe with new settings.
// only update these runtime-modifiable settings if they have not been modified.
if (!minCompactionThreshold.isModified())
for (ColumnFamilyStore cfs : concatWithIndexes())
- cfs.minCompactionThreshold = new DefaultValue(metadata().params.compaction.minCompactionThreshold());
+ cfs.minCompactionThreshold = new DefaultValue<>(tableMetadata.params.compaction.minCompactionThreshold());
if (!maxCompactionThreshold.isModified())
for (ColumnFamilyStore cfs : concatWithIndexes())
- cfs.maxCompactionThreshold = new DefaultValue(metadata().params.compaction.maxCompactionThreshold());
+ cfs.maxCompactionThreshold = new DefaultValue<>(tableMetadata.params.compaction.maxCompactionThreshold());
if (!crcCheckChance.isModified())
for (ColumnFamilyStore cfs : concatWithIndexes())
- cfs.crcCheckChance = new DefaultValue(metadata().params.crcCheckChance);
+ cfs.crcCheckChance = new DefaultValue<>(tableMetadata.params.crcCheckChance);
- compactionStrategyManager.maybeReloadParamsFromSchema(metadata().params.compaction);
+ compactionStrategyManager.maybeReloadParamsFromSchema(tableMetadata.params.compaction);
- indexManager.reload();
+ indexManager.reload(tableMetadata);
- memtableFactory = metadata().params.memtable.factory();
- switchMemtableOrNotify(FlushReason.SCHEMA_CHANGE, Memtable::metadataUpdated);
+ memtableFactory = tableMetadata.params.memtable.factory();
+ switchMemtableOrNotify(FlushReason.SCHEMA_CHANGE, tableMetadata, Memtable::metadataUpdated);
}
public static Runnable getBackgroundCompactionTaskSubmitter()
@@ -453,7 +461,9 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner
{
CompressionParams params = CompressionParams.fromMap(opts);
params.validate();
- metadata.setLocalOverrides(metadata().unbuild().compression(params).build());
+
+ TableMetadata orig = metadata();
+ metadata.setLocalOverrides(orig.unbuild().compression(params).epoch(orig.epoch).build());
}
catch (ConfigurationException e)
{
@@ -470,27 +480,26 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner
public ColumnFamilyStore(Keyspace keyspace,
String columnFamilyName,
Supplier extends SSTableId> sstableIdGenerator,
- TableMetadataRef metadata,
+ TableMetadata initMetadata,
Directories directories,
boolean loadSSTables,
- boolean registerBookeeping,
- boolean offline)
+ boolean registerBookeeping)
{
assert directories != null;
- assert metadata != null : "null metadata for " + keyspace + ':' + columnFamilyName;
+ assert initMetadata != null : "null metadata for " + keyspace + ':' + columnFamilyName;
this.keyspace = keyspace;
- this.metadata = metadata;
+ this.metadata = initMetadata.ref;
this.directories = directories;
name = columnFamilyName;
- minCompactionThreshold = new DefaultValue<>(metadata.get().params.compaction.minCompactionThreshold());
- maxCompactionThreshold = new DefaultValue<>(metadata.get().params.compaction.maxCompactionThreshold());
- crcCheckChance = new DefaultValue<>(metadata.get().params.crcCheckChance);
- viewManager = keyspace.viewManager.forTable(metadata.id);
+ minCompactionThreshold = new DefaultValue<>(initMetadata.params.compaction.minCompactionThreshold());
+ maxCompactionThreshold = new DefaultValue<>(initMetadata.params.compaction.maxCompactionThreshold());
+ crcCheckChance = new DefaultValue<>(initMetadata.params.crcCheckChance);
+ viewManager = keyspace.viewManager.forTable(initMetadata);
this.sstableIdGenerator = sstableIdGenerator;
sampleReadLatencyMicros = DatabaseDescriptor.getReadRpcTimeout(TimeUnit.MICROSECONDS) / 2;
additionalWriteLatencyMicros = DatabaseDescriptor.getWriteRpcTimeout(TimeUnit.MICROSECONDS) / 2;
- memtableFactory = metadata.get().params.memtable.factory();
+ memtableFactory = initMetadata.params.memtable.factory();
logger.info("Initializing {}.{}", getKeyspaceName(), name);
@@ -514,7 +523,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner
{
Directories.SSTableLister sstableFiles = directories.sstableLister(Directories.OnTxnErr.IGNORE).skipTemporary(true);
sstables = SSTableReader.openAll(this, sstableFiles.list().entrySet(), metadata);
- data.addInitialSSTablesWithoutUpdatingSize(sstables);
+ data.addInitialSSTablesWithoutUpdatingSize(sstables, this);
}
// compaction strategy should be created after the CFS has been prepared
@@ -528,7 +537,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner
// create the private ColumnFamilyStores for the secondary column indexes
indexManager = new SecondaryIndexManager(this);
- for (IndexMetadata info : metadata.get().indexes)
+ for (IndexMetadata info : initMetadata.indexes)
{
indexManager.addIndex(info, true);
}
@@ -563,7 +572,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner
if (DatabaseDescriptor.isClientOrToolInitialized() || SchemaConstants.isSystemKeyspace(getKeyspaceName()))
topPartitions = null;
else
- topPartitions = new TopPartitionTracker(metadata());
+ topPartitions = new TopPartitionTracker(initMetadata);
}
public static String getTableMBeanName(String ks, String name, boolean isIndex)
@@ -741,32 +750,31 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner
}
- public static ColumnFamilyStore createColumnFamilyStore(Keyspace keyspace, TableMetadataRef metadata, boolean loadSSTables)
+ public static ColumnFamilyStore createColumnFamilyStore(Keyspace keyspace, TableMetadata metadata, boolean loadSSTables)
{
return createColumnFamilyStore(keyspace, metadata.name, metadata, loadSSTables);
}
public static ColumnFamilyStore createColumnFamilyStore(Keyspace keyspace,
- String columnFamily,
- TableMetadataRef metadata,
- boolean loadSSTables)
+ String columnFamily,
+ TableMetadata metadata,
+ boolean loadSSTables)
{
- Directories directories = new Directories(metadata.get());
- return createColumnFamilyStore(keyspace, columnFamily, metadata, directories, loadSSTables, true, false);
+ Directories directories = new Directories(metadata);
+ return createColumnFamilyStore(keyspace, columnFamily, metadata, directories, loadSSTables, true);
}
/** This is only directly used by offline tools */
public static synchronized ColumnFamilyStore createColumnFamilyStore(Keyspace keyspace,
String columnFamily,
- TableMetadataRef metadata,
+ TableMetadata metadata,
Directories directories,
boolean loadSSTables,
- boolean registerBookkeeping,
- boolean offline)
+ boolean registerBookkeeping)
{
return new ColumnFamilyStore(keyspace, columnFamily,
directories.getUIDGenerator(SSTableIdFactory.instance.defaultBuilder()),
- metadata, directories, loadSSTables, registerBookkeeping, offline);
+ metadata, directories, loadSSTables, registerBookkeeping);
}
/**
@@ -963,10 +971,10 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner
* Checks with the memtable if it should be switched for the given reason, and if not, calls the specified
* notification method.
*/
- private void switchMemtableOrNotify(FlushReason reason, Consumer elseNotify)
+ private void switchMemtableOrNotify(FlushReason reason, TableMetadata metadata, Consumer elseNotify)
{
Memtable currentMemtable = data.getView().getCurrentMemtable();
- if (currentMemtable.shouldSwitch(reason))
+ if (currentMemtable.shouldSwitch(reason, metadata))
switchMemtableIfCurrent(currentMemtable, reason);
else
elseNotify.accept(currentMemtable);
@@ -1476,9 +1484,9 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner
public static class VersionedLocalRanges extends ArrayList
{
- public final long ringVersion;
+ public final Epoch ringVersion;
- public VersionedLocalRanges(long ringVersion, int initialSize)
+ public VersionedLocalRanges(Epoch ringVersion, int initialSize)
{
super(initialSize);
this.ringVersion = ringVersion;
@@ -1487,16 +1495,16 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner
public VersionedLocalRanges localRangesWeighted()
{
+ ClusterMetadata metadata = ClusterMetadata.current();
if (!SchemaConstants.isLocalSystemKeyspace(getKeyspaceName())
- && getPartitioner() == StorageService.instance.getTokenMetadata().partitioner)
+ && getPartitioner() == metadata.partitioner)
{
DiskBoundaryManager.VersionedRangesAtEndpoint versionedLocalRanges = DiskBoundaryManager.getVersionedLocalRanges(this);
Set> localRanges = versionedLocalRanges.rangesAtEndpoint.ranges();
- long ringVersion = versionedLocalRanges.ringVersion;
-
+ Epoch epoch = versionedLocalRanges.epoch;
if (!localRanges.isEmpty())
{
- VersionedLocalRanges weightedRanges = new VersionedLocalRanges(ringVersion, localRanges.size());
+ VersionedLocalRanges weightedRanges = new VersionedLocalRanges(epoch, localRanges.size());
for (Range r : localRanges)
{
// WeightedRange supports only unwrapped ranges as it relies
@@ -1509,7 +1517,7 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner
}
else
{
- return fullWeightedRange(ringVersion, getPartitioner());
+ return fullWeightedRange(epoch, getPartitioner());
}
}
else
@@ -1527,11 +1535,13 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner
return ShardBoundaries.NONE;
ShardBoundaries shardBoundaries = cachedShardBoundaries;
+ ClusterMetadata metadata = ClusterMetadata.currentNullable();
+ if (metadata == null)
+ return ShardBoundaries.NONE;
if (shardBoundaries == null ||
shardBoundaries.shardCount() != shardCount ||
- (shardBoundaries.ringVersion != RING_VERSION_IRRELEVANT &&
- shardBoundaries.ringVersion != StorageService.instance.getTokenMetadata().getRingVersion()))
+ (!shardBoundaries.epoch.equals(Epoch.EMPTY) && !shardBoundaries.epoch.equals(metadata.epoch)))
{
VersionedLocalRanges weightedRanges = localRangesWeighted();
@@ -1545,9 +1555,9 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner
}
@VisibleForTesting
- public static VersionedLocalRanges fullWeightedRange(long ringVersion, IPartitioner partitioner)
+ public static VersionedLocalRanges fullWeightedRange(Epoch epoch, IPartitioner partitioner)
{
- VersionedLocalRanges ranges = new VersionedLocalRanges(ringVersion, 1);
+ VersionedLocalRanges ranges = new VersionedLocalRanges(epoch, 1);
ranges.add(new Splitter.WeightedRange(1.0, new Range<>(partitioner.getMinimumToken(), partitioner.getMinimumToken())));
return ranges;
}
@@ -3341,14 +3351,19 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean, Memtable.Owner
public DiskBoundaries getDiskBoundaries()
{
- return diskBoundaryManager.getDiskBoundaries(this);
+ return diskBoundaryManager.getDiskBoundaries(this, metadata.get());
+ }
+
+ public DiskBoundaries getDiskBoundaries(TableMetadata initialMetadata)
+ {
+ return diskBoundaryManager.getDiskBoundaries(this, initialMetadata);
}
public void invalidateLocalRanges()
{
diskBoundaryManager.invalidate();
- switchMemtableOrNotify(FlushReason.OWNED_RANGES_CHANGE, Memtable::localRangesUpdated);
+ switchMemtableOrNotify(FlushReason.OWNED_RANGES_CHANGE, metadata(), Memtable::localRangesUpdated);
}
@Override
diff --git a/src/java/org/apache/cassandra/db/CounterMutationVerbHandler.java b/src/java/org/apache/cassandra/db/CounterMutationVerbHandler.java
index e4c7669806..2fef53a993 100644
--- a/src/java/org/apache/cassandra/db/CounterMutationVerbHandler.java
+++ b/src/java/org/apache/cassandra/db/CounterMutationVerbHandler.java
@@ -21,20 +21,20 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.config.DatabaseDescriptor;
-import org.apache.cassandra.net.IVerbHandler;
+import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.net.Message;
import org.apache.cassandra.net.MessagingService;
import org.apache.cassandra.service.StorageProxy;
import static org.apache.cassandra.utils.Clock.Global.nanoTime;
-public class CounterMutationVerbHandler implements IVerbHandler
+public class CounterMutationVerbHandler extends AbstractMutationVerbHandler
{
public static final CounterMutationVerbHandler instance = new CounterMutationVerbHandler();
private static final Logger logger = LoggerFactory.getLogger(CounterMutationVerbHandler.class);
- public void doVerb(final Message message)
+ protected void applyMutation(final Message message, InetAddressAndPort respondToAddress)
{
long queryStartNanoTime = nanoTime();
final CounterMutation cm = message.payload;
@@ -50,7 +50,7 @@ public class CounterMutationVerbHandler implements IVerbHandler
// it's own in that case.
StorageProxy.applyCounterMutationOnLeader(cm,
localDataCenter,
- () -> MessagingService.instance().send(message.emptyResponse(), message.from()),
+ () -> MessagingService.instance().send(message.emptyResponse(), respondToAddress),
queryStartNanoTime);
}
}
diff --git a/src/java/org/apache/cassandra/db/DiskBoundaries.java b/src/java/org/apache/cassandra/db/DiskBoundaries.java
index 7fe10f4c13..75868e35e6 100644
--- a/src/java/org/apache/cassandra/db/DiskBoundaries.java
+++ b/src/java/org/apache/cassandra/db/DiskBoundaries.java
@@ -27,28 +27,32 @@ import com.google.common.collect.ImmutableList;
import org.apache.cassandra.io.sstable.Descriptor;
import org.apache.cassandra.io.sstable.format.SSTableReader;
-import org.apache.cassandra.service.StorageService;
+import org.apache.cassandra.tcm.Epoch;
public class DiskBoundaries
{
public final List directories;
public final ImmutableList positions;
- final long ringVersion;
+ final Epoch epoch;
final int directoriesVersion;
private final ColumnFamilyStore cfs;
private volatile boolean isInvalid = false;
public DiskBoundaries(ColumnFamilyStore cfs, Directories.DataDirectory[] directories, int diskVersion)
{
- this(cfs, directories, null, -1, diskVersion);
+ this(cfs, directories, null, Epoch.EMPTY, diskVersion);
}
@VisibleForTesting
- public DiskBoundaries(ColumnFamilyStore cfs, Directories.DataDirectory[] directories, List positions, long ringVersion, int diskVersion)
+ public DiskBoundaries(ColumnFamilyStore cfs,
+ Directories.DataDirectory[] directories,
+ List positions,
+ Epoch epoch,
+ int diskVersion)
{
this.directories = directories == null ? null : ImmutableList.copyOf(directories);
this.positions = positions == null ? null : ImmutableList.copyOf(positions);
- this.ringVersion = ringVersion;
+ this.epoch = epoch;
this.directoriesVersion = diskVersion;
this.cfs = cfs;
}
@@ -60,7 +64,7 @@ public class DiskBoundaries
DiskBoundaries that = (DiskBoundaries) o;
- if (ringVersion != that.ringVersion) return false;
+ if (!epoch.equals(that.epoch)) return false;
if (directoriesVersion != that.directoriesVersion) return false;
if (!directories.equals(that.directories)) return false;
return positions != null ? positions.equals(that.positions) : that.positions == null;
@@ -70,7 +74,7 @@ public class DiskBoundaries
{
int result = directories != null ? directories.hashCode() : 0;
result = 31 * result + (positions != null ? positions.hashCode() : 0);
- result = 31 * result + (int) (ringVersion ^ (ringVersion >>> 32));
+ result = 31 * result + epoch.hashCode();
result = 31 * result + directoriesVersion;
return result;
}
@@ -80,7 +84,7 @@ public class DiskBoundaries
return "DiskBoundaries{" +
"directories=" + directories +
", positions=" + positions +
- ", ringVersion=" + ringVersion +
+ ", epoch=" + epoch +
", directoriesVersion=" + directoriesVersion +
'}';
}
@@ -93,8 +97,7 @@ public class DiskBoundaries
if (isInvalid)
return true;
int currentDiskVersion = DisallowedDirectories.getDirectoriesVersion();
- long currentRingVersion = StorageService.instance.getTokenMetadata().getRingVersion();
- return currentDiskVersion != directoriesVersion || (ringVersion != -1 && currentRingVersion != ringVersion);
+ return currentDiskVersion != directoriesVersion;
}
public void invalidate()
diff --git a/src/java/org/apache/cassandra/db/DiskBoundaryManager.java b/src/java/org/apache/cassandra/db/DiskBoundaryManager.java
index 7857d0cff8..13d1585858 100644
--- a/src/java/org/apache/cassandra/db/DiskBoundaryManager.java
+++ b/src/java/org/apache/cassandra/db/DiskBoundaryManager.java
@@ -31,9 +31,12 @@ import org.apache.cassandra.dht.Range;
import org.apache.cassandra.dht.Splitter;
import org.apache.cassandra.dht.Token;
import org.apache.cassandra.locator.RangesAtEndpoint;
-import org.apache.cassandra.locator.TokenMetadata;
-import org.apache.cassandra.service.PendingRangeCalculatorService;
+import org.apache.cassandra.tcm.ClusterMetadataService;
+import org.apache.cassandra.tcm.Epoch;
+import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.StorageService;
+import org.apache.cassandra.tcm.ClusterMetadata;
+import org.apache.cassandra.tcm.ownership.DataPlacement;
import org.apache.cassandra.utils.FBUtilities;
public class DiskBoundaryManager
@@ -43,18 +46,24 @@ public class DiskBoundaryManager
public DiskBoundaries getDiskBoundaries(ColumnFamilyStore cfs)
{
- if (!cfs.getPartitioner().splitter().isPresent())
+ return getDiskBoundaries(cfs, cfs.metadata());
+ }
+
+ public DiskBoundaries getDiskBoundaries(ColumnFamilyStore cfs, TableMetadata metadata)
+ {
+ if (!metadata.partitioner.splitter().isPresent())
return new DiskBoundaries(cfs, cfs.getDirectories().getWriteableLocations(), DisallowedDirectories.getDirectoriesVersion());
+
if (diskBoundaries == null || diskBoundaries.isOutOfDate())
{
synchronized (this)
{
if (diskBoundaries == null || diskBoundaries.isOutOfDate())
{
- logger.debug("Refreshing disk boundary cache for {}.{}", cfs.getKeyspaceName(), cfs.getTableName());
+ logger.trace("Refreshing disk boundary cache for {}.{}", cfs.getKeyspaceName(), cfs.getTableName());
DiskBoundaries oldBoundaries = diskBoundaries;
- diskBoundaries = getDiskBoundaryValue(cfs);
- logger.debug("Updating boundaries from {} to {} for {}.{}", oldBoundaries, diskBoundaries, cfs.getKeyspaceName(), cfs.getTableName());
+ diskBoundaries = getDiskBoundaryValue(cfs, metadata.partitioner);
+ logger.trace("Updating boundaries from {} to {} for {}.{}", oldBoundaries, diskBoundaries, cfs.getKeyspaceName(), cfs.getTableName());
}
}
}
@@ -70,12 +79,12 @@ public class DiskBoundaryManager
static class VersionedRangesAtEndpoint
{
public final RangesAtEndpoint rangesAtEndpoint;
- public final long ringVersion;
+ public final Epoch epoch;
- VersionedRangesAtEndpoint(RangesAtEndpoint rangesAtEndpoint, long ringVersion)
+ VersionedRangesAtEndpoint(RangesAtEndpoint rangesAtEndpoint, Epoch epoch)
{
this.rangesAtEndpoint = rangesAtEndpoint;
- this.ringVersion = ringVersion;
+ this.epoch = epoch;
}
}
@@ -83,26 +92,35 @@ public class DiskBoundaryManager
{
RangesAtEndpoint localRanges;
- long ringVersion;
- TokenMetadata tmd;
+ Epoch epoch;
+ ClusterMetadata metadata;
do
{
- tmd = StorageService.instance.getTokenMetadata();
- ringVersion = tmd.getRingVersion();
- localRanges = getLocalRanges(cfs, tmd);
- logger.debug("Got local ranges {} (ringVersion = {})", localRanges, ringVersion);
+ metadata = ClusterMetadata.current();
+ epoch = metadata.epoch;
+ localRanges = getLocalRanges(cfs, metadata);
+ logger.debug("Got local ranges {} (epoch = {})", localRanges, epoch);
}
- while (ringVersion != tmd.getRingVersion()); // if ringVersion is different here it means that
- // it might have changed before we calculated localRanges - recalculate
-
- return new VersionedRangesAtEndpoint(localRanges, ringVersion);
+ while (!metadata.epoch.equals(ClusterMetadata.current().epoch)); // if epoch is different here it means that
+ // it might have changed before we calculated localRanges - recalculate
+ return new VersionedRangesAtEndpoint(localRanges, epoch);
}
- private static DiskBoundaries getDiskBoundaryValue(ColumnFamilyStore cfs)
+ private static DiskBoundaries getDiskBoundaryValue(ColumnFamilyStore cfs, IPartitioner partitioner)
{
- VersionedRangesAtEndpoint rangesAtEndpoint = getVersionedLocalRanges(cfs);
- RangesAtEndpoint localRanges = rangesAtEndpoint.rangesAtEndpoint;
- long ringVersion = rangesAtEndpoint.ringVersion;
+ if (ClusterMetadataService.instance() == null)
+ return new DiskBoundaries(cfs, cfs.getDirectories().getWriteableLocations(), null, Epoch.EMPTY, DisallowedDirectories.getDirectoriesVersion());
+
+ RangesAtEndpoint localRanges;
+
+ ClusterMetadata metadata;
+ do
+ {
+ metadata = ClusterMetadata.current();
+ localRanges = getLocalRanges(cfs, metadata);
+ logger.debug("Got local ranges {} (epoch = {})", localRanges, metadata.epoch);
+ }
+ while (metadata.epoch != ClusterMetadata.current().epoch);
int directoriesVersion;
Directories.DataDirectory[] dirs;
@@ -114,29 +132,31 @@ public class DiskBoundaryManager
while (directoriesVersion != DisallowedDirectories.getDirectoriesVersion()); // if directoriesVersion has changed we need to recalculate
if (localRanges == null || localRanges.isEmpty())
- return new DiskBoundaries(cfs, dirs, null, ringVersion, directoriesVersion);
+ return new DiskBoundaries(cfs, dirs, null, metadata.epoch, directoriesVersion);
- List positions = getDiskBoundaries(localRanges, cfs.getPartitioner(), dirs);
+ List positions = getDiskBoundaries(localRanges, partitioner, dirs);
- return new DiskBoundaries(cfs, dirs, positions, ringVersion, directoriesVersion);
+ return new DiskBoundaries(cfs, dirs, positions, metadata.epoch, directoriesVersion);
}
- private static RangesAtEndpoint getLocalRanges(ColumnFamilyStore cfs, TokenMetadata tmd)
+
+ 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
+ && !StorageService.isReplacingSameAddress()) // When replacing same address, the node marks itself as UN locally
{
- PendingRangeCalculatorService.instance.blockUntilFinished();
- localRanges = tmd.getPendingRanges(cfs.getKeyspaceName(), FBUtilities.getBroadcastAddressAndPort());
+ placement = metadata.placements.get(cfs.keyspace.getMetadata().params.replication);
}
else
{
- // Reason we use use the future settled TMD is that if we decommission a node, we want to stream
+ // Reason we use 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
- localRanges = cfs.keyspace.getReplicationStrategy().getAddressReplicas(tmd.cloneAfterAllSettled(), FBUtilities.getBroadcastAddressAndPort());
+ placement = metadata.writePlacementAllSettled(cfs.keyspace.getMetadata());
}
+ localRanges = placement.writes.byEndpoint().get(FBUtilities.getBroadcastAddressAndPort());
return localRanges;
}
diff --git a/src/java/org/apache/cassandra/db/Keyspace.java b/src/java/org/apache/cassandra/db/Keyspace.java
index a06c7e137a..ded130f8cc 100644
--- a/src/java/org/apache/cassandra/db/Keyspace.java
+++ b/src/java/org/apache/cassandra/db/Keyspace.java
@@ -56,13 +56,11 @@ import org.apache.cassandra.locator.AbstractReplicationStrategy;
import org.apache.cassandra.metrics.KeyspaceMetrics;
import org.apache.cassandra.repair.KeyspaceRepairManager;
import org.apache.cassandra.schema.KeyspaceMetadata;
-import org.apache.cassandra.schema.ReplicationParams;
import org.apache.cassandra.schema.Schema;
import org.apache.cassandra.schema.SchemaConstants;
import org.apache.cassandra.schema.SchemaProvider;
import org.apache.cassandra.schema.TableId;
import org.apache.cassandra.schema.TableMetadata;
-import org.apache.cassandra.schema.TableMetadataRef;
import org.apache.cassandra.service.snapshot.TableSnapshot;
import org.apache.cassandra.tracing.Tracing;
import org.apache.cassandra.utils.ByteBufferUtil;
@@ -91,6 +89,7 @@ public class Keyspace
private static int TEST_FAIL_MV_LOCKS_COUNT = CassandraRelevantProperties.TEST_FAIL_MV_LOCKS_COUNT.getInt();
public final KeyspaceMetrics metric;
+ public final KeyspaceMetadataRef metadataRef;
// It is possible to call Keyspace.open without a running daemon, so it makes sense to ensure
// proper directories here as well as in CassandraDaemon.
@@ -100,8 +99,6 @@ public class Keyspace
DatabaseDescriptor.createAllDirectories();
}
- private volatile KeyspaceMetadata metadata;
-
//OpOrder is defined globally since we need to order writes across
//Keyspaces in the case of Views (batchlog of view mutations)
public static final OpOrder writeOrder = new OpOrder();
@@ -109,12 +106,11 @@ public class Keyspace
/* ColumnFamilyStore per column family */
private final ConcurrentMap columnFamilyStores = new ConcurrentHashMap<>();
- private volatile AbstractReplicationStrategy replicationStrategy;
public final ViewManager viewManager;
private final KeyspaceWriteHandler writeHandler;
- private volatile ReplicationParams replicationParams;
private final KeyspaceRepairManager repairManager;
private final SchemaProvider schema;
+ private final String name;
private static volatile boolean initialized = false;
@@ -148,23 +144,15 @@ public class Keyspace
public static Keyspace open(String keyspaceName)
{
assert initialized || SchemaConstants.isLocalSystemKeyspace(keyspaceName) : "Initialized: " + initialized;
- return open(keyspaceName, Schema.instance, true);
+ Keyspace ks = Schema.instance.getKeyspaceInstance(keyspaceName);
+ assert ks != null : "Unknown keyspace " + keyspaceName;
+ return ks;
}
// to only be used by org.apache.cassandra.tools.Standalone* classes
public static Keyspace openWithoutSSTables(String keyspaceName)
{
- return open(keyspaceName, Schema.instance, false);
- }
-
- public static Keyspace open(String keyspaceName, SchemaProvider schema, boolean loadSSTables)
- {
- return schema.maybeAddKeyspaceInstance(keyspaceName, () -> new Keyspace(keyspaceName, schema, loadSSTables));
- }
-
- public static ColumnFamilyStore openAndGetStore(TableMetadataRef tableRef)
- {
- return open(tableRef.keyspace).getColumnFamilyStore(tableRef.id);
+ return Schema.instance.getKeyspaceInstance(keyspaceName);
}
public static ColumnFamilyStore openAndGetStore(TableMetadata table)
@@ -188,15 +176,9 @@ public class Keyspace
}
}
- public void setMetadata(KeyspaceMetadata metadata)
- {
- this.metadata = metadata;
- createReplicationStrategy(metadata);
- }
-
public KeyspaceMetadata getMetadata()
{
- return metadata;
+ return metadataRef.get();
}
public Collection getColumnFamilyStores()
@@ -216,7 +198,7 @@ public class Keyspace
{
ColumnFamilyStore cfs = columnFamilyStores.get(id);
if (cfs == null)
- throw new IllegalArgumentException("Unknown CF " + id);
+ throw new IllegalArgumentException(String.format("Unknown CF %s %s", id, columnFamilyStores));
return cfs;
}
@@ -317,38 +299,53 @@ public class Keyspace
return getColumnFamilyStores().stream().flatMap(cfs -> cfs.listSnapshots().values().stream());
}
+ public static Keyspace forSchema(String keyspaceName, SchemaProvider schema)
+ {
+ return new Keyspace(keyspaceName, schema, true);
+ }
+
private Keyspace(String keyspaceName, SchemaProvider schema, boolean loadSSTables)
+ {
+ this(schema, schema.getKeyspaceMetadata(keyspaceName), loadSSTables);
+ }
+
+ public Keyspace(SchemaProvider schema, KeyspaceMetadata metadata, boolean loadSSTables)
{
this.schema = schema;
- metadata = schema.getKeyspaceMetadata(keyspaceName);
- assert metadata != null : "Unknown keyspace " + keyspaceName;
-
+ this.name = metadata.name;
+
+ assert metadata != null : "Unknown keyspace " + metadata.name;
+
if (metadata.isVirtual())
- throw new IllegalStateException("Cannot initialize Keyspace with virtual metadata " + keyspaceName);
- createReplicationStrategy(metadata);
+ throw new IllegalStateException("Cannot initialize Keyspace with virtual metadata " + metadata.name);
this.metric = new KeyspaceMetrics(this);
this.viewManager = new ViewManager(this);
+
+ this.metadataRef = new KeyspaceMetadataRef(metadata, schema);
for (TableMetadata cfm : metadata.tablesAndViews())
{
logger.trace("Initializing {}.{}", getName(), cfm.name);
- initCf(schema.getTableMetadataRef(cfm.id), loadSSTables);
+ initCf(cfm, loadSSTables);
}
- this.viewManager.reload(false);
+
+ this.viewManager.reload(metadata);
+ this.metadataRef.unsetInitial();
this.repairManager = new CassandraKeyspaceRepairManager(this);
this.writeHandler = new CassandraKeyspaceWriteHandler(this);
}
- private Keyspace(KeyspaceMetadata metadata)
+ public Keyspace(KeyspaceMetadata metadata)
{
this.schema = Schema.instance;
- this.metadata = metadata;
- createReplicationStrategy(metadata);
+ this.name = metadata.name;
+
this.metric = new KeyspaceMetrics(this);
this.viewManager = new ViewManager(this);
this.repairManager = new CassandraKeyspaceRepairManager(this);
this.writeHandler = new CassandraKeyspaceWriteHandler(this);
+ this.metadataRef = new KeyspaceMetadataRef(metadata, schema);
}
public KeyspaceRepairManager getRepairManager()
@@ -361,18 +358,6 @@ public class Keyspace
return new Keyspace(metadata);
}
- private void createReplicationStrategy(KeyspaceMetadata ksm)
- {
- logger.info("Creating replication strategy " + ksm.name + " params " + ksm.params);
- replicationStrategy = ksm.createReplicationStrategy();
- if (!ksm.params.replication.equals(replicationParams))
- {
- logger.debug("New replication settings for keyspace {} - invalidating disk boundary caches", ksm.name);
- columnFamilyStores.values().forEach(ColumnFamilyStore::invalidateLocalRanges);
- }
- replicationParams = ksm.params.replication;
- }
-
// best invoked on the compaction manager.
public void dropCf(TableId tableId, boolean dropData)
{
@@ -433,7 +418,7 @@ public class Keyspace
/**
* adds a cf to internal structures, ends up creating disk files).
*/
- public void initCf(TableMetadataRef metadata, boolean loadSSTables)
+ public void initCf(TableMetadata metadata, boolean loadSSTables)
{
ColumnFamilyStore cfs = columnFamilyStores.get(metadata.id);
@@ -442,7 +427,8 @@ public class Keyspace
// CFS being created for the first time, either on server startup or new CF being added.
// We don't worry about races here; startup is safe, and adding multiple idential CFs
// simultaneously is a "don't do that" scenario.
- ColumnFamilyStore oldCfs = columnFamilyStores.putIfAbsent(metadata.id, ColumnFamilyStore.createColumnFamilyStore(this, metadata, loadSSTables));
+ ColumnFamilyStore oldCfs = columnFamilyStores.putIfAbsent(metadata.id,
+ ColumnFamilyStore.createColumnFamilyStore(this, metadata, loadSSTables));
// CFS mbean instantiation will error out before we hit this, but in case that changes...
if (oldCfs != null)
throw new IllegalStateException("added multiple mappings for cf id " + metadata.id);
@@ -452,7 +438,7 @@ public class Keyspace
// re-initializing an existing CF. This will happen if you cleared the schema
// on this node and it's getting repopulated from the rest of the cluster.
assert cfs.name.equals(metadata.name);
- cfs.reload();
+ cfs.reload(metadata);
}
}
@@ -514,7 +500,7 @@ public class Keyspace
boolean isDeferrable,
Promise> future)
{
- if (TEST_FAIL_WRITES && metadata.name.equals(TEST_FAIL_WRITES_KS))
+ if (TEST_FAIL_WRITES && getMetadata().name.equals(TEST_FAIL_WRITES_KS))
throw new RuntimeException("Testing write failures");
Lock[] locks = null;
@@ -626,7 +612,7 @@ public class Keyspace
try
{
Tracing.trace("Creating materialized view mutations from base table replica");
- viewManager.forTable(upd.metadata().id).pushViewReplicaUpdates(upd, makeDurable, baseComplete);
+ viewManager.forTable(upd.metadata()).pushViewReplicaUpdates(upd, makeDurable, baseComplete);
}
catch (Throwable t)
{
@@ -661,7 +647,7 @@ public class Keyspace
public AbstractReplicationStrategy getReplicationStrategy()
{
- return replicationStrategy;
+ return getMetadata().replicationStrategy;
}
public List> flush(ColumnFamilyStore.FlushReason reason)
@@ -749,6 +735,11 @@ public class Keyspace
return Schema.instance.getKeyspaces().stream().map(Schema.instance::getKeyspaceInstance).filter(Objects::nonNull);
}
+ public static Iterable nonSystem()
+ {
+ return Iterables.transform(Schema.instance.distributedKeyspaces().names(), Keyspace::open);
+ }
+
public static Iterable nonLocalStrategy()
{
return Iterables.transform(Schema.instance.distributedKeyspaces().names(), Keyspace::open);
@@ -767,6 +758,37 @@ public class Keyspace
public String getName()
{
- return metadata.name;
+ return name;
+ }
+
+ private static class KeyspaceMetadataRef
+ {
+ // We need "initial" keyspace metadata for initCF to run, due to circular dependency
+ // between keyspace keyspace -> column family -> keyspace metadata. There are some
+ // calls within initCF that try accessing keyspace metadata, which requires the metadata
+ // of initializing keyspace to already be visible via ClusterMetadata#schema.
+ private KeyspaceMetadata initial;
+
+ private final String name;
+ private final SchemaProvider provider;
+
+ public KeyspaceMetadataRef(KeyspaceMetadata initial, SchemaProvider provider)
+ {
+ this.initial = initial;
+ this.name = initial.name;
+ this.provider = provider;
+ }
+
+ public KeyspaceMetadata get()
+ {
+ if (initial != null)
+ return initial;
+ return provider.getKeyspaceMetadata(name);
+ }
+
+ public void unsetInitial()
+ {
+ this.initial = null;
+ }
}
}
diff --git a/src/java/org/apache/cassandra/db/MutationVerbHandler.java b/src/java/org/apache/cassandra/db/MutationVerbHandler.java
index 1ab6711fdb..62b762f069 100644
--- a/src/java/org/apache/cassandra/db/MutationVerbHandler.java
+++ b/src/java/org/apache/cassandra/db/MutationVerbHandler.java
@@ -24,7 +24,7 @@ import org.apache.cassandra.tracing.Tracing;
import static org.apache.cassandra.db.commitlog.CommitLogSegment.ENTRY_OVERHEAD_SIZE;
-public class MutationVerbHandler implements IVerbHandler
+public class MutationVerbHandler extends AbstractMutationVerbHandler
{
public static final MutationVerbHandler instance = new MutationVerbHandler();
@@ -51,7 +51,7 @@ public class MutationVerbHandler implements IVerbHandler
InetAddressAndPort respondToAddress = message.respondTo();
try
{
- message.payload.applyFuture().addCallback(o -> respond(message, respondToAddress), wto -> failed());
+ processMessage(message, respondToAddress);
}
catch (WriteTimeoutException wto)
{
@@ -59,6 +59,11 @@ public class MutationVerbHandler implements IVerbHandler
}
}
+ protected void applyMutation(Message message, InetAddressAndPort respondToAddress)
+ {
+ message.payload.applyFuture().addCallback(o -> respond(message, respondToAddress), wto -> failed());
+ }
+
private static void forwardToLocalNodes(Message originalMessage, ForwardingInfo forwardTo)
{
Message.Builder builder =
diff --git a/src/java/org/apache/cassandra/db/PartitionRangeReadCommand.java b/src/java/org/apache/cassandra/db/PartitionRangeReadCommand.java
index 7aa1e01b6a..4a08c23542 100644
--- a/src/java/org/apache/cassandra/db/PartitionRangeReadCommand.java
+++ b/src/java/org/apache/cassandra/db/PartitionRangeReadCommand.java
@@ -53,6 +53,7 @@ import org.apache.cassandra.net.Verb;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.service.StorageProxy;
+import org.apache.cassandra.tcm.Epoch;
import org.apache.cassandra.tracing.Tracing;
/**
@@ -65,25 +66,27 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
protected final DataRange dataRange;
protected final Slices requestedSlices;
- private PartitionRangeReadCommand(boolean isDigest,
- int digestVersion,
- boolean acceptsTransient,
- TableMetadata metadata,
- long nowInSec,
- ColumnFilter columnFilter,
- RowFilter rowFilter,
- DataLimits limits,
- DataRange dataRange,
- Index.QueryPlan indexQueryPlan,
- boolean trackWarnings)
+ @VisibleForTesting
+ protected PartitionRangeReadCommand(Epoch serializedAtEpoch,
+ boolean isDigest,
+ int digestVersion,
+ boolean acceptsTransient,
+ TableMetadata metadata,
+ long nowInSec,
+ ColumnFilter columnFilter,
+ RowFilter rowFilter,
+ DataLimits limits,
+ DataRange dataRange,
+ Index.QueryPlan indexQueryPlan,
+ boolean trackWarnings)
{
- super(Kind.PARTITION_RANGE, isDigest, digestVersion, acceptsTransient, metadata, nowInSec, columnFilter, rowFilter, limits, indexQueryPlan, trackWarnings);
+ super(serializedAtEpoch, Kind.PARTITION_RANGE, isDigest, digestVersion, acceptsTransient, metadata, nowInSec, columnFilter, rowFilter, limits, indexQueryPlan, trackWarnings);
this.dataRange = dataRange;
this.requestedSlices = dataRange.clusteringIndexFilter.getSlices(metadata());
-
}
- private static PartitionRangeReadCommand create(boolean isDigest,
+ private static PartitionRangeReadCommand create(Epoch serializedAtEpoch,
+ boolean isDigest,
int digestVersion,
boolean acceptsTransient,
TableMetadata metadata,
@@ -109,7 +112,8 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
indexQueryPlan,
trackWarnings);
}
- return new PartitionRangeReadCommand(isDigest,
+ return new PartitionRangeReadCommand(serializedAtEpoch,
+ isDigest,
digestVersion,
acceptsTransient,
metadata,
@@ -129,7 +133,8 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
DataLimits limits,
DataRange dataRange)
{
- return create(false,
+ return create(metadata.epoch,
+ false,
0,
false,
metadata,
@@ -152,7 +157,8 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
*/
public static PartitionRangeReadCommand allDataRead(TableMetadata metadata, long nowInSec)
{
- return create(false,
+ return create(metadata.epoch,
+ false,
0,
false,
metadata,
@@ -202,7 +208,8 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
// DataLimits.CQLGroupByLimits.GroupByAwareCounter assumes that if GroupingState.hasClustering(), then we're in
// the middle of a group, but we can't make that assumption if we query and range "in advance" of where we are
// on the ring.
- return create(isDigestQuery(),
+ return create(serializedAtEpoch(),
+ isDigestQuery(),
digestVersion(),
acceptsTransient(),
metadata(),
@@ -217,7 +224,8 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
public PartitionRangeReadCommand copy()
{
- return create(isDigestQuery(),
+ return create(serializedAtEpoch(),
+ isDigestQuery(),
digestVersion(),
acceptsTransient(),
metadata(),
@@ -233,7 +241,8 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
@Override
protected PartitionRangeReadCommand copyAsDigestQuery()
{
- return create(true,
+ return create(serializedAtEpoch(),
+ true,
digestVersion(),
false,
metadata(),
@@ -249,7 +258,8 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
@Override
protected PartitionRangeReadCommand copyAsTransientQuery()
{
- return create(false,
+ return create(serializedAtEpoch(),
+ false,
0,
true,
metadata(),
@@ -265,7 +275,8 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
@Override
public PartitionRangeReadCommand withUpdatedLimit(DataLimits newLimits)
{
- return create(isDigestQuery(),
+ return create(serializedAtEpoch(),
+ isDigestQuery(),
digestVersion(),
acceptsTransient(),
metadata(),
@@ -281,7 +292,8 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
@Override
public PartitionRangeReadCommand withUpdatedLimitsAndDataRange(DataLimits newLimits, DataRange newDataRange)
{
- return create(isDigestQuery(),
+ return create(serializedAtEpoch(),
+ isDigestQuery(),
digestVersion(),
acceptsTransient(),
metadata(),
@@ -515,6 +527,7 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
{
public ReadCommand deserialize(DataInputPlus in,
int version,
+ Epoch serializedAtEpoch,
boolean isDigest,
int digestVersion,
boolean acceptsTransient,
@@ -527,7 +540,7 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
throws IOException
{
DataRange range = DataRange.serializer.deserialize(in, version, metadata);
- return PartitionRangeReadCommand.create(isDigest, digestVersion, acceptsTransient, metadata, nowInSec, columnFilter, rowFilter, limits, range, indexQueryPlan, false);
+ return PartitionRangeReadCommand.create(serializedAtEpoch, isDigest, digestVersion, acceptsTransient, metadata, nowInSec, columnFilter, rowFilter, limits, range, indexQueryPlan, false);
}
}
@@ -545,7 +558,7 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR
Index.QueryPlan indexQueryPlan,
boolean trackWarnings)
{
- super(isDigest, digestVersion, acceptsTransient, metadata, nowInSec, columnFilter, rowFilter, limits, dataRange, indexQueryPlan, trackWarnings);
+ super(metadata.epoch, isDigest, digestVersion, acceptsTransient, metadata, nowInSec, columnFilter, rowFilter, limits, dataRange, indexQueryPlan, trackWarnings);
}
@Override
diff --git a/src/java/org/apache/cassandra/db/ReadCommand.java b/src/java/org/apache/cassandra/db/ReadCommand.java
index b8ac0a2229..84cc9bfa24 100644
--- a/src/java/org/apache/cassandra/db/ReadCommand.java
+++ b/src/java/org/apache/cassandra/db/ReadCommand.java
@@ -38,7 +38,10 @@ import org.slf4j.LoggerFactory;
import io.netty.util.concurrent.FastThreadLocal;
import org.apache.cassandra.config.*;
import org.apache.cassandra.db.filter.*;
+import org.apache.cassandra.exceptions.CoordinatorBehindException;
import org.apache.cassandra.exceptions.QueryCancelledException;
+import org.apache.cassandra.exceptions.UnknownTableException;
+import org.apache.cassandra.metrics.TCMMetrics;
import org.apache.cassandra.net.MessageFlag;
import org.apache.cassandra.net.MessagingService;
import org.apache.cassandra.net.ParamType;
@@ -67,9 +70,12 @@ import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.schema.SchemaProvider;
import org.apache.cassandra.service.ActiveRepairService;
import org.apache.cassandra.service.ClientWarn;
+import org.apache.cassandra.tcm.ClusterMetadata;
+import org.apache.cassandra.tcm.Epoch;
import org.apache.cassandra.tracing.Tracing;
import org.apache.cassandra.utils.CassandraUInt;
import org.apache.cassandra.utils.FBUtilities;
+import org.apache.cassandra.utils.NoSpamLogger;
import org.apache.cassandra.utils.ObjectSizes;
import org.apache.cassandra.utils.TimeUUID;
@@ -100,6 +106,7 @@ public abstract class ReadCommand extends AbstractReadQuery
private final boolean isDigestQuery;
private final boolean acceptsTransient;
+ private final Epoch serializedAtEpoch;
// if a digest query, the version for which the digest is expected. Ignored if not a digest.
private int digestVersion;
@@ -112,6 +119,7 @@ public abstract class ReadCommand extends AbstractReadQuery
{
public abstract ReadCommand deserialize(DataInputPlus in,
int version,
+ Epoch serializedAtEpoch,
boolean isDigest,
int digestVersion,
boolean acceptsTransient,
@@ -136,7 +144,8 @@ public abstract class ReadCommand extends AbstractReadQuery
}
}
- protected ReadCommand(Kind kind,
+ protected ReadCommand(Epoch serializedAtEpoch,
+ Kind kind,
boolean isDigestQuery,
int digestVersion,
boolean acceptsTransient,
@@ -158,6 +167,7 @@ public abstract class ReadCommand extends AbstractReadQuery
this.acceptsTransient = acceptsTransient;
this.indexQueryPlan = indexQueryPlan;
this.trackWarnings = trackWarnings;
+ this.serializedAtEpoch = serializedAtEpoch;
}
public static ReadCommand getCommand()
@@ -197,6 +207,15 @@ public abstract class ReadCommand extends AbstractReadQuery
return isDigestQuery;
}
+ /**
+ * the schema version on the table when serializing this read command
+ * @return
+ */
+ public Epoch serializedAtEpoch()
+ {
+ return serializedAtEpoch;
+ }
+
/**
* If the query is a digest one, the requested digest version.
*
@@ -1011,6 +1030,11 @@ public abstract class ReadCommand extends AbstractReadQuery
@VisibleForTesting
public static class Serializer implements IVersionedSerializer
{
+ private static final NoSpamLogger noSpamLogger = NoSpamLogger.getLogger(logger, 10L, TimeUnit.SECONDS);
+ private static final NoSpamLogger.NoSpamLogStatement schemaMismatchStmt =
+ noSpamLogger.getStatement("Schema epoch mismatch during read command deserialization. " +
+ "TableId: {}, remote epoch: {}, local epoch: {}", 10L, TimeUnit.SECONDS);
+
private final SchemaProvider schema;
public Serializer()
@@ -1075,6 +1099,8 @@ public abstract class ReadCommand extends AbstractReadQuery
if (command.isDigestQuery())
out.writeUnsignedVInt32(command.digestVersion());
command.metadata().id.serialize(out);
+ if (version >= MessagingService.VERSION_50)
+ Epoch.serializer.serialize(command.serializedAtEpoch, out);
out.writeInt(version >= MessagingService.VERSION_50 ? CassandraUInt.fromLong(command.nowInSec()) : (int) command.nowInSec());
ColumnFilter.serializer.serialize(command.columnFilter(), out, version);
RowFilter.serializer.serialize(command.rowFilter(), out, version);
@@ -1098,28 +1124,47 @@ public abstract class ReadCommand extends AbstractReadQuery
// better complain loudly than doing the wrong thing.
if (isForThrift(flags))
throw new IllegalStateException("Received a command with the thrift flag set. "
- + "This means thrift is in use in a mixed 3.0/3.X and 4.0+ cluster, "
- + "which is unsupported. Make sure to stop using thrift before "
- + "upgrading to 4.0");
+ + "This means thrift is in use in a mixed 3.0/3.X and 4.0+ cluster, "
+ + "which is unsupported. Make sure to stop using thrift before "
+ + "upgrading to 4.0");
boolean hasIndex = hasIndex(flags);
- int digestVersion = isDigest ? in.readUnsignedVInt32() : 0;
- TableMetadata metadata = schema.getExistingTableMetadata(TableId.deserialize(in));
- long nowInSec = version >= MessagingService.VERSION_50 ? CassandraUInt.toLong(in.readInt()) : in.readInt();
- ColumnFilter columnFilter = ColumnFilter.serializer.deserialize(in, version, metadata);
- RowFilter rowFilter = RowFilter.serializer.deserialize(in, version, metadata);
- DataLimits limits = DataLimits.serializer.deserialize(in, version, metadata);
+ int digestVersion = isDigest ? (int)in.readUnsignedVInt() : 0;
+ TableId tableId = TableId.deserialize(in);
+ Epoch schemaVersion = null;
+ if (version >= MessagingService.VERSION_50)
+ schemaVersion = Epoch.serializer.deserialize(in);
+ TableMetadata tableMetadata;
+ try
+ {
+ tableMetadata = schema.getExistingTableMetadata(tableId);
+ }
+ catch (UnknownTableException e)
+ {
+ ClusterMetadata metadata = ClusterMetadata.current();
+ Epoch localCurrentEpoch = metadata.epoch;
+ if (schemaVersion != null && localCurrentEpoch.isAfter(schemaVersion))
+ {
+ TCMMetrics.instance.coordinatorBehindSchema.mark();
+ throw new CoordinatorBehindException(e.getMessage());
+ }
+ throw e;
+ }
+ long nowInSec = version >= MessagingService.VERSION_50 ? CassandraUInt.toLong(in.readInt()) : in.readInt();
+ ColumnFilter columnFilter = ColumnFilter.serializer.deserialize(in, version, tableMetadata);
+ RowFilter rowFilter = RowFilter.serializer.deserialize(in, version, tableMetadata);
+ DataLimits limits = DataLimits.serializer.deserialize(in, version, tableMetadata);
Index.QueryPlan indexQueryPlan = null;
if (hasIndex)
{
- IndexMetadata index = deserializeIndexMetadata(in, version, metadata);
- Index.Group indexGroup = Keyspace.openAndGetStore(metadata).indexManager.getIndexGroup(index);
+ IndexMetadata index = deserializeIndexMetadata(in, version, tableMetadata);
+ Index.Group indexGroup = Keyspace.openAndGetStore(tableMetadata).indexManager.getIndexGroup(index);
if (indexGroup != null)
indexQueryPlan = indexGroup.queryPlanFor(rowFilter);
}
- return kind.selectionDeserializer.deserialize(in, version, isDigest, digestVersion, acceptsTransient, metadata, nowInSec, columnFilter, rowFilter, limits, indexQueryPlan);
+ return kind.selectionDeserializer.deserialize(in, version, schemaVersion, isDigest, digestVersion, acceptsTransient, tableMetadata, nowInSec, columnFilter, rowFilter, limits, indexQueryPlan);
}
private IndexMetadata deserializeIndexMetadata(DataInputPlus in, int version, TableMetadata metadata) throws IOException
@@ -1144,6 +1189,7 @@ public abstract class ReadCommand extends AbstractReadQuery
return 2 // kind + flags
+ (command.isDigestQuery() ? TypeSizes.sizeofUnsignedVInt(command.digestVersion()) : 0)
+ command.metadata().id.serializedSize()
+ + (version >= MessagingService.VERSION_50 ? Epoch.serializer.serializedSize(command.metadata().epoch) : 0)
+ TypeSizes.INT_SIZE // command.nowInSec() is serialized as uint
+ ColumnFilter.serializer.serializedSize(command.columnFilter(), version)
+ RowFilter.serializer.serializedSize(command.rowFilter(), version)
diff --git a/src/java/org/apache/cassandra/db/ReadCommandVerbHandler.java b/src/java/org/apache/cassandra/db/ReadCommandVerbHandler.java
index d8069eebf4..5d430f32cf 100644
--- a/src/java/org/apache/cassandra/db/ReadCommandVerbHandler.java
+++ b/src/java/org/apache/cassandra/db/ReadCommandVerbHandler.java
@@ -22,15 +22,24 @@ import org.slf4j.LoggerFactory;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.db.partitions.UnfilteredPartitionIterator;
+import org.apache.cassandra.exceptions.CoordinatorBehindException;
+import org.apache.cassandra.exceptions.InvalidRoutingException;
+import org.apache.cassandra.exceptions.QueryCancelledException;
+import org.apache.cassandra.dht.AbstractBounds;
import org.apache.cassandra.dht.Token;
import org.apache.cassandra.exceptions.InvalidRequestException;
-import org.apache.cassandra.exceptions.QueryCancelledException;
import org.apache.cassandra.locator.Replica;
+import org.apache.cassandra.metrics.TCMMetrics;
+import org.apache.cassandra.schema.SchemaConstants;
+import org.apache.cassandra.service.StorageService;
+import org.apache.cassandra.tcm.ClusterMetadataService;
import org.apache.cassandra.net.IVerbHandler;
import org.apache.cassandra.net.Message;
import org.apache.cassandra.net.MessagingService;
-import org.apache.cassandra.service.StorageService;
+import org.apache.cassandra.tcm.ClusterMetadata;
+import org.apache.cassandra.tcm.Epoch;
import org.apache.cassandra.tracing.Tracing;
+import org.apache.cassandra.utils.FBUtilities;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
@@ -42,16 +51,17 @@ public class ReadCommandVerbHandler implements IVerbHandler
public void doVerb(Message message)
{
- if (StorageService.instance.isBootstrapMode())
+ if (message.epoch().isAfter(Epoch.EMPTY))
{
- throw new RuntimeException("Cannot service reads while bootstrapping!");
+ ClusterMetadata metadata = ClusterMetadata.current();
+ metadata = checkTokenOwnership(metadata, message);
+ metadata = checkSchemaVersion(metadata, message);
}
- ReadCommand command = message.payload;
- validateTransientStatus(message);
MessageParams.reset();
long timeout = message.expiresAtNanos() - message.createdAtNanos();
+ ReadCommand command = message.payload;
command.setMonitoringTime(message.createdAtNanos(), message.isCrossNode(), timeout, DatabaseDescriptor.getSlowQueryTimeout(NANOSECONDS));
if (message.trackWarnings())
@@ -103,40 +113,110 @@ public class ReadCommandVerbHandler implements IVerbHandler
}
}
- private void validateTransientStatus(Message message)
+ private ClusterMetadata checkSchemaVersion(ClusterMetadata metadata, Message message)
+ {
+ ReadCommand readCommand = message.payload;
+
+ if (SchemaConstants.isSystemKeyspace(readCommand.metadata().keyspace) ||
+ readCommand.serializedAtEpoch() == null) // don't try to catch up with pre-5.0 nodes
+ return metadata;
+
+ Keyspace ks = metadata.schema.getKeyspace(readCommand.metadata().keyspace);
+ ColumnFamilyStore cfs = ks != null ? ks.getColumnFamilyStore(readCommand.metadata().id) : null;
+ Epoch localComparisonEpoch = metadata.epoch;
+ if (cfs != null)
+ localComparisonEpoch = cfs.metadata().epoch;
+
+ if (localComparisonEpoch.isBefore(readCommand.serializedAtEpoch()))
+ metadata = ClusterMetadataService.instance().fetchLogFromPeerOrCMS(metadata, message.from(), message.epoch());
+ else if (localComparisonEpoch.isAfter(readCommand.serializedAtEpoch()))
+ {
+ TCMMetrics.instance.coordinatorBehindSchema.mark();
+ throw new CoordinatorBehindException(String.format("Coordinator schema for %s.%s with epoch %s is behind our schema %s",
+ message.payload.metadata().keyspace,
+ message.payload.metadata().name,
+ readCommand.serializedAtEpoch(),
+ localComparisonEpoch));
+ }
+ ks = metadata.schema.getKeyspace(readCommand.metadata().keyspace);
+ if (ks == null || ks.getColumnFamilyStore(readCommand.metadata().id) == null)
+ throw new IllegalStateException("Unknown table " + readCommand.metadata().id +" after fetching remote log entries");
+ return metadata;
+ }
+
+ private ClusterMetadata checkTokenOwnership(ClusterMetadata metadata, Message message)
{
ReadCommand command = message.payload;
if (command.metadata().isVirtual())
- return;
- Token token;
+ return metadata;
+
+ if (command.isTopK())
+ return metadata;
if (command instanceof SinglePartitionReadCommand)
- token = ((SinglePartitionReadCommand) command).partitionKey().getToken();
+ {
+ Token token = ((SinglePartitionReadCommand) command).partitionKey().getToken();
+ Replica localReplica = getLocalReplica(metadata, token, command.metadata().keyspace);
+ if (localReplica == null)
+ {
+ metadata = ClusterMetadataService.instance().fetchLogFromPeerOrCMS(metadata, message.from(), message.epoch());
+ localReplica = getLocalReplica(metadata, token, command.metadata().keyspace);
+ }
+ if (localReplica == null)
+ {
+ StorageService.instance.incOutOfRangeOperationCount();
+ Keyspace.open(command.metadata().keyspace).metric.outOfRangeTokenReads.inc();
+ throw InvalidRoutingException.forTokenRead(message.from(), token, metadata.epoch, message.payload);
+ }
+
+ if (!command.acceptsTransient() && localReplica.isTransient())
+ {
+ MessagingService.instance().metrics.recordDroppedMessage(message, message.elapsedSinceCreated(NANOSECONDS), NANOSECONDS);
+ throw new InvalidRequestException(String.format("Attempted to serve %s data request from %s node in %s",
+ command.acceptsTransient() ? "transient" : "full",
+ localReplica.isTransient() ? "transient" : "full",
+ this));
+ }
+ }
else
- token = ((PartitionRangeReadCommand) command).dataRange().keyRange().right.getToken();
-
- Replica replica = Keyspace.open(command.metadata().keyspace)
- .getReplicationStrategy()
- .getLocalReplicaFor(token);
-
- if (replica == null)
{
- if (command.isTopK())
- return;
+ AbstractBounds range = ((PartitionRangeReadCommand) command).dataRange().keyRange();
- logger.warn("Received a read request from {} for a range that is not owned by the current replica {}.",
- message.from(),
- command);
- return;
- }
+ // TODO: preexisting issue: for the range queries or queries that span multiple replicas, we can only make requests where the right token is owned, but not the left one
+ Replica maxTokenLocalReplica = getLocalReplica(metadata, range.right.getToken(), command.metadata().keyspace);
+ if (maxTokenLocalReplica == null)
+ {
+ metadata = ClusterMetadataService.instance().fetchLogFromPeerOrCMS(metadata, message.from(), message.epoch());
+ maxTokenLocalReplica = getLocalReplica(metadata, range.right.getToken(), command.metadata().keyspace);
+ }
+ if (maxTokenLocalReplica == null)
+ {
+ StorageService.instance.incOutOfRangeOperationCount();
+ Keyspace.open(command.metadata().keyspace).metric.outOfRangeTokenReads.inc();
+ throw InvalidRoutingException.forRangeRead(message.from(), range, metadata.epoch, message.payload);
+ }
- if (!command.acceptsTransient() && replica.isTransient())
- {
- MessagingService.instance().metrics.recordDroppedMessage(message, message.elapsedSinceCreated(NANOSECONDS), NANOSECONDS);
- throw new InvalidRequestException(String.format("Attempted to serve %s data request from %s node in %s",
- command.acceptsTransient() ? "transient" : "full",
- replica.isTransient() ? "transient" : "full",
- this));
+
+ // TODO: preexisting issue: we should change the whole range for transient-ness, not just the right token
+ if (command.acceptsTransient() != maxTokenLocalReplica.isTransient())
+ {
+ MessagingService.instance().metrics.recordDroppedMessage(message, message.elapsedSinceCreated(NANOSECONDS), NANOSECONDS);
+ throw new InvalidRequestException(String.format("Attempted to serve %s data request from %s node in %s",
+ command.acceptsTransient() ? "transient" : "full",
+ maxTokenLocalReplica.isTransient() ? "transient" : "full",
+ this));
+ }
}
+ return metadata;
+ }
+
+ private static Replica getLocalReplica(ClusterMetadata metadata, Token token, String keyspace)
+ {
+ return metadata.placements
+ .get(metadata.schema.getKeyspaces().getNullable(keyspace).params.replication)
+ .reads
+ .forToken(token)
+ .get()
+ .lookup(FBUtilities.getBroadcastAddressAndPort());
}
}
diff --git a/src/java/org/apache/cassandra/db/ReadRepairVerbHandler.java b/src/java/org/apache/cassandra/db/ReadRepairVerbHandler.java
index 903b3d43bd..8ca29eba13 100644
--- a/src/java/org/apache/cassandra/db/ReadRepairVerbHandler.java
+++ b/src/java/org/apache/cassandra/db/ReadRepairVerbHandler.java
@@ -17,17 +17,17 @@
*/
package org.apache.cassandra.db;
-import org.apache.cassandra.net.IVerbHandler;
+import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.net.Message;
import org.apache.cassandra.net.MessagingService;
-public class ReadRepairVerbHandler implements IVerbHandler
+public class ReadRepairVerbHandler extends AbstractMutationVerbHandler
{
public static final ReadRepairVerbHandler instance = new ReadRepairVerbHandler();
- public void doVerb(Message message)
+ void applyMutation(Message message, InetAddressAndPort respondToAddress)
{
message.payload.apply();
- MessagingService.instance().send(message.emptyResponse(), message.from());
+ MessagingService.instance().send(message.emptyResponse(), respondToAddress);
}
}
diff --git a/src/java/org/apache/cassandra/db/ReadResponse.java b/src/java/org/apache/cassandra/db/ReadResponse.java
index a9e2cec4a7..d4906b2dd5 100644
--- a/src/java/org/apache/cassandra/db/ReadResponse.java
+++ b/src/java/org/apache/cassandra/db/ReadResponse.java
@@ -34,6 +34,8 @@ import org.apache.cassandra.net.MessagingService;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.utils.ByteBufferUtil;
+import static org.apache.cassandra.db.RepairedDataInfo.NO_OP_REPAIRED_DATA_INFO;
+
public abstract class ReadResponse
{
// Serializer for single partition read response
@@ -48,6 +50,11 @@ public abstract class ReadResponse
return new LocalDataResponse(data, command, rdi);
}
+ public static ReadResponse createDataResponse(UnfilteredPartitionIterator data, ReadCommand command)
+ {
+ return new LocalDataResponse(data, command, NO_OP_REPAIRED_DATA_INFO);
+ }
+
public static ReadResponse createSimpleDataResponse(UnfilteredPartitionIterator data, ColumnFilter selection)
{
return new LocalDataResponse(data, selection);
diff --git a/src/java/org/apache/cassandra/db/SinglePartitionReadCommand.java b/src/java/org/apache/cassandra/db/SinglePartitionReadCommand.java
index ae692fc5b8..aa7b72e4ef 100644
--- a/src/java/org/apache/cassandra/db/SinglePartitionReadCommand.java
+++ b/src/java/org/apache/cassandra/db/SinglePartitionReadCommand.java
@@ -76,6 +76,7 @@ import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.CacheService;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.service.StorageProxy;
+import org.apache.cassandra.tcm.Epoch;
import org.apache.cassandra.tracing.Tracing;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.btree.BTreeSet;
@@ -91,7 +92,8 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
protected final ClusteringIndexFilter clusteringIndexFilter;
@VisibleForTesting
- protected SinglePartitionReadCommand(boolean isDigest,
+ protected SinglePartitionReadCommand(Epoch serializedAtEpoch,
+ boolean isDigest,
int digestVersion,
boolean acceptsTransient,
TableMetadata metadata,
@@ -104,24 +106,25 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
Index.QueryPlan indexQueryPlan,
boolean trackWarnings)
{
- super(Kind.SINGLE_PARTITION, isDigest, digestVersion, acceptsTransient, metadata, nowInSec, columnFilter, rowFilter, limits, indexQueryPlan, trackWarnings);
+ super(serializedAtEpoch, Kind.SINGLE_PARTITION, isDigest, digestVersion, acceptsTransient, metadata, nowInSec, columnFilter, rowFilter, limits, indexQueryPlan, trackWarnings);
assert partitionKey.getPartitioner() == metadata.partitioner;
this.partitionKey = partitionKey;
this.clusteringIndexFilter = clusteringIndexFilter;
}
- private static SinglePartitionReadCommand create(boolean isDigest,
- int digestVersion,
- boolean acceptsTransient,
- TableMetadata metadata,
- long nowInSec,
- ColumnFilter columnFilter,
- RowFilter rowFilter,
- DataLimits limits,
- DecoratedKey partitionKey,
- ClusteringIndexFilter clusteringIndexFilter,
- Index.QueryPlan indexQueryPlan,
- boolean trackWarnings)
+ private static SinglePartitionReadCommand create(Epoch serializedAtEpoch,
+ boolean isDigest,
+ int digestVersion,
+ boolean acceptsTransient,
+ TableMetadata metadata,
+ long nowInSec,
+ ColumnFilter columnFilter,
+ RowFilter rowFilter,
+ DataLimits limits,
+ DecoratedKey partitionKey,
+ ClusteringIndexFilter clusteringIndexFilter,
+ Index.QueryPlan indexQueryPlan,
+ boolean trackWarnings)
{
if (metadata.isVirtual())
{
@@ -138,7 +141,8 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
indexQueryPlan,
trackWarnings);
}
- return new SinglePartitionReadCommand(isDigest,
+ return new SinglePartitionReadCommand(serializedAtEpoch,
+ isDigest,
digestVersion,
acceptsTransient,
metadata,
@@ -175,7 +179,8 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
ClusteringIndexFilter clusteringIndexFilter,
Index.QueryPlan indexQueryPlan)
{
- return create(false,
+ return create(metadata.epoch,
+ false,
0,
false,
metadata,
@@ -352,7 +357,8 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
public SinglePartitionReadCommand copy()
{
- return create(isDigestQuery(),
+ return create(serializedAtEpoch(),
+ isDigestQuery(),
digestVersion(),
acceptsTransient(),
metadata(),
@@ -369,7 +375,8 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
@Override
protected SinglePartitionReadCommand copyAsDigestQuery()
{
- return create(true,
+ return create(serializedAtEpoch(),
+ true,
digestVersion(),
acceptsTransient(),
metadata(),
@@ -386,7 +393,8 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
@Override
protected SinglePartitionReadCommand copyAsTransientQuery()
{
- return create(false,
+ return create(serializedAtEpoch(),
+ false,
0,
true,
metadata(),
@@ -403,7 +411,8 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
@Override
public SinglePartitionReadCommand withUpdatedLimit(DataLimits newLimits)
{
- return create(isDigestQuery(),
+ return create(serializedAtEpoch(),
+ isDigestQuery(),
digestVersion(),
acceptsTransient(),
metadata(),
@@ -1302,6 +1311,7 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
{
public ReadCommand deserialize(DataInputPlus in,
int version,
+ Epoch serializedAtEpoch,
boolean isDigest,
int digestVersion,
boolean acceptsTransient,
@@ -1315,7 +1325,7 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
{
DecoratedKey key = metadata.partitioner.decorateKey(metadata.partitionKeyType.readBuffer(in, DatabaseDescriptor.getMaxValueSize()));
ClusteringIndexFilter filter = ClusteringIndexFilter.serializer.deserialize(in, version, metadata);
- return SinglePartitionReadCommand.create(isDigest, digestVersion, acceptsTransient, metadata, nowInSec, columnFilter, rowFilter, limits, key, filter, indexQueryPlan, false);
+ return SinglePartitionReadCommand.create(serializedAtEpoch, isDigest, digestVersion, acceptsTransient, metadata, nowInSec, columnFilter, rowFilter, limits, key, filter, indexQueryPlan, false);
}
}
@@ -1362,7 +1372,7 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar
Index.QueryPlan indexQueryPlan,
boolean trackWarnings)
{
- super(isDigest, digestVersion, acceptsTransient, metadata, nowInSec, columnFilter, rowFilter, limits, partitionKey, clusteringIndexFilter, indexQueryPlan, trackWarnings);
+ super(metadata.epoch, isDigest, digestVersion, acceptsTransient, metadata, nowInSec, columnFilter, rowFilter, limits, partitionKey, clusteringIndexFilter, indexQueryPlan, trackWarnings);
}
@Override
diff --git a/src/java/org/apache/cassandra/db/SizeEstimatesRecorder.java b/src/java/org/apache/cassandra/db/SizeEstimatesRecorder.java
index f9233bf904..56e8370cda 100644
--- a/src/java/org/apache/cassandra/db/SizeEstimatesRecorder.java
+++ b/src/java/org/apache/cassandra/db/SizeEstimatesRecorder.java
@@ -19,7 +19,10 @@ package org.apache.cassandra.db;
import java.util.*;
import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.Lists;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -29,15 +32,17 @@ import org.apache.cassandra.db.lifecycle.View;
import org.apache.cassandra.dht.Range;
import org.apache.cassandra.dht.Token;
import org.apache.cassandra.io.sstable.format.SSTableReader;
-import org.apache.cassandra.locator.TokenMetadata;
import org.apache.cassandra.schema.Schema;
import org.apache.cassandra.schema.SchemaChangeListener;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.StorageService;
+import org.apache.cassandra.tcm.ClusterMetadata;
+import org.apache.cassandra.tcm.membership.NodeId;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.Pair;
import org.apache.cassandra.utils.concurrent.Refs;
+import static org.apache.cassandra.tcm.compatibility.TokenRingUtils.getAllRanges;
import static org.apache.cassandra.utils.Clock.Global.nanoTime;
/**
@@ -62,8 +67,7 @@ public class SizeEstimatesRecorder implements SchemaChangeListener, Runnable
public void run()
{
- TokenMetadata metadata = StorageService.instance.getTokenMetadata().cloneOnlyTokenMap();
- if (!metadata.isMember(FBUtilities.getBroadcastAddressAndPort()))
+ if (!ClusterMetadata.current().directory.allAddresses().contains(FBUtilities.getBroadcastAddressAndPort()))
{
logger.debug("Node is not part of the ring; not recording size estimates");
return;
@@ -73,6 +77,9 @@ public class SizeEstimatesRecorder implements SchemaChangeListener, Runnable
for (Keyspace keyspace : Keyspace.nonLocalStrategy())
{
+ if (keyspace.getMetadata().params.replication.isMeta())
+ continue;
+
// In tools the call to describe_splits_ex() used to be coupled with the call to describe_local_ring() so
// most access was for the local primary range; after creating the size_estimates table this was changed
// to be the primary range.
@@ -88,7 +95,7 @@ public class SizeEstimatesRecorder implements SchemaChangeListener, Runnable
// range. If we publish multiple ranges downstream integrations may start to see duplicate data.
// See CASSANDRA-15637
Collection> primaryRanges = StorageService.instance.getPrimaryRanges(keyspace.getName());
- Collection> localPrimaryRanges = StorageService.instance.getLocalPrimaryRange();
+ Collection> localPrimaryRanges = getLocalPrimaryRange();
boolean rangesAreEqual = primaryRanges.equals(localPrimaryRanges);
for (ColumnFamilyStore table : keyspace.getColumnFamilyStores())
{
@@ -116,6 +123,34 @@ public class SizeEstimatesRecorder implements SchemaChangeListener, Runnable
}
}
+ @VisibleForTesting
+ public static Collection> getLocalPrimaryRange()
+ {
+ ClusterMetadata metadata = ClusterMetadata.current();
+ NodeId localNodeId = metadata.myNodeId();
+ return getLocalPrimaryRange(metadata, localNodeId);
+ }
+
+ @VisibleForTesting
+ public static Collection> getLocalPrimaryRange(ClusterMetadata metadata, NodeId nodeId)
+ {
+ String dc = metadata.directory.location(nodeId).datacenter;
+ Set tokens = new HashSet<>(metadata.tokenMap.tokens(nodeId));
+
+ // filter tokens to the single DC
+ List filteredTokens = Lists.newArrayList();
+ for (Token token : metadata.tokenMap.tokens())
+ {
+ NodeId owner = metadata.tokenMap.owner(token);
+ if (dc.equals(metadata.directory.location(owner).datacenter))
+ filteredTokens.add(token);
+ }
+ return getAllRanges(filteredTokens).stream()
+ .filter(t -> tokens.contains(t.right))
+ .collect(Collectors.toList());
+ }
+
+ @SuppressWarnings("resource")
private static Map, Pair> computeSizeEstimates(ColumnFamilyStore table, Collection> ranges)
{
// for each local primary range, estimate (crudely) mean partition size and partitions count.
diff --git a/src/java/org/apache/cassandra/db/SystemKeyspace.java b/src/java/org/apache/cassandra/db/SystemKeyspace.java
index b8ecc5afb4..d751f6ea2c 100644
--- a/src/java/org/apache/cassandra/db/SystemKeyspace.java
+++ b/src/java/org/apache/cassandra/db/SystemKeyspace.java
@@ -36,7 +36,6 @@ import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
-import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import javax.management.openmbean.OpenDataException;
@@ -59,7 +58,6 @@ import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.cql3.QueryProcessor;
import org.apache.cassandra.cql3.UntypedResultSet;
import org.apache.cassandra.cql3.statements.schema.CreateTableStatement;
-import org.apache.cassandra.db.commitlog.CommitLog;
import org.apache.cassandra.db.commitlog.CommitLogPosition;
import org.apache.cassandra.db.compaction.CompactionHistoryTabularData;
import org.apache.cassandra.db.marshal.ByteBufferAccessor;
@@ -77,6 +75,9 @@ import org.apache.cassandra.dht.LocalPartitioner;
import org.apache.cassandra.dht.Range;
import org.apache.cassandra.dht.Token;
import org.apache.cassandra.exceptions.ConfigurationException;
+import org.apache.cassandra.gms.EndpointState;
+import org.apache.cassandra.gms.HeartBeatState;
+import org.apache.cassandra.gms.VersionedValue;
import org.apache.cassandra.io.sstable.SSTableId;
import org.apache.cassandra.io.sstable.SequenceBasedSSTableId;
import org.apache.cassandra.io.util.DataInputBuffer;
@@ -110,6 +111,10 @@ import org.apache.cassandra.service.paxos.PaxosState;
import org.apache.cassandra.service.paxos.uncommitted.PaxosRows;
import org.apache.cassandra.service.paxos.uncommitted.PaxosUncommittedIndex;
import org.apache.cassandra.streaming.StreamOperation;
+import org.apache.cassandra.tcm.ClusterMetadata;
+import org.apache.cassandra.tcm.Epoch;
+import org.apache.cassandra.tcm.Sealed;
+import org.apache.cassandra.tcm.membership.NodeState;
import org.apache.cassandra.transport.ProtocolVersion;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.CassandraVersion;
@@ -127,6 +132,14 @@ import static org.apache.cassandra.config.DatabaseDescriptor.paxosStatePurging;
import static org.apache.cassandra.cql3.QueryProcessor.executeInternal;
import static org.apache.cassandra.cql3.QueryProcessor.executeInternalWithNowInSec;
import static org.apache.cassandra.cql3.QueryProcessor.executeOnceInternal;
+import static org.apache.cassandra.gms.ApplicationState.DC;
+import static org.apache.cassandra.gms.ApplicationState.HOST_ID;
+import static org.apache.cassandra.gms.ApplicationState.INTERNAL_ADDRESS_AND_PORT;
+import static org.apache.cassandra.gms.ApplicationState.NATIVE_ADDRESS_AND_PORT;
+import static org.apache.cassandra.gms.ApplicationState.RACK;
+import static org.apache.cassandra.gms.ApplicationState.RELEASE_VERSION;
+import static org.apache.cassandra.gms.ApplicationState.STATUS_WITH_PORT;
+import static org.apache.cassandra.gms.ApplicationState.TOKENS;
import static org.apache.cassandra.service.paxos.Commit.latest;
import static org.apache.cassandra.utils.CassandraVersion.NULL_VERSION;
import static org.apache.cassandra.utils.CassandraVersion.UNREADABLE_VERSION;
@@ -163,6 +176,10 @@ public final class SystemKeyspace
public static final String PREPARED_STATEMENTS = "prepared_statements";
public static final String REPAIRS = "repairs";
public static final String TOP_PARTITIONS = "top_partitions";
+ public static final String METADATA_LOG = "local_metadata_log";
+ public static final String SNAPSHOT_TABLE_NAME = "metadata_snapshots";
+ public static final String SEALED_PERIODS_TABLE_NAME = "metadata_sealed_periods";
+ public static final String LAST_SEALED_PERIOD_TABLE_NAME = "metadata_last_sealed_period";
/**
* By default the system keyspace tables should be stored in a single data directory to allow the server
@@ -195,13 +212,15 @@ public final class SystemKeyspace
COMPACTION_HISTORY, SSTABLE_ACTIVITY_V2, TABLE_ESTIMATES, TABLE_ESTIMATES_TYPE_PRIMARY,
TABLE_ESTIMATES_TYPE_LOCAL_PRIMARY, AVAILABLE_RANGES_V2, TRANSFERRED_RANGES_V2, VIEW_BUILDS_IN_PROGRESS,
BUILT_VIEWS, PREPARED_STATEMENTS, REPAIRS, TOP_PARTITIONS, LEGACY_PEERS, LEGACY_PEER_EVENTS,
- LEGACY_TRANSFERRED_RANGES, LEGACY_AVAILABLE_RANGES, LEGACY_SIZE_ESTIMATES, LEGACY_SSTABLE_ACTIVITY);
+ LEGACY_TRANSFERRED_RANGES, LEGACY_AVAILABLE_RANGES, LEGACY_SIZE_ESTIMATES, LEGACY_SSTABLE_ACTIVITY,
+ METADATA_LOG, SNAPSHOT_TABLE_NAME, SEALED_PERIODS_TABLE_NAME, LAST_SEALED_PERIOD_TABLE_NAME);
public static final Set TABLE_NAMES = ImmutableSet.of(
BATCHES, PAXOS, PAXOS_REPAIR_HISTORY, BUILT_INDEXES, LOCAL, PEERS_V2, PEER_EVENTS_V2,
COMPACTION_HISTORY, SSTABLE_ACTIVITY_V2, TABLE_ESTIMATES, AVAILABLE_RANGES_V2, TRANSFERRED_RANGES_V2, VIEW_BUILDS_IN_PROGRESS,
BUILT_VIEWS, PREPARED_STATEMENTS, REPAIRS, TOP_PARTITIONS, LEGACY_PEERS, LEGACY_PEER_EVENTS,
- LEGACY_TRANSFERRED_RANGES, LEGACY_AVAILABLE_RANGES, LEGACY_SIZE_ESTIMATES, LEGACY_SSTABLE_ACTIVITY);
+ LEGACY_TRANSFERRED_RANGES, LEGACY_AVAILABLE_RANGES, LEGACY_SIZE_ESTIMATES, LEGACY_SSTABLE_ACTIVITY,
+ METADATA_LOG, SNAPSHOT_TABLE_NAME, SEALED_PERIODS_TABLE_NAME, LAST_SEALED_PERIOD_TABLE_NAME);
public static final TableMetadata Batches =
parse(BATCHES,
@@ -466,7 +485,45 @@ public final class SystemKeyspace
+ "cfids set, "
+ "PRIMARY KEY (parent_id))").build();
- /** @deprecated See CASSANDRA-7544 */
+ public static final TableMetadata LocalMetadataLog =
+ parse(METADATA_LOG,
+ "Local Metadata Log",
+ "CREATE TABLE %s ("
+ + "period bigint,"
+ + "current_epoch bigint static,"
+ + "epoch bigint,"
+ + "entry_id bigint,"
+ + "transformation blob,"
+ + "kind text,"
+ + "PRIMARY KEY (period, epoch))")
+ .compaction(CompactionParams.twcs(ImmutableMap.of("compaction_window_unit","DAYS",
+ "compaction_window_size","1")))
+ .build();
+
+ public static final TableMetadata Snapshots = parse(SNAPSHOT_TABLE_NAME,
+ "ClusterMetadata snapshots",
+ "CREATE TABLE IF NOT EXISTS %s (" +
+ "epoch bigint PRIMARY KEY," +
+ "period bigint," +
+ "snapshot blob)")
+ .build();
+
+ public static final TableMetadata SealedPeriods = parse(SEALED_PERIODS_TABLE_NAME,
+ "ClusterMetadata sealed periods",
+ "CREATE TABLE IF NOT EXISTS %s (" +
+ "max_epoch bigint PRIMARY KEY," +
+ "period bigint)")
+ .partitioner(new LocalPartitioner(LongType.instance))
+ .build();
+
+ public static final TableMetadata LastSealedPeriod = parse(LAST_SEALED_PERIOD_TABLE_NAME,
+ "ClusterMetadata last sealed period",
+ "CREATE TABLE IF NOT EXISTS %s (" +
+ "key text PRIMARY KEY," +
+ "epoch bigint," +
+ "period bigint)")
+ .build();
+
@Deprecated(since = "4.0")
private static final TableMetadata LegacyPeers =
parse(LEGACY_PEERS,
@@ -557,7 +614,11 @@ public final class SystemKeyspace
BuiltViews,
PreparedStatements,
Repairs,
- TopPartitions);
+ TopPartitions,
+ LocalMetadataLog,
+ LastSealedPeriod,
+ SealedPeriods,
+ Snapshots);
}
private static volatile Map> truncationRecords;
@@ -567,16 +628,31 @@ public final class SystemKeyspace
NEEDS_BOOTSTRAP,
COMPLETED,
IN_PROGRESS,
- DECOMMISSIONED
+ DECOMMISSIONED;
+
+ public static BootstrapState fromNodeState(NodeState nodeState)
+ {
+ if (nodeState == null) // todo, handle this properly
+ return DECOMMISSIONED;
+ switch (nodeState)
+ {
+ case REGISTERED:
+ return NEEDS_BOOTSTRAP;
+ case BOOTSTRAPPING:
+ case BOOT_REPLACING:
+ return IN_PROGRESS;
+ case JOINED:
+ case LEAVING:
+ case MOVING:
+ return COMPLETED;
+ case LEFT:
+ default:
+ return DECOMMISSIONED;
+ }
+ }
}
public static void persistLocalMetadata()
- {
- persistLocalMetadata(UUID::randomUUID);
- }
-
- @VisibleForTesting
- public static void persistLocalMetadata(Supplier nodeIdSupplier)
{
String req = "INSERT INTO system.%s (" +
"key," +
@@ -610,13 +686,6 @@ public final class SystemKeyspace
DatabaseDescriptor.getStoragePort(),
FBUtilities.getJustLocalAddress(),
DatabaseDescriptor.getStoragePort());
-
- // We should store host ID as soon as possible in the system.local table and flush that table to disk so that
- // we can be sure that those changes are stored in sstable and not in the commit log (see CASSANDRA-18153).
- // It is very unlikely that when upgrading the host id is not flushed to disk, but if that's the case, we limit
- // this change only to the new installations or the user should just flush system.local table.
- if (!CommitLog.instance.hasFilesToReplay())
- SystemKeyspace.getOrInitializeLocalHostId(nodeIdSupplier);
}
public static void updateCompactionHistory(TimeUUID taskId,
@@ -831,7 +900,10 @@ public final class SystemKeyspace
public static synchronized void updateTokens(InetAddressAndPort ep, Collection tokens)
{
if (ep.equals(FBUtilities.getBroadcastAddressAndPort()))
+ {
+ updateLocalTokens(tokens);
return;
+ }
String req = "INSERT INTO system.%s (peer, tokens) VALUES (?, ?)";
executeInternal(String.format(req, LEGACY_PEERS), ep.getAddress(), tokensAsSet(tokens));
@@ -895,11 +967,11 @@ public final class SystemKeyspace
executeInternal(format(req, LOCAL, LOCAL), version);
}
- private static Set tokensAsSet(Collection tokens)
+ public static Set tokensAsSet(Collection tokens)
{
if (tokens.isEmpty())
return Collections.emptySet();
- Token.TokenFactory factory = StorageService.instance.getTokenFactory();
+ Token.TokenFactory factory = ClusterMetadata.current().partitioner.getTokenFactory();
Set s = new HashSet<>(tokens.size());
for (Token tk : tokens)
s.add(factory.toString(tk));
@@ -908,7 +980,7 @@ public final class SystemKeyspace
private static Collection deserializeTokens(Collection tokensStrings)
{
- Token.TokenFactory factory = StorageService.instance.getTokenFactory();
+ Token.TokenFactory factory = ClusterMetadata.current().partitioner.getTokenFactory();
List tokens = new ArrayList<>(tokensStrings.size());
for (String tk : tokensStrings)
tokens.add(factory.fromString(tk));
@@ -928,9 +1000,10 @@ public final class SystemKeyspace
}
/**
+ *
* This method is used to update the System Keyspace with the new tokens for this node
*/
- public static synchronized void updateTokens(Collection tokens)
+ public static synchronized void updateLocalTokens(Collection tokens)
{
assert !tokens.isEmpty() : "removeEndpoint should be used instead";
@@ -1240,27 +1313,6 @@ public final class SystemKeyspace
return null;
}
- /**
- * Read the host ID from the system keyspace, creating (and storing) one if
- * none exists.
- */
- public static synchronized UUID getOrInitializeLocalHostId()
- {
- return getOrInitializeLocalHostId(UUID::randomUUID);
- }
-
- private static synchronized UUID getOrInitializeLocalHostId(Supplier nodeIdSupplier)
- {
- UUID hostId = getLocalHostId();
- if (hostId != null)
- return hostId;
-
- // ID not found, generate a new one, persist, and then return it.
- hostId = nodeIdSupplier.get();
- logger.warn("No host ID found, created {} (Note: This should happen exactly once per node).", hostId);
- return setLocalHostId(hostId);
- }
-
/**
* Sets the local host ID explicitly. Should only be called outside of SystemTable when replacing a node.
*/
@@ -1316,6 +1368,20 @@ public final class SystemKeyspace
return null;
}
+ public static Set allKnownDatacenters()
+ {
+ Set dcs = new HashSet<>();
+ dcs.add(getDatacenter());
+ String req = "SELECT data_center FROM system.%s";
+ UntypedResultSet result = executeInternal(format(req, PEERS_V2));
+ if (result != null)
+ {
+ for (UntypedResultSet.Row row : result)
+ dcs.add(row.getString("data_center"));
+ }
+ return dcs;
+ }
+
/**
* Load the current paxos state for the table and key
*/
@@ -1937,4 +2003,122 @@ public final class SystemKeyspace
return TopPartitionTracker.StoredTopPartitions.EMPTY;
}
}
+
+ public static void storeSnapshot(Epoch epoch, long period, ByteBuffer snapshot)
+ {
+ logger.info("Storing snapshot of cluster metadata at epoch {} (period {})", epoch, period);
+ String query = String.format("INSERT INTO %s.%s (epoch, period, snapshot) VALUES (?, ?, ?)", SchemaConstants.SYSTEM_KEYSPACE_NAME, SNAPSHOT_TABLE_NAME);
+ executeInternal(query, epoch.getEpoch(), period, snapshot);
+ }
+
+ public static ByteBuffer getSnapshot(Epoch epoch)
+ {
+ logger.info("Getting snapshot of epoch = {}", epoch);
+ String query = String.format("SELECT SNAPSHOT FROM %s.%s WHERE epoch = ?", SchemaConstants.SYSTEM_KEYSPACE_NAME, SNAPSHOT_TABLE_NAME);
+ UntypedResultSet res = executeInternal(query, epoch.getEpoch());
+ if (res == null || res.isEmpty())
+ return null;
+ return res.one().getBytes("snapshot").duplicate();
+ }
+
+ public static Sealed findSealedPeriodForEpochScan(Epoch search)
+ {
+ String query = String.format("SELECT max_epoch, period FROM %s.%s WHERE max_epoch >= ? LIMIT 1 ALLOW FILTERING", SchemaConstants.SYSTEM_KEYSPACE_NAME, SEALED_PERIODS_TABLE_NAME);
+ UntypedResultSet res = executeInternal(query, search.getEpoch());
+ if (res != null && !res.isEmpty())
+ {
+ long period = res.one().getLong("period");
+ long epoch = res.one().getLong("max_epoch");
+ return new Sealed(period, epoch);
+ }
+
+ // nothing found for this epoch, is the table empty or is the search epoch > the maximum
+ query = String.format("SELECT max_epoch, period FROM %s.%s LIMIT 1", SchemaConstants.SYSTEM_KEYSPACE_NAME, SEALED_PERIODS_TABLE_NAME);
+ res = executeInternal(query);
+ // table is empty, so any scan for the epoch will have to begin at Period.EMPTY
+ if (res == null || res.isEmpty())
+ return Sealed.EMPTY;
+
+ // the index table has some data, but is the search target greater than the max epoch in last sealed period?
+ // This query is relatively costly, so we do it last. Retain the min period/epoch that we did find in the
+ // previous query just in case we need them
+ // TODO add a nodetool command to rebuild the local sealed periods table
+ long lowestPeriod = res.one().getLong("period");
+ long lowestMaxEpoch = res.one().getLong("max_epoch");
+ logger.info("Scanning sealed periods by epoch table, this may be an expensive operation and the index table {} should be rebuilt", SEALED_PERIODS_TABLE_NAME);
+ query = String.format("SELECT max(max_epoch) AS max_epoch FROM %s.%s LIMIT 1 ALLOW FILTERING;", SchemaConstants.SYSTEM_KEYSPACE_NAME, SEALED_PERIODS_TABLE_NAME);
+ res = executeInternal(query);
+
+ // should never happen because the previous query returned the min, but just in case the table has been
+ // truncated since then, return the min Sealed.
+ if (res == null || res.isEmpty())
+ return new Sealed(lowestPeriod, lowestMaxEpoch);
+
+ // use the max epoch to look up the sealed period
+ long maxEpoch = res.one().getLong("max_epoch");
+ query = String.format("SELECT period FROM %s.%s WHERE max_epoch = ?", SchemaConstants.SYSTEM_KEYSPACE_NAME, SEALED_PERIODS_TABLE_NAME);
+ res = executeInternal(query, maxEpoch);
+ if (res == null || res.isEmpty())
+ return new Sealed(lowestPeriod, lowestMaxEpoch);
+ // this is the last recorded sealed period *before* the target epoch, so any scan should start at the
+ // *next* period, so we bump both period and epoch by 1
+ long maxPeriod = res.one().getLong("period");
+ return new Sealed(maxPeriod + 1, maxEpoch + 1);
+ }
+
+ public static Sealed getLastSealedPeriod()
+ {
+ String query = String.format("SELECT epoch, period FROM %s.%s WHERE key = 'latest'", SchemaConstants.SYSTEM_KEYSPACE_NAME, LAST_SEALED_PERIOD_TABLE_NAME);
+ UntypedResultSet res = executeInternal(query);
+ if (res == null || res.isEmpty())
+ return Sealed.EMPTY;
+ long epoch = res.one().getLong("epoch");
+ long period = res.one().getLong("period");
+ return new Sealed(period, Epoch.create(epoch));
+ }
+
+ public static void sealPeriod(long period, Epoch epoch)
+ {
+ String query = String.format("INSERT INTO %s.%s (max_epoch, period) VALUES (?,?)", SchemaConstants.SYSTEM_KEYSPACE_NAME, SEALED_PERIODS_TABLE_NAME);
+ executeInternal(query, epoch.getEpoch(), period);
+ query = String.format("UPDATE %s.%s SET period = ?, epoch = ? WHERE key = 'latest'", SchemaConstants.SYSTEM_KEYSPACE_NAME, LAST_SEALED_PERIOD_TABLE_NAME);
+ executeInternal(query, period, epoch.getEpoch());
+ }
+
+ public static Map peerEndpointStates()
+ {
+ Map epstates = new HashMap<>();
+ VersionedValue.VersionedValueFactory vf = StorageService.instance.valueFactory;
+ String query = String.format("select * from %s.%s", SchemaConstants.SYSTEM_KEYSPACE_NAME, PEERS_V2);
+ UntypedResultSet res = executeInternal(query);
+ for (UntypedResultSet.Row row : res)
+ {
+ EndpointState epstate = new EndpointState(new HeartBeatState(0, 0));
+ InetAddressAndPort endpoint = InetAddressAndPort.getByAddressOverrideDefaults(row.getInetAddress("peer"), row.getInt("peer_port"));
+ epstate.addApplicationState(DC, vf.datacenter(row.getString("data_center")));
+ epstate.addApplicationState(RACK, vf.rack(row.getString("rack")));
+ epstate.addApplicationState(RELEASE_VERSION, vf.releaseVersion(row.getString("release_version")));
+ epstate.addApplicationState(HOST_ID, vf.hostId(row.getUUID("host_id")));
+ Collection tokens = deserializeTokens(row.getSet("tokens", UTF8Type.instance));
+ epstate.addApplicationState(STATUS_WITH_PORT, vf.normal(tokens));
+ epstate.addApplicationState(TOKENS, vf.tokens(tokens));
+
+ if (row.has("preferred_ip"))
+ {
+ epstate.addApplicationState(INTERNAL_ADDRESS_AND_PORT,
+ vf.internalAddressAndPort(InetAddressAndPort.getByAddressOverrideDefaults(row.getInetAddress("preferred_ip"),
+ row.getInt("preferred_port"))));
+ }
+
+ if (row.has("native_ip"))
+ {
+ epstate.addApplicationState(NATIVE_ADDRESS_AND_PORT,
+ vf.nativeaddressAndPort(InetAddressAndPort.getByAddressOverrideDefaults(row.getInetAddress("native_ip"),
+ row.getInt("native_port"))));
+ }
+
+ epstates.put(endpoint, epstate);
+ }
+ return epstates;
+ }
}
diff --git a/src/java/org/apache/cassandra/db/commitlog/AbstractCommitLogSegmentManager.java b/src/java/org/apache/cassandra/db/commitlog/AbstractCommitLogSegmentManager.java
index e6cc2fa814..e5e82f9662 100644
--- a/src/java/org/apache/cassandra/db/commitlog/AbstractCommitLogSegmentManager.java
+++ b/src/java/org/apache/cassandra/db/commitlog/AbstractCommitLogSegmentManager.java
@@ -320,11 +320,12 @@ public abstract class AbstractCommitLogSegmentManager
void forceRecycleAll(Collection droppedTables)
{
List segmentsToRecycle = new ArrayList<>(activeSegments);
- CommitLogSegment last = segmentsToRecycle.get(segmentsToRecycle.size() - 1);
+ CommitLogSegment last = segmentsToRecycle.isEmpty() ? null : segmentsToRecycle.get(segmentsToRecycle.size() - 1);
advanceAllocatingFrom(last);
// wait for the commit log modifications
- last.waitForModifications();
+ if (last != null)
+ last.waitForModifications();
// make sure the writes have materialized inside of the memtables by waiting for all outstanding writes
// to complete
@@ -350,7 +351,7 @@ public abstract class AbstractCommitLogSegmentManager
}
CommitLogSegment first;
- if ((first = activeSegments.peek()) != null && first.id <= last.id)
+ if ((first = activeSegments.peek()) != null && last != null && first.id <= last.id)
logger.error("Failed to force-recycle all segments; at least one segment is still in use with dirty CFs.");
}
catch (Throwable t)
diff --git a/src/java/org/apache/cassandra/db/compaction/CompactionManager.java b/src/java/org/apache/cassandra/db/compaction/CompactionManager.java
index 0eeed14be6..f228540394 100644
--- a/src/java/org/apache/cassandra/db/compaction/CompactionManager.java
+++ b/src/java/org/apache/cassandra/db/compaction/CompactionManager.java
@@ -96,6 +96,7 @@ import org.apache.cassandra.io.sstable.metadata.MetadataCollector;
import org.apache.cassandra.io.sstable.metadata.StatsMetadata;
import org.apache.cassandra.io.util.File;
import org.apache.cassandra.io.util.FileUtils;
+import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.locator.RangesAtEndpoint;
import org.apache.cassandra.metrics.CompactionMetrics;
import org.apache.cassandra.metrics.TableMetrics;
@@ -113,10 +114,13 @@ import org.apache.cassandra.utils.OutputHandler;
import org.apache.cassandra.utils.Throwables;
import org.apache.cassandra.utils.TimeUUID;
import org.apache.cassandra.utils.WrappedRunnable;
+import org.apache.cassandra.tcm.ClusterMetadata;
+import org.apache.cassandra.tcm.ownership.DataPlacement;
import org.apache.cassandra.utils.concurrent.Future;
import org.apache.cassandra.utils.concurrent.ImmediateFuture;
import org.apache.cassandra.utils.concurrent.Refs;
+
import static java.util.Collections.singleton;
import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory;
import static org.apache.cassandra.concurrent.FutureTask.callable;
@@ -617,13 +621,26 @@ public class CompactionManager implements CompactionManagerMBean, ICompactionMan
assert !cfStore.isIndex();
Keyspace keyspace = cfStore.keyspace;
- // if local ranges is empty, it means no data should remain
- final RangesAtEndpoint replicas = StorageService.instance.getLocalReplicas(keyspace.getName());
- final Set> allRanges = replicas.ranges();
- final Set> transientRanges = replicas.onlyTransient().ranges();
- final Set> fullRanges = replicas.onlyFull().ranges();
+ if (!StorageService.instance.isJoined())
+ {
+ logger.info("Cleanup cannot run before a node has joined the ring");
+ return AllSSTableOpStatus.ABORTED;
+ }
+ if (cfStore.keyspace.getMetadata().params.replication.isMeta())
+ return AllSSTableOpStatus.SUCCESSFUL; // todo - we probably want to be able to cleanup MetaStrategy keyspaces
final boolean hasIndexes = cfStore.indexManager.hasIndexes();
+ // if local ranges is empty, it means no data should remain
+ // we only consider write placements during cleanup as range movements always ensure
+ // overlap between new replicas accepting reads and old replicas accepting writes
+ ClusterMetadata cm = ClusterMetadata.current();
+ DataPlacement placement = cm.placements.get(keyspace.getMetadata().params.replication);
+ InetAddressAndPort local = FBUtilities.getBroadcastAddressAndPort();
+ RangesAtEndpoint localWrites = placement.writes.byEndpoint().get(local);
+ final Set> allRanges = new HashSet<>(localWrites.ranges());
+ final Set> transientRanges = new HashSet<>(localWrites.onlyTransient().ranges());
+ final Set> fullRanges = new HashSet<>(localWrites.onlyFull().ranges());
+
return parallelAllSSTableOperation(cfStore, new OneSSTableOperation()
{
@Override
@@ -665,7 +682,7 @@ public class CompactionManager implements CompactionManagerMBean, ICompactionMan
public void execute(LifecycleTransaction txn) throws IOException
{
CleanupStrategy cleanupStrategy = CleanupStrategy.get(cfStore, allRanges, transientRanges, txn.onlyOne().isRepaired(), FBUtilities.nowInSeconds());
- doCleanupOne(cfStore, txn, cleanupStrategy, replicas.ranges(), hasIndexes);
+ doCleanupOne(cfStore, txn, cleanupStrategy, allRanges, hasIndexes);
}
}, jobs, OperationType.CLEANUP);
}
@@ -1291,7 +1308,7 @@ public class CompactionManager implements CompactionManagerMBean, ICompactionMan
/* Used in tests. */
public void disableAutoCompaction()
{
- for (String ksname : Schema.instance.distributedKeyspaces().names())
+ for (String ksname : Schema.instance.getKeyspaces())
{
for (ColumnFamilyStore cfs : Keyspace.open(ksname).getColumnFamilyStores())
cfs.disableAutoCompaction();
diff --git a/src/java/org/apache/cassandra/db/compaction/CompactionStrategyManager.java b/src/java/org/apache/cassandra/db/compaction/CompactionStrategyManager.java
index dfcd9aee4e..c31a9b2259 100644
--- a/src/java/org/apache/cassandra/db/compaction/CompactionStrategyManager.java
+++ b/src/java/org/apache/cassandra/db/compaction/CompactionStrategyManager.java
@@ -70,6 +70,7 @@ import org.apache.cassandra.io.sstable.metadata.StatsMetadata;
import org.apache.cassandra.io.util.File;
import org.apache.cassandra.notifications.INotification;
import org.apache.cassandra.notifications.INotificationConsumer;
+import org.apache.cassandra.notifications.InitialSSTableAddedNotification;
import org.apache.cassandra.notifications.SSTableAddedNotification;
import org.apache.cassandra.notifications.SSTableDeletingNotification;
import org.apache.cassandra.notifications.SSTableListChangedNotification;
@@ -890,6 +891,11 @@ public class CompactionStrategyManager implements INotificationConsumer
SSTableAddedNotification flushedNotification = (SSTableAddedNotification) notification;
handleFlushNotification(flushedNotification.added);
}
+ else if (notification instanceof InitialSSTableAddedNotification)
+ {
+ InitialSSTableAddedNotification flushedNotification = (InitialSSTableAddedNotification) notification;
+ handleFlushNotification(flushedNotification.added);
+ }
else if (notification instanceof SSTableListChangedNotification)
{
SSTableListChangedNotification listChangedNotification = (SSTableListChangedNotification) notification;
diff --git a/src/java/org/apache/cassandra/db/compaction/ShardManagerNoDisks.java b/src/java/org/apache/cassandra/db/compaction/ShardManagerNoDisks.java
index 6174612a94..0b89111ccd 100644
--- a/src/java/org/apache/cassandra/db/compaction/ShardManagerNoDisks.java
+++ b/src/java/org/apache/cassandra/db/compaction/ShardManagerNoDisks.java
@@ -55,8 +55,8 @@ public class ShardManagerNoDisks implements ShardManager
public boolean isOutOfDate(long ringVersion)
{
- return ringVersion != localRanges.ringVersion &&
- localRanges.ringVersion != ColumnFamilyStore.RING_VERSION_IRRELEVANT;
+ return ringVersion != localRanges.ringVersion.getEpoch() &&
+ !localRanges.ringVersion.is(ColumnFamilyStore.RING_VERSION_IRRELEVANT);
}
@Override
diff --git a/src/java/org/apache/cassandra/db/compaction/UnifiedCompactionStrategy.java b/src/java/org/apache/cassandra/db/compaction/UnifiedCompactionStrategy.java
index 2539c21b8e..0cb1f46cc8 100644
--- a/src/java/org/apache/cassandra/db/compaction/UnifiedCompactionStrategy.java
+++ b/src/java/org/apache/cassandra/db/compaction/UnifiedCompactionStrategy.java
@@ -52,7 +52,7 @@ import org.apache.cassandra.io.sstable.Descriptor;
import org.apache.cassandra.io.sstable.SSTableMultiWriter;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.schema.TableMetadata;
-import org.apache.cassandra.service.StorageService;
+import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.utils.Clock;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.Overlaps;
@@ -295,13 +295,14 @@ public class UnifiedCompactionStrategy extends AbstractCompactionStrategy
private void maybeUpdateShardManager()
{
- if (shardManager != null && !shardManager.isOutOfDate(StorageService.instance.getTokenMetadata().getRingVersion()))
+ // TODO - modify ShardManager::isOutOfDate to take an Epoch
+ if (shardManager != null && !shardManager.isOutOfDate(ClusterMetadata.current().epoch.getEpoch()))
return; // the disk boundaries (and thus the local ranges too) have not changed since the last time we calculated
synchronized (this)
{
// Recheck after entering critical section, another thread may have beaten us to it.
- while (shardManager == null || shardManager.isOutOfDate(StorageService.instance.getTokenMetadata().getRingVersion()))
+ while (shardManager == null || shardManager.isOutOfDate(ClusterMetadata.current().epoch.getEpoch()))
shardManager = ShardManager.create(cfs);
// Note: this can just as well be done without the synchronization (races would be benign, just doing some
// redundant work). For the current usages of this blocking is fine and expected to perform no worse.
diff --git a/src/java/org/apache/cassandra/db/filter/RowFilter.java b/src/java/org/apache/cassandra/db/filter/RowFilter.java
index cefd941622..da9bc61271 100644
--- a/src/java/org/apache/cassandra/db/filter/RowFilter.java
+++ b/src/java/org/apache/cassandra/db/filter/RowFilter.java
@@ -65,6 +65,7 @@ public abstract class RowFilter implements Iterable
private static final Logger logger = LoggerFactory.getLogger(RowFilter.class);
public static final Serializer serializer = new Serializer();
+ public static final RowFilter NONE = new CQLFilter(Collections.emptyList());
protected final List expressions;
diff --git a/src/java/org/apache/cassandra/db/guardrails/Guardrail.java b/src/java/org/apache/cassandra/db/guardrails/Guardrail.java
index 0f6831cbbc..b9d21a412d 100644
--- a/src/java/org/apache/cassandra/db/guardrails/Guardrail.java
+++ b/src/java/org/apache/cassandra/db/guardrails/Guardrail.java
@@ -95,7 +95,7 @@ public abstract class Guardrail
*/
public boolean enabled(@Nullable ClientState state)
{
- return DatabaseDescriptor.isDaemonInitialized() && (state == null || state.isOrdinaryUser());
+ return DatabaseDescriptor.isDaemonInitialized() && (state == null || (state.isOrdinaryUser() && state.applyGuardrails()));
}
protected void warn(String message)
diff --git a/src/java/org/apache/cassandra/db/lifecycle/Tracker.java b/src/java/org/apache/cassandra/db/lifecycle/Tracker.java
index e959c72fa9..5decf00384 100644
--- a/src/java/org/apache/cassandra/db/lifecycle/Tracker.java
+++ b/src/java/org/apache/cassandra/db/lifecycle/Tracker.java
@@ -230,9 +230,15 @@ public class Tracker
addSSTablesInternal(sstables, true, false, true);
}
- public void addInitialSSTablesWithoutUpdatingSize(Iterable sstables)
+ public void addInitialSSTablesWithoutUpdatingSize(Iterable sstables, ColumnFamilyStore cfs)
{
- addSSTablesInternal(sstables, true, false, false);
+ if (!isDummy())
+ {
+ for (SSTableReader reader : sstables)
+ reader.setupOnline();
+ }
+ apply(updateLiveSet(emptySet(), sstables));
+ notifyAdded(sstables, true);
}
public void updateInitialSSTableSize(Iterable sstables)
diff --git a/src/java/org/apache/cassandra/db/memtable/AbstractAllocatorMemtable.java b/src/java/org/apache/cassandra/db/memtable/AbstractAllocatorMemtable.java
index 8526dace39..b431d360ed 100644
--- a/src/java/org/apache/cassandra/db/memtable/AbstractAllocatorMemtable.java
+++ b/src/java/org/apache/cassandra/db/memtable/AbstractAllocatorMemtable.java
@@ -32,6 +32,7 @@ import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.db.ClusteringComparator;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.commitlog.CommitLogPosition;
+import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.schema.TableMetadataRef;
import org.apache.cassandra.utils.Clock;
import org.apache.cassandra.utils.FBUtilities;
@@ -128,13 +129,13 @@ public abstract class AbstractAllocatorMemtable extends AbstractMemtableWithComm
}
@Override
- public boolean shouldSwitch(ColumnFamilyStore.FlushReason reason)
+ public boolean shouldSwitch(ColumnFamilyStore.FlushReason reason, TableMetadata latest)
{
switch (reason)
{
case SCHEMA_CHANGE:
- return initialComparator != metadata().comparator // If the CF comparator has changed, because our partitions reference the old one
- || !initialFactory.equals(metadata().params.memtable.factory()); // If a different type of memtable is requested
+ return initialComparator != latest.comparator // If the CF comparator has changed, because our partitions reference the old one
+ || !initialFactory.equals(latest.params.memtable.factory()); // If a different type of memtable is requested
case OWNED_RANGES_CHANGE:
return false; // by default we don't use the local ranges, thus this has no effect
default:
diff --git a/src/java/org/apache/cassandra/db/memtable/Memtable.java b/src/java/org/apache/cassandra/db/memtable/Memtable.java
index 5ce59f6191..06dfe19976 100644
--- a/src/java/org/apache/cassandra/db/memtable/Memtable.java
+++ b/src/java/org/apache/cassandra/db/memtable/Memtable.java
@@ -399,10 +399,19 @@ public interface Memtable extends Comparable, UnfilteredSource
* - SNAPSHOT will be followed by performSnapshot().
* - STREAMING/REPAIR will be followed by creating a FlushSet for the streamed/repaired ranges. This data will be
* used to create sstables, which will be streamed and then deleted.
+ * The table metadata is supplied explicitly as this might not be the same as the current published metadata for
+ * the table. When applying a schema change, the ColumnFamilyStore instance is reloaded using the new table metadata
+ * before the Schema registry is updated. The memtable needs to examine the new metadata in order to determine
+ * whether the changes warrant a switch.
* This will not be called to perform truncation or drop (in that case the memtable is unconditionally dropped),
* but a flush may nevertheless be requested in that case to prepare a snapshot.
*/
- boolean shouldSwitch(ColumnFamilyStore.FlushReason reason);
+ boolean shouldSwitch(ColumnFamilyStore.FlushReason reason, TableMetadata latest);
+
+ default boolean shouldSwitch(ColumnFamilyStore.FlushReason reason)
+ {
+ return shouldSwitch(reason, metadata());
+ }
/**
* Called when the table's metadata is updated. The memtable's metadata reference now points to the new version.
diff --git a/src/java/org/apache/cassandra/db/memtable/ShardBoundaries.java b/src/java/org/apache/cassandra/db/memtable/ShardBoundaries.java
index 864899f6a4..5412c2cf62 100644
--- a/src/java/org/apache/cassandra/db/memtable/ShardBoundaries.java
+++ b/src/java/org/apache/cassandra/db/memtable/ShardBoundaries.java
@@ -25,6 +25,7 @@ import com.google.common.annotations.VisibleForTesting;
import org.apache.cassandra.db.Keyspace;
import org.apache.cassandra.db.PartitionPosition;
import org.apache.cassandra.dht.Token;
+import org.apache.cassandra.tcm.Epoch;
/**
* Holds boundaries (tokens) used to map a particular token (so partition key) to a shard id.
@@ -43,21 +44,21 @@ public class ShardBoundaries
// - there is only 1 shard configured
// - the default partitioner doesn't support splitting
// - the keyspace is local system keyspace
- public static final ShardBoundaries NONE = new ShardBoundaries(EMPTY_TOKEN_ARRAY, -1);
+ public static final ShardBoundaries NONE = new ShardBoundaries(EMPTY_TOKEN_ARRAY, Epoch.EMPTY);
private final Token[] boundaries;
- public final long ringVersion;
+ public final Epoch epoch;
@VisibleForTesting
- public ShardBoundaries(Token[] boundaries, long ringVersion)
+ public ShardBoundaries(Token[] boundaries, Epoch epoch)
{
this.boundaries = boundaries;
- this.ringVersion = ringVersion;
+ this.epoch = epoch;
}
- public ShardBoundaries(List boundaries, long ringVersion)
+ public ShardBoundaries(List boundaries, Epoch epoch)
{
- this(boundaries.toArray(EMPTY_TOKEN_ARRAY), ringVersion);
+ this(boundaries.toArray(EMPTY_TOKEN_ARRAY), epoch);
}
/**
diff --git a/src/java/org/apache/cassandra/db/partitions/PartitionUpdate.java b/src/java/org/apache/cassandra/db/partitions/PartitionUpdate.java
index 1047fc0a70..9f25468495 100644
--- a/src/java/org/apache/cassandra/db/partitions/PartitionUpdate.java
+++ b/src/java/org/apache/cassandra/db/partitions/PartitionUpdate.java
@@ -35,11 +35,17 @@ import org.slf4j.LoggerFactory;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.filter.ColumnFilter;
import org.apache.cassandra.db.rows.*;
+import org.apache.cassandra.exceptions.CoordinatorBehindException;
+import org.apache.cassandra.exceptions.UnknownTableException;
import org.apache.cassandra.index.IndexRegistry;
import org.apache.cassandra.io.util.DataInputBuffer;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputBuffer;
import org.apache.cassandra.io.util.DataOutputPlus;
+import org.apache.cassandra.metrics.TCMMetrics;
+import org.apache.cassandra.tcm.ClusterMetadata;
+import org.apache.cassandra.tcm.Epoch;
+import org.apache.cassandra.net.MessagingService;
import org.apache.cassandra.schema.ColumnMetadata;
import org.apache.cassandra.schema.Schema;
import org.apache.cassandra.schema.TableId;
@@ -73,10 +79,12 @@ public class PartitionUpdate extends AbstractBTreePartition
private final BTreePartitionData holder;
private final DeletionInfo deletionInfo;
private final TableMetadata metadata;
+ public final Epoch serializedAtEpoch;
private final boolean canHaveShadowedData;
private PartitionUpdate(TableMetadata metadata,
+ Epoch serializedAtEpoch,
DecoratedKey key,
BTreePartitionData holder,
MutableDeletionInfo deletionInfo,
@@ -87,6 +95,7 @@ public class PartitionUpdate extends AbstractBTreePartition
this.holder = holder;
this.deletionInfo = deletionInfo;
this.canHaveShadowedData = canHaveShadowedData;
+ this.serializedAtEpoch = serializedAtEpoch;
}
/**
@@ -101,7 +110,7 @@ public class PartitionUpdate extends AbstractBTreePartition
{
MutableDeletionInfo deletionInfo = MutableDeletionInfo.live();
BTreePartitionData holder = new BTreePartitionData(RegularAndStaticColumns.NONE, BTree.empty(), deletionInfo, Rows.EMPTY_STATIC_ROW, EncodingStats.NO_STATS);
- return new PartitionUpdate(metadata, key, holder, deletionInfo, false);
+ return new PartitionUpdate(metadata, metadata.epoch, key, holder, deletionInfo, false);
}
/**
@@ -118,7 +127,7 @@ public class PartitionUpdate extends AbstractBTreePartition
{
MutableDeletionInfo deletionInfo = new MutableDeletionInfo(timestamp, nowInSec);
BTreePartitionData holder = new BTreePartitionData(RegularAndStaticColumns.NONE, BTree.empty(), deletionInfo, Rows.EMPTY_STATIC_ROW, EncodingStats.NO_STATS);
- return new PartitionUpdate(metadata, key, holder, deletionInfo, false);
+ return new PartitionUpdate(metadata, metadata.epoch, key, holder, deletionInfo, false);
}
/**
@@ -144,7 +153,7 @@ public class PartitionUpdate extends AbstractBTreePartition
staticRow == null ? Rows.EMPTY_STATIC_ROW : staticRow,
EncodingStats.NO_STATS
);
- return new PartitionUpdate(metadata, key, holder, deletionInfo, false);
+ return new PartitionUpdate(metadata, metadata.epoch, key, holder, deletionInfo, false);
}
/**
@@ -191,7 +200,7 @@ public class PartitionUpdate extends AbstractBTreePartition
iterator = UnfilteredRowIterators.withOnlyQueriedData(iterator, filter);
BTreePartitionData holder = build(iterator, 16);
MutableDeletionInfo deletionInfo = (MutableDeletionInfo) holder.deletionInfo;
- return new PartitionUpdate(iterator.metadata(), iterator.partitionKey(), holder, deletionInfo, false);
+ return new PartitionUpdate(iterator.metadata(), iterator.metadata().epoch, iterator.partitionKey(), holder, deletionInfo, false);
}
/**
@@ -210,7 +219,7 @@ public class PartitionUpdate extends AbstractBTreePartition
iterator = RowIterators.withOnlyQueriedData(iterator, filter);
MutableDeletionInfo deletionInfo = MutableDeletionInfo.live();
BTreePartitionData holder = build(iterator, deletionInfo, true);
- return new PartitionUpdate(iterator.metadata(), iterator.partitionKey(), holder, deletionInfo, false);
+ return new PartitionUpdate(iterator.metadata(), iterator.metadata().epoch, iterator.partitionKey(), holder, deletionInfo, false);
}
@@ -223,7 +232,7 @@ public class PartitionUpdate extends AbstractBTreePartition
columnSet.add(column.column());
RegularAndStaticColumns columns = RegularAndStaticColumns.builder().addAll(columnSet).build();
- return new PartitionUpdate(this.metadata, this.partitionKey, this.holder.withColumns(columns), this.deletionInfo.mutableCopy(), false);
+ return new PartitionUpdate(this.metadata, this.metadata.epoch, this.partitionKey, this.holder.withColumns(columns), this.deletionInfo.mutableCopy(), false);
}
@@ -465,7 +474,7 @@ public class PartitionUpdate extends AbstractBTreePartition
/**
*
- * @return the estimated number of rows affected by this mutation
+ * @return the estimated number of rows affected by this mutation
*/
public int affectedRowCount()
{
@@ -506,7 +515,7 @@ public class PartitionUpdate extends AbstractBTreePartition
for (Row row : this)
{
if (row.deletion().isLive())
- // If the row is live, this will include simple tombstones as well as cells w/ actual data.
+ // If the row is live, this will include simple tombstones as well as cells w/ actual data.
count += row.columnCount();
else
// We have a row deletion, so account for the columns that might be deleted.
@@ -539,7 +548,14 @@ public class PartitionUpdate extends AbstractBTreePartition
*/
public static SimpleBuilder simpleBuilder(TableMetadata metadata, Object... partitionKeyValues)
{
- return new SimpleBuilders.PartitionUpdateBuilder(metadata, partitionKeyValues);
+ // Here we dereference the current version of the supplied TableMetadata. The reason for this is that in some
+ // places we still reference static TableMetadata instances.
+ // For instance, TraceKeyspace contains Sessions & Events static members which are created at startup when the
+ // current epoch is Epoch.EMPTY. These are used to construct mutations when tracing is enabled and when the
+ // mutations are serialised and sent between replica & coordinator the epoch comparisons in PartitionUpdate
+ // deserializer trigger an IncompatibleSchemaException.
+ // TODO ultimately remove the use of static TableMetadata instances in System/Tracing/Auth keyspaces.
+ return new SimpleBuilders.PartitionUpdateBuilder(metadata.ref.get(), partitionKeyValues);
}
public void validateIndexedColumns()
@@ -554,7 +570,7 @@ public class PartitionUpdate extends AbstractBTreePartition
MutableDeletionInfo deletionInfo,
boolean canHaveShadowedData)
{
- return new PartitionUpdate(metadata, key, holder, deletionInfo, canHaveShadowedData);
+ return new PartitionUpdate(metadata, metadata.epoch, key, holder, deletionInfo, canHaveShadowedData);
}
/**
@@ -713,24 +729,45 @@ public class PartitionUpdate extends AbstractBTreePartition
assert !iter.isReverseOrder();
update.metadata.id.serialize(out);
+ if (version >= MessagingService.VERSION_50)
+ Epoch.serializer.serialize(update.metadata.epoch != null ? update.metadata.epoch : Epoch.EMPTY, out);
UnfilteredRowIteratorSerializer.serializer.serialize(iter, null, out, version, update.rowCount());
}
}
public PartitionUpdate deserialize(DataInputPlus in, int version, DeserializationHelper.Flag flag) throws IOException
{
- TableMetadata metadata = Schema.instance.getExistingTableMetadata(TableId.deserialize(in));
- UnfilteredRowIteratorSerializer.Header header = UnfilteredRowIteratorSerializer.serializer.deserializeHeader(metadata, null, in, version, flag);
+ TableId tableId = TableId.deserialize(in);
+ Epoch remoteVersion = null;
+ if (version >= MessagingService.VERSION_50)
+ remoteVersion = Epoch.serializer.deserialize(in);
+ TableMetadata tableMetadata;
+ try
+ {
+ tableMetadata = Schema.instance.getExistingTableMetadata(tableId);
+ }
+ catch (UnknownTableException e)
+ {
+ ClusterMetadata metadata = ClusterMetadata.current();
+ Epoch localCurrentEpoch = metadata.epoch;
+ if (remoteVersion != null && localCurrentEpoch.isAfter(remoteVersion))
+ {
+ TCMMetrics.instance.coordinatorBehindSchema.mark();
+ throw new CoordinatorBehindException(e.getMessage(), e);
+ }
+ throw e;
+ }
+ UnfilteredRowIteratorSerializer.Header header = UnfilteredRowIteratorSerializer.serializer.deserializeHeader(tableMetadata, null, in, version, flag);
if (header.isEmpty)
- return emptyUpdate(metadata, header.key);
+ return emptyUpdate(tableMetadata, header.key);
assert !header.isReversed;
assert header.rowEstimate >= 0;
- MutableDeletionInfo.Builder deletionBuilder = MutableDeletionInfo.builder(header.partitionDeletion, metadata.comparator, false);
+ MutableDeletionInfo.Builder deletionBuilder = MutableDeletionInfo.builder(header.partitionDeletion, tableMetadata.comparator, false);
Object[] rows;
try (BTree.FastBuilder builder = BTree.fastBuilder();
- UnfilteredRowIterator partition = UnfilteredRowIteratorSerializer.serializer.deserialize(in, version, metadata, flag, header))
+ UnfilteredRowIterator partition = UnfilteredRowIteratorSerializer.serializer.deserialize(in, version, tableMetadata, flag, header))
{
while (partition.hasNext())
{
@@ -744,19 +781,27 @@ public class PartitionUpdate extends AbstractBTreePartition
}
MutableDeletionInfo deletionInfo = deletionBuilder.build();
- return new PartitionUpdate(metadata,
+ return new PartitionUpdate(tableMetadata,
+ remoteVersion,
header.key,
new BTreePartitionData(header.sHeader.columns(), rows, deletionInfo, header.staticRow, header.sHeader.stats()),
deletionInfo,
false);
}
- public static boolean isEmpty(ByteBuffer in, DeserializationHelper.Flag flag, DecoratedKey key) throws IOException
+ public static boolean isEmpty(ByteBuffer in, DeserializationHelper.Flag flag, DecoratedKey key, int version) throws IOException
{
int position = in.position();
position += 16; // CFMetaData.serializer.deserialize(in, version);
if (position >= in.limit())
throw new EOFException();
+
+ if (version >= MessagingService.VERSION_50)
+ {
+ long epoch = VIntCoding.getUnsignedVInt(in, position);
+ position += VIntCoding.computeVIntSize(epoch);
+ }
+
// DecoratedKey key = metadata.decorateKey(ByteBufferUtil.readWithVIntLength(in));
int keyLength = VIntCoding.getUnsignedVInt32(in, position);
position += keyLength + VIntCoding.computeUnsignedVIntSize(keyLength);
@@ -771,6 +816,7 @@ public class PartitionUpdate extends AbstractBTreePartition
try (UnfilteredRowIterator iter = update.unfilteredIterator())
{
return update.metadata.id.serializedSize()
+ + (version >= MessagingService.VERSION_50 ? Epoch.serializer.serializedSize(update.metadata.epoch) : 0)
+ UnfilteredRowIteratorSerializer.serializer.serializedSize(iter, null, version, update.rowCount());
}
}
@@ -964,6 +1010,7 @@ public class PartitionUpdate extends AbstractBTreePartition
isBuilt = true;
return new PartitionUpdate(metadata,
+ metadata.epoch,
partitionKey(),
new BTreePartitionData(columns,
merged,
diff --git a/src/java/org/apache/cassandra/db/streaming/CassandraCompressedStreamReader.java b/src/java/org/apache/cassandra/db/streaming/CassandraCompressedStreamReader.java
index 0e0fcaa960..db5fb25373 100644
--- a/src/java/org/apache/cassandra/db/streaming/CassandraCompressedStreamReader.java
+++ b/src/java/org/apache/cassandra/db/streaming/CassandraCompressedStreamReader.java
@@ -74,8 +74,8 @@ public class CassandraCompressedStreamReader extends CassandraStreamReader
try (CompressedInputStream cis = new CompressedInputStream(inputPlus, compressionInfo, ChecksumType.CRC32, cfs::getCrcCheckChance))
{
TrackedDataInputPlus in = new TrackedDataInputPlus(cis);
- deserializer = new StreamDeserializer(cfs.metadata(), in, inputVersion, getHeader(cfs.metadata()));
writer = createWriter(cfs, totalSize, repairedAt, pendingRepair, inputVersion.format);
+ deserializer = new StreamDeserializer(cfs.metadata(), in, inputVersion, getHeader(cfs.metadata()), session, writer);
String filename = writer.getFilename();
String sectionName = filename + '-' + fileSeqNum;
int sectionIdx = 0;
diff --git a/src/java/org/apache/cassandra/db/streaming/CassandraStreamReader.java b/src/java/org/apache/cassandra/db/streaming/CassandraStreamReader.java
index 9819d4cf27..af4b4dbc18 100644
--- a/src/java/org/apache/cassandra/db/streaming/CassandraStreamReader.java
+++ b/src/java/org/apache/cassandra/db/streaming/CassandraStreamReader.java
@@ -20,6 +20,9 @@ package org.apache.cassandra.db.streaming;
import java.io.IOError;
import java.io.IOException;
import java.util.Collection;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.concurrent.TimeUnit;
import com.google.common.base.Preconditions;
import com.google.common.collect.UnmodifiableIterator;
@@ -38,6 +41,8 @@ import org.apache.cassandra.db.rows.EncodingStats;
import org.apache.cassandra.db.rows.Row;
import org.apache.cassandra.db.rows.Unfiltered;
import org.apache.cassandra.db.rows.UnfilteredRowIterator;
+import org.apache.cassandra.dht.Range;
+import org.apache.cassandra.dht.Token;
import org.apache.cassandra.exceptions.UnknownColumnException;
import org.apache.cassandra.io.sstable.RangeAwareSSTableWriter;
import org.apache.cassandra.io.sstable.SSTableMultiWriter;
@@ -47,15 +52,19 @@ import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.io.sstable.format.Version;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.TrackedDataInputPlus;
+import org.apache.cassandra.metrics.StorageMetrics;
import org.apache.cassandra.schema.TableId;
import org.apache.cassandra.schema.TableMetadata;
+import org.apache.cassandra.service.StorageService;
import org.apache.cassandra.streaming.ProgressInfo;
+import org.apache.cassandra.streaming.StreamReceivedOutOfTokenRangeException;
import org.apache.cassandra.streaming.StreamReceiver;
import org.apache.cassandra.streaming.StreamSession;
import org.apache.cassandra.streaming.compress.StreamCompressionInputStream;
import org.apache.cassandra.streaming.messages.StreamMessageHeader;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.FBUtilities;
+import org.apache.cassandra.utils.NoSpamLogger;
import org.apache.cassandra.utils.TimeUUID;
import static org.apache.cassandra.net.MessagingService.current_version;
@@ -66,6 +75,7 @@ import static org.apache.cassandra.net.MessagingService.current_version;
public class CassandraStreamReader implements IStreamReader
{
private static final Logger logger = LoggerFactory.getLogger(CassandraStreamReader.class);
+ private static final String logMessageTemplate = "[Stream #{}] Received streamed SSTable {} from {} containing key outside valid ranges {}";
protected final TableId tableId;
protected final long estimatedKeys;
protected final Collection sections;
@@ -121,8 +131,8 @@ public class CassandraStreamReader implements IStreamReader
try (StreamCompressionInputStream streamCompressionInputStream = new StreamCompressionInputStream(inputPlus, current_version))
{
TrackedDataInputPlus in = new TrackedDataInputPlus(streamCompressionInputStream);
- deserializer = new StreamDeserializer(cfs.metadata(), in, inputVersion, getHeader(cfs.metadata()));
writer = createWriter(cfs, totalSize, repairedAt, pendingRepair, inputVersion.format);
+ deserializer = getDeserializer(cfs.metadata(), in, inputVersion, session, writer);
String sequenceName = writer.getFilename() + '-' + fileSeqNum;
long lastBytesRead = 0;
while (in.getBytesRead() < totalSize)
@@ -149,6 +159,15 @@ public class CassandraStreamReader implements IStreamReader
}
}
+ protected StreamDeserializer getDeserializer(TableMetadata metadata,
+ TrackedDataInputPlus in,
+ Version inputVersion,
+ StreamSession session,
+ SSTableMultiWriter writer) throws IOException
+ {
+ return new StreamDeserializer(metadata, in, inputVersion, getHeader(metadata), session, writer);
+ }
+
protected SerializationHeader getHeader(TableMetadata metadata) throws UnknownColumnException
{
return header != null? header.toHeader(metadata) : null; //pre-3.0 sstable have no SerializationHeader
@@ -188,29 +207,52 @@ public class CassandraStreamReader implements IStreamReader
private final SerializationHeader header;
private final DeserializationHelper helper;
- private DecoratedKey key;
- private DeletionTime partitionLevelDeletion;
- private SSTableSimpleIterator iterator;
- private Row staticRow;
+ private final List> ownedRanges;
+ private final StreamSession session;
+ private final SSTableMultiWriter writer;
+
+ private int lastCheckedRangeIndex;
+ protected DecoratedKey key;
+ protected DeletionTime partitionLevelDeletion;
+ protected SSTableSimpleIterator iterator;
+ protected Row staticRow;
private IOException exception;
private Version version;
- public StreamDeserializer(TableMetadata metadata, DataInputPlus in, Version version, SerializationHeader header) throws IOException
+ public StreamDeserializer(TableMetadata metadata, DataInputPlus in, Version version, SerializationHeader header, StreamSession session, SSTableMultiWriter writer) throws IOException
{
this.metadata = metadata;
this.in = in;
this.helper = new DeserializationHelper(metadata, version.correspondingMessagingVersion(), DeserializationHelper.Flag.PRESERVE_SIZE);
this.header = header;
this.version = version;
+ ownedRanges = Range.normalize(StorageService.instance.getLocalAndPendingRanges(metadata.keyspace));
+ lastCheckedRangeIndex = 0;
+
+ this.session = session;
+ this.writer = writer;
}
- public StreamDeserializer newPartition() throws IOException
+ public UnfilteredRowIterator newPartition() throws IOException
+ {
+ readKey();
+ readPartition();
+ return this;
+ }
+
+ protected void readKey() throws IOException
{
key = metadata.partitioner.decorateKey(ByteBufferUtil.readWithShortLength(in));
+ lastCheckedRangeIndex = verifyKeyInOwnedRanges(key,
+ ownedRanges,
+ lastCheckedRangeIndex);
+ }
+
+ protected void readPartition() throws IOException
+ {
partitionLevelDeletion = DeletionTime.getSerializer(version).deserialize(in);
iterator = SSTableSimpleIterator.create(metadata, in, header, helper, partitionLevelDeletion);
staticRow = iterator.readStaticRow();
- return this;
}
public TableMetadata metadata()
@@ -291,5 +333,27 @@ public class CassandraStreamReader implements IStreamReader
public void close()
{
}
+
+ private int verifyKeyInOwnedRanges(final DecoratedKey key,
+ List> ownedRanges,
+ int lastCheckedRangeIndex)
+ {
+ if (lastCheckedRangeIndex < ownedRanges.size())
+ {
+ ListIterator> rangesToCheck = ownedRanges.listIterator(lastCheckedRangeIndex);
+ while (rangesToCheck.hasNext())
+ {
+ Range range = rangesToCheck.next();
+ if (range.contains(key.getToken()))
+ return lastCheckedRangeIndex;
+
+ lastCheckedRangeIndex++;
+ }
+ }
+
+ StorageMetrics.totalOpsForInvalidToken.inc();
+ NoSpamLogger.log(logger, NoSpamLogger.Level.WARN, 1, TimeUnit.SECONDS, logMessageTemplate, session.planId(), writer.getFilename(), session.peer, ownedRanges);
+ throw new StreamReceivedOutOfTokenRangeException(ownedRanges, key, writer.getFilename());
+ }
}
}
diff --git a/src/java/org/apache/cassandra/db/view/TableViews.java b/src/java/org/apache/cassandra/db/view/TableViews.java
index 366d4278e0..f717e3ec85 100644
--- a/src/java/org/apache/cassandra/db/view/TableViews.java
+++ b/src/java/org/apache/cassandra/db/view/TableViews.java
@@ -17,7 +17,14 @@
*/
package org.apache.cassandra.db.view;
-import java.util.*;
+import java.util.AbstractCollection;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.NavigableSet;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
@@ -27,14 +34,36 @@ import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import com.google.common.collect.PeekingIterator;
-import org.apache.cassandra.db.*;
+import org.apache.cassandra.db.Clustering;
+import org.apache.cassandra.db.ColumnFamilyStore;
+import org.apache.cassandra.db.DecoratedKey;
+import org.apache.cassandra.db.DeletionInfo;
+import org.apache.cassandra.db.DeletionTime;
+import org.apache.cassandra.db.Keyspace;
+import org.apache.cassandra.db.Mutation;
+import org.apache.cassandra.db.RangeTombstone;
+import org.apache.cassandra.db.ReadExecutionController;
+import org.apache.cassandra.db.ReadQuery;
+import org.apache.cassandra.db.SinglePartitionReadCommand;
+import org.apache.cassandra.db.Slice;
+import org.apache.cassandra.db.Slices;
+import org.apache.cassandra.db.SystemKeyspace;
import org.apache.cassandra.db.commitlog.CommitLogPosition;
-import org.apache.cassandra.db.filter.*;
-import org.apache.cassandra.db.partitions.*;
-import org.apache.cassandra.db.rows.*;
+import org.apache.cassandra.db.filter.ClusteringIndexFilter;
+import org.apache.cassandra.db.filter.ClusteringIndexNamesFilter;
+import org.apache.cassandra.db.filter.ClusteringIndexSliceFilter;
+import org.apache.cassandra.db.filter.ColumnFilter;
+import org.apache.cassandra.db.filter.DataLimits;
+import org.apache.cassandra.db.filter.RowFilter;
+import org.apache.cassandra.db.partitions.PartitionUpdate;
+import org.apache.cassandra.db.partitions.UnfilteredPartitionIterators;
+import org.apache.cassandra.db.rows.BTreeRow;
+import org.apache.cassandra.db.rows.RangeTombstoneMarker;
+import org.apache.cassandra.db.rows.Row;
+import org.apache.cassandra.db.rows.Rows;
+import org.apache.cassandra.db.rows.Unfiltered;
+import org.apache.cassandra.db.rows.UnfilteredRowIterator;
import org.apache.cassandra.dht.Token;
-import org.apache.cassandra.schema.Schema;
-import org.apache.cassandra.schema.TableId;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.schema.TableMetadataRef;
import org.apache.cassandra.service.StorageProxy;
@@ -57,9 +86,9 @@ public class TableViews extends AbstractCollection
// list is the best option.
private final List views = new CopyOnWriteArrayList();
- public TableViews(TableId id)
+ public TableViews(TableMetadata tableMetadata)
{
- baseTableMetadata = Schema.instance.getTableMetadataRef(id);
+ baseTableMetadata = tableMetadata.ref;
}
public boolean hasViews()
diff --git a/src/java/org/apache/cassandra/db/view/ViewBuilder.java b/src/java/org/apache/cassandra/db/view/ViewBuilder.java
index daedf48f29..c59cfec855 100644
--- a/src/java/org/apache/cassandra/db/view/ViewBuilder.java
+++ b/src/java/org/apache/cassandra/db/view/ViewBuilder.java
@@ -43,6 +43,7 @@ import org.apache.cassandra.locator.RangesAtEndpoint;
import org.apache.cassandra.locator.Replicas;
import org.apache.cassandra.schema.SystemDistributedKeyspace;
import org.apache.cassandra.service.StorageService;
+import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.Pair;
import org.apache.cassandra.utils.concurrent.Future;
@@ -66,7 +67,7 @@ class ViewBuilder
private final ColumnFamilyStore baseCfs;
private final View view;
private final String ksName;
- private final UUID localHostId = SystemKeyspace.getOrInitializeLocalHostId();
+ private final UUID localHostId;
private final Set> builtRanges = Sets.newConcurrentHashSet();
private final Map, Pair> pendingRanges = Maps.newConcurrentMap();
private final Set tasks = Sets.newConcurrentHashSet();
@@ -79,6 +80,7 @@ class ViewBuilder
this.baseCfs = baseCfs;
this.view = view;
ksName = baseCfs.metadata.keyspace;
+ this.localHostId = ClusterMetadata.current().myNodeId().toUUID();
}
public void start()
diff --git a/src/java/org/apache/cassandra/db/view/ViewBuilderTask.java b/src/java/org/apache/cassandra/db/view/ViewBuilderTask.java
index 7bda2fddac..0b53542c7d 100644
--- a/src/java/org/apache/cassandra/db/view/ViewBuilderTask.java
+++ b/src/java/org/apache/cassandra/db/view/ViewBuilderTask.java
@@ -110,7 +110,7 @@ public class ViewBuilderTask extends CompactionInfo.Holder implements Callable> mutations = baseCfs.keyspace.viewManager
- .forTable(baseCfs.metadata.id)
+ .forTable(baseCfs.metadata.get())
.generateViewUpdates(Collections.singleton(view), data, empty, nowInSec, true);
AtomicLong noBase = new AtomicLong(Long.MAX_VALUE);
diff --git a/src/java/org/apache/cassandra/db/view/ViewManager.java b/src/java/org/apache/cassandra/db/view/ViewManager.java
index 106a15fdd9..cf6b916e05 100644
--- a/src/java/org/apache/cassandra/db/view/ViewManager.java
+++ b/src/java/org/apache/cassandra/db/view/ViewManager.java
@@ -28,12 +28,9 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.config.DatabaseDescriptor;
-import org.apache.cassandra.schema.TableId;
-import org.apache.cassandra.schema.ViewMetadata;
+import org.apache.cassandra.schema.*;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.partitions.*;
-import org.apache.cassandra.schema.SystemDistributedKeyspace;
-import org.apache.cassandra.schema.Views;
import org.apache.cassandra.service.StorageService;
import static org.apache.cassandra.config.CassandraRelevantProperties.MV_ENABLE_COORDINATOR_BATCHLOG;
@@ -84,7 +81,7 @@ public class ViewManager
if (coordinatorBatchlog && keyspace.getReplicationStrategy().getReplicationFactor().allReplicas == 1)
continue;
- if (!forTable(update.metadata().id).updatedViews(update).isEmpty())
+ if (!forTable(update.metadata()).updatedViews(update).isEmpty())
return true;
}
}
@@ -97,9 +94,9 @@ public class ViewManager
return viewsByName.values();
}
- public void reload(boolean buildAllViews)
+ public void reload(KeyspaceMetadata keyspaceMetadata)
{
- Views views = keyspace.getMetadata().views;
+ Views views = keyspaceMetadata.views;
Map newViewsByName = Maps.newHashMapWithExpectedSize(views.size());
for (ViewMetadata definition : views)
{
@@ -111,10 +108,16 @@ public class ViewManager
if (!viewsByName.containsKey(entry.getKey()))
addView(entry.getValue());
}
+ }
- if (!buildAllViews)
- return;
-
+ public void buildViews()
+ {
+ Views views = keyspace.getMetadata().views;
+ Map newViewsByName = Maps.newHashMapWithExpectedSize(views.size());
+ for (ViewMetadata definition : views)
+ {
+ newViewsByName.put(definition.name(), definition);
+ }
// Building views involves updating view build status in the system_distributed
// keyspace and therefore it requires ring information. This check prevents builds
// being submitted when Keyspaces are initialized during CassandraDaemon::setup as
@@ -149,7 +152,7 @@ public class ViewManager
}
View view = new View(definition, keyspace.getColumnFamilyStore(definition.baseTableId));
- forTable(view.getDefinition().baseTableId).add(view);
+ forTable(keyspace.getMetadata().tables.getNullable(view.getDefinition().baseTableId)).add(view);
viewsByName.put(definition.name(), view);
}
@@ -166,7 +169,7 @@ public class ViewManager
return;
view.stopBuild();
- forTable(view.getDefinition().baseTableId).removeByName(name);
+ forTable(view.getDefinition().baseTableMetadata()).removeByName(name);
SystemKeyspace.setViewRemoved(keyspace.getName(), view.name);
SystemDistributedKeyspace.setViewRemoved(keyspace.getName(), view.name);
}
@@ -182,13 +185,13 @@ public class ViewManager
view.build();
}
- public TableViews forTable(TableId id)
+ public TableViews forTable(TableMetadata metadata)
{
- TableViews views = viewsByBaseTable.get(id);
+ TableViews views = viewsByBaseTable.get(metadata.id);
if (views == null)
{
- views = new TableViews(id);
- TableViews previous = viewsByBaseTable.putIfAbsent(id, views);
+ views = new TableViews(metadata);
+ TableViews previous = viewsByBaseTable.putIfAbsent(metadata.id, views);
if (previous != null)
views = previous;
}
diff --git a/src/java/org/apache/cassandra/db/view/ViewUtils.java b/src/java/org/apache/cassandra/db/view/ViewUtils.java
index 55a462c31b..b41150b7a5 100644
--- a/src/java/org/apache/cassandra/db/view/ViewUtils.java
+++ b/src/java/org/apache/cassandra/db/view/ViewUtils.java
@@ -24,10 +24,11 @@ import java.util.function.Predicate;
import com.google.common.collect.Iterables;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.dht.Token;
-import org.apache.cassandra.locator.AbstractReplicationStrategy;
import org.apache.cassandra.locator.EndpointsForToken;
import org.apache.cassandra.locator.NetworkTopologyStrategy;
import org.apache.cassandra.locator.Replica;
+import org.apache.cassandra.schema.KeyspaceMetadata;
+import org.apache.cassandra.tcm.ClusterMetadata;
public final class ViewUtils
{
@@ -57,11 +58,13 @@ public final class ViewUtils
*
* @return Optional.empty() if this method is called using a base token which does not belong to this replica
*/
- public static Optional getViewNaturalEndpoint(AbstractReplicationStrategy replicationStrategy, Token baseToken, Token viewToken)
+ public static Optional getViewNaturalEndpoint(ClusterMetadata metadata, String keyspace, Token baseToken, Token viewToken)
{
String localDataCenter = DatabaseDescriptor.getEndpointSnitch().getLocalDatacenter();
- EndpointsForToken naturalBaseReplicas = replicationStrategy.getNaturalReplicasForToken(baseToken);
- EndpointsForToken naturalViewReplicas = replicationStrategy.getNaturalReplicasForToken(viewToken);
+ KeyspaceMetadata keyspaceMetadata = metadata.schema.getKeyspaces().getNullable(keyspace);
+
+ EndpointsForToken naturalBaseReplicas = metadata.placements.get(keyspaceMetadata.params.replication).reads.forToken(baseToken).get();
+ EndpointsForToken naturalViewReplicas = metadata.placements.get(keyspaceMetadata.params.replication).reads.forToken(viewToken).get();
Optional localReplica = Iterables.tryFind(naturalViewReplicas, Replica::isSelf).toJavaUtil();
if (localReplica.isPresent())
@@ -69,7 +72,7 @@ public final class ViewUtils
// We only select replicas from our own DC
// TODO: this is poor encapsulation, leaking implementation details of replication strategy
- Predicate isLocalDC = r -> !(replicationStrategy instanceof NetworkTopologyStrategy)
+ Predicate isLocalDC = r -> !(keyspaceMetadata.replicationStrategy instanceof NetworkTopologyStrategy)
|| DatabaseDescriptor.getEndpointSnitch().getDatacenter(r).equals(localDataCenter);
// We have to remove any endpoint which is shared between the base and the view, as it will select itself
@@ -84,7 +87,9 @@ public final class ViewUtils
// The replication strategy will be the same for the base and the view, as they must belong to the same keyspace.
// Since the same replication strategy is used, the same placement should be used and we should get the same
// number of replicas for all of the tokens in the ring.
- assert baseReplicas.size() == viewReplicas.size() : "Replication strategy should have the same number of endpoints for the base and the view";
+ assert baseReplicas.size() == viewReplicas.size() :
+ String.format("Replication strategy should have the same number of endpoints for the base (%d) and the view (%d)",
+ baseReplicas.size(), viewReplicas.size());
int baseIdx = -1;
for (int i=0; i(cm.tokenMap.tokens(peer).stream().map((token) -> token.getToken().getTokenValue().toString()).collect(Collectors.toList())));
+ //.column(TRUNCATED_AT, status(cm)); // todo?
+
+ return result;
+ }
+
+ private static String status(ClusterMetadata cm)
+ {
+ if (StorageService.instance.isDraining())
+ return StorageService.Mode.DRAINING.toString();
+ if (StorageService.instance.isDrained())
+ return StorageService.Mode.DRAINED.toString();
+ return cm.directory.peerState(getBroadcastAddressAndPort()).toString();
+ }
+}
\ No newline at end of file
diff --git a/src/java/org/apache/cassandra/db/virtual/PeersTable.java b/src/java/org/apache/cassandra/db/virtual/PeersTable.java
new file mode 100644
index 0000000000..5b011de604
--- /dev/null
+++ b/src/java/org/apache/cassandra/db/virtual/PeersTable.java
@@ -0,0 +1,200 @@
+/*
+ * 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.db.virtual;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.cassandra.cql3.QueryProcessor;
+import org.apache.cassandra.db.SystemKeyspace;
+import org.apache.cassandra.db.marshal.InetAddressType;
+import org.apache.cassandra.db.marshal.Int32Type;
+import org.apache.cassandra.db.marshal.SetType;
+import org.apache.cassandra.db.marshal.UTF8Type;
+import org.apache.cassandra.db.marshal.UUIDType;
+import org.apache.cassandra.dht.LocalPartitioner;
+import org.apache.cassandra.locator.InetAddressAndPort;
+import org.apache.cassandra.schema.Schema;
+import org.apache.cassandra.schema.TableMetadata;
+import org.apache.cassandra.tcm.ClusterMetadata;
+import org.apache.cassandra.tcm.membership.Location;
+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.utils.FBUtilities;
+
+import static org.apache.cassandra.db.SystemKeyspace.LEGACY_PEERS;
+import static org.apache.cassandra.db.SystemKeyspace.PEERS_V2;
+import static org.apache.cassandra.schema.SchemaConstants.SYSTEM_KEYSPACE_NAME;
+
+public class PeersTable extends AbstractVirtualTable
+{
+
+ public static String PEER = "peer";
+ public static String PEER_PORT = "peer_port";
+ public static String DATA_CENTER = "data_center";
+ public static String HOST_ID = "host_id";
+ public static String PREFERRED_IP = "preferred_ip";
+ public static String PREFERRED_PORT = "preferred_port";
+ public static String RACK = "rack";
+ public static String RELEASE_VERSION = "release_version";
+ public static String NATIVE_ADDRESS = "native_address";
+ public static String NATIVE_PORT = "native_port";
+ public static String SCHEMA_VERSION = "schema_version";
+ public static String TOKENS = "tokens";
+ public static String STATE = "state";
+
+ public PeersTable(String keyspace)
+ {
+ super(TableMetadata.builder(keyspace, "peers")
+ .comment("Peers")
+ .kind(TableMetadata.Kind.VIRTUAL)
+ .partitioner(new LocalPartitioner(InetAddressType.instance))
+ .addPartitionKeyColumn(PEER, InetAddressType.instance)
+ .addClusteringColumn(PEER_PORT, Int32Type.instance)
+ .addRegularColumn(DATA_CENTER, UTF8Type.instance)
+ .addRegularColumn(RACK, UTF8Type.instance)
+ .addRegularColumn(HOST_ID, UUIDType.instance)
+ .addRegularColumn(PREFERRED_IP, InetAddressType.instance)
+ .addRegularColumn(PREFERRED_PORT, Int32Type.instance)
+ .addRegularColumn(NATIVE_ADDRESS, InetAddressType.instance)
+ .addRegularColumn(NATIVE_PORT, Int32Type.instance)
+ .addRegularColumn(RELEASE_VERSION, UTF8Type.instance)
+ .addRegularColumn(SCHEMA_VERSION, UUIDType.instance)
+ .addRegularColumn(STATE, UTF8Type.instance)
+ .addRegularColumn(TOKENS, SetType.getInstance(UTF8Type.instance, false))
+ .build());
+ }
+
+ public DataSet data()
+ {
+ SimpleDataSet result = new SimpleDataSet(metadata());
+
+ ClusterMetadata metadata = ClusterMetadata.current();
+ for (InetAddressAndPort addr : metadata.directory.allJoinedEndpoints())
+ {
+ NodeId peer = metadata.directory.peerId(addr);
+
+ NodeAddresses addresses = metadata.directory.getNodeAddresses(peer);
+ result.row(addr.getAddress(), addr.getPort())
+ .column(DATA_CENTER, metadata.directory.location(peer).datacenter)
+ .column(RACK, metadata.directory.location(peer).rack)
+ .column(HOST_ID, peer.toUUID())
+ .column(PREFERRED_IP, addresses.broadcastAddress.getAddress())
+ .column(PREFERRED_PORT, addresses.broadcastAddress.getPort())
+ .column(NATIVE_ADDRESS, addresses.nativeAddress.getAddress())
+ .column(NATIVE_PORT, addresses.nativeAddress.getPort())
+ .column(RELEASE_VERSION, metadata.directory.version(peer).cassandraVersion.toString())
+ .column(SCHEMA_VERSION, Schema.instance.getVersion()) //TODO
+ .column(STATE, metadata.directory.peerState(peer).toString())
+ .column(TOKENS, new HashSet<>(metadata.tokenMap.tokens(peer).stream().map((token) -> token.getToken().getTokenValue().toString()).collect(Collectors.toList())));
+ }
+
+ return result;
+ }
+
+ public static void initializeLegacyPeerTables(ClusterMetadata prev, ClusterMetadata next)
+ {
+ QueryProcessor.executeInternal(String.format("TRUNCATE %s.%s", SYSTEM_KEYSPACE_NAME, PEERS_V2));
+ QueryProcessor.executeInternal(String.format("TRUNCATE %s.%s", SYSTEM_KEYSPACE_NAME, LEGACY_PEERS));
+
+ for (NodeId nodeId : next.directory.peerIds())
+ updateLegacyPeerTable(nodeId, prev, next);
+ }
+
+ private static String peers_v2_query = "INSERT INTO %s.%s ("
+ + "peer, peer_port, "
+ + "preferred_ip, preferred_port, "
+ + "native_address, native_port, "
+ + "data_center, rack, "
+ + "host_id, "
+ + "release_version, "
+ + "schema_version,"
+ + "tokens) " +
+ "VALUES " +
+ "(?,?,?,?,?,?,?,?,?,?,?,?)";
+
+ private static String legacy_peers_query = "INSERT INTO %s.%s ("
+ + "peer, preferred_ip, rpc_address, "
+ + "data_center, rack, "
+ + "host_id, "
+ + "release_version, "
+ + "schema_version,"
+ + "tokens) " +
+ "VALUES " +
+ "(?,?,?,?,?,?,?,?,?)";
+
+ private static String peers_delete_query = "DELETE FROM %s.%s WHERE peer=? and peer_port=?";
+ private static String legacy_peers_delete_query = "DELETE FROM %s.%s WHERE peer=?";
+
+ private static final Logger logger = LoggerFactory.getLogger(PeersTable.class);
+ public static void updateLegacyPeerTable(NodeId nodeId, ClusterMetadata prev, ClusterMetadata next)
+ {
+ if (nodeId.equals(next.directory.peerId(FBUtilities.getBroadcastAddressAndPort())))
+ return;
+
+ if (next.directory.peerState(nodeId) == null || next.directory.peerState(nodeId) == NodeState.LEFT)
+ {
+ NodeAddresses addresses = prev.directory.getNodeAddresses(nodeId);
+ logger.debug("Purging {} from system.peers_v2 table", addresses);
+ QueryProcessor.executeInternal(String.format(peers_delete_query, SYSTEM_KEYSPACE_NAME, PEERS_V2), addresses.broadcastAddress.getAddress(), addresses.broadcastAddress.getPort());
+ QueryProcessor.executeInternal(String.format(legacy_peers_delete_query, SYSTEM_KEYSPACE_NAME, LEGACY_PEERS), addresses.broadcastAddress.getAddress());
+ }
+ else if (NodeState.isPreJoin(next.directory.peerState(nodeId)))
+ {
+ logger.debug("{} is in pre-join state {}, not updating system.peers_v2 table", nodeId, next.directory.peerState(nodeId));
+ }
+ else
+ {
+ NodeAddresses addresses = next.directory.getNodeAddresses(nodeId);
+ NodeAddresses oldAddresses = prev.directory.getNodeAddresses(nodeId);
+ if (oldAddresses != null && !oldAddresses.equals(addresses))
+ {
+ logger.debug("Purging {} from system.peers_v2 table", oldAddresses);
+ QueryProcessor.executeInternal(String.format(peers_delete_query, SYSTEM_KEYSPACE_NAME, PEERS_V2), oldAddresses.broadcastAddress.getAddress(), oldAddresses.broadcastAddress.getPort());
+ QueryProcessor.executeInternal(String.format(legacy_peers_delete_query, SYSTEM_KEYSPACE_NAME, LEGACY_PEERS), oldAddresses.broadcastAddress.getAddress());
+ }
+
+ Location location = next.directory.location(nodeId);
+
+ Set tokens = SystemKeyspace.tokensAsSet(next.tokenMap.tokens(nodeId));
+ QueryProcessor.executeInternal(String.format(peers_v2_query, SYSTEM_KEYSPACE_NAME, PEERS_V2),
+ addresses.broadcastAddress.getAddress(), addresses.broadcastAddress.getPort(),
+ addresses.broadcastAddress.getAddress(), addresses.broadcastAddress.getPort(),
+ addresses.nativeAddress.getAddress(), addresses.nativeAddress.getPort(),
+ location.datacenter, location.rack,
+ nodeId.toUUID(),
+ next.directory.version(nodeId).cassandraVersion.toString(),
+ next.schema.getVersion(),
+ tokens);
+
+ QueryProcessor.executeInternal(String.format(legacy_peers_query, SYSTEM_KEYSPACE_NAME, LEGACY_PEERS),
+ addresses.broadcastAddress.getAddress(), addresses.broadcastAddress.getAddress(), addresses.nativeAddress.getAddress(),
+ location.datacenter, location.rack,
+ nodeId.toUUID(),
+ next.directory.version(nodeId).cassandraVersion.toString(),
+ next.schema.getVersion(),
+ tokens);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/java/org/apache/cassandra/db/virtual/SystemViewsKeyspace.java b/src/java/org/apache/cassandra/db/virtual/SystemViewsKeyspace.java
index 7d6152bdc2..7b7e28e30a 100644
--- a/src/java/org/apache/cassandra/db/virtual/SystemViewsKeyspace.java
+++ b/src/java/org/apache/cassandra/db/virtual/SystemViewsKeyspace.java
@@ -52,6 +52,9 @@ public final class SystemViewsKeyspace extends VirtualKeyspace
.add(new QueriesTable(VIRTUAL_VIEWS))
.add(new LogMessagesTable(VIRTUAL_VIEWS))
.add(new SnapshotsTable(VIRTUAL_VIEWS))
+ .add(new PeersTable(VIRTUAL_VIEWS))
+ .add(new LocalTable(VIRTUAL_VIEWS))
+ .add(new ClusterMetadataLogTable(VIRTUAL_VIEWS))
.addAll(LocalRepairTables.getAll(VIRTUAL_VIEWS))
.addAll(CIDRFilteringMetricsTable.getAll(VIRTUAL_VIEWS))
.addAll(StorageAttachedIndexTables.getAll(VIRTUAL_VIEWS))
diff --git a/src/java/org/apache/cassandra/dht/AbstractBounds.java b/src/java/org/apache/cassandra/dht/AbstractBounds.java
index 7a603b0a5d..9faee77d20 100644
--- a/src/java/org/apache/cassandra/dht/AbstractBounds.java
+++ b/src/java/org/apache/cassandra/dht/AbstractBounds.java
@@ -50,7 +50,7 @@ public abstract class AbstractBounds> implements Seria
public AbstractBounds(T left, T right)
{
- assert left.getPartitioner() == right.getPartitioner();
+ assert left.getPartitioner().getClass().equals(right.getPartitioner().getClass()); // todo: is this enough?
this.left = left;
this.right = right;
}
diff --git a/src/java/org/apache/cassandra/dht/BootStrapper.java b/src/java/org/apache/cassandra/dht/BootStrapper.java
index 5d5529e15f..82f587ed30 100644
--- a/src/java/org/apache/cassandra/dht/BootStrapper.java
+++ b/src/java/org/apache/cassandra/dht/BootStrapper.java
@@ -17,25 +17,33 @@
*/
package org.apache.cassandra.dht;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
+import org.apache.cassandra.tcm.ownership.MovementMap;
import org.apache.cassandra.utils.concurrent.Future;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.config.DatabaseDescriptor;
-import org.apache.cassandra.schema.Schema;
import org.apache.cassandra.db.Keyspace;
import org.apache.cassandra.dht.tokenallocator.TokenAllocation;
import org.apache.cassandra.exceptions.ConfigurationException;
-import org.apache.cassandra.gms.Gossiper;
import org.apache.cassandra.locator.AbstractReplicationStrategy;
import org.apache.cassandra.locator.InetAddressAndPort;
-import org.apache.cassandra.locator.TokenMetadata;
-import org.apache.cassandra.service.StorageService;
-import org.apache.cassandra.streaming.*;
-import org.apache.cassandra.utils.FBUtilities;
+import org.apache.cassandra.schema.KeyspaceMetadata;
+import org.apache.cassandra.schema.Schema;
+import org.apache.cassandra.streaming.StreamEvent;
+import org.apache.cassandra.streaming.StreamEventHandler;
+import org.apache.cassandra.streaming.StreamOperation;
+import org.apache.cassandra.streaming.StreamResultFuture;
+import org.apache.cassandra.streaming.StreamState;
+import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.utils.progress.ProgressEvent;
import org.apache.cassandra.utils.progress.ProgressEventNotifierSupport;
import org.apache.cassandra.utils.progress.ProgressEventType;
@@ -47,39 +55,49 @@ public class BootStrapper extends ProgressEventNotifierSupport
/* endpoint that needs to be bootstrapped */
protected final InetAddressAndPort address;
/* token of the node being bootstrapped. */
- protected final Collection tokens;
- protected final TokenMetadata tokenMetadata;
+ protected final ClusterMetadata metadata;
+ private final MovementMap movements;
+ private final MovementMap strictMovements;
- public BootStrapper(InetAddressAndPort address, Collection tokens, TokenMetadata tmd)
+ public BootStrapper(InetAddressAndPort address,
+ ClusterMetadata metadata,
+ MovementMap movements,
+ MovementMap strictMovements)
{
assert address != null;
- assert tokens != null && !tokens.isEmpty();
this.address = address;
- this.tokens = tokens;
- this.tokenMetadata = tmd;
+ this.metadata = metadata;
+ this.movements = movements;
+ this.strictMovements = strictMovements;
}
- public Future bootstrap(StreamStateStore stateStore, boolean useStrictConsistency)
+ public Future bootstrap(StreamStateStore stateStore, boolean useStrictConsistency, InetAddressAndPort beingReplaced)
{
logger.trace("Beginning bootstrap process");
- RangeStreamer streamer = new RangeStreamer(tokenMetadata,
- tokens,
- address,
+ RangeStreamer streamer = new RangeStreamer(metadata,
StreamOperation.BOOTSTRAP,
useStrictConsistency,
DatabaseDescriptor.getEndpointSnitch(),
stateStore,
true,
- DatabaseDescriptor.getStreamingConnectionsPerHost());
- final Collection nonLocalStrategyKeyspaces = Schema.instance.distributedKeyspaces().names();
+ DatabaseDescriptor.getStreamingConnectionsPerHost(),
+ movements,
+ strictMovements);
+
+ if (beingReplaced != null)
+ streamer.addSourceFilter(new RangeStreamer.ExcludedSourcesFilter(Collections.singleton(beingReplaced)));
+
+ final Collection nonLocalStrategyKeyspaces = Schema.instance.getNonLocalStrategyKeyspaces().names();
if (nonLocalStrategyKeyspaces.isEmpty())
logger.debug("Schema does not contain any non-local keyspaces to stream on bootstrap");
for (String keyspaceName : nonLocalStrategyKeyspaces)
{
- AbstractReplicationStrategy strategy = Keyspace.open(keyspaceName).getReplicationStrategy();
- streamer.addRanges(keyspaceName, strategy.getPendingAddressRanges(tokenMetadata, tokens, address));
+ KeyspaceMetadata ksm = metadata.schema.getKeyspaces().get(keyspaceName).get();
+ if (ksm.params.replication.isMeta())
+ continue;
+ streamer.addKeyspaceToFetch(keyspaceName);
}
StreamResultFuture bootstrapStreamResult = streamer.fetchAsync();
@@ -153,7 +171,7 @@ public class BootStrapper extends ProgressEventNotifierSupport
* otherwise, if allocationKeyspace is specified use the token allocation algorithm to generate suitable tokens
* else choose num_tokens tokens at random
*/
- public static Collection getBootstrapTokens(final TokenMetadata metadata, InetAddressAndPort address, long schemaTimeoutMillis, long ringTimeoutMillis) throws ConfigurationException
+ public static Collection getBootstrapTokens(final ClusterMetadata metadata, InetAddressAndPort address) throws ConfigurationException
{
String allocationKeyspace = DatabaseDescriptor.getAllocateTokensForKeyspace();
Integer allocationLocalRf = DatabaseDescriptor.getAllocateTokensForLocalRf();
@@ -174,10 +192,10 @@ public class BootStrapper extends ProgressEventNotifierSupport
throw new ConfigurationException("num_tokens must be >= 1");
if (allocationKeyspace != null)
- return allocateTokens(metadata, address, allocationKeyspace, numTokens, schemaTimeoutMillis, ringTimeoutMillis);
+ return allocateTokens(metadata, address, allocationKeyspace, numTokens);
if (allocationLocalRf != null)
- return allocateTokens(metadata, address, allocationLocalRf, numTokens, schemaTimeoutMillis, ringTimeoutMillis);
+ return allocateTokens(metadata, address, allocationLocalRf, numTokens);
if (numTokens == 1)
logger.warn("Picking random token for a single vnode. You should probably add more vnodes and/or use the automatic token allocation mechanism.");
@@ -187,32 +205,26 @@ public class BootStrapper extends ProgressEventNotifierSupport
return tokens;
}
- private static Collection getSpecifiedTokens(final TokenMetadata metadata,
+ private static Collection getSpecifiedTokens(final ClusterMetadata metadata,
Collection initialTokens)
{
logger.info("tokens manually specified as {}", initialTokens);
List tokens = new ArrayList<>(initialTokens.size());
for (String tokenString : initialTokens)
{
- Token token = metadata.partitioner.getTokenFactory().fromString(tokenString);
- if (metadata.getEndpoint(token) != null)
+ Token token = metadata.tokenMap.partitioner().getTokenFactory().fromString(tokenString);
+ if (metadata.tokenMap.owner(token) != null)
throw new ConfigurationException("Bootstrapping to existing token " + tokenString + " is not allowed (decommission/removenode the old node first).");
tokens.add(token);
}
return tokens;
}
- static Collection allocateTokens(final TokenMetadata metadata,
+ static Collection allocateTokens(final ClusterMetadata metadata,
InetAddressAndPort address,
String allocationKeyspace,
- int numTokens,
- long schemaTimeoutMillis,
- long ringTimeoutMillis)
+ int numTokens)
{
- StorageService.instance.waitForSchema(schemaTimeoutMillis, ringTimeoutMillis);
- if (!FBUtilities.getBroadcastAddressAndPort().equals(InetAddressAndPort.getLoopbackAddress()))
- Gossiper.waitToSettle();
-
Keyspace ks = Keyspace.open(allocationKeyspace);
if (ks == null)
throw new ConfigurationException("Problem opening token allocation keyspace " + allocationKeyspace);
@@ -224,33 +236,35 @@ public class BootStrapper extends ProgressEventNotifierSupport
}
- static Collection allocateTokens(final TokenMetadata metadata,
+ static Collection allocateTokens(final ClusterMetadata metadata,
InetAddressAndPort address,
int rf,
- int numTokens,
- long schemaTimeoutMillis,
- long ringTimeoutMillis)
+ int numTokens)
{
- StorageService.instance.waitForSchema(schemaTimeoutMillis, ringTimeoutMillis);
- if (!FBUtilities.getBroadcastAddressAndPort().equals(InetAddressAndPort.getLoopbackAddress()))
- Gossiper.waitToSettle();
-
Collection tokens = TokenAllocation.allocateTokens(metadata, rf, address, numTokens);
BootstrapDiagnostics.tokensAllocated(address, metadata, rf, numTokens, tokens);
return tokens;
}
- public static Collection getRandomTokens(TokenMetadata metadata, int numTokens)
+ public static Set getRandomTokens(ClusterMetadata metadata, int numTokens)
{
Set tokens = new HashSet<>(numTokens);
while (tokens.size() < numTokens)
{
- Token token = metadata.partitioner.getRandomToken();
- if (metadata.getEndpoint(token) == null)
+ Token token = metadata.tokenMap.partitioner().getRandomToken();
+ if (metadata.tokenMap.owner(token) == null)
tokens.add(token);
}
logger.info("Generated random tokens. tokens are {}", tokens);
return tokens;
}
+
+ public String toString()
+ {
+ return "BootStrapper{" +
+ "address=" + address +
+ ", metadata=" + metadata +
+ '}';
+ }
}
diff --git a/src/java/org/apache/cassandra/dht/BootstrapDiagnostics.java b/src/java/org/apache/cassandra/dht/BootstrapDiagnostics.java
index 5c2b46a030..3f3bfbe2e4 100644
--- a/src/java/org/apache/cassandra/dht/BootstrapDiagnostics.java
+++ b/src/java/org/apache/cassandra/dht/BootstrapDiagnostics.java
@@ -19,12 +19,13 @@
package org.apache.cassandra.dht;
import java.util.Collection;
+
import com.google.common.collect.ImmutableList;
import org.apache.cassandra.dht.BootstrapEvent.BootstrapEventType;
import org.apache.cassandra.diag.DiagnosticEventService;
import org.apache.cassandra.locator.InetAddressAndPort;
-import org.apache.cassandra.locator.TokenMetadata;
+import org.apache.cassandra.tcm.ClusterMetadata;
/**
* Utility methods for bootstrap related activities.
@@ -50,38 +51,38 @@ final class BootstrapDiagnostics
ImmutableList.copyOf(initialTokens)));
}
- static void useRandomTokens(InetAddressAndPort address, TokenMetadata metadata, int numTokens, Collection tokens)
+ static void useRandomTokens(InetAddressAndPort address, ClusterMetadata metadata, int numTokens, Collection tokens)
{
if (isEnabled(BootstrapEventType.BOOTSTRAP_USING_RANDOM_TOKENS))
service.publish(new BootstrapEvent(BootstrapEventType.BOOTSTRAP_USING_RANDOM_TOKENS,
address,
- metadata.cloneOnlyTokenMap(),
+ metadata,
null,
null,
numTokens,
ImmutableList.copyOf(tokens)));
}
- static void tokensAllocated(InetAddressAndPort address, TokenMetadata metadata,
+ static void tokensAllocated(InetAddressAndPort address, ClusterMetadata metadata,
String allocationKeyspace, int numTokens, Collection tokens)
{
if (isEnabled(BootstrapEventType.TOKENS_ALLOCATED))
service.publish(new BootstrapEvent(BootstrapEventType.TOKENS_ALLOCATED,
address,
- metadata.cloneOnlyTokenMap(),
+ metadata,
allocationKeyspace,
null,
numTokens,
ImmutableList.copyOf(tokens)));
}
- static void tokensAllocated(InetAddressAndPort address, TokenMetadata metadata,
+ static void tokensAllocated(InetAddressAndPort address, ClusterMetadata metadata,
int rf, int numTokens, Collection tokens)
{
if (isEnabled(BootstrapEventType.TOKENS_ALLOCATED))
service.publish(new BootstrapEvent(BootstrapEventType.TOKENS_ALLOCATED,
address,
- metadata.cloneOnlyTokenMap(),
+ metadata,
null,
rf,
numTokens,
diff --git a/src/java/org/apache/cassandra/dht/BootstrapEvent.java b/src/java/org/apache/cassandra/dht/BootstrapEvent.java
index 4936c2942a..e5a5cbc078 100644
--- a/src/java/org/apache/cassandra/dht/BootstrapEvent.java
+++ b/src/java/org/apache/cassandra/dht/BootstrapEvent.java
@@ -28,7 +28,7 @@ import com.google.common.collect.ImmutableCollection;
import org.apache.cassandra.diag.DiagnosticEvent;
import org.apache.cassandra.locator.InetAddressAndPort;
-import org.apache.cassandra.locator.TokenMetadata;
+import org.apache.cassandra.tcm.ClusterMetadata;
/**
* DiagnosticEvent implementation for bootstrap related activities.
@@ -38,7 +38,7 @@ final class BootstrapEvent extends DiagnosticEvent
private final BootstrapEventType type;
@Nullable
- private final TokenMetadata tokenMetadata;
+ private final ClusterMetadata metadata;
private final InetAddressAndPort address;
@Nullable
private final String allocationKeyspace;
@@ -47,12 +47,12 @@ final class BootstrapEvent extends DiagnosticEvent
private final Integer numTokens;
private final Collection tokens;
- BootstrapEvent(BootstrapEventType type, InetAddressAndPort address, @Nullable TokenMetadata tokenMetadata,
+ BootstrapEvent(BootstrapEventType type, InetAddressAndPort address, @Nullable ClusterMetadata metadata,
@Nullable String allocationKeyspace, @Nullable Integer rf, int numTokens, ImmutableCollection tokens)
{
this.type = type;
this.address = address;
- this.tokenMetadata = tokenMetadata;
+ this.metadata = metadata;
this.allocationKeyspace = allocationKeyspace;
this.rf = rf;
this.numTokens = numTokens;
@@ -76,7 +76,7 @@ final class BootstrapEvent extends DiagnosticEvent
{
// be extra defensive against nulls and bugs
HashMap ret = new HashMap<>();
- ret.put("tokenMetadata", String.valueOf(tokenMetadata));
+ ret.put("metadata", metadata.legacyToString());
ret.put("allocationKeyspace", allocationKeyspace);
ret.put("rf", rf);
ret.put("numTokens", numTokens);
diff --git a/src/java/org/apache/cassandra/dht/ComparableObjectToken.java b/src/java/org/apache/cassandra/dht/ComparableObjectToken.java
index 98e4017342..4a6aa8d5a8 100644
--- a/src/java/org/apache/cassandra/dht/ComparableObjectToken.java
+++ b/src/java/org/apache/cassandra/dht/ComparableObjectToken.java
@@ -62,7 +62,7 @@ abstract class ComparableObjectToken> extends Token
public int compareTo(Token o)
{
if (o.getClass() != getClass())
- throw new IllegalArgumentException("Invalid type of Token.compareTo() argument.");
+ throw new IllegalArgumentException(String.format("Invalid type of Token.compareTo() argument. %s != %s", o.getClass(), getClass()));
return token.compareTo(((ComparableObjectToken) o).token);
}
diff --git a/src/java/org/apache/cassandra/dht/Datacenters.java b/src/java/org/apache/cassandra/dht/Datacenters.java
index b1d96eb729..2c0328e8d3 100644
--- a/src/java/org/apache/cassandra/dht/Datacenters.java
+++ b/src/java/org/apache/cassandra/dht/Datacenters.java
@@ -22,13 +22,10 @@ import java.util.HashSet;
import java.util.Set;
import org.apache.cassandra.config.DatabaseDescriptor;
-import org.apache.cassandra.locator.IEndpointSnitch;
-import org.apache.cassandra.locator.InetAddressAndPort;
-import org.apache.cassandra.service.StorageService;
+import org.apache.cassandra.tcm.ClusterMetadata;
public class Datacenters
{
-
private static class DCHandle
{
private static final String thisDc = DatabaseDescriptor.getEndpointSnitch().getLocalDatacenter();
@@ -41,22 +38,15 @@ public class Datacenters
/*
* (non-javadoc) Method to generate list of valid data center names to be used to validate the replication parameters during CREATE / ALTER keyspace operations.
- * All peers of current node are fetched from {@link TokenMetadata} and then a set is build by fetching DC name of each peer.
* @return a set of valid DC names
*/
- public static Set getValidDatacenters()
+ public static Set getValidDatacenters(ClusterMetadata metadata)
{
final Set validDataCenters = new HashSet<>();
- final IEndpointSnitch snitch = DatabaseDescriptor.getEndpointSnitch();
-
// Add data center of localhost.
validDataCenters.add(thisDatacenter());
// Fetch and add DCs of all peers.
- for (InetAddressAndPort peer : StorageService.instance.getTokenMetadata().getAllEndpoints())
- {
- validDataCenters.add(snitch.getDatacenter(peer));
- }
-
+ validDataCenters.addAll(metadata.directory.knownDatacenters());
return validDataCenters;
}
}
diff --git a/src/java/org/apache/cassandra/dht/IPartitioner.java b/src/java/org/apache/cassandra/dht/IPartitioner.java
index b1fcf8fed5..7e63cb422e 100644
--- a/src/java/org/apache/cassandra/dht/IPartitioner.java
+++ b/src/java/org/apache/cassandra/dht/IPartitioner.java
@@ -24,9 +24,9 @@ import java.util.Map;
import java.util.Optional;
import java.util.Random;
+import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.marshal.AbstractType;
-import org.apache.cassandra.service.StorageService;
import javax.annotation.Nullable;
@@ -34,7 +34,7 @@ public interface IPartitioner
{
static IPartitioner global()
{
- return StorageService.instance.getTokenMetadata().partitioner;
+ return DatabaseDescriptor.getPartitioner();
}
static void validate(Collection extends AbstractBounds>> allBounds)
diff --git a/src/java/org/apache/cassandra/dht/LocalPartitioner.java b/src/java/org/apache/cassandra/dht/LocalPartitioner.java
index 74a1264c8d..185871d9a2 100644
--- a/src/java/org/apache/cassandra/dht/LocalPartitioner.java
+++ b/src/java/org/apache/cassandra/dht/LocalPartitioner.java
@@ -161,7 +161,9 @@ public class LocalPartitioner implements IPartitioner
@Override
public int compareTo(Token o)
{
- assert getPartitioner() == o.getPartitioner() : String.format("partitioners do not match; %s != %s", getPartitioner(), o.getPartitioner());
+ // todo (tcm); seems partitioner got mutated on alter type (for example) before tcm, now we create a new one - not sure its enough just making sure that its the same type of partitioner
+ assert o.getPartitioner().getClass().equals(getPartitioner().getClass());
+// assert getPartitioner() == o.getPartitioner() : String.format("partitioners do not match; %s != %s", getPartitioner(), o.getPartitioner());
return comparator.compare(token, ((LocalToken) o).token);
}
diff --git a/src/java/org/apache/cassandra/dht/OrderPreservingPartitioner.java b/src/java/org/apache/cassandra/dht/OrderPreservingPartitioner.java
index 2566cbf710..304c20f5f9 100644
--- a/src/java/org/apache/cassandra/dht/OrderPreservingPartitioner.java
+++ b/src/java/org/apache/cassandra/dht/OrderPreservingPartitioner.java
@@ -45,6 +45,15 @@ public class OrderPreservingPartitioner implements IPartitioner
private static final String rndchars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
public static final StringToken MINIMUM = new StringToken("");
+ public static final StringToken MAXIMUM = new StringToken("") {
+ public int compareTo(Token o)
+ {
+ if (o == MAXIMUM)
+ return 0;
+
+ return 1;
+ }
+ };
public static final BigInteger CHAR_MASK = new BigInteger("65535");
@@ -116,6 +125,11 @@ public class OrderPreservingPartitioner implements IPartitioner
return MINIMUM;
}
+ public StringToken getMaximumToken()
+ {
+ return MAXIMUM;
+ }
+
public StringToken getRandomToken()
{
return getRandomToken(ThreadLocalRandom.current());
@@ -208,6 +222,16 @@ public class OrderPreservingPartitioner implements IPartitioner
{
return ByteSource.of(token, version);
}
+
+ @Override
+ public int compareTo(Token o)
+ {
+ // todo (rebase): I have no recollection of why this is needed - investigate
+ if (o == MAXIMUM)
+ return -1;
+
+ return super.compareTo(o);
+ }
}
public StringToken getToken(ByteBuffer key)
diff --git a/src/java/org/apache/cassandra/dht/OwnedRanges.java b/src/java/org/apache/cassandra/dht/OwnedRanges.java
new file mode 100644
index 0000000000..dd60283d94
--- /dev/null
+++ b/src/java/org/apache/cassandra/dht/OwnedRanges.java
@@ -0,0 +1,139 @@
+/*
+ * 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.dht;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import com.google.common.annotations.VisibleForTesting;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.cassandra.locator.InetAddressAndPort;
+import org.apache.cassandra.metrics.StorageMetrics;
+
+public final class OwnedRanges
+{
+ private static final Logger logger = LoggerFactory.getLogger(OwnedRanges.class);
+
+ private static final Comparator> rangeComparator = (r1, r2) ->
+ {
+ int cmp = r1.left.compareTo(r2.left);
+
+ return cmp == 0 ? r1.right.compareTo(r2.right) : cmp;
+ };
+
+ // the set of token ranges that this node is a replica for
+ private final List> ownedRanges;
+
+ public OwnedRanges(Collection> ownedRanges)
+ {
+ this.ownedRanges = Range.normalize(ownedRanges);
+ }
+
+ /**
+ * Check that all ranges in a requested set are contained by those in the owned set. Used in several contexts, such
+ * as validating StreamRequests in StreamSession & PrepareMessage and ValidationRequest in RepairMessageVerbHandler.
+ * In those callers, we want to verify that the token ranges specified in some request from a peer are not outside
+ * the ranges owned by the local node. There are 2 levels of response if invalid ranges are detected, controlled
+ * by options in Config; logging the event and rejecting the request and either/neither/both of these options may be
+ * enabled. If neither are enabled, we short ciruit and immediately return success without any further processing.
+ * If either option is enabled and we do detect unowned ranges in the request, we increment a metric then take further
+ * action depending on the config.
+ *
+ * @param requestedRanges the set of token ranges contained in a request from a peer
+ * @param requestId an identifier for the peer request, to be used in logging (e.g. Stream or Repair Session #)
+ * @param requestType description of the request type, to be used in logging (e.g. "prepare request" or "validation")
+ * @param from the originator of the request
+ * @return true if the request should be accepted (either because no checking was performed, invalid ranges were d
+ * identified but only the logging action is enabled, or because all request ranges were valid. Otherwise,
+ * returns false to indicate the request should be rejected.
+ */
+ public boolean validateRangeRequest(Collection> requestedRanges, String requestId, String requestType, InetAddressAndPort from)
+ {
+ Collection> unownedRanges = testRanges(requestedRanges);
+
+ if (!unownedRanges.isEmpty())
+ {
+ StorageMetrics.totalOpsForInvalidToken.inc();
+ logger.warn("[{}] Received {} from {} containing ranges {} outside valid ranges {}",
+ requestId,
+ requestType,
+ from,
+ unownedRanges,
+ ownedRanges);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Takes a collection of ranges and returns ranges from that collection that are not covered by the this node's owned ranges.
+ *
+ * This normalizes the range collections internally, so:
+ * a) be cautious about using this in any hot path
+ * b) any returned ranges may not be identical to those present. That is, the returned values are post-normalization.
+ *
+ * e.g Given two collections:
+ * { (0, 100], (100, 200] }
+ * { (90, 100], (100, 110], (110, 300] }
+ * the normalized forms are:
+ * { (0, 200] }
+ * { (90, 300] }
+ * and so the return value would be:
+ * { (90, 300] }
+ * which is equivalent, but not strictly equal to any member of the original supplied collection.
+ *
+ * @param testedRanges collection of candidate ranges to be checked
+ * @return the ranges in testedRanges which are not covered by the owned ranges
+ */
+ @VisibleForTesting
+ Collection> testRanges(final Collection> testedRanges)
+ {
+ if (ownedRanges.isEmpty())
+ return testedRanges;
+
+ // now normalize the second and check coverage of its members in the normalized first collection
+ return Range.normalize(testedRanges).stream().filter(requested ->
+ {
+ // Find the point at which the target range would insert into the superset
+ int index = Collections.binarySearch(ownedRanges, requested, rangeComparator);
+
+ // an index >= 0 means an exact match was found so we can definitely accept this range
+ if (index >= 0)
+ return false;
+
+ // convert to an insertion point in the superset
+ index = Math.abs(index) - 1;
+
+ // target sorts before the last list item, so we only need to check that one
+ if (index >= ownedRanges.size())
+ return !ownedRanges.get(index - 1).contains(requested);
+
+ // target sorts before the first list item, so we only need to check that one
+ if (index == 0)
+ return !ownedRanges.get(index).contains(requested);
+
+ // otherwise, check if the range on either side of the insertion point wholly contains the target
+ return !(ownedRanges.get(index - 1).contains(requested) || ownedRanges.get(index).contains(requested));
+ }).collect(Collectors.toSet());
+ }
+}
diff --git a/src/java/org/apache/cassandra/dht/Range.java b/src/java/org/apache/cassandra/dht/Range.java
index 0ba6d20870..b5d06967ac 100644
--- a/src/java/org/apache/cassandra/dht/Range.java
+++ b/src/java/org/apache/cassandra/dht/Range.java
@@ -17,6 +17,7 @@
*/
package org.apache.cassandra.dht;
+import java.io.IOException;
import java.io.Serializable;
import java.util.*;
import java.util.function.Predicate;
@@ -25,6 +26,12 @@ import com.google.common.collect.Iterables;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.cassandra.db.PartitionPosition;
+import org.apache.cassandra.io.util.DataInputPlus;
+import org.apache.cassandra.io.util.DataOutputPlus;
+import org.apache.cassandra.net.MessagingService;
+import org.apache.cassandra.tcm.ClusterMetadata;
+import org.apache.cassandra.tcm.serialization.MetadataSerializer;
+import org.apache.cassandra.tcm.serialization.Version;
import org.apache.cassandra.utils.Pair;
/**
@@ -38,6 +45,7 @@ import org.apache.cassandra.utils.Pair;
*/
public class Range> extends AbstractBounds implements Comparable>, Serializable
{
+ public static final Serializer serializer = new Serializer();
public static final long serialVersionUID = 1L;
public Range(T left, T right)
@@ -682,4 +690,24 @@ public class Range> extends AbstractBounds implemen
}
}
}
+
+ public static class Serializer implements MetadataSerializer>
+ {
+ private static final int SERDE_VERSION = MessagingService.VERSION_40;
+
+ public void serialize(Range t, DataOutputPlus out, Version version) throws IOException
+ {
+ tokenSerializer.serialize(t, out, SERDE_VERSION);
+ }
+
+ public Range deserialize(DataInputPlus in, Version version) throws IOException
+ {
+ return (Range) tokenSerializer.deserialize(in, ClusterMetadata.current().partitioner, SERDE_VERSION);
+ }
+
+ public long serializedSize(Range t, Version version)
+ {
+ return tokenSerializer.serializedSize(t, SERDE_VERSION);
+ }
+ }
}
diff --git a/src/java/org/apache/cassandra/dht/RangeStreamer.java b/src/java/org/apache/cassandra/dht/RangeStreamer.java
index 9b7833b90a..7cf919b91f 100644
--- a/src/java/org/apache/cassandra/dht/RangeStreamer.java
+++ b/src/java/org/apache/cassandra/dht/RangeStreamer.java
@@ -20,9 +20,9 @@ package org.apache.cassandra.dht;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
-import java.util.Optional;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Function;
@@ -33,11 +33,9 @@ import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableMultimap;
-import com.google.common.collect.Iterables;
import com.google.common.collect.Multimap;
-
+import com.google.common.collect.Multimaps;
import org.apache.commons.lang3.StringUtils;
-
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -47,7 +45,6 @@ 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.Endpoints;
import org.apache.cassandra.locator.EndpointsByRange;
import org.apache.cassandra.locator.EndpointsByReplica;
import org.apache.cassandra.locator.EndpointsForRange;
@@ -60,11 +57,13 @@ import org.apache.cassandra.locator.Replica;
import org.apache.cassandra.locator.ReplicaCollection;
import org.apache.cassandra.locator.ReplicaCollection.Builder.Conflict;
import org.apache.cassandra.locator.Replicas;
-import org.apache.cassandra.locator.TokenMetadata;
+import org.apache.cassandra.schema.ReplicationParams;
import org.apache.cassandra.streaming.PreviewKind;
import org.apache.cassandra.streaming.StreamOperation;
import org.apache.cassandra.streaming.StreamPlan;
import org.apache.cassandra.streaming.StreamResultFuture;
+import org.apache.cassandra.tcm.ClusterMetadata;
+import org.apache.cassandra.tcm.ownership.MovementMap;
import org.apache.cassandra.utils.FBUtilities;
import static com.google.common.base.Predicates.and;
@@ -82,17 +81,12 @@ public class RangeStreamer
private static final Logger logger = LoggerFactory.getLogger(RangeStreamer.class);
public static Predicate ALIVE_PREDICATE = replica ->
- (!Gossiper.instance.isEnabled() ||
- (Gossiper.instance.getEndpointStateForEndpoint(replica.endpoint()) == null ||
- Gossiper.instance.getEndpointStateForEndpoint(replica.endpoint()).isAlive())) &&
- FailureDetector.instance.isAlive(replica.endpoint());
+ (!Gossiper.instance.isEnabled() ||
+ (Gossiper.instance.getEndpointStateForEndpoint(replica.endpoint()) == null ||
+ Gossiper.instance.getEndpointStateForEndpoint(replica.endpoint()).isAlive())) &&
+ FailureDetector.instance.isAlive(replica.endpoint());
- /* bootstrap tokens. can be null if replacing the node. */
- private final Collection tokens;
- /* current token ring */
- private final TokenMetadata metadata;
- /* address of this node */
- private final InetAddressAndPort address;
+ private final ClusterMetadata metadata;
/* streaming description */
private final String description;
private final Map> toFetch = new HashMap<>();
@@ -101,6 +95,8 @@ public class RangeStreamer
private final boolean useStrictConsistency;
private final IEndpointSnitch snitch;
private final StreamStateStore stateStore;
+ private final MovementMap movements;
+ private final MovementMap strictMovements;
public static class FetchReplica
{
@@ -272,40 +268,61 @@ public class RangeStreamer
}
}
- public RangeStreamer(TokenMetadata metadata,
- Collection