|  | @@ -67,6 +67,7 @@ import org.elasticsearch.common.lucene.index.ElasticsearchDirectoryReader;
 | 
	
		
			
				|  |  |  import org.elasticsearch.common.metrics.CounterMetric;
 | 
	
		
			
				|  |  |  import org.elasticsearch.common.metrics.MeanMetric;
 | 
	
		
			
				|  |  |  import org.elasticsearch.common.settings.Settings;
 | 
	
		
			
				|  |  | +import org.elasticsearch.common.unit.ByteSizeValue;
 | 
	
		
			
				|  |  |  import org.elasticsearch.common.unit.TimeValue;
 | 
	
		
			
				|  |  |  import org.elasticsearch.common.util.BigArrays;
 | 
	
		
			
				|  |  |  import org.elasticsearch.common.util.concurrent.AbstractRunnable;
 | 
	
	
		
			
				|  | @@ -157,6 +158,7 @@ import java.nio.charset.StandardCharsets;
 | 
	
		
			
				|  |  |  import java.util.ArrayList;
 | 
	
		
			
				|  |  |  import java.util.Collections;
 | 
	
		
			
				|  |  |  import java.util.EnumSet;
 | 
	
		
			
				|  |  | +import java.util.HashSet;
 | 
	
		
			
				|  |  |  import java.util.List;
 | 
	
		
			
				|  |  |  import java.util.Locale;
 | 
	
		
			
				|  |  |  import java.util.Map;
 | 
	
	
		
			
				|  | @@ -268,6 +270,7 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      private final AtomicLong lastSearcherAccess = new AtomicLong();
 | 
	
		
			
				|  |  |      private final AtomicReference<Translog.Location> pendingRefreshLocation = new AtomicReference<>();
 | 
	
		
			
				|  |  | +    private volatile boolean useRetentionLeasesInPeerRecovery;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      public IndexShard(
 | 
	
		
			
				|  |  |              final ShardRouting shardRouting,
 | 
	
	
		
			
				|  | @@ -364,6 +367,7 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl
 | 
	
		
			
				|  |  |          refreshListeners = buildRefreshListeners();
 | 
	
		
			
				|  |  |          lastSearcherAccess.set(threadPool.relativeTimeInMillis());
 | 
	
		
			
				|  |  |          persistMetadata(path, indexSettings, shardRouting, null, logger);
 | 
	
		
			
				|  |  | +        this.useRetentionLeasesInPeerRecovery = replicationTracker.hasAllPeerRecoveryRetentionLeases();
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      public ThreadPool getThreadPool() {
 | 
	
	
		
			
				|  | @@ -600,6 +604,17 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl
 | 
	
		
			
				|  |  |          if (newRouting.equals(currentRouting) == false) {
 | 
	
		
			
				|  |  |              indexEventListener.shardRoutingChanged(this, currentRouting, newRouting);
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        if (indexSettings.isSoftDeleteEnabled() && useRetentionLeasesInPeerRecovery == false) {
 | 
	
		
			
				|  |  | +            final RetentionLeases retentionLeases = replicationTracker.getRetentionLeases();
 | 
	
		
			
				|  |  | +            final Set<ShardRouting> shardRoutings = new HashSet<>(routingTable.getShards());
 | 
	
		
			
				|  |  | +            shardRoutings.addAll(routingTable.assignedShards()); // include relocation targets
 | 
	
		
			
				|  |  | +            if (shardRoutings.stream().allMatch(
 | 
	
		
			
				|  |  | +                shr -> shr.assignedToNode() && retentionLeases.contains(ReplicationTracker.getPeerRecoveryRetentionLeaseId(shr)))) {
 | 
	
		
			
				|  |  | +                useRetentionLeasesInPeerRecovery = true;
 | 
	
		
			
				|  |  | +                turnOffTranslogRetention();
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      /**
 | 
	
	
		
			
				|  | @@ -1877,38 +1892,63 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl
 | 
	
		
			
				|  |  |      public void onSettingsChanged() {
 | 
	
		
			
				|  |  |          Engine engineOrNull = getEngineOrNull();
 | 
	
		
			
				|  |  |          if (engineOrNull != null) {
 | 
	
		
			
				|  |  | -            engineOrNull.onSettingsChanged();
 | 
	
		
			
				|  |  | +            final boolean useRetentionLeasesInPeerRecovery = this.useRetentionLeasesInPeerRecovery;
 | 
	
		
			
				|  |  | +            engineOrNull.onSettingsChanged(
 | 
	
		
			
				|  |  | +                useRetentionLeasesInPeerRecovery ? TimeValue.MINUS_ONE : indexSettings.getTranslogRetentionAge(),
 | 
	
		
			
				|  |  | +                useRetentionLeasesInPeerRecovery ? new ByteSizeValue(-1) : indexSettings.getTranslogRetentionSize(),
 | 
	
		
			
				|  |  | +                indexSettings.getSoftDeleteRetentionOperations()
 | 
	
		
			
				|  |  | +            );
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +    private void turnOffTranslogRetention() {
 | 
	
		
			
				|  |  | +        logger.debug("turn off the translog retention for the replication group {} " +
 | 
	
		
			
				|  |  | +            "as it starts using retention leases exclusively in peer recoveries", shardId);
 | 
	
		
			
				|  |  | +        // Off to the generic threadPool as pruning the delete tombstones can be expensive.
 | 
	
		
			
				|  |  | +        threadPool.generic().execute(new AbstractRunnable() {
 | 
	
		
			
				|  |  | +            @Override
 | 
	
		
			
				|  |  | +            public void onFailure(Exception e) {
 | 
	
		
			
				|  |  | +                if (state != IndexShardState.CLOSED) {
 | 
	
		
			
				|  |  | +                    logger.warn("failed to turn off translog retention", e);
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            @Override
 | 
	
		
			
				|  |  | +            protected void doRun() {
 | 
	
		
			
				|  |  | +                onSettingsChanged();
 | 
	
		
			
				|  |  | +                trimTranslog();
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        });
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |      /**
 | 
	
		
			
				|  |  |       * Acquires a lock on the translog files and Lucene soft-deleted documents to prevent them from being trimmed
 | 
	
		
			
				|  |  |       */
 | 
	
		
			
				|  |  | -    public Closeable acquireRetentionLock() {
 | 
	
		
			
				|  |  | -        return getEngine().acquireRetentionLock();
 | 
	
		
			
				|  |  | +    public Closeable acquireHistoryRetentionLock(Engine.HistorySource source) {
 | 
	
		
			
				|  |  | +        return getEngine().acquireHistoryRetentionLock(source);
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      /**
 | 
	
		
			
				|  |  |       * Returns the estimated number of history operations whose seq# at least the provided seq# in this shard.
 | 
	
		
			
				|  |  |       */
 | 
	
		
			
				|  |  | -    public int estimateNumberOfHistoryOperations(String source, long startingSeqNo) throws IOException {
 | 
	
		
			
				|  |  | -        return getEngine().estimateNumberOfHistoryOperations(source, mapperService, startingSeqNo);
 | 
	
		
			
				|  |  | +    public int estimateNumberOfHistoryOperations(String reason, Engine.HistorySource source, long startingSeqNo) throws IOException {
 | 
	
		
			
				|  |  | +        return getEngine().estimateNumberOfHistoryOperations(reason, source, mapperService, startingSeqNo);
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      /**
 | 
	
		
			
				|  |  |       * Creates a new history snapshot for reading operations since the provided starting seqno (inclusive).
 | 
	
		
			
				|  |  |       * The returned snapshot can be retrieved from either Lucene index or translog files.
 | 
	
		
			
				|  |  |       */
 | 
	
		
			
				|  |  | -    public Translog.Snapshot getHistoryOperations(String source, long startingSeqNo) throws IOException {
 | 
	
		
			
				|  |  | -        return getEngine().readHistoryOperations(source, mapperService, startingSeqNo);
 | 
	
		
			
				|  |  | +    public Translog.Snapshot getHistoryOperations(String reason, Engine.HistorySource source, long startingSeqNo) throws IOException {
 | 
	
		
			
				|  |  | +        return getEngine().readHistoryOperations(reason, source, mapperService, startingSeqNo);
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      /**
 | 
	
		
			
				|  |  |       * Checks if we have a completed history of operations since the given starting seqno (inclusive).
 | 
	
		
			
				|  |  | -     * This method should be called after acquiring the retention lock; See {@link #acquireRetentionLock()}
 | 
	
		
			
				|  |  | +     * This method should be called after acquiring the retention lock; See {@link #acquireHistoryRetentionLock(Engine.HistorySource)}
 | 
	
		
			
				|  |  |       */
 | 
	
		
			
				|  |  | -    public boolean hasCompleteHistoryOperations(String source, long startingSeqNo) throws IOException {
 | 
	
		
			
				|  |  | -        return getEngine().hasCompleteOperationHistory(source, mapperService, startingSeqNo);
 | 
	
		
			
				|  |  | +    public boolean hasCompleteHistoryOperations(String reason, Engine.HistorySource source, long startingSeqNo) throws IOException {
 | 
	
		
			
				|  |  | +        return getEngine().hasCompleteOperationHistory(reason, source, mapperService, startingSeqNo);
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      /**
 | 
	
	
		
			
				|  | @@ -2097,9 +2137,9 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl
 | 
	
		
			
				|  |  |          assert assertPrimaryMode();
 | 
	
		
			
				|  |  |          verifyNotClosed();
 | 
	
		
			
				|  |  |          ensureSoftDeletesEnabled("retention leases");
 | 
	
		
			
				|  |  | -        try (Closeable ignore = acquireRetentionLock()) {
 | 
	
		
			
				|  |  | +        try (Closeable ignore = acquireHistoryRetentionLock(Engine.HistorySource.INDEX)) {
 | 
	
		
			
				|  |  |              final long actualRetainingSequenceNumber =
 | 
	
		
			
				|  |  | -                    retainingSequenceNumber == RETAIN_ALL ? getMinRetainedSeqNo() : retainingSequenceNumber;
 | 
	
		
			
				|  |  | +                retainingSequenceNumber == RETAIN_ALL ? getMinRetainedSeqNo() : retainingSequenceNumber;
 | 
	
		
			
				|  |  |              return replicationTracker.addRetentionLease(id, actualRetainingSequenceNumber, source, listener);
 | 
	
		
			
				|  |  |          } catch (final IOException e) {
 | 
	
		
			
				|  |  |              throw new AssertionError(e);
 | 
	
	
		
			
				|  | @@ -2119,7 +2159,7 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl
 | 
	
		
			
				|  |  |          assert assertPrimaryMode();
 | 
	
		
			
				|  |  |          verifyNotClosed();
 | 
	
		
			
				|  |  |          ensureSoftDeletesEnabled("retention leases");
 | 
	
		
			
				|  |  | -        try (Closeable ignore = acquireRetentionLock()) {
 | 
	
		
			
				|  |  | +        try (Closeable ignore = acquireHistoryRetentionLock(Engine.HistorySource.INDEX)) {
 | 
	
		
			
				|  |  |              final long actualRetainingSequenceNumber =
 | 
	
		
			
				|  |  |                      retainingSequenceNumber == RETAIN_ALL ? getMinRetainedSeqNo() : retainingSequenceNumber;
 | 
	
		
			
				|  |  |              return replicationTracker.renewRetentionLease(id, actualRetainingSequenceNumber, source);
 | 
	
	
		
			
				|  | @@ -2600,6 +2640,10 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl
 | 
	
		
			
				|  |  |          return replicationTracker.getPeerRecoveryRetentionLeases();
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +    public boolean useRetentionLeasesInPeerRecovery() {
 | 
	
		
			
				|  |  | +        return useRetentionLeasesInPeerRecovery;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |      private SafeCommitInfo getSafeCommitInfo() {
 | 
	
		
			
				|  |  |          final Engine engine = getEngineOrNull();
 | 
	
		
			
				|  |  |          return engine == null ? SafeCommitInfo.EMPTY : engine.getSafeCommitInfo();
 |