ADR-0085 — Shard the renewal-saga GSI4 partition key (kill the single-partition hot key)

Context

Production throws ThrottlingException: Throughput exceeds the current capacity of your table or index … check if you have a hot key on the renewal detail route (GET /leasing/renewals/[leaseId]), on a DynamoDB Query. Sentry issue 7500098427: 4,481 events over ~6 weeks (first 2026-05-22, last 2026-07-08), bursty, not constant. It is user-facing — a PM's renewal page intermittently errors.

Root cause — a single-partition hot key by construction. The renewal-saga-person-index (GSI4) uses a constant partition key: GSI4PK = "RenewalSaga" for every saga row; GSI4SK = "{personId}#{openedAt}". That constant PK was a deliberate trade-off so getAllOpenSagas() / getAllSagas() can enumerate every saga with one partition-wide Query (the reconcilers/sweeps rely on it). The cost: all saga reads and writes converge on one physical DynamoDB partition, which is hard-capped at ~3000 RCU / 1000 WCU.

The partition is small — 179 rows — so this is not a size problem, it is a concurrent-throughput convergence problem. On the PAY_PER_REQUEST (on-demand) propflow-prod table, a single partition can't exceed that per-partition ceiling and its adaptive capacity lags a sudden burst. Everything below hits that one partition:

When a sweep + a polled list-page load + a workflow-write burst coincide, aggregate throughput to the one partition exceeds the ceiling → throttle, surfacing on whichever query lands during saturation. The detail route is the observed victim, not the cause.

Decision

Shard the GSI4 partition key across a fixed fan-out of N partitions, deterministically by personId.

N is a small fixed constant (proposed 16) — enough to lift the aggregate ceiling ~16× (≈48k RCU / 16k WCU spread) while keeping the all-sagas fan-out cheap (16 bounded parallel queries over a tiny dataset). N is a code constant, not stored per-row (the shard is recomputable from personId), so it can only change with a re-backfill — documented as a one-way choice.

Alternatives considered

Consequences

Positive

Costs / risks

Verification (the bar for "done")

See the handoff doc for the shard helper, the exact backfill script shape, the read-cutover sequence, the drift guard, and the rollback path.