Add a database default for project_authorizations.is_unique
What this does
Gives project_authorizations.is_unique a database default of true, following
the procedure for changing column defaults.
- A post-deployment migration setting the default.
SafelyChangeColumnDefaultandattribute :is_unique, default: trueonProjectAuthorization.- The
before_create :assign_is_uniquecallback is replaced by that attribute default, which does the same job through the documented mechanism.
Why
The column has no default today, so every write path sets it by hand.
project_authorizations_for_migration, the table this one is being swapped for,
has no is_unique column at all. Giving the column a default is what lets the
application stop setting it.
The default is load bearing until the swap happens. The partial unique index
index_unique_project_authorizations_on_unique_project_user covers only rows
where is_unique is true, and it is the only thing preventing duplicate
(project_id, user_id) rows on this table. A row written with NULL falls
outside it.
Why the concern is needed
Rails is configured with partial_inserts, so an INSERT omits a column whose
in-memory value equals the cached default and lets the database supply it. While
a default is in flux, a process can hold a stale schema cache and fall back to a
default it cannot see. SafelyChangeColumnDefault forces the column into every
INSERT, and the attribute default keeps the written value owned by the
application rather than the schema cache.
What is deliberately left alone
The two write paths that bypass the model are untouched:
ProjectAuthorizations::Changes#insert_all_in_batches- the
upsertinProjectAuthorization.find_or_create_authorization_for
Neither the concern nor the attribute default reaches insert_all or upsert,
and post-deployment migrations run after the code is deployed. Dropping those
writes now would leave a window where rows are written with NULL and escape the
partial index.
Notes for database review
No table rewrite. change_column_default is catalog only in PostgreSQL 11
and later, so this does not touch the table itself, which is classified
over_limit. It takes a brief ACCESS EXCLUSIVE lock to update the catalog.
Existing rows are unchanged. change_column_default does not backfill, so
rows with NULL stay NULL and remain outside the partial index. That is the
existing duplicate problem tracked in
https://gitlab.com/gitlab-org/gitlab/-/issues/418205 and is neither improved nor
made worse here.
Follow-up in 19.5
Once the default has shipped everywhere, a second MR removes
columns_changing_default, the SafelyChangeColumnDefault include, the
attribute default, both remaining writes, and an error message in
ee/app/services/onboarding/finish_service.rb that interpolates the column. The
procedure requires waiting a full minor release, because self managed bundles a
minor release into a single zero downtime deployment.
References
Closes https://gitlab.com/gitlab-org/gitlab/-/issues/627241
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.