Tips & Tricks Statnive Live · Parhum Khoshbakht

单台服务器,每分钟百万页面浏览:Statnive Live 的规模化工程实践

一个 Go 二进制文件、ClickHouse 聚合视图和 687 字节统计脚本,如何在一台 8 核服务器上处理每分钟百万次页面浏览——同时不拖慢您的网站。

网站分析的性能问题本质上是网站速度问题

大多数关于网站分析性能的文章都聚焦于后端——服务器每秒能处理多少事件。这是个错误的切入点。网站所有者真正承担代价的,是统计脚本对访客页面加载时间的影响——进而影响 Core Web Vitals、转化率和 SEO。

Google 的 Core Web VitalsINP 于 2024 年 3 月 12 日取代 FID)——LCP、INP、CLS——是搜索排名信号。移动端 JavaScript 解析速度约比桌面端慢 2–5 倍,这意味着桌面端 50 KB 的统计脚本在手机上的解析成本相当于 200 KB。阻塞渲染的统计脚本是此类产品中最大的性能杀手。

Statnive Live 的工程设计正是以这种不对称性为出发点。核心数据——每节点每天 2 亿事件、687 字节统计脚本、p99 查询延迟低于 500 ms——都服务于同一个目标:统计层永远不会成为结账流程变慢的原因。本文将逐一说明原理,并附上文件路径供验证。

本文是四篇系列文章 statnive.live 预发布系列的收尾篇。每项可量化的声明均附有对应的文件或命令作为依据。

687 字节统计脚本

Statnive Live 的统计脚本在 2026-04-28 测得为 1,394 字节(压缩前)/ 687 字节(gzip 压缩后)。这不是估算数字——它们是 Go 的 go:embed 指令嵌入二进制文件时的实际字节数,可在任意克隆的仓库中重现:

$ wc -c internal/tracker/dist/tracker.js
    1394 internal/tracker/dist/tracker.js

$ gzip -9 -c internal/tracker/dist/tracker.js | wc -c
     687

这些数字不会漂移,因为该文件通过 go:embed 嵌入二进制——在不重新构建的情况下,无法使用与仓库不一致的统计脚本。同时,它们也不会悄然膨胀:internal/tracker/tracker_test.go 中的 Go 测试将预算约束在 1,500 字节(压缩前)/ 700 字节(gzip 后),一旦超过任一阈值即构建失败:

const (
    maxMinifiedBytes = 1500
    maxGzippedBytes  = 700
)

同一测试还通过字符串匹配禁止脚本中出现任何不必要的传输机制——XMLHttpRequestlocalStoragesessionStorageindexedDBdocument.cookie、明文 URL、CDN 引入。若某次重构不小心引入了更大的传输库,或某个新功能使用了 localStorage,CI 将直接拒绝该 PR。

作为对比,GA4 的 gtag.js 脚本压缩后约为 110 KBPlausible 公布的同类数字是 135 KB(gzip 后)。无论以哪个数字为基准,Statnive Live 的统计脚本小两个数量级——比 GA4 轻逾 50 倍

传输协议采用 sendBeaconfetch keepalive——均为”发完即忘”,不阻塞主线程。结构是原生 JS 的 IIFE;没有引入任何框架,因为 1,394 字节容不下框架。脚本通过 go:embed 以第一方形式分发:没有外部 CDN、没有运营商域名之外的 DNS 查询、没有第三方标签管理器。CI 中的 air-gap-validator 规则会拒绝任何重新引入外部引用的脚本变更。

采集路径——发完即忘,WAL 优先

网站所有者与统计脚本的约定是”绝不阻塞我的页面”。服务器与统计脚本的约定是”绝不丢失您的事件”。Statnive Live 的采集管道正是为了以低成本同时兑现这两个承诺而构建的。

每个采集请求都经过预写日志(WAL)后处理器才响应 202。处理器等待 fsync——但基于 100 ms 的组提交定时器,而非逐事件 fsync,因为逐事件 fsync 在普通磁盘上会将吞吐量限制在约 100 事件/秒,而 SaaS 最低配置需要持续维持约 7,000 EPS。WAL 使用 MIT 协议的 tidwall/wal(已 vendor),以 NoSync: true 打开;100 ms 定时器负责持久性保障。处理器通过 AppendAndWait 等待后再发送 202 确认。若同步失败,进程直接退出——网站分析不是默默破坏历史数据的地方。

处理器通过 Go 的 http.MaxBytesReader 将请求体上限设为 8 KB:

const (
    maxBodyBytes  = 8 * 1024  // 8 KB MaxBytesReader
    maxArrayItems = 10        // batch at most 10 events per request
    uaMinLen      = 16
    uaMaxLen      = 500
)

WAL 之前,快速拒绝门会以 HTTP 204 过滤掉明显的垃圾请求——User-Agent 长度超出 16–500 范围、非 ASCII UA、以 IP 作为 UA、以 UUID 作为 UA、预取头(X-PurposeX-Moz)。这些请求永远不会触达数据丰富、WAL 或聚合视图。ClickHouse 的异步写入存在,但仅用于独立的 /ingest-fallback 端点——从不用于核心的 /api/event 热路径。

限流采用CGNAT 感知策略:来自移动运营商 ASN 的请求使用复合键 (ip, site_id),限制为 1,000 请求/秒持续 / 2,000 突发,其余请求按 IP 限制为 100 请求/秒。每个 site_id 的全局上限为 25,000 请求/秒,防止单个客户独占宿主机。CGNAT 感知至关重要,因为手机网络网关可能共用同一个 IP——简单的 per-IP 限流会屏蔽同一运营商下数千名真实访客。

原始 IP 永不持久化。它仅在 GeoIP 查询期间进入管道,随后在批量写入器处理该行之前被丢弃。审计日志同样不含 IP——限流器仍以 IP 作为限流决策的键,但审计日志序列化时将其丢弃。CI 中的 gdpr-code-review 规则对此进行强制检查。

查询路径——三层聚合视图 + HyperLogLog

仪表盘从不查询原始事件。所有仪表盘读取均来自聚合视图——这是架构规则 1,由 CI 检查点强制执行。原始的 events_raw 表是只写的,仅漏斗窗口会调用带有一小时缓存结果的 windowFunnel()

v1 的三个聚合视图均为 AggregatingMergeTree 视图,均以 site_id 作为第一排序键:

  • hourly_visitorsENGINE = AggregatingMergeTree() PARTITION BY toYYYYMM(hour) ORDER BY (site_id, hour)
  • daily_pagesORDER BY (site_id, day, pathname)
  • daily_sourcesORDER BY (site_id, day, channel, referrer_name, utm_source, utm_medium)

访客去重采用 HyperLogLog,通过 AggregateFunction(uniqCombined64, FixedString(16)) 实现——误差约 0.5%,内存占用次线性增长。FixedString(16) 是截取至 16 字节的 BLAKE3-128 哈希;身份标识为 BLAKE3(daily_salt || identity_input),每日盐值由 HMAC(master_secret, site_id || YYYY-MM-DD) 派生,每日轮换且永不持久化。同一访客每天生成不同的哈希——聚合视图中只保留哈希状态,从不保存输入。

所有仪表盘查询均通过同一个辅助函数:

// whereTimeAndTenant emits the WHERE clause every read query MUST start
// with: site_id = ? AND <timeColumn> >= ? AND <timeColumn> < ?.
// site_id is the first WHERE term so the (site_id, …) ORDER BY prefix
// can prune partitions cleanly.
func whereTimeAndTenant(f *Filter, timeColumn string) (string, []any) {
    clause := fmt.Sprintf("WHERE site_id = ? AND %s >= ? AND %s < ?",
        timeColumn, timeColumn)
    return clause, []any{f.SiteID, f.From, f.To}
}

CI 规则会拒绝任何绕过 whereTimeAndTenant 或不以 WHERE site_id = ? 开头的新查询。这听起来苛刻,但实际上决定了分区能否被干净地剪枝,还是在每次仪表盘渲染时让多租户 ClickHouse 扫描所有人的数据。

Nullable(...) 在统计列中被禁止——其在聚合计算上的实测成本为 10–200%(项目文档 20 测得 Nullable(Int8) 有 2 倍开销)。聚合视图改用 DEFAULT ''DEFAULT 0,以保持写入和合并路径的高效。

数据说明

/live 页面的”经过验证的指标”栏列出了四项:

  • 600 B gzip 统计脚本(687 B 的营销取整版本)
  • 每节点每天 2 亿事件
  • p99 低于 500 ms
  • 数据仅在欧盟和 EEA 处理

对每项的如实说明:

  • 统计脚本: 2026-04-28 测得 1,394 B(压缩前)/ 687 B(gzip 后);预算上限 1,500 B / 700 B(gzip 后),由 CI 断言。
  • 每天 2 亿事件: 设计上限,非生产实测值。 来源:项目文档 19 的 Hetzner 级别容量测算;SaaS 最低配置为 Hetzner AX42(8 核 / 64 GB),具有充足余量。每天 2 亿事件 = 持续约 2,300 EPS,完全在 ClickHouse 公布的吞吐量范围内(Cloudflare 在 36 节点上实现每秒 1,100 万行写入;Plausible 从 PostgreSQL 迁移正是因为超过约 100 万事件/天后 ClickHouse 是必选项)。
  • p99 低于 500 ms: 设计上限,非生产实测值。 Phase-11a 生产环境的 p99 将在公开注册上线后发布;ProofStrip 中的声明是毕业门槛,而非实测结果。
  • 数据仅在欧盟和 EEA 处理: 在德国纽伦堡的 Netcup VPS 2000 G12 NUE 上处理——“已验证”的含义是有集成测试在 iptables -P OUTPUT DROP 下运行二进制文件,证明不存在必要的出站流量。

仪表盘的初始 JS 预算为 16 KB(gzip 后),由 size-limit 针对构建产物 index-*.js 块进行断言。懒加载图表块上限 25 KB,懒加载面板块上限 10 KB,CSS 上限 5 KB / 3 KB。可在本地重新运行验证:

$ npm --prefix web run bundle-gate

测试门强制执行的分析不变量 SLO:

  • 事件丢失率 ≤ 0.05%(服务端)/ ≤ 0.5%(客户端)
  • 重复率 ≤ 0.1%
  • 归因准确率 ≥ 99.5%
  • 同意 / 个人信息泄露 = 0
  • TTFB 额外开销 ≤ +10% / +25 ms

每个阈值都是发布阻断条件,在每个 PR 上由 CI 断言,并在每次生产上线前经历 72 小时浸泡测试加 6 场景混沌矩阵的额外检验。无论下一次流量高峰是什么形态,都必须在发布前通过这些门禁。

诚实的权衡——1 小时延迟

1 小时延迟是 Statnive Live 中部分读者可能不喜欢的部分,因此我们直接点明。架构规则 3 写道:

1 小时延迟,而非实时——节省 98% 的查询成本。永远不要构建 5 分钟实时管道。

“98%“是与同一技术栈上假设存在的 5 分钟管道相比的——保持聚合视图写入成本低廉,将每站点的聚合视图占用维持在每天每站点 100 KB 以下(v1 有 3 个聚合视图;v1.1 最多 6 个),使仪表盘查询从紧凑聚合数据中提供服务,而不是扫描热表。如果您每小时或每天查看一次数据,1 小时延迟是无感知的。如果您需要针对直播活动的流量峰值监控进行亚分钟级反馈,Live 并非适合的工具——请选择实时网站分析产品,接受约高 50 倍的查询成本,然后继续。

实时面板仍然存在,它基于同一个 hourly_visitors 聚合视图展示最近一小时内活跃的访客。背后没有单独的 5 分钟管道,这是刻意为之。这个权衡是架构的核心,而非隐性成本。

这对您的网站意味着什么

上述架构正是让网站所有者故事平淡无奇的原因:

统计脚本不会阻塞您的结账流程。 sendBeaconfetch keepalive 是发完即忘——即使统计服务离线,页面仍会正常跳转,客户仍能完成支付。验证方法:关闭统计端点,观察页面是否正常运行。

Core Web Vitals 影响上限为 687 字节加一个内联 IIFE。 这远低于此类产品中任何有记录的”阻塞渲染”阈值。我们在另一篇文章中对 WordPress 插件的统计脚本 LCP 影响进行了基准测试;Live 统计脚本的 LCP 增量尚未发布实测数据,在未有数据前不会进行声明。

服务端开销在独立的源站处理。 统计脚本将数据发送至 Statnive Live 端点,而非您的 Web 应用。100 ms WAL fsync 定时器在 SaaS 最低配置上支撑约 7,000 EPS 持续吞吐——这与您应用的 PHP、Node 或 Rails 请求预算毫无竞争关系。

常见问题

能支撑每天 1,000 万次页面浏览吗?

可以。每天 1,000 万次页面浏览约等于持续 115 事件/秒——远低于单台 8 核 / 32 GB 机器上设计上限(持续约 2,300 EPS,折合每天 2 亿事件)。如果单节点无法满足需求,迁移脚本已使用 {{if .Cluster}} Go 模板,单节点向 Distributed 集群的切换只是修改配置,无需重新部署。

可以在共享主机上运行吗?

不行。ClickHouse 需要真实的服务器(最低 8 核 / 32 GB)。对于共享主机,WordPress 插件是正确选择——它使用现有的 MySQL/MariaDB 存储,零额外运维负担。

与 GA4 的 110 KB 脚本相比如何?

GA4 的 gtag.js 压缩后大小介于 110 KB(Stape 数据)135 KB(Plausible 数据)之间,视 payload 版本而定。Statnive Live 的统计脚本 gzip 后为 687 B。无论以哪个 GA4 数字为基准,均小逾 50 倍。 移动端解析时间差异尤为显著;在中端 Android 手机上,这个统计脚本的耗时可以忽略不计。

SaaS 方案运行在什么硬件上?

SaaS 最低配置为 Hetzner AX42(8 核 / 64 GB)。当前 SaaS 生产 VPS 为位于德国纽伦堡的 Netcup VPS 2000 G12 NUE——数据仅在欧盟和 EEA 处理,无第 V 章数据传输。第三篇文章涵盖合同层面;第二篇文章涵盖监管层面。

体积预算如何执行?

每个 PR 运行两项 CI 门禁。(a) go test ./internal/tracker/... 强制执行统计脚本 1,500 B / 700 B(gzip 后)预算以及禁止令牌检查。(b) npm --prefix web run bundle-gateweb/.size-limit.json 中所有五个仪表盘入口运行 size-limit。两者都是 make ci-local 的组成部分,GitHub Actions 工作流会在真实的 ClickHouse 环境中端到端运行,耗时 8–12 分钟。

出示证明

上述每项声明均可从 statnive-live 的克隆仓库中重现:

# Tracker size budget — 1,500 B min / 700 B gz, asserted by Go test
$ wc -c internal/tracker/dist/tracker.js
    1394 internal/tracker/dist/tracker.js
$ gzip -9 -c internal/tracker/dist/tracker.js | wc -c
     687
$ go test ./internal/tracker/...
ok      github.com/statnive/statnive.live/internal/tracker      0.32s

# Dashboard bundle budget — five size-limit entries
$ npm --prefix web run bundle-gate

# Whole gate — ClickHouse + integration + smoke + e2e (~8–12 min)
$ make ci-local

同样的命令在每个 PR 上通过 GitHub Actions 运行。没有单独的”发布基准”——若某个 PR 破坏了预算,不允许合并;若某次发布在 72 小时浸泡测试中违反了 SLO,不允许上线。为每分钟百万次页面浏览所做的工程工作,近看并不华丽:大多是 CI 门禁、快速拒绝过滤器和聚合视图,英雄主义的成分极少。

结语

您在 2026 年所采用的网站分析技术栈,评判标准大多是它对您网站做了什么,而非它为您提供了什么。Statnive Live 的设计取舍是明确的:687 字节的第一方统计脚本、节省了实时方案 98% 查询成本的 1 小时延迟聚合管道,以及一组由 CI 断言的 SLO,在问题抵达您之前就阻断发布。我们不会声明尚未上线的生产 p99 数字,也不会声明未经基准测试的 LCP 增量——但上述每个数字都有对应的文件路径供验证。

Statnive Live 即将在 zh.statnive.com/live 上线。 这个四篇系列是缓慢展开的介绍:WordPress 插件 vs Statnive Live 提供决策树,2026 年符合 GDPR 的网站分析 涵盖监管层面,掌控您的分析数据 涵盖部署形态,本文则涵盖工程层面。功能页面是一页概览。如果本文中的某个数字有误,请写信告诉我——每项声明背后都有文件或命令,我们宁愿纠正一个错误,也不愿发布一个精致的半真半假。

免费获取 Statnive