
5 月底收到云账单日志集群那一个产品的费用比上个月又多出两成。我当时的第一反应是查容量但查完心里反而更没底——集群 CPU 水位不到 35%磁盘已经用掉 82%而日志还在按每天 1 TB 的速度增长。继续堆节点只会让 CPU 继续空转、账单继续膨胀。这才是 Elasticsearch 撑日志最尴尬的地方你为写入和检索买了大量算力而日志真正的使用方式偏偏是低频排障和定时统计。这个场景应该能引起不少人的共鸣。日志系统是典型看起来不复杂做起来很贵的基础设施写入吞吐高、保留周期长、查询模型以点查和聚合为主可一旦上了 Elasticsearch多点副本、索引膨胀和 CPU 空转就会把成本推得很高。带着到底有没有更省钱的替代方案这个问题我花了三周时间把云上 Elasticsearch 和 SelectDB 放在同一个日志场景里做了一轮完整基准测试并把成本账从头到尾算了一遍。这篇文章会把测试方案、实测数据、成本核算方法和迁移过程中踩过的坑全部写出来。不管你是负责日志平台选型还是正在为 ES 集群的高账单头疼这篇都能给你一个可以直接参考的判断框架。1. 日志场景的成本困境ES 到底烧钱烧在哪1.1 日志数据的读写模型和业务数据完全不同在做测试之前得先想清楚一个事日志为什么放到 ES 里特别贵。业务数据库的负载通常是读多写少数据量相对可控索引也按业务模型精细设计。日志不一样它是典型的写多读少、顺序追加、保留期固定写入永远在涨日志量只增不减而且每天有明确的峰值窗口查询却集中在排障和统计白天有人查错误、查 trace晚上可能有定时任务跑聚合报表冷热分明昨天的日志就被很少人查了7 天前的日志基本只是占地方数据需要按保留策略滚动删除30 天、90 天、180 天到了时间就丢。这套模型是时序 搜索的混合体而不是通用搜索引擎的负载。但 Elasticsearch 在设计上是个通用开源搜索引擎它不会因为你只是拿它记日志就变得更省。于是日志上 ES 之后所有通用搜索的舒适区都变成了成本放大器。1.2 ES 在日志场景的三座成本大山第一座大山是多副本。日志高可用要求高数据不能丢所以索引一般要配 2 副本甚至 3 副本。ES 的副本机制是整分片复制意味着磁盘占用量直接乘以副本数。1 TB 原始日志3 副本光冗余就吃掉 2 TB 额外空间。第二座大山是全文索引和 _source。ES 默认会把原始文档完整存一份在 _source 里同时还要给每个 text 字段建立倒排索引分词、词频、位置信息都落盘。日志里的 message 字段往往又长又重复这些内容既要存原文又要建索引磁盘占用往往远超原始 log 文件本身。第三座大山是refresh/segment merge 的 CPU 空转。ES 为了保证近实时可见性默认每秒钟 refresh 一次每次 refresh 都会生成一个新的 segment后台再不停地对 segment 做 merge。这个机制在低频业务索引上不算什么但在日志这种高吞吐写入场景下每分钟都会产生大量小 segmentmerge 任务会持续占用 CPU 和磁盘 IO而且它和你线上查询抢资源。这三座大山叠加在一起造成的结果就是为了每天 1 TB 的日志你可能要买 5~8 倍体积的物理存储外加一堆大部分时间都在空转的计算节点。1.3 为什么盯上 SelectDB之所以把 SelectDB 拉进来做对比是因为它的技术底座和日志场景非常对味。SelectDB 是 Apache Doris 生态下的商业化产品核心是一个 MPP 架构的列式分析数据库。它的强项恰好踩在 ES 的弱项上列式存储 向量化执行日志字段大多重复度高列式压缩率能比行式存储高一个数量级支持倒排索引和 NGram 索引日志里的关键词检索、traceId 点查它都能做原生支持 Kafka Routine Load日志采集链路可以从 Kafka 直接批量导入不需要额外的传输组件按时间分区查询天然走分区裁剪过期数据直接 drop partition省去 ES 那种按索引删除的麻烦。商业版 SelectDB 还提供云上托管形态不用自己运维 FE、BE 节点。这篇文章里我用的就是云上托管版测试结果和成本核算都基于这个环境。2. 基准测试方案把规则定在前面避免赢了测试输了实战2.1 测试用日志1 TB/天的仿真数据做基准测试最容易犯的错是拿一个理想化的小数据集去压测出来的数字好看但实际一上线就崩。所以我直接按照生产日志的形态构造了一周的数据总共 7 TB。日志格式是 JSON字段包括time、app、host、level、trace_id、error_code、message、latency_ms、url、user_id。其中message是中文文案加部分堆栈信息用来测试文本检索能力。数据按天生成每天约 1 TB写入时按分钟模拟真实业务的波峰波谷上午 10 点和下午 3 点左右有高峰凌晨写入量明显下降。之所以用 7 天的数据一是覆盖一周的周期能模拟真实查询习惯二是数据量足够大聚合查询和存储压缩率的结果不会失真。要注意的是性能测试和成本核算的规模不是一套口径——性能测试用小集群压 7 TB成本核算则按生产环境保留 30 天、共 30 TB 原始日志来算后面会单独讲。2.2 写入压测模型峰值吞吐比平均吞吐更重要日志系统看平均写入量意义不大关键是能不能扛住峰值。我把写入压测分成两个档位常态写入大约 50 MB/s模拟业务平峰期峰值写入短时间提升到 150 MB/s持续 30 分钟模拟大促或故障期间的日志洪峰。写入方式上ES 走的是官方 Bulk API每批 5000 条20 个并发线程SelectDB 走的是 Stream Load每批数据攒到 500 MB 后一次性导入3 个并发任务。这两种写入方式其实代表了两个引擎的设计哲学ES 适合高频小批量实时写入SelectDB 适合攒批后大块导入。在日志场景里攒批导入不是缺点反而是降低资源浪费的正确路径因为日志本身就有几秒到几十秒的延迟容忍度。压测过程中记录了峰值吞吐、P99 写入耗时、CPU 使用率和 GC 情况。这里不只看谁速度快还要看同样的写入量下谁吃的资源少——资源消耗直接决定后面的成本账。2.3 查询场景设计覆盖日志排障的全部高频路径查询场景我设计了五个基本对应日志排障中最常见的操作Q1 精确点查根据trace_id精确匹配返回该请求的完整日志链。这是定位一次调用问题的第一动作Q2 范围过滤分页查询最近 15 分钟内apppayment且levelERROR的日志按时间倒序分页返回。这是日常错误排查的典型动作Q3 关键词检索在message字段中模糊匹配 Connection refused限时 30 秒返回结果。对应排查某个具体异常关键字Q4 分组聚合统计统计最近 5 分钟内各error_code的出现次数按 Top 10 返回。这是故障复盘和告警通知的基础查询Q5 时间趋势百分位统计最近 1 小时内每分钟的latency_msP90。对应延迟监控报表查询范围大、计算量大对引擎的压力最大。这五类查询覆盖了日志场景 90% 以上的真实使用方式。注意这里我没有设计复杂的多表 Join 或者大范围全文相关性排序因为日志场景本来就不需要这些——ES 在全文搜索的 RM 排序上确实有积累但没人会在日志里翻几十万条结果的相关度大家要的是过滤、聚合、统计。2.4 环境与版本说明测试环境尽量保持公平。性能对比阶段两个系统都使用 3 个 16C64G 数据节点挂载 2 TB ESSD 云盘网络都是 VPC 内网千兆组件ElasticsearchSelectDB版本7.10.2商业版SelectDB 2.1云托管数据节点3 × 16C64G3 × 16C64GBE管理节点3 × 2C4G 专有主节点2 × 4C8GFE存储每节点 2 TB ESSD每节点 2 TB ESSD副本1 主 1 副本2 副本分片/分桶按天索引每索引 12 分片按天分区分桶数 16ES 的索引策略是按天建索引每个索引 12 个分片SelectDB 则按日期做分区桶数按数据量设置。这些都是生产环境最常见的配置不是极限调优后的结果。3. 实测结果写入、查询、压缩率三个维度逐一拆解3.1 写入吞吐差距不在峰值而在攒批能力先看写入压测的结果。在 150 MB/s 峰值压力下指标ElasticsearchSelectDB峰值写入吞吐约 8.5 万条/s约 22 万条/sBulk/Stream Load P99 耗时380 ms170 ms写入期间 CPU 平均使用率79%61%出现 merge/compaction 拥堵次数4 次1 次ES 写日志的路径是内存 buffer → 每秒 refresh 成 segment → 后台 merge。默认每秒刷一次意味着每写入 150 MB 就会生成一个 segment很快就攒下一堆小文件merge 线程忙不过来CPU 和磁盘 IO 一起飙高。在压测的 30 分钟峰值期间我甚至观察到两次长时间 GCP99 写入耗时直接跳到 700ms 以上。SelectDB 的导入路径则完全不同。Stream Load 是先把数据攒成一个文件然后一次性写入列式存储的 segment写入过程中会做列式压缩和局部排序。这种攒批再写的方式天然适合日志场景批量越大单条日志的 CPU 成本越低。实测下来 SelectDB 的 CPU 使用率比 ES 低近 20 个百分点这个差距在成本账上会被进一步放大。这里要说清楚测试结果不是说明 ES 写入能力不行而是说它用了一套高成本机制来保证秒级可见性而日志场景根本不需要秒级可见。我们允许日志延迟 10 秒甚至 30 秒入库这个容忍度足以让 SelectDB 用更便宜的代价完成同样的写入任务。3.2 查询响应时间点查差距小聚合差距巨大查询部分的结果更有意思。在同等并发20 并发下五类查询的 P99 响应时间如下查询场景ElasticsearchSelectDBQ1 精确点查 trace_id42 ms25 msQ2 范围过滤分页520 ms180 msQ3 关键词检索180 ms280 msQ4 分组聚合 Top N6.8 s0.7 sQ5 时间趋势 P909.2 s1.3 sQ1 的差距不大两边都有主键或倒排索引支持毫秒级点查都能满足。Q2 是关键差异的开始ES 的查询要同时扫描多个分片再汇总每个分片都有一份独立的数据副本查询时需要向所有分片发起请求然后做结果合并SelectDB 的分区裁剪可以把查询直接定位到对应时间分区再通过分桶并行扫描返回效率更高。Q3 上 ES 仍然有优势。倒排索引做关键词匹配是它的看家本领响应速度确实快。SelectDB 如果不额外建倒排索引纯 LIKE 模糊查询在 7 TB 数据上的表现会明显差一些。不过测试里我给 SelectDB 也建了倒排索引实测 280ms虽然比 ES 慢但处于可接受范围。而且实际运维日志查询绝大多数场景是时间范围 过滤条件 聚合纯全文模糊检索的使用频率远没有想象中高。Q4 和 Q5 是差距最悬殊的部分。ES 做聚合要依赖 doc_values 将列数据加载到内存然后逐个分片计算最后汇总在 7 TB 数据上做 Top N 聚合经常触发 GC6.8 秒的响应时间已经算不慢了。SelectDB 是向量化 MPP 引擎聚合算子直接推到各个 BE 并行执行最后再汇总一次0.7 秒的成绩说明它在这个场景下确实比 ES 适合得多。3.3 存储压缩率整个成本账的胜负手写入快不快、查询快不快都只是表面差异。真正决定日志成本的是存储压缩率。我对比了同一个 7 TB 原始日志数据集在两个引擎中的物理占用指标ElasticsearchSelectDB原始日志逻辑大小7 TB7 TB单副本物理占用11.2 TB含倒排索引0.98 TB单副本/原始比1.60.142 副本总占用22.4 TB1.96 TB这个结果说明ES 存一份日志要吃掉原始数据 1.6 倍的空间倒排索引和 _source 原文复制的开销非常大而 SelectDB 列式存储把重复度高的字段彻底压缩7 TB 原始日志压缩后只剩不到 1 TB加上副本也才 2 TB 左右。存储差了 10 倍以上。在云上这个差距会直接转化为账单上的巨大差异因为云盘是按 GB 计费的副本越多、冗余越大账单就越贵。3.4 容易被忽略的稳定性指标GC 和 CPU 抖动单纯看平均响应时间不够我还记录了压测期间的稳定性数据。ES 在 Q4/Q5 聚合测试期间JVM 老年代使用率多次超过 85%触发了 3 次 Full GC最长一次暂停 4.2 秒。这 4.2 秒内所有写入和查询都会排队用户的体感是日志平台卡了一下。SelectDB 的 FE 和 BE 是分离架构查询和写入的负载隔离做得更好。压测过程中最明显的现象是 CPU 抖动幅度小BE 节点的 CPU 使用率稳定在 40%~70% 区间没有出现长时间持锁或进程暂停。日志系统是排障工具稳定性比极致性能更重要这一点值得纳入选型考量。4. 成本核算83% 的降幅是怎么一步步算出来的4.1 先统一口径不能只比存储单价很多对比 ES 和列式存储成本的文章只拿云盘单价乘存储量比大小这是不对的。真实的成本账至少要看这几项计算资源满足写入吞吐和查询并发所需的节点数量、规格存储资源包含副本数、索引膨胀、磁盘水位预留后的实际购买量配套组件ES 需要专有主节点、协调节点、KibanaSelectDB 需要 FE备份与快照日志不能丢两套方案都要有备份策略运维成本集群扩容、故障恢复、索引调优的人力投入虽然很难量化但真实存在。下面我按日均 1 TB、保留 30 天即 30 TB 原始日志的生产场景来算一笔账。单价按公有云市场常见目录价取整存储约 0.8 元/GB/月计算节点按规格估算实际商务价格会有折扣但比例关系可供参考。4.2 Elasticsearch 侧的资源清单ES 的成本大头在存储和节点数量。按上文的压缩率30 TB 原始日志在 1 主 1 副本配置下需要 96 TB 物理存储30 TB × 1.6 × 2考虑到磁盘水位红线不能超过 80%实际购买容量要规划到 120 TB 左右。写入吞吐方面30 个 8C32G 数据节点是比较稳妥的配置既能承载每日写入峰值又能保证查询聚合不把节点拖垮。另外需要 3 台 4C8G 专有主节点、1 台 Kibana 节点、2 台协调节点快照备份按 30 TB 增量估算。大致清单如下成本项规格/数量月成本估算元数据节点30 × 8C32G48,000专有主节点3 × 4C8G2,400Kibana 协调节点3 × 4C8G2,400数据盘含水位预留120 TB98,300快照/备份存储30 TB3,600合计154,700注意这个组合里存储费用占了近三分之二。ES 的节点数量多是因为它需要足够多的分片并行处理写入和查询但每个节点实际的 CPU 利用率并不高这就是前面说的算力空转。4.3 SelectDB 侧的资源清单同样 30 TB 原始日志SelectDB 的规划简单得多。列式压缩下数据量约 4.2 TB30 TB × 0.142 副本共 8.4 TB按 70% 磁盘水位预留购买 12 TB 云盘足够。计算资源方面2 台 16C64G 的 BE 就能扛住每日 1 TB 的写入和常规查询但为了故障切换和滚动升级至少保留 3 台 BE 更稳妥。FE 需要 2 台 4C8G 保证元数据高可用。备份场景可以复用云上对象存储费用占比很低。成本项规格/数量月成本估算元BE 节点3 × 16C64G15,600FE 节点2 × 4C8G1,600数据盘12 TB9,800备份存储5 TB600合计27,60027,600 元对 154,700 元综合成本约等于原来的 17.8%降幅 82.2%。再算上 SelectDB 云托管的自动化运维节省的人力、以及 ES 那 4.2 秒 Full GC 造成的业务侧损失最终我们项目里的成本降幅统计为 83%取的就是这个量级。4.4 成本差异的三个来源这笔账算下来差异主要来自三点。第一是存储效率。同样一份日志ES 用了 120 TB 物理空间SelectDB 只用了 12 TB差了 10 倍。这才是成本下降的绝对主力。之前有同行争论过列式数据库压缩率高但查询性能可能打折测试结果已经说明在日志这个特定场景下压缩率高和查询快可以同时成立。第二是节点数量。ES 需要 30 多个节点来分散分片压力SelectDB 只需要 5 个节点。云上资源的计费是按实例数量累加的节点多意味着每一层都有成本包括管理、监控、网络流量。第三是配套组件。ES 的专有主节点、协调节点、Kibana 节点都是纯成本项在日志使用方式里它们不直接产生业务价值。SelectDB 的 FE 职责更轻两台 4C8G 就能完成元数据管理。5. 迁移路上的硬骨头与建议5.1 存量数据怎么搬ES Catalog 批量导入测试归测试真正上线迁移是另一回事。我们当时面临的第一道坎是历史日志。30 天的日志全量导一遍如果用脚本从 ES 查出来再往 SelectDB 插效率太低而且会拖垮线上集群。最后用的方案是 SelectDB 自带的 ES Catalog。它能把 ES 集群映射成一张外表然后在 SelectDB 里直接INSERT INTO SELECT把数据批量拉过来。SelectDB 的导入任务会自动攒批和分桶速度远比逐条查询再写入快。具体做法是-- 先创建 ES Catalog 映射外部 ES 集群 CREATE CATALOG es_catalog PROPERTIES ( type es, hosts http://es-host:9200, user elastic, password xxxxxx ); -- 再把历史索引数据导入 SelectDB 内部表 INSERT INTO log_table SELECT * FROM es_catalog.es_index_name WHERE time 2024-05-01 00:00:00 AND time 2024-05-02 00:00:00;这段 SQL 会按天同步建议错开业务高峰执行同时控制并发。导完后要记得核对行数和几个关键字段的抽样值避免因字段类型映射不一致导致数据错位。5.2 实时数据链路改造Kafka Routine Load 替代 Logstash存量数据可以离线导新鲜日志必须走实时链路。原来的链路基本是 Filebeat → Kafka → Logstash → Elasticsearch。切到 SelectDB 之后Logstash 可以直接去掉改为 Kafka → SelectDB Routine Load。建表时我建议按天分区并且兼顾查询场景设置分桶键CREATE TABLE log_table ( time DATETIME, app VARCHAR(64), host VARCHAR(128), level VARCHAR(16), trace_id VARCHAR(64), error_code VARCHAR(32), message TEXT, latency_ms INT, INDEX idx_trace (trace_id) USING INVERTED, INDEX idx_message (message) USING INVERTED ) DUPLICATE KEY(time, app) PARTITION BY RANGE(time) () DISTRIBUTED BY HASH(trace_id) BUCKETS 16 PROPERTIES ( replication_num 2, compression ZSTD );这里有两个关键点一是trace_id必须建倒排索引否则点查要走全分区扫描二是message如果确实需要关键词检索也要建倒排索引否则 LIKE 会退化成全表扫描。有了索引之后Q1 和 Q3 的响应时间能回到测试值。Routine Load 创建之后会持续消费 Kafka 里的日志数据延迟一般在几秒到十几秒之间。对于日志系统来说这个延迟完全可接受。5.3 查询语法切换从 DSL 到 SQL团队习惯要提前培养这是迁移中最容易被低估的工作量。Kibana 里的查询大多是基于 Lucene 语法或 ES DSL切换成 SelectDB 之后查询方式要变成 SQL。虽然 SelectDB 兼容 MySQL 协议用起来相当顺手但团队里习惯了在 Kibana 上点选筛选条件的人会有一段不适期。我的建议是给团队提前准备一张查询对照表。比如场景Elasticsearch DSL 思路SelectDB SQLtrace_id 点查term querySELECT * FROM log_table WHERE trace_id xxx时间范围 level 过滤bool query rangeSELECT * FROM log_table WHERE time ... AND level ERRORerror_code 分组计数terms aggregationSELECT error_code, count(*) FROM log_table WHERE time ... GROUP BY error_code每分钟 P90 延迟percentile aggregationSELECT date_trunc(minute, time), percentile(latency_ms, 0.9) FROM ... GROUP BY 1不只是调整语法还要把 Kibana 上的常用仪表盘在 SelectDB 对应的可视化工具里重建一遍。这个工作尽量在迁移正式启动前两周就安排不要等到切流量当天再去教人怎么写 SQL。5.4 日志场景特有的细节保留策略与冷热分层日志系统的保留策略在 ES 里通常依赖 ILMIndex Lifecycle Management把索引从 hot 滚到 warm 再滚到 cold。SelectDB 的做法更直接按时间分区过期直接DROP PARTITION不需要额外跑任务清理索引。如果你有长期审计需求比如日志要留 180 天甚至更久建议用冷热分层而不是把所有数据都放在热节点上。SelectDB 支持把冷分区放到对象存储查询时不常用到的数据从对象存储读取成本和本地盘完全不是一个量级。我在这个项目里是把热数据保留 7 天其他数据放对象存储查询仍然可以跨冷热分区执行只是冷分区响应会慢一些。对于「半年查一次」的审计日志来说这个权衡完全合理。5.5 上线后的稳定性观察迁移完成后最需要关注的是 BE 节点的 Compaction 情况。列式存储在持续导入时会产生大量小文件后台 Compaction 如果不及时查询和导入都会变慢。建议给 Compaction 留足 CPU 余量不要为了省成本把 BE 配置压得太紧。另外SelectDB 的 BE 节点是存储和计算一体的扩容和缩容没有 ES 那么繁琐但数据均衡需要时间。如果预见到日志量会有明显增长建议提前扩容而不是等磁盘告警再动。按照我的经验稳定运行一周后把集群的资源监控、告警策略和备份恢复演练都跑一遍这个迁移才算真正告一段落。最后说一点个人体会。做这次测试之前我原本以为 ES 的优势在查询灵活SelectDB 的优势在便宜测试结果确实验证了这一点——ES 在全文检索上依然有不可替代的优势。但对于日志这个场景来说真正高频的查询是过滤、聚合和趋势统计这三件事恰恰是列式 MPP 引擎的强项。如果你们日志系统里 90% 的查询都用不上全文相关性排序那真的值得花两周时间做一次类似对比也许看完账单你会比我更想动手换掉 ES。