mcpField
A
adding-personhog-rpc

adding-personhog-rpc

github.com/posthog开发40.5k151 次安装今天更新
代码仓库

关于 adding-personhog-rpc

面向 PostHog 工程师的 PersonHog RPC 开发技能,指导从数据访问设计到 proto、Rust 服务端及 Python 和 Node.js 客户端更新的完整流程。内容强调索引查询、路由与重试安全性。

主要功能

  1. 01检查数据表与访问资格
  2. 02设计索引支持的 SQL 查询
  3. 03定义服务与路由 proto
  4. 04更新 Python、Node.js 客户端
  5. 05实现 Rust 存储层与 RPC
  6. 06添加测试并运行构建验证

适用场景

  1. 01为 PersonHog 支持的数据类别新增 RPC
  2. 02为新 RPC 更新客户端封装和模拟实现
  3. 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_personPersonData读取:副本(最终一致)或 leader(强一致)。写入:leader
posthog_persondistinctidPersonData与 person 相同
posthog_groupNonPersonData所有操作:副本
posthog_grouptypemappingNonPersonData所有操作:副本
posthog_cohortpeopleNonPersonData所有操作:副本
posthog_featureflaghashkeyoverrideNonPersonData所有操作:副本
posthog_personoverridePersonData读取/写入遵循 person 路由
posthog_personlessdistinctidPersonData与 person 相同

如果表未在上方列出,停止 —— 该数据不应经过 personhog。

步骤 0:设计数据访问模式

在编写任何 proto 之前,先弄清楚你需要的 SQL 查询。 然后根据可用索引验证它 —— 参见 references/database-indexes.md。

关键问题:

  • 此查询涉及哪些表?
  • WHERE 子句是否匹配现有索引?每个查询都必须是索引扫描,绝不能是全表顺序扫描。
  • 这是读还是写?这决定了路由(参见上表)。
  • 对于读取:调用方需要强一致性(主库连接池),还是最终一致性(副本连接池)可以接受?
  • 对于批量查询:批次是在单个 team 内还是跨 team?

步骤 1:定义 proto 消息和 RPC

所有 proto 文件都位于 proto/personhog/。 有关消息约定和完整示例,参见 references/proto-conventions.md。

在何处添加什么
  1. 消息类型 → proto/personhog/types/v1/<domain>.proto(person.proto、group.proto、cohort.proto、feature_flag.proto 或 common.proto)
  2. 服务 RPC → proto/personhog/service/v1/service.proto(客户端调用的公共 API)
  3. 副本 RPC → proto/personhog/replica/v1/replica.proto(路由器委派给的内部 API)
  4. 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)
  1. 添加一个 trait 方法,位于 rust/personhog-replica/src/storage/traits/<domain>.rs
  2. 实现查询,位于 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
    • 对空的批量输入提前返回
  3. 添加存储测试,位于 rust/personhog-replica/tests/storage_tests.rs
3b. 服务层(personhog-replica)
  1. 添加 RPC 处理器,位于 rust/personhog-replica/src/service/mod.rs
    • 从 proto 请求中提取字段
    • 调用存储 trait 方法
    • 将存储结果转换为 proto 响应
    • 将存储错误映射为 tonic Status 代码
  2. 添加服务测试,位于 rust/personhog-replica/tests/service_tests.rs
3c. Router 接线(personhog-router)

router 转发请求字节而不对其进行解码,因此新的 RPC 在那里不需要处理器。

  1. 将方法名添加到 rust/personhog-router/src/proxy.rs 中的 KNOWN_METHODS,并保持列表有序
    • 必须到达 leader 的方法会在 proxy.rs 中按名称匹配;其他所有方法都转发到 replica
    • 同一文件中的 known_methods_is_sorted 和 known_methods_matches_service_proto 在该列表与 service.proto 匹配之前会失败
  2. 将该方法添加到每个实现生成 trait 的 PersonHogService mock 中,包括 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 均通过

评论

支持 Markdown