eBay Elasticsearch 性能优化实战-中文篇

原文:Elasticsearch Performance Tuning Practice at eBay

Elasticsearch是基于Apache Lucene并具有近实时存储、搜索、分析数据的开源搜索和分析引擎。虽然Elasticsearch是为快速查询而设计,但其性能主要取决于应用场景、索引数据量和用户查询数据的频率。本文总结了Pronto团队应对的挑战的策略。并展示了不同配置的基准测试结果。

Elasticsearch是基于Apache Lucene并具有近实时存储、搜索、分析数据的开源搜索和分析引擎。Pronto是eBay托管的Elasticsearch集群平台,使eBay内部客户能够轻松部署、操作和扩展Elasticsearch以进行全文搜索、实时分析和日志/事件监控。

Pronto如今管理了60+集群和2000+节点。日采集量达到180亿,日均搜索请求达35亿。 Pronto平台支持搭建、修复、安全到监控、警报和诊断全部功能。虽然Elasticsearch是为了快速查询而设计的,但其性能主要取决于应用场景、索引数据量和用户查询数据的频率。本文总结了Pronto团队应对的挑战的策略。并展示了不同配置的基准测试结果。

挑战

迄今为止Pronto/Elasticsearch遇到的挑战如下:

  1. 高吞吐量:有些集群每日索引量达5TB,而有的集群每日搜索请求可达4亿。如果Elasticsearch无法实时处理则请求将会在上游堆积。
  2. 低延时:集群性能至关重要,尤其是面向站点的系统,低搜索延迟是必须的,否则将影响用户体验。
  3. 由于数据或查询是变化的所以最佳设置也是可变的。没有最优设置试用于所有场景。例如,将索引分片有利于减少查询耗时,但这可能会损害一些其他查询性能。

解决方案

为了帮助用户客服以上挑战,Pronto团队构建了用于性能测试、调优和监视的策略,从用户提出需求开始一直存在于整个集群生命周期。

  1. 分级:在一个新用户案例接入之前,收集类似吞吐量、文档大小、文档数、搜索类型等信息以评估集群初始大小。
  2. 优化索引设计:为用户审查索引设计合理性
  3. 优化索引性能:结合场景优化索引性能和搜索性能
  4. 优化搜索性能:使用真实数据或搜索执行性能测试,结合Elasticsearch配置参数比较和分析测试结果。
  5. 执行性能测试:业务上线后,集群是被监控的,并且在数据或查询改变、流量爆增等情况时用户可随时执行性能测试。

分级

Pronto团队为每类机器和每个支持的Elasticsearch版本都运行基准测试来收集性能数据,然后使用客户提供的信息来评估集群初始大小,包括:

  • 索引吞吐量
  • 文档数
  • 搜索吞吐量
  • 查询类型
  • 热索引文档数
  • 保留策略
  • 需要的响应时间
  • SLA 级别

优化索引设计

在索引数据和执行查询前请思考以下几个问题:索引代表什么?Elastic官方回答是“具有相似特征的一类文档集合”。所以下个问题是“我应该使用哪个特征对我的数据分类?将所有文档放入一个还是多个索引?答案是这取决于你的查询。

下面是一些关于如何根据频繁使用的查询来组织索引的建议。

  • 如果查询有一个筛选字段,并且其值是可枚举的,则将数据分割为多个索引。例如,你有海量全球商品信息需要索引,而大多数查询是都有一个“region”过滤子句,同时几乎没有几乎运行跨region查询。则查询体可以优化:
{
    "query": {
        "bool": {
            "must": {
                "match": {
                    "title": "${title}" }
            },
            "filter": {
                "term": {
                    "region": "US" }
            }
        }
    }
}

在这种场景下,我们可以根据region拆分为多个小索引以获得更好的性能,例如US、Euro等。然后filter子句可以从查询中删除。如果需要跨region查询,则可以使用多索引或者通配符查询。

  • 如果查询有一个filter字段同时该字段不是枚举类型则可以使用路由。我们可以使用filter字段作为路由key并移除查询中的filter子句,这样就可以将索引拆成多个分片。

