/* * 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.usergrid.persistence.graph.serialization.impl.shard.impl; import java.util.Iterator; import java.util.concurrent.Callable; import java.util.concurrent.RejectedExecutionException; import javax.annotation.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.usergrid.persistence.core.consistency.TimeService; import org.apache.usergrid.persistence.core.scope.ApplicationScope; import org.apache.usergrid.persistence.graph.MarkedEdge; import org.apache.usergrid.persistence.graph.serialization.impl.shard.AsyncTaskExecutor; import org.apache.usergrid.persistence.graph.serialization.impl.shard.DirectedEdgeMeta; import org.apache.usergrid.persistence.graph.serialization.impl.shard.EdgeShardSerialization; import org.apache.usergrid.persistence.graph.serialization.impl.shard.Shard; import org.apache.usergrid.persistence.graph.serialization.impl.shard.ShardEntryGroup; import org.apache.usergrid.persistence.graph.serialization.impl.shard.ShardGroupDeletion; import com.google.common.base.Preconditions; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.inject.Inject; import com.google.inject.Singleton; import com.netflix.astyanax.MutationBatch; import com.netflix.astyanax.connectionpool.exceptions.ConnectionException; /** * Implementation of the shard group deletion task */ @Singleton public class ShardGroupDeletionImpl implements ShardGroupDeletion { private static final Logger logger = LoggerFactory.getLogger( ShardGroupDeletionImpl.class ); private final ListeningExecutorService asyncTaskExecutor; private final EdgeShardSerialization edgeShardSerialization; private final TimeService timeService; @Inject public ShardGroupDeletionImpl( final AsyncTaskExecutor asyncTaskExecutor, final EdgeShardSerialization edgeShardSerialization, final TimeService timeService ) { this.edgeShardSerialization = edgeShardSerialization; this.timeService = timeService; this.asyncTaskExecutor = asyncTaskExecutor.getExecutorService(); } @Override public ListenableFuture<DeleteResult> maybeDeleteShard( final ApplicationScope applicationScope, final DirectedEdgeMeta directedEdgeMeta, final ShardEntryGroup shardEntryGroup, final Iterator<MarkedEdge> edgeIterator ) { /** * Try and submit. During back pressure, we may not be able to submit, that's ok. Better to drop than to * hose the system */ final ListenableFuture<DeleteResult> future; try { future = asyncTaskExecutor .submit( new ShardDeleteTask( applicationScope, directedEdgeMeta, shardEntryGroup, edgeIterator ) ); } catch ( RejectedExecutionException ree ) { //ignore, if this happens we don't care, we're saturated, we can check later logger.info( "Rejected shard delete check for group {}", edgeIterator ); return Futures.immediateFuture( DeleteResult.NOT_CHECKED ); } /** * Log our success or failures for debugging purposes */ Futures.addCallback( future, new FutureCallback<DeleteResult>() { @Override public void onSuccess( @Nullable final ShardGroupDeletion.DeleteResult result ) { if (logger.isTraceEnabled()) logger.trace( "Successfully completed delete of task {}", result ); } @Override public void onFailure( final Throwable t ) { logger.error( "Unable to perform shard delete audit. Exception is ", t ); } } ); return future; } /** * Execute the logic for the delete */ private DeleteResult maybeDeleteShardInternal( final ApplicationScope applicationScope, final DirectedEdgeMeta directedEdgeMeta, final ShardEntryGroup shardEntryGroup, final Iterator<MarkedEdge> edgeIterator ) { //Use ths to TEMPORARILY remove deletes from occurring //return DeleteResult.NO_OP; if (logger.isTraceEnabled()) logger.trace( "Beginning audit of shard group {}", shardEntryGroup ); /** * Compaction is pending, we cannot check it */ if ( shardEntryGroup.isCompactionPending() ) { if (logger.isTraceEnabled()) logger.trace( "Shard group {} is compacting, not auditing group", shardEntryGroup ); return DeleteResult.COMPACTION_PENDING; } if (logger.isTraceEnabled()) logger.trace( "Shard group {} has no compaction pending", shardEntryGroup ); final long currentTime = timeService.getCurrentTime(); if ( shardEntryGroup.isNew( currentTime ) ) { if (logger.isTraceEnabled()) logger.trace( "Shard group {} contains a shard that is is too new, not auditing group", shardEntryGroup ); return DeleteResult.TOO_NEW; } if (logger.isTraceEnabled()) { logger.trace("Shard group {} has passed the delta timeout at {}", shardEntryGroup, currentTime); } /** * We have edges, and therefore cannot delete them */ if ( edgeIterator.hasNext() ) { if (logger.isTraceEnabled()) { logger.trace("Shard group {} has edges, not deleting", shardEntryGroup); } return DeleteResult.CONTAINS_EDGES; } if (logger.isTraceEnabled()) { logger.trace("Shard group {} has no edges continuing to delete", shardEntryGroup, currentTime); } //now we can proceed based on the shard meta state and we don't have any edge DeleteResult result = DeleteResult.NO_OP; MutationBatch rollup = null; for ( final Shard shard : shardEntryGroup.getReadShards() ) { //skip the min shard if(shard.isMinShard()){ if (logger.isTraceEnabled()) { logger.trace("Shard {} in group {} is the minimum, not deleting", shard, shardEntryGroup); } continue; } //The shard is not compacted, we cannot remove it. This should never happen, a bit of an "oh shit" scenario. //the isCompactionPending should return false in this case if(!shard.isCompacted()){ logger.warn( "Shard {} in group {} is not compacted yet was checked. Short circuiting", shard, shardEntryGroup ); return DeleteResult.NO_OP; } if(shard.isDeleted()){ if(logger.isTraceEnabled()){ logger.trace("Shard {} already deleted. Short circuiting.", shard); } return DeleteResult.NO_OP; } shard.setDeleted(true); final MutationBatch setShardDeletedFlagMutation = edgeShardSerialization.writeShardMeta(applicationScope, shard, directedEdgeMeta ); /* Previously the below was used for actually deleting the shard vs.a marking strategy with read filtering final MutationBatch shardRemovalMutation = edgeShardSerialization.removeShardMeta( applicationScope, shard, directedEdgeMeta ); */ if ( rollup == null ) { rollup = setShardDeletedFlagMutation; } else { rollup.mergeShallow( setShardDeletedFlagMutation ); } result = DeleteResult.DELETED; logger.info( "Marking shard {} as deleted in group {}", shard, shardEntryGroup ); } if( rollup != null) { try { rollup.execute(); } catch ( ConnectionException e ) { logger.error( "Unable to execute shard deletion", e ); throw new RuntimeException( "Unable to execute shard deletion", e ); } } if (logger.isTraceEnabled()) { logger.trace("Completed auditing shard group {}", shardEntryGroup); } return result; } /** * Glue for executing the task */ private final class ShardDeleteTask implements Callable<DeleteResult> { private final ApplicationScope applicationScope; private final DirectedEdgeMeta directedEdgeMeta; private final ShardEntryGroup shardEntryGroup; private final Iterator<MarkedEdge> edgeIterator; private ShardDeleteTask( final ApplicationScope applicationScope, final DirectedEdgeMeta directedEdgeMeta, final ShardEntryGroup shardEntryGroup, final Iterator<MarkedEdge> edgeIterator ) { this.applicationScope = applicationScope; this.directedEdgeMeta = directedEdgeMeta; this.shardEntryGroup = shardEntryGroup; this.edgeIterator = edgeIterator; } @Override public DeleteResult call() throws Exception { return maybeDeleteShardInternal( applicationScope, directedEdgeMeta, shardEntryGroup, edgeIterator ); } } }