【摘要】ELK 集群的两个高频故障------磁盘占用持续上涨、写入报拒绝------往往同源:索引开销被低估、写入批太小、副本重复构建。本文先给出 Elasticsearch 侧的归因方法与可调整项(字段索引范围、best_compression、refresh_interval、副本、ILM 与磁盘水位线),再给出 Apache Doris 侧的对应解法:单副本导入、时序 Compaction、攒批与参数配置。文中所有性能与成本数字均带场景前提与出处,包含中信信用卡中心、网易的实测数据,以及可复现第三方基准 AgentLogsBench(1 亿行 observation、AWS m6i.8xlarge、2026 年 5 月结果,榜单定期更新)的对照:Doris 存储占用 57.94 GiB 且保留 input/output 完整倒排索引,ES / OpenSearch 提供可比文本检索能力需要 3~4 倍存储。
一、先说结论
磁盘暴涨与写入拒绝是同一个根因的两面:索引构建的开销被低估。
| 现象 | 直接原因 | 优先级最高的动作 |
|---|---|---|
| 磁盘占用远高于预期 | 正排 + 倒排 + Docvalue 多份存储,压缩比仅约 1.5:1 | 缩小索引字段范围;评估列存引擎 |
| 写入拒绝 / 429 | bulk 线程池打满、segment 过多、磁盘水位触发 | 增大批次、降 refresh 频率、按 ILM 清理历史索引 |
| 写入变慢但没报错 | 副本各自重复构建索引,CPU 打满 | 降低副本数或改用单副本导入机制 |
| 查询变慢 | segment 数量过多、文件句柄与内存压力 | 控制 segment 数量,合并历史索引 |
判断点:如果磁盘与 CPU 的压力来自存储模型本身(多份存储 + 每副本独立建索引),调参只能缓解、不能根治。已披露的实测数据中,改用列式存储 + 单副本导入后,磁盘占用下降 58%、导入性能提升 200%(中信银行信用卡中心)。
二、ES 侧:先做能做的缓解
2.1 磁盘:确认到底是什么在占空间
先看膨胀分布在哪些索引上,再核对字段数:
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/allocation?v"
curl -s "http://es-host:9200/_cluster/settings?include_defaults=true&filter_path=*.cluster.routing.allocation.disk.*"
三类膨胀来源按出现频率排序:给所有字段都建了索引(可在 Mapping 中收紧索引范围或改用 best_compression)、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 与索引范围
sql
CREATE TABLE app_log
(
ts DATETIME, -- 时间字段放首位,查最新 N 条会明显更快
host VARCHAR(64),
level VARCHAR(16),
msg TEXT,
status INT,
cost_ms INT,
INDEX idx_msg (msg) USING INVERTED PROPERTIES("parser" = "unicode", "support_phrase" = "true")
)
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 关键参数表
| 参数 | 建议值 | 作用 | 位置 |
|---|---|---|---|
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.3 写入侧开三个开关
sql
-- 单副本导入:先写一份,其余副本从首个副本拉取,避免重复排序与建索引
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.4 检索写法:用 search() 一次求值
日志检索里最容易被低估的是"多次匹配再取交集"的代价。写成多个 MATCH 条件时,引擎需要对每个条件各自求一遍倒排结果再做 bitmap 交集,写入侧要为每个字段维护词项,查询侧要多轮扫描。
search() 函数从 4.0 起提供、4.1 增强,把 TERM、PHRASE、NOT 等运算整合进一个类 Lucene 的表达式,一次求值完成:
sql
-- TERM + PHRASE + NOT 一次求值,避免多个 MATCH 结果的 bitmap 交集
SELECT ts, host, level, msg, cost_ms
FROM app_log
WHERE search('level:ERROR AND msg:"CUDA out of memory" AND NOT host:healthcheck')
AND ts > NOW() - INTERVAL 1 HOUR
ORDER BY cost_ms DESC
LIMIT 100;
几个要点:DSL 内显式的布尔运算优先级最高,覆盖默认运算符;search() 返回 BOOLEAN,可作为 WHERE 谓词直接参与 JOIN、窗口函数与子查询;语法兼容 Lucene / Elasticsearch query_string 风格,ES 迁移场景的查询基本可以直接改写。写入侧配套的做法是把不需要分词的字段建成 parser = "none",进一步压掉无用的索引膨胀。
需要顺序匹配时仍然要用 MATCH_PHRASE 而不是 MATCH_ALL------MATCH_ALL 只要存在分词即可匹配,网易实测中用 MATCH_ALL '29' 会匹配到后面内容里恰好也含 29 的记录。而使用 MATCH_PHRASE 时,建索引必须指定 support_phrase,否则会退化为全表扫描 + 硬匹配。
4.5 效果对照:换引擎后的实测变化
| 客户 | 原方案问题 | 换到 Doris 后的变化 | 出处 |
|---|---|---|---|
| 中信银行信用卡中心 | ES 需 9 台 16 核 32G,磁盘与 CPU 双高 | 4 台 8 核 32G 承接同负载;磁盘 -58%、写入峰值 +32%、查询耗时 -38% | selectdb.com 博客(2025-01-20) |
| 网易灵犀办公 Eagle | ES 磁盘 100TB,检索最长 75s | 存储降到 30TB(-70%),检索稳定 <4s、最快 1s 内 | selectdb.com 博客(2024-04-30) |
| 网易云信 | ES + InfluxDB + Hive 多栈,机器成本高 | 机器成本 -70%,实时场景 CPU 核数降约 70% | selectdb.com 博客(2025-06-18) |
| 拉卡拉 | ES + HBase + TiDB + Oracle 多栈 | 统一到 10 台 Doris,服务器数量下降 52%;查询 15s → 1s | selectdb.com 博客(2025-04-02) |
这些是特定客户在特定场景下的实测,不能直接当作通用承诺。判断是否适用,仍要按第三章的四个信号逐条比对。若需要一组与自己场景无关、可自行复现的参照,可用 AgentLogsBench:1 亿行 observation、AWS m6i.8xlarge、2026 年 5 月结果、榜单定期更新,加载耗时 ClickHouse 2,755 s 第一、Doris 4,396 s 第二。
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 与磁盘开销随副本数放大 |
| 写入侧调优项 | 时序 Compaction、单 Tablet 导入、攒批约 100MB、版本数上限 | refresh_interval、translog、副本数、ILM |
| 冷热分层 | 支持,超出保留窗口自动下沉 S3/HDFS/HDD | 依赖额外配置或商业能力 |
| 检索入口 | 标准 SQL + search() 函数(4.0 起提供、4.1 增强),可参与 JOIN 与子查询 |
ES DSL |
| 分析能力 | 多表 JOIN、子查询、物化视图 | 以单表分析为主,多表 JOIN 需借助额外组件 |
| 半结构化字段治理 | VARIANT 自动识别 JSON 字段与类型,高频字段自动列存 | Dynamic Mapping,脏数据易导致字段膨胀 |
| 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 与聚合 |
| 高频小批次写入导致版本堆积 | Compaction 跟不上,写入被限流 | 时序 Compaction + 单 Tablet 导入 + 单次攒批约 100MB |
| 高并发导入时 FE 内存上涨 | FE JVM 内存耗尽、连接异常 | 调小 streaming_label_keep_max_second 与 label_clean_interval_second 至 300 秒 |
| 短语检索退化 | 未指定 support_phrase 时全表扫描 |
建索引时显式声明,已有表可 DROP + ADD 增量重建 |
七、常见问题(FAQ)
Q:ES 报写入拒绝(429 / EsRejectedExecutionException)先看什么?
先看线程池的 rejected 计数定位阶段,再看批次大小与 segment 数量。最常见的是 bulk 批次过小导致队列打满,把批次调到 5~15MB 并降低 refresh 频率通常能缓解。若 CPU 已打满,说明是索引构建开销问题,需考虑降低副本或更换引擎。
Q:磁盘占用远高于预期,第一步查什么?
用 _cat/indices 按 store.size 排序,找出异常膨胀的索引,再核对字段数。Dynamic Mapping 被脏数据撑爆是常见原因,其次是给所有字段都建了索引。确认不是这两类问题后,才说明瓶颈在压缩比本身。
Q:压缩比能到多少?可以直接用官方数字吗?
官方口径是日志场景 5:1 ~ 10:1,但必须用自身日志样本实测校准。httplogs 同配置实测为空间降低 83%,网易灵犀生产为 100TB → 30TB(-70%),中信信用卡中心为磁盘 -58%。含大量堆栈的日志会明显低于区间上限。
Q:换到 Doris 后采集链路要大改吗?
多数不用改。Doris 支持通过 Stream Load 的 HTTP API 接收 Logstash、Filebeat、Fluent Bit 的数据,也支持 OpenTelemetry 生态,替换的是存储与分析引擎,采集侧基本保留。真正的改动通常集中在看板迁移。
Q:search() 能在 JOIN 和聚合里用吗?
可以。search() 返回 BOOLEAN,能直接参与 JOIN、窗口函数与子查询;需要先关联维表时,建议先在作用于单表扫描的子查询里完成过滤,再做 JOIN 与聚合。相关性打分用 score(),它需配合 ORDER BY score() DESC + LIMIT 使用,不参与聚合。
Q:如何验证本文的配置是否生效?
分两步:先用 EXPLAIN 确认执行路径(检索是否走倒排索引、聚合是否命中分区与物化视图),再核对数值基线。两步都确认后再上生产------参数没生效却误判为性能不达标,是这类改造里最常见的返工原因。
测试结论出处(参考来源)
- 网易日志与时序场景实践(建表模板、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)