mirror of
https://github.com/profullstack/logicsrc.git
synced 2026-08-14 06:47:28 +00:00
perf(credshare): list a team's vaults in one query instead of two per vault (#128)
GET /api/credshare/teams/:slug/vaults built its response in a loop, asking the database for a grant row and a secret count once per vault. libSQL is remote, so each of those is a network round trip, and the endpoint cost 2N+1 of them. On a team with 176 vaults that is 353 round trips and ~10.6s of server time. `logicsrc teams pull` resolves the vault id twice -- once planning the sync, once reading values -- so a pull of a single ten-key vault took ~24s, nearly all of it spent listing vaults the command does not want. Replaced with one SELECT carrying two correlated subqueries. Both are covered by existing primary keys (credshare_secrets is keyed (vault_id, name), credshare_vault_grants (vault_id, user_id)), so the per-vault work becomes an index probe inside the database instead of a round trip across the network. No schema or index change. Measured on a local libSQL seeded to match that team -- 176 vaults, 17 secrets each -- the endpoint goes from 355 round trips to 3, and returns identical rows. The response shape is unchanged: hasAccess is still a real boolean rather than the 0/1 SQLite hands back, and secretCount is still a number. Tests pin behaviour and cost separately. The behavioural cases pass against both the old loop and the new query, which is the point -- only the round-trip count changed. The regression guard asserts the query count for 3 vaults EQUALS the count for 30 rather than matching a magic number, so any future rewrite that reintroduces per-vault I/O fails no matter what the constant part costs. Against the old loop it reports 9 vs 63. Two sibling endpoints have the same shape -- /teams/:slug/members and /vaults/:id/grants both call publicKeyFor() per member. Neither is on the pull path and both scale with member count rather than vault count, so they are left alone here. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
4c88155f08
commit
fd253b0485
2 changed files with 196 additions and 8 deletions
|
|
@ -157,14 +157,33 @@ credshareRouter.post("/api/credshare/invites/accept", api(async (req, res, user)
|
|||
// ---- vaults ----
|
||||
credshareRouter.get("/api/credshare/teams/:slug/vaults", api(async (req, res, user) => {
|
||||
const ctx = await requireMember(res, req.params.slug, user.id); if (!ctx) return;
|
||||
const vaults = await all(`SELECT * FROM credshare_vaults WHERE team_id = ? ORDER BY name`, [ctx.team.id]);
|
||||
const out = [];
|
||||
for (const v of vaults) {
|
||||
const grant = await get(`SELECT 1 FROM credshare_vault_grants WHERE vault_id = ? AND user_id = ?`, [v.id, user.id]);
|
||||
const count = await get(`SELECT COUNT(*) AS n FROM credshare_secrets WHERE vault_id = ?`, [v.id]);
|
||||
out.push({ id: v.id, name: v.name, hasAccess: Boolean(grant), secretCount: Number(count?.n || 0) });
|
||||
}
|
||||
res.json({ vaults: out });
|
||||
// One statement, not one per vault. libSQL is remote, so every execute() is a
|
||||
// network round trip: the previous loop cost 2N+1 of them, and a team with 176
|
||||
// vaults spent ~10s here -- doubled by `teams pull`, which resolves the vault
|
||||
// id twice. Both correlated subqueries are covered by existing primary keys
|
||||
// (credshare_secrets is keyed (vault_id, name), grants (vault_id, user_id)),
|
||||
// so this is an index scan per vault inside the database rather than a
|
||||
// round trip per vault across the network.
|
||||
const vaults = await all(
|
||||
`SELECT v.id,
|
||||
v.name,
|
||||
(SELECT COUNT(*) FROM credshare_secrets s
|
||||
WHERE s.vault_id = v.id) AS secret_count,
|
||||
EXISTS(SELECT 1 FROM credshare_vault_grants g
|
||||
WHERE g.vault_id = v.id AND g.user_id = ?) AS has_access
|
||||
FROM credshare_vaults v
|
||||
WHERE v.team_id = ?
|
||||
ORDER BY v.name`,
|
||||
[user.id, ctx.team.id]
|
||||
);
|
||||
res.json({
|
||||
vaults: vaults.map((v) => ({
|
||||
id: v.id,
|
||||
name: v.name,
|
||||
hasAccess: Boolean(v.has_access),
|
||||
secretCount: Number(v.secret_count || 0)
|
||||
}))
|
||||
});
|
||||
}));
|
||||
|
||||
credshareRouter.post("/api/credshare/teams/:slug/vaults", api(async (req, res, user) => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue