search-speed.asciidoc 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567
  1. [[tune-for-search-speed]]
  2. == Tune for search speed
  3. [discrete]
  4. === Give memory to the filesystem cache
  5. Elasticsearch heavily relies on the filesystem cache in order to make search
  6. fast. In general, you should make sure that at least half the available memory
  7. goes to the filesystem cache so that Elasticsearch can keep hot regions of the
  8. index in physical memory.
  9. [discrete]
  10. tag::readahead[]
  11. === Avoid page cache thrashing by using modest readahead values on Linux
  12. Search can cause a lot of randomized read I/O. When the underlying block
  13. device has a high readahead value, there may be a lot of unnecessary
  14. read I/O done, especially when files are accessed using memory mapping
  15. (see <<file-system,storage types>>).
  16. Most Linux distributions use a sensible readahead value of `128KiB` for a
  17. single plain device, however, when using software raid, LVM or dm-crypt the
  18. resulting block device (backing Elasticsearch <<path-settings,path.data>>)
  19. may end up having a very large readahead value (in the range of several MiB).
  20. This usually results in severe page (filesystem) cache thrashing adversely
  21. affecting search (or <<docs,update>>) performance.
  22. You can check the current value in `KiB` using
  23. `lsblk -o NAME,RA,MOUNTPOINT,TYPE,SIZE`.
  24. Consult the documentation of your distribution on how to alter this value
  25. (for example with a `udev` rule to persist across reboots, or via
  26. https://man7.org/linux/man-pages/man8/blockdev.8.html[blockdev --setra]
  27. as a transient setting). We recommend a value of `128KiB` for readahead.
  28. WARNING: `blockdev` expects values in 512 byte sectors whereas `lsblk` reports
  29. values in `KiB`. As an example, to temporarily set readahead to `128KiB`
  30. for `/dev/nvme0n1`, specify `blockdev --setra 256 /dev/nvme0n1`.
  31. end::readahead[]
  32. [discrete]
  33. === Use faster hardware
  34. If your searches are I/O-bound, consider increasing the size of the filesystem
  35. cache (see above) or using faster storage. Each search involves a mix of
  36. sequential and random reads across multiple files, and there may be many
  37. searches running concurrently on each shard, so SSD drives tend to perform
  38. better than spinning disks.
  39. Directly-attached (local) storage generally performs better than remote storage
  40. because it is simpler to configure well and avoids communications overheads.
  41. With careful tuning it is sometimes possible to achieve acceptable performance
  42. using remote storage too. Benchmark your system with a realistic workload to
  43. determine the effects of any tuning parameters. If you cannot achieve the
  44. performance you expect, work with the vendor of your storage system to identify
  45. the problem.
  46. If your searches are CPU-bound, consider using a larger number of faster CPUs.
  47. [discrete]
  48. === Document modeling
  49. Documents should be modeled so that search-time operations are as cheap as possible.
  50. In particular, joins should be avoided. <<nested,`nested`>> can make queries
  51. several times slower and <<parent-join,parent-child>> relations can make
  52. queries hundreds of times slower. So if the same questions can be answered without
  53. joins by denormalizing documents, significant speedups can be expected.
  54. [discrete]
  55. [[search-as-few-fields-as-possible]]
  56. === Search as few fields as possible
  57. The more fields a <<query-dsl-query-string-query,`query_string`>> or
  58. <<query-dsl-multi-match-query,`multi_match`>> query targets, the slower it is.
  59. A common technique to improve search speed over multiple fields is to copy
  60. their values into a single field at index time, and then use this field at
  61. search time. This can be automated with the <<copy-to,`copy-to`>> directive of
  62. mappings without having to change the source of documents. Here is an example
  63. of an index containing movies that optimizes queries that search over both the
  64. name and the plot of the movie by indexing both values into the `name_and_plot`
  65. field.
  66. [source,console]
  67. --------------------------------------------------
  68. PUT movies
  69. {
  70. "mappings": {
  71. "properties": {
  72. "name_and_plot": {
  73. "type": "text"
  74. },
  75. "name": {
  76. "type": "text",
  77. "copy_to": "name_and_plot"
  78. },
  79. "plot": {
  80. "type": "text",
  81. "copy_to": "name_and_plot"
  82. }
  83. }
  84. }
  85. }
  86. --------------------------------------------------
  87. [discrete]
  88. === Pre-index data
  89. You should leverage patterns in your queries to optimize the way data is indexed.
  90. For instance, if all your documents have a `price` field and most queries run
  91. <<search-aggregations-bucket-range-aggregation,`range`>> aggregations on a fixed
  92. list of ranges, you could make this aggregation faster by pre-indexing the ranges
  93. into the index and using a <<search-aggregations-bucket-terms-aggregation,`terms`>>
  94. aggregations.
  95. For instance, if documents look like:
  96. [source,console]
  97. --------------------------------------------------
  98. PUT index/_doc/1
  99. {
  100. "designation": "spoon",
  101. "price": 13
  102. }
  103. --------------------------------------------------
  104. and search requests look like:
  105. [source,console]
  106. --------------------------------------------------
  107. GET index/_search
  108. {
  109. "aggs": {
  110. "price_ranges": {
  111. "range": {
  112. "field": "price",
  113. "ranges": [
  114. { "to": 10 },
  115. { "from": 10, "to": 100 },
  116. { "from": 100 }
  117. ]
  118. }
  119. }
  120. }
  121. }
  122. --------------------------------------------------
  123. // TEST[continued]
  124. Then documents could be enriched by a `price_range` field at index time, which
  125. should be mapped as a <<keyword,`keyword`>>:
  126. [source,console]
  127. --------------------------------------------------
  128. PUT index
  129. {
  130. "mappings": {
  131. "properties": {
  132. "price_range": {
  133. "type": "keyword"
  134. }
  135. }
  136. }
  137. }
  138. PUT index/_doc/1
  139. {
  140. "designation": "spoon",
  141. "price": 13,
  142. "price_range": "10-100"
  143. }
  144. --------------------------------------------------
  145. And then search requests could aggregate this new field rather than running a
  146. `range` aggregation on the `price` field.
  147. [source,console]
  148. --------------------------------------------------
  149. GET index/_search
  150. {
  151. "aggs": {
  152. "price_ranges": {
  153. "terms": {
  154. "field": "price_range"
  155. }
  156. }
  157. }
  158. }
  159. --------------------------------------------------
  160. // TEST[continued]
  161. [discrete]
  162. [[map-ids-as-keyword]]
  163. === Consider mapping identifiers as `keyword`
  164. include::../mapping/types/numeric.asciidoc[tag=map-ids-as-keyword]
  165. [discrete]
  166. === Avoid scripts
  167. If possible, avoid using <<modules-scripting,script>>-based sorting, scripts in
  168. aggregations, and the <<query-dsl-script-score-query,`script_score`>> query. See
  169. <<scripts-and-search-speed>>.
  170. [discrete]
  171. === Search rounded dates
  172. Queries on date fields that use `now` are typically not cacheable since the
  173. range that is being matched changes all the time. However switching to a
  174. rounded date is often acceptable in terms of user experience, and has the
  175. benefit of making better use of the query cache.
  176. For instance the below query:
  177. [source,console]
  178. --------------------------------------------------
  179. PUT index/_doc/1
  180. {
  181. "my_date": "2016-05-11T16:30:55.328Z"
  182. }
  183. GET index/_search
  184. {
  185. "query": {
  186. "constant_score": {
  187. "filter": {
  188. "range": {
  189. "my_date": {
  190. "gte": "now-1h",
  191. "lte": "now"
  192. }
  193. }
  194. }
  195. }
  196. }
  197. }
  198. --------------------------------------------------
  199. could be replaced with the following query:
  200. [source,console]
  201. --------------------------------------------------
  202. GET index/_search
  203. {
  204. "query": {
  205. "constant_score": {
  206. "filter": {
  207. "range": {
  208. "my_date": {
  209. "gte": "now-1h/m",
  210. "lte": "now/m"
  211. }
  212. }
  213. }
  214. }
  215. }
  216. }
  217. --------------------------------------------------
  218. // TEST[continued]
  219. In that case we rounded to the minute, so if the current time is `16:31:29`,
  220. the range query will match everything whose value of the `my_date` field is
  221. between `15:31:00` and `16:31:59`. And if several users run a query that
  222. contains this range in the same minute, the query cache could help speed things
  223. up a bit. The longer the interval that is used for rounding, the more the query
  224. cache can help, but beware that too aggressive rounding might also hurt user
  225. experience.
  226. NOTE: It might be tempting to split ranges into a large cacheable part and
  227. smaller not cacheable parts in order to be able to leverage the query cache,
  228. as shown below:
  229. [source,console]
  230. --------------------------------------------------
  231. GET index/_search
  232. {
  233. "query": {
  234. "constant_score": {
  235. "filter": {
  236. "bool": {
  237. "should": [
  238. {
  239. "range": {
  240. "my_date": {
  241. "gte": "now-1h",
  242. "lte": "now-1h/m"
  243. }
  244. }
  245. },
  246. {
  247. "range": {
  248. "my_date": {
  249. "gt": "now-1h/m",
  250. "lt": "now/m"
  251. }
  252. }
  253. },
  254. {
  255. "range": {
  256. "my_date": {
  257. "gte": "now/m",
  258. "lte": "now"
  259. }
  260. }
  261. }
  262. ]
  263. }
  264. }
  265. }
  266. }
  267. }
  268. --------------------------------------------------
  269. // TEST[continued]
  270. However such practice might make the query run slower in some cases since the
  271. overhead introduced by the `bool` query may defeat the savings from better
  272. leveraging the query cache.
  273. [discrete]
  274. === Force-merge read-only indices
  275. Indices that are read-only may benefit from being <<indices-forcemerge,merged
  276. down to a single segment>>. This is typically the case with time-based indices:
  277. only the index for the current time frame is getting new documents while older
  278. indices are read-only. Shards that have been force-merged into a single segment
  279. can use simpler and more efficient data structures to perform searches.
  280. IMPORTANT: Do not force-merge indices to which you are still writing, or to
  281. which you will write again in the future. Instead, rely on the automatic
  282. background merge process to perform merges as needed to keep the index running
  283. smoothly. If you continue to write to a force-merged index then its performance
  284. may become much worse.
  285. [discrete]
  286. === Warm up global ordinals
  287. <<eager-global-ordinals,Global ordinals>> are a data structure that is used to
  288. optimize the performance of aggregations. They are calculated lazily and stored in
  289. the JVM heap as part of the <<modules-fielddata, field data cache>>. For fields
  290. that are heavily used for bucketing aggregations, you can tell {es} to construct
  291. and cache the global ordinals before requests are received. This should be done
  292. carefully because it will increase heap usage and can make <<indices-refresh, refreshes>>
  293. take longer. The option can be updated dynamically on an existing mapping by
  294. setting the <<eager-global-ordinals, eager global ordinals>> mapping parameter:
  295. [source,console]
  296. --------------------------------------------------
  297. PUT index
  298. {
  299. "mappings": {
  300. "properties": {
  301. "foo": {
  302. "type": "keyword",
  303. "eager_global_ordinals": true
  304. }
  305. }
  306. }
  307. }
  308. --------------------------------------------------
  309. tag::warm-fs-cache[]
  310. [discrete]
  311. === Warm up the filesystem cache
  312. If the machine running Elasticsearch is restarted, the filesystem cache will be
  313. empty, so it will take some time before the operating system loads hot regions
  314. of the index into memory so that search operations are fast. You can explicitly
  315. tell the operating system which files should be loaded into memory eagerly
  316. depending on the file extension using the
  317. <<preload-data-to-file-system-cache,`index.store.preload`>> setting.
  318. WARNING: Loading data into the filesystem cache eagerly on too many indices or
  319. too many files will make search _slower_ if the filesystem cache is not large
  320. enough to hold all the data. Use with caution.
  321. end::warm-fs-cache[]
  322. [discrete]
  323. === Use index sorting to speed up conjunctions
  324. <<index-modules-index-sorting,Index sorting>> can be useful in order to make
  325. conjunctions faster at the cost of slightly slower indexing. Read more about it
  326. in the <<index-modules-index-sorting-conjunctions,index sorting documentation>>.
  327. [discrete]
  328. [[preference-cache-optimization]]
  329. === Use `preference` to optimize cache utilization
  330. There are multiple caches that can help with search performance, such as the
  331. {wikipedia}/Page_cache[filesystem cache], the
  332. <<shard-request-cache,request cache>> or the <<query-cache,query cache>>. Yet
  333. all these caches are maintained at the node level, meaning that if you run the
  334. same request twice in a row, have 1 replica or more
  335. and use {wikipedia}/Round-robin_DNS[round-robin], the default
  336. routing algorithm, then those two requests will go to different shard copies,
  337. preventing node-level caches from helping.
  338. Since it is common for users of a search application to run similar requests
  339. one after another, for instance in order to analyze a narrower subset of the
  340. index, using a preference value that identifies the current user or session
  341. could help optimize usage of the caches.
  342. [discrete]
  343. === Replicas might help with throughput, but not always
  344. In addition to improving resiliency, replicas can help improve throughput. For
  345. instance if you have a single-shard index and three nodes, you will need to
  346. set the number of replicas to 2 in order to have 3 copies of your shard in
  347. total so that all nodes are utilized.
  348. Now imagine that you have a 2-shards index and two nodes. In one case, the
  349. number of replicas is 0, meaning that each node holds a single shard. In the
  350. second case the number of replicas is 1, meaning that each node has two shards.
  351. Which setup is going to perform best in terms of search performance? Usually,
  352. the setup that has fewer shards per node in total will perform better. The
  353. reason for that is that it gives a greater share of the available filesystem
  354. cache to each shard, and the filesystem cache is probably Elasticsearch's
  355. number 1 performance factor. At the same time, beware that a setup that does
  356. not have replicas is subject to failure in case of a single node failure, so
  357. there is a trade-off between throughput and availability.
  358. So what is the right number of replicas? If you have a cluster that has
  359. `num_nodes` nodes, `num_primaries` primary shards _in total_ and if you want to
  360. be able to cope with `max_failures` node failures at once at most, then the
  361. right number of replicas for you is
  362. `max(max_failures, ceil(num_nodes / num_primaries) - 1)`.
  363. === Tune your queries with the Search Profiler
  364. The {ref}/search-profile.html[Profile API] provides detailed information about
  365. how each component of your queries and aggregations impacts the time it takes
  366. to process the request.
  367. The {kibana-ref}/xpack-profiler.html[Search Profiler] in {kib}
  368. makes it easy to navigate and analyze the profile results and
  369. give you insight into how to tune your queries to improve performance and reduce load.
  370. Because the Profile API itself adds significant overhead to the query,
  371. this information is best used to understand the relative cost of the various
  372. query components. It does not provide a reliable measure of actual processing time.
  373. [[faster-phrase-queries]]
  374. === Faster phrase queries with `index_phrases`
  375. The <<text,`text`>> field has an <<index-phrases,`index_phrases`>> option that
  376. indexes 2-shingles and is automatically leveraged by query parsers to run phrase
  377. queries that don't have a slop. If your use-case involves running lots of phrase
  378. queries, this can speed up queries significantly.
  379. [[faster-prefix-queries]]
  380. === Faster prefix queries with `index_prefixes`
  381. The <<text,`text`>> field has an <<index-prefixes,`index_prefixes`>> option that
  382. indexes prefixes of all terms and is automatically leveraged by query parsers to
  383. run prefix queries. If your use-case involves running lots of prefix queries,
  384. this can speed up queries significantly.
  385. [[faster-filtering-with-constant-keyword]]
  386. === Use `constant_keyword` to speed up filtering
  387. There is a general rule that the cost of a filter is mostly a function of the
  388. number of matched documents. Imagine that you have an index containing cycles.
  389. There are a large number of bicycles and many searches perform a filter on
  390. `cycle_type: bicycle`. This very common filter is unfortunately also very costly
  391. since it matches most documents. There is a simple way to avoid running this
  392. filter: move bicycles to their own index and filter bicycles by searching this
  393. index instead of adding a filter to the query.
  394. Unfortunately this can make client-side logic tricky, which is where
  395. `constant_keyword` helps. By mapping `cycle_type` as a `constant_keyword` with
  396. value `bicycle` on the index that contains bicycles, clients can keep running
  397. the exact same queries as they used to run on the monolithic index and
  398. Elasticsearch will do the right thing on the bicycles index by ignoring filters
  399. on `cycle_type` if the value is `bicycle` and returning no hits otherwise.
  400. Here is what mappings could look like:
  401. [source,console]
  402. --------------------------------------------------
  403. PUT bicycles
  404. {
  405. "mappings": {
  406. "properties": {
  407. "cycle_type": {
  408. "type": "constant_keyword",
  409. "value": "bicycle"
  410. },
  411. "name": {
  412. "type": "text"
  413. }
  414. }
  415. }
  416. }
  417. PUT other_cycles
  418. {
  419. "mappings": {
  420. "properties": {
  421. "cycle_type": {
  422. "type": "keyword"
  423. },
  424. "name": {
  425. "type": "text"
  426. }
  427. }
  428. }
  429. }
  430. --------------------------------------------------
  431. We are splitting our index in two: one that will contain only bicycles, and
  432. another one that contains other cycles: unicycles, tricycles, etc. Then at
  433. search time, we need to search both indices, but we don't need to modify
  434. queries.
  435. [source,console]
  436. --------------------------------------------------
  437. GET bicycles,other_cycles/_search
  438. {
  439. "query": {
  440. "bool": {
  441. "must": {
  442. "match": {
  443. "description": "dutch"
  444. }
  445. },
  446. "filter": {
  447. "term": {
  448. "cycle_type": "bicycle"
  449. }
  450. }
  451. }
  452. }
  453. }
  454. --------------------------------------------------
  455. // TEST[continued]
  456. On the `bicycles` index, Elasticsearch will simply ignore the `cycle_type`
  457. filter and rewrite the search request to the one below:
  458. [source,console]
  459. --------------------------------------------------
  460. GET bicycles,other_cycles/_search
  461. {
  462. "query": {
  463. "match": {
  464. "description": "dutch"
  465. }
  466. }
  467. }
  468. --------------------------------------------------
  469. // TEST[continued]
  470. On the `other_cycles` index, Elasticsearch will quickly figure out that
  471. `bicycle` doesn't exist in the terms dictionary of the `cycle_type` field and
  472. return a search response with no hits.
  473. This is a powerful way of making queries cheaper by putting common values in a
  474. dedicated index. This idea can also be combined across multiple fields: for
  475. instance if you track the color of each cycle and your `bicycles` index ends up
  476. having a majority of black bikes, you could split it into a `bicycles-black`
  477. and a `bicycles-other-colors` indices.
  478. The `constant_keyword` is not strictly required for this optimization: it is
  479. also possible to update the client-side logic in order to route queries to the
  480. relevant indices based on filters. However `constant_keyword` makes it
  481. transparently and allows to decouple search requests from the index topology in
  482. exchange of very little overhead.