• elasticsearch 官方优化建议


    1.一般建议

      a.不要返回过大的结果集。这个建议对一般数据库都是适用的,如果要获取大量结果,可以使用search_after api,或者scroll (新版本中已经不推荐)。

      b.避免大的文档。

    2. 如何提高索引速度

      a.使用批量请求。为了达到最好的效果,可以进行测试,递增地提高bulk的数量,比如从100,到200,再到400,达到一个吞吐量和响应时间的平衡。

      b.使用多线程发送数据。

      c.关闭或者减小refresh_interval。从内存缓存写入磁盘缓存(memorybuffer -> filesystem cache),这个过程叫做refresh。在这个过程之前内存缓存里面的文档是不可被搜索的,这也是为什么es被称为近实时索引的原因。

        在索引初始化(大量导入文档)的时候,可以关闭refresh_interval。当产品允许较大的不可搜索时间,可以将index.refresh_interval设置为30s,提高索引速度。

      d.初始化时关闭复制分片。索引时设置index.number_of_replicas为0,避免主分片复制数据,索引完毕后再调整到正常的复制分片数。

      e.关闭swapping。swap会极大地降低es的索引速度。

    Swap分区(即交换区)在系统的物理内存不够用的时候,把硬盘空间中的一部分空间释放出来,以供当前运行的程序使用。
    那些被释放的空间可能来自一些很长时间没有什么操作的程序,这些被释放的空间被临时保存到Swap分区中,等到那些程序要运行时,再从Swap分区中恢复保存的数据到内存中。

      f.给文件系统缓存分配足够多的内存。文件系统换行用来处理io操作,至少要将物理机一半的内存分配给文件系统缓存。比如物理机内存64g,那么至少分配32g给文件系统缓存,剩下的内存才考虑分配给es。

      g.使用自动生成的id。如果使用指定的id,es会检查这个id是否已经存在,而且随着文档数越多,这个判重操作越耗时。索引的时候,如果没有指定id,es会自动生成id。

    {
        "_index": "sales",
        "_type": "_doc",
        "_id": "xb7IY4cB6Rdc8HbDycuE", // auto-generated id
        "_version": 1,
        "result": "created",
        "_shards": {
            "total": 2,
            "successful": 1,
            "failed": 0
        },
        "_seq_no": 10,
        "_primary_term": 1
    }

      h.使用更好的硬件。比如SSD,或者Amazon的Elastic Block Storage。

      i.调整索引缓存大小。确保每个索引分片能获得512M的缓存,即 indices.memory.index_buffer_size = 512M,大于512M没有更多提升效果。

      j.使用cross-cluster replication 来实现读写分离,这样让索引集群压力更小。这和mysql中的读写分离很类似。

    3.如何提到搜索速度

      a.给文件系统缓存分配足够多的内存。

      b.在linux环境中设置合适的readahead。但是es中的查询更多的是随机io,过大的readahead反而使文件系统的页缓存严重抖动,从而使查询性能下降。

    Linux的文件预读readahead,指Linux系统内核将指定文件的某区域预读进页缓存起来,便于接下来对该区域进行读取时,不会因缺页(page fault)而阻塞。因为从内存读取比从磁盘读取要快很多。
    预读可以有效的减少磁盘的寻道次数和应用程序的I/O等待时间,是改进磁盘读I/O性能的重要优化手段之一。使用命令lsblk查看readahead值。

      c.使用更好的硬件。

      d.好的文档模型。酌情使用nested query, parent query, 避免使用join query。

    文档模型 对比普通查询
    nested query 慢几倍
    parent query 慢几百倍
    join query 应当避免

      e.尽可能少的查询字段。在越多的字段上匹配,查询速度就越慢。在索引的时候可以将需要查询的多个字段聚合到一个字段中。使用copy_to 可以自动实现这一功能,以下示例将name和plot字段聚合到name_and_plot字段中。

    PUT movies
    {
      "mappings": {
        "properties": {
          "name_and_plot": {
            "type": "text"
          },
          "name": {
            "type": "text",
            "copy_to": "name_and_plot"
          },
          "plot": {
            "type": "text",
            "copy_to": "name_and_plot"
          }
        }
      }
    }

      f.预先索引数据。比如如果想对price字段做range聚合,那么预先计算出单个文档的price范围,那么就能将range聚合转化成terms聚合。这样确实能提高效率,但是不太灵活。

    插入文档:

    PUT index/_doc/1
    {
      "designation": "spoon",
      "price": 13
    }

    range聚合查询:

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

    另一种做法,预先计算price_range:

    PUT index
    {
      "mappings": {
        "properties": {
          "price_range": {
            "type": "keyword"
          }
        }
      }
    }
    
    PUT index/_doc/1
    {
      "designation": "spoon",
      "price": 13,
      "price_range": "10-100"
    }

    使用terms聚合:

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

      g.尽可能将字段自定义为keyword。对于数字类型的字段,es对其range查询做了优化。在term层级的查询下,keyword字段比数字类型要好。

        在以下两种情况下可以考虑将数字类型定义为keyword:

          1.不需要对这些数据进行range查询

          2.有很高的查询速度要求。

        如果实在不清楚哪个好,可以用 multi-field为数字类型的字段同时定义数字类型和keyword类型。

      h.避免使用脚本。如果可能,避免使用脚本排序,使用脚本聚合,以及script_scorequery。

      i.使用四舍五入的日期。这样有助于es进行缓存,精确到秒级别的查询有时候并无必要。

    实时查询(秒级):

    PUT index/_doc/1
    {
      "my_date": "2016-05-11T16:30:55.328Z"
    }
    
    GET index/_search
    {
      "query": {
        "constant_score": {
          "filter": {
            "range": {
              "my_date": {
                "gte": "now-1h",
                "lte": "now"
              }
            }
          }
        }
      }
    }

    分钟级查询:

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

      j.对只读索引进行force-merge。在时序索引中,过期的索引都是只读的,将其合并成一个段能加快查询速度。

      k.预热global ordinals。ordinals 是doc values的具体存储形式。一般情况下一个字段的global ordinals是懒加载的。如果某个字段在聚合上用到很多,我们可以先将其预热(加载到heap),当做field data cache.的一部分。

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

      l.预热文件系统缓存。设置index.store.preload参数即可。注意,必须确保文件系统缓存足够大,否则会让查询变得更慢。

      m.使用索引排序来加速连接查询。比如我们要进行过滤 a AND b AND …​,然后a是low-cardinality(低区分度)。那么我们可以先对a进行排序,那么一旦a的某个值不匹配这个表达式,那么有相同的值的文档都可以跳过。

      n.使用preference进行缓存使用优化。es中有非常多的缓存,比如文件系统缓存(最重要),请求缓存,查询缓存,但是这些缓存都是在节点层面。默认情况下es会使用round-robin算法分配查询到不同的分片上去,这样缓存就失效了。

        如果可以,使用preference参数将用户的请求和对应的分片或者节点绑定起来,这样缓存就不会失效。例如:

    GET /_search?preference=_shards:2,3
    {
        "query": {
            "match": {
                "title": "elasticsearch"
            }
        }
    }

      o.更多的复制分片会提升吞吐量(但并不一定)。在系统资源充足的情况下,复制分片越多吞吐量会越高。但是过多的分片会让故障恢复变得更慢。

      p.使用profile api优化查询语句。和mysql中的explain类似,例如:

    GET /my-index-000001/_search
    {
      "profile": true,
      "query" : {
        "match" : { "message" : "GET /search" }
      }
    }
    
    {
      "took": 25,
      "timed_out": false,
      "_shards": {
        "total": 1,
        "successful": 1,
        "skipped": 0,
        "failed": 0
      },
      "hits": {
        "total": {
          "value": 5,
          "relation": "eq"
        },
        "max_score": 0.17402273,
        "hits": [...] 
      },
      "profile": {
        "shards": [
          {
            "id": "[2aE02wS1R8q_QFnYu6vDVQ][my-index-000001][0]",
            "searches": [
              {
                "query": [
                  {
                    "type": "BooleanQuery",
                    "description": "message:get message:search",
                    "time_in_nanos" : 11972972,
                    "breakdown" : {
                      "set_min_competitive_score_count": 0,
                      "match_count": 5,
                      "shallow_advance_count": 0,
                      "set_min_competitive_score": 0,
                      "next_doc": 39022,
                      "match": 4456,
                      "next_doc_count": 5,
                      "score_count": 5,
                      "compute_max_score_count": 0,
                      "compute_max_score": 0,
                      "advance": 84525,
                      "advance_count": 1,
                      "score": 37779,
                      "build_scorer_count": 2,
                      "create_weight": 4694895,
                      "shallow_advance": 0,
                      "create_weight_count": 1,
                      "build_scorer": 7112295
                    },...

      q.使用 index_phrases 加速phrase query。index_phrases,会将两个单词的组合单独索引,这样可以加速phrase query。

      r.使用 index_phrases 加速prefix query。同上。

      s.使用constant_keyword加速过滤。如果某个字段的大多数情况下的值是个常量,但是我们又经常要对其进行过滤,我们可以将其拆分成两个索引,一个使用constant_keyword,一个不使用。

    mapping如下:

    UT bicycles
    {
      "mappings": {
        "properties": {
          "cycle_type": {
            "type": "constant_keyword",
            "value": "bicycle"
          },
          "name": {
            "type": "text"
          }
        }
      }
    }
    
    PUT other_cycles
    {
      "mappings": {
        "properties": {
          "cycle_type": {
            "type": "keyword"
          },
          "name": {
            "type": "text"
          }
        }
      }
    }

    查询语句:

    GET bicycles,other_cycles/_search
    {
      "query": {
        "bool": {
          "must": {
            "match": {
              "description": "dutch"
            }
          },
          "filter": {
            "term": {
              "cycle_type": "bicycle"
            }
          }
        }
      }
    }

    在查询bicycles索引时,es会将查询语句自动转换为:

    GET bicycles,other_cycles/_search
    {
      "query": {
        "match": {
          "description": "dutch"
        }
      }
    }

    4.磁盘优化

      a.禁用不需要的特性。

        比如数字类型的字段如果不需要进行过滤,可以不对其进行索引。

    PUT index
    {
      "mappings": {
        "properties": {
          "foo": {
            "type": "integer",
            "index": false
          }
        }
      }
    }

        es会对text类型的字段存储一些打分信息,如果不需要对这些字段进行打分,可以将其设置为match_only_text类型

      b.不要使用默认动态字符串映射。默认动态字符串映射会将字符串类型映射为text和keyword类型,这样很浪费空间。可以预先配置所有字符串映射类型为keyword。

    PUT index
    {
      "mappings": {
        "dynamic_templates": [
          {
            "strings": {
              "match_mapping_type": "string",
              "mapping": {
                "type": "keyword"
              }
            }
          }
        ]
      }
    }

      c.监控分片大小。越大的分片能更有效地存储数据。但是分片越大,故障恢复也会越慢。

      d.禁用_source字段。_source会存储原始的json数据,如果不需要,就将其禁用。

      e.使用best_compression进行压缩。es默认使用 LZ4 进行压缩,使用best_compression可以提升压缩比率,但是会影响数据存取性能。

      f.force-merge.强制合并段能提升存储效率。注意,force-merge应当在没有文件写入后进行,  比如在过期的时序索引节点上。

      g.shrink 索引。即收缩索引,将当前索引重新索引成分片数更少的索引。分片越大,存储效率越高。

        shrink索引有如下条件。

        1.索引必须只读。

        2.节点必须包含索引的所有分片(主分片,或者复制分片都可以)

        3.索引状态必须是健康的。

      h.使用能满足需求的最小的数字类型。比如能用byte, 不用short。这个在其他db比如mysql中也适用。

      i.使用索引排序来提升文档的压缩性能。排序后相似的文档会放在一起,es能根据他们的特性有效地进行压缩。

        设定索引排序:

    PUT my-index-000001
    {
      "settings": {
        "index": {
          "sort.field": "date", 
          "sort.order": "desc"  
        }
      },
      "mappings": {
        "properties": {
          "date": {
            "type": "date"
          }
        }
      }
    }

      j.索引文档时保证json字段顺序一致。es在存储的时候将多个文档压缩成一成block,如果json文档顺序一致,es能更好的对更长的相同的字符串进行压缩。

      k.roll-up历史数据。使用roll up api来归档历史数据,他们依然可以访问,但是有着更高的存储效率。

    5.分片大小

      1.将索引分片大小保持在10G~50G之间

      2.平均下来每G堆内存下不要超过20个分片。

  • 相关阅读:
    FLIP动画做拖拽排序效果
    实在智能Agent——RPA终极进化方向
    shellctf2022-writeup
    穿越时空的创新:解析云原生与Web3.0的奇妙渊源
    【论文笔记】图神经网络采样相关工作整理9.19
    俄罗斯网络间谍组织在有针对性的攻击中部署LitterDrifter USB蠕虫
    C语言牛客网(NowCoder)刷题篇
    高效处理异常值的算法:One-class SVM模型的自动化方案
    Docker 容器的数据卷的使用
    Oracle迁移中关于大容量表使用数据泵(expdp、impdp)导出导入容易出现的问题和注意事项
  • 原文地址:https://www.cnblogs.com/darcy-yuan/p/17266043.html