Elasticsearch搜索优化[翻译版]

在上一篇文章中翻译了索引写入,接下来看看索引的查询有哪些优化手段。

原文链接:https://www.elastic.co/guide/en/elasticsearch/reference/current/tune-for-search-speed.html

1.增加文件系统缓存

Elasticsearch严重依赖文件系统缓存来加快查询速度。一般来说,至少需要保留一半的可用内存给文件系统,以便Elasticsearch在物理内存中保留索引热点数据。

2.使用更快的硬件

如果搜索遇到了I/O瓶颈,考虑增加文件系统缓存或者使用更快的存储设备。每次查询涉及随机读和顺序读的混合操作,跨越多个文件,而且每个分片上可能有多个搜索的并发请求,因SSD磁盘比普通硬盘性能更佳。

本地磁盘比网络云盘更加高效,因为配置更加简单而且可以避免频繁的网络通信。经过优化配置,网络云盘有时也能达到所期望的性能。通过真实负载下的基准测试,来确定优化参数是否生效。如果不能达到你所期望的效果,可以联系你的存储供应商来解决问题。

如果你的搜索遇到的是CPU瓶颈,可以考虑增加速度更快的CPU。

3.文档结构化

文档结构越简单搜索成本越低。

在实际使用中,应当避免文档关联。嵌套结构nested可能导致查询速度慢上好几倍,父子文档parent-child关联的查询速度可能要慢几百倍。如果可以使用非规范化文档来规避关联关系,查询速度可以得到显著的提升。

4.只搜索必要的字段

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"
      }
    }
  }
}

5.预索引数据

在搜索和数据存储优化方面需要取得一个平衡。比如说,如果你所有的文档都有一个 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字段进行聚合:

GET index/_search
{
  "aggs": {
    "price_ranges": {
      "terms": {
        "field": "price_range"
      }
    }
  }
}

6.唯一标记使用 keword字段类型

不是所有的数字都应该使用 numeric 字段类型。Elasticsearch对数字类型进行了优化,例如integer 或者 long,在range查询场景下。然而,keyword类型在termterm-level查询中表现更好。

唯一标记,例如ISBN或者产品id,很少使用范围查询,却经常使用term-level查询。

以下情况可以考虑将数字类型的唯一标记存储为keyword类型:

  • 唯一标记不会用于范围查询;
  • 更看重搜索性能。keyword字段类型上的term查询比数字类型要快许多;

如果你不确定使用哪种方式,可以使用multi-field来同存储keywrod和数字类型。

7.避免使用脚本

如果可能,避免使用基于脚本的排序、聚合,以及用脚本计算评分。

8.搜索近似时间

使用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:29my_date字段的范围查询将返回所有从15:31:0016:31:59时间段内的数据。如果同一时间好几个用户的查询条件包含这个范围,查询缓存能够加快查询速度。近似查询的范围越长,缓存的效果越明显,但是需要注意过度的近似值可能会破坏用户体验。

9.强制合并只读索引

强制合并为一个段对只读索引来说是有益的。在时间线索引中比较常见的场景:只有当前时间的索引会新增数据,历史索引是只读的。分片被强制合并为一个段,可以让查询更加简单和有效。

10.预热全局序号

全局序号是用来优化聚合的一种数据结构。他们作为字段缓存的一部分会在JVM中延迟计算和存储。作为分桶查询中被频繁使用的字段,你可以让Elasticsearch在请求到达前实例化和缓存。这个操作应该谨慎使用,因为他会占用更多内存使得refresh变长。这个选项可以在已经创建的索引上动态设置,通过修改eager global ordinals 参数:

PUT index
{
  "mappings": {
    "properties": {
      "foo": {
        "type": "keyword",
        "eager_global_ordinals": true
      }
    }
  }
}

11.预热文件系统缓存

如果运行Elasticsearch的机器重启了,文件系统缓存会被清空,因此操作系统需要花费一些时间来加载热点索引缓存数据到内存,以便加快查询速度。你可以明确的告诉操作系统哪些文件需要提前加载到缓存中,通过index.store.preload 参数来进行指定。

12.使用索引排序来加快连接速度

索引排序在加快连接速度方面很有效,代价是文档写入会变慢。

13.使用preference来优化缓存使用

有多种缓存可以用来加快查询速度,诸如文件系统缓存、请求缓存、查询缓存。这些缓存大多是在节点层面的,意味着如果你连续发起2次相同的请求,有一个或者多个副本而且所有了负载策略,根据默认的路由算法,2次请求会分配到不同的分片节点,节点层面的缓存无法有效利用。

由于搜索程序的用户会一个接着一个的发起类似的查询请求,比如说为了分析索引索引的子集,使用偏好值来标记当前用户或者请求能够帮助优化缓存的使用。

14.副本或许可以提升吞吐量

