87 lines
3.5 KiB
Markdown
87 lines
3.5 KiB
Markdown
# T7.5 — Partition keys on adapters
|
|||
|
|
|
||
|
|
| Field | Value |
|
||
|
|
|---|---|
|
||
|
|
| Phase | P7 — Distribution |
|
||
|
|
| Size | S — under 1 day |
|
||
|
|
| Status | Not started |
|
||
|
|
| Flags | — |
|
||
|
|
| Spec | inlined below |
|
||
|
|
| Blocks | — |
|
||
|
|
|
||
|
|
## Goal
|
||
|
|
|
||
|
|
Every broker adapter sets the partition key from `(TenantId, RunId)`. Never from
|
||
|
|
a correlation id.
|
||
|
|
|
||
|
|
## Facts (inlined — no spec read needed)
|
||
|
|
|
||
|
|
- **Per-run total order, nothing promised across runs.** Downstream consumers
|
||
|
|
must therefore partition by run key.
|
||
|
|
- A **correlation id collapses unrelated runs onto one partition while splitting
|
||
|
|
single runs across several** — the exact inversion of the ordering contract.
|
||
|
|
- The same reasoning drives the outbox key shape: per `BranchKey`, ascending
|
||
|
|
`Lsn`, which is what gives the relay its defined order.
|
||
|
|
- Ordering guarantees are only as strong as the weakest adapter, so this applies
|
||
|
|
to every adapter shipped, not just the first one.
|
||
|
|
|
||
|
|
## Steps
|
||
|
|
|
||
|
|
1. Define one helper that derives the partition key from `(TenantId, RunId)` and
|
||
|
|
make every adapter call it. A single function is what makes this auditable.
|
||
|
|
2. Remove any adapter parameter that lets a caller supply their own partition
|
||
|
|
key. If one is needed for an external contract, name it explicitly and
|
||
|
|
document that ordering is then the caller's problem.
|
||
|
|
3. Wire the helper into the relay's publish path (T7.4).
|
||
|
|
4. Test: two runs sharing a correlation id land on different partitions.
|
||
|
|
5. Test: all events of one run land on a single partition — assert across a run
|
||
|
|
that spans branches and attempts.
|
||
|
|
|
||
|
|
## Acceptance
|
||
|
|
|
||
|
|
- Two runs sharing a correlation land on **different partitions**.
|
||
|
|
- One run's events **never split across partitions**.
|
||
|
|
|
||
|
|
## Verify
|
||
|
|
|
||
|
|
**Harness:** a broker (or fake) exposing the partition assignment per message.
|
||
|
|
|
||
|
|
**Integration test** — `tests/it_partition_keys.rs`:
|
||
|
|
1. Two runs sharing one correlation id. Assert their events land on **different**
|
||
|
|
partitions.
|
||
|
|
2. One run spanning multiple branches, attempts and steps. Assert **every** event
|
||
|
|
lands on a **single** partition.
|
||
|
|
3. Two tenants with colliding `RunId` values. Assert they do not share a
|
||
|
|
partition — the tenant must be in the key.
|
||
|
|
4. Assert every shipped adapter derives its key through the one shared helper —
|
||
|
|
an audit test enumerating adapters and asserting each calls it.
|
||
|
|
5. Assert no public adapter parameter allows a caller-supplied partition key; if
|
||
|
|
one exists for an external contract, assert it is separately named and
|
||
|
|
documented.
|
||
|
|
6. Stability: the same `(TenantId, RunId)` maps to the same partition across
|
||
|
|
process restarts.
|
||
|
|
|
||
|
|
**Command:** `cargo test -p distribution partition_keys`
|
||
|
|
|
||
|
|
**False pass:**
|
||
|
|
- Step 2 with a short run whose events happen to hash to one partition regardless.
|
||
|
|
Use a run long enough that a per-event key would demonstrably split it, and
|
||
|
|
assert the partition set has size exactly 1.
|
||
|
|
- Step 1 passing by coincidence with only two runs — use 50 correlated pairs and
|
||
|
|
assert the distribution is spread.
|
||
|
|
- Step 4 omitted: one adapter doing it correctly proves nothing about the next
|
||
|
|
one added, and ordering is only as strong as the weakest adapter.
|
||
|
|
|
||
|
|
## Traps
|
||
|
|
|
||
|
|
- Using a correlation id because it is already threaded through the request. It
|
||
|
|
is the natural choice and it breaks the only ordering promise the system makes.
|
||
|
|
- Hashing `RunId` alone without the tenant. Two tenants can then collide on one
|
||
|
|
partition and, worse, the key stops being tenant-scoped.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
Background (not required to do this task):
|
||
|
|
[rust-agentic-sys.md](../../../rust-agentic-sys.md) §8.3, §9.2 ·
|
||
|
|
[rust-agentic-task.md](../../../rust-agentic-task.md)
|