Skip to content

Commit a9afdd7

Browse files
authored
Remove fixed_auto_queue_size threadpool type (#52280)
* Remove fixed_auto_queue_size threadpool type * Remove less * compilation fix * weaken assertion to accomodate tests that mock threadpool
1 parent cecee07 commit a9afdd7

File tree

32 files changed

+316
-1134
lines changed

32 files changed

+316
-1134
lines changed

docs/reference/migration/migrate_8_0.asciidoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,5 +81,6 @@ include::migrate_8_0/http.asciidoc[]
8181
include::migrate_8_0/reindex.asciidoc[]
8282
include::migrate_8_0/search.asciidoc[]
8383
include::migrate_8_0/settings.asciidoc[]
84+
include::migrate_8_0/threadpool.asciidoc[]
8485
include::migrate_8_0/indices.asciidoc[]
8586
include::migrate_8_0/api.asciidoc[]
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[float]
2+
[[breaking_80_threadpool_changes]]
3+
=== Thread pool changes
4+
5+
[float]
6+
==== Removal of the `fixed_auto_queue_size` thread pool type
7+
8+
The `fixed_auto_queue_size` thread pool type, previously marked as an
9+
experimental feature, was deprecated in 7.x and has been removed in 8.0.
10+
The `search` and `search_throttled` thread pools have the `fixed` type now.

docs/reference/modules/threadpool.asciidoc

Lines changed: 3 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,13 @@ There are several thread pools, but the important ones include:
1515

1616
`search`::
1717
For count/search/suggest operations. Thread pool type is
18-
`fixed_auto_queue_size` with a size of
19-
`int((# of available_processors * 3) / 2) + 1`, and initial queue_size of
18+
`fixed` with a size of
19+
`int((# of available_processors * 3) / 2) + 1`, and queue_size of
2020
`1000`.
2121

2222
[[search-throttled]]`search_throttled`::
2323
For count/search/suggest/get operations on `search_throttled indices`.
24-
Thread pool type is `fixed_auto_queue_size` with a size of `1`, and initial
25-
queue_size of `100`.
24+
Thread pool type is `fixed` with a size of `1`, and queue_size of `100`.
2625

2726
`get`::
2827
For get operations. Thread pool type is `fixed`
@@ -119,52 +118,6 @@ thread_pool:
119118
queue_size: 1000
120119
--------------------------------------------------
121120

122-
[float]
123-
[[fixed-auto-queue-size]]
124-
==== `fixed_auto_queue_size`
125-
126-
experimental[]
127-
128-
The `fixed_auto_queue_size` thread pool holds a fixed size of threads to handle
129-
the requests with a bounded queue for pending requests that have no threads to
130-
service them. It's similar to the `fixed` threadpool, however, the `queue_size`
131-
automatically adjusts according to calculations based on
132-
https://en.wikipedia.org/wiki/Little%27s_law[Little's Law]. These calculations
133-
will potentially adjust the `queue_size` up or down by 50 every time
134-
`auto_queue_frame_size` operations have been completed.
135-
136-
The `size` parameter controls the number of threads.
137-
138-
The `queue_size` allows to control the initial size of the queue of pending
139-
requests that have no threads to execute them.
140-
141-
The `min_queue_size` setting controls the minimum amount the `queue_size` can be
142-
adjusted to.
143-
144-
The `max_queue_size` setting controls the maximum amount the `queue_size` can be
145-
adjusted to.
146-
147-
The `auto_queue_frame_size` setting controls the number of operations during
148-
which measurement is taken before the queue is adjusted. It should be large
149-
enough that a single operation cannot unduly bias the calculation.
150-
151-
The `target_response_time` is a time value setting that indicates the targeted
152-
average response time for tasks in the thread pool queue. If tasks are routinely
153-
above this time, the thread pool queue will be adjusted down so that tasks are
154-
rejected.
155-
156-
[source,yaml]
157-
--------------------------------------------------
158-
thread_pool:
159-
search:
160-
size: 30
161-
queue_size: 500
162-
min_queue_size: 10
163-
max_queue_size: 1000
164-
auto_queue_frame_size: 2000
165-
target_response_time: 1s
166-
--------------------------------------------------
167-
168121
[float]
169122
[[scaling]]
170123
==== `scaling`

qa/evil-tests/src/test/java/org/elasticsearch/threadpool/EvilThreadPoolTests.java

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ public void testExecutionErrorOnDirectExecutorService() throws InterruptedExcept
7171

7272
public void testExecutionErrorOnFixedESThreadPoolExecutor() throws InterruptedException {
7373
final EsThreadPoolExecutor fixedExecutor = EsExecutors.newFixed("test", 1, 1,
74-
EsExecutors.daemonThreadFactory("test"), threadPool.getThreadContext());
74+
EsExecutors.daemonThreadFactory("test"), threadPool.getThreadContext(), randomBoolean());
7575
try {
7676
checkExecutionError(getExecuteRunner(fixedExecutor));
7777
checkExecutionError(getSubmitRunner(fixedExecutor));
@@ -91,17 +91,6 @@ public void testExecutionErrorOnScalingESThreadPoolExecutor() throws Interrupted
9191
}
9292
}
9393

94-
public void testExecutionErrorOnAutoQueueFixedESThreadPoolExecutor() throws InterruptedException {
95-
final EsThreadPoolExecutor autoQueueFixedExecutor = EsExecutors.newAutoQueueFixed("test", 1, 1,
96-
1, 1, 1, TimeValue.timeValueSeconds(10), EsExecutors.daemonThreadFactory("test"), threadPool.getThreadContext());
97-
try {
98-
checkExecutionError(getExecuteRunner(autoQueueFixedExecutor));
99-
checkExecutionError(getSubmitRunner(autoQueueFixedExecutor));
100-
} finally {
101-
ThreadPool.terminate(autoQueueFixedExecutor, 10, TimeUnit.SECONDS);
102-
}
103-
}
104-
10594
public void testExecutionErrorOnSinglePrioritizingThreadPoolExecutor() throws InterruptedException {
10695
final PrioritizedEsThreadPoolExecutor prioritizedExecutor = EsExecutors.newSinglePrioritizing("test",
10796
EsExecutors.daemonThreadFactory("test"), threadPool.getThreadContext(), threadPool.scheduler());
@@ -180,7 +169,7 @@ public void testExecutionExceptionOnDirectExecutorService() throws InterruptedEx
180169

181170
public void testExecutionExceptionOnFixedESThreadPoolExecutor() throws InterruptedException {
182171
final EsThreadPoolExecutor fixedExecutor = EsExecutors.newFixed("test", 1, 1,
183-
EsExecutors.daemonThreadFactory("test"), threadPool.getThreadContext());
172+
EsExecutors.daemonThreadFactory("test"), threadPool.getThreadContext(), randomBoolean());
184173
try {
185174
checkExecutionException(getExecuteRunner(fixedExecutor), true);
186175
checkExecutionException(getSubmitRunner(fixedExecutor), false);
@@ -200,18 +189,6 @@ public void testExecutionExceptionOnScalingESThreadPoolExecutor() throws Interru
200189
}
201190
}
202191

