MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

索引设置对 ElasticSearch 性能的影响

2023-06-064.0k 阅读

1. 索引分片设置对 ElasticSearch 性能的影响

ElasticSearch 中的索引由一个或多个分片组成,每个分片是一个独立的 Lucene 索引。分片的设置直接关系到 ElasticSearch 的性能表现,特别是在处理大规模数据和高并发请求时。

1.1 分片数量对查询性能的影响

  • 过少分片的情况:当索引的分片数量设置过少时,单个分片需要承载大量的数据。在进行查询操作时,由于单个分片的数据量巨大,Lucene 在执行查询时可能需要扫描大量的文档,这会导致查询响应时间变长。例如,假设我们有一个包含数十亿条文档的索引,若只设置了 1 个分片,那么每次查询都需要在这数十亿条文档中进行全量扫描,这显然是非常低效的。
// 创建一个只有1个分片的索引示例
PUT my_index_1shard
{
    "settings": {
        "number_of_shards": 1,
        "number_of_replicas": 1
    }
}
  • 过多分片的情况:然而,过多的分片也会带来问题。每个分片在 ElasticSearch 集群中都需要占用一定的资源,包括文件句柄、内存等。当分片数量过多时,集群的管理开销会显著增加。ElasticSearch 需要花费更多的时间和资源来协调各个分片之间的通信和数据同步。同时,查询时需要聚合来自多个分片的结果,过多的分片会导致聚合操作变得复杂且耗时。例如,若我们将一个原本适合 10 个分片的索引设置为 1000 个分片,虽然每个分片的数据量减少了,但查询时需要从 1000 个分片中收集结果,这大大增加了网络开销和协调成本。
// 创建一个有1000个分片的索引示例(实际中一般不会这么设置,仅为示例)
PUT my_index_1000shards
{
    "settings": {
        "number_of_shards": 1000,
        "number_of_replicas": 1
    }
}
  • 合适分片数量的确定:确定合适的分片数量需要综合考虑多种因素,包括数据量的大小、硬件资源以及查询模式。一般来说,可以根据预估的数据量和单个分片能够承载的合理数据量来估算分片数量。通常,单个分片在 10GB 到 50GB 数据量时性能表现较好。例如,如果我们预计索引的数据量为 500GB,那么可以初步估算设置 10 到 50 个分片。同时,还需要考虑查询模式,如果查询主要是针对全量数据的聚合查询,那么较少的分片可能更合适,因为可以减少聚合的开销;如果查询主要是基于单个文档或小范围数据的检索,较多的分片可以提高并行处理能力。

1.2 分片数量对写入性能的影响

  • 过少分片的写入瓶颈:在写入操作时,过少的分片会导致写入请求集中在少数几个分片上,形成写入瓶颈。例如,只有 1 个分片的索引,所有的写入请求都要在这个分片上进行处理,当写入并发量较高时,这个分片可能会成为性能瓶颈,导致写入延迟增加。
// 使用Java客户端进行写入操作示例(假设索引为my_index_1shard)
RestHighLevelClient client = new RestHighLevelClient(
    RestClient.builder(
        new HttpHost("localhost", 9200, "http")));

IndexRequest request = new IndexRequest("my_index_1shard")
   .source("field1", "value1", "field2", "value2",
            XContentType.JSON);
try {
    IndexResponse response = client.index(request, RequestOptions.DEFAULT);
} catch (IOException e) {
    e.printStackTrace();
}
  • 过多分片对写入的影响:过多的分片虽然可以分散写入负载,但由于每个分片都需要进行一些内部的处理,如日志记录、索引更新等,过多的分片会导致整体的写入开销增加。而且,在写入时 ElasticSearch 需要保证数据在各个分片之间的一致性,过多的分片会使这个过程变得更加复杂,从而影响写入性能。

1.3 分片副本设置对性能的影响

  • 副本的作用:副本是分片的拷贝,主要用于提高数据的可用性和容错性。当某个主分片所在的节点出现故障时,副本分片可以替代主分片继续提供服务。同时,副本也可以用于分担读请求,提高查询性能。
  • 副本数量对查询性能的提升:增加副本数量可以提高查询的并行度。例如,当有多个读请求时,不同的副本可以同时处理这些请求,从而减少查询的响应时间。假设我们有一个索引,设置了 3 个副本,那么在查询时,最多可以有 4 个分片(1 个主分片和 3 个副本分片)参与查询处理,大大提高了查询的吞吐量。
