Repository navigation
perf(db): stop the events recent pass at a ceiling for regular-kind reads - #9157
Conversation
The recent pass of a split newest-first read had only a floor, so Postgres still planned and locked the empty monthlies made ahead of time and the open-ended last partition: about nine partitions, each with its index set. A read of regular kinds only now also bounds the recent pass below the start of the month after now + 1 h, as a literal, so it locks the previous and current monthlies (and the next one in the last hour of a month). Ingest rejects regular events more than 900 s ahead, and the relay stamps its own regular events with its clock, so no such row sits at or above the ceiling. Replaceable and addressable rows have no such bound: the relay stamps its own replacements one second past the live row, so a coordinate replaced faster than once a second runs ahead of the clock. A read that may match them keeps the open recent pass. Co-authored-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Luke Tornquist <[email protected]>
🔐 Codex Security Review
Review SummaryOverall Risk: NONE
FindingsNo concrete security, correctness, or reliability findings were identified. Notes
Generated by Codex Security Review | |
`pg_locks` lists locks in every database, and the per-test databases are clones of one source, so a partition has the same OID in all of them. The snapshot fixtures waited for any ungranted lock on that OID, so a test in another database waiting on its own copy of the partition released the fixture before its read reached the older pass. The replacement then committed before the read's snapshot, and the page showed it. Match only waits in the current database, as the advisory-lock waits in the other fixtures already do. Co-authored-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Luke Tornquist <[email protected]>
wpfleger96
left a comment
There was a problem hiding this comment.
🤖 LGTM. Checked the one claim the ceiling depends on: that no regular-kind row is stored with created_at past now + 1h. Client events, including the HTTP bridge, go through ingest_event, which rejects anything more than 900 s off the relay clock. The relay-signed regular kinds I traced (workflow stream messages, huddle lifecycle events) are stamped at signing time, and moderation notices reuse a past outbox timestamp. Keeping the open pass for any read that can match replaceable or addressable kinds covers the max(previous + 1 s, now) restamps. The pg_locks test pins the pruning behaviour under generic plans, and the database filter added to the snapshot fixtures fixes a real cross-database false positive.
…al (#9197) ## Summary A newest-first `events` read whose own `since` is at or above the recent-pass floor now runs as a single recent pass. Postgres prunes it to the recent monthlies at plan time instead of locking every partition. Results are unchanged. **Why.** `split_for` skipped any read whose `since` was at or above the floor (`since_prunes`), on the theory that its `since` already prunes. It does, but only as a bind parameter. Once a prepared statement switches to a cached generic plan, Postgres prunes bind-parameter bounds at execution, after `AcquireExecutorLocks` has already locked every `events` partition and its indexes. Live REQs with a recent `since` are this shape, for example `#p` mention reads from reconnecting agents. Under load they spill past the 16 fast-path lock slots and contend on `LWLock:LockManager`. The ceiling PR (#9157) named this gap as identified debt. **What changes.** Such a read now gets the same `Split` as other recent reads, so the recent pass carries the floor literal (plus the ceiling literal for regular-kind reads) on `e.created_at` and on `m.event_created_at` for `#p`. `Split` gains `older: bool`. It is false when the read's own `since` is at or above the floor, because no row below the floor can match, so no older pass follows a short recent pass. A read whose `until` falls below the floor stays unsplit, as before. No protocol, schema, index or client change. ### Related issue Follow-up to #9157 (its identified debt: bind-parameter `since` reads). ### Testing - `recent_pass::tests::split_eligibility`: the table now records not split / ceiled / open and whether an older pass follows. New rows: `since` at the floor, a recent `since` with regular, any and addressable-only kinds, and a recent `since` with `until` below the floor. - `recent_pass::tests` in-memory exactness: adds a `since`-at-floor split that runs exactly one pass and returns the rows at or above the floor. - `postgres_tests::since_bounded_mention_read_locks_only_recent_partitions` (new): runs partition maintenance, inserts `#p` rows at the floor, now, one second below the ceiling and one day below the floor, then reads with `since = floor`. The production path returns the three recent rows through the mention join. Under `plan_cache_mode = force_generic_plan` the recent pass locks only the partitions holding those rows. The unsplit read locks every partition. - The partition-lock helper from #9157's test is now shared by both lock tests. - Diagnostic on Postgres 17.11 with the 19-partition layout partition maintenance keeps, for a `#p` read with `since` at the floor (not committed): | | `events` partitions locked | Relation locks | Non-fast-path locks | |---|---|---|---| | Before (bind-only `since`, generic plan after five runs) | 19 | 26 | 10 | | After (floor and ceiling literals) | 2 | 9 | 0 | `EXPLAIN (GENERIC_PLAN)` before appends all 19 partitions. After, it appends only the previous and current monthlies. Mutation-checked on Blox. Each of these fails the new Postgres test or the unit tests: removing the `since_prunes` split, ignoring `older`, and dropping the `e.` literal. ## What got simpler Nothing got simpler. The change adds one flag to the existing `Split` type and no new concept. The duplicated partition-lock reader in the #9157 test became one shared helper. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: Luke Tornquist <[email protected]> Co-authored-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz> Co-authored-by: Claude Opus 5.5 <[email protected]>
Summary
The recent pass of a split newest-first
eventsread now has an upper bound when every requested kind is a regular kind. Postgres stops planning the empty monthlies made ahead of time and the open-ended catch-all. Results are unchanged.Why. #9091 gave the recent pass a floor (
created_at >= <start of previous month>) but no ceiling, and its "Not covered" section names that gap. The partition manager keeps 6 empty monthlies ahead of the current month, plus an open-ended catch-all, so the recent pass still planned and locked about 9 partitions, each with its full index set. That is far past a backend's 16 fast-path lock slots.What changes. For a regular-kinds-only read, the recent pass reads
floor <= created_at < ceiling. The ceiling is a SQL literal at 00:00 UTC on the first day of the month afternow + 1 h, so the pass plans the previous and current monthlies, plus the next one during the last hour of a month. The older pass is unchanged.Why no row is lost. Ingest rejects events more than 900 s ahead of the relay's clock (
MAX_TIMESTAMP_DRIFT_SECS). Every regular-kind event the relay signs itself is stamped with its clock. The extra margin up to 1 h covers clock differences between relay processes. I audited every writer ofeventsrows for this.Why only regular kinds. The relay stamps its own replacements of some replaceable and addressable rows at
max(previous + 1 s, now): group discovery (39000–39002), the NIP-43 membership list, the archived list, DM visibility and verified profiles. A coordinate replaced faster than once a second can therefore run ahead of the clock. A read that may match those kinds (nokinds, or any replaceable/addressable kind) keeps the open recent pass from #9091.No protocol, schema, index or client change.
Related issue
Follow-up to #9091 (its "Not covered" upper bound).
Testing
recent_pass::tests: ceiling arithmetic (month and year wraps, and either side of the 1 h margin). Predicate literals with and without a ceiling. The eligibility table now records not split / ceiled / open per read shape, including mixed regular and latest-state kinds. The in-memory exactness test runs both the ceiled and the open split.split_passes_bound_created_at_with_a_literal: SQL shape. The recent pass is bounded on both sides, one.andm.for#p. The older pass and unsplit reads carry no ceiling.postgres_tests::recent_pass_locks_only_the_partitions_below_its_ceiling(new): runs partition maintenance to get the layout a running relay keeps, then runs the recent pass underplan_cache_mode = force_generic_planinside a transaction and reads the backend'spg_locks. The ceiled pass locks exactly the partitions holding rows at the floor, now, and one second below the ceiling. The open pass locks strictly more.split_page_reads_match_the_unsplit_read: now matches the recent pass by its exact floor and ceiling literals.pg_locksrow on a partition's OID.pg_lockscovers every database, and the per-test databases are clones that share OIDs, so a wait in another test's database could release the fixture early. That likely caused one CI failure ofsplit_read_passes_share_one_snapshot, which I couldn't reproduce on Blox. Both lock checks now match only the current database, as several advisory-lock waits in other fixtures already do.Mutation-checked on Blox. Each of these fails the new Postgres test or the unit tests: dropping the ceiling from the predicate, applying it to every split read, and widening it.
What got simpler
Nothing got simpler. The change adds one optional bound to the existing
Split/Rangetypes instore/recent_pass.rsand no new concept.floor_forbecamesplit_for, and it returns the floor and the ceiling together.Identified, out of scope: unsplit reads whose
sinceis a bind parameter (live REQs with a recentsince) still lock every partition under a cached generic plan on PG 16. Postgres prunes bind-parameter bounds only at execution, after it has taken the locks.🤖 Generated with Claude Code