Skip to content

Boot seeds permission sets and positions one at a time: 4 sequential DB round trips each, 2 of them an unconditional UPDATE #10946

Description

@os-zhuang

Every permission set and every position an environment declares costs exactly 4 sequential database round trips on every kernel boot, and 2 of those 4 are an UPDATE that fires even when nothing changed.

Measured

Real per-environment kernel build (ArtifactKernelFactory.create(), hosted capability slate, real TursoDriver in remote transport with every @libsql/client call counted), measuring a REBUILD — tables present, rows already seeded, nothing to change:

permission sets round trips positions round trips
0 242 0 242
5 262 5 262
10 282 10 282
20 322 20 322
40 402 40 402

Least squares: slope exactly 4.0000, R² = 1.000000 on both axes.

Diffing the per-statement round-trip histogram between 0 and 40 of each names the four:

per permission set per position
+2 SELECT * FROM sys_permission_set …LIMIT +2 SELECT * FROM sys_position … LIMIT
+1 UPDATE sys_permission_set SET … +1 UPDATE sys_position SET …
+1 SELECT * FROM sys_permission_set … +1 SELECT * FROM sys_position …

For contrast, on the same rig objects, views and artifact seed records add 0.00 round trips each (0 → 400 objects all boot in 242): schema sync is already batched through TursoDriver.supports.batchSchemaSync. Identity content is the one content axis that is not.

Why it costs what it costs

bootstrapDeclaredPermissionSets (packages/plugins/plugin-security/src/bootstrap-declared-permissions.ts:166) awaits upsertPackagePermissionSet once per set inside a for loop; the position binder has the same shape. On a local file database the loop is invisible. On a remote libsql/Turso database — every hosted environment — each leg is its own HTTP request, and the legs are awaited one after another.

The same rig measures how much that matters: injecting a synthetic per-call latency grows the whole bootstrap step at 171.7 ms per ms of RTT (R² = 0.998), so ~172 of the 242 baseline round trips sit on the critical path. At that rate 40 permission sets plus 40 positions add 320 round trips — more than doubling a boot that a request will only wait 20 s for (objectstack-ai/cloud#1555).

Direction

Both loops are read-then-write per item over a set known in full before the loop starts:

  1. Replace the per-item existence SELECT with ONE find({ name: { $in: [...] } }) before the loop.
  2. Skip the UPDATE when the stored row already equals what would be written — an idempotent reconcile that writes unconditionally is paying two round trips per item forever to change nothing.

That takes the steady-state rebuild cost of both loops to O(1) round trips, leaving writes only where something genuinely drifted.

Reproducing

objectstack-ai/cloud:

node scripts/dev-local/bootstrap-curve.mjs --hosted --permission-sets 0,5,10,20,40
node scripts/dev-local/bootstrap-curve.mjs --hosted --positions 0,5,10,20,40
node scripts/dev-local/bootstrap-curve.mjs --hosted --permission-sets 40 --positions 40 --sql-histogram

Refs objectstack-ai/cloud#1555

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions