
SaaS 可观测性平台的按量计费,痛点从来不是单价,是数据量涨上去之后那句"要不要限制保留期"。多数团队的日志和指标只留 15 天,不是不想留,是留不起。
AWS 存储博客上个月发了一个案例,讲病理 AI 公司 Proscia 怎么把自托管可观测性跑起来的。数字值得一看:遥测数据保留从 15 天改成无限期,每月记录量从约 24 亿涨到约 337 亿,存储成本反而从每 TB 500 美元以上降到 5 美元左右。在超过 25 TB 的数据上,部分查询的 p95 延迟刚过 250 毫秒。
这类案例文容易读成"AWS 又秀肌肉"。但把架构拆开看,真正扛住成本的不是哪朵云,是两个通用设计:ClickHouse 用 TTL 把冷数据卸到对象存储,clickhouse-backup 把备份也压到对象存储上。这两个设计不绑定任何云,自托管侧可以原样复刻。这篇就把这两块配一遍,对象存储用 RustFS。
Proscia 搭了什么
Proscia 的数字病理平台要处理千兆像素级医学影像,客户里有前 20 大制药公司中的 16 家,接入的实验室每年处理约 1200 万患者病例。这套可观测性系统跑了 10 多个 K8s 集群、550 多个节点,6 个月从验证走到生产。
架构用一句话概括:应用通过 OpenTelemetry 上报日志、指标和 span,经三层 Collector 汇到 ClickHouse 集群;ClickHouse 用 Altinity 的 Kubernetes Operator 管理,单 shard 三副本跨可用区,配三节点 ClickHouse Keeper 做共识;查询和告警走 Grafana。
跟存储有关的是两条链路:
热冷分层。 数据先落本地 SSD,TTL 规则到期后自动移到 S3。官方给出的整体压缩比是 40:1------这是 ClickHouse 列存加编码的功劳,不是换存储换出来的,后面还会说到这点。
备份。 用 clickhouse-backup 工具把全量和增量备份写到 S3,做灾难恢复。
至于跨账户 IAM、Cognito 联合身份、KMS 这些云专属件,自托管场景各有等价物,不在本文展开。
起一个桶
对象存储这块,自托管用 RustFS 就一行 docker 命令的事,按官方文档:
bash
docker run -d \
--name rustfs \
--restart unless-stopped \
-p 9000:9000 \
-p 9001:9001 \
-v rustfs-data:/data \
-e RUSTFS_ACCESS_KEY="<your-access-key>" \
-e RUSTFS_SECRET_KEY="<your-secret-key>" \
-e RUSTFS_ADDRESS=":9000" \
-e RUSTFS_CONSOLE_ADDRESS=":9001" \
-e RUSTFS_CONSOLE_ENABLE=true \
rustfs/rustfs:latest \
/data
建两个桶,一个放冷数据,一个放备份:
bash
export AWS_ACCESS_KEY_ID=<your-access-key>
export AWS_SECRET_ACCESS_KEY=<your-secret-key>
aws --endpoint-url http://<rustfs-host>:9000 s3 mb s3://clickhouse-cold
aws --endpoint-url http://<rustfs-host>:9000 s3 mb s3://clickhouse-backup
两个桶都不要叠 lifecycle expiration 规则:冷数据桶里的对象由 ClickHouse 自己删(part 过期移除时连远端对象一起删),备份桶由 clickhouse-backup 的保留策略管。外部规则再插一手,等于两套删除逻辑抢方向盘。
数据面:S3 disk 与 TTL
ClickHouse 的存储策略机制允许一块表的数据分散在多块盘上,S3 兼容存储可以直接声明成一块盘。在 config.d 下的配置文件里写:
xml
<storage_configuration>
<disks>
<local>
<path>/var/lib/clickhouse/disks/local/</path>
</local>
<s3_cold>
<type>s3</type>
<endpoint>http://<rustfs-host>:9000/clickhouse-cold/data/</endpoint>
<access_key_id><your-access-key></access_key_id>
<secret_access_key><your-secret-key></secret_access_key>
</s3_cold>
</disks>
<policies>
<hot_cold>
<volumes>
<hot>
<disk>local</disk>
</hot>
<cold>
<disk>s3_cold</disk>
</cold>
</volumes>
<move_factor>0.1</move_factor>
</hot_cold>
</policies>
</storage_configuration>
几个容易理解偏的地方:
endpoint 里要包含桶名和根路径,官方示例和惯例都带尾斜杠;ClickHouse 按 path-style 寻址,这对自托管端点是天然匹配的。
move_factor 是空间触发,不是时间触发。 默认 0.1 的含义是:本地盘剩余空间低于 10% 时,后台才开始把 part 按大小从大到小往下一个 volume 搬。它不负责"数据满 7 天就搬",那是 TTL 的职责,两者是互补关系。示例里显式写 0.1 只是为了把行为写明白。
TTL 在后台 merge 时评估,不是插入时逐行判断,所以 TTL 表达式只能用确定性的列值。表这么建:
sql
CREATE TABLE logs
(
ts DateTime,
service LowCardinality(String),
message String
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(ts)
ORDER BY ts
TTL ts + INTERVAL 7 DAY TO VOLUME 'cold'
SETTINGS storage_policy = 'hot_cold'
已有表用 ALTER TABLE ... MODIFY TTL 改规则,用 ALTER TABLE ... MODIFY SETTING storage_policy 换策略,注意换策略时新策略必须包含原来的磁盘和同名 volume,否则改不动。
还有两个开关值得知道:perform_ttl_move_on_insert 默认会让插入时就已过期的 part 立刻往慢速目标搬,写入高峰期可以禁掉它避免拖慢插入;排查数据落在哪块盘时,看 system.parts 的 disk_name 列,后台搬动在 system.part_log 里对应 MovePart 事件类型。想手动搬,ALTER TABLE ... MOVE PART ... TO VOLUME 'cold' 随时可用。
备份面:clickhouse-backup
工具选型不用纠结,Proscia 用的就是社区里事实标准的 clickhouse-backup,当前版本 v2.8.3。工作原理是对本地 part 做硬链接快照再流式压缩上传,所以它必须跟 clickhouse-server 跑在同一台主机或同一个 Pod 里,只支持 MergeTree 家族的表(22.7 以上配 USE_EMBEDDED_BACKUP_RESTORE=true 可支持更多表类型)------这两条对可观测性场景都不算限制。
/etc/clickhouse-backup/config.yml 核心就三段:
yaml
general:
remote_storage: s3
backups_to_keep_remote: 3
clickhouse:
username: default
password: "<your-password>"
s3:
access_key: "<your-access-key>"
secret_key: "<your-secret-key>"
bucket: clickhouse-backup
endpoint: http://<rustfs-host>:9000
path: clickhouse
region: us-east-1
force_path_style: true
force_path_style 打开后走 path-style 寻址,别让 SDK 拿桶名去拼虚拟主机域名,自托管端点基本都要开。backups_to_keep_remote: 3 让远端自动滚动保留最近 3 份备份,桶里不会无限堆积。压缩格式默认 tar,想要更高的压缩比可以换成 zstd。
日常就三条命令:
bash
clickhouse-backup create_remote # 本地建快照并直传
clickhouse-backup list remote # 看远端有哪些备份
clickhouse-backup restore_remote # 恢复:下载并 ATTACH 回来
不想手点的话,watch 子命令能定时跑增量加全量混合备份,调度方式二选一:固定间隔配 watch_interval 和 full_interval,或者 cron 配 watch_schedules(命令行写法 --schedule=name=...,full=<cron>,increment=<cron>),README 明确标注两种方式互斥,别一起配。
一个安全细节顺带提一句:/var/lib/clickhouse/backup 目录里是硬链接,权限动一下可能连坐正在使用的数据文件,运维清理时别碰这个目录的权限。
和案例的差距在哪
诚实说三件事,免得照抄之后期望错位。
40:1 的压缩比别归功给对象存储。 那是 ClickHouse 列存加编码算法的收益,数据落在本地盘还是对象存储都一样。自托管方案的成本优势来自"压缩后体积小 + 对象存储单价低"的乘积,两半都成立,但没有哪一半是魔法。
案例里的云侧组件要自己找等价物。 跨账户隔离对应网络分段加最小权限的访问密钥,KMS 加密对应自托管端的 SSE 想法(RustFS 支持 SSE-S3、SSE-C 和 SSE-KMS),Athena 直查 Iceberg 导出对应 RustFS 内置的 S3 Tables 能力(官方文档标注目前为预览功能,客户端兼容性限于文档列出的工作流,排期前先核对)。对齐得起来,但每一项都是要动手配的活,不是开箱即得。
那组数字是 Proscia 规模的个案。 每月 337 亿条记录、每 TB 5 美元的月成本,前提是他们有自己的运维投入和数据规模摊薄。自托管省的是按量计费的溢价,付的是运维工时,这笔账每个团队要自己算。
值不值得照抄
案例文末给了三条自建的前提:有能运维 K8s 有状态系统的团队、需要长期甚至无限期保留遥测数据、希望对整个管线有完全控制。原文的判断很干脆:三条都满足,自建才成立;占不全,就走混合方案,一部分留在托管服务上,自建只做真正需要控制的那一段。
我的看法是,这套架构里对象存储是最薄的一层:热冷分层和备份两件事,ClickHouse 和 clickhouse-backup 把机制都做好了,对象存储只需要老老实实做一个 S3 兼容的持久层。RustFS 单二进制部署加 S3 全兼容,把这一层补上的成本很低,剩下的决定权在你们团队手里。