Skip to content

M1-72 Performance gate of the server core

No Date Ruling Reason
M1-72/0 2026-10-18 Base int/m1-wave38 at 297ab0c, branch feat/M1-72-performance-gate in its own worktree; the branch is not merged locally and not pushed, the PR lane lands it. The brief relies on no core rule or ruling that main has and the base lacks (ADR 0063 with the modes of point 3 and the perf.* rows are on the base), so nothing is ported. The brief's English chapters are the German files of the base (02-architektur.md, 16-betrieb.md, 07-rechte.md); the text this ticket adds is English. Instructions of the launcher, which override the brief's landing step; precedent M1-23/9 and M1-25/8 for English text in German chapters.
M1-72/1 2026-10-18 perf/internal/report reads the thresholds from 02-architecture.md, or 02-architektur.md where the English file does not exist, finds the section by either title ("Configuration values of this chapter", "Konfigurationswerte dieses Kapitels"), the place by either name ("Load test", "Lasttest") and the comparison by the English meaning or its German translation ("target (minimum)" is >=; "target (maximum)" and "target" are <=). The base has the German chapter, main the English one; the loader must hold on both without a literal threshold.
M1-72/2 2026-10-18 The profile mode also compares the container runtime of the database (docker info) with perf.reference_profile: when it is below the profile the mode is below, whatever the host of the test process has. The profile names a local PostgreSQL with its memory; a Docker VM with fewer CPUs or less memory cannot give the container the limits of the emulated mode, so its numbers say nothing about the profile. The build machine of this ticket runs its database in a smaller Docker VM: mode below; the numbers are in the report.
M1-72/3 2026-10-18 make perf runs ./perf/... and ./test/chaos/... in one go test -p 1, then perfreport prints the table and decides the exit code; each test also fails itself on a missed target or a broken invariant, and a target without a result fails the report. The shared frame of the load tests is the tagged package perf/internal/perfkit; BenchmarkAuthorizeCached already reported p99-ns and stays unchanged; its test is the package perf/authorize_cached. Two measurements must not share the machine; one results file and one table for all targets (brief scope 1).
M1-72/4 2026-10-18 Every simulated client of a load test sends X-Forwarded-For with its own documentation address (its own /64 of 2001:db8::/32) through the loopback peer that the default server.trusted_proxies trusts; the measured server keeps its default configuration. The credential check reserves a token of rl.public_auth per client address before it verifies (02 "Chain in the api node"), so the whole load from 127.0.0.1 measured that limiter (429 answers) instead of the server; behind the reverse proxy of a deployment the server sees distinct clients.
M1-72/5 2026-10-18 TestPerf_FanoutLocalP99 writes into the main threads of ten channels of one space, like load_messages, and its receivers read the space, not one channel. One thread serializes its messages (thread_seq); the sustained rate in a single thread exhausted the pool on this machine (503 no database connection was free within database.acquire_timeout), which measures the thread lock, not the fan-out.
M1-72/6 2026-10-18 extra holds the pool occupancy and the lock waiters sampled from pg_stat_activity (in use p99 and maximum, share of samples with every connection of database.max_conns in use, waiting backends), not the histograms of pool acquire wait and bothy.db.event_seq.lock_wait. The measured server is a child process without an in-process telemetry sink (M1-69 adds the sinks); the database side shows saturation and lock waits without one.
M1-72/7 2026-10-18 The production request validator keeps no responses of the operations, so the library compiles request and parameter schemas only; the test build with contract_validate keeps them for G8 (keepResponses, set by contractcheck_install.go). make perf measured the time to ready and the idle RSS above perf.startup_ready_max and perf.idle_rss_max; the warm-up of the response schemas of every operation took most of the start and memory the process never used. After the fix both are below their thresholds on the build machine (numbers in the report). Tests TestNewValidatorLeavesOutResponseSchemas, TestG8_ChainInstallsTheCheck.
M1-72/8 2026-10-18 The Authorizer reads the thread and channel facts of a resource once per structure scope (authorize.WithStructureScope, a memo separate from the request memo and the rights version); the hub opens one scope per live event for all candidate connections, PageEvents one per catch-up page. Every check read thread and channel from the database, so a live event cost two queries per connection: with 100 receivers at the sustained rate the log tail fell behind, receivers had about a tenth of the events after a minute and live delivery looked stalled. After the fix every receiver got every event in order, and the fan-out and the fractional catch-up page dropped by an order of magnitude on the build machine (numbers in the report). Tests TestStructureScopeReadsThreadAndChannelOnce, TestHub_OneStructureScopePerEvent, TestCatchUpPageOneStructureScope, TestWithStructureScope.
M1-72/9 2026-10-18 database.max_conns is neither confirmed nor changed: the run on this machine is in mode below, and the saturation it shows comes from a database that commits far below perf.event_commit_min_per_s there. The row keeps its default and its note; chapter 16 "Capacity and resources" points to make perf and perf/README.md for the measurement on the reference host. Brief design note 9 asks for evidence; a value from a host below the profile would be a guess. The milestone-close run on the reference host is an owner operation (brief "Out of scope").
M1-72/10 2026-10-18 The catch-up page with a large visible set (largeChannels channels, each with its own grant) stays above perf.catchup_page_p99 on this machine: its pages spread over hundreds of channels, so the structure scope saves little and each event still costs two round trips to the database in the Docker VM. It is reported as not proven, not optimized further. Mode below; brief "Out of scope": no optimization beyond what a missed target on the profile requires. Reading the structure of a page in one query per kind is the lever if the reference run misses it.
M1-72/11 2026-10-18 The chaos writers repeat an unacknowledged send with the same client_msg_id and the same Idempotency-Key; the receivers reconnect with identify and cursor = the last seq they saw (Client.GatewayFrom). harness.Client.Gateway is built here (M1-71/7 left it out). A real client repeats a request with its key (06 "Idempotency"); the cursor makes the server catch the receiver up, so a lost frame would show as a gap.
M1-72/12 2026-10-18 The chaos receivers keep their gateway connections until every acknowledged message arrived and are never reconnected by the test; the load runs past the delivery bound after the last fault, and every message acknowledged after it must reach every receiver within sync.log_poll_interval plus database.listen_reconnect_backoff_cap plus a slack (CheckDeliveredWithin); after a LISTEN loss no receiver may have reconnected. Red team round 1 (Major, reproduced): the receivers were reconnected with a cursor before the checks, so the catch-up of the reconnect delivered what a dead log tail never sent, and both tests stayed green with a tail that stopped after the first fault. With the same sabotage both tests now fail.
M1-72/13 2026-10-18 Port onto main: code and tests as in the ticket range (297ab0c..7afc96ae4), which entered the integration as one squash with authorize.WithStructureScope in Hub.Dispatch after structureChanged of M1-66, except the points named here. Main's chapters are English, so perf/internal/report reads only 02-architecture.md, the section "Configuration values of this chapter", the place "Load test" and the English meanings, and the doc.go files and perf/README.md name the English chapters; M1-72/1 applies only to the base. Main's pgtest has the lease watchdog with AutoRemove of every container (PR #41), which the base lacked and which removes a stopped container, so DB.Restart does not stop and start the container: it sends SIGUSR1 to the watchdog script, which stops PostgreSQL with a fast shutdown and starts it again in the same container (TestWatchdogScriptRestartsOnUSR1); the durable container keeps the random loopback port, AutoRemove and its lease, and the free-port pick of the ticket is not ported. TestFailedTemplateIsDropped of main takes the new clusterKey. The Dart step of make perf (M6-12) and the cross-OS lint loop of the base are not on main, so make lint only gains the tag perf, the make perf test passes no PERF_HOST, and docs/quality/gates.md gets the M1-72 paragraph without the Dart one. The changelog takes only the entry of this ticket. The integration rulings INT-W40/1 and INT-W42/0 adapt the harness gateway client to M1-70 and M1-67, which are not on main, so none applies. No migration. The base int/m1-wave38 held tickets that are not on main (for example M2-24, M2-26, M3-13, M3-18, M4-18, M6-01, M6-02) and lacked main's container cleanup; the port keeps the behaviour of the ticket (a restart breaks every connection and keeps host port and data) and adapts only to what main has.
M1-72/14 2026-10-21 TestWatchdogScriptRestartsOnUSR1 sends SIGUSR1 only after the stub server has its SIGINT handler (the stub writes ready after installing it), not already when it wrote started. The stub writes started before it executes the Perl process with the handler; under load the restart's INT arrived in between, ended the stub without the fast shutdown and failed the test on a build run (reproduced locally with a one second pause in the stub; with the fix the test passes with that pause).
M1-72/15 2026-10-21 Port onto main after M1-66, M1-67 and M1-68: the gateway client of test/harness dials through the G9 entry point wstest.Dial(t, ...) of M1-67 and passes the test of its server (Client.tb), the harness part of INT-W42/0; M1-72/13 named it as not applying while M1-67 was not on main. M1-67 changed the signature of wstest.Dial; without the change the harness does not compile.
M1-72/16 2026-10-21 A host or container runtime has the memory of perf.reference_profile when the memory the operating system reports (MemTotal of /proc/meminfo, hw.memsize, the memory of the Docker VM) is at least the share memoryReportedNum/memoryReportedDen of perf/internal/report of it; the CPU count stays exact for match. The reported memory leaves out what the kernel and the firmware keep: a Linux VM with exactly the 8 GiB of the profile reports less than 8 GiB, so the reference profile itself was classified below and make perf could never end green on it (review finding on the pull request, test with the MemTotal of an 8 GiB VM).