203-
public void testExecutionExceptionOnAutoQueueFixedESThreadPoolExecutor() throws InterruptedException {
204-
final EsThreadPoolExecutor autoQueueFixedExecutor = EsExecutors.newAutoQueueFixed("test", 1, 1,
205-
1, 1, 1, TimeValue.timeValueSeconds(10), EsExecutors.daemonThreadFactory("test"), threadPool.getThreadContext());
206-
try {
207-
// fixed_auto_queue_size wraps stuff into TimedRunnable, which is an AbstractRunnable
208-
checkExecutionException(getExecuteRunner(autoQueueFixedExecutor), true);
209-
checkExecutionException(getSubmitRunner(autoQueueFixedExecutor), false);
210-
} finally {
211-
ThreadPool.terminate(autoQueueFixedExecutor, 10, TimeUnit.SECONDS);
212-
}
213-
}
214-
215192
public void testExecutionExceptionOnSinglePrioritizingThreadPoolExecutor() throws InterruptedException {
216193
final PrioritizedEsThreadPoolExecutor prioritizedExecutor = EsExecutors.newSinglePrioritizing("test",
217194
EsExecutors.daemonThreadFactory("test"), threadPool.getThreadContext(), threadPool.scheduler());
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.common.util.concurrent;
21+
22+
import org.elasticsearch.common.ExponentiallyWeightedMovingAverage;
23+
import org.elasticsearch.common.unit.TimeValue;
24+
25+
import java.util.concurrent.BlockingQueue;
26+
import java.util.concurrent.ThreadFactory;
27+
import java.util.concurrent.TimeUnit;
28+
import java.util.function.Function;
29+
30+
/**
31+
* An extension to thread pool executor, which tracks the exponentially weighted moving average of the task execution time.
32+
*/
33+
public final class EWMATrackingEsThreadPoolExecutor extends EsThreadPoolExecutor {
34+
35+
// This is a random starting point alpha. TODO: revisit this with actual testing and/or make it configurable
36+
public static double EWMA_ALPHA = 0.3;
37+
38+
private final Function<Runnable, WrappedRunnable> runnableWrapper;
39+
private final ExponentiallyWeightedMovingAverage executionEWMA;
40+
41+
EWMATrackingEsThreadPoolExecutor(String name, int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
42+
BlockingQueue<Runnable> workQueue, Function<Runnable, WrappedRunnable> runnableWrapper,
43+
ThreadFactory threadFactory, XRejectedExecutionHandler handler, ThreadContext contextHolder) {
44+
super(name, corePoolSize, maximumPoolSize, keepAliveTime, unit,
45+
workQueue, threadFactory, handler, contextHolder);
46+
this.runnableWrapper = runnableWrapper;
47+
this.executionEWMA = new ExponentiallyWeightedMovingAverage(EWMA_ALPHA, 0);
48+
}
49+
50+
@Override
51+
protected Runnable wrapRunnable(Runnable command) {
52+
return super.wrapRunnable(this.runnableWrapper.apply(command));
53+
}
54+
55+
@Override
56+
protected Runnable unwrap(Runnable runnable) {
57+
final Runnable unwrapped = super.unwrap(runnable);
58+
if (unwrapped instanceof WrappedRunnable) {
59+
return ((WrappedRunnable) unwrapped).unwrap();
60+
} else {
61+
return unwrapped;
62+
}
63+
}
64+
65+
/**
66+
* Returns the exponentially weighted moving average of the task execution time
67+
*/
68+
public double getTaskExecutionEWMA() {
69+
return executionEWMA.getAverage();
70+
}
71+
72+
/**
73+
* Returns the current queue size (operations that are queued)
74+
*/
75+
public int getCurrentQueueSize() {
76+
return getQueue().size();
77+
}
78+
79+
@Override
80+
protected void afterExecute(Runnable r, Throwable t) {
81+
super.afterExecute(r, t);
82+
// A task has been completed, it has left the building. We should now be able to get the
83+
// total time as a combination of the time in the queue and time spent running the task. We
84+
// only want runnables that did not throw errors though, because they could be fast-failures
85+
// that throw off our timings, so only check when t is null.
86+
assert super.unwrap(r) instanceof TimedRunnable : "expected only TimedRunnables in queue";
87+
final TimedRunnable timedRunnable = (TimedRunnable) super.unwrap(r);
88+
final boolean failedOrRejected = timedRunnable.getFailedOrRejected();
89+
final long taskExecutionNanos = timedRunnable.getTotalExecutionNanos();
90+
assert taskExecutionNanos >= 0 || (failedOrRejected && taskExecutionNanos == -1) :
91+
"expected task to always take longer than 0 nanoseconds or have '-1' failure code, got: " + taskExecutionNanos +
92+
", failedOrRejected: " + failedOrRejected;
93+
if (taskExecutionNanos != -1) {
94+
// taskExecutionNanos may be -1 if the task threw an exception
95+
executionEWMA.addValue(taskExecutionNanos);
96+
}
97+
}
98+
99+
@Override
100+
protected void appendThreadPoolExecutorDetails(StringBuilder sb) {
101+
sb.append("task execution EWMA = ").append(TimeValue.timeValueNanos((long) executionEWMA.getAverage())).append(", ");
102+
}
103+
104+
}

server/src/main/java/org/elasticsearch/common/util/concurrent/EsExecutors.java

Lines changed: 7 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import org.elasticsearch.common.settings.Setting;
2525
import org.elasticsearch.common.settings.Setting.Property;
2626
import org.elasticsearch.common.settings.Settings;
27-
import org.elasticsearch.common.unit.TimeValue;
2827
import org.elasticsearch.node.Node;
2928

3029
import java.util.Arrays;
@@ -83,38 +82,20 @@ public static EsThreadPoolExecutor newScaling(String name, int min, int max, lon
8382
}
8483

8584
public static EsThreadPoolExecutor newFixed(String name, int size, int queueCapacity,
86-
ThreadFactory threadFactory, ThreadContext contextHolder) {
85+
ThreadFactory threadFactory, ThreadContext contextHolder, boolean trackEWMA) {
8786
BlockingQueue<Runnable> queue;
8887
if (queueCapacity < 0) {
8988
queue = ConcurrentCollections.newBlockingQueue();
9089
} else {
9190
queue = new SizeBlockingQueue<>(ConcurrentCollections.<Runnable>newBlockingQueue(), queueCapacity);
9291
}
93-
return new EsThreadPoolExecutor(name, size, size, 0, TimeUnit.MILLISECONDS,
94-
queue, threadFactory, new EsAbortPolicy(), contextHolder);
95-
}
96-
97-
/**
98-
* Return a new executor that will automatically adjust the queue size based on queue throughput.
99-
*
100-
* @param size number of fixed threads to use for executing tasks
101-
* @param initialQueueCapacity initial size of the executor queue
102-
* @param minQueueSize minimum queue size that the queue can be adjusted to
103-
* @param maxQueueSize maximum queue size that the queue can be adjusted to
104-
* @param frameSize number of tasks during which stats are collected before adjusting queue size
105-
*/
106-
public static EsThreadPoolExecutor newAutoQueueFixed(String name, int size, int initialQueueCapacity, int minQueueSize,
107-
int maxQueueSize, int frameSize, TimeValue targetedResponseTime,
108-
ThreadFactory threadFactory, ThreadContext contextHolder) {
109-
if (initialQueueCapacity <= 0) {
110-
throw new IllegalArgumentException("initial queue capacity for [" + name + "] executor must be positive, got: " +
111-
initialQueueCapacity);
92+
if (trackEWMA) {
93+
return new EWMATrackingEsThreadPoolExecutor(name, size, size, 0, TimeUnit.MILLISECONDS,
94+
queue, TimedRunnable::new, threadFactory, new EsAbortPolicy(), contextHolder);
95+
} else {
96+
return new EsThreadPoolExecutor(name, size, size, 0, TimeUnit.MILLISECONDS,
97+
queue, threadFactory, new EsAbortPolicy(), contextHolder);
11298
}
113-
ResizableBlockingQueue<Runnable> queue =
114-
new ResizableBlockingQueue<>(ConcurrentCollections.<Runnable>newBlockingQueue(), initialQueueCapacity);
115-
return new QueueResizingEsThreadPoolExecutor(name, size, size, 0, TimeUnit.MILLISECONDS,
116-
queue, minQueueSize, maxQueueSize, TimedRunnable::new, frameSize, targetedResponseTime, threadFactory,
117-
new EsAbortPolicy(), contextHolder);
11899
}
119100

120101
/**

0 commit comments

Comments
 (0)