feat: schema rework — reservation tables, LockedUser, read migration, perf#322
Open
porcellus wants to merge 106 commits into
Open
feat: schema rework — reservation tables, LockedUser, read migration, perf#322porcellus wants to merge 106 commits into
porcellus wants to merge 106 commits into
Conversation
…Y mode In LEGACY mode, recipe_user_account_infos is empty, so the subquery to get primary_user_id returns NULL. Use the primaryUserId parameter directly instead. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…t 17) In LEGACY mode, these methods call per-recipe query methods (EP, PL, TP, WebAuthn) instead of the unified AccountInfoQueries. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The legacy query incorrectly referenced thirdparty_user_to_tenant.email which doesn't exist. Fixed to use thirdparty_users.email with a 3-way join (thirdparty_users + all_auth_recipe_users + thirdparty_user_to_tenant). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Multiple query paths were unconditionally reading from new tables (recipe_user_tenants, recipe_user_account_infos, primary_user_tenants) which are empty in LEGACY migration mode. This caused: - Infinite loops in signInUpHelper (empty tenant IDs → retry forever) - WrongCredentialsException on sign-in (users not found by email) - Missing email conflict checks on email/password/phone updates - Missing account linking conflict detection (checkIfLoginMethodCanBecomePrimary, checkIfLoginMethodsCanBeLinked) - Missing tenant association conflict detection (addTenantIdToPrimaryUser) - Missing bulk import conflict detection (reservePrimaryUserAccountInfos) Key changes: - getTenantIdsForUserIds: read from all_auth_recipe_users in LEGACY mode - getPrimaryUserInfoForUserIds_Transaction: JOIN on old table for tenant IDs - listPrimaryUsersByThirdPartyInfo: use thirdparty_users table in LEGACY mode - updateUsersEmail_Transaction (EP/TP/PL/WebAuthn): add legacy email conflict check - checkIfLoginMethodCanBecomePrimary: full legacy implementation using old tables - checkIfLoginMethodsCanBeLinked: full legacy implementation with tenant overlap checks - makePrimaryUser_Transaction: legacy conflict detection and already-primary check - linkAccounts_Transaction: legacy conflict detection with resolved primary user ID - addTenantIdToPrimaryUser_Transaction: legacy email/phone/TP conflict check - reservePrimaryUserAccountInfos_Transaction: legacy conflict detection for bulk import - deleteDevicesByPhoneNumber/Email_Transaction: use old table for tenant subquery - ConnectionPool: add socketTimeout=60 for JDBC connections - New helper methods: listPrimaryUsersByEmail_legacy_forApp, listPrimaryUsersByPhoneNumber_legacy_forApp, listPrimaryUserIdsByThirdPartyInfo_legacy Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ation In LEGACY mode (without reservation tables), detect duplicate account infos within the same batch of PrimaryUsers before checking against existing users in old tables. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ation Adds MigrationBackfillQueries with per-user backfill logic for all recipe types (EP, PL, TP, WebAuthn). Implements MigrationBackfillStorage in Start.java. Includes 10 unit tests and 5 integration tests covering multi-recipe, multi-tenant, linked accounts, batch sizing, and idempotency. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Set devConfig.yaml migration_mode to MIGRATED (was commented out, defaulting to LEGACY) so race condition tests work correctly - Add setMigrationModeForTesting() to PostgreSQLConfig for test overrides - Fix BackfillIntegrationTest: use DUAL_WRITE_READ_OLD mode before user creation so all_auth_recipe_users gets populated (backfill JOINs on it) - Fix backfillCronSkipsInLegacyMode: explicitly set LEGACY mode REVIEW-005 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- DualWriteConsistencyTest: replace invalid cross-table comparison
(linked-user rows legitimately diverge between old/new tables) with
independent checks:
* checkOldTableConsistency — linking flags, primary_or_recipe_user_id
* checkReservationConsistency — invariants I1–I6
3/3 tests passing (was 0/3).
- New: BackfillConcurrencyTest, DeleteUserRaceTest, LegacyModeRaceTest,
MigrationModeTest covering mode-specific race paths.
- Race tests & RaceTestUtils updated for new invariants and helpers.
…fallback Implement recipe_user_tenants INSERT/DELETE for EmailPassword, ThirdParty, and Passwordless in MIGRATED mode. Add COALESCE fallback in linkAccounts_Transaction for deleted primary user reservation rows. Document migration_mode config option.
Set DUAL_WRITE_READ_OLD mode in BackfillTest, fix CHAR(36) trim comparison, increase DeadlockTest executor timeout to 180s, add migration_mode to devConfig ignore list in PostgresSQLConfigTest.
…ization Without third_party_id in the WHERE clause, the planner cannot use the full idx_recipe_user_tenants_account_info index (app_id, tenant_id, account_info_type, third_party_id, account_info_value) as a point lookup. It falls back to scanning on (app_id, tenant_id, account_info_type) and filtering the rest — measured at 31ms vs 0.065ms with the fix (477x). For email and phone rows, third_party_id is always empty string. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…fault WebAuthNQueries.getUsersInfoUsingIdList was wrapping a pure read in start.startTransaction, while its EmailPassword/ThirdParty/Passwordless siblings borrowed a pooled connection directly. That wrapper fired on every /users/by-accountinfo request via the 4-way fan-out in GeneralQueries.getPrimaryUserInfoForUserIds_new, producing per-request SET and SHOW TRANSACTION ISOLATION LEVEL round-trips visible in pg_stats. Replace the wrapper with a try-with-resources pooled connection, matching the sibling implementations. Also add HikariCP connectionInitSql to establish READ COMMITTED as the session default once per physical connection, preparing for a follow-up that can let startTransactionHelper skip the isolation dance when the requested level equals the current one.
The composite index idx_recipe_user_tenants_account_info had
third_party_id between account_info_type and account_info_value. For
tparty rows the column is always empty (''), and for email/phone rows
of non-thirdparty recipes it is also empty — the only rows with a
non-empty value are the shadow email rows of thirdparty users. With the
column in the index, getPrimaryUserIdByThirdPartyInfo could only match
the 3-column prefix and had to scan+filter the rest, running ~10x
slower than the equivalent email lookup.
Drop the column from the index so all three account-info lookups
(email, phone, tparty) hit a 4-column point lookup; add the matching
third_party_id='' predicate to the tparty query for symmetry with the
email/phone variants.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced May 28, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements supertokens/supertokens-plugin-interface#199. See that PR for context.
Supersedes:
Companion PRs: supertokens/supertokens-plugin-interface#199, supertokens/supertokens-core#1277