埋点这件事,别等系统跑起来才想
原创 · 约 35 分钟阅读 · 阅读 --

埋点这件事,别等系统跑起来才想

作者: Alex Xiang


古董级程序员,大厂出来后一直在创业公司,现在仍活跃在一线做 AI 相关的开发。更完整的更新写在微信公众号「字与码」:工作经历、对新技术的想法,以及这些年走弯路的记录,会不定期发在那里。若觉得博客对你有用,欢迎顺手关注。

很多系统上线时都说“先加点日志,后面再分析”。过几个月再看,日志确实不少,问题还是回答不了。

用户从哪里来,不知道。注册页打开了多少次,不知道。免费用户为什么没有付费,不知道。某个接口调用量突然涨了,是新用户增长、爬虫、重试风暴,还是某个客户接入脚本写错了,也不知道。

日志不是埋点。日志通常是给工程师排障看的,讲的是“程序发生了什么”。埋点是给产品、运营、增长、风控、计费和工程一起看的,讲的是“用户和业务发生了什么”。

这篇文章不讲某个具体项目,而是把服务埋点这件事拆开:什么服务需要埋点,不同流量阶段怎么做,数据库怎么选,成本怎么算,性能怎么评估,最后用几个真实业务场景把“从用户源头到分析结果”的链路串起来。

服务埋点和分析链路

埋点到底解决什么问题

埋点最常见的误区,是把它当成“访问量统计”。访问量当然重要,但只看 PV/UV,最多知道门口来了多少人。真正有价值的问题通常更尖锐:

  • 哪个渠道来的用户更容易注册?
  • 注册后多久会完成第一次关键行为?
  • 免费用户在什么地方掉队?
  • 付费前通常调用了哪些功能?
  • 某个版本上线后,错误率上升是集中在一个入口,还是所有入口都变差?
  • 高价值用户和普通用户的行为差异是什么?
  • 一个接口调用量涨了,收入有没有跟着涨,成本有没有失控?

这些问题都有一个共同点:它们不只需要一条日志,而是需要把用户、会话、来源、页面、接口、订单、功能、错误、成本串起来。

所以服务埋点的核心不是“多记一点”,而是建立一条可追踪的事实链:

用户来源 -> 访问页面 -> 注册/登录 -> 激活行为 -> 功能使用 -> 成本消耗 -> 付费/留存

如果这条链断了,再多日志也只能做局部猜测。

哪些服务需要埋点

不是所有服务一上来都需要复杂埋点。埋点设计应该和服务类型、业务阶段、流量规模一起看。

服务类型最重要的问题优先埋点
内容站、官网、文档站流量从哪来,哪些内容带来转化page_view、referrer、utm、入口按钮、注册点击
SaaS 后台用户是否真正激活,哪些功能被使用login、workspace_created、feature_used、invite_sent
电商/订阅服务漏斗哪里流失,支付失败原因是什么product_view、checkout_started、payment_succeeded、payment_failed
API 平台调用量、成功率、耗时、成本、客户价值api_call、first_api_call、quota_exceeded、billing_usage
B2B 内部系统关键流程是否完成,审批/任务卡在哪里workflow_started、step_completed、approval_rejected
高流量网关峰值、异常、区域分布、计费和风控request_sample、error_bucket、rate_limited、billable_event

小服务最怕过度设计。刚上线的官网,不需要立刻建 ClickHouse 集群;但至少要有稳定的 anonymous_id、页面访问、关键按钮点击和注册成功事件。反过来,一个每天几百万 API 调用的平台,如果还只靠应用日志和后台 SQL 查账,很快会把业务库拖慢。

先定事件,不要先定数据库

数据库选型很重要,但埋点系统最容易失败的地方不是数据库,而是事件口径。

一个合格的事件至少要回答四个问题:

{
  "event": "api_call",
  "time": "2026-06-29T10:23:45.123Z",
  "user_id": "u_123",
  "anonymous_id": "anon_abc",
  "session_id": "sess_789",
  "source": "web",
  "page": "/pricing",
  "properties": {
    "tool_id": "stock_quote",
    "success": true,
    "latency_ms": 183,
    "credits": 2,
    "plan": "pro"
  }
}

这四个问题是:

  • 谁:user_idanonymous_idaccount_idapi_key_id
  • 什么时候:事件时间和服务端接收时间最好都保留
  • 在哪里:页面、入口、客户端、地区、设备、服务名
  • 做了什么:事件名和业务属性

这里有两个细节很关键。

第一,匿名用户不能丢。很多转化发生在注册前,如果没有 anonymous_id,注册前的广告来源、落地页、定价页访问就没法和注册后的用户连起来。

第二,事件名要稳定。checkout_startstart_checkoutclick_pay_button 混着用,后面看板一定会乱。事件名应该有 allowlist,字段应该有 schema version。

可选的埋点方式

埋点入口大致分四类。

前端埋点适合采页面浏览、按钮点击、表单展示、页面停留、客户端错误。它离用户最近,能拿到 referrer、utm、屏幕、浏览器、入口位置。但它不可靠:用户可能关页面,浏览器可能拦截,网络可能失败,前端也容易被伪造。

后端埋点适合采注册成功、登录成功、API Key 创建、订单支付、接口调用、扣费、权限失败。这类事件必须以后端为准,特别是支付和计费,不能相信前端说“我付费成功了”。

网关埋点适合采 API 平台和微服务入口:请求路径、状态码、耗时、租户、key、限流、熔断、上游错误。它的优点是统一,缺点是业务语义有限。网关知道某个接口被调用了,但不一定知道这次调用对业务意味着什么。

异步埋点适合高流量场景。主流程只把事件放进内存队列、本地 buffer、Kafka、Pulsar 或云消息队列,后面的消费者批量写库。它的好处是不会让数据库写入反压用户请求;坏处是系统复杂度上来了,需要处理丢失、重复、乱序和重放。

一套常见的分工是:

前端:页面和交互
后端:业务事实
网关:请求事实
队列:削峰和批量写入
任务:聚合、修复、回填

从用户来源到分析结果的完整链路

采集时要避开的坑

服务埋点不是写一条 insert 就完事。下面几个坑很常见。

不要把敏感信息写进埋点。邮箱、手机号、token、API Key、支付卡号、原始 prompt、完整查询文本,都要非常谨慎。需要分析时可以写 hash、长度、分类、是否为空,不要把隐私和密钥带进分析库。

不要让埋点阻塞主流程。注册、支付、API 调用这些路径上,埋点失败不能导致业务失败。常见做法是 best effort、异步队列、超时丢弃、批量写入。

不要采太细的无意义事件。滚动、鼠标移动、hover 这类事件很容易把量打爆。除非明确做行为热力图,否则先别碰。

不要只存原始事件不做聚合。Dashboard 如果每次都扫 180 天明细表,迟早会变成数据库压力测试。常用指标应该做日聚合、小时聚合,明细只用于 drilldown。

不要让 properties 变成垃圾桶。JSON 字段可以灵活,但每个事件应该有字段字典。否则三个月后你会发现同一个含义有 planplan_typetierpackage 四种写法。

分析方法:从单点事件到用户旅程

埋点分析通常分几层。

最简单的是计数和趋势:

select date_trunc('day', time) as day, count(*) as pv
from event_logs
where event = 'page_view'
group by 1
order by 1;

第二层是分组对比:

select utm_source, count(distinct anonymous_id) as visitors
from event_logs
where event = 'page_view'
  and time >= now() - interval '7 days'
group by utm_source
order by visitors desc;

第三层是漏斗:

landing_view -> signup_click -> signup_completed -> first_api_call -> payment_succeeded

漏斗不要只看总转化率。要按渠道、设备、国家、套餐、入口页面拆开看。总转化率下降 20%,可能只是某个渠道买量变差;也可能是移动端注册页坏了;也可能是支付失败率上升。

第四层是 cohort 留存。比如看“完成首次 API 调用的用户,7 天后是否还有调用”。这比 DAU 更接近产品是否真的有用。

第五层是单位经济账。API 平台尤其需要这层:某个客户调用量很高,但如果每次调用都带来第三方成本,收入没覆盖成本,就不是好事。

数据库怎么选

服务埋点常见的存储选项有 PostgreSQL、MongoDB、ClickHouse,也可以直接用产品分析工具,比如 PostHog、Amplitude、Mixpanel。自己做的好处是口径可控,能和业务数据打通;坏处是要承担数据质量、性能和运维。

PostgreSQL:中小规模最稳的起点

如果你的服务本来就用 PostgreSQL,日事件量在几十万以内,查询主要是近 7 天、近 30 天的 dashboard,PostgreSQL 是很好的起点。

它的优势是业务 join 方便。用户表、订单表、积分表、团队表都在同一个数据库或同类数据库里,分析时不用同步一堆维表。JSONB 也能承接不同事件的扩展属性。PostgreSQL 官方文档里,GIN 索引用于处理 JSONB、全文检索这类复合值查询,这让“结构化字段 + JSONB 扩展”成为一个现实方案。

但 PostgreSQL 不是无限事件仓库。写事件表时要注意:

  • 按时间分区,比如月分区或日分区
  • 常用查询字段单独列出来,不要全部塞进 JSONB
  • JSONB 的 GIN 索引少建,确实需要按 properties 查询再建
  • Dashboard 查聚合表,不要扫明细表
  • 明细保留周期要明确,比如 90 天或 180 天

一个比较稳的表结构是:

create table event_logs (
  id uuid primary key,
  event text not null,
  event_time timestamptz not null,
  received_at timestamptz not null default now(),
  user_id text,
  anonymous_id text,
  session_id text,
  source text,
  page text,
  referrer text,
  utm_source text,
  utm_campaign text,
  properties jsonb not null default '{}',
  schema_version int not null default 1
) partition by range (event_time);

MongoDB:能存,但不适合作为主分析库

MongoDB 存事件很自然。事件本来就是 JSON,字段变化频繁,写入也简单。MongoDB 也有 time series collections,官方文档说明它会把相似时间和相同来源的数据组织到一起,提高时间序列数据的存储和查询效率。

所以 MongoDB 不是不能做埋点。它适合这些场景:

  • 事件结构非常不稳定,早期还在探索
  • 主要用于调试和审计,不做复杂漏斗
  • 已经有成熟 MongoDB 运维体系
  • 单用户或单对象维度查询多,跨全量聚合少

但如果你要长期做 DAU、渠道转化、留存、付费漏斗、用户分层、API 成本趋势,MongoDB 会越来越吃力。不是完全做不了,而是复杂 aggregation pipeline、索引数量、数据扫描成本、BI 接入成本都会上来。

一句话:MongoDB 可以当灵活事件仓库,不建议当核心埋点分析主库。

ClickHouse:高流量分析的正路

ClickHouse 是列式 OLAP 数据库,适合 append-only 的大规模事件、日志、时间序列和多维聚合。ClickHouse 文档里 MergeTree、分区和 TTL 是核心能力,TTL 文档也明确建议按同一个时间字段做分区,这样过期数据可以整块 drop,而不是逐行删除。

它的优势很适合埋点:

  • 列式存储,只读查询需要的列
  • 压缩率高,重复字段多的事件数据很省空间
  • 按时间、事件、用户、渠道、区域聚合很快
  • 批量写入吞吐高
  • TTL、分区、冷热数据管理适合长期明细保留

Cloudflare 的公开案例很有代表性。Cloudflare 在一篇博客里介绍过用 ClickHouse 支撑 HTTP Analytics,处理每秒数百万请求级别的分析流水线;后来也多次分享过 ClickHouse 在日志分析、客户 dashboard、Bot 管理等场景中的使用。这类案例说明 ClickHouse 不是“更快的 PostgreSQL”,而是完全不同定位的分析型基础设施。

但 ClickHouse 也不是所有阶段都该上。它不适合频繁单行更新,不适合强事务,不适合替代业务主库。它最好作为分析库,由 PostgreSQL、日志、队列、业务服务异步同步过来。

不同流量阶段的数据库演进

一个简单的选型表

阶段日事件量推荐方案说明
起步1 千 - 10 万PostgreSQL 单表或月分区先把事件口径做对
成长10 万 - 50 万PostgreSQL 分区 + 聚合表控制索引,聚合先行
高速增长50 万 - 100 万PostgreSQL + ClickHouse 旁路同步开始验证列式分析库
高流量100 万 - 500 万ClickHouse 做明细分析,PostgreSQL 做业务事实Dashboard 不扫业务库
超高流量500 万以上ClickHouse 集群 + 队列 + 分层存储需要专门的数据平台治理

这里的日事件量只是经验线,不是绝对阈值。如果事件很窄、查询简单,PostgreSQL 能撑得更久;如果每个事件 properties 很大、看板又天天扫 180 天,几十万日事件也可能很痛苦。

成本怎么估算

埋点成本主要由五个变量决定:

日事件数 × 单条事件大小 × 保留周期 × 副本数 × 查询复杂度

PostgreSQL 里,一条带 JSONB 的事件,连同行开销和索引,粗略可以按 1KB 到 3KB 估。ClickHouse 压缩后可能只有 200B 到 800B,具体取决于字段设计和重复度。

按 50 万事件/日估算:

存储单条估算月明细保留 12 个月
PostgreSQL2KB约 30GB约 360GB
PostgreSQL 加索引膨胀4KB约 60GB约 720GB
ClickHouse 压缩后500B约 7.5GB约 90GB

这只是明细,不含备份、副本、聚合表、WAL/binlog、对象存储归档。比较稳的预算方式,是再乘一个 1.5 到 2 的预留系数。

如果自建 ClickHouse,一个 4-8 vCPU、16-32GB 内存、几百 GB 到 1TB SSD 的单节点,通常足够做早期验证和中等规模分析。到了每天几百万事件,或者保留一年以上明细,就要认真规划副本、备份、冷热分层和查询隔离。

性能怎么评估

不要只压测写入。埋点系统真正容易出问题的是“写入 + 查询 + 聚合 + 清理”同时发生。

我会看这些指标:

  • 写入延迟:主业务请求是否被埋点拖慢
  • 写入吞吐:每秒事件数、批量大小、失败重试
  • 查询延迟:常用 dashboard 的 P50/P95
  • 聚合耗时:每日任务是否能在窗口内跑完
  • 存储增长:每天新增多少数据,索引占比多少
  • 数据延迟:事件产生后多久能在看板看到
  • 数据质量:重复率、丢失率、无效 user_id、非法 event

一个简单的验收标准可以这样定:

指标小中型服务目标高流量服务目标
主流程埋点开销P95 小于 10ms,失败不影响业务主流程只入队,P95 小于 3ms
前端事件可见延迟1-5 分钟1 分钟内或准实时
日聚合任务30 分钟内完成分钟级增量聚合
常用看板查询P95 小于 2 秒P95 小于 1 秒
明细查询范围默认限制 7-30 天通过 ClickHouse 支撑长窗口

性能评估还有一个容易被忽略的点:事件增长不是线性的。业务正常增长是一种;某个 SDK 出 bug、客户端疯狂重试、debug 事件忘关、爬虫打爆接口,是另一种。埋点系统要能降级,比如采样 DEBUG 事件、丢弃低优先级事件、限制单用户事件频率。

端到端案例一:内容站如何分析注册转化

假设你有一个技术内容站,目标不是单纯看文章阅读量,而是希望读者注册产品。

事件设计:

阶段事件关键字段
来源page_viewanonymous_id、utm_source、referrer、slug
阅读article_readscroll_depth、read_seconds、slug
点击signup_clicklocation、slug、button_type
注册signup_completeduser_id、anonymous_id、method
激活first_key_actionuser_id、action、hours_since_signup

分析链路:

广告/搜索/社交 -> 文章页 -> 注册按钮 -> 注册完成 -> 第一次关键行为

分析结果可能是:

  • 搜索流量 PV 最大,但注册率一般
  • 某篇深度教程 PV 不高,但注册转化率很高
  • 移动端 signup_click 多,signup_completed 少,说明注册页移动端有问题
  • 某个渠道带来的用户注册快,但激活率低,可能是低质量流量

这时优化动作就很明确:不是“多写文章”,而是把高转化文章前置、修移动端注册页、调整投放渠道。

端到端案例二:API 平台如何分析成本和收入

API 平台只看调用量很危险。调用量越高,成本可能越高;如果没有收入或留存,增长反而会变成亏损。

事件设计:

阶段事件关键字段
注册signup_completeduser_id、plan、utm_source
API Keyapi_key_createduser_id、key_type
首次调用first_api_calluser_id、tool_id、signup_to_first_call_hours
调用api_calluser_id、api_key_id、tool_id、success、latency_ms、cost、credits
限额quota_exceededuser_id、plan、requested_action
付费payment_succeededuser_id、amount、plan、calls_before_pay

核心分析:

select
  tool_id,
  count(*) as calls,
  sum((properties->>'cost')::numeric) as cost,
  sum((properties->>'credits')::numeric) as credits,
  avg((properties->>'latency_ms')::numeric) as avg_latency
from event_logs
where event = 'api_call'
  and event_time >= now() - interval '1 day'
group by tool_id
order by calls desc;

这类分析能回答:

  • 哪些工具调用最多,但转化不付费?
  • 哪些工具毛成本高,需要改定价?
  • 免费用户到首次调用的时间是否太长?
  • 首次调用成功率低,是参数难填、文档不清楚,还是第三方 provider 不稳定?

API 平台建议把“调用事实”和“业务行为”分开。调用事实可以来自网关或服务端,必须准确;业务行为可以来自产品埋点,负责解释用户为什么走到这里。

端到端案例三:订阅服务如何看付费漏斗

订阅服务最怕只看支付成功。支付成功当然重要,但付费前的路径更重要。

事件链路:

pricing_view -> plan_select -> checkout_started -> payment_succeeded/payment_failed -> subscription_renewed/subscription_cancelled

关键字段:

  • current_plan
  • selected_plan
  • price
  • payment_method
  • failure_reason
  • country
  • device_type
  • entry_point

分析时不要只算一个总漏斗,要拆:

  • 移动端和桌面端
  • 不同国家/地区
  • 不同入口,比如首页、文档、用量不足提醒
  • 新用户和老用户
  • 月付和年付

一个常见发现是:plan_select -> checkout_started 转化很好,但 checkout_started -> payment_succeeded 很差。这个问题一般不在定价页,而在支付链路:支付方式不适配、3DS 验证失败、账单地址要求过多、错误提示不清楚。

演进策略:别一步到位,也别没有退路

我比较喜欢的演进方式是四步。

第一步,只做核心事件。页面访问、注册成功、登录成功、关键功能使用、支付成功、API 调用。先保证口径准确,不要追求事件数量。

第二步,补身份链路。匿名用户、登录用户、账号、团队、API Key 要能串起来。没有身份链路,漏斗和留存都不稳。

第三步,做聚合表和看板。DAU、注册转化、首次关键行为、付费转化、功能使用、调用成功率、成本趋势。不要让业务方天天找工程师跑 SQL。

第四步,引入分析库。PostgreSQL 扛不住长窗口明细扫描时,再同步到 ClickHouse。这个时候事件口径已经稳定,表设计也更容易做对。

一个健康的埋点系统,应该允许你从简单开始,但未来能平滑长大:

PostgreSQL 原始事件
  -> PostgreSQL 分区 + 聚合表
  -> PostgreSQL + ClickHouse 旁路分析
  -> ClickHouse 承担明细分析,PostgreSQL 保留业务事实

最后:埋点是产品和工程之间的协议

服务埋点不是产品经理写一张事件表,也不是工程师随手加几行日志。它更像一份协议:我们如何定义用户行为,如何定义成功,如何定义成本,如何定义一次真正有价值的使用。

一开始不用做得很重。小服务先把十几个核心事件采准,比一口气采一百个混乱事件有用得多。等流量上来,再考虑队列、分区、ClickHouse、冷热分层。

真正重要的是,系统上线后,团队能持续回答这些问题:

  • 用户从哪里来?
  • 他们做了什么?
  • 哪里卡住了?
  • 哪些行为带来收入?
  • 哪些调用消耗成本?
  • 改了一版之后,结果到底变好了还是变坏了?

能回答这些问题,埋点才算开始有价值。

参考资料

微信公众号

欢迎关注「字与码」

如果这篇文章对你有用,也欢迎在微信里继续关注后续更新。

微信公众号字与码二维码