举个例子,现在Elasticsearch中有百万订单数据,同时大多数查询是以用户ID去排序的。因为不可能为每个用户创建一个索引,所有我们以 用户ID将数据划分到多个分片中。一个合理的解决方法是将同一用户ID的所有订单路由到同一个分片,之后几乎所有的查询都可以在匹配 路由key的分片内完成。

  • 如果查询中有日期范围filter则组织一下数据。这适用于大多数日志或者监控场景。我们以日、周、月组织索引,然后可以获得指定时间范围内的索引列表。Elasticsearch只需要查询一个小数据集替代索引数据。同时,当数据过期时收缩/删除旧索引也十分方便。

  • 显式设置mapping。 Elasticsearch可以动态创建mapping,但这并不适用于所有场景。例如,在Elasticsearch5.x中默认string字段同时是”keyword”和”text”类型。这在很多场景下是不需要的。

  • 如果文档是以用户自定义ID或者路由方式索引的请避免不平衡的分片。 Elasticsearch使用随机生成DI和hash算法以确保文档均匀的分配到各个分配。当你使用用户自定义ID或者路由,ID或者可能可能不够随机,这样某些索引可能会比其他索引大很多。在这种场景下,在大分片上进行读/写操作可能会比较慢。我们可以使用index.routing_partition_size 优化ID/路由key(5.3及以上版本)

  • 使分片均分的分布在节点上。 如果一个节点分片数大于其他节点,这个将比其他节点承担更多的负载,并可能成为整个系统的瓶颈。

索引性能调优

对于类似日志和监控的重场景索引性能是关键指标,以下是一些建议:

  • 使用bulk请求。
  • 使用多线程/工作发送请求。
  • 增加refresh间隔。每当refresh发生时,Elasticsearch都会创建一个新Lucene段并在之后进行合并。增加refresh间隔将减少创建/合并段的成本。 需要注意的是,文档只有在refresh之后才能被搜索到。

这里写图片描述
性能和refresh间隔关系

从上图可知,随着refresh间隔变长,集群吞吐量增加同时响应时间变快了。可以使用以下请求来检查段的数量以及refresh和merge花费了多少时间。

Index/_stats?filter_path= indices.**.refresh,indices.**.segments,indices.**.merges
  • 减少副本数。需要为每个索引请求将在文档写入主分片和副本。显然,大副本会降低索引速度。但定一方面,增加副本也将提高搜索性能。我们将在之后讨论这个问题。

这里写图片描述
性能和副本数关系

上图可知,随着副本数量增加吞吐量降低,同时响应时间变长。

  • 尽量使用自生成ID。 Elasticsearch自生成ID是可用保证唯一的以避免版本查询。如果客户真的需要自定义ID,建议选一个对Lucene友好的ID,例如 zero-padded顺序ID、UUID-1和Nano时间。这些ID是一致和顺序的这样很容易压缩。相反,类似UUID-4本质上随机的,较差的压缩比会降低Lucene速度。

优化搜索性能

支持数据的搜索是使用的Elasticsearch的一个主要原因。用户应该能更快的定位到查询的内容,搜索性能取决于很多因素:

  • 尽量使用filter而不是query。一个query子句可以理解成“这个文档是如何与子句匹配的”,一个filter子句可以理解成“这个文档是否匹配该子句”。Elasticsearch仅仅需要回答“yes”和“no”。它不需要计算filter子句的相关性得分,并且filter结果可以被缓存。详见Query and filter context

这里写图片描述
query和filter 比较

  • 增加refresh间隔。 正如 tune indexing performance 提到的,Elasticsearch每次刷新都会创建一个新的段。增加refresh间隔将有助于减少段的数量并降低搜索IO。而且一旦发生refresh并且数据改变,缓存将无效。增加refresh间隔将使得Elasticsearch高效的利用缓存。

  • 增加副本数。 Elasticsearch Elasticsearch 可以在主或副本上执行搜索。副本越多,可搜索的节点就越多。

