profile.asciidoc 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868
  1. [[search-profile]]
  2. == Profile API
  3. WARNING: The Profile API is a debugging tool and adds significant overhead to search execution.
  4. The Profile API provides detailed timing information about the execution of individual components
  5. in a search request. It gives the user insight into how search requests are executed at a low level so that
  6. the user can understand why certain requests are slow, and take steps to improve them.
  7. The output from the Profile API is *very* verbose, especially for complicated requests executed across
  8. many shards. Pretty-printing the response is recommended to help understand the output
  9. [float]
  10. === Usage
  11. Any `_search` request can be profiled by adding a top-level `profile` parameter:
  12. [source,js]
  13. --------------------------------------------------
  14. GET /twitter/_search
  15. {
  16. "profile": true,<1>
  17. "query" : {
  18. "match" : { "message" : "some number" }
  19. }
  20. }
  21. --------------------------------------------------
  22. // CONSOLE
  23. // TEST[setup:twitter]
  24. <1> Setting the top-level `profile` parameter to `true` will enable profiling
  25. for the search
  26. This will yield the following result:
  27. [source,js]
  28. --------------------------------------------------
  29. {
  30. "took": 25,
  31. "timed_out": false,
  32. "_shards": {
  33. "total": 1,
  34. "successful": 1,
  35. "skipped" : 0,
  36. "failed": 0
  37. },
  38. "hits": {
  39. "total": 4,
  40. "max_score": 0.5093388,
  41. "hits": [...] <1>
  42. },
  43. "profile": {
  44. "shards": [
  45. {
  46. "id": "[2aE02wS1R8q_QFnYu6vDVQ][twitter][0]",
  47. "searches": [
  48. {
  49. "query": [
  50. {
  51. "type": "BooleanQuery",
  52. "description": "message:some message:number",
  53. "time_in_nanos": "1873811",
  54. "breakdown": {
  55. "score": 51306,
  56. "score_count": 4,
  57. "build_scorer": 2935582,
  58. "build_scorer_count": 1,
  59. "match": 0,
  60. "match_count": 0,
  61. "create_weight": 919297,
  62. "create_weight_count": 1,
  63. "next_doc": 53876,
  64. "next_doc_count": 5,
  65. "advance": 0,
  66. "advance_count": 0,
  67. "compute_max_score": 0,
  68. "compute_max_score_count": 0,
  69. "shallow_advance": 0,
  70. "shallow_advance_count": 0
  71. },
  72. "children": [
  73. {
  74. "type": "TermQuery",
  75. "description": "message:some",
  76. "time_in_nanos": "391943",
  77. "breakdown": {
  78. "score": 28776,
  79. "score_count": 4,
  80. "build_scorer": 784451,
  81. "build_scorer_count": 1,
  82. "match": 0,
  83. "match_count": 0,
  84. "create_weight": 1669564,
  85. "create_weight_count": 1,
  86. "next_doc": 10111,
  87. "next_doc_count": 5,
  88. "advance": 0,
  89. "advance_count": 0,
  90. "compute_max_score": 0,
  91. "compute_max_score_count": 0,
  92. "shallow_advance": 0,
  93. "shallow_advance_count": 0
  94. }
  95. },
  96. {
  97. "type": "TermQuery",
  98. "description": "message:number",
  99. "time_in_nanos": "210682",
  100. "breakdown": {
  101. "score": 4552,
  102. "score_count": 4,
  103. "build_scorer": 42602,
  104. "build_scorer_count": 1,
  105. "match": 0,
  106. "match_count": 0,
  107. "create_weight": 89323,
  108. "create_weight_count": 1,
  109. "next_doc": 2852,
  110. "next_doc_count": 5,
  111. "advance": 0,
  112. "advance_count": 0,
  113. "compute_max_score": 0,
  114. "compute_max_score_count": 0,
  115. "shallow_advance": 0,
  116. "shallow_advance_count": 0
  117. }
  118. }
  119. ]
  120. }
  121. ],
  122. "rewrite_time": 51443,
  123. "collector": [
  124. {
  125. "name": "CancellableCollector",
  126. "reason": "search_cancelled",
  127. "time_in_nanos": "304311",
  128. "children": [
  129. {
  130. "name": "SimpleTopScoreDocCollector",
  131. "reason": "search_top_hits",
  132. "time_in_nanos": "32273"
  133. }
  134. ]
  135. }
  136. ]
  137. }
  138. ],
  139. "aggregations": []
  140. }
  141. ]
  142. }
  143. }
  144. --------------------------------------------------
  145. // TESTRESPONSE[s/"took": 25/"took": $body.took/]
  146. // TESTRESPONSE[s/"hits": \[...\]/"hits": $body.$_path/]
  147. // TESTRESPONSE[s/(?<=[" ])\d+(\.\d+)?/$body.$_path/]
  148. // TESTRESPONSE[s/\[2aE02wS1R8q_QFnYu6vDVQ\]\[twitter\]\[0\]/$body.$_path/]
  149. <1> Search results are returned, but were omitted here for brevity
  150. Even for a simple query, the response is relatively complicated. Let's break it down piece-by-piece before moving
  151. to more complex examples.
  152. First, the overall structure of the profile response is as follows:
  153. [source,js]
  154. --------------------------------------------------
  155. {
  156. "profile": {
  157. "shards": [
  158. {
  159. "id": "[2aE02wS1R8q_QFnYu6vDVQ][twitter][0]", <1>
  160. "searches": [
  161. {
  162. "query": [...], <2>
  163. "rewrite_time": 51443, <3>
  164. "collector": [...] <4>
  165. }
  166. ],
  167. "aggregations": [...] <5>
  168. }
  169. ]
  170. }
  171. }
  172. --------------------------------------------------
  173. // TESTRESPONSE[s/"profile": /"took": $body.took, "timed_out": $body.timed_out, "_shards": $body._shards, "hits": $body.hits, "profile": /]
  174. // TESTRESPONSE[s/(?<=[" ])\d+(\.\d+)?/$body.$_path/]
  175. // TESTRESPONSE[s/\[2aE02wS1R8q_QFnYu6vDVQ\]\[twitter\]\[0\]/$body.$_path/]
  176. // TESTRESPONSE[s/"query": \[...\]/"query": $body.$_path/]
  177. // TESTRESPONSE[s/"collector": \[...\]/"collector": $body.$_path/]
  178. // TESTRESPONSE[s/"aggregations": \[...\]/"aggregations": []/]
  179. <1> A profile is returned for each shard that participated in the response, and is identified
  180. by a unique ID
  181. <2> Each profile contains a section which holds details about the query execution
  182. <3> Each profile has a single time representing the cumulative rewrite time
  183. <4> Each profile also contains a section about the Lucene Collectors which run the search
  184. <5> Each profile contains a section which holds the details about the aggregation execution
  185. Because a search request may be executed against one or more shards in an index, and a search may cover
  186. one or more indices, the top level element in the profile response is an array of `shard` objects.
  187. Each shard object lists it's `id` which uniquely identifies the shard. The ID's format is
  188. `[nodeID][indexName][shardID]`.
  189. The profile itself may consist of one or more "searches", where a search is a query executed against the underlying
  190. Lucene index. Most Search Requests submitted by the user will only execute a single `search` against the Lucene index.
  191. But occasionally multiple searches will be executed, such as including a global aggregation (which needs to execute
  192. a secondary "match_all" query for the global context).
  193. Inside each `search` object there will be two arrays of profiled information:
  194. a `query` array and a `collector` array. Alongside the `search` object is an `aggregations` object that contains the profile information for the aggregations. In the future, more sections may be added, such as `suggest`, `highlight`, etc
  195. There will also be a `rewrite` metric showing the total time spent rewriting the query (in nanoseconds).
  196. NOTE: As with other statistics apis, the Profile API supports human readable outputs. This can be turned on by adding
  197. `?human=true` to the query string. In this case, the output contains the additional `time` field containing rounded,
  198. human readable timing information (e.g. `"time": "391,9ms"`, `"time": "123.3micros"`).
  199. === Profiling Queries
  200. [NOTE]
  201. =======================================
  202. The details provided by the Profile API directly expose Lucene class names and concepts, which means
  203. that complete interpretation of the results require fairly advanced knowledge of Lucene. This
  204. page attempts to give a crash-course in how Lucene executes queries so that you can use the Profile API to successfully
  205. diagnose and debug queries, but it is only an overview. For complete understanding, please refer
  206. to Lucene's documentation and, in places, the code.
  207. With that said, a complete understanding is often not required to fix a slow query. It is usually
  208. sufficient to see that a particular component of a query is slow, and not necessarily understand why
  209. the `advance` phase of that query is the cause, for example.
  210. =======================================
  211. ==== `query` Section
  212. The `query` section contains detailed timing of the query tree executed by Lucene on a particular shard.
  213. The overall structure of this query tree will resemble your original Elasticsearch query, but may be slightly
  214. (or sometimes very) different. It will also use similar but not always identical naming. Using our previous
  215. `match` query example, let's analyze the `query` section:
  216. [source,js]
  217. --------------------------------------------------
  218. "query": [
  219. {
  220. "type": "BooleanQuery",
  221. "description": "message:some message:number",
  222. "time_in_nanos": "1873811",
  223. "breakdown": {...}, <1>
  224. "children": [
  225. {
  226. "type": "TermQuery",
  227. "description": "message:some",
  228. "time_in_nanos": "391943",
  229. "breakdown": {...}
  230. },
  231. {
  232. "type": "TermQuery",
  233. "description": "message:number",
  234. "time_in_nanos": "210682",
  235. "breakdown": {...}
  236. }
  237. ]
  238. }
  239. ]
  240. --------------------------------------------------
  241. // TESTRESPONSE[s/^/{\n"took": $body.took,\n"timed_out": $body.timed_out,\n"_shards": $body._shards,\n"hits": $body.hits,\n"profile": {\n"shards": [ {\n"id": "$body.$_path",\n"searches": [{\n/]
  242. // TESTRESPONSE[s/]$/],"rewrite_time": $body.$_path, "collector": $body.$_path}], "aggregations": []}]}}/]
  243. // TESTRESPONSE[s/(?<=[" ])\d+(\.\d+)?/$body.$_path/]
  244. // TESTRESPONSE[s/"breakdown": \{...\}/"breakdown": $body.$_path/]
  245. <1> The breakdown timings are omitted for simplicity
  246. Based on the profile structure, we can see that our `match` query was rewritten by Lucene into a BooleanQuery with two
  247. clauses (both holding a TermQuery). The `type` field displays the Lucene class name, and often aligns with
  248. the equivalent name in Elasticsearch. The `description` field displays the Lucene explanation text for the query, and
  249. is made available to help differentiating between parts of your query (e.g. both `message:search` and `message:test`
  250. are TermQuery's and would appear identical otherwise.
  251. The `time_in_nanos` field shows that this query took ~1.8ms for the entire BooleanQuery to execute. The recorded time is inclusive
  252. of all children.
  253. The `breakdown` field will give detailed stats about how the time was spent, we'll look at
  254. that in a moment. Finally, the `children` array lists any sub-queries that may be present. Because we searched for two
  255. values ("search test"), our BooleanQuery holds two children TermQueries. They have identical information (type, time,
  256. breakdown, etc). Children are allowed to have their own children.
  257. ===== Timing Breakdown
  258. The `breakdown` component lists detailed timing statistics about low-level Lucene execution:
  259. [source,js]
  260. --------------------------------------------------
  261. "breakdown": {
  262. "score": 51306,
  263. "score_count": 4,
  264. "build_scorer": 2935582,
  265. "build_scorer_count": 1,
  266. "match": 0,
  267. "match_count": 0,
  268. "create_weight": 919297,
  269. "create_weight_count": 1,
  270. "next_doc": 53876,
  271. "next_doc_count": 5,
  272. "advance": 0,
  273. "advance_count": 0,
  274. "compute_max_score": 0,
  275. "compute_max_score_count": 0,
  276. "shallow_advance": 0,
  277. "shallow_advance_count": 0
  278. }
  279. --------------------------------------------------
  280. // TESTRESPONSE[s/^/{\n"took": $body.took,\n"timed_out": $body.timed_out,\n"_shards": $body._shards,\n"hits": $body.hits,\n"profile": {\n"shards": [ {\n"id": "$body.$_path",\n"searches": [{\n"query": [{\n"type": "BooleanQuery",\n"description": "message:some message:number",\n"time_in_nanos": $body.$_path,/]
  281. // TESTRESPONSE[s/}$/},\n"children": $body.$_path}],\n"rewrite_time": $body.$_path, "collector": $body.$_path}], "aggregations": []}]}}/]
  282. // TESTRESPONSE[s/(?<=[" ])\d+(\.\d+)?/$body.$_path/]
  283. Timings are listed in wall-clock nanoseconds and are not normalized at all. All caveats about the overall
  284. `time_in_nanos` apply here. The intention of the breakdown is to give you a feel for A) what machinery in Lucene is
  285. actually eating time, and B) the magnitude of differences in times between the various components. Like the overall time,
  286. the breakdown is inclusive of all children times.
  287. The meaning of the stats are as follows:
  288. [float]
  289. ==== All parameters:
  290. [horizontal]
  291. `create_weight`::
  292. A Query in Lucene must be capable of reuse across multiple IndexSearchers (think of it as the engine that
  293. executes a search against a specific Lucene Index). This puts Lucene in a tricky spot, since many queries
  294. need to accumulate temporary state/statistics associated with the index it is being used against, but the
  295. Query contract mandates that it must be immutable.
  296. {empty} +
  297. {empty} +
  298. To get around this, Lucene asks each query to generate a Weight object which acts as a temporary context
  299. object to hold state associated with this particular (IndexSearcher, Query) tuple. The `weight` metric
  300. shows how long this process takes
  301. `build_scorer`::
  302. This parameter shows how long it takes to build a Scorer for the query. A Scorer is the mechanism that
  303. iterates over matching documents generates a score per-document (e.g. how well does "foo" match the document?).
  304. Note, this records the time required to generate the Scorer object, not actually score the documents. Some
  305. queries have faster or slower initialization of the Scorer, depending on optimizations, complexity, etc.
  306. {empty} +
  307. {empty} +
  308. This may also showing timing associated with caching, if enabled and/or applicable for the query
  309. `next_doc`::
  310. The Lucene method `next_doc` returns Doc ID of the next document matching the query. This statistic shows
  311. the time it takes to determine which document is the next match, a process that varies considerably depending
  312. on the nature of the query. Next_doc is a specialized form of advance() which is more convenient for many
  313. queries in Lucene. It is equivalent to advance(docId() + 1)
  314. `advance`::
  315. `advance` is the "lower level" version of next_doc: it serves the same purpose of finding the next matching
  316. doc, but requires the calling query to perform extra tasks such as identifying and moving past skips, etc.
  317. However, not all queries can use next_doc, so `advance` is also timed for those queries.
  318. {empty} +
  319. {empty} +
  320. Conjunctions (e.g. `must` clauses in a boolean) are typical consumers of `advance`
  321. `matches`::
  322. Some queries, such as phrase queries, match documents using a "Two Phase" process. First, the document is
  323. "approximately" matched, and if it matches approximately, it is checked a second time with a more rigorous
  324. (and expensive) process. The second phase verification is what the `matches` statistic measures.
  325. {empty} +
  326. {empty} +
  327. For example, a phrase query first checks a document approximately by ensuring all terms in the phrase are
  328. present in the doc. If all the terms are present, it then executes the second phase verification to ensure
  329. the terms are in-order to form the phrase, which is relatively more expensive than just checking for presence
  330. of the terms.
  331. {empty} +
  332. {empty} +
  333. Because this two-phase process is only used by a handful of queries, the `metric` statistic will often be zero
  334. `score`::
  335. This records the time taken to score a particular document via it's Scorer
  336. `*_count`::
  337. Records the number of invocations of the particular method. For example, `"next_doc_count": 2,`
  338. means the `nextDoc()` method was called on two different documents. This can be used to help judge
  339. how selective queries are, by comparing counts between different query components.
  340. ==== `collectors` Section
  341. The Collectors portion of the response shows high-level execution details. Lucene works by defining a "Collector"
  342. which is responsible for coordinating the traversal, scoring and collection of matching documents. Collectors
  343. are also how a single query can record aggregation results, execute unscoped "global" queries, execute post-query
  344. filters, etc.
  345. Looking at the previous example:
  346. [source,js]
  347. --------------------------------------------------
  348. "collector": [
  349. {
  350. "name": "CancellableCollector",
  351. "reason": "search_cancelled",
  352. "time_in_nanos": "304311",
  353. "children": [
  354. {
  355. "name": "SimpleTopScoreDocCollector",
  356. "reason": "search_top_hits",
  357. "time_in_nanos": "32273"
  358. }
  359. ]
  360. }
  361. ]
  362. --------------------------------------------------
  363. // TESTRESPONSE[s/^/{\n"took": $body.took,\n"timed_out": $body.timed_out,\n"_shards": $body._shards,\n"hits": $body.hits,\n"profile": {\n"shards": [ {\n"id": "$body.$_path",\n"searches": [{\n"query": $body.$_path,\n"rewrite_time": $body.$_path,/]
  364. // TESTRESPONSE[s/]$/]}], "aggregations": []}]}}/]
  365. // TESTRESPONSE[s/(?<=[" ])\d+(\.\d+)?/$body.$_path/]
  366. We see a single collector named `SimpleTopScoreDocCollector` wrapped into `CancellableCollector`. `SimpleTopScoreDocCollector` is the default "scoring and sorting"
  367. `Collector` used by Elasticsearch. The `reason` field attempts to give a plain english description of the class name. The
  368. `time_in_nanos` is similar to the time in the Query tree: a wall-clock time inclusive of all children. Similarly, `children` lists
  369. all sub-collectors. The `CancellableCollector` that wraps `SimpleTopScoreDocCollector` is used by Elasticsearch to detect if the current
  370. search was cancelled and stop collecting documents as soon as it occurs.
  371. It should be noted that Collector times are **independent** from the Query times. They are calculated, combined
  372. and normalized independently! Due to the nature of Lucene's execution, it is impossible to "merge" the times
  373. from the Collectors into the Query section, so they are displayed in separate portions.
  374. For reference, the various collector reason's are:
  375. [horizontal]
  376. `search_sorted`::
  377. A collector that scores and sorts documents. This is the most common collector and will be seen in most
  378. simple searches
  379. `search_count`::
  380. A collector that only counts the number of documents that match the query, but does not fetch the source.
  381. This is seen when `size: 0` is specified
  382. `search_terminate_after_count`::
  383. A collector that terminates search execution after `n` matching documents have been found. This is seen
  384. when the `terminate_after_count` query parameter has been specified
  385. `search_min_score`::
  386. A collector that only returns matching documents that have a score greater than `n`. This is seen when
  387. the top-level parameter `min_score` has been specified.
  388. `search_multi`::
  389. A collector that wraps several other collectors. This is seen when combinations of search, aggregations,
  390. global aggs and post_filters are combined in a single search.
  391. `search_timeout`::
  392. A collector that halts execution after a specified period of time. This is seen when a `timeout` top-level
  393. parameter has been specified.
  394. `aggregation`::
  395. A collector that Elasticsearch uses to run aggregations against the query scope. A single `aggregation`
  396. collector is used to collect documents for *all* aggregations, so you will see a list of aggregations
  397. in the name rather.
  398. `global_aggregation`::
  399. A collector that executes an aggregation against the global query scope, rather than the specified query.
  400. Because the global scope is necessarily different from the executed query, it must execute it's own
  401. match_all query (which you will see added to the Query section) to collect your entire dataset
  402. ==== `rewrite` Section
  403. All queries in Lucene undergo a "rewriting" process. A query (and its sub-queries) may be rewritten one or
  404. more times, and the process continues until the query stops changing. This process allows Lucene to perform
  405. optimizations, such as removing redundant clauses, replacing one query for a more efficient execution path,
  406. etc. For example a Boolean -> Boolean -> TermQuery can be rewritten to a TermQuery, because all the Booleans
  407. are unnecessary in this case.
  408. The rewriting process is complex and difficult to display, since queries can change drastically. Rather than
  409. showing the intermediate results, the total rewrite time is simply displayed as a value (in nanoseconds). This
  410. value is cumulative and contains the total time for all queries being rewritten.
  411. ==== A more complex example
  412. To demonstrate a slightly more complex query and the associated results, we can profile the following query:
  413. [source,js]
  414. --------------------------------------------------
  415. GET /twitter/_search
  416. {
  417. "profile": true,
  418. "query": {
  419. "term": {
  420. "user": {
  421. "value": "test"
  422. }
  423. }
  424. },
  425. "aggs": {
  426. "my_scoped_agg": {
  427. "terms": {
  428. "field": "likes"
  429. }
  430. },
  431. "my_global_agg": {
  432. "global": {},
  433. "aggs": {
  434. "my_level_agg": {
  435. "terms": {
  436. "field": "likes"
  437. }
  438. }
  439. }
  440. }
  441. },
  442. "post_filter": {
  443. "match": {
  444. "message": "some"
  445. }
  446. }
  447. }
  448. --------------------------------------------------
  449. // CONSOLE
  450. // TEST[s/_search/_search\?filter_path=profile.shards.id,profile.shards.searches,profile.shards.aggregations/]
  451. // TEST[continued]
  452. This example has:
  453. - A query
  454. - A scoped aggregation
  455. - A global aggregation
  456. - A post_filter
  457. And the response:
  458. [source,js]
  459. --------------------------------------------------
  460. {
  461. ...
  462. "profile": {
  463. "shards": [
  464. {
  465. "id": "[P6-vulHtQRWuD4YnubWb7A][test][0]",
  466. "searches": [
  467. {
  468. "query": [
  469. {
  470. "type": "TermQuery",
  471. "description": "message:some",
  472. "time_in_nanos": "409456",
  473. "breakdown": {
  474. "score": 0,
  475. "build_scorer_count": 1,
  476. "match_count": 0,
  477. "create_weight": 31584,
  478. "next_doc": 0,
  479. "match": 0,
  480. "create_weight_count": 1,
  481. "next_doc_count": 2,
  482. "score_count": 1,
  483. "build_scorer": 377872,
  484. "advance": 0,
  485. "advance_count": 0,
  486. "compute_max_score": 0,
  487. "compute_max_score_count": 0,
  488. "shallow_advance": 0,
  489. "shallow_advance_count": 0
  490. }
  491. },
  492. {
  493. "type": "TermQuery",
  494. "description": "user:test",
  495. "time_in_nanos": "303702",
  496. "breakdown": {
  497. "score": 0,
  498. "build_scorer_count": 1,
  499. "match_count": 0,
  500. "create_weight": 185215,
  501. "next_doc": 5936,
  502. "match": 0,
  503. "create_weight_count": 1,
  504. "next_doc_count": 2,
  505. "score_count": 1,
  506. "build_scorer": 112551,
  507. "advance": 0,
  508. "advance_count": 0,
  509. "compute_max_score": 0,
  510. "compute_max_score_count": 0,
  511. "shallow_advance": 0,
  512. "shallow_advance_count": 0
  513. }
  514. }
  515. ],
  516. "rewrite_time": 7208,
  517. "collector": [
  518. {
  519. "name": "CancellableCollector",
  520. "reason": "search_cancelled",
  521. "time_in_nanos": 2390,
  522. "children": [
  523. {
  524. "name": "MultiCollector",
  525. "reason": "search_multi",
  526. "time_in_nanos": 1820,
  527. "children": [
  528. {
  529. "name": "FilteredCollector",
  530. "reason": "search_post_filter",
  531. "time_in_nanos": 7735,
  532. "children": [
  533. {
  534. "name": "SimpleTopScoreDocCollector",
  535. "reason": "search_top_hits",
  536. "time_in_nanos": 1328
  537. }
  538. ]
  539. },
  540. {
  541. "name": "MultiBucketCollector: [[my_scoped_agg, my_global_agg]]",
  542. "reason": "aggregation",
  543. "time_in_nanos": 8273
  544. }
  545. ]
  546. }
  547. ]
  548. }
  549. ]
  550. }
  551. ],
  552. "aggregations": [...] <1>
  553. }
  554. ]
  555. }
  556. }
  557. --------------------------------------------------
  558. // TESTRESPONSE[s/"aggregations": \[\.\.\.\]/"aggregations": $body.$_path/]
  559. // TESTRESPONSE[s/\.\.\.//]
  560. // TESTRESPONSE[s/(?<=[" ])\d+(\.\d+)?/$body.$_path/]
  561. // TESTRESPONSE[s/"id": "\[P6-vulHtQRWuD4YnubWb7A\]\[test\]\[0\]"/"id": $body.profile.shards.0.id/]
  562. <1> The ``"aggregations"` portion has been omitted because it will be covered in the next section
  563. As you can see, the output is significantly verbose from before. All the major portions of the query are
  564. represented:
  565. 1. The first `TermQuery` (user:test) represents the main `term` query
  566. 2. The second `TermQuery` (message:some) represents the `post_filter` query
  567. The Collector tree is fairly straightforward, showing how a single CancellableCollector wraps a MultiCollector
  568. which also wraps a FilteredCollector to execute the post_filter (and in turn wraps the normal scoring SimpleCollector),
  569. a BucketCollector to run all scoped aggregations.
  570. ==== Understanding MultiTermQuery output
  571. A special note needs to be made about the `MultiTermQuery` class of queries. This includes wildcards, regex and fuzzy
  572. queries. These queries emit very verbose responses, and are not overly structured.
  573. Essentially, these queries rewrite themselves on a per-segment basis. If you imagine the wildcard query `b*`, it technically
  574. can match any token that begins with the letter "b". It would be impossible to enumerate all possible combinations,
  575. so Lucene rewrites the query in context of the segment being evaluated. E.g. one segment may contain the tokens
  576. `[bar, baz]`, so the query rewrites to a BooleanQuery combination of "bar" and "baz". Another segment may only have the
  577. token `[bakery]`, so query rewrites to a single TermQuery for "bakery".
  578. Due to this dynamic, per-segment rewriting, the clean tree structure becomes distorted and no longer follows a clean
  579. "lineage" showing how one query rewrites into the next. At present time, all we can do is apologize, and suggest you
  580. collapse the details for that query's children if it is too confusing. Luckily, all the timing statistics are correct,
  581. just not the physical layout in the response, so it is sufficient to just analyze the top-level MultiTermQuery and
  582. ignore its children if you find the details too tricky to interpret.
  583. Hopefully this will be fixed in future iterations, but it is a tricky problem to solve and still in-progress :)
  584. === Profiling Aggregations
  585. ==== `aggregations` Section
  586. The `aggregations` section contains detailed timing of the aggregation tree executed by a particular shard.
  587. The overall structure of this aggregation tree will resemble your original Elasticsearch request. Let's
  588. execute the previous query again and look at the aggregation profile this time:
  589. [source,js]
  590. --------------------------------------------------
  591. GET /twitter/_search
  592. {
  593. "profile": true,
  594. "query": {
  595. "term": {
  596. "user": {
  597. "value": "test"
  598. }
  599. }
  600. },
  601. "aggs": {
  602. "my_scoped_agg": {
  603. "terms": {
  604. "field": "likes"
  605. }
  606. },
  607. "my_global_agg": {
  608. "global": {},
  609. "aggs": {
  610. "my_level_agg": {
  611. "terms": {
  612. "field": "likes"
  613. }
  614. }
  615. }
  616. }
  617. },
  618. "post_filter": {
  619. "match": {
  620. "message": "some"
  621. }
  622. }
  623. }
  624. --------------------------------------------------
  625. // CONSOLE
  626. // TEST[s/_search/_search\?filter_path=profile.shards.aggregations/]
  627. // TEST[continued]
  628. Which yields the following aggregation profile output
  629. [source,js]
  630. --------------------------------------------------
  631. {
  632. "profile" : {
  633. "shards" : [
  634. {
  635. "aggregations" : [
  636. {
  637. "type" : "LongTermsAggregator",
  638. "description" : "my_scoped_agg",
  639. "time_in_nanos" : 195386,
  640. "breakdown" : {
  641. "reduce" : 0,
  642. "build_aggregation" : 81171,
  643. "build_aggregation_count" : 1,
  644. "initialize" : 22753,
  645. "initialize_count" : 1,
  646. "reduce_count" : 0,
  647. "collect" : 91456,
  648. "collect_count" : 4
  649. }
  650. },
  651. {
  652. "type" : "GlobalAggregator",
  653. "description" : "my_global_agg",
  654. "time_in_nanos" : 190430,
  655. "breakdown" : {
  656. "reduce" : 0,
  657. "build_aggregation" : 59990,
  658. "build_aggregation_count" : 1,
  659. "initialize" : 29619,
  660. "initialize_count" : 1,
  661. "reduce_count" : 0,
  662. "collect" : 100815,
  663. "collect_count" : 4
  664. },
  665. "children" : [
  666. {
  667. "type" : "LongTermsAggregator",
  668. "description" : "my_level_agg",
  669. "time_in_nanos" : 160329,
  670. "breakdown" : {
  671. "reduce" : 0,
  672. "build_aggregation" : 55712,
  673. "build_aggregation_count" : 1,
  674. "initialize" : 10559,
  675. "initialize_count" : 1,
  676. "reduce_count" : 0,
  677. "collect" : 94052,
  678. "collect_count" : 4
  679. }
  680. }
  681. ]
  682. }
  683. ]
  684. }
  685. ]
  686. }
  687. }
  688. --------------------------------------------------
  689. // TESTRESPONSE[s/\.\.\.//]
  690. // TESTRESPONSE[s/(?<=[" ])\d+(\.\d+)?/$body.$_path/]
  691. // TESTRESPONSE[s/"id": "\[P6-vulHtQRWuD4YnubWb7A\]\[test\]\[0\]"/"id": $body.profile.shards.0.id/]
  692. From the profile structure we can see that the `my_scoped_agg` is internally being run as a `LongTermsAggregator` (because the field it is
  693. aggregating, `likes`, is a numeric field). At the same level, we see a `GlobalAggregator` which comes from `my_global_agg`. That
  694. aggregation then has a child `LongTermsAggregator` which from the second terms aggregation on `likes`.
  695. The `time_in_nanos` field shows the time executed by each aggregation, and is inclusive of all children. While the overall time is useful,
  696. the `breakdown` field will give detailed stats about how the time was spent.
  697. ===== Timing Breakdown
  698. The `breakdown` component lists detailed timing statistics about low-level Lucene execution:
  699. [source,js]
  700. --------------------------------------------------
  701. "breakdown": {
  702. "reduce": 0,
  703. "reduce_count": 0,
  704. "build_aggregation": 49765,
  705. "build_aggregation_count": 300,
  706. "initialize": 52785,
  707. "initialize_count": 300,
  708. "reduce_count": 0,
  709. "collect": 3155490036,
  710. "collect_count": 1800
  711. }
  712. --------------------------------------------------
  713. // NOTCONSOLE
  714. Timings are listed in wall-clock nanoseconds and are not normalized at all. All caveats about the overall
  715. `time` apply here. The intention of the breakdown is to give you a feel for A) what machinery in Elasticsearch is
  716. actually eating time, and B) the magnitude of differences in times between the various components. Like the overall time,
  717. the breakdown is inclusive of all children times.
  718. The meaning of the stats are as follows:
  719. [float]
  720. ==== All parameters:
  721. [horizontal]
  722. `initialise`::
  723. This times how long it takes to create and initialise the aggregation before starting to collect documents.
  724. `collect`::
  725. This represents the cumulative time spent in the collect phase of the aggregation. This is where matching documents are passed to the aggregation and the state of the aggregator is updated based on the information contained in the documents.
  726. `build_aggregation`::
  727. This represents the time spent creating the shard level results of the aggregation ready to pass back to the reducing node after the collection of documents is finished.
  728. `reduce`::
  729. This is not currently used and will always report `0`. Currently aggregation profiling only times the shard level parts of the aggregation execution. Timing of the reduce phase will be added later.
  730. `*_count`::
  731. Records the number of invocations of the particular method. For example, `"collect_count": 2,`
  732. means the `collect()` method was called on two different documents.
  733. === Profiling Considerations
  734. ==== Performance Notes
  735. Like any profiler, the Profile API introduces a non-negligible overhead to search execution. The act of instrumenting
  736. low-level method calls such as `collect`, `advance` and `next_doc` can be fairly expensive, since these methods are called
  737. in tight loops. Therefore, profiling should not be enabled in production settings by default, and should not
  738. be compared against non-profiled query times. Profiling is just a diagnostic tool.
  739. There are also cases where special Lucene optimizations are disabled, since they are not amenable to profiling. This
  740. could cause some queries to report larger relative times than their non-profiled counterparts, but in general should
  741. not have a drastic effect compared to other components in the profiled query.
  742. ==== Limitations
  743. - Profiling statistics are currently not available for suggestions, highlighting, `dfs_query_then_fetch`
  744. - Profiling of the reduce phase of aggregation is currently not available
  745. - The Profiler is still highly experimental. The Profiler is instrumenting parts of Lucene that were
  746. never designed to be exposed in this manner, and so all results should be viewed as a best effort to provide detailed
  747. diagnostics. We hope to improve this over time. If you find obviously wrong numbers, strange query structures or
  748. other bugs, please report them!