-
Notifications
You must be signed in to change notification settings - Fork 25.6k
Enable Shard Search-load rate metrics for injection #127743
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 43 commits
9d9b6af
02ddf82
bf79ef3
f1c91bd
ba67bff
2c3654a
58d4762
bc38090
ef0447b
fe009d7
a747a40
f3e47ae
2bc0107
f3b3d00
f52789e
ec243b7
f93eb9b
2777916
623bd7b
3fee6af
af3fff9
a41bbad
8115a61
6b2361e
41dcc1c
2df7f62
6b95b0c
eadf8cf
41dc557
e0e1740
1ba2eaa
6820c35
8280559
e82375e
ce4c1c9
ff92f92
c0f4d18
02ac377
42aa647
bd258ab
0d5b0d3
a6f543a
89a6aca
19b0900
9f1af25
9cedf78
a23541b
406af11
d324d5f
fc2b041
206454b
f36ed2a
acc28b5
f8d3ce0
c34cc87
e2c36f8
8398854
037eedd
2a8a7d3
83b8fab
b6a0df7
c33891e
0c64b54
e7ba6ee
66453b4
a345f39
e9dac7a
2cb1681
d90c490
0a9a91d
1e23708
7639368
98fe3cf
93a7ee7
75a5398
db45d45
c5a249d
cce3194
01d1aa9
dfed174
6d4928d
5210c25
44aced7
6efbdba
7a60718
9839c83
2891849
fa837fa
c395c50
da312f2
cdbe6ff
3b692ae
6ccf982
2eadc7f
9023b18
7dccbe7
1e797fc
c4353ae
36a861d
c786d43
5fea235
88635ca
a8b99c1
a3a050b
7fa6707
da34d84
f072f3b
c1b8b13
1a5005c
62c4349
1f9d90e
341d44d
2a8a7eb
984063f
04558d5
6e70bd6
f03a83b
d12c34d
046faba
6015323
043cb6a
a488d9f
30e138b
80e62e8
5545cfc
36e19da
e60add0
e551894
2f3017c
c55a725
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| /* | ||
| * 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; | ||
|
|
||
| import java.util.concurrent.atomic.AtomicReference; | ||
|
|
||
| /** | ||
| * Container for cluster settings | ||
| */ | ||
| public class SearchStatsSettings { | ||
|
|
||
| public static final TimeValue RECENT_READ_LOAD_HALF_LIFE_DEFAULT = TimeValue.timeValueSeconds(15); // TODO this is set to seconds for | ||
| // debugging | ||
| 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 | ||
| * | ||
| * <p>This is dynamic, but changes only apply to newly-opened shards. | ||
| */ | ||
| public static final Setting<TimeValue> 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 final AtomicReference<TimeValue> recentReadLoadHalfLifeForNewShards = new AtomicReference<>( | ||
drempapis marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| RECENT_READ_LOAD_HALF_LIFE_SETTING.getDefault(Settings.EMPTY) | ||
| ); | ||
|
|
||
| public SearchStatsSettings(ClusterSettings clusterSettings) { | ||
| clusterSettings.initializeAndWatch(RECENT_READ_LOAD_HALF_LIFE_SETTING, recentReadLoadHalfLifeForNewShards::set); | ||
drempapis marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| public TimeValue getRecentReadLoadHalfLifeForNewShards() { | ||
| return recentReadLoadHalfLifeForNewShards.get(); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| /* | ||
| * 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 java.util.function.Supplier; | ||
|
|
||
| /** | ||
| * A factory interface for creating instances of {@link ShardSearchLoadRateService} using configurable settings | ||
| * and a time source. | ||
| * <p> | ||
| * This abstraction allows for flexible instantiation of load rate tracking services, potentially with | ||
| * different strategies based on provided settings or runtime context. | ||
| */ | ||
| public interface ShardSearchLoadRateProvider { | ||
|
|
||
| /** | ||
| * The default no-op implementation of the provider, which always returns {@link ShardSearchLoadRateService#NOOP}. | ||
| * <p> | ||
| * Useful as a fallback or when search load rate tracking is disabled or not required. | ||
| */ | ||
| ShardSearchLoadRateProvider DEFAULT = (settings, timeProvider) -> ShardSearchLoadRateService.NOOP; | ||
|
|
||
| /** | ||
| * Creates a new instance of {@link ShardSearchLoadRateService} using the given settings and time provider. | ||
| * | ||
| * @param settings the search statistics configuration settings that may influence the service behavior | ||
| * @param timeProvider a supplier of the current time, typically in milliseconds or nanoseconds | ||
| * @return a {@code ShardSearchLoadRateService} instance configured with the provided context | ||
| */ | ||
| ShardSearchLoadRateService create(SearchStatsSettings settings, Supplier<Long> timeProvider); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| /* | ||
| * 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; | ||
|
|
||
| /** | ||
| * Service interface for estimating the search load rate of a shard using provided search statistics. | ||
| * <p> | ||
| * Implementations may apply various heuristics or models, such as exponentially weighted moving averages, | ||
| * to track and estimate the current load on a shard based on its search activity. | ||
| */ | ||
| public interface ShardSearchLoadRateService { | ||
|
|
||
| /** | ||
| * A no-op implementation of {@code ShardSearchLoadRateService} that always returns {@link SearchLoadRate#NO_OP}. | ||
| * This can be used as a fallback or default when no actual load tracking is required. | ||
| */ | ||
| ShardSearchLoadRateService NOOP = (stats) -> SearchLoadRate.NO_OP; | ||
|
|
||
| /** | ||
| * Computes the search load rate based on the provided shard-level search statistics. | ||
| * | ||
| * @param stats the search statistics for the shard, typically including metrics like query count, latency, etc. | ||
| * @return the {@link SearchLoadRate} representing the current estimated load on the shard | ||
| */ | ||
| SearchLoadRate getSearchLoadRate(SearchStats.Stats stats); | ||
|
|
||
| /** | ||
| * Represents the search load rate as computed over time using an exponentially weighted moving average (EWM). | ||
| * <p> | ||
| * This record captures the timing of the last update, the delta since the last observation, | ||
| * and the computed rate itself. | ||
| * | ||
| * @param lastTrackedTime the timestamp (e.g., in milliseconds or nanoseconds) of the last update | ||
| * @param delta the elapsed time since the previous update | ||
| * @param ewmRate the current exponentially weighted moving average rate | ||
| */ | ||
| record SearchLoadRate(long lastTrackedTime, long delta, double ewmRate) { | ||
drempapis marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| /** | ||
| * A static no-op instance representing a default or zeroed state of {@code SearchLoadRate}. | ||
| * All numeric values are initialized to zero. | ||
| */ | ||
| public static final SearchLoadRate NO_OP = new SearchLoadRate(0, 0, 0.0); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -117,7 +117,10 @@ | |
| 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.ShardSearchLoadRateProvider; | ||
| import org.elasticsearch.index.search.stats.ShardSearchLoadRateService; | ||
| import org.elasticsearch.index.search.stats.ShardSearchStats; | ||
| import org.elasticsearch.index.seqno.ReplicationTracker; | ||
| import org.elasticsearch.index.seqno.RetentionLease; | ||
|
|
@@ -204,6 +207,7 @@ public class IndexShard extends AbstractIndexShardComponent implements IndicesCl | |
| private final Store store; | ||
| private final InternalIndexingStats internalIndexingStats; | ||
| private final ShardSearchStats searchStats = new ShardSearchStats(); | ||
| private final ShardSearchLoadRateService shardSearchLoadRateService; | ||
| private final ShardFieldUsageTracker fieldUsageTracker; | ||
| private final String shardUuid = UUIDs.randomBase64UUID(); | ||
| private final long shardCreationTime; | ||
|
|
@@ -341,7 +345,9 @@ public IndexShard( | |
| final LongSupplier relativeTimeInNanosSupplier, | ||
| final Engine.IndexCommitListener indexCommitListener, | ||
| final MapperMetrics mapperMetrics, | ||
| final IndexingStatsSettings indexingStatsSettings | ||
| final IndexingStatsSettings indexingStatsSettings, | ||
| final SearchStatsSettings searchStatsSettings, | ||
| final ShardSearchLoadRateProvider shardSearchLoadRateProvider | ||
| ) throws IOException { | ||
| super(shardRouting.shardId(), indexSettings); | ||
| assert shardRouting.initializing(); | ||
|
|
@@ -366,6 +372,7 @@ public IndexShard( | |
| CollectionUtils.appendToCopyNoNullElements(listeners, internalIndexingStats, indexingFailuresDebugListener), | ||
| logger | ||
| ); | ||
| this.shardSearchLoadRateService = shardSearchLoadRateProvider.create(searchStatsSettings, System::currentTimeMillis); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure why we're injecting a service here. Could the index shard calculate the ewmr directly?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's a good question. The main reason for injecting the service via SPI rather than calculating the EWMR directly in IndexShard is to avoid unnecessary memory overhead when the functionality isn't needed. While lazy initialization behind a proxy is an option on the stateful side, it would add unnecessary complexity to the core codebase. Additionally, this design helps separate concerns: the implementation is specific to the multi-project setup and doesn't belong in the core logic of IndexShard. Placing it behind an SPI keeps the core codebase clean and aligns the implementation more closely with the project that owns the logic, improving maintainability and modularity. So, keeping it behind an SPI ensures it's only enabled where applicable. |
||
| this.bulkOperationListener = new ShardBulkStats(); | ||
| this.globalCheckpointSyncer = globalCheckpointSyncer; | ||
| this.retentionLeaseSyncer = Objects.requireNonNull(retentionLeaseSyncer); | ||
|
|
@@ -1414,6 +1421,13 @@ public SearchStats searchStats(String... groups) { | |
| return searchStats.stats(groups); | ||
| } | ||
|
|
||
| /** | ||
| * Returns the search load rate stats for this shard. | ||
| */ | ||
| public ShardSearchLoadRateService.SearchLoadRate getSearchLoadRate() { | ||
| return shardSearchLoadRateService.getSearchLoadRate(searchStats.stats().getTotal()); | ||
| } | ||
|
|
||
| public FieldUsageStats fieldUsageStats(String... fields) { | ||
| return fieldUsageTracker.stats(fields); | ||
| } | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.