ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

TongSearch存储降本实战:ILM与可搜索快照组合拳

TongSearch存储降本实战:ILM与可搜索快照组合拳

凌晨两点半,监控大屏上那条磁盘使用率的曲线还是笔直地顶在 92% 的位置,一点面子都不给。我当时负责的 TongSearch 集群正扛着一个核心业务的全量搜索请求,索引规模一天涨 90GB,三副本一算就是 270GB 的物理消耗,按这个速度,再有 12 天就要写满。扩容不是不行,但哪怕只是加一台数据节点,采购审批、机房上架、索引分片重新平衡,加起来至少一周才能让集群恢复正常水位。更让我头疼的是,这堆数据里真正高频访问的只有最近几天的,剩下几十 TB 的冷数据躺在热节点上,偶尔被翻一次旧账,却长期占着最贵的存储资源。

这个局面的解法,就是标题里写的那套组合拳:用 TongSearch 的 ILM 做索引生命周期管理,把温冷数据逐步往廉价存储沉降,再用可搜索快照让归档数据“看起来还在线”,随时能查、不用还原。这套实践跑完,集群磁盘水位降到了 40% 出头,查询侧没有出现明显劣化,存储成本算下来省了大概六成。这篇就把它整个拆开,从原理到配置,从踩坑到压测,完整过一遍,适合正在被索引膨胀追着跑、又舍不得删历史数据的搜索平台负责人和运维同学参考。

1. 为什么“存不下”成了必答题

1.1 一个典型的容量危机现场

以我当时负责的订单检索业务为例,业务方要求全量订单保留 180 天可查,日增索引大约 90GB,初始设计是 3 副本。不看别的,单是 180 天的原始数据量就是 90GB × 180 ≈ 16.2TB,三副本物理占用接近 49TB。TongSearch 底层基于 Lucene,索引在写入主分片之后还会经历段合并、translog 落地这些常规动作,实际物理占用通常比逻辑数据量再多出 10%~15%。也就是说,真实磁盘需求大约是 54~56TB。

这个量级落在原本为“近期数据滚动”设计的集群上,很快就撞墙了。表面上,业务方说“偶尔查一下半年前的数据”,但实际上这种低频查询恰恰是最难处理的——你不能因为这些数据访问率低就删掉,因为真的有人会查;也不能像热数据那样堆在高速盘上,因为九成时间它们都在睡大觉。

1.2 先别急着加盘

遇到磁盘告警,第一反应自然是扩容。但加盘解决的是“当前爆满”的问题,解决不了“为什么爆满”的问题。TongSearch 集群里每个节点能承载的分片数量是有上限的,数据量上去了,光有磁盘不行,CPU、堆内内存、文件描述符都要跟着涨。换句话说,加一堆节点只是把雪崩往后拖,而且拖不了多久。

另一个被低估的问题是运维复杂度的上升。节点数从 8 变成 15,集群的 master 选举、分片分配、跨节点复制全部进入新的并发水位,任何一个节点响应变慢,都可能引发分片迁移风暴。我见过不止一个集群在扩容后出现更严重的写阻塞,原因就是节点变多之后,协调节点压力大增,反而把原本平稳的写入链路拖垮了。

1.3 为什么不能直接删历史数据

有人说,低频数据直接删了不就行了?业务方那边要做对账审计、客服那边要查几个月前的订单详情、算法团队时不时还要拉一段历史日志做特征回溯,这些都是真实存在的查询需求。删数据一时爽,后续随便一个业务分析都会变成“对不起,数据只保留 30 天”。

而且从成本角度看,历史数据虽然访问频率低,但不等于是垃圾数据。数据是攒出来的资产,你要做的是把“保存方式”变得便宜,而不是把“数据本身”抛弃。这也是后面 ILM 和可搜索快照存在的根本价值:让对的数据在对的时间,待在对的存储层上。

1.4 传统快照为什么不够用

提到降本,绝大多数人第一反应是“做快照备份,然后删本地索引”。听起来合理,操作上也简单:定期把索引快照推到备份仓库,然后删掉本地数据。但问题是,备份库里的数据是不能被 TongSearch 直接查询的。一旦业务方真的来一句“我要查 3 个月前的 XX 订单”,你只能老老实实跑一次 restore,把快照恢复到集群里。

数据量小的集群这么干没问题,但如果是几十 TB 的索引,从备份仓库拉回来再建索引,单是恢复过程就要跑好几个小时。而且恢复期间,本就紧张的集群磁盘还会再被撑一轮。传统的快照方案本质上还是“备份—恢复—查询—再删除”的循环,操作成本高,响应速度完全达不到“随心查”的体验。

2. 可搜索快照到底改变了什么

2.1 快照不再是“不能动的备份文件”

可搜索快照(searchable snapshot)这个概念,简单说就是把快照直接从备份仓库里“挂载”回集群,让索引的数据文件不用全部下载到本地,也能被检索。查询时,TongSearch 会先读本地缓存,缓存没命中就去远端存储里拉取对应的数据块,拉回来之后在本地留一份缓存,下次再查同一批数据就快很多。

听起来好像没什么特别?关键在于“按需拉取 + 本地缓存”这个模型,它把传统的“全量恢复”变成了“用多少取多少”,查询端根本不需要把几十 TB 数据全部搬回本地就能工作。这打破了过去“数据要么在集群里、要么在冷备里”的二选一困境,让数据可以长期停留在存储成本更低的远端仓库,同时又保持“在线可查”的状态。

2.2 部分挂载 vs 完全挂载

可搜索快照在实现上分为两种挂载方式:完全挂载(full mounted)和部分挂载(partial mounted)。完全挂载会把远端仓库里的数据文件完整下载到本地,本质上接近一次 restore,好处是查询性能等同普通索引,坏处是本地磁盘占用没降下来,这在降本场景里几乎没有任何意义。

真正适合降本的是部分挂载。部分挂载的索引在本地只保留分片的元数据 + 按需加载的缓存数据,数据主体永远放在远端的备份仓库里。本地缓存默认给到每个分片一定配额,在这个配额内,最近访问过的数据块会常驻本地;超出配额或者长时间没被访问的块会被淘汰,释放磁盘空间。这样既保住了查询速度,又保住了磁盘成本,是典型的“以缓存换容量”。

2.3 这轮优化里的“降本”到底降在哪

先说清楚钱省在哪儿。热节点用的是 NVMe 或者企业级 SSD,单价高,性能好;如果只是保存历史数据,完全可以把它们挪到本地 HDD 或者直接放进对象存储里。可搜索快照方案下,数据主体放在廉价存储上,冷数据查询时会引入一次网络读,但换来的是每 GB 存储成本下降到原来的五分之一甚至十分之一。

用生活类比来说,以前是把全部家当都塞在市中心精装房里,房租贵;现在把不常用的季节衣物打包送到郊区的自助仓,要穿的时候跑一趟取出来,穿完再送回去。地方还是那个地方,但租金完全不是一个量级。

3. ILM 四个阶段怎么设计与实践

3.1 阶段总览与数据流向

TongSearch 生态里的 ILM 把索引生命周期分成 hot、warm、cold、frozen 四个阶段。每个阶段可以挂不同的 action,配置的核心思路是:数据越老,存储越便宜,冗余和性能要求越低。我的落地方案是这样的:

阶段存储介质副本数主要操作查询响应预期
HotSSD 热节点1~3按容量滚动、新索引写入毫秒级
WarmHDD 温节点1分片压缩、段合并、只读几十毫秒
Cold远端快照仓库 + 本地小缓存0(依赖快照冗余)创建可搜索快照、删除本地分片首次秒级,缓存命中后百毫秒
Frozen远端快照仓库批量缓存0老化淘汰本地缓存更慢,但可查

可以看到,从 warm 进入 cold 开始,本地数据就不再全量保留,数据真正的“家”从节点磁盘搬到了仓库。这一步操作完成后,磁盘释放效果立竿见影。

3.2 Hot/Warm 阶段的配置细节

hot 阶段的核心是 rollover:按数据量或文档数触发新索引切换。我一直用“超过 50GB 或 7 天滚动一次”这类条件来约束,避免单个索引过大影响后续 shrink 操作。rollover 条件是支持 OR 组合的,只要满足其中一个就会滚动,这里要看实际业务量来调整阈值,太小会导致索引碎片化,太大会让后续 warm 阶段的合并压力大增。

warm 阶段的动作就两个核心:shrink 把分片数从多个收缩到更少,forcemerge 把多个段合并成少数大段。很多人忽略 shrink 的一个前提:目标分片数必须是源分片数的因数,否则 API 直接报错。另外 forcemerge 一旦执行,索引就变成只读了,必须在确认业务侧不再对这个阶段的数据发起新写入之后再挂这个动作。

3.3 Cold 阶段挂接可搜索快照

cold 阶段是这个方案最关键的转折点。ILM 进入 cold 阶段后,会执行 searchable_snapshot 动作,自动把索引的数据做一份快照推到我们指定的备份仓库,然后会把本地分片删除,再以“可搜索快照”的形式把同一个索引挂载回集群。

配置里我写的策略大致是:

{ "policy": { "phases": { "hot": { "min_age": "0ms", "actions": { "rollover": { "max_size": "50gb", "max_age": "7d" }, "set_priority": { "priority": 100 } } }, "warm": { "min_age": "2d", "actions": { "shrink": { "number_of_shards": 1 }, "forcemerge": { "max_num_segments": 1 }, "set_priority": { "priority": 50 } } }, "cold": { "min_age": "7d", "actions": { "searchable_snapshot": { "snapshot_repository": "archive_repo", "storage": "shared_cache" }, "set_priority": { "priority": 0 } } } } } }

这段配置的含义就是:索引生成后 7 天进入 cold 阶段,交给 searchable_snapshot 动作处理。snapshot_repository 指定我们注册好的仓库;storage 指定为 shared_cache,对应的就是部分挂载模式,数据主体留在仓库,本地只做缓存。这个阶段执行完之后,你再去看这个索引的 store 大小,会发现本地实际占用大幅缩水。

3.4 用索引模板把策略串起来

策略建好之后,不能每次手工去关联,得让索引在滚动创建的时候就自动挂上策略,这就是索引模板的作用。模板里设置 alias、分片数、副本数这些基础信息,同时在 settings 里把它和上文匹配的 ILM policy 绑定。

我这边模板里的关键片段是:

{ "index_patterns": ["order-log-*"], "settings": { "number_of_shards": 3, "number_of_replicas": 1, "index.lifecycle.name": "searchable_ilm_policy", "index.lifecycle.rollover_alias": "order-log-write" } }

这样写的好处是,业务方只需要往别名 order-log-write 里写入新数据,TongSearch 的 rollover 流程会自动创建新索引,新索引自动继承 ILM 策略,策略会自动把旧索引推到 warm、cold。整个生命周期管理完全不用人工介入。

3.5 挂载后的索引长什么样

cold 阶段执行完成后,用 GET _cat/indices 去看,索引还是那个索引,别名、映射、文档数都在。但是如果看 store.size 和 pri.store.size,会发现它已经变得非常小,因为本地只剩下元数据和部分缓存。真正的数据文件在快照仓库里。对于查询端来说,它仍能正常响应,不需要你额外做任何“恢复”操作。

这一步其实对业务方影响很大:在业务方看来,数据一点没少,随便怎么查;在运维方看来,磁盘占用实打实降下来了。双方都不需要做任何额外的行为,这套方案才能真正落地成功。

4. 实操全过程记录

4.1 先把备份仓库注册好

可搜索快照的前提是有一个稳定的远端仓库。TongSearch 支持多种仓库类型,我们生产用的是 S3 兼容的对象存储,所以在集群配置里注册仓库是第一步:

PUT _snapshot/archive_repo { "type": "s3", "settings": { "bucket": "search-archive-bucket", "endpoint": "oss-cn-region.internal", "access_key": "xxx", "secret_key": "yyy", "base_path": "searchable_snapshot" } }

注册完之后建议立刻做一次连通性验证,比如创建再删掉一个临时快照仓库里的空快照,或者直接列出 bucket 目录确认读写权限。这一步看着简单,但仓库配置错误是后面所有问题里最容易爆的雷,尤其是自建对象存储,endpoint 填成外网地址、访问密钥权限给错,都会导致快照任务反复重试、甚至拖累集群状态。

4.2 ILM 策略的创建与验证

把上面的 ILM 策略丢到集群里执行,然后做一次 explain 检查:

GET order-log-2025.06.01/_ilm/explain

explain 接口会返回这个索引当前处于哪个阶段、正在执行哪个 action,如果还有报错,会在这里显示具体原因和重试建议。这一步非常推荐排在迁移已有索引之前做,先拿一个小索引跑完整流程:hot 写入、滚动、进入 warm、执行 shrink、进入 cold、生成可搜索快照、删除本地数据。整套流程大部分情况下不会一次通过,提前用小索引把坑踩完,比直接拿生产数据试验要稳妥得多。

4.3 已有索引如何手工迁移

ILM 对新建索引是自动的,但对已经存在于集群里、刚挂上策略的老索引,需要手动设置滚动或直接让策略生效。我在代码(这里指管理脚本)里做的操作是,把符合条件的历史索引直接改设置,让它们立刻从某个阶段开始执行:

PUT order-log-2025.05.10/_settings { "index.lifecycle.indexing_complete": true }

或者直接对存量索引调用 move-to-step 接口,让 ILM 跳过部分前置动作,直接进入需要的阶段:

POST _ilm/move/order-log-2025.05.10 { "current_step": {"phase": "hot", "action": "rollover", "name": "attempt-rollover"}, "next_step": {"phase": "warm", "action": "shrink", "name": "shrink-index"} }

这个操作要小心,它是一次“快进”,跳过的步骤不会自动补齐。比如索引还没做过 forcemerge 就直接进了 cold,可能因为段太多,导致后续查询时缓存命中率偏低,查询耗时会明显增加。

4.4 迁移过程中的磁盘变化观察

迁移过程中我重点盯了三个指标:集群级磁盘水位、单个索引的 store.size、快照任务执行进度。

最直接的是 disk 水位。在 cold 阶段把搜索快照建好、本地分片删掉的那一瞬间,磁盘使用率会出现一次肉眼可见的跳水。比如我当时观察到节点磁盘占用从 85% 直接降到 63%,过了几天,随着更多历史索引被 ILM 处理掉,整体来到 42% 附近,这才敢说方案真的生效了。

快照任务用这个接口看:

GET _snapshot/archive_repo/_status

这里要说一句,快照任务在 cold 阶段执行期间,是会占对象存储的带宽的。如果同时有大量索引一起进入 cold,仓库写入可能成为瓶颈。稳妥的做法是错峰操作,给集群加一点限速,避免把对象存储的带宽打满之后,连带影响到其他依赖仓库的备份任务。

4.5 查询侧怎么验证“随心查”

数据迁移到快照仓库之后,业务方最关心的就是“现在查会不会变慢”。我的验证思路很简单:挑几个真实业务查询,把过滤器限定到不同阶段的老索引上,分别看首次查询和第二次查询的耗时。

实测下来,首次命中缓存之外的冷数据,查询耗时确实会从几十毫秒跳到 2~4 秒,这个主要受仓库网络带宽和数据块大小影响;第二次查询相同范围,耗时基本能回到 200~500ms 左右,因为数据块已经缓存在本地了。这个结果我提前同步给了业务方,让他们知道“偶尔首次查历史数据会慢一点,但可接受”,算是把预期管理做在了前面。

5. 成本账本与关键经验

5.1 存储单价差距到底有多大

降本这件事,不能光靠感觉,要算账。我按通用云资源价格折算了一下,不同类型存储的每 TB 每月成本大致如下:

