Skip to content

M3-24 Push handoff performance target

No Date Ruling Reason
D-M3-24-1 2026-10-23 TestPerf_PushHandoffP95 runs the server in the test process (harness.Start, role all, Options.DurableDB). harness.Options.APNsFake points the wired APNs adapter at Fake-APNs through two new test overrides of wire.Deps, APNsEndpoints (to apns.Deps.Endpoints) and APNsRootCAs (to the egress root CAs of the APNs adapter only, named after OIDCRootCAs instead of the brief's EgressRootCAs), and appends the Fake's rule to egress.allowed_hosts; APNs is still enabled through push.apns.*. No configuration key, flag or environment variable reaches the overrides; TestProductionWiringHasNoTestOverrides shows that the deps of cmd/bothy leave them nil and that cmd/bothy names neither field. The key is a P-256 key per run in the secrets directory (file:<secrets.dir>/apns/key.p8 through Options.SecretFiles, mode 0400) instead of a t.TempDir() file, because the secret provider of the harness reads that directory. harness.Options also gains DBCPUs and DBMemoryBytes (pgtest.Limits) for the emulated profile. Place: internal/wire/serve.go, module_notify.go, push_apns.go, test/harness/harness.go, perf/internal/pushworld/server.go. M3-20 left no Go-level path to the wired adapter (APNsProviders keeps Endpoints nil), and a server subprocess could only reach the Apple hosts.
D-M3-24-2 2026-10-23 t0 is occurred_at of the message.created event of the mention (the time of its transaction, at or before the commit), read after Drain in one read-only query; t1 is the new apnstest.Request.AcceptedAt, stamped after the Fake read the request and verified the JWT, right before it writes 200 (zero for every other answer). A sample is t1 minus t0 corrected by report.ClockOffset (perfkit.ClockOffsetOf over the admin pool); one sample per mention message scheduled in the window and per mentioned recipient; the value is the nearest-rank p95. Place: perf/internal/pushhandoff/samples.go, internal/notify/adapters/apns/apnstest/server.go. 13 section 9; both points can only make a sample larger, never smaller than the real handoff.
D-M3-24-3 2026-10-23 Only mention hints, under perf.sustained_messages_per_s human messages, with the named constants of perf/internal/pushhandoff/constants.go (BudgetShare, SendersPerChannel, RecipientsPerChannel, MentionEvery, MentionsPerMessage, Warmup, Window, Drain, MinSamples, LagSampleEvery, values in perf/README.md). Plan derives every count from the defaults of ratelimit.rl.message.* and from 02; the open-loop scheduler of M1-72 became perfkit.Schedule, which perfkit.OpenLoop now uses, so both load tests share one scheduler. Place: perf/internal/pushhandoff/plan.go, perf/internal/perfkit/load.go. A mention goes at once (13 4.5 point 3), so its t0 is the commit; 02 17 names no load for this target, and the consumer reads every event of the log, so the chat load belongs to the measurement.
D-M3-24-4 2026-10-23 One server for seeding and measurement: every ratelimit.* key raised except ratelimit.rl.message.per_minute and .burst (pushworld.RateLimits); the seeding sends no message, the measured sends are only createMessage. The registrations are fixture rows through the admin pool (apns, production, com.example.chat, 64 hex digits, the recipient's device and the newest session of that device), before the measurement. Place: perf/internal/pushworld/pushworld.go. The in-process server cannot restart on the same database; registerPush (M3-22) is not on the measured path.
D-M3-24-5 2026-10-23 A value counts only when the path behaved correctly while it was taken: the result fails on any condition of pushhandoff.Check besides the threshold (an answer other than 201, a missing, duplicated or misdirected push, a wrong kind, thread, collapse ID or badge, a badge sync, the canary in a body or header, a Fake answer other than 200, an unhealthy registration, fewer than MinSamples samples). Messages name counts and IDs, never tokens. perf.push_handoff_p95 joins report.Required, so a run without its result does not end green. Place: perf/internal/pushhandoff/checks.go, perf/internal/report/results.go. ADR 0063 point 5 and T-PU-01: a fast but wrong path must not end green.
D-M3-24-7 2026-10-23 Fix of the measured path (design note 14): the consumer push handles each event under authorize.WithRequestScope and authorize.WithStructureScope, so all push.deliver checks of one event share one rights version and one read of the thread structure, and it reads the coalescing window of a recipient only when domain.NeedsWindow says the decision reaches that step. Decisions do not change (TestNeedsWindowMatchesDecide enumerates kinds, levels, switches, devices and windows); a rights change is seen by the next event. On the build host the consumer fell far behind the sustained load before the fix and kept up with the same load of a sequential sender after it. Place: internal/notify/app/consumer.go, gather.go, internal/notify/domain/decide.go. The consumer read the rights version, the structure and the window again for every reader of the channel and fell behind the sustained load, so no mention of the window reached Fake-APNs; the checks of one request already share one version the same way.