👉 点击关注不迷路 👉 点击关注不迷路 👉 点击关注不迷路
文章大纲
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 文档更新底层实现原理
Update请求
创建新文档
标记旧文档为删除
Refresh操作后可见
Segment Merge时物理删除
操作类型 写入放大系数
磁盘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 × 副本数
16 B × 100 , 000 , 000 × 2 = 3.2 GB
2.3 真实问题诊断数据
// 节点性能分析
{
/ / "io" 部分包含了与磁盘输入输出(I / O)操作相关的性能指标
"io" : {
/ / "write_throughput" 表示磁盘的写入吞吐量,即单位时间内磁盘能够写入的数据量
/ / 这里显示为 "450MB/s" ,但注释提示正常值应小于 200 MB / s
/ / 较高的写入吞吐量可能意味着系统正在进行大量的数据写入操作,可能会对磁盘性能造成较大压力
"write_throughput" : "450MB/s" , / / 正常值 < 200 MB / 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.2 GB 的内存,总容量为 5 GB
/ / 这反映了堆外内存的使用情况,较高的使用比例可能会导致堆外内存压力增大,甚至可能引发内存溢出错误
"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
架构实现 :