Skip to content

Commit cabff09

Browse files
drempapisbenchaplin
authored andcommitted
Enable Shard-Level Search-load rate metric (elastic#128660)
Introduces a new search load metric to the stats infrastructure, measured and tracked on a per-shard basis. The metric represents the Exponentially Weighted Moving Rate (EWMR) of search operations, calculated using the "took" time from each completed search phase.
1 parent 24ad4d1 commit cabff09

File tree

22 files changed

+467
-31
lines changed

22 files changed

+467
-31
lines changed
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
---
2+
setup:
3+
- requires:
4+
cluster_features: ["mapper.search_load_per_shard"]
5+
reason: Shard search load stats were introduced in 9.1
6+
---
7+
"Search load is tracked at shard level":
8+
- do:
9+
indices.create:
10+
index: index
11+
body:
12+
mappings:
13+
properties:
14+
name:
15+
type: text
16+
description:
17+
type: text
18+
price:
19+
type: double
20+
21+
- do:
22+
indices.stats:
23+
index: "index"
24+
level: shards
25+
metric: [ search ]
26+
27+
- match: { _all.total.search.recent_search_load: 0.0 }
28+
- match: { indices.index.total.search.recent_search_load: 0.0 }
29+
- match: { indices.index.shards.0.0.search.recent_search_load: 0.0 }
30+
31+
- do:
32+
index:
33+
index: index
34+
body: { "name": "specialty coffee", "description": "arabica coffee beans", "price": 100 }
35+
36+
- do:
37+
search:
38+
index: index
39+
body:
40+
query:
41+
match: { name: "specialty coffee" }
42+
size: 1
43+
44+
- do:
45+
indices.stats:
46+
index: "index"
47+
level: shards
48+
metric: [ search ]
49+
50+
- gte: { _all.total.search.recent_search_load: 0.0 }
51+
- gte: { indices.index.total.search.recent_search_load: 0.0 }
52+
- gte: { indices.index.shards.0.0.search.recent_search_load: 0.0 }

server/src/internalClusterTest/java/org/elasticsearch/index/shard/IndexShardIT.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
import org.elasticsearch.index.flush.FlushStats;
5454
import org.elasticsearch.index.mapper.MapperMetrics;
5555
import org.elasticsearch.index.mapper.SourceToParse;
56+
import org.elasticsearch.index.search.stats.SearchStatsSettings;
5657
import org.elasticsearch.index.seqno.RetentionLeaseSyncer;
5758
import org.elasticsearch.index.seqno.SequenceNumbers;
5859
import org.elasticsearch.index.translog.TestTranslog;
@@ -638,7 +639,8 @@ public static final IndexShard newIndexShard(
638639
System::nanoTime,
639640
null,
640641
MapperMetrics.NOOP,
641-
new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings())
642+
new IndexingStatsSettings(ClusterSettings.createBuiltInClusterSettings()),
643+
new SearchStatsSettings(ClusterSettings.createBuiltInClusterSettings())
642644
);
643645
}
644646

