PROD-04工程级14 min

A/B 测试实验平台:独立站转化率优化

用 Node.js + PostgreSQL 构建轻量 A/B 测试平台,支持多变量同时实验、实时统计显著性检验、置信区间自动计算,服务于 3 个独立站日均 5 万次 UV 的转化率优化。

A/B测试Bayes统计PostgreSQL转化率优化实验平台

项目背景

独立站运营的日常困惑:按钮颜色改蓝色还是红色?产品描述放前面还是后面?每个改动都有人说服力,但谁也说不准。背后缺的是数据驱动的实验文化——用 A/B 测试量化每个改动的真实影响。

本系统构建了一个内部 A/B 测试平台,支持多变量同时跑实验、实时显著性检验、自动停机和邮件告警。目前服务于 3 个独立站,日均处理 5 万 UV 的实验分流,日均运行 12 组并发实验。

技术架构

架构分三层:分流层(Node.js Middleware 读取用户 Cookie 中已分配的实验 ID,新用户按哈希均匀分流)+ 数据层(PostgreSQL 存储实验配置、用户分配记录、每小时的转化事件)+ 分析层(每 10 分钟跑一次统计检验,计算 p 值和置信区间)。

split_engine.js
const { createClient } = require('@supabase/supabase-js')
const crypto = require('crypto')

const supabase = createClient(SUPABASE_URL, SUPABASE_KEY)

function hashUser(userId, experimentId) {
  return crypto.createHash('sha256').update(`${userId}:${experimentId}`).digest('hex')
}

function assignVariant(userId, experimentId, variants) {
  const hash = hashUser(userId, experimentId)
  const bucket = parseInt(hash.substring(0, 8), 16) % 100
  let cumulative = 0
  for (let i = 0; i < variants.length; i++) {
    cumulative += variants[i].weight
    if (bucket < cumulative) return variants[i].name
  }
  return variants[variants.length - 1].name
}

function getOrCreateUser(userId, experimentId) {
  const variant = assignVariant(userId, experimentId, [
    { name: 'control', weight: 50 },
    { name: 'variant_a', weight: 50 },
  ])
  return { userId, experimentId, variant, bucket: parseInt(hashUser(userId, experimentId).substring(0, 8), 16) % 100 }
}

const user = getOrCreateUser('user_12345', 'exp_checkout_btn_color')
console.log('Assigned:', user)

核心代码

统计显著性用 Bayesian 方法(Beta 分布)计算,比 frequentist 的 p 值更直观——直接给出" variant 优于 control 的概率是多少"。转化率数据每 10 分钟聚合一次,低于设定阈值自动告警。

stats.py
import math
from scipy.stats import beta

def bayes_ctr_confidence(control_visits, control_convs, variant_visits, variant_convs, alpha=0.05):
    control_rate = control_convs / control_visits if control_visits &gt; 0 else 0
    variant_rate = variant_convs / variant_visits if variant_vis &gt; 0> 0 else 0
    
    # Beta posterior samples
    control_samples = beta.rvs(control_convs + 1, control_visits - control_convs + 1, size=10000)
    variant_samples = beta.rvs(variant_convs + 1, variant_visits - variant_convs + 1, size=10000)
    
    # Probability variant &gt; control
    prob = (variant_samp &gt; c> control_samples).mean()
    
    # Credible interval for variant
    ci_low, ci_high = beta.ppf(alpha/2, variant_convs+1, variant_visits-variant_convs+1), \
                      beta.ppf(1-alpha/2, variant_convs+1, variant_visits-variant_convs+1)
    
    return {'prob_better': prob, 'variant_rate': variant_rate, 'ci_95': (ci_low, ci_high)}

result = bayes_ctr_confidence(10000, 320, 10000, 385)
print(f"Variant is {result['prob_better']*100:.1f}% likely to be better")
print(f"Variant CTR: {result['variant_rate']*100:.2f}%, 95% CI: [{result['ci_95'][0]*100:.2f}%, {result['ci_95'][1]*100:.2f}%]")

关键指标

· 分流均匀度:Chi-square test p > 0.05,各实验组访问量偏差 < 5%
· 决策速度:达到 95% 置信度平均需要 7 天(视转化率样本量而定)
· 误判率:历史实验回测,错误停机率(实验无真实效果但被判定有效)< 5%
· 支持并发实验:最多同时跑 20 组实验,互不干扰

最小样本量:转化率实验的最小样本量取决于基线转化率和最小可检测效应(MDE)。基线 3%、MDE 20% 时,每组至少需要 15000 UV 才能有足够的统计功效。别在样本量不足时提前决策。