80 lines
2.9 KiB
PL/PgSQL
80 lines
2.9 KiB
PL/PgSQL
-- Claim 只需要查看每个用户当前未完成 head。把 head 持久化后,领取复杂度由
|
||
-- “扫描全部 outbox 积压并 DISTINCT ON”降为“扫描有积压的用户 lane”。
|
||
-- 迁移期间阻止并发写,保证 backfill 与随后安装的触发器之间没有缺口。
|
||
LOCK TABLE dispatch_outbox IN SHARE ROW EXCLUSIVE MODE;
|
||
|
||
CREATE TABLE dispatch_outbox_user_heads (
|
||
target_user_id bigint PRIMARY KEY,
|
||
head_id bigint NOT NULL,
|
||
head_pts integer NOT NULL CHECK (head_pts >= 0),
|
||
logical_shard smallint GENERATED ALWAYS AS (
|
||
mod(target_user_id, 256::bigint)::smallint
|
||
) STORED,
|
||
CHECK (logical_shard >= 0 AND logical_shard < 256)
|
||
);
|
||
|
||
CREATE INDEX dispatch_outbox_user_heads_shard_idx
|
||
ON dispatch_outbox_user_heads (logical_shard, target_user_id);
|
||
|
||
INSERT INTO dispatch_outbox_user_heads (target_user_id, head_id, head_pts)
|
||
SELECT DISTINCT ON (target_user_id)
|
||
target_user_id,
|
||
id,
|
||
pts
|
||
FROM dispatch_outbox
|
||
ORDER BY target_user_id ASC, pts ASC, id ASC;
|
||
|
||
CREATE FUNCTION dispatch_outbox_maintain_user_head()
|
||
RETURNS trigger
|
||
LANGUAGE plpgsql
|
||
AS $$
|
||
DECLARE
|
||
removed_head bigint;
|
||
BEGIN
|
||
IF TG_OP = 'INSERT' THEN
|
||
INSERT INTO dispatch_outbox_user_heads (target_user_id, head_id, head_pts)
|
||
VALUES (NEW.target_user_id, NEW.id, NEW.pts)
|
||
ON CONFLICT (target_user_id) DO UPDATE
|
||
SET head_id = EXCLUDED.head_id,
|
||
head_pts = EXCLUDED.head_pts
|
||
WHERE (EXCLUDED.head_pts, EXCLUDED.head_id) <
|
||
(dispatch_outbox_user_heads.head_pts, dispatch_outbox_user_heads.head_id);
|
||
RETURN NULL;
|
||
END IF;
|
||
|
||
-- 删除非 head 不需要重算。删除 head 时用 (target_user_id, pts, id)
|
||
-- 索引找下一条;failed head 同样会一直阻塞,直到被显式删除。
|
||
DELETE FROM dispatch_outbox_user_heads
|
||
WHERE target_user_id = OLD.target_user_id
|
||
AND head_id = OLD.id
|
||
RETURNING head_id INTO removed_head;
|
||
|
||
IF removed_head IS NOT NULL THEN
|
||
INSERT INTO dispatch_outbox_user_heads (target_user_id, head_id, head_pts)
|
||
SELECT target_user_id, id, pts
|
||
FROM dispatch_outbox
|
||
WHERE target_user_id = OLD.target_user_id
|
||
ORDER BY pts ASC, id ASC
|
||
LIMIT 1
|
||
ON CONFLICT (target_user_id) DO UPDATE
|
||
SET head_id = EXCLUDED.head_id,
|
||
head_pts = EXCLUDED.head_pts
|
||
WHERE (EXCLUDED.head_pts, EXCLUDED.head_id) <
|
||
(dispatch_outbox_user_heads.head_pts, dispatch_outbox_user_heads.head_id);
|
||
END IF;
|
||
RETURN NULL;
|
||
END;
|
||
$$;
|
||
|
||
CREATE TRIGGER dispatch_outbox_insert_user_head
|
||
AFTER INSERT ON dispatch_outbox
|
||
FOR EACH ROW
|
||
EXECUTE FUNCTION dispatch_outbox_maintain_user_head();
|
||
|
||
CREATE TRIGGER dispatch_outbox_delete_user_head
|
||
AFTER DELETE ON dispatch_outbox
|
||
FOR EACH ROW
|
||
EXECUTE FUNCTION dispatch_outbox_maintain_user_head();
|
||
|
||
-- Claim 已不再读取表达式 shard 索引;移除它避免每次 enqueue 的重复写放大。
|
||
DROP INDEX IF EXISTS dispatch_outbox_logical_shard_head_idx;
|