PostHog 生产数据库只读分析通过 Metabase 查询 ClickHouse query_log 与 Postgres 应用库【免费下载链接】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/posthogPostHog 的工程团队通过内部 Metabase 实例对生产 ClickHousesystem.query_log慢查询分析与 Postgres应用数据库查询计划执行临时、只读的诊断分析。本文以仓库中的官方技能文档 querying-production-databases-via-metabase 为主体结合hogliCLI 的真实实现源码tools/hogli-commands/hogli_commands/metabase.py与其自动化消费方tools/query-performance-ai完整讲解环境发现、SSO 认证、查询执行、慢查询判定标准、Postgres 安全规则与结果解析读者读完即可复现整套慢查询定位 → 逐团队成本归因 → 单查询下钻的调查工作流。背景为什么通过 Metabase 访问生产库PostHog 的生产数据库并非直接暴露连接串而是通过内部 Metabase 实例提供临时、只读的分析入口。两个 Metabase 都位于 AWS ALB 之后并使用 Cognito OAuth因此认证是SSO 门控SSO-gated的——仅持有 Metabase API Key 无法通过 ALB 校验必须携带完整的会话 Cookiemetabase.SESSION、metabase.DEVICE以及 ALB 侧的ph_int_auth-0、ph_int_auth-1见 metabase.py 源码。同一套 API 表面背后是两个截然不同的引擎各自的适用场景也不同ClickHouse—— 面向system.query_log分析哪些查询慢、它们读取了多少数据、是谁哪个 team在跑。Postgres应用数据库—— 拿到应用查询的真实执行计划以及某个按项目per-project分表的行在集群中的分布情况。对于现成的 ClickHouse 慢查询汇总、物化列分析等预置查询官方指引是参考query-performance-analysis仓库它被描述为这些预置查询的事实来源并复用同一套 Metabase API 表面。在本仓库内与之一脉相承的可直接阅读的实现是 tools/query-performance-ai 的slow_queries.py与backends/metabase.py。环境区域与 Metabase 地址区域Metabase URLUShttps://metabase.prod-us.posthog.devEUhttps://metabase.prod-eu.posthog.dev源码中 REGIONS 映射 还包含第三个dev区域https://metabase.dev.posthog.dev用于开发环境联调。数据库 ID 不是稳定的——当 Metabase 的元数据库被重建或数据库连接被重新添加时ID 会发生变化。切勿硬编码任何 ID每次都要动态发现当前列表hogli metabase:databases --region us hogli metabase:databases --region eu该命令在源码中对应 metabase_databases它请求/api/database并以表格输出ID / NAME / ENGINE三列源码同时兼容 Metabase 新版{data: [...], total: N}包装与旧版裸列表两种响应形态。各区域的数据布局名称可能有出入务必以metabase:databases实测为准US暴露一个 ClickHouse 数据库用于query_log与数据读取。EU暴露两个 ClickHouse 数据库——一个query 层用于query_log分析和一个data 层生产数据读取events、persons 等。选择名称指示 query 层的那个。两个 Metabase 也都暴露 Postgres 数据库应用 DBEU 还额外暴露 ingestion 层与 migrations 数据库。认证通过hogli完成 SSO 登录使用hogli获取有效 Cookie。它打开系统浏览器进行 SSO从用户已登录的浏览器配置文件中抓取 Cookie并缓存在~/.config/posthog/metabase/cookie-{region}权限0600。# 每个区域登录一次。--region 是必填参数没有默认值——由你选择目标区域。 # 已有效的会话会走快速路径不弹出浏览器标签页因此重复执行代价很低。 hogli metabase:login --region us hogli metabase:login --region eu从源码看metabase_login 的行为细节包括默认支持 Chrome、Chromium、Brave、Arc、Dia、Firefox、Safari 七类浏览器Chromium 系浏览器会对每个 profile 目录Default、Profile 1…的CookiesSQLite 文件做 glob 扫描用户无需关心自己是用哪个 profile 登录的--browser可指定只读取某个浏览器--no-open跳过打开浏览器仅抓取 Cookie--timeout默认 180 秒期间以 1 秒间隔轮询浏览器 Cookie 存储并通过请求/api/user/current_check_cookie确认会话真实有效Cookie 文件通过 os.open 显式 0600 模式原子写入避免write_text的 umask 竞态窗口——这点在 test_metabase.py 中有专门测试断言。请让用户亲自执行hogli metabase:login——harness自动化外壳会阻断 Agent shell 对系统钥匙串Keychain的访问因此必须由用户交互式完成认证。Agent 场景请使用metabase:queryhogli metabase:query在内部读取缓存的 Cookie 且只输出查询结果——会话值永远不会出现在 Agent 的 transcript 中。metabase:cookie则是为想手写curl的人提供的命令它把 Cookie 头打印到 stdout支持--check先做有效性校验无尾随换行以便METABASE_COOKIE$(...)捕获。metabase:query的实现位于 metabase_query它向/api/datasetPOST{database: id, type: native, native: {query: sql, template-tags: {}}}Cookie 读取、使用、丢弃全部在函数内部完成。运行一条 ClickHouse 查询发现当前 ClickHouse 数据库 IDhogli metabase:databases --region region。把该 ID 传给hogli metabase:query。SQL 通过 stdin 管道传入或用--file指定。# 1. 找到你所在区域的 ClickHouse 数据库 ID hogli metabase:databases --region us # e.g. output row: 42 ClickHouse clickhouse # 2. 执行查询。Cookie 在内部读取不会有任何泄漏到 stdout。 hogli metabase:query --region us --database-id 42 --save /tmp/out.tsv SQL SELECT JSONExtractInt(log_comment, team_id) AS team_id, count() AS query_count, formatReadableSize(sum(read_bytes)) AS total_bytes FROM clusterAllReplicas(posthog, system, query_log) WHERE event_time now() - INTERVAL 1 DAY AND is_initial_query AND query_duration_ms 30000 GROUP BY team_id ORDER BY query_count DESC LIMIT 20 SQLclusterAllReplicas(posthog, system, query_log)是标准表引用写法——它会跨整个集群扇出fan out。对于较大的结果集使用--save path让结果行落到文件而非流经终端/transcript。默认输出为 TSV--format json会给出/api/dataset的原始响应体。源码中--save同样复用 0600 安全写入测试 test_metabase_query_save_to_file 同时断言值未泄漏到 stdout且文件权限为 0600。如果数据库 ID 错误metabase:query会以非零退出码结束并提示回看metabase:databases。fail-fast 是有意设计——静默查询错误的数据库比直接失败更糟。源码中的对应行为包括HTTP 302/401 提示重新登录、404 提示Database 不存在请运行metabase:databases查看当前 ID、{status: failed, error: ...}响应体被提升为ClickException空 SQL 直接拒绝执行。ClickHouse什么算慢查询query_duration_ms 30000 OR exception_code IN (159, 160, 241)Code含义159TIMEOUT_EXCEEDED160TOO_SLOW241MEMORY_LIMIT_EXCEEDED这套判定标准在仓库中不止一处被引用tools/query-performance-ai的 slow_queries.py 查询模板 将query_duration_ms 30000 OR exception_code IN (159, 160, 241)作为从生产system.query_log筛选慢查询的硬性谓词并额外要求JSONExtractString(log_comment, ai_data_processing_approved) true只把客户显式批准 AI 分析的查询喂给自动化 Agent。ClickHouse 查询模式最近 24 小时 Top 慢查询SELECT query_id, JSONExtractInt(log_comment, team_id) AS team_id, query_duration_ms, formatReadableSize(memory_usage) AS memory, formatReadableSize(read_bytes) AS read_bytes, exception_code, substring(query, 1, 200) AS query_preview FROM clusterAllReplicas(posthog, system, query_log) WHERE event_time now() - INTERVAL 1 DAY AND type QueryFinish AND (query_duration_ms 30000 OR exception_code IN (159, 160, 241)) AND JSONExtractString(log_comment, workload) NOT IN (Workload.OFFLINE, OFFLINE) AND JSONExtractString(log_comment, kind) NOT IN (temporal) AND JSONExtractString(log_comment, access_method) NOT IN (personal_api_key) AND is_initial_query AND JSONExtractInt(log_comment, team_id) ! 0 ORDER BY query_duration_ms DESC LIMIT 100几个过滤条件的意图可在 slow_queries.py 看到同款谓词workload NOT IN (Workload.OFFLINE, OFFLINE)排除离线负载kind NOT IN (temporal)排除 temporal 工作流内部查询access_method NOT IN (personal_api_key)排除个人 API key 触发的查询is_initial_query只保留集群中实际发起的初始查询排除分布式子查询team_id ! 0剔除未归属项目的系统级查询。逐团队查询成本汇总7 天SELECT JSONExtractInt(log_comment, team_id) AS team_id, count() AS queries, countIf(query_duration_ms 30000) AS slow_queries, formatReadableSize(sum(read_bytes)) AS total_read, formatReadableSize(max(memory_usage)) AS peak_memory, quantile(0.95)(query_duration_ms) AS p95_duration_ms FROM clusterAllReplicas(posthog, system, query_log) WHERE event_time now() - INTERVAL 7 DAY AND type QueryFinish AND JSONExtractString(log_comment, workload) NOT IN (Workload.OFFLINE, OFFLINE) AND JSONExtractString(log_comment, kind) NOT IN (temporal) AND JSONExtractString(log_comment, access_method) NOT IN (personal_api_key) AND is_initial_query AND JSONExtractInt(log_comment, team_id) ! 0 GROUP BY team_id ORDER BY total_read DESC LIMIT 20countIf(query_duration_ms 30000)复用慢查询阈值quantile(0.95)(query_duration_ms)给出 p95 时延formatReadableSize让字节数可读化如1.23 TiB。按query_id查找单条查询两个区域都提供了已保存的卡片saved cardURL 与查询实际运行的区域匹配# US https://metabase.prod-us.posthog.dev/question/795-look-up-query-by-query-id?query_idIDinclude_query_startNoevent_dateYYYY-MM-DD # EU卡片 ID 可能不同——若 795 无法解析请在 EU Metabase 中查找 https://metabase.prod-eu.posthog.dev/question/795-look-up-query-by-query-id?query_idIDinclude_query_startNoevent_dateYYYY-MM-DD同样的查询可以用WHERE query_id ...子句通过/api/dataset对正确区域的数据库 ID 编程式复现。这正是query-performance-ai后端所做的事它在执行候选 SQL 前注入SETTINGS log_comment {...autoresearch_run_id...}之后轮询system.query_log按autoresearch_run_id取回权威的query_duration_ms / read_rows / read_bytes / query_id见 backends/metabase.py而非信任 Metabase 的running_time。Postgres 应用数据库当一次 Django 请求在应用数据库中耗时明显时使用 Postgres 连接。生产数据与统计分布可能产生与本地数据不同的执行计划。先发现当前数据库 ID选择 Postgres 应用数据库hogli metabase:databases --region us列表中也可能包含 ingestion 与 migrations 数据库。完整的排查工作流可参考 profiling-slow-api-endpoints 技能文档。安全规则Metabase 连接使用的是共享只读副本read replica只允许运行SELECT与EXPLAIN语句禁止写入与 schema 变更从EXPLAIN开始——它不会真正执行查询EXPLAIN ANALYZE会真正执行查询因此只用于窄范围、安全的SELECT保留端点原有的 filters、order 与 limit——它们会改变执行计划。按租户分组的 count 即使结果有限制也可能全表扫描。优先使用已有的聚合结果或数据库所有者批准的其他数据源。针对单个租户把工作限定在 count 内部SELECT count(*) FROM ( SELECT 1 FROM table WHERE tenant_key tenant_id LIMIT threshold_plus_one ) AS bounded_rows结果可能包含客户标识符、查询文本与私有的规模数据。不要将它们复制进公开代码、测试、PR、issue 或评论中。公开输出请使用占位符与宽泛的数据形态。解析 Metabase 响应/api/dataset的标准响应体{ data: { cols: [{name: team_id, base_type: type/Integer}, ...], rows: [[55348, 142, 1.23 TiB], ...] }, status: completed, row_count: 20 }--format json直接输出该响应体--format tsv则由 _render_rows_tsv 渲染为带表头的 TSVNone渲染为空字符串测试见 test_render_rows_tsv_formats_cols_and_rows。快速 TSV 管道... | python3 -c import json, sys d json.load(sys.stdin) cols [c[name] for c in d[data][cols]] print(\t.join(cols)) for row in d[data][rows]: print(\t.join(str(v) for v in row)) 错误响应速查症状原因修复HTTP 302 到/auth/...Cookie 过期或缺失让用户执行hogli metabase:login --region regionHTTP 401Cookie 被 ALB 拒绝同上与 302 相同处理status: failederror数据库错误语法、表不存在等读取error修正 SQL挂起 / 超时宽范围query_log扫描收窄event_time范围、加team_id过滤、使用cluster()这些症状与 metabase.py 的 HTTP 分支处理302/401 提示重新登录一一对应超时兜底来自metabase:query默认 120 秒的 HTTP timeout--timeout可调。ClickHouse 调查工作流界定问题。是某个团队慢某种查询模式慢还是成本/内存回归选择能回答问题的最小时间窗口**——query_log很大默认 1h–24h必要时再扩大。过滤到type QueryFinish获取实际运行了什么——另外还有QueryStart与ExceptionBeforeStart行。先聚合、再下钻。先做逐团队或逐模式的聚合然后针对最差者加WHERE查看单条查询。在写报告中附上query_id示例便于审阅者自行从query_log拉取完整行。已知限制Metabase 响应超时。原生查询默认约 60 秒非常宽的扫描会被截断。请收窄时间范围或使用采样表。log_commentJSON 漂移。新字段会随时间出现JSONExtractString(log_comment, foo)在字段缺失时返回——只要基于它过滤就一定要带上IS NOT NULL/! 保护。Cookie 作用域。每个区域有独立的 Cookie 缓存。每个需要的区域都要执行hogli metabase:login --region region--region必填。仓库内延伸阅读技能文档原文.agents/skills/querying-production-databases-via-metabase/SKILL.mdhogli命令实现登录/列库/查询/Cookietools/hogli-commands/hogli_commands/metabase.py测试覆盖见 tools/hogli-commands/hogli_commands/tests/test_metabase.py基于本技能构建的自动化慢查询优化工具Metabase 后端执行、system.query_log权威指标回捞tools/query-performance-ai/README.md 与 backends/metabase.py、slow_queries.py配套排查流程profiling-slow-api-endpoints、querying-local-postgres【免费下载链接】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),仅供参考