UBA RESEARCHBEHAVIOR / SYSTEMS / DECISIONS
下载研究报告
CHAPTER 10 · ARCHITECTURE / COST

参考架构:把事件流做成可查询、可治理、可负担的系统

架构没有标准答案,只有对写入、查询、实时性、隔离和成本的明确取舍;三套栈,对应三种规模与组织能力。

10,122 字 24 分钟 38 条引用

架构讨论很容易滑向引擎选型:ClickHouse 还是 Doris,Kafka 还是 Pulsar。但真正决定一套埋点分析平台能否活过三年的,是五个更早的约定:一条事件在哪一层变成不可变事实;谁有权在采集端拒绝它;一次查询要扫多少字节;哪个租户的故障可以影响别人;合同结束时数据能不能带走。这五个问题没有答案,换什么引擎都只是在给一条没有责任链的系统加速。

本章给出一套可以拿去评审的参考架构:八层责任链、五张核心表、五种部署模式、四档实时性、一个成本公式,以及三套按规模分档的栈。它们不是某家厂商的最佳实践,而是从公开文档、定价页和故障边界里反推出来的最小共识。

10.1 分层架构:先定义不可变的那一层#

一条事件从 SDK 到看板要经过八道手。评审时第一个问题不该是“用哪个引擎”,而是“这一层坏了,上一层的什么能力会消失”。

图 10-1 · 埋点分析平台参考架构
图 10-1 · 埋点分析平台参考架构
层 职责 典型实现 先坏在哪里 要观测什么
采集 鉴权、限流、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 排序,前四条通常能拿回大部分收益。

  1. 按时间分区加分区裁剪,看板默认 7 天或 30 天,查询必须带时间谓词。
  2. 只保留必要列,长尾属性进 JSON 或 Map,别把 200 个属性打平成宽表。
  3. 预聚合 Top 查询,把 30 天漏斗、留存做成日聚合,查询从扫 10 亿行变成扫 30 行。
  4. 采样,路径、探索、异常检测用 1–10% 采样,关键指标不采样。
  5. TTL 加冷热分层,明细 90 天、聚合 24 个月、原始归档 S3 或 Glacier。
  6. 压缩编码,低基数用 LowCardinality,时间戳用 Delta,价格用 ZSTD。
  7. 控制 part 数,批量写入每批 1–10 万行,避免小批高频。
  8. 查询网关限流,每租户并发、扫描字节上限、超时,大查询进队列。
  9. 结果缓存,同一看板 5 分钟内复用,缓存 key 带租户和权限。
  10. 同区部署,计算和存储同区,跨区看板用只读副本。

隐藏成本也要进预算:一次 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 影响大一个数量级。

UBA RESEARCH / 10查看全部引用 ↗