欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Elasticsearch优化——搜索速度优化

程序员文章站 2022-03-05 09:53:59
...

Elasticsearch优化——搜索速度优化

1. 为文件系统cache预留足够的内存

filesystem cache越大越好,为了使得搜索速度更快,ES严重依赖filesystem cache。一般来说,需要至少一般的可用内存作为filesystem cache,这样ES可以在内存中保有索引的热点区域

2. 使用更快的硬件

搜索一般是I/O bound的,此时,你需要:

  1. filesystem cache分配更多的内存。
  2. 使用SSD硬盘。
  3. 使用local storage,不要使用NFS、SMB等远程文件系统。

3. 文档模型

文档需要使用适合的类型,从而使得搜索时操作消耗更少的资源。避免使用join操作,嵌套会使查询慢几倍,父子关系可能使查询慢数百倍

4. 预索引数据

针对某些查询的模式在优化数据的索引方式。例如,如果所有文档都有一个price字段,并且大多数查询在一个固定范围上进行range聚合,那么可以通过将范围"pre-indexing"(预索引)到索引中,并使用terms聚合加快聚合速度。

例如,文档起初是这样的:

PUT index/type/1
{
    "name":"苹果",
    "price":13
}

采用下面的搜索方式:

GET index/type/_search
{
    "aggs":{
        "price_ranges":{
            "range":{
                "field":"price",
                "ranges":[
                    {"to":10},
                    {"from":10,"to":50},
                    {"from":50,"to":100},
                    {"from":100,"to":150}
                ]
            }
        
        }
    }
}

那么我们优化的是在建立索引时对文档进行富化,增加price_range字段,mapping为keyword类型:

PUT index/_mapping/type
{
    "properties":{
        "price_range":{
            "type":"keyword"
        }
    }
}

PUT index/type/1
{
    "name":"苹果",
    "price":12,
    "price_range":"10-50"
}

接下来,搜索请求可以聚合这个新字段,而不是在price字段上运行range聚合。

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

5. 字段映射

有些字段的内容是数字,但并不意味着一定得使用数字类型的字段。一般来说,存储标识符的字段,使用keyword更好。

6. 避免使用脚本

一般来说,应该避免使用脚本。如果一定要使用,则应该优先考虑painlessexpressions

7. 优化日期搜索

在使用日期范围检索时,使用now的查询通常不能缓存,因为匹配到的范围一直在变化。但是,从用户体验的角度来看,切换到一个完整的日期通常是可以接受的,这样可以更好的利用查询缓存。

例如,有下列查询:

PUT index/type/1
{
    "createAt":"2020-01-04 15:31:23.369"
}

GET index/type/_search
{
    "query":{
        "constant_score":{
            "filter":{
                "range":{
                    "createAt":{
                        "gte":"now-1h",
                        "lte":"now"
                    }
                }
            }
        }
    }
}

可以替换成下面的查询方式:

GET index/type/_search
{
    "query":{
        "constant_score":{
            "filter":{
                "range":{
                    "createAt":{
                        "gte":"now-1h/m",
                        "lte":"now/m"
                    }
                }
            }
        }
    }
}

在这个例子中,我们将日期四舍五入到分钟,因此如果当时间是15:32:23,那么range查询将匹配createAt字段的值在14:32-15:32之间的所有内容。如果多个用户同时运行一个包含此查询范围的查询,则查询缓存可以加快查询速度。用于舍入得的时间间隔越长,查询缓存就越有帮助。但是要注意,太高的舍入也可能损害用户体验。

8. 为只读所以执行force-merge

为不再更新的只读索引执行force merge,将Lucene索引河北为单个分段,可以提升查询速度。当一个Lucene索引存在多个分段时,每个分段会单独执行搜索再将结果合并,将只读索引强制合并为一个Lucene分段不仅可以优化搜索过程,对索引恢复速度也有好处。

基于日期进行轮询的索引的旧数据一般都不会再更新。应该避免持续的写一个固定的索引,然后用别名关联,或者使用索引通配符。这样,可以每天选一个时间点对冷的索引进行force-mergeShrink等操作。

8.1 force-merge操作

$curl -X POST "http://127.0.0.1:9200/index/_forcemerge?max_num_segments=1"

全部merge如下

$curl -X POST "http://127.0.0.1:9200/_forcemerge?max_num_segments=1"

max_num_segments:设置最大segment数量,数量越小,查询速度提高越明显,但merge耗时越长。

8.2 Shrink操作

PUT source_index/_setting
{
    "settings":{
        "index.routing.allocation.require._name":"node-1",
        "index.blocks.write":true
    }
}

index.blocks.write:设置索引为只读。

