Skip to content

perf(onboarding): replace sequential update loops with bulk SQL#2647

Merged
tofikwest merged 1 commit intomainfrom
fix/onboarding-transaction-perf
Apr 23, 2026
Merged

perf(onboarding): replace sequential update loops with bulk SQL#2647
tofikwest merged 1 commit intomainfrom
fix/onboarding-transaction-perf

Conversation

@tofikwest
Copy link
Copy Markdown
Contributor

@tofikwest tofikwest commented Apr 23, 2026

Summary

  • Org init transaction was firing ~100 sequential queries for a typical SOC 2 onboarding (two update loops inside _upsertOrgFrameworkStructureCore).
  • On Prisma v6's native Rust engine that was ~5-10s. The v6→v7 migration (native engine → @prisma/adapter-pg) roughly tripled per-query overhead, pushing the same workload to 30+s and tripping the transaction timeout (8d1764a67 bumped it 30s→60s as a stopgap).
  • This PR removes the loops. Query count drops to ~20, flat regardless of framework size. Expected wall-clock 2-4s.

What changed

Single file: apps/app/src/actions/organization/lib/initialize-organization.ts.

Policy block — eliminated N sequential policy.update calls:

  • Pre-generate both Policy and PolicyVersion CUIDs in one $queryRaw via generate_prefixed_cuid().
  • createMany Policy (IDs pre-set, currentVersionId still null).
  • createMany PolicyVersion (IDs pre-set, policyId pre-known).
  • One bulk UPDATE "Policy" ... FROM (VALUES ...) to set currentVersionId for all policies at once.
  • FK cycle (Policy.currentVersionIdPolicyVersion.policyId) handled by insert ordering — no schema change.

Control block — eliminated M sequential control.update({ connect }) calls:

  • Collect (controlId, policyId) and (controlId, taskId) pairs in memory during the existing requirement-map pass.
  • Two bulk INSERT INTO "_ControlToPolicy" ... ON CONFLICT DO NOTHING / "_ControlToTask" calls.
  • ON CONFLICT DO NOTHING preserves the idempotency the old connect loop provided for re-runs (e.g. adding a framework to an existing org).

What did NOT change

  • Public API (initializeOrganization) and return shape.
  • _upsertOrgFrameworkStructureCore has no external callers (grep-verified).
  • All console.warn diagnostics preserved verbatim.
  • Org-scoping on every read/write.
  • Empty-input guards (if (policyTemplatesForCreation.length > 0) etc.).
  • Prisma version stays 7.6.0. No package or client config changes.
  • Connection pool usage: same (one connection per tx, just held for less time — net win under load).
  • 60s tx timeout kept as a safety margin. Can revert to 30s in a follow-up once this is validated in prod.

Test plan

  • Run a fresh SOC 2 onboarding in dev; confirm all expected policies exist with currentVersionId set, matching PolicyVersion rows, Control.policies + Control.tasks populated, and RequirementMap entries present
  • Run initializeOrganization twice against the same org (adding a framework) — confirm no unique-constraint errors and no duplicate M2M rows (idempotency)
  • Time the transaction end-to-end; expect sub-10s (likely 2-4s)
  • After validation, open follow-up PR to revert tx timeout 60s → 30s

🤖 Generated with Claude Code


Summary by cubic

Speeds up org onboarding by replacing sequential update loops with bulk SQL. Query count drops from ~100 to ~20 and typical runtime falls to ~2–4s, with no API changes.

  • Refactors
    • Policies: pre-generate IDs in one tx.$queryRaw using generate_prefixed_cuid(), bulk createMany for Policy and PolicyVersion, then one bulk UPDATE ... FROM (VALUES ...) to set Policy.currentVersionId (FK cycle handled by insert order).
    • Controls: collect control↔policy and control↔task pairs and bulk INSERT into "_ControlToPolicy" and "_ControlToTask" with ON CONFLICT DO NOTHING, replacing per-control update({ connect }) loops.
    • No API or schema changes. Org scoping and logs unchanged. Prisma stays 7.6.0. 60s tx timeout kept for now.

Written for commit 0c5d332. Summary will update on new commits.

…g init

The org initialization transaction was firing ~100 sequential queries for a
typical SOC 2 onboarding — two loops inside the transaction were the main
contributors:

1. Per-policy `policy.update` to set `currentVersionId` after creating
   PolicyVersion rows (one UPDATE per new policy).
2. Per-control `control.update({ policies: { connect }, tasks: { connect } })`
   to link policies/tasks to controls (one UPDATE per control).

On Prisma v6 with the native Rust engine this ran in ~5-10s. The v6→v7
migration swapped the Rust engine for `@prisma/adapter-pg` (node-postgres),
which roughly tripled per-query overhead, pushing the same workload to 30+s
and causing onboarding to trip the 30s transaction timeout (which we bumped
to 60s as a stopgap in 8d1764a).

Fix:

- Policy block: pre-generate Policy and PolicyVersion CUIDs in a single
  `$queryRaw` call using `generate_prefixed_cuid()`. Create policies, then
  versions, then set `currentVersionId` for all rows in a single bulk
  `UPDATE ... FROM (VALUES ...)` statement. Handles the FK cycle
  (Policy.currentVersionId ↔ PolicyVersion.policyId) by insert ordering
  rather than schema changes. Kills N sequential updates.

- Control block: collect control↔policy and control↔task pairs in memory
  during the existing requirement-map pass, then bulk-insert into the
  implicit M2M join tables `_ControlToPolicy` and `_ControlToTask` with
  `ON CONFLICT ("A","B") DO NOTHING`. Preserves idempotency for re-runs
  (e.g. adding a framework to an existing org). Kills M sequential updates.

Result: ~100 queries → ~20, flat regardless of framework size. Expected
wall-clock 2-4s. Public API, return shape, console.warn diagnostics, and
org-scoping are all preserved.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 23, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
app Ready Ready Preview, Comment Apr 23, 2026 3:36pm
comp-framework-editor Ready Ready Preview, Comment Apr 23, 2026 3:36pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
portal Skipped Skipped Apr 23, 2026 3:36pm

Request Review

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 1 file

Requires human review: This PR is a significant performance refactor of the core onboarding logic. It introduces raw SQL for bulk updates and directly manipulates Prisma join tables, requiring human review.

@tofikwest tofikwest merged commit 10b51fe into main Apr 23, 2026
11 checks passed
@tofikwest tofikwest deleted the fix/onboarding-transaction-perf branch April 23, 2026 15:41
@claudfuen
Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 3.30.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants