PostHog ClickHouse 查询基准测试套件实战指南:用 ASV 持续追踪查询性能回归
2026/9/14 14:28:07 网站建设 项目流程

PostHog ClickHouse 查询基准测试套件实战指南:用 ASV 持续追踪查询性能回归

【免费下载链接】posthog:hedgehog: PostHog is the leading platform for building self-driving products. Our developer tools – AI observability, analytics, session replay, flags, experiments, error tracking, logs, and more – capture all the context agents need to diagnose problems, uncover opportunities, and ship fixes. Steer it all from Slack, web, desktop, or the MCP.项目地址: https://gitcode.com/GitHub_Trending/po/posthog

本文是一份面向 PostHog 后端开发者的 ClickHouse 查询基准测试(Benchmark)实战指南。文章以 ee/benchmarks/README.md 为核心骨架,结合 benchmarks.py、helpers.py、asv.conf.json 与 measure.sh 等仓库源码,系统讲解基准测试套件的架构、本地运行方法、PR 性能回归检测流程、新用例编写规范以及历史结果回填技巧。读完本文,你将掌握如何在本地复现 PostHog 官方基准测试、读懂各项 ClickHouse 指标(耗时、读取行数、内存占用)的含义,并为自己的查询优化 PR 添加可追踪的性能证据。

基准测试套件定位:为什么 PostHog 需要一套 ClickHouse 查询基准

PostHog 的产品分析、会话回放、用户行为洞察等核心能力,几乎都建立在 ClickHouse 之上的大量复杂查询之上。随着数据量增长与查询逻辑演进,一次不经意的 SQL 改动或物化列策略调整,都可能在线上引发显著的查询性能回退。

ee/benchmarks/README.md 明确指出:ee/benchmarksPostHog ClickHouse 查询的基准测试套件(benchmark suite),其目标是在时间轴上持续追踪 ClickHouse 查询的性能改进(tracks performance improvements to clickhouse queries over time)。这意味着它不是一次性压测工具,而是与 CI、PR 流程深度绑定的长期性能治理基础设施。

为了获得跨时间的稳定可比较结果,套件坚持两个关键设计决策:

  1. 基于 airspeed velocity(asv)驱动:asv 是专门面向 Python 项目的基准测试框架,负责环境构建、多次采样、结果归档与回归检测;
  2. 使用预填充数据的稳定 ClickHouse 节点:基准测试始终跑在一个数据形态固定的 ClickHouse 节点上,避免因线上数据增长导致结果失真,从而保证不同提交之间的性能差异真实反映代码变化。

历史基准测试结果由独立的PostHog/benchmark-results仓库承载,套件本身只负责“生产”结果,不负责长期保存。

套件文件布局:五个文件组成的性能观测体系

在继续之前,先整体了解ee/benchmarks目录的结构:

文件职责
README.md使用文档:安装、运行、回填、FAQ
asv.conf.jsonASV 框架配置文件,定义项目路径、构建命令、结果目录等
benchmarks.py基准用例集合,定义被测查询与测试环境准备逻辑
helpers.py核心支撑库:Django/ClickHouse 环境初始化、@benchmark_clickhouse装饰器、查询统计采集
measure.sh独立于 ASV 的单查询测量脚本,支持火焰图与 EXPLAIN

其中benchmarks.pyhelpers.py是套件的主体:前者描述“测什么”,后者解决“怎么稳定地测”。

安装与本地运行:从零跑起一套基准

环境准备

基准测试由 asv 驱动,而 asv 依赖 virtualenv(或 Anaconda 发行版)来隔离基准运行环境。按 README 的说明,安装只需:

pip install asv virtualenv

需要特别说明的是:PostHog 的基准用例直接运行在 Django 应用上下文中(见下文helpers.py),因此本地运行前还需要保证项目本身的 Python 依赖可用,并且能够连上目标 ClickHouse 节点。

一键本地运行

README 给出了标准的本地运行流程。核心是两步:先声明机器环境,再执行基准:

# 1) 设置机器环境(machine 标识用于区分不同运行环境的结果) asv machine --machine ci-benchmarks --config ee/benchmarks/asv.conf.json # 2) 运行全部基准(X 替换为实际凭据) CLICKHOUSE_HOST=X CLICKHOUSE_USER=X CLICKHOUSE_PASSWORD=X CLICKHOUSE_DATABASE=posthog asv run --config ee/benchmarks/asv.conf.json

这里的环境变量直接决定了基准用例执行时连接的 ClickHouse 实例。从 helpers.py 可以看到,套件运行时还强制注入了两个环境变量:

os.environ["POSTHOG_DB_NAME"] = "posthog_test" os.environ["DJANGO_SETTINGS_MODULE"] = "posthog.settings"

也就是说,无论外部传入的CLICKHOUSE_DATABASE是什么,Django 侧的POSTHOG_DB_NAME恒为posthog_test,这是 PostHog 测试环境与生产环境隔离的一部分。随后脚本把仓库根目录加入sys.path并调用django.setup(),确保基准用例可以像正常后端代码一样使用 ORM、模型与 ClickHouse 客户端。

快速迭代单个用例

整套基准耗时较长,日常开发中最常用的是 README 提供的“快速单测”模式:

asv run --config ee/benchmarks/asv.conf.json --bench track_lifecycle --quick

--bench track_lifecycle是一个正则表达式,匹配任何名称包含track_lifecycle的用例;--quick则只让每个用例运行一次,用于快速验证逻辑正确性而非采集稳定数据。关于asv run的更多参数(如--steps--date-period--record-samples),README 建议查阅 asv 官方文档。

基准用例剖析:QuerySuite 到底在测什么

benchmarks.py 是套件的用例主体,其核心是一个名为QuerySuite的类。先看它的类级配置:

class QuerySuite: timeout = 3000.0 # Timeout for the whole suite version = "v001" # Version. Incrementing this will invalidate previous results
  • timeout = 3000.0:整个套件的超时上限(秒),避免单个提交的异常查询拖垮整轮基准;
  • version = "v001":套件版本号。这是一个容易被忽略却非常重要的设计——一旦基准用例本身发生结构性变化(例如新增了被测查询、改变了数据准备逻辑),旧版本的结果与新版本不可直接比较,此时应递增版本号使历史结果失效,防止回归检测出现误判。

当前QuerySuite定义了 5 个基准用例,全部通过@benchmark_clickhouse装饰器标记为被测目标:

用例名被测功能特别说明
track_earliest_timestamp查询团队最早事件时间戳对应 timestamp_utils.py 中的get_earliest_timestamp_unfiltered,是许多查询的时间范围默认值来源
track_event_property_values事件属性取值查询($browserno_materialized_columns()上下文内执行,模拟无物化列场景
track_event_property_values_materialized同上,但允许使用物化列与上一用例成对,用于量化物化列收益
track_person_property_values用户属性取值查询($browser同样在无物化列上下文内执行
track_person_property_values_materialized同上,但允许使用物化列与上一用例成对

四个属性取值用例实际复用同一个执行入口_run_event_property_values/_run_person_property_values,它们都通过PropertyValuesQueryRunner运行一个标准的PropertyValuesQueryproperty_type区分 EVENT 与 PERSON),并以ExecutionMode.CALCULATE_BLOCKING_ALWAYS模式强制同步阻塞执行——确保查询完整跑完,指标采集落在真实执行上,而不是被缓存或异步机制“糊弄”过去。

setup:基准数据准备的关键

QuerySuite.setup()是每个提交运行前都会执行的准备逻辑,其作用有三:

  1. 物化列准备:按照模块级常量MATERIALIZED_PROPERTIES的定义,为events表物化$current_url$event_type$host,为person表物化$browseremail,并通过backfill_materialized_columns(..., backfill_period=timedelta(days=1_000))回填过去 1000 天的历史数据。materializebackfill_materialized_columns均来自 ee/clickhouse/materialized_columns/analyze.py,与生产环境的物化列管理共用同一套实现,保证基准环境与生产语义一致。
  2. 团队数据准备:由于基准服务器上的数据约定ID=2,setup 会优先查找Team id=2,不存在则创建一个名为 "The Bakery" 的团队并指定id=2,确保后续查询总是作用于同一份团队数据。
  3. 人群(Cohort)准备:查找或创建名为 "benchmarking cohort" 的人群,其筛选条件为person.email包含.com,创建后立即调用cohort.calculate_people_ch(pending_version=0)计算人群成员,供未来人群相关基准用例使用。

这套 setup 逻辑保证了无论跑在哪个提交上,基准数据的“形状”都保持一致,这正是 README 强调的“稳定结果”的落地实现。

运行原理:@benchmark_clickhouse装饰器如何采集指标

性能基准最怕三件事:结果抖动、采样不足、指标口径不一致。helpers.py 通过一个装饰器和一个查询统计函数解决了这些问题。

单次执行的指标采集

def run_query(fn, *args): uuid = str(UUIDT()) tag_queries(kind="benchmark", id=f"{uuid}::${fn.__name__}") try: fn(*args) return get_clickhouse_query_stats(uuid) finally: reset_query_tags()

执行前,先用tag_queries给当前线程的后续查询打上benchmark:{uuid}::${函数名}的标签;执行后,get_clickhouse_query_stats通过SYSTEM FLUSH LOGS强制落盘查询日志,再从system.query_log中按标签匹配出该次执行产生的所有查询:

SELECT query_duration_ms, read_rows, read_bytes, memory_usage FROM system.query_log WHERE query NOT LIKE '%%query_log%%' AND query LIKE %(matcher)s AND type = 'QueryFinish'

最终聚合为四个指标:query_count(查询次数)、ch_query_time(ClickHouse 总耗时毫秒)、read_rows(读取行数)、read_bytes(读取字节数)、memory_usage(内存占用)。读取行数与内存占用是比单纯耗时更稳健的性能信号——它们不受机器负载抖动影响,能更真实地反映查询的“计算量”。

装饰器的采样策略

def benchmark_clickhouse(fn): @wraps(fn) def inner(*args): samples = [run_query(fn, *args)["ch_query_time"] for _ in range(4)] return {"samples": samples, "number": len(samples)} return inner

每个用例默认连续执行4 次采样,并以 ASV 标准的{"samples": [...], "number": 4}结构返回——这正好对应 README 中--quick模式“只运行一次”的行为差异(快速模式下number为 1)。4 次采样的均值与分布由 asv 负责统计,用于计算置信区间与回归判定。

no_materialized_columns:量化物化列收益的开关

@contextmanager def no_materialized_columns(): "Allows running a function without any materialized columns being used in query" cast(Any, get_enabled_materialized_columns)._cache = { ("events",): (now(), {}), ("person",): (now(), {}), } yield cast(Any, get_enabled_materialized_columns)._cache = {}

PostHog 的查询编译器通过get_enabled_materialized_columns判断属性是否可走物化列。该上下文管理器临时把此函数对eventsperson的缓存置为空映射,让编译器认为“没有可用物化列”,从而在同一数据、同一查询下获得“无物化列 vs 有物化列”的对照数据。这正是 benchmarks.py 中四对..._values/..._materialized用例的意义:用同一把尺子量化 PostHog 物化列特性带来的真实性能收益。

在 CI 中持续运行:master 每日基准与 PR 性能检测

README 的 FAQ 部分描述了基准套件在团队协作流程中的实际地位:

  • master 分支每日自动运行:基准测试每天对 master 分支执行一次,形成持续的性能时间序列,任何历史回归都能通过时间轴发现;
  • PR 标记performance触发评论:如果你的分支包含显著的查询性能改动,给 PR 打上performance标签,CI action 会在 PR 上自动运行基准并评论基准结果,把性能影响直接呈现在评审上下文中。

这意味着性能治理不是“事后追责”,而是嵌入到了日常代码评审流程中。若你的改动涉及查询性能(例如调整了 HogQL 编译器、物化列逻辑或 ClickHouse 查询生成),记得主动添加performance标签以获取基准反馈。

新增基准用例:两条规则

README 给出的新增规范非常精简:

Edit thebenchmarks.pyfile as needed. Use@benchmark_clickhousedecorator to select tests to run

即:

  1. 在 benchmarks.py 的QuerySuite类中添加新的方法;
  2. @benchmark_clickhouse装饰该方法,即可被 asv 自动发现并纳入基准。

结合现有代码,写一个新用例至少要注意两点:一是方法内应真正执行到目标查询(可参考现有用例通过PropertyValuesQueryRunner等 Query Runner 触发完整查询链路);二是若用例依赖特定数据形态(如人群、物化列),需在setup()中补充对应的数据准备逻辑,否则基准环境无法保证一致性。

回填历史基准:把性能曲线补到过去

当你需要评估“过去一段时间”的查询性能走势,或者新基准用例需要历史基线时,README 提供了回填(backfilling)流程:

# 1) 将历史结果仓库克隆到套件目录下 # 将 benchmark-results 克隆到 ee/benchmarks/results # 2) 对过去约 4 天(或任意历史区间)的提交运行基准 CLICKHOUSE_HOST=X CLICKHOUSE_USER=X CLICKHOUSE_PASSWORD=X CLICKHOUSE_DATABASE=posthog \ asv run --config ee/benchmarks/asv.conf.json --date-period 4d master~500.. # 3) 发布结果并提交到 benchmark-results 仓库 asv publish

master~500..表示从 master 向前 500 个提交直到当前 HEAD 的全部历史提交;--date-period 4d则按时间窗口选取提交。回填完成后asv publish会生成静态 HTML 报告(输出目录对应 asv.conf.json 中的html_dir: results/docs),将增量结果合入历史仓库。README 同时提示:若对回填细节有疑问,可直接以仓库中的 benchmark GitHub Action 工作流作为执行参考。

measure.sh:脱离 ASV 的单查询深度剖析

除了 ASV 套件,ee/benchmarks还提供了一个独立的测量脚本 measure.sh,用于对单个 SQL 文件进行针对性分析。其定位与 ASV 互补:ASV 负责长期回归追踪,measure.sh 负责“当场把一条查询的性能与执行计划看透”。

用法与参数

./ee/benchmarks/measure.sh \ --clickhouse-server clickhouse-server \ --tunnel-server some-server \ --password PW \ --query-file some-query.sql

支持的全部参数如下:

参数含义
-h, --help打印帮助信息
-q, --query-file要测量的查询文件路径
-s, --clickhouse-serverClickHouse 服务器地址
-t, --tunnel-server用于 SSH 隧道访问 ClickHouse 的中转服务器
-u, --userClickHouse 用户(默认default
-p, --passwordClickHouse 用户密码
--explain输出查询执行计划(EXPLAIN PIPELINE graph=1, header=1)
--drop-cache执行前清空 ClickHouse mark cache(切勿在生产环境使用
--no-flamegraphs跳过火焰图生成

执行细节

脚本会为查询附加一组剖析设置后发送执行,包括开启内省函数(allow_introspection_functions=1)、实时与 CPU 剖析器采样周期(40ms)、内存剖析步长(1MB)与采样概率(0.01)、禁用未压缩缓存(use_uncompressed_cache=0)并限制最大执行时间 400 秒——这些设置确保能采集到采样栈与内存火焰图所需的信号。

查询执行完毕后,脚本依次执行SYSTEM FLUSH LOGS,从system.query_log反查该查询的query_id,并输出event_timequery_duration_msread_rowsread_bytesresult_rowsmemory_usage、涉及表与列等完整统计。若未禁用火焰图,还会调用clickhouse-flamegraph工具按 query-id 生成火焰图并用浏览器打开,便于直观定位热点函数。

若指定了--tunnel-server,脚本会先通过ssh -L建立本地端口转发(8124 -> clickhouse-server:8123),让本机以localhost:8124访问 ClickHouse,适合无法直连内网节点的场景。--drop-cache通过SYSTEM DROP MARK CACHE在每次执行前清空 mark 缓存,用于测量冷缓存下的真实性能,README 与脚本帮助信息都明确警告不要在生产环境使用。

FAQ 速查:把常用操作汇总成一张表

场景操作
检查我的 PR 是否影响查询性能在 PR 上添加performance标签,CI 会自动运行基准并评论结果
本地装好工具pip install asv virtualenv
跑全部基准asv machine --machine ci-benchmarks --config ee/benchmarks/asv.conf.json,再带环境变量执行asv run --config ee/benchmarks/asv.conf.json
快速迭代单个用例asv run --config ee/benchmarks/asv.conf.json --bench track_lifecycle --quick
添加新用例在 benchmarks.py 中添加方法并用@benchmark_clickhouse装饰
回填历史结果克隆 benchmark-results 至ee/benchmarks/resultsasv run ... --date-period 4d master~500..,再asv publish
单条 SQL 深度剖析使用 measure.sh,配合--explain与火焰图

总结

PostHog 的 ClickHouse 基准测试套件是一套“框架 + 用例 + 指标采集 + CI 联动 + 独立剖析工具”的完整性能治理方案:ASV 负责环境管理与长期回归检测,@benchmark_clickhouse装饰器基于system.query_log稳定采集耗时、读取行数与内存指标,QuerySuite通过物化列对照用例量化特性收益,performance标签把性能反馈嵌入 PR 评审,measure.sh则为单查询提供执行计划与火焰图级剖析能力。无论是排查线上查询性能问题、评估自己的优化改动,还是为 PostHog 贡献新的查询路径,这套套件都提供了可复制、可对比、可追溯的实践范式。

【免费下载链接】posthog:hedgehog: PostHog is the leading platform for building self-driving products. Our developer tools – AI observability, analytics, session replay, flags, experiments, error tracking, logs, and more – capture all the context agents need to diagnose problems, uncover opportunities, and ship fixes. Steer it all from Slack, web, desktop, or the MCP.项目地址: https://gitcode.com/GitHub_Trending/po/posthog

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询