缩小索引

执行shrink:

POST source_index/_shrink/target_index
{
    "settings":{
        "index.number_of_replicas":1,
        "index.number_of_shards":1,
        "index.codec":"best_compression"
    },
    "aliases":{
        "my_search_indices"{}
    }
}

9. 预热全局序号(global ordinals)

全局序号是一种数据结构,用于在keyword字段上运行terms聚合。它用一个数值来代表字段中的字符串值,然后为每个数值分配一个bucket,这需要一个对blobal ordinalsbucket的构建过程。默认情况下,它们被延迟构建,因为ES不知道哪些字段将用于terms聚合,哪些字段不会。可以通过配置映射在刷新时告诉ES预先加载全局序数:

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

10. execution hint(执行提示)

terms聚合有两种不同的机制:

  • 通过直接使用字段值来聚合每个桶的数据(map)
  • 通过使用字段的全局序号并为每个全局序号分配一个bucket

ES使用global_ordinals作为keyword字段的默认选项,它使用全局序号动态的分配bucket,因此内存使用与聚合结果中的字段数量是线性关系。在大部分情况下,这种方式的速度很快。当查询只会匹配少量文档时,可以考虑使用map。默认情况下,map只在脚本上运行聚合时使用,因为它们没有序数。

GET index/type/_search
{
    "aggs":{
        "city":{
            "terms":{
                "field":"city",
                "execution_hint":"map"
            }
        }
    }
}

11. 预热文件系统cache

如果ES主机重启,则文件系统缓存将清空,此时搜索会比较慢,可以使用index.store.preload设置,通过指定文件扩展名,显示的告诉操作系统应该将哪些文件加载到内存中。

例如,配置到elasticsearch.yml文件中:

index.store.preload: ["nvd","dvd"]

或者在创建索引的时候设置:

PUT index
{
    "settings":{
        "index.store.preload":["nvd","dvd"]
    }
}

如果文件系统缓存不够大,则无法保存所有数据,那么为太多文件预加载到文件系统中会使搜索速度变慢,应谨慎使用。

12. 使用preference来优化高速缓存利用率

有多个缓存可以帮助提高搜索性能,例如文件系统缓存、请求缓存或查询缓存。

然而,所有这些缓存都维护在节点级别,这意味着如果运行如果运行两次相同的请求,则有一个或多个副本,并使用循环(默认路由算法),那么这2个请求将转发到不同的分片副本,阻止节点级别的缓存帮助。

由于搜索应用程序的用户一个接一个的请求类似的搜索,使相似的搜索请求落到相同的节点,帮助优化高速缓存的使用。

13. 调节搜索请求中的batched_reduce_size

该字段是搜索请求中的一个参数。默认情况下,聚合操作是在协调节点需要等待所有的分片都取回结果后才执行,使用batched_reduce_size参数可以不等待全部分片返回结果,而是在指定数量的分片返回结果只会就可以先处理一部分(reduce)。这样可以避免协调节点在等待全部结果的过程中占用大量内存,避免极端情况下可能导致的OOM。该字段默认值512,从ES 5.4开始支持。

14. 副本有助于吞吐量

副本除了提高弹性外,也可以帮助提高吞吐量。搜索请求的时候轮询转发到不同的副本。

15. 打开自适应副本选择(ARS)提升ES响应速度

ARS公式为:
Elasticsearch优化——搜索速度优化

每项的含义如下:

  • os(s):节点未完成的搜索请求数
  • n:系统中数据节点的数量
  • R(s):响应时间的EWMA,单位为毫秒
  • q(s):搜索线程池队列中等待任务数量的EWMA
  • μ(s):数据节点上的搜索服务时间的EWMA,单位毫秒。

ES通过这些信息大致可以评估出分片副本所在的节点压力和健康度。

ARS从6.1版本开始支持,默认是关闭的,从ES 7.0开始,ARS将默认开启。可以使用下面的命令动态开启:

PUT /_cluster/settings
{
    "transient":{
        "cluster.routing.use_adaptive_replica_selection":true
    }
}

R(s):响应时间的EWMA,单位为毫秒

  • q(s):搜索线程池队列中等待任务数量的EWMA
  • μ(s):数据节点上的搜索服务时间的EWMA,单位毫秒。

ES通过这些信息大致可以评估出分片副本所在的节点压力和健康度。

ARS从6.1版本开始支持,默认是关闭的,从ES 7.0开始,ARS将默认开启。可以使用下面的命令动态开启:

PUT /_cluster/settings
{
    "transient":{
        "cluster.routing.use_adaptive_replica_selection":true
    }
}
相关标签: 搜索引擎