Lift retention lease expiration to index shard (#38380)
This commit lifts the control of when retention leases are expired to index shard. In this case, we move expiration to an explicit action rather than a side-effect of calling ReplicationTracker#getRetentionLeases. This explicit action is invoked on a timer. If any retention leases expire, then we hard sync the retention leases to the replicas. Otherwise, we proceed with a background sync.
This commit is contained in:
parent
4a15e2b29e
commit
b03d138122
|
@ -121,7 +121,7 @@ public class IndexService extends AbstractIndexComponent implements IndicesClust
|
||||||
private volatile AsyncRefreshTask refreshTask;
|
private volatile AsyncRefreshTask refreshTask;
|
||||||
private volatile AsyncTranslogFSync fsyncTask;
|
private volatile AsyncTranslogFSync fsyncTask;
|
||||||
private volatile AsyncGlobalCheckpointTask globalCheckpointTask;
|
private volatile AsyncGlobalCheckpointTask globalCheckpointTask;
|
||||||
private volatile AsyncRetentionLeaseBackgroundSyncTask retentionLeaseBackgroundSyncTask;
|
private volatile AsyncRetentionLeaseSyncTask retentionLeaseSyncTask;
|
||||||
|
|
||||||
// don't convert to Setting<> and register... we only set this in tests and register via a plugin
|
// don't convert to Setting<> and register... we only set this in tests and register via a plugin
|
||||||
private final String INDEX_TRANSLOG_RETENTION_CHECK_INTERVAL_SETTING = "index.translog.retention.check_interval";
|
private final String INDEX_TRANSLOG_RETENTION_CHECK_INTERVAL_SETTING = "index.translog.retention.check_interval";
|
||||||
|
@ -198,7 +198,7 @@ public class IndexService extends AbstractIndexComponent implements IndicesClust
|
||||||
this.refreshTask = new AsyncRefreshTask(this);
|
this.refreshTask = new AsyncRefreshTask(this);
|
||||||
this.trimTranslogTask = new AsyncTrimTranslogTask(this);
|
this.trimTranslogTask = new AsyncTrimTranslogTask(this);
|
||||||
this.globalCheckpointTask = new AsyncGlobalCheckpointTask(this);
|
this.globalCheckpointTask = new AsyncGlobalCheckpointTask(this);
|
||||||
this.retentionLeaseBackgroundSyncTask = new AsyncRetentionLeaseBackgroundSyncTask(this);
|
this.retentionLeaseSyncTask = new AsyncRetentionLeaseSyncTask(this);
|
||||||
rescheduleFsyncTask(indexSettings.getTranslogDurability());
|
rescheduleFsyncTask(indexSettings.getTranslogDurability());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -289,7 +289,7 @@ public class IndexService extends AbstractIndexComponent implements IndicesClust
|
||||||
fsyncTask,
|
fsyncTask,
|
||||||
trimTranslogTask,
|
trimTranslogTask,
|
||||||
globalCheckpointTask,
|
globalCheckpointTask,
|
||||||
retentionLeaseBackgroundSyncTask);
|
retentionLeaseSyncTask);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -788,8 +788,8 @@ public class IndexService extends AbstractIndexComponent implements IndicesClust
|
||||||
sync(is -> is.maybeSyncGlobalCheckpoint("background"), "global checkpoint");
|
sync(is -> is.maybeSyncGlobalCheckpoint("background"), "global checkpoint");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void backgroundSyncRetentionLeases() {
|
private void syncRetentionLeases() {
|
||||||
sync(IndexShard::backgroundSyncRetentionLeases, "retention lease");
|
sync(IndexShard::syncRetentionLeases, "retention lease");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sync(final Consumer<IndexShard> sync, final String source) {
|
private void sync(final Consumer<IndexShard> sync, final String source) {
|
||||||
|
@ -812,11 +812,11 @@ public class IndexService extends AbstractIndexComponent implements IndicesClust
|
||||||
&& e instanceof IndexShardClosedException == false) {
|
&& e instanceof IndexShardClosedException == false) {
|
||||||
logger.warn(
|
logger.warn(
|
||||||
new ParameterizedMessage(
|
new ParameterizedMessage(
|
||||||
"{} failed to execute background {} sync", shard.shardId(), source), e);
|
"{} failed to execute {} sync", shard.shardId(), source), e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ThreadPool.Names.SAME,
|
ThreadPool.Names.SAME,
|
||||||
"background " + source + " sync");
|
source + " sync");
|
||||||
} catch (final AlreadyClosedException | IndexShardClosedException e) {
|
} catch (final AlreadyClosedException | IndexShardClosedException e) {
|
||||||
// the shard was closed concurrently, continue
|
// the shard was closed concurrently, continue
|
||||||
}
|
}
|
||||||
|
@ -957,15 +957,15 @@ public class IndexService extends AbstractIndexComponent implements IndicesClust
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final class AsyncRetentionLeaseBackgroundSyncTask extends BaseAsyncTask {
|
final class AsyncRetentionLeaseSyncTask extends BaseAsyncTask {
|
||||||
|
|
||||||
AsyncRetentionLeaseBackgroundSyncTask(final IndexService indexService) {
|
AsyncRetentionLeaseSyncTask(final IndexService indexService) {
|
||||||
super(indexService, RETENTION_LEASE_SYNC_INTERVAL_SETTING.get(indexService.getIndexSettings().getSettings()));
|
super(indexService, RETENTION_LEASE_SYNC_INTERVAL_SETTING.get(indexService.getIndexSettings().getSettings()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void runInternal() {
|
protected void runInternal() {
|
||||||
indexService.backgroundSyncRetentionLeases();
|
indexService.syncRetentionLeases();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -975,7 +975,7 @@ public class IndexService extends AbstractIndexComponent implements IndicesClust
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "retention_lease_background_sync";
|
return "retention_lease_sync";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -339,6 +339,10 @@ public final class IndexSettings {
|
||||||
return retentionLeaseMillis;
|
return retentionLeaseMillis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setRetentionLeaseMillis(final TimeValue retentionLease) {
|
||||||
|
this.retentionLeaseMillis = retentionLease.millis();
|
||||||
|
}
|
||||||
|
|
||||||
private volatile boolean warmerEnabled;
|
private volatile boolean warmerEnabled;
|
||||||
private volatile int maxResultWindow;
|
private volatile int maxResultWindow;
|
||||||
private volatile int maxInnerResultWindow;
|
private volatile int maxInnerResultWindow;
|
||||||
|
@ -523,6 +527,7 @@ public final class IndexSettings {
|
||||||
scopedSettings.addSettingsUpdateConsumer(DEFAULT_PIPELINE, this::setDefaultPipeline);
|
scopedSettings.addSettingsUpdateConsumer(DEFAULT_PIPELINE, this::setDefaultPipeline);
|
||||||
scopedSettings.addSettingsUpdateConsumer(INDEX_SOFT_DELETES_RETENTION_OPERATIONS_SETTING, this::setSoftDeleteRetentionOperations);
|
scopedSettings.addSettingsUpdateConsumer(INDEX_SOFT_DELETES_RETENTION_OPERATIONS_SETTING, this::setSoftDeleteRetentionOperations);
|
||||||
scopedSettings.addSettingsUpdateConsumer(INDEX_SEARCH_THROTTLED, this::setSearchThrottled);
|
scopedSettings.addSettingsUpdateConsumer(INDEX_SEARCH_THROTTLED, this::setSearchThrottled);
|
||||||
|
scopedSettings.addSettingsUpdateConsumer(INDEX_SOFT_DELETES_RETENTION_LEASE_SETTING, this::setRetentionLeaseMillis);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setSearchIdleAfter(TimeValue searchIdleAfter) { this.searchIdleAfter = searchIdleAfter; }
|
private void setSearchIdleAfter(TimeValue searchIdleAfter) { this.searchIdleAfter = searchIdleAfter; }
|
||||||
|
|
|
@ -28,6 +28,7 @@ import org.elasticsearch.cluster.routing.AllocationId;
|
||||||
import org.elasticsearch.cluster.routing.IndexShardRoutingTable;
|
import org.elasticsearch.cluster.routing.IndexShardRoutingTable;
|
||||||
import org.elasticsearch.cluster.routing.ShardRouting;
|
import org.elasticsearch.cluster.routing.ShardRouting;
|
||||||
import org.elasticsearch.common.SuppressForbidden;
|
import org.elasticsearch.common.SuppressForbidden;
|
||||||
|
import org.elasticsearch.common.collect.Tuple;
|
||||||
import org.elasticsearch.common.io.stream.StreamInput;
|
import org.elasticsearch.common.io.stream.StreamInput;
|
||||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||||
import org.elasticsearch.common.io.stream.Writeable;
|
import org.elasticsearch.common.io.stream.Writeable;
|
||||||
|
@ -155,10 +156,10 @@ public class ReplicationTracker extends AbstractIndexShardComponent implements L
|
||||||
private final LongSupplier currentTimeMillisSupplier;
|
private final LongSupplier currentTimeMillisSupplier;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A callback when a new retention lease is created or an existing retention lease expires. In practice, this callback invokes the
|
* A callback when a new retention lease is created. In practice, this callback invokes the retention lease sync action, to sync
|
||||||
* retention lease sync action, to sync retention leases to replicas.
|
* retention leases to replicas.
|
||||||
*/
|
*/
|
||||||
private final BiConsumer<RetentionLeases, ActionListener<ReplicationResponse>> onSyncRetentionLeases;
|
private final BiConsumer<RetentionLeases, ActionListener<ReplicationResponse>> onAddRetentionLease;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This set contains allocation IDs for which there is a thread actively waiting for the local checkpoint to advance to at least the
|
* This set contains allocation IDs for which there is a thread actively waiting for the local checkpoint to advance to at least the
|
||||||
|
@ -177,16 +178,27 @@ public class ReplicationTracker extends AbstractIndexShardComponent implements L
|
||||||
private RetentionLeases retentionLeases = RetentionLeases.EMPTY;
|
private RetentionLeases retentionLeases = RetentionLeases.EMPTY;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all non-expired retention leases tracked on this shard. Note that only the primary shard calculates which leases are expired,
|
* Get all retention leases tracked on this shard.
|
||||||
* and if any have expired, syncs the retention leases to any replicas.
|
|
||||||
*
|
*
|
||||||
* @return the retention leases
|
* @return the retention leases
|
||||||
*/
|
*/
|
||||||
public RetentionLeases getRetentionLeases() {
|
public RetentionLeases getRetentionLeases() {
|
||||||
final boolean wasPrimaryMode;
|
return getRetentionLeases(false).v2();
|
||||||
final RetentionLeases nonExpiredRetentionLeases;
|
}
|
||||||
synchronized (this) {
|
|
||||||
if (primaryMode) {
|
/**
|
||||||
|
* If the expire leases parameter is false, gets all retention leases tracked on this shard and otherwise first calculates
|
||||||
|
* expiration of existing retention leases, and then gets all non-expired retention leases tracked on this shard. Note that only the
|
||||||
|
* primary shard calculates which leases are expired, and if any have expired, syncs the retention leases to any replicas. If the
|
||||||
|
* expire leases parameter is true, this replication tracker must be in primary mode.
|
||||||
|
*
|
||||||
|
* @return a tuple indicating whether or not any retention leases were expired, and the non-expired retention leases
|
||||||
|
*/
|
||||||
|
public synchronized Tuple<Boolean, RetentionLeases> getRetentionLeases(final boolean expireLeases) {
|
||||||
|
if (expireLeases == false) {
|
||||||
|
return Tuple.tuple(false, retentionLeases);
|
||||||
|
}
|
||||||
|
assert primaryMode;
|
||||||
// the primary calculates the non-expired retention leases and syncs them to replicas
|
// the primary calculates the non-expired retention leases and syncs them to replicas
|
||||||
final long currentTimeMillis = currentTimeMillisSupplier.getAsLong();
|
final long currentTimeMillis = currentTimeMillisSupplier.getAsLong();
|
||||||
final long retentionLeaseMillis = indexSettings.getRetentionLeaseMillis();
|
final long retentionLeaseMillis = indexSettings.getRetentionLeaseMillis();
|
||||||
|
@ -196,24 +208,12 @@ public class ReplicationTracker extends AbstractIndexShardComponent implements L
|
||||||
.collect(Collectors.groupingBy(lease -> currentTimeMillis - lease.timestamp() > retentionLeaseMillis));
|
.collect(Collectors.groupingBy(lease -> currentTimeMillis - lease.timestamp() > retentionLeaseMillis));
|
||||||
if (partitionByExpiration.get(true) == null) {
|
if (partitionByExpiration.get(true) == null) {
|
||||||
// early out as no retention leases have expired
|
// early out as no retention leases have expired
|
||||||
return retentionLeases;
|
return Tuple.tuple(false, retentionLeases);
|
||||||
}
|
}
|
||||||
final Collection<RetentionLease> nonExpiredLeases =
|
final Collection<RetentionLease> nonExpiredLeases =
|
||||||
partitionByExpiration.get(false) != null ? partitionByExpiration.get(false) : Collections.emptyList();
|
partitionByExpiration.get(false) != null ? partitionByExpiration.get(false) : Collections.emptyList();
|
||||||
retentionLeases = new RetentionLeases(operationPrimaryTerm, retentionLeases.version() + 1, nonExpiredLeases);
|
retentionLeases = new RetentionLeases(operationPrimaryTerm, retentionLeases.version() + 1, nonExpiredLeases);
|
||||||
}
|
return Tuple.tuple(true, retentionLeases);
|
||||||
/*
|
|
||||||
* At this point, we were either in primary mode and have updated the non-expired retention leases into the tracking map, or
|
|
||||||
* we were in replica mode and merely need to copy the existing retention leases since a replica does not calculate the
|
|
||||||
* non-expired retention leases, instead receiving them on syncs from the primary.
|
|
||||||
*/
|
|
||||||
wasPrimaryMode = primaryMode;
|
|
||||||
nonExpiredRetentionLeases = retentionLeases;
|
|
||||||
}
|
|
||||||
if (wasPrimaryMode) {
|
|
||||||
onSyncRetentionLeases.accept(nonExpiredRetentionLeases, ActionListener.wrap(() -> {}));
|
|
||||||
}
|
|
||||||
return nonExpiredRetentionLeases;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -246,7 +246,7 @@ public class ReplicationTracker extends AbstractIndexShardComponent implements L
|
||||||
Stream.concat(retentionLeases.leases().stream(), Stream.of(retentionLease)).collect(Collectors.toList()));
|
Stream.concat(retentionLeases.leases().stream(), Stream.of(retentionLease)).collect(Collectors.toList()));
|
||||||
currentRetentionLeases = retentionLeases;
|
currentRetentionLeases = retentionLeases;
|
||||||
}
|
}
|
||||||
onSyncRetentionLeases.accept(currentRetentionLeases, listener);
|
onAddRetentionLease.accept(currentRetentionLeases, listener);
|
||||||
return retentionLease;
|
return retentionLease;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -563,7 +563,7 @@ public class ReplicationTracker extends AbstractIndexShardComponent implements L
|
||||||
* @param indexSettings the index settings
|
* @param indexSettings the index settings
|
||||||
* @param operationPrimaryTerm the current primary term
|
* @param operationPrimaryTerm the current primary term
|
||||||
* @param globalCheckpoint the last known global checkpoint for this shard, or {@link SequenceNumbers#UNASSIGNED_SEQ_NO}
|
* @param globalCheckpoint the last known global checkpoint for this shard, or {@link SequenceNumbers#UNASSIGNED_SEQ_NO}
|
||||||
* @param onSyncRetentionLeases a callback when a new retention lease is created or an existing retention lease expires
|
* @param onAddRetentionLease a callback when a new retention lease is created or an existing retention lease expires
|
||||||
*/
|
*/
|
||||||
public ReplicationTracker(
|
public ReplicationTracker(
|
||||||
final ShardId shardId,
|
final ShardId shardId,
|
||||||
|
@ -573,7 +573,7 @@ public class ReplicationTracker extends AbstractIndexShardComponent implements L
|
||||||
final long globalCheckpoint,
|
final long globalCheckpoint,
|
||||||
final LongConsumer onGlobalCheckpointUpdated,
|
final LongConsumer onGlobalCheckpointUpdated,
|
||||||
final LongSupplier currentTimeMillisSupplier,
|
final LongSupplier currentTimeMillisSupplier,
|
||||||
final BiConsumer<RetentionLeases, ActionListener<ReplicationResponse>> onSyncRetentionLeases) {
|
final BiConsumer<RetentionLeases, ActionListener<ReplicationResponse>> onAddRetentionLease) {
|
||||||
super(shardId, indexSettings);
|
super(shardId, indexSettings);
|
||||||
assert globalCheckpoint >= SequenceNumbers.UNASSIGNED_SEQ_NO : "illegal initial global checkpoint: " + globalCheckpoint;
|
assert globalCheckpoint >= SequenceNumbers.UNASSIGNED_SEQ_NO : "illegal initial global checkpoint: " + globalCheckpoint;
|
||||||
this.shardAllocationId = allocationId;
|
this.shardAllocationId = allocationId;
|
||||||
|
@ -585,7 +585,7 @@ public class ReplicationTracker extends AbstractIndexShardComponent implements L
|
||||||
checkpoints.put(allocationId, new CheckpointState(SequenceNumbers.UNASSIGNED_SEQ_NO, globalCheckpoint, false, false));
|
checkpoints.put(allocationId, new CheckpointState(SequenceNumbers.UNASSIGNED_SEQ_NO, globalCheckpoint, false, false));
|
||||||
this.onGlobalCheckpointUpdated = Objects.requireNonNull(onGlobalCheckpointUpdated);
|
this.onGlobalCheckpointUpdated = Objects.requireNonNull(onGlobalCheckpointUpdated);
|
||||||
this.currentTimeMillisSupplier = Objects.requireNonNull(currentTimeMillisSupplier);
|
this.currentTimeMillisSupplier = Objects.requireNonNull(currentTimeMillisSupplier);
|
||||||
this.onSyncRetentionLeases = Objects.requireNonNull(onSyncRetentionLeases);
|
this.onAddRetentionLease = Objects.requireNonNull(onAddRetentionLease);
|
||||||
this.pendingInSync = new HashSet<>();
|
this.pendingInSync = new HashSet<>();
|
||||||
this.routingTable = null;
|
this.routingTable = null;
|
||||||
this.replicationGroup = null;
|
this.replicationGroup = null;
|
||||||
|
|
|
@ -1892,13 +1892,26 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all non-expired retention leases tracked on this shard.
|
* Get all retention leases tracked on this shard.
|
||||||
*
|
*
|
||||||
* @return the retention leases
|
* @return the retention leases
|
||||||
*/
|
*/
|
||||||
public RetentionLeases getRetentionLeases() {
|
public RetentionLeases getRetentionLeases() {
|
||||||
|
return getRetentionLeases(false).v2();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the expire leases parameter is false, gets all retention leases tracked on this shard and otherwise first calculates
|
||||||
|
* expiration of existing retention leases, and then gets all non-expired retention leases tracked on this shard. Note that only the
|
||||||
|
* primary shard calculates which leases are expired, and if any have expired, syncs the retention leases to any replicas. If the
|
||||||
|
* expire leases parameter is true, this replication tracker must be in primary mode.
|
||||||
|
*
|
||||||
|
* @return a tuple indicating whether or not any retention leases were expired, and the non-expired retention leases
|
||||||
|
*/
|
||||||
|
public Tuple<Boolean, RetentionLeases> getRetentionLeases(final boolean expireLeases) {
|
||||||
|
assert expireLeases == false || assertPrimaryMode();
|
||||||
verifyNotClosed();
|
verifyNotClosed();
|
||||||
return replicationTracker.getRetentionLeases();
|
return replicationTracker.getRetentionLeases(expireLeases);
|
||||||
}
|
}
|
||||||
|
|
||||||
public RetentionLeaseStats getRetentionLeaseStats() {
|
public RetentionLeaseStats getRetentionLeaseStats() {
|
||||||
|
@ -1956,10 +1969,15 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl
|
||||||
/**
|
/**
|
||||||
* Syncs the current retention leases to all replicas.
|
* Syncs the current retention leases to all replicas.
|
||||||
*/
|
*/
|
||||||
public void backgroundSyncRetentionLeases() {
|
public void syncRetentionLeases() {
|
||||||
assert assertPrimaryMode();
|
assert assertPrimaryMode();
|
||||||
verifyNotClosed();
|
verifyNotClosed();
|
||||||
retentionLeaseSyncer.backgroundSync(shardId, getRetentionLeases());
|
final Tuple<Boolean, RetentionLeases> retentionLeases = getRetentionLeases(true);
|
||||||
|
if (retentionLeases.v1()) {
|
||||||
|
retentionLeaseSyncer.sync(shardId, retentionLeases.v2(), ActionListener.wrap(() -> {}));
|
||||||
|
} else {
|
||||||
|
retentionLeaseSyncer.backgroundSync(shardId, retentionLeases.v2());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -37,7 +37,6 @@ import java.util.Map;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import java.util.function.LongSupplier;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static org.elasticsearch.index.seqno.SequenceNumbers.UNASSIGNED_SEQ_NO;
|
import static org.elasticsearch.index.seqno.SequenceNumbers.UNASSIGNED_SEQ_NO;
|
||||||
|
@ -46,7 +45,6 @@ import static org.hamcrest.Matchers.empty;
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
import static org.hamcrest.Matchers.hasItem;
|
import static org.hamcrest.Matchers.hasItem;
|
||||||
import static org.hamcrest.Matchers.hasSize;
|
import static org.hamcrest.Matchers.hasSize;
|
||||||
import static org.hamcrest.Matchers.lessThanOrEqualTo;
|
|
||||||
|
|
||||||
public class ReplicationTrackerRetentionLeaseTests extends ReplicationTrackerTestCase {
|
public class ReplicationTrackerRetentionLeaseTests extends ReplicationTrackerTestCase {
|
||||||
|
|
||||||
|
@ -78,7 +76,7 @@ public class ReplicationTrackerRetentionLeaseTests extends ReplicationTrackerTes
|
||||||
minimumRetainingSequenceNumbers[i] = randomLongBetween(SequenceNumbers.NO_OPS_PERFORMED, Long.MAX_VALUE);
|
minimumRetainingSequenceNumbers[i] = randomLongBetween(SequenceNumbers.NO_OPS_PERFORMED, Long.MAX_VALUE);
|
||||||
replicationTracker.addRetentionLease(
|
replicationTracker.addRetentionLease(
|
||||||
Integer.toString(i), minimumRetainingSequenceNumbers[i], "test-" + i, ActionListener.wrap(() -> {}));
|
Integer.toString(i), minimumRetainingSequenceNumbers[i], "test-" + i, ActionListener.wrap(() -> {}));
|
||||||
assertRetentionLeases(replicationTracker, i + 1, minimumRetainingSequenceNumbers, () -> 0L, primaryTerm, 1 + i, true);
|
assertRetentionLeases(replicationTracker, i + 1, minimumRetainingSequenceNumbers, primaryTerm, 1 + i, true, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < length; i++) {
|
for (int i = 0; i < length; i++) {
|
||||||
|
@ -88,7 +86,7 @@ public class ReplicationTrackerRetentionLeaseTests extends ReplicationTrackerTes
|
||||||
}
|
}
|
||||||
minimumRetainingSequenceNumbers[i] = randomLongBetween(minimumRetainingSequenceNumbers[i], Long.MAX_VALUE);
|
minimumRetainingSequenceNumbers[i] = randomLongBetween(minimumRetainingSequenceNumbers[i], Long.MAX_VALUE);
|
||||||
replicationTracker.renewRetentionLease(Integer.toString(i), minimumRetainingSequenceNumbers[i], "test-" + i);
|
replicationTracker.renewRetentionLease(Integer.toString(i), minimumRetainingSequenceNumbers[i], "test-" + i);
|
||||||
assertRetentionLeases(replicationTracker, length, minimumRetainingSequenceNumbers, () -> 0L, primaryTerm, 1 + length + i, true);
|
assertRetentionLeases(replicationTracker, length, minimumRetainingSequenceNumbers, primaryTerm, 1 + length + i, true, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -193,7 +191,7 @@ public class ReplicationTrackerRetentionLeaseTests extends ReplicationTrackerTes
|
||||||
assertThat(retentionLeases.leases(), hasSize(1));
|
assertThat(retentionLeases.leases(), hasSize(1));
|
||||||
final RetentionLease retentionLease = retentionLeases.leases().iterator().next();
|
final RetentionLease retentionLease = retentionLeases.leases().iterator().next();
|
||||||
assertThat(retentionLease.timestamp(), equalTo(currentTimeMillis.get()));
|
assertThat(retentionLease.timestamp(), equalTo(currentTimeMillis.get()));
|
||||||
assertRetentionLeases(replicationTracker, 1, retainingSequenceNumbers, currentTimeMillis::get, primaryTerm, 1, primaryMode);
|
assertRetentionLeases(replicationTracker, 1, retainingSequenceNumbers, primaryTerm, 1, primaryMode, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// renew the lease
|
// renew the lease
|
||||||
|
@ -215,108 +213,20 @@ public class ReplicationTrackerRetentionLeaseTests extends ReplicationTrackerTes
|
||||||
assertThat(retentionLeases.leases(), hasSize(1));
|
assertThat(retentionLeases.leases(), hasSize(1));
|
||||||
final RetentionLease retentionLease = retentionLeases.leases().iterator().next();
|
final RetentionLease retentionLease = retentionLeases.leases().iterator().next();
|
||||||
assertThat(retentionLease.timestamp(), equalTo(currentTimeMillis.get()));
|
assertThat(retentionLease.timestamp(), equalTo(currentTimeMillis.get()));
|
||||||
assertRetentionLeases(replicationTracker, 1, retainingSequenceNumbers, currentTimeMillis::get, primaryTerm, 2, primaryMode);
|
assertRetentionLeases(replicationTracker, 1, retainingSequenceNumbers, primaryTerm, 2, primaryMode, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// now force the lease to expire
|
// now force the lease to expire
|
||||||
currentTimeMillis.set(currentTimeMillis.get() + randomLongBetween(retentionLeaseMillis, Long.MAX_VALUE - currentTimeMillis.get()));
|
currentTimeMillis.set(currentTimeMillis.get() + randomLongBetween(retentionLeaseMillis, Long.MAX_VALUE - currentTimeMillis.get()));
|
||||||
if (primaryMode) {
|
if (primaryMode) {
|
||||||
assertRetentionLeases(replicationTracker, 0, retainingSequenceNumbers, currentTimeMillis::get, primaryTerm, 3, true);
|
assertRetentionLeases(replicationTracker, 1, retainingSequenceNumbers, primaryTerm, 2, true, false);
|
||||||
|
assertRetentionLeases(replicationTracker, 0, new long[0], primaryTerm, 3, true, true);
|
||||||
} else {
|
} else {
|
||||||
// leases do not expire on replicas until synced from the primary
|
// leases do not expire on replicas until synced from the primary
|
||||||
assertRetentionLeases(replicationTracker, 1, retainingSequenceNumbers, currentTimeMillis::get, primaryTerm, 2, false);
|
assertRetentionLeases(replicationTracker, 1, retainingSequenceNumbers, primaryTerm, 2, false, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testRetentionLeaseExpirationCausesRetentionLeaseSync() {
|
|
||||||
final AllocationId allocationId = AllocationId.newInitializing();
|
|
||||||
final AtomicLong currentTimeMillis = new AtomicLong(randomLongBetween(0, 1024));
|
|
||||||
final long retentionLeaseMillis = randomLongBetween(1, TimeValue.timeValueHours(12).millis());
|
|
||||||
final Settings settings = Settings
|
|
||||||
.builder()
|
|
||||||
.put(
|
|
||||||
IndexSettings.INDEX_SOFT_DELETES_RETENTION_LEASE_SETTING.getKey(),
|
|
||||||
TimeValue.timeValueMillis(retentionLeaseMillis))
|
|
||||||
.build();
|
|
||||||
final Map<String, Tuple<Long, Long>> retentionLeases = new HashMap<>();
|
|
||||||
final AtomicBoolean invoked = new AtomicBoolean();
|
|
||||||
final AtomicReference<ReplicationTracker> reference = new AtomicReference<>();
|
|
||||||
final ReplicationTracker replicationTracker = new ReplicationTracker(
|
|
||||||
new ShardId("test", "_na", 0),
|
|
||||||
allocationId.getId(),
|
|
||||||
IndexSettingsModule.newIndexSettings("test", settings),
|
|
||||||
randomNonNegativeLong(),
|
|
||||||
UNASSIGNED_SEQ_NO,
|
|
||||||
value -> {},
|
|
||||||
currentTimeMillis::get,
|
|
||||||
(leases, listener) -> {
|
|
||||||
// we do not want to hold a lock on the replication tracker in the callback!
|
|
||||||
assertFalse(Thread.holdsLock(reference.get()));
|
|
||||||
invoked.set(true);
|
|
||||||
assertThat(
|
|
||||||
leases.leases()
|
|
||||||
.stream()
|
|
||||||
.collect(Collectors.toMap(RetentionLease::id, ReplicationTrackerRetentionLeaseTests::toTuple)),
|
|
||||||
equalTo(retentionLeases));
|
|
||||||
});
|
|
||||||
reference.set(replicationTracker);
|
|
||||||
replicationTracker.updateFromMaster(
|
|
||||||
randomNonNegativeLong(),
|
|
||||||
Collections.singleton(allocationId.getId()),
|
|
||||||
routingTable(Collections.emptySet(), allocationId),
|
|
||||||
Collections.emptySet());
|
|
||||||
replicationTracker.activatePrimaryMode(SequenceNumbers.NO_OPS_PERFORMED);
|
|
||||||
|
|
||||||
final int length = randomIntBetween(0, 8);
|
|
||||||
long version = 0;
|
|
||||||
for (int i = 0; i < length; i++) {
|
|
||||||
final String id = randomAlphaOfLength(8);
|
|
||||||
final long retainingSequenceNumber = randomLongBetween(SequenceNumbers.NO_OPS_PERFORMED, Long.MAX_VALUE);
|
|
||||||
retentionLeases.put(id, Tuple.tuple(retainingSequenceNumber, currentTimeMillis.get()));
|
|
||||||
replicationTracker.addRetentionLease(id, retainingSequenceNumber, "test", ActionListener.wrap(() -> {}));
|
|
||||||
version++;
|
|
||||||
assertThat(replicationTracker.getRetentionLeases().version(), equalTo(version));
|
|
||||||
// assert that the new retention lease callback was invoked
|
|
||||||
assertTrue(invoked.get());
|
|
||||||
|
|
||||||
// reset the invocation marker so that we can assert the callback was not invoked when renewing the lease
|
|
||||||
invoked.set(false);
|
|
||||||
currentTimeMillis.set(1 + currentTimeMillis.get());
|
|
||||||
retentionLeases.put(id, Tuple.tuple(retainingSequenceNumber, currentTimeMillis.get()));
|
|
||||||
replicationTracker.renewRetentionLease(id, retainingSequenceNumber, "test");
|
|
||||||
version++;
|
|
||||||
assertThat(replicationTracker.getRetentionLeases().version(), equalTo(version));
|
|
||||||
|
|
||||||
// reset the invocation marker so that we can assert the callback was invoked if any leases are expired
|
|
||||||
assertFalse(invoked.get());
|
|
||||||
// randomly expire some leases
|
|
||||||
final long currentTimeMillisIncrement = randomLongBetween(0, Long.MAX_VALUE - currentTimeMillis.get());
|
|
||||||
// calculate the expired leases and update our tracking map
|
|
||||||
final List<String> expiredIds = retentionLeases.entrySet()
|
|
||||||
.stream()
|
|
||||||
.filter(r -> currentTimeMillis.get() + currentTimeMillisIncrement > r.getValue().v2() + retentionLeaseMillis)
|
|
||||||
.map(Map.Entry::getKey)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
expiredIds.forEach(retentionLeases::remove);
|
|
||||||
if (expiredIds.isEmpty() == false) {
|
|
||||||
version++;
|
|
||||||
}
|
|
||||||
currentTimeMillis.set(currentTimeMillis.get() + currentTimeMillisIncrement);
|
|
||||||
// getting the leases has the side effect of calculating which leases are expired and invoking the sync callback
|
|
||||||
final RetentionLeases current = replicationTracker.getRetentionLeases();
|
|
||||||
assertThat(current.version(), equalTo(version));
|
|
||||||
// the current leases should equal our tracking map
|
|
||||||
assertThat(
|
|
||||||
current.leases()
|
|
||||||
.stream()
|
|
||||||
.collect(Collectors.toMap(RetentionLease::id, ReplicationTrackerRetentionLeaseTests::toTuple)),
|
|
||||||
equalTo(retentionLeases));
|
|
||||||
// the callback should only be invoked if there were expired leases
|
|
||||||
assertThat(invoked.get(), equalTo(expiredIds.isEmpty() == false));
|
|
||||||
}
|
|
||||||
assertThat(replicationTracker.getRetentionLeases().version(), equalTo(version));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testReplicaIgnoresOlderRetentionLeasesVersion() {
|
public void testReplicaIgnoresOlderRetentionLeasesVersion() {
|
||||||
final AllocationId allocationId = AllocationId.newInitializing();
|
final AllocationId allocationId = AllocationId.newInitializing();
|
||||||
final ReplicationTracker replicationTracker = new ReplicationTracker(
|
final ReplicationTracker replicationTracker = new ReplicationTracker(
|
||||||
|
@ -370,19 +280,29 @@ public class ReplicationTrackerRetentionLeaseTests extends ReplicationTrackerTes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Tuple<Long, Long> toTuple(final RetentionLease retentionLease) {
|
|
||||||
return Tuple.tuple(retentionLease.retainingSequenceNumber(), retentionLease.timestamp());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void assertRetentionLeases(
|
private void assertRetentionLeases(
|
||||||
final ReplicationTracker replicationTracker,
|
final ReplicationTracker replicationTracker,
|
||||||
final int size,
|
final int size,
|
||||||
final long[] minimumRetainingSequenceNumbers,
|
final long[] minimumRetainingSequenceNumbers,
|
||||||
final LongSupplier currentTimeMillisSupplier,
|
|
||||||
final long primaryTerm,
|
final long primaryTerm,
|
||||||
final long version,
|
final long version,
|
||||||
final boolean primaryMode) {
|
final boolean primaryMode,
|
||||||
final RetentionLeases retentionLeases = replicationTracker.getRetentionLeases();
|
final boolean expireLeases) {
|
||||||
|
assertTrue(expireLeases == false || primaryMode);
|
||||||
|
final RetentionLeases retentionLeases;
|
||||||
|
if (expireLeases == false) {
|
||||||
|
if (randomBoolean()) {
|
||||||
|
retentionLeases = replicationTracker.getRetentionLeases();
|
||||||
|
} else {
|
||||||
|
final Tuple<Boolean, RetentionLeases> tuple = replicationTracker.getRetentionLeases(false);
|
||||||
|
assertFalse(tuple.v1());
|
||||||
|
retentionLeases = tuple.v2();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
final Tuple<Boolean, RetentionLeases> tuple = replicationTracker.getRetentionLeases(true);
|
||||||
|
assertTrue(tuple.v1());
|
||||||
|
retentionLeases = tuple.v2();
|
||||||
|
}
|
||||||
assertThat(retentionLeases.primaryTerm(), equalTo(primaryTerm));
|
assertThat(retentionLeases.primaryTerm(), equalTo(primaryTerm));
|
||||||
assertThat(retentionLeases.version(), equalTo(version));
|
assertThat(retentionLeases.version(), equalTo(version));
|
||||||
final Map<String, RetentionLease> idToRetentionLease = new HashMap<>();
|
final Map<String, RetentionLease> idToRetentionLease = new HashMap<>();
|
||||||
|
@ -395,12 +315,6 @@ public class ReplicationTrackerRetentionLeaseTests extends ReplicationTrackerTes
|
||||||
assertThat(idToRetentionLease.keySet(), hasItem(Integer.toString(i)));
|
assertThat(idToRetentionLease.keySet(), hasItem(Integer.toString(i)));
|
||||||
final RetentionLease retentionLease = idToRetentionLease.get(Integer.toString(i));
|
final RetentionLease retentionLease = idToRetentionLease.get(Integer.toString(i));
|
||||||
assertThat(retentionLease.retainingSequenceNumber(), equalTo(minimumRetainingSequenceNumbers[i]));
|
assertThat(retentionLease.retainingSequenceNumber(), equalTo(minimumRetainingSequenceNumbers[i]));
|
||||||
if (primaryMode) {
|
|
||||||
// retention leases can be expired on replicas, so we can only assert on primaries here
|
|
||||||
assertThat(
|
|
||||||
currentTimeMillisSupplier.getAsLong() - retentionLease.timestamp(),
|
|
||||||
lessThanOrEqualTo(replicationTracker.indexSettings().getRetentionLeaseMillis()));
|
|
||||||
}
|
|
||||||
assertThat(retentionLease.source(), equalTo("test-" + i));
|
assertThat(retentionLease.source(), equalTo("test-" + i));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,33 +20,58 @@
|
||||||
package org.elasticsearch.index.seqno;
|
package org.elasticsearch.index.seqno;
|
||||||
|
|
||||||
import org.elasticsearch.action.ActionListener;
|
import org.elasticsearch.action.ActionListener;
|
||||||
|
import org.elasticsearch.action.support.master.AcknowledgedResponse;
|
||||||
import org.elasticsearch.action.support.replication.ReplicationResponse;
|
import org.elasticsearch.action.support.replication.ReplicationResponse;
|
||||||
import org.elasticsearch.cluster.routing.ShardRouting;
|
import org.elasticsearch.cluster.routing.ShardRouting;
|
||||||
|
import org.elasticsearch.common.settings.Setting;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.common.unit.TimeValue;
|
import org.elasticsearch.common.unit.TimeValue;
|
||||||
|
import org.elasticsearch.index.IndexService;
|
||||||
import org.elasticsearch.index.IndexSettings;
|
import org.elasticsearch.index.IndexSettings;
|
||||||
import org.elasticsearch.index.engine.Engine;
|
import org.elasticsearch.index.engine.Engine;
|
||||||
import org.elasticsearch.index.shard.IndexShard;
|
import org.elasticsearch.index.shard.IndexShard;
|
||||||
import org.elasticsearch.index.shard.ShardId;
|
import org.elasticsearch.index.shard.ShardId;
|
||||||
import org.elasticsearch.indices.IndicesService;
|
import org.elasticsearch.indices.IndicesService;
|
||||||
|
import org.elasticsearch.plugins.Plugin;
|
||||||
import org.elasticsearch.test.ESIntegTestCase;
|
import org.elasticsearch.test.ESIntegTestCase;
|
||||||
import org.elasticsearch.threadpool.ThreadPool;
|
import org.elasticsearch.threadpool.ThreadPool;
|
||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.anyOf;
|
import static org.hamcrest.Matchers.anyOf;
|
||||||
import static org.hamcrest.Matchers.contains;
|
import static org.hamcrest.Matchers.contains;
|
||||||
import static org.hamcrest.Matchers.empty;
|
import static org.hamcrest.Matchers.empty;
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
import static org.hamcrest.Matchers.hasItem;
|
|
||||||
|
|
||||||
@ESIntegTestCase.ClusterScope(scope = ESIntegTestCase.Scope.TEST)
|
@ESIntegTestCase.ClusterScope(scope = ESIntegTestCase.Scope.TEST)
|
||||||
public class RetentionLeaseSyncIT extends ESIntegTestCase {
|
public class RetentionLeaseSyncIT extends ESIntegTestCase {
|
||||||
|
|
||||||
|
public static final class RetentionLeaseSyncIntervalSettingPlugin extends Plugin {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Setting<?>> getSettings() {
|
||||||
|
return Collections.singletonList(IndexService.RETENTION_LEASE_SYNC_INTERVAL_SETTING);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Collection<Class<? extends Plugin>> nodePlugins() {
|
||||||
|
return Stream.concat(
|
||||||
|
super.nodePlugins().stream(),
|
||||||
|
Stream.of(RetentionLeaseBackgroundSyncIT.RetentionLeaseSyncIntervalSettingPlugin.class))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
public void testRetentionLeasesSyncedOnAdd() throws Exception {
|
public void testRetentionLeasesSyncedOnAdd() throws Exception {
|
||||||
final int numberOfReplicas = 2 - scaledRandomIntBetween(0, 2);
|
final int numberOfReplicas = 2 - scaledRandomIntBetween(0, 2);
|
||||||
internalCluster().ensureAtLeastNumDataNodes(1 + numberOfReplicas);
|
internalCluster().ensureAtLeastNumDataNodes(1 + numberOfReplicas);
|
||||||
|
@ -99,7 +124,6 @@ public class RetentionLeaseSyncIT extends ESIntegTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@AwaitsFix(bugUrl = "https://github.com/elastic/elasticsearch/issues/37963")
|
|
||||||
public void testRetentionLeasesSyncOnExpiration() throws Exception {
|
public void testRetentionLeasesSyncOnExpiration() throws Exception {
|
||||||
final int numberOfReplicas = 2 - scaledRandomIntBetween(0, 2);
|
final int numberOfReplicas = 2 - scaledRandomIntBetween(0, 2);
|
||||||
internalCluster().ensureAtLeastNumDataNodes(1 + numberOfReplicas);
|
internalCluster().ensureAtLeastNumDataNodes(1 + numberOfReplicas);
|
||||||
|
@ -109,7 +133,7 @@ public class RetentionLeaseSyncIT extends ESIntegTestCase {
|
||||||
final Settings settings = Settings.builder()
|
final Settings settings = Settings.builder()
|
||||||
.put("index.number_of_shards", 1)
|
.put("index.number_of_shards", 1)
|
||||||
.put("index.number_of_replicas", numberOfReplicas)
|
.put("index.number_of_replicas", numberOfReplicas)
|
||||||
.put(IndexSettings.INDEX_SOFT_DELETES_RETENTION_LEASE_SETTING.getKey(), retentionLeaseTimeToLive)
|
.put(IndexService.RETENTION_LEASE_SYNC_INTERVAL_SETTING.getKey(), TimeValue.timeValueSeconds(1))
|
||||||
.build();
|
.build();
|
||||||
createIndex("index", settings);
|
createIndex("index", settings);
|
||||||
ensureGreen("index");
|
ensureGreen("index");
|
||||||
|
@ -121,6 +145,17 @@ public class RetentionLeaseSyncIT extends ESIntegTestCase {
|
||||||
// we will add multiple retention leases, wait for some to expire, and assert a consistent view between the primary and the replicas
|
// we will add multiple retention leases, wait for some to expire, and assert a consistent view between the primary and the replicas
|
||||||
final int length = randomIntBetween(1, 8);
|
final int length = randomIntBetween(1, 8);
|
||||||
for (int i = 0; i < length; i++) {
|
for (int i = 0; i < length; i++) {
|
||||||
|
// update the index for retention leases to live a long time
|
||||||
|
final AcknowledgedResponse longTtlResponse = client().admin()
|
||||||
|
.indices()
|
||||||
|
.prepareUpdateSettings("index")
|
||||||
|
.setSettings(
|
||||||
|
Settings.builder()
|
||||||
|
.putNull(IndexSettings.INDEX_SOFT_DELETES_RETENTION_LEASE_SETTING.getKey())
|
||||||
|
.build())
|
||||||
|
.get();
|
||||||
|
assertTrue(longTtlResponse.isAcknowledged());
|
||||||
|
|
||||||
final String id = randomAlphaOfLength(8);
|
final String id = randomAlphaOfLength(8);
|
||||||
final long retainingSequenceNumber = randomLongBetween(0, Long.MAX_VALUE);
|
final long retainingSequenceNumber = randomLongBetween(0, Long.MAX_VALUE);
|
||||||
final String source = randomAlphaOfLength(8);
|
final String source = randomAlphaOfLength(8);
|
||||||
|
@ -137,19 +172,26 @@ public class RetentionLeaseSyncIT extends ESIntegTestCase {
|
||||||
final IndexShard replica = internalCluster()
|
final IndexShard replica = internalCluster()
|
||||||
.getInstance(IndicesService.class, replicaShardNodeName)
|
.getInstance(IndicesService.class, replicaShardNodeName)
|
||||||
.getShardOrNull(new ShardId(resolveIndex("index"), 0));
|
.getShardOrNull(new ShardId(resolveIndex("index"), 0));
|
||||||
assertThat(replica.getRetentionLeases().leases(), hasItem(currentRetentionLease));
|
assertThat(replica.getRetentionLeases().leases(), anyOf(empty(), contains(currentRetentionLease)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// sleep long enough that *possibly* the current retention lease has expired, and certainly that any previous have
|
// update the index for retention leases to short a long time, to force expiration
|
||||||
|
final AcknowledgedResponse shortTtlResponse = client().admin()
|
||||||
|
.indices()
|
||||||
|
.prepareUpdateSettings("index")
|
||||||
|
.setSettings(
|
||||||
|
Settings.builder()
|
||||||
|
.put(IndexSettings.INDEX_SOFT_DELETES_RETENTION_LEASE_SETTING.getKey(), retentionLeaseTimeToLive)
|
||||||
|
.build())
|
||||||
|
.get();
|
||||||
|
assertTrue(shortTtlResponse.isAcknowledged());
|
||||||
|
|
||||||
|
// sleep long enough that the current retention lease has expired
|
||||||
final long later = System.nanoTime();
|
final long later = System.nanoTime();
|
||||||
Thread.sleep(Math.max(0, retentionLeaseTimeToLive.millis() - TimeUnit.NANOSECONDS.toMillis(later - now)));
|
Thread.sleep(Math.max(0, retentionLeaseTimeToLive.millis() - TimeUnit.NANOSECONDS.toMillis(later - now)));
|
||||||
final RetentionLeases currentRetentionLeases = primary.getRetentionLeases();
|
assertBusy(() -> assertThat(primary.getRetentionLeases().leases(), empty()));
|
||||||
assertThat(currentRetentionLeases.leases(), anyOf(empty(), contains(currentRetentionLease)));
|
|
||||||
|
|
||||||
/*
|
// now that all retention leases are expired should have been synced to all replicas
|
||||||
* Check that expiration of retention leases has been synced to all replicas. We have to assert busy since syncing happens in
|
|
||||||
* the background.
|
|
||||||
*/
|
|
||||||
assertBusy(() -> {
|
assertBusy(() -> {
|
||||||
for (final ShardRouting replicaShard : clusterService().state().routingTable().index("index").shard(0).replicaShards()) {
|
for (final ShardRouting replicaShard : clusterService().state().routingTable().index("index").shard(0).replicaShards()) {
|
||||||
final String replicaShardNodeId = replicaShard.currentNodeId();
|
final String replicaShardNodeId = replicaShard.currentNodeId();
|
||||||
|
@ -157,13 +199,7 @@ public class RetentionLeaseSyncIT extends ESIntegTestCase {
|
||||||
final IndexShard replica = internalCluster()
|
final IndexShard replica = internalCluster()
|
||||||
.getInstance(IndicesService.class, replicaShardNodeName)
|
.getInstance(IndicesService.class, replicaShardNodeName)
|
||||||
.getShardOrNull(new ShardId(resolveIndex("index"), 0));
|
.getShardOrNull(new ShardId(resolveIndex("index"), 0));
|
||||||
if (currentRetentionLeases.leases().isEmpty()) {
|
|
||||||
assertThat(replica.getRetentionLeases().leases(), empty());
|
assertThat(replica.getRetentionLeases().leases(), empty());
|
||||||
} else {
|
|
||||||
assertThat(
|
|
||||||
replica.getRetentionLeases().leases(),
|
|
||||||
contains(currentRetentionLeases.leases().toArray(new RetentionLease[0])));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ import org.elasticsearch.action.ActionListener;
|
||||||
import org.elasticsearch.action.admin.indices.flush.FlushRequest;
|
import org.elasticsearch.action.admin.indices.flush.FlushRequest;
|
||||||
import org.elasticsearch.cluster.routing.RecoverySource;
|
import org.elasticsearch.cluster.routing.RecoverySource;
|
||||||
import org.elasticsearch.cluster.routing.ShardRoutingHelper;
|
import org.elasticsearch.cluster.routing.ShardRoutingHelper;
|
||||||
|
import org.elasticsearch.common.collect.Tuple;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.common.unit.TimeValue;
|
import org.elasticsearch.common.unit.TimeValue;
|
||||||
import org.elasticsearch.index.IndexSettings;
|
import org.elasticsearch.index.IndexSettings;
|
||||||
|
@ -43,14 +44,12 @@ import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
import java.util.function.LongSupplier;
|
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.contains;
|
import static org.hamcrest.Matchers.contains;
|
||||||
import static org.hamcrest.Matchers.empty;
|
import static org.hamcrest.Matchers.empty;
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
import static org.hamcrest.Matchers.hasItem;
|
import static org.hamcrest.Matchers.hasItem;
|
||||||
import static org.hamcrest.Matchers.hasSize;
|
import static org.hamcrest.Matchers.hasSize;
|
||||||
import static org.hamcrest.Matchers.lessThanOrEqualTo;
|
|
||||||
import static org.mockito.Matchers.anyString;
|
import static org.mockito.Matchers.anyString;
|
||||||
import static org.mockito.Mockito.doAnswer;
|
import static org.mockito.Mockito.doAnswer;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
@ -85,13 +84,20 @@ public class IndexShardRetentionLeaseTests extends IndexShardTestCase {
|
||||||
indexShard.addRetentionLease(
|
indexShard.addRetentionLease(
|
||||||
Integer.toString(i), minimumRetainingSequenceNumbers[i], "test-" + i, ActionListener.wrap(() -> {}));
|
Integer.toString(i), minimumRetainingSequenceNumbers[i], "test-" + i, ActionListener.wrap(() -> {}));
|
||||||
assertRetentionLeases(
|
assertRetentionLeases(
|
||||||
indexShard, i + 1, minimumRetainingSequenceNumbers, () -> 0L, primaryTerm, 1 + i, true);
|
indexShard, i + 1, minimumRetainingSequenceNumbers, primaryTerm, 1 + i, true, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < length; i++) {
|
for (int i = 0; i < length; i++) {
|
||||||
minimumRetainingSequenceNumbers[i] = randomLongBetween(minimumRetainingSequenceNumbers[i], Long.MAX_VALUE);
|
minimumRetainingSequenceNumbers[i] = randomLongBetween(minimumRetainingSequenceNumbers[i], Long.MAX_VALUE);
|
||||||
indexShard.renewRetentionLease(Integer.toString(i), minimumRetainingSequenceNumbers[i], "test-" + i);
|
indexShard.renewRetentionLease(Integer.toString(i), minimumRetainingSequenceNumbers[i], "test-" + i);
|
||||||
assertRetentionLeases(indexShard, length, minimumRetainingSequenceNumbers, () -> 0L, primaryTerm, 1 + length + i, true);
|
assertRetentionLeases(
|
||||||
|
indexShard,
|
||||||
|
length,
|
||||||
|
minimumRetainingSequenceNumbers,
|
||||||
|
primaryTerm,
|
||||||
|
1 + length + i,
|
||||||
|
true,
|
||||||
|
false);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
closeShards(indexShard);
|
closeShards(indexShard);
|
||||||
|
@ -121,8 +127,7 @@ public class IndexShardRetentionLeaseTests extends IndexShardTestCase {
|
||||||
final long[] retainingSequenceNumbers = new long[1];
|
final long[] retainingSequenceNumbers = new long[1];
|
||||||
retainingSequenceNumbers[0] = randomLongBetween(0, Long.MAX_VALUE);
|
retainingSequenceNumbers[0] = randomLongBetween(0, Long.MAX_VALUE);
|
||||||
if (primary) {
|
if (primary) {
|
||||||
indexShard.addRetentionLease("0", retainingSequenceNumbers[0], "test-0", ActionListener.wrap(() -> {
|
indexShard.addRetentionLease("0", retainingSequenceNumbers[0], "test-0", ActionListener.wrap(() -> {}));
|
||||||
}));
|
|
||||||
} else {
|
} else {
|
||||||
final RetentionLeases retentionLeases = new RetentionLeases(
|
final RetentionLeases retentionLeases = new RetentionLeases(
|
||||||
primaryTerm,
|
primaryTerm,
|
||||||
|
@ -137,7 +142,7 @@ public class IndexShardRetentionLeaseTests extends IndexShardTestCase {
|
||||||
assertThat(retentionLeases.leases(), hasSize(1));
|
assertThat(retentionLeases.leases(), hasSize(1));
|
||||||
final RetentionLease retentionLease = retentionLeases.leases().iterator().next();
|
final RetentionLease retentionLease = retentionLeases.leases().iterator().next();
|
||||||
assertThat(retentionLease.timestamp(), equalTo(currentTimeMillis.get()));
|
assertThat(retentionLease.timestamp(), equalTo(currentTimeMillis.get()));
|
||||||
assertRetentionLeases(indexShard, 1, retainingSequenceNumbers, currentTimeMillis::get, primaryTerm, 1, primary);
|
assertRetentionLeases(indexShard, 1, retainingSequenceNumbers, primaryTerm, 1, primary, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// renew the lease
|
// renew the lease
|
||||||
|
@ -159,16 +164,17 @@ public class IndexShardRetentionLeaseTests extends IndexShardTestCase {
|
||||||
assertThat(retentionLeases.leases(), hasSize(1));
|
assertThat(retentionLeases.leases(), hasSize(1));
|
||||||
final RetentionLease retentionLease = retentionLeases.leases().iterator().next();
|
final RetentionLease retentionLease = retentionLeases.leases().iterator().next();
|
||||||
assertThat(retentionLease.timestamp(), equalTo(currentTimeMillis.get()));
|
assertThat(retentionLease.timestamp(), equalTo(currentTimeMillis.get()));
|
||||||
assertRetentionLeases(indexShard, 1, retainingSequenceNumbers, currentTimeMillis::get, primaryTerm, 2, primary);
|
assertRetentionLeases(indexShard, 1, retainingSequenceNumbers, primaryTerm, 2, primary, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// now force the lease to expire
|
// now force the lease to expire
|
||||||
currentTimeMillis.set(
|
currentTimeMillis.set(
|
||||||
currentTimeMillis.get() + randomLongBetween(retentionLeaseMillis, Long.MAX_VALUE - currentTimeMillis.get()));
|
currentTimeMillis.get() + randomLongBetween(retentionLeaseMillis, Long.MAX_VALUE - currentTimeMillis.get()));
|
||||||
if (primary) {
|
if (primary) {
|
||||||
assertRetentionLeases(indexShard, 0, retainingSequenceNumbers, currentTimeMillis::get, primaryTerm, 3, true);
|
assertRetentionLeases(indexShard, 1, retainingSequenceNumbers, primaryTerm, 2, true, false);
|
||||||
|
assertRetentionLeases(indexShard, 0, new long[0], primaryTerm, 3, true, true);
|
||||||
} else {
|
} else {
|
||||||
assertRetentionLeases(indexShard, 1, retainingSequenceNumbers, currentTimeMillis::get, primaryTerm, 2, false);
|
assertRetentionLeases(indexShard, 1, retainingSequenceNumbers, primaryTerm, 2, false, false);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
closeShards(indexShard);
|
closeShards(indexShard);
|
||||||
|
@ -191,8 +197,7 @@ public class IndexShardRetentionLeaseTests extends IndexShardTestCase {
|
||||||
minimumRetainingSequenceNumbers[i] = randomLongBetween(SequenceNumbers.NO_OPS_PERFORMED, Long.MAX_VALUE);
|
minimumRetainingSequenceNumbers[i] = randomLongBetween(SequenceNumbers.NO_OPS_PERFORMED, Long.MAX_VALUE);
|
||||||
currentTimeMillis.set(TimeUnit.NANOSECONDS.toMillis(randomNonNegativeLong()));
|
currentTimeMillis.set(TimeUnit.NANOSECONDS.toMillis(randomNonNegativeLong()));
|
||||||
indexShard.addRetentionLease(
|
indexShard.addRetentionLease(
|
||||||
Integer.toString(i), minimumRetainingSequenceNumbers[i], "test-" + i, ActionListener.wrap(() -> {
|
Integer.toString(i), minimumRetainingSequenceNumbers[i], "test-" + i, ActionListener.wrap(() -> {}));
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
currentTimeMillis.set(TimeUnit.NANOSECONDS.toMillis(Long.MAX_VALUE));
|
currentTimeMillis.set(TimeUnit.NANOSECONDS.toMillis(Long.MAX_VALUE));
|
||||||
|
@ -250,13 +255,10 @@ public class IndexShardRetentionLeaseTests extends IndexShardTestCase {
|
||||||
final RetentionLeaseStats stats = indexShard.getRetentionLeaseStats();
|
final RetentionLeaseStats stats = indexShard.getRetentionLeaseStats();
|
||||||
assertRetentionLeases(
|
assertRetentionLeases(
|
||||||
stats.retentionLeases(),
|
stats.retentionLeases(),
|
||||||
indexShard.indexSettings().getRetentionLeaseMillis(),
|
|
||||||
length,
|
length,
|
||||||
minimumRetainingSequenceNumbers,
|
minimumRetainingSequenceNumbers,
|
||||||
() -> 0L,
|
|
||||||
length == 0 ? RetentionLeases.EMPTY.primaryTerm() : indexShard.getOperationPrimaryTerm(),
|
length == 0 ? RetentionLeases.EMPTY.primaryTerm() : indexShard.getOperationPrimaryTerm(),
|
||||||
length,
|
length);
|
||||||
true);
|
|
||||||
} finally {
|
} finally {
|
||||||
closeShards(indexShard);
|
closeShards(indexShard);
|
||||||
}
|
}
|
||||||
|
@ -266,30 +268,39 @@ public class IndexShardRetentionLeaseTests extends IndexShardTestCase {
|
||||||
final IndexShard indexShard,
|
final IndexShard indexShard,
|
||||||
final int size,
|
final int size,
|
||||||
final long[] minimumRetainingSequenceNumbers,
|
final long[] minimumRetainingSequenceNumbers,
|
||||||
final LongSupplier currentTimeMillisSupplier,
|
|
||||||
final long primaryTerm,
|
final long primaryTerm,
|
||||||
final long version,
|
final long version,
|
||||||
final boolean primary) {
|
final boolean primary,
|
||||||
|
final boolean expireLeases) {
|
||||||
|
assertTrue(expireLeases == false || primary);
|
||||||
|
final RetentionLeases retentionLeases;
|
||||||
|
if (expireLeases == false) {
|
||||||
|
if (randomBoolean()) {
|
||||||
|
retentionLeases = indexShard.getRetentionLeases();
|
||||||
|
} else {
|
||||||
|
final Tuple<Boolean, RetentionLeases> tuple = indexShard.getRetentionLeases(false);
|
||||||
|
assertFalse(tuple.v1());
|
||||||
|
retentionLeases = tuple.v2();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
final Tuple<Boolean, RetentionLeases> tuple = indexShard.getRetentionLeases(true);
|
||||||
|
assertTrue(tuple.v1());
|
||||||
|
retentionLeases = tuple.v2();
|
||||||
|
}
|
||||||
assertRetentionLeases(
|
assertRetentionLeases(
|
||||||
indexShard.getEngine().config().retentionLeasesSupplier().get(),
|
retentionLeases,
|
||||||
indexShard.indexSettings().getRetentionLeaseMillis(),
|
|
||||||
size,
|
size,
|
||||||
minimumRetainingSequenceNumbers,
|
minimumRetainingSequenceNumbers,
|
||||||
currentTimeMillisSupplier,
|
|
||||||
primaryTerm,
|
primaryTerm,
|
||||||
version,
|
version);
|
||||||
primary);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertRetentionLeases(
|
private void assertRetentionLeases(
|
||||||
final RetentionLeases retentionLeases,
|
final RetentionLeases retentionLeases,
|
||||||
final long retentionLeaseMillis,
|
|
||||||
final int size,
|
final int size,
|
||||||
final long[] minimumRetainingSequenceNumbers,
|
final long[] minimumRetainingSequenceNumbers,
|
||||||
final LongSupplier currentTimeMillisSupplier,
|
|
||||||
final long primaryTerm,
|
final long primaryTerm,
|
||||||
final long version,
|
final long version) {
|
||||||
final boolean primary) {
|
|
||||||
assertThat(retentionLeases.primaryTerm(), equalTo(primaryTerm));
|
assertThat(retentionLeases.primaryTerm(), equalTo(primaryTerm));
|
||||||
assertThat(retentionLeases.version(), equalTo(version));
|
assertThat(retentionLeases.version(), equalTo(version));
|
||||||
final Map<String, RetentionLease> idToRetentionLease = new HashMap<>();
|
final Map<String, RetentionLease> idToRetentionLease = new HashMap<>();
|
||||||
|
@ -302,10 +313,6 @@ public class IndexShardRetentionLeaseTests extends IndexShardTestCase {
|
||||||
assertThat(idToRetentionLease.keySet(), hasItem(Integer.toString(i)));
|
assertThat(idToRetentionLease.keySet(), hasItem(Integer.toString(i)));
|
||||||
final RetentionLease retentionLease = idToRetentionLease.get(Integer.toString(i));
|
final RetentionLease retentionLease = idToRetentionLease.get(Integer.toString(i));
|
||||||
assertThat(retentionLease.retainingSequenceNumber(), equalTo(minimumRetainingSequenceNumbers[i]));
|
assertThat(retentionLease.retainingSequenceNumber(), equalTo(minimumRetainingSequenceNumbers[i]));
|
||||||
if (primary) {
|
|
||||||
// retention leases can be expired on replicas, so we can only assert on primaries here
|
|
||||||
assertThat(currentTimeMillisSupplier.getAsLong() - retentionLease.timestamp(), lessThanOrEqualTo(retentionLeaseMillis));
|
|
||||||
}
|
|
||||||
assertThat(retentionLease.source(), equalTo("test-" + i));
|
assertThat(retentionLease.source(), equalTo("test-" + i));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue