Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ public class CatShardsRequest extends ClusterManagerNodeReadRequest<CatShardsReq
private TimeValue cancelAfterTimeInterval;
private PageParams pageParams = null;
private boolean requestLimitCheckSupported;
// True (default) preserves existing behavior: the transport action fetches IndicesStats for all
// requested shards. When the REST layer determines that no requested column (h= or s=) requires
// per-shard stats, it sets this to false to skip the broadcast fan-out entirely.
private boolean indicesStatsRequired = true;

public CatShardsRequest() {}

Expand All @@ -45,6 +49,9 @@ public CatShardsRequest(StreamInput in) throws IOException {
}
requestLimitCheckSupported = in.readBoolean();
}
if (in.getVersion().onOrAfter(Version.V_3_8_0)) {
indicesStatsRequired = in.readBoolean();
}
}

@Override
Expand All @@ -63,6 +70,9 @@ public void writeTo(StreamOutput out) throws IOException {
}
out.writeBoolean(requestLimitCheckSupported);
}
if (out.getVersion().onOrAfter(Version.V_3_8_0)) {
out.writeBoolean(indicesStatsRequired);
}
}

@Override
Expand Down Expand Up @@ -102,6 +112,14 @@ public boolean isRequestLimitCheckSupported() {
return this.requestLimitCheckSupported;
}

public void setIndicesStatsRequired(boolean indicesStatsRequired) {
this.indicesStatsRequired = indicesStatsRequired;
}

public boolean isIndicesStatsRequired() {
return this.indicesStatsRequired;
}

