From e0b4e4b743b5c217d02b7eb4c3a9d6ca9aeaf996 Mon Sep 17 00:00:00 2001 From: Dimitris Rempapis Date: Fri, 30 May 2025 11:23:56 +0300 Subject: [PATCH 01/25] Calculate search load per shard in multiproject env --- .../index/shard/IndexShardIT.java | 4 +- .../org/elasticsearch/TransportVersions.java | 1 + .../elasticsearch/action/ActionModule.java | 3 + .../search/load/ShardSearchLoadStats.java | 130 ++++++++++ .../load/ShardSearchLoadStatsAction.java | 44 ++++ .../load/ShardSearchLoadStatsResponse.java | 89 +++++++ .../TransportShardSearchLoadStatsAction.java | 223 ++++++++++++++++++ .../common/settings/ClusterSettings.java | 2 + .../org/elasticsearch/index/IndexModule.java | 9 +- .../org/elasticsearch/index/IndexService.java | 9 +- .../index/search/stats/SearchStats.java | 33 ++- .../search/stats/SearchStatsSettings.java | 50 ++++ .../index/search/stats/ShardSearchStats.java | 24 +- .../elasticsearch/index/shard/IndexShard.java | 14 +- .../elasticsearch/indices/IndicesService.java | 9 +- .../cluster/node/stats/NodeStatsTests.java | 1 + .../elasticsearch/index/IndexModuleTests.java | 19 +- .../index/search/stats/SearchStatsTests.java | 6 +- .../stats/SearchStatsSettingsTests.java | 56 +++++ .../index/shard/IndexShardTestCase.java | 4 +- .../indices/IndexStatsMonitoringDocTests.java | 18 +- .../IndicesStatsMonitoringDocTests.java | 2 +- .../node/NodeStatsMonitoringDocTests.java | 18 +- .../xpack/security/SecurityTests.java | 4 +- .../xpack/watcher/WatcherPluginTests.java | 4 +- 25 files changed, 746 insertions(+), 30 deletions(-) create mode 100644 server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStats.java create mode 100644 server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStatsAction.java create mode 100644 server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStatsResponse.java create mode 100644 server/src/main/java/org/elasticsearch/action/search/load/TransportShardSearchLoadStatsAction.java create mode 100644 server/src/main/java/org/elasticsearch/index/search/stats/SearchStatsSettings.java create mode 100644 server/src/test/java/org/elasticsearch/search/stats/SearchStatsSettingsTests.java diff --git a/server/src/internalClusterTest/java/org/elasticsearch/index/shard/IndexShardIT.java b/server/src/internalClusterTest/java/org/elasticsearch/index/shard/IndexShardIT.java index 507724bbaeb3c..11e8b15432d35 100644 --- a/server/src/internalClusterTest/java/org/elasticsearch/index/shard/IndexShardIT.java +++ b/server/src/internalClusterTest/java/org/elasticsearch/index/shard/IndexShardIT.java @@ -53,6 +53,7 @@ import org.elasticsearch.index.flush.FlushStats; import org.elasticsearch.index.mapper.MapperMetrics; import org.elasticsearch.index.mapper.SourceToParse; +import org.elasticsearch.index.search.stats.SearchStatsSettings; import org.elasticsearch.index.seqno.RetentionLeaseSyncer; import org.elasticsearch.index.seqno.SequenceNumbers; import org.elasticsearch.index.translog.TestTranslog; @@ -638,7 +639,8 @@ public static final IndexShard newIndexShard( System::nanoTime, null, MapperMetrics.NOOP, - new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings()) + new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings()), + new SearchStatsSettings(ClusterSettings.createBuiltInClusterSettings()) ); } diff --git a/server/src/main/java/org/elasticsearch/TransportVersions.java b/server/src/main/java/org/elasticsearch/TransportVersions.java index bfe32ca591eeb..1ffd7dff62e7c 100644 --- a/server/src/main/java/org/elasticsearch/TransportVersions.java +++ b/server/src/main/java/org/elasticsearch/TransportVersions.java @@ -272,6 +272,7 @@ static TransportVersion def(int id) { public static final TransportVersion ML_INFERENCE_VERTEXAI_CHATCOMPLETION_ADDED = def(9_083_0_00); public static final TransportVersion INFERENCE_CUSTOM_SERVICE_ADDED = def(9_084_0_00); public static final TransportVersion ESQL_LIMIT_ROW_SIZE = def(9_085_0_00); + public static final TransportVersion EWMR_STATS = def(9_086_0_00); /* * STOP! READ THIS FIRST! No, really, diff --git a/server/src/main/java/org/elasticsearch/action/ActionModule.java b/server/src/main/java/org/elasticsearch/action/ActionModule.java index 2f9f4340bfa70..dc0e258b51e52 100644 --- a/server/src/main/java/org/elasticsearch/action/ActionModule.java +++ b/server/src/main/java/org/elasticsearch/action/ActionModule.java @@ -183,6 +183,8 @@ import org.elasticsearch.action.search.TransportSearchAction; import org.elasticsearch.action.search.TransportSearchScrollAction; import org.elasticsearch.action.search.TransportSearchShardsAction; +import org.elasticsearch.action.search.load.ShardSearchLoadStatsAction; +import org.elasticsearch.action.search.load.TransportShardSearchLoadStatsAction; import org.elasticsearch.action.support.ActionFilter; import org.elasticsearch.action.support.ActionFilters; import org.elasticsearch.action.support.AutoCreateIndex; @@ -745,6 +747,7 @@ public void reg actions.register(FieldUsageStatsAction.INSTANCE, TransportFieldUsageAction.class); actions.register(MasterHistoryAction.INSTANCE, MasterHistoryAction.TransportAction.class); actions.register(CoordinationDiagnosticsAction.INSTANCE, CoordinationDiagnosticsAction.TransportAction.class); + actions.register(ShardSearchLoadStatsAction.INSTANCE, TransportShardSearchLoadStatsAction.class); // Indexed scripts actions.register(TransportPutStoredScriptAction.TYPE, TransportPutStoredScriptAction.class); diff --git a/server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStats.java b/server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStats.java new file mode 100644 index 0000000000000..4f0b2c55a083a --- /dev/null +++ b/server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStats.java @@ -0,0 +1,130 @@ +/* + * ELASTICSEARCH CONFIDENTIAL + * __________________ + * + * Copyright Elasticsearch B.V. All rights reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Elasticsearch B.V. and its suppliers, if any. + * The intellectual and technical concepts contained herein + * are proprietary to Elasticsearch B.V. and its suppliers and + * may be covered by U.S. and Foreign Patents, patents in + * process, and are protected by trade secret or copyright + * law. Dissemination of this information or reproduction of + * this material is strictly forbidden unless prior written + * permission is obtained from Elasticsearch B.V. + */ + +package org.elasticsearch.action.search.load; + +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.io.stream.Writeable; +import org.elasticsearch.transport.Transports; + +import java.io.IOException; +import java.util.Objects; + +/** + * ShardStats class represents the statistics of a shard in an index. + * It contains information such as the index name, shard ID, allocation ID, and EWMA rate. + */ +public class ShardSearchLoadStats implements Writeable { + + private final String indexName; + + private final Integer shardId; + + private final String allocationId; + + private final Double emwRate; + + /** + * Constructor to create a ShardStats object from a StreamInput. + * + * @param in the StreamInput to read from + * @throws IOException if an I/O error occurs + */ + public ShardSearchLoadStats(StreamInput in) throws IOException { + assert Transports.assertNotTransportThread("O(#shards) work must always fork to an appropriate executor"); + this.indexName = in.readString(); + this.shardId = in.readVInt(); + this.allocationId = in.readString(); + this.emwRate = in.readDouble(); + } + + /** + * Constructor to create a ShardStats object with the given parameters. + * + * @param indexName the name of the index + * @param shardId the ID of the shard + * @param allocationId the allocation ID of the shard + * @param ewma the EWMA rate of the shard + */ + public ShardSearchLoadStats(String indexName, Integer shardId, String allocationId, Double ewma) { + this.indexName = indexName; + this.shardId = shardId; + this.allocationId = allocationId; + this.emwRate = ewma; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ShardSearchLoadStats that = (ShardSearchLoadStats) o; + return Objects.equals(indexName, that.indexName) + && Objects.equals(shardId, that.shardId) + && Objects.equals(allocationId, that.allocationId) + && Objects.equals(emwRate, that.emwRate); + } + + @Override + public int hashCode() { + return Objects.hash(indexName, shardId, allocationId, emwRate); + } + + /** + * Returns the index name of the shard. + * + * @return the index name + */ + public String getIndexName() { + return this.indexName; + } + + /** + * Returns the shard ID of the shard. + * + * @return the shard ID + */ + public Integer getShardId() { + return this.shardId; + } + + /** + * Returns the allocation ID of the shard. + * + * @return the allocation ID + */ + public String getAllocationId() { + return this.allocationId; + } + + /** + * Returns the EWMA rate of the shard. + * + * @return the EWMA rate + */ + public Double getEwmRate() { + return this.emwRate; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeString(indexName); + out.writeVInt(shardId); + out.writeString(allocationId); + out.writeDouble(emwRate); + } +} diff --git a/server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStatsAction.java b/server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStatsAction.java new file mode 100644 index 0000000000000..0a1cc4a4ff4b9 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStatsAction.java @@ -0,0 +1,44 @@ +/* + * ELASTICSEARCH CONFIDENTIAL + * __________________ + * + * Copyright Elasticsearch B.V. All rights reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Elasticsearch B.V. and its suppliers, if any. + * The intellectual and technical concepts contained herein + * are proprietary to Elasticsearch B.V. and its suppliers and + * may be covered by U.S. and Foreign Patents, patents in + * process, and are protected by trade secret or copyright + * law. Dissemination of this information or reproduction of + * this material is strictly forbidden unless prior written + * permission is obtained from Elasticsearch B.V. + */ + +package org.elasticsearch.action.search.load; + +import org.elasticsearch.action.ActionType; +import org.elasticsearch.action.RemoteClusterActionType; + +/** + * Action definition for retrieving shard-level search load statistics. + *

+ * This action serves as a marker for executing {@link TransportShardSearchLoadStatsAction} + *

+ */ +public class ShardSearchLoadStatsAction extends ActionType { + + /** + * Singleton instance of the action type. + */ + public static final ShardSearchLoadStatsAction INSTANCE = new ShardSearchLoadStatsAction(); + public static final String NAME = "internal:monitor/stats"; + public static final RemoteClusterActionType REMOTE_TYPE = new RemoteClusterActionType<>( + NAME, + ShardSearchLoadStatsResponse::new + ); + + private ShardSearchLoadStatsAction() { + super(NAME); + } +} diff --git a/server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStatsResponse.java b/server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStatsResponse.java new file mode 100644 index 0000000000000..8393e9cc8abb2 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStatsResponse.java @@ -0,0 +1,89 @@ +/* + * ELASTICSEARCH CONFIDENTIAL + * __________________ + * + * Copyright Elasticsearch B.V. All rights reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Elasticsearch B.V. and its suppliers, if any. + * The intellectual and technical concepts contained herein + * are proprietary to Elasticsearch B.V. and its suppliers and + * may be covered by U.S. and Foreign Patents, patents in + * process, and are protected by trade secret or copyright + * law. Dissemination of this information or reproduction of + * this material is strictly forbidden unless prior written + * permission is obtained from Elasticsearch B.V. + */ + +package org.elasticsearch.action.search.load; + +import org.elasticsearch.action.support.DefaultShardOperationFailedException; +import org.elasticsearch.action.support.broadcast.ChunkedBroadcastResponse; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.xcontent.ToXContent; + +import java.io.IOException; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; + +/** + * Response to a shard stats request. + */ +public class ShardSearchLoadStatsResponse extends ChunkedBroadcastResponse { + + private final ShardSearchLoadStats[] shards; + + /** + * Constructor to create a ShardStatsResponse object from a StreamInput. + * + * @param in the StreamInput to read from + * @throws IOException if an I/O error occurs + */ + ShardSearchLoadStatsResponse(StreamInput in) throws IOException { + super(in); + shards = in.readArray(ShardSearchLoadStats::new, ShardSearchLoadStats[]::new); + } + + /** + * Constructor to create a ShardStatsResponse object with the given parameters. + * + * @param shards the array of shard stats + * @param totalShards the total number of shards + * @param successfulShards the number of successful shards + * @param failedShards the number of failed shards + * @param shardFailures the list of shard failures + */ + ShardSearchLoadStatsResponse( + ShardSearchLoadStats[] shards, + int totalShards, + int successfulShards, + int failedShards, + List shardFailures + ) { + super(totalShards, successfulShards, failedShards, shardFailures); + this.shards = shards; + Objects.requireNonNull(shards); + } + + /** + * Returns the array of shard stats. + * + * @return the array of shard stats + */ + public ShardSearchLoadStats[] getShards() { + return shards; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + super.writeTo(out); + out.writeArray(shards); + } + + @Override + protected Iterator customXContentChunks(ToXContent.Params params) { + return null; + } +} diff --git a/server/src/main/java/org/elasticsearch/action/search/load/TransportShardSearchLoadStatsAction.java b/server/src/main/java/org/elasticsearch/action/search/load/TransportShardSearchLoadStatsAction.java new file mode 100644 index 0000000000000..02d82b29b0208 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/action/search/load/TransportShardSearchLoadStatsAction.java @@ -0,0 +1,223 @@ +/* + * ELASTICSEARCH CONFIDENTIAL + * __________________ + * + * Copyright Elasticsearch B.V. All rights reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Elasticsearch B.V. and its suppliers, if any. + * The intellectual and technical concepts contained herein + * are proprietary to Elasticsearch B.V. and its suppliers and + * may be covered by U.S. and Foreign Patents, patents in + * process, and are protected by trade secret or copyright + * law. Dissemination of this information or reproduction of + * this material is strictly forbidden unless prior written + * permission is obtained from Elasticsearch B.V. + */ + +package org.elasticsearch.action.search.load; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.support.ActionFilters; +import org.elasticsearch.action.support.broadcast.BroadcastRequest; +import org.elasticsearch.action.support.broadcast.node.TransportBroadcastByNodeAction; +import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.block.ClusterBlockException; +import org.elasticsearch.cluster.block.ClusterBlockLevel; +import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; +import org.elasticsearch.cluster.project.ProjectResolver; +import org.elasticsearch.cluster.routing.PlainShardsIterator; +import org.elasticsearch.cluster.routing.ShardRouting; +import org.elasticsearch.cluster.routing.ShardsIterator; +import org.elasticsearch.cluster.service.ClusterService; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.index.shard.IndexShard; +import org.elasticsearch.index.shard.ShardId; +import org.elasticsearch.indices.IndicesService; +import org.elasticsearch.injection.guice.Inject; +import org.elasticsearch.tasks.CancellableTask; +import org.elasticsearch.tasks.Task; +import org.elasticsearch.tasks.TaskId; +import org.elasticsearch.threadpool.ThreadPool; +import org.elasticsearch.transport.TransportService; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Transport action responsible for collecting shard-level search load statistics across the cluster. + *

+ * This action broadcasts requests to all replica shards of the specified indices (scoped by a project) + * and aggregates their metrics into a final response. + *

+ * + * Extends {@link TransportBroadcastByNodeAction} to handle fan-out to all replica shards. + */ +public class TransportShardSearchLoadStatsAction extends TransportBroadcastByNodeAction< + TransportShardSearchLoadStatsAction.Request, + ShardSearchLoadStatsResponse, + ShardSearchLoadStats> { + + private static final Logger logger = LogManager.getLogger(TransportShardSearchLoadStatsAction.class); + + private final IndicesService indicesService; + private final ProjectResolver projectResolver; + + /** + * Constructs a new {@code TransportShardSearchLoadStatsAction}. + * + * @param clusterService the cluster service + * @param transportService the transport service for communication + * @param indicesService service to access index and shard-level operations + * @param actionFilters filters applied to the action + * @param projectResolver resolves the project context for scoping operations + * @param indexNameExpressionResolver resolves index name expressions + */ + @Inject + public TransportShardSearchLoadStatsAction( + ClusterService clusterService, + TransportService transportService, + IndicesService indicesService, + ActionFilters actionFilters, + ProjectResolver projectResolver, + IndexNameExpressionResolver indexNameExpressionResolver + ) { + super( + ShardSearchLoadStatsAction.NAME, + clusterService, + transportService, + actionFilters, + indexNameExpressionResolver, + Request::new, + transportService.getThreadPool().executor(ThreadPool.Names.MANAGEMENT) + ); + this.indicesService = indicesService; + this.projectResolver = projectResolver; + } + + /** + * Returns a {@link ShardsIterator} over non-primary shards for the given indices. + *

+ * This method retrieves all shard routings for the specified indices in the cluster state, + * filters out the primary shards, and returns an iterator over the remaining shard routings + * (i.e., the replica shards). + * + * @param clusterState the current state of the cluster, used to retrieve routing information + * @param request the incoming request (currently unused in this method, but may be relevant in overrides) + * @param concreteIndices the list of index names to resolve shard routings for + * @return a {@link ShardsIterator} over the non-primary (replica) shards of the specified indices + */ + @Override + protected ShardsIterator shards(ClusterState clusterState, Request request, String[] concreteIndices) { + // Create a modifiable list of shard routings for the given indices + List shardRoutingList = new ArrayList<>( + clusterState.routingTable(projectResolver.getProjectId()).allShards(concreteIndices).getShardRoutings() + ); + + // Remove all primary shard routings from the list + shardRoutingList.removeIf(ShardRouting::primary); + + // Return an iterator over the remaining (replica) shards + return new PlainShardsIterator(shardRoutingList); + } + + @Override + protected ClusterBlockException checkGlobalBlock(ClusterState state, Request request) { + return state.blocks().globalBlockedException(ClusterBlockLevel.METADATA_READ); + } + + @Override + protected ClusterBlockException checkRequestBlock(ClusterState state, Request request, String[] concreteIndices) { + return state.blocks().indicesBlockedException(projectResolver.getProjectId(), ClusterBlockLevel.METADATA_READ, concreteIndices); + } + + @Override + protected ShardSearchLoadStats readShardResult(StreamInput in) throws IOException { + return new ShardSearchLoadStats(in); + } + + /** + * Returns the factory used to construct the final response from individual shard responses. + */ + @Override + protected ResponseFactory getResponseFactory( + Request request, + ClusterState clusterState + ) { + return (totalShards, successfulShards, failedShards, responses, shardFailures) -> new ShardSearchLoadStatsResponse( + responses.toArray(new ShardSearchLoadStats[0]), + totalShards, + successfulShards, + failedShards, + shardFailures + ); + } + + /** + * Reads the request object from the stream. + */ + @Override + protected Request readRequestFrom(StreamInput in) throws IOException { + return new Request(in); + } + + /** + * Executes the shard-level operation for collecting search load statistics. + * + * @param request the original request + * @param shardRouting routing info of the shard + * @param task the parent task + * @param listener listener to notify on result + */ + @Override + protected void shardOperation(Request request, ShardRouting shardRouting, Task task, ActionListener listener) { + ActionListener.completeWith(listener, () -> { + assert task instanceof CancellableTask; + + ShardId shardId = shardRouting.shardId(); + IndexShard indexShard = indicesService.indexServiceSafe(shardId.getIndex()).getShard(shardId.id()); + + return new ShardSearchLoadStats( + shardId.getIndex().getName(), + shardId.getId(), + shardRouting.allocationId().getId(), + indexShard.getSearchLoadRate() + ); + }); + } + + /** + * A broadcast request for collecting shard-level search load statistics. + */ + public static class Request extends BroadcastRequest { + + /** + * Constructs a new request. + */ + public Request() { + super((String[]) null); + } + + /** + * Deserializes the request from the stream input. + * + * @param in the stream input + * @throws IOException if an I/O exception occurs + */ + public Request(StreamInput in) throws IOException { + super(in); + } + + /** + * Creates a cancellable task to track execution of this request. + */ + @Override + public Task createTask(long id, String type, String action, TaskId parentTaskId, Map headers) { + return new CancellableTask(id, type, action, "", parentTaskId, headers); + } + } +} diff --git a/server/src/main/java/org/elasticsearch/common/settings/ClusterSettings.java b/server/src/main/java/org/elasticsearch/common/settings/ClusterSettings.java index a15d4f3049528..28233086c84ec 100644 --- a/server/src/main/java/org/elasticsearch/common/settings/ClusterSettings.java +++ b/server/src/main/java/org/elasticsearch/common/settings/ClusterSettings.java @@ -89,6 +89,7 @@ import org.elasticsearch.index.IndexingPressure; import org.elasticsearch.index.MergePolicyConfig; import org.elasticsearch.index.engine.ThreadPoolMergeScheduler; +import org.elasticsearch.index.search.stats.SearchStatsSettings; import org.elasticsearch.index.shard.IndexingStatsSettings; import org.elasticsearch.indices.IndexingMemoryController; import org.elasticsearch.indices.IndicesQueryCache; @@ -636,6 +637,7 @@ public void apply(Settings value, Settings current, Settings previous) { ShardsAvailabilityHealthIndicatorService.REPLICA_UNASSIGNED_BUFFER_TIME, DataStreamFailureStoreSettings.DATA_STREAM_FAILURE_STORED_ENABLED_SETTING, IndexingStatsSettings.RECENT_WRITE_LOAD_HALF_LIFE_SETTING, + SearchStatsSettings.RECENT_READ_LOAD_HALF_LIFE_SETTING, TransportGetAllocationStatsAction.CACHE_TTL_SETTING ); } diff --git a/server/src/main/java/org/elasticsearch/index/IndexModule.java b/server/src/main/java/org/elasticsearch/index/IndexModule.java index 3418d8a9b7b2e..42410c6b8025e 100644 --- a/server/src/main/java/org/elasticsearch/index/IndexModule.java +++ b/server/src/main/java/org/elasticsearch/index/IndexModule.java @@ -48,6 +48,7 @@ import org.elasticsearch.index.mapper.MapperMetrics; import org.elasticsearch.index.mapper.MapperRegistry; import org.elasticsearch.index.mapper.MapperService; +import org.elasticsearch.index.search.stats.SearchStatsSettings; import org.elasticsearch.index.shard.IndexEventListener; import org.elasticsearch.index.shard.IndexingOperationListener; import org.elasticsearch.index.shard.IndexingStatsSettings; @@ -179,6 +180,7 @@ public interface DirectoryWrapper { private final SetOnce indexCommitListener = new SetOnce<>(); private final MapperMetrics mapperMetrics; private final IndexingStatsSettings indexingStatsSettings; + private final SearchStatsSettings searchStatsSettings; /** * Construct the index module for the index with the specified index settings. The index module contains extension points for plugins @@ -200,7 +202,8 @@ public IndexModule( final SlowLogFieldProvider slowLogFieldProvider, final MapperMetrics mapperMetrics, final List searchOperationListeners, - final IndexingStatsSettings indexingStatsSettings + final IndexingStatsSettings indexingStatsSettings, + final SearchStatsSettings searchStatsSettings ) { this.indexSettings = indexSettings; this.analysisRegistry = analysisRegistry; @@ -216,6 +219,7 @@ public IndexModule( this.recoveryStateFactories = recoveryStateFactories; this.mapperMetrics = mapperMetrics; this.indexingStatsSettings = indexingStatsSettings; + this.searchStatsSettings = searchStatsSettings; } /** @@ -552,7 +556,8 @@ public IndexService newIndexService( indexCommitListener.get(), mapperMetrics, queryRewriteInterceptor, - indexingStatsSettings + indexingStatsSettings, + searchStatsSettings ); success = true; return indexService; diff --git a/server/src/main/java/org/elasticsearch/index/IndexService.java b/server/src/main/java/org/elasticsearch/index/IndexService.java index d5c00294aa6b8..2c9616e0791e5 100644 --- a/server/src/main/java/org/elasticsearch/index/IndexService.java +++ b/server/src/main/java/org/elasticsearch/index/IndexService.java @@ -67,6 +67,7 @@ import org.elasticsearch.index.query.QueryRewriteContext; import org.elasticsearch.index.query.SearchExecutionContext; import org.elasticsearch.index.query.SearchIndexNameMatcher; +import org.elasticsearch.index.search.stats.SearchStatsSettings; import org.elasticsearch.index.seqno.RetentionLeaseSyncer; import org.elasticsearch.index.shard.GlobalCheckpointSyncer; import org.elasticsearch.index.shard.IndexEventListener; @@ -170,6 +171,7 @@ public class IndexService extends AbstractIndexComponent implements IndicesClust private final MapperMetrics mapperMetrics; private final QueryRewriteInterceptor queryRewriteInterceptor; private final IndexingStatsSettings indexingStatsSettings; + private final SearchStatsSettings searchStatsSettings; @SuppressWarnings("this-escape") public IndexService( @@ -207,7 +209,8 @@ public IndexService( Engine.IndexCommitListener indexCommitListener, MapperMetrics mapperMetrics, QueryRewriteInterceptor queryRewriteInterceptor, - IndexingStatsSettings indexingStatsSettings + IndexingStatsSettings indexingStatsSettings, + SearchStatsSettings searchStatsSettings ) { super(indexSettings); assert indexCreationContext != IndexCreationContext.RELOAD_ANALYZERS @@ -293,6 +296,7 @@ public IndexService( this.retentionLeaseSyncTask = new AsyncRetentionLeaseSyncTask(this); } this.indexingStatsSettings = indexingStatsSettings; + this.searchStatsSettings = searchStatsSettings; updateFsyncTaskIfNecessary(); } @@ -583,7 +587,8 @@ public synchronized IndexShard createShard( System::nanoTime, indexCommitListener, mapperMetrics, - indexingStatsSettings + indexingStatsSettings, + searchStatsSettings ); eventListener.indexShardStateChanged(indexShard, null, indexShard.state(), "shard created"); eventListener.afterIndexShardCreated(indexShard); diff --git a/server/src/main/java/org/elasticsearch/index/search/stats/SearchStats.java b/server/src/main/java/org/elasticsearch/index/search/stats/SearchStats.java index 8b19d72ccc09d..426422f92c920 100644 --- a/server/src/main/java/org/elasticsearch/index/search/stats/SearchStats.java +++ b/server/src/main/java/org/elasticsearch/index/search/stats/SearchStats.java @@ -49,6 +49,8 @@ public static class Stats implements Writeable, ToXContentFragment { private long queryFailure; private long fetchFailure; + private double exponentiallyWeightedMovingRate; + private Stats() { // for internal use, initializes all counts to 0 } @@ -67,7 +69,8 @@ public Stats( long scrollCurrent, long suggestCount, long suggestTimeInMillis, - long suggestCurrent + long suggestCurrent, + double exponentiallyWeightedMovingRate ) { this.queryCount = queryCount; this.queryTimeInMillis = queryTimeInMillis; @@ -86,6 +89,9 @@ public Stats( this.suggestCount = suggestCount; this.suggestTimeInMillis = suggestTimeInMillis; this.suggestCurrent = suggestCurrent; + + this.exponentiallyWeightedMovingRate = exponentiallyWeightedMovingRate; + } private Stats(StreamInput in) throws IOException { @@ -109,6 +115,10 @@ private Stats(StreamInput in) throws IOException { queryFailure = in.readVLong(); fetchFailure = in.readVLong(); } + + if (in.getTransportVersion().onOrAfter(TransportVersions.EWMR_STATS)) { + exponentiallyWeightedMovingRate = in.readDouble(); + } } @Override @@ -133,6 +143,10 @@ public void writeTo(StreamOutput out) throws IOException { out.writeVLong(queryFailure); out.writeVLong(fetchFailure); } + + if (out.getTransportVersion().onOrAfter(TransportVersions.EWMR_STATS)) { + out.writeDouble(exponentiallyWeightedMovingRate); + } } public void add(Stats stats) { @@ -153,6 +167,8 @@ public void add(Stats stats) { suggestCount += stats.suggestCount; suggestTimeInMillis += stats.suggestTimeInMillis; suggestCurrent += stats.suggestCurrent; + + exponentiallyWeightedMovingRate += stats.exponentiallyWeightedMovingRate; } public void addForClosingShard(Stats stats) { @@ -171,6 +187,8 @@ public void addForClosingShard(Stats stats) { suggestCount += stats.suggestCount; suggestTimeInMillis += stats.suggestTimeInMillis; + + exponentiallyWeightedMovingRate += stats.exponentiallyWeightedMovingRate; } public long getQueryCount() { @@ -245,6 +263,10 @@ public long getSuggestCurrent() { return suggestCurrent; } + public double getExponentiallyWeightedMovingRate() { + return exponentiallyWeightedMovingRate; + } + public static Stats readStats(StreamInput in) throws IOException { return new Stats(in); } @@ -269,6 +291,8 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws builder.humanReadableField(Fields.SUGGEST_TIME_IN_MILLIS, Fields.SUGGEST_TIME, getSuggestTime()); builder.field(Fields.SUGGEST_CURRENT, suggestCurrent); + builder.field(Fields.EMW_RATE, exponentiallyWeightedMovingRate); + return builder; } @@ -290,7 +314,8 @@ public boolean equals(Object o) { && scrollCurrent == that.scrollCurrent && suggestCount == that.suggestCount && suggestTimeInMillis == that.suggestTimeInMillis - && suggestCurrent == that.suggestCurrent; + && suggestCurrent == that.suggestCurrent + && exponentiallyWeightedMovingRate == that.exponentiallyWeightedMovingRate; } @Override @@ -309,7 +334,8 @@ public int hashCode() { scrollCurrent, suggestCount, suggestTimeInMillis, - suggestCurrent + suggestCurrent, + exponentiallyWeightedMovingRate ); } } @@ -427,6 +453,7 @@ static final class Fields { static final String SUGGEST_TIME = "suggest_time"; static final String SUGGEST_TIME_IN_MILLIS = "suggest_time_in_millis"; static final String SUGGEST_CURRENT = "suggest_current"; + static final String EMW_RATE = "ewm_rate"; } @Override diff --git a/server/src/main/java/org/elasticsearch/index/search/stats/SearchStatsSettings.java b/server/src/main/java/org/elasticsearch/index/search/stats/SearchStatsSettings.java new file mode 100644 index 0000000000000..bb1feb6353653 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/index/search/stats/SearchStatsSettings.java @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.index.search.stats; + +import org.elasticsearch.common.settings.ClusterSettings; +import org.elasticsearch.common.settings.Setting; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.core.TimeValue; + +/** + * Container for cluster settings + */ +public class SearchStatsSettings { + + public static final TimeValue RECENT_READ_LOAD_HALF_LIFE_DEFAULT = TimeValue.timeValueMinutes(5); + static final TimeValue RECENT_READ_LOAD_HALF_LIFE_MIN = TimeValue.timeValueSeconds(1); // A sub-second half-life makes no sense + static final TimeValue RECENT_READ_LOAD_HALF_LIFE_MAX = TimeValue.timeValueDays(100_000); // Long.MAX_VALUE nanos, rounded down + + /** + * A cluster setting giving the half-life, in seconds, to use for the Exponentially Weighted Moving Rate calculation used for the + * recency-weighted read load + * + *

This is dynamic, but changes only apply to newly-opened shards. + */ + public static final Setting RECENT_READ_LOAD_HALF_LIFE_SETTING = Setting.timeSetting( + "indices.stats.recent_read_load.half_life", + RECENT_READ_LOAD_HALF_LIFE_DEFAULT, + RECENT_READ_LOAD_HALF_LIFE_MIN, + RECENT_READ_LOAD_HALF_LIFE_MAX, + Setting.Property.Dynamic, + Setting.Property.NodeScope + ); + + private TimeValue recentReadLoadHalfLifeForNewShards = RECENT_READ_LOAD_HALF_LIFE_SETTING.getDefault(Settings.EMPTY); + + public SearchStatsSettings(ClusterSettings clusterSettings) { + clusterSettings.initializeAndWatch(RECENT_READ_LOAD_HALF_LIFE_SETTING, value -> recentReadLoadHalfLifeForNewShards = value); + } + + public TimeValue getRecentReadLoadHalfLifeForNewShards() { + return recentReadLoadHalfLifeForNewShards; + } +} diff --git a/server/src/main/java/org/elasticsearch/index/search/stats/ShardSearchStats.java b/server/src/main/java/org/elasticsearch/index/search/stats/ShardSearchStats.java index 6e6f744f6b719..4c285a467ba23 100644 --- a/server/src/main/java/org/elasticsearch/index/search/stats/ShardSearchStats.java +++ b/server/src/main/java/org/elasticsearch/index/search/stats/ShardSearchStats.java @@ -10,6 +10,7 @@ package org.elasticsearch.index.search.stats; import org.elasticsearch.common.metrics.CounterMetric; +import org.elasticsearch.common.metrics.ExponentiallyWeightedMovingRate; import org.elasticsearch.common.metrics.MeanMetric; import org.elasticsearch.common.regex.Regex; import org.elasticsearch.common.util.CollectionUtils; @@ -26,9 +27,15 @@ public final class ShardSearchStats implements SearchOperationListener { - private final StatsHolder totalStats = new StatsHolder(); + private final StatsHolder totalStats; private final CounterMetric openContexts = new CounterMetric(); private volatile Map groupsStats = emptyMap(); + private final SearchStatsSettings searchStatsSettings; + + public ShardSearchStats(SearchStatsSettings searchStatsSettings) { + this.searchStatsSettings = searchStatsSettings; + this.totalStats = new StatsHolder(searchStatsSettings); + } /** * Returns the stats, including group specific stats. If the groups are null/0 length, then nothing @@ -78,9 +85,11 @@ public void onFailedQueryPhase(SearchContext searchContext) { @Override public void onQueryPhase(SearchContext searchContext, long tookInNanos) { computeStats(searchContext, searchContext.hasOnlySuggest() ? statsHolder -> { + statsHolder.exponentiallyWeightedMovingRate.addIncrement(tookInNanos, System.nanoTime()); statsHolder.suggestMetric.inc(tookInNanos); statsHolder.suggestCurrent.dec(); } : statsHolder -> { + statsHolder.exponentiallyWeightedMovingRate.addIncrement(tookInNanos, System.nanoTime()); statsHolder.queryMetric.inc(tookInNanos); statsHolder.queryCurrent.dec(); }); @@ -102,6 +111,7 @@ public void onFailedFetchPhase(SearchContext searchContext) { @Override public void onFetchPhase(SearchContext searchContext, long tookInNanos) { computeStats(searchContext, statsHolder -> { + statsHolder.exponentiallyWeightedMovingRate.addIncrement(tookInNanos, System.nanoTime()); statsHolder.fetchMetric.inc(tookInNanos); statsHolder.fetchCurrent.dec(); }); @@ -123,7 +133,7 @@ private StatsHolder groupStats(String group) { synchronized (this) { stats = groupsStats.get(group); if (stats == null) { - stats = new StatsHolder(); + stats = new StatsHolder(searchStatsSettings); groupsStats = Maps.copyMapWithAddedEntry(groupsStats, group, stats); } } @@ -173,6 +183,13 @@ static final class StatsHolder { final CounterMetric queryFailure = new CounterMetric(); final CounterMetric fetchFailure = new CounterMetric(); + final ExponentiallyWeightedMovingRate exponentiallyWeightedMovingRate; + + StatsHolder(SearchStatsSettings searchStatsSettings) { + double lambdaInInverseNanos = Math.log(2.0) / searchStatsSettings.getRecentReadLoadHalfLifeForNewShards().nanos(); + this.exponentiallyWeightedMovingRate = new ExponentiallyWeightedMovingRate(lambdaInInverseNanos, System.nanoTime()); + } + SearchStats.Stats stats() { return new SearchStats.Stats( queryMetric.count(), @@ -188,7 +205,8 @@ SearchStats.Stats stats() { scrollCurrent.count(), suggestMetric.count(), TimeUnit.NANOSECONDS.toMillis(suggestMetric.sum()), - suggestCurrent.count() + suggestCurrent.count(), + exponentiallyWeightedMovingRate.getRate(System.nanoTime()) ); } } diff --git a/server/src/main/java/org/elasticsearch/index/shard/IndexShard.java b/server/src/main/java/org/elasticsearch/index/shard/IndexShard.java index 4b38a5d378ecf..e9cc87388e1bc 100644 --- a/server/src/main/java/org/elasticsearch/index/shard/IndexShard.java +++ b/server/src/main/java/org/elasticsearch/index/shard/IndexShard.java @@ -117,6 +117,7 @@ import org.elasticsearch.index.refresh.RefreshStats; import org.elasticsearch.index.search.stats.FieldUsageStats; import org.elasticsearch.index.search.stats.SearchStats; +import org.elasticsearch.index.search.stats.SearchStatsSettings; import org.elasticsearch.index.search.stats.ShardFieldUsageTracker; import org.elasticsearch.index.search.stats.ShardSearchStats; import org.elasticsearch.index.seqno.ReplicationTracker; @@ -203,7 +204,7 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl private final IndexCache indexCache; private final Store store; private final InternalIndexingStats internalIndexingStats; - private final ShardSearchStats searchStats = new ShardSearchStats(); + private final ShardSearchStats searchStats; private final ShardFieldUsageTracker fieldUsageTracker; private final String shardUuid = UUIDs.randomBase64UUID(); private final long shardCreationTime; @@ -341,7 +342,8 @@ public IndexShard( final LongSupplier relativeTimeInNanosSupplier, final Engine.IndexCommitListener indexCommitListener, final MapperMetrics mapperMetrics, - final IndexingStatsSettings indexingStatsSettings + final IndexingStatsSettings indexingStatsSettings, + final SearchStatsSettings searchStatsSettings ) throws IOException { super(shardRouting.shardId(), indexSettings); assert shardRouting.initializing(); @@ -369,6 +371,7 @@ public IndexShard( this.bulkOperationListener = new ShardBulkStats(); this.globalCheckpointSyncer = globalCheckpointSyncer; this.retentionLeaseSyncer = Objects.requireNonNull(retentionLeaseSyncer); + this.searchStats = new ShardSearchStats(searchStatsSettings); this.searchOperationListener = new SearchOperationListener.CompositeListener( CollectionUtils.appendToCopyNoNullElements(searchOperationListener, searchStats), logger @@ -1422,6 +1425,13 @@ public GetStats getStats() { return getService.stats(); } + /** + * Returns the search load rate stats for this shard. + */ + public double getSearchLoadRate() { + return searchStats.stats().getTotal().getExponentiallyWeightedMovingRate(); + } + public StoreStats storeStats() { try { final RecoveryState recoveryState = this.recoveryState; diff --git a/server/src/main/java/org/elasticsearch/indices/IndicesService.java b/server/src/main/java/org/elasticsearch/indices/IndicesService.java index 73747bc798d30..bc4bde2006acd 100644 --- a/server/src/main/java/org/elasticsearch/indices/IndicesService.java +++ b/server/src/main/java/org/elasticsearch/indices/IndicesService.java @@ -118,6 +118,7 @@ import org.elasticsearch.index.recovery.RecoveryStats; import org.elasticsearch.index.refresh.RefreshStats; import org.elasticsearch.index.search.stats.SearchStats; +import org.elasticsearch.index.search.stats.SearchStatsSettings; import org.elasticsearch.index.seqno.RetentionLeaseStats; import org.elasticsearch.index.seqno.RetentionLeaseSyncer; import org.elasticsearch.index.seqno.SeqNoStats; @@ -281,6 +282,7 @@ public class IndicesService extends AbstractLifecycleComponent private final QueryRewriteInterceptor queryRewriteInterceptor; final SlowLogFieldProvider slowLogFieldProvider; // pkg-private for testingå private final IndexingStatsSettings indexStatsSettings; + private final SearchStatsSettings searchStatsSettings; @Override protected void doStart() { @@ -406,6 +408,7 @@ public void onRemoval(ShardId shardId, String fieldName, boolean wasEvicted, lon this.searchOperationListeners = builder.searchOperationListener; this.slowLogFieldProvider = builder.slowLogFieldProvider; this.indexStatsSettings = new IndexingStatsSettings(clusterService.getClusterSettings()); + this.searchStatsSettings = new SearchStatsSettings(clusterService.getClusterSettings()); } private static final String DANGLING_INDICES_UPDATE_THREAD_NAME = "DanglingIndices#updateTask"; @@ -795,7 +798,8 @@ private synchronized IndexService createIndexService( slowLogFieldProvider, mapperMetrics, searchOperationListeners, - indexStatsSettings + indexStatsSettings, + searchStatsSettings ); for (IndexingOperationListener operationListener : indexingOperationListeners) { indexModule.addIndexOperationListener(operationListener); @@ -893,7 +897,8 @@ public synchronized MapperService createIndexMapperServiceForValidation(IndexMet slowLogFieldProvider, mapperMetrics, searchOperationListeners, - indexStatsSettings + indexStatsSettings, + searchStatsSettings ); pluginsService.forEach(p -> p.onIndexModule(indexModule)); return indexModule.newIndexMapperService(clusterService, parserConfig, mapperRegistry, scriptService); diff --git a/server/src/test/java/org/elasticsearch/action/admin/cluster/node/stats/NodeStatsTests.java b/server/src/test/java/org/elasticsearch/action/admin/cluster/node/stats/NodeStatsTests.java index 70f278bd8d369..e8bbe412cbbab 100644 --- a/server/src/test/java/org/elasticsearch/action/admin/cluster/node/stats/NodeStatsTests.java +++ b/server/src/test/java/org/elasticsearch/action/admin/cluster/node/stats/NodeStatsTests.java @@ -619,6 +619,7 @@ private static CommonStats createShardLevelCommonStats() { ++iota, ++iota, ++iota, + ++iota, ++iota ); Map groupStats = new HashMap<>(); diff --git a/server/src/test/java/org/elasticsearch/index/IndexModuleTests.java b/server/src/test/java/org/elasticsearch/index/IndexModuleTests.java index 043b982ad4344..a7a520c5db9c6 100644 --- a/server/src/test/java/org/elasticsearch/index/IndexModuleTests.java +++ b/server/src/test/java/org/elasticsearch/index/IndexModuleTests.java @@ -67,6 +67,7 @@ import org.elasticsearch.index.mapper.MapperRegistry; import org.elasticsearch.index.mapper.ParsedDocument; import org.elasticsearch.index.mapper.Uid; +import org.elasticsearch.index.search.stats.SearchStatsSettings; import org.elasticsearch.index.seqno.RetentionLeaseSyncer; import org.elasticsearch.index.shard.IndexEventListener; import org.elasticsearch.index.shard.IndexShard; @@ -250,7 +251,8 @@ public void testWrapperIsBound() throws IOException { mock(SlowLogFieldProvider.class), MapperMetrics.NOOP, emptyList(), - new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings()) + new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings()), + new SearchStatsSettings(ClusterSettings.createBuiltInClusterSettings()) ); module.setReaderWrapper(s -> new Wrapper()); @@ -279,7 +281,8 @@ public void testRegisterIndexStore() throws IOException { mock(SlowLogFieldProvider.class), MapperMetrics.NOOP, emptyList(), - new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings()) + new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings()), + new SearchStatsSettings(ClusterSettings.createBuiltInClusterSettings()) ); final IndexService indexService = newIndexService(module); @@ -306,7 +309,8 @@ public void testDirectoryWrapper() throws IOException { mock(SlowLogFieldProvider.class), MapperMetrics.NOOP, emptyList(), - new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings()) + new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings()), + new SearchStatsSettings(ClusterSettings.createBuiltInClusterSettings()) ); module.setDirectoryWrapper(new TestDirectoryWrapper()); @@ -661,7 +665,8 @@ public void testRegisterCustomRecoveryStateFactory() throws IOException { mock(SlowLogFieldProvider.class), MapperMetrics.NOOP, emptyList(), - new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings()) + new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings()), + new SearchStatsSettings(ClusterSettings.createBuiltInClusterSettings()) ); final IndexService indexService = newIndexService(module); @@ -685,7 +690,8 @@ public void testIndexCommitListenerIsBound() throws IOException, ExecutionExcept mock(SlowLogFieldProvider.class), MapperMetrics.NOOP, emptyList(), - new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings()) + new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings()), + new SearchStatsSettings(ClusterSettings.createBuiltInClusterSettings()) ); final AtomicLong lastAcquiredPrimaryTerm = new AtomicLong(); @@ -789,7 +795,8 @@ private static IndexModule createIndexModule( mock(SlowLogFieldProvider.class), MapperMetrics.NOOP, emptyList(), - new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings()) + new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings()), + new SearchStatsSettings(ClusterSettings.createBuiltInClusterSettings()) ); } diff --git a/server/src/test/java/org/elasticsearch/index/search/stats/SearchStatsTests.java b/server/src/test/java/org/elasticsearch/index/search/stats/SearchStatsTests.java index a4430e1c1499d..9bd518ae50475 100644 --- a/server/src/test/java/org/elasticsearch/index/search/stats/SearchStatsTests.java +++ b/server/src/test/java/org/elasticsearch/index/search/stats/SearchStatsTests.java @@ -22,9 +22,9 @@ public void testShardLevelSearchGroupStats() throws Exception { // let's create two dummy search stats with groups Map groupStats1 = new HashMap<>(); Map groupStats2 = new HashMap<>(); - groupStats2.put("group1", new Stats(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)); - SearchStats searchStats1 = new SearchStats(new Stats(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1), 0, groupStats1); - SearchStats searchStats2 = new SearchStats(new Stats(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1), 0, groupStats2); + groupStats2.put("group1", new Stats(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.0)); + SearchStats searchStats1 = new SearchStats(new Stats(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.0), 0, groupStats1); + SearchStats searchStats2 = new SearchStats(new Stats(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.0), 0, groupStats2); // adding these two search stats and checking group stats are correct searchStats1.add(searchStats2); diff --git a/server/src/test/java/org/elasticsearch/search/stats/SearchStatsSettingsTests.java b/server/src/test/java/org/elasticsearch/search/stats/SearchStatsSettingsTests.java new file mode 100644 index 0000000000000..6084a3be862bb --- /dev/null +++ b/server/src/test/java/org/elasticsearch/search/stats/SearchStatsSettingsTests.java @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.search.stats; + +import org.elasticsearch.common.settings.ClusterSettings; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.core.TimeValue; +import org.elasticsearch.index.search.stats.SearchStatsSettings; +import org.elasticsearch.test.ESTestCase; + +import static org.hamcrest.Matchers.equalTo; + +/** + * Unit tests for {@link SearchStatsSettings}. + */ +public class SearchStatsSettingsTests extends ESTestCase { + + /** + * Test the default value of the recent read load half-life setting. + */ + public void testRecentReadLoadHalfLife_defaultValue() { + SearchStatsSettings settings = new SearchStatsSettings(ClusterSettings.createBuiltInClusterSettings()); + assertThat(settings.getRecentReadLoadHalfLifeForNewShards(), equalTo(SearchStatsSettings.RECENT_READ_LOAD_HALF_LIFE_DEFAULT)); + } + + /** + * Test the initial value of the recent read load half-life setting. + */ + public void testRecentReadLoadHalfLife_initialValue() { + SearchStatsSettings settings = new SearchStatsSettings( + ClusterSettings.createBuiltInClusterSettings( + Settings.builder().put(SearchStatsSettings.RECENT_READ_LOAD_HALF_LIFE_SETTING.getKey(), "2h").build() + ) + ); + assertThat(settings.getRecentReadLoadHalfLifeForNewShards(), equalTo(TimeValue.timeValueHours(2))); + } + + /** + * Test updating the recent read load half-life setting. + */ + public void testRecentReadLoadHalfLife_updateValue() { + ClusterSettings clusterSettings = ClusterSettings.createBuiltInClusterSettings(); + SearchStatsSettings settings = new SearchStatsSettings(clusterSettings); + clusterSettings.applySettings( + Settings.builder().put(SearchStatsSettings.RECENT_READ_LOAD_HALF_LIFE_SETTING.getKey(), "90m").build() + ); + assertThat(settings.getRecentReadLoadHalfLifeForNewShards(), equalTo(TimeValue.timeValueMinutes(90))); + } +} diff --git a/test/framework/src/main/java/org/elasticsearch/index/shard/IndexShardTestCase.java b/test/framework/src/main/java/org/elasticsearch/index/shard/IndexShardTestCase.java index 89ce1f4eb06cd..3f8295b0e5024 100644 --- a/test/framework/src/main/java/org/elasticsearch/index/shard/IndexShardTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/index/shard/IndexShardTestCase.java @@ -59,6 +59,7 @@ import org.elasticsearch.index.mapper.MapperMetrics; import org.elasticsearch.index.mapper.MapperService; import org.elasticsearch.index.mapper.SourceToParse; +import org.elasticsearch.index.search.stats.SearchStatsSettings; import org.elasticsearch.index.seqno.ReplicationTracker; import org.elasticsearch.index.seqno.RetentionLeaseSyncer; import org.elasticsearch.index.seqno.SequenceNumbers; @@ -554,7 +555,8 @@ protected IndexShard newShard( relativeTimeSupplier, null, MapperMetrics.NOOP, - new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings()) + new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings()), + new SearchStatsSettings(ClusterSettings.createBuiltInClusterSettings()) ); indexShard.addShardFailureCallback(DEFAULT_SHARD_FAILURE_HANDLER); success = true; diff --git a/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/collector/indices/IndexStatsMonitoringDocTests.java b/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/collector/indices/IndexStatsMonitoringDocTests.java index fbea440c81e58..b501591ea37d4 100644 --- a/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/collector/indices/IndexStatsMonitoringDocTests.java +++ b/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/collector/indices/IndexStatsMonitoringDocTests.java @@ -409,7 +409,23 @@ private static CommonStats mockCommonStats() { ); commonStats.getIndexing().add(new IndexingStats(indexingStats)); - final SearchStats.Stats searchStats = new SearchStats.Stats(++iota, ++iota, no, no, no, no, no, no, no, no, no, no, no, no); + final SearchStats.Stats searchStats = new SearchStats.Stats( + ++iota, + ++iota, + no, + no, + no, + no, + no, + no, + no, + no, + no, + no, + no, + no, + Double.valueOf(no) + ); commonStats.getSearch().add(new SearchStats(searchStats, no, null)); final SegmentsStats segmentsStats = new SegmentsStats(); diff --git a/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/collector/indices/IndicesStatsMonitoringDocTests.java b/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/collector/indices/IndicesStatsMonitoringDocTests.java index 4ff3895551b96..d01f2e0168a72 100644 --- a/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/collector/indices/IndicesStatsMonitoringDocTests.java +++ b/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/collector/indices/IndicesStatsMonitoringDocTests.java @@ -186,7 +186,7 @@ private CommonStats mockCommonStats() { final IndexingStats.Stats indexingStats = new IndexingStats.Stats(3L, 4L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, true, 5L, 0, 0, 0, 0.0, 0.0); commonStats.getIndexing().add(new IndexingStats(indexingStats)); - final SearchStats.Stats searchStats = new SearchStats.Stats(6L, 7L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L); + final SearchStats.Stats searchStats = new SearchStats.Stats(6L, 7L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0.0); commonStats.getSearch().add(new SearchStats(searchStats, 0L, null)); final BulkStats bulkStats = new BulkStats(0L, 0L, 0L, 0L, 0L); diff --git a/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/collector/node/NodeStatsMonitoringDocTests.java b/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/collector/node/NodeStatsMonitoringDocTests.java index f58059c288d3d..dc64365a503d0 100644 --- a/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/collector/node/NodeStatsMonitoringDocTests.java +++ b/x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/collector/node/NodeStatsMonitoringDocTests.java @@ -357,7 +357,23 @@ private static NodeStats mockNodeStats() { indicesCommonStats.getQueryCache().add(new QueryCacheStats(++iota, ++iota, ++iota, ++iota, no)); indicesCommonStats.getRequestCache().add(new RequestCacheStats(++iota, ++iota, ++iota, ++iota)); - final SearchStats.Stats searchStats = new SearchStats.Stats(++iota, ++iota, no, no, no, no, no, no, no, no, no, no, no, no); + final SearchStats.Stats searchStats = new SearchStats.Stats( + ++iota, + ++iota, + no, + no, + no, + no, + no, + no, + no, + no, + no, + no, + no, + no, + Double.valueOf(no) + ); indicesCommonStats.getSearch().add(new SearchStats(searchStats, no, null)); final SegmentsStats segmentsStats = new SegmentsStats(); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java index 4eac3ddf85f1b..fd590630094f2 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java @@ -45,6 +45,7 @@ import org.elasticsearch.index.analysis.AnalysisRegistry; import org.elasticsearch.index.engine.InternalEngineFactory; import org.elasticsearch.index.mapper.MapperMetrics; +import org.elasticsearch.index.search.stats.SearchStatsSettings; import org.elasticsearch.index.shard.IndexingStatsSettings; import org.elasticsearch.indices.TestIndexNameExpressionResolver; import org.elasticsearch.license.ClusterStateLicenseService; @@ -461,7 +462,8 @@ public void testOnIndexModuleIsNoOpWithSecurityDisabled() throws Exception { mock(SlowLogFieldProvider.class), MapperMetrics.NOOP, List.of(), - new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings()) + new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings()), + new SearchStatsSettings(ClusterSettings.createBuiltInClusterSettings()) ); security.onIndexModule(indexModule); // indexReaderWrapper is a SetOnce so if Security#onIndexModule had already set an ReaderWrapper we would get an exception here diff --git a/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/WatcherPluginTests.java b/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/WatcherPluginTests.java index bd8d15ea809fe..1cfa5669363c4 100644 --- a/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/WatcherPluginTests.java +++ b/x-pack/plugin/watcher/src/test/java/org/elasticsearch/xpack/watcher/WatcherPluginTests.java @@ -15,6 +15,7 @@ import org.elasticsearch.index.analysis.AnalysisRegistry; import org.elasticsearch.index.engine.InternalEngineFactory; import org.elasticsearch.index.mapper.MapperMetrics; +import org.elasticsearch.index.search.stats.SearchStatsSettings; import org.elasticsearch.index.shard.IndexingStatsSettings; import org.elasticsearch.indices.SystemIndexDescriptor; import org.elasticsearch.indices.TestIndexNameExpressionResolver; @@ -74,7 +75,8 @@ public void testWatcherDisabledTests() throws Exception { mock(SlowLogFieldProvider.class), MapperMetrics.NOOP, List.of(), - new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings()) + new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings()), + new SearchStatsSettings(ClusterSettings.createBuiltInClusterSettings()) ); // this will trip an assertion if the watcher indexing operation listener is null (which it is) but we try to add it watcher.onIndexModule(indexModule); From 6d9b8bfdfb30a9858c152ab87fdfcab36c6ac26a Mon Sep 17 00:00:00 2001 From: Dimitris Rempapis Date: Fri, 30 May 2025 11:41:23 +0300 Subject: [PATCH 02/25] export package for transport action --- server/src/main/java/module-info.java | 1 + 1 file changed, 1 insertion(+) diff --git a/server/src/main/java/module-info.java b/server/src/main/java/module-info.java index 8da4f403c29bd..7638a041d2f01 100644 --- a/server/src/main/java/module-info.java +++ b/server/src/main/java/module-info.java @@ -137,6 +137,7 @@ exports org.elasticsearch.action.ingest; exports org.elasticsearch.action.resync; exports org.elasticsearch.action.search; + exports org.elasticsearch.action.search.load; exports org.elasticsearch.action.support; exports org.elasticsearch.action.support.broadcast; exports org.elasticsearch.action.support.broadcast.node; From c058a3358320d61e79817e62db2f06621b17e209 Mon Sep 17 00:00:00 2001 From: Dimitris Rempapis Date: Fri, 30 May 2025 12:35:58 +0300 Subject: [PATCH 03/25] update transport version --- server/src/main/java/org/elasticsearch/TransportVersions.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/org/elasticsearch/TransportVersions.java b/server/src/main/java/org/elasticsearch/TransportVersions.java index 60a10796760be..0e655cf5a13f7 100644 --- a/server/src/main/java/org/elasticsearch/TransportVersions.java +++ b/server/src/main/java/org/elasticsearch/TransportVersions.java @@ -273,7 +273,7 @@ static TransportVersion def(int id) { public static final TransportVersion INFERENCE_CUSTOM_SERVICE_ADDED = def(9_084_0_00); public static final TransportVersion ESQL_LIMIT_ROW_SIZE = def(9_085_0_00); public static final TransportVersion ESQL_REGEX_MATCH_WITH_CASE_INSENSITIVITY = def(9_086_0_00); - public static final TransportVersion EWMR_STATS = def(9_086_0_00); + public static final TransportVersion EWMR_STATS = def(9_087_0_00); /* * STOP! READ THIS FIRST! No, really, From 6614cf9b5ca1fb402239de5e2081ad432fa0bb0e Mon Sep 17 00:00:00 2001 From: Dimitris Rempapis Date: Fri, 30 May 2025 13:31:53 +0300 Subject: [PATCH 04/25] update license headers --- .../search/load/ShardSearchLoadStats.java | 21 +++++++------------ .../load/ShardSearchLoadStatsAction.java | 21 +++++++------------ .../load/ShardSearchLoadStatsResponse.java | 21 +++++++------------ .../TransportShardSearchLoadStatsAction.java | 21 +++++++------------ 4 files changed, 28 insertions(+), 56 deletions(-) diff --git a/server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStats.java b/server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStats.java index 4f0b2c55a083a..6da0939d58b43 100644 --- a/server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStats.java +++ b/server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStats.java @@ -1,20 +1,13 @@ /* - * ELASTICSEARCH CONFIDENTIAL - * __________________ - * - * Copyright Elasticsearch B.V. All rights reserved. - * - * NOTICE: All information contained herein is, and remains - * the property of Elasticsearch B.V. and its suppliers, if any. - * The intellectual and technical concepts contained herein - * are proprietary to Elasticsearch B.V. and its suppliers and - * may be covered by U.S. and Foreign Patents, patents in - * process, and are protected by trade secret or copyright - * law. Dissemination of this information or reproduction of - * this material is strictly forbidden unless prior written - * permission is obtained from Elasticsearch B.V. + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". */ + package org.elasticsearch.action.search.load; import org.elasticsearch.common.io.stream.StreamInput; diff --git a/server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStatsAction.java b/server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStatsAction.java index 0a1cc4a4ff4b9..db7d406b053f5 100644 --- a/server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStatsAction.java +++ b/server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStatsAction.java @@ -1,20 +1,13 @@ /* - * ELASTICSEARCH CONFIDENTIAL - * __________________ - * - * Copyright Elasticsearch B.V. All rights reserved. - * - * NOTICE: All information contained herein is, and remains - * the property of Elasticsearch B.V. and its suppliers, if any. - * The intellectual and technical concepts contained herein - * are proprietary to Elasticsearch B.V. and its suppliers and - * may be covered by U.S. and Foreign Patents, patents in - * process, and are protected by trade secret or copyright - * law. Dissemination of this information or reproduction of - * this material is strictly forbidden unless prior written - * permission is obtained from Elasticsearch B.V. + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". */ + package org.elasticsearch.action.search.load; import org.elasticsearch.action.ActionType; diff --git a/server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStatsResponse.java b/server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStatsResponse.java index 8393e9cc8abb2..c466c1090fbb3 100644 --- a/server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStatsResponse.java +++ b/server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStatsResponse.java @@ -1,20 +1,13 @@ /* - * ELASTICSEARCH CONFIDENTIAL - * __________________ - * - * Copyright Elasticsearch B.V. All rights reserved. - * - * NOTICE: All information contained herein is, and remains - * the property of Elasticsearch B.V. and its suppliers, if any. - * The intellectual and technical concepts contained herein - * are proprietary to Elasticsearch B.V. and its suppliers and - * may be covered by U.S. and Foreign Patents, patents in - * process, and are protected by trade secret or copyright - * law. Dissemination of this information or reproduction of - * this material is strictly forbidden unless prior written - * permission is obtained from Elasticsearch B.V. + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". */ + package org.elasticsearch.action.search.load; import org.elasticsearch.action.support.DefaultShardOperationFailedException; diff --git a/server/src/main/java/org/elasticsearch/action/search/load/TransportShardSearchLoadStatsAction.java b/server/src/main/java/org/elasticsearch/action/search/load/TransportShardSearchLoadStatsAction.java index 02d82b29b0208..0e2c9ad9b61de 100644 --- a/server/src/main/java/org/elasticsearch/action/search/load/TransportShardSearchLoadStatsAction.java +++ b/server/src/main/java/org/elasticsearch/action/search/load/TransportShardSearchLoadStatsAction.java @@ -1,20 +1,13 @@ /* - * ELASTICSEARCH CONFIDENTIAL - * __________________ - * - * Copyright Elasticsearch B.V. All rights reserved. - * - * NOTICE: All information contained herein is, and remains - * the property of Elasticsearch B.V. and its suppliers, if any. - * The intellectual and technical concepts contained herein - * are proprietary to Elasticsearch B.V. and its suppliers and - * may be covered by U.S. and Foreign Patents, patents in - * process, and are protected by trade secret or copyright - * law. Dissemination of this information or reproduction of - * this material is strictly forbidden unless prior written - * permission is obtained from Elasticsearch B.V. + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". */ + package org.elasticsearch.action.search.load; import org.apache.logging.log4j.LogManager; From aa0c5b253376964d5af743cd5c096e056dfef31d Mon Sep 17 00:00:00 2001 From: elasticsearchmachine Date: Fri, 30 May 2025 10:43:10 +0000 Subject: [PATCH 05/25] [CI] Auto commit changes from spotless --- .../elasticsearch/action/search/load/ShardSearchLoadStats.java | 1 - .../action/search/load/ShardSearchLoadStatsAction.java | 1 - .../action/search/load/ShardSearchLoadStatsResponse.java | 1 - .../action/search/load/TransportShardSearchLoadStatsAction.java | 1 - 4 files changed, 4 deletions(-) diff --git a/server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStats.java b/server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStats.java index 6da0939d58b43..f197a639131b4 100644 --- a/server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStats.java +++ b/server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStats.java @@ -7,7 +7,6 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ - package org.elasticsearch.action.search.load; import org.elasticsearch.common.io.stream.StreamInput; diff --git a/server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStatsAction.java b/server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStatsAction.java index db7d406b053f5..92a457a08d782 100644 --- a/server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStatsAction.java +++ b/server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStatsAction.java @@ -7,7 +7,6 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ - package org.elasticsearch.action.search.load; import org.elasticsearch.action.ActionType; diff --git a/server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStatsResponse.java b/server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStatsResponse.java index c466c1090fbb3..120776c210b55 100644 --- a/server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStatsResponse.java +++ b/server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStatsResponse.java @@ -7,7 +7,6 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ - package org.elasticsearch.action.search.load; import org.elasticsearch.action.support.DefaultShardOperationFailedException; diff --git a/server/src/main/java/org/elasticsearch/action/search/load/TransportShardSearchLoadStatsAction.java b/server/src/main/java/org/elasticsearch/action/search/load/TransportShardSearchLoadStatsAction.java index 0e2c9ad9b61de..fbcb8a6b379dd 100644 --- a/server/src/main/java/org/elasticsearch/action/search/load/TransportShardSearchLoadStatsAction.java +++ b/server/src/main/java/org/elasticsearch/action/search/load/TransportShardSearchLoadStatsAction.java @@ -7,7 +7,6 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ - package org.elasticsearch.action.search.load; import org.apache.logging.log4j.LogManager; From c8fb56069911777bfa604309f2457140e725d9ec Mon Sep 17 00:00:00 2001 From: Dimitris Rempapis Date: Fri, 30 May 2025 14:45:38 +0300 Subject: [PATCH 06/25] Register action name in constants --- .../action/search/load/ShardSearchLoadStatsAction.java | 2 +- .../org/elasticsearch/xpack/security/operator/Constants.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStatsAction.java b/server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStatsAction.java index 92a457a08d782..883f1b609e3e2 100644 --- a/server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStatsAction.java +++ b/server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStatsAction.java @@ -24,7 +24,7 @@ public class ShardSearchLoadStatsAction extends ActionType REMOTE_TYPE = new RemoteClusterActionType<>( NAME, ShardSearchLoadStatsResponse::new diff --git a/x-pack/plugin/security/qa/operator-privileges-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/operator/Constants.java b/x-pack/plugin/security/qa/operator-privileges-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/operator/Constants.java index 891a0badfd741..65d6fdbf514fb 100644 --- a/x-pack/plugin/security/qa/operator-privileges-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/operator/Constants.java +++ b/x-pack/plugin/security/qa/operator-privileges-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/operator/Constants.java @@ -637,6 +637,7 @@ public class Constants { "internal:gateway/local/started_shards", "internal:admin/indices/prevalidate_shard_path", "internal:index/metadata/migration_version/update", + "internal:search/stats", "indices:admin/migration/reindex_status", "indices:admin/data_stream/index/reindex", "indices:admin/data_stream/reindex", From 182f3b60f130cdde9792b7e04abb934c229ebc80 Mon Sep 17 00:00:00 2001 From: Dimitris Rempapis Date: Mon, 2 Jun 2025 15:55:41 +0300 Subject: [PATCH 07/25] update after review --- .../org/elasticsearch/TransportVersions.java | 2 +- .../search/load/ShardSearchLoadStats.java | 26 ++++++++--------- .../load/ShardSearchLoadStatsAction.java | 6 ++-- .../TransportShardSearchLoadStatsAction.java | 5 ---- .../index/search/stats/SearchStats.java | 28 +++++++++---------- .../search/stats/SearchStatsSettings.java | 2 +- .../index/search/stats/ShardSearchStats.java | 12 ++++---- .../elasticsearch/index/shard/IndexShard.java | 2 +- .../xpack/security/operator/Constants.java | 2 +- 9 files changed, 39 insertions(+), 46 deletions(-) diff --git a/server/src/main/java/org/elasticsearch/TransportVersions.java b/server/src/main/java/org/elasticsearch/TransportVersions.java index 0e655cf5a13f7..8ad2e6e17b3be 100644 --- a/server/src/main/java/org/elasticsearch/TransportVersions.java +++ b/server/src/main/java/org/elasticsearch/TransportVersions.java @@ -273,7 +273,7 @@ static TransportVersion def(int id) { public static final TransportVersion INFERENCE_CUSTOM_SERVICE_ADDED = def(9_084_0_00); public static final TransportVersion ESQL_LIMIT_ROW_SIZE = def(9_085_0_00); public static final TransportVersion ESQL_REGEX_MATCH_WITH_CASE_INSENSITIVITY = def(9_086_0_00); - public static final TransportVersion EWMR_STATS = def(9_087_0_00); + public static final TransportVersion SEARCH_LOAD_PER_INDEX_STATS = def(9_087_0_00); /* * STOP! READ THIS FIRST! No, really, diff --git a/server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStats.java b/server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStats.java index f197a639131b4..71815641d60ac 100644 --- a/server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStats.java +++ b/server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStats.java @@ -18,7 +18,7 @@ import java.util.Objects; /** - * ShardStats class represents the statistics of a shard in an index. + * ShardSearchLoadStats class represents the statistics of a shard in an index. * It contains information such as the index name, shard ID, allocation ID, and EWMA rate. */ public class ShardSearchLoadStats implements Writeable { @@ -29,7 +29,7 @@ public class ShardSearchLoadStats implements Writeable { private final String allocationId; - private final Double emwRate; + private final Double searchLoad; /** * Constructor to create a ShardStats object from a StreamInput. @@ -42,7 +42,7 @@ public ShardSearchLoadStats(StreamInput in) throws IOException { this.indexName = in.readString(); this.shardId = in.readVInt(); this.allocationId = in.readString(); - this.emwRate = in.readDouble(); + this.searchLoad = in.readDouble(); } /** @@ -51,13 +51,13 @@ public ShardSearchLoadStats(StreamInput in) throws IOException { * @param indexName the name of the index * @param shardId the ID of the shard * @param allocationId the allocation ID of the shard - * @param ewma the EWMA rate of the shard + * @param searchLoad the search load of the shard */ - public ShardSearchLoadStats(String indexName, Integer shardId, String allocationId, Double ewma) { + public ShardSearchLoadStats(String indexName, Integer shardId, String allocationId, Double searchLoad) { this.indexName = indexName; this.shardId = shardId; this.allocationId = allocationId; - this.emwRate = ewma; + this.searchLoad = searchLoad; } @Override @@ -68,12 +68,12 @@ public boolean equals(Object o) { return Objects.equals(indexName, that.indexName) && Objects.equals(shardId, that.shardId) && Objects.equals(allocationId, that.allocationId) - && Objects.equals(emwRate, that.emwRate); + && Objects.equals(searchLoad, that.searchLoad); } @Override public int hashCode() { - return Objects.hash(indexName, shardId, allocationId, emwRate); + return Objects.hash(indexName, shardId, allocationId, searchLoad); } /** @@ -104,12 +104,12 @@ public String getAllocationId() { } /** - * Returns the EWMA rate of the shard. + * Returns the search load of the shard. * - * @return the EWMA rate + * @return the search load as a Double */ - public Double getEwmRate() { - return this.emwRate; + public Double getSearchLoad() { + return this.searchLoad; } @Override @@ -117,6 +117,6 @@ public void writeTo(StreamOutput out) throws IOException { out.writeString(indexName); out.writeVInt(shardId); out.writeString(allocationId); - out.writeDouble(emwRate); + out.writeDouble(searchLoad); } } diff --git a/server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStatsAction.java b/server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStatsAction.java index 883f1b609e3e2..d2af8335a18d5 100644 --- a/server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStatsAction.java +++ b/server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStatsAction.java @@ -16,15 +16,13 @@ * Action definition for retrieving shard-level search load statistics. *

* This action serves as a marker for executing {@link TransportShardSearchLoadStatsAction} + * *

*/ public class ShardSearchLoadStatsAction extends ActionType { - /** - * Singleton instance of the action type. - */ public static final ShardSearchLoadStatsAction INSTANCE = new ShardSearchLoadStatsAction(); - public static final String NAME = "internal:search/stats"; + public static final String NAME = "internal:search/load"; public static final RemoteClusterActionType REMOTE_TYPE = new RemoteClusterActionType<>( NAME, ShardSearchLoadStatsResponse::new diff --git a/server/src/main/java/org/elasticsearch/action/search/load/TransportShardSearchLoadStatsAction.java b/server/src/main/java/org/elasticsearch/action/search/load/TransportShardSearchLoadStatsAction.java index fbcb8a6b379dd..4e4340136cf23 100644 --- a/server/src/main/java/org/elasticsearch/action/search/load/TransportShardSearchLoadStatsAction.java +++ b/server/src/main/java/org/elasticsearch/action/search/load/TransportShardSearchLoadStatsAction.java @@ -105,15 +105,10 @@ public TransportShardSearchLoadStatsAction( */ @Override protected ShardsIterator shards(ClusterState clusterState, Request request, String[] concreteIndices) { - // Create a modifiable list of shard routings for the given indices List shardRoutingList = new ArrayList<>( clusterState.routingTable(projectResolver.getProjectId()).allShards(concreteIndices).getShardRoutings() ); - - // Remove all primary shard routings from the list shardRoutingList.removeIf(ShardRouting::primary); - - // Return an iterator over the remaining (replica) shards return new PlainShardsIterator(shardRoutingList); } diff --git a/server/src/main/java/org/elasticsearch/index/search/stats/SearchStats.java b/server/src/main/java/org/elasticsearch/index/search/stats/SearchStats.java index 426422f92c920..7289225f8915a 100644 --- a/server/src/main/java/org/elasticsearch/index/search/stats/SearchStats.java +++ b/server/src/main/java/org/elasticsearch/index/search/stats/SearchStats.java @@ -49,7 +49,7 @@ public static class Stats implements Writeable, ToXContentFragment { private long queryFailure; private long fetchFailure; - private double exponentiallyWeightedMovingRate; + private double recentSearchLoad; private Stats() { // for internal use, initializes all counts to 0 @@ -70,7 +70,7 @@ public Stats( long suggestCount, long suggestTimeInMillis, long suggestCurrent, - double exponentiallyWeightedMovingRate + double recentSearchLoad ) { this.queryCount = queryCount; this.queryTimeInMillis = queryTimeInMillis; @@ -90,7 +90,7 @@ public Stats( this.suggestTimeInMillis = suggestTimeInMillis; this.suggestCurrent = suggestCurrent; - this.exponentiallyWeightedMovingRate = exponentiallyWeightedMovingRate; + this.recentSearchLoad = recentSearchLoad; } @@ -116,8 +116,8 @@ private Stats(StreamInput in) throws IOException { fetchFailure = in.readVLong(); } - if (in.getTransportVersion().onOrAfter(TransportVersions.EWMR_STATS)) { - exponentiallyWeightedMovingRate = in.readDouble(); + if (in.getTransportVersion().onOrAfter(TransportVersions.SEARCH_LOAD_PER_INDEX_STATS)) { + recentSearchLoad = in.readDouble(); } } @@ -144,8 +144,8 @@ public void writeTo(StreamOutput out) throws IOException { out.writeVLong(fetchFailure); } - if (out.getTransportVersion().onOrAfter(TransportVersions.EWMR_STATS)) { - out.writeDouble(exponentiallyWeightedMovingRate); + if (out.getTransportVersion().onOrAfter(TransportVersions.SEARCH_LOAD_PER_INDEX_STATS)) { + out.writeDouble(recentSearchLoad); } } @@ -168,7 +168,7 @@ public void add(Stats stats) { suggestTimeInMillis += stats.suggestTimeInMillis; suggestCurrent += stats.suggestCurrent; - exponentiallyWeightedMovingRate += stats.exponentiallyWeightedMovingRate; + recentSearchLoad += stats.recentSearchLoad; } public void addForClosingShard(Stats stats) { @@ -188,7 +188,7 @@ public void addForClosingShard(Stats stats) { suggestCount += stats.suggestCount; suggestTimeInMillis += stats.suggestTimeInMillis; - exponentiallyWeightedMovingRate += stats.exponentiallyWeightedMovingRate; + recentSearchLoad += stats.recentSearchLoad; } public long getQueryCount() { @@ -263,8 +263,8 @@ public long getSuggestCurrent() { return suggestCurrent; } - public double getExponentiallyWeightedMovingRate() { - return exponentiallyWeightedMovingRate; + public double getSearchLoadRate() { + return recentSearchLoad; } public static Stats readStats(StreamInput in) throws IOException { @@ -291,7 +291,7 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws builder.humanReadableField(Fields.SUGGEST_TIME_IN_MILLIS, Fields.SUGGEST_TIME, getSuggestTime()); builder.field(Fields.SUGGEST_CURRENT, suggestCurrent); - builder.field(Fields.EMW_RATE, exponentiallyWeightedMovingRate); + builder.field(Fields.EMW_RATE, recentSearchLoad); return builder; } @@ -315,7 +315,7 @@ public boolean equals(Object o) { && suggestCount == that.suggestCount && suggestTimeInMillis == that.suggestTimeInMillis && suggestCurrent == that.suggestCurrent - && exponentiallyWeightedMovingRate == that.exponentiallyWeightedMovingRate; + && recentSearchLoad == that.recentSearchLoad; } @Override @@ -335,7 +335,7 @@ public int hashCode() { suggestCount, suggestTimeInMillis, suggestCurrent, - exponentiallyWeightedMovingRate + recentSearchLoad ); } } diff --git a/server/src/main/java/org/elasticsearch/index/search/stats/SearchStatsSettings.java b/server/src/main/java/org/elasticsearch/index/search/stats/SearchStatsSettings.java index bb1feb6353653..2ab13ae60411e 100644 --- a/server/src/main/java/org/elasticsearch/index/search/stats/SearchStatsSettings.java +++ b/server/src/main/java/org/elasticsearch/index/search/stats/SearchStatsSettings.java @@ -38,7 +38,7 @@ public class SearchStatsSettings { Setting.Property.NodeScope ); - private TimeValue recentReadLoadHalfLifeForNewShards = RECENT_READ_LOAD_HALF_LIFE_SETTING.getDefault(Settings.EMPTY); + private volatile TimeValue recentReadLoadHalfLifeForNewShards = RECENT_READ_LOAD_HALF_LIFE_SETTING.getDefault(Settings.EMPTY); public SearchStatsSettings(ClusterSettings clusterSettings) { clusterSettings.initializeAndWatch(RECENT_READ_LOAD_HALF_LIFE_SETTING, value -> recentReadLoadHalfLifeForNewShards = value); diff --git a/server/src/main/java/org/elasticsearch/index/search/stats/ShardSearchStats.java b/server/src/main/java/org/elasticsearch/index/search/stats/ShardSearchStats.java index 4c285a467ba23..d01ea2a184dbd 100644 --- a/server/src/main/java/org/elasticsearch/index/search/stats/ShardSearchStats.java +++ b/server/src/main/java/org/elasticsearch/index/search/stats/ShardSearchStats.java @@ -85,11 +85,11 @@ public void onFailedQueryPhase(SearchContext searchContext) { @Override public void onQueryPhase(SearchContext searchContext, long tookInNanos) { computeStats(searchContext, searchContext.hasOnlySuggest() ? statsHolder -> { - statsHolder.exponentiallyWeightedMovingRate.addIncrement(tookInNanos, System.nanoTime()); + statsHolder.recentSearchLoad.addIncrement(tookInNanos, System.nanoTime()); statsHolder.suggestMetric.inc(tookInNanos); statsHolder.suggestCurrent.dec(); } : statsHolder -> { - statsHolder.exponentiallyWeightedMovingRate.addIncrement(tookInNanos, System.nanoTime()); + statsHolder.recentSearchLoad.addIncrement(tookInNanos, System.nanoTime()); statsHolder.queryMetric.inc(tookInNanos); statsHolder.queryCurrent.dec(); }); @@ -111,7 +111,7 @@ public void onFailedFetchPhase(SearchContext searchContext) { @Override public void onFetchPhase(SearchContext searchContext, long tookInNanos) { computeStats(searchContext, statsHolder -> { - statsHolder.exponentiallyWeightedMovingRate.addIncrement(tookInNanos, System.nanoTime()); + statsHolder.recentSearchLoad.addIncrement(tookInNanos, System.nanoTime()); statsHolder.fetchMetric.inc(tookInNanos); statsHolder.fetchCurrent.dec(); }); @@ -183,11 +183,11 @@ static final class StatsHolder { final CounterMetric queryFailure = new CounterMetric(); final CounterMetric fetchFailure = new CounterMetric(); - final ExponentiallyWeightedMovingRate exponentiallyWeightedMovingRate; + final ExponentiallyWeightedMovingRate recentSearchLoad; StatsHolder(SearchStatsSettings searchStatsSettings) { double lambdaInInverseNanos = Math.log(2.0) / searchStatsSettings.getRecentReadLoadHalfLifeForNewShards().nanos(); - this.exponentiallyWeightedMovingRate = new ExponentiallyWeightedMovingRate(lambdaInInverseNanos, System.nanoTime()); + this.recentSearchLoad = new ExponentiallyWeightedMovingRate(lambdaInInverseNanos, System.nanoTime()); } SearchStats.Stats stats() { @@ -206,7 +206,7 @@ SearchStats.Stats stats() { suggestMetric.count(), TimeUnit.NANOSECONDS.toMillis(suggestMetric.sum()), suggestCurrent.count(), - exponentiallyWeightedMovingRate.getRate(System.nanoTime()) + recentSearchLoad.getRate(System.nanoTime()) ); } } diff --git a/server/src/main/java/org/elasticsearch/index/shard/IndexShard.java b/server/src/main/java/org/elasticsearch/index/shard/IndexShard.java index e9cc87388e1bc..19db95dbefd11 100644 --- a/server/src/main/java/org/elasticsearch/index/shard/IndexShard.java +++ b/server/src/main/java/org/elasticsearch/index/shard/IndexShard.java @@ -1429,7 +1429,7 @@ public GetStats getStats() { * Returns the search load rate stats for this shard. */ public double getSearchLoadRate() { - return searchStats.stats().getTotal().getExponentiallyWeightedMovingRate(); + return searchStats.stats().getTotal().getSearchLoadRate(); } public StoreStats storeStats() { diff --git a/x-pack/plugin/security/qa/operator-privileges-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/operator/Constants.java b/x-pack/plugin/security/qa/operator-privileges-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/operator/Constants.java index 65d6fdbf514fb..a7f2b28ea6cf2 100644 --- a/x-pack/plugin/security/qa/operator-privileges-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/operator/Constants.java +++ b/x-pack/plugin/security/qa/operator-privileges-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/operator/Constants.java @@ -637,7 +637,7 @@ public class Constants { "internal:gateway/local/started_shards", "internal:admin/indices/prevalidate_shard_path", "internal:index/metadata/migration_version/update", - "internal:search/stats", + "internal:search/load", "indices:admin/migration/reindex_status", "indices:admin/data_stream/index/reindex", "indices:admin/data_stream/reindex", From 102bdc1b38ca30b092a3651c4d66f694a96f28e1 Mon Sep 17 00:00:00 2001 From: Dimitris Rempapis Date: Tue, 3 Jun 2025 12:29:24 +0300 Subject: [PATCH 08/25] Removing the allocationId --- .../search/load/ShardSearchLoadStats.java | 22 ++--------- .../load/ShardSearchLoadStatsResponse.java | 38 ++++++++++++++++--- .../TransportShardSearchLoadStatsAction.java | 1 - 3 files changed, 35 insertions(+), 26 deletions(-) diff --git a/server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStats.java b/server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStats.java index 71815641d60ac..2b153e83f77f9 100644 --- a/server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStats.java +++ b/server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStats.java @@ -19,7 +19,7 @@ /** * ShardSearchLoadStats class represents the statistics of a shard in an index. - * It contains information such as the index name, shard ID, allocation ID, and EWMA rate. + * It contains information such as the index name, shard ID, and search load. */ public class ShardSearchLoadStats implements Writeable { @@ -27,8 +27,6 @@ public class ShardSearchLoadStats implements Writeable { private final Integer shardId; - private final String allocationId; - private final Double searchLoad; /** @@ -41,7 +39,6 @@ public ShardSearchLoadStats(StreamInput in) throws IOException { assert Transports.assertNotTransportThread("O(#shards) work must always fork to an appropriate executor"); this.indexName = in.readString(); this.shardId = in.readVInt(); - this.allocationId = in.readString(); this.searchLoad = in.readDouble(); } @@ -50,13 +47,11 @@ public ShardSearchLoadStats(StreamInput in) throws IOException { * * @param indexName the name of the index * @param shardId the ID of the shard - * @param allocationId the allocation ID of the shard * @param searchLoad the search load of the shard */ - public ShardSearchLoadStats(String indexName, Integer shardId, String allocationId, Double searchLoad) { + public ShardSearchLoadStats(String indexName, Integer shardId, Double searchLoad) { this.indexName = indexName; this.shardId = shardId; - this.allocationId = allocationId; this.searchLoad = searchLoad; } @@ -67,13 +62,12 @@ public boolean equals(Object o) { ShardSearchLoadStats that = (ShardSearchLoadStats) o; return Objects.equals(indexName, that.indexName) && Objects.equals(shardId, that.shardId) - && Objects.equals(allocationId, that.allocationId) && Objects.equals(searchLoad, that.searchLoad); } @Override public int hashCode() { - return Objects.hash(indexName, shardId, allocationId, searchLoad); + return Objects.hash(indexName, shardId, searchLoad); } /** @@ -94,15 +88,6 @@ public Integer getShardId() { return this.shardId; } - /** - * Returns the allocation ID of the shard. - * - * @return the allocation ID - */ - public String getAllocationId() { - return this.allocationId; - } - /** * Returns the search load of the shard. * @@ -116,7 +101,6 @@ public Double getSearchLoad() { public void writeTo(StreamOutput out) throws IOException { out.writeString(indexName); out.writeVInt(shardId); - out.writeString(allocationId); out.writeDouble(searchLoad); } } diff --git a/server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStatsResponse.java b/server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStatsResponse.java index 120776c210b55..a8902713c316f 100644 --- a/server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStatsResponse.java +++ b/server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStatsResponse.java @@ -16,9 +16,14 @@ import org.elasticsearch.xcontent.ToXContent; import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.Optional; /** * Response to a shard stats request. @@ -39,7 +44,7 @@ public class ShardSearchLoadStatsResponse extends ChunkedBroadcastResponse { } /** - * Constructor to create a ShardStatsResponse object with the given parameters. + * Constructor to create a ShardSearchLoadStatsResponse object with the given parameters. * * @param shards the array of shard stats * @param totalShards the total number of shards @@ -55,17 +60,18 @@ public class ShardSearchLoadStatsResponse extends ChunkedBroadcastResponse { List shardFailures ) { super(totalShards, successfulShards, failedShards, shardFailures); - this.shards = shards; - Objects.requireNonNull(shards); + this.shards = aggregateSearchLoadByShard(Objects.requireNonNull(shards)); } + + /** - * Returns the array of shard stats. + * Returns a copy of the array of shard stats. * * @return the array of shard stats */ public ShardSearchLoadStats[] getShards() { - return shards; + return Arrays.copyOf(shards, shards.length); } @Override @@ -76,6 +82,26 @@ public void writeTo(StreamOutput out) throws IOException { @Override protected Iterator customXContentChunks(ToXContent.Params params) { - return null; + return Collections.emptyIterator(); } + + private ShardSearchLoadStats[] aggregateSearchLoadByShard(ShardSearchLoadStats[] shards) { + Map aggregated = new HashMap<>(); + + for (var stat : shards) { + var key = new ShardKey(stat.getIndexName(), stat.getShardId()); + var load = Optional.ofNullable(stat.getSearchLoad()).orElse(0.0); + aggregated.merge(key, load, Double::sum); + } + + return aggregated.entrySet().stream() + .map(e -> new ShardSearchLoadStats( + e.getKey().indexName(), + e.getKey().shardId(), + e.getValue() + )) + .toArray(ShardSearchLoadStats[]::new); + } + + private record ShardKey(String indexName, int shardId) {} } diff --git a/server/src/main/java/org/elasticsearch/action/search/load/TransportShardSearchLoadStatsAction.java b/server/src/main/java/org/elasticsearch/action/search/load/TransportShardSearchLoadStatsAction.java index 4e4340136cf23..0a3951d9c35e0 100644 --- a/server/src/main/java/org/elasticsearch/action/search/load/TransportShardSearchLoadStatsAction.java +++ b/server/src/main/java/org/elasticsearch/action/search/load/TransportShardSearchLoadStatsAction.java @@ -171,7 +171,6 @@ protected void shardOperation(Request request, ShardRouting shardRouting, Task t return new ShardSearchLoadStats( shardId.getIndex().getName(), shardId.getId(), - shardRouting.allocationId().getId(), indexShard.getSearchLoadRate() ); }); From 31b63b0e91e4455af958851cd366693748122f88 Mon Sep 17 00:00:00 2001 From: elasticsearchmachine Date: Tue, 3 Jun 2025 09:37:19 +0000 Subject: [PATCH 09/25] [CI] Auto commit changes from spotless --- .../search/load/ShardSearchLoadStatsResponse.java | 13 ++++--------- .../load/TransportShardSearchLoadStatsAction.java | 6 +----- 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStatsResponse.java b/server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStatsResponse.java index a8902713c316f..b571c471caa58 100644 --- a/server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStatsResponse.java +++ b/server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStatsResponse.java @@ -63,8 +63,6 @@ public class ShardSearchLoadStatsResponse extends ChunkedBroadcastResponse { this.shards = aggregateSearchLoadByShard(Objects.requireNonNull(shards)); } - - /** * Returns a copy of the array of shard stats. * @@ -94,13 +92,10 @@ private ShardSearchLoadStats[] aggregateSearchLoadByShard(ShardSearchLoadStats[] aggregated.merge(key, load, Double::sum); } - return aggregated.entrySet().stream() - .map(e -> new ShardSearchLoadStats( - e.getKey().indexName(), - e.getKey().shardId(), - e.getValue() - )) - .toArray(ShardSearchLoadStats[]::new); + return aggregated.entrySet() + .stream() + .map(e -> new ShardSearchLoadStats(e.getKey().indexName(), e.getKey().shardId(), e.getValue())) + .toArray(ShardSearchLoadStats[]::new); } private record ShardKey(String indexName, int shardId) {} diff --git a/server/src/main/java/org/elasticsearch/action/search/load/TransportShardSearchLoadStatsAction.java b/server/src/main/java/org/elasticsearch/action/search/load/TransportShardSearchLoadStatsAction.java index 0a3951d9c35e0..36feebce5a3e1 100644 --- a/server/src/main/java/org/elasticsearch/action/search/load/TransportShardSearchLoadStatsAction.java +++ b/server/src/main/java/org/elasticsearch/action/search/load/TransportShardSearchLoadStatsAction.java @@ -168,11 +168,7 @@ protected void shardOperation(Request request, ShardRouting shardRouting, Task t ShardId shardId = shardRouting.shardId(); IndexShard indexShard = indicesService.indexServiceSafe(shardId.getIndex()).getShard(shardId.id()); - return new ShardSearchLoadStats( - shardId.getIndex().getName(), - shardId.getId(), - indexShard.getSearchLoadRate() - ); + return new ShardSearchLoadStats(shardId.getIndex().getName(), shardId.getId(), indexShard.getSearchLoadRate()); }); } From b2760355ce530c711847c573fbb5d71470df7f11 Mon Sep 17 00:00:00 2001 From: Dimitris Rempapis Date: Wed, 4 Jun 2025 11:28:11 +0300 Subject: [PATCH 10/25] update after review --- .../load/ShardSearchLoadStatsResponse.java | 102 --------- .../TransportShardSearchLoadStatsAction.java | 205 ------------------ 2 files changed, 307 deletions(-) delete mode 100644 server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStatsResponse.java delete mode 100644 server/src/main/java/org/elasticsearch/action/search/load/TransportShardSearchLoadStatsAction.java diff --git a/server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStatsResponse.java b/server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStatsResponse.java deleted file mode 100644 index b571c471caa58..0000000000000 --- a/server/src/main/java/org/elasticsearch/action/search/load/ShardSearchLoadStatsResponse.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -package org.elasticsearch.action.search.load; - -import org.elasticsearch.action.support.DefaultShardOperationFailedException; -import org.elasticsearch.action.support.broadcast.ChunkedBroadcastResponse; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import org.elasticsearch.xcontent.ToXContent; - -import java.io.IOException; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; - -/** - * Response to a shard stats request. - */ -public class ShardSearchLoadStatsResponse extends ChunkedBroadcastResponse { - - private final ShardSearchLoadStats[] shards; - - /** - * Constructor to create a ShardStatsResponse object from a StreamInput. - * - * @param in the StreamInput to read from - * @throws IOException if an I/O error occurs - */ - ShardSearchLoadStatsResponse(StreamInput in) throws IOException { - super(in); - shards = in.readArray(ShardSearchLoadStats::new, ShardSearchLoadStats[]::new); - } - - /** - * Constructor to create a ShardSearchLoadStatsResponse object with the given parameters. - * - * @param shards the array of shard stats - * @param totalShards the total number of shards - * @param successfulShards the number of successful shards - * @param failedShards the number of failed shards - * @param shardFailures the list of shard failures - */ - ShardSearchLoadStatsResponse( - ShardSearchLoadStats[] shards, - int totalShards, - int successfulShards, - int failedShards, - List shardFailures - ) { - super(totalShards, successfulShards, failedShards, shardFailures); - this.shards = aggregateSearchLoadByShard(Objects.requireNonNull(shards)); - } - - /** - * Returns a copy of the array of shard stats. - * - * @return the array of shard stats - */ - public ShardSearchLoadStats[] getShards() { - return Arrays.copyOf(shards, shards.length); - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeArray(shards); - } - - @Override - protected Iterator customXContentChunks(ToXContent.Params params) { - return Collections.emptyIterator(); - } - - private ShardSearchLoadStats[] aggregateSearchLoadByShard(ShardSearchLoadStats[] shards) { - Map aggregated = new HashMap<>(); - - for (var stat : shards) { - var key = new ShardKey(stat.getIndexName(), stat.getShardId()); - var load = Optional.ofNullable(stat.getSearchLoad()).orElse(0.0); - aggregated.merge(key, load, Double::sum); - } - - return aggregated.entrySet() - .stream() - .map(e -> new ShardSearchLoadStats(e.getKey().indexName(), e.getKey().shardId(), e.getValue())) - .toArray(ShardSearchLoadStats[]::new); - } - - private record ShardKey(String indexName, int shardId) {} -} diff --git a/server/src/main/java/org/elasticsearch/action/search/load/TransportShardSearchLoadStatsAction.java b/server/src/main/java/org/elasticsearch/action/search/load/TransportShardSearchLoadStatsAction.java deleted file mode 100644 index 36feebce5a3e1..0000000000000 --- a/server/src/main/java/org/elasticsearch/action/search/load/TransportShardSearchLoadStatsAction.java +++ /dev/null @@ -1,205 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -package org.elasticsearch.action.search.load; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.elasticsearch.action.ActionListener; -import org.elasticsearch.action.support.ActionFilters; -import org.elasticsearch.action.support.broadcast.BroadcastRequest; -import org.elasticsearch.action.support.broadcast.node.TransportBroadcastByNodeAction; -import org.elasticsearch.cluster.ClusterState; -import org.elasticsearch.cluster.block.ClusterBlockException; -import org.elasticsearch.cluster.block.ClusterBlockLevel; -import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; -import org.elasticsearch.cluster.project.ProjectResolver; -import org.elasticsearch.cluster.routing.PlainShardsIterator; -import org.elasticsearch.cluster.routing.ShardRouting; -import org.elasticsearch.cluster.routing.ShardsIterator; -import org.elasticsearch.cluster.service.ClusterService; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.index.shard.IndexShard; -import org.elasticsearch.index.shard.ShardId; -import org.elasticsearch.indices.IndicesService; -import org.elasticsearch.injection.guice.Inject; -import org.elasticsearch.tasks.CancellableTask; -import org.elasticsearch.tasks.Task; -import org.elasticsearch.tasks.TaskId; -import org.elasticsearch.threadpool.ThreadPool; -import org.elasticsearch.transport.TransportService; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -/** - * Transport action responsible for collecting shard-level search load statistics across the cluster. - *

- * This action broadcasts requests to all replica shards of the specified indices (scoped by a project) - * and aggregates their metrics into a final response. - *

- * - * Extends {@link TransportBroadcastByNodeAction} to handle fan-out to all replica shards. - */ -public class TransportShardSearchLoadStatsAction extends TransportBroadcastByNodeAction< - TransportShardSearchLoadStatsAction.Request, - ShardSearchLoadStatsResponse, - ShardSearchLoadStats> { - - private static final Logger logger = LogManager.getLogger(TransportShardSearchLoadStatsAction.class); - - private final IndicesService indicesService; - private final ProjectResolver projectResolver; - - /** - * Constructs a new {@code TransportShardSearchLoadStatsAction}. - * - * @param clusterService the cluster service - * @param transportService the transport service for communication - * @param indicesService service to access index and shard-level operations - * @param actionFilters filters applied to the action - * @param projectResolver resolves the project context for scoping operations - * @param indexNameExpressionResolver resolves index name expressions - */ - @Inject - public TransportShardSearchLoadStatsAction( - ClusterService clusterService, - TransportService transportService, - IndicesService indicesService, - ActionFilters actionFilters, - ProjectResolver projectResolver, - IndexNameExpressionResolver indexNameExpressionResolver - ) { - super( - ShardSearchLoadStatsAction.NAME, - clusterService, - transportService, - actionFilters, - indexNameExpressionResolver, - Request::new, - transportService.getThreadPool().executor(ThreadPool.Names.MANAGEMENT) - ); - this.indicesService = indicesService; - this.projectResolver = projectResolver; - } - - /** - * Returns a {@link ShardsIterator} over non-primary shards for the given indices. - *

- * This method retrieves all shard routings for the specified indices in the cluster state, - * filters out the primary shards, and returns an iterator over the remaining shard routings - * (i.e., the replica shards). - * - * @param clusterState the current state of the cluster, used to retrieve routing information - * @param request the incoming request (currently unused in this method, but may be relevant in overrides) - * @param concreteIndices the list of index names to resolve shard routings for - * @return a {@link ShardsIterator} over the non-primary (replica) shards of the specified indices - */ - @Override - protected ShardsIterator shards(ClusterState clusterState, Request request, String[] concreteIndices) { - List shardRoutingList = new ArrayList<>( - clusterState.routingTable(projectResolver.getProjectId()).allShards(concreteIndices).getShardRoutings() - ); - shardRoutingList.removeIf(ShardRouting::primary); - return new PlainShardsIterator(shardRoutingList); - } - - @Override - protected ClusterBlockException checkGlobalBlock(ClusterState state, Request request) { - return state.blocks().globalBlockedException(ClusterBlockLevel.METADATA_READ); - } - - @Override - protected ClusterBlockException checkRequestBlock(ClusterState state, Request request, String[] concreteIndices) { - return state.blocks().indicesBlockedException(projectResolver.getProjectId(), ClusterBlockLevel.METADATA_READ, concreteIndices); - } - - @Override - protected ShardSearchLoadStats readShardResult(StreamInput in) throws IOException { - return new ShardSearchLoadStats(in); - } - - /** - * Returns the factory used to construct the final response from individual shard responses. - */ - @Override - protected ResponseFactory getResponseFactory( - Request request, - ClusterState clusterState - ) { - return (totalShards, successfulShards, failedShards, responses, shardFailures) -> new ShardSearchLoadStatsResponse( - responses.toArray(new ShardSearchLoadStats[0]), - totalShards, - successfulShards, - failedShards, - shardFailures - ); - } - - /** - * Reads the request object from the stream. - */ - @Override - protected Request readRequestFrom(StreamInput in) throws IOException { - return new Request(in); - } - - /** - * Executes the shard-level operation for collecting search load statistics. - * - * @param request the original request - * @param shardRouting routing info of the shard - * @param task the parent task - * @param listener listener to notify on result - */ - @Override - protected void shardOperation(Request request, ShardRouting shardRouting, Task task, ActionListener listener) { - ActionListener.completeWith(listener, () -> { - assert task instanceof CancellableTask; - - ShardId shardId = shardRouting.shardId(); - IndexShard indexShard = indicesService.indexServiceSafe(shardId.getIndex()).getShard(shardId.id()); - - return new ShardSearchLoadStats(shardId.getIndex().getName(), shardId.getId(), indexShard.getSearchLoadRate()); - }); - } - - /** - * A broadcast request for collecting shard-level search load statistics. - */ - public static class Request extends BroadcastRequest { - - /** - * Constructs a new request. - */ - public Request() { - super((String[]) null); - } - - /** - * Deserializes the request from the stream input. - * - * @param in the stream input - * @throws IOException if an I/O exception occurs - */ - public Request(StreamInput in) throws IOException { - super(in); - } - - /** - * Creates a cancellable task to track execution of this request. - */ - @Override - public Task createTask(long id, String type, String action, TaskId parentTaskId, Map headers) { - return new CancellableTask(id, type, action, "", parentTaskId, headers); - } - } -} From 6e73c2e67f9395bdcab6a53b9fef6fddfb185a17 Mon Sep 17 00:00:00 2001 From: Dimitris Rempapis Date: Wed, 4 Jun 2025 11:29:26 +0300 Subject: [PATCH 11/25] remove stale package from .info file --- server/src/main/java/module-info.java | 1 - 1 file changed, 1 deletion(-) diff --git a/server/src/main/java/module-info.java b/server/src/main/java/module-info.java index d01ac6478fd27..c0e180c543a57 100644 --- a/server/src/main/java/module-info.java +++ b/server/src/main/java/module-info.java @@ -138,7 +138,6 @@ exports org.elasticsearch.action.ingest; exports org.elasticsearch.action.resync; exports org.elasticsearch.action.search; - exports org.elasticsearch.action.search.load; exports org.elasticsearch.action.support; exports org.elasticsearch.action.support.broadcast; exports org.elasticsearch.action.support.broadcast.node; From 7cfec46e3ea6e1943f577afc85dce076c26cbcfa Mon Sep 17 00:00:00 2001 From: Dimitris Rempapis Date: Wed, 4 Jun 2025 11:41:11 +0300 Subject: [PATCH 12/25] update after review --- .../src/main/java/org/elasticsearch/action/ActionModule.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/server/src/main/java/org/elasticsearch/action/ActionModule.java b/server/src/main/java/org/elasticsearch/action/ActionModule.java index c85fed41034c8..d7cb57bad4812 100644 --- a/server/src/main/java/org/elasticsearch/action/ActionModule.java +++ b/server/src/main/java/org/elasticsearch/action/ActionModule.java @@ -183,8 +183,6 @@ import org.elasticsearch.action.search.TransportSearchAction; import org.elasticsearch.action.search.TransportSearchScrollAction; import org.elasticsearch.action.search.TransportSearchShardsAction; -import org.elasticsearch.action.search.load.ShardSearchLoadStatsAction; -import org.elasticsearch.action.search.load.TransportShardSearchLoadStatsAction; import org.elasticsearch.action.support.ActionFilter; import org.elasticsearch.action.support.ActionFilters; import org.elasticsearch.action.support.AutoCreateIndex; @@ -746,7 +744,6 @@ public void reg actions.register(FieldUsageStatsAction.INSTANCE, TransportFieldUsageAction.class); actions.register(MasterHistoryAction.INSTANCE, MasterHistoryAction.TransportAction.class); actions.register(CoordinationDiagnosticsAction.INSTANCE, CoordinationDiagnosticsAction.TransportAction.class); - actions.register(ShardSearchLoadStatsAction.INSTANCE, TransportShardSearchLoadStatsAction.class); // Indexed scripts actions.register(TransportPutStoredScriptAction.TYPE, TransportPutStoredScriptAction.class); From 73d93a5017a85cabc6ad27cdc0ff79bc681f67ac Mon Sep 17 00:00:00 2001 From: Dimitris Rempapis Date: Fri, 6 Jun 2025 14:53:37 +0300 Subject: [PATCH 13/25] update after review --- .../elasticsearch/index/shard/IndexShard.java | 7 - .../search/stats/ShardSearchStatsTests.java | 259 ++++++++++++++++++ 2 files changed, 259 insertions(+), 7 deletions(-) create mode 100644 server/src/test/java/org/elasticsearch/search/stats/ShardSearchStatsTests.java diff --git a/server/src/main/java/org/elasticsearch/index/shard/IndexShard.java b/server/src/main/java/org/elasticsearch/index/shard/IndexShard.java index 19db95dbefd11..397532a4182f1 100644 --- a/server/src/main/java/org/elasticsearch/index/shard/IndexShard.java +++ b/server/src/main/java/org/elasticsearch/index/shard/IndexShard.java @@ -1425,13 +1425,6 @@ public GetStats getStats() { return getService.stats(); } - /** - * Returns the search load rate stats for this shard. - */ - public double getSearchLoadRate() { - return searchStats.stats().getTotal().getSearchLoadRate(); - } - public StoreStats storeStats() { try { final RecoveryState recoveryState = this.recoveryState; diff --git a/server/src/test/java/org/elasticsearch/search/stats/ShardSearchStatsTests.java b/server/src/test/java/org/elasticsearch/search/stats/ShardSearchStatsTests.java new file mode 100644 index 0000000000000..9cc994946b27c --- /dev/null +++ b/server/src/test/java/org/elasticsearch/search/stats/ShardSearchStatsTests.java @@ -0,0 +1,259 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.search.stats; + +import org.elasticsearch.common.settings.ClusterSettings; +import org.elasticsearch.index.search.stats.SearchStats; +import org.elasticsearch.index.search.stats.SearchStatsSettings; +import org.elasticsearch.index.search.stats.ShardSearchStats; +import org.elasticsearch.search.builder.SearchSourceBuilder; +import org.elasticsearch.search.internal.ReaderContext; +import org.elasticsearch.search.internal.SearchContext; +import org.elasticsearch.search.internal.ShardSearchRequest; +import org.elasticsearch.search.suggest.SuggestBuilder; +import org.elasticsearch.test.ESTestCase; + +import java.util.Arrays; +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class ShardSearchStatsTests extends ESTestCase { + + private static final long TEN_MILLIS = 10; + + public void testQueryPhase() { + ShardSearchStats listener = new ShardSearchStats(new SearchStatsSettings(ClusterSettings.createBuiltInClusterSettings())); + SearchContext sc = mock(SearchContext.class); + ShardSearchRequest req = mock(ShardSearchRequest.class); + when(sc.request()).thenReturn(req); + when(sc.groupStats()).thenReturn(null); + + listener.onPreQueryPhase(sc); + listener.onQueryPhase(sc, TimeUnit.MILLISECONDS.toNanos(TEN_MILLIS)); + + SearchStats.Stats stats = listener.stats().getTotal(); + assertEquals(0, stats.getQueryCurrent()); + assertEquals(1, stats.getQueryCount()); + assertEquals(TEN_MILLIS, stats.getQueryTimeInMillis()); + assertTrue(stats.getSearchLoadRate() > 0.0); + } + + public void testQueryPhase_SuggestOnly() { + ShardSearchStats listener = new ShardSearchStats(new SearchStatsSettings(ClusterSettings.createBuiltInClusterSettings())); + SearchContext sc = mock(SearchContext.class); + ShardSearchRequest req = mock(ShardSearchRequest.class); + SearchSourceBuilder ssb = new SearchSourceBuilder().suggest(new SuggestBuilder()); + when(sc.request()).thenReturn(req); + when(sc.groupStats()).thenReturn(null); + when(req.source()).thenReturn(ssb); + + listener.onPreQueryPhase(sc); + listener.onQueryPhase(sc, TimeUnit.MILLISECONDS.toNanos(TEN_MILLIS)); + + SearchStats.Stats stats = listener.stats().getTotal(); + assertEquals(0, stats.getSuggestCurrent()); + assertEquals(1, stats.getSuggestCount()); + assertEquals(TEN_MILLIS, stats.getSuggestTimeInMillis()); + assertEquals(0, stats.getQueryCurrent()); + assertEquals(0, stats.getQueryCount()); + assertEquals(0, stats.getQueryTimeInMillis()); + assertTrue(stats.getSearchLoadRate() > 0.0); + } + + public void testQueryPhase_withGroups() { + String[] groups = new String[] { "group1" }; + + ShardSearchStats listener = new ShardSearchStats(new SearchStatsSettings(ClusterSettings.createBuiltInClusterSettings())); + SearchContext sc = mock(SearchContext.class); + ShardSearchRequest req = mock(ShardSearchRequest.class); + when(sc.request()).thenReturn(req); + when(sc.groupStats()).thenReturn(Arrays.asList(groups)); + + listener.onPreQueryPhase(sc); + listener.onQueryPhase(sc, TimeUnit.MILLISECONDS.toNanos(TEN_MILLIS)); + + SearchStats searchStats = listener.stats("_all"); + SearchStats.Stats stats = listener.stats().getTotal(); + assertEquals(0, stats.getQueryCurrent()); + assertEquals(1, stats.getQueryCount()); + assertEquals(TEN_MILLIS, stats.getQueryTimeInMillis()); + assertTrue(stats.getSearchLoadRate() > 0.0); + + stats = Objects.requireNonNull(searchStats.getGroupStats()).get("group1"); + assertEquals(0, stats.getQueryCurrent()); + assertEquals(1, stats.getQueryCount()); + assertEquals(TEN_MILLIS, stats.getQueryTimeInMillis()); + assertTrue(stats.getSearchLoadRate() > 0.0); + } + + public void testQueryPhase_withGroups_SuggestOnly() { + String[] groups = new String[] { "group1" }; + + ShardSearchStats listener = new ShardSearchStats(new SearchStatsSettings(ClusterSettings.createBuiltInClusterSettings())); + SearchContext sc = mock(SearchContext.class); + ShardSearchRequest req = mock(ShardSearchRequest.class); + SearchSourceBuilder ssb = new SearchSourceBuilder().suggest(new SuggestBuilder()); + when(sc.request()).thenReturn(req); + when(sc.groupStats()).thenReturn(null); + when(req.source()).thenReturn(ssb); + when(sc.groupStats()).thenReturn(Arrays.asList(groups)); + + listener.onPreQueryPhase(sc); + listener.onQueryPhase(sc, TimeUnit.MILLISECONDS.toNanos(TEN_MILLIS)); + + SearchStats searchStats = listener.stats("_all"); + SearchStats.Stats stats = listener.stats().getTotal(); + assertEquals(0, stats.getSuggestCurrent()); + assertEquals(1, stats.getSuggestCount()); + assertEquals(TEN_MILLIS, stats.getSuggestTimeInMillis()); + assertEquals(0, stats.getQueryCurrent()); + assertEquals(0, stats.getQueryCount()); + assertEquals(0, stats.getQueryTimeInMillis()); + assertTrue(stats.getSearchLoadRate() > 0.0); + + stats = Objects.requireNonNull(searchStats.getGroupStats()).get("group1"); + assertEquals(0, stats.getSuggestCurrent()); + assertEquals(1, stats.getSuggestCount()); + assertEquals(TEN_MILLIS, stats.getSuggestTimeInMillis()); + assertEquals(0, stats.getQueryCurrent()); + assertEquals(0, stats.getQueryCount()); + assertEquals(0, stats.getQueryTimeInMillis()); + assertTrue(stats.getSearchLoadRate() > 0.0); + } + + public void testQueryPhase_SuggestOnly_Failure() { + ShardSearchStats listener = new ShardSearchStats(new SearchStatsSettings(ClusterSettings.createBuiltInClusterSettings())); + SearchContext sc = mock(SearchContext.class); + ShardSearchRequest req = mock(ShardSearchRequest.class); + SearchSourceBuilder ssb = new SearchSourceBuilder().suggest(new SuggestBuilder()); + when(sc.request()).thenReturn(req); + when(sc.groupStats()).thenReturn(null); + when(req.source()).thenReturn(ssb); + + listener.onPreQueryPhase(sc); + listener.onFailedQueryPhase(sc); + + SearchStats.Stats stats = listener.stats().getTotal(); + assertEquals(0, stats.getSuggestCurrent()); + assertEquals(0, stats.getSuggestCount()); + assertEquals(0, stats.getQueryCurrent()); + assertEquals(0, stats.getQueryCount()); + assertEquals(0, stats.getQueryFailure()); + assertEquals(0.0, stats.getSearchLoadRate(), 0); + } + + public void testQueryPhase_Failure() { + ShardSearchStats listener = new ShardSearchStats(new SearchStatsSettings(ClusterSettings.createBuiltInClusterSettings())); + SearchContext sc = mock(SearchContext.class); + ShardSearchRequest req = mock(ShardSearchRequest.class); + when(sc.request()).thenReturn(req); + when(sc.groupStats()).thenReturn(null); + + listener.onPreQueryPhase(sc); + listener.onFailedQueryPhase(sc); + + SearchStats.Stats stats = listener.stats().getTotal(); + assertEquals(0, stats.getQueryCurrent()); + assertEquals(0, stats.getQueryCount()); + assertEquals(1, stats.getQueryFailure()); + assertEquals(0.0, stats.getSearchLoadRate(), 0); + } + + public void testFetchPhase() { + ShardSearchStats listener = new ShardSearchStats(new SearchStatsSettings(ClusterSettings.createBuiltInClusterSettings())); + SearchContext sc = mock(SearchContext.class); + ShardSearchRequest req = mock(ShardSearchRequest.class); + when(sc.request()).thenReturn(req); + when(sc.groupStats()).thenReturn(null); + + listener.onPreFetchPhase(sc); + listener.onFetchPhase(sc, TimeUnit.MILLISECONDS.toNanos(TEN_MILLIS)); + + SearchStats.Stats stats = listener.stats().getTotal(); + assertEquals(0, stats.getFetchCurrent()); + assertEquals(1, stats.getFetchCount()); + assertEquals(TEN_MILLIS, stats.getFetchTimeInMillis()); + assertTrue(stats.getSearchLoadRate() > 0.0); + } + + public void testFetchPhase_withGroups() { + String[] groups = new String[] { "group1" }; + + ShardSearchStats listener = new ShardSearchStats(new SearchStatsSettings(ClusterSettings.createBuiltInClusterSettings())); + SearchContext sc = mock(SearchContext.class); + when(sc.groupStats()).thenReturn(Arrays.asList(groups)); + + listener.onPreFetchPhase(sc); + listener.onFetchPhase(sc, TimeUnit.MILLISECONDS.toNanos(TEN_MILLIS)); + + SearchStats searchStats = listener.stats("_all"); + SearchStats.Stats stats = listener.stats().getTotal(); + assertEquals(0, stats.getFetchCurrent()); + assertEquals(1, stats.getFetchCount()); + assertEquals(TEN_MILLIS, stats.getFetchTimeInMillis()); + assertTrue(stats.getSearchLoadRate() > 0.0); + + stats = Objects.requireNonNull(searchStats.getGroupStats()).get("group1"); + assertEquals(0, stats.getFetchCurrent()); + assertEquals(1, stats.getFetchCount()); + assertEquals(TEN_MILLIS, stats.getFetchTimeInMillis()); + assertTrue(stats.getSearchLoadRate() > 0.0); + } + + public void testFetchPhase_Failure() { + ShardSearchStats listener = new ShardSearchStats(new SearchStatsSettings(ClusterSettings.createBuiltInClusterSettings())); + SearchContext sc = mock(SearchContext.class); + when(sc.groupStats()).thenReturn(null); + + listener.onPreFetchPhase(sc); + listener.onFailedFetchPhase(sc); + + SearchStats.Stats stats = listener.stats().getTotal(); + assertEquals(0, stats.getFetchCurrent()); + assertEquals(0, stats.getFetchCount()); + assertEquals(1, stats.getFetchFailure()); + assertEquals(0.0, stats.getSearchLoadRate(), 0); + } + + public void testReaderContext() { + ShardSearchStats listener = new ShardSearchStats(new SearchStatsSettings(ClusterSettings.createBuiltInClusterSettings())); + ReaderContext rc = mock(ReaderContext.class); + SearchContext sc = mock(SearchContext.class); + when(sc.groupStats()).thenReturn(null); + + listener.onNewReaderContext(rc); + SearchStats stats = listener.stats(); + assertEquals(1, stats.getOpenContexts()); + + listener.onFreeReaderContext(rc); + stats = listener.stats(); + assertEquals(0, stats.getOpenContexts()); + } + + public void testScrollContext() { + ShardSearchStats listener = new ShardSearchStats(new SearchStatsSettings(ClusterSettings.createBuiltInClusterSettings())); + ReaderContext rc = mock(ReaderContext.class); + SearchContext sc = mock(SearchContext.class); + when(sc.groupStats()).thenReturn(null); + + listener.onNewScrollContext(rc); + SearchStats stats = listener.stats(); + assertEquals(1, stats.getTotal().getScrollCurrent()); + + listener.onFreeScrollContext(rc); + stats = listener.stats(); + assertEquals(0, stats.getTotal().getScrollCurrent()); + assertEquals(1, stats.getTotal().getScrollCount()); + assertTrue(stats.getTotal().getScrollTimeInMillis() > 0); + } +} From 8f36172714960bde9ab3a80af6951df3a4e0b619 Mon Sep 17 00:00:00 2001 From: Dimitris Rempapis Date: Fri, 6 Jun 2025 14:58:20 +0300 Subject: [PATCH 14/25] update after review --- .../org/elasticsearch/index/search/stats/SearchStats.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/org/elasticsearch/index/search/stats/SearchStats.java b/server/src/main/java/org/elasticsearch/index/search/stats/SearchStats.java index 7289225f8915a..e0c2f2741a0da 100644 --- a/server/src/main/java/org/elasticsearch/index/search/stats/SearchStats.java +++ b/server/src/main/java/org/elasticsearch/index/search/stats/SearchStats.java @@ -291,7 +291,7 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws builder.humanReadableField(Fields.SUGGEST_TIME_IN_MILLIS, Fields.SUGGEST_TIME, getSuggestTime()); builder.field(Fields.SUGGEST_CURRENT, suggestCurrent); - builder.field(Fields.EMW_RATE, recentSearchLoad); + builder.field(Fields.RECENT_SEARCH_LOAD, recentSearchLoad); return builder; } @@ -453,7 +453,7 @@ static final class Fields { static final String SUGGEST_TIME = "suggest_time"; static final String SUGGEST_TIME_IN_MILLIS = "suggest_time_in_millis"; static final String SUGGEST_CURRENT = "suggest_current"; - static final String EMW_RATE = "ewm_rate"; + static final String RECENT_SEARCH_LOAD = "recent_search_load"; } @Override From 8b1f24d44fbf08dd567bb10868e2d779ed3c1224 Mon Sep 17 00:00:00 2001 From: Dimitris Rempapis Date: Fri, 6 Jun 2025 15:13:34 +0300 Subject: [PATCH 15/25] add brief documentation for searchLoad --- .../java/org/elasticsearch/index/search/stats/SearchStats.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/src/main/java/org/elasticsearch/index/search/stats/SearchStats.java b/server/src/main/java/org/elasticsearch/index/search/stats/SearchStats.java index e0c2f2741a0da..1548a607d3e3e 100644 --- a/server/src/main/java/org/elasticsearch/index/search/stats/SearchStats.java +++ b/server/src/main/java/org/elasticsearch/index/search/stats/SearchStats.java @@ -49,6 +49,8 @@ public static class Stats implements Writeable, ToXContentFragment { private long queryFailure; private long fetchFailure; + //This is the ExponentiallyWeightedMovingRate for a shard, tracking execution time across different phases + // (e.g., query, fetch, etc.), favoring more recent load values by assigning them greater significance than older values. private double recentSearchLoad; private Stats() { From 7c1627939a62388b597871a52b561986dd2ff75b Mon Sep 17 00:00:00 2001 From: elasticsearchmachine Date: Fri, 6 Jun 2025 12:23:05 +0000 Subject: [PATCH 16/25] [CI] Auto commit changes from spotless --- .../java/org/elasticsearch/index/search/stats/SearchStats.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/org/elasticsearch/index/search/stats/SearchStats.java b/server/src/main/java/org/elasticsearch/index/search/stats/SearchStats.java index 1548a607d3e3e..5e5fd8a678736 100644 --- a/server/src/main/java/org/elasticsearch/index/search/stats/SearchStats.java +++ b/server/src/main/java/org/elasticsearch/index/search/stats/SearchStats.java @@ -49,7 +49,7 @@ public static class Stats implements Writeable, ToXContentFragment { private long queryFailure; private long fetchFailure; - //This is the ExponentiallyWeightedMovingRate for a shard, tracking execution time across different phases + // This is the ExponentiallyWeightedMovingRate for a shard, tracking execution time across different phases // (e.g., query, fetch, etc.), favoring more recent load values by assigning them greater significance than older values. private double recentSearchLoad; From 91ef8085b4ad5e1bdfea50debd984dc0ab5672a8 Mon Sep 17 00:00:00 2001 From: Dimitris Rempapis Date: Fri, 6 Jun 2025 16:12:44 +0300 Subject: [PATCH 17/25] Added yaml file for the search load / shard level --- .../test/indices.stats/80_search_load.yml | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/indices.stats/80_search_load.yml diff --git a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/indices.stats/80_search_load.yml b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/indices.stats/80_search_load.yml new file mode 100644 index 0000000000000..e320a6afa20da --- /dev/null +++ b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/indices.stats/80_search_load.yml @@ -0,0 +1,54 @@ +--- +"Search load is tracked at shard level": + - do: + indices.create: + index: testindex + body: + settings: + index.number_of_shards: 1 + index.number_of_replicas: 0 + mappings: + properties: + name: + type: text + description: + type: text + price: + type: double + + - do: + indices.stats: + index: "testindex" + level: shards + metric: [ search ] + + - match: { _all.total.search.recent_search_load: 0.0 } + - match: { indices.testindex.total.search.recent_search_load: 0.0 } + - match: { indices.testindex.shards.0.0.search.recent_search_load: 0.0 } + + - do: + index: + index: testindex + body: { "name": "specialty coffee", "description": "arabica coffee beans", "price": 100 } + - do: + index: + index: testindex + body: { "name": "commercial coffee", "description": "robusta coffee beans", "price": 50 } + - do: + index: + index: testindex + body: { "name": "raw coffee", "description": "colombian coffee beans", "price": 25 } + - do: + index: + index: testindex + body: { "name": "book", "description": "some book", "price": 1000 } + + - do: + indices.stats: + index: "testindex" + level: shards + metric: [ search ] + + - gte: { _all.total.search.recent_search_load: 0.0 } + - gte: { indices.testindex.total.search.recent_search_load: 0.0 } + - gte: { indices.testindex.shards.0.0.search.recent_search_load: 0.0 } From 30ad337787ca96b14522947470389e64fd9f4ef5 Mon Sep 17 00:00:00 2001 From: Dimitris Rempapis Date: Tue, 10 Jun 2025 11:41:16 +0300 Subject: [PATCH 18/25] update after review --- .../search/stats/ShardSearchStatsTests.java | 315 ++++++++---------- 1 file changed, 132 insertions(+), 183 deletions(-) diff --git a/server/src/test/java/org/elasticsearch/search/stats/ShardSearchStatsTests.java b/server/src/test/java/org/elasticsearch/search/stats/ShardSearchStatsTests.java index 9cc994946b27c..cb61fa3750993 100644 --- a/server/src/test/java/org/elasticsearch/search/stats/ShardSearchStatsTests.java +++ b/server/src/test/java/org/elasticsearch/search/stats/ShardSearchStatsTests.java @@ -9,251 +9,200 @@ package org.elasticsearch.search.stats; + +import org.elasticsearch.action.OriginalIndices; +import org.elasticsearch.action.search.SearchRequest; +import org.elasticsearch.cluster.metadata.IndexMetadata; import org.elasticsearch.common.settings.ClusterSettings; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.index.IndexSettings; +import org.elasticsearch.index.IndexVersion; +import org.elasticsearch.index.mapper.MapperMetrics; +import org.elasticsearch.index.mapper.MappingLookup; +import org.elasticsearch.index.query.SearchExecutionContext; import org.elasticsearch.index.search.stats.SearchStats; import org.elasticsearch.index.search.stats.SearchStatsSettings; import org.elasticsearch.index.search.stats.ShardSearchStats; +import org.elasticsearch.index.shard.ShardId; import org.elasticsearch.search.builder.SearchSourceBuilder; -import org.elasticsearch.search.internal.ReaderContext; +import org.elasticsearch.search.internal.AliasFilter; import org.elasticsearch.search.internal.SearchContext; import org.elasticsearch.search.internal.ShardSearchRequest; import org.elasticsearch.search.suggest.SuggestBuilder; import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.test.TestSearchContext; +import org.junit.Before; import java.util.Arrays; +import java.util.Collections; +import java.util.List; import java.util.Objects; import java.util.concurrent.TimeUnit; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - public class ShardSearchStatsTests extends ESTestCase { private static final long TEN_MILLIS = 10; + private ShardSearchStats shardSearchStatsListener; + + @Before + public void setup() { + ClusterSettings clusterSettings = ClusterSettings.createBuiltInClusterSettings(); + SearchStatsSettings searchStatsSettings = new SearchStatsSettings(clusterSettings); + this.shardSearchStatsListener = new ShardSearchStats(searchStatsSettings); + } + public void testQueryPhase() { - ShardSearchStats listener = new ShardSearchStats(new SearchStatsSettings(ClusterSettings.createBuiltInClusterSettings())); - SearchContext sc = mock(SearchContext.class); - ShardSearchRequest req = mock(ShardSearchRequest.class); - when(sc.request()).thenReturn(req); - when(sc.groupStats()).thenReturn(null); - - listener.onPreQueryPhase(sc); - listener.onQueryPhase(sc, TimeUnit.MILLISECONDS.toNanos(TEN_MILLIS)); - - SearchStats.Stats stats = listener.stats().getTotal(); - assertEquals(0, stats.getQueryCurrent()); - assertEquals(1, stats.getQueryCount()); - assertEquals(TEN_MILLIS, stats.getQueryTimeInMillis()); + SearchContext sc = createSearchContext(false); + shardSearchStatsListener.onPreQueryPhase(sc); + shardSearchStatsListener.onQueryPhase(sc, TimeUnit.MILLISECONDS.toNanos(TEN_MILLIS)); + + SearchStats.Stats stats = shardSearchStatsListener.stats().getTotal(); assertTrue(stats.getSearchLoadRate() > 0.0); } public void testQueryPhase_SuggestOnly() { - ShardSearchStats listener = new ShardSearchStats(new SearchStatsSettings(ClusterSettings.createBuiltInClusterSettings())); - SearchContext sc = mock(SearchContext.class); - ShardSearchRequest req = mock(ShardSearchRequest.class); - SearchSourceBuilder ssb = new SearchSourceBuilder().suggest(new SuggestBuilder()); - when(sc.request()).thenReturn(req); - when(sc.groupStats()).thenReturn(null); - when(req.source()).thenReturn(ssb); - - listener.onPreQueryPhase(sc); - listener.onQueryPhase(sc, TimeUnit.MILLISECONDS.toNanos(TEN_MILLIS)); - - SearchStats.Stats stats = listener.stats().getTotal(); - assertEquals(0, stats.getSuggestCurrent()); - assertEquals(1, stats.getSuggestCount()); - assertEquals(TEN_MILLIS, stats.getSuggestTimeInMillis()); - assertEquals(0, stats.getQueryCurrent()); - assertEquals(0, stats.getQueryCount()); - assertEquals(0, stats.getQueryTimeInMillis()); + SearchContext sc = createSearchContext(true); + shardSearchStatsListener.onPreQueryPhase(sc); + shardSearchStatsListener.onQueryPhase(sc, TimeUnit.MILLISECONDS.toNanos(TEN_MILLIS)); + + SearchStats.Stats stats = shardSearchStatsListener.stats().getTotal(); assertTrue(stats.getSearchLoadRate() > 0.0); } - public void testQueryPhase_withGroups() { - String[] groups = new String[] { "group1" }; - - ShardSearchStats listener = new ShardSearchStats(new SearchStatsSettings(ClusterSettings.createBuiltInClusterSettings())); - SearchContext sc = mock(SearchContext.class); - ShardSearchRequest req = mock(ShardSearchRequest.class); - when(sc.request()).thenReturn(req); - when(sc.groupStats()).thenReturn(Arrays.asList(groups)); - - listener.onPreQueryPhase(sc); - listener.onQueryPhase(sc, TimeUnit.MILLISECONDS.toNanos(TEN_MILLIS)); + public void testQueryPhase_withGroup() { + SearchContext sc = createSearchContext(false); + shardSearchStatsListener.onPreQueryPhase(sc); + shardSearchStatsListener.onQueryPhase(sc, TimeUnit.MILLISECONDS.toNanos(TEN_MILLIS)); - SearchStats searchStats = listener.stats("_all"); - SearchStats.Stats stats = listener.stats().getTotal(); - assertEquals(0, stats.getQueryCurrent()); - assertEquals(1, stats.getQueryCount()); - assertEquals(TEN_MILLIS, stats.getQueryTimeInMillis()); + SearchStats searchStats = shardSearchStatsListener.stats("_all"); + SearchStats.Stats stats = shardSearchStatsListener.stats().getTotal(); assertTrue(stats.getSearchLoadRate() > 0.0); stats = Objects.requireNonNull(searchStats.getGroupStats()).get("group1"); - assertEquals(0, stats.getQueryCurrent()); - assertEquals(1, stats.getQueryCount()); - assertEquals(TEN_MILLIS, stats.getQueryTimeInMillis()); assertTrue(stats.getSearchLoadRate() > 0.0); } - public void testQueryPhase_withGroups_SuggestOnly() { - String[] groups = new String[] { "group1" }; - - ShardSearchStats listener = new ShardSearchStats(new SearchStatsSettings(ClusterSettings.createBuiltInClusterSettings())); - SearchContext sc = mock(SearchContext.class); - ShardSearchRequest req = mock(ShardSearchRequest.class); - SearchSourceBuilder ssb = new SearchSourceBuilder().suggest(new SuggestBuilder()); - when(sc.request()).thenReturn(req); - when(sc.groupStats()).thenReturn(null); - when(req.source()).thenReturn(ssb); - when(sc.groupStats()).thenReturn(Arrays.asList(groups)); - - listener.onPreQueryPhase(sc); - listener.onQueryPhase(sc, TimeUnit.MILLISECONDS.toNanos(TEN_MILLIS)); - - SearchStats searchStats = listener.stats("_all"); - SearchStats.Stats stats = listener.stats().getTotal(); - assertEquals(0, stats.getSuggestCurrent()); - assertEquals(1, stats.getSuggestCount()); - assertEquals(TEN_MILLIS, stats.getSuggestTimeInMillis()); - assertEquals(0, stats.getQueryCurrent()); - assertEquals(0, stats.getQueryCount()); - assertEquals(0, stats.getQueryTimeInMillis()); + public void testQueryPhase_withGroup_SuggestOnly() { + SearchContext sc = createSearchContext(true); + + shardSearchStatsListener.onPreQueryPhase(sc); + shardSearchStatsListener.onQueryPhase(sc, TimeUnit.MILLISECONDS.toNanos(TEN_MILLIS)); + + SearchStats searchStats = shardSearchStatsListener.stats("_all"); + SearchStats.Stats stats = shardSearchStatsListener.stats().getTotal(); assertTrue(stats.getSearchLoadRate() > 0.0); stats = Objects.requireNonNull(searchStats.getGroupStats()).get("group1"); - assertEquals(0, stats.getSuggestCurrent()); - assertEquals(1, stats.getSuggestCount()); - assertEquals(TEN_MILLIS, stats.getSuggestTimeInMillis()); - assertEquals(0, stats.getQueryCurrent()); - assertEquals(0, stats.getQueryCount()); - assertEquals(0, stats.getQueryTimeInMillis()); assertTrue(stats.getSearchLoadRate() > 0.0); } public void testQueryPhase_SuggestOnly_Failure() { - ShardSearchStats listener = new ShardSearchStats(new SearchStatsSettings(ClusterSettings.createBuiltInClusterSettings())); - SearchContext sc = mock(SearchContext.class); - ShardSearchRequest req = mock(ShardSearchRequest.class); - SearchSourceBuilder ssb = new SearchSourceBuilder().suggest(new SuggestBuilder()); - when(sc.request()).thenReturn(req); - when(sc.groupStats()).thenReturn(null); - when(req.source()).thenReturn(ssb); - - listener.onPreQueryPhase(sc); - listener.onFailedQueryPhase(sc); - - SearchStats.Stats stats = listener.stats().getTotal(); - assertEquals(0, stats.getSuggestCurrent()); - assertEquals(0, stats.getSuggestCount()); - assertEquals(0, stats.getQueryCurrent()); - assertEquals(0, stats.getQueryCount()); - assertEquals(0, stats.getQueryFailure()); + SearchContext sc = createSearchContext(true); + shardSearchStatsListener.onPreQueryPhase(sc); + shardSearchStatsListener.onFailedQueryPhase(sc); + + SearchStats.Stats stats = shardSearchStatsListener.stats().getTotal(); assertEquals(0.0, stats.getSearchLoadRate(), 0); } public void testQueryPhase_Failure() { - ShardSearchStats listener = new ShardSearchStats(new SearchStatsSettings(ClusterSettings.createBuiltInClusterSettings())); - SearchContext sc = mock(SearchContext.class); - ShardSearchRequest req = mock(ShardSearchRequest.class); - when(sc.request()).thenReturn(req); - when(sc.groupStats()).thenReturn(null); - - listener.onPreQueryPhase(sc); - listener.onFailedQueryPhase(sc); - - SearchStats.Stats stats = listener.stats().getTotal(); - assertEquals(0, stats.getQueryCurrent()); - assertEquals(0, stats.getQueryCount()); - assertEquals(1, stats.getQueryFailure()); + SearchContext sc = createSearchContext(false); + shardSearchStatsListener.onPreQueryPhase(sc); + shardSearchStatsListener.onFailedQueryPhase(sc); + + SearchStats.Stats stats = shardSearchStatsListener.stats().getTotal(); assertEquals(0.0, stats.getSearchLoadRate(), 0); } public void testFetchPhase() { - ShardSearchStats listener = new ShardSearchStats(new SearchStatsSettings(ClusterSettings.createBuiltInClusterSettings())); - SearchContext sc = mock(SearchContext.class); - ShardSearchRequest req = mock(ShardSearchRequest.class); - when(sc.request()).thenReturn(req); - when(sc.groupStats()).thenReturn(null); - - listener.onPreFetchPhase(sc); - listener.onFetchPhase(sc, TimeUnit.MILLISECONDS.toNanos(TEN_MILLIS)); - - SearchStats.Stats stats = listener.stats().getTotal(); - assertEquals(0, stats.getFetchCurrent()); - assertEquals(1, stats.getFetchCount()); - assertEquals(TEN_MILLIS, stats.getFetchTimeInMillis()); + SearchContext sc = createSearchContext(false); + shardSearchStatsListener.onPreFetchPhase(sc); + shardSearchStatsListener.onFetchPhase(sc, TimeUnit.MILLISECONDS.toNanos(TEN_MILLIS)); + + SearchStats.Stats stats = shardSearchStatsListener.stats().getTotal(); assertTrue(stats.getSearchLoadRate() > 0.0); } - public void testFetchPhase_withGroups() { - String[] groups = new String[] { "group1" }; - - ShardSearchStats listener = new ShardSearchStats(new SearchStatsSettings(ClusterSettings.createBuiltInClusterSettings())); - SearchContext sc = mock(SearchContext.class); - when(sc.groupStats()).thenReturn(Arrays.asList(groups)); - - listener.onPreFetchPhase(sc); - listener.onFetchPhase(sc, TimeUnit.MILLISECONDS.toNanos(TEN_MILLIS)); + public void testFetchPhase_withGroup() { + SearchContext sc = createSearchContext(false); + shardSearchStatsListener.onPreFetchPhase(sc); + shardSearchStatsListener.onFetchPhase(sc, TimeUnit.MILLISECONDS.toNanos(TEN_MILLIS)); - SearchStats searchStats = listener.stats("_all"); - SearchStats.Stats stats = listener.stats().getTotal(); - assertEquals(0, stats.getFetchCurrent()); - assertEquals(1, stats.getFetchCount()); - assertEquals(TEN_MILLIS, stats.getFetchTimeInMillis()); + SearchStats searchStats = shardSearchStatsListener.stats("_all"); + SearchStats.Stats stats = shardSearchStatsListener.stats().getTotal(); assertTrue(stats.getSearchLoadRate() > 0.0); stats = Objects.requireNonNull(searchStats.getGroupStats()).get("group1"); - assertEquals(0, stats.getFetchCurrent()); - assertEquals(1, stats.getFetchCount()); - assertEquals(TEN_MILLIS, stats.getFetchTimeInMillis()); assertTrue(stats.getSearchLoadRate() > 0.0); } public void testFetchPhase_Failure() { - ShardSearchStats listener = new ShardSearchStats(new SearchStatsSettings(ClusterSettings.createBuiltInClusterSettings())); - SearchContext sc = mock(SearchContext.class); - when(sc.groupStats()).thenReturn(null); + SearchContext sc = createSearchContext(false); + shardSearchStatsListener.onPreFetchPhase(sc); + shardSearchStatsListener.onFailedFetchPhase(sc); - listener.onPreFetchPhase(sc); - listener.onFailedFetchPhase(sc); - - SearchStats.Stats stats = listener.stats().getTotal(); - assertEquals(0, stats.getFetchCurrent()); - assertEquals(0, stats.getFetchCount()); - assertEquals(1, stats.getFetchFailure()); + SearchStats.Stats stats = shardSearchStatsListener.stats().getTotal(); assertEquals(0.0, stats.getSearchLoadRate(), 0); } - public void testReaderContext() { - ShardSearchStats listener = new ShardSearchStats(new SearchStatsSettings(ClusterSettings.createBuiltInClusterSettings())); - ReaderContext rc = mock(ReaderContext.class); - SearchContext sc = mock(SearchContext.class); - when(sc.groupStats()).thenReturn(null); - - listener.onNewReaderContext(rc); - SearchStats stats = listener.stats(); - assertEquals(1, stats.getOpenContexts()); - - listener.onFreeReaderContext(rc); - stats = listener.stats(); - assertEquals(0, stats.getOpenContexts()); - } - - public void testScrollContext() { - ShardSearchStats listener = new ShardSearchStats(new SearchStatsSettings(ClusterSettings.createBuiltInClusterSettings())); - ReaderContext rc = mock(ReaderContext.class); - SearchContext sc = mock(SearchContext.class); - when(sc.groupStats()).thenReturn(null); - - listener.onNewScrollContext(rc); - SearchStats stats = listener.stats(); - assertEquals(1, stats.getTotal().getScrollCurrent()); - - listener.onFreeScrollContext(rc); - stats = listener.stats(); - assertEquals(0, stats.getTotal().getScrollCurrent()); - assertEquals(1, stats.getTotal().getScrollCount()); - assertTrue(stats.getTotal().getScrollTimeInMillis() > 0); + private static SearchContext createSearchContext(boolean suggested) { + IndexSettings indexSettings = new IndexSettings( + IndexMetadata.builder("index") + .settings(Settings.builder().put(IndexMetadata.SETTING_VERSION_CREATED, IndexVersion.current())) + .numberOfShards(1) + .numberOfReplicas(0) + .creationDate(System.currentTimeMillis()) + .build(), + Settings.EMPTY + ); + + SearchExecutionContext searchExecutionContext = new SearchExecutionContext( + 0, + 0, + indexSettings, + null, + null, + null, + MappingLookup.EMPTY, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + Collections.emptyMap(), + null, + MapperMetrics.NOOP + ); + return new TestSearchContext(searchExecutionContext) { + private final SearchRequest searchquest = new SearchRequest().allowPartialSearchResults(true); + private final ShardSearchRequest request = new ShardSearchRequest( + OriginalIndices.NONE, + suggested ? searchquest.source(new SearchSourceBuilder().suggest(new SuggestBuilder())) : searchquest, + new ShardId("index", "indexUUID", 0), + 0, + 1, + AliasFilter.EMPTY, + 1f, + 0L, + null + ); + + @Override + public ShardSearchRequest request() { + return request; + } + + @Override + public List groupStats() { + return Arrays.asList("group1"); + } + }; } } From c7b322ac2c756581284fccca56d9dc14dbcbbc76 Mon Sep 17 00:00:00 2001 From: Dimitris Rempapis Date: Tue, 10 Jun 2025 11:45:35 +0300 Subject: [PATCH 19/25] Update server/src/main/java/org/elasticsearch/index/search/stats/SearchStats.java Update after review Co-authored-by: Andrei Dan --- .../org/elasticsearch/index/search/stats/SearchStats.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/org/elasticsearch/index/search/stats/SearchStats.java b/server/src/main/java/org/elasticsearch/index/search/stats/SearchStats.java index 5e5fd8a678736..75305a16238e8 100644 --- a/server/src/main/java/org/elasticsearch/index/search/stats/SearchStats.java +++ b/server/src/main/java/org/elasticsearch/index/search/stats/SearchStats.java @@ -49,8 +49,8 @@ public static class Stats implements Writeable, ToXContentFragment { private long queryFailure; private long fetchFailure; - // This is the ExponentiallyWeightedMovingRate for a shard, tracking execution time across different phases - // (e.g., query, fetch, etc.), favoring more recent load values by assigning them greater significance than older values. + // This tracks the search execution time across different phases (e.g., query, fetch, etc.), favouring more recent + // values by assigning them greater significance than older values. private double recentSearchLoad; private Stats() { From 877c62eb2f1bd56a26c10403be35b4183316644c Mon Sep 17 00:00:00 2001 From: Dimitris Rempapis Date: Tue, 10 Jun 2025 11:50:44 +0300 Subject: [PATCH 20/25] update after review --- .../stats/SearchStatsSettingsTests.java | 56 ------------------- 1 file changed, 56 deletions(-) delete mode 100644 server/src/test/java/org/elasticsearch/search/stats/SearchStatsSettingsTests.java diff --git a/server/src/test/java/org/elasticsearch/search/stats/SearchStatsSettingsTests.java b/server/src/test/java/org/elasticsearch/search/stats/SearchStatsSettingsTests.java deleted file mode 100644 index 6084a3be862bb..0000000000000 --- a/server/src/test/java/org/elasticsearch/search/stats/SearchStatsSettingsTests.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -package org.elasticsearch.search.stats; - -import org.elasticsearch.common.settings.ClusterSettings; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.core.TimeValue; -import org.elasticsearch.index.search.stats.SearchStatsSettings; -import org.elasticsearch.test.ESTestCase; - -import static org.hamcrest.Matchers.equalTo; - -/** - * Unit tests for {@link SearchStatsSettings}. - */ -public class SearchStatsSettingsTests extends ESTestCase { - - /** - * Test the default value of the recent read load half-life setting. - */ - public void testRecentReadLoadHalfLife_defaultValue() { - SearchStatsSettings settings = new SearchStatsSettings(ClusterSettings.createBuiltInClusterSettings()); - assertThat(settings.getRecentReadLoadHalfLifeForNewShards(), equalTo(SearchStatsSettings.RECENT_READ_LOAD_HALF_LIFE_DEFAULT)); - } - - /** - * Test the initial value of the recent read load half-life setting. - */ - public void testRecentReadLoadHalfLife_initialValue() { - SearchStatsSettings settings = new SearchStatsSettings( - ClusterSettings.createBuiltInClusterSettings( - Settings.builder().put(SearchStatsSettings.RECENT_READ_LOAD_HALF_LIFE_SETTING.getKey(), "2h").build() - ) - ); - assertThat(settings.getRecentReadLoadHalfLifeForNewShards(), equalTo(TimeValue.timeValueHours(2))); - } - - /** - * Test updating the recent read load half-life setting. - */ - public void testRecentReadLoadHalfLife_updateValue() { - ClusterSettings clusterSettings = ClusterSettings.createBuiltInClusterSettings(); - SearchStatsSettings settings = new SearchStatsSettings(clusterSettings); - clusterSettings.applySettings( - Settings.builder().put(SearchStatsSettings.RECENT_READ_LOAD_HALF_LIFE_SETTING.getKey(), "90m").build() - ); - assertThat(settings.getRecentReadLoadHalfLifeForNewShards(), equalTo(TimeValue.timeValueMinutes(90))); - } -} From 011e6549f5111d430789f91e31898fb20fcd1ab9 Mon Sep 17 00:00:00 2001 From: elasticsearchmachine Date: Tue, 10 Jun 2025 09:00:29 +0000 Subject: [PATCH 21/25] [CI] Auto commit changes from spotless --- .../java/org/elasticsearch/index/search/stats/SearchStats.java | 2 +- .../org/elasticsearch/search/stats/ShardSearchStatsTests.java | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/server/src/main/java/org/elasticsearch/index/search/stats/SearchStats.java b/server/src/main/java/org/elasticsearch/index/search/stats/SearchStats.java index 75305a16238e8..4d6247796af59 100644 --- a/server/src/main/java/org/elasticsearch/index/search/stats/SearchStats.java +++ b/server/src/main/java/org/elasticsearch/index/search/stats/SearchStats.java @@ -49,7 +49,7 @@ public static class Stats implements Writeable, ToXContentFragment { private long queryFailure; private long fetchFailure; - // This tracks the search execution time across different phases (e.g., query, fetch, etc.), favouring more recent + // This tracks the search execution time across different phases (e.g., query, fetch, etc.), favouring more recent // values by assigning them greater significance than older values. private double recentSearchLoad; diff --git a/server/src/test/java/org/elasticsearch/search/stats/ShardSearchStatsTests.java b/server/src/test/java/org/elasticsearch/search/stats/ShardSearchStatsTests.java index cb61fa3750993..82bd23b7b54d9 100644 --- a/server/src/test/java/org/elasticsearch/search/stats/ShardSearchStatsTests.java +++ b/server/src/test/java/org/elasticsearch/search/stats/ShardSearchStatsTests.java @@ -9,7 +9,6 @@ package org.elasticsearch.search.stats; - import org.elasticsearch.action.OriginalIndices; import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.cluster.metadata.IndexMetadata; From 17b561171297a93bd844d555b9df53a4b8c014ca Mon Sep 17 00:00:00 2001 From: Dimitris Rempapis Date: Tue, 10 Jun 2025 12:33:19 +0300 Subject: [PATCH 22/25] update after review --- .../test/indices.stats/80_search_load.yml | 39 ++++++++++--------- .../index/mapper/MapperFeatures.java | 4 +- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/indices.stats/80_search_load.yml b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/indices.stats/80_search_load.yml index e320a6afa20da..8c1cc8709c12b 100644 --- a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/indices.stats/80_search_load.yml +++ b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/indices.stats/80_search_load.yml @@ -1,8 +1,13 @@ --- +setup: + - requires: + cluster_features: ["mapper.search_load_per_shard"] + reason: Shard search load stats were introduced in 9.1 +--- "Search load is tracked at shard level": - do: indices.create: - index: testindex + index: index body: settings: index.number_of_shards: 1 @@ -18,37 +23,33 @@ - do: indices.stats: - index: "testindex" + index: "index" level: shards metric: [ search ] - match: { _all.total.search.recent_search_load: 0.0 } - - match: { indices.testindex.total.search.recent_search_load: 0.0 } - - match: { indices.testindex.shards.0.0.search.recent_search_load: 0.0 } + - match: { indices.index.total.search.recent_search_load: 0.0 } + - match: { indices.index.shards.0.0.search.recent_search_load: 0.0 } - do: index: - index: testindex + index: index body: { "name": "specialty coffee", "description": "arabica coffee beans", "price": 100 } + - do: - index: - index: testindex - body: { "name": "commercial coffee", "description": "robusta coffee beans", "price": 50 } - - do: - index: - index: testindex - body: { "name": "raw coffee", "description": "colombian coffee beans", "price": 25 } - - do: - index: - index: testindex - body: { "name": "book", "description": "some book", "price": 1000 } + search: + index: index + body: + query: + match: { name: "specialty coffee" } + size: 1 - do: indices.stats: - index: "testindex" + index: "index" level: shards metric: [ search ] - gte: { _all.total.search.recent_search_load: 0.0 } - - gte: { indices.testindex.total.search.recent_search_load: 0.0 } - - gte: { indices.testindex.shards.0.0.search.recent_search_load: 0.0 } + - gte: { indices.index.total.search.recent_search_load: 0.0 } + - gte: { indices.index.shards.0.0.search.recent_search_load: 0.0 } diff --git a/server/src/main/java/org/elasticsearch/index/mapper/MapperFeatures.java b/server/src/main/java/org/elasticsearch/index/mapper/MapperFeatures.java index 70b6790c4dc1d..423432cd870a2 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/MapperFeatures.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/MapperFeatures.java @@ -42,6 +42,7 @@ public class MapperFeatures implements FeatureSpecification { ); static final NodeFeature NPE_ON_DIMS_UPDATE_FIX = new NodeFeature("mapper.npe_on_dims_update_fix"); static final NodeFeature IVF_FORMAT_CLUSTER_FEATURE = new NodeFeature("mapper.ivf_format_cluster_feature"); + public static final NodeFeature SEARCH_LOAD_PER_SHARD = new NodeFeature("mapper.search_load_per_shard"); @Override public Set getTestFeatures() { @@ -70,7 +71,8 @@ public Set getTestFeatures() { NPE_ON_DIMS_UPDATE_FIX, RESCORE_ZERO_VECTOR_QUANTIZED_VECTOR_MAPPING, USE_DEFAULT_OVERSAMPLE_VALUE_FOR_BBQ, - IVF_FORMAT_CLUSTER_FEATURE + IVF_FORMAT_CLUSTER_FEATURE, + SEARCH_LOAD_PER_SHARD ); } } From 34d7b6dc16b993abe6cae0c70dce80f593eba6dd Mon Sep 17 00:00:00 2001 From: Dimitris Rempapis Date: Tue, 10 Jun 2025 15:45:49 +0300 Subject: [PATCH 23/25] Update after review --- .../search/stats/ShardSearchStatsTests.java | 103 ++++++++++-------- 1 file changed, 56 insertions(+), 47 deletions(-) diff --git a/server/src/test/java/org/elasticsearch/search/stats/ShardSearchStatsTests.java b/server/src/test/java/org/elasticsearch/search/stats/ShardSearchStatsTests.java index 82bd23b7b54d9..b7283811572c8 100644 --- a/server/src/test/java/org/elasticsearch/search/stats/ShardSearchStatsTests.java +++ b/server/src/test/java/org/elasticsearch/search/stats/ShardSearchStatsTests.java @@ -52,79 +52,86 @@ public void setup() { } public void testQueryPhase() { - SearchContext sc = createSearchContext(false); - shardSearchStatsListener.onPreQueryPhase(sc); - shardSearchStatsListener.onQueryPhase(sc, TimeUnit.MILLISECONDS.toNanos(TEN_MILLIS)); + try (SearchContext sc = createSearchContext(false)) { + shardSearchStatsListener.onPreQueryPhase(sc); + shardSearchStatsListener.onQueryPhase(sc, TimeUnit.MILLISECONDS.toNanos(TEN_MILLIS)); - SearchStats.Stats stats = shardSearchStatsListener.stats().getTotal(); - assertTrue(stats.getSearchLoadRate() > 0.0); + SearchStats.Stats stats = shardSearchStatsListener.stats().getTotal(); + assertTrue(stats.getSearchLoadRate() > 0.0); + } } public void testQueryPhase_SuggestOnly() { - SearchContext sc = createSearchContext(true); - shardSearchStatsListener.onPreQueryPhase(sc); - shardSearchStatsListener.onQueryPhase(sc, TimeUnit.MILLISECONDS.toNanos(TEN_MILLIS)); + try (SearchContext sc = createSearchContext(true)) { + shardSearchStatsListener.onPreQueryPhase(sc); + shardSearchStatsListener.onQueryPhase(sc, TimeUnit.MILLISECONDS.toNanos(TEN_MILLIS)); - SearchStats.Stats stats = shardSearchStatsListener.stats().getTotal(); - assertTrue(stats.getSearchLoadRate() > 0.0); + SearchStats.Stats stats = shardSearchStatsListener.stats().getTotal(); + assertTrue(stats.getSearchLoadRate() > 0.0); + } } public void testQueryPhase_withGroup() { - SearchContext sc = createSearchContext(false); - shardSearchStatsListener.onPreQueryPhase(sc); - shardSearchStatsListener.onQueryPhase(sc, TimeUnit.MILLISECONDS.toNanos(TEN_MILLIS)); + try (SearchContext sc = createSearchContext(false)) { + shardSearchStatsListener.onPreQueryPhase(sc); + shardSearchStatsListener.onQueryPhase(sc, TimeUnit.MILLISECONDS.toNanos(TEN_MILLIS)); - SearchStats searchStats = shardSearchStatsListener.stats("_all"); - SearchStats.Stats stats = shardSearchStatsListener.stats().getTotal(); - assertTrue(stats.getSearchLoadRate() > 0.0); + SearchStats searchStats = shardSearchStatsListener.stats("_all"); + SearchStats.Stats stats = shardSearchStatsListener.stats().getTotal(); + assertTrue(stats.getSearchLoadRate() > 0.0); - stats = Objects.requireNonNull(searchStats.getGroupStats()).get("group1"); - assertTrue(stats.getSearchLoadRate() > 0.0); + stats = Objects.requireNonNull(searchStats.getGroupStats()).get("group1"); + assertTrue(stats.getSearchLoadRate() > 0.0); + } } public void testQueryPhase_withGroup_SuggestOnly() { - SearchContext sc = createSearchContext(true); + try (SearchContext sc = createSearchContext(true)) { - shardSearchStatsListener.onPreQueryPhase(sc); - shardSearchStatsListener.onQueryPhase(sc, TimeUnit.MILLISECONDS.toNanos(TEN_MILLIS)); + shardSearchStatsListener.onPreQueryPhase(sc); + shardSearchStatsListener.onQueryPhase(sc, TimeUnit.MILLISECONDS.toNanos(TEN_MILLIS)); - SearchStats searchStats = shardSearchStatsListener.stats("_all"); - SearchStats.Stats stats = shardSearchStatsListener.stats().getTotal(); - assertTrue(stats.getSearchLoadRate() > 0.0); + SearchStats searchStats = shardSearchStatsListener.stats("_all"); + SearchStats.Stats stats = shardSearchStatsListener.stats().getTotal(); + assertTrue(stats.getSearchLoadRate() > 0.0); - stats = Objects.requireNonNull(searchStats.getGroupStats()).get("group1"); - assertTrue(stats.getSearchLoadRate() > 0.0); + stats = Objects.requireNonNull(searchStats.getGroupStats()).get("group1"); + assertTrue(stats.getSearchLoadRate() > 0.0); + } } public void testQueryPhase_SuggestOnly_Failure() { - SearchContext sc = createSearchContext(true); - shardSearchStatsListener.onPreQueryPhase(sc); - shardSearchStatsListener.onFailedQueryPhase(sc); + try (SearchContext sc = createSearchContext(true)) { + shardSearchStatsListener.onPreQueryPhase(sc); + shardSearchStatsListener.onFailedQueryPhase(sc); - SearchStats.Stats stats = shardSearchStatsListener.stats().getTotal(); - assertEquals(0.0, stats.getSearchLoadRate(), 0); + SearchStats.Stats stats = shardSearchStatsListener.stats().getTotal(); + assertEquals(0.0, stats.getSearchLoadRate(), 0); + } } public void testQueryPhase_Failure() { - SearchContext sc = createSearchContext(false); - shardSearchStatsListener.onPreQueryPhase(sc); - shardSearchStatsListener.onFailedQueryPhase(sc); + try (SearchContext sc = createSearchContext(false)) { + shardSearchStatsListener.onPreQueryPhase(sc); + shardSearchStatsListener.onFailedQueryPhase(sc); - SearchStats.Stats stats = shardSearchStatsListener.stats().getTotal(); - assertEquals(0.0, stats.getSearchLoadRate(), 0); + SearchStats.Stats stats = shardSearchStatsListener.stats().getTotal(); + assertEquals(0.0, stats.getSearchLoadRate(), 0); + } } public void testFetchPhase() { - SearchContext sc = createSearchContext(false); - shardSearchStatsListener.onPreFetchPhase(sc); - shardSearchStatsListener.onFetchPhase(sc, TimeUnit.MILLISECONDS.toNanos(TEN_MILLIS)); + try (SearchContext sc = createSearchContext(false)) { + shardSearchStatsListener.onPreFetchPhase(sc); + shardSearchStatsListener.onFetchPhase(sc, TimeUnit.MILLISECONDS.toNanos(TEN_MILLIS)); - SearchStats.Stats stats = shardSearchStatsListener.stats().getTotal(); - assertTrue(stats.getSearchLoadRate() > 0.0); + SearchStats.Stats stats = shardSearchStatsListener.stats().getTotal(); + assertTrue(stats.getSearchLoadRate() > 0.0); + } } public void testFetchPhase_withGroup() { - SearchContext sc = createSearchContext(false); + try (SearchContext sc = createSearchContext(false)) { shardSearchStatsListener.onPreFetchPhase(sc); shardSearchStatsListener.onFetchPhase(sc, TimeUnit.MILLISECONDS.toNanos(TEN_MILLIS)); @@ -134,15 +141,17 @@ public void testFetchPhase_withGroup() { stats = Objects.requireNonNull(searchStats.getGroupStats()).get("group1"); assertTrue(stats.getSearchLoadRate() > 0.0); + } } public void testFetchPhase_Failure() { - SearchContext sc = createSearchContext(false); - shardSearchStatsListener.onPreFetchPhase(sc); - shardSearchStatsListener.onFailedFetchPhase(sc); + try (SearchContext sc = createSearchContext(false)) { + shardSearchStatsListener.onPreFetchPhase(sc); + shardSearchStatsListener.onFailedFetchPhase(sc); - SearchStats.Stats stats = shardSearchStatsListener.stats().getTotal(); - assertEquals(0.0, stats.getSearchLoadRate(), 0); + SearchStats.Stats stats = shardSearchStatsListener.stats().getTotal(); + assertEquals(0.0, stats.getSearchLoadRate(), 0); + } } private static SearchContext createSearchContext(boolean suggested) { From e4e82f7f602577faf42b51a0510945dfdd4998b9 Mon Sep 17 00:00:00 2001 From: elasticsearchmachine Date: Tue, 10 Jun 2025 12:55:56 +0000 Subject: [PATCH 24/25] [CI] Auto commit changes from spotless --- .../search/stats/ShardSearchStatsTests.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/server/src/test/java/org/elasticsearch/search/stats/ShardSearchStatsTests.java b/server/src/test/java/org/elasticsearch/search/stats/ShardSearchStatsTests.java index b7283811572c8..b1a33893f26c8 100644 --- a/server/src/test/java/org/elasticsearch/search/stats/ShardSearchStatsTests.java +++ b/server/src/test/java/org/elasticsearch/search/stats/ShardSearchStatsTests.java @@ -132,15 +132,15 @@ public void testFetchPhase() { public void testFetchPhase_withGroup() { try (SearchContext sc = createSearchContext(false)) { - shardSearchStatsListener.onPreFetchPhase(sc); - shardSearchStatsListener.onFetchPhase(sc, TimeUnit.MILLISECONDS.toNanos(TEN_MILLIS)); + shardSearchStatsListener.onPreFetchPhase(sc); + shardSearchStatsListener.onFetchPhase(sc, TimeUnit.MILLISECONDS.toNanos(TEN_MILLIS)); - SearchStats searchStats = shardSearchStatsListener.stats("_all"); - SearchStats.Stats stats = shardSearchStatsListener.stats().getTotal(); - assertTrue(stats.getSearchLoadRate() > 0.0); + SearchStats searchStats = shardSearchStatsListener.stats("_all"); + SearchStats.Stats stats = shardSearchStatsListener.stats().getTotal(); + assertTrue(stats.getSearchLoadRate() > 0.0); - stats = Objects.requireNonNull(searchStats.getGroupStats()).get("group1"); - assertTrue(stats.getSearchLoadRate() > 0.0); + stats = Objects.requireNonNull(searchStats.getGroupStats()).get("group1"); + assertTrue(stats.getSearchLoadRate() > 0.0); } } From cb4899139a51143a4ba740c0ebe238b1b4a2a733 Mon Sep 17 00:00:00 2001 From: Dimitris Rempapis Date: Wed, 11 Jun 2025 09:49:53 +0300 Subject: [PATCH 25/25] Update for serverless checks --- .../rest-api-spec/test/indices.stats/80_search_load.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/indices.stats/80_search_load.yml b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/indices.stats/80_search_load.yml index 8c1cc8709c12b..a8c8fe46c9f34 100644 --- a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/indices.stats/80_search_load.yml +++ b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/indices.stats/80_search_load.yml @@ -9,9 +9,6 @@ setup: indices.create: index: index body: - settings: - index.number_of_shards: 1 - index.number_of_replicas: 0 mappings: properties: name: