Skip to content

Prevent duplicate users from concurrent TMC create-user requests - #1745

Merged
nygrenh merged 3 commits into
masterfrom
unique-users-upstream-id
Jul 7, 2026
Merged

Prevent duplicate users from concurrent TMC create-user requests#1745
nygrenh merged 3 commits into
masterfrom
unique-users-upstream-id

Conversation

@Redande

@Redande Redande commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

find-then-insert in get_or_create_user_from_tmc_mooc_fi_response races when two requests arrive for the same not-yet-created upstream_id (which the tmc-server login-migration PR makes likely — parallel basic-auth API requests); adds a partial unique index on active users.upstream_id and makes the loser of the race fetch and return the winner's row. Include the pre-merge caution prominently: if production already has duplicate active users with the same upstream_id, the migration fails and blocks deploy — the check query is in the migration file's comment.

see testmycode/tmc-server#595

Summary by CodeRabbit

  • Bug Fixes
    • Prevent duplicate active user records for the same external upstream identifier.
    • Improves reliability of the user creation flow under concurrent requests by correctly resolving uniqueness conflicts.
    • Enables recreating users that were previously soft-deleted without running into active-user collisions.
  • Other
    • Makes external TMC user field parsing more tolerant of missing/empty optional data (e.g., first/last name and organization ID).

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c4d44c81-df00-428a-bd14-04a4a5cfeca4

📥 Commits

Reviewing files that changed from the base of the PR and between 375ee3d and 5941e45.

📒 Files selected for processing (4)
  • services/headless-lms/migrations/20260703090000_add-unique-index-users-upstream-id.down.sql
  • services/headless-lms/migrations/20260703090000_add-unique-index-users-upstream-id.up.sql
  • services/headless-lms/models/src/error.rs
  • services/headless-lms/server/src/domain/authorization.rs
🚧 Files skipped from review as they are similar to previous changes (3)
  • services/headless-lms/migrations/20260703090000_add-unique-index-users-upstream-id.down.sql
  • services/headless-lms/migrations/20260703090000_add-unique-index-users-upstream-id.up.sql
  • services/headless-lms/server/src/domain/authorization.rs

📝 Walkthrough

Walkthrough

Adds a partial unique index for active users by upstream_id, tolerates missing TMC user-field values, and updates user creation to recover from concurrent insert failures by re-reading the existing user.

Changes

Duplicate user race fix

Layer / File(s) Summary
TMC user-field defaults
services/headless-lms/utils/src/services/tmc.rs
TMCUserField now uses optional fields with serde defaults so absent or null user-field data deserializes successfully.
Active upstream_id index and mapping
services/headless-lms/migrations/20260703090000_add-unique-index-users-upstream-id.up.sql, services/headless-lms/migrations/20260703090000_add-unique-index-users-upstream-id.down.sql, services/headless-lms/models/src/error.rs
Adds and removes a partial unique index on active users.upstream_id values, and maps the new constraint name to a specific database-constraint error.
Race-safe user creation
services/headless-lms/server/src/domain/authorization.rs
Captures the insert result, filters blank first and last names, and re-fetches by upstream_id when the insert fails.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

Suggested labels: bug

Suggested reviewers: nygrenh

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: preventing duplicate users during concurrent TMC create-user requests.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ 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 unique-users-upstream-id

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.

@coderabbitai coderabbitai Bot added the bug Something isn't working label Jul 3, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
services/headless-lms/server/src/domain/authorization.rs (1)

963-973: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider narrowing the fallback to unique-violation errors.

Any insert error (not just a unique-constraint violation from the new index) triggers a re-fetch by upstream_id before falling back to insert_error. Functionally safe since the original error is preserved when re-fetch finds nothing, but for unrelated failures (e.g. connection issues) this adds an extra query on the failure path. Optionally match on the underlying DB error code (23505) to only retry on genuine unique-violation races.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@services/headless-lms/server/src/domain/authorization.rs` around lines 963 -
973, In the user insert fallback inside the authorization flow, the current
retry path in the match on inserted retries a lookup for every insert failure,
not just genuine unique-violation races. Narrow this logic in the user creation
path around models::users::find_by_upstream_id and the insert_error handling so
the re-fetch only happens when the underlying DB error indicates a unique
constraint violation (for example code 23505), and let other errors return
immediately without the extra query.
services/headless-lms/migrations/20260703090000_add-unique-index-users-upstream-id.up.sql (1)

9-11: 🚀 Performance & Scalability | 🔵 Trivial

Use a concurrent index build here. CREATE UNIQUE INDEX will block users while the index is built; if this needs to stay online, switch to CREATE UNIQUE INDEX CONCURRENTLY and add -- no-transaction to this migration.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@services/headless-lms/migrations/20260703090000_add-unique-index-users-upstream-id.up.sql`
around lines 9 - 11, This migration builds a unique index on users.upstream_id
in a blocking way, so update the migration to use a concurrent index build
instead. Change the CREATE UNIQUE INDEX statement in the
users_upstream_id_active_uniq_idx migration to CREATE UNIQUE INDEX CONCURRENTLY,
and mark the migration as no-transaction so the database can build it online
without locking users.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In
`@services/headless-lms/migrations/20260703090000_add-unique-index-users-upstream-id.up.sql`:
- Around line 9-11: This migration builds a unique index on users.upstream_id in
a blocking way, so update the migration to use a concurrent index build instead.
Change the CREATE UNIQUE INDEX statement in the
users_upstream_id_active_uniq_idx migration to CREATE UNIQUE INDEX CONCURRENTLY,
and mark the migration as no-transaction so the database can build it online
without locking users.

In `@services/headless-lms/server/src/domain/authorization.rs`:
- Around line 963-973: In the user insert fallback inside the authorization
flow, the current retry path in the match on inserted retries a lookup for every
insert failure, not just genuine unique-violation races. Narrow this logic in
the user creation path around models::users::find_by_upstream_id and the
insert_error handling so the re-fetch only happens when the underlying DB error
indicates a unique constraint violation (for example code 23505), and let other
errors return immediately without the extra query.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 03958a72-9701-4cad-889b-062bddbf6e1d

📥 Commits

Reviewing files that changed from the base of the PR and between a4bfc8b and d0dd214.

📒 Files selected for processing (3)
  • services/headless-lms/migrations/20260703090000_add-unique-index-users-upstream-id.down.sql
  • services/headless-lms/migrations/20260703090000_add-unique-index-users-upstream-id.up.sql
  • services/headless-lms/server/src/domain/authorization.rs

@nygrenh
nygrenh merged commit cee60f8 into master Jul 7, 2026
19 checks passed
@nygrenh
nygrenh deleted the unique-users-upstream-id branch July 7, 2026 09:53
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