存储类型相对价格系数特点
NVMe SSD 热节点10低延迟、高并发、适合高频写入
HDD 温节点3~4顺序读写快、随机读一般、单位容量便宜
对象存储/S3 兼容1容量弹性、按量付费、读取有网络延迟

假设我们有 30TB 逻辑数据需要存 180 天,在纯热节点方案里,三副本物理占用约 90TB,按 NVMe 价格系数 10 来算,月成本相当于 900 个“对象存储单位”。切到 ILM 加可搜索快照之后,热数据只保留最近几天,大约 5TB;温数据保留 1~2 个月,约 15TB;剩下的 10TB 冷数据放进对象存储仓库,本地只留少量缓存。综合算下来月成本大约是 5×10 + 15×3.5 + 10×1 = 107.5 个单位,相比原来的 900 个单位,省了将近九成。当然这只是粗略对比,实际还要算网络流量、节点资源空置等情况,但量级差异已经很说明问题了。

5.2 性能损失能接受吗

这个方案最让人犹豫的点是冷数据查询性能。我的观点是:冷数据本来就是低频访问,如果业务查询真的是高频且对延迟极度敏感,那它就不该被定义为冷数据。可搜索快照通过本地缓存已经能顶住大部分“偶尔翻旧账”的查询场景,真正需要全量扫冷数据的场景本来也不多。

在实施时,我额外做了一步:给冷索引单独设置更低的副本优先级,确保集群发生节点故障时,恢复顺序永远是热数据优先。这一步属于集群调优的经验范畴,具体做法是在 ILM 策略的 cold 阶段通过 set_priority 来控制,让冷索引的优先级尽量低,从而保证集群资源永远是热数据优先。

5.3 缓存参数怎么定

部分挂载模式下,本地缓存的大小直接决定查询体验。缓存给得太小,冷数据查询几乎每次都要回源拉取,首次查询等到起飞;缓存给得太大,又失去了降本的意义,磁盘又会被撑满。

我这边给分片级缓存设置了约 15GB 的配额,同时配合“按查询访问频率自然淘汰”的策略。实际跑下来,日常批量回查历史订单的查询,命中率能稳定在 60%~70% 左右,剩下未命中的部分,走仓库回源,耗时也能接受。如果你的冷查询模式是“经常翻同一段时间”,缓存命中率还会更高,效果更好。

缓存参数的查看方式是:

GET order-log-2025.03.01/_stats/store?level=shards&filter_path=*.store.size

再结合监控里 cold 索引的缓存命中率,调整 cache_size 到一个合理的值。这里建议每次调整只改 20% 左右,观察几天再动,避免一次调得过大导致磁盘水位异常反弹。

5.4 批量迁移的操作经验

在批量处理历史索引时,我的做法是分批推进,每次最多同时处理 10~15 个索引进入 cold 阶段。这个区间是我在测试环境实测过的“安全水位”,超过这个数量,对象存储的写入带宽容易被快照任务打满,直接导致队列里其他快照任务等待超时。

具体节奏是:先挑 2~3 个索引做“先遣队”,跑通全流程;然后每批处理 10 个左右,每批之间间隔 10 分钟,观察集群的磁盘水位、仓库写入 QPS 和冷索引查询延迟,没问题再放大到下一批。整个过程大概花了两天半,全部历史索引稳定进入 cold 阶段,期间业务查询没有出现任何告警。

6. 常见问题与排查技巧实录

6.1 快照仓库连接失败或超时

这个是最常见的第一个拦路虎。现象是创建仓库或者创建快照时报错,提示连不上 bucket 或者认证失败。排查思路很简单,先确认对象存储的读写权限,再确认网络连通性,最后检查 base_path 是否存在。很多时候是自建对象存储的 endpoint 要配成内网地址,我最初配成了公网地址,导致每次执行快照都超时,拖慢了集群节奏。

