/* * Copyright (c) 2012 EMC Corporation * All Rights Reserved */ package com.emc.storageos.db.client.impl; import java.util.HashMap; import java.util.Map; import java.util.UUID; import java.util.concurrent.atomic.AtomicLong; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.netflix.astyanax.ColumnListMutation; import com.netflix.astyanax.Keyspace; import com.netflix.astyanax.MutationBatch; import com.netflix.astyanax.connectionpool.exceptions.ConnectionException; import com.netflix.astyanax.connectionpool.exceptions.OperationTimeoutException; import com.netflix.astyanax.connectionpool.exceptions.TimeoutException; import com.netflix.astyanax.connectionpool.exceptions.TokenRangeOfflineException; import com.netflix.astyanax.impl.AstyanaxConfigurationImpl; import com.netflix.astyanax.model.ColumnFamily; import com.netflix.astyanax.model.ConsistencyLevel; import com.netflix.astyanax.util.TimeUUIDUtils; import com.emc.storageos.db.exceptions.DatabaseException; /** * Encapsulates batch queries for record and index updates */ public class RowMutator<T extends CompositeIndexColumnName> { private static final Logger log = LoggerFactory.getLogger(RowMutator.class); //this offset is to make sure timeuuid is unique for each data colun to resolve COP-26680 private static int TIME_STAMP_OFFSET = 1; private Map<String, Map<String, ColumnListMutation<CompositeColumnName>>> _cfRowMap; private Map<String, Map<String, ColumnListMutation<T>>> _cfIndexMap; private AtomicLong _timeStamp = new AtomicLong(); private MutationBatch _mutationBatch; private Keyspace keyspace; private boolean retryFailedWriteWithLocalQuorum = false; /** * Construct RowMutator instance for for index CF and object CF updates * * @param keyspace - cassandra keyspace object * @param retryWithLocalQuorum - true - retry once with LOCAL_QUORUM for write failure */ public RowMutator(Keyspace keyspace, boolean retryWithLocalQuorum) { this.keyspace = keyspace; long microsTimeStamp = TimeUUIDUtils.getMicrosTimeFromUUID(TimeUUIDUtils.getUniqueTimeUUIDinMicros()); this._timeStamp.set(microsTimeStamp); _mutationBatch = keyspace.prepareMutationBatch(); _mutationBatch.setTimestamp(microsTimeStamp).withAtomicBatch(true); _cfRowMap = new HashMap<String, Map<String, ColumnListMutation<CompositeColumnName>>>(); _cfIndexMap = new HashMap<String, Map<String, ColumnListMutation<T>>>(); this.retryFailedWriteWithLocalQuorum = retryWithLocalQuorum; } public UUID getTimeUUID() { return TimeUUIDUtils.getMicrosTimeUUID(_timeStamp.addAndGet(TIME_STAMP_OFFSET)); } public void resetTimeUUIDStartTime(long startTime) { _timeStamp.set(startTime); } /** * Get record row for given CF * * @param cf * @param key * @return */ public ColumnListMutation<CompositeColumnName> getRecordColumnList( ColumnFamily<String, CompositeColumnName> cf, String key) { Map<String, ColumnListMutation<CompositeColumnName>> rowMap = _cfRowMap.get(cf.getName()); if (rowMap == null) { rowMap = new HashMap<String, ColumnListMutation<CompositeColumnName>>(); _cfRowMap.put(cf.getName(), rowMap); } ColumnListMutation<CompositeColumnName> row = rowMap.get(key); if (row == null) { row = _mutationBatch.withRow(cf, key); rowMap.put(key, row); } return row; } /*** * Get index row for given CF * * @param cf * @param key * @return */ public ColumnListMutation<T> getIndexColumnList( ColumnFamily<String, T> cf, String key) { Map<String, ColumnListMutation<T>> rowMap = _cfIndexMap.get(cf.getName()); if (rowMap == null) { rowMap = new HashMap<String, ColumnListMutation<T>>(); _cfIndexMap.put(cf.getName(), rowMap); } ColumnListMutation<T> row = rowMap.get(key); if (row == null) { row = _mutationBatch.withRow(cf, key); rowMap.put(key, row); } return row; } /** * Updates record and index with atomic batch */ public void execute() { try { executeMutatorWithRetry(_mutationBatch); } catch (ConnectionException e) { throw DatabaseException.retryables.connectionFailed(e); } } /** * Retry with LOCAL_QUORUM if remote site is not reachable. See DbClientContext.checkAndResetConsistencyLevel on * how the consistency level is changed back after remote site is available again later. * * It is supposed to happen on active site only. * * @param mutator * @throws ConnectionException */ private void executeMutatorWithRetry(MutationBatch mutator) throws ConnectionException { if (mutator.isEmpty()) { return; } try { mutator.execute(); } catch (TimeoutException | TokenRangeOfflineException | OperationTimeoutException ex) { // change consistency level and retry once with LOCAL_QUORUM ConsistencyLevel currentConsistencyLevel = keyspace.getConfig().getDefaultWriteConsistencyLevel(); if (retryFailedWriteWithLocalQuorum && currentConsistencyLevel.equals(ConsistencyLevel.CL_EACH_QUORUM)) { mutator.setConsistencyLevel(ConsistencyLevel.CL_LOCAL_QUORUM); mutator.execute(); log.info("Reduce write consistency level to CL_LOCAL_QUORUM"); ((AstyanaxConfigurationImpl)keyspace.getConfig()).setDefaultWriteConsistencyLevel(ConsistencyLevel.CL_LOCAL_QUORUM); _mutationBatch.setConsistencyLevel(ConsistencyLevel.CL_LOCAL_QUORUM); } else { throw ex; } } } }