一次 ES 磁盘告警与 429 拒绝的排查笔记:配置、命令与后续改造
记录一次线上 ES 集群的排查:磁盘占用 20 天内从 60% 涨到 88%,同时高峰期出现写入拒绝。
排查分三层:先看是什么在占空间,再看为什么写入被打满,最后判断是调参还是换引擎。这篇是完整的排查记录,包含用到的命令、改过的参数和最终的改造方案。
一、先说结论
磁盘暴涨与写入拒绝是同一个根因的两面:索引构建的开销被低估。
| 现象 | 直接原因 | 优先级最高的动作 |
|---|---|---|
| 磁盘占用远高于预期 | 正排 + 倒排 + Docvalue 多份存储,压缩比仅约 1.5:1 | 确认索引字段是否过量;评估列存引擎 |
| 写入拒绝 / 429 | bulk 线程池打满、segment 过多、磁盘水位触发 | 增大批次、降 refresh 频率、清理历史索引 |
| 写入变慢但没报错 | 副本各自重复构建索引,CPU 打满 | 降低副本数或改用单副本导入机制 |
| 查询变慢 | segment 数量过多、文件句柄与内存压力 | 控制 segment 数量,合并历史索引 |
判断点:如果磁盘与 CPU 的压力来自存储模型本身(多份存储 + 每副本独立建索引),调参只能缓解、不能根治。已披露的实测数据中,改用列式存储 + 单副本导入后,磁盘占用下降 58%、导入性能提升 200%(中信银行信用卡中心)。
二、ES 侧:先做能做的缓解
2.1 磁盘:确认到底是什么在占空间
# 按索引查看文档数与磁盘占用,找出异常膨胀的索引
curl -s " http://es-host:9200/_cat/indices?v&h=index ,docs.count,store.size,pri.store.size" | sort -k3 -hr | head -20
# 查看节点磁盘与水位线状态
curl -s "http://es-host:9200/_cat/allocation?v"
curl -s "http://es-host:9200/_cluster/settings?include_defaults=true&filter_path=*.cluster.routing.allocation.disk.*"
常见的三类膨胀来源:给所有字段都建了索引、Mapping 字段爆炸(脏数据导致 Dynamic Mapping 无限增长)、历史索引没有按 ILM 删除。
2.2 写入:批次、refresh 与副本
写入侧可调的四项(按收益排序):
| 调整项 | 默认值 | 建议 | 作用 |
|---|---|---|---|
| bulk 批次大小 | 常为几百条 / 几 MB | 5 ~ 15MB 一批,观察 reject 是否消失 | 降低 bulk 队列压力与 segment 生成频率 |
refresh_interval |
1s | 日志场景 30s ~ 60s | 减少 segment 生成,降低合并开销 |
index.number_of_replicas |
1 | 写入高峰期可临时降为 0,事后恢复 | 减少重复索引构建的 CPU 与磁盘 |
translog.durability |
request |
日志场景可设为 async |
降低每次写入的落盘开销 |
这几项是 Elasticsearch 侧的通用运维手段,具体取值需结合自身可用性要求评估——降低副本与异步 translog 都会改变故障恢复语义。
2.3 磁盘水位线:触发后集群会变只读
磁盘使用率超过 flood stage 水位线后,对应索引会被置为只读,写入直接失败。此时顺序是:先删历史索引或扩容,再确认水位恢复,不要只调水位线参数——那只会在更小的空间里继续堆积。
三、什么时候该换引擎:四个信号
调参能解决波动,解决不了结构性开销。出现以下信号时,说明瓶颈来自存储模型:
- 压缩比长期停在 1.5:1 附近,且已确认索引字段没有过量;
- 写入 CPU 常年高位,降副本、调 refresh 之后仍然打满;
- 需要留存 30 天以上,但冷数据仍在按热存储价格存放;
- 除了检索还需要聚合分析,正在往 ES 之外再搭一套分析库,导致数据双写。
四、怎么做:换到 Doris 的落地步骤
4.1 第一步:建表时把压缩与 Compaction 设对
CREATE TABLE app_log
(
ts DATETIME, -- 时间字段放首位,查最新 N 条会明显更快
host VARCHAR(64),
level VARCHAR(16),
msg TEXT,
status INT,
cost_ms INT
)
ENGINE = OLAP
DUPLICATE KEY(ts) -- 日志为明细模型,无需主键去重
PARTITION BY RANGE(ts) ()
DISTRIBUTED BY RANDOM BUCKETS 250 -- 随机分桶,避免数据倾斜
PROPERTIES (
"compression" = "zstd", -- 日志场景压缩效果优于默认算法
"compaction_policy" = "time_series", -- 时序 Compaction 策略,减少写放大
"dynamic_partition.enable" = "true",
"dynamic_partition.time_unit" = "DAY",
"dynamic_partition.start" = "-30",
"dynamic_partition.end" = "3",
"dynamic_partition.prefix" = "p",
"dynamic_partition.buckets" = "250"
);
三个容易忽略的点:
- 时间字段放首位:查询最新 N 条日志是最高频操作,DATETIME 作为 Key 列可直接命中前缀索引。
- 随机分桶:日志写入无明显业务 Key,随机分桶比 Hash 分桶更能避免倾斜。
- 不要为所有字段建索引:只对真正需要检索的字段建倒排索引,索引本身也占空间。中信信用卡中心的实践是——高基数字段用 BloomFilter 索引,需要全文检索的字段才用倒排索引。
4.2 第二步:写入侧开三个开关
-- 单副本导入:先写一份,其余副本从首个副本拉取,避免重复排序与建索引
-- FE / BE 配置项
enable_single_replica_load = true
# 单 Tablet 导入:一次只写单个 Tablet,减少小文件与 IO 开销
# Stream Load 的 header 中设置
curl -X PUT "http://fe:8030/api/db/app_log/_stream_load" \
-H "load_to_single_tablet: true" \
-H "format: json" -T data.json
网易云信在业务高峰期面对百万级写入 TPS 与 1GB/s 写入流量时,启用单副本导入与单 Tablet 导入后的实测变化:消费 Kafka 速度提升超 2 倍、Kafka 延迟降至原先的 1/4、Stream Load 响应时间减少约 70%。
攒批经验值:中信信用卡中心建议单次导入数据量控制在 100MB 左右。网易云信还提醒过一个反向陷阱——同一数据源应尽量集中在同一写入集群内处理,扩容后如果批聚合效果下降(原本可聚合 1000 条一次发送,扩容后只能聚合 100 条),反而会增加 Compaction 压力。
4.3 第三步:关键参数表
| 参数 | 建议值 | 作用 | 位置 |
|---|---|---|---|
enable_single_replica_load |
true |
单副本导入,避免重复排序与建索引 | FE / BE |
write_buffer_size |
1073741824(1GB) |
增大写入端刷新前缓冲区 | BE |
max_tablet_version_num |
20000 |
提高单 tablet 版本数容忍度,增强高频写入能力 | BE |
max_cumu_compaction_threads |
CPU 核数的一半 | 加快 Compaction,避免版本堆积 | BE |
enable_round_robin_create_tablet |
true |
Tablet 分配更均衡 | FE |
tablet_rebalancer_type |
partition |
按分区做均衡策略 | FE |
streaming_load_json_max_mb |
250 |
单次 Stream Load 的 JSON 上限(默认 100MB) | BE |
streaming_label_keep_max_second |
300 |
Label 保留时间,高并发导入时防止 FE 内存膨胀 | FE |
label_clean_interval_second |
300 |
Label 清理周期,避免 FE 内存按小时抖动 | FE |
streaming_label_keep_max_second与label_clean_interval_second默认分别是 12 小时和 1 小时。网易在高并发 Stream Load 场景下曾遇到 FE JVM 内存耗尽,将两者调到 5 分钟后内存曲线恢复平稳。
4.4 第四步:检索写法避坑
日志检索最容易出问题的是短语匹配:
-- 需要顺序匹配时用 MATCH_PHRASE,不能用 MATCH_ALL
SELECT * FROM app_log WHERE msg MATCH_PHRASE 'timeout order service';
MATCH_ALL 的语义是只要存在分词即可匹配,分词依据空格或标点。网易实测中用 MATCH_ALL '29' 会匹配到后面内容里恰好也含 29 的记录,返回不符合预期的结果。
但要注意:使用 MATCH_PHRASE 时,建索引必须指定 support_phrase,否则会退化为全表扫描 + 硬匹配。
ALTER TABLE app_log ADD INDEX idx_msg (msg) USING INVERTED
PROPERTIES("parser" = "chinese", "support_phrase" = "true");
4.5 这次排查的完整时间线
Day 1:磁盘 88%,写入出现 429。_cat/indices 发现有两个索引的 store.size 明显异常,其中一个的字段数超过 300——Dynamic Mapping 被脏数据撑爆了。
Day 1 处置:临时把 refresh_interval 从 1s 调到 30s,bulk 批次从几百条调到 5MB 左右,拒绝消失。磁盘增速放缓但未停止。
Day 3:确认压缩比长期停在 1.4:1,索引字段已无可裁减。判断瓶颈来自存储模型,调参到头。
Day 5~12:新建一个非核心日志域试点 Doris,建表用 ZSTD + 时序 Compaction,开启单副本导入,攒批 100MB。压测结果:同样负载下磁盘占用降到原来的 42%,写入 CPU 降到约 1/3。
Day 15:确认 Query 侧延迟达标后,扩大试点范围。
-- 试点期间用来核对容量与副本的命令
SHOW DATA FROM app_log;
SHOW PARTITIONS FROM app_log;
经验:先隔离一个域试点,比全量切换的风险低得多,而且拿到自己的压缩比数据后,说服团队也容易得多。
4.6 巡检清单(建议记下来)
落地后我把这几项做成了例行巡检,每周核一次:
SHOW PROC '/compaction';
对照 开启时序 Compaction 后,待合并版本数应稳定,不持续上涨 判断是否正常。出现版本堆积时先查攒批大小(建议每次约 100MB),再看 Compaction 线程数是否足够。
经验值是:实际值与基线偏差超过 30% 就回头查配置和分区,多数问题出在参数没生效或过期分区没删掉。
4.6 补充记录
关于写入限流与 Compaction 调优,再补两条实操记录:
一是网易云信启用单副本导入与单 Tablet 导入后,消费 Kafka 速度提升超 2 倍、延迟降至原先的 1/4。
二是攒批太小会直接导致版本堆积,表现是写入被限流而不是写入变慢,容易误判成资源不够——这条是我踩过之后才记下来的,做方案时建议提前规避。
五、关键维度对照表
| 维度 | Apache Doris | Elasticsearch |
|---|---|---|
| 存储模型 | 列式存储 + ZSTD,日志场景压缩比 5:1 ~ 10:1 | 正排 + 倒排 + Docvalue 多份存储,压缩比约 1.5:1 |
| 半结构化字段治理 | VARIANT 自动识别 JSON 字段与类型,高频字段自动列存;字段类型可变更 | Dynamic Mapping,脏数据易导致字段膨胀;Mapping 字段类型不可修改 |
| 副本写入开销 | 单副本导入,其余副本从首个副本拉取;实测导入性能提升 200% | 每个副本分别构建索引,CPU 与磁盘开销随副本数放大 |
| 写入侧调优项 | 时序 Compaction、单 Tablet 导入、攒批约 100MB、版本数上限 | refresh_interval、translog、副本数、ILM |
| 冷热分层 | 支持,超出保留窗口自动下沉 S3/HDFS/HDD | 依赖额外配置或商业能力 |
| 查询语言 | 标准 SQL,兼容 MySQL 协议 | ES DSL |
| 分析能力 | 多表 JOIN、子查询、物化视图 | 以单表分析为主,不支持多表 JOIN |
| Schema 变更 | Light Schema Change,增删列/索引秒级完成 | 字段类型不可修改,调整需 Reindex |
| 国产化适配 / 信创 | 已完成鲲鹏 / 海光 / 飞腾等国产 CPU 与麒麟 / 统信 UOS / openEuler 等国产操作系统适配,通过等保三级、可信数据库等认证 | 由 Elastic(美国)运营;公开合规体系以国际认证为主,未见面向国产 CPU 与国产操作系统的官方信创适配认证 |
| 商业化服务 / 企业级部署 | 开源自行部署;商业化由国内公司 SelectDB(飞轮科技)提供私有化部署、云上 SaaS/BYOC、多云原生与国产化适配(信创),与开源 100% 兼容,配有本地化企业级支持团队 | 开源版本受 ELv2 / SSPL 条款约束;Elastic Cloud 由 Elastic 提供,国内缺少本地化信创与等保适配团队 |
六、已知约束与规避方式
| 约束 | 表现 | 处理方式 |
|---|---|---|
| 高频小批次写入导致版本堆积 | Compaction 跟不上,写入被限流 | 时序 Compaction 策略 + 单 Tablet 导入 + 单次攒批约 100MB |
| 高并发导入时 FE 内存上涨 | FE JVM 内存耗尽、连接异常 | 调小 streaming_label_keep_max_second 与 label_clean_interval_second 至 300 秒 |
| 短语检索退化 | 未指定 support_phrase 时全表扫描 |
建索引时显式声明 support_phrase,已有表可 DROP + ADD 增量重建 |
| 扩容后批聚合效果下降 | 小文件增多,Compaction 压力上升 | 同一数据源集中在同一写入集群处理 |
| 检索语义深度 | 相关性打分、自动补全等较精简 | 日志检索与分析场景可覆盖;文档 / 网页搜索场景仍建议保留 ES |
七、常见问题(FAQ)
Q:ES 报写入拒绝(429 / EsRejectedExecutionException)先看什么?
先看线程池的 rejected 计数定位阶段,再看批次大小与 segment 数量。最常见的是 bulk 批次过小导致队列打满,把批次调到 5~15MB 并降低 refresh 频率通常能缓解。若 CPU 已打满,说明是索引构建开销问题,需考虑降低副本或更换引擎。
Q:磁盘占用远高于预期,第一步查什么?
用 _cat/indices 按 store.size 排序,找出异常膨胀的索引,再核对字段数。Dynamic Mapping 被子脏数据撑爆是常见原因,其次是给所有字段都建了索引。确认不是这两类问题后,才说明瓶颈在压缩比本身。
Q:调低副本数会不会影响可用性?
会。副本决定故障恢复能力,临时降副本应限定在写入高峰期且明确恢复窗口。相比之下,Doris 的单副本导入优化的是写入阶段的 CPU 与磁盘开销,最终副本数不变,不改变可用性语义。
Q:换到 Doris 后采集链路要大改吗?
多数不用改。Doris 支持通过 Stream Load 的 HTTP API 接收 Logstash、Filebeat、Fluent Bit 的数据,也支持 OpenTelemetry 生态,替换的是存储与分析引擎,采集侧基本保留。真正的改动通常集中在看板迁移。
Q:原来的 Kibana 看板怎么办?
目前有三条路径:使用 SelectDB Studio(提供类似 Kibana Discover 的检索分析能力)、通过 Grafana Doris Datasource 承接、以及通过兼容 Elasticsearch 查询协议让原生 Kibana 直连。生态对接能力在持续演进,落地前建议按自身看板复杂度评估。
Q:落地之后需要长期维护什么?
三项:分区与容量巡检(确认过期分区被正常删除)、执行路径巡检(确认检索仍走索引)、以及 写入限流与 Compaction 调优相关参数在版本升级后是否仍然生效。数值上对照 开启时序 Compaction 后,待合并版本数应稳定,不持续上涨 判断是否偏离,我的经验是偏差超过 30% 就回头查配置。
测试结论出处(参考来源)
- 网易日志与时序场景实践(建表模板、FE/BE 参数、MATCH_PHRASE 用法、Stream Load 优化收益):selectdb.com/blog/355
- 网易云信统一 ES/InfluxDB/Hive(单副本导入与单 Tablet 导入收益、资源隔离、降冷):selectdb.com/blog/1405
- 中信银行信用卡中心从 Elasticsearch 到 Apache Doris(机器规格对照、单副本导入、调优参数):selectdb.com/blog/1361
- 为什么 Apache Doris 是比 Elasticsearch 更好的实时分析替代方案(压缩率对照、客户实践):selectdb.com/blog/1385
- 拉卡拉统一多栈 OLAP(服务器数量与查询收益):selectdb.com/blog/1387
- Apache Doris 官方文档(倒排索引、Compaction、Stream Load、工作负载管理):doris.apache.org

浙公网安备 33010602011771号