👉 点击关注不迷路
👉 点击关注不迷路
👉 点击关注不迷路
文章大纲
- Elasticsearch数据更新与删除深度解析:2.3.1 避免频繁更新(Update by Query的代价)
- 案例背景
- 1. `Update by Query`的内部机制解析
- 1.1 文档更新底层实现原理
- 1.2 更新操作资源消耗模型
- 2. 频繁更新引发的性能问题
- 2.1 分段(`Segment`)爆炸效应
- 2.2 版本控制开销
- 2.3 真实问题诊断数据
- 3. 真实场景压力测试数据
- 3.1 测试环境
- 3.2 不同更新频率对比(更新频率与性能衰减关系)
- 3.3 性能衰减曲线
- 4. 优化方案与替代策略
- 4.1 数据结构优化
- 4.2 写入模式改造
- 4.3 版本控制优化配置
- 5. 生产环境故障恢复实践
- 5.1 紧急止血方案
- 5.2 长期治理措施
- 5.3 效果验证
- 关键结论与最佳实践
-
Elasticsearch数据更新与删除深度解析:2.3.1 避免频繁更新(Update by Query的代价)
案例背景
某物流追踪平台在业务升级后出现集群性能断崖式下降
:
- 数据规模:每日处理5亿条物流状态更新
- 更新模式:使用
_update_by_query
实时修改运单状态 - 问题表现:
- 写入吞吐量从12万ops/s暴跌至2.3万ops/s
- 查询延迟
P99
从180ms上升到2100ms - 磁盘
IOPS
持续保持98%以上
1. Update by Query
的内部机制解析
1.1 文档更新底层实现原理
操作类型 | 写入放大系数 | 磁盘IO类型 | 是否触发Merge |
---|
Index (新建) | 1x | 顺序写入 | 低概率 |
Update (更新) | 3-5x | 随机读写混合 | 必然触发 |
Delete (删除) | 2-3x | 随机读+顺序写 | 中等概率 |
1.2 更新操作资源消耗模型
总消耗 = 读操作(获取原文) + 写操作(新文档) + 删除标记 + 版本更新
≈ 2.5 × 文档大小 × 副本数
- 典型资源消耗比例:
- CPU消耗:比新建操作高
40-60%
- 磁盘IO:比新建操作高
300-500%
- 内存压力:需要维护版本映射表
2. 频繁更新引发的性能问题
2.1 分段(Segment
)爆炸效应
# 查看分段状态
GET /_cat/segments?v
# 问题集群输出示例:
index shard prirep segment generation docs.count size.mb
logs-2023 0 p _6 1500000 350
logs-2023 0 p _7 800000 180 # 更新产生的分段
logs-2023 0 p _8 750000 170
- 分段异常特征:
- 小分段(<100MB)数量超过50个
- 单个分片分段总数超过100
- 存在大量
docs.deleted>30%
的分段
2.2 版本控制开销
总内存消耗 ≈ 文档数 × 16 bytes × 副本数
16B × 100,000,000 × 2 = 3.2GB
2.3 真实问题诊断数据
// 节点性能分析
{
// "io" 部分包含了与磁盘输入输出(I/O)操作相关的性能指标
"io": {
// "write_throughput" 表示磁盘的写入吞吐量,即单位时间内磁盘能够写入的数据量
// 这里显示为 "450MB/s",但注释提示正常值应小于 200MB/s
// 较高的写入吞吐量可能意味着系统正在进行大量的数据写入操作,可能会对磁盘性能造成较大压力
"write_throughput": "450MB/s", // 正常值<200MB/s
// "iowait_percent" 表示 CPU 等待磁盘 I/O 操作完成的时间占比
// 这里的值为 98.7,意味着 CPU 大部分时间都在等待磁盘 I/O 操作,磁盘 I/O 可能成为系统的性能瓶颈
// 高 iowait 可能会导致系统响应变慢,影响应用程序的性能
"iowait_percent": 98.7
},
// "jvm" 部分包含了与 Java 虚拟机(JVM)相关的性能指标
"jvm": {
// "old_gc_count" 表示老年代垃圾回收(Old GC)的次数
// 注释提示正常情况下老年代垃圾回收次数应小于 5 次/分钟
// 这里显示为 35,说明老年代垃圾回收过于频繁
// 频繁的老年代垃圾回收会导致系统停顿,影响应用程序的响应时间和吞吐量
"old_gc_count": 35, // 正常<5次/分钟
// "buffer_pools" 表示 JVM 中的缓冲区池信息
"buffer_pools": {
// "direct" 表示直接缓冲区池的使用情况
// "4.2GB/5GB" 表示当前直接缓冲区池已使用 4.2GB 的内存,总容量为 5GB
// 这反映了堆外内存的使用情况,较高的使用比例可能会导致堆外内存压力增大,甚至可能引发内存溢出错误
"direct": "4.2GB/5GB" // 堆外内存压力
}
}
}
3. 真实场景压力测试数据
3.1 测试环境
组件 | 配置详情 |
---|
ES集群 | 3节点(16C64G NVMe SSD) |
测试数据集 | 1亿条物流数据(含15个字段) |
测试模式 | 持续30分钟混合负载(更新+查询) |
3.2 不同更新频率对比(更新频率与性能衰减关系)
更新频率 | 吞吐量(ops/s) | 磁盘IOPS | 段数量/分片 | GC停顿(s/min) |
---|
100次/秒 | 82,000 | 18,000 | 12 | 0.8 |
500次/秒 | 47,000 | 53,000 | 38 | 3.2 |
1000次/秒 | 19,000 | 89,000 | 71 | 6.5 |
2000次/秒 | 服务不可用 | 100% | 120+ | Full GC卡死 |
3.3 性能衰减曲线
Throughput (k ops/s)
100 ┤■■■■■■■■■■■■■■■■■■■■
80 ┤■■■■■■■■■■■□□□□□□□□
60 ┤■■■■■■■□□□□□□□□□□□□
40 ┤■■■□□□□□□□□□□□□□□□□
20 ┤□□□□□□□□□□□□□□□□□□□
└───────────────────
原始负载 更新+10% 更新+30% 更新+50%
- 吞吐量的单位是千操作每秒(k ops/s)
- 原始负载:对应柱状图高度达到 100 k ops/s 的柱子。这表明在原始负载的情况下,系统的吞吐量能够达到 100 千操作每秒,此时系统处于一个相对稳定且高效的处理状态。
- 更新 +10%:柱状图高度约为 80 k ops/s。当负载在原始基础上增加 10% 时,系统的吞吐量下降到了 80 千操作每秒。这可能是因为系统开始受到额外负载的影响,资源逐渐变得紧张,但仍能保持较高的处理能力。
- 更新 +50%:柱状图高度约为 40 k ops/s。当负载增加到原始负载的 50% 时,系统的吞吐量大幅下降到 40 千操作每秒。
这显示出系统在高负载下已经难以维持高效的处理能力,可能出现了性能瓶颈
,如 CPU 使用率过高、内存不足、磁盘 I/O 瓶颈等问题。
4. 优化方案与替代策略
4.1 数据结构优化
- 不可变数据模型设计:
- 在不可变数据模型里,一旦数据被写入 Elasticsearch,就不会再被修改。
- 若要更新数据,不会直接在原数据上操作,而是创建一条新的数据记录来替换旧的,旧数据仍然保留在系统中。
// 原始结构(可修改)
{
"order_no": "20230809123456",
"status": "shipped",
"update_time": "2023-08-09T12:00:00"
}
// 优化结构(不可变)
{
"order_no": "20230809123456",
"status_history": [
{
"status": "created",
"time": "2023-08-09T10:00:00"
},
{
"status": "shipped",
"time": "2023-08-09T12:00:00"
}
]
}
4.2 写入模式改造
- 事件溯源模式:


4.3 版本控制优化配置
# 调整索引配置
# 此操作是对名为 "orders" 的 Elasticsearch 索引进行配置调整,目的是优化索引的性能和功能,以适应特定的业务需求。
# PUT 请求用于更新资源,这里是更新 "orders" 索引的设置。
PUT /orders/_settings
{
"index": {
# "refresh_interval" 用于设置索引的刷新间隔。
# 索引刷新操作会将内存中的数据刷新到磁盘上,使其可以被搜索到。
# 这里将刷新间隔设置为 "30s",即每隔 30 秒进行一次刷新操作。
# 降低刷新频率可以减少磁盘 I/O 操作,提高索引性能,但会增加数据的可见延迟,即新写入的数据可能需要更长时间才能被搜索到。
"refresh_interval": "30s", # 降低刷新频率
# "number_of_replicas" 表示索引的副本数量。
# 副本用于提高数据的可用性和可靠性,当主分片出现故障时,副本分片可以替代主分片继续提供服务。
# 这里将副本数量设置为 0,意味着在写入数据时关闭副本。
# 关闭副本可以减少写入时的同步开销,提高写入性能,但会降低数据的冗余性和可用性。在数据写入完成后,可以根据需要再将副本数量调整回来。
"number_of_replicas": 0, # 写入时关闭副本
# "soft_deletes" 用于配置软删除功能。
# 软删除是指在删除文档时,并不立即从磁盘上物理删除文档,而是标记为已删除,以便后续可以恢复。
"soft_deletes": {
# "enabled": true 表示启用软删除功能。
# 此功能从 Elasticsearch 7.0 版本开始支持,启用后可以在删除文档时保留文档的元数据,方便进行数据恢复和审计。
"enabled": true, # 7.0+ 启用软删除
# "retention_leases" 用于控制软删除文档的保留策略。
"retention_leases": {
# "enabled": true 表示启用保留租约控制。
# 保留租约控制可以确保在指定的时间内,软删除的文档不会被物理删除,以便在需要时可以恢复这些文档。
"enabled": true # 保留租约控制
}
}
}
}
5. 生产环境故障恢复实践
5.1 紧急止血方案
# 第一步:限制更新速率
# 此步骤的目的是对整个 Elasticsearch 集群的索引存储更新速率进行限制,避免因过高的写入速率导致磁盘 I/O 压力过大,影响集群的稳定性和性能。
# PUT 请求用于更新集群的设置,这里更新的是集群的持久化设置,持久化设置会在集群重启后依然生效。
PUT _cluster/settings
{
"persistent": {
# "indices.store.throttle.max_bytes_per_sec" 是一个集群级别的设置参数,用于限制索引存储时每秒允许的最大字节数。
# 这里将其设置为 "50mb",意味着集群中所有索引在存储数据时,每秒写入磁盘的数据量不能超过 50 兆字节。
"indices.store.throttle.max_bytes_per_sec": "50mb"
}
}
# 第二步:关闭副本加快 merge
# 此步骤是为了在进行某些操作(如强制合并分段)时,提高操作的速度。因为副本的存在会增加数据同步和处理的开销,关闭副本可以减少这些额外的操作。
# PUT 请求用于更新索引的设置,这里使用 /_all 表示对集群中的所有索引进行设置更新。
PUT /_all/_settings
{
# "index.number_of_replicas" 用于设置索引的副本数量。
# 这里将其设置为 0,即关闭所有索引的副本。这样在后续的强制合并分段操作中,就不需要考虑副本数据的同步问题,从而加快合并的速度。
# 但需要注意的是,关闭副本会降低数据的冗余性和可用性,在操作完成后,建议根据实际需求恢复副本数量。
"index.number_of_replicas": 0
}
# 第三步:强制合并分段
# 此步骤的主要目的是对名为 "orders" 的索引进行分段合并操作,以减少索引中的分段数量,提高查询性能。
# 在 Elasticsearch 中,数据是以分段(Segment)的形式存储的,随着数据的不断写入和删除,分段数量会逐渐增多,这会增加查询的开销。
# 通过强制合并分段,可以将多个小分段合并成较少的大分段,从而提高查询效率。
# POST 请求用于触发强制合并操作。
# /orders/_forcemerge 表示对 "orders" 索引执行强制合并操作。
#?max_num_segments=10 是一个查询参数,指定合并后索引中分段的最大数量为 10。
POST /orders/_forcemerge?max_num_segments=10
5.2 长期治理措施
更新类型 | 频率要求 | 实现方式 | 目标延迟 |
---|
实时型 | <1秒 | 应用层直接更新 | 500ms |
准实时型 | 1-5分钟 | Kafka +批量更新 | 3分钟 |
延迟型 | >30分钟 | Logstash 聚合更新 | 1小时 |
5.3 效果验证
指标 | 优化前 | 优化后 | 改善幅度 |
---|
写入吞吐量 | 19,000/s | 68,000/s | 258% |
段数量/分片 | 71 | 9 | 87% |
磁盘IOPS | 89,000 | 22,000 | 75% |
GC 停顿时间 | 6.5s/min | 1.2s/min | 81% |
关键结论与最佳实践
避坑指南
-
-
- 更新频率红线:单个分片每秒更新不超过50次
-
- 版本数监控:定期检查
_version
字段的统计分布
-
- 分段健康度:控制每个分片分段数<50,单个分段>100MB
-
- 更新模式选择:
- 单字段更新 → 使用
Painless
脚本 - 多字段更新 → 整文档替换
状态变更 → 采用追加模式
终极解决方案
Lambda
架构实现:

