Skip to content

Commit e377a86

Browse files
authored
Simplify IndicesShardStoresAction (#94507)
- No need to use an `AsyncShardFetch` here, there is no caching - Response may be very large, introduce chunking - Fan-out may be very large, introduce throttling - Processing time may be nontrivial, introduce cancellability - Eliminate many unnecessary intermediate data structures - Do shard-level response processing more eagerly - Determine allocation from `RoutingTable` not `RoutingNodes` - Add tests Relates #81081
1 parent acf1348 commit e377a86

File tree

9 files changed

+595
-207
lines changed

9 files changed

+595
-207
lines changed

docs/reference/indices/shard-stores.asciidoc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,16 @@ regardless of health status.
9393
Defaults to `yellow,red`.
9494
--
9595

96+
`max_concurrent_shard_requests`::
97+
+
98+
--
99+
(Optional, integer)
100+
Maximum number of concurrent shard-level requests sent by the coordinating
101+
node. Defaults to `100`. Larger values may yield a quicker response to requests
102+
that target many shards, but may also cause a larger impact on other cluster
103+
operations.
104+
--
105+
96106
[[index-shard-stores-api-example]]
97107
==== {api-examples-title}
98108

server/src/main/java/org/elasticsearch/action/admin/indices/shards/IndicesShardStoresRequest.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88
package org.elasticsearch.action.admin.indices.shards;
99

10+
import org.elasticsearch.TransportVersion;
1011
import org.elasticsearch.action.ActionRequestValidationException;
1112
import org.elasticsearch.action.IndicesRequest;
1213
import org.elasticsearch.action.support.IndicesOptions;
@@ -15,18 +16,25 @@
1516
import org.elasticsearch.common.Strings;
1617
import org.elasticsearch.common.io.stream.StreamInput;
1718
import org.elasticsearch.common.io.stream.StreamOutput;
19+
import org.elasticsearch.tasks.CancellableTask;
20+
import org.elasticsearch.tasks.Task;
21+
import org.elasticsearch.tasks.TaskId;
1822

1923
import java.io.IOException;
2024
import java.util.EnumSet;
25+
import java.util.Map;
2126

2227
/**
2328
* Request for {@link IndicesShardStoresAction}
2429
*/
2530
public class IndicesShardStoresRequest extends MasterNodeReadRequest<IndicesShardStoresRequest> implements IndicesRequest.Replaceable {
2631

32+
static final int DEFAULT_MAX_CONCURRENT_SHARD_REQUESTS = 100;
33+
2734
private String[] indices = Strings.EMPTY_ARRAY;
2835
private IndicesOptions indicesOptions = IndicesOptions.strictExpand();
2936
private EnumSet<ClusterHealthStatus> statuses = EnumSet.of(ClusterHealthStatus.YELLOW, ClusterHealthStatus.RED);
37+
private int maxConcurrentShardRequests = DEFAULT_MAX_CONCURRENT_SHARD_REQUESTS;
3038

3139
/**
3240
* Create a request for shard stores info for <code>indices</code>
@@ -46,6 +54,12 @@ public IndicesShardStoresRequest(StreamInput in) throws IOException {
4654
statuses.add(ClusterHealthStatus.readFrom(in));
4755
}
4856
indicesOptions = IndicesOptions.readIndicesOptions(in);
57+
if (in.getTransportVersion().onOrAfter(TransportVersion.V_8_8_0)) {
58+
maxConcurrentShardRequests = in.readVInt();
59+
} else {
60+
// earlier versions had unlimited concurrency
61+
maxConcurrentShardRequests = Integer.MAX_VALUE;
62+
}
4963
}
5064

5165
@Override
@@ -54,6 +68,17 @@ public void writeTo(StreamOutput out) throws IOException {
5468
out.writeStringArrayNullable(indices);
5569
out.writeCollection(statuses, (o, v) -> o.writeByte(v.value()));
5670
indicesOptions.writeIndicesOptions(out);
71+
if (out.getTransportVersion().onOrAfter(TransportVersion.V_8_8_0)) {
72+
out.writeVInt(maxConcurrentShardRequests);
73+
} else if (maxConcurrentShardRequests != DEFAULT_MAX_CONCURRENT_SHARD_REQUESTS) {
74+
throw new IllegalArgumentException(
75+
"support for maxConcurrentShardRequests=["
76+
+ maxConcurrentShardRequests
77+
+ "] was added in version [8.8.0], cannot send this request using transport version ["
78+
+ out.getTransportVersion()
79+
+ "]"
80+
);
81+
} // else just drop the value and use the default behaviour
5782
}
5883

5984
/**
@@ -114,8 +139,21 @@ public IndicesOptions indicesOptions() {
114139
return indicesOptions;
115140
}
116141

142+
public void maxConcurrentShardRequests(int maxConcurrentShardRequests) {
143+
this.maxConcurrentShardRequests = maxConcurrentShardRequests;
144+
}
145+
146+
public int maxConcurrentShardRequests() {
147+
return maxConcurrentShardRequests;
148+
}
149+
117150
@Override
118151
public ActionRequestValidationException validate() {
119152
return null;
120153
}
154+
155+
@Override
156+
public Task createTask(long id, String type, String action, TaskId parentTaskId, Map<String, String> headers) {
157+
return new CancellableTask(id, type, action, "", parentTaskId, headers);
158+
}
121159
}

server/src/main/java/org/elasticsearch/action/admin/indices/shards/IndicesShardStoresResponse.java

Lines changed: 47 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,19 @@
1212
import org.elasticsearch.action.ActionResponse;
1313
import org.elasticsearch.action.support.DefaultShardOperationFailedException;
1414
import org.elasticsearch.cluster.node.DiscoveryNode;
15+
import org.elasticsearch.common.collect.Iterators;
1516
import org.elasticsearch.common.io.stream.StreamInput;
1617
import org.elasticsearch.common.io.stream.StreamOutput;
1718
import org.elasticsearch.common.io.stream.Writeable;
19+
import org.elasticsearch.common.xcontent.ChunkedToXContentHelper;
20+
import org.elasticsearch.common.xcontent.ChunkedToXContentObject;
21+
import org.elasticsearch.xcontent.ToXContent;
1822
import org.elasticsearch.xcontent.ToXContentFragment;
1923
import org.elasticsearch.xcontent.XContentBuilder;
2024

2125
import java.io.IOException;
26+
import java.util.Collections;
27+
import java.util.Iterator;
2228
import java.util.List;
2329
import java.util.Map;
2430

@@ -28,7 +34,7 @@
2834
* Consists of {@link StoreStatus}s for requested indices grouped by
2935
* indices and shard ids and a list of encountered node {@link Failure}s
3036
*/
31-
public class IndicesShardStoresResponse extends ActionResponse implements ToXContentFragment {
37+
public class IndicesShardStoresResponse extends ActionResponse implements ChunkedToXContentObject {
3238

3339
/**
3440
* Shard store information from a node
@@ -196,7 +202,7 @@ public int compareTo(StoreStatus other) {
196202
* Single node failure while retrieving shard store information
197203
*/
198204
public static class Failure extends DefaultShardOperationFailedException {
199-
private String nodeId;
205+
private final String nodeId;
200206

201207
public Failure(String nodeId, String index, int shardId, Throwable reason) {
202208
super(index, shardId, reason);
@@ -273,38 +279,45 @@ public void writeTo(StreamOutput out) throws IOException {
273279
}
274280

275281
@Override
276-
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
277-
if (failures.size() > 0) {
278-
builder.startArray(Fields.FAILURES);
279-
for (Failure failure : failures) {
280-
failure.toXContent(builder, params);
281-
}
282-
builder.endArray();
283-
}
284-
285-
builder.startObject(Fields.INDICES);
286-
for (Map.Entry<String, Map<Integer, List<StoreStatus>>> indexShards : storeStatuses.entrySet()) {
287-
builder.startObject(indexShards.getKey());
288-
289-
builder.startObject(Fields.SHARDS);
290-
for (Map.Entry<Integer, List<StoreStatus>> shardStatusesEntry : indexShards.getValue().entrySet()) {
291-
builder.startObject(String.valueOf(shardStatusesEntry.getKey()));
292-
builder.startArray(Fields.STORES);
293-
for (StoreStatus storeStatus : shardStatusesEntry.getValue()) {
294-
builder.startObject();
295-
storeStatus.toXContent(builder, params);
296-
builder.endObject();
297-
}
298-
builder.endArray();
299-
300-
builder.endObject();
301-
}
302-
builder.endObject();
303-
304-
builder.endObject();
305-
}
306-
builder.endObject();
307-
return builder;
282+
public Iterator<? extends ToXContent> toXContentChunked(ToXContent.Params outerParams) {
283+
return Iterators.concat(
284+
ChunkedToXContentHelper.startObject(),
285+
286+
failures.isEmpty()
287+
? Collections.emptyIterator()
288+
: Iterators.concat(
289+
ChunkedToXContentHelper.startArray(Fields.FAILURES),
290+
failures.iterator(),
291+
ChunkedToXContentHelper.endArray()
292+
),
293+
294+
ChunkedToXContentHelper.startObject(Fields.INDICES),
295+
296+
Iterators.flatMap(
297+
storeStatuses.entrySet().iterator(),
298+
indexShards -> Iterators.concat(
299+
ChunkedToXContentHelper.startObject(indexShards.getKey()),
300+
ChunkedToXContentHelper.startObject(Fields.SHARDS),
301+
Iterators.flatMap(
302+
indexShards.getValue().entrySet().iterator(),
303+
shardStatusesEntry -> Iterators.single((ToXContent) (builder, params) -> {
304+
builder.startObject(String.valueOf(shardStatusesEntry.getKey())).startArray(Fields.STORES);
305+
for (StoreStatus storeStatus : shardStatusesEntry.getValue()) {
306+
builder.startObject();
307+
storeStatus.toXContent(builder, params);
308+
builder.endObject();
309+
}
310+
return builder.endArray().endObject();
311+
})
312+
),
313+
ChunkedToXContentHelper.endObject(),
314+
ChunkedToXContentHelper.endObject()
315+
)
316+
),
317+
318+
ChunkedToXContentHelper.endObject(),
319+
ChunkedToXContentHelper.endObject()
320+
);
308321
}
309322

310323
static final class Fields {

0 commit comments

Comments
 (0)