@Override
public ClusterAdminTask createTask(long id, String type, String action, TaskId parentTaskId, Map<String, String> headers) {
return new ClusterAdminTask(id, type, action, parentTaskId, headers, this.cancelAfterTimeInterval);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,15 @@ public void onResponse(ClusterStateResponse clusterStateResponse) {
cancellableListener.onResponse(catShardsResponse);
return;
}
// Fast path: if the REST layer determined that no requested column (h=/s=)
// requires per-shard stats, skip the IndicesStats broadcast entirely. The
// resulting Table has null cells for stats columns, which are not displayed
// because they were not in h=.
if (shardsRequest.isIndicesStatsRequired() == false) {
catShardsResponse.setIndicesStatsResponse(IndicesStatsResponse.getEmptyResponse());
cancellableListener.onResponse(catShardsResponse);
return;
}
IndicesStatsRequest indicesStatsRequest = new IndicesStatsRequest();
indicesStatsRequest.setShouldCancelOnTimeout(true);
indicesStatsRequest.all();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ public RestChannelConsumer prepareRequest(final RestRequest request, final NodeC
}

static Set<String> RESPONSE_PARAMS = Collections.unmodifiableSet(
new HashSet<>(Arrays.asList("format", "h", "v", "ts", "pri", "bytes", "size", "time", "s", "timeout"))
new HashSet<>(Arrays.asList("format", "h", "v", "ts", "pri", "bytes", "size", "time", "s", "timeout", "limit"))
);

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
import java.time.Instant;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.function.Function;

import static java.util.Arrays.asList;
Expand Down Expand Up @@ -123,6 +124,7 @@ public RestChannelConsumer doCatRequest(final RestRequest request, final NodeCli
shardsRequest.setIndices(indices);
shardsRequest.setRequestLimitCheckSupported(isRequestLimitCheckSupported());
shardsRequest.setPageParams(pageParams);
shardsRequest.setIndicesStatsRequired(requestNeedsIndicesStats(request));
parseDeprecatedMasterTimeoutParameter(shardsRequest, request, deprecationLogger, getName());
return channel -> client.execute(CatShardsAction.INSTANCE, shardsRequest, new RestResponseListener<CatShardsResponse>(channel) {
@Override
Expand All @@ -141,6 +143,94 @@ public RestResponse buildResponse(CatShardsResponse catShardsResponse) throws Ex
});
}

/**
* Canonical names and aliases for shard columns that are derivable purely from cluster state
* (ShardRouting + DiscoveryNodes + UnassignedInfo). These do NOT require a per-shard
* IndicesStats broadcast.
*/
private static final Set<String> ROUTING_ONLY_COLUMNS = Set.of(
// index
"index",
"i",
"idx",
// shard
"shard",
"s",
"sh",
// prirep
"prirep",
"p",
"pr",
"primaryOrReplica",
// state
"state",
"st",
// node identity
"ip",
"id",
"node",
"n",
// unassigned info
"unassigned.reason",
"ur",
"unassigned.at",
"ua",
"unassigned.for",
"uf",
"unassigned.details",
"ud",
// recovery source type (derived from ShardRouting.recoverySource)
"recoverysource.type",
"rs"
);

/**
* Returns true if the IndicesStats broadcast is required to build the response. The fast path
* (false) applies only when:
* <ul>
* <li>h= is explicitly set (the default header set includes 'docs' and 'store' which need stats)</li>
* <li>every token in h= matches a routing-only column (no wildcards, no stats columns)</li>
* <li>s= (if set) only references routing-only columns</li>
* </ul>
* In all other cases we err on the side of correctness and fetch stats as today.
*
* Package-private for testing.
*/
static boolean requestNeedsIndicesStats(RestRequest request) {
String hParam = request.param("h");
if (hParam == null || hParam.isEmpty()) {
return true; // default headers include stats columns
}
for (String token : Strings.splitStringByCommaToArray(hParam)) {
if (isRoutingOnlyToken(token) == false) {
return true;
}
}
String sParam = request.param("s");
if (sParam != null && sParam.isEmpty() == false) {
for (String token : Strings.splitStringByCommaToArray(sParam)) {
// strip :asc / :desc suffix before checking (case-insensitive, whitespace-tolerant)
String col = token.trim();
String lower = col.toLowerCase(Locale.ROOT);
if (lower.endsWith(":desc")) col = col.substring(0, col.length() - 5);
else if (lower.endsWith(":asc")) col = col.substring(0, col.length() - 4);
if (isRoutingOnlyToken(col) == false) {
return true;
}
}
}
return false;
}

private static boolean isRoutingOnlyToken(String token) {
if (token == null) return false;
String trimmed = token.trim();
if (trimmed.isEmpty()) return false;
// Wildcards conservatively force the slow path — we don't expand them here.
if (trimmed.indexOf('*') >= 0 || trimmed.indexOf('?') >= 0) return false;
return ROUTING_ONLY_COLUMNS.contains(trimmed);
}

@Override
protected Table getTableWithHeader(final RestRequest request) {
return getTableWithHeader(request, null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,15 @@ public class RestTable {

public static RestResponse buildResponse(Table table, RestChannel channel) throws Exception {
RestRequest request = channel.request();
// Non-consuming read: buildDisplayHeaders later calls request.param("h") to mark it
// consumed. Accessing the raw map here avoids reordering param-consumption semantics.
String hParam = request.params().get("h");
// Grouping/aggregation is inferred from h=: if any h= token is an aggregation function
// (e.g. sum(docs)), summarize the table; bare tokens are treated as GROUP BY keys. Plain
// h= listings (no functions) are returned unchanged by TableSummarizer.summarize.
if (TableSummarizer.hasAggregation(hParam)) {
table = TableSummarizer.summarize(table, hParam);
}
MediaType mediaType = getXContentType(request);
if (mediaType != null) {
return buildXContentBuilder(table, channel);
Expand Down Expand Up @@ -205,6 +214,13 @@ static List<Integer> getRowOrder(Table table, RestRequest request) {
}
Collections.sort(rowOrder, new TableIndexComparator(table, ordering));
}

// Apply the limit parameter (if positive). Applied after sort so `?s=field:desc&limit=N`
// yields the top-N rows by the sort key. This also caps unsummarized responses.
int limit = request.paramAsInt("limit", -1);
if (limit > 0 && rowOrder.size() > limit) {
rowOrder = new ArrayList<>(rowOrder.subList(0, limit));
}
return rowOrder;
}

Expand Down Expand Up @@ -391,7 +407,8 @@ public static void pad(Table.Cell cell, int width, RestRequest request, UTF8Stre
}
}

private static String renderValue(RestRequest request, Object value) {
// package-private for testing
static String renderValue(RestRequest request, Object value) {
if (value == null) {
return null;
}
Expand Down
Loading
Loading