除了弹性扩展,副本还能提升吞吐量。例如你有一个单一分片索引和三个节点,你需要将副本数量设置为2,这样总共3份副本让每个节点都能充分利用。

现在假设你有2个分片索引和2个节点。第一种情况,副本数量设置为0,意味着每个节点有一个分片。第二种情况副本数量设置为1,意味着每个节点有2个分片。哪种情况能够有更好的查询性能呢?通常情况下,节点的分片数量越少的方案更优。因为能够给每个分片更多的文件系统缓存,而文件系统缓存可能是Elasticsearch最有效的优化策略。与此同时,需要注意物副本的方案在单节点失败情况下的风险,需要权衡吞吐量和可用性。

因此分片数量设置为多少比较合适?如果你的集群有num_nodes个节点,总共有num_primaries个主分片,你期望同时可以应对最多max_failures个节点失败的状况,正确的副本数量结算方式为:max(max_failures, ceil(num_nodes / num_primaries) - 1)

15.使用Search Profiler优化查询

Profile API提供了查询和聚合在每一步处理耗时的详细信息。

Kibana上使用 Search Profiler可以清楚直观的看到分析结果,以及如何优化查询和减轻负载压力。

因为Profile API在查询上增加了大量开销,返回的结果适用于了解各个查询阶段的相对耗时。不代表实际的处理时间。

16.使用index_phrases加快短语查询

text字段有一个index_phrases选项来索引2-shingles,能够被短语查询自动应用,在没有slop的情况下。如果你的案例中有大量的短语查询,可以显著的加快查询速度。

17.使用index_prefixes来加快前缀查询

text字段有一个index_prefixes选项来索引前缀,在前缀查询条件中能够被自动的应用。如果你的案例中有大量的前缀查询,可以显著的加快查询速度。

18.使用constant_keyword来加快过滤速度

有一个通用规则,过滤查询的耗时基本上是匹配文档数量的一个函数。假设你有一个包含骑行的索引。有大量的自行车数据,许多查询上基于过滤条件:cycle_type: bicycle。这个常见的过滤会很耗时,因为匹配到了大量的文档。有一个简单的方法来避免运行此类查询,把自行车移动他自己的索引中,查询这个索引来代替过滤查询。

不幸的是这样会使客户端的逻辑变得复杂,而这正是constant_keyword发挥作用的地方。通过在bicycles索引上将cycle_type字段类型设置为constant_keyword,值设置为bicycle,客户端依然可以运行和之前单片索引上一样的查询语句,Elasticsearch会在bicycles索引上忽略条件为cycle_type并且值为bicycle的过滤条件,返回正确的结果。

索引结构如下所示:

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会快速的发现在cycle_type中不包含bicycle,返回无匹配结果。

这是一个非常有效的手段,将通用的查询字符内容放在专属索引里面。这个方法也适用于多字段,例如你需要追踪每个骑行工具的颜色,你的bicycles索引中大部分都是黑色的自行车,你可以将他分为bicycles-blackbicycles-other-colors索引。

constant_keyword不属于严格意义上的索引优化:更像是将客户端的逻辑查询路由到指定的索引上。但是constant_keyword使其透明化,将查询和索引结果解耦来优化性能。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
JVM Profiler是一种用于监测和分析Java虚拟机(JVM)运行时性能的工具。它可以通过收集和分析JVM的运行时信息来帮助开发人员识别和解决性能问题。JVM Profiler可以通过使用JVM Agent来实现,JVM Agent是一种通过在JVM启动时加载的方式来修改JVM行为的工具。通过JVM Agent,JVM Profiler可以获取程序运行时的信息,如方法调用、内存使用情况、线程状态等。这些信息可以用于分析程序的性能瓶颈,并进行优化。一种常见的使用方式是通过命令行调用JAR文件来运行JVM Profiler,并指定相应的参数和报告器。例如,可以使用以下命令来运行JVM Profiler并将结果输出到控制台:java -javaagent:target/jvm-profiler-0.0.5.jar=reporter=com.uber.profiling.reporters.ConsoleOutputReporter -cp target/jvm-profiler-0.0.5.jar com.uber.profiling.examples.HelloWorldApplication。此外,JVM Profiler还可以使用JVMTI(JVM Tool Interface)来实现,JVMTI是JVM提供的一套标准的C/C++编程接口,用于实现Debugger、Profiler、Monitor、Thread Analyzer等工具。通过JVMTI,开发人员可以编写自定义的JVM Profiler来满足特定的需求。 #### 引用[.reference_title] - *1* *3* [JVM CPU Profiler技术原理及源码深度解析](https://blog.csdn.net/weixin_45678149/article/details/130775520)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [JVM Profiler介绍](https://blog.csdn.net/weixin_33676492/article/details/89589464)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值