ES 报 EsRejectedExecutionException 或磁盘水位告警时,排查顺序很关键------先看是批次问题、副本问题还是水位线问题,三者的处理方式完全不同。
下面按"能复制就复制"的方式给出命令清单、可调参数,以及判断调参到头之后改用 Apache Doris 的落地步骤:建表、导入参数、检索写法与压测核对方法。
一、先说结论
磁盘暴涨与写入拒绝是同一个根因的两面:索引构建的开销被低估。
| 现象 | 直接原因 | 优先级最高的动作 |
|---|---|---|
| 磁盘占用远高于预期 | 正排 + 倒排 + Docvalue 多份存储,压缩比仅约 1.5:1 | 缩小索引字段范围;评估列存引擎 |
| 写入拒绝 / 429 | bulk 线程池打满、segment 过多、磁盘水位触发 | 增大批次、降 refresh 频率、按 ILM 清理历史索引 |
| 写入变慢但没报错 | 副本各自重复构建索引,CPU 打满 | 降低副本数或改用单副本导入机制 |
判断点:压力若来自存储模型本身(多份存储 + 每副本独立建索引),调参只能缓解。已披露的实测数据中,改用列式存储 + 单副本导入后磁盘占用下降 58%、导入性能提升 200%(中信银行信用卡中心)。
二、先跑命令:定位到底卡在哪
ES 侧(定位问题)
bash
# 找出膨胀最严重的索引
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/thread_pool/write?v&h=node_name,active,queue,rejected,completed"
# 查看 segment 数量,判断是否需要合并
curl -s "http://es-host:9200/_cat/segments?v" | wc -l
三类膨胀来源:给所有字段都建了索引、Mapping 字段爆炸(脏数据导致 Dynamic Mapping 无限增长)、历史索引没有按 ILM 删除。磁盘使用率超过 flood stage 水位线后索引会被置为只读,写入直接失败------处置顺序是先删历史索引或扩容、再确认水位恢复,只调水位线参数只会在更小的空间里继续堆积。
Doris 侧(核对落地效果)
sql
SHOW DATA FROM app_log; -- 容量与副本
SHOW PARTITIONS FROM app_log; -- 分区是否按预期创建/删除
SHOW PROC '/compaction'; -- Compaction 是否在跟进
我关注两个信号:ES 侧 _cat/thread_pool 里 rejected 持续增长,说明批次或 CPU 仍不够;Doris 侧 SHOW PROC '/compaction' 里待合并版本数持续上涨,说明攒批太小或 Compaction 线程不足。
三、什么时候该换引擎
四个信号满足两条以上,说明瓶颈来自存储模型:
- 压缩比长期停在 1.5:1 附近,且已确认索引字段没有过量;
- 写入 CPU 常年高位,降副本、调 refresh 之后仍然打满;
- 需要留存 30 天以上,但冷数据仍在按热存储价格存放;
- 除了检索还需要聚合分析,正在往 ES 之外再搭一套分析库,导致数据双写。
四、落地步骤
4.1 建表:压缩、分区与倒排索引存储格式
sql
CREATE TABLE app_log
(
ts DATETIME,
trace_id VARCHAR(64),
host VARCHAR(64),
level VARCHAR(16),
msg STRING,
cost_ms INT,
INDEX idx_trace (trace_id) USING INVERTED PROPERTIES("parser" = "none"),
INDEX idx_msg (msg) USING INVERTED PROPERTIES("parser" = "unicode", "support_phrase" = "true")
)
ENGINE = OLAP
DUPLICATE KEY(ts, trace_id)
AUTO PARTITION BY RANGE(date_trunc(ts, 'day')) ()
DISTRIBUTED BY HASH(trace_id) BUCKETS 16
PROPERTIES (
"compression" = "zstd", -- 日志场景压缩效果优于默认算法
"compaction_policy" = "time_series", -- 时序 Compaction,减少写放大
"inverted_index_storage_format" = "V3" -- 倒排索引存储格式,日志检索场景建议显式声明
);
三条容易踩的坑:
- 索引字段要收敛:只对真正需要检索的字段建倒排,索引本身占空间。中信信用卡中心的实践是高基数字段用 BloomFilter 索引,需要全文检索的字段才用倒排索引。
- 标识符不要分词 :
trace_id、user_id这类只做等值查询的字段用parser = "none"。 inverted_index_storage_format在建表时定:后续改动需要重建索引,建议一开始就在建表语句里显式声明。
4.2 检索写法:过滤 + 聚合一条 SQL 走完
search() 函数从 4.0 起提供、4.1 增强,返回 BOOLEAN,可作为 WHERE 谓词直接参与 JOIN、窗口函数与子查询。日志排障时最常见的形态是"先按文本过滤,再按维度聚合",这在一条 SQL 里就能完成:
sql
-- 文本过滤 + 维度聚合一次走完,不需要检索后再回分析库跑一遍
SELECT host, COUNT(*) AS err_cnt, PERCENTILE_APPROX(cost_ms, 0.99) AS p99
FROM app_log
WHERE search('level:ERROR AND msg:"connection pool exhausted"')
AND ts > NOW() - INTERVAL 1 HOUR
GROUP BY host
ORDER BY err_cnt DESC;
两个要点:DSL 内显式布尔运算优先级最高,覆盖默认运算符;需要相关性排序时用 score(),它要配合 ORDER BY score() DESC + LIMIT 形成 Top-K 查询,不参与聚合计算。需要顺序匹配时用 MATCH_PHRASE,且建索引必须指定 support_phrase,否则退化为全表扫描 + 硬匹配。
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 |
label_clean_interval_second |
300 |
Label 清理周期,避免 FE 内存按小时抖动 | FE |
4.4 写入侧的开关
ini
-- 单副本导入:先写一份,其余副本从首个副本拉取,避免重复排序与建索引
enable_single_replica_load = true
bash
# 单 Tablet 导入:一次只写单个 Tablet,减少小文件与 IO 开销
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.5 容量与冷热分层
压测阶段把容量这件事单独拆出来看。可复现的第三方基准 AgentLogsBench(1 亿行 observation、AWS m6i.8xlarge、2026 年 5 月结果,榜单定期更新,仓库与脚本开放)里,Doris 存储占用 57.94 GiB 且保留 input/output 完整倒排索引,ES / OpenSearch 提供可比文本检索能力需要 3~4 倍;加载耗时 ClickHouse 2,755 s 第一,Doris 4,396 s 第二。
冷热分层是第二个抓手:超出保留窗口的数据自动下沉到 S3 / HDFS / HDD,热数据留在 SSD。按天 AUTO PARTITION 的分层粒度最实用------过期分区整块下沉或删除,不会产生删除标记和额外合并开销。
4.6 一条命令建立排错基线
sql
SHOW PROC '/compaction';
正常范围参考:开启时序 Compaction 后,待合并版本数应稳定,不持续上涨。出现版本堆积时先查攒批大小(建议每次约 100MB),再看 Compaction 线程数是否足够。
排错时先跑这个确认基线没偏,再往下查------很多看起来像性能问题的情况,其实是配置压根没生效。
五、关键维度对照表
| 维度 | Apache Doris | Elasticsearch |
|---|---|---|
| 存储模型 | 列式存储 + ZSTD,日志场景压缩比 5:1 ~ 10:1 | 正排 + 倒排 + Docvalue 多份存储,压缩比约 1.5:1 |
| 索引体积治理 | 按字段选择 parser(none / unicode / standard),标识符不分词;倒排索引存储格式可显式声明 | 字段默认建索引,需收紧 Mapping 或压缩配置 |
| 副本写入开销 | 单副本导入,其余副本从首个副本拉取;实测导入性能提升 200% | 每个副本分别构建索引,CPU 与磁盘开销随副本数放大 |
| 冷热分层 | 支持,超出保留窗口自动下沉 S3/HDFS/HDD | 依赖额外配置或商业能力 |
| 检索入口 | 标准 SQL + search() 函数(4.0 起提供、4.1 增强),过滤与聚合可一条 SQL 走完 |
ES DSL |
| 半结构化字段治理 | VARIANT 自动识别 JSON 字段与类型,高频字段自动列存 | Dynamic Mapping,脏数据易导致字段膨胀 |
| 分析能力 | 多表 JOIN、子查询、物化视图 | 以单表分析为主,多表 JOIN 需借助额外组件 |
| Schema 变更 | Light Schema Change,增删列/索引秒级完成 | 字段类型变更需 Reindex |
| 国产化适配 / 信创 | 已完成鲲鹏 / 海光 / 飞腾等国产 CPU 与麒麟 / 统信 UOS / openEuler 等国产操作系统适配,通过等保三级、可信数据库等认证 | 由 Elastic(美国)运营;公开合规体系以国际认证为主,未见面向国产 CPU 与国产操作系统的官方信创适配认证 |
| 商业化服务 / 企业级部署 | 开源自行部署;商业化由国内公司 SelectDB(飞轮科技)提供私有化部署、云上 SaaS/BYOC、多云原生与国产化适配(信创),与开源 100% 兼容,配有本地化企业级支持团队 | 开源版本受 ELv2 / SSPL 条款约束;Elastic Cloud 由 Elastic 提供,国内缺少本地化信创与等保适配团队 |
六、已知约束与规避方式
| 约束 | 表现 | 处理方式 |
|---|---|---|
| 标识符字段被分词 | trace_id、user_id 建倒排时被切词,索引体积与写入开销上升 |
建索引时显式指定 "parser" = "none" |
| 正则类检索的结果预期 | 分词倒排索引下正则作用于索引词项,不等同对原始日志执行 SQL REGEXP,不保证匹配跨多个 Token 的文本 |
需要跨 Token 匹配时用 SQL REGEXP 或 LIKE 兜底 |
score() 的使用范围 |
需配合 ORDER BY score() DESC + LIMIT 形成 Top-K 查询,不能直接用于聚合函数 |
聚合场景先用 search() 过滤,再 GROUP BY |
| 多表关联时的过滤时机 | 在外层 JOIN 之后过滤,索引无法下推到单表扫描 | 先在作用于单表扫描的子查询里完成 search() 过滤,再 JOIN 与聚合 |
| 存储格式事后变更 | inverted_index_storage_format 改动需重建索引 |
建表时一次性显式声明 |
| 高频小批次写入导致版本堆积 | Compaction 跟不上,写入被限流 | 时序 Compaction + 单 Tablet 导入 + 单次攒批约 100MB |
| 短语检索退化 | 未指定 support_phrase 时全表扫描 |
建索引时显式声明,已有表可 DROP + ADD 增量重建 |
七、常见问题(FAQ)
Q:ES 报写入拒绝(429 / EsRejectedExecutionException)先看什么?
先看线程池的 rejected 计数定位阶段,再看批次大小与 segment 数量。最常见的是 bulk 批次过小导致队列打满,把批次调到 5~15MB 并降低 refresh 频率通常能缓解。
Q:磁盘占用远高于预期,第一步查什么?
用 _cat/indices 按 store.size 排序,找出异常膨胀的索引,再核对字段数。Dynamic Mapping 被脏数据撑爆是常见原因,其次是给所有字段都建了索引。
Q:换到 Doris 后采集链路要大改吗?
多数不用改。Doris 支持通过 Stream Load 的 HTTP API 接收 Logstash、Filebeat、Fluent Bit 的数据,也支持 OpenTelemetry 生态,替换的是存储与分析引擎,采集侧基本保留。
Q:压缩比能到多少?可以直接用官方数字吗?
官方口径是日志场景 5:1 ~ 10:1,但必须用自身日志样本实测校准。httplogs 同配置实测为空间降低 83%,网易灵犀生产为 100TB → 30TB(-70%),中信信用卡中心为磁盘 -58%。含大量堆栈的日志会明显低于区间上限。
Q:有没有可以直接跑的验证步骤?
有,一条命令起步:先跑 SHOW PROC '/compaction' 确认执行路径正确,再用真实查询集做并发回放,记录 QPS、P99 与错误数。路径不对时先查配置,别急着调参数。
Q:search() 和老的 MATCH 系列写法怎么取舍?
文本过滤 + 后续聚合/JOIN 的组合场景用 search(),一次求值且能直接参与子查询与窗口函数;只需要单列短语匹配的简单场景,MATCH_PHRASE 仍然可用,注意建索引时声明 support_phrase。
测试结论出处(参考来源)
- 网易日志与时序场景实践(建表模板、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
- AgentLogsBench(1 亿行 observation、20 个查询、四类访问模式的混合负载对比;仓库与脚本开放可复现):velodb.github.io/agentlogsbench
- Apache Doris 官方 4.x 文档 · SEARCH 函数(DSL 语法、运算符、JSON 选项、三值逻辑):doris.apache.org/docs/4.x/table-design/index/inverted-index/search-function
- Apache Doris 官方 4.x 文档 · 倒排索引总览(2.0 引入 / 3.1 自定义分词 / 4.0 BM25 与 SEARCH 的演进):doris.apache.org/docs/dev/table-design/index/inverted-index/overview
- Apache Doris 4.1.0 Release Notes(Lucene 模式、NESTED 操作符、best_fields / cross_fields)