-
Notifications
You must be signed in to change notification settings - Fork 25.6k
Suspend Index throttling when relocating #128797
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 9 commits
dfe639f
2601960
ec91a19
3ddb78b
f12949e
90670f3
45e3799
cd43ab3
bf91cab
e642fea
a249357
a11d2fd
82a37f5
560a035
1bb089e
048f944
3e044bf
7abedf2
76f59b6
4afc8c5
c5cce6e
8485a8e
6315189
5e81c31
661fa12
77058ad
1d872c1
129622a
610bc0f
e50c200
343d9f2
94f212a
6a2bf2b
ee22887
8e9d456
371dfbe
c9438b4
31fe364
9729ebf
1134a5e
8d6fc17
a1bf5a3
3571c46
2ee8dfb
f492df1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -43,6 +43,8 @@ | |||||||||
| import org.elasticsearch.index.shard.IndexShard; | ||||||||||
| import org.elasticsearch.index.shard.IndexShardState; | ||||||||||
| import org.elasticsearch.index.shard.ShardId; | ||||||||||
| import org.elasticsearch.indices.IndexingMemoryController; | ||||||||||
| import org.elasticsearch.indices.IndicesService; | ||||||||||
| import org.elasticsearch.indices.recovery.PeerRecoveryTargetService; | ||||||||||
| import org.elasticsearch.indices.recovery.RecoveryFileChunkRequest; | ||||||||||
| import org.elasticsearch.plugins.Plugin; | ||||||||||
|
|
@@ -162,10 +164,82 @@ public void testSimpleRelocationNoIndexing() { | |||||||||
| assertHitCount(prepareSearch("test").setSize(0), 20); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| // This tests that relocation can successfully suspend index throttling to grab | ||||||||||
| // indexing permits required for relocation to succeed. | ||||||||||
| public void testSimpleRelocationWithIndexingPaused() throws Exception { | ||||||||||
| logger.info("--> starting [node1] ..."); | ||||||||||
| // Start node with PAUSE_INDEXING_ON_THROTTLE setting set to true. This means that if we activate | ||||||||||
| // index throttling for a shard on this node, it will pause indexing for that shard until throttling | ||||||||||
| // is deactivated. | ||||||||||
| final String node_1 = internalCluster().startNode( | ||||||||||
| Settings.builder().put(IndexingMemoryController.PAUSE_INDEXING_ON_THROTTLE.getKey(), true) | ||||||||||
| ); | ||||||||||
|
|
||||||||||
| logger.info("--> creating test index ..."); | ||||||||||
| prepareCreate("test", indexSettings(1, 0)).get(); | ||||||||||
|
|
||||||||||
| logger.info("--> index 10 docs"); | ||||||||||
| for (int i = 0; i < 10; i++) { | ||||||||||
| prepareIndex("test").setId(Integer.toString(i)).setSource("field", "value" + i).get(); | ||||||||||
| } | ||||||||||
| logger.info("--> flush so we have an actual index"); | ||||||||||
| indicesAdmin().prepareFlush().get(); | ||||||||||
| logger.info("--> index more docs so we have something in the translog"); | ||||||||||
| for (int i = 10; i < 20; i++) { | ||||||||||
| prepareIndex("test").setId(Integer.toString(i)).setSource("field", "value" + i).get(); | ||||||||||
| } | ||||||||||
|
||||||||||
|
|
||||||||||
| logger.info("--> verifying count"); | ||||||||||
| indicesAdmin().prepareRefresh().get(); | ||||||||||
| assertHitCount(prepareSearch("test").setSize(0), 20L); | ||||||||||
|
|
||||||||||
| logger.info("--> start another node"); | ||||||||||
| final String node_2 = internalCluster().startNode(); | ||||||||||
| ClusterHealthResponse clusterHealthResponse = clusterAdmin().prepareHealth(TEST_REQUEST_TIMEOUT) | ||||||||||
| .setWaitForEvents(Priority.LANGUID) | ||||||||||
| .setWaitForNodes("2") | ||||||||||
| .get(); | ||||||||||
| assertThat(clusterHealthResponse.isTimedOut(), equalTo(false)); | ||||||||||
|
|
||||||||||
| // Activate index throttling on "test" index primary shard | ||||||||||
| IndicesService indicesService = internalCluster().getInstance(IndicesService.class, node_1); | ||||||||||
| IndexShard shard = indicesService.indexServiceSafe(resolveIndex("test")).getShard(0); | ||||||||||
| shard.activateThrottling(); | ||||||||||
| // Verify that indexing is paused for the throttled shard | ||||||||||
| assertBusy(() -> { assertThat(shard.isIndexingPaused(), equalTo(true)); }); | ||||||||||
|
||||||||||
| // Try to index a document into the "test" index which is currently throttled | ||||||||||
| logger.info("--> Try to index a doc while indexing is paused"); | ||||||||||
| IndexRequestBuilder indexRequestBuilder = prepareIndex("test").setId(Integer.toString(20)).setSource("field", "value" + 20); | ||||||||||
| var future = indexRequestBuilder.execute(); | ||||||||||
| // Verify that the new document has not been indexed indicating that the indexing thread is paused. | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we wait for the thread to be blocked on the condition here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I made a change to wait for the future to complete, which times out. Were you thinking of something different ? |
||||||||||
| logger.info("--> verifying count is unchanged..."); | ||||||||||
| indicesAdmin().prepareRefresh().get(); | ||||||||||
| assertHitCount(prepareSearch("test").setSize(0), 20); | ||||||||||
|
|
||||||||||
| logger.info("--> relocate the shard from node1 to node2"); | ||||||||||
| ClusterRerouteUtils.reroute(client(), new MoveAllocationCommand("test", 0, node_1, node_2)); | ||||||||||
|
||||||||||
|
|
||||||||||
| // Relocation will suspend throttling for the paused shard, allow the indexing thread to proceed, thereby releasing | ||||||||||
| // the indexing permit it holds, in turn allowing relocation to acquire the permits and proceed. | ||||||||||
| clusterHealthResponse = clusterAdmin().prepareHealth(TEST_REQUEST_TIMEOUT) | ||||||||||
| .setWaitForEvents(Priority.LANGUID) | ||||||||||
| .setWaitForNoRelocatingShards(true) | ||||||||||
| .setTimeout(ACCEPTABLE_RELOCATION_TIME) | ||||||||||
| .get(); | ||||||||||
| assertThat(clusterHealthResponse.isTimedOut(), equalTo(false)); | ||||||||||
|
|
||||||||||
| // Relocated shard is not throttled | ||||||||||
| assertThat(shard.isIndexingPaused(), equalTo(false)); | ||||||||||
|
||||||||||
| logger.info("--> verifying count after relocation ..."); | ||||||||||
ankikuma marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||
| indicesAdmin().prepareRefresh().get(); | ||||||||||
| assertHitCount(prepareSearch("test").setSize(0), 21); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| public void testRelocationWhileIndexingRandom() throws Exception { | ||||||||||
| int numberOfRelocations = scaledRandomIntBetween(1, rarely() ? 10 : 4); | ||||||||||
| int numberOfReplicas = randomBoolean() ? 0 : 1; | ||||||||||
| int numberOfNodes = numberOfReplicas == 0 ? 2 : 3; | ||||||||||
| boolean throttleIndexing = randomBoolean(); | ||||||||||
|
|
||||||||||
| logger.info( | ||||||||||
| "testRelocationWhileIndexingRandom(numRelocations={}, numberOfReplicas={}, numberOfNodes={})", | ||||||||||
|
|
@@ -174,9 +248,12 @@ public void testRelocationWhileIndexingRandom() throws Exception { | |||||||||
| numberOfNodes | ||||||||||
| ); | ||||||||||
|
|
||||||||||
| // Start node with PAUSE_INDEXING_ON_THROTTLE setting set to true. This means that if we activate | ||||||||||
| // index throttling for a shard on this node, it will pause indexing for that shard until throttling | ||||||||||
| // is deactivated. | ||||||||||
|
||||||||||
| // Start node with PAUSE_INDEXING_ON_THROTTLE setting set to true. This means that if we activate | |
| // index throttling for a shard on this node, it will pause indexing for that shard until throttling | |
| // is deactivated. | |
| // Randomly use pause throttling vs lock throttling, to verify that relocations proceed regardless |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!
ankikuma marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
ankikuma marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -460,7 +460,10 @@ protected static final class IndexThrottle { | |
| private final Condition pauseCondition = pauseIndexingLock.newCondition(); | ||
| private final ReleasableLock pauseLockReference = new ReleasableLock(pauseIndexingLock); | ||
| private volatile AtomicBoolean suspendThrottling = new AtomicBoolean(); | ||
| private final boolean pauseWhenThrottled; // Should throttling pause indexing ? | ||
|
|
||
| // Should throttling pause indexing ? This is decided by the | ||
| // IndexingMemoryController#PAUSE_INDEXING_ON_THROTTLE setting for this node. | ||
| private final boolean pauseWhenThrottled; | ||
| private volatile ReleasableLock lock = NOOP_LOCK; | ||
|
|
||
| public IndexThrottle(boolean pause) { | ||
|
|
@@ -491,7 +494,6 @@ public Releasable acquireThrottle() { | |
| /** Activate throttling, which switches the lock to be a real lock */ | ||
| public void activate() { | ||
| assert lock == NOOP_LOCK : "throttling activated while already active"; | ||
|
|
||
| startOfThrottleNS = System.nanoTime(); | ||
| if (pauseWhenThrottled) { | ||
| lock = pauseLockReference; | ||
|
|
@@ -539,10 +541,14 @@ boolean isThrottled() { | |
| return lock != NOOP_LOCK; | ||
| } | ||
|
|
||
| boolean isIndexingPaused() { | ||
| return (lock == pauseLockReference); | ||
| } | ||
|
|
||
| /** Suspend throttling to allow another task such as relocation to acquire all indexing permits */ | ||
| public void suspendThrottle() { | ||
| if (pauseWhenThrottled) { | ||
| try (Releasable releasableLock = pauseLockReference.acquire()) { | ||
| try (Releasable ignored = pauseLockReference.acquire()) { | ||
| suspendThrottling.setRelease(true); | ||
| pauseCondition.signalAll(); | ||
| } | ||
|
|
@@ -552,7 +558,7 @@ public void suspendThrottle() { | |
| /** Reverse what was done in {@link #suspendThrottle()} */ | ||
| public void resumeThrottle() { | ||
| if (pauseWhenThrottled) { | ||
| try (Releasable releasableLock = pauseLockReference.acquire()) { | ||
| try (Releasable ignored = pauseLockReference.acquire()) { | ||
| suspendThrottling.setRelease(false); | ||
| pauseCondition.signalAll(); | ||
| } | ||
|
|
@@ -2258,6 +2264,20 @@ public interface Warmer { | |
| */ | ||
| public abstract void deactivateThrottling(); | ||
|
|
||
| /** | ||
| * If indexing is throttled to the point where it is paused completely, | ||
| * another task trying to get indexing permits might want to pause throttling | ||
| * by letting one thread pass at a time so that it does not get starved. | ||
| */ | ||
| public abstract void suspendThrottling(); | ||
|
|
||
| /** | ||
| * Reverses a previous {@link #suspendThrottling} call. | ||
| */ | ||
| public abstract void resumeThrottling(); | ||
|
|
||
| public abstract boolean isIndexingPaused(); | ||
|
||
|
|
||
| /** | ||
| * This method replays translog to restore the Lucene index which might be reverted previously. | ||
| * This ensures that all acknowledged writes are restored correctly when this engine is promoted. | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -879,8 +879,14 @@ public void onFailure(Exception e) { | |||||
| listener.onFailure(e); | ||||||
| } | ||||||
| } | ||||||
| }, 30L, TimeUnit.MINUTES, EsExecutors.DIRECT_EXECUTOR_SERVICE); // Wait on current thread because this execution is wrapped by | ||||||
| // CancellableThreads and we want to be able to interrupt it | ||||||
| }, | ||||||
| 30L, | ||||||
| TimeUnit.MINUTES, | ||||||
| // Wait on current thread because this execution is wrapped by CancellableThreads and we want to be able to interrupt it | ||||||
| EsExecutors.DIRECT_EXECUTOR_SERVICE, | ||||||
| this | ||||||
| ); | ||||||
|
|
||||||
| } | ||||||
| } | ||||||
|
|
||||||
|
|
@@ -2752,6 +2758,40 @@ public void deactivateThrottling() { | |||||
| } | ||||||
| } | ||||||
|
|
||||||
| public boolean isIndexingPaused() { | ||||||
|
||||||
| Engine engine = getEngineOrNull(); | ||||||
| final boolean indexingPaused; | ||||||
| if (engine == null) { | ||||||
| indexingPaused = false; | ||||||
| } else { | ||||||
| indexingPaused = engine.isIndexingPaused(); | ||||||
| } | ||||||
| return (indexingPaused); | ||||||
| } | ||||||
|
|
||||||
| public boolean suspendThrottling() { | ||||||
ankikuma marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
| Engine engine = getEngineOrNull(); | ||||||
| final boolean indexingPaused; | ||||||
| if (engine == null) { | ||||||
| indexingPaused = false; | ||||||
| } else { | ||||||
| indexingPaused = engine.isIndexingPaused(); | ||||||
| } | ||||||
| if (indexingPaused) { | ||||||
ankikuma marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
| engine.suspendThrottling(); | ||||||
| return (true); | ||||||
| } | ||||||
| return (false); | ||||||
| } | ||||||
|
|
||||||
| public void resumeThrottling() { | ||||||
|
||||||
| public void resumeThrottling() { | |
| private void resumeThrottling() { |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -84,10 +84,16 @@ public void blockOperations( | |
| final ActionListener<Releasable> onAcquired, | ||
| final long timeout, | ||
| final TimeUnit timeUnit, | ||
| final Executor executor | ||
| final Executor executor, | ||
| @Nullable IndexShard indexShard | ||
| ) { | ||
| delayOperations(); | ||
| // In case indexing is paused on the shard, suspend throttling so that any currently paused task can | ||
| // go ahead and release the indexing permit it holds. | ||
| indexShard.suspendThrottling(); | ||
| waitUntilBlocked(ActionListener.assertOnce(onAcquired), timeout, timeUnit, executor); | ||
| // TODO: Does this do anything ? Looks like the relocated shard does not have throttling enabled | ||
| indexShard.resumeThrottling(); | ||
|
||
| } | ||
|
|
||
| private void waitUntilBlocked(ActionListener<Releasable> onAcquired, long timeout, TimeUnit timeUnit, Executor executor) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let us randomize the doc count to signal that 10 is not a special number:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.