adding-personhog-rpc について
PersonHog の RPC を追加する PostHog エンジニア向け開発スキルです。データアクセス設計から proto、Rust サービス、Python と Node.js のクライアント更新までを扱い、インデックス、ルーティング、再試行時の安全性を重視します。
主な機能
- 01対象テーブルとデータ種別の確認
- 02既存インデックスに沿ったクエリ設計
- 03サービスとルーティングの proto 定義
- 04Python と Node.js クライアントの更新
- 05Rust のストレージ層と RPC 実装
- 06テスト追加とビルド検証
活用シーン
- 01PersonHog が扱うデータ向け RPC の追加
- 02RPC 用クライアントラッパーとテスト用実装の更新
- 03RPC ルーティング、再試行、DB アクセスの検証
インストール
コマンドをコピーしてターミナルで実行します。
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 | 読み取り: replica (結果整合) または leader (強整合)。書き込み: leader |
posthog_persondistinctid | PersonData | person と同じ |
posthog_group | NonPersonData | すべての操作: replica |
posthog_grouptypemapping | NonPersonData | すべての操作: replica |
posthog_cohortpeople | NonPersonData | すべての操作: replica |
posthog_featureflaghashkeyoverride | NonPersonData | すべての操作: replica |
posthog_personoverride | PersonData | 読み取り/書き込みは person のルーティングに従う |
posthog_personlessdistinctid | PersonData | person と同じ |
上記にテーブルが記載されていない場合は、停止してください — このデータは personhog を経由すべきではありません。
ステップ 0: データアクセスパターンを設計する
proto を書く前に、必要な SQL クエリを明確にしてください。 次に、利用可能なインデックスに対してそれを検証します — references/database-indexes.md を参照してください。
重要な質問:
- このクエリはどのテーブルにアクセスしますか?
- WHERE 句は既存のインデックスに一致しますか? すべてのクエリはインデックススキャンでなければならず、シーケンシャルスキャンであってはなりません。
- これは読み取りですか、それとも書き込みですか? これがルーティングを決定します (上記の表を参照してください)。
- 読み取りの場合: 呼び出し元は強整合性 (primary プール) を必要としますか、それとも結果整合性 (replica プール) で許容できますか?
- バッチ検索の場合: バッチは単一のチーム内ですか、それともチームをまたぎますか?
ステップ 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) - リーダー RPC →
proto/personhog/leader/v1/leader.proto(これが leader にルーティングされる person データの書き込みである場合のみ)
サービスとレプリカの proto は、同一のシグネチャで RPC を宣言する必要があります。 ルーターは service → replica (または leader) への委譲を透過的に行います。
リーダー RPC は at-least-once 配信下で安全でなければならない
リーダーパスのリクエストは複数回配信される可能性があります。クライアントは不明瞭な失敗の後に UNAVAILABLE をリトライし、
ルーターは内部的にフェンスおよびトランスポートでバウンスされたリクエストを再送します。
したがって、すべてのリーダー RPC は再配信下で収束するか —
読み取り専用のルックアップ、同じ状態に再適用されるマージ、トゥームストーン方式の削除、max-merge のバージョンフロア —
または重複を検出できるように明示的な操作アイデンティティを持つ必要があります。
正確な契約に注意してください。収束するマージであってもインターリーブには敗北します
(再送が、その間に別の呼び出し元が行った同一フィールドへの書き込みを上書きする可能性があります)。
この残差は現時点では許容されており、操作アイデンティティによって解消されます(personhog-leader の README を参照)。
収束もせずアイデンティティも持たない RPC(ガードされていないインクリメント、append)を追加してはなりません。
必要な操作がどちらの形にも当てはまらない場合は、proto を定義する前に冪等キーを持つように再設計してください。
Step 2: クライアントスタブを生成する
Python
bin/generate_personhog_proto.sh
次に 3 つのファイルを更新します:
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へデフォルトスタブを追加
Rust
生成ステップは不要です — tonic は cargo build で再生成します。
ただし、RPC を実装する必要があります(次のステップ)。そうしないとビルドが失敗します。
Step 3: Rust で実装する
コンパイラーが導いてくれます — proto を定義すれば、cargo build のエラーがどのトレイトメソッドが不足しているかを正確に教えてくれます。
3a. ストレージ層(personhog-replica)
- トレイトメソッドを追加 する:
rust/personhog-replica/src/storage/traits/<domain>.rs - クエリを実装 する:
rust/personhog-replica/src/storage/postgres/<domain>.rssqlx::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 リクエストからフィールドを抽出
- ストレージトレイトメソッドを呼び出す
- ストレージの結果を proto レスポンスに変換
- ストレージのエラーを tonic の
Statusコードにマッピング
- サービステストを追加 する:
rust/personhog-replica/tests/service_tests.rs
3c. ルーターの配線(personhog-router)
ルーターはリクエストバイトをデコードせずに転送するため、新しい RPC にハンドラーは不要です。
- メソッド名を追加 する:
rust/personhog-router/src/proxy.rsのKNOWN_METHODSに、リストをソートした状態で追加- リーダーに到達する必要があるメソッドは
proxy.rsで名前によってマッチングされます。それ以外はすべてレプリカに転送されます - 同じファイル内の
known_methods_is_sortedとknown_methods_matches_service_protoは、リストがservice.protoと一致するまで失敗します
- リーダーに到達する必要があるメソッドは
- メソッドを追加 する: 生成されたトレイトを実装するすべての
PersonHogServiceモックに追加します。personhog 外のものも含みます(rust/personhog-router/tests/common/mod.rs、rust/property-defs-rs/tests/)。tonic のトレイトにはデフォルトメソッドがありません
同じ振る舞いの複数のバリエーションをテストする場合は、rstest のパラメーター化テストを使用してください。
Step 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にも) - リーダー RPC のみ: 操作が at-least-once 配信下で安全である(再配信に対して収束する — 読み取り専用、再適用可能なマージ、tombstone、max-merge — または明示的な操作アイデンティティを持つ)
- Python スタブが生成され、
proto/__init__.pyが更新され、client.pyのメソッドが追加され、fake_client.pyが更新されている - Node.js スタブが生成され、
client.test.tsの SERVICE_DEFAULTS が更新されている - Rust のストレージトレイト + postgres 実装 + サービスハンドラー + ルーター配線が実装されている
- ストレージ、サービス、ルーターの各レイヤーにテストが追加されている
- 3 つのクレートすべてで
cargo buildとcargo testが成功する