/astyanax-contrib/src/main/java/com/netflix/astyanax/contrib/dualwrites/DualWritesKeyspace.java
Java | 529 lines | 422 code | 79 blank | 28 comment | 21 complexity | b5579fbc98bcf688273225185c6badf0 MD5 | raw file
- /**
- * Copyright 2013 Netflix, Inc.
- *
- * Licensed 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 com.netflix.astyanax.contrib.dualwrites;
- import java.util.Collections;
- import java.util.List;
- import java.util.Map;
- import java.util.Properties;
- import java.util.concurrent.atomic.AtomicBoolean;
- import java.util.concurrent.atomic.AtomicReference;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- import com.google.common.util.concurrent.ListenableFuture;
- import com.netflix.astyanax.AstyanaxConfiguration;
- import com.netflix.astyanax.ColumnMutation;
- import com.netflix.astyanax.Execution;
- import com.netflix.astyanax.Keyspace;
- import com.netflix.astyanax.MutationBatch;
- import com.netflix.astyanax.SerializerPackage;
- import com.netflix.astyanax.connectionpool.ConnectionPool;
- import com.netflix.astyanax.connectionpool.Operation;
- import com.netflix.astyanax.connectionpool.OperationResult;
- import com.netflix.astyanax.connectionpool.TokenRange;
- import com.netflix.astyanax.connectionpool.exceptions.ConnectionException;
- import com.netflix.astyanax.connectionpool.exceptions.OperationException;
- import com.netflix.astyanax.cql.CqlStatement;
- import com.netflix.astyanax.ddl.KeyspaceDefinition;
- import com.netflix.astyanax.ddl.SchemaChangeResult;
- import com.netflix.astyanax.model.ColumnFamily;
- import com.netflix.astyanax.partitioner.Partitioner;
- import com.netflix.astyanax.query.ColumnFamilyQuery;
- import com.netflix.astyanax.retry.RetryPolicy;
- import com.netflix.astyanax.serializers.UnknownComparatorException;
- /**
- * Main class that orchistrates all the dual writes. It wraps the 2 keyspaces (source and destination)
- * and appropriately forwards reads and writes to it as dictated by updates using the {@link DualWritesUpdateListener}
- *
- * Note that if dual writes are enabled then the writes are sent to the {@link DualWritesMutationBatch} or {@link DualWritesColumnMutation}
- * classes appropriately.
- *
- * The reads are always served from the primary data source.
- *
- * @author poberai
- *
- */
- public class DualWritesKeyspace implements Keyspace, DualWritesUpdateListener {
- private static final Logger Logger = LoggerFactory.getLogger(DualWritesKeyspace.class);
-
- private final AtomicReference<KeyspacePair> ksPair = new AtomicReference<KeyspacePair>(null);
- private final AtomicBoolean dualWritesEnabled = new AtomicBoolean(false);
-
- private final DualWritesStrategy executionStrategy;
-
- public DualWritesKeyspace(DualKeyspaceMetadata dualKeyspaceSetup,
- Keyspace primaryKS, Keyspace secondaryKS,
- DualWritesStrategy execStrategy) {
- ksPair.set(new KeyspacePair(dualKeyspaceSetup, primaryKS, secondaryKS));
- executionStrategy = execStrategy;
- }
-
- private Keyspace getPrimaryKS() {
- return ksPair.get().getPrimaryKS();
- }
-
- public DualKeyspaceMetadata getDualKeyspaceMetadata() {
- return ksPair.get().getDualKSMetadata();
- }
- @Override
- public AstyanaxConfiguration getConfig() {
- return getPrimaryKS().getConfig();
- }
- @Override
- public String getKeyspaceName() {
- return getPrimaryKS().getKeyspaceName();
- }
- @Override
- public Partitioner getPartitioner() throws ConnectionException {
- return getPrimaryKS().getPartitioner();
- }
- @Override
- public String describePartitioner() throws ConnectionException {
- return getPrimaryKS().describePartitioner();
- }
- @Override
- public List<TokenRange> describeRing() throws ConnectionException {
- return getPrimaryKS().describeRing();
- }
- @Override
- public List<TokenRange> describeRing(String dc) throws ConnectionException {
- return getPrimaryKS().describeRing(dc);
- }
- @Override
- public List<TokenRange> describeRing(String dc, String rack) throws ConnectionException {
- return getPrimaryKS().describeRing(dc, rack);
- }
- @Override
- public List<TokenRange> describeRing(boolean cached) throws ConnectionException {
- return getPrimaryKS().describeRing(cached);
- }
- @Override
- public KeyspaceDefinition describeKeyspace() throws ConnectionException {
- return getPrimaryKS().describeKeyspace();
- }
- @Override
- public Properties getKeyspaceProperties() throws ConnectionException {
- return getPrimaryKS().getKeyspaceProperties();
- }
- @Override
- public Properties getColumnFamilyProperties(String columnFamily) throws ConnectionException {
- return getPrimaryKS().getColumnFamilyProperties(columnFamily);
- }
- @Override
- public SerializerPackage getSerializerPackage(String cfName, boolean ignoreErrors) throws ConnectionException, UnknownComparatorException {
- return getPrimaryKS().getSerializerPackage(cfName, ignoreErrors);
- }
- @Override
- public MutationBatch prepareMutationBatch() {
- if (dualWritesEnabled.get()) {
- KeyspacePair pair = ksPair.get();
- return new DualWritesMutationBatch(
- pair.getDualKSMetadata(),
- pair.getPrimaryKS().prepareMutationBatch(),
- pair.getSecondaryKS().prepareMutationBatch(),
- executionStrategy);
- } else {
- return getPrimaryKS().prepareMutationBatch();
- }
- }
- @Override
- public <K, C> ColumnFamilyQuery<K, C> prepareQuery(ColumnFamily<K, C> cf) {
- return getPrimaryKS().prepareQuery(cf);
- }
- @Override
- public <K, C> ColumnMutation prepareColumnMutation(ColumnFamily<K, C> columnFamily, K rowKey, C column) {
- KeyspacePair pair = ksPair.get();
- if (dualWritesEnabled.get()) {
-
- WriteMetadata md = new WriteMetadata(pair.getDualKSMetadata(), columnFamily.getName(), rowKey.toString());
- return new DualWritesColumnMutation(md,
- pair.getPrimaryKS() .prepareColumnMutation(columnFamily, rowKey, column),
- pair.getSecondaryKS().prepareColumnMutation(columnFamily, rowKey, column),
- executionStrategy);
- } else {
- return pair.getPrimaryKS().prepareColumnMutation(columnFamily, rowKey, column);
- }
- }
- @Override
- public <K, C> OperationResult<Void> truncateColumnFamily(final ColumnFamily<K, C> columnFamily) throws OperationException, ConnectionException {
-
- return execDualKeyspaceOperation(new KeyspaceOperation<Void>() {
- @Override
- public OperationResult<Void> exec(Keyspace ks) throws ConnectionException {
- return ks.truncateColumnFamily(columnFamily);
- }
- });
- }
- @Override
- public OperationResult<Void> truncateColumnFamily(final String columnFamily) throws ConnectionException {
- return execDualKeyspaceOperation(new KeyspaceOperation<Void>() {
- @Override
- public OperationResult<Void> exec(Keyspace ks) throws ConnectionException {
- return ks.truncateColumnFamily(columnFamily);
- }
- });
- }
- @Override
- public OperationResult<Void> testOperation(final Operation<?, ?> operation) throws ConnectionException {
- return execDualKeyspaceOperation(new KeyspaceOperation<Void>() {
- @Override
- public OperationResult<Void> exec(Keyspace ks) throws ConnectionException {
- return ks.testOperation(operation);
- }
- });
- }
- @Override
- public OperationResult<Void> testOperation(final Operation<?, ?> operation, RetryPolicy retry) throws ConnectionException {
- return execDualKeyspaceOperation(new KeyspaceOperation<Void>() {
- @Override
- public OperationResult<Void> exec(Keyspace ks) throws ConnectionException {
- return ks.testOperation(operation);
- }
- });
- }
- @Override
- public <K, C> OperationResult<SchemaChangeResult> createColumnFamily(final ColumnFamily<K, C> columnFamily, final Map<String, Object> options) throws ConnectionException {
- return execDualKeyspaceOperation(new KeyspaceOperation<SchemaChangeResult>() {
- @Override
- public OperationResult<SchemaChangeResult> exec(Keyspace ks) throws ConnectionException {
- return ks.createColumnFamily(columnFamily, options);
- }
- });
- }
- @Override
- public OperationResult<SchemaChangeResult> createColumnFamily(final Properties props) throws ConnectionException {
- return execDualKeyspaceOperation(new KeyspaceOperation<SchemaChangeResult>() {
- @Override
- public OperationResult<SchemaChangeResult> exec(Keyspace ks) throws ConnectionException {
- return ks.createColumnFamily(props);
- }
- });
- }
- @Override
- public OperationResult<SchemaChangeResult> createColumnFamily(final Map<String, Object> options) throws ConnectionException {
- return execDualKeyspaceOperation(new KeyspaceOperation<SchemaChangeResult>() {
- @Override
- public OperationResult<SchemaChangeResult> exec(Keyspace ks) throws ConnectionException {
- return ks.createColumnFamily(options);
- }
- });
- }
- @Override
- public <K, C> OperationResult<SchemaChangeResult> updateColumnFamily(final ColumnFamily<K, C> columnFamily, final Map<String, Object> options) throws ConnectionException {
- return execDualKeyspaceOperation(new KeyspaceOperation<SchemaChangeResult>() {
- @Override
- public OperationResult<SchemaChangeResult> exec(Keyspace ks) throws ConnectionException {
- return ks.updateColumnFamily(columnFamily, options);
- }
- });
- }
- @Override
- public OperationResult<SchemaChangeResult> updateColumnFamily(final Properties props) throws ConnectionException {
- return execDualKeyspaceOperation(new KeyspaceOperation<SchemaChangeResult>() {
- @Override
- public OperationResult<SchemaChangeResult> exec(Keyspace ks) throws ConnectionException {
- return ks.updateColumnFamily(props);
- }
- });
- }
- @Override
- public OperationResult<SchemaChangeResult> updateColumnFamily(final Map<String, Object> options) throws ConnectionException {
- return execDualKeyspaceOperation(new KeyspaceOperation<SchemaChangeResult>() {
- @Override
- public OperationResult<SchemaChangeResult> exec(Keyspace ks) throws ConnectionException {
- return ks.updateColumnFamily(options);
- }
- });
- }
- @Override
- public OperationResult<SchemaChangeResult> dropColumnFamily(final String columnFamilyName) throws ConnectionException {
- return execDualKeyspaceOperation(new KeyspaceOperation<SchemaChangeResult>() {
- @Override
- public OperationResult<SchemaChangeResult> exec(Keyspace ks) throws ConnectionException {
- return ks.dropColumnFamily(columnFamilyName);
- }
- });
- }
- @Override
- public <K, C> OperationResult<SchemaChangeResult> dropColumnFamily(final ColumnFamily<K, C> columnFamily) throws ConnectionException {
- return execDualKeyspaceOperation(new KeyspaceOperation<SchemaChangeResult>() {
- @Override
- public OperationResult<SchemaChangeResult> exec(Keyspace ks) throws ConnectionException {
- return ks.dropColumnFamily(columnFamily);
- }
- });
- }
- @Override
- public OperationResult<SchemaChangeResult> createKeyspace(final Map<String, Object> options) throws ConnectionException {
- return execDualKeyspaceOperation(new KeyspaceOperation<SchemaChangeResult>() {
- @Override
- public OperationResult<SchemaChangeResult> exec(Keyspace ks) throws ConnectionException {
- return ks.createKeyspace(options);
- }
- });
- }
- @Override
- public OperationResult<SchemaChangeResult> createKeyspaceIfNotExists(final Map<String, Object> options) throws ConnectionException {
- return execDualKeyspaceOperation(new KeyspaceOperation<SchemaChangeResult>() {
- @Override
- public OperationResult<SchemaChangeResult> exec(Keyspace ks) throws ConnectionException {
- return ks.createKeyspaceIfNotExists(options);
- }
- });
- }
- @Override
- public OperationResult<SchemaChangeResult> createKeyspace(final Properties properties) throws ConnectionException {
- return execDualKeyspaceOperation(new KeyspaceOperation<SchemaChangeResult>() {
- @Override
- public OperationResult<SchemaChangeResult> exec(Keyspace ks) throws ConnectionException {
- return ks.createKeyspace(properties);
- }
- });
- }
- @Override
- public OperationResult<SchemaChangeResult> createKeyspaceIfNotExists(final Properties properties) throws ConnectionException {
- return execDualKeyspaceOperation(new KeyspaceOperation<SchemaChangeResult>() {
- @Override
- public OperationResult<SchemaChangeResult> exec(Keyspace ks) throws ConnectionException {
- return ks.createKeyspaceIfNotExists(properties);
- }
- });
- }
- @Override
- public OperationResult<SchemaChangeResult> createKeyspace(final Map<String, Object> options, final Map<ColumnFamily, Map<String, Object>> cfs) throws ConnectionException {
- return execDualKeyspaceOperation(new KeyspaceOperation<SchemaChangeResult>() {
- @Override
- public OperationResult<SchemaChangeResult> exec(Keyspace ks) throws ConnectionException {
- return ks.createKeyspace(options, cfs);
- }
- });
- }
- @Override
- public OperationResult<SchemaChangeResult> createKeyspaceIfNotExists(final Map<String, Object> options, final Map<ColumnFamily, Map<String, Object>> cfs) throws ConnectionException {
- return execDualKeyspaceOperation(new KeyspaceOperation<SchemaChangeResult>() {
- @Override
- public OperationResult<SchemaChangeResult> exec(Keyspace ks) throws ConnectionException {
- return ks.createKeyspaceIfNotExists(options, cfs);
- }
- });
- }
- @Override
- public OperationResult<SchemaChangeResult> updateKeyspace(final Map<String, Object> options) throws ConnectionException {
- return execDualKeyspaceOperation(new KeyspaceOperation<SchemaChangeResult>() {
- @Override
- public OperationResult<SchemaChangeResult> exec(Keyspace ks) throws ConnectionException {
- return ks.updateKeyspace(options);
- }
- });
- }
- @Override
- public OperationResult<SchemaChangeResult> updateKeyspace(final Properties props) throws ConnectionException {
- return execDualKeyspaceOperation(new KeyspaceOperation<SchemaChangeResult>() {
- @Override
- public OperationResult<SchemaChangeResult> exec(Keyspace ks) throws ConnectionException {
- return ks.updateKeyspace(props);
- }
- });
- }
- @Override
- public OperationResult<SchemaChangeResult> dropKeyspace() throws ConnectionException {
- return execDualKeyspaceOperation(new KeyspaceOperation<SchemaChangeResult>() {
- @Override
- public OperationResult<SchemaChangeResult> exec(Keyspace ks) throws ConnectionException {
- return ks.dropKeyspace();
- }
- });
- }
- @Override
- public Map<String, List<String>> describeSchemaVersions() throws ConnectionException {
- return getPrimaryKS().describeSchemaVersions();
- }
- @Override
- public CqlStatement prepareCqlStatement() {
- KeyspacePair pair = ksPair.get();
- CqlStatement primaryStmt = pair.getPrimaryKS().prepareCqlStatement();
- CqlStatement secondaryStmt = pair.getSecondaryKS().prepareCqlStatement();
- return new DualWritesCqlStatement(primaryStmt, secondaryStmt, executionStrategy, pair.getDualKSMetadata());
- }
- @Override
- public ConnectionPool<?> getConnectionPool() throws ConnectionException {
- return getPrimaryKS().getConnectionPool();
- }
- private class KeyspacePair {
-
- private final DualKeyspaceMetadata dualKeyspaceMetadata;
- private final Keyspace ksPrimary;
- private final Keyspace ksSecondary;
-
- private KeyspacePair(final DualKeyspaceMetadata dualKeyspaceSetup, final Keyspace pKS, final Keyspace sKS) {
- dualKeyspaceMetadata = dualKeyspaceSetup;
- ksPrimary = pKS;
- ksSecondary = sKS;
- }
-
- private Keyspace getPrimaryKS() {
- return ksPrimary;
- }
- private Keyspace getSecondaryKS() {
- return ksSecondary;
- }
- private DualKeyspaceMetadata getDualKSMetadata() {
- return dualKeyspaceMetadata;
- }
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + ((ksPrimary == null) ? 0 : ksPrimary.hashCode());
- result = prime * result + ((ksSecondary == null) ? 0 : ksSecondary.hashCode());
- result = prime * result + ((dualKeyspaceMetadata == null) ? 0 : dualKeyspaceMetadata.hashCode());
- return result;
- }
- @Override
- public boolean equals(Object obj) {
-
- if (this == obj) return true;
- if (obj == null) return false;
- if (getClass() != obj.getClass()) return false;
-
- KeyspacePair other = (KeyspacePair) obj;
- boolean equals = true;
-
- equals &= (ksPrimary == null) ? (other.ksPrimary == null) : (ksPrimary.equals(other.ksPrimary));
- equals &= (ksSecondary == null) ? (other.ksSecondary == null) : (ksSecondary.equals(other.ksSecondary));
- equals &= (dualKeyspaceMetadata == null) ? (other.dualKeyspaceMetadata == null) : (dualKeyspaceMetadata.equals(other.dualKeyspaceMetadata));
-
- return equals;
- }
- }
- @Override
- public void dualWritesEnabled() {
- Logger.info("ENABLING dual writes for dual keyspace setup: " + ksPair.get().getDualKSMetadata());
- dualWritesEnabled.set(true);
- }
- @Override
- public void dualWritesDisabled() {
- Logger.info("DISABLING dual writes for dual keyspace setup: " + ksPair.get().getDualKSMetadata());
- dualWritesEnabled.set(false);
- }
- @Override
- public void flipPrimaryAndSecondary() {
-
- // Check that the expected state is actually reverse of what the destination state should be
- KeyspacePair currentPair = ksPair.get();
- DualKeyspaceMetadata currentKeyspaceSetup = currentPair.getDualKSMetadata();
-
- DualKeyspaceMetadata newDualKeyspaceSetup =
- new DualKeyspaceMetadata(currentKeyspaceSetup.getSecondaryCluster(), currentKeyspaceSetup.getSecondaryKeyspaceName(),
- currentKeyspaceSetup.getPrimaryCluster(), currentKeyspaceSetup.getPrimaryKeyspaceName());
- KeyspacePair newPair =
- new KeyspacePair(newDualKeyspaceSetup, currentPair.getSecondaryKS(), currentPair.getPrimaryKS());
- boolean success = ksPair.compareAndSet(currentPair, newPair);
-
- if (success) {
- Logger.info("Successfully flipped to new dual keyspace setup" + ksPair.get().getDualKSMetadata());
- } else {
- Logger.info("Could not flip keyspace pair: " + currentPair + " to new pair: " + newPair);
- }
- }
-
- private abstract class SimpleSyncExec<R> implements Execution<R> {
- @Override
- public ListenableFuture<OperationResult<R>> executeAsync() throws ConnectionException {
- throw new RuntimeException("executeAsync not implemented for SimpleSyncExec");
- }
- }
-
- private interface KeyspaceOperation<R> {
- OperationResult<R> exec(Keyspace ks) throws ConnectionException;
- }
-
- private <R> OperationResult<R> execDualKeyspaceOperation(final KeyspaceOperation<R> ksOperation) throws ConnectionException {
-
- final KeyspacePair pair = ksPair.get();
-
- final Execution<R> exec1 = new SimpleSyncExec<R>() {
- @Override
- public OperationResult<R> execute() throws ConnectionException {
- return ksOperation.exec(pair.getPrimaryKS());
- }
- };
- final Execution<R> exec2 = new SimpleSyncExec<R>() {
- @Override
- public OperationResult<R> execute() throws ConnectionException {
- return ksOperation.exec(pair.getSecondaryKS());
- }
- };
-
- WriteMetadata writeMd = new WriteMetadata(pair.getDualKSMetadata(), null, null);
- return executionStrategy.wrapExecutions(exec1, exec2, Collections.singletonList(writeMd)).execute();
- }
- }