Skip to content

feat: Phase 1 — migrate read queries to reservation tables (PG)#313

Open
porcellus wants to merge 25 commits intofeat/isolation-reservation-table-lockingfrom
feat/isolation-reservation-table-migration
Open

feat: Phase 1 — migrate read queries to reservation tables (PG)#313
porcellus wants to merge 25 commits intofeat/isolation-reservation-table-lockingfrom
feat/isolation-reservation-table-migration

Conversation

@porcellus
Copy link
Copy Markdown
Collaborator

Summary

  • Migrate all read queries off all_auth_recipe_users and per-recipe *_user_to_tenant tables to reservation tables (recipe_user_tenants, recipe_user_account_infos, primary_user_tenants, app_id_to_user_id)
  • Migrate getUsers() dashboard search to unified reservation table queries with proper search tag combinations
  • Migrate listUsersByAccountInfo, getPrimaryUserInfo, getTenantIds, session queries, and doesUserIdExist
  • Fix Primery typo and optimize lockUsers to single FOR UPDATE query
  • Fix Phase 1 bugs: phone_number column name, dual email+phone passwordless reservations, linkAccounts time_joined ordering, search tag edge cases, CHAR(36) padding in lockUsers
  • Add comprehensive reservation table integrity checker (I1-I6) and 10 new tests

Phase 1 of the DEPRECATE plan — moves all read paths to the new reservation tables. After this, the old tables are only used for writes (Phase 2 will address those).

Companion PRs:

Test plan

  • Full test suite (running)
  • ReservationTableIntegrityTest — 10 tests verifying all 6 invariants across email, phone, third-party
  • ExceptionParsingTest — integrity checks added to signup methods
  • 6 race test classes upgraded from email-only to full reservation consistency checks
  • Lint passes (spotlessJavaCheck clean)

🤖 Generated with Claude Code

Mihaly and others added 25 commits February 6, 2026 12:05
Reduce iterations from 3000 to 500 and thread pool from 1000 to 200
threads across all three deadlock tests. Tighten awaitTermination from
2min to 60s with an assertion. Deadlocks still trigger reliably since
even 200 threads competing for the same rows produces heavy contention.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reduce idle timeout from 30s to 3s and set HikariCP housekeeping
period to 1s (via system property) in both pool tests. This cuts the
Thread.sleep from 65s to 8s. Also reduce concurrent sign-in
operations from 10000 to 1000 in testIdleConnectionTimeout.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Cache per-worker test database (create once, reuse across tests)
- Add truncateAllData() that truncates all tables instead of
  dropping and recreating the database each test
- Use killAll(false) in reset() to preserve tables across tests,
  making CREATE TABLE IF NOT EXISTS statements no-ops
- Add killAll(boolean) overload to TestingProcessManager
- Update DbConnectionPoolTest idle timeout to use HikariCP minimum

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add time_joined and primary_or_recipe_user_time_joined columns to
app_id_to_user_id with 4 pagination indexes. Update all signup write
paths (EP, Pless, TP, WebAuthn + bulk import) and account linking
methods (updateTimeJoined, unlinkAccounts) to maintain these columns.

This prepares app_id_to_user_id to replace all_auth_recipe_users
for pagination queries (DEPRECATE ticket 01).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Prepares for future CASCADE FK migration by adding ON UPDATE CASCADE
to all foreign keys that reference app_id_to_user_id(app_id, user_id).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…able

Replaces the old query that joined emailpassword_user_to_tenant with
all_auth_recipe_users. The new query joins recipe_user_tenants with
app_id_to_user_id, which works for both linked and unlinked users
without needing to query primary_user_tenants separately.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Migrate 5 methods in PasswordlessQueries.java from passwordless_user_to_tenant
and all_auth_recipe_users to recipe_user_tenants and app_id_to_user_id:
- deleteDevicesByPhoneNumber_Transaction
- deleteDevicesByEmail_Transaction
- getUserInfosWithTenant_Transaction
- getPrimaryUserIdUsingEmail
- getPrimaryUserByPhoneNumber

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…JVM hang

afterTesting() only cleaned up files but never killed test processes.
The last test in each class left a SuperTokens instance running with
non-daemon threads (webserver, cron jobs, HikariCP) that prevented
the test JVM from exiting, causing gradle to hang indefinitely.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Migrate getUserIdByThirdPartyInfo and getPrimaryUserIdUsingEmail to use
recipe_user_tenants + app_id_to_user_id instead of thirdparty_user_to_tenant
+ all_auth_recipe_users. Delete dead code: getPrimaryUserIdUsingEmail_Transaction
and getPrimaryUserIdsUsingMultipleEmails_Transaction (no callers).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Migrate 4 methods in WebAuthNQueries.java off webauthn_user_to_tenant
and all_auth_recipe_users to use recipe_user_tenants (tenant-scoped)
and recipe_user_account_infos (app-scoped) joined with app_id_to_user_id.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…e queries

Replace per-recipe UNION queries (emailpassword, thirdparty, passwordless, webauthn
user_to_tenant tables + all_auth_recipe_users) with unified queries using
app_id_to_user_id + recipe_user_tenants. Uses ILIKE for case-insensitive search.
Non-search pagination path also migrated to use app_id_to_user_id + recipe_user_tenants.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Migrate listPrimaryUsersByEmail, listPrimaryUsersByPhoneNumber,
getPrimaryUserByThirdPartyInfo, and listPrimaryUsersByThirdPartyInfo
(including _Transaction variant) to use recipe_user_tenants and
recipe_user_account_infos tables instead of per-recipe query methods.

New unified lookup methods added to AccountInfoQueries.java replace
4 separate per-recipe queries with single queries against the
reservation tables.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…l_auth_recipe_users

Migrate getUsersCount (app-scoped and tenant-scoped), checkIfUsesAccountLinking
to use app_id_to_user_id and recipe_user_tenants instead of all_auth_recipe_users.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ff all_auth_recipe_users

- getPrimaryUserInfoForUserIds: LEFT JOIN recipe_user_tenants for tenant_id, time_joined from app_id_to_user_id
- getPrimaryUserIdStrForUserId: read from app_id_to_user_id
- getTenantIdsForUserIds: use recipe_user_tenants with DISTINCT
- SessionQueries: use app_id_to_user_id for primary_or_recipe_user_id lookups

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace all_auth_recipe_users with recipe_user_tenants and
primary_user_tenants for the tenant-scoped doesUserIdExist query.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Part A (ISO-021): Fix wasAlreadyAPrimeryUserResult typo in AccountInfoQueries.
Part B (ISO-025): Rewrite lockUsers() to use a single query with FOR UPDATE
instead of 2N+M individual queries. Uses UNION to fetch both requested users
and their primary users, with ORDER BY user_id for deadlock prevention.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- PasswordlessQueries: fix phone_number column name in ResultSet read,
  remove dead PasswordlessDeviceRowMapper call, insert both email AND
  phone rows into recipe_user_tenants for dual-contact users
- GeneralQueries: move updateTimeJoinedForPrimaryUser after app_id_to_user_id
  update in linkAccounts so MIN(time_joined) sees all linked users;
  add hasPhones&&hasProviders and hasEmails&&hasPhones&&hasProviders
  search tag cases to prevent false matches in getUsers dashboard search
- UserLockingQueries: trim user_id from ResultSet to handle CHAR(36)
  padding in HashMap lookup (fixes lockUser with non-UUID user IDs)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extend RaceTestUtils with full integrity checks (I1-I6) for all
account_info_types (EMAIL, PHONE_NUMBER, THIRD_PARTY):
- I1/I2: primary_user_tenants completeness and accuracy
- I3: recipe_user_tenants consistency
- I4: recipe_user_tenants row count per tenant
- I5: recipe_user_account_infos row count
- I6: time_joined consistency in app_id_to_user_id

Create ReservationTableIntegrityTest with 10 tests covering:
- Passwordless dual email+phone reservations (Bug #3 regression)
- Time_joined consistency after linking (Bug #2 regression)
- Unlink cleanup and primary reservation preservation
- Dashboard search tag combinations (Bug #4 regression)
- Cross-recipe linking, third-party type, email clearing

Hook integrity checks into ExceptionParsingTest signup methods.
Update all 6 race test classes to use generalized checker.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.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