PostHog 数据查询指南:深入解析system.session_recordings会话录制元数据模型
【免费下载链接】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
导读
system.session_recordings是 PostHog 中用于检索、过滤会话录制(Session Recording)记录的元数据系统表,它把 SDK 采集的录制信息以"录制一条元数据"的形式暴露给 HogQL/SQL 查询。本文以 PostHog 仓库中 querying-posthog-data 技能的 Schema 参考文档 为核心骨架,结合 SessionRecording 模型源码 与配套查询示例,完整讲解该表的字段语义、存储分层、关联关系与软删除处理,并给出可直接运行的查询语句。读完本文,你将能在posthog:execute-sql中准确列出、筛选和定位会话录制记录,并理解哪些字段来自 Postgres、哪些来自 ClickHouse。
一、表概览:元数据在 Postgres,重放数据在 ClickHouse 与对象存储
官方 Schema 参考文档对system.session_recordings的定位非常明确:它保存的是由 PostHog SDK 捕获的会话录制的元数据(Metadata)。实际的重放(replay)数据并不在这张表里,而是存放在 ClickHouse 和对象存储中;这张 Postgres 表只存储"录制级"的元数据,供列表展示与过滤使用。
从源码可以印证这一分层设计。在 SessionRecording 模型 中,持久化到数据库的字段只包含录制的基本信息(session_id、team、时间区间、各类计数、存储路径等),而一批"动态字段"(viewed、ongoing、activity_score、expiry_time、recording_ttl、total_size、event_count、matching_events、_metadata)则不是数据库列,它们由 ClickHouse 或 S3 在运行时加载填充:
# posthog/session_recordings/models/session_recording.py # DYNAMIC FIELDS viewed: Optional[bool] = False ongoing: Optional[bool] = None activity_score: Optional[float] = None expiry_time: Optional[datetime] = None recording_ttl: Optional[int] = None total_size: Optional[int] = None event_count: Optional[int] = None _metadata: Optional[RecordingMetadata] = None这意味着:当你通过system.session_recordings查询时,能可靠查询到的是 Postgres 中落库的元数据列;而活动统计类字段(见下节)可能为 NULL——这与原文档中"Activity data (click_count, duration, etc.) is populated from ClickHouse and may be NULL for older recordings"的提示完全一致。
两条数据通路
模型中的load_metadata()方法(源码位置)展示了元数据的两种来源:
- V2 录制路径(
full_recording_v2_path非空):元数据已完整持久化在模型/Postgres 中,无需额外加载; - 旧版录制路径:通过
SessionReplayEvents().get_metadata(...)从ClickHouse拉取元数据,再回填distinct_id、start_time、end_time、duration、click_count、keypress_count、active_seconds、inactive_seconds、各类 console 计数、retention_period_days、expiry_time、recording_ttl、ongoing、total_size、event_count等字段。
这正是"旧录制这些字段可能为 NULL、新录制则可能是完整值"的根因——历史数据没有经过 ClickHouse 元数据回填流程。
重要语义细节:源码注释明确指出,
active_seconds是对各时间块活跃时间的求和,当同一会话存在并发标签页时,各块活跃时间会各自计数,因此总活跃秒数可能超过实际墙钟时长;而由于只存储了汇总值,这部分重叠无法再被扣除。在基于该字段做统计口径时需注意这一点。
二、核心字段(Columns)
以下字段来自SessionRecordingDjango 模型的实际定义(源码位置),即system.session_recordings表所暴露的列(system.*表暴露的是各 Django 模型的精选子集,因此请以本表为准,而非 REST 返回的字段形状)。
| 字段 | 类型(源码) | 语义说明 |
|---|---|---|
id | UUIDT(主键) | 内部主键,使用 PostHog 标准的 UUIDT 生成 |
session_id | CharField(unique, max_length=200) | 面向用户的录制 ID,用于 URL 与 API 调用;由 posthog-js 生成,与内部id不同,注意区分 |
team_id | ForeignKey → Team | 录制所属团队,on_delete=CASCADE,删除团队级联删除录制 |
created_at | DateTimeField(auto_now_add) | 元数据行创建时间(可能为 NULL) |
deleted | BooleanField(null=True) | 软删除标记,整数语义 0/1,旧行可能为 NULL |
object_storage_path | CharField(max_length=200) | 旧版录制对象存储路径(可能为 NULL) |
full_recording_v2_path | CharField(max_length=1000) | V2 录制完整数据路径,非空时元数据已完整落库 |
distinct_id | CharField(max_length=400) | 关联到用户(person)的标识,用于与 persons 表关联 |
duration | IntegerField | 录制总时长(秒),来自 ClickHouse,旧录制可能为 NULL |
active_seconds | IntegerField | 活跃秒数(见上文重叠语义) |
inactive_seconds | IntegerField | 非活跃秒数,源码计算为max(duration - active_seconds, 0) |
start_time/end_time | DateTimeField | 录制开始/结束时间,来自 ClickHouse |
click_count/keypress_count/mouse_activity_count | IntegerField | 点击、按键、鼠标活动计数,来自 ClickHouse |
console_log_count/console_warn_count/console_error_count | IntegerField | 控制台日志/警告/错误计数,来自 ClickHouse |
start_url | CharField(max_length=512) | 录制起始 URL(截断至 512 字符,去查询参数) |
storage_version | CharField(max_length=20) | 存储版本标识 |
retention_period_days | IntegerField | 录制保留天数,来自 ClickHouse |
session_idvsid:最容易踩的坑
原文档专门强调:session_id是面向用户的 ID(用于 URL 和 API 调用),而不是内部id。从源码注释可以进一步理解其来历(源码位置):
session_id由 posthog-js 中的独立工具生成(非 UUIDT 标准),并作为唯一键(unique=True)保存,用于与其他录制相关模型建立关联;- 模型同时创建 UUIDT 格式的内部
id和唯一的session_id字段,以保持向后兼容; - 所有与会话录制相关的其他模型(如播放列表关联、已查看记录)都通过这个唯一的
session_id建立链接。
因此在查询中,如果你要通过 API/UI 拿到的 ID 反查录制,应使用session_id进行过滤,而不是id。
三、关键关联关系(Key Relationships)
原文档列出三条核心关联,对应源码中的外键与查询模式:
- 每个录制属于一个团队(
team_id):SessionRecording.team = ForeignKey("Team", on_delete=CASCADE)(源码位置)。查询时务必用team_id限定范围,避免跨团队数据串扰。 - 录制通过
distinct_id关联到用户:录制本身不直接存 person 外键,而是通过distinct_id与 persons 关联。源码中load_person()(源码位置)通过 personhog 客户端按distinct_id解析出 Person。因此 SQL 联查时,应使用system.session_recordings.distinct_id与 persons 侧匹配。 - 录制可被加入 Session Recording Playlists:通过
SessionRecordingPlaylistItem关联(该关联表未作为系统表暴露)。播放列表本身的元数据模型见 models-session-recording-playlists.md.j2:播放列表分为collection(手动精选集合)与filters(按保存的筛选条件动态匹配)两种类型。
四、重要注意事项与查询实践
4.1 录制由 SDK 创建,而非 API
原文档明确:"Recordings are created by the SDK, not via the API"。即你不能通过 API 往这张表里"写入"一条录制——录制生命周期完全由 PostHog SDK(如 posthog-js)在浏览器/客户端捕获并上报触发。系统表中的行是采集流程的产物,查询侧只读。
4.2 软删除过滤:ifNull(deleted, 0) = 0
deleted字段是整数语义的 0/1,且旧行可能为 NULL(源码中deleted = BooleanField(null=True, blank=True))。因此标准过滤写法是:
SELECT session_id, start_time, duration, active_seconds, click_count FROM system.session_recordings WHERE team_id = 1 AND ifNull(deleted, 0) = 0 ORDER BY start_time DESC LIMIT 20不要直接写WHERE deleted = 0——那会把deleted IS NULL的旧录制排除在结果之外。
4.3 活动统计字段可能为 NULL
click_count、duration、active_seconds、console 系列计数等字段由 ClickHouse 回填(见第一节的数据通路),对旧录制可能为 NULL。在聚合前建议用coalesce/ifNull处理,例如:
SELECT session_id, ifNull(active_seconds, 0) AS active_seconds, ifNull(click_count, 0) AS clicks FROM system.session_recordings WHERE team_id = 1 AND ifNull(deleted, 0) = 0 AND ifNull(active_seconds, 0) > 5 ORDER BY active_seconds DESC4.4 联查用户(persons)
按distinct_id关联到人员信息:
SELECT r.session_id, r.start_time, r.duration, p.properties['email'] AS email FROM system.session_recordings AS r LEFT JOIN persons AS p ON p.id = r.distinct_id WHERE r.team_id = 1 AND ifNull(r.deleted, 0) = 0 LIMIT 50提示:persons 属性分"事件时点"与"查询时点"两种模式,涉及
person.properties.*语义时请先阅读 person-property-modes 参考。
4.5 使用录制查询(RecordingsQuery)过滤活动指标
对于"列出某段时间内活跃时长超过 N 秒的录制"这类需求,技能库提供了标准的录制查询示例(example-session-replay.md.j2),核心参数为:
-- RecordingsQuery 语义(经渲染后的 HogQL/SQL 形态) -- order: start_time;order_direction: DESC -- date_from: -3d(近 3 天) -- having_predicates: [{"type": "recording", "key": "active_seconds", "value": 5, "operator": "gt"}] -- filter_test_accounts: false;operand: AND;limit: 20即:按start_time倒序,取近 3 天、active_seconds > 5、排除测试账号后最多 20 条录制。这类"带活动过滤的录制列表"正是system.session_recordings与 ClickHouse 活动数据配合使用的典型场景。
4.6 定位单条录制并获取完整实体
按 SKILL.md 的实体查找流程,当需要定位一条具体录制时:
- 先用
posthog:execute-sql查询system.session_recordings,通过session_id(或时间、团队、distinct_id 组合)找到目标; - 再用对应的只读工具(如
posthog:recording-get类工具)按 ID 获取完整录制实体; - 不要试图用 SQL 重建完整实体——
execute-sql只用于发现/检索,完整实体交给专用读取工具。
SELECT session_id, start_time, duration, object_storage_path, full_recording_v2_path FROM system.session_recordings WHERE team_id = 1 AND session_id = '你的录制session_id' AND ifNull(deleted, 0) = 0五、与 Session Recording Playlists 的衔接
录制与播放列表通过SessionRecordingPlaylistItem关联(关联表不暴露为系统表)。播放列表本身可查system.session_recording_playlists(详见 播放列表 Schema 参考),其中:
type = 'collection'表示手动精选的录制集合;type = 'filters'表示保存的筛选条件,会动态匹配符合条件的录制;- 播放列表用
short_id作为 API 查询键; - 同样需要用
deleted = 0过滤软删除的播放列表。
当你想知道"某个 collection 播放列表里有哪些录制"时,由于关联表未暴露,可从播放列表的filters字段(仅type='filters'时有意义)反推匹配条件,或通过录制侧信息与播放列表条目核对。
六、实操速查表
| 需求 | 写法要点 |
|---|---|
| 列出近 7 天录制 | WHERE team_id = ? AND ifNull(deleted,0)=0 AND start_time > now() - INTERVAL 7 DAY |
| 过滤活跃录制 | AND ifNull(active_seconds, 0) > 5 |
| 关联用户 | LEFT JOIN persons AS p ON p.id = r.distinct_id |
按session_id精查 | AND session_id = '...'(勿用内部id) |
| 聚合活动指标 | SELECT avg(ifNull(active_seconds,0)) ...(先处理 NULL) |
| 排除软删除 | 一律ifNull(deleted, 0) = 0 |
延伸阅读
- 技能总览与查询路径选择:SKILL.md(含 typed query 与 SQL 的选择原则、Data Schema 全表索引)
- 播放列表模型:models-session-recording-playlists.md.j2
- 录制查询示例:example-session-replay.md.j2
- 模型源码:posthog/session_recordings/models/session_recording.py
- HogQL 扩展(含 replay 相关函数与语法):hogql-extensions.md
【免费下载链接】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),仅供参考