GeekCat

怎样去最大化Elasticsearch索引性能(第一部分)

原文链接: qbox.io

这是关于调优 Elasticsearch 索引三部分的第一部分。 这一系列的重点是调优Elasticsearch以达到最大的索引吞吐量并减少监控和管理负荷。

首先,假设你已经开始使用Elasticsearch,创建索引,没有引入框架来填充JSON文档。Elasticsearch会迭代索引JSON文档中的每一个域,判断出它的域,创建对应映射。这听起来很理想,但是Elasticsearch的映射(mapping)不一定总是正确的。如果为域选择了错误的类型,将会出现索引错误。

不用次索引作为模型存储,Elasticsearch就是这样。我们不需要事先定义数据结构,它会从你的文档结构推导并且依此决定去如何创建索引。在这方面,Elasticsearch索引数据使用了隐式模式,而不是显示模式。

因此,索引的决策是非常重要的,在你搜索你的数据的时候它有很大的影响。如果它是字符串域(string field),是否应该标记化和规范化,如果是,怎样进行?如果是数值域(numeric field),需要怎样的精度?里面还有很多类型像日期,空间形状,和父子关系域,它们需要特别注意。

如果不进行分析,存储,甚至发送给Elasticsearch的数据都不需要回应搜索请求。特别地,仔细检查不是由自己定义的映射(mapping)内容(例如,Logstash就会为你自动生成映射)。我们打算去索引大量的数据进Elasticsearch吗?或者我们已经试着这么做了,但是结果吞吐量非常低?如果你搜索需要,在定义我们的索引映射过程中这里有许多优化技术。这篇教程收集了一些列技巧和想法去增加Elasticsearch索引的吞吐量。

自定义域映射

对于分析域( analyzed fields), 可以使用满足你域需要的最简单的分析器。如果没问题你甚至可以设置为not_analyzed

域类型默认是字符串的,被认为是包含全文的。也就是说它们的值在索引前先要通过分析器,对于域的全文查询查询字符串也将在搜索前通过分析器的处理。

对于字符串域有两个非常重要的映射参数是index和analyzer。

  • index: 这个索引参数控制如何将字符串索引。它有三个值可供选择。

  • analyzed: 先分析字符串再索引它。换句话说索引的域是可以全文检索的。

  • not_analyzed: 索引的域是可以被搜索的,但是索引的值是明确的。并不会对它进行分析。

  • no: 不对这个域索引。这个域不能被搜索。

字符串域默认的索引参数是 analyzed。如果我想映射域作为一个确切的值,我们需要设置为 not_analyzed:

{
    "tag": {
        "type":     "string",
        "index":    "not_analyzed"
    }
}

另外一些简单的类型(例如long, double, date等)也同样接收索引参数,但是对应的值只能是no和not_analyzed,因为他们的值是不用分析的。

analyzer: 用于分析字符串域, 使用分析器参数来指定搜索和索引时使用哪个分析器。默认,Elasticsearch使用standard 分析器,但是你也可以改用一个内建的其他分析器,例如 whitespacesimple 或者 english

{
    "tweet": {
        "type":     "string",
        "analyzer": "english"
    }
}

禁用 _source 域

_source 域包含索引时原始的JSON文档串。_source域它是不会被索引的(因此也是不可以被搜索),但是它是被存储的,在执行例如get或者search请求的时候它可以被返回。 虽然它很方便,但是source导致了索引的额外开销。因为这个原因,可以如下这样禁用:

curl -XPUT ‘http://localhost:9200/index_name/’ -d '{
  "mappings": {
    "tweet": {
      "_source": {
        "enabled": false
      }
    }
  }
}'

用户经常没怎么思考后果就禁用了_source域,然后就后悔了。如果_source域是不可用的,有几个特性就不能支持:

  • update, update_by_query, 和 reindex APIs.

  • 即时 高亮.

  • 不能通过一个Elasticsearch索引去重建另一个索引,不能改变映射或者分析设置,或者更新索引到一个新的主版本。

  • 不能通过观察索引时的原始文档去调试查询或者聚合接口。

  • 不具有自动修复索引的能力。

引入和排除_source的域

如果你使用_source域, 你设置任何其他域为_stored也不会附加存储。如果你不使用 _source 域,你就必须设置_stored你要存储的域。 注,使用_source可以带来使用更新API的能力。

一个专家级的特性就是剪裁_source域,在文档已经索引后,存储前。移除_source中的域有点类似于禁用_source域,尤其是无法从一个Elasticsearch索引重建另一个索引。

引入、排除参数,也可以接受占位符,可以如下使用:

curl -XPUT ‘http://localhost:9200/logs’ -d ‘{
  "mappings": {
    "event": {
      "_source": {
        "includes": [
          "*.count",
          "meta.*"
        ],
        "excludes": [
          "meta.description",
          "meta.attributes.*"
        ]
      }
    }
  }
}’

这些域(1,2,3,4)将被从_source域里移除。

curl -XPUT ‘http://localhost:9200/logs/event/1’ -d ‘{
  "requests": {
    "count": 10,
    "foo": "bar" //1
  },
  "meta": {
    "name": "Some metric", 
    "description": "Some metric description", //2
    "attributes": {
      "foo": "one", //3
      "baz": "two" //4
    }
  }
}’

我们可以搜索 meta.attributes.foo 域, 即使它没有存储在_source域里。

curl -XGET ‘http://localhost:9200/logs/event/_search’ -d ’{
  "query": {
    "match": {
      "meta.attributes.foo": "one" //searchable field
    }
  }
}’

禁用 _all 域

_all这个域是一个特殊的,拥有所有域的,使用空格作分隔符将所有字段拼接在一起的大字符串,它可以用于分析和索引,但是不存储。这就意味着他可以被搜索,但是不可以检索。

_all域允许你搜索文档中的值,在你并不知道哪个域包含这个值的时候。这在开始一个新的数据集的时候是非常有用的。举例:

curl -XPUT ‘http://localhost:9200/my_index/user/1’ -d ’{
  "first_name":    "Will",
  "last_name":     "Smith",
  "date_of_birth": "1975-10-25"
}’
curl -XGET ‘http://localhost:9200/my_index/_search’ -d ’{
  "query": {
    "match": {
      "_all": "will smith 1975"
    }
  }
}’

_all完全可以禁用通过在setting中把enabled设为false

curl -XPUT ‘http://localhost:9200/my_index’ -d ‘{
  "mappings": {
    "type_1": {
      "properties": {...}
    },
    "type_2": {
      "_all": {
        "enabled": false
      },
      "properties": {...}
    }
  }
}’

如果_all域是禁用的,使用URI请求将不可以查询query_stringsimple_query_string。我们可以配置请求使用默认index.query.default_field配置

curl -XPUT ‘http://localhost:9200/my_index’ -d ‘{
  "mappings": {
    "my_type": {
      "_all": {
        "enabled": false
      },
      "properties": {
        "content": {
          "type": "text"
        }
      }
    }
  },
  "settings": {
    "index.query.default_field": "content" 
  }
}’

禁用分析域的 Norms

Norms 用于存储各种各样的规范(数值表示域的相对长度和索引时boost设置),它稍后用与在查询文档时计算得分依此排序。

虽然是使用于计算得分,norms还是要使用非常多的内存(甚至是要记录每个域中一个字节一个字节的顺序,即便这个域在这个文档中并不存在)。 如果你并不需要在这个字段上计算得分,你就可以在这个域上禁用norms。仅用于过滤或者聚合的字段就特别适合如此。

使用PUT mapping API 禁用Norms,如下:

curl -XPUT ‘http://localhost:9200/my_index/_mapping/my_type’ -d ‘{
  "properties": {
    "title": {
      "type": "string",
      "norms": {
        "enabled": false
      }
    }
  }
}’

Norms并不会立即移除,它将在继续索引的时候,在老的段合并为新段的过程中移除。在一个已经移除norms的域上计算分数都可能的到不一致的结果,因为这些域不在有norms,而其他域依然保持有norms。

当下默认的index_options参数

你需要存储词项的频率和位置么,默认情况是会这么做的,或者你也可以减少它,可能你只需要文档号。设置 index_options为你真正需要的值,这个参数是影响字符串索引的核心。

index_options 参数用于控制增加到倒排索引的信息,为了搜索和高亮。它可以接受如下设置:

  • docs: 只索引文档号。可以用于回答词项是否存在于文档中的这个域。

  • freqs: 文档号和词频都会被存储. 词项频率越高积分越高。

  • positions: 文档号,词项,还有词的位置被索引。位置可以用于模糊或者短语查询。

  • offsets: 文档号,词项,词的位置,和开始到结束的字符偏移(词项映射到原来的字符串)被索引。 偏移提供postings highlighter

分析字符串域默认是会使用positions,其他域默认使用docs。

curl -XPUT ‘http://localhost:9200/my_index’ -d ‘{
  "mappings": {
    "my_type": {
      "properties": {
        "text": {
          "type": "text",
          "index_options": "offsets"
        }
      }
    }
  }
}’
curl -XPUT ‘http://localhost:9200/my_index/my_type/1’ -d ‘{
  "text": "Quick brown fox"
}’

文本域默认可以使用postings highlighter,索引参数是offsets

curl -XGET ‘http://localhost:9200/my_index/_search’ -d ‘{
  "query": {
    "match": {
      "text": "brown fox"
    }
  },
  "highlight": {
    "fields": {
      "text": {} 
    }
  }
}’

使用自动ID功能

如果你并没有为每篇原始文档设置ID,那可以使用Elasticsearch自动生成ID的功能。它可以避免查找version因为自动产生的ID是唯一的。

如果使用你自己的ID,试着挑选一个对Lucene友好的ID。例如包含用零填充的IDS,UUID-1, 和 nanotime; 这些ID一致,顺序模式使得非常好进行压缩。相反,以UUID-4作为ID,基本上是随机的,压缩差,Lucene会变慢。

继续第二部分 "How to Maximize Elasticsearch Indexing Performance.''