Elasticsearch优化——搜索速度优化


文章目录

  • Elasticsearch优化——搜索速度优化
  • 1. 为文件系统cache预留足够的内存
  • 2. 使用更快的硬件
  • 3. 文档模型
  • 4. 预索引数据
  • 5. 字段映射
  • 6. 避免使用脚本
  • 7. 优化日期搜索
  • 8. 为只读所以执行force-merge
  • 8.1 force-merge操作
  • 8.2 Shrink操作
  • 9. 预热全局序号(global ordinals)
  • 10. execution hint(执行提示)
  • 11. 预热文件系统cache
  • 12. 使用preference来优化高速缓存利用率
  • 13. 调节搜索请求中的batched_reduce_size
  • 14. 副本有助于吞吐量
  • 15. 打开自适应副本选择(ARS)提升ES响应速度
  • 16. 关注我


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公式为:

ES查询最近一段时间数据 es查询大量数据很慢_文件系统

每项的含义如下:

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