关于 adding-personhog-rpc
面向 PostHog 工程师的 PersonHog RPC 开发技能,指导从数据访问设计到 proto、Rust 服务端及 Python 和 Node.js 客户端更新的完整流程。内容强调索引查询、路由与重试安全性。
主要功能
- 01检查数据表与访问资格
- 02设计索引支持的 SQL 查询
- 03定义服务与路由 proto
- 04更新 Python、Node.js 客户端
- 05实现 Rust 存储层与 RPC
- 06添加测试并运行构建验证
适用场景
- 01为 PersonHog 支持的数据类别新增 RPC
- 02为新 RPC 更新客户端封装和模拟实现
- 03验证 RPC 路由、重试行为与数据库访问
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ posthog/ posthog --skill adding-personhog-rpc安装后在 Claude Code 中输入/adding-personhog-rpc确认已加载。
附带文件references/database-indexes.mdreferences/proto-conventions.md
详细介绍
添加一个 personhog RPC
本技能将端到端地引导你添加一个新的 RPC: proto 定义、代码生成、Rust 实现以及客户端更新。
开始之前:资格检查
Personhog 服务于 person、distinct ID、group、group type mapping、cohort membership 以及 feature flag hash key override 数据。 如果要访问的数据不在以下某个表中,那么这个 RPC 就不属于 personhog:
| 表 | 数据类别 | 路由 |
|---|---|---|
posthog_person | PersonData | 读取:副本(最终一致)或 leader(强一致)。写入:leader |
posthog_persondistinctid | PersonData | 与 person 相同 |
posthog_group | NonPersonData | 所有操作:副本 |
posthog_grouptypemapping | NonPersonData | 所有操作:副本 |
posthog_cohortpeople | NonPersonData | 所有操作:副本 |
posthog_featureflaghashkeyoverride | NonPersonData | 所有操作:副本 |
posthog_personoverride | PersonData | 读取/写入遵循 person 路由 |
posthog_personlessdistinctid | PersonData | 与 person 相同 |
如果表未在上方列出,停止 —— 该数据不应经过 personhog。
步骤 0:设计数据访问模式
在编写任何 proto 之前,先弄清楚你需要的 SQL 查询。 然后根据可用索引验证它 —— 参见 references/database-indexes.md。
关键问题:
- 此查询涉及哪些表?
- WHERE 子句是否匹配现有索引?每个查询都必须是索引扫描,绝不能是全表顺序扫描。
- 这是读还是写?这决定了路由(参见上表)。
- 对于读取:调用方需要强一致性(主库连接池),还是最终一致性(副本连接池)可以接受?
- 对于批量查询:批次是在单个 team 内还是跨 team?
步骤 1:定义 proto 消息和 RPC
所有 proto 文件都位于 proto/personhog/。
有关消息约定和完整示例,参见 references/proto-conventions.md。
在何处添加什么
- 消息类型 →
proto/personhog/types/v1/<domain>.proto(person.proto、group.proto、cohort.proto、feature_flag.proto 或 common.proto) - 服务 RPC →
proto/personhog/service/v1/service.proto(客户端调用的公共 API) - 副本 RPC →
proto/personhog/replica/v1/replica.proto(路由器委派给的内部 API) - Leader RPC →
proto/personhog/leader/v1/leader.proto(仅当这是一个路由到 leader 的 person 数据写入时)
service 和 replica proto 必须以相同的签名声明该 RPC。 路由器会透明地从 service 委派到 replica(或 leader)。
Leader RPC 必须在至少一次投递下保持安全
leader 路径的请求可能被投递多次:客户端在遇到不明确的失败后会重试 UNAVAILABLE,
而 router 会在内部重放被 fence 和 transport 反弹的请求。
因此,每个 leader RPC 都必须能在重投递下收敛 ——
只读查询、可重复应用到同一状态的 merge、墓碑式删除、max-merge 版本下限 ——
或者携带显式的操作标识,以便检测重复。
注意精确的契约:收敛式 merge 仍然会输给交错执行
(一次重放可能覆盖另一个调用方在此期间对同一字段所做的写入);
这一残留目前被接受,并将通过操作标识解决(参见 personhog-leader 的 README)。
既不收敛也不携带标识的 RPC(无保护的 increment、append)不得添加。
如果你需要的操作不符合这两种形态,请在定义 proto 之前重新设计,使其携带幂等键。
步骤 2:生成客户端 stub
Python
bin/generate_personhog_proto.sh
然后更新三个文件:
posthog/personhog_client/proto/__init__.py— 为新的请求/响应消息类型添加再导出posthog/personhog_client/client.py— 添加一个与现有方法模式一致的包装方法posthog/personhog_client/fake_client.py— 为该方法的测试用途实现它
Node.js
cd nodejs && pnpm run generate:personhog-proto
然后更新:
nodejs/src/common/personhog/groups.ts或persons.ts— 向匹配的操作类 (PersonHogGroupOperations/PersonHogPersonOperations)添加一个包装方法, 遵循现有方法的模式。client.ts只构造并暴露这些操作对象; 它本身不持有任何 RPC 包装方法。nodejs/src/common/personhog/client.test.ts— 为新的 RPC 在SERVICE_DEFAULTS中添加一个默认 stub
Rust
无需生成步骤 —— tonic 会在 cargo build 时重新生成。
但你必须实现该 RPC(下一步),否则构建会失败。
步骤 3:在 Rust 中实现
编译器会引导你 —— 一旦 proto 定义完成,cargo build 的错误会准确告诉你缺少哪些 trait 方法。
3a. 存储层(personhog-replica)
- 添加一个 trait 方法,位于
rust/personhog-replica/src/storage/traits/<domain>.rs - 实现查询,位于
rust/personhog-replica/src/storage/postgres/<domain>.rs- 使用
sqlx::query_as!或sqlx::query!宏 - 通过
DB_QUERY_DURATION和DB_ROWS_RETURNED指标添加耗时插桩 - 读取使用
self.replica_pool,写入使用self.primary_pool - 对空的批量输入提前返回
- 使用
- 添加存储测试,位于
rust/personhog-replica/tests/storage_tests.rs
3b. 服务层(personhog-replica)
- 添加 RPC 处理器,位于
rust/personhog-replica/src/service/mod.rs- 从 proto 请求中提取字段
- 调用存储 trait 方法
- 将存储结果转换为 proto 响应
- 将存储错误映射为 tonic
Status代码
- 添加服务测试,位于
rust/personhog-replica/tests/service_tests.rs
3c. Router 接线(personhog-router)
router 转发请求字节而不对其进行解码,因此新的 RPC 在那里不需要处理器。
- 将方法名添加到
rust/personhog-router/src/proxy.rs中的KNOWN_METHODS,并保持列表有序- 必须到达 leader 的方法会在
proxy.rs中按名称匹配;其他所有方法都转发到 replica - 同一文件中的
known_methods_is_sorted和known_methods_matches_service_proto在该列表与service.proto匹配之前会失败
- 必须到达 leader 的方法会在
- 将该方法添加到每个实现生成 trait 的
PersonHogServicemock 中,包括 personhog 之外的 mock(rust/personhog-router/tests/common/mod.rs、rust/property-defs-rs/tests/);tonic trait 没有默认方法
在对同一行为的多个变体进行测试时,使用 rstest 参数化测试。
步骤 4:验证
cargo build -p personhog-proto
cargo build -p personhog-replica
cargo build -p personhog-router
cargo test -p personhog-replica
cargo test -p personhog-router
检查清单
- 查询使用了现有索引(无顺序扫描)
- Proto 消息已添加到
types/v1/<domain>.proto - RPC 已同时添加到
service.proto和replica.proto(如有需要,也添加到leader.proto) - 仅限 Leader RPC:该操作在至少一次投递下是安全的(重投递下收敛 —— 只读、可重新应用的合并、墓碑、最大合并 —— 或携带显式操作标识)
- 已生成 Python stub,已更新
proto/__init__.py,已添加client.py方法,已更新fake_client.py - 已生成 Node.js stub,已更新
client.test.ts的 SERVICE_DEFAULTS - Rust 存储 trait + postgres 实现 + 服务处理器 + 路由接线均已实现
- 已在存储层、服务层和路由层添加测试
- 三个 crate 的
cargo build和cargo test均通过