fix: atomic alias reassignment in upsert_aliases_for_model#446
fix: atomic alias reassignment in upsert_aliases_for_model#446henrypark133 wants to merge 2 commits intomainfrom
Conversation
Replace DELETE-then-INSERT with a merge approach to eliminate the visibility gap during cross-model alias reassignment. The new flow within a single transaction: 1. UPSERT each new alias (ON CONFLICT atomically reassigns it) 2. DELETE stale aliases no longer in the list 3. SELECT and return the final alias set Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary of ChangesHello @henrypark133, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request significantly enhances the reliability and consistency of model alias management. By transitioning to an atomic upsert-then-delete mechanism, it resolves potential race conditions and visibility issues that could arise during alias reassignments, ensuring that alias states are always accurate and immediately reflected across the system. Highlights
Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
PR Review: atomic alias reassignmentThe core fix is correct — replacing DELETE-then-INSERT with UPSERT-then-delete-stale properly handles cross-model alias reassignment without unique constraint violations. The schema confirms Issues Found1. Deadlock risk from inconsistent lock ordering ( The UPSERT loop acquires row-level locks in the order callers pass aliases. Two concurrent calls sharing overlapping aliases but in different order will deadlock:
PostgreSQL detects and aborts one, and let mut sorted_aliases: Vec<&String> = alias_names.iter().collect();
sorted_aliases.sort();
for alias_name in &sorted_aliases {
// UPSERT...
}2. N+1 queries in the UPSERT loop (minor perf concern) One round-trip per alias. For typical small alias lists this is fine, but a single batch UPSERT using INSERT INTO model_aliases (alias_name, canonical_model_id, is_active)
SELECT unnest(::text[]), , true
ON CONFLICT (alias_name) DO UPDATE SET
canonical_model_id = EXCLUDED.canonical_model_id,
is_active = true3. Misleading description of the original bug The PR description says the old approach created a "visibility gap" for concurrent readers. This isn't accurate — both approaches use a transaction, so READ COMMITTED readers only see committed states. The real bug was that cross-model alias reassignment would fail with a unique constraint violation (no ON CONFLICT handling on the old INSERT). Worth correcting the description for future reference, though it doesn't affect the code. 4. The With an empty alias list, SummaryThe logic is sound and the fix is a genuine improvement. Items 3 and 4 are cosmetic. Item 1 (lock ordering) is the only one worth fixing before merge to avoid unnecessary retry churn in high-concurrency scenarios. |
There was a problem hiding this comment.
Code Review
This pull request correctly addresses a potential race condition in upsert_aliases_for_model by switching from a DELETE-then-INSERT pattern to an UPSERT-then-DELETE approach. This ensures alias reassignments are atomic. My review includes a suggestion to further improve the implementation by simplifying the logic and using a single bulk UPSERT query, which will be more performant, especially with a large number of aliases.
Replace per-alias UPSERT loop with a single batch INSERT using unnest(), which acquires row locks in a consistent order and eliminates deadlock risk between concurrent calls with overlapping aliases. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Thanks for the thorough review! Here's how each item was addressed: 1. Deadlock risk from inconsistent lock ordering — Fixed ✅ 2. N+1 queries — Fixed ✅ 3. Misleading description of the original bug — Acknowledged 4. |
Problem
upsert_aliases_for_modelpreviously used a DELETE-then-INSERT pattern inside a transaction:This created a visibility gap: if another model held an alias being reassigned, a concurrent reader between the DELETE and INSERT steps could observe the alias momentarily missing or pointing to the wrong model.
Solution
Replace with a merge (UPSERT-then-delete-stale) approach within a single transaction:
INSERT ... ON CONFLICT (alias) DO UPDATE SET model_id = EXCLUDED.model_idatomically reassigns the alias with no visibility gap.This ensures alias ownership transitions are atomic: there is never a moment when a valid alias is unowned.
Edge Case
Empty alias list: the UPSERT loop is a no-op and the DELETE removes all aliases for the model, which is the correct behaviour.
Files Changed
crates/database/src/repositories/model_alias.rs🤖 Generated with Claude Code