参考架构:把事件流做成可查询、可治理、可负担的系统
架构没有标准答案,只有对写入、查询、实时性、隔离和成本的明确取舍;三套栈,对应三种规模与组织能力。
架构讨论很容易滑向引擎选型:ClickHouse 还是 Doris,Kafka 还是 Pulsar。但真正决定一套埋点分析平台能否活过三年的,是五个更早的约定:一条事件在哪一层变成不可变事实;谁有权在采集端拒绝它;一次查询要扫多少字节;哪个租户的故障可以影响别人;合同结束时数据能不能带走。这五个问题没有答案,换什么引擎都只是在给一条没有责任链的系统加速。
本章给出一套可以拿去评审的参考架构:八层责任链、五张核心表、五种部署模式、四档实时性、一个成本公式,以及三套按规模分档的栈。它们不是某家厂商的最佳实践,而是从公开文档、定价页和故障边界里反推出来的最小共识。
10.1 分层架构:先定义不可变的那一层#
一条事件从 SDK 到看板要经过八道手。评审时第一个问题不该是“用哪个引擎”,而是“这一层坏了,上一层的什么能力会消失”。
| 层 | 职责 | 典型实现 | 先坏在哪里 | 要观测什么 |
|---|---|---|---|---|
| 采集 | 鉴权、限流、schema 校验、脱敏、批量、背压 | Snowplow Collector、PostHog capture、自建 Go 服务、API Gateway | 流量尖峰打满连接;校验失败静默丢事件 | 4xx/5xx、p95 延迟、拒收率、批量大小 |
| 队列 | 缓冲、解耦、重放、顺序 | Kafka、Kinesis、Pulsar、S3 加队列 | 分区倾斜;保留期太短无法回放 | consumer lag、分区吞吐、存储增长 |
| 流处理 | enrichment、身份、会话、去重、迟到数据 | Flink、Kafka Streams、ClickHouse Kafka Engine | 状态膨胀;checkpoint 超时;sink 重复 | checkpoint 时长与失败率、状态大小、反压 |
| 存储 | 事实、维度、会话、聚合、TTL | ClickHouse、Doris、StarRocks、Iceberg、BigQuery | part 过多;合并跟不上;更新口径错 | part 数、合并队列、扫描字节、磁盘 |
| 语义 | 事件命名、属性类型、指标口径、版本 | 自建 metadata、dbt、LookML、Cube | 同名不同义;版本漂移 | 口径变更记录、覆盖率、验收通过率 |
| 查询 | 预聚合、缓存、队列、隔离 | 物化视图、Projection、Query Cache、资源组 | 大查询拖垮集群;缓存返回旧数 | p95/p99、排队时长、内存峰值、扫描量 |
| 应用 | 看板、漏斗、留存、路径、告警 | 自建、Superset、Metabase、厂商 UI | 口径不可解释;下钻断链 | 查询采用率、错误率、下钻成功率 |
| 治理 | 血缘、质量、审计、保留、删除 | OpenMetadata、自建、厂商控制台 | 删除不彻底;审计缺口 | 质量分、DSAR 时长、保留合规 |
这张表最容易被读成技术清单,其实它划的是四个故障域。采集与队列负责“不丢”,流处理负责“可重放的状态”,存储负责“不可变事实”,语义与查询负责“口径和速度”。分层的分界线不是技术栈,是事件是否可变。采集层只做鉴权、校验、限流、落盘;流处理只做可重放的状态计算;存储层保留不可变事实;语义层承担口径。把 enrichment 放进采集,collector 就有状态,一个地理库变慢会拖垮入口。把口径写进 SQL,换引擎时就要重写所有看板。
判断:分层的目的不是把图画整齐,而是让每一层可以单独替换、单独回滚、单独计量。做不到这三件事的“分层”,只是把单体拆成了更多部署单元。
10.2 采集与接入:只做认人、验形、落盘#
采集网关的职责应该被压到最小:认人是 write key、token、域名白名单;验形是 schema 版本、必填字段、属性类型、大小上限;落盘是批量写队列,成功返回 202,失败返回可重试的 429 或 503。
返回码就是合同。202 表示“已接收”,不等于“已入库”;客户端重试必须带同一个 event_id,否则重复无法收敛。很多团队在 collector 里重新生成 ID,重试一次就多一条。背压也必须向前传:队列满时正确做法是拒绝并给 Retry-After,而不是把内存吃满。Kafka producer 的 buffer 满了会阻塞发送,阻塞会沿着 collector 的线程池一路放大,最后表现为“所有租户一起超时”。
采集端不应该做 enrichment。地理库、UA 解析、身份拼接、风控规则都会让它从无状态变成有状态。Snowplow 把链路拆成 Collect、Validate、Enrich 三段,坏事件进 bad stream,而不是让 collector 做业务判断,这个拆分值得抄Snowplow 管道文档。边缘采集能降低首跳延迟,但日志、采样、PII 脱敏、版本发布仍要单独治理;对 100 亿/月以上的量,它的主要价值是吸收尖峰和就近终结 TLS。
同意状态必须成为事件模型的一等字段。EDPB 在 2024-10-07 通过的最终指引把 ePrivacy 第 5(3) 条的“终端设备存储与访问”扩展到 localStorage、像素、指纹和 SDK 标识CNIL 对指引的说明。CNIL 给出的免同意窄门是:唯一目的、匿名统计、不跨站、不转第三方;它建议 cookie 不超过 13 个月、数据不超过 25 个月CNIL 受众测量豁免。多数产品分析 SDK 默认写第一方 cookie 并生成跨会话 ID,落在同意区。Google 的 Consent Mode 在拒绝期间发送不带 cookie 的 ping,它是信号传递,不是同意管理,仍要配 CMPConsent Mode 文档。
PII 也很少从“姓名字段”正门进来。URL query 里的邮箱与 token、autocapture 抓到的 DOM 文本、会话回放的输入框,都是入口。哈希不是匿名化,无盐 SHA-256 的邮箱可以被彩虹表反查;IP 截断到 /24 或 /48 仍可用于城市级定位,仍属去标识化。GA4 对欧盟、瑞士、英国用户不记录 IP,而是在 EU 域与服务器写入前丢弃GA4 IP 处理;Heap 提供 IP 与地理位置采集的关闭开关Heap 安全页。
采集层的成熟度,看它敢拒绝多少流量,而不是能接住多少流量。一个只会返回 200、只会加机器、不会区分坏事件与坏客户的入口,会把所有治理成本推给下游。
10.3 流处理:状态是成本,不是吞吐#
队列的第一个决定是分区键。Kafka 只保证分区内有序、分区之间并行,所以顺序只到 key。会话切分要按 user_id 分区;按 event_id 分区会打散顺序,按租户分区会让大租户变成热分区。
保留期是回放窗口,不是归档。Kafka 的 retention 到期就删;要长期保留,要么 tiered storage,要么落 S3 或 Iceberg。KIP-405 已合并,并从 Kafka 3.9 起标记为 production-ready,但同一份 KIP 明确不支持 compacted topic 与 tiered storage 组合,也不支持 JBODKIP-405。
Flink 的容错靠 checkpoint:barrier 注入 source,算子对齐后快照状态,失败时回到最近一次完成的 checkpoint 并重放 sourceFlink 状态文档。这套机制有三个直接成本项。状态大小:身份图谱、会话窗口、去重集合都在 state 里,RocksDB 放磁盘不等于免费。Checkpoint 间隔:间隔短恢复快、重复少,但 I/O 与 CPU 开销高;官方明确默认关闭,需要显式开启。对齐:多输入算子在 barrier 处要对齐,unaligned checkpoint 把 in-flight 数据也写进状态,用 I/O 换延迟。
流里值得做的事,按投入产出排序只有四件。第一,schema 校验与坏事件分流,别让一条脏数据阻塞整个分区。第二,enrichment,只做“查询时必须用到”的字段,能批处理做的不要放流里。第三,会话切分,按 user_id keyBy,事件时间加 gap 窗口,迟到超过 allowed lateness 的进 side output。第四,去重,用 event_id 加状态 TTL,且 TTL 必须大于“最大可能迟到加重放窗口”,否则重放时会把已去重的事件再放过去。
身份解析是最容易低估的。匿名 ID 到登录 ID 的合并本质是图问题。流里做实时合并只能处理简单情况;复杂合并要离线图计算加版本化映射表,流里只查表。把图算法塞进 Flink 状态,结果通常是状态越来越大、checkpoint 越来越慢、合并结果还不一致。
Exactly-once 是端到端合同,不是组件开关。Flink 官方表格显示,Elasticsearch、OpenSearch、DynamoDB、Kinesis 等 sink 仍是 at-least-once,只有 Kafka sink 在事务生产者下才是 exactly-onceFlink sink 保证。Kafka 的 KIP-98 则是“生产者视角”:幂等生产者保证单会话内不重复,事务保证跨分区原子写,但消费者侧不能保证一个已提交事务的所有消息被一起消费KIP-98。工程上要的是幂等键、可重放、对账,而不是一个开关。
流处理的钱主要花在状态上,不是花在吞吐上。状态越大,checkpoint 越慢,恢复越久,回滚越不敢做;最后不是系统撑不住,是团队不敢发版。
10.4 存储建模:事实不可变,派生可重建#
UBA 的存储建模可以压缩成一句话:事实表不可变,派生表可重建,聚合表可丢弃。
Snowplow 的 atomic.events 是一个好参照:一行一个事件,宽表,标准字段加自描述事件与实体列;官方明确“不要修改 atomic 表”,会变化的属性应该在分析时派生、单独存表、再 joinatomic 表文档。这条规则让 schema 变更、口径修正、引擎迁移时还能重算。
| 表 | 粒度 | 关键列 | 引擎与建模 | 保留 |
|---|---|---|---|---|
event_fact |
一条事件 | tenant_id, event_id, event_name, event_time, ingest_time, user_id, anonymous_id, session_id, schema_version, consent_state, properties |
MergeTree,按月分区,ORDER BY (tenant_id, event_name, user_id, event_time) |
12–24 个月热,之后归档 |
user_dim |
一个用户 | tenant_id, user_id, traits, version, updated_at |
ReplacingMergeTree(version) 或主键表 | 全量 |
identity_map |
一次绑定 | tenant_id, anonymous_id, user_id, valid_from, valid_to, source |
追加加拉链(SCD2) | 全量 |
session_fact |
一个会话 | tenant_id, session_id, user_id, start, end, duration, entry, exit, pageviews, is_bounce |
流式产出加离线修正 | 12 个月 |
agg_daily_event |
租户 × 事件 × 天 | tenant_id, event_date, event_name, users, events, sessions |
AggregatingMergeTree 或物化视图 | 按需 |
分区键用时间,不要用租户。按租户分区会让小租户产生大量小 part,大租户产生超大 part;按时间分区才能做 TTL 和冷热分层。排序键把租户放前面,ORDER BY (tenant_id, event_name, user_id, event_time) 让同一租户的查询能裁剪 granule。高基数属性不要进排序键,它会把稀疏索引撑大,写入和合并都变慢;用跳数索引、Projection 或预聚合。ClickHouse 的主键索引是稀疏索引,按 granule 定位,不是逐行索引,列顺序直接决定裁剪效率稀疏索引文档。
TTL 不是即时删除。ClickHouse 的 TTL 只在合并时触发,merge_with_ttl_timeout 默认 14400 秒,也就是 4 小时TTL 文档。GDPR 或 DSAR 的“立即删除”不能只靠 TTL,需要 lightweight delete 或分区重写,并保留删除审计。一次删除请求至少要覆盖原始事件、用户属性、身份绑定表、人群与人群快照、会话回放片段、导出文件、错误日志、备份,以及已经进入模型训练的数据;备份删除通常按备份周期滚动,合同应写“最长一个备份周期内删除”,而不是“合理努力”PIPL 全文。
去重的现实更粗糙。ReplacingMergeTree 只在合并时去重;官方明确“不保证没有重复”,要正确结果必须用 FINAL 或查询时 argMaxReplacingMergeTree 文档。FINAL 在十亿级明细上会显著放大 CPU 和内存,把它用在所有看板,等于把成本乘 2–5 倍(经验估算,需按查询实测)。更稳的做法是写入端幂等:event_id 作为幂等键,存储只做最终收敛,关键指标用 uniqExact(event_id) 或预聚合时去重。PostHog 也用 ReplacingMergeTree,但官方文档直接写明“应避免写入重复数据,去重不是保证”PostHog ClickHouse 架构。
判断:明细表是资产,聚合表是缓存。把聚合当真相,改口径时就没有退路;把 TTL 当删除,是把合规问题误认为数据库配置。
10.5 查询与预聚合:只覆盖承认重要的那几十个问题#
预聚合不是“建个物化视图就快了”,它有三种代价:新鲜度、存储、刷新成本。
ClickHouse 的普通物化视图是插入触发器:只在插入时转换“本次插入的块”,GROUP BY 只在这个块内聚合,跨块聚合要靠 AggregatingMergeTree 在合并时继续CREATE VIEW 文档。这意味着明细迟到、补数、修数不会自动回到 MV,需要重放或重建;Refreshable MV 靠定时刷新,APPEND INCREMENTAL 只支持单张普通 MergeTree 源表,且拒绝 JOIN、UNION、子查询、视图和表函数。
Doris 的物化视图分同步和异步:同步 MV 写入时增量维护,异步 MV 由刷新任务 INSERT OVERWRITE,明确最终一致;窗口函数、UNION ALL、LIMIT、ORDER BY、CROSS JOIN 都不能透明改写Doris MV 概览异步 MV 文档。StarRocks 的异步 MV 适合 SPJG 形态的查询加速;查询队列默认关闭,需要显式开启。
预聚合的正确姿势是四条。只覆盖 Top 20–50 个高频、稳定、可解释的查询。明细表永远保留,MV 可重建,schema 变更要有回滚脚本。对“必须实时”的指标用流式聚合,对“必须准确”的指标用明细重算,不要让同一个 MV 同时满足两者。监控 MV 的刷新延迟、失败率、存储放大;MV 数量超过 50 个时,刷新本身会变成负载。
查询缓存和预聚合不是一回事。它缓存中间结果,不改变数据模型。StarRocks 的 query cache 默认关闭,只对聚合查询生效,不支持异步 MV、外部表和高基数 GROUP BY;每个 BE 独立缓存,默认 512 MB,LRU 淘汰,表截断或结构变更会失效查询缓存文档。缓存适合高并发、低基数看板;明细下钻命中率低时反而增加开销。多租户下缓存 key 必须带租户和权限。
漏斗、留存、路径的 SQL 模式,决定查询层能不能复用。漏斗按 user_id 分组、按 event_time 排序,用 windowFunnel 或自连接判断步骤是否在窗口内按序发生。留存先算每个用户的 first_seen,再按 day N 与活动表 join,关键是新老用户口径和时区。路径在 session 内按时间排序,用 lead/lag 或数组展开生成前后节点。ClickHouse 提供 windowFunnel、retention、sequenceMatch 等参数聚合函数参数聚合函数,但它们的语义与产品口径未必一致:windowFunnel 默认要求事件时间递增、在同一窗口内匹配;retention 返回的是按条件的布尔数组,不是现成的留存率。把这些函数直接当产品指标,常在没有数据时返回 0,或把乱序事件算错。工程上要在 SQL 外面包一层口径定义,并用一组已知答案的测试用例验收。
预聚合覆盖哪些查询,等于公开承认哪些问题重要。Top 20–50 之外的长尾,应该用明细、采样和查询配额解决,而不是再建一个 MV。
10.6 五种架构模式:数据在哪,责任就在哪#
把市场上的做法归成五类,比按厂商列功能更能指导决策。五种模式的差别不只是部署位置,还有实时性、隔离责任、单位成本和退出能力。
| 模式 | 数据在哪里 | 实时性 | 多租户 | 单位成本 | 退出能力 | 适合 |
|---|---|---|---|---|---|---|
| SaaS 多租户 | 厂商云 | 秒到分钟 | 厂商负责,租户共享 | 按事件、MTU、席位 | 取决于导出 API | 快速上线、无强合规 |
| 私有化 | 客户机房、专有云 | 秒到小时 | 客户自己 | 许可加硬件加人力 | 最强,数据在本地 | 金融、政务、军工 |
| warehouse-native | 客户数仓 | 分钟到小时 | 数仓权限 | 存储便宜、查询按扫描 | 强,原始表可带走 | 已有数仓、SQL 能力强 |
| composable | 客户自建管道 | 秒到小时 | 自建 | 组件加人力 | 最强,组件可换 | 平台团队、超大规模 |
| lakehouse | 对象存储加表格式 | 分钟到小时 | catalog 与引擎 | 存储最便宜、计算弹性 | 强,开放格式 | 归档、跨引擎、AI |
SaaS 的架构价值不在功能,而在把采集、存储、升级、合规、值班打包。PostHog 是一个可读样本:事件不直接写 ClickHouse,而是从 Kafka 拉取,经 kafka_events 表引擎与物化视图进入 sharded_events(ReplicatedReplacingMergeTree);person 表在每个节点复制一份以避免跨网络 JOINPostHog ClickHouse 架构。取舍是:Kafka 表引擎解耦摄入与写入,ReplacingMergeTree 接受最终去重,复制换来查询简化但放大内存成本。
私有化的难点不是装一个 ClickHouse,而是升级、备份、监控、故障恢复和许可证。Snowplow Community Edition 是一个典型:官方给出的规模约 100 事件/秒,AWS 约 200 美元/月、GCP 约 240 美元/月Snowplow 自建文档。但同一页明确:Community Edition 受 SLULA 约束,不得用于生产。Snowplow 从 2024-01-08 起把核心管道的新版本改用 SLULA,2024-12 的 v1.1 进一步把生产使用划到商业授权之外许可公告许可 FAQ。这就是“开源自建”的陷阱:基础设施看起来便宜,许可证把生产使用挡住了。
私有化也不自动解决合规。远程运维、遥测、离线补丁、云端模型都可能把数据带出去;合同应写明“默认无外网回传,任何回传需书面批准并可审计”。在中国,私有化还要面对等保 2.0、密评与信创清单,“支持信创”要拆成可测试条目:在目标环境跑通采集、查询、导出、升级GrowingIO 私有化页。跨境则由人数门槛决定路径:非关键信息基础设施运营者累计出境不满 10 万人免申报,10 万到 100 万人走标准合同或认证,100 万人以上或 1 万人以上敏感个人信息走安全评估网信办跨境规定。
warehouse-native 把成本从存储转到查询纪律。GA4 的 BigQuery Export 是最大众化的入口:标准属性每天最多导出 100 万事件,360 属性每天最多 200 亿;流式导出是 best-effort、没有完整性 SLOGA4 BigQuery Export。它的成本结构不同于 SaaS:存储便宜,查询按扫描计费,因此“一个看板刷一次等于扫 30 天全表”是致命模式。warehouse-native 不是更便宜,而是把固定成本变成可变成本;它奖励分区裁剪、聚簇、预聚合和查询治理,惩罚 SELECT * 加大时间范围加高并发。自建管道的钱花在组件、值班和扩容,warehouse-native 的钱花在扫描和查询治理;前者是固定成本,后者是可变成本。
composable 是 Kafka 加 Flink 加 ClickHouse 或 Doris 加自建应用加语义层。优势是每一层都能替换,劣势是集成成本归你:schema registry、dead-letter、重放工具、租户配额、备份恢复、升级窗口、on-call,缺一项都会在第 6–12 个月变成瓶颈。AWS 的 Clickstream Analytics 参考实现给出一条可部署路径:ALB、ECS、MSK、Kinesis、S3、EMR Serverless、Redshift、QuickSight,官方称无 license 费用,只付 AWS 服务用量Clickstream on AWS。
lakehouse 的价值是一份数据、多引擎读、可回溯,不是低延迟看板。把 Iceberg 当 serving 层,查询延迟和并发通常撑不住。正确用法是原始事件落 S3 或 Iceberg 作为唯一真相和重算源,ClickHouse、Doris、StarRocks 做 serving 并按需回灌,归档、审计、AI 训练读 Iceberg。lakehouse 解决复用,不解决低延迟。
SaaS 与私有化的分界是:无强合规、想快速上线,选 SaaS;数据必须留在场内、且能承担升级与值班,选私有化。
多租户不是加 tenant_id,而是决定故障域;四种做法的隔离度与成本一起上升。
| 层级 | 做法 | 爆炸半径 | 适用 |
|---|---|---|---|
| L0 | 共享表加 tenant_id 加行策略 |
一个大查询拖垮所有租户 | 长尾小客户 |
| L1 | 共享集群加资源组、工作负载组 | 同机其他租户受影响 | 大多数 SaaS |
| L2 | 计算组、独立仓库、独立 ClickHouse 服务 | 限于该计算组 | 大客户、合规客户 |
| L3 | 独立集群、独立账号、独立区域 | 仅该租户 | 金融、政务、跨国 |
判断:SaaS 与私有化的差别,不是“数据在哪”,而是“谁负责升级、备份、删除和值班”。把私有化当合规证书,会把四项责任一起买回来,却不一定买到相应的团队。
10.7 实时 vs 批:先写新鲜度 SLO,再决定付几倍#
实时不是二选一,而是四档。先把“实时”拆成可验收的延迟,再谈架构。
| 档位 | 端到端延迟 | 典型实现 | 成本倍数(估算) | 值得的场景 |
|---|---|---|---|---|
| 真实时 | 小于 1 秒 | Flink 或 KSQL 加内存或实时 OLAP | 5–10 倍 | 风控、反欺诈、实时出价 |
| 准实时 | 1–60 秒 | Kafka 加 Flink 或 ClickHouse Kafka Engine | 3–5 倍 | 告警、实验曝光、实时个性化 |
| 微批 | 1–15 分钟 | 调度器加微批写入 | 1.5–3 倍 | 运营看板、会话近实时 |
| 批 | 1–24 小时 | dbt、Spark、数仓调度 | 1 倍 | 经营报表、留存、财务对账 |
成本倍数是经验估算,包含计算、存储、监控和人力,不是官方数字;真实倍数要用自己的查询组合压测。
值得实时的判据只有四个。决策窗口小于 1 分钟,晚 10 分钟结论就失效;动作能自动执行,实时告警要能触发工单、封禁、推送,如果最后还是人看报表,实时没有意义;数据量可控,实时链路的状态和反压成本随 key 基数上升,高基数用户画像不适合放流里;可接受近似,实时看板通常允许 1–5% 误差,财务口径要求精确就走批。
反例是把“昨天出了多少单”做成秒级。它会让 Flink 状态、Kafka 分区、ClickHouse 合并都为一个没人看的数字付钱。另一个误判是“实时等于更快”:它牺牲修正能力,迟到、重复、口径变更都更难回补;流处理重跑要停流、回放、去重。
Lambda 与 Kappa 的旧争论,在 UBA 里可以这样落地。Lambda 把批层和速度层并列,同一指标有两套代码,优点是批层能修正、速度层能低延迟,缺点是两套代码、两个真相。Kappa 只保留流层,靠日志重放修正,优点是只有一套代码,缺点是重放窗口受 retention 限制、状态重建贵。今天更常见的是“流式摄入加批式重算”:原始事件进对象存储或湖,流负责分钟级,批负责小时级、天级的修正,把“修正”放在湖上。选型时问三个问题:修正窗口多长、重放成本多少、两套代码能不能维护。
实时是溢价,不是默认值。80% 的“实时”需求,实际是“准实时加可接受的新鲜度”;把新鲜度写成 5 分钟的 SLO,比笼统说“实时”更能省钱。
10.8 成本模型:账单的五个乘数#
月度基础设施成本可以写成:
C = 采集与传输(事件数 × 单条字节 × $/GB)
+ 存储(原始字节 × 压缩比⁻¹ × 留存月数 × $/TB/月)
+ 计算(查询与流处理资源 × 小时 × $/unit-hour)
+ 出口(跨区与公网 GB × $/GB)
+ 运维(人力、值班、工具)
换成“每千事件成本”更好比较:单位成本 = C ÷ (月事件数 ÷ 1000)。下表单价为 2026-10-18 抓取;这不是厂商报价,而是自建或云资源的成本;不含产品研发、支持、合规认证,也不含迁移期的一次性投入。
| 项目 | 单价 | 计价单位 | 来源 |
|---|---|---|---|
| ClickHouse Cloud 存储 | 25.30 美元/TB/月 | 存储 | ClickHouse 定价 |
| ClickHouse Cloud 计算 Scale | 0.2985 美元/unit/小时 | 计算 | 同上 |
| ClickHouse 公网出口 | 0.1152 美元/GB 起 | 出口 | 同上 |
| S3 Standard | 0.023 美元/GB/月 | 存储 | S3 定价 |
| S3 Glacier Instant Retrieval | 0.004 美元/GB/月 | 存储 | 同上 |
| BigQuery on-demand | 6.25 美元/TiB 扫描 | 查询 | BigQuery 定价 |
| BigQuery 活跃逻辑存储 | 约 23.55 美元/TiB/月 | 存储 | 同上,按 GiB-hour 换算 |
| BigQuery slot(Enterprise) | 0.06 美元/槽小时 | 计算 | 同上 |
| Snowflake credit(Standard/Enterprise/BC) | 2/3/4 美元 | 计算 | Snowflake 定价 |
ClickHouse 的 unit 是计费单位,官方没有给出它与 vCPU、内存的换算,不要直接拿它做容量规划。BigQuery 的存储单价按 GiB-hour 报价,上表做了换算,实际账单以官方为准。
按单条事件 1 KB、压缩 4 倍、留存 12 个月、ClickHouse Cloud Scale 估算。
1 亿事件/月约 330 万/天、38/秒均值、峰值 5 倍约 190/秒。原始 100 GB/月,压缩后 25 GB/月,12 个月 0.3 TB;存储约 7.6 美元/月,计算 2 units 乘 0.2985 乘 730 约 436 美元/月,合计约 444 美元/月。
10 亿事件/月约 3,300 万/天、386/秒均值、峰值约 1,930/秒。原始 1 TB/月,压缩后 250 GB/月,12 个月 3 TB;存储约 76 美元/月,计算 8 units 约 1,743 美元/月,出口 1 TB 约 115 美元/月;ClickHouse 小计约 1,934 美元/月,加 Kafka、Flink、应用与 Redis,合计约 2,500–4,000 美元/月,每千事件约 0.0025–0.004 美元。
100 亿事件/月约 3.3 亿/天、3,858/秒均值、峰值约 19,300/秒。原始 10 TB/月,压缩后 2.5 TB/月,12 个月 30 TB;存储约 759 美元/月,计算 48 units 约 10,459 美元/月,出口 3 TB 约 346 美元/月;ClickHouse 小计约 11,564 美元/月,加 Kafka、Flink、应用、对象存储,合计约 16,000–22,000 美元/月,每千事件约 0.0016–0.0022 美元。
三个算例的共同结论是:存储占比不到 7%,计算占大头。所以“冷数据放 S3 能省很多”通常不成立:S3 Standard 每 TB 23 美元,ClickHouse 每 TB 25.30 美元,几乎同价;只有 Glacier Instant Retrieval 的每 TB 4 美元才有明显差异。真正省钱的是减少扫描、减少副本、减少常驻计算。
同样 10 亿事件/月换成 warehouse-native:3 TB 存储按 BigQuery 活跃逻辑存储约 70 美元/月;一次 30 天全表扫描约 1 TB,按 6.25 美元/TiB 计约 6.25 美元;每天 1,000 次全表查询约 187,500 美元/月,即使分区裁剪把扫描降到 5%,仍约 9,375 美元/月。100 个 Enterprise slot 约 4,380 美元/月,能否覆盖取决于查询复杂度,必须回放实测。Snowflake 按 credit 计费,同样 workload 要实测 credit 消耗,不能拿存储单价直接比较。
降本清单按 ROI 排序,前四条通常能拿回大部分收益。
- 按时间分区加分区裁剪,看板默认 7 天或 30 天,查询必须带时间谓词。
- 只保留必要列,长尾属性进 JSON 或 Map,别把 200 个属性打平成宽表。
- 预聚合 Top 查询,把 30 天漏斗、留存做成日聚合,查询从扫 10 亿行变成扫 30 行。
- 采样,路径、探索、异常检测用 1–10% 采样,关键指标不采样。
- TTL 加冷热分层,明细 90 天、聚合 24 个月、原始归档 S3 或 Glacier。
- 压缩编码,低基数用 LowCardinality,时间戳用 Delta,价格用 ZSTD。
- 控制 part 数,批量写入每批 1–10 万行,避免小批高频。
- 查询网关限流,每租户并发、扫描字节上限、超时,大查询进队列。
- 结果缓存,同一看板 5 分钟内复用,缓存 key 带租户和权限。
- 同区部署,计算和存储同区,跨区看板用只读副本。
隐藏成本也要进预算:一次 schema 变更可能重算 12 个月;迁移期双写让成本变成 1.5–2 倍,持续 3–6 个月;日志、指标、trace 的存储可能达到事件存储的 10–20%;2–3 名平台工程师的年成本,通常超过 10 亿事件/月的基础设施;出口按 GB 计费。
判断:存储便宜是幻觉,重复扫描才是账单。把“每次查询扫多少字节”做成和“每天摄入多少事件”同级的指标,成本治理才算开始。
10.9 可观测性与 SLO:先能对账,再谈可用#
UBA 的 SLO 不只是“服务活着”,还包括“数字对得上”。八个指标应该出现在同一张看板上。
| 类别 | 指标 | 定义 | 建议目标(需按业务校准) |
|---|---|---|---|
| 可用性 | 采集成功率 | 2xx 除以总请求 | ≥ 99.9% |
| 延迟 | 采集 p95 | collector 处理时间 | < 500 ms |
| 延迟 | 端到端新鲜度 | now 减 max(event_time) | 实时 < 1 分钟;批 < 2 小时 |
| 正确性 | 重复率 | 重复 event_id 除以总事件 | < 0.1% |
| 正确性 | schema 违规率 | 坏事件除以总事件 | < 0.5% |
| 查询 | 查询 p95 | 查询网关到结果 | < 3 秒(30 天看板) |
| 成本 | 每千事件成本 | 日成本除以日事件乘 1000 | 按预算设阈值 |
| 治理 | DSAR 完成时长 | 从请求到删除证明 | 按法定期限,GDPR 一般 1 个月,CCPA 45 天 |
对账要做三层。计数对账:源端发送数、队列写入数、存储行数,按小时或天、按事件名、按租户,差异超过 0.1% 告警。去重对账:count(*) 与 uniqExact(event_id) 对比,差异突然变大说明重试或重放异常。指标对账:新旧平台的 DAU、漏斗转化率、留存率,按同一时间窗、同一人群、同一口径对比,差异要能解释到“口径”,而不是“大概”。对账任务要幂等、可重跑、有基线,结果进同一张质量看板。
告警要分级,也要带影响面。P0 是采集成功率低于 95% 持续 5 分钟、新鲜度超过 30 分钟、查询错误率超过 5%;P1 是重复率超过 1%、某租户事件量环比下降超过 50%、Kafka lag 超过 10 分钟;P2 是 MV 刷新、part 数、存储增长异常;P3 是成本与慢查询变化。没有“哪些租户、哪些看板、哪些事件”的告警,最后都会被静音。
没有对账的管道等于没有管道。一个只能证明“服务在跑”、不能证明“数字对得上”的平台,在第一次口径争议时就会失去信任。
10.10 迁移:搬的是语义,不是文件#
迁移不是把数据搬过去,而是把语义、身份、口径、权限一起搬过去。七个阶段,每个阶段都有退出标准。
| 阶段 | 做什么 | 退出标准 | 产物 |
|---|---|---|---|
| 0 盘点 | 事件、属性、量级、留存、下游、身份、PII | 清单完整,业务 owner 确认 | 事件字典、数据流图 |
| 1 映射 | 旧事件到新事件、类型、时区、空值 | 映射表评审通过 | mapping sheet、schema |
| 2 双写 | SDK 或服务端 fan-out,同一 event_id | 双写 7 天,差异低于 0.5% | 双写开关、采样对比 |
| 3 回填 | 历史导出、转换、分区写入 | 回填完成,行数对账 | 回填脚本、批次记录 |
| 4 对账 | 计数、去重、指标三层 | 关键指标差异低于 1%,可解释 | 对账报告 |
| 5 切读 | 看板与 API 切到新平台 | 切读 14 天,无 P0 | 切读清单、回滚开关 |
| 6 停旧 | 停止写入、只读保留、导出归档 | 合同保留期结束 | 删除证明 |
阶段 0 最容易被跳过,也最贵。事件字典不只是事件名列表,还要有谁负责、什么时候触发、属性类型、是否含 PII、下游谁在用、保留多久。没有这些,阶段 1 的映射会变成猜谜,阶段 4 的对账会变成吵架。
平台会消失,这不是假设。Universal Analytics 在 2023-07-01 停止处理命中,2024-07-01 删除数据;360 属性有一次延期到 2024-07-01,BigQuery 历史回填请求截止 2024-06-30GA4 替代说明。Contentsquare 在 2023-12-07 完成对 Heap 的收购收购公告。Amplitude 在 2025-07-10 收购 KraftfulAmplitude 公告。并购期的风险不是产品立刻消失,而是 roadmap、合同主体、数据区域、支持团队一起变。
双写有三个容易忽略的细节。第一,同一 event_id:旧管道和新管道写同一个 ID,才能做去重对账,不要让两边各自生成 UUID。第二,影子读:新平台先只读,跑 2–4 周历史查询对比,用同一批看板 SQL 比较结果和耗时。第三,分层对账:总行数、事件名、用户与会话、指标,越往下越难,但越能定位问题。
回滚不是“再切回去”这么简单,要满足三个条件。旧管道还在写,或至少还能写,否则切回去只能恢复旧数据,恢复不了切换期间的新数据。新数据能反向导出,切换期间写进新平台的事件要能按时间窗导出并灌回旧平台,否则回滚会丢一段。看板有双版本,同一张看板在两边都有定义,切读时只改路由,不改语义。回滚演练要在切读前做。
| 类别 | 检查项 | 验收证据 |
|---|---|---|
| 盘点 | 事件字典含 owner、触发时机、属性类型、PII 标记;身份映射可导出 | 评审记录与导出样例 |
| 映射 | 时区、空值、枚举、金额精度有明确规则 | mapping sheet |
| 双写 | 两条管道使用同一 event_id | 抽样比对报告 |
| 回填 | 历史数据按天、按事件名对账,字节数一致 | 批次记录 |
| 对账 | 计数、去重、指标三层差异低于 1% 且可解释 | 对账报告 |
| 切读 | 回滚开关能在 1 小时内切回 | 演练记录 |
| 合同 | 原始事件、身份映射、看板定义可全量导出;删除有 SLA 与证明 | 导出与删除演练 |
采购时就要把退出条款写清楚:原始事件能否全量导出,格式、频率、是否含 schema;导出是否收费、是否限速;删除是否有证明、多少天内完成、备份是否包含;身份映射能否导出,这是最容易被锁死的资产;看板、漏斗、人群定义能否导出为 SQL 或 JSON;停服或被收购时的过渡期与支持义务。
判断:迁移的难点是历史语义,不是数据搬运。不能导出原始事件和身份映射的平台,迁移成本会高于重新采集;把退出能力写进合同,比压价更能降低长期 TCO。
10.11 三套参考架构:三种规模,三种组织#
三套方案都假设:事件 1 KB、压缩 4 倍、留存 12 个月、按 ClickHouse Cloud 公开计价;实际要按自己的事件大小、查询模式、合规要求重算。成本是基础设施估算,不含人力。
| 档位 | 事件量 | 均值与峰值 | 核心组件 | 月成本(估算) | 团队 | 关键风险 |
|---|---|---|---|---|---|---|
| 轻量 | 少于 1 亿/月 | 38/秒、190/秒 | 单节点 ClickHouse 或 SaaS | 400–700 美元 | 1–2 人兼职 | 单点故障、备份 |
| 标准 | 10 亿/月 | 386/秒、1,930/秒 | Kafka 加 Flink 加 ClickHouse 6 节点 | 2,500–4,000 美元 | 2–3 人 | 热分区、合并、状态 |
| 超大 | 100 亿/月 | 3,858/秒、19,300/秒 | Kafka 分层加 Flink 加多套 ClickHouse 加 Iceberg | 16,000–22,000 美元 | 6–10 人 | 元数据、恢复、成本 |
轻量:少于 1 亿事件/月#
假设 330 万/天、38/秒均值、峰值 190/秒;少于 50 个租户;12 个月留存;1–2 名兼职运维。
首选 SaaS,除非数据主权不允许。PostHog 公开的云区域是美国弗吉尼亚和欧盟法兰克福,选之前先确认数据驻留要求PostHog 定价。自建最小栈是单节点 ClickHouse(4 vCPU、16 GB、500 GB SSD)加 S3 备份加 Metabase 或 Superset;采集用 Snowplow Collector 或自建 Go 服务;不引入 Kafka 和 Flink。如果必须实时,用 ClickHouse Kafka Engine 直接消费,不上 Flink。
自建约 400–500 美元/月,主要是 ClickHouse Basic 2 units 的 320 美元与应用监控;SaaS 按量可能接近 700(估算)。
失败模式是单节点磁盘满、没有副本、schema 变更没有回滚。这个档位最值得投入的不是性能,而是备份演练和 schema 版本管理。
标准:10 亿事件/月#
假设 3,300 万/天、386/秒均值、峰值 1,930/秒;200 个租户;12 个月留存;2–3 名平台工程师。
采集用 3 个无状态 collector,前面放负载均衡或 API Gateway,write key 加租户限流。队列用 Kafka 3 broker,按 tenant_id 加 user_id 分区,保留 7 天。流处理用 Flink 2–4 个 TaskManager,做 enrichment、session、identity 查表;checkpoint 1 分钟,状态放 RocksDB 加 S3。存储用 ClickHouse 3 分片乘 2 副本(6 节点,16 vCPU、64 GB),事件表按月分区,TTL 12 个月;聚合表用 AggregatingMergeTree。查询层加查询网关,做每租户并发与扫描字节上限,Top 20 看板走预聚合。治理层配 schema registry、dead-letter、对账任务、成本看板。
成本约 2,500–4,000 美元/月:ClickHouse 约 1,900 美元,Kafka、Flink、应用约 1,000–2,000 美元,另加人力。
失败模式是大租户热分区、合并落后、Flink 状态膨胀、双写期间成本翻倍。这个档位最常见的返工是“先上实时,后发现 80% 查询是 T+1”,于是流处理变成昂贵的 ETL。把 Kafka 保留期拉到 90 天当归档,最后会在扩容、恢复、跨区复制上一起付钱。
超大:100 亿以上事件/月#
假设 3.3 亿/天、3,858/秒均值、峰值 19,300/秒;1,000 个以上租户;24 个月热数据加 7 年归档;6–10 名平台工程师。
队列用 Kafka 6 个以上 broker,开 tiered storage(本地 6 小时,远端 7 天),按租户做配额,避免单租户打满。流处理用 Flink 集群(10–20 个 TaskManager),RocksDB 增量 checkpoint;身份解析改为“流内查表加离线图计算”,不把全图放状态。存储用三套 ClickHouse:热集群 30 天明细,高配,服务实时看板;温集群 12–24 个月明细,低配,服务下钻;冷层用 S3 或 Iceberg 归档,按需回灌。多租户上,大租户独立 compute service 或独立库,小租户共享加 workload group,查询网关做准入控制。预聚合覆盖 Top 50 指标的日、小时聚合,长尾查询走明细加采样。治理层做数据质量、血缘、成本归因、租户级 SLO。
成本约 16,000–22,000 美元/月:ClickHouse 约 11,600 美元,Kafka、Flink、S3、应用约 5,000–8,000 美元,另加人力。
失败模式是热分区、合并与副本恢复、元数据瓶颈、跨区出口、成本失控。到了这个量级,一次全量回填可能就是几十万美元的计算,变更管理必须比代码发布更严格。三套架构的组织含义也不同:轻量买“不用建团队”,标准买“团队能维护”,超大买“组织能治理”;差别在值班、变更、成本和权限的制度密度。
判断:到了 100 亿/月,最大的成本优化不是换引擎,而是少存、少扫、少复制。采样、TTL、预聚合、冷热分层这四件事,比选 ClickHouse 还是 Doris 影响大一个数量级。