PostHog 数据查询指南:深入解析 `system.session_recordings` 会话录制元数据模型
2026/9/17 5:40:23 网站建设 项目流程

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_idteam、时间区间、各类计数、存储路径等),而一批"动态字段"(viewedongoingactivity_scoreexpiry_timerecording_ttltotal_sizeevent_countmatching_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()方法(源码位置)展示了元数据的两种来源:

  1. V2 录制路径full_recording_v2_path非空):元数据已完整持久化在模型/Postgres 中,无需额外加载;
  2. 旧版录制路径:通过SessionReplayEvents().get_metadata(...)ClickHouse拉取元数据,再回填distinct_idstart_timeend_timedurationclick_countkeypress_countactive_secondsinactive_seconds、各类 console 计数、retention_period_daysexpiry_timerecording_ttlongoingtotal_sizeevent_count等字段。

这正是"旧录制这些字段可能为 NULL、新录制则可能是完整值"的根因——历史数据没有经过 ClickHouse 元数据回填流程。

重要语义细节:源码注释明确指出,active_seconds是对各时间块活跃时间的求和,当同一会话存在并发标签页时,各块活跃时间会各自计数,因此总活跃秒数可能超过实际墙钟时长;而由于只存储了汇总值,这部分重叠无法再被扣除。在基于该字段做统计口径时需注意这一点。

二、核心字段(Columns)

以下字段来自SessionRecordingDjango 模型的实际定义(源码位置),即system.session_recordings表所暴露的列(system.*表暴露的是各 Django 模型的精选子集,因此请以本表为准,而非 REST 返回的字段形状)。

字段类型(源码)语义说明
idUUIDT(主键)内部主键,使用 PostHog 标准的 UUIDT 生成
session_idCharField(unique, max_length=200)面向用户的录制 ID,用于 URL 与 API 调用;由 posthog-js 生成,与内部id不同,注意区分
team_idForeignKey → Team录制所属团队,on_delete=CASCADE,删除团队级联删除录制
created_atDateTimeField(auto_now_add)元数据行创建时间(可能为 NULL)
deletedBooleanField(null=True)软删除标记,整数语义 0/1,旧行可能为 NULL
object_storage_pathCharField(max_length=200)旧版录制对象存储路径(可能为 NULL)
full_recording_v2_pathCharField(max_length=1000)V2 录制完整数据路径,非空时元数据已完整落库
distinct_idCharField(max_length=400)关联到用户(person)的标识,用于与 persons 表关联
durationIntegerField录制总时长(秒),来自 ClickHouse,旧录制可能为 NULL
active_secondsIntegerField活跃秒数(见上文重叠语义)
inactive_secondsIntegerField非活跃秒数,源码计算为max(duration - active_seconds, 0)
start_time/end_timeDateTimeField录制开始/结束时间,来自 ClickHouse
click_count/keypress_count/mouse_activity_countIntegerField点击、按键、鼠标活动计数,来自 ClickHouse
console_log_count/console_warn_count/console_error_countIntegerField控制台日志/警告/错误计数,来自 ClickHouse
start_urlCharField(max_length=512)录制起始 URL(截断至 512 字符,去查询参数)
storage_versionCharField(max_length=20)存储版本标识
retention_period_daysIntegerField录制保留天数,来自 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)

原文档列出三条核心关联,对应源码中的外键与查询模式:

  1. 每个录制属于一个团队(team_idSessionRecording.team = ForeignKey("Team", on_delete=CASCADE)(源码位置)。查询时务必用team_id限定范围,避免跨团队数据串扰。
  2. 录制通过distinct_id关联到用户:录制本身不直接存 person 外键,而是通过distinct_id与 persons 关联。源码中load_person()(源码位置)通过 personhog 客户端按distinct_id解析出 Person。因此 SQL 联查时,应使用system.session_recordings.distinct_id与 persons 侧匹配。
  3. 录制可被加入 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_countdurationactive_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 DESC

4.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 的实体查找流程,当需要定位一条具体录制时:

  1. 先用posthog:execute-sql查询system.session_recordings,通过session_id(或时间、团队、distinct_id 组合)找到目标;
  2. 再用对应的只读工具(如posthog:recording-get类工具)按 ID 获取完整录制实体;
  3. 不要试图用 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),仅供参考

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

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

立即咨询