Skip to content

Add columns with volatile DEFAULTs without an ACCESS_EXCLUSIVE table lock #694

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Feb 23, 2025

Conversation

andrew-farries
Copy link
Collaborator

Ensure that adding a new column (with an add_column operation) does not take an ACCESS_EXCLUSIVE lock on the table even when the column has a volatile DEFAULT expression.

Adding a column with a DEFAULT expression in Postgres can have one of two different locking behaviours depending on the expression used as the DEFAULT:

  • For non-volatile DEFAULT expressions, the column addition can use the fast-path optimization to add the column default; this is a catalog-only operation and so holds a lock only for a very short time.
  • For volatile DEFAULT expressions, the fast-path optimization cannot be used; the change requires a full table rewrite - this means holding an ACCESS_EXCLUSIVE lock on the table until the rewrite is complete.

This PR makes add_column operations aware of this distinction and able to add columns with volatile defaults without taking an ACCESS_EXCLUSIVE lock on the table.

When adding a column with a DEFAULT:

  • create a temporary schema-only copy of the table.
  • add the column to this temporary table
  • read the pg_attribute.atthasmissing system catalog field to determine whether the fast-path optimization was used to add the column DEFAULT.
  • drop the temporary table

Now the operation knows whether the column can be added using the fast-path DEFAULT optimization. In the case where the column can use the optimization, the column can be added with a DEFAULT directly. In the case where the fast-path optimization can't be used:

  • Add the column without a DEFAULT value
  • Create an up trigger to backfill the column using the column DEFAULT expression
  • Add the DEFAULT to the column on migration completion

This avoids the ACCESS_EXCLUSIVE lock; effectively replacing the table rewrite that would have caused it with a backfill operation instead.

If the column to be added has a volatile DEFAULT and a NOT NULL constraint, the column must be added with a NOT NULL ... NOT VALID constraint to allow the column to be added without a DEFAULT; the constraint is validated and upgraded on migration completion.

For a non-volatile DEFAULTs, the operation must set up to the same expression as is set in default. The operation will fail and roll back if they differ.

References

Volatile vs non-volatile functions:

The fast-path optimization for non-volatile column DEFAULT expressions is explained here:

Fixes #643

@andrew-farries andrew-farries force-pushed the lock-free-add-column-with-default branch from a8296e1 to 36d8a91 Compare February 19, 2025 15:55
Test the new column's `DEFAULT` expression for volatility. If the
expression is volatile, add the column without a `DEFAULT` and create a
trigger to backfill the column with the default value. The `DEFAULT`
value will be added on migration completion. If the added column is `NOT
NULL`, a `NOT NULL NOT VALID` constraint is created on the field.

If the `DEFAULT` expression is non-volatile, the column can be added
with a `DEFAULT` directly.

For a volatile default, the column default will be set on operation
completion, along with upgrading any `NOT NULL NOT VALID` constraints on
the column.
Add testcases for:
* adding columns with volatile and non-volatile defaults.
* when adding a column with a volatile `DEFAULT`, the `up` expression
  must be equal to the `default` expression.
Describe how `pgroll` handles adding columns with defaults in the
`add_column` operation.
@andrew-farries andrew-farries force-pushed the lock-free-add-column-with-default branch from 36d8a91 to 2e7a519 Compare February 19, 2025 15:56
@andrew-farries andrew-farries marked this pull request as ready for review February 19, 2025 16:18
@andrew-farries andrew-farries merged commit 5410020 into main Feb 23, 2025
28 checks passed
@andrew-farries andrew-farries deleted the lock-free-add-column-with-default branch February 23, 2025 16:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Adding columns with volatile DEFAULTs locks tables for duration of table rewrite
2 participants