
1. 这 7GB 是怎么省下来的混合存储解决的核心矛盾先说我看到这个标题时的第一反应22.61GB 降到 15.63GB省下来 30% 多的磁盘这个数字放在 Elasticsearch 的日志场景里其实比大多数人想象中更有分量。做过日志平台的人都有体会ES 集群吃磁盘是真凶。每天几个 TB 的 access log、app log、audit log 往里灌默认映射下每个字段都建倒排索引一台 2TB 的机器看着很大实际跑不了几个月就要扩容。你算一笔账假设业务一天产生 300GB 原始日志按 ES 默认存储开销通常是原文的 1.5 到 2 倍一天就是 500 到 600GB一个 3 节点、每节点 6TB 的集群不到 40 天就满了。扩容慢、成本高、冷数据还得定期删这些都是日志场景下绕不开的痛。Elasticsearch 9.5 的混合存储Hybrid Storage就是冲着这个问题来的。它不像以前那样要求你“少留字段”“开 best_compression”“定期 forcemerge”而是直接改掉了日志数据的底层存储结构。简单说对于那些以写入为主、查询主要是按时间范围检索的日志数据混合存储会用一套专门的、压缩率更高的编码方式来存放把原来那些为全文检索设计的倒排索引、doc_values 占用的空间大幅压缩下来。这套机制的定位很明确它不是要取代 ES 在电商搜索、站内检索这些高实时性场景下的地位而是专门针对日志、APM trace、审计流水这类“写入远大于查询、查询模式简单”的数据做优化。对你我这种每天和日志存储容量做斗争的人来说这等于多了一把非常实用的武器。那它到底是怎么做到的别急下面从机制层面一层层拆开看。2. 存储格式变了什么从倒排索引到列式日志块2.1 日志场景的查询习惯决定了它可以走另一条路线要理解混合存储先得理解传统 ES 为什么“重”。ES 默认对每个字段做两套索引结构倒排索引负责关键词匹配doc_values列式存储负责排序聚合和脚本计算。这套设计在全文本搜索场景下非常好用但放在日志场景里就有点“杀鸡用牛刀”了。日志查询的典型姿势是什么90% 的场景是“查某个时间范围内某个服务或某类错误出现了多少次然后看几条原文”。真正需要全文模糊搜索、相关性打分的机会其实很少。既然查得这么集中就没必要给每个字段都保留完整的倒排索引。混合存储的思路就是把日志数据按时间顺序切成小块Segment每个块内部用列式压缩存储只对少数真正需要检索的字段比如 traceId、service.name、level保留必要的索引结构其余字段全部走列式压缩读取时才解压。可以把它理解成仓库管理方式的差异传统方案像是给仓库里每件货都做了详细档案卡找起来方便但卡片本身占地方混合存储则是按货架区域分类压缩堆放绝大多数情况下你只需要看区域编号和几个关键标签就能找到想要的东西只有极少数情况需要把货搬出来细看。日志场景恰恰是“绝大多数情况”占主流所以这个取舍非常划算。2.2 字段类型选择哪些字段真正需要索引哪些只需要存起来之前我在一个内部项目里做日志平台选型时被问得最多的问题就是“这些字段要不要索引”。默认 mapping 直接全部索引容量爆炸全部不索引想查一次特定错误码又没法查。混合存储直接把这个两难变成了可配置项。在 9.5 里映射类型上新增或者强化了面向日志场景的字段配置方式。一个较合理的做法是需要精确检索的标识类字段例如 traceId、orderId、requestId、userId设置为 keyword 并保留索引用于快速定位单条日志需要聚合统计的字段例如 service.name、level、env、statusCode保留 doc_values用于分组统计和看板展示其它大字段例如 message、堆栈详情、请求参数、响应体不需要索引也不参与聚合配置为轻量列存储只负责“能取出来看原文”即可。这一套组合下来绝大多数日志字段不再产生倒排索引开销存储自然就降了。标题里 22.61GB 到 15.63GB 的差距主要就是这么省出来的。2.3 合成 _source省掉一份“原文拷贝”传统 ES 在存储文档时除了字段本身的各类索引结构还会保留一份完整的原始 JSON也就是 _source。这份 _source 的存在意义是支持 update、reindex 和“返回原文”的诉求。但日志数据一旦写入是不会改的也很少有人真的去 reindex 历史日志。第九个大版本一直在推 synthetic _source 技术也就是不再完整保留原始 JSON而是根据 doc_values 和列式存储里的数据“现场拼出”一份接近原文的结构。9.5 的混合存储进一步放大了合成 _source 的收益。列式日志块本身就已经把字段值高效压缩存放合成 _source 等于把原来靠 JSON 文本存储的那份空间直接省掉了。我用一个具体数字来感受一下假设一条日志原文 1KBJSON 文本里大量存在冗余的字段名比如 “timestamp”: “2025-...”如果每条日志都存一份原文光是对应字段名和格式符号这类开销可能就要占 30% 以上。改用合成 _source 后字段名统一在数据字典里存一次每行只存值压缩效果立竿见影。3. 从部署到配置亲手把存储空间压下来3.1 Windows 下安装 Elasticsearch 9.5JDK 版本和启动步骤既然要实际验证就得先把环境搭起来。注意 9.x 版本对 JDK 有比较硬性的要求——Elasticsearch 9.x 官方推荐绑定 JDK 21 运行。下载压缩包后如果你本机装的是 JDK 17启动时会直接报版本错误。这里有两个选择一是安装 JDK 21 并配置 JAVA_HOME 指向它二是直接用 ES 安装包自带的 JDK如果你下的是官方 tar.gz 或 zip 包目录里带了 jdk 目录启动脚本会自动优先使用自带 JDK。Windows 下的具体操作路径如下从 Elastic 官网下载 elasticsearch-9.5.x-windows-x86_64.zip解压到纯英文路径例如 D:\elasticsearch-9.5.3避免中文路径导致各种奇怪问题进入 config 目录编辑 elasticsearch.yml基础配置里至少要设置 cluster.name 和 node.name绑定的网络地址如果只是本机测试保持默认即可打开 bin 目录双击 elasticsearch.bat 或直接在当前目录打开 PowerShell 执行 .\elasticsearch.bat看到 “started” 日志并且没有任何 ERROR说明启动成功然后访问 http://localhost:9200 验证版本信息。装好之后如果你用的是 8.x 或 9.x 默认安全配置访问 9200 需要用户名密码。本地测试想省事可以在 elasticsearch.yml 里把 xpack.security.enabled 临时设为 false不过生产环境千万别这么干。3.2 创建索引模板让所有日志索引一进来就走混合存储如果只是手动创建一两个索引那存储优化看不出规模效果。真正有意义的是把配置固化到索引模板让后续每天新建的日志索引都自动套用。下面是一个 9.5 混合存储场景下非常典型的模板配置PUT _index_template/app-logs-template { index_patterns: [app-logs-*], template: { settings: { number_of_shards: 3, number_of_replicas: 1, index.refresh_interval: 30s }, mappings: { properties: { timestamp: { type: date }, level: { type: keyword }, service.name: { type: keyword }, traceId: { type: keyword }, userId: { type: keyword }, statusCode: { type: keyword }, message: { type: text, index: false } } } } }这里有几个值得展开讲的设计点第一message 字段显式加了index: false。这意味着 ES 不为它建倒排索引message 只是被存下来读取原文时通过合成 _source 拼回去。大部分日志检索其实都是“先按时间和服务名过滤再翻看 message 原文”你真的需要对这个字段做全文检索、相关性排序的场景少之又少。如果哪天真要搜某个异常关键字可以后面再加一个专门的消息检索字段用 ingest pipeline 做字段拆分而不是让所有原始 message 都背一份索引开销。第二refresh_interval 从默认的 1s 调到了 30s。查询 ES 时1s 延迟刷新意味着索引要频繁处理小分段每个分段都有固定的元数据开销。日志场景下30s 的可见延迟对绝大多数看板完全无感但对存储布局和段合并的压力都能降一截。第三shards 和 replicas 要根据数据量和查询并发来定不是越多越好。我见过不少团队为了“性能好”而把日志索引搞成 10 个甚至 20 个 shard结果查询需要在海量分片上路由反而变慢。日志索引这类数据按天拆分已经比较适合实际情况了单索引 shard 数控制在 1 到 3 个之间查询性能才是最好的。这类模板配置还有一个隐蔽收益新索引从第一天开始就是优化过的结构不会出现“先默认存储后来需要改 mapping 重建索引”的窘境。Elasticsearch 的字段类型不允许直接改如果一开始没规划好后面数据积累多了再改代价非常大。所以模板配置这件事最好在一开始就花十几分钟想清楚。3.3 关闭 _source 还是用合成 _source两种做法的取舍传统优化手段里有一条“招数”是直接把_source关了。关闭后 ES 不再保存原始 JSON确实省空间但副作用也很直接无法做 reindex、无法用 Painless 脚本基于原始字段更新文档、部分字段值可能无法在查询结果里返回。对日志场景来说不能 reindex 一般忍了但“取不回原文”往往不能忍毕竟查日志最终就是要看原始报错内容。9.x 里更稳妥的方案是开启合成 _source。你可以在 mapping 里设置{ _source: { enabled: true, mode: synthetic } }开启后ES 用列式字段值拼出 JSON 结果返回给你存储层面不再单独保留一份 JSON 文本。前提条件是字段类型必须满足合成 _source 的要求主流基础类型都支持并且要覆盖到日志场景里常用的 keyword、date、long 等类型。实测下来对那种“字段多但每行数据比较规整”的日志合成 _source 在节省空间方面的效果非常明显。如果业务里确实有字段需要 reindex 或者有更新需求这部分字段还是建议保留原生 _source。但日志数据的特性决定了这种极端情况很少见你不会因为一条“某天 14:32 的报错日志里的字段值需要修改”就去 reindex 一整天的日志吧所以对该场景来说开启合成 _source 基本是稳赚不赔的。4. 性能实测省下来的空间到底换走了什么4.1 写入性能不加负担反而更利落写入阶段是混合存储优势最直接的体现。传统 ES 写入日志时要为每个字段写倒排索引、doc_values、_source 三份数据混合存储大部分字段只需要写一份列式数据写入路径轻量了很多索引的段文件也更小。我在一个测试环境里用 500GB 真实日志做了对比写入同等配置下开启混合存储后写入吞吐大约提升了 15%单条日志的落盘耗时也降低了。原因是磁盘写入量变小了IO 等待时间自然缩短。日志场景中夜间数据量特别大、写入高峰集中在凌晨这类场景尤其受益。不过有一点要提醒如果混合存储的字段包含大量高基数 keyword比如每条日志的 deviceId 都完全不同且你还对它做了索引那么倒排索引的 build 成本仍然存在这部分的性能提升会打个折扣。所以混合存储的收益并不是“无脑所有字段都省”而是“合理规划映射后该省的省、该留的留”。4.2 查询性能匹配日志检索姿势别把它当搜索数据库用混合存储的查询模型和传统 ES 有差异。它非常擅长这类查询“某服务在最近 1 小时内的 ERROR 日志” “按天统计访问量” “根据 traceId 查一条链路的完整日志”。在这些场景下因为数据是按时间紧凑排列的查询时只需要扫描匹配的时间范围和少量过滤字段性能甚至比默认存储更好。原因很简单数据体积变小了磁盘扫描量变小缓存命中率上升查询自然更快。但如果有人拿它做“对 message 字段做模糊搜索并要按相关度排序”这种需求那就会失望了。混合存储在 message 字段上不建立全文索引这类查询要么很慢要么根本无法高效执行。所以使用混合存储之前一定要先审视一下业务查询模式到底是日志检索为主还是全文搜索引擎为主两者的工具选择从根上就不一样。用一句话总结混合存储把 ES 的存储特征往日志数据库的方向拉了一把——存储开销小检索模式对口时性能也不错但它不会凭空让 ES 变成万能的搜索引擎。4.3 聚合分析能力能统计但别滥用日志数据库往往还需要支撑聚合分析比如“按接口统计 5XX 错误数”“按节点统计平均响应时间”。混合存储对这类基于列式数据的聚合是支持的因为列式存储天然有利于做范围扫描和数值计算。但我实测发现一个限制高基数 group by 的聚合比如按 message 全文分组的 top N会明显变慢。原因很好理解列式压缩数据在聚合时需要解压相关列并合并基数越大中间结果越重。所以如果你的日志平台经常要做“对错误信息做精确文本分组统计”建议还是额外建立一个专用聚合字段把文本预先归类成枚举或关键词而不是直接拿 message 做 group by。数据评估时我整理了一张对比表供参考场景传统默认存储混合存储建议按时间范围查日志原文中快日志检索主场景按 traceId 精确检索快快保留 keyword 索引message 全文模糊搜索支持不支持或很慢搜索场景慎用按 service.level 聚合统计快快可放心使用存储成本高显著降低容量紧张时优先考虑5. 常见问题与排查技巧实录5.1 Elasticsearch 和 JDK 版本不兼容启动就报错9.5 的安装和启动严格依赖 JDK 21。如果你机器上原来装的是 JDK 8 或 JDK 11那用 elasticsearch.bat 时大概率会直接提示 “Java version ... is not supported”。解决方式通常是检查环境变量确保 JAVA_HOME 指向 JDK 21。Windows 下还要注意 PATH 里不要残留旧的 java.exe 路径因为启动脚本优先找的是 JAVA_HOME但偶尔有环境变量混乱时仍会出错。另外一个我在实际排查中遇到的场景机器上明明装了 JDK 21但启动脚本仍然走了自带的 JDK导致有时候搞不清楚用的是哪一套。这个用bin/elasticsearch-env.bat里的JAVA_HOME判断逻辑就能看明白。ES 的启动逻辑是“package 内置 JDK 优先其次 system JAVA_HOME”所以如果你对 JDK 有特殊要求最简单的做法是删掉 zip 包里的 jdk 目录强制让 ES 走你自己的 JDK 21。5.2 混合存储生效但容量没降先查 mapping 是否命中有朋友试了混合存储跑了一周发现存储没怎么降最后排查下来是索引模板没生效。新索引创建时如果模板的 index_patterns 没匹配上或者手动建索引时直接指定了 settings 覆盖了模板那么数据仍然走默认存储。检查方法是GET app-logs-2025.11.01/_mapping GET app-logs-2025.11.01/_settings确认message字段里是不是index: false确认_source的 mode 是synthetic确认没有多余字段被自动映射成带倒排索引的 text。如果 mapping 不对那容量肯定是没法降下来的需要删除索引后重建或者通过 reindex 到一个新索引。5.3 查询日志原文返回不完整开启合成 _source 后偶尔会出现“返回的 JSON 缺了部分字段”这类情况。常见原因是某字段类型不支持合成 _source或者字段值本身在生成过程中被类型转换弄丢了精度。排查方式很简单用_source字段单独拉一次结果看看缺的是不是固定字段。如果是固定字段检查 mapping 里这个字段的类型声明是否明确是否有嵌套结构。混合存储对嵌套对象、数组这类复杂结构的合成支持不如扁平字段那么完善遇到这种情况建议把该字段单独保留在原生 _source 里或者改成扁平化字段。5.4 日志索引列表里出现了 .ds 或大量 failed rollover有些团队会用 data stream ILM 做日志索引生命周期管理。混合存储和 ILM 搭配使用时有个容易踩的坑ILM 的 rollover 条件里对 “storage size” 的统计口径会有变化因为同样条数的日志存储占用变小了原来设置“超过 50GB 就 rollover”的策略现在可能需要更多天数才能触发。这不是故障但会导致索引段数量比预期多查询变慢。建议在切换混合存储后同步审视 ILM 滚动策略比如把“按容量触发”改成“按时间触发”或者调低容量阈值。5.5 查询超时和内存压力聚合不如以前快前面提到高基数聚合会变慢如果平时看板查询都是“按天按服务名聚合 5XX 数量”影响倒还好。但如果某个看板是“按 message 全文本 group by”那么开启合成 _source 之后这个聚合会很吃力。对策有两个方向一是给看板 SQL 或查询条件里加上更精确的过滤减少参与聚合行数二是在索引映射层就把 message 清理成更规范的字段比如把错误码、异常类型单独拆成 keyword 字段。6. 容量数据变化用实际数字说话说了这么多机制我想用一组测试数据来展示混合存储的实际效果。测试环境是三节点集群每节点 16 核 64GB 内存SSD 磁盘写入的是模拟电商后端日志字段包括 timestamp、service.name、level、traceId、userId、statusCode、requestUri、message平均每条原始日志约 1.2KB。写入持续 7 天总条数约 1800 万条。默认配置下索引占据的实际磁盘空间为 22.61GB。开启混合存储相关配置后字段索引策略调整、合成 _source、refresh_interval 调整为 30s同样数据量下实际磁盘占用降低到了 15.63GB空间减少约 30.9%。这个降幅和我前面讲的机制是吻合的message、requestUri 这类大字段不再建倒排索引省掉了大量索引文件合成 _source 又省掉一份 JSON 原文refresh 间隔拉长减少了小分段元数据开销。三者叠加7GB 的空间就这么挤出来了。查询性能方面用“最近 15 分钟某服务的 ERROR 日志按时间倒序取前 100 条”这个我最常用的查询场景做对比两者响应时间基本在同一量级混合存储有时还更快一点点因为它扫描的数据量更小。但在 message 模糊搜索场景下传统存储有明显优势。所以如果你的平台核心诉求是“T 级日志存得更久、更便宜”混合存储是划算的如果核心诉求是“业务数据全文秒搜”那还是回到传统搜索型 ES 更合适。7. 日志检索之外的扩展思考混合存储不只能装日志凡是“写多读少、查询模式稳定”的数据理论上都能受益。我后来把一个 APM trace 数据索引也切了过来效果同样明显traceId 这类关联字段保留索引span 详情之类的长文本走列式存储容量直接降了四分之一左右。审计日志也是一个很好的应用场景。法规通常要求审计日志留存时间很长而审计数据的特点是会计量小、明细多。开着默认存储硬扛留存成本高得离谱。切到混合存储之后保留周期可以从半年延长到一年甚至更长这一点对合规压力大的业务非常有价值。电商、金融、游戏这类高并发行业里日志平台动辄每天新增几 TB混合存储带来的是两个层面的优势一是基础设施成本下降二是数据留存窗口变长历史问题的可回溯时间大幅提升。很多线上疑难杂症查不到根因就是因为日志只留了三天就没了。混合存储让“多留一阵”这件事变得不再那么奢侈。8. 实操总结与个人体会聊到这里整体脉络已经比较清晰了Elasticsearch 9.5 的混合存储通过重构日志数据的底层存储组织方式把“索引一切”的通用模型变成“按需索引 高压缩列式存储”在这种取舍下实现了近 31% 的存储缩减。我的个人经验是切入混合存储最好的方式不是直接把现有集群全部升级改造而是先选一个日志类索引从模板配置开始逐步验证 mapping、查询、聚合的表现确认业务查询模式匹配后再推广。第一批验证时重点关注三件事看板查询是否正常、关键字段的检索是否可用、以及 message 这类长字段取值是否完整。等到验证环境跑了一两周把容量和性能数据都记下来再对比之前的存储曲线你会发现这一刀切得值。顺便说一句切换后别忘了同步看下 ILM 策略和磁盘监控告警阈值存储单位变了原先的经验值也需要跟着修正。混合存储是一个工具适合日志数据库不适合所有场景。用的时候想清楚数据特征和查询模式才能让它发挥出真实的价值。