日志、审计记录、历史订单——这些数据有明确的「热三天、温一月、冷一年」的访问规律,却往往占着最贵的 SSD 和最多数的数据节点。把它们删掉舍不得,留着又养不起。可搜索快照(searchable snapshots)给出了第三条路:数据只存在于对象存储的快照里,节点按需从快照读取并缓存,查询依然可用。冻结层则在此基础上把本地缓存压缩到极小的比例,用共享缓存服务多个节点,把每 GB 成本压到热层的几分之一。代价是首次查询的延迟明显上升,理解这个代价的边界是设计分层架构的关键。
1. 存储分层与冷数据困境
一句话总结: 冷数据占容量的大头却极少被访问,把它留在热层是最典型的成本浪费。
1.1 数据的访问规律
典型的日志或时序数据呈现强烈的长尾分布:最近 7 天的数据承载 90% 以上的查询,1 个月前的数据查询量降到个位数百分比,1 年前的几乎只在合规审计时被翻出来。而容量分布恰好相反——时间越久的数据累积越多。
1.2 传统分层的极限
用 ILM 把数据从热节点滚到温节点、冷节点,能缓解问题,但冷节点仍然是「数据节点」:它要持有完整的分片副本、占用本地磁盘、参与分片分配与恢复。数据量增长时,冷节点集群也要跟着扩,成本只是从 SSD 换成了 HDD,量级并没有质变。
1.3 可搜索快照的定位
可搜索快照让「冷分片」不再占用数据节点的本地存储:分片数据存放在快照仓库(通常是 S3 或兼容对象存储)里,节点只保存一份元数据与少量本地缓存。查询时按需从对象存储拉取数据块。它的成本结构从「按容量买节点」变成「按请求付对象存储费用」。
1.4 三层架构
| 层 | 数据位置 | 本地缓存 | 典型延迟 | 成本 |
|---|---|---|---|---|
| hot | 本地 SSD | 全量 | 毫秒级 | 最高 |
| warm | 本地 HDD | 全量 | 毫秒级 | 中 |
| frozen | 对象存储 | 极小比例 | 秒级 | 最低 |
2. 可搜索快照的原理
一句话总结: 可搜索快照把快照索引挂载成普通索引,读取时从对象存储按块拉取并缓存到本地,Lucene 层感知不到差别。
2.1 从快照直接读取
Elasticsearch 的段文件是不可变的,这个特性是可搜索快照的基础。快照仓库里保存的就是这些不可变的段文件。挂载一个可搜索快照索引时,节点不会把数据下载到本地,而是把快照目录「挂」进 Lucene 的目录抽象层,读取时按需从对象存储取块。
2.2 缓存机制
节点上有一个专用的共享缓存目录(path.shared_data),用于存放从对象存储拉取的数据块。缓存是块粒度的:一次查询只拉取涉及的块,后续命中同一块则直接从本地读取。缓存采用 LRU 淘汰,大小通过节点配置控制。
2.3 挂载的两种模式
- full_copy:挂载时把快照数据完整复制到本地,之后与普通索引无异。适合数据量小、查询频繁的场景,本质上还是一次「恢复」。
- shared_cache:共享缓存模式,数据留在对象存储,本地只放缓存。这是可搜索快照的核心模式,也是成本优势的来源。
2.4 只读与不可变
共享缓存模式挂载的索引是只读的:不能写入、不能更新、不能改映射。这符合冷数据的特性,也简化了实现。如果需要对冷数据做修正,只能重新生成快照并重新挂载。
3. 配置与使用
一句话总结: 使用可搜索快照需要先注册支持它的快照仓库,再通过挂载 API 把快照索引变成可查询索引。
3.1 注册仓库
PUT /_snapshot/cold_store
{
"type": "s3",
"settings": {
"bucket": "my-es-cold",
"base_path": "snapshots",
"max_restore_bytes_per_sec": "500mb"
}
}
只有支持 searchable_snapshots 能力的仓库类型才能挂载可搜索快照,S3、GCS、Azure Blob 等对象存储都支持。
3.2 创建快照
PUT /_snapshot/cold_store/snap-logs-2026.09?wait_for_completion=true
{
"indices": "logs-2026.09",
"include_global_state": false
}
快照完成后即可挂载。
3.3 挂载可搜索快照
POST /_snapshot/cold_store/snap-logs-2026.09/_mount?wait_for_completion=true
{
"index": "logs-2026.09",
"renamed_index": "logs-2026.09-cold",
"index_settings": {
"index.number_of_replicas": 0
},
"storage": "shared_cache"
}
挂载后得到一个名为 logs-2026.09-cold 的只读索引,可以像普通索引一样查询。renamed_index 是必要的——原索引名可能仍被占用。
3.4 解除挂载
DELETE /logs-2026.09-cold
删除索引即解除挂载,快照本身不受影响,需要时还可以重新挂载。这正是可搜索快照的灵活性所在:挂载与卸载的成本都很低。
3.5 节点角色与配置
可搜索快照节点需要配置共享缓存目录:
node.roles: [ data_frozen ]
path.shared_data: /mnt/shared_cache
data_frozen 是专用于冻结层的角色。共享缓存目录应挂在大容量的本地盘上,它是性能的关键。
4. frozen tier 与部分索引
一句话总结: 冻结层用极小本地缓存加共享缓存服务多个节点,并通过部分索引把不参与查询的段完全留在对象存储。
4.1 冻结层的定义
冻结层是可搜索快照的一种特定部署形态:节点角色为 data_frozen,本地缓存与数据量的比例极低(官方建议 1% 到 5% 量级),多个节点共享同一份缓存目录(通过网络文件系统挂载)。查询时数据从对象存储流入共享缓存,所有节点都能受益。
4.2 部分索引
共享缓存模式有两种行为:
- 完全挂载:所有段都参与查询,未缓存的段从对象存储拉取。
- 部分挂载:只有带
partial标记的索引设置才生效——查询时只搜索已缓存的部分,未缓存的段不参与。这牺牲了完整性换取速度。
4.3 部分搜索的语义
部分搜索意味着结果可能不完整,但能极快返回。它的典型用法是「先看个大概」——用户在仪表盘上快速扫一眼历史趋势,不需要精确到每一条。是否启用部分搜索由查询参数控制,而不是索引设置,这样同一份数据可以按场景选择精确或快速。
4.4 与 ILM 的集成
ILM 的 frozen 阶段可以直接执行 searchable_snapshot 动作:
{
"frozen": {
"min_age": "90d",
"actions": {
"searchable_snapshot": {
"snapshot_repository": "cold_store"
}
}
}
}
数据到 90 天后自动转为可搜索快照,无需人工干预。这是把分层策略落地的标准做法。
4.5 冻结层与副本
冻结层的索引通常设置 number_of_replicas: 0:数据在对象存储里本身就是多副本的(对象存储自带冗余),再在本地存副本没有意义,只是浪费缓存空间。可用性由对象存储的持久性保证。
5. 缓存策略与性能
一句话总结: 可搜索快照的性能取决于缓存命中率,首次查询受对象存储延迟支配,重复查询接近本地读。
5.1 首次与后续查询
首次查询一个未缓存的段时,节点要从对象存储拉取数据块,延迟由网络与对象存储的响应时间决定,通常是几十到几百毫秒的额外开销,极端情况到秒级。缓存命中后,后续查询的延迟与本地磁盘读接近。
5.2 缓存预热
对已知会被频繁查询的索引,可以主动预热:
POST /logs-2026.09-cold/_search?request_cache=false
{
"size": 0,
"query": { "match_all": {} },
"aggs": { "all": { "terms": { "field": "service", "size": 1 } } }
}
跑一次覆盖主要字段的聚合,能把相关段拉进缓存。预热通常在挂载后立即执行,或由定时任务在低峰期执行。
5.3 缓存大小与命中率
缓存大小决定命中率。缓存过小会导致频繁的拉取与淘汰,性能剧烈波动;缓存过大则成本优势被削弱。经验做法是让缓存能覆盖「最近一个查询周期内被访问的数据」,再根据监控的缓存命中率微调。
5.4 查询模式的影响
- 时间范围窄的查询:只涉及少数段,缓存效率高。
- 全量扫描或大范围聚合:会拉取几乎所有段,首次执行极慢。
- 按字段排序或聚合:需要 doc values,同样要拉取数据块。
对冻结层的数据,应尽量避免「无边界的大范围聚合」,把查询范围收窄到具体时间段。
5.5 监控指标
关注 searchable_snapshots 相关的统计:缓存命中率、从对象存储读取的字节数、读取延迟。如果读取字节数持续高企而命中率低,说明缓存配置不合理或查询模式不适合冻结层。
6. 成本与延迟权衡
一句话总结: 可搜索快照把存储成本降低一个量级,代价是首次查询延迟上升,是否划算取决于数据的访问频率。
6.1 成本结构对比
| 维度 | 本地节点存储 | 可搜索快照 |
|---|---|---|
| 存储介质 | SSD 或 HDD | 对象存储 |
| 计费方式 | 按节点容量 | 按存储量加请求数 |
| 副本成本 | 成倍增加 | 对象存储自带冗余 |
| 冷启动延迟 | 无 | 首次查询明显 |
| 扩展方式 | 加节点 | 无需扩容 |
6.2 划算的临界点
判断标准是「查询频率」。如果某份数据每周被查几次,留在本地节点上是浪费;如果每天被查上千次,冻结层的延迟会累积成明显的体验问题,此时应该用温层。经验阈值是每天访问几次以下的数据,适合放进冻结层。
6.3 请求费用
对象存储按请求计费,大量小范围查询会产生可观的请求费用。把查询范围收窄、减少重复的小查询、利用请求缓存,都能降低这部分成本。这是从「买硬件」转向「付流量」后必须重新建立的成本意识。
6.4 延迟预算
如果业务对冷数据的查询有明确延迟要求(比如报表必须在 5 秒内返回),冻结层不一定合适。可以做的折中是:把最近 3 个月的数据放温层,更早的放冻结层,让延迟敏感的部分留在本地。
6.5 与 CCR 和搜索副本的关系
可搜索快照的索引只读且副本数为 0,不参与跨集群复制,也不提供搜索副本带来的查询吞吐扩展。它的定位是「降低存储成本」,不是「提升查询能力」。需要查询吞吐时,仍然要靠本地索引加副本。
7. 生产实践与常见坑
一句话总结: 可搜索快照的实施要点是「先用 ILM 自动流转、再监控缓存命中率、最后按访问频率验证分层是否合理」。
7.1 上线顺序
建议的实施顺序:
- 确认快照仓库可用且已完成首次全量快照。
- 挑选一个访问量低的索引做试点挂载,观察查询延迟。
- 用 ILM 的 frozen 阶段把流转自动化。
- 建立缓存命中率与对象存储请求量的监控。
- 根据实测数据调整分层边界(多少天进冻结层)。
7.2 常见坑
- 快照仓库与数据盘同盘:对象存储应独立,不要用本地文件系统仓库冒充。
- 缓存目录空间不足:
path.shared_data写满会导致缓存失效、性能崩塌。 - 首次查询超时:客户端超时设置过短,冻结层首次查询被中断;应对冷数据查询放宽超时。
- 大范围聚合打到冻结层:一次全量聚合可能拉取整个索引,耗时数分钟。
- 忘记副本设为 0:白白占用缓存空间。
- 仓库删除策略冲突:SLM 的保留策略可能删掉仍被挂载的快照,导致索引不可读。
7.3 与其他分层手段的配合
可搜索快照解决的是「冷数据存储成本」,与之配合的手段还有:用 ILM 强制合并把段数降到最少(段越少,对象存储拉取越高效)、用 index.codec: best_compression 压缩、以及把不需要查询的字段从映射里去掉。这些措施叠加起来,能进一步压低成本。
7.4 何时不该用
如果数据量本身不大(几百 GB)、或者查询频率很高、或者对延迟极度敏感,可搜索快照带来的复杂度与延迟代价可能超过收益。分层架构的价值随数据规模增长而增长,小规模集群直接加盘往往更简单。
8. 总结
| 环节 | 要点 |
|---|---|
| 核心原理 | 段不可变,快照目录直读并按块缓存 |
| 挂载模式 | full_copy 复制到本地,shared_cache 留对象存储 |
| 冻结层 | data_frozen 角色加极小本地缓存与共享缓存 |
| 部分搜索 | 只查已缓存段,快但结果可能不完整 |
| 性能特征 | 首次查询慢,缓存命中后接近本地读 |
| 成本结构 | 从按容量买节点转为按存储与请求付费 |
| 自动化 | ILM 的 searchable_snapshot 动作完成流转 |
| 适用边界 | 低频访问的冷数据,高频与低延迟场景不适用 |
可搜索快照是 Elasticsearch 分层架构里最漂亮的一块拼图:它把「不可变的段」这个底层设计发挥到了极致,让冷数据可以完全脱离数据节点而仍然可查。它的代价也很明确——首次查询的延迟。把这份延迟放进业务可接受的范围内,就是容量规划与分层策略要回答的问题。
延伸阅读
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。