调整搜索速度
edit调整搜索速度
edit将内存分配给文件系统缓存
editElasticsearch 严重依赖于文件系统缓存,以确保搜索速度快。通常,您应确保至少一半的可用内存用于文件系统缓存,以便 Elasticsearch 可以将索引的热门区域保留在物理内存中。
在Linux上使用适度的预读值以避免页面缓存抖动
edit搜索可能会导致大量的随机读取I/O。当底层块设备具有较高的预读值时,可能会执行大量不必要的读取I/O,特别是在使用内存映射访问文件时(参见存储类型)。
大多数Linux发行版对单个普通设备使用合理的预读值128KiB,然而,当使用软件RAID、LVM或dm-crypt时,生成的块设备(支持Elasticsearch path.data)可能会具有非常大的预读值(在几MiB的范围内)。这通常会导致严重的页面(文件系统)缓存抖动,从而对搜索(或更新)性能产生不利影响。
您可以使用 lsblk -o NAME,RA,MOUNTPOINT,TYPE,SIZE 检查当前的 KiB 值。
请查阅您发行版的文档,了解如何更改此值(例如,使用 udev 规则以在重启后保持,或通过 blockdev --setra 作为临时设置)。我们建议将 readahead 值设置为 128KiB。
blockdev 期望值以512字节扇区为单位,而 lsblk 报告的值以 KiB 为单位。例如,要暂时将预读设置为 128KiB 对于 /dev/nvme0n1,请指定 blockdev --setra 256 /dev/nvme0n1。
使用更快的硬件
edit如果你的搜索是I/O密集型的,考虑增加文件系统缓存的大小(见上文)或使用更快的存储设备。每次搜索都涉及多个文件的顺序和随机读取,并且每个分片上可能同时运行许多搜索,因此固态硬盘(SSD)通常比机械硬盘(HDD)表现更好。
如果您的搜索是CPU密集型的,考虑使用更多数量的更快的CPU。
本地存储 vs. 远程存储
edit直接附加(本地)存储通常比远程存储性能更好,因为它更容易配置良好,并且避免了通信开销。
一些远程存储的性能非常差,尤其是在Elasticsearch施加的负载下。然而,通过仔细调优,有时也可以在使用远程存储时达到可接受的性能。在承诺使用特定的存储架构之前,请使用实际工作负载对系统进行基准测试,以确定任何调优参数的影响。如果无法达到预期的性能,请与您的存储系统供应商合作,以识别问题所在。
文档建模
edit文档应建模,以便搜索时操作尽可能便宜。
特别是,应避免使用连接。嵌套可以使查询速度变慢几倍,而父子关系可以使查询速度变慢数百倍。因此,如果可以通过非规范化文档来回答相同的问题而不使用连接,则可以预期显著的速度提升。
尽可能少搜索字段
edit一个 query_string 或
multi_match 查询所针对的字段越多,速度就越慢。
提高在多个字段上搜索速度的常见技术是在索引时将它们的值复制到一个单独的字段中,然后在搜索时使用这个字段。这可以通过映射中的 copy-to 指令来自动化,而无需更改文档的源。以下是一个包含电影的索引示例,通过将电影的名称和剧情索引到 name_and_plot 字段中,优化了在名称和剧情上进行搜索的查询。
PUT movies
{
"mappings": {
"properties": {
"name_and_plot": {
"type": "text"
},
"name": {
"type": "text",
"copy_to": "name_and_plot"
},
"plot": {
"type": "text",
"copy_to": "name_and_plot"
}
}
}
}
预索引数据
edit您应该利用查询中的模式来优化数据索引的方式。
例如,如果所有文档都有一个price字段,并且大多数查询在固定范围列表上运行
range聚合,您可以通过预先将范围索引到索引中并使用terms
聚合来加快此聚合速度。
例如,如果文档看起来像:
PUT index/_doc/1
{
"designation": "spoon",
"price": 13
}
搜索请求如下所示:
GET index/_search
{
"aggs": {
"price_ranges": {
"range": {
"field": "price",
"ranges": [
{ "to": 10 },
{ "from": 10, "to": 100 },
{ "from": 100 }
]
}
}
}
}
然后可以在索引时通过一个 price_range 字段来丰富文档,该字段应映射为 keyword:
PUT index
{
"mappings": {
"properties": {
"price_range": {
"type": "keyword"
}
}
}
}
PUT index/_doc/1
{
"designation": "spoon",
"price": 13,
"price_range": "10-100"
}
然后搜索请求可以聚合这个新字段,而不是在price字段上运行range聚合。
GET index/_search
{
"aggs": {
"price_ranges": {
"terms": {
"field": "price_range"
}
}
}
}
考虑将标识符映射为keyword
edit并非所有数值数据都应映射为数值字段数据类型。
Elasticsearch 优化了数值字段,例如 integer 或 long,用于
range 查询。然而,keyword 字段
更适合 term 和其他
词项级别查询。
标识符,如ISBN或产品ID,很少用于范围查询。然而,它们通常使用术语级查询来检索。
如果满足以下条件,请考虑将数值标识符映射为keyword:
-
您不打算使用
range查询来搜索标识符数据。 -
快速检索很重要。
term查询在keyword字段上的搜索通常比在数值字段上的term搜索更快。
如果你不确定使用哪一个,你可以使用一个多字段来将数据映射为keyword 和数字数据类型。
避免脚本
edit如果可能,避免使用基于脚本的排序、聚合中的脚本以及script_score查询。请参阅脚本、缓存和搜索速度。
搜索四舍五入的日期
edit使用 now 的日期字段查询通常是不可缓存的,因为匹配的范围一直在变化。然而,切换到四舍五入的日期通常在用户体验上是可接受的,并且可以更好地利用查询缓存。
例如下面的查询:
PUT index/_doc/1
{
"my_date": "2016-05-11T16:30:55.328Z"
}
GET index/_search
{
"query": {
"constant_score": {
"filter": {
"range": {
"my_date": {
"gte": "now-1h",
"lte": "now"
}
}
}
}
}
}
可以替换为以下查询:
GET index/_search
{
"query": {
"constant_score": {
"filter": {
"range": {
"my_date": {
"gte": "now-1h/m",
"lte": "now/m"
}
}
}
}
}
}
在这种情况下,我们四舍五入到分钟,所以如果当前时间是 16:31:29,
范围查询将匹配 my_date 字段值在 15:31:00 和 16:31:59 之间的所有内容。如果多个用户在同一分钟内运行包含此范围的查询,查询缓存可以帮助加快一点速度。用于四舍五入的时间间隔越长,查询缓存的帮助就越大,但要注意过于激进的四舍五入也可能会损害用户体验。
为了能够利用查询缓存,可能会诱使将范围分割成一个较大的可缓存部分和较小的不可缓存部分,如下所示:
GET index/_search
{
"query": {
"constant_score": {
"filter": {
"bool": {
"should": [
{
"range": {
"my_date": {
"gte": "now-1h",
"lte": "now-1h/m"
}
}
},
{
"range": {
"my_date": {
"gt": "now-1h/m",
"lt": "now/m"
}
}
},
{
"range": {
"my_date": {
"gte": "now/m",
"lte": "now"
}
}
}
]
}
}
}
}
}
然而,这种做法在某些情况下可能会使查询运行得更慢,因为bool查询引入的开销可能会抵消更好地利用查询缓存所带来的节省。
强制合并只读索引
edit只读索引可能会受益于被合并到一个段。这通常是基于时间的索引的情况:只有当前时间段的索引会接收新文档,而较旧的索引是只读的。已经被强制合并到一个段的 shards 可以使用更简单和更高效的数据结构来执行搜索。
不要强制合并你仍在写入或将来会再次写入的索引。相反,依赖自动后台合并过程来根据需要执行合并,以保持索引的顺利运行。如果你继续向强制合并的索引写入,那么它的性能可能会变得非常差。
预热全局序数
edit全局序号是一种用于优化聚合性能的数据结构。它们是惰性计算的,并作为字段数据缓存的一部分存储在JVM堆中。对于那些频繁用于分桶聚合的字段,您可以告诉Elasticsearch在接收到请求之前构建并缓存全局序号。这应该谨慎进行,因为它会增加堆的使用量,并可能使刷新时间变长。可以通过在现有映射上设置eager global ordinals映射参数来动态更新此选项:
PUT index
{
"mappings": {
"properties": {
"foo": {
"type": "keyword",
"eager_global_ordinals": true
}
}
}
}
预热文件系统缓存
edit如果运行 Elasticsearch 的机器重新启动,文件系统缓存将会是空的,因此需要一些时间才能将索引的热门区域加载到内存中,以便搜索操作能够快速执行。您可以明确地告诉操作系统应该根据文件扩展名将哪些文件急切地加载到内存中,使用 index.store.preload 设置。
在太多索引或太多文件上急切地将数据加载到文件系统缓存中,如果文件系统缓存不够大以容纳所有数据,将会使搜索变慢。请谨慎使用。
使用索引排序来加速连接
edit索引排序可以在稍微降低索引速度的情况下,使连接操作更快。更多信息请参阅索引排序文档。
使用 preference 优化缓存利用率
edit有多种缓存可以提高搜索性能,例如 文件系统缓存、 请求缓存 或 查询缓存。然而, 所有这些缓存都是在节点级别维护的,这意味着如果您连续运行两次相同的请求,拥有1个或更多副本, 并使用默认的路由算法 轮询, 那么这两个请求将转到不同的分片副本,从而阻止节点级别的缓存发挥作用。
由于搜索应用程序的用户通常会一个接一个地运行类似的请求,例如为了分析索引的较窄子集,使用一个标识当前用户或会话的首选项值可以帮助优化缓存的使用。
副本可能有助于提高吞吐量,但并非总是如此
edit除了提高弹性,副本还可以帮助提高吞吐量。例如,如果你有一个单分片索引和三个节点,你需要将副本数量设置为2,以便总共有3个分片的副本,从而充分利用所有节点。
现在想象一下,你有一个2分片的索引和两个节点。在第一种情况下,副本数量为0,这意味着每个节点持有一个分片。在第二种情况下,副本数量为1,这意味着每个节点有两个分片。哪种设置在搜索性能方面表现最佳?通常,每个节点总分片数较少的设置会表现更好。原因是它为每个分片提供了更大的可用文件系统缓存份额,而文件系统缓存可能是Elasticsearch性能的第一大因素。同时,请注意,没有副本的设置在单个节点故障的情况下容易失败,因此在吞吐量和可用性之间存在权衡。
那么,正确的副本数量是多少?如果你有一个包含
num_nodes 个节点、num_primaries 个主分片 总共 的集群,并且你希望最多能够同时应对 max_failures 个节点故障,那么适合你的副本数量是
max(max_failures, ceil(num_nodes / num_primaries) - 1)。
使用搜索分析器调整您的查询
editThe Profile API 提供了关于查询和聚合的每个组件如何影响处理请求所需时间的详细信息。
Kibana 中的 Search Profiler 使得浏览和分析配置文件结果变得容易,并为您提供如何调整查询以提高性能和减少负载的见解。
由于Profile API本身会增加显著的开销到查询中, 这个信息最好用于理解各个查询组件的相对成本。它并不提供实际处理时间的可靠度量。
使用index_phrases加快短语查询
editThe text 字段有一个 index_phrases 选项,该选项索引2-shingles,并由查询解析器自动利用来运行没有slop的短语查询。如果你的用例涉及运行大量短语查询,这可以显著加快查询速度。
使用index_prefixes加速前缀查询
editThe text 字段有一个 index_prefixes 选项,该选项会索引所有词项的前缀,并由查询解析器自动利用来运行前缀查询。如果你的用例涉及运行大量前缀查询,这可以显著加快查询速度。
使用 constant_keyword 加速过滤
edit有一个普遍的规则,即过滤器的成本主要是匹配文档数量的函数。想象一下,你有一个包含各种自行车的索引。有很多自行车,许多搜索都会对cycle_type: bicycle进行过滤。这个非常常见的过滤器不幸的是也非常昂贵,因为它匹配了大多数文档。有一个简单的方法可以避免运行这个过滤器:将自行车移动到它们自己的索引中,并通过搜索这个索引来过滤自行车,而不是在查询中添加过滤器。
不幸的是,这可能会使客户端逻辑变得复杂,这就是constant_keyword发挥作用的地方。通过将cycle_type映射为constant_keyword,并在包含自行车的索引上设置值为bicycle,客户端可以继续运行与在单体索引上相同的查询,而Elasticsearch将在自行车索引上做正确的事情,即如果值为bicycle则忽略对cycle_type的过滤,否则不返回任何结果。
以下是映射可能的样子:
PUT bicycles
{
"mappings": {
"properties": {
"cycle_type": {
"type": "constant_keyword",
"value": "bicycle"
},
"name": {
"type": "text"
}
}
}
}
PUT other_cycles
{
"mappings": {
"properties": {
"cycle_type": {
"type": "keyword"
},
"name": {
"type": "text"
}
}
}
}
我们将索引分为两个:一个仅包含自行车,另一个包含其他类型的自行车:独轮车、三轮车等。然后在搜索时,我们需要搜索这两个索引,但我们不需要修改查询。
GET bicycles,other_cycles/_search
{
"query": {
"bool": {
"must": {
"match": {
"description": "dutch"
}
},
"filter": {
"term": {
"cycle_type": "bicycle"
}
}
}
}
}
在 bicycles 索引上,Elasticsearch 将简单地忽略 cycle_type 过滤器并重写搜索请求为以下内容:
GET bicycles,other_cycles/_search
{
"query": {
"match": {
"description": "dutch"
}
}
}
在other_cycles索引上,Elasticsearch会很快发现bicycle不在cycle_type字段的词典中,并返回一个没有命中的搜索响应。
这是一种通过将常用值放入专用索引中来降低查询成本的强大方法。这个想法也可以跨多个字段结合使用:例如,如果您跟踪每辆自行车的颜色,并且您的 bicycles 索引最终有大多数黑色自行车,您可以将其拆分为 bicycles-black 和 bicycles-other-colors 索引。
对于这种优化,constant_keyword 并不是严格必需的:也可以通过更新客户端逻辑来根据过滤器将查询路由到相关的索引。然而,constant_keyword 使得这一过程变得透明,并允许将搜索请求与索引拓扑解耦,代价是非常小的开销。