想降低数据量,于是把事件保留一部分,再把结果乘回倍率。这对某些明确设计的总量估计可能有适用条件,但漏斗并不是几个总量的简单拼接。开始事件和成功事件一旦分开丢失,同一账号是否走过完整路径的信息也可能一起丢掉。
本文由 SensorFlow 项目维护方整理,AI协助起草。示例全部离线合成,不是任何厂商的采样实现、客户测量结果,也不代表产品内置采样能力。
采样单位决定了什么关系还能留下
事件级采样决定每条记录是否保留;账号级选择决定一个固定账号在同一策略下的相关事件是否一起进入分析集合。前者可能打断路径,后者能在接收完整的前提下保留所选账号的内部关系,但仍不能保证样本比例等于总体比例。
先问清四件事:抽的是事件、浏览器、会话还是账号;哪些事件参加;同一条路径是否使用同一策略;结果是样本描述还是总体估计。只写“采样率50%”,并不能回答这四个问题。
即使按账号保留数据,匿名阶段和登录阶段的ID变化、客户端漏发、服务端过滤及统计截止时间也可能造成断链。采样策略不能替代身份定义和采集质量验收。
一个不需要概率公式的反例
假设合成账号a、b、c都触发开始,只有a、b触发完成。完整记录里的阶段共现比例是2/3;如果删减后恰好只剩三条开始记录,看到的就是0/3。把开始数和完成数同时乘2,仍然是0/6,丢失的账号关系没有恢复。
这是一个证明“不能保证恢复”的反例,不是关于某个随机采样器平均偏差的统计结论。若要讨论概率估计、方差或置信区间,需要明确抽样设计、纳入概率和估计方法,不能用这个小样本代替研究。
同样,不要把“阶段都出现过”叫作完整漏斗。本节和代码没有时间字段,只检查阶段共现;真正漏斗还要固定先后顺序、窗口、重复尝试及跨天规则。先把简化例子的边界说清,才不会把演示结果当成正式指标。
稳定账号选择的最小可运行验收
下面用固定策略名与账号字符串生成一个稳定分数,判断是否进入样本。来源是Node createHash与Buffer.readUInt32BE;策略名不是密钥,哈希不构成匿名化、授权或反作弊机制。
保存为 sampling.cjs,运行 node sampling.cjs。不联网、不读生产数据、不修改采集设置。
const assert = require('node:assert/strict');
const {
createHash } = require('node:crypto');
function selected(account, fraction, policy = 'teaching-v1') {
assert(typeof account === 'string' && account.length > 0);
assert(typeof policy === 'string' && policy.length > 0);
assert(Number.isFinite(fraction) && fraction >= 0 && fraction <= 1);
const key = JSON.stringify([policy, account]);
const number = createHash('sha256').update(key, 'utf8').digest().readUInt32BE(0);
return number / 2 ** 32 < fraction;
}
function stages(rows) {
const starts = new Set(), finishes = new Set();
for (const row of rows) {
if (row.event === 'start') starts.add(row.account);
if (row.event === 'finish') finishes.add(row.account);
}
let both = 0;
for (const account of starts) if (finishes.has(account)) both += 1;
return {
starts: starts.size, both, rate: starts.size ? both / starts.size : null };
}
const rows = [
{
account: 'a', event: 'start' }, {
account: 'a', event: 'finish' },
{
account: 'b', event: 'start' }, {
account: 'b', event: 'finish' },
{
account: 'c', event: 'start' }
];
const broken = rows.filter(row => row.event === 'start');
const sampled = rows.filter(row => selected(row.account, 0.5));
assert.equal(stages(rows).rate, 2 / 3);
assert.equal(stages(broken).rate, 0);
assert.equal(stages([]).rate, null);
assert.equal(selected('a', 0), false);
assert.equal(selected('a', 1), true);
assert.equal(selected('a', 0.5), selected('a', 0.5));
assert.equal(sampled.filter(row => row.account === 'a').length, selected('a', 0.5) ? 2 : 0);
assert.equal(stages(rows.concat(rows)).rate, stages(rows).rate);
assert.throws(() => selected('', 0.5));
assert.throws(() => selected('a', NaN));
assert.throws(() => selected('a', 1.1));
assert.throws(() => selected(1, 0.5));
console.log({
full: stages(rows), broken: stages(broken), accountSample: stages(sampled) });
console.log('PASS: 12 synthetic sampling checks');
固定输入数组经JSON.stringify编码,避免直接拼接两段字符串造成边界歧义。它不是通用规范化JSON协议;跨语言实现还要用同一编码、摘要、大端读取和比较边界的测试向量验证。
Set 仅把这个教学样本中的同账号阶段重复合并,并不检查同事件ID冲突。Node strict assert 固定反例和输入边界。少量账号中恰好选中几个,都不能当作算法在真实人群中的分布或估计精度证明。
把策略版本当成数据契约保存
实际变更可分四步:
- 固定分析单位与身份命名空间,明确未知ID如何处理,不能把全部未知都归一个账号。
- 固定策略版本、目标比例、纳入事件与排除规则,并让同一分析路径使用一致决策。
- 用合成账号验证重试稳定、跨端一致、0与1比例边界、非法输入和策略切换。
- 在报表展示所用策略、观察窗口、样本单位数与完整性限制,保留能复现的验收记录。
不要在一次漏斗的中途切换策略后,直接与原来全量结果比较。策略名变化可能改变入选账号;代码没有实现旧策略迁移、身份合并或重放,需要另做契约。
更不能把哈希后的账号当成“没有隐私风险”。这里的输入仍然是账号标识;合法性、访问权限、保留期和敏感字段控制是另外的工作,普通哈希不会让这些责任消失。
哪些指标不适合这样简化
收入对账、订单状态和需要逐笔追溯的关键业务事实,不应因为埋点量大就随意丢弃原始凭证。是否允许采样,要由该数据用途决定;行为观察和交易事实不能混为一类。
路径分析关心关联,低频行为关心是否还剩足够观测,分组比较还可能遇到不同群体的覆盖差异。本文没有提供总体估计器、置信区间、实验分流或生产容量建议,也不能据启动成功推导节省比例。
最值得带走的不是这段哈希代码,而是验收问题:我删掉的究竟是记录,还是整条分析单位的观察?结果里哪些关系还能复现?先用一个会失败的反例把边界写进测试,再决定是否调整真实采集策略。