Skip to content

fix(db): seek replaceable heads by channel - #9147

Merged
TheSentinel454 merged 9 commits into
mainfrom
elrond/events-drop-deleted-index-exact-replace-head
Nov 12, 2026
Merged

TheSentinel454 merged 9 commits into
mainfrom
elrond/events-drop-deleted-index-exact-replace-head

Conversation

@TheSentinel454

@TheSentinel454 TheSentinel454 commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Summary

replace_addressable_event_under_lock read and superseded a replaceable coordinate's head with channel_id IS NOT DISTINCT FROM $4. A btree can't use that as an index condition, so the read filtered every live row of the (kind, pubkey) across all channels. The relay signs every channel's state events (39000–39002) with one key, so each channel-state write walked the whole community's relay state for that kind. Both Db::replace_addressable_event and the relay's locked group-state snapshots go through this function.

The predicate is now spelled per case, as channel_id = $4 or channel_id IS NULL, and $4 is bound only for the first. The head read becomes an index seek to one channel instead of a filtered walk. It reads the same rows as before.

The WHERE scope and the head read are shared as macros (replacement_scope!, replacement_head_sql!), so the plan test checks the same predicate production runs. The supersede statement is spelled inside the function itself, which takes &mut AdmittedTx, as the guarded-write source scan requires. superseded_with_mentions_sql! now takes an expr, so it accepts a concat!.

Tests

  • replacement_channel_predicate_is_an_index_condition seeds one relay head per channel plus one channel-less head and refreshes the statistics. Then, for both cases, it checks that the head read and the supersede each have channel_id in an Index Cond. With IS NOT DISTINCT FROM, the planner walks the newest-first (pubkey, kind) index and applies the channel as a Filter. On an empty table it does that even with the per-case spelling, which is why the test needs the fixture.
  • addressable_replacement_supersedes_only_its_own_channel_scope: a channel write supersedes only that channel's head, and a channel-less write is ordered against the global head only.
  • Mutants, each killed by its test:
    • the scope restored to IS NOT DISTINCT FROM;
    • the global scope ignores the channel;
    • the in-channel supersede ignores the channel.

What got simpler

  • One predicate per case instead of a NULL-safe comparison the index can't use.
  • The head read and supersede share one scope definition.

🤖 Generated with Claude Code

TheSentinel454 and others added 4 commits November 5, 2026 16:21
Drop idx_events_community_deleted (migration 0084). No relay query reads
by (community_id, deleted_at), yet every events write maintains it and
every read that plans against events locks it on each partition. A
partitioned index cannot be dropped CONCURRENTLY, so the drop is a plain
DROP INDEX under a 1s lock budget that fails rather than queue writers
behind a long read; the next boot retries it.

replace_addressable_event read and superseded the coordinate's head with
`channel_id IS NOT DISTINCT FROM $4`, which a btree cannot use as an
index condition. Every relay channel-state write (39000-39002) filtered
the relay's live rows of that kind across the whole community. Spell the
predicate per case, `channel_id = $4` or `channel_id IS NULL`.

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]>
The planner picks idx_events_community_deleted for community deletion's
row count and purge, as the smallest community_id-led index. Every other
events index is also community_id-led and serves them after the drop.

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]>
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]>
On an empty table the planner prefers the newest-first (pubkey, kind)
index for LIMIT 1 and filters the channel, so the probe needs the shape
it guards: one relay head per channel with fresh statistics. The 0083
upgrade tests now check that 0083 is applied rather than that it is the
newest migration.

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]>
@TheSentinel454
TheSentinel454 marked this pull request as ready for review November 5, 2026 22:10
@TheSentinel454
TheSentinel454 requested a review from a team as a code owner November 5, 2026 22:10
lock_timeout applies to each lock wait, and DROP INDEX holds `events`
while it waits on each partition, so the whole stall is bounded by the
5s statement_timeout, not the 1s lock budget.

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]>
@github-actions github-actions Bot added the codex-security-review-current The posted Codex security review matches its recorded range. label Nov 5, 2026
@github-actions

github-actions Bot commented Nov 5, 2026 •

Copy link
Copy Markdown

🔐 Codex Security Review

Note: This is an automated, security-focused review generated by Codex.
Use it as a supplement to human review; false positives are possible.

Scope

  • Exact PR diff: 962b3fdd882b2cd78699ea2896beb9f1cbebf305...14576a0610cacfaa698bd3b8883fdea6540a8e35
  • Model: gpt-6.1-sol

💡 Click "edited" above to see earlier reviews for this PR.


Review Summary

Overall Risk: NONE

No concrete security, correctness, or reliability findings in the authorized PR range. The revised predicates preserve community and channel scoping, replacement ordering, locking, and rollback behavior.

Findings

No concrete security, correctness, or reliability findings were identified.

Notes

  • Review used read-only source and history inspection. Builds, tests, and live database query plans were not executed, as instructed.

Generated by Codex Security Review |
Requested by: @TheSentinel454 |
Workflow run

The per-case statements lived in a helper fn, which the guarded-write
source scan reads as a write seam outside the tenant chokepoint. Share
only the WHERE scope and head read as macros; the supersede is spelled in
replace_addressable_event, which opens the chokepoint.

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]>
@github-actions github-actions Bot added codex-security-review-current The posted Codex security review matches its recorded range. and removed codex-security-review-current The posted Codex security review matches its recorded range. labels Nov 5, 2026
…-head

Main moved the addressable replacement body into
replace_addressable_event_under_lock; the per-case head and supersede
statements now live there.

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]>
@github-actions github-actions Bot removed the codex-security-review-current The posted Codex security review matches its recorded range. label Nov 5, 2026
Production reader stats show the index scanned continuously, so the
drop comes out of this PR. The replaceable head and supersede fix stays.

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]>
@TheSentinel454 TheSentinel454 changed the title fix(db): drop unused events index, seek replaceable heads by channel fix(db): seek replaceable heads by channel Nov 5, 2026
@TheSentinel454

Copy link
Copy Markdown
Contributor Author

@buzz-security-review a948a43

@TheSentinel454

Copy link
Copy Markdown
Contributor Author

@buzz-security-review a948a43

@github-actions github-actions Bot added the codex-security-review-current The posted Codex security review matches its recorded range. label Nov 6, 2026

@wpfleger96 wpfleger96 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Automated review of a948a4337c (base cec918ae): no blocking findings.

The two new spellings, channel_id = $4 for a channel write and channel_id IS NULL for a channel-less one, select exactly the rows the old IS NOT DISTINCT FROM $4 did, for both the head read and the supersede. The coordinate lock, the newest-first/lowest-id ordering, the stale-write check, the roster guard and the rollback path are unchanged, including through LockedGroupStateSnapshot::replace. Each branch binds exactly the parameters its SQL uses. With all four equality columns in the scope, idx_events_addressable (community_id, kind, pubkey, channel_id, deleted_at) takes the channel as an index condition. The UPDATE events literals stay inside the &mut AdmittedTx function, so the guarded-write source scan still covers them.

Run against an isolated PostgreSQL 17 at this head: both new tests pass, along with all 18 replaceable-module tests, the 2 channel replacement-lock tests and the 222 default buzz-db tests and source-policy checks. With the new tests moved onto the base commit, the scope-behavior test passes and the plan test fails in all four cases (head read and supersede, channel and channel-less), with channel_id only in Filter. So the plan test catches the regression it is meant to catch.

Nonblocking:

  • The plan assertion accepts a channel_id index condition from any node in the text plan. events is partitioned, so a match on one node doesn't show that every populated partition scan seeks by channel. Checking the JSON plan and requiring the condition on the populated partition scans would make the test exact.
  • The plan test builds its own copy of the supersede string ("UPDATE events SET deleted_at = NOW() " + scope) rather than sharing production's, so the two prefixes could drift apart. That copy is what keeps the UPDATE events literal inside the admitted function for the source scan, so it's a fair trade.

The plan check matched an Index Cond on any node of the text plan. Read
the JSON plan instead and require each scan of a populated events
partition to seek by channel_id, and every populated partition to be
scanned.

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]>
@github-actions github-actions Bot removed the codex-security-review-current The posted Codex security review matches its recorded range. label Nov 6, 2026
@github-actions github-actions Bot added the codex-security-review-current The posted Codex security review matches its recorded range. label Nov 6, 2026

@wpfleger96 wpfleger96 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Automated review (Auto PR Review v1) of head 14576a0610: no blocking findings.

This head adds one commit on top of the previously reviewed a948a4337c, with the same base. That commit only changes the test replacement_channel_predicate_is_an_index_condition. It now reads the plan as EXPLAIN (FORMAT JSON), finds the partitions that actually hold the seeded community's rows, and requires every one of those partitions to be scanned exactly once, with a channel_id index condition either on the scan itself or on the bitmap index scan beneath it. This closes the earlier nonblocking note that the plan check passed when any single node matched. A sequential scan on a populated partition, a missing partition, or a duplicated partition now fails the test.

Checked:

  • The partition names come from pg_class.relname via tableoid, which matches the unqualified Relation Name in the JSON plan. Partitions with no seeded rows are ignored on purpose.
  • The bitmap path recurses through Plans, so a Bitmap Heap Scan passes only when an index child seeks by channel_id.
  • The supersede statement still comes from the shared superseded_with_mentions_sql! / replacement_scope! macros, so the test plans the same SQL the store runs.
  • CI's PostgreSQL Tests job at this exact head ran the test, and it passed (store::replaceable::postgres_tests::replacement_channel_predicate_is_an_index_condition).

@kazemek21 kazemek21 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kyle reviewed the PR and the Nexus review-tracker notes and approved with no comments.

Posted by Codex (gpt-6.1-sol) on behalf of kylez

@TheSentinel454
TheSentinel454 merged commit 193fda2 into main Nov 12, 2026
90 checks passed
@TheSentinel454
TheSentinel454 deleted the elrond/events-drop-deleted-index-exact-replace-head branch November 12, 2026 15:13
ravarora2 pushed a commit that referenced this pull request Nov 12, 2026
…small

* origin/main:
  fix(db): seek replaceable heads by channel (#9147)
  buzz-db: check events created_at floor in app writers (phase 1) (#8325)
  fix(mobile): keep a thread tail behind the keyboard unread (#9295)
  fix(mobile): restore header cues and per-channel identity check for #9369 (#9370)
  fix(relay): treat empty filter sets as match-nothing in SQL (#9342)
  buzz-cli: add --before and --before-id to messages search (#9382)
  feat(relay): agents cannot write in DMs except with their owner (#9371)
  fix(relay): bound each cross-node delivery attempt (#9305)
  feat(mobile): add iOS Home Screen shortcuts for New chat, New DM and the Bestie (#9360)
  perf(mobile): stop downloading whole videos for posterless stills (#9367)
  Enforce single-community relay mode (#9149)
  fix(relay): match #a tag filters in SQL before LIMIT (#9339)
  Add participant avatars to mobile conversation headers (#9369)
  fix(mobile): stop asking for an API token when the channel list is refused (#9327)
  chore(release): release Buzz Relay version 0.3.0 (#9362)
  Fix gallery video selection, thumbnails, and swipe dismissal (#9356)

Signed-off-by: dessert <7d616e7e143e5d408159614dccff1371350a1c1bd36c8de838a45ece5aca0aca@buzz.block.builderlab.xyz>

This branch was previously deployed

1 inactive deployment
codex-review — 14576a06 Deployed Nov 6, 2026 by TheSentinel454 via Run Codex Security Review #13449
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

codex-security-review-current The posted Codex security review matches its recorded range.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants