Fix racy project_authorizations sync trigger losing rows
What does this MR do and why?
We're restoring the unique index on project_authorizations (#418205). To do that without downtime, we backfill a new table, project_authorizations_for_migration, and keep it in sync with a database trigger while the backfill runs.
The source table can hold multiple rows per user and project, one for each access level. The new table keeps only one row per user and project. The trigger's delete branch didn't account for that. When one access level row was deleted, it removed the pair from the new table even though the user still had another access level for the project.
Once that happens, the pair is gone for good. The surviving source row never changes again, so the trigger doesn't fire for it, and the backfill inserts with ON CONFLICT DO NOTHING, so it doesn't repair the row either. In practice this is triggered by two permission refreshes racing each other: one deletes an old access level row after another has already written its replacement. This is how a row went missing on GitLab.com (see #526000).
This MR replaces the trigger function. On delete it now recomputes the row in the new table from whatever source rows remain, and only deletes it when the user has no rows left for that project.
A follow-up MR (!246509 (merged)) requeues the backfill migration to restore rows that were lost before this fix.
Database queries
- Case A: pair still has a source row, dest row missing (upsert insert arm)
- Case B: duplicate source rows, dest row present (upsert DO UPDATE arm)
- Case C: no source rows, dest already empty (delete arm, no-op)
- Case D: no source rows, dest row present (delete arm removes the row)
References
- #525999
- #526000
- #418205
MR acceptance checklist
Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.
Related to #525999