这里写图片描述
性能和副本关系

上图可知,搜索吞吐量与副本数几乎是线性相关的。注意到在这个测试中,测试集群有足够的数据节点来确保每个分片都有一个独占节点,如果这个条件不能满足,搜索吞吐量就不会很好。

  • 尝试不同的分片数。 “应该为索引设置多少分片?”这可能是最长讨论的问题。不幸的是,所有场景都没有标准,而这完全取决于实际情况。

太少的分片数不利于搜索扩展。例如,如果分片数据为1,则索引中所有文档都将存储在一个分片中,这样每次搜索都在同一个节点。如果有很多文档则十分耗时。同时,索引分配太多对性能有危害,因为Elasticsearch需要在所有分片上执行查找,除非指定了路由键,然后抓取和合并将返回所有数据。

根据经验,如果索引小于1G,将分片设为1没什么问题。在大多数场景下我们可以保留默认为5的分片数量,但是当分片大小超过30GB,应该增加分片数以将索引拆分到更多的分片。创建索引后分片数不能改变,但是可以创建新索引并使用redinexAPI来迁移数据。

我们对一个拥有大约一亿个文档的150G左右大小的索引进行测试,使用100个线程以发送搜索请求。

这里写图片描述
性能与分片数的关系

从上图可知,我们发现最优的分片数是11。开始搜索吞吐量是在上升(响应时间变快),但是随着分片数持续增加搜索吞吐量便开始下降。

需要注意的是,正如副本数测试一样,每个分片独占一个节点。如果这种条件不能满足,搜索吞吐量就不会得到上图那样好的效果。

在这种情况下,建议尝试一个小于优化值得分片数,因为每个分片独占一个节点时意味着更多的分片需要更多的节点。

  • 结点query缓存。Node query cache 只缓存filter情景下的查询。与query子句不同的是,一个filter子句仅仅是“yes”和“no”的问题。Elasticsearch使用bit位机制来缓存filter结果,使得在之后的查询使用相同的filter时可以快速命中。需要注意的是一个段的文档数超过10000(或者文档总数的3%,以较大的值为准)才能启用query缓存。更多细节详见All about caching

可以使用以下请求检验节点查询缓存是否有效。

GET index_name/_stats?filter_path=indices.**.query_cache
{
  "indices": {
    "index_name": {
      "primaries": {
        "query_cache": {
          "memory_size_in_bytes": 46004616,
          "total_count": 1588886,
          "hit_count": 515001,
          "miss_count": 1073885,
          "cache_size": 630,
          "cache_count": 630,
          "evictions": 0
        }
      },
      "total": {
        "query_cache": {
          "memory_size_in_bytes": 46004616,
          "total_count": 1588886,
          "hit_count": 515001,
          "miss_count": 1073885,
          "cache_size": 630,
          "cache_count": 630,
          "evictions": 0
        }
      }
    }
  }
}
  • 分片query缓存。 如果大多数查询是聚合查询,我们应该看一下 shard query cache, 它可以缓存聚合结果以便Elasticsearch以更小的成本处理请求。不过以下几件事情需要注意:

    – 设置”size”:0。分片query缓存只缓存聚合结合和建议。他不会缓存命中结果,所以如果你将size设为非0则无法从缓存中受益。

    – Payload JSON 必须一致。分片query缓存使用JSON体作为缓存键,所以需要保证JSON体不变且JSON中的键顺序一致。

    – Round 日期时间。不要使用类似Date.now这种变量,Round它否则每个请求都会有一个不同的payload 体,从而使得缓存无效。我们建议round日期时间为小时或天以有效利用缓存。

我们可以使用以下请求来建议分片query缓存是否有效。