另一个隐蔽的问题是对同一个 bucket 并发执行太多快照任务,对象存储可能会主动限流。此时报错信息可能不是“认证失败”,而是“RequestTimeTooSkewed”或者“SlowDown”之类的状态码。这时候不要反复重试,先看看是不是有大量索引在同时触发了快照,把所有待执行策略的索引分批处理,限流问题通常就消失了。

6.2 第一次查询冷数据慢到不科学

冷索引首次查询慢,正常的。如果慢到完全不科学,比如 10 秒以上,优先怀疑是本地缓存配额太小,导致每次查询都触发大面积的仓库回源。排查方法是在查询时加上 profiling,看耗时是不是全部堆在 fetch 阶段。

另一种可能性是快照仓库所在的对象存储和 TongSearch 集群不在同一个可用区,或者走的是公网链路。跨网络拉数据,带宽和延迟都不可控。这时最好的做法是把对象存储的 endpoint 改成内网,或者把仓库迁移到同区域的存储桶上,查询性能会立刻好转。

6.3 磁盘水位没有降多少

有同学反馈说执行完冷阶段之后,磁盘还是满的,这里要区分两种情况:第一,ILM 策略可能还没执行到 cold 阶段,你只是建好了策略但没让存量索引 move;第二,部分挂载模式的本地缓存设置过大,实际缓存数据把磁盘又占满了。

排查方式就是看 explain 和历史索引的 store 大小,确认索引都进入了 cold 阶段;再看集群里是不是有大量正在执行的 recovery 过程,分片在节点间搬来搬去,磁盘空间被临时占用得厉害。这种临时占用通常在 recovery 结束后会自动回收,不必太紧张。

6.4 ILM 策略一直卡在某个阶段不往下走

ILM 策略执行不推进,第一步永远是看 explain 输出,看它卡在哪一步。常见的原因是 shrink 阶段分片数不是源分片数的因数,或者 forcemerge 失败导致进入死循环,也可能是集群的节点不是数据节点导致某些 action 无法执行。

如果是 shrink 报错,就重新设置 number_of_shards 为源分片数的因数,然后再尝试。如果是 forcemerge 失败,先确认索引是不是处于只读状态、有没有阻塞写入的操作,把阻塞源解决掉再重试。最后还有一种情况是集群的冷热节点配置没有配好,导致 ILM 想往冷阶段迁,但找不到满足条件的节点,也会卡住,需要检查节点属性是否匹配。

6.5 误删了恢复中的索引怎么办

有一次我在测试过程中误删了一个还在迁移中的索引,说实话当时慌了一下,担心数据丢没丢。后来验证下来,删除一个可搜索快照索引,本质上只是把它在集群里的“挂载实例”拿掉,远端仓库里真正的快照数据并不会丢。恢复方式也很简单,直接用仓库里对应的快照重新挂载一次即可。

这给了我们一个天然的数据保护冗余:就算本地索引被误删,只要仓库里的快照还在,随时能挂回来。所以在实施整个方案之前,务必确认仓库里已经存有有效快照,操作前做一次快照验证,心里就有底了。

6.6 定期做恢复演练

最后提醒一个容易被忽略但很重要的步骤:定期做可搜索快照的恢复演练。不是每次迁移都会出错,但一旦出问题,比如近端仓库上的快照数据因为某些原因缺失了一部分,等到业务方真要查数据的时候才发现,那就晚了。

我的做法是每季度取一个历史索引,从仓库重新挂载一份副本到集群,查询几个典型条件,确认返回数据正常。整个演练成本很低,但能确保我们的快照链路是真正可靠的,而不是纸上谈兵。


这个方案落地到现在快半年了,我从里面学到的最大一课是:数据管理不能只看“存得下”,还要看“存得起、查得到”。以前遇到磁盘告警第一反应是扩容,现在我更愿意先想想哪些数据其实可以放到更便宜的层里去。可搜索快照和 ILM 给我的,不只是一个降本手段,更是一套重新审视数据价值的方法论。如果你现在也在为了磁盘扩容审批焦头烂额,不妨先不急着申请新机器,把这套组合拳在自己的测试环境跑一遍,给数据分层,让每份数据都待在该待的位置上。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进