一口气看完 43 个关于 ElasticSearch 的使用建议

一、前言本文分享了在工作中关于 ElasticSearch 的一些使用建议 。和其他更偏向手册化更注重结论的文章不同 , 本文将一定程度上阐述部分建议背后的原理及使用姿势参考,避免流于表面,只知其然而不知其所以然 。如有不当的地方,欢迎指正!
二、查询相关充分利用缓存

  • 分片查询缓存(Shard Request Cache)
ES 层面的缓存实现 , 封装在 IndicesRequestCache 类中 。缓存的 Key 是整个客户端请求,缓存内容为单个分片的查询结果 。主要作用是对聚合的缓存 , 查询结果中被缓存的内容主要包括:Aggregations(聚合结果)、Hits.total、以及 Suggestions等 。
并非所有的分片级查询都会被缓存 。只有客户端查询请求中 size=0 的情况下才会被缓存 。其他不被缓存的条件还包括 Scroll、设置了 Profile 属性,查询类型不是 QUERY_THEN_FETCH , 以及设置了 requestCache=false 等 。另外一些存在不确定性的查询例如:范围查询带有 Now,由于它是毫秒级别的,缓存下来没有意义,类似的还有在脚本查询中使用了 Math.random() 等函数的查询也不会进行缓存 。
当有新的 Segment 写入到分片后,缓存会失效,因为之前的缓存结果已经无法代表整个分片的查询结果 。所以分片每次 Refresh 之后,缓存会被清除 。
  • 节点查询缓存/过滤器缓存(Node Query Cache /Filter Cache)
Lucene 层面的缓存实现,封装在 LRUQueryCache 类中,默认开启 。缓存的是某个 Filter 子查询语句在一个 Segment 上的查询结果 。
并非所有的 Filter 查询都会被缓存 。对于体积较小的 Segment 不会建立 Query Cache,因为他们很快会被合并 。Segment 的 Doc 数量需要大于 10000,并且占整个分片的 3% 以上才会走 Cache 策略(参考:缓存) 。
当 Segment 合并的时候,被删除的 Segment 其关联 Cache 会失效 。
01. 使用过滤器上下文(Filter)替代查询上下文(Query) 。
  • Filter不会进行打分操作,而 Must 会 。
  • Filter 查询可以被缓存,从而提高查询性能 。
正例:
// 创建BoolQueryBuilderBoolQueryBuilder boolQuery = QueryBuilders.boolQuery();// 构建过滤器上下文boolQuery.filter(QueryBuilders.termQuery("field", "value"));反例:
// 创建BoolQueryBuilderBoolQueryBuilder boolQuery = QueryBuilders.boolQuery();// 构建查询上下文boolQuery.must(QueryBuilders.termQuery("field1", "value1"));02. 只关注聚合结果而不关注文档细节时,Size 设置为 0 利用分片查询缓存 。参考示例:
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();// 添加聚合查询sourceBuilder.aggregation(AggregationBuilders.terms("term_agg").field("field").subAggregation(AggregationBuilders.sum("sum_agg").field("field")));// 设置size为0,只返回聚合结果而不返回文档sourceBuilder.size(0);03. 日期范围查询使用绝对时间值 。日期字段上使用 Now,一般来说不会被缓存 , 因为匹配到的时间一直在变化 。因此,可以从业务的角度来考虑是否一定要用 Now,尽量使用绝对时间值,不需要解析相对时间表达式且利用 Query Cache 能够提高查询效率 。例如时间范围查询中使用 Now/h , 使用小时级别的单位 , 可以让缓存在 1 小时内都可能被访问到 。
正例:
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();// 获取当前日期并格式化为绝对时间值LocalDateTime now = LocalDateTime.now();DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE;String currentDate = now.format(formatter);// 创建日期范围查询sourceBuilder.query(QueryBuilders.rangeQuery("date_field").gte("2022-01-01").lte(currentDate)); 反例:SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();// 创建日期范围查询 , 使用相对时间值sourceBuilder.query(QueryBuilders.rangeQuery("date_field").gte("now-7d").lte("now"));聚合查询04.避免多层聚合嵌套查询 。聚合查询的中间结果和最终结果都会在内存中进行,嵌套过多,会导致内存耗尽 。
如:
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();// 创建主要查询sourceBuilder.query(QueryBuilders.matchAllQuery());// 创建第一层聚合TermsAggregationBuilder termAggBuilder1 = AggregationBuilders.terms("term_agg1").field("field_name1");// 创建第二层聚合TermsAggregationBuilder termAggBuilder2 = AggregationBuilders.terms("term_agg2").field("field_name2");termAggBuilder1.subAggregation(termAggBuilder2);// 创建第三层聚合TermsAggregationBuilder termAggBuilder3 = AggregationBuilders.terms("term_agg3").field("field_name3");termAggBuilder2.subAggregation(termAggBuilder3);sourceBuilder.aggregation(termAggBuilder1);


推荐阅读