Files
multica/server/cmd
Eveandmultica-agent faf03e8364 fix(workspace): keyset-page the teardown sweep and fail closed on leaks
Addresses the second review round on MUL-5999.

1. Bounded scan, not just bounded result. The page query was
   `owner = $1 ORDER BY id LIMIT n` against indexes that cannot produce id
   order, so the executor had to read the owner's whole task set before
   applying LIMIT — quadratic in pages for a busy owner, which is the case
   this issue is about. Measured on a 550k-row probe: the old shape spent
   11,630 buffers per page (planner chose a full pkey index scan; on other
   statistics it chose an explicit Sort), the keyset shape spends ~2,000
   and a later page costs the same as the first.

   Pages are now `owner = $1 AND id > $cursor ORDER BY id LIMIT n` against
   composite indexes: 273 becomes (runtime_id, id), 278 adds (agent_id, id),
   279 adds (issue_id, id) and 280 drops the single-column issue_id index it
   supersedes so the hot table does not carry both.

2. Owner enumeration is bounded too. The three LockWorkspaceTaskOwner*
   statements no longer return ids at all — they are a set-based write fence
   and now cost the API process nothing. Owners are walked separately in
   keyset pages of 500 (281-283 add the (workspace_id, id) indexes those
   pages need), so a workspace with a huge agent or issue set never has its
   whole owner list held in memory.

   The keyset column is id rather than a natural key on purpose: a cursor
   column must be immutable, and renaming an owner mid-teardown would
   otherwise let it sort behind the cursor and escape the sweep.

3. The fence is no longer the only guarantee. Blocking a concurrent enqueue
   still relies on an INSERT taking FOR KEY SHARE on the owner rows it
   references, which is a property of the legacy FKs rather than a protocol
   every enqueue path opts into. Two things now backstop it:

   - every owner is re-swept until a pass finds nothing, and teardown fails
     closed (rolling the whole transaction back) if tasks keep arriving,
     instead of committing a partially cleaned tenant;
   - a schema-contract test asserts the three owner FKs still exist, so a
     migration that removes them turns the missing fence into a red build
     rather than a silent hole.

   An explicit deleting-state protocol shared with all seven enqueue paths
   remains the right long-term fix and is out of scope here.

End-to-end teardown, real handler, identical synthetic data, main vs this
branch (1.2M-row agent_task_queue, 200k task_token, 300k task_usage):

  small workspace, 5k tasks:   29,247 ms -> 1,013 ms   (4.38M -> 0.27M blocks)
  busy workspace, 60k tasks:  243,863 ms -> 1,724 ms   (5.76M -> 2.23M blocks)

Refs MUL-5999

Co-authored-by: multica-agent <[email protected]>
2026-08-11 17:33:52 +08:00
..