Restructure ALTER TABLE notes to clarify table rewrites and verification scans

Started by James Colemanalmost 4 years ago6 messages
#1James Coleman
jtc331@gmail.com
1 attachment(s)

Over in the "Document atthasmissing default optimization avoids
verification table scan" thread David Johnston (who I've cc'd)
suggested that my goals might be better implemented with a simple
restructuring of the Notes section of the ALTER TABLE docs. I think
this is also along the lines of Tom Lane's suggestion of a "unified
discussion", but I've chosen for now (and simplicity's sake) not to
break this into an entirely new page. If reviewers feel that is
warranted at this stage, I can do that, but it seems to me that for
now this improves the structure and sets us up for such a future page
but falls short of sufficient content to move into its own page.

One question on the changes: the current docs say "when attaching a
new partition it may be scanned to verify that existing rows meet the
partition constraint". The word "may" there seems to suggest there may
also be occasions where scans are not needed, but no examples of such
cases are present. I'm not immediately aware of such a case. Are these
constraints always validated? If not, in which cases can such a scan
be skipped?

I've also incorporated the slight correction in "Correct docs re:
rewriting indexes when table rewrite is skipped" [2] here, and will
rebase this patch if that gets committed.

Thanks,
James Coleman

1: /messages/by-id/CAKFQuwZyBaJjNepdTM3kO8PLaCpRdRd8+mtLT8QdE73oAsGv8Q@mail.gmail.com
2: /messages/by-id/CAAaqYe90Ea3RG=A7H-ONvTcx549-oQhp07BrHErwM=AyH2ximg@mail.gmail.com

Attachments:

v1-0001-Restructure-ALTER-TABLE-notes-to-clarify-table-re.patchapplication/octet-stream; name=v1-0001-Restructure-ALTER-TABLE-notes-to-clarify-table-re.patchDownload
From 4967ed4d0e1cff0c6e74c9b13369b88436ba217e Mon Sep 17 00:00:00 2001
From: jcoleman <jtc331@gmail.com>
Date: Tue, 29 Mar 2022 13:45:25 +0000
Subject: [PATCH v1] Restructure ALTER TABLE notes to clarify table rewrites
 and verification scans

The current set of notes includes several examples of when rewriting table
(and its indexes) or scanning a table to validate constraints is required
and when it can be avoided. However the structure makes this hard to
follow. Here we layout out rewriting and validating as two distinct
possibilities and list the cases where they happen. That also has the
benefit of moving some of the details about rewrites (e.g., time and
disk space requirements) out of the discussion for a specific operation.

Thanks to David G. Johnston for the initial inspiration for this
structuring.
---
 doc/src/sgml/ref/alter_table.sgml | 55 +++++++++++++++++++------------
 1 file changed, 34 insertions(+), 21 deletions(-)

diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 5c0735e08a..b3af264ecc 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -1351,36 +1351,49 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
    </para>
 
    <para>
-    When a column is added with <literal>ADD COLUMN</literal> and a
-    non-volatile <literal>DEFAULT</literal> is specified, the default is
-    evaluated at the time of the statement and the result stored in the
-    table's metadata.  That value will be used for the column for all existing
-    rows.  If no <literal>DEFAULT</literal> is specified, NULL is used.  In
-    neither case is a rewrite of the table required.
+    The following alterations of the table require the entire table, and in some 
+    cases its indexes as well, to be rewritten. For a large table such a rewrite
+    may take a significant amount of time and will temporarily require as much as
+    double the disk space.
    </para>
 
    <para>
-    Adding a column with a volatile <literal>DEFAULT</literal> or
-    changing the type of an existing column will require the entire table and
-    its indexes to be rewritten. As an exception, when changing the type of an
-    existing column, if the <literal>USING</literal> clause does not change
-    the column contents and the old type is either binary coercible to the new
-    type or an unconstrained domain over the new type, a table rewrite is not
-    needed; but any indexes on the affected columns must still be rebuilt.
-    Table and/or index rebuilds may take a
-    significant amount of time for a large table; and will temporarily require
-    as much as double the disk space.
+    Changing the type of an existing column will require the entire table and its
+    indexes to be rewritten. As an exception, if the <literal>USING</literal> clause
+    does not change the column contents and the old type is either binary coercible
+    to the new type or an unconstrained domain over the new type, a table rewrite is
+    not needed.
    </para>
 
    <para>
-    Adding a <literal>CHECK</literal> or <literal>NOT NULL</literal> constraint requires
-    scanning the table to verify that existing rows meet the constraint,
-    but does not require a table rewrite.
+    Adding a column with a volatile <literal>DEFAULT</literal> also requires the
+    entire table and its indexes to be rewritten. A non-volatile (or absent, in
+    which case <literal>NULL</literal> is used) <literal>DEFAULT</literal> is
+    evaluated at the time of the statement and the result is stored in the table's
+    metadata, thus avoiding the rewrite requirement.
    </para>
 
    <para>
-    Similarly, when attaching a new partition it may be scanned to verify that
-    existing rows meet the partition constraint.
+    The following alterations of the table require that it be scanned in its entirety
+    to ensure that no existing values are contrary to the new constraints placed on
+    the table. Constraints backed by indexes will scan the table as a side-effect of
+    populating the new index with data.
+   </para>
+
+   <para>
+    Adding either a CHECK constraint or a <literal>NOT NULL</literal> constraint on
+    an existing column requires scanning the table to verify that existing rows meet
+    the constraint.
+   </para>
+
+   <para>
+    Adding a foreign key constraint requires scanning the table to verify that all
+    existing values exist in the referenced table.
+   </para>
+
+   <para>
+    Attaching a new partition requires scanning the table to verify that existing
+    rows meet the partition constraint.
    </para>
 
    <para>
-- 
2.20.1

#2Matthias van de Meent
boekewurm+postgres@gmail.com
In reply to: James Coleman (#1)
Re: Restructure ALTER TABLE notes to clarify table rewrites and verification scans

On Tue, 29 Mar 2022 at 16:20, James Coleman <jtc331@gmail.com> wrote:

Over in the "Document atthasmissing default optimization avoids
verification table scan" thread David Johnston (who I've cc'd)
suggested that my goals might be better implemented with a simple
restructuring of the Notes section of the ALTER TABLE docs. I think
this is also along the lines of Tom Lane's suggestion of a "unified
discussion", but I've chosen for now (and simplicity's sake) not to
break this into an entirely new page. If reviewers feel that is
warranted at this stage, I can do that, but it seems to me that for
now this improves the structure and sets us up for such a future page
but falls short of sufficient content to move into its own page.

One question on the changes: the current docs say "when attaching a
new partition it may be scanned to verify that existing rows meet the
partition constraint". The word "may" there seems to suggest there may
also be occasions where scans are not needed, but no examples of such
cases are present. I'm not immediately aware of such a case. Are these
constraints always validated? If not, in which cases can such a scan
be skipped?

I've also incorporated the slight correction in "Correct docs re:
rewriting indexes when table rewrite is skipped" [2] here, and will
rebase this patch if that gets committed.

See comments in that thread.

+    Changing the type of an existing column will require the entire table and its
+    indexes to be rewritten. As an exception, if the <literal>USING</literal> clause
+    does not change the column contents and the old type is either binary coercible
+    to the new type or an unconstrained domain over the new type, a table rewrite is
+    not needed.

This implies "If the old type is [...] an unconstrained domain over
the new type, a table rewrite is not needed.", which is the wrong way
around.

I'd go with something along the lines of:

+    Changing the type of an existing column will require the entire table to be
+    rewritten, unless the <literal>USING</literal> clause is only a
binary coercible
+    cast, or if the new type is an unconstrained
<literal>DOMAIN<literal> over the
+    old type.

That would drop the reference to index rebuilding; but that should be
covered in other parts of the docs.

+    The following alterations of the table require the entire table, and in some
+    cases its indexes as well, to be rewritten.

It is impossible to rewrite the table without at the same time also
rewriting the indexes; as the location of tuples changes and thus
previously generated indexes will become invalid. At the same time;
changes to columns might not require a table rewrite, while still
requiring the indexes to be rewritten. I suggest changing the order of
"table" and "index", or dropping the clause.

+    [...] For a large table such a rewrite
+    may take a significant amount of time and will temporarily require as much as
+    double the disk space.

I'd replace the will with could. Technically, this "double the disk
space" could be even higher than that; due to index rebuilds taking up
to 3x normal space (one original index which is only dropped at the
end, one sorted tuple store for the rebuild, and one new index).

-    Similarly, when attaching a new partition it may be scanned to verify that
-    existing rows meet the partition constraint.
+    Attaching a new partition requires scanning the table to verify that existing
+    rows meet the partition constraint.

This is also (and better!) documented under section
sql-altertable-attach-partition: we will skip full table scan if the
table partition's existing constraints already imply the new partition
constraints. The previous wording is better in that regard ("may
need", instead of "requires"), though it could be improved by refering
to the sql-altertable-attach-partition section.

Kind regards,

Matthias van de Meent

#3James Coleman
jtc331@gmail.com
In reply to: Matthias van de Meent (#2)
1 attachment(s)
Re: Restructure ALTER TABLE notes to clarify table rewrites and verification scans

On Thu, Mar 31, 2022 at 10:58 AM Matthias van de Meent
<boekewurm+postgres@gmail.com> wrote:

On Tue, 29 Mar 2022 at 16:20, James Coleman <jtc331@gmail.com> wrote:

Over in the "Document atthasmissing default optimization avoids
verification table scan" thread David Johnston (who I've cc'd)
suggested that my goals might be better implemented with a simple
restructuring of the Notes section of the ALTER TABLE docs. I think
this is also along the lines of Tom Lane's suggestion of a "unified
discussion", but I've chosen for now (and simplicity's sake) not to
break this into an entirely new page. If reviewers feel that is
warranted at this stage, I can do that, but it seems to me that for
now this improves the structure and sets us up for such a future page
but falls short of sufficient content to move into its own page.

One question on the changes: the current docs say "when attaching a
new partition it may be scanned to verify that existing rows meet the
partition constraint". The word "may" there seems to suggest there may
also be occasions where scans are not needed, but no examples of such
cases are present. I'm not immediately aware of such a case. Are these
constraints always validated? If not, in which cases can such a scan
be skipped?

I've also incorporated the slight correction in "Correct docs re:
rewriting indexes when table rewrite is skipped" [2] here, and will
rebase this patch if that gets committed.

See comments in that thread.

Rebased since that thread has now resulted in a committed patch.

+    Changing the type of an existing column will require the entire table and its
+    indexes to be rewritten. As an exception, if the <literal>USING</literal> clause
+    does not change the column contents and the old type is either binary coercible
+    to the new type or an unconstrained domain over the new type, a table rewrite is
+    not needed.

This implies "If the old type is [...] an unconstrained domain over
the new type, a table rewrite is not needed.", which is the wrong way
around.

I'd go with something along the lines of:

+    Changing the type of an existing column will require the entire table to be
+    rewritten, unless the <literal>USING</literal> clause is only a
binary coercible
+    cast, or if the new type is an unconstrained
<literal>DOMAIN<literal> over the
+    old type.

That language is actually unchanged from the existing docs; is there
an error in the existing docs you're seeing? I'm actually imagining
that it can probably got either way -- from unconstrained domain over
new type to new type or from old type to unconstrained domain over old
type.

That would drop the reference to index rebuilding; but that should be
covered in other parts of the docs.

Part of the whole point of this restructuring is to make both of those
clear; I think we should retain the comments about indexes.

+    The following alterations of the table require the entire table, and in some
+    cases its indexes as well, to be rewritten.

It is impossible to rewrite the table without at the same time also
rewriting the indexes; as the location of tuples changes and thus
previously generated indexes will become invalid. At the same time;
changes to columns might not require a table rewrite, while still
requiring the indexes to be rewritten. I suggest changing the order of
"table" and "index", or dropping the clause.

Ah, that's a good point. I've rewritten that part.

+    [...] For a large table such a rewrite
+    may take a significant amount of time and will temporarily require as much as
+    double the disk space.

I'd replace the will with could. Technically, this "double the disk
space" could be even higher than that; due to index rebuilds taking up
to 3x normal space (one original index which is only dropped at the
end, one sorted tuple store for the rebuild, and one new index).

That's also the existing language, but I agree it seems a bit overly
precise (and in the process probably incorrect). There's a lot of
complexity here: depending on the type change (and USING clause!) and
table width it could be even more than 3x. I've reworded to try to
capture what's really going on here.

Why "could" instead of "will"? All table rewrites will always require
a extra disk space, right?

-    Similarly, when attaching a new partition it may be scanned to verify that
-    existing rows meet the partition constraint.
+    Attaching a new partition requires scanning the table to verify that existing
+    rows meet the partition constraint.

This is also (and better!) documented under section
sql-altertable-attach-partition: we will skip full table scan if the
table partition's existing constraints already imply the new partition
constraints. The previous wording is better in that regard ("may
need", instead of "requires"), though it could be improved by refering
to the sql-altertable-attach-partition section.

Updated, and I added an xref to that section (I think that's the
correct tagging).

Thanks,
James Coleman

Attachments:

v2-0001-Restructure-ALTER-TABLE-notes-to-clarify-table-re.patchapplication/octet-stream; name=v2-0001-Restructure-ALTER-TABLE-notes-to-clarify-table-re.patchDownload
From 8d8c846833cba00a23f9c6d90620ab4460fc6da3 Mon Sep 17 00:00:00 2001
From: jcoleman <jtc331@gmail.com>
Date: Tue, 29 Mar 2022 13:45:25 +0000
Subject: [PATCH v2] Restructure ALTER TABLE notes to clarify table rewrites
 and verification scans

The current set of notes includes several examples of when rewriting table
(and its indexes) or scanning a table to validate constraints is required
and when it can be avoided. However the structure makes this hard to
follow. Here we layout out rewriting and validating as two distinct
possibilities and list the cases where they happen. That also has the
benefit of moving some of the details about rewrites (e.g., time and
disk space requirements) out of the discussion for a specific operation.

Thanks to David G. Johnston for the initial inspiration for this
structuring.
---
 doc/src/sgml/ref/alter_table.sgml | 56 +++++++++++++++++++------------
 1 file changed, 35 insertions(+), 21 deletions(-)

diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index e610cbbc0e..ad2031c12c 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -1351,42 +1351,56 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
    </para>
 
    <para>
-    When a column is added with <literal>ADD COLUMN</literal> and a
-    non-volatile <literal>DEFAULT</literal> is specified, the default is
-    evaluated at the time of the statement and the result stored in the
-    table's metadata.  That value will be used for the column for all existing
-    rows.  If no <literal>DEFAULT</literal> is specified, NULL is used.  In
-    neither case is a rewrite of the table required.
+    The following alterations of the table require the entire table to be rewritten
+    and its indexes to be rebuilt. These operations will temporarily require disk
+    space for both the old and new tables along with all of their indexes. For a
+    large table those operations may take a significant amount of time.
    </para>
 
    <para>
-    Adding a column with a volatile <literal>DEFAULT</literal> or
-    changing the type of an existing column will require the entire table and
-    its indexes to be rewritten. As an exception, when changing the type of an
-    existing column, if the <literal>USING</literal> clause does not change
-    the column contents and the old type is either binary coercible to the new
-    type or an unconstrained domain over the new type, a table rewrite is not
-    needed. However, indexes must always be rebuilt unless the system can
+    Changing the type of an existing column will require the entire table and its
+    indexes to be rewritten. As an exception, if the <literal>USING</literal> clause
+    does not change the column contents and the old type is either binary coercible
+    to the new type or an unconstrained domain over the new type, a table rewrite is
+    not needed. However, indexes must always be rebuilt unless the system can
     verify that the new index would be logically equivalent to the existing
     one.  For example, if the collation for a column has been changed an index
     rebuild is always required because the new sort order might be different.
     However, in the absence of a collation change, a column can be changed
     from <type>text</type> to <type>varchar</type> (or vice versa) without
     rebuilding the indexes because these data types sort identically.
-    Table and/or index rebuilds may take a
-    significant amount of time for a large table; and will temporarily require
-    as much as double the disk space.
    </para>
 
    <para>
-    Adding a <literal>CHECK</literal> or <literal>NOT NULL</literal> constraint requires
-    scanning the table to verify that existing rows meet the constraint,
-    but does not require a table rewrite.
+    Adding a column with a volatile <literal>DEFAULT</literal> also requires the
+    entire table and its indexes to be rewritten. A non-volatile (or absent, in
+    which case <literal>NULL</literal> is used) <literal>DEFAULT</literal> is
+    evaluated at the time of the statement and the result is stored in the table's
+    metadata, thus avoiding the rewrite requirement.
    </para>
 
    <para>
-    Similarly, when attaching a new partition it may be scanned to verify that
-    existing rows meet the partition constraint.
+    The following alterations of the table require that it be scanned in its entirety
+    to ensure that no existing values are contrary to the new constraints placed on
+    the table. Constraints backed by indexes will scan the table as a side-effect of
+    populating the new index with data.
+   </para>
+
+   <para>
+    Adding either a CHECK constraint or a <literal>NOT NULL</literal> constraint on
+    an existing column requires scanning the table to verify that existing rows meet
+    the constraint.
+   </para>
+
+   <para>
+    Adding a foreign key constraint requires scanning the table to verify that all
+    existing values exist in the referenced table.
+   </para>
+
+   <para>
+    Attaching a new partition may require scanning the table to verify that existing
+    rows meet the partition constraint. See <xref linkend="sql-altertable-attach-partition"/>
+    for more details.
    </para>
 
    <para>
-- 
2.20.1

#4Matthias van de Meent
boekewurm+postgres@gmail.com
In reply to: James Coleman (#3)
Re: Restructure ALTER TABLE notes to clarify table rewrites and verification scans

On Fri, 1 Apr 2022 at 16:10, James Coleman <jtc331@gmail.com> wrote:

On Thu, Mar 31, 2022 at 10:58 AM Matthias van de Meent
<boekewurm+postgres@gmail.com> wrote:

On Tue, 29 Mar 2022 at 16:20, James Coleman <jtc331@gmail.com> wrote:

Over in the "Document atthasmissing default optimization avoids
verification table scan" thread David Johnston (who I've cc'd)
suggested that my goals might be better implemented with a simple
restructuring of the Notes section of the ALTER TABLE docs. I think
this is also along the lines of Tom Lane's suggestion of a "unified
discussion", but I've chosen for now (and simplicity's sake) not to
break this into an entirely new page. If reviewers feel that is
warranted at this stage, I can do that, but it seems to me that for
now this improves the structure and sets us up for such a future page
but falls short of sufficient content to move into its own page.

One question on the changes: the current docs say "when attaching a
new partition it may be scanned to verify that existing rows meet the
partition constraint". The word "may" there seems to suggest there may
also be occasions where scans are not needed, but no examples of such
cases are present. I'm not immediately aware of such a case. Are these
constraints always validated? If not, in which cases can such a scan
be skipped?

I've also incorporated the slight correction in "Correct docs re:
rewriting indexes when table rewrite is skipped" [2] here, and will
rebase this patch if that gets committed.

See comments in that thread.

Rebased since that thread has now resulted in a committed patch.

+    Changing the type of an existing column will require the entire table and its
+    indexes to be rewritten. As an exception, if the <literal>USING</literal> clause
+    does not change the column contents and the old type is either binary coercible
+    to the new type or an unconstrained domain over the new type, a table rewrite is
+    not needed.

This implies "If the old type is [...] an unconstrained domain over
the new type, a table rewrite is not needed.", which is the wrong way
around.

I'd go with something along the lines of:

+    Changing the type of an existing column will require the entire table to be
+    rewritten, unless the <literal>USING</literal> clause is only a
binary coercible
+    cast, or if the new type is an unconstrained
<literal>DOMAIN<literal> over the
+    old type.

That language is actually unchanged from the existing docs; is there
an error in the existing docs you're seeing? I'm actually imagining
that it can probably got either way -- from unconstrained domain over
new type to new type or from old type to unconstrained domain over old
type.

CREATE DOMAIN constrained AS text NOT NULL;
CREATE DOMAIN unconstrained_on_constrained AS constrained;

CREATE TABLE tst (col unconstrained_on_constrained);
ALTER TABLE tst ALTER COLUMN col TYPE constrained; -- table scan

Moving from an unconstrained domain over a constrained domain means
that we still do the table scan. Domain nesting is weird in that way.

That would drop the reference to index rebuilding; but that should be
covered in other parts of the docs.

Part of the whole point of this restructuring is to make both of those
clear; I think we should retain the comments about indexes.

OK; I mentioned it because table rewrite also implies index rewrite;
assuming this is correctly referenced in other parts of the docs.

+    The following alterations of the table require the entire table, and in some
+    cases its indexes as well, to be rewritten.

It is impossible to rewrite the table without at the same time also
rewriting the indexes; as the location of tuples changes and thus
previously generated indexes will become invalid. At the same time;
changes to columns might not require a table rewrite, while still
requiring the indexes to be rewritten. I suggest changing the order of
"table" and "index", or dropping the clause.

Ah, that's a good point. I've rewritten that part.

+    [...] For a large table such a rewrite
+    may take a significant amount of time and will temporarily require as much as
+    double the disk space.

I'd replace the will with could. Technically, this "double the disk
space" could be even higher than that; due to index rebuilds taking up
to 3x normal space (one original index which is only dropped at the
end, one sorted tuple store for the rebuild, and one new index).

That's also the existing language, but I agree it seems a bit overly
precise (and in the process probably incorrect). There's a lot of
complexity here: depending on the type change (and USING clause!) and
table width it could be even more than 3x. I've reworded to try to
capture what's really going on here.

Why "could" instead of "will"? All table rewrites will always require
a extra disk space, right?

Table bloat will be removed, as an equivalent of `VACUUM FULL` or
`CLUSTER` is run on the database. This can remove up to 99.99...% of
the current size of the table; e.g. if there's a few tuples of
MaxHeapTupleSize at the end of the table (likely in 32-bit mode, or
pre-pg14 systems).

-    Similarly, when attaching a new partition it may be scanned to verify that
-    existing rows meet the partition constraint.
+    Attaching a new partition requires scanning the table to verify that existing
+    rows meet the partition constraint.

This is also (and better!) documented under section
sql-altertable-attach-partition: we will skip full table scan if the
table partition's existing constraints already imply the new partition
constraints. The previous wording is better in that regard ("may
need", instead of "requires"), though it could be improved by refering
to the sql-altertable-attach-partition section.

Updated, and I added an xref to that section (I think that's the
correct tagging).

+    The following alterations of the table require the entire table to be rewritten
+    and its indexes to be rebuilt.
+    The following alterations of the table require that it be scanned in its entirety
+    to ensure that no existing values are contrary to the new constraints placed on
+    the table.

Could you maybe reword these sentences to replace "alterations of"
with "changes to"? Although fittingly used in the context of 'ALTER
TABLE", I'm not a fan of the phrasing that comes with the use of
'alterations'.

+    Attaching a new partition may require scanning the table to verify that existing
+    rows meet the partition constraint.

I think that "the table" should be "some tables", as default
partitions of the parent table might need to be checked as well.

Thanks!

- Matthias

#5Justin Pryzby
pryzby@telsasoft.com
In reply to: James Coleman (#1)
Re: Restructure ALTER TABLE notes to clarify table rewrites and verification scans

I think this patch is missing "SET [UN]LOGGED", defaults of identity columns
and domains, and access method.

And tablespace, even though that rewrites the *files*, but not tuples (maybe
these docs should say that).

#6Jacob Champion
jchampion@timescale.com
In reply to: Matthias van de Meent (#4)
Re: Restructure ALTER TABLE notes to clarify table rewrites and verification scans

This entry has been waiting on author input for a while (our current
threshold is roughly two weeks), so I've marked it Returned with
Feedback.

Once you think the patchset is ready for review again, you (or any
interested party) can resurrect the patch entry by visiting

https://commitfest.postgresql.org/38/3604/

and changing the status to "Needs Review", and then changing the
status again to "Move to next CF". (Don't forget the second step;
hopefully we will have streamlined this in the near future!)

Thanks,
--Jacob