All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.thinkaurelius.titan.diskstorage.cassandra.thrift.CassandraThriftStoreManager Maven / Gradle / Ivy

The newest version!
package com.thinkaurelius.titan.diskstorage.cassandra.thrift;

import static com.thinkaurelius.titan.diskstorage.cassandra.CassandraTransaction.getTx;
import static org.apache.cassandra.db.Table.SYSTEM_KS;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;


import org.apache.cassandra.dht.ByteOrderedPartitioner;
import org.apache.cassandra.dht.IPartitioner;
import org.apache.cassandra.dht.Token;
import org.apache.cassandra.thrift.Cassandra;
import org.apache.cassandra.thrift.CfDef;
import org.apache.cassandra.thrift.Column;
import org.apache.cassandra.thrift.ColumnOrSuperColumn;
import org.apache.cassandra.thrift.ConsistencyLevel;
import org.apache.cassandra.thrift.Deletion;
import org.apache.cassandra.thrift.InvalidRequestException;
import org.apache.cassandra.thrift.KsDef;
import org.apache.cassandra.thrift.NotFoundException;
import org.apache.cassandra.thrift.SchemaDisagreementException;
import org.apache.cassandra.thrift.SlicePredicate;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.commons.configuration.Configuration;
import org.apache.commons.pool.impl.GenericKeyedObjectPool;
import org.apache.thrift.TException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import com.thinkaurelius.titan.diskstorage.Backend;
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.thrift.thriftpool.CTConnection;
import com.thinkaurelius.titan.diskstorage.cassandra.thrift.thriftpool.CTConnectionFactory;
import com.thinkaurelius.titan.diskstorage.cassandra.thrift.thriftpool.CTConnectionPool;
import com.thinkaurelius.titan.diskstorage.keycolumnvalue.Entry;
import com.thinkaurelius.titan.diskstorage.keycolumnvalue.KCVMutation;
import com.thinkaurelius.titan.diskstorage.keycolumnvalue.StoreTransaction;
import com.thinkaurelius.titan.diskstorage.util.ByteBufferUtil;
import com.thinkaurelius.titan.diskstorage.util.Hex;
import com.thinkaurelius.titan.graphdb.configuration.GraphDatabaseConfiguration;

/**
 * This class creates {@see CassandraThriftKeyColumnValueStore}s and
 * handles Cassandra-backed allocation of vertex IDs for Titan (when so
 * configured).
 *
 * @author Dan LaRocque 
 */
public class CassandraThriftStoreManager extends AbstractCassandraStoreManager {
    private static final Logger log = LoggerFactory.getLogger(CassandraThriftStoreManager.class);

    private final Map openStores;
    private final CTConnectionPool pool;

    public CassandraThriftStoreManager(Configuration config) throws StorageException {
        super(config);

        int thriftTimeoutMS = config.getInt(
                GraphDatabaseConfiguration.CONNECTION_TIMEOUT_KEY,
                GraphDatabaseConfiguration.CONNECTION_TIMEOUT_DEFAULT);

        int maxTotalConnections = config.getInt(
                GraphDatabaseConfiguration.CONNECTION_POOL_SIZE_KEY,
                GraphDatabaseConfiguration.CONNECTION_POOL_SIZE_DEFAULT);

        CTConnectionFactory factory = new CTConnectionFactory(hostnames, port, username, password, thriftTimeoutMS, thriftFrameSize);
        CTConnectionPool p = new CTConnectionPool(factory);
        p.setTestOnBorrow(true);
        p.setTestOnReturn(true);
        p.setTestWhileIdle(false);
        p.setWhenExhaustedAction(GenericKeyedObjectPool.WHEN_EXHAUSTED_BLOCK);
        p.setMaxActive(-1); // "A negative value indicates no limit"
        p.setMaxTotal(maxTotalConnections); // maxTotal limits active + idle
        p.setMinIdle(0); // prevent evictor from eagerly creating unused connections
        p.setMinEvictableIdleTimeMillis(60 * 1000L);
        p.setTimeBetweenEvictionRunsMillis(30 * 1000L);

        this.pool = p;

        this.openStores = new HashMap();
    }


