-- 任务推进的**跨进程租约** —— 两个后端连同一个库时,一张单只许一个进程在推。 -- -- 起因(2026-08-24,柴浩那一单熔断):本机与局域网另一台机器上跑着两个后端, -- 连的是同一个库。两边的推进器各自 2 秒扫一次「running 的单」,同时推同一张 —— -- 而互斥锁是**进程内**的(`task-lock.ts` 开头写着「MVP 单进程…多进程部署时再叠一层」)。 -- 于是两拍并发跑在同一份 `sys.loop_state` 上,**后写的整份覆盖先写的**: -- 「给 S2 发过卡」这个事实(worldTouched)被抹掉,S1 被收了两遍。 -- 随后 finish 那道门说「从没给它发过卡」,重发那道门说「这张卡人已答过」—— -- 两堵墙对着撞,连拒五拍熔断。 -- -- 租约怎么用:推之前 `update … where advancing_until is null or advancing_until < now()` -- 抢到才推(RETURNING 有行 = 我抢到了),推完清空。**带过期时间**是为了进程崩了不锁死: -- 租约一过,别的进程照样接得上。 alter table sys.task add column if not exists advancing_until timestamptz; -- 推进器每轮扫的就是「running 且没被别人占着」,给它一条能走的索引 create index if not exists task_advancing_idx on sys.task (runtime, advancing_until);