Skip to content

fix(db-part-4): enforce consistent cross-resource lock ordering#5027

Merged
icecrasher321 merged 1 commit into
stagingfrom
improvement/db-patterns-4
Jun 13, 2026
Merged

fix(db-part-4): enforce consistent cross-resource lock ordering#5027
icecrasher321 merged 1 commit into
stagingfrom
improvement/db-patterns-4

Conversation

@icecrasher321

Copy link
Copy Markdown
Collaborator

Summary

  • knowledge: updateDocument locks embeddings before the document row, matching
    the chunk-mutation paths (embedding → document)

  • tables: column-creating CSV imports take the rows_pos advisory before writing
    user_table_definitions, matching the order plain inserts use

  • billing: lock userStats before organization across all paths (flip
    captureDepartedUsage + resetUsageForSubscription to match the join, threshold,
    and storage-transfer paths), and subscription before userStats on paid-org
    join (matching restoreUserProSubscription)

  • harden the workspace env/byok/file-folder advisory locks with a 5s
    lock_timeout, and switch hashtext → hashtextextended (64-bit) at the remaining
    32-bit advisory sites to cut spurious collisions

Type of Change

  • Bug fix

Testing

Tested manually

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

@vercel

vercel Bot commented Jun 13, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Jun 13, 2026 5:53pm

Request Review

@cursor

cursor Bot commented Jun 13, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Touches concurrent billing, schedule, and workspace secret paths where wrong lock order causes production deadlocks; changes are ordering/timeouts rather than business rules, with new regression tests.

Overview
Aligns Postgres lock acquisition order across billing, knowledge, tables, and workspace APIs to avoid AB-BA deadlocks, and tightens advisory-lock behavior.

Billing: Paid-org join now FOR UPDATE locks the personal Pro subscription before snapshotting/updating userStats, matching restoreUserProSubscription. Org usage reset (resetUsageForSubscription) locks all members’ userStats before the organization row; member removal drops the org pre-lock in departed-usage capture so userStats is locked before org updates. Regression tests guard subscription→userStats and userStats→organization ordering.

Knowledge / tables: Document tag updates write embedding rows before the document row. CSV imports that add columns acquireRowOrderLock before user_table_definitions, matching plain insert order.

Advisory locks: Remaining 32-bit sites use hashtextextended(key, 0) (e.g. schedule execution queue). Workspace env, BYOK, and file-folder mutations set a transaction-scoped 5s lock_timeout before pg_advisory_xact_lock.

Reviewed by Cursor Bugbot for commit b160c2d. Configure here.

@greptile-apps

greptile-apps Bot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR enforces consistent cross-resource lock ordering across several code paths to eliminate AB-BA deadlock potential, adds lock_timeout guards on workspace advisory locks, and upgrades remaining 32-bit hashtext advisory keys to 64-bit hashtextextended.

  • Knowledge service: embedding rows are now updated before the document row in updateDocument, matching the chunk-mutation path order (embedding → document) so document tag edits and concurrent chunk edits on the same document can no longer deadlock.
  • Table import: acquireRowOrderLock is now called before addTableColumnsWithTx in both importAppendRows and importReplaceRows, establishing the same rows_pos → user_table_definitions order as plain inserts and preventing deadlocks against concurrent insertions.
  • Billing: resetUsageForSubscription and captureDepartedUsage are reordered to lock userStats before organization (removing the early org SELECT FOR UPDATE), and applyPaidOrgJoinBillingTx adds a subscription lock before the userStats snapshot to match restoreUserProSubscription's ordering.

Confidence Score: 4/5

The production lock-ordering changes are logically sound and consistent across all affected paths; the main concern is in the updated test for invoices.ts whose assertions may not actually verify the ordering they claim to guard.

The deadlock-prevention logic in every production file looks correct — the canonical userStats → organization, subscription → userStats, embedding → document, and rows_pos → definitions orderings are consistently established. The test regression guards for knowledge, table, and billing/organizations use reliable invocationCallOrder patterns. The invoices.test.ts guard, however, relies on strict equality between Drizzle column objects and plain strings, which is unlikely to hold; if those assertions silently mismatch, the PR's own regression net for the invoices path has a gap. The hashtext → hashtextextended key migration is also worth a note for deployments that overlap old and new instances.

apps/sim/lib/billing/webhooks/invoices.test.ts — the new lock-order assertion logic should be verified; apps/sim/app/api/schedules/execute/route.ts — advisory lock key migration needs care in rolling-deploy scenarios.

Important Files Changed

Filename Overview
apps/sim/lib/billing/webhooks/invoices.ts Reorders org-reset to lock all member userStats before the organization row; the fix is correct but the missing blank line before the follow-up if block is a minor readability gap.
apps/sim/lib/billing/webhooks/invoices.test.ts Updated lock-order assertions use arg?.column === 'userId' and arg?.left === 'id' to identify WHERE-clause nodes, but the mocked eq/inArray operators store raw Drizzle column objects (not strings) in those fields, making both findIndex calls likely return -1 and the regression guard non-functional.
apps/sim/lib/billing/organizations/membership.ts Adds subscription FOR UPDATE lock before userStats snapshot (canonical subscription → userStats order) and removes the premature org lock from captureDepartedUsage; both changes are correct.
apps/sim/lib/knowledge/documents/service.ts Swaps embedding update before document update inside the transaction; straightforward reordering that matches the chunk-mutation canonical order.
apps/sim/lib/table/service.ts Adds acquireRowOrderLock before addTableColumnsWithTx in both importAppendRows and importReplaceRows; correctly establishes rows_pos → definitions ordering.
apps/sim/app/api/schedules/execute/route.ts Upgrades both advisory lock sites from hashtext (32-bit) to hashtextextended (64-bit); produces a different lock key ID which could cause a brief serialization gap during rolling deploys.

Sequence Diagram

sequenceDiagram
    participant A as applyPaidOrgJoinBillingTx
    participant B as restoreUserProSubscription
    participant C as resetUsageForSubscription
    participant D as captureDepartedUsage

    Note over A,D: Canonical lock order (fixed in this PR)

    A->>A: SELECT subscription FOR UPDATE
    A->>A: SELECT userStats FOR UPDATE
    A->>A: UPDATE userStats (snapshot)
    A->>A: UPDATE subscription (cancel)

    B->>B: SELECT subscription FOR UPDATE
    B->>B: SELECT userStats FOR UPDATE
    B->>B: UPDATE userStats (restore)

    C->>C: SELECT member (owner) FOR UPDATE
    C->>C: SELECT userStats (owner) FOR UPDATE
    C->>C: SELECT userStats (all members) FOR UPDATE
    C->>C: SELECT organization FOR UPDATE
    C->>C: UPDATE userStats (reset)
    C->>C: UPDATE organization

    D->>D: SELECT userStats FOR UPDATE
    D->>D: UPDATE organization (implicit lock)
    D->>D: UPDATE userStats
Loading

Reviews (1): Last reviewed commit: "fix(db-part-4): enforce consistent cross..." | Re-trigger Greptile

Comment thread apps/sim/lib/billing/webhooks/invoices.test.ts
Comment thread apps/sim/app/api/schedules/execute/route.ts
@icecrasher321 icecrasher321 merged commit b74a56d into staging Jun 13, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant