PostHog HogQL 慢查询深度剖析:从 query_log_archive 定位、归因并根因分析用户与 AI 编写的任意 SQL
PostHog HogQL 慢查询深度剖析从 query_log_archive 定位、归因并根因分析用户与 AI 编写的任意 SQL【免费下载链接】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/posthogHogQLQuery 是 PostHog ClickHouse 慢查询报告中唯一一个“用户或 AI 想怎么写就怎么写”的分析桶它的慢由因与产品级 insightsTrends、Funnels 等截然不同没有强制的日期范围、没有感知物化列的属性访问、可以任意 join。本文基于仓库中.agents/skills/generating-clickhouse-query-performance-reports/references/hogql-deep-dive.md的核心方法论结合源码与可执行 SQL讲清如何在posthog.query_log_archive中把慢 HogQL 扫出来、判断其中有多少是 AI 写的、并逐条做根因取证。为什么 HogQLQuery 需要独立成桶PostHog 会把进入 ClickHouse 的每条查询都打上结构化标签写入system.query_log.log_comment再由归档表展开为带lc_前缀的类型化列。lc_query__kind HogQLQuery因此成为一个独立分析桶与产品生成的查询TrendsQuery、FunnelsQuery等不同HogQL 是任意的、由用户或 AI 撰写的 SQL。它来自四个主要入口Web 端的SQL 编辑器 / DataVisualization 节点数据可视化里写自由 SQL 的画布节点/query/API以personal_api_key数据集成、API 消费者或oauth方式直接投递 HogQLMCP server外部 AI agent 通过 PostHog MCP 服务器发起的工具调用Max assistantPostHog 内置的 AI 助手及其子工具。在源码中可以看到这条执行链路的落点HogQL 查询由 hogql_query_runner.py 中的HogQLQueryRunner承载执行时显式传入query_typeHogQLQuery见 hogql_query_runner.pyexecute_hogql_query()的默认query_type也会落到这个桶上query.py。由于这是任意 SQL它绕过了塑造产品 insights 的那层护栏没有强制的日期范围没有物化感知的属性访问properties.x走不带物化列优化的路径时是危险的允许任意 join。因此其慢因更五花八门且在 OOM 与超时中占比显著偏高异常码241/159见下文异常码表。两张关键证据列query与lc_query__query分析 HogQL 慢查询时归档表里有两列起着决定性作用列含义何时读它query编译后的 ClickHouse SQL真正执行的那份分析执行层面granule 裁剪、join 顺序、读了多少字节lc_query__query用户或 AI 写的源 HogQL理解意图它比编译后的 SQL 短得多、清楚得多lc_query__query是意图层证据query-patterns.md第 6 节的query_link配方见 query-patterns.md生成的共享 Metabase 链接默认就只 SELECTlc_query__query方便读者直接点开看原始查询。需要执行细节时再扩宽 SELECT例如把query一并带出。全景扫描慢 HogQL 到底是谁发出的先用一个聚合把“慢 HogQL 地图”铺开。下面这条 SQL 将慢集按产品、特性与访问方式分组是报告的起点SELECT lc_product, lc_feature, lc_access_method, count() AS slow, uniqExact(team_id) AS teams, countIf(exception_code 241) AS ooms, countIf(exception_code 159) AS timeouts, round(avg(query_duration_ms)/1000) AS avg_s, formatReadableSize(sum(read_bytes)) AS total_read FROM posthog.query_log_archive WHERE event_time now() - INTERVAL 14 DAY AND is_initial_query AND lc_query__kind HogQLQuery AND (query_duration_ms 30000 OR exception_code IN (159,160,241)) GROUP BY lc_product, lc_feature, lc_access_method ORDER BY slow DESC LIMIT 40历史观测下这张表有稳定的特征读法如下主体是product_analytics/query/personal_api_key即数据集成与 API 消费者它们是慢 HogQL 的大头数量上的“噪声”另有其人紧超时tight-timeout的 API 流量平均约 13 秒、大多是超时与cache_warmup后台 insight 刷新在原始计数里反而占优正确排序不看 count按惯例用OOM 数与**集群小时cluster-hours**去加权否则超时数量会伪装成真实算力消耗。两条慢查询判定的关键约束完整方法论见 SKILL.md必须带is_initial_query 1避免分布式子查询被重复计数慢集谓词是query_duration_ms 30000 OR exception_code IN (159,160,241)三个异常码含义如下异常码含义159TIMEOUT_EXCEEDED160TOO_SLOW241MEMORY_LIMIT_EXCEEDED识别 AI 编写的 HogQL用lc_productlc_feature而不是ai_query_source不存在单一布尔列“是否为 AI 写的”。归档里没有这种字段正确的做法是从lc_product与lc_feature两个标签维度拼出证据信号含义lc_product max_aiPostHog 的 Max 助手及其工具发出的查询在ee/hogai/**中通过tags_context(productProduct.MAX_AI, ...)打标lc_product mcp或lc_feature mcp外部 AI agent 经由 PostHog MCP 服务器发起的查询lc_feature posthog_aiAI 功能标签存在实践中较少见推荐过滤条件lc_product IN (max_ai,mcp) OR lc_feature IN (mcp,posthog_ai)代码侧可验证的映射源头两个枚举定义在 query_tagging.pyProductMAX_AI max_ai、MCP mcp注释明言“queries originating through the MCP server (agent tool calls)”与同文件的Feature枚举POSTHOG_AI、MCPMax 侧的打标现场如 manage_memories.py、filter_session_recordings.py均使用tags_context(productProduct.MAX_AI, featureFeature.POSTHOG_AI, ...)场景 → 产品/特性的兜底映射、以及“先场景 → 再 kind → 再查询结构 → 再 HogQL features → 最后 MCP 来源”的 fallback 顺序都在 query_tagging.py。千万不要用ai_query_sourceai_query_source这名字极具误导性。它在 ai_table_resolver.py 这类 LLM-analytics 解析器中被设置为dedicated_table/shared_table_fallback等取值记录的是这次查询选择了哪张 AI events 表与“这段 SQL 是否为 AI 所写”毫无关系。更关键的是它没有被物化为归档里的lc_*列在query_log_archive上按它过滤根本拿不到数据。识别结果的边界与启发式信号该方法标记的是**“在 AI/MCP 上下文中执行”的查询**。Max 起草、随后被人类保存并重新加载的 insight会被重新标记为普通的product_analytics归档无从得知其 AI 出身——这类“AI 原创但被人类固化成 insight”的查询无法通过标签识别一个有用的次级信号AI 编写的 HogQL 往往在lc_query__query里带着解释性的-- …注释人很少给临时 SQL 写注释。它只作为旁证不是权威依据标签体系的事实来源是Product/Feature枚举与“节点类型 → 产品”映射query_tagging.py调用点分布在ee/hogai/**与 MCP server 中。把上述过滤拼进慢集全景就得到“AI 占了多慢”的视图SELECT lc_product, lc_feature, lc_access_method, count() AS slow, uniqExact(team_id) AS teams, countIf(exception_code 241) AS ooms, countIf(exception_code 159) AS timeouts, round(100 * countIf(exception_code 241) / count()) AS oom_pct FROM posthog.query_log_archive WHERE event_time now() - INTERVAL 14 DAY AND is_initial_query AND lc_query__kind HogQLQuery AND (lc_product IN (max_ai,mcp) OR lc_feature IN (mcp,posthog_ai)) AND (query_duration_ms 30000 OR exception_code IN (159,160,241)) GROUP BY lc_product, lc_feature, lc_access_method ORDER BY slow DESC观测结果是 AI/MCP 的 HogQLOOM 与超时比例不成比例地高——仓库文档记录过一个单周案例MCP-over-OAuth 桶里大约三分之一的慢查询直接 OOM。原因很直接这些是雄心勃勃的分析查询却完全没有产品级 insights 那套护栏约束无强制日期、无物化感知、随意 join所以拿 241/159 的概率远高于“被护栏约束成规范形状”的产品查询。AI 与 ad-hoc HogQL 慢的六类高频根因在慢集上读lc_query__query源 HogQL而非编译后的 SQL反复出现的根因有这几类未物化的 JSON 提取AI 直接写JSONExtractString(properties, x)或properties.x去取事件/用户属性。关键在于JSONExtract*(...)这种函数调用形式绕过了物化列细节见 investigation-playbook.md 与 materialization-analysis.md导致每行都要完整读一遍 JSON blob。events上的自 join / 交叉 join例如在某个时间窗内按 person 做events e1 JOIN events e2或拿每人聚合做CROSS JOIN。这会把扫描量直接乘上多倍。跨源 join把数仓表postgres.*、vitally.*、s3(...)与 events 做 join。外源一侧没有任何 ClickHouse 索引代价全部落在扫描与 shuffle 上。没有或过宽的日期范围ad-hoc SQL 常常漏写紧致的timestamp过滤等于扫全量历史。按宽列排序/过滤的全量导出即“函数包裹的排序键 / 过滤键”反模式function-wrapped sort/filter key让主键/分区无法裁剪详见 investigation-playbook.md。判读要点与配套文档判断物化问题前先对照“哪些列已物化”仓库用SHOW CREATE TABLE sharded_events交叉核对materialization-analysis.md。若物化列已存在但查询仍走 JSONExtract通常是属性以JSONExtractString(properties, $foo)形式被访问形成ast.Call从而跳过visit_property_type()而不是properties.$foo单条查询级根因bytes vs CPU vs duration、运行时成因、EXPLAIN 验证属于 optimizing-clickhouse-and-hogql-queries 技能范畴。单条 AI 查询取证把源 HogQL 与编译后 SQL 放在同一行在报告中引用任何一条慢查询时都需要同时给出来源与执行形态按query_idevent_date从归档表回溯system.query_log只保留几小时归档表才能覆盖多天窗口SELECT lc_query__query AS source_hogql, query AS compiled_sql, exception FROM posthog.query_log_archive WHERE query_id id AND event_date YYYY-MM-DD AND is_initial_query读法建议先用lc_query__query理解 AI 想干什么意图层再切换到query检查执行层慢因的判断不应停在“team X 慢”这种粒度而要形成“为什么慢”的假设如“时间过滤被函数包裹、granule 无法裁剪所以扫了全量历史”再用EXPLAIN验证——这正是根因取证 playbook 的职责范围报告中的每个案例都应带着可点击的共享query_link编码规则在 query-patterns.md 第 6 节保证读者能一键直达原始查询。把这份 deep dive 放回报告流程这份材料不是孤立文档而是 ClickHouse 慢查询报告方法论中的固定一环在 SKILL.md 的标准工作流第 6 步报告需要对用户侧查询做分桶其中 “HogQLQuery任意用户/AI SQL值得一次专门深潜包括其中多少由 AI 撰写、以及它为什么慢”即指向本文所有 SQL 均以posthog.query_log_archive为数据源Distributed 归档表、类型化lc_*列、约三周保留期并通过hogli metabase:query --region us|eu执行跨区域报告需分别对 US / EU 各跑一遍因为两地的物化列与负载不同报告落盘位置在私有仓库或临时目录公开仓库只承载方法与工具——读者在公开仓库中看到的是完整的 SQL 配方与分析路径。检查清单收尾时可用这张清单自查一份 HogQL 分析是否完整是否只用了lc_query__query/query双列做意图层与执行层取证判定 AI 出身是否只依赖lc_product/lc_feature而没有误用ai_query_source是否区分了“AI 执行上下文”与“AI 原创”之间的边界被人类保存的 AI insight 会重打标排序是否用 OOM 与集群小时加权而非被紧超时噪声带偏每条慢因是否落到六类根因之一并给出可验证的假设而不是停在统计层。【免费下载链接】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),仅供参考