GET index_name/_stats?filter_path=indices.**.request_cache
{
  "indices": {
    "index_name": {
      "primaries": {
        "request_cache": {
          "memory_size_in_bytes": 0,
          "evictions": 0,
          "hit_count": 541,
          "miss_count": 514098
        }
      },
      "total": {
        "request_cache": {
          "memory_size_in_bytes": 0,
          "evictions": 0,
          "hit_count": 982,
          "miss_count": 947321
        }
      }
    }
  }
}
  • 只返回需要的字段。如果稳定很大而只需要少数几个字段,使用 stored_fields 获得需要的字段而不是所有字段。

  • 避免搜索停用词。 类似“a” 和“the” 可能会导致结果爆炸。假设你有一百万稳定,搜索“fox”可能返回几十个结果,但是搜索“the fox”可能会返回所有的文档,因为“the”几乎在所有文档中都有出现。Elasticsearch需要对所有的结果集进行评分排序,像”the fox”这种查询可能会降低整体系统的性能。可以使用停用词库移除停用词,或者使用“and”将“the fox“查询改写成“ the AND fox”以获得更准确的结果。
    如果某些词在在索引中经常使用但不在停用词列表中,可以使用cutoff-frequency 来动态处理。

  • 如果不关心文档返回顺序则按_doc排序。 Elasticsearch默认使用“_score” 字段排序。如果你不关心顺序,可以使用 “sort”: “_doc” 让Elasticsearch按索引顺序排序。

  • 避免在返回中使用script query计算,最好是索引时将计算结果存储。 例如,有一个索引存储大量用户信息,我们想查询一“1234”开头的所有用户,我们可以使用script query执行”source”: “doc[‘num’].value.startsWith(‘1234’).” 这个查询十分耗资源并会降低整个集群的速度。可以考虑在索引时加一个”num_prefix” 字段。这样我们在查询时只需要 “name_prefix”: “1234.”

  • 避免通配符查询。

执行性能测试

对于每次变化,都需要执行性能测试来验证是否合适。因为Elasticsearch是一个restful服务,所以可以使用类似Rally,Apache Jmeter和Gatling等工具来执行性能测试。因为Pronto团队需要在每类机器和Elasticsearch版本上进行大量基准测试,而且需要在大量集群上配置Elasticsearch参数执行性能测试,因此这些工具并不难满足我们的需求。

Pronto团队构建了基于 Gatling 的在线性能分析服务以帮助客户执行性能测试和回归。该服务支持一下功能:

  1. 轻松新增/编辑实验。用户可以根据自己的输入查询或文档结构生成测试,同时不需要Gatling或Scala知识。
  2. 无需人工干预顺序执行多个测试。它会检查状态并在每次测试前后更高Elasticsearch设置。
  3. 帮助用户比较和分析测试结果。实验期间的结果和集群信息会持久化并可通过Kibana分析。
  4. 使用命令行或web UI执行测试。Rest API也提供了与其他系统的集成功能。

架构图如下:
这里写图片描述

性能测试服务架构 (click to enlarge diagram)

用户可以查询每个测试的Gatling报告,也可以查看Kibana预定义的可视化结果以进行分析比较,界面如下:
这里写图片描述
Gatling 报告

这里写图片描述
Gatling 报告

总结

本文总结了在设计高摄取和搜索性能Elasticsearch集群时索引/分片/副本设计和一些其他配置。同时还指出Pronto团队如何帮助客户初始化大小,索引设计和性能调优。截止到现在,Pronto团队已经帮助包括订单管理系统(OMS)和搜索引擎优化(SEO)等大量客户的压合要求,从而为eBay的核心业务贡献出力。

Elasticsearch性能取决于很多因素,包括文档结构,文档大小,索引设置/mappingt,请求率,数据集大小,查询命中率等。一个场景的性能优化并不一定适用另一场景。全面的性能测试,搜集数据,根据负载优化配置,优化集群以满足性能要求则十分重要。

TOPICS: Performance Engineering, Search Science
Previous Post:Beyond HTTPS

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值