server/src/main/java/org/elasticsearch/TransportVersions.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,7 @@ static TransportVersion def(int id) {
292292
public static final TransportVersion SEARCH_SOURCE_EXCLUDE_VECTORS_PARAM = def(9_092_0_00);
293293
public static final TransportVersion SNAPSHOT_INDEX_SHARD_STATUS_MISSING_STATS = def(9_093_0_00);
294294
public static final TransportVersion ML_INFERENCE_ELASTIC_RERANK = def(9_094_0_00);
295+
public static final TransportVersion SEARCH_LOAD_PER_INDEX_STATS = def(9_095_0_00);
295296

296297
/*
297298
* STOP! READ THIS FIRST! No, really,

server/src/main/java/org/elasticsearch/common/settings/ClusterSettings.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@
9090
import org.elasticsearch.index.MergePolicyConfig;
9191
import org.elasticsearch.index.engine.ThreadPoolMergeExecutorService;
9292
import org.elasticsearch.index.engine.ThreadPoolMergeScheduler;
93+
import org.elasticsearch.index.search.stats.SearchStatsSettings;
9394
import org.elasticsearch.index.shard.IndexingStatsSettings;
9495
import org.elasticsearch.indices.IndexingMemoryController;
9596
import org.elasticsearch.indices.IndicesQueryCache;
@@ -640,6 +641,7 @@ public void apply(Settings value, Settings current, Settings previous) {
640641
ShardsAvailabilityHealthIndicatorService.REPLICA_UNASSIGNED_BUFFER_TIME,
641642
DataStreamFailureStoreSettings.DATA_STREAM_FAILURE_STORED_ENABLED_SETTING,
642643
IndexingStatsSettings.RECENT_WRITE_LOAD_HALF_LIFE_SETTING,
644+
SearchStatsSettings.RECENT_READ_LOAD_HALF_LIFE_SETTING,
643645
TransportGetAllocationStatsAction.CACHE_TTL_SETTING
644646
);
645647
}

server/src/main/java/org/elasticsearch/index/IndexModule.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
import org.elasticsearch.index.mapper.MapperMetrics;
4949
import org.elasticsearch.index.mapper.MapperRegistry;
5050
import org.elasticsearch.index.mapper.MapperService;
51+
import org.elasticsearch.index.search.stats.SearchStatsSettings;
5152
import org.elasticsearch.index.shard.IndexEventListener;
5253
import org.elasticsearch.index.shard.IndexingOperationListener;
5354
import org.elasticsearch.index.shard.IndexingStatsSettings;
@@ -179,6 +180,7 @@ public interface DirectoryWrapper {
179180
private final SetOnce<Engine.IndexCommitListener> indexCommitListener = new SetOnce<>();
180181
private final MapperMetrics mapperMetrics;
181182
private final IndexingStatsSettings indexingStatsSettings;
183+
private final SearchStatsSettings searchStatsSettings;
182184

183185
/**
184186
* 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(
200202
final SlowLogFieldProvider slowLogFieldProvider,
201203
final MapperMetrics mapperMetrics,
202204
final List<SearchOperationListener> searchOperationListeners,
203-
final IndexingStatsSettings indexingStatsSettings
205+
final IndexingStatsSettings indexingStatsSettings,
206+
final SearchStatsSettings searchStatsSettings
204207
) {
205208
this.indexSettings = indexSettings;
206209
this.analysisRegistry = analysisRegistry;
@@ -216,6 +219,7 @@ public IndexModule(
216219
this.recoveryStateFactories = recoveryStateFactories;
217220
this.mapperMetrics = mapperMetrics;
218221
this.indexingStatsSettings = indexingStatsSettings;
222+
this.searchStatsSettings = searchStatsSettings;
219223
}
220224

221225
/**
@@ -552,7 +556,8 @@ public IndexService newIndexService(
552556
indexCommitListener.get(),
553557
mapperMetrics,
554558
queryRewriteInterceptor,
555-
indexingStatsSettings
559+
indexingStatsSettings,
560+
searchStatsSettings
556561
);
557562
success = true;
558563
return indexService;

server/src/main/java/org/elasticsearch/index/IndexService.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
import org.elasticsearch.index.query.QueryRewriteContext;
6868
import org.elasticsearch.index.query.SearchExecutionContext;
6969
import org.elasticsearch.index.query.SearchIndexNameMatcher;
70+
import org.elasticsearch.index.search.stats.SearchStatsSettings;
7071
import org.elasticsearch.index.seqno.RetentionLeaseSyncer;
7172
import org.elasticsearch.index.shard.GlobalCheckpointSyncer;
7273
import org.elasticsearch.index.shard.IndexEventListener;
@@ -170,6 +171,7 @@ public class IndexService extends AbstractIndexComponent implements IndicesClust
170171
private final MapperMetrics mapperMetrics;
171172
private final QueryRewriteInterceptor queryRewriteInterceptor;
172173
private final IndexingStatsSettings indexingStatsSettings;
174+
private final SearchStatsSettings searchStatsSettings;
173175

174176
@SuppressWarnings("this-escape")
175177
public IndexService(
@@ -207,7 +209,8 @@ public IndexService(
207209
Engine.IndexCommitListener indexCommitListener,
208210
MapperMetrics mapperMetrics,
209211
QueryRewriteInterceptor queryRewriteInterceptor,
210-
IndexingStatsSettings indexingStatsSettings
212+
IndexingStatsSettings indexingStatsSettings,
213+
SearchStatsSettings searchStatsSettings
211214
) {
212215
super(indexSettings);
213216
assert indexCreationContext != IndexCreationContext.RELOAD_ANALYZERS
@@ -293,6 +296,7 @@ public IndexService(
293296
this.retentionLeaseSyncTask = new AsyncRetentionLeaseSyncTask(this);
294297
}
295298
this.indexingStatsSettings = indexingStatsSettings;
299+
this.searchStatsSettings = searchStatsSettings;
296300
updateFsyncTaskIfNecessary();
297301
}
298302

@@ -583,7 +587,8 @@ public synchronized IndexShard createShard(
583587
System::nanoTime,
584588
indexCommitListener,
585589
mapperMetrics,
586-
indexingStatsSettings
590+
indexingStatsSettings,
591+
searchStatsSettings
587592
);
588593
eventListener.indexShardStateChanged(indexShard, null, indexShard.state(), "shard created");
589594
eventListener.afterIndexShardCreated(indexShard);

server/src/main/java/org/elasticsearch/index/mapper/MapperFeatures.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ public class MapperFeatures implements FeatureSpecification {
4343
static final NodeFeature NPE_ON_DIMS_UPDATE_FIX = new NodeFeature("mapper.npe_on_dims_update_fix");
4444
static final NodeFeature IVF_FORMAT_CLUSTER_FEATURE = new NodeFeature("mapper.ivf_format_cluster_feature");
4545
static final NodeFeature IVF_NESTED_SUPPORT = new NodeFeature("mapper.ivf_nested_support");
46+
static final NodeFeature SEARCH_LOAD_PER_SHARD = new NodeFeature("mapper.search_load_per_shard");
4647

4748
@Override
4849
public Set<NodeFeature> getTestFeatures() {
@@ -72,7 +73,8 @@ public Set<NodeFeature> getTestFeatures() {
7273
RESCORE_ZERO_VECTOR_QUANTIZED_VECTOR_MAPPING,
7374
USE_DEFAULT_OVERSAMPLE_VALUE_FOR_BBQ,
7475
IVF_FORMAT_CLUSTER_FEATURE,
75-
IVF_NESTED_SUPPORT
76+
IVF_NESTED_SUPPORT,
77+
SEARCH_LOAD_PER_SHARD
7678
);
7779
}
7880
}

server/src/main/java/org/elasticsearch/index/search/stats/SearchStats.java

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ public static class Stats implements Writeable, ToXContentFragment {
4949
private long queryFailure;
5050
private long fetchFailure;
5151

52+
// This tracks the search execution time across different phases (e.g., query, fetch, etc.), favouring more recent
53+
// values by assigning them greater significance than older values.
54+
private double recentSearchLoad;
55+
5256
private Stats() {
5357
// for internal use, initializes all counts to 0
5458
}
@@ -67,7 +71,8 @@ public Stats(
6771
long scrollCurrent,
6872
long suggestCount,
6973
long suggestTimeInMillis,
70-
long suggestCurrent
74+
long suggestCurrent,
75+
double recentSearchLoad
7176
) {
7277
this.queryCount = queryCount;
7378
this.queryTimeInMillis = queryTimeInMillis;
@@ -86,6 +91,9 @@ public Stats(
8691
this.suggestCount = suggestCount;
8792
this.suggestTimeInMillis = suggestTimeInMillis;
8893
this.suggestCurrent = suggestCurrent;
94+
95+
this.recentSearchLoad = recentSearchLoad;
96+
8997
}
9098

9199
private Stats(StreamInput in) throws IOException {
@@ -109,6 +117,10 @@ private Stats(StreamInput in) throws IOException {
109117
queryFailure = in.readVLong();
110118
fetchFailure = in.readVLong();
111119
}
120+
121+
if (in.getTransportVersion().onOrAfter(TransportVersions.SEARCH_LOAD_PER_INDEX_STATS)) {
122+
recentSearchLoad = in.readDouble();
123+
}
112124
}
113125

114126
@Override
@@ -133,6 +145,10 @@ public void writeTo(StreamOutput out) throws IOException {
133145
out.writeVLong(queryFailure);
134146
out.writeVLong(fetchFailure);
135147
}
148+
149+
if (out.getTransportVersion().onOrAfter(TransportVersions.SEARCH_LOAD_PER_INDEX_STATS)) {
150+
out.writeDouble(recentSearchLoad);
151+
}
136152
}
137153

138154
public void add(Stats stats) {
@@ -153,6 +169,8 @@ public void add(Stats stats) {
153169
suggestCount += stats.suggestCount;
154170
suggestTimeInMillis += stats.suggestTimeInMillis;
155171
suggestCurrent += stats.suggestCurrent;
172+
173+
recentSearchLoad += stats.recentSearchLoad;
156174
}
157175

158176
public void addForClosingShard(Stats stats) {
@@ -171,6 +189,8 @@ public void addForClosingShard(Stats stats) {
171189

172190
suggestCount += stats.suggestCount;
173191
suggestTimeInMillis += stats.suggestTimeInMillis;
192+
193+
recentSearchLoad += stats.recentSearchLoad;
174194
}
175195

176196
public long getQueryCount() {
@@ -245,6 +265,10 @@ public long getSuggestCurrent() {
245265
return suggestCurrent;
246266
}
247267

268+
public double getSearchLoadRate() {
269+
return recentSearchLoad;
270+
}
271+
248272
public static Stats readStats(StreamInput in) throws IOException {
249273
return new Stats(in);
250274
}
@@ -269,6 +293,8 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
269293
builder.humanReadableField(Fields.SUGGEST_TIME_IN_MILLIS, Fields.SUGGEST_TIME, getSuggestTime());
270294
builder.field(Fields.SUGGEST_CURRENT, suggestCurrent);
271295

296+
builder.field(Fields.RECENT_SEARCH_LOAD, recentSearchLoad);
297+
272298
return builder;
273299
}
274300

@@ -290,7 +316,8 @@ public boolean equals(Object o) {
290316
&& scrollCurrent == that.scrollCurrent
291317
&& suggestCount == that.suggestCount
292318
&& suggestTimeInMillis == that.suggestTimeInMillis
293-
&& suggestCurrent == that.suggestCurrent;
319+
&& suggestCurrent == that.suggestCurrent
320+
&& recentSearchLoad == that.recentSearchLoad;
294321
}
295322

296323
@Override
@@ -309,7 +336,8 @@ public int hashCode() {
309336
scrollCurrent,
310337
suggestCount,
311338
suggestTimeInMillis,
312-
suggestCurrent
339+
suggestCurrent,
340+
recentSearchLoad
313341
);
314342
}
315343
}
@@ -427,6 +455,7 @@ static final class Fields {
427455
static final String SUGGEST_TIME = "suggest_time";
428456
static final String SUGGEST_TIME_IN_MILLIS = "suggest_time_in_millis";
429457
static final String SUGGEST_CURRENT = "suggest_current";
458+
static final String RECENT_SEARCH_LOAD = "recent_search_load";
430459
}
431460

432461
@Override
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
package org.elasticsearch.index.search.stats;
11+
12+
import org.elasticsearch.common.settings.ClusterSettings;
13+
import org.elasticsearch.common.settings.Setting;
14+
import org.elasticsearch.common.settings.Settings;
15+
import org.elasticsearch.core.TimeValue;
16+
17+
/**
18+
* Container for cluster settings
19+
*/
20+
public class SearchStatsSettings {
21+
22+
public static final TimeValue RECENT_READ_LOAD_HALF_LIFE_DEFAULT = TimeValue.timeValueMinutes(5);
23+
static final TimeValue RECENT_READ_LOAD_HALF_LIFE_MIN = TimeValue.timeValueSeconds(1); // A sub-second half-life makes no sense
24+
static final TimeValue RECENT_READ_LOAD_HALF_LIFE_MAX = TimeValue.timeValueDays(100_000); // Long.MAX_VALUE nanos, rounded down
25+
26+
/**
27+
* A cluster setting giving the half-life, in seconds, to use for the Exponentially Weighted Moving Rate calculation used for the
28+
* recency-weighted read load
29+
*
30+
* <p>This is dynamic, but changes only apply to newly-opened shards.
31+
*/
32+
public static final Setting<TimeValue> RECENT_READ_LOAD_HALF_LIFE_SETTING = Setting.timeSetting(
33+
"indices.stats.recent_read_load.half_life",
34+
RECENT_READ_LOAD_HALF_LIFE_DEFAULT,
35+
RECENT_READ_LOAD_HALF_LIFE_MIN,
36+
RECENT_READ_LOAD_HALF_LIFE_MAX,
37+
Setting.Property.Dynamic,
38+
Setting.Property.NodeScope
39+
);
40+
41+
private volatile TimeValue recentReadLoadHalfLifeForNewShards = RECENT_READ_LOAD_HALF_LIFE_SETTING.getDefault(Settings.EMPTY);
42+
43+
public SearchStatsSettings(ClusterSettings clusterSettings) {
44+
clusterSettings.initializeAndWatch(RECENT_READ_LOAD_HALF_LIFE_SETTING, value -> recentReadLoadHalfLifeForNewShards = value);
45+
}
46+
47+
public TimeValue getRecentReadLoadHalfLifeForNewShards() {
48+
return recentReadLoadHalfLifeForNewShards;
49+
}
50+
}

0 commit comments

Comments
 (0)