Skip to content

Route read-only raw Prisma queries to read replica#1467

Open
N2D4 wants to merge 5 commits into
devfrom
devin/1779398666-raw-queries-read-replica
Open

Route read-only raw Prisma queries to read replica#1467
N2D4 wants to merge 5 commits into
devfrom
devin/1779398666-raw-queries-read-replica

Conversation

@N2D4
Copy link
Copy Markdown
Contributor

@N2D4 N2D4 commented May 21, 2026

Routes all read-only raw Prisma queries ($queryRaw/$queryRawUnsafe) to the database read replica via $replica(). Adds an AGENTS.md note documenting when to use $replica() for raw queries.

Queries intentionally left on primary: queries inside transactions and write queries (INSERT/UPDATE/DELETE, including CTEs).

Link to Devin session: https://app.devin.ai/sessions/58c157f791f0424498bab3a92bc50f3e
Requested by: @N2D4

Summary by CodeRabbit

  • Chores
    • Optimized database read operations across backend services, including authentication routes, payment processing, session management, and internal APIs, to enhance overall system performance.

Review Change Stack

- Update all pure-read $queryRaw/$queryRawUnsafe calls to use $replica()
- Add AGENTS.md note about when to use read replicas for raw queries
- Skip queries inside transactions, queries with writes in CTEs,
  and lookups that feed directly into create/upsert logic

Co-Authored-By: Konstantin Wohlwend <n2d4xc@gmail.com>
@devin-ai-integration
Copy link
Copy Markdown
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI' or '@devin'.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@vercel
Copy link
Copy Markdown

vercel Bot commented May 21, 2026

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

Project Deployment Actions Updated (UTC)
stack-auth-hosted-components Ready Ready Preview, Comment May 22, 2026 12:53am
stack-auth-mcp Ready Ready Preview, Comment May 22, 2026 12:53am
stack-auth-skills Ready Ready Preview, Comment May 22, 2026 12:53am
stack-backend Ready Ready Preview, Comment May 22, 2026 12:53am
stack-dashboard Ready Ready Preview, Comment May 22, 2026 12:53am
stack-demo Ready Ready Preview, Comment May 22, 2026 12:53am
stack-docs Ready Ready Preview, Comment May 22, 2026 12:53am
stack-preview-backend Ready Ready Preview, Comment May 22, 2026 12:53am
stack-preview-dashboard Ready Ready Preview, Comment May 22, 2026 12:53am

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 21, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a343ae7c-7b05-4498-98ab-7e5606c3a849

📥 Commits

Reviewing files that changed from the base of the PR and between 600a0d6 and dc81fb2.

📒 Files selected for processing (11)
  • AGENTS.md
  • apps/backend/src/app/api/latest/auth/cli/complete/route.tsx
  • apps/backend/src/app/api/latest/auth/cli/poll/route.tsx
  • apps/backend/src/app/api/latest/auth/cli/route.tsx
  • apps/backend/src/app/api/latest/internal/external-db-sync/status/route.ts
  • apps/backend/src/app/api/latest/internal/local-emulator/project/route.tsx
  • apps/backend/src/app/api/latest/internal/payments/transactions/route.tsx
  • apps/backend/src/app/api/latest/internal/session-replays/session-replay-admin-rows.ts
  • apps/backend/src/lib/conversations.tsx
  • apps/backend/src/lib/development-environment.ts
  • apps/backend/src/lib/managed-email-domains.tsx

📝 Walkthrough

Walkthrough

This PR routes all read-only Prisma database queries through $replica() to leverage database read replicas, reducing load on the primary connection. The changes span authentication endpoints, status diagnostics, project management, and core business services, with a new coding guideline in AGENTS.md documenting the pattern.

Changes

Replica Read Routing Implementation

Layer / File(s) Summary
Coding guideline and pattern documentation
AGENTS.md
New guideline instructs agents to route raw Prisma read-only queries ($queryRaw / $queryRawUnsafe) through $replica().$queryRaw, excluding transactions and write operations.
CLI authentication endpoints
apps/backend/src/app/api/latest/auth/cli/complete/route.tsx, apps/backend/src/app/api/latest/auth/cli/poll/route.tsx, apps/backend/src/app/api/latest/auth/cli/route.tsx
CLI auth routes route identity lookups through replica: getPendingCliAuthAttempt, getRefreshTokenSession in complete flow; polling endpoint's attempt lookup; anon refresh token lookup.
Status and diagnostics endpoints
apps/backend/src/app/api/latest/internal/external-db-sync/status/route.ts, apps/backend/src/app/api/latest/internal/session-replays/session-replay-admin-rows.ts
External DB sync status routes all stats queries (fetchInternalStats and global scope counts) through replica; session replay admin rows routes admin query through replica.
Local emulator project management
apps/backend/src/app/api/latest/internal/local-emulator/project/route.tsx
Project lookup by filepath, column existence check, onboarding status fetch, and project listing all switch to read replica.
Business logic and utility services
apps/backend/src/app/api/latest/internal/payments/transactions/route.tsx, apps/backend/src/lib/conversations.tsx, apps/backend/src/lib/development-environment.ts, apps/backend/src/lib/managed-email-domains.tsx
Payments ledger and refund queries; conversation row, state, summary, detail, and message/entry-point queries; development environment project check; managed email domain lookups all route through read replica.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

  • hexclave/stack-auth#1419: Modifies apps/backend/src/app/api/latest/auth/cli/poll/route.tsx polling handler database access patterns.
  • hexclave/stack-auth#1463: Similarly routes Prisma reads through replica infrastructure (prisma.$replica()) for scalability.

Suggested reviewers

  • BilalG1
  • nams1570

Poem

🐰 Hopping through queries with replica in sight,
Read-only scrolls now routed just right,
No primary strain from the data we seek,
Just replica lanes, load-balanced and sleek! 🌙

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.56% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely summarizes the main change: routing read-only raw Prisma queries to the read replica.
Description check ✅ Passed The description clearly explains what was changed, why (routing reads to replica), and what was intentionally excluded (writes, transactions), with supporting context links.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch devin/1779398666-raw-queries-read-replica

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

…nous)

Co-Authored-By: Konstantin Wohlwend <n2d4xc@gmail.com>
Now that we know writes await replication, there's no read-after-write
concern. Update remaining SELECT queries in CLI auth routes,
getManagedEmailDomainByTenancyAndSubdomain, and local emulator project
lookup.

Co-Authored-By: Konstantin Wohlwend <n2d4xc@gmail.com>
Co-Authored-By: Konstantin Wohlwend <n2d4xc@gmail.com>
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 22, 2026

Greptile Summary

This PR routes all read-only raw Prisma queries ($queryRaw/$queryRawUnsafe) to the database read replica via $replica(), reducing load on the primary. A new AGENTS.md note documents when to use $replica() and explicitly calls out the exceptions (transactions and write queries).

  • Most of the 30+ changed queries are pure read-only SELECTs (stats, conversations, domain lookups, session replays, payments listing) where replica routing is safe.
  • Two queries in the local emulator path are incorrectly routed to the replica: the "get-or-create" lookup in getOrCreateLocalEmulatorProjectId and the onboarding-status read in syncLocalEmulatorOnboardingStatus both execute immediately before or after primary writes, making them sensitive to replication lag.

Confidence Score: 3/5

The bulk of the changes are safe, but the local emulator endpoint has two reads incorrectly moved to the replica that bracket primary writes — one can silently overwrite the project mapping and the other will throw an assertion error on first use.

The overwhelming majority of queries are straightforward read-only SELECTs with no writes depending on them. The local emulator route is the exception: getOrCreateLocalEmulatorProjectId reads from the replica to decide whether to create a new project ID then writes unconditionally to the primary, causing silent project mapping corruption; syncLocalEmulatorOnboardingStatus then reads the just-written Project row from the replica before it replicates, throwing an uncaught StackAssertionError on every first-time creation.

apps/backend/src/app/api/latest/internal/local-emulator/project/route.tsx — both getOrCreateLocalEmulatorProjectId and syncLocalEmulatorOnboardingStatus need their $replica() reads reverted to primary reads.

Important Files Changed

Filename Overview
apps/backend/src/app/api/latest/internal/local-emulator/project/route.tsx Two replica reads follow primary writes without any lag protection: the "get-or-create" lookup in getOrCreateLocalEmulatorProjectId can return stale/empty results and corrupt the project mapping, and the onboarding-status lookup in syncLocalEmulatorOnboardingStatus can throw an assertion error on first project creation.
apps/backend/src/app/api/latest/auth/cli/complete/route.tsx Both reads (CliAuthAttempt and RefreshToken) routed to replica; the subsequent writes use atomic UPDATE…WHERE CAS conditions on the primary, so replica staleness can cause a spurious 400 at worst but not a security bypass.
apps/backend/src/app/api/latest/internal/external-db-sync/status/route.ts All 12 queries are aggregate stats/counts for a monitoring dashboard; slightly stale replica reads are acceptable here.
apps/backend/src/app/api/latest/internal/payments/transactions/route.tsx getTransactions is called with a regular (non-transaction) Prisma client; both the main and refund queries are read-only and safe to route to the replica.
apps/backend/src/lib/conversations.tsx Four read-only conversation queries correctly routed to replica.
AGENTS.md Adds clear documentation on when to use $replica() for raw queries, including the transaction and write exclusions.

Comments Outside Diff (2)

  1. apps/backend/src/app/api/latest/internal/local-emulator/project/route.tsx, line 85-91 (link)

    P1 Stale replica read breaks "get-or-create" idempotency

    getOrCreateLocalEmulatorProjectId reads the existing project mapping from the replica, but immediately after writes to the primary. If the replica lags behind a recent write (e.g., on a rapid second call after project creation), the lookup returns no row, so a fresh UUID is generated — and then the ON CONFLICT DO UPDATE SET "projectId" = EXCLUDED."projectId" overwrites the mapping to point at the newly-generated project instead of the one already in the database. This silently reassigns which project a given file path resolves to. The read here should remain on the primary to preserve the original idempotency guarantee.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: apps/backend/src/app/api/latest/internal/local-emulator/project/route.tsx
    Line: 85-91
    
    Comment:
    **Stale replica read breaks "get-or-create" idempotency**
    
    `getOrCreateLocalEmulatorProjectId` reads the existing project mapping from the replica, but immediately after writes to the primary. If the replica lags behind a recent write (e.g., on a rapid second call after project creation), the lookup returns no row, so a fresh UUID is generated — and then the `ON CONFLICT DO UPDATE SET "projectId" = EXCLUDED."projectId"` overwrites the mapping to point at the newly-generated project instead of the one already in the database. This silently reassigns which project a given file path resolves to. The read here should remain on the primary to preserve the original idempotency guarantee.
    
    How can I resolve this? If you propose a fix, please make it concise.
  2. apps/backend/src/app/api/latest/internal/local-emulator/project/route.tsx, line 201-209 (link)

    P1 Assertion error on replica lag after project creation

    syncLocalEmulatorOnboardingStatus is called immediately after getOrCreateLocalEmulatorProjectId writes a newly-created Project row to the primary. If the replica hasn't propagated that write yet, the $queryRaw on line 201 returns no rows and line 208 throws a StackAssertionError. This means first-time project creation will reliably fail with an internal server error whenever replication lag exists. The read should target the primary for this write-following query.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: apps/backend/src/app/api/latest/internal/local-emulator/project/route.tsx
    Line: 201-209
    
    Comment:
    **Assertion error on replica lag after project creation**
    
    `syncLocalEmulatorOnboardingStatus` is called immediately after `getOrCreateLocalEmulatorProjectId` writes a newly-created `Project` row to the primary. If the replica hasn't propagated that write yet, the `$queryRaw` on line 201 returns no rows and line 208 throws a `StackAssertionError`. This means first-time project creation will reliably fail with an internal server error whenever replication lag exists. The read should target the primary for this write-following query.
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
apps/backend/src/app/api/latest/internal/local-emulator/project/route.tsx:85-91
**Stale replica read breaks "get-or-create" idempotency**

`getOrCreateLocalEmulatorProjectId` reads the existing project mapping from the replica, but immediately after writes to the primary. If the replica lags behind a recent write (e.g., on a rapid second call after project creation), the lookup returns no row, so a fresh UUID is generated — and then the `ON CONFLICT DO UPDATE SET "projectId" = EXCLUDED."projectId"` overwrites the mapping to point at the newly-generated project instead of the one already in the database. This silently reassigns which project a given file path resolves to. The read here should remain on the primary to preserve the original idempotency guarantee.

### Issue 2 of 2
apps/backend/src/app/api/latest/internal/local-emulator/project/route.tsx:201-209
**Assertion error on replica lag after project creation**

`syncLocalEmulatorOnboardingStatus` is called immediately after `getOrCreateLocalEmulatorProjectId` writes a newly-created `Project` row to the primary. If the replica hasn't propagated that write yet, the `$queryRaw` on line 201 returns no rows and line 208 throws a `StackAssertionError`. This means first-time project creation will reliably fail with an internal server error whenever replication lag exists. The read should target the primary for this write-following query.

Reviews (1): Last reviewed commit: "Merge branch 'dev' into devin/1779398666..." | Re-trigger Greptile

@N2D4 N2D4 enabled auto-merge (squash) May 22, 2026 00:29
Co-Authored-By: Konstantin Wohlwend <n2d4xc@gmail.com>
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