Skip to content

Commit e2c8ff9

Browse files
author
Andrew Selden
committed
Benchmark API
Add an API endpoint at /_bench for submitting, listing, and aborting search benchmarks. This API can be used for timing search requests, subject to various user-defined settings. Benchmark results provide summary and detailed statistics on such values as min, max, and mean time. Values are reported per-node so that it is easy to spot outliers. Slow requests are also reported. Long running benchmarks can be viewed with a GET request, or aborted with a POST request. Benchmark results are optionally stored in an index for subsequent analysis. Closes #5407
1 parent af0278b commit e2c8ff9

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+5691
-24
lines changed
Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
[[search-benchmark]]
2+
== Benchmark
3+
4+
.Experimental!
5+
[IMPORTANT]
6+
=====
7+
This feature is marked as experimental, and may be subject to change in the
8+
future. If you use this feature, please let us know your experience with it!
9+
=====
10+
11+
The benchmark API provides a standard mechanism for submitting queries and
12+
measuring their performance relative to one another.
13+
14+
[IMPORTANT]
15+
=====
16+
To be eligible to run benchmarks nodes must be started with: *es.node.bench=true*. This is just a way to mark certain nodes as "executors". Searches will still be distributed out to the cluster in the normal manner. This is primarily a defensive measure to prevent production nodes from being flooded with potentially many requests. Typically one would start a single node with this setting and sumbmit benchmark requests to it.
17+
=====
18+
19+
[source,bash]
20+
--------------------------------------------------
21+
$ ./bin/elasticsearch -Des.node.bench=true
22+
--------------------------------------------------
23+
24+
Benchmarking a search request is as simple as executing the following command:
25+
26+
[source,js]
27+
--------------------------------------------------
28+
$ curl -XPUT 'localhost:9200/_bench/?pretty=true' -d
29+
'{
30+
"name": "my_benchmark",
31+
"competitors": [ {
32+
"name": "my_competitor",
33+
"requests": [ {
34+
"query": {
35+
"match": { "_all": "a*" }
36+
}
37+
} ]
38+
} ]
39+
}'
40+
--------------------------------------------------
41+
42+
Response:
43+
44+
[source,js]
45+
--------------------------------------------------
46+
{
47+
"status" : "complete",
48+
"competitors" : {
49+
"my_competitor" : {
50+
"summary" : {
51+
"nodes" : [ "localhost" ],
52+
"total_iterations" : 5,
53+
"completed_iterations" : 5,
54+
"total_queries" : 1000,
55+
"concurrency" : 5,
56+
"multiplier" : 100,
57+
"avg_warmup_time" : 43.0,
58+
"statistics" : {
59+
"min" : 1,
60+
"max" : 10,
61+
"mean" : 4.19,
62+
"qps" : 238.663,
63+
"std_dev" : 1.938,
64+
"millis_per_hit" : 1.064,
65+
"percentile_10" : 2,
66+
"percentile_25" : 3,
67+
"percentile_50" : 4,
68+
"percentile_75" : 5,
69+
"percentile_90" : 7,
70+
"percentile_99" : 10
71+
},
72+
"slowest" : [ {
73+
"node" : "localhost",
74+
"max_time" : 15,
75+
"avg_time" : 4,
76+
"request":{"query":{"match":{"_all":"a*"}}}
77+
} ]
78+
}
79+
}
80+
}
81+
}
82+
--------------------------------------------------
83+
84+
A 'competitor' defines one or more search requests to execute along with parameters that describe how the search(es) should be run.
85+
Multiple competitors may be submitted as a group in which case they will execute one after the other. This makes it easy to compare various
86+
competing alternatives side-by-side.
87+
88+
There are several parameters which may be set at the competition level:
89+
[horizontal]
90+
`name`:: Unique name for the competition
91+
`iterations`:: Number of times to run the competitors
92+
`concurrency`:: Within each iteration use this level of parallelism
93+
`multiplier`:: Within each iteration run the query this many times
94+
`warmup`:: Perform warmup of query
95+
`num_slowest`:: Record N slowest queries
96+
`search_type`:: Type of search, e.g. "query_then_fetch", "dfs_query_then_fetch", "count"
97+
`requests`:: Query DSL describing search requests
98+
`clear_caches`:: Whether caches should be cleared on each iteration, and if so, how
99+
`indices`:: Array of indices (and optional types) to search, e.g. ["my_index_1/my_type_1", "my_index_2", "my_index_3/my_type_3"]
100+
101+
Cache clearing parameters:
102+
[horizontal]
103+
`clear_caches`:: Set to 'false' to disable cache clearing completely
104+
`clear_caches.filter`:: Whether to clear the filter cache
105+
`clear_caches.field_data`:: Whether to clear the field data cache
106+
`clear_caches.id`:: Whether to clear the id cache
107+
`clear_caches.recycler`:: Whether to clear the recycler cache
108+
`clear_caches.fields`:: Array of fields to clear
109+
`clear_caches.filter_keys`:: Array of filter keys to clear
110+
111+
Global parameters:
112+
[horizontal]
113+
`name`:: Unique name for the benchmark
114+
`num_executor_nodes`:: Number of cluster nodes from which to submit and time benchmarks. Allows user to run a benchmark simultaneously on one or more nodes and compare timings. Note that this does not control how many nodes a search request will actually execute on. Defaults to: 1.
115+
`percentiles`:: Array of percentile values to report. Defaults to: [10, 25, 50, 75, 90, 99]
116+
117+
Additionally, the following competition-level parameters may be set globally: iteration, concurrency, multiplier, warmup, and clear_caches.
118+
119+
Using these parameters it is possible to describe precisely how to execute a benchmark under various conditions. In the following example we run a filtered query against two different indices using two different search types.
120+
121+
[source,js]
122+
--------------------------------------------------
123+
$ curl -XPUT 'localhost:9200/_bench/?pretty=true' -d
124+
{
125+
"name": "my_benchmark",
126+
"num_executor_nodes": 1,
127+
"percentiles" : [ 25, 50, 75 ],
128+
"iterations": 5,
129+
"multiplier": 1000,
130+
"concurrency": 5,
131+
"num_slowest": 0,
132+
"warmup": true,
133+
"clear_caches": false,
134+
135+
"requests": [ {
136+
"query" : {
137+
"filtered" : {
138+
"query" : { "match" : { "_all" : "*" } },
139+
"filter" : {
140+
"and" : [ { "term" : { "title" : "Spain" } },
141+
{ "term" : { "title" : "rain" } },
142+
{ "term" : { "title" : "plain" } } ]
143+
}
144+
}
145+
}
146+
} ],
147+
148+
"competitors": [ {
149+
"name": "competitor_1",
150+
"search_type": "query_then_fetch",
151+
"indices": [ "my_index_1" ],
152+
"clear_caches" : {
153+
"filter" : true,
154+
"field_data" : true,
155+
"id" : true,
156+
"recycler" : true,
157+
"fields": ["title"]
158+
}
159+
}, {
160+
"name": "competitor_2",
161+
"search_type": "dfs_query_then_fetch",
162+
"indices": [ "my_index_2" ],
163+
"clear_caches" : {
164+
"filter" : true,
165+
"field_data" : true,
166+
"id" : true,
167+
"recycler" : true,
168+
"fields": ["title"]
169+
}
170+
} ]
171+
}
172+
--------------------------------------------------
173+
174+
Response:
175+
176+
[source,js]
177+
--------------------------------------------------
178+
{
179+
"status" : "complete",
180+
"competitors" : {
181+
"competitor_1" : {
182+
"summary" : {
183+
"nodes" : [ "localhost" ],
184+
"total_iterations" : 5,
185+
"completed_iterations" : 5,
186+
"total_queries" : 5000,
187+
"concurrency" : 5,
188+
"multiplier" : 1000,
189+
"avg_warmup_time" : 54.0,
190+
"statistics" : {
191+
"min" : 0,
192+
"max" : 3,
193+
"mean" : 0.533,
194+
"qps" : 1872.659,
195+
"std_dev" : 0.528,
196+
"millis_per_hit" : 0.0,
197+
"percentile_25" : 0.0,
198+
"percentile_50" : 1.0,
199+
"percentile_75" : 1.0
200+
},
201+
"slowest" : [ ]
202+
}
203+
},
204+
"competitor_2" : {
205+
"summary" : {
206+
"nodes" : [ "localhost" ],
207+
"total_iterations" : 5,
208+
"completed_iterations" : 5,
209+
"total_queries" : 5000,
210+
"concurrency" : 5,
211+
"multiplier" : 1000,
212+
"avg_warmup_time" : 4.0,
213+
"statistics" : {
214+
"min" : 0,
215+
"max" : 4,
216+
"mean" : 0.487,
217+
"qps" : 2049.180,
218+
"std_dev" : 0.545,
219+
"millis_per_hit" : 0.0,
220+
"percentile_25" : 0.0,
221+
"percentile_50" : 0.0,
222+
"percentile_75" : 1.0
223+
},
224+
"slowest" : [ ]
225+
}
226+
}
227+
}
228+
}
229+
--------------------------------------------------
230+
231+
In some cases it may be desirable to view the progress of a long-running benchmark and optionally terminate it early. To view all active benchmarks use:
232+
233+
[source,js]
234+
--------------------------------------------------
235+
$ curl -XGET 'localhost:9200/_bench?pretty'
236+
--------------------------------------------------
237+
238+
This would display run-time statistics in the same format as the sample output above.
239+
240+
To abort a long-running benchmark use the 'abort' endpoint:
241+
242+
[source,js]
243+
--------------------------------------------------
244+
$ curl -XPOST 'localhost:9200/_bench/abort/my_benchmark?pretty'
245+
--------------------------------------------------
246+
247+
Response:
248+
249+
[source,js]
250+
--------------------------------------------------
251+
{
252+
"aborted_benchmarks" : [
253+
"node" "localhost",
254+
"benchmark_name", "my_benchmark",
255+
"aborted", true
256+
]
257+
}
258+
--------------------------------------------------
259+

