PostHog 仓库本地 PostgreSQL 只读查询实战:连接配置、安全约束与 EXPLAIN 性能分析
【免费下载链接】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 仓库内置的querying-local-postgres技能文档(.agents/skills/querying-local-postgres/SKILL.md),它规范了如何对本地 Postgres 应用数据库执行只读SQL(SELECT、EXPLAIN、EXPLAIN ANALYZE),适用于排查团队/项目/功能开关等行级数据、检查迁移与约束、分析慢查询执行计划等场景。读完本文,你将掌握 PostHog 本地开发环境 Postgres 的完整连接参数与多库布局、强制只读连接的正确命令模式(含PGOPTIONS原理)、禁止执行的写操作清单,以及基于执行计划的性能分析工作流。
存储架构背景:Postgres 管元数据,ClickHouse 管事件
PostHog 的数据存储是典型的"双引擎"架构。本技能文档首先划清了一条重要边界:
- PostgreSQL承载应用元数据:团队(teams)、项目(projects)、功能开关(flags)、用户(users)以及大量 Django 模型数据;
- ClickHouse承载分析事件数据:
events类的事件、会话回放、行为分析等原始分析数据。
因此,凡是涉及"事件"的查询问题,应使用 HogQL / ClickHouse 相关工具(仓库中另有 writing-clickhouse-queries 等技能),除非用户明确要求查询 Postgres。这一分工在源码层面同样清晰:posthog/settings/data_stores.py 在配置 DjangoDATABASES的同时,也配置了CLICKHOUSE_HOST、CLICKHOUSE_DATABASE等 ClickHouse 连接参数,两类存储的配置同处一个模块、职责分明。
何时使用本地 Postgres 查询
该技能适用于以下三类典型诉求:
- 直接查库:用户要求查询数据库、检查表结构、对 Postgres 执行 SQL;
- 调试数据问题:行级检查(例如某个 team/project/flag 行为什么看起来不对)、迁移状态核对、约束检查、重复键排查;
- 性能分析:对 Django 或应用表的只读
SELECT执行EXPLAIN/EXPLAIN (ANALYZE, …),分析执行计划。
红线:变更操作严格禁止
技能文档用一整节强调只读约束。以下操作不得运行、不得建议、不得生成,一旦用户请求写操作,应直接拒绝并说明该技能是只读的:
| 类别 | 禁止的语句 |
|---|---|
| DML | INSERT、UPDATE、DELETE、MERGE、TRUNCATE |
| DDL | CREATE、DROP、ALTER、RENAME |
| 其他写操作 | COPY ... TO program、CALL(若会变更数据)、GRANT/REVOKE |
| 危险组合 | 对非只读SELECT的内容执行EXPLAIN ANALYZE(包括WITH … SELECT)——不要把 DML 包进EXPLAIN ANALYZE,因为它会真的执行写操作 |
允许执行的操作:
SELECT(包括WITH … SELECT);EXPLAIN … SELECT(仅估算计划,不执行);EXPLAIN (ANALYZE, …) SELECT——会真实执行一次该SELECT,仅用于性能分析,且必须在只读连接上运行;SHOW、对 catalog 视图(pg_stat_*、information_schema等)的只读查询。
当用户请求写操作时,标准话术为:"This skill is read-only. I can't run INSERT/UPDATE/DELETE or other mutations. Use a DB client or migration tool for writes."
连接信息:本地默认连接与配置来源
本地默认连接(宿主机的默认入口)
本地开发环境(典型 Docker Compose + localhost 端口 5432、关闭 SSL)的连接参数如下,也是该技能日常查询优先使用的硬编码 URL:
| 设置项 | 值 |
|---|---|
| Host | localhost |
| Port | 5432 |
| User | posthog |
| Password | posthog |
| Database | posthog |
| SSL | off |
# 除非用户明确说明本地密码/库名不同,优先使用此 URL LOCAL_POSTGRES_URL='postgres://posthog:posthog@localhost:5432/posthog'等价写法:postgresql://posthog:posthog@localhost:5432/posthog。同一服务器上的其他本地库只需替换路径部分,例如...5432/posthog_persons。
这些参数并非凭空设定。从源码可验证:posthog/settings/data_stores.py 中,当TEST or DEBUG为真时,PG_HOST默认db(容器内主机名)、PGUSER默认posthog、PGPASSWORD默认posthog、PGPORT默认5432、PGDATABASE默认posthog,并据此拼出DATABASE_URL。而在宿主机上,容器内的db主机名不可达,应改用localhost,用户名/密码/库名保持一致——这正是本技能文档的默认连接来源。docker-compose.dev.yml 也印证了端口映射'5432:5432'以及DATABASE_URL: postgres://posthog:posthog@db:5432/${POSTHOG_DB_NAME:-posthog}的服务配置。
配置源(应用侧)
应用侧的数据库配置权威来源同样在 posthog/settings/data_stores.py:
- Django
DATABASES主配置(default,通过dj_database_url解析DATABASE_URL); - 可选只读副本
POSTHOG_POSTGRES_READ_HOST:设置后注册DATABASES["replica"],并挂载posthog.dbrouter.ReplicaRouter; - 绕过 PgBouncer 的直连
POSTHOG_POSTGRES_DIRECT_HOST:用于迁移等场景,会复制default配置并追加lock_timeout(默认 20000ms,可用MIGRATE_LOCK_TIMEOUT覆盖); - Persons 库
PERSONS_DB_WRITER_URL(persons 数据实际通过 personhog 服务或 posthog/persons_db.py 的非 ORM psycopg 连接访问); - 产品隔离库路由来自 products/db_routing.yaml,由 posthog/product_db_config.py 加载,按 app_label 路由到独立数据库。
连接时的常见坑
fe_sendauth: no password supplied:本地开发手册 docs/published/handbook/engineering/developing-locally.md 明确建议设置DATABASE_URL=postgres://posthog:posthog@localhost:5432/posthog并确保容器已启动;文档还提到,若端口 5432 被本机 Postgres 占用(报role "posthog" does not exist或端口绑定错误),可用lsof -i :5432排查进程。- DEBUG 模式下默认
DATABASE_URL的推导:Django 用PGHOST(默认db)、PGUSER/PGPASSWORD、PGPORT、PGDATABASE拼接出默认DATABASE_URL,匹配的是容器内主机名;从宿主机连接要用localhost,除非 shell 已导出DATABASE_URL。
同一服务器上的多个 PostgreSQL 数据库
本地 compose 中同一台服务器上存在多个逻辑库:
| 数据库 | 用途 | 依据 |
|---|---|---|
posthog | 主应用库 | 默认DATABASE_URL |
posthog_persons | Persons 库 | PERSONS_DB_WRITER_URL/PERSONS_DB_READER_URL,本地默认值见 posthog/persons_db.py(postgres://posthog:posthog@db:5432/posthog_persons) |
posthog_<name> | 产品隔离库 | products/db_routing.yaml 中的路由(如stamphog、visual_review、warehouse_sources_queue),由 docker/postgres-init-scripts/create-product-dbs.sh 读取路由文件创建,命名规则为posthog_${db_name} |
| 其他 | cyclotron 等 | 由 docker/postgres-init-scripts 下的初始化脚本创建,按需查看 |
查询时只需修改DATABASE_URL的路径部分即可切换库,例如.../posthog_persons。另外,部分 Rust 服务在 rust/README.md 中说明从rust/.env读取DATABASE_URL。
使用模式:强制只读的命令模板
始终通过PGOPTIONS='-c default_transaction_read_only=on'强制连接为只读,这样即使生成的 SQL 有误,Postgres 也会拒绝写入。
为什么用PGOPTIONS而不是SET SESSION CHARACTERISTICS?
这是本技能文档解释得最透彻的细节:psql -c "..."中的多条语句运行在同一个隐式事务中。而SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY只设置后续事务的默认值——当前已进行中的事务仍保持BEGIN时被赋予的读写模式,因此同一条-c字符串里的写操作不会被拒绝。PGOPTIONS='-c default_transaction_read_only=on'则在连接启动时设置该 GUC,所有事务(包括-c的隐式事务)一开始就是只读的。行内等价做法是:把SET TRANSACTION READ ONLY;作为-c字符串的第一条语句(它作用于当前事务,与SET SESSION CHARACTERISTICS不同)。
三种运行方式
默认——本地硬编码 URL:
PGOPTIONS='-c default_transaction_read_only=on' psql "postgres://posthog:posthog@localhost:5432/posthog" -v ON_ERROR_STOP=1 -c "SELECT 1;"方式 A——shell 中已有DATABASE_URL(例如flox activate后或手动export):
PGOPTIONS='-c default_transaction_read_only=on' psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -c "SELECT 1;"方式 B——从仓库根目录的 gitignored env 文件加载(若其中设置了DATABASE_URL):
npx dotenv -e .env -- bash -c "PGOPTIONS='-c default_transaction_read_only=on' psql \"\$DATABASE_URL\" -c 'SELECT ...'"补充约定:
- 建议从 PostHog 仓库根目录运行,使相对 env 路径能够正确解析;
- SQL 中的字符串字面量照常使用单引号;在
-c内嵌套引号时注意转义; - 默认
LIMIT 100,除非用户另有指定; - 宽行输出加
-x:psql ... -x -c "..."。
性能分析:EXPLAIN 与 EXPLAIN ANALYZE 的选用
技能文档用一张表给出了按目标选择工具的准则:
| 目标 | 使用什么 |
|---|---|
| 计划形状、估算代价、不执行 | EXPLAIN (FORMAT TEXT, COSTS),可加VERBOSE |
| 实际耗时、行数、buffer 命中 | EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)作用于SELECT |
| Buffer + WAL 统计 | BUFFERS需要ANALYZE;WAL需要ANALYZE(PostgreSQL 13+) |
安全模式:被分析的语句必须只能是SELECT(或WITH … SELECT),并在只读连接上运行:
PGOPTIONS='-c default_transaction_read_only=on' psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -c " EXPLAIN (ANALYZE, BUFFERS, VERBOSE, FORMAT TEXT) SELECT … LIMIT 100;"可选标志(按需使用):SETTINGS(展示非默认 GUC)、WAL(需配合ANALYZE)、TIMING(新版ANALYZE默认开启)。
注意事项:
EXPLAIN ANALYZE会真实运行查询,在大扫描上可能很慢或很重;探索阶段建议使用有界SELECT(合理的WHERE、贴近生产形状的LIMIT);- 对生产/共享库,分析热查询或宽查询会增加负载,应在用户在意影响时优先使用 staging、副本或非高峰时段;
- 不带
ANALYZE的EXPLAIN不执行内部语句(少数特殊情况除外),但仍只能包裹只读 SQL。
Schema 参考:表名、迁移与 Person 表
- Django 模型 → 表:模型定义见 posthog/models 以及
products/下的产品包。表名通常以posthog_为前缀并采用 snake_case(例如posthog_team、posthog_user)。可在 psql 中用\dt posthog_*确认,若模型使用了非标准表名,可查看其Meta.db_table。 - 迁移:posthog/migrations(以及产品迁移路径)定义了随时间演进的权威 DDL。
- Person 表名:通过
PERSON_TABLE_NAME环境变量配置(见 posthog/settings/data_stores.py),默认posthog_person;若启用分区表则设为posthog_person_new(该表由 Rust sqlx 迁移创建,必须先存在)。
调试场景(PostHog 风格)
- 确认某个 team、project、user 或功能开关关联行是否存在;留意软删除(
deleted字段)场景; - 将计数与 join 结果和应用侧的假设做对比(例如成员关系、项目访问权限);
- 仅当确实连接到正确主机(副本:
POSTHOG_POSTGRES_READ_HOST)时,才验证副本与主库的读取差异; - 对复刻为 SQL 的慢 Django 查询使用
EXPLAIN ANALYZE,注意生产规模数据带来的负载。
延伸阅读与交叉引用
- 本地环境搭建与数据库排障: docs/published/handbook/engineering/developing-locally.md(含
fe_sendauth排障); - 仓库 CLI:
hogli,见 .agents/skills/hogli/SKILL.md(例如hogli docker:services:remove等容器管理命令可配合本地数据库环境使用); - 应用数据库配置权威源码:posthog/settings/data_stores.py;
- 产品隔离库路由配置:products/db_routing.yaml 与初始化脚本 docker/postgres-init-scripts/create-product-dbs.sh;
- Persons 库连接逻辑:posthog/persons_db.py。
一句话总结:对 PostHog 本地 Postgres 的一切查询,都应以PGOPTIONS='-c default_transaction_read_only=on'起手,用SELECT/EXPLAIN/EXPLAIN ANALYZE完成调试与性能分析,绝不触碰任何写操作;牢记"事件数据在 ClickHouse、元数据在 Postgres"的分工,即可在 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),仅供参考