ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

PostHog ClickHouse 迁移实战:节点角色、run_sql_with_exceptions 与云/本地一致性规则

PostHog ClickHouse 迁移实战:节点角色、run_sql_with_exceptions 与云/本地一致性规则 PostHog ClickHouse 迁移实战节点角色、run_sql_with_exceptions 与云/本地一致性规则【免费下载链接】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 这类多集群 ClickHouse 架构中一条写错的迁移migration可能导致对象落到错误的集群、卡住发布甚至破坏各环境间有意为之的 schema 差异。本文基于 ClickHouse Migrations 技能文档 展开完整覆盖迁移结构、节点角色选择、表引擎写法与关键红线并结合 集群与迁移模式文档、迁移执行器源码 与 运行模式实现 深入讲解其底层机制。读完你可以掌握如何为任意新表/新列写出符合 PostHog 规范的迁移以及为什么 PR 范围、本地一致性local parity和 HCL 声明式 schema 必须同步维护。集群布局为什么节点角色是迁移的第一决策PostHog 的 ClickHouse 不是单一大集群而是主分片集群 一组卫星集群 接入层节点的复合拓扑。AGENTS.md 描述了三个环境的布局US 生产主集群 30 个 worker 节点10 分片 x 3 副本外加ai_events、aux、batch_exports、endpoints、logs、sessions、ops各 1x2 的卫星集群开发环境镜像 US 生产的布局同样的主集群 卫星集群只是节点数缩减。这意味着 Dev 与 US 生产之间期望 schema 完全一致迁移对两者采用相同目标EU 生产主集群 24 个 worker 节点8 分片 x 3 副本所有表都定义在 worker 节点上另有 k8s 上的无状态节点。接入层ingestion nodes负责把 Kafka 数据灌入 ClickHouse 分片其固定模式为三步创建一个可写的 Distributed 引擎表创建一个 Kafka 引擎表创建一个从 Kafka 读取、写入可写表的物化视图MV。如果目标表是非分片的Distributed 表只选一个节点作为数据目标。正是这种拓扑决定了迁移的第一原则节点角色node role由对象属于哪个集群决定而不是由对象类型单独决定。角色枚举定义在 connection.py 的NodeRole中合法值仅包括ALL、DATA、INGESTION_EVENTS、INGESTION_SMALL、INGESTION_MEDIUM、ENDPOINTS、LOGS、AI_EVENTS、AUX、BATCH_EXPORTS、OPS、SESSIONS。写入一个不在枚举中的角色名会在 import 阶段直接失败——这会中止迁移发现过程并拖垮所有执行迁移的任务因此错误角色名在本地开发时就会暴露属于快速失败设计。选择规则角色适用对象[NodeRole.DATA]主集群上的一切分片表、非分片复制表、Distributed 读表、视图、字典。默认选择适用于大多数迁移[NodeRole.INGESTION_SMALL]接入层上的可写表、Kafka 表、物化视图[NodeRole.OPS]/LOGS/AUX/AI_EVENTS/SESSIONS/BATCH_EXPORTS驻留在某个卫星集群上的对象。若把这类表误放到DATA它会落到错误的节点上而目标集群里根本没有它。默认用DATA之前先确认对象在哪里被读写[NodeRole.ALL]很少使用从源码结构看migration_tools.py 进一步区分了DATA_NODE_ROLES承载复制型 MergeTree 数据、可作为 ALTER 目标的七个角色与SINGLE_SHARD_DATA_NODE_ROLESAI_EVENTS、AUX、BATCH_EXPORTS、OPS、SESSIONS五个单分片卫星集群对单分片卫星集群执行 ALTER 时走any_host_by_roles在任意一台该角色的主机上执行复制机制负责传播而对真正的分片集群则走map_one_host_per_shard每个分片执行一次。这也解释了 connection.py 中两段注释LOGS集群同样承载复制表如迁移 0283 的metric_series1/metric_samples1其非分片 ALTER 与卫星集群走同一条单主机路径。另一个容易踩的坑由于 Dev 环境与 US/EU 生产运行完全相同的卫星集群永远不要基于CLOUD_DEPLOYMENT分支来给 Dev 提供不同布局。迁移结构run_sql_with_exceptions 全解析所有迁移都通过run_sql_with_exceptions表达操作基本骨架operations [ run_sql_with_exceptions( SQL_FUNCTION(), node_roles[...], shardedFalse, # 分片表时为 True is_alter_on_replicated_tableFalse # 对非分片复制表执行 ALTER 时为 True ), ]AGENTS.md 给出的参数语义sql要执行的 SQL 字符串也可以是返回 SQL 的函数调用node_rolesNodeRole值的列表缺省为[NodeRole.DATA]sharded操作分片表时设为True保证每个分片执行一次is_alter_on_replicated_table对复制表执行 ALTER 时设为True只在每个分片的一台主机上执行复制机制负责传播。执行器实现 揭示了几个文档不会强调的实现细节本地/E2E 环境的角色坍缩_collapses_to_all_nodes()在 E2E 测试、非部署云环境且未开启MULTINODE_CLICKHOUSE时把node_roles统一改写为[NodeRole.ALL]因为单机 Docker 栈没有多集群拓扑。而MULTINODE_CLICKHOUSE会恢复角色路由让冒烟栈验证迁移确实落在正确的集群上参数冲突断言同时设置shardedTrue和is_alter_on_replicated_tableTrue时要求node_roles恰好是DATA_NODE_ROLES中的一个角色否则直接AssertionError元数据附着每个返回的 operation 会挂上_sql、_node_roles、_effective_node_roles等属性供校验工具使用注意_node_roles用的是角色坍缩之前的原始值_effective_node_roles才是当前设置下实际生效的角色。表引擎速查MergeTree 系引擎来自table_engines模块均支持table与replication_scheme参数后者默认ReplicationScheme.REPLICATED# 分片表分片 复制 engineAggregatingMergeTree( sharded_events, replication_schemeReplicationScheme.SHARDED ) # 非分片复制表ReplicationScheme.REPLICATEDZK 路径带 noshard engineReplacingMergeTree(table, replication_schemeReplicationScheme.REPLICATED)可用的变体还包括MergeTreeEngine、ReplacingMergeTreeDeleted带删除标记的去重、CollapsingMergeTree折叠行。replication_scheme的三个取值NOT_SHARDED单节点无复制、SHARDED分片复制、REPLICATED复制但不分片。Distributed 引擎签名为Distributed(data_table, sharding_keyNone, clusterNone)# 分片 Distributed 表cluster 缺省为 settings.CLICKHOUSE_CLUSTER engineDistributed( data_tablesharded_events, sharding_keysipHash64(person_id), ) # 非分片 Distributed 表接入层写入路径显式指向单分片集群 engineDistributed( data_tablemy_table, clustersettings.CLICKHOUSE_SINGLE_SHARD_CLUSTER )各类对象的标准 CREATE / ALTER 写法AGENTS.md 按对象类型给出了标准模式核心是表建在哪迁移就路由到哪对象引擎示例node_roles附加参数非分片复制表ReplacingMergeTree(..., REPLICATED)DATA—分片表AggregatingMergeTree(..., SHARDED)DATA—Distributed 读表Distributed(data_table, sharding_key)DATA—Distributed 可写表主集群旧模式同上DATA—Distributed 可写表新接入层模式同上clusterSINGLE_SHARD_CLUSTERINGESTION_SMALL—Kafka 表推荐KafkaINGESTION_SMALL旧写法为DATA—物化视图推荐MVINGESTION_SMALL旧写法为DATA—普通视图 / 字典VIEW / DictionaryDATA—ALTER 操作的两种标志位各有语义# 非分片复制表只在单台主机上执行复制机制传播 run_sql_with_exceptions( ALTER TABLE my_table ADD COLUMN IF NOT EXISTS ..., node_roles[NodeRole.DATA], is_alter_on_replicated_tableTrue, ) # 分片表每个分片执行一次 run_sql_with_exceptions( ALTER TABLE sharded_my_table ADD COLUMN IF NOT EXISTS ..., node_roles[NodeRole.DATA], shardedTrue, )重建表若要在同一条迁移里删除并重建一个复制表必须用DROP TABLE IF EXISTS my_table SYNC——SYNC保证 drop 完成后才执行后续 CREATE因为复制表在 ZooKeeper 中保留元数据异步 drop 可能让 CREATE 撞上残留状态。非复制对象Kafka、Distributed 表、MV不需要SYNC。新表的完整接入层模式推荐四步走operations [ # 1. 主集群DATA 节点上的数据表 run_sql_with_exceptions(DATA_TABLE_SQL(), node_roles[NodeRole.DATA]), # 2. 接入层上的可写 Distributed 表非分片表用 CLICKHOUSE_SINGLE_SHARD_CLUSTER run_sql_with_exceptions(WRITABLE_TABLE_SQL(), node_roles[NodeRole.INGESTION_SMALL]), # 3. 接入层上的 Kafka 表 run_sql_with_exceptions(KAFKA_TABLE_SQL(), node_roles[NodeRole.INGESTION_SMALL]), # 4. 接入层上的物化视图 run_sql_with_exceptions(MV_SQL(), node_roles[NodeRole.INGESTION_SMALL]), ]该模式将接入负载与查询负载隔离到不同节点上。迁移 0153 是一个完整示例可参考。关键红线规则技能文档列出的 Critical rules 值得逐条对照执行绝不使用ON CLUSTER子句。迁移器自己负责跨节点分发ON CLUSTER与这套迁移机制不兼容。相应地如果某个生成 SQL 的函数带on_cluster参数调用时必须显式传on_clusterFalse——仓库中的 SQL 函数普遍采用此签名例如 exchange_rate/sql.py 中EXCHANGE_RATE_TABLE_SQL(on_clusterTrue)的默认值就是给非迁移路径用的迁移调用方需要自行关掉。始终使用IF EXISTS/IF NOT EXISTS守卫但注意守卫挂在操作上而非表上ALTER TABLE IF EXISTS ...在 ClickHouse 中是语法错误。正确写法CREATE TABLE IF NOT EXISTS my_table ... ALTER TABLE my_table ADD COLUMN IF NOT EXISTS my_col ... ALTER TABLE my_table MODIFY COLUMN IF EXISTS my_col ... ALTER TABLE my_table DROP COLUMN IF EXISTS my_col绝不要手写CODEC(ZSTD(1))——服务端已经用 ZSTD 压缩所有列显式声明毫无收益。只有当 CODEC 能胜过这个默认值时才声明且必须先检查ORDER BYDelta/DoubleDelta要求列在存储顺序上近乎有序即排序键前缀如果排序键只是按toDate(timestamp)分桶或根本不含该列DoubleDelta会因最大跳变拓宽编码、反而劣化压缩T64/Gorilla不依赖顺序是排序键不利时的更好选择。所选 codec 需与 ZSTD 二级串联如CODEC(DoubleDelta, ZSTD(1))因为专用编码会替代而非叠加默认压缩。此外CODEC 只能写在存储表上——Distributed/Kafka 表不存数据声明的 CODEC 只是SHOW CREATE TABLE中的惰性元数据会与其前置的分片表产生漂移。绝不自行写DROP COLUMN迁移。DROP COLUMN可能卡在 ClickHouse 中阻塞发布。删列是两步流程(1) ClickHouse 团队直接在集群上删列(2) 你再写一条带对应DROP COLUMN的迁移使代码库 schema 保持同步。没有第 1 步的确认绝不允许发起第 2 步。绝不 drop 或重建kafka_events_json_ws与events_json_ws_mv——这是禁区no-go zone。该 MV 的定义在 US 生产、EU 生产和 Dev 之间差异显著数十个环境特有的mat_*物化列而这些差异没有反映在仓库里。用仓库 SQL 重建会摧毁环境特有 schema 并破坏事件接入。任何变更必须经由 ClickHouse 团队。PR 范围迁移 PR 必须纯迁移包含 ClickHouse 迁移的 PR必须是纯迁移 PR不得混入功能代码、API 变更、模型变更或前端变更。迁移相关的文件仅限迁移文件本身posthog/clickhouse/migrations/0NNN_*.py迁移依赖的 SQL 定义文件如posthog/clickhouse/sql/*.py、表引擎辅助函数;直接验证该迁移或其触及的 SQL 定义的测试。如果新 schema 需要配套应用代码先单独发布迁移 PR 并合并再提交应用代码 PR。本地一致性Local Parity云端守卫与 run_mode原则任何表都不应只存在于云上。通过迁移创建的每张表都必须在本地开发环境存在。部分迁移是云端守卫的会在本地/hobby 开发中跳过。守卫必须基于posthog.run_mode直接比较settings.CLOUD_DEPLOYMENT会被 semgrep 规则clickhouse-migrations-use-run-mode拦截from posthog.run_mode import RunMode, run_mode operations ( [] if not run_mode().is_deployed_cloud # US/EU/DEV else [...] )run_mode.py 中各谓词的精确语义谓词为 True 的场景run_mode().is_deployed_cloudUS、EU、DEV常规迁移守卫run_mode().is_prod_cloudUS、EU排除 stagingrun_mode() is RunMode.CLOUD_US单一区域CLOUD_EU、CLOUD_DEV同理注意run_mode().is_cloud还把 E2E 算作云与posthog.cloud_utils.is_cloud对齐因此它不适合做迁移守卫。实现上的一个微妙点run_mode()是刻意不缓存的——每次调用都从posthog.settings重新读取。因为 test_migrations.py 会在打补丁的CLOUD_DEPLOYMENT下重新 import 每条迁移逐一检查各部署分支中是否存在游离的ON CLUSTER如果你把 run mode 赋成模块级常量缓存值会让这部分覆盖被静默跳过。新建表时同步登记 schema.py若你在云端守卫里创建了新表必须把它的 SQL 函数加进 schema.py 对应元组本地环境才会建出该表表类型schema.py 中的元组MergeTree / 基础表CREATE_MERGETREE_TABLE_QUERIESDistributed / 可写表CREATE_DISTRIBUTED_TABLE_QUERIESKafka 消费表CREATE_KAFKA_TABLE_QUERIES物化视图CREATE_MV_TABLE_QUERIES非物化视图CREATE_VIEW_QUERIES字典CREATE_DICTIONARY_QUERIES唯一例外是定义刻意因环境而异、且不纳入仓库跟踪的表如禁区表events_json_ws_mv。字典凭据当字典使用SOURCE(CLICKHOUSE(...))时源端用户/密码必须通过get_clickhouse_creds(ClickHouseUser.DICT_READER)解析并插值到USER/PASSWORD子句不要硬编码default/CLICKHOUSE_USER或省略凭据。这会把字典鉴权固定在低权限专用用户dict_reader上与default解耦且环境变量未设置时回退到default凭据。exchange_rate/sql.py 是标准范式_dict_reader_creds get_clickhouse_creds(ClickHouseUser.DICT_READER) CLICKHOUSE_DICT_READER_USER _dict_reader_creds.user CLICKHOUSE_DICT_READER_PASSWORD _dict_reader_creds.password # ... SOURCE(CLICKHOUSE(QUERY {query} USER {clickhouse_user} PASSWORD {clickhouse_password}))对应地ClickHouseUser.DICT_READER在 connection.py 中有明确注释baked into dictionary SOURCE blocks, decoupling dictionary credentials from the default user其凭据由环境变量CLICKHOUSE_DICT_READER_USER/CLICKHOUSE_DICT_READER_PASSWORD提供get_clickhouse_creds的查找逻辑见 connection.py未配置时回退default。声明式 HCL schema 必须同步部分角色同时由 posthog/clickhouse/hcl/ 声明式管理。多节点迁移冒烟会在迁移后重新内省集群并把线上 schema 与提交的 golden 比对任何漂移都会让 CI 在check_live_hcl处以DRIFT:块点名你的新对象失败。因此在受管角色上新建表的迁移必须在同一 PR 里更新 HCL。data角色在local-multi组合中同样受管所以NodeRole.DATA上的新表也算数。写完迁移后的标准流程HCLposthog/clickhouse/hcl # 1. 把新表加入组合该表的分层例如 $HCL/roles/data/local/tables.hcl # 2. 刷新生成物 bash $HCL/gen-golden.sh bash $HCL/gen-sql.sh # 3. 校验必须退出码 0 bash $HCL/check.sh编辑任何分层之前先读 hcl/README.md它覆盖分层选择、抽象列清单abstract column lists与上文 CODEC 规则呼应抽象列不带 CODEC在分片实例上经patch_column补回以及能替你写出迁移的 codegen 路径codegen/gen_migration.py。README 还描述了两步收敛门禁dump-live.sh内省迁移后的节点生成 HCL dumpcheck-live.sh将 dump 与 golden diff空操作列表才算通过——这正是捕获迁移改了线上 schema 但 HCL 没跟上这类静默失同步的机制。测试与重跑迁移想重跑某条迁移从infi_clickhouse_orm_migrations表中删除对应记录即可迁移发现与分支正确性由 test_migrations.py 保障它在打补丁的CLOUD_DEPLOYMENT下重新 import 全部迁移逐部署检查分支中是否存在游离的ON CLUSTER多节点行为由 multinode 迁移冒烟MULTINODE_CLICKHOUSE恢复角色路由与前述 HCL 收敛门禁共同验证。小结PostHog 的 ClickHouse 迁移规范可以浓缩为五条决策链先按对象归属选角色NodeRole枚举硬约束错名 import 即失败→再按表形态选参数sharded对分片、is_alter_on_replicated_table对复制表→SQL 层面禁用ON CLUSTER、强制幂等守卫、禁区表不碰→云端守卫一律走run_mode()并同步登记 schema.py 与 HCL→纯迁移 PR 先行合入。这套规则把跨集群路由从人工心智负担变成了编译器级别的检查枚举校验、semgrep、golden diff值得任何运行多集群 ClickHouse 的团队借鉴。【免费下载链接】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),仅供参考
返回列表