rest-api-spec/api/bench.json

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{
2+
"bench" : {
3+
"documentation": "http://www.elasticsearch.org/guide/en/elasticsearch/reference/master/search-benchmark.html",
4+
"methods": ["GET", "PUT", "POST"],
5+
"url": {
6+
"path": "/_bench",
7+
"paths": [
8+
"/_bench",
9+
"/{index}/_bench",
10+
"/{index}/{type}/_bench",
11+
"/_bench/abort/{name}"
12+
],
13+
"parts": {
14+
"index": {
15+
"type" : "list",
16+
"description" : "A comma-separated list of index names; use `_all` or empty string to perform the operation on all indices"
17+
},
18+
"type": {
19+
"type" : "string",
20+
"required" : true,
21+
"description" : "The name of the document type"
22+
}
23+
},
24+
"params": {
25+
"wait_for_completion": {
26+
"type": "boolean",
27+
"description": "Specify whether the caller will wait for the benchmark to complete or return immediately after submission (default: true)"
28+
},
29+
"verbose": {
30+
"type": "boolean",
31+
"description": "Specify whether to return verbose statistics about each iteration (default: false)"
32+
}
33+
},
34+
"body": {
35+
"description": "The search definition using the Query DSL"
36+
}
37+
}
38+
}
39+
}

src/main/java/org/elasticsearch/action/ActionModule.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@
119119
import org.elasticsearch.action.admin.indices.warmer.get.TransportGetWarmersAction;
120120
import org.elasticsearch.action.admin.indices.warmer.put.PutWarmerAction;
121121
import org.elasticsearch.action.admin.indices.warmer.put.TransportPutWarmerAction;
122+
import org.elasticsearch.action.bench.*;
122123
import org.elasticsearch.action.bulk.BulkAction;
123124
import org.elasticsearch.action.bulk.TransportBulkAction;
124125
import org.elasticsearch.action.bulk.TransportShardBulkAction;
@@ -284,6 +285,9 @@ protected void configure() {
284285
registerAction(ExplainAction.INSTANCE, TransportExplainAction.class);
285286
registerAction(ClearScrollAction.INSTANCE, TransportClearScrollAction.class);
286287
registerAction(RecoveryAction.INSTANCE, TransportRecoveryAction.class);
288+
registerAction(BenchmarkAction.INSTANCE, TransportBenchmarkAction.class);
289+
registerAction(AbortBenchmarkAction.INSTANCE, TransportAbortBenchmarkAction.class);
290+
registerAction(BenchmarkStatusAction.INSTANCE, TransportBenchmarkStatusAction.class);
287291

288292
// register Name -> GenericAction Map that can be injected to instances.
289293
MapBinder<String, GenericAction> actionsBinder
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.action.bench;
21+
22+
import org.elasticsearch.action.Action;
23+
import org.elasticsearch.client.Client;
24+
25+
/**
26+
* Abort benchmark action
27+
*/
28+
public class AbortBenchmarkAction extends Action<AbortBenchmarkRequest, AbortBenchmarkResponse, AbortBenchmarkRequestBuilder> {
29+
30+
public static final AbortBenchmarkAction INSTANCE = new AbortBenchmarkAction();
31+
public static final String NAME = "benchmark/abort";
32+
33+
private AbortBenchmarkAction() {
34+
super(NAME);
35+
}
36+
37+
@Override
38+
public AbortBenchmarkResponse newResponse() {
39+
return new AbortBenchmarkResponse();
40+
}
41+
42+
@Override
43+
public AbortBenchmarkRequestBuilder newRequestBuilder(Client client) {
44+
return new AbortBenchmarkRequestBuilder(client);
45+
}
46+
}

0 commit comments

Comments
 (0)