com.thinkaurelius.titan.diskstorage.cassandra.astyanax.AstyanaxStoreManager Maven / Gradle / Ivy
The newest version!
package com.thinkaurelius.titan.diskstorage.cassandra.astyanax;
import com.google.common.base.Preconditions;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableMap;
import com.netflix.astyanax.*;
import com.netflix.astyanax.connectionpool.Host;
import com.netflix.astyanax.connectionpool.NodeDiscoveryType;
import com.netflix.astyanax.connectionpool.RetryBackoffStrategy;
import com.netflix.astyanax.connectionpool.exceptions.ConnectionException;
import com.netflix.astyanax.connectionpool.impl.*;
import com.netflix.astyanax.ddl.ColumnFamilyDefinition;
import com.netflix.astyanax.ddl.KeyspaceDefinition;
import com.netflix.astyanax.impl.AstyanaxConfigurationImpl;
import com.netflix.astyanax.model.ColumnFamily;
import com.netflix.astyanax.retry.RetryPolicy;
import com.netflix.astyanax.serializers.ByteBufferSerializer;
import com.netflix.astyanax.serializers.StringSerializer;
import com.netflix.astyanax.thrift.ThriftFamilyFactory;
import com.thinkaurelius.titan.diskstorage.PermanentStorageException;
import com.thinkaurelius.titan.diskstorage.StaticBuffer;
import com.thinkaurelius.titan.diskstorage.StorageException;
import com.thinkaurelius.titan.diskstorage.TemporaryStorageException;
import com.thinkaurelius.titan.diskstorage.cassandra.AbstractCassandraStoreManager;
import com.thinkaurelius.titan.diskstorage.cassandra.astyanax.locking.AstyanaxRecipeLocker;
import com.thinkaurelius.titan.diskstorage.keycolumnvalue.Entry;
import com.thinkaurelius.titan.diskstorage.keycolumnvalue.KCVMutation;
import com.thinkaurelius.titan.diskstorage.keycolumnvalue.StoreTransaction;
import org.apache.cassandra.dht.IPartitioner;
import org.apache.cassandra.dht.Token;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.commons.configuration.Configuration;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Constructor;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static com.thinkaurelius.titan.diskstorage.cassandra.CassandraTransaction.getTx;
public class AstyanaxStoreManager extends AbstractCassandraStoreManager {
private static final Logger log = LoggerFactory.getLogger(AstyanaxStoreManager.class);
//################### ASTYANAX SPECIFIC CONFIGURATION OPTIONS ######################
/**
* Default name for the Cassandra cluster
*
* Value = {@value}
*/
public static final String CLUSTER_DEFAULT = "Titan Cluster";
public static final String CLUSTER_KEY = "cluster-name";
/**
* Maximum pooled connections per host.
*
* Value = {@value}
*/
public static final int MAX_CONNECTIONS_PER_HOST_DEFAULT = 32;
public static final String MAX_CONNECTIONS_PER_HOST_KEY = "max-connections-per-host";
/**
* Maximum open connections allowed in the pool (counting all hosts).
*
* Value = {@value}
*/
public static final int MAX_CONNECTIONS_DEFAULT = -1;
public static final String MAX_CONNECTIONS_KEY = "max-connections";
/**
* Maximum number of operations allowed per connection before the connection is closed.
*
* Value = {@value}
*/
public static final int MAX_OPERATIONS_PER_CONNECTION_DEFAULT = 100 * 1000;
public static final String MAX_OPERATIONS_PER_CONNECTION_KEY = "max-operations-per-connection";
/**
* Maximum pooled "cluster" connections per host.
*
* These connections are mostly idle and only used for DDL operations
* (like creating keyspaces). Titan doesn't need many of these connections
* in ordinary operation.
*/
public static final int MAX_CLUSTER_CONNECTIONS_PER_HOST_DEFAULT = 3;
public static final String MAX_CLUSTER_CONNECTIONS_PER_HOST_KEY = "max-cluster-connections-per-host";
/**
* How Astyanax discovers Cassandra cluster nodes. This must be one of the
* values of the Astyanax NodeDiscoveryType enum.
*
* Value = {@value}
*/
public static final String NODE_DISCOVERY_TYPE_DEFAULT = "RING_DESCRIBE";
public static final String NODE_DISCOVERY_TYPE_KEY = "node-discovery-type";
/**
* Astyanax specific host supplier useful only when discovery type set to DISCOVERY_SERVICE or TOKEN_AWARE.
* Excepts fully qualified class name which extends google.common.base.Supplier>.
*/
public static final String ASTYANAX_HOST_SUPPLIER_KEY= "host-supplier";
/**
* Astyanax's connection pooler implementation. This must be one of the
* values of the Astyanax ConnectionPoolType enum.
*
* Value = {@value}
*/
public static final String CONNECTION_POOL_TYPE_DEFAULT = "TOKEN_AWARE";
public static final String CONNECTION_POOL_TYPE_KEY = "connection-pool-type";
/**
* This must be the fully-qualified classname (i.e. the complete package
* name, a dot, and then the class name) of an implementation of Astyanax's
* RetryPolicy interface. This string may be followed by a sequence of
* integers, separated from the full classname and from each other by
* commas; in this case, the integers are cast to native Java ints and
* passed to the class constructor as arguments.
*
* In Astyanax, RetryPolicy and RetryBackoffStrategy sound and look similar
* but are used for distinct purposes. RetryPolicy is for retrying failed
* operations. RetryBackoffStrategy is for retrying attempts to talk to
* uncommunicative hosts. This config option controls RetryPolicy.
*
* Value = {@value}
*/
public static final String RETRY_POLICY_DEFAULT = "com.netflix.astyanax.retry.BoundedExponentialBackoff,100,25000,8";
public static final String RETRY_POLICY_KEY = "retry-policy";
/**
* If non-null, this must be the fully-qualified classname (i.e. the
* complete package name, a dot, and then the class name) of an
* implementation of Astyanax's RetryBackoffStrategy interface. This string
* may be followed by a sequence of integers, separated from the full
* classname and from each other by commas; in this case, the integers are
* cast to native Java ints and passed to the class constructor as
* arguments. Here's an example setting that would instantiate an Astyanax
* FixedRetryBackoffStrategy with an delay interval of 1s and suspend time
* of 5s:
*
*
* com.netflix.astyanax.connectionpool.impl.FixedRetryBackoffStrategy,1000,5000
*
*
* If null, then Astyanax uses its default strategy, which is an
* ExponentialRetryBackoffStrategy instance. The instance parameters take
* Astyanax's built-in default values, which can be overridden via the
* following config keys:
*
* - {@value #RETRY_DELAY_SLICE_KEY}
* - {@value #RETRY_MAX_DELAY_SLICE_KEY}
* - {@value #RETRY_SUSPEND_WINDOW_KEY}
*
*
* In Astyanax, RetryPolicy and RetryBackoffStrategy sound and look similar
* but are used for distinct purposes. RetryPolicy is for retrying failed
* operations. RetryBackoffStrategy is for retrying attempts to talk to
* uncommunicative hosts. This config option controls RetryBackoffStrategy.
*
* TODO port to ConfigOption for 0.5.
*
* Value = {@value}
*/
public static final String RETRY_BACKOFF_STRATEGY_DEFAULT = "com.netflix.astyanax.connectionpool.impl.FixedRetryBackoffStrategy,1000,5000";
//public static final String RETRY_BACKOFF_STRATEGY_DEFAULT = null;
public static final String RETRY_BACKOFF_STRATEGY_KEY = "retry-backoff-strategy";
/**
* Controls the retryDelaySlice parameter on Astyanax
* ConnectionPoolConfigurationImpl objects, which is in turn used by
* ExponentialRetryBackoffStrategy. See the code for
* {@link ConnectionPoolConfigurationImpl},
* {@link ExponentialRetryBackoffStrategy}, and the javadoc for
* {@link #RETRY_BACKOFF_STRATEGY_KEY} for more information.
*
* This parameter is not meaningful for and has no effect on
* FixedRetryBackoffStrategy.
*
* TODO port to ConfigOption for 0.5.
*
* Value = {@value}
*/
public static final String RETRY_DELAY_SLICE_KEY = "retry-delay-slice";
/**
* Controls the retryMaxDelaySlice parameter on Astyanax
* ConnectionPoolConfigurationImpl objects, which is in turn used by
* ExponentialRetryBackoffStrategy. See the code for
* {@link ConnectionPoolConfigurationImpl},
* {@link ExponentialRetryBackoffStrategy}, and the javadoc for
* {@link #RETRY_BACKOFF_STRATEGY_KEY} for more information.
*
* This parameter is not meaningful for and has no effect on
* FixedRetryBackoffStrategy.
*
* TODO port to ConfigOption for 0.5.
*
* Value = {@value}
*/
public static final String RETRY_MAX_DELAY_SLICE_KEY = "retry-max-delay-slice";
/**
* Controls the retrySuspendWindow parameter on Astyanax
* ConnectionPoolConfigurationImpl objects, which is in turn used by
* ExponentialRetryBackoffStrategy. See the code for
* {@link ConnectionPoolConfigurationImpl},
* {@link ExponentialRetryBackoffStrategy}, and the javadoc for
* {@link #RETRY_BACKOFF_STRATEGY_KEY} for more information.
*
* This parameter is not meaningful for and has no effect on
* FixedRetryBackoffStrategy.
*
* TODO port to ConfigOption for 0.5.
*
* Value = {@value}
*/
public static final String RETRY_SUSPEND_WINDOW_KEY = "retry-suspend-window";
private final String clusterName;
private final AstyanaxContext keyspaceContext;
private final AstyanaxContext clusterContext;
private final RetryPolicy retryPolicy;
private final int retryDelaySlice;
private final int retryMaxDelaySlice;
private final int retrySuspendWindow;
private final RetryBackoffStrategy retryBackoffStrategy;
private final Map openStores;
public AstyanaxStoreManager(Configuration config) throws StorageException {
super(config);
// Check if we have non-default thrift frame size or max message size set and warn users
// because there is nothing we can do in Astyanax to apply those, warning is good enough here
// otherwise it would make bad user experience if we don't warn at all or crash on this.
if (config.containsKey(THRIFT_FRAME_SIZE_MB))
log.warn("Couldn't set custom Thrift Frame Size property, use 'cassandrathrift' instead.");
retryDelaySlice = config.getInt(RETRY_DELAY_SLICE_KEY,
ConnectionPoolConfigurationImpl.DEFAULT_RETRY_DELAY_SLICE);
retryMaxDelaySlice = config.getInt(RETRY_MAX_DELAY_SLICE_KEY,
ConnectionPoolConfigurationImpl.DEFAULT_RETRY_MAX_DELAY_SLICE);
retrySuspendWindow = config.getInt(RETRY_SUSPEND_WINDOW_KEY,
ConnectionPoolConfigurationImpl.DEFAULT_RETRY_SUSPEND_WINDOW);
retryBackoffStrategy = getRetryBackoffStrategy(
config.getString(RETRY_BACKOFF_STRATEGY_KEY, RETRY_BACKOFF_STRATEGY_DEFAULT));
this.clusterName = config.getString(CLUSTER_KEY, CLUSTER_DEFAULT);
this.retryPolicy = getRetryPolicy(config.getString(RETRY_POLICY_KEY, RETRY_POLICY_DEFAULT));
final int maxConnsPerHost =
config.getInt(
MAX_CONNECTIONS_PER_HOST_KEY,
MAX_CONNECTIONS_PER_HOST_DEFAULT);
final int maxClusterConnsPerHost =
config.getInt(
MAX_CLUSTER_CONNECTIONS_PER_HOST_KEY,
MAX_CLUSTER_CONNECTIONS_PER_HOST_DEFAULT);
this.clusterContext = createCluster(getContextBuilder(config, maxClusterConnsPerHost, "Cluster"));
ensureKeyspaceExists(clusterContext.getClient());
this.keyspaceContext = getContextBuilder(config, maxConnsPerHost, "Keyspace").buildKeyspace(ThriftFamilyFactory.getInstance());
this.keyspaceContext.start();
openStores = new HashMap(8);
}
@Override
public Deployment getDeployment() {
return Deployment.REMOTE;
}
@Override
@SuppressWarnings("unchecked")
public IPartitioner extends Token>> getCassandraPartitioner() throws StorageException {
Cluster cl = clusterContext.getClient();
try {
return FBUtilities.newPartitioner(cl.describePartitioner());
} catch (ConnectionException e) {
throw new TemporaryStorageException(e);
} catch (ConfigurationException e) {
throw new PermanentStorageException(e);
}
}
@Override
public String toString() {
return "astyanax" + super.toString();
}
@Override
public void close() {
// Shutdown the Astyanax contexts
openStores.clear();
keyspaceContext.shutdown();
clusterContext.shutdown();
}
@Override
public synchronized AstyanaxOrderedKeyColumnValueStore openDatabase(String name) throws StorageException {
if (openStores.containsKey(name)) return openStores.get(name);
else {
ensureColumnFamilyExists(name);
AstyanaxOrderedKeyColumnValueStore store = new AstyanaxOrderedKeyColumnValueStore(name, keyspaceContext.getClient(), this, retryPolicy);
openStores.put(name, store);
return store;
}
}
public AstyanaxRecipeLocker openLocker(String cfName) throws StorageException {
ColumnFamily cf = new ColumnFamily(
cfName,
ByteBufferSerializer.get(),
StringSerializer.get());
return new AstyanaxRecipeLocker.Builder(keyspaceContext.getClient(), cf).build();
}
@Override
public void mutateMany(Map> batch, StoreTransaction txh) throws StorageException {
MutationBatch m = keyspaceContext.getClient().prepareMutationBatch()
.setConsistencyLevel(getTx(txh).getWriteConsistencyLevel().getAstyanaxConsistency())
.withRetryPolicy(retryPolicy.duplicate());
final Timestamp timestamp = getTimestamp(txh);
for (Map.Entry> batchentry : batch.entrySet()) {
String storeName = batchentry.getKey();
Preconditions.checkArgument(openStores.containsKey(storeName), "Store cannot be found: " + storeName);
ColumnFamily columnFamily = openStores.get(storeName).getColumnFamily();
Map mutations = batchentry.getValue();
for (Map.Entry ent : mutations.entrySet()) {
// The CLMs for additions and deletions are separated because
// Astyanax's operation timestamp cannot be set on a per-delete
// or per-addition basis.
KCVMutation titanMutation = ent.getValue();
if (titanMutation.hasDeletions()) {
ColumnListMutation dels = m.withRow(columnFamily, ent.getKey().asByteBuffer());
dels.setTimestamp(timestamp.deletionTime);
for (StaticBuffer b : titanMutation.getDeletions())
dels.deleteColumn(b.asByteBuffer());
}
if (titanMutation.hasAdditions()) {
ColumnListMutation upds = m.withRow(columnFamily, ent.getKey().asByteBuffer());
upds.setTimestamp(timestamp.additionTime);
for (Entry e : titanMutation.getAdditions())
upds.putColumn(e.getColumn().asByteBuffer(), e.getValue().asByteBuffer());
}
}
}
try {
m.execute();
} catch (ConnectionException e) {
throw new TemporaryStorageException(e);
}
}
@Override
public void clearStorage() throws StorageException {
try {
Cluster cluster = clusterContext.getClient();
Keyspace ks = cluster.getKeyspace(keySpaceName);
// Not a big deal if Keyspace doesn't not exist (dropped manually by user or tests).
// This is called on per test setup basis to make sure that previous test cleaned
// everything up, so first invocation would always fail as Keyspace doesn't yet exist.
if (ks == null)
return;
for (ColumnFamilyDefinition cf : cluster.describeKeyspace(keySpaceName).getColumnFamilyList()) {
ks.truncateColumnFamily(new ColumnFamily
© 2015 - 2025 Weber Informatics LLC | Privacy Policy