ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

PostHog Web Analytics 架构与开发指南:从前端 Tiles 到 HogQL 查询与 Sessions 表

PostHog Web Analytics 架构与开发指南:从前端 Tiles 到 HogQL 查询与 Sessions 表 PostHog Web Analytics 架构与开发指南从前端 Tiles 到 HogQL 查询与 Sessions 表【免费下载链接】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 开源仓库中 Web Analytics网页分析模块的深度开发指南。Web Analytics 是 PostHog 面向匿名流量、以会话session属性为核心的强观点opinionated分析产品其前端由WebAnalyticsScene与 Tiles 拼装而成后端则依托 HogQL 查询运行器Query Runner和基于 ClickHouseAggregatingMergeTree的 sessions 预聚合表。读完本文你将掌握 Web Analytics 的前后端数据流、HogQL 快照测试与/queryAPI 调试方法、sessions 表的聚合原理以及 posthog-js 采集会话与页面浏览的完整机制。一、整体工作原理从 Scene 到 Query 的前后端链路Web Analytics 页面在前端以「场景Scene」为导航单元启动其核心调用链如下入口 SceneWebAnalyticsScenefrontend/src/scenes/web-analytics/WebAnalyticsScene.tsx是该页面的前端导航入口内部渲染主 UI 组件WebAnalyticsDashboardfrontend/src/scenes/web-analytics/WebAnalyticsDashboard.tsx。Tiles 渲染WebAnalyticsDashboard渲染Tiles/组件Tiles从 KeaPostHog 使用的状态管理库中获取 tile 列表并逐项渲染。Tiles 数据源tile 列表由webAnalyticsLogicfrontend/src/scenes/web-analytics/webAnalyticsLogic.tsx中的tilesselector 创建。这是一个规模很大的函数返回页面上每个 tile 的数据tile 的类型由WebAnalyticsTile定义。在 frontend/src/scenes/web-analytics/common.ts 中可以确认该联合类型由五种 tile 组成export type WebAnalyticsTile QueryTile | TabsTile | ReplayTile | ErrorTrackingTile | SectionTile其中SectionTile还支持递归嵌套tiles: WebAnalyticsTile[]实现分组布局GraphsTab、SourceTab、DeviceTab、PathTab等枚举则定义了图表、来源、设备、路径等页签的拆分维度。Tile 的渲染组件不同类型的 tile 对应不同组件核心是QueryTileItem/与TabsTileItem/。它们调用WebQuery/——该组件为不同种类的查询补充 UI过滤条件、展示模式等但所有 tile 最终都会包含一个Query/。Query/的职责Query是前端真正负责「运行并可视化一条查询」的组件涵盖网络请求、缓存、表格渲染等全部逻辑。进入后端查询通过 API 进入 Django 层后get_query_runnerposthog/hogql_queries/query_runner.py会根据查询的kind字段分派到具体的查询运行器。以 Web Overview 为例其运行器是WebOverviewQueryRunner当前源码位于 products/web_analytics/backend/hogql_queries/web_overview.py。这类运行器都具备两个关键方法to_query根据输入参数生成 SQLHogQL ASTcalculate在 ClickHouse 上执行查询并返回响应。从get_query_runner的分派逻辑posthog/hogql_queries/query_runner.py可以看到来自 Web Analytics 产品的查询通过tags.productKey web_analytics标记被识别并会进一步分派到WebTrendsQueryRunner、WebCalendarHeatmapTrendsQueryRunner等 Web Analytics 专属子类未命中专属路径时则回退到标准的TrendsQueryRunner。这套「tags 标记 兜底回退」的设计保证了旧查询形态不受破坏。Schema 的双向同步查询的输入输出类型用 TypeScript 定义仓库内脚本会将其转换为 Pydantic 模型供后端类型提示使用对应 frontend/src/queries/schema/schema-general.ts 与 posthog/schema.py。二、如何重新生成 Schema查询类型定义TS与 Pydantic 模型Python的同步在仓库根目录执行一条命令pnpm run schema:build该命令在 package.json 中定义为schema:build: bin/hogli build:schema即由 HogLi 构建工具完成 TS → Pydantic 的代码生成。修改schema-general.ts中任何查询类型如WebOverviewQuery、WebStatsTableQuery后都应重新运行此命令让 posthog/schema.py 同步更新否则后端类型提示与校验会与前端定义脱节。三、HogQL 查询示例与测试关于 Web Analytics 查询的完整文档位于 frontend/src/scenes/web-analytics/docs/hogql_queries.md覆盖如何通过快照测试查看生成的 HogQL 查询如何使用 PostHog API 直接测试查询所有 Web Analytics 查询类型overview、trends、breakdowns的查询结构模式所有 breakdown 类型及其对应的 HogQL 字段Event 属性与 Session 属性的使用模式周期对比period comparison与跳出率bounce rate的计算细节修改与自定义查询的实用技巧。3.1 查看查询快照以快照测试为「活文档」查看 Web Analytics 实际生成的 HogQL 查询最直接的方式是阅读快照测试文件products/web_analytics/backend/hogql_queries/test/test_sample_web_analytics_queries.py。该测试文件的文档字符串明确指出其快照是 Web Analytics 各组件的 HogQL 查询「活文档」living documentation既用于在开发过程中检测查询的无意变更也能在支持工单中回答「Web Analytics 到底跑了什么查询」并可直接把 HogQL 语句粘贴到 SQL 编辑器或/queryAPI 中复用。其覆盖范围包括Web overview 查询含与不含过滤条件两种形态Web trends 查询唯一访客、页面浏览、会话随时间变化全部 24 种 breakdown 类型Page、InitialPage、DeviceType、Country 等Event 属性过滤例如按 pathname 过滤Session 属性过滤例如按 channel type 过滤。3.2 重新生成快照修改查询逻辑后用--snapshot-update重新生成 HogQL 快照pytest products/web_analytics/backend/hogql_queries/test/test_sample_web_analytics_queries.py --snapshot-update快照文件本身存储在products/web_analytics/backend/hogql_queries/test/__snapshots__/下如test_sample_web_analytics_queries.hogql.ambr。快照测试由系统自动维护更新是查询生成的「真相来源」source of truth。3.3 通过/queryAPI 直接测试查询第一步创建个人 API Key进入 PostHog 实例 → Settings → Personal API keys路径形如/project/project_id/settings/user-api-keys创建 key 时至少勾选Query: Read权限复制生成的 token。第二步发起查询请求Web Overview 查询示例curl -X POST https://app.posthog.com/api/projects/:project_id/query \ -H Authorization: Bearer PERSONAL_API_KEY \ -H Content-Type: application/json \ -d { query: { kind: WebOverviewQuery, dateRange: { date_from: 2025-10-01, date_to: 2025-10-31 }, properties: [] } }Web Stats Table 带 Breakdown 的查询示例curl -X POST https://app.posthog.com/api/projects/:project_id/query \ -H Authorization: Bearer PERSONAL_API_KEY \ -H Content-Type: application/json \ -d { query: { kind: WebStatsTableQuery, dateRange: { date_from: 2025-10-01, date_to: 2025-10-31 }, breakdownBy: Page, limit: 10, properties: [] } }带 Event 属性过滤的查询示例curl -X POST https://app.posthog.com/api/projects/:project_id/query \ -H Authorization: Bearer PERSONAL_API_KEY \ -H Content-Type: application/json \ -d { query: { kind: WebStatsTableQuery, dateRange: { date_from: 2025-10-01, date_to: 2025-10-31 }, breakdownBy: Page, limit: 10, properties: [ { key: $pathname, operator: exact, value: /pricing } ] } }Web Trends 查询唯一访客随时间变化该查询驱动 Web Analytics Graphs 页签中的 Unique visitors 趋势线curl -X POST https://app.posthog.com/api/projects/:project_id/query \ -H Authorization: Bearer PERSONAL_API_KEY \ -H Content-Type: application/json \ -d { query: { kind: TrendsQuery, dateRange: { date_from: 2025-10-01, date_to: 2025-10-31 }, interval: day, series: [ { event: $pageview, kind: EventsNode, math: dau, name: Pageview, custom_name: Unique visitors } ], trendsFilter: { display: ActionsLineGraph }, filterTestAccounts: true } }其他趋势查询变体页面浏览数把math从dau换成total会话数把math换成unique_session。3.4 直接使用快照中的原生 HogQL 查询上面的示例使用的是WebOverviewQuery、WebStatsTableQuery这类高层查询类型你也可以直接从快照文件中取原生 HogQL/SQL使用获得完全的控制力与定制自由度。在快照中定位查询打开__snapshots__下的.hogql.ambr文件每个快照以# name: TestSampleWebAnalyticsQueries.test_xxx命名将三重引号之间的 HogQL 语句复制出来即可。以HogQLQuery类型调用curl -s -X POST https://app.posthog.com/api/projects/:project_id/query \ -H Authorization: Bearer PERSONAL_API_KEY \ -H Content-Type: application/json \ -d - EOF { query: { kind: HogQLQuery, query: SELECT uniq(session_person_id) AS unique_users, sum(filtered_pageview_count) AS total_filtered_pageview_count FROM (SELECT any(events.person_id) AS session_person_id, session.session_id AS session_id, countIf(or(equals(event, $pageview), equals(event, $screen))) AS filtered_pageview_count FROM events WHERE and(notEquals(events.$session_id, NULL), or(equals(event, $pageview), equals(event, $screen)), and(greaterOrEquals(timestamp, toDateTime(2025-10-01 00:00:00)), lessOrEquals(timestamp, toDateTime(2025-10-31 23:59:59)))) GROUP BY session_id) LIMIT 50000 } } EOF在 SQL 编辑器中测试进入 PostHog → Data Management → SQL Editor路径/project/:project_id/hogql粘贴快照中的 HogQL 语句点击 Run query 即可。SQL 编辑器底层使用的就是HogQLQuery因此在编辑器里可行的写法在 API 中同样可行。一个完整可用的示例源自test_web_overview_query_snapshotSELECT uniq(session_person_id) AS unique_users, sum(filtered_pageview_count) AS total_views, uniq(session_id) AS unique_sessions FROM ( SELECT any(events.person_id) AS session_person_id, session.session_id AS session_id, countIf(or(equals(event, $pageview), equals(event, $screen))) AS filtered_pageview_count FROM events WHERE and( notEquals(events.$session_id, NULL), or(equals(event, $pageview), equals(event, $screen)), and( greaterOrEquals(timestamp, toDateTime(2025-10-01 00:00:00)), lessOrEquals(timestamp, toDateTime(2025-10-31 23:59:59)) ) ) GROUP BY session_id ) LIMIT 50000对应 API 请求把上述 SQL 压缩成单行填入query: { kind: HogQLQuery, query: ... }预期响应结构如下{ results: [[12345, 45678, 23456]], columns: [unique_users, total_views, unique_sessions], types: [UInt64, UInt64, UInt64], hogql: SELECT ..., timings: [...] }3.5 自定义修改查询的常用技巧修改日期范围替换 WHERE 子句中的两个时间条件greaterOrEquals(timestamp, toDateTime(2025-11-01 00:00:00)), lessOrEquals(timestamp, toDateTime(2025-11-30 23:59:59))追加过滤条件WHERE and( notEquals(events.$session_id, NULL), or(equals(event, $pageview), equals(event, $screen)), -- 在此追加过滤条件 equals(properties.$pathname, /pricing), equals(session.$channel_type, Paid Search) )调整聚合在 SELECT 中追加列例如avg(session_duration) AS avg_duration、max(session_duration) AS max_duration。调整 LIMIT例如把结尾的LIMIT 50000改为LIMIT 100。3.6 原生 HogQL 的性能与调试要点查询性能务必使用具体日期范围以缩减扫描数据量。ClickHouse 这类 OLAP 数据库是资源密集型的若不加约束尤其是 sorting key 字段会拉取海量数据。尽量在 WHERE 中包含 sorting key 字段timestamp、event等。长查询用异步模式async: trueclient_query_id: my-custom-query-123用client_query_id在 PostHog 的查询日志Settings → System → Query Log中追踪查询。响应结构results为结果行数组columns与 SELECT 列一一对应types是各列的 ClickHouse 类型hogql是实际执行的 HogQL调试利器timings是执行耗时分解。常见坑JSON 内嵌时不要忘记转义引号时间值必须使用toDateTime()函数session.*的会话关联是自动的不要手动加 JOINHogQL 中比较用equals()函数而非运算符。3.7 查询结构模式Session 属性 vs Event 属性Web Analytics 大量使用session 属性而非 person 属性Session 属性session.$entry_pathname、session.$channel_type、session.$is_bounceEvent 属性events.properties.$pathname、events.properties.$browser。周期对比Period Comparison使用带时间条件的*If函数内层查询用or(current_period, previous_period)一次性取回两个周期的数据-- 当前周期 uniqIf(person_id, and( greaterOrEquals(start_timestamp, toDateTime(2025-10-01 00:00:00)), lessOrEquals(start_timestamp, toDateTime(2025-10-31 23:59:59)) )) AS current_users -- 上一周期 uniqIf(person_id, and( greaterOrEquals(start_timestamp, toDateTime(2023-12-01 00:00:00)), lessOrEquals(start_timestamp, toDateTime(2023-12-31 23:59:59)) )) AS previous_users跳出率Bounce Rate的注意点按页面展示跳出率时计数visitors/views用events.properties.$pathname正在浏览的页面而跳出率用session.$entry_pathname会话起始页面。因此「/pricing 的跳出率」表示的是从 /pricing 开始的会话中的跳出比例。3.8 全部 Breakdown 字段对照表不同的 breakdown 类型使用不同的字段权威清单以 posthog/schema.py 中的WebStatsBreakdown为准Breakdown 类型字段类型Pageevents.properties.$pathnameEventInitialPagesession.$entry_pathnameSessionExitPagesession.$end_pathnameSessionPreviousPageevents.properties.$prev_pathnameEventScreenNameevents.properties.$screen_nameEventExitClicksession.$exit_click_targetSessionInitialReferringDomainsession.$entry_referring_domainSessionInitialUTMSourcesession.$entry_utm_sourceSessionInitialUTMMediumsession.$entry_utm_mediumSessionInitialUTMCampaignsession.$entry_utm_campaignSessionInitialUTMContentsession.$entry_utm_contentSessionInitialUTMTermsession.$entry_utm_termSessionInitialUTMSourceMediumCampaign组合字段SessionInitialChannelTypesession.$channel_typeSessionBrowserproperties.$browserEventOSproperties.$osEventDeviceTypeproperties.$device_typeEventViewportproperties.$viewport_widthEventCountryproperties.$geoip_country_codeEventRegionproperties.$geoip_subdivision_1_codeEventCityproperties.$geoip_city_nameEventLanguageproperties.$browser_languageEventTimezoneproperties.$timezoneEventFrustrationMetrics计算得出Session常用过滤模式速查日期范围用greaterOrEquals/lessOrEquals配toDateTime()事件类型过滤用or(equals(event, $pageview), equals(event, $screen))。3.9 在测试中调试查询想在测试中打印 HogQL 语法而非 ClickHouse SQL可以这样写from posthog.hogql.printer import prepare_and_print_ast from posthog.hogql.context import HogQLContext # In your test runner WebOverviewQueryRunner(teamteam, queryquery) query_ast runner.to_query() context HogQLContext( team_idteam.pk, enable_select_queriesTrue, ) # prepare_and_print_ast returns a (sql, prepared_ast) tuple hogql, _ prepare_and_print_ast(query_ast, contextcontext, dialecthogql) print(hogql)四、Sessions 表Web Analytics 的核心数据支柱4.1 工作原理AggregatingMergeTree 持续聚合虽然我们习惯称之为「sessions 表」它实际上是一组协同工作的 ClickHouse 表分片表、分布式表 物化视图。其核心机制使用 ClickHouse 的AggregatingMergeTree引擎按 session ID 持续聚合事件事件通过物化视图在插入时自动聚合无需额外 ETL同一 session ID 的所有事件按照排序键sorting key合并。在 posthog/models/raw_sessions/sessions_v3.py 中可以确认 v3 表的定义排序键为ORDER BY (team_id, session_timestamp, session_id_v7)按月份分区PARTITION BY toYYYYMM(session_timestamp)posthog/models/raw_sessions/sessions_v3.pysession_id_v7以UInt128存储posthog/models/raw_sessions/sessions_v3.py会话时间戳可由其按bitShiftRight(session_id_v7, 80)推导天然具备时间有序性表头注释还说明不保证 ClickHouse 会把一个会话的所有事件合并为一行因此任何针对该表的查询都应再次按session_id聚合——HogQL 的 session 表会自动完成这一步HogQL 使用者无需操心。v3 相对 v2 的主要升级见该文件文档字符串用属性映射property map存储低层级广告 ID便于后续新增广告网络将广告 ID 的「存在性」与「值」分离存储channel type 计算只需读 1 bit 而非最长 100 字符的 gclid 字符串每个事件只解析一次 JSON而非每列一次显著节省 CPU移除大量废弃字段新增专用 channel type 属性列减少计算 channel type 时的 timestamp 读取次数。4.2 聚合了哪些内容Sessions 表存储预计算的会话属性是 Web Analytics 分析的基础会话时间戳开始、结束进入/退出 URL以及所有访问过的 URL设备信息浏览器、OS、设备类型、视口地理位置数据国家、地区、城市、时区归因数据UTM 参数、referring domain、gclid/fbclid 等 15 广告网络的广告 ID渠道类型由归因数据计算得出事件计数pageviews、screens、autocapture 事件跳出检测使用高效的uniqUpTo聚合会话期间观测到的 feature flag 值会话回放session replay是否存在。4.3 为什么 Session 属性是 Web Analytics 的中心Session 属性让快速、面向归因的分析成为可能归因分析进入 UTM 参数、referring domain、channel type 天然是会话级属性行为分析进入/退出页面、跳出率、页面计数天然以会话为作用域匿名友好无需 person 识别即可立即对匿名用户生效——这正是 Web Analytics 与 Product Analytics 最本质的差异。4.4 Sessions 的定义会话遵循 PostHog 的会话定义会话把具有相同 session ID 的事件归为一组session ID 由 PostHog 客户端库根据不活动超时与会话重置自动管理。4.5 posthog-js 中的事件采集理解 posthog-js 如何采集会话与页面浏览是使用 Web Analytics 数据的前提相关实现可在 posthog-js 仓库的session-props.ts、sessionid.ts、page-view.ts中找到会话生命周期Session ID 使用 UUIDv7 格式生成时间有序默认 30 分钟空闲超时可通过session_id_timeout_seconds配置范围 1 分钟至 10 小时最大会话时长为 24 小时无论活动与否新会话在以下情况创建不存在既有会话、空闲超时、或超出最大时长存储会话数据存放于 cookie/localStorage形如[lastActivityTimestamp, sessionId, sessionStartTimestamp]每个浏览器标签页拥有唯一windowId刷新后保持标签页复制时生成新 ID。会话归因属性在会话开始时采集并贯穿整个会话存活期进入 URL、referring domain、UTM 参数、初始 pathname会变成事件上的$session_entry_*属性例如$session_entry_url聚合进 sessions 表用于快速归因分析。页面浏览追踪自动 vs SPA传统网站页面加载时自动采集$pageview事件SPA使用capture_pageview: history_change或defaults: 2025-05-24通过 History API 追踪导航手动方案在需要时调用posthog.capture($pageview)。页面浏览属性$pageview_id当前视图的唯一标识$prev_pageview_id、$prev_pageview_pathname、$prev_pageview_duration上一页上下文滚动指标$prev_pageview_max_scroll、$prev_pageview_max_scroll_percentage内容指标$prev_pageview_max_content、$prev_pageview_max_content_percentage。4.6 Sessions 表相关实现文件posthog/models/raw_sessions/sessions_v3.pySQL 表定义与物化视图查询posthog/hogql/database/schema/sessions_v3.py向查询暴露 sessions 表的 HogQL schema其中aggregate_fields定义了把原始物化状态还原成可查询列的聚合逻辑并自动补上session_id_v7字段、聚合 ad IDs 等posthog/clickhouse/migrations/搜索sessions_v3可找到相关迁移。五、什么是 HogQLWeb Analytics 查询用 HogQL 编写有时称为 Hog SQL 或 PostHog SQL。其核心思想是你可以通过解析字符串或在 Python 中直接构造 AST 节点两种方式构建 HogQL 查询两者都会被转换为 ClickHouse SQL 执行存在lazy joins惰性连接让属性访问更简单例如你可以直接写SELECT person.properties from events而无需手写 events 与 persons 之间的 JOIN——该 JOIN 只在实际需要时才被注入查询。在WebOverviewQueryRunner的实现products/web_analytics/backend/hogql_queries/web_overview.py中可以清楚看到这条「AST → SQL」链路的工程实践to_query()会根据不同执行策略返回不同的ast.SelectQueryno_join_select、session_id_set_select、outer_select再交由execute_hogql_query打印并执行。其中session_id_set_select是一种优化策略当所有过滤条件都可在 events 侧求值时先扫描过滤后的事件收集匹配的 session ID 集合再只对该集合内的会话做聚合通过 GLOBAL IN 把 ID 集合一次性广播到各分片避免在每个分片上重复执行会话子查询。这说明 Web Analytics 的查询不仅有「能用」的 baseline还有针对大数据集的执行路径优化。六、事件从哪里来绝大多数 Web Analytics 用户使用 posthog-js 生成事件。需要特别强调的是Web Analytics 与 Product Analytics 的事件是同一种事件——两个产品只是对同一批事件的不同视图。二者最大的区别在于 Web Analytics 更「强观点」opinionated它专门设计为对匿名事件友好匿名事件成本更低其手段就是重度依赖 session 属性而非 person 属性。这也是为什么本文反复强调 session 属性在 Web Analytics 中的中心地位。七、Toolbar 中的 Web Analytics 功能部分 Web Analytics 能力也出现在 PostHog Toolbar 中例如 Toolbar 会在当前页面上展示 Web Vitals网页核心性能指标。相关前端逻辑可以在 frontend/src/scenes/web-analytics/PagePerformance.tsx 等页面性能组件中看到端倪。八、进一步学习资源ClickHousePostHog 维护官方 ClickHouse 手册ClickHouse 官方提供视频课程可跳过「从其他工具迁移到 ClickHouse」类视频《Designing Data-Intensive Applications》第 3 章系统介绍了 OLAP / 列式数据库——若已熟悉 OLAP 概念直接上 ClickHouse 课程收益更大这本书更侧重概念普及而非 ClickHouse 细节。HogQL 深入继续阅读 frontend/src/scenes/web-analytics/docs/hogql_queries.md 与仓库内的 posthog/hogql 实现parser、printer、database schema。前端架构从 frontend/src/scenes/web-analytics/WebAnalyticsScene.tsx 出发沿 Tiles → webAnalyticsLogic → Query 的调用链阅读并结合 frontend/src/scenes/web-analytics/webAnalyticsLogic.test.ts 等测试理解各 selector 的行为约定。【免费下载链接】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),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进