Elasticsearch 优化方案
一、概要
Elasticsearch 优化是一个系统工程,需要根据实际业务场景、数据特点和查询模式进行针对性调整。关键优化方向包括:
- 精心设计索引结构:字段类型选择,生命周期管理。
- 优化查询DSL:避免高开销操作,合理使用缓存。
- 提升写入效率:批量操作,调整刷新策略。
- 保障硬件资源:足够的内存、高性能存储和网络。
- 合理规划集群架构:节点角色分离,分片策略优化。
- 持续监控维护:定期健康检查,性能调优。
二、索引设计优化
-
索引结构优化
-
数据类型选择优化:
- 避免过度使用 text 类型:对于不需要全文搜索的字段,使用 keyword 类型更高效。
- 数值类型选择:根据实际范围选择最小够用的类型(byte, short, integer, long, float, double)。
- 地理数据:使用 geo_point 或 geo_shape 而非字符串存储。
- 日期类型:始终使用 date 类型而非字符串存储日期。
-
字段索引控制:
- 禁用不需要搜索的字段索引。
- 纯展示字段。
- 大文本日志字段。
- 二进制数据。
{ "mappings": { "properties": { "log_time": { "type": "date", "index": false // 不索引该字段 } } } }
- norms 控制:对不需要评分(score)的字段禁用 norms 节省空间。
- 仅用于过滤(filter)的字段。
- 仅用于聚合(aggregations)的字段。
- 精确值匹配的字段(keyword)。
- 不需要相关性评分的 text 字段。
{ "text_field": { "type": "text", "norms": false } }
- 禁用不需要搜索的字段索引。
-
文本字段优化:
-
text vs keyword:
- 需要全文搜索:text + keyword(多字段)
- 只需要精确匹配/聚合:仅 keyword
-
分词器配置:
- 选择合适的分析器(analyzer)
- 搜索时和索引时分析器可以不同
- 考虑自定义分析器链
-
字段长度控制:
- 防止过长的关键字被索引:
- 超过指定长度的文本不被索引。
- 减少索引大小,提高性能。
- 控制存储开销:
- 过长的关键字会占用大量内存,影响性能。
- 特别对聚合、排序操作有显著影响。
{ "long_text": { "type": "text", "ignore_above": 1024 // 超过此长度的文本不被索引 } }
- 防止过长的关键字被索引:
-
-
多字段(Multi-fields)策略
- 多种搜索方式支持:同一字段可以同时支持精确匹配和全文搜索。
- 减少字段冗余:避免为同一数据创建多个字段,降低索引大小。
- 支持不同的分析器:同一字段可以使用不同分析器建立多个索引。
- 共享字段数据:多字段共享原始值,减少存储开销。
{ "product_name": { "type": "text", "analyzer": "ik_max_word", // 中文分词 "fields": { "keyword": { "type": "keyword", "ignore_above": 256 }, "english": { "type": "text", "analyzer": "english" } } } }
-
nested(嵌套)类型
PUT /products { "mappings": { "properties": { "name": { "type": "text" }, "reviews": { "type": "nested", "properties": { "author": { "type": "keyword" }, "rating": { "type": "float" }, "comment": { "type": "text" } } } } } }
- 控制嵌套深度:
- 避免多层嵌套(建议不超过3层)。
- 嵌套层级过深会显著增加查询复杂度。
- 限制数组大小:
- 单个文档的嵌套对象数量不宜过多(通常<100)。
- 大数组会明显影响性能。
- 与父子关系的比较
特性 nested类型 父子关系 数据模型 一对少量,强关联 一对多量,弱关联 性能特点 查询快,写入成本高 写入快,查询成本较高 适用场景 对象少且频繁共同查询 对象多且需要独立更新 内存消耗 较高 较低
- 控制嵌套深度:
-
其他优化技巧
- copy_to 合并字段:创建自定义的"超级字段"。
- 搜索简化:无需构建复杂的多字段查询,单字段搜索替代多个should子句。
- 性能优化:减少查询解析时间,避免昂贵的跨字段查询(如multi_match)。
- 灵活性:为组合字段单独配置分析器,不影响原始字段的独立使用。
{ "first_name": { "type": "text", "copy_to": "full_name" }, "last_name": { "type": "text", "copy_to": "full_name" }, "full_name": { "type": "text" } }
- doc_values 控制:对不需要排序/聚合的字段禁用
{ "session_data": { "type": "keyword", "doc_values": false } }
- index_options:控制倒排索引中存储的内容(docs/freqs/positions/offsets)
- docs (最基本)
- 只索引文档是否存在。
- 适用于仅需判断文档是否匹配的场景。
- 不支持位置查询、高亮等高级功能。
- freqs (文档+词频)
- 记录文档和词项频率。
- 支持相关性评分(TF-IDF)。
- 仍不支持位置查询。
- positions (文档+词频+位置) - 默认值
- 记录文档、词频和词项位置。
- 支持短语查询、位置查询和高亮。
- 大多数全文搜索场景的推荐设置。
- offsets (文档+词频+位置+偏移量)
- 记录文档、词频、位置和字符偏移。
- 支持高亮显示时需要此选项。
- 索引体积最大,性能开销最高。
- 性能与存储影响
选项 索引大小 查询性能 功能支持 docs 最小 最快 仅匹配 freqs 小 快 匹配+评分 positions 中等 中等 匹配+评分+短语查询 offsets 最大 最慢 匹配+评分+短语查询+高亮
- docs (最基本)
- copy_to 合并字段:创建自定义的"超级字段"。
-
-
索引生命周期管理
-
阶段配置优化
- 热阶段(Hot)精细化配置
"hot": { "actions": { "rollover": { "max_primary_shard_size": "50gb", // 更精确的主分片大小控制 "max_age": "24h", // 根据写入速率调整 "max_docs": 20000000 // 基于文档体积设定 }, "shrink": { // 热阶段预收缩 "number_of_shards": 3 // 保留适当分片数平衡吞吐 }, "set_priority": { "priority": 100 // 确保热索引高优先级 } } }
- 优化要点:
- 使用max_primary_shard_size替代max_size实现分片均衡。
- 根据业务高峰时段调整max_age避免集中滚动。
- 热阶段预收缩减少后续温阶段压力。
- 优化要点:
- 温阶段(Warm)性能优化
"warm": { "min_age": "12h", // 根据查询模式调整 "actions": { "allocate": { "number_of_replicas": 1, // 温数据保留1副本 "require": { "data": "warm", "disk_type": "hdd" // 明确存储类型 } }, "forcemerge": { "max_num_segments": 3, // 不完全合并为1,平衡IO压力 "index_codec": "best_compression" // 启用压缩 }, "readonly": {} // 确保只读 } }
- 优化要点:
- 分阶段执行forcemerge(先合并到10段,再合并到3段)。
- 采用best_compression编码节省30%存储空间。
- 设置min_age避免过早迁移影响查询。
- 优化要点:
- 冷阶段(Cold)存储优化
"cold": { "min_age": "30d", "actions": { "allocate": { "require": { "data": "cold", "storage": "object" // 对象存储标识 } }, "searchable_snapshot": { // 可搜索快照 "snapshot_repository": "s3-repo", "force_merge_index": true // 快照前强制合并 }, "unfollow": {} // 解除CCR跟随 } }
- 优化要点:
- 与对象存储(S3/OBS)深度集成。
- 冷索引采用searchable_snapshot+force_merge组合。
- 对于CCR索引自动解除跟随关系。
- 优化要点:
- 热阶段(Hot)精细化配置
-
存储分层优化
- 热数据层配置
# elasticsearch.yml node.roles: [ data_hot, ingest ] node.attr.storage_tier: "hot_ssd" path.data: /opt/elasticsearch/hot_data # SSD存储路径
- 温数据层配置
node.roles: [ data_warm ] node.attr.storage_tier: "warm_hdd" path.data: /opt/elasticsearch/warm_data # HDD存储路径
- 冷数据层对象存储集成
PUT _snapshot/s3-repository { "type": "s3", "settings": { "bucket": "my-es-cold-data", "endpoint": "s3.ap-east-1.amazonaws.com", "base_path": "indices/", "chunk_size": "1gb", "max_restore_bytes_per_sec": "200mb" } }
- 热数据层配置
-
三、查询性能优化
- 使用过滤器(filter)替代查询(query)
- filter结果可缓存,避免评分计算。
GET /orders/_search { "query": { "bool": { "must": [ { "match": { "product": "laptop" } } ], "filter": [ // 不计算相关性的过滤条件 { "range": { "price": { "gte": 1000 } } }, { "term": { "status": "completed" } } ] } } }
- filter结果可缓存,避免评分计算。
- 避免深度分页
- 深度分页使用search_after代替from+size。
{ "size": 10, "sort": ["_doc"], "search_after": [12345] // 使用search_after代替from+size }
- 深度分页使用search_after代替from+size。
- 字段选择性加载
- 通过 _source 控制必要的返回字段。
GET /products/_search { "_source": ["name", "price"], // 只返回必要字段 "query": { ... } }
- 通过 _source 控制必要的返回字段。
- 使用异步搜索
- 对于复杂查询且对实时性要求不高。
POST /sales/_async_search { "query": { ... }, "size": 100 }
- 对于复杂查询且对实时性要求不高。
- 查询结果缓存
- 启用后自动缓存查询结果。
PUT /my_index/_settings { "index.requests.cache.enable": true }
- 启用后自动缓存查询结果。
- 合理使用doc_values
- doc_values列式存储比fielddata更高效,内存占用更低。
PUT /products { "mappings": { "properties": { "category": { "type": "keyword", // keyword类型默认启用doc_values "doc_values": true // 显式启用确保可用 } } } }
- doc_values列式存储比fielddata更高效,内存占用更低。
- 限制聚合桶数量
- 可以减少计算和网络传输开销。
GET /sales/_search { "aggs": { "top_categories": { "terms": { "field": "category.keyword", "size": 10 // 限制返回桶数量 } } } }
- 可以减少计算和网络传输开销。
- 使用近似聚合
- 大数据集去重计数,牺牲精确性换取性能。
GET /logs/_search { "aggs": { "unique_visitors": { "cardinality": { "field": "user_id.keyword", "precision_threshold": 100 // 精度控制 } } } }
- 大数据集去重计数,牺牲精确性换取性能。
- 分层采样聚合
- 大数据集探索性分析。
GET /events/_search { "aggs": { "sampled": { "sampler": { "shard_size": 200 // 每分片采样数量 }, "aggs": { "keywords": { "significant_terms": { "field": "message.keyword" } } } } } }
- 大数据集探索性分析。
四、写入性能优化
-
写入配置优化
- 调整刷新间隔:默认1s,增大可减少刷新开销,批量导入数据时可临时设置为-1(禁用刷新)。
PUT /logs/_settings { "index.refresh_interval": "30s" // 默认1s,增大可减少刷新开销 }
- 禁用暂时不需要的功能:提高写入速度,写入完成后恢复配置。
PUT /temp_data { "settings": { "index.number_of_replicas": 0, "index.refresh_interval": "-1", "index.translog.durability": "async" // 异步写事务日志 }, "mappings": { "dynamic": false, // 禁用动态映射 "properties": {...} } }
- 调整刷新间隔:默认1s,增大可减少刷新开销,批量导入数据时可临时设置为-1(禁用刷新)。
-
使用 Bulk API 批量写入
POST _bulk { "index" : { "_index" : "logs", "_id" : "1" } } { "timestamp": "2023-01-01T00:00:00", "message": "test" } { "create" : { "_index" : "logs", "_id" : "2" } } { "timestamp": "2023-01-01T00:00:01", "message": "test2" }
最佳实践:
- 单批次5-15MB数据
- 每批1000-5000个文档
- 多线程并发发送(3-5个线程)
-
使用Indexing Buffer调优
- index_buffer_size 默认10%,可适当增加JVM堆的堆内存占比。
PUT _cluster/settings { "persistent": { "indices.memory.index_buffer_size": "20%" // 默认10%,可适当增加 } }
- index_buffer_size 默认10%,可适当增加JVM堆的堆内存占比。
五、硬件与系统优化
-
硬件配置建议
- 内存:数据节点至少64GB,堆内存不超过32GB
- 磁盘:使用SSD,RAID 0配置
- CPU:16核以上
- 网络:10Gbps或更高带宽
-
JVM配置
# jvm.options -Xms16g # 堆内存最小值 -Xmx16g # 堆内存最大值,不超过物理内存50% -XX:+UseG1GC
-
操作系统优化
- Linux内核参数:
# 增加文件描述符限制 echo "* - nofile 655350" >> /etc/security/limits.conf # 虚拟内存配置 echo "vm.max_map_count=262144" >> /etc/sysctl.conf sysctl -p # 禁用swap swapoff -a echo "vm.swappiness = 1" >> /etc/sysctl.conf
- Linux内核参数:
六、集群层面优化
- 集群规划与配置
- 节点角色分离:
- 专用主节点:node.master: true, node.data: false
- 专用数据节点:node.master: false, node.data: true
- 专用协调节点:node.master: false, node.data: false
- 优化建议:
- 生产集群至少3个专用master节点
- 大数据集群分离data和ingest节点
- 节点角色分离:
- 分片策略优化
- 合理设置分片大小和数量:
- 每个分片大小建议在10-50GB之间
- 分片总数 = 节点数 × 每节点最大分片数(建议不超过1000)
PUT /logs { "settings": { "number_of_shards": 10, // 根据数据量决定 "number_of_replicas": 1, // 生产环境至少1个 "index.routing.allocation.total_shards_per_node": 2 // 控制每节点分片数 } }
- 冷热数据分层
PUT _ilm/policy/hot_warm_policy { "policy": { "phases": { "hot": { "actions": { "rollover": { "max_size": "50GB" }, "set_priority": { "priority": 100 } } }, "warm": { "min_age": "7d", "actions": { "forcemerge": { "max_num_segments": 1 }, "shrink": { "number_of_shards": 1 }, "allocate": { "require": { "data": "warm" } } } } } } }
- 合理设置分片大小和数量:
七、监控与维护
- 关键监控指标
# 集群健康 GET _cluster/health # 节点状态 GET _nodes/stats # 热点线程 GET _nodes/hot_threads # 磁盘使用 GET _cat/allocation?v
- 定期维护操作
# 强制合并段(只读索引) POST /logs-2023-01/_forcemerge?max_num_segments=1 # 清理缓存 POST /_cache/clear # 分片重平衡 PUT _cluster/settings { "persistent": { "cluster.routing.rebalance.enable": "all" } }
- 安全与稳定性
- 关键安全配置
# elasticsearch.yml xpack.security.enabled: true xpack.security.transport.ssl.enabled: true
- 熔断器设置
PUT _cluster/settings { "persistent": { "indices.breaker.total.limit": "70%", "network.breaker.inflight_requests.limit": "80%" } }
- 关键安全配置
- 灾难恢复策略
- 快照备份配置
PUT _snapshot/my_backup { "type": "fs", "settings": { "location": "/mnt/backups/es_backups", "max_snapshot_bytes_per_sec": "50mb", "max_restore_bytes_per_sec": "50mb" } } # 创建快照 PUT _snapshot/my_backup/snapshot_1?wait_for_completion=true { "indices": "*", "ignore_unavailable": true, "include_global_state": false }
- 跨集群复制(CCR)
PUT /_ccr/follow/logs-follower { "remote_cluster": "remote-cluster", "leader_index": "logs-leader", "max_read_request_operation_count": 5120, "max_outstanding_read_requests": 12 }
- 快照备份配置