mirror of
https://github.com/HeyPuter/puter.git
synced 2026-10-13 23:29:50 +00:00
Measured first, against a real server, counting DB round trips: a 40-seat delete was 284 queries, 7.1 per seat, scaling linearly. Four of those seven answered for the whole page and had no reason to be asked per account. 120x SELECT * FROM `user` WHERE `id` = ? 41x INSERT INTO `audit_team_membership` 40x SELECT COALESCE(SUM(size), 0) FROM fsentries 40x UPDATE `user` SET `suspended` = ? 40x SELECT `uuid` FROM `sessions` WHERE `user_id` = ? The page now reads who is already suspended, what each holds, and the rows the notices need in one query each, and writes the audit as one statement. What is genuinely per account -- the suspension, its billing event, the mail -- stays per account under the same concurrency bound. Same 40-seat delete is now 170 queries, 4.3 per seat. Three new store methods rather than raw SQL at the call site: `getSuspendedByIds` reads the primary, deliberately not `getByIds`, which is cache-first. A stale `suspended` is exactly what re-charges a seat on a retry, which is the thing this path was just fixed to stop doing. `getHeldBytesForUsers` is `getHeldBytes` with a GROUP BY. `appendAuditMany` is `appendAudit` as one multi-row insert, still written before any suspension, so a failed append cannot leave an unlogged one. What is left is concentrated in `#suspend`: the UPDATE, the forced re-read the user store does afterwards to refresh its cache, and the session drop. Those are shared store paths and batching them means owning their cache invalidation, so they are not in scope here.