Elasticsearch heavily relies on the filesystem cache in order to make search
fast. In general, you should make sure that at least half the available memory
goes to the filesystem cache so that Elasticsearch can keep hot regions of the
index in physical memory.

If your search is I/O bound, you should investigate giving more memory to the
filesystem cache (see above) or buying faster drives. In particular SSD drives
are known to perform better than spinning disks. Always use local storage,
remote filesystems such as NFS or SMB should be avoided. Also beware of
virtualized storage such as Amazon’s Elastic Block Storage. Virtualized
storage works very well with Elasticsearch, and it is appealing since it is so
fast and simple to set up, but it is also unfortunately inherently slower on an
ongoing basis when compared to dedicated local storage. If you put an index on
EBS, be sure to use provisioned IOPS otherwise operations could be quickly
throttled.

If your search is CPU-bound, you should investigate buying faster CPUs.

Documents should be modeled so that search-time operations are as cheap as possible.

In particular, joins should be avoided. nested can make queries
several times slower and parent-child relations can make
queries hundreds of times slower. So if the same questions can be answered without
joins by denormalizing documents, significant speedups can be expected.

The more fields a query_string or
multi_match query targets, the slower it is.
A common technique to improve search speed over multiple fields is to copy
their values into a single field at index time, and then use this field at
search time. This can be automated with the copy-to directive of
mappings without having to change the source of documents. Here is an example
of an index containing movies that optimizes queries that search over both the
name and the plot of the movie by indexing both values into the name_and_plot
field.

You should leverage patterns in your queries to optimize the way data is indexed.
For instance, if all your documents have a price field and most queries run
range aggregations on a fixed
list of ranges, you could make this aggregation faster by pre-indexing the ranges
into the index and using a terms
aggregations.

The fact that some data is numeric does not mean it should always be mapped as a
numeric field. The way that Elasticsearch indexes numbers optimizes
for range queries while keyword fields are better at term queries. Typically,
fields storing identifiers such as an ISBN or any number identifying a record
from another database are rarely used in range queries or aggregations. This is
why they might benefit from being mapped as keyword rather than as
integer or long.

Queries on date fields that use now are typically not cacheable since the
range that is being matched changes all the time. However switching to a
rounded date is often acceptable in terms of user experience, and has the
benefit of making better use of the query cache.

In that case we rounded to the minute, so if the current time is 16:31:29,
the range query will match everything whose value of the my_date field is
between 15:31:00 and 16:31:59. And if several users run a query that
contains this range in the same minute, the query cache could help speed things
up a bit. The longer the interval that is used for rounding, the more the query
cache can help, but beware that too aggressive rounding might also hurt user
experience.

It might be tempting to split ranges into a large cacheable part and
smaller not cacheable parts in order to be able to leverage the query cache,
as shown below:

Indices that are read-only would benefit from being
merged down to a single segment. This is typically the
case with time-based indices: only the index for the current time frame is
getting new documents while older indices are read-only.

Don’t force-merge indices that are still being written to — leave
merging to the background merge process.

Global ordinals are a data-structure that is used in order to run
terms aggregations on
keyword fields. They are loaded lazily in memory because
Elasticsearch does not know which fields will be used in terms aggregations
and which fields won’t. You can tell Elasticsearch to load global ordinals
eagerly at refresh-time by configuring mappings as described below:

If the machine running Elasticsearch is restarted, the filesystem cache will be
empty, so it will take some time before the operating system loads hot regions
of the index into memory so that search operations are fast. You can explicitly
tell the operating system which files should be loaded into memory eagerly
depending on the file extension using the index.store.preload
setting.

Loading data into the filesystem cache eagerly on too many indices or
too many files will make search slower if the filesystem cache is not large
enough to hold all the data. Use with caution.

There are multiple caches that can help with search performance, such as the
filesystem cache, the
request cache or the query cache. Yet
all these caches are maintained at the node level, meaning that if you run the
same request twice in a row, have 1 replica or more
and use round-robin, the default
routing algorithm, then those two requests will go to different shard copies,
preventing node-level caches from helping.

Since it is common for users of a search application to run similar requests
one after another, for instance in order to analyze a narrower subset of the
index, using a preference value that identifies the current user or session
could help optimize usage of the caches.

In addition to improving resiliency, replicas can help improve throughput. For
instance if you have a single-shard index and three nodes, you will need to
set the number of replicas to 2 in order to have 3 copies of your shard in
total so that all nodes are utilized.

Now imagine that you have a 2-shards index and two nodes. In one case, the
number of replicas is 0, meaning that each node holds a single shard. In the
second case the number of replicas is 1, meaning that each node has two shards.
Which setup is going to perform best in terms of search performance? Usually,
the setup that has fewer shards per node in total will perform better. The
reason for that is that it gives a greater share of the available filesystem
cache to each shard, and the filesystem cache is probably Elasticsearch’s
number 1 performance factor. At the same time, beware that a setup that does
not have replicas is subject to failure in case of a single node failure, so
there is a trade-off between throughput and availability.

So what is the right number of replicas? If you have a cluster that has
num_nodes nodes, num_primaries primary shards in total and if you want to
be able to cope with max_failures node failures at once at most, then the
right number of replicas for you is
max(max_failures, ceil(num_nodes / num_primaries) - 1).

When multiple copies of data are present, elasticsearch can use a set of
criteria called adaptive replica selection to select
the best copy of the data based on response time, service time, and queue size
of the node containing each copy of the shard. This can improve query throughput
and reduce latency for search-heavy applications.