您的位置:首页 > 其它

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

2018-01-16 13:41 246 查看
原文: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遇到的挑战如下:

高吞吐量:有些集群每日索引量达5TB,而有的集群每日搜索请求可达4亿。如果Elasticsearch无法实时处理则请求将会在上游堆积。

低延时:集群性能至关重要,尤其是面向站点的系统,低搜索延迟是必须的,否则将影响用户体验。

由于数据或查询是变化的所以最佳设置也是可变的。没有最优设置试用于所有场景。例如,将索引分片有利于减少查询耗时,但这可能会损害一些其他查询性能。

解决方案

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

分级:在一个新用户案例接入之前,收集类似吞吐量、文档大小、文档数、搜索类型等信息以评估集群初始大小。

优化索引设计:为用户审查索引设计合理性

优化索引性能:结合场景优化索引性能和搜索性能

优化搜索性能:使用真实数据或搜索执行性能测试,结合Elasticsearch配置参数比较和分析测试结果。

执行性能测试:业务上线后,集群是被监控的,并且在数据或查询改变、流量爆增等情况时用户可随时执行性能测试。

分级

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 的在线性能分析服务以帮助客户执行性能测试和回归。该服务支持一下功能:

轻松新增/编辑实验。用户可以根据自己的输入查询或文档结构生成测试,同时不需要Gatling或Scala知识。

无需人工干预顺序执行多个测试。它会检查状态并在每次测试前后更高Elasticsearch设置。

帮助用户比较和分析测试结果。实验期间的结果和集群信息会持久化并可通过Kibana分析。

使用命令行或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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息