    @Override
    public Deployment getDeployment() {
        return Deployment.REMOTE;
    }

    @Override
    @SuppressWarnings("unchecked")
    public IPartitioner> getCassandraPartitioner() throws StorageException {
        CTConnection conn = null;
        try {
            conn = pool.borrowObject(SYSTEM_KS);
            return FBUtilities.newPartitioner(conn.getClient().describe_partitioner());
        } catch (Exception e) {
            throw new TemporaryStorageException(e);
        } finally {
            pool.returnObjectUnsafe(SYSTEM_KS, conn);
        }
    }

    @Override
    public String toString() {
        return "thriftCassandra" + super.toString();
    }

    @Override
    public void close() throws StorageException {
        openStores.clear();
        closePool();
    }

    @Override
    public void mutateMany(Map> mutations, StoreTransaction txh) throws StorageException {
        Preconditions.checkNotNull(mutations);

        final Timestamp timestamp = getTimestamp(txh);

        ConsistencyLevel consistency = getTx(txh).getWriteConsistencyLevel().getThriftConsistency();

        // Generate Thrift-compatible batch_mutate() datastructure
        // key -> cf -> cassmutation
        int size = 0;
        for (Map mutation : mutations.values()) size += mutation.size();
        Map>> batch =
                new HashMap>>(size);


        for (Map.Entry> keyMutation : mutations.entrySet()) {
            String columnFamily = keyMutation.getKey();
            for (Map.Entry mutEntry : keyMutation.getValue().entrySet()) {
                StaticBuffer key = mutEntry.getKey();
                ByteBuffer keyBB = key.asByteBuffer();

                // Get or create the single Cassandra Mutation object responsible for this key
                Map> cfmutation = batch.get(keyBB);
                if (cfmutation == null) {
                    cfmutation = new HashMap>(3); // TODO where did the magic number 3 come from?
                    batch.put(keyBB, cfmutation);
                }

                KCVMutation mutation = mutEntry.getValue();
                List thriftMutation =
                        new ArrayList(mutations.size());

                if (mutation.hasDeletions()) {
                    for (StaticBuffer buf : mutation.getDeletions()) {
                        Deletion d = new Deletion();
                        SlicePredicate sp = new SlicePredicate();
                        sp.addToColumn_names(buf.asByteBuffer());
                        d.setPredicate(sp);
                        d.setTimestamp(timestamp.deletionTime);
                        org.apache.cassandra.thrift.Mutation m = new org.apache.cassandra.thrift.Mutation();
                        m.setDeletion(d);
                        thriftMutation.add(m);
                    }
                }

                if (mutation.hasAdditions()) {
                    for (Entry ent : mutation.getAdditions()) {
                        ColumnOrSuperColumn cosc = new ColumnOrSuperColumn();
                        Column column = new Column(ent.getColumn().asByteBuffer());
                        column.setValue(ent.getValue().asByteBuffer());
                        column.setTimestamp(timestamp.additionTime);
                        cosc.setColumn(column);
                        org.apache.cassandra.thrift.Mutation m = new org.apache.cassandra.thrift.Mutation();
                        m.setColumn_or_supercolumn(cosc);
                        thriftMutation.add(m);
                    }
                }

                cfmutation.put(columnFamily, thriftMutation);
            }
        }

        CTConnection conn = null;
        try {
            conn = pool.borrowObject(keySpaceName);
            Cassandra.Client client = conn.getClient();
            client.batch_mutate(batch, consistency);
        } catch (Exception ex) {
            throw CassandraThriftKeyColumnValueStore.convertException(ex);
        } finally {
            pool.returnObjectUnsafe(keySpaceName, conn);
        }
    }

    @Override // TODO: *BIG FAT WARNING* 'synchronized is always *bad*, change openStores to use ConcurrentLinkedHashMap
    public synchronized CassandraThriftKeyColumnValueStore openDatabase(final String name) throws StorageException {
        if (openStores.containsKey(name))
            return openStores.get(name);

        ensureColumnFamilyExists(keySpaceName, name);

        CassandraThriftKeyColumnValueStore store = new CassandraThriftKeyColumnValueStore(keySpaceName, name, this, pool);
        openStores.put(name, store);
        return store;
    }


    /**
     * Connect to Cassandra via Thrift on the specified host and port and attempt to truncate the named keyspace.
     * 

* This is a utility method intended mainly for testing. It is * equivalent to issuing 'truncate ' for each of the column families in keyspace using * the cassandra-cli tool. *

* Using truncate is better for a number of reasons, most significantly because it doesn't * involve any schema modifications which can take time to propagate across the cluster such * leaves nodes in the inconsistent state and could result in read/write failures. * Any schema modifications are discouraged until there is no traffic to Keyspace or ColumnFamilies. * * @throws StorageException if any checked Thrift or UnknownHostException is thrown in the body of this method */ public void clearStorage() throws StorageException { openStores.clear(); final String lp = "ClearStorage: "; // "log prefix" /* * log4j is capable of automatically writing the name of a method that * generated a log message, but the docs warn that "generating caller * location information is extremely slow and should be avoided unless * execution speed is not an issue." */ CTConnection conn = null; try { conn = pool.borrowObject(SYSTEM_KS); Cassandra.Client client = conn.getClient(); KsDef ksDef; try { client.set_keyspace(keySpaceName); ksDef = client.describe_keyspace(keySpaceName); } catch (NotFoundException e) { log.debug(lp + "Keyspace {} does not exist, not attempting to truncate.", keySpaceName); return; } catch (InvalidRequestException e) { log.debug(lp + "InvalidRequestException when attempting to describe keyspace {}, not attempting to truncate.", keySpaceName); return; } if (null == ksDef) { log.debug(lp + "Received null KsDef for keyspace {}; not truncating its CFs", keySpaceName); return; } List cfDefs = ksDef.getCf_defs(); if (null == cfDefs) { log.debug(lp + "Received empty CfDef list for keyspace {}; not truncating CFs", keySpaceName); return; } for (CfDef cfDef : ksDef.getCf_defs()) { client.truncate(cfDef.name); log.info(lp + "Truncated CF {} in keyspace {}", cfDef.name, keySpaceName); } /* * Clearing the CTConnectionPool is unnecessary. This method * removes no keyspaces. All open Cassandra connections will * remain valid. */ } catch (Exception e) { throw new TemporaryStorageException(e); } finally { if (conn != null && conn.getClient() != null) { try { conn.getClient().set_keyspace(SYSTEM_KS); } catch (InvalidRequestException e) { log.warn("Failed to reset keyspace", e); } catch (TException e) { log.warn("Failed to reset keyspace", e); } } pool.returnObjectUnsafe(SYSTEM_KS, conn); } } private KsDef ensureKeyspaceExists(String keyspaceName) throws NotFoundException, InvalidRequestException, TException, SchemaDisagreementException, StorageException { CTConnection connection = null; try { connection = pool.borrowObject(SYSTEM_KS); Cassandra.Client client = connection.getClient(); try { // Side effect: throws Exception if keyspaceName doesn't exist client.set_keyspace(keyspaceName); // Don't remove client.set_keyspace(SYSTEM_KS); log.debug("Found existing keyspace {}", keyspaceName); } catch (InvalidRequestException e) { // Keyspace didn't exist; create it log.debug("Creating keyspace {}...", keyspaceName); KsDef ksdef = new KsDef().setName(keyspaceName) .setCf_defs(new LinkedList()) // cannot be null but can be empty .setStrategy_class("org.apache.cassandra.locator.SimpleStrategy") .setStrategy_options(ImmutableMap.of("replication_factor", String.valueOf(replicationFactor))); client.set_keyspace(SYSTEM_KS); try { client.system_add_keyspace(ksdef); log.debug("Created keyspace {}", keyspaceName); } catch (InvalidRequestException ire) { log.error("system_add_keyspace failed for keyspace=" + keyspaceName, ire); throw ire; } } return client.describe_keyspace(keyspaceName); } catch (Exception e) { throw new TemporaryStorageException(e); } finally { pool.returnObjectUnsafe(SYSTEM_KS, connection); } } private void ensureColumnFamilyExists(String ksName, String cfName) throws StorageException { ensureColumnFamilyExists(ksName, cfName, "org.apache.cassandra.db.marshal.BytesType"); } private void ensureColumnFamilyExists(String ksName, String cfName, String comparator) throws StorageException { CTConnection conn = null; try { KsDef keyspaceDef = ensureKeyspaceExists(ksName); conn = pool.borrowObject(ksName); Cassandra.Client client = conn.getClient(); log.debug("Looking up metadata on keyspace {}...", ksName); boolean foundColumnFamily = false; for (CfDef cfDef : keyspaceDef.getCf_defs()) { String curCfName = cfDef.getName(); if (curCfName.equals(cfName)) foundColumnFamily = true; } if (!foundColumnFamily) { createColumnFamily(client, ksName, cfName, comparator); } else { log.debug("Keyspace {} and ColumnFamily {} were found.", ksName, cfName); } } catch (SchemaDisagreementException e) { throw new TemporaryStorageException(e); } catch (Exception e) { throw new PermanentStorageException(e); } finally { pool.returnObjectUnsafe(ksName, conn); } } private void createColumnFamily(Cassandra.Client client, String ksName, String cfName, String comparator) throws StorageException { CfDef createColumnFamily = new CfDef(); createColumnFamily.setName(cfName); createColumnFamily.setKeyspace(ksName); createColumnFamily.setComparator_type(comparator); ImmutableMap.Builder compressionOptions = new ImmutableMap.Builder(); if (compressionEnabled) { compressionOptions.put("sstable_compression", compressionClass) .put("chunk_length_kb", Integer.toString(compressionChunkSizeKB)); } createColumnFamily.setCompression_options(compressionOptions.build()); // Hard-coded caching settings if (cfName.startsWith(Backend.EDGESTORE_NAME)) { createColumnFamily.setCaching("keys_only"); } else if (cfName.startsWith(Backend.VERTEXINDEX_STORE_NAME)) { createColumnFamily.setCaching("rows_only"); } log.debug("Adding column family {} to keyspace {}...", cfName, ksName); try { client.system_add_column_family(createColumnFamily); } catch (SchemaDisagreementException e) { throw new TemporaryStorageException("Error in setting up column family", e); } catch (Exception e) { throw new PermanentStorageException(e); } log.debug("Added column family {} to keyspace {}.", cfName, ksName); } @Override public Map getCompressionOptions(String cf) throws StorageException { CTConnection conn = null; Map result = null; try { conn = pool.borrowObject(keySpaceName); Cassandra.Client client = conn.getClient(); KsDef ksDef = client.describe_keyspace(keySpaceName); for (CfDef cfDef : ksDef.getCf_defs()) { if (null != cfDef && cfDef.getName().equals(cf)) { result = cfDef.getCompression_options(); break; } } return result; } catch (InvalidRequestException e) { log.debug("Keyspace {} does not exist", keySpaceName); return null; } catch (Exception e) { throw new TemporaryStorageException(e); } finally { pool.returnObjectUnsafe(keySpaceName, conn); } } private void closePool() { /* * pool.close() does not affect borrowed connections. * * Connections currently borrowed by some thread which are * talking to the old host will eventually be destroyed by * CTConnectionFactory#validateObject() returning false when * those connections are returned to the pool. */ try { pool.close(); log.info("Closed Thrift connection pooler."); } catch (Exception e) { log.warn("Failed to close connection pooler. " + "We might be leaking Cassandra connections.", e); // There's still hope: CTConnectionFactory#validateObject() // will be called on borrow() and might tear down the // connections that close() failed to tear down } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy