mirror of
https://github.com/block/buzz.git
synced 2026-10-27 20:41:57 +00:00
Under 0056 only a read intent creates personal_read_accounts rows, so every existing account has started. Leaving started_at NULL would hide everything that arrives between deploy and the account's next intent. 0057 now sets started_at to the migration's time for accounts in active communities; the write fence would reject fenced or deleting ones. A migration test covers an existing account (unread mention after upgrade, no new intent) next to a fenced community's account. Docs: bounded locking still lets ingestion queue within the budget, and a non-prebuilt index on a large event_mentions times out and rolls back. Signed-off-by: hMB2 <0400eb0da32afbbf16cdd1dee6ffd243587e8f3d6a2823fe7dfa95d16b002b7d@buzz.block.builderlab.xyz>
98 lines
5.1 KiB
SQL
98 lines
5.1 KiB
SQL
-- Channel and thread read scopes on top of 0056 (checksum-frozen).
|
|
-- Startup is bounded like 0049: this migration takes exclusive locks on
|
|
-- thread_metadata, channels and event_mentions, and must fail rather than
|
|
-- queue ingestion behind a long reader or an index build on a populated
|
|
-- table. Brownfield operators prebuild idx_event_mentions_scope_received and
|
|
-- pre-drop idx_thread_metadata_root as documented in
|
|
-- docs/buzz-v1-read-state.md.
|
|
SET LOCAL lock_timeout = '1s';
|
|
SET LOCAL statement_timeout = '5s';
|
|
|
|
-- started_at is the actor's first read intent and the floor of every
|
|
-- position: until then nothing counts, and from then the actor starts caught
|
|
-- up. Ingest creates accounts for thread membership without starting them.
|
|
ALTER TABLE personal_read_accounts ADD COLUMN started_at TIMESTAMPTZ;
|
|
|
|
-- Under 0056 only a read intent creates an account, so every existing
|
|
-- account has already started. Start it now (this transaction's time) so
|
|
-- arrivals from deploy onward count without waiting for its next intent. The
|
|
-- arrival columns below fill in from the same point, so every position is
|
|
-- still floored after them. Communities being deleted are skipped: their
|
|
-- write fence would reject the update, and their accounts are going away.
|
|
UPDATE personal_read_accounts a
|
|
SET started_at = now()
|
|
FROM communities c
|
|
WHERE c.id = a.community_id
|
|
AND c.deletion_state = 'active'
|
|
AND a.started_at IS NULL;
|
|
|
|
-- A thread row exists for each thread the actor follows or has unfollowed.
|
|
-- Ingest moves the author's own frontiers through each message they post.
|
|
-- following: thread rows only, false after unfollow, kept so later replies
|
|
-- don't re-follow. Replying or a follow intent sets it again.
|
|
ALTER TABLE personal_read_frontiers
|
|
ADD COLUMN following BOOLEAN NOT NULL DEFAULT true
|
|
CHECK (following OR root_id <> ''::bytea);
|
|
|
|
-- Arrival time of a thread root's latest reply: the sidebar skips threads
|
|
-- with nothing new since the reader's position without reading replies.
|
|
ALTER TABLE thread_metadata ADD COLUMN last_reply_received_at TIMESTAMPTZ;
|
|
|
|
-- Arrival time of a channel's latest eligible timeline message (top-level or
|
|
-- broadcast reply): the sidebar answers "anything new?" for a caught-up
|
|
-- reader with one comparison instead of walking past replies. Unindexed so
|
|
-- updates stay HOT. NULL for channels with no timeline message since this
|
|
-- migration, which no position can be behind: every position is floored at
|
|
-- a started_at set by this migration or later.
|
|
ALTER TABLE channels ADD COLUMN last_timeline_received_at TIMESTAMPTZ;
|
|
|
|
-- Arrival time and read scope of each mention, so the sidebar counts a
|
|
-- reader's unread mentions in one channel timeline or thread from its
|
|
-- position. root_id is the thread a reply belongs to, NULL when the mention
|
|
-- shows on the channel timeline (a top-level message or broadcast reply).
|
|
-- Existing rows stay NULL: every position is floored at its account's
|
|
-- started_at, set by this migration or a later read intent, so no earlier
|
|
-- mention can be unread.
|
|
-- IF NOT EXISTS admits the brownfield prebuild, which adds these columns
|
|
-- before building the index concurrently. As in 0049, a prebuilt index skips
|
|
-- CREATE INDEX and must pass the catalog check below.
|
|
ALTER TABLE event_mentions
|
|
ADD COLUMN IF NOT EXISTS received_at TIMESTAMPTZ,
|
|
ADD COLUMN IF NOT EXISTS root_id BYTEA;
|
|
DO $$
|
|
BEGIN
|
|
IF NOT EXISTS (
|
|
SELECT 1 FROM information_schema.columns
|
|
WHERE table_schema = 'public' AND table_name = 'event_mentions'
|
|
AND ((column_name = 'received_at' AND data_type = 'timestamp with time zone')
|
|
OR (column_name = 'root_id' AND data_type = 'bytea'))
|
|
HAVING count(*) = 2
|
|
) THEN
|
|
RAISE EXCEPTION 'event_mentions.received_at/root_id have the wrong type; see docs/buzz-v1-read-state.md';
|
|
END IF;
|
|
|
|
IF to_regclass('public.idx_event_mentions_scope_received') IS NULL THEN
|
|
CREATE INDEX idx_event_mentions_scope_received
|
|
ON public.event_mentions (community_id, pubkey_hex, channel_id, root_id, received_at);
|
|
END IF;
|
|
|
|
IF NOT EXISTS (
|
|
SELECT 1 FROM pg_index i
|
|
JOIN pg_class c ON c.oid = i.indexrelid
|
|
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
WHERE n.nspname = 'public' AND c.relname = 'idx_event_mentions_scope_received'
|
|
AND i.indisvalid AND i.indisready AND i.indislive
|
|
AND pg_get_indexdef(i.indexrelid) =
|
|
'CREATE INDEX idx_event_mentions_scope_received ON public.event_mentions USING btree (community_id, pubkey_hex, channel_id, root_id, received_at)'
|
|
) THEN
|
|
RAISE EXCEPTION 'idx_event_mentions_scope_received invalid or wrong definition; see docs/buzz-v1-read-state.md';
|
|
END IF;
|
|
END $$;
|
|
|
|
-- idx_thread_metadata_window (0049) leads with the same columns. With both,
|
|
-- the planner can take this narrower index for a thread's latest reply and
|
|
-- then sort every reply in the thread instead of reading the first row.
|
|
-- This supersedes 0049's "legacy root/parent indexes remain" for the root
|
|
-- index (0049 is checksum-frozen); the parent index remains.
|
|
DROP INDEX IF EXISTS idx_thread_metadata_root;
|