// 创建一个有3个副本的索引示例
PUT my_index_3replicas
{
    "settings": {
        "number_of_shards": 5,
        "number_of_replicas": 3
    }
}
  • 副本数量对写入性能的影响:然而,副本数量的增加会对写入性能产生负面影响。每次写入操作不仅要更新主分片,还要同步更新所有的副本分片。这意味着写入操作需要更多的网络传输和磁盘 I/O 操作。例如,当有 3 个副本时,写入操作需要将数据同步到 4 个分片(1 个主分片和 3 个副本分片),这无疑增加了写入的延迟和资源消耗。

2. 索引存储设置对 ElasticSearch 性能的影响

ElasticSearch 的索引存储设置决定了数据如何在磁盘上存储,这对性能有着至关重要的影响。

2.1 存储类型选择

  • 默认存储类型:ElasticSearch 默认使用的是 Lucene 的存储格式,它将数据以倒排索引的形式存储。倒排索引非常适合文本搜索,它通过将文档中的每个词映射到包含该词的文档列表,大大提高了查询效率。在大多数情况下,默认的存储类型能够满足需求。
  • 其他存储类型:除了默认的 Lucene 存储,ElasticSearch 还支持一些其他的存储类型,如 TDR(Translog - Delayed Recovery)存储。TDR 存储主要用于特定场景,例如在数据恢复时,它可以减少恢复时间。然而,这种存储类型并不适用于所有场景,在选择时需要根据实际需求进行评估。

2.2 存储路径设置

  • 多路径存储:合理设置存储路径可以提高 ElasticSearch 的性能。可以将索引数据存储在多个磁盘路径上,这样可以利用多个磁盘的 I/O 带宽。例如,在一个拥有多个物理磁盘的服务器上,可以将不同的分片存储在不同的磁盘路径下。
# 在elasticsearch.yml中设置多个存储路径示例
path.data: /var/lib/elasticsearch/data1,/var/lib/elasticsearch/data2
  • 存储路径的 I/O 性能:存储路径所在磁盘的 I/O 性能对 ElasticSearch 性能影响巨大。如果使用的是机械硬盘(HDD),其读写速度相对较慢,可能会成为性能瓶颈。而固态硬盘(SSD)具有更高的读写速度,能够显著提升 ElasticSearch 的性能。因此,在条件允许的情况下,应尽量将索引数据存储在 SSD 上。

2.3 存储压缩设置

  • 压缩的好处:ElasticSearch 支持对索引数据进行压缩,压缩可以减少磁盘空间的占用,同时在某些情况下还能提高 I/O 性能。当数据被压缩后,从磁盘读取的数据量减少,这意味着可以更快地将数据加载到内存中进行处理。
  • 压缩算法选择:ElasticSearch 提供了多种压缩算法,如 LZ4、Snappy 等。不同的压缩算法在压缩比和压缩速度上有所不同。例如,LZ4 算法具有较高的压缩速度,适用于对实时性要求较高的场景;而 Snappy 算法则在压缩比和速度之间取得了较好的平衡。
// 创建索引时设置压缩算法为LZ4示例
PUT my_index_compressed
{
    "settings": {
        "index.codec": "lz4"
    }
}
  • 压缩对性能的影响:虽然压缩可以带来一些好处,但也并非没有代价。压缩和解压缩过程需要消耗 CPU 资源。如果服务器的 CPU 资源有限,过多的压缩操作可能会导致 CPU 使用率过高,从而影响 ElasticSearch 的整体性能。因此,在选择压缩设置时,需要综合考虑磁盘空间、I/O 性能和 CPU 资源等因素。

3. 索引映射设置对 ElasticSearch 性能的影响

索引映射定义了文档中字段的类型、存储方式以及如何进行索引等信息,它对 ElasticSearch 的性能也有着重要的影响。

3.1 字段类型选择

  • 正确选择字段类型:选择合适的字段类型是优化性能的关键。例如,如果一个字段只包含数字,应选择合适的数值类型,如 longdouble,而不是将其定义为字符串类型。将数字存储为字符串类型不仅会占用更多的存储空间,而且在进行数值计算或范围查询时,性能会受到严重影响。
// 正确定义数值类型字段示例
PUT my_index_numeric
{
    "mappings": {
        "properties": {
            "price": {
                "type": "double"
            }
        }
    }
}
  • 复杂类型字段:对于复杂类型字段,如 objectnested 类型,需要谨慎使用。这些类型在处理数据时相对复杂,查询性能可能不如简单类型。例如,nested 类型用于处理数组中的对象,每个嵌套对象都被索引为一个单独的文档,这在查询时会增加一些开销。

3.2 字段索引设置

  • 是否索引字段:并非所有的字段都需要进行索引。如果一个字段仅用于存储数据,而不会在查询中使用,那么将其设置为不索引可以节省存储空间和索引构建时间。例如,一个存储文档创建时间戳的字段,如果只是用于记录而不会用于查询过滤等操作,可以设置为不索引。
// 设置字段不索引示例
PUT my_index_nonindexed
{
    "mappings": {
        "properties": {
            "creation_timestamp": {
                "type": "date",
                "index": false
            }
        }
    }
}
  • 索引方式选择:对于需要索引的字段,还可以选择不同的索引方式。例如,text 类型字段默认会进行分词处理,将文本拆分成多个词项进行索引。但在某些情况下,如果不需要分词,如字段是一个固定的标识符,可以将其设置为 keyword 类型,这样可以提高查询性能。
// 将字段设置为keyword类型示例
PUT my_index_keyword
{
    "mappings": {
        "properties": {
            "product_id": {
                "type": "keyword"
            }
        }
    }
}

3.3 动态映射与静态映射

  • 动态映射:ElasticSearch 支持动态映射,即当写入一个新的文档时,如果文档中的字段在映射中不存在,ElasticSearch 会自动为其添加映射。动态映射方便了开发,但也可能带来一些性能问题。例如,如果文档结构不固定,动态映射可能会不断创建新的字段映射,增加索引的复杂性和维护成本。
// 动态映射示例,写入一个新文档时自动创建映射
POST my_index_dynamic/_doc
{
    "new_field": "value"
}
  • 静态映射:相比之下,静态映射需要在创建索引时就明确定义好所有的字段及其映射。虽然这需要更多的前期规划,但可以避免动态映射带来的一些问题,提高索引的稳定性和性能。例如,在处理大规模数据时,静态映射可以确保索引结构的一致性,减少不必要的映射调整。
// 静态映射示例,创建索引时定义好所有字段
PUT my_index_static
{
    "mappings": {
        "properties": {
            "field1": {
                "type": "text"
            },
            "field2": {
                "type": "integer"
            }
        }
    }
}

4. 索引刷新与合并设置对 ElasticSearch 性能的影响

索引刷新和合并操作是 ElasticSearch 维护索引结构和性能的重要机制,但它们的设置也会对性能产生显著影响。

4.1 索引刷新设置

  • 刷新的概念:刷新是指将内存中的数据写入磁盘,使新的数据能够被搜索到。默认情况下,ElasticSearch 每隔 1 秒自动执行一次刷新操作,这使得 ElasticSearch 能够提供近实时的搜索功能。
  • 刷新间隔调整:然而,在某些场景下,默认的刷新间隔可能并不合适。如果写入操作非常频繁,频繁的刷新会导致大量的磁盘 I/O 操作,从而影响写入性能。在这种情况下,可以适当延长刷新间隔,例如将刷新间隔设置为 5 秒或更长时间,以减少磁盘 I/O 压力。
// 创建索引时设置刷新间隔为5秒示例
PUT my_index_refresh
{
    "settings": {
        "refresh_interval": "5s"
    }
}
  • 手动刷新:除了自动刷新,还可以手动执行刷新操作。例如,在批量写入完成后,可以手动刷新索引,使新写入的数据立即可用。但手动刷新应谨慎使用,因为它也会触发磁盘 I/O 操作。
// 使用Java客户端手动刷新索引示例
RestHighLevelClient client = new RestHighLevelClient(
    RestClient.builder(
        new HttpHost("localhost", 9200, "http")));

RefreshRequest request = new RefreshRequest("my_index_refresh");
try {
    RefreshResponse response = client.refresh(request, RequestOptions.DEFAULT);
} catch (IOException e) {
    e.printStackTrace();
}

4.2 索引合并设置

  • 合并的作用:索引合并是将多个较小的 Lucene 段合并成一个较大的段的过程。合并可以减少段的数量,提高查询性能。因为查询时需要搜索的段数量减少,从而减少了查询的开销。
  • 合并策略:ElasticSearch 提供了多种合并策略,如 log - doc 策略和 log - size 策略。log - doc 策略根据段中的文档数量来决定是否进行合并,而 log - size 策略则根据段的大小来决定。选择合适的合并策略需要根据数据的特点和查询模式来确定。
// 创建索引时设置合并策略为log - size示例
PUT my_index_merge
{
    "settings": {
        "index.merge.policy.type": "log_size"
    }
}
  • 合并频率调整:合并操作会消耗大量的磁盘 I/O 和 CPU 资源。如果合并频率过高,会影响 ElasticSearch 的正常运行。可以通过调整合并相关的参数,如 max_merge_at_once(一次最多合并的段数)和 merge_factor(触发合并的段数阈值)来控制合并的频率。例如,适当降低 max_merge_at_once 的值可以减少合并时的资源消耗,但可能会导致合并过程变长。
// 创建索引时设置合并参数示例
PUT my_index_merge_params
{
    "settings": {
        "index.merge.policy.max_merge_at_once": 3,
        "index.merge.policy.merge_factor": 10
    }
}

5. 索引查询设置对 ElasticSearch 性能的影响

索引查询设置涉及到查询语句的编写、查询缓存的使用等方面,这些设置对 ElasticSearch 的查询性能有着直接的影响。

5.1 查询语句优化

  • 避免全量扫描:在编写查询语句时,应尽量避免全量扫描。例如,使用过滤器来缩小查询范围,而不是直接进行全量的 match_all 查询。通过添加过滤条件,可以减少需要检索的文档数量,从而提高查询性能。
// 避免全量扫描,使用过滤器示例
GET my_index/_search
{
    "query": {
        "bool": {
            "filter": {
                "term": {
                    "category": "electronics"
                }
            }
        }
    }
}
  • 使用合适的查询类型:根据查询需求选择合适的查询类型。例如,对于文本搜索,match 查询适用于模糊匹配,而 term 查询适用于精确匹配。如果使用不当,可能会导致查询结果不准确或性能低下。例如,在对 keyword 类型字段进行精确匹配时,应使用 term 查询。
// 对keyword类型字段使用term查询示例
GET my_index/_search
{
    "query": {
        "term": {
            "product_id": "12345"
        }
    }
}

5.2 查询缓存设置

  • 查询缓存的原理:ElasticSearch 提供了查询缓存功能,它可以缓存查询结果,当相同的查询再次执行时,直接从缓存中获取结果,从而提高查询性能。查询缓存基于查询语句和索引版本进行缓存。
  • 缓存配置:可以通过配置来控制查询缓存的行为。例如,可以设置缓存的大小、缓存的过期时间等。合理设置缓存大小非常重要,如果缓存设置过小,可能无法充分利用缓存的优势;如果设置过大,可能会占用过多的内存资源。
// 设置查询缓存大小示例
PUT my_index_cache
{
    "settings": {
        "indices.queries.cache.size": "10%"
    }
}
  • 缓存的局限性:需要注意的是,查询缓存并非适用于所有场景。如果索引数据更新频繁,缓存的命中率可能会很低,因为每次数据更新都会导致索引版本变化,使得缓存失效。在这种情况下,过多依赖查询缓存可能无法带来显著的性能提升。

6. 索引生命周期管理设置对 ElasticSearch 性能的影响

索引生命周期管理(ILM)允许用户定义索引的创建、滚动、删除等操作的策略,合理设置 ILM 对 ElasticSearch 的性能和资源管理至关重要。

6.1 索引创建策略

  • 基于时间的创建:可以根据时间来创建索引,例如每天创建一个新的索引。这种方式适用于数据按时间序列增长的场景,如日志数据。通过定期创建新索引,可以避免单个索引数据量过大,同时也便于管理和查询。
// 使用ILM策略每天创建新索引示例
PUT _ilm/policy/my_ilm_policy
{
    "policy": {
        "phases": {
            "hot": {
                "actions": {
                    "rollover": {
                        "max_age": "1d",
                        "max_docs": 1000000
                    }
                }
            }
        }
    }
}
  • 基于数据量的创建:除了基于时间,还可以根据数据量来创建新索引。当索引中的文档数量达到一定阈值时,自动创建新索引。这种方式可以确保每个索引的数据量保持在一个合理的范围内,从而提高性能。

6.2 索引滚动策略

  • 滚动的概念:索引滚动是指当一个索引达到一定的条件(如时间或数据量)时,将其切换为只读状态,并创建一个新的可写索引。滚动操作可以确保数据的持续写入,同时避免单个索引过大。
  • 滚动策略优化:合理设置滚动策略的参数,如滚动的时间间隔和数据量阈值,对于性能优化非常重要。如果滚动过于频繁,会增加系统的开销;如果滚动不及时,可能会导致索引数据量过大,影响性能。

6.3 索引删除策略

  • 及时删除无用索引:对于不再需要的索引,应及时删除。无用索引不仅占用磁盘空间,还会增加 ElasticSearch 的管理开销。可以通过设置 ILM 策略,在索引达到一定的保留时间后自动删除。
// 在ILM策略中设置索引删除示例
PUT _ilm/policy/my_ilm_policy
{
    "policy": {
        "phases": {
            "delete": {
                "min_age": "30d",
                "actions": {
                    "delete": {}
                }
            }
        }
    }
}
  • 删除操作的影响:虽然删除索引可以释放资源,但删除操作本身也会消耗一定的资源。在执行删除操作时,ElasticSearch 需要处理索引的元数据和数据文件的删除,因此应选择合适的时机进行删除操作,避免对